#!/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}).`);