84 lines
3.0 KiB
JavaScript
84 lines
3.0 KiB
JavaScript
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,
|
|
};
|