#!/usr/bin/env node import { readFile, access } from "node:fs/promises"; import { dirname, join, resolve } from "node:path"; import { fileURLToPath } from "node:url"; const root = resolve(dirname(fileURLToPath(import.meta.url)), ".."); const errors = []; const notices = []; const requiredArtifactFields = [ "id", "title", "artifact_type", "primary_url", "authors", "mnemoverse_authored", "summary", "memory_scope", "evaluation_targets", "memory_unit", "time_model", "conflict_update_model", "outcome_level", "evidence_status", "code_url", "project_url", "dataset_url", "license", "limitations", "sources", "verified_at" ]; function fail(message) { errors.push(message); } function assert(condition, message) { if (!condition) fail(message); } async function read(relativePath) { return readFile(join(root, relativePath), "utf8"); } async function exists(relativePath) { try { await access(join(root, relativePath)); return true; } catch { return false; } } function parseFrontMatter(readme) { const match = readme.match(/^---\n([\s\S]*?)\n---\n/); assert(match, "README.md must start with YAML front matter"); if (!match) return new Map(); const fields = new Map(); for (const line of match[1].split("\n")) { const field = line.match(/^([A-Za-z][A-Za-z0-9_-]*):(?:\s*(.*))?$/); if (field) fields.set(field[1], (field[2] || "").trim()); } return fields; } function localReferences(indexHtml) { return [...indexHtml.matchAll(/(?:href|src)="([^"]+)"/g)] .map(match => match[1]) .filter(value => !value.startsWith("#") && !value.startsWith("http") && !value.startsWith("data:")); } function publicUrls(manifest) { const urls = []; for (const artifact of manifest.artifacts || []) { for (const key of ["primary_url", "code_url", "project_url", "dataset_url"]) { if (artifact[key]) urls.push(artifact[key]); } urls.push(...(artifact.sources || [])); } return [...new Set(urls)]; } async function checkUrl(url) { const controller = new AbortController(); const timer = setTimeout(() => controller.abort(), 15000); try { let response = await fetch(url, { method: "HEAD", redirect: "follow", signal: controller.signal }); if (response.status === 405 || response.status === 403) { response = await fetch(url, { method: "GET", redirect: "follow", signal: controller.signal }); } assert(response.ok, `Public URL returned ${response.status}: ${url}`); } catch (error) { fail(`Public URL check failed for ${url}: ${error.message}`); } finally { clearTimeout(timer); } } function normalizeText(value) { return String(value).replace(/\s+/g, " ").trim(); } function validDate(value) { if (typeof value !== "string" || !/^\d{4}-\d{2}-\d{2}$/.test(value)) return false; const parsed = new Date(`${value}T00:00:00Z`); return Number.isFinite(parsed.getTime()) && parsed.toISOString().slice(0, 10) === value; } async function fetchSource(url, format = "json") { const controller = new AbortController(); const timer = setTimeout(() => controller.abort(), 15000); try { const response = await fetch(url, { headers: { Accept: format === "json" ? "application/json" : "text/html", "User-Agent": "mnemoverse-space-preflight" }, redirect: "follow", signal: controller.signal }); assert(response.ok, `Metadata URL returned ${response.status}: ${url}`); if (!response.ok) return null; return format === "json" ? await response.json() : await response.text(); } catch (error) { fail(`Metadata check failed for ${url}: ${error.message}`); return null; } finally { clearTimeout(timer); } } function decodeHtml(value) { return value.replace(/&#(x[0-9a-f]+|\d+);/gi, (_, code) => String.fromCodePoint(code[0].toLowerCase() === "x" ? parseInt(code.slice(1), 16) : Number(code))) .replaceAll(""", '"').replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">").replaceAll("&", "&"); } async function checkPrimaryMetadata(artifact) { const paperId = artifact.id.split(":", 2)[1]; const metadataUrl = artifact.metadata_source || `https://huggingface.co/api/papers/${paperId}`; let metadata; if (artifact.metadata_source) { const html = await fetchSource(metadataUrl, "html"); if (html) { const title = html.match(//i)?.[1]; const authors = [...html.matchAll(//gi)].map(match => { const name = decodeHtml(match[1]); const comma = name.indexOf(","); return comma < 0 ? name : `${name.slice(comma + 1).trim()} ${name.slice(0, comma).trim()}`; }); assert(title && authors.length, `${artifact.id} pinned arXiv citation metadata was not found`); metadata = { title: decodeHtml(title || ""), authors }; } } else { metadata = await fetchSource(metadataUrl); } if (metadata) { assert(normalizeText(artifact.title) === normalizeText(metadata.title), `${artifact.id} title differs from ${metadataUrl}`); const apiAuthors = (metadata.authors || []).map(author => normalizeText(typeof author === "string" ? author : author.name)); const manifestAuthors = (artifact.authors || []).map(normalizeText); assert(JSON.stringify(manifestAuthors) === JSON.stringify(apiAuthors), `${artifact.id} authors differ from ${metadataUrl}`); } if (!artifact.code_url) return; const codeUrl = new URL(artifact.code_url); if (codeUrl.hostname !== "github.com") return; const [owner, repository] = codeUrl.pathname.split("/").filter(Boolean); if (!owner || !repository) { fail(`${artifact.id} has an unparseable GitHub code repository URL`); return; } const repositoryMetadata = await fetchSource(`https://api.github.com/repos/${owner}/${repository}`); if (!repositoryMetadata) return; const observedSpdx = repositoryMetadata.license?.spdx_id || "unknown"; const declaredSpdx = artifact.license.startsWith("MIT") ? "MIT" : artifact.license.startsWith("Apache-2.0") ? "Apache-2.0" : "unknown"; assert(observedSpdx === declaredSpdx, `${artifact.id} code-repository license differs: manifest ${declaredSpdx}, GitHub ${observedSpdx}`); } const [readme, indexHtml, appJs, manifestText, schemaText, licenseText, sources, deploymentChecklist] = await Promise.all([ read("README.md"), read("index.html"), read("app.js"), read("data/artifacts.json"), read("data/schema.json"), read("LICENSE"), read("SOURCES.md"), read("DEPLOYMENT-CHECKLIST.md") ]); let manifest; let schema; try { manifest = JSON.parse(manifestText); } catch (error) { fail(`data/artifacts.json is not valid JSON: ${error.message}`); manifest = {}; } try { schema = JSON.parse(schemaText); } catch (error) { fail(`data/schema.json is not valid JSON: ${error.message}`); schema = {}; } const frontMatter = parseFrontMatter(readme); assert(frontMatter.get("sdk") === "static", "README.md must declare sdk: static"); assert(frontMatter.get("app_file") === "index.html", "README.md must declare app_file: index.html"); assert(frontMatter.get("license") === "mit", "README.md must declare license: mit"); assert((frontMatter.get("short_description") || "").length <= 60, "README.md short_description must fit the current 60-character Space limit"); assert(await exists(frontMatter.get("app_file") || "__missing__"), "Configured app_file does not exist"); assert(!frontMatter.has("app_build_command"), "Vanilla Static Space must not declare an unnecessary build command"); const upstreamLicenseBoundary = "The MIT license covers original files in this Space. Linked papers, repositories, datasets, and project pages remain under their own licenses and terms; links and factual summaries do not relicense upstream works."; assert(licenseText.startsWith("MIT License\n"), "LICENSE must contain the standard MIT grant"); assert(licenseText.includes("Copyright (c) 2026 Mnemoverse"), "LICENSE must name Mnemoverse and the 2026 copyright year"); assert(licenseText.includes("Permission is hereby granted, free of charge"), "LICENSE must contain the MIT permission grant"); assert(licenseText.includes('THE SOFTWARE IS PROVIDED "AS IS"'), "LICENSE must contain the MIT warranty disclaimer"); assert(readme.includes(upstreamLicenseBoundary), "README.md must preserve the upstream-license boundary"); assert(sources.includes(upstreamLicenseBoundary), "SOURCES.md must preserve the upstream-license boundary"); for (const reference of localReferences(indexHtml)) { const cleanReference = reference.split(/[?#]/, 1)[0]; assert(await exists(cleanReference), `Missing local application reference: ${reference}`); } assert(appJs.includes('fetch("data/artifacts.json")'), "app.js must load the versioned local manifest"); assert(!appJs.includes("new Date()"), "Recipe exports must not contain a runtime timestamp"); assert(appJs.includes("evidence_verified_at: state.manifest.verified_at"), "Recipe exports must record the evidence verification date"); assert(appJs.includes('history.replaceState(null, "", "#evidence")'), "Skip navigation must preserve the evidence fragment"); assert(appJs.includes("elements.evidence.focus()"), "Skip navigation must move keyboard focus to the evidence section"); assert(indexHtml.includes('http-equiv="Content-Security-Policy"'), "index.html must declare a Content Security Policy"); assert(indexHtml.includes("object-src 'none'"), "Content Security Policy must block object embedding"); assert(/^\d+\.\d+\.\d+$/.test(manifest.manifest_version || ""), "manifest_version must be semantic x.y.z"); assert(validDate(manifest.verified_at), "manifest verified_at must be a valid YYYY-MM-DD calendar date"); assert(Array.isArray(manifest.targets) && manifest.targets.length === 8, "Manifest must define the reviewed eight evaluation targets"); const expectedArtifactIds = ["paper:2603.08965", "paper:2410.10813", "paper:2501.13956", "paper:2407.04363", "paper:2507.03724", "paper:2602.05665", "paper:2608.15008"]; assert(JSON.stringify((manifest.artifacts || []).map(artifact => artifact.id).sort()) === JSON.stringify([...expectedArtifactIds].sort()), "Manifest must contain exactly the seven source-reviewed artifacts"); assert(manifest.verified_at === (manifest.artifacts || []).map(artifact => artifact.verified_at).sort().at(-1), "manifest verified_at must equal the latest artifact source-check date"); const targetIds = new Set(); for (const target of manifest.targets || []) { assert(target && typeof target.id === "string", "Every target must have a string id"); assert(!targetIds.has(target.id), `Duplicate target id: ${target.id}`); targetIds.add(target.id); assert(appJs.includes(`${target.id}: [`), `app.js has no recipe prompts for target: ${target.id}`); } const artifactIds = new Set(); for (const artifact of manifest.artifacts || []) { const label = artifact?.id || ""; for (const field of requiredArtifactFields) { assert(Object.hasOwn(artifact, field), `${label} is missing required field: ${field}`); } assert(/^paper:\d{4}\.\d{5}$/.test(label), `Unexpected artifact id format: ${label}`); assert(!artifactIds.has(label), `Duplicate artifact id: ${label}`); artifactIds.add(label); assert(validDate(artifact.verified_at) && artifact.verified_at <= manifest.verified_at, `${label} verification date must be valid and no later than the latest manifest source check`); const fieldSchemas = schema.$defs?.artifact?.properties || {}; for (const field of Object.keys(artifact)) { assert(Object.hasOwn(fieldSchemas, field), `${label} has an undeclared field: ${field}`); } for (const [field, definition] of Object.entries(fieldSchemas)) { if (definition.enum) assert(definition.enum.includes(artifact[field]), `${label} ${field} must be one of: ${definition.enum.join(", ")}`); } if (artifact.metadata_source) { assert(new RegExp(`^https://arxiv\\.org/abs/${label.slice(6).replace(".", "\\.")}v[1-9][0-9]*$`).test(artifact.metadata_source), `${label} metadata_source must pin the same arXiv paper`); assert(artifact.sources.includes(artifact.metadata_source), `${label} metadata_source must be in sources`); } assert(Array.isArray(artifact.authors) && artifact.authors.length > 0, `${label} must name at least one author`); assert(Array.isArray(artifact.limitations) && artifact.limitations.length > 0, `${label} must state at least one limitation`); for (const targetId of artifact.evaluation_targets || []) { assert(targetIds.has(targetId), `${label} references undefined target: ${targetId}`); } } const urls = publicUrls(manifest); for (const url of urls) { let parsed; try { parsed = new URL(url); } catch { fail(`Malformed public URL: ${url}`); continue; } assert(parsed.protocol === "https:", `Public evidence URL must use HTTPS: ${url}`); } const graphMemorySurvey = (manifest.artifacts || []).find(artifact => artifact.id === "paper:2602.05665"); assert(graphMemorySurvey?.code_url === null, "Awesome-GraphMemory is a curated resource repository, not implementation code"); assert(graphMemorySurvey?.project_url === "https://github.com/DEEP-PolyU/Awesome-GraphMemory", "Graph-memory survey must expose its curated repository as a project link"); assert(schema.$schema === "https://json-schema.org/draft/2020-12/schema", "Schema must use JSON Schema 2020-12"); assert(schema.$id === "https://huggingface.co/spaces/mnemoverse/agent-memory-evaluation-map/raw/main/data/schema.json", "Schema $id must match the planned Space path"); const publicPayload = `${readme}\n${indexHtml}\n${appJs}\n${manifestText}\n${schemaText}\n${sources}\n${deploymentChecklist}`.toLocaleLowerCase(); for (const forbidden of ["internal-only", "confidential", "do not publish", "room atom"]) { assert(!publicPayload.includes(forbidden), `Deployable app payload contains blocked phrase: ${forbidden}`); } if (process.argv.includes("--check-urls")) { notices.push(`Checking ${urls.length} unique public URLs…`); for (const url of urls) await checkUrl(url); notices.push(`Checking titles/authors against each declared metadata source and code-repository SPDX identifiers…`); for (const artifact of manifest.artifacts || []) await checkPrimaryMetadata(artifact); } else { notices.push(`Skipped ${urls.length} live URL checks; add --check-urls to enable them.`); } for (const notice of notices) console.log(`NOTICE: ${notice}`); if (errors.length) { for (const error of errors) console.error(`ERROR: ${error}`); console.error(`Preflight failed with ${errors.length} error(s).`); process.exit(1); } console.log(`PASS: Static Space package is internally consistent (${manifest.artifacts.length} artifacts, ${manifest.targets.length} targets, manifest ${manifest.manifest_version}).`);