/** * manifest.js — Minimal manifest parsing and export lookup. * * The manifest is a JSON object with fields: * schema, bundleType, tree, runtime, closure, roots, exports, * imports, sections, metadata * * We parse only what we need for runtime entrypoint selection. */ /** * Validate the manifest against the runtime profile requirements. * Throws on violation. */ export function validateManifest(manifest) { if (manifest.schema !== "arboricx.bundle.manifest.v1") { throw new Error( `unsupported manifest schema: ${manifest.schema}` ); } if (manifest.bundleType !== "tree-calculus-executable-object") { throw new Error( `unsupported bundle type: ${manifest.bundleType}` ); } const tree = manifest.tree; if (tree.calculus !== "tree-calculus.v1") { throw new Error(`unsupported calculus: ${tree.calculus}`); } if (tree.nodeHash.algorithm !== "sha256") { throw new Error( `unsupported node hash algorithm: ${tree.nodeHash.algorithm}` ); } if (tree.nodeHash.domain !== "arboricx.merkle.node.v1") { throw new Error( `unsupported node hash domain: ${tree.nodeHash.domain}` ); } if (tree.nodePayload !== "arboricx.merkle.payload.v1") { throw new Error(`unsupported node payload: ${tree.nodePayload}`); } const runtime = manifest.runtime; if (runtime.semantics !== "tree-calculus.v1") { throw new Error(`unsupported runtime semantics: ${runtime.semantics}`); } if (runtime.abi !== "arboricx.abi.tree.v1") { throw new Error(`unsupported runtime ABI: ${runtime.abi}`); } if (runtime.capabilities && runtime.capabilities.length > 0) { throw new Error( `host/runtime capabilities not supported: ${runtime.capabilities.join(", ")}` ); } if (manifest.closure !== "complete") { throw new Error("bundle v1 requires closure = complete"); } if (manifest.imports && manifest.imports.length > 0) { throw new Error("bundle v1 requires an empty imports list"); } if (!manifest.roots || manifest.roots.length === 0) { throw new Error("manifest has no roots"); } if (!manifest.exports || manifest.exports.length === 0) { throw new Error("manifest has no exports"); } for (const exp of manifest.exports) { if (!exp.name) { throw new Error("manifest export has empty name"); } if (!exp.root) { throw new Error("manifest export has empty root"); } } } /** * Select an export hash given a requested name. * * Selection strategy: * 1. Explicit export name * 2. Export named "main" * 3. Single export (auto-select) * 4. Error if multiple exports and no "main" */ export function selectExport(manifest, requestedName) { const exports = manifest.exports || []; // Strategy 1: explicit name if (requestedName) { const found = exports.find((e) => e.name === requestedName); if (found) { return found; } throw new Error( `requested export "${requestedName}" not found. Available: ${exports.map((e) => e.name).join(", ")}` ); } // Strategy 2: prefer "main" const mainExport = exports.find((e) => e.name === "main"); if (mainExport) { return mainExport; } // Strategy 3: single export if (exports.length === 1) { return exports[0]; } // Strategy 4: multiple exports, require explicit throw new Error( `multiple exports available but none named "main": ${exports.map((e) => e.name).join(", ")}. Specify an export name.` ); } /** * Get all root hashes from the manifest. */ export function getRootHashes(manifest) { return (manifest.roots || []).map((r) => r.hash); } /** * Get all export names. */ export function getExportNames(manifest) { return (manifest.exports || []).map((e) => e.name); } /** * Print manifest summary info. */ export function printManifestInfo(manifest, indent = "") { const tree = manifest.tree; const runtime = manifest.runtime; console.log(`${indent}Schema: ${manifest.schema}`); console.log(`${indent}Bundle type: ${manifest.bundleType}`); console.log(`${indent}Closure: ${manifest.closure}`); console.log(`${indent}Tree calculus: ${tree.calculus}`); console.log(`${indent}Hash algo: ${tree.nodeHash.algorithm}`); console.log(`${indent}Hash domain: ${tree.nodeHash.domain}`); console.log(`${indent}Runtime: ${runtime.semantics}`); console.log(`${indent}ABI: ${runtime.abi}`); console.log(`${indent}Evaluation: ${runtime.evaluation || "N/A"}`); console.log(""); console.log(`${indent}Roots (${getRootHashes(manifest).length}):`); for (const root of getRootHashes(manifest)) { console.log(`${indent} ${root.substring(0, 16)}...`); } console.log(""); console.log(`${indent}Exports (${getExportNames(manifest).length}):`); for (const name of getExportNames(manifest)) { console.log(`${indent} ${name}`); } const meta = manifest.metadata; if (meta && meta.createdBy) { console.log(""); console.log(`${indent}Created by: ${meta.createdBy}`); } }