function issue(code, message, details = {}) { return { code, message, ...details }; } function isReadableUrl(value) { const text = String(value ?? '').trim(); return ( text.startsWith('http://') || text.startsWith('https://') || text.startsWith('/api/') || text.startsWith('/MindSpace/') ); } function normalizeStorageKey(value) { return String(value ?? '').trim().replace(/^\/+/, ''); } export async function verifyConversationPackageManifest(manifest, options = {}) { const issues = []; const statObject = typeof options.statObject === 'function' ? options.statObject : null; const packageId = String(manifest?.packageId ?? '').trim(); const storagePrefix = normalizeStorageKey(manifest?.storagePrefix); const artifacts = Array.isArray(manifest?.artifacts) ? manifest.artifacts : null; if (!packageId) issues.push(issue('missing_package_id', 'manifest.packageId is required')); if (!String(manifest?.userId ?? '').trim()) issues.push(issue('missing_user_id', 'manifest.userId is required')); if (!String(manifest?.sessionId ?? '').trim()) { issues.push(issue('missing_session_id', 'manifest.sessionId is required')); } if (!storagePrefix) issues.push(issue('missing_storage_prefix', 'manifest.storagePrefix is required')); if (!artifacts) issues.push(issue('invalid_artifacts', 'manifest.artifacts must be an array')); const seenArtifactIds = new Set(); for (const artifact of artifacts ?? []) { const artifactId = String(artifact?.artifactId ?? '').trim(); if (!artifactId) { issues.push(issue('missing_artifact_id', 'artifact.artifactId is required')); continue; } if (seenArtifactIds.has(artifactId)) { issues.push(issue('duplicate_artifact_id', `duplicate artifact id: ${artifactId}`, { artifactId })); } seenArtifactIds.add(artifactId); const canonicalUrl = String(artifact?.canonicalUrl ?? '').trim(); const storageKey = normalizeStorageKey(artifact?.storageKey); if (!canonicalUrl && !storageKey) { issues.push( issue('artifact_without_backing_reference', 'artifact must have canonicalUrl or storageKey', { artifactId }), ); } if (canonicalUrl && !isReadableUrl(canonicalUrl)) { issues.push(issue('invalid_canonical_url', `artifact canonicalUrl is not readable: ${canonicalUrl}`, { artifactId, canonicalUrl, })); } if (storageKey && storagePrefix && !storageKey.startsWith(`${storagePrefix}/`)) { issues.push(issue('storage_key_outside_package', 'artifact storageKey is outside manifest.storagePrefix', { artifactId, storageKey, storagePrefix, })); } if (storageKey && statObject) { const stat = await statObject(storageKey); if (!stat?.exists) { issues.push(issue('missing_backing_object', `artifact backing object is missing: ${storageKey}`, { artifactId, storageKey, })); } } } return { ok: issues.length === 0, issues }; } export const conversationPackageVerifyInternals = { isReadableUrl, normalizeStorageKey, };