#!/usr/bin/env node /** * Test-only differential bridge for scripts/foundation/verify-schema.py. * * node scripts/foundation/validate-record.mjs * * Input: a JSON array of {name, raw} where raw is the exact JSON text of one * candidate record. Output (stdout): a JSON array, same order, of * {name, parse: "ok" | , byteOffset, * schemaValid: true|false|null, profileValid: bool, verdict, rule, path} * * schemaValid is the inspector's record-schema verdict (null when the strict * parser rejected the text, or when the kind is unsupported and therefore not * judged beyond the kind gate); it reproduces the pinned checker's verdict, * including its end-of-string newline tolerance on id/runtimeId/digest patterns. * profileValid is the strict bundle/profile verdict: strict lexical parse AND * shape-valid AND a supported kind AND the strict typed-string profile (addendum * FI-C2-1: the whole value satisfies the grammar). A schema-valid, profile-invalid * record reports rule "profile-pattern-mismatch" with its first violating path. * The two columns are deliberately separate (charter §8, §10.5). * * This bridge is not part of the inspector and may read the named file; the * inspector itself never imports it. */ import { readFileSync } from "node:fs"; import { parseStrict, StrictJsonError, utf8Encode } from "./strict-json.mjs"; import { validateRecordShape } from "./resolve.mjs"; export function judgeRaw(raw) { let value; try { value = parseStrict(utf8Encode(raw)); } catch (e) { if (e instanceof StrictJsonError) { return { parse: e.code, byteOffset: e.byteOffset, schemaValid: null, profileValid: false, verdict: "unparsed", rule: null, path: null }; } throw e; } const v = validateRecordShape(value); // Unsupported kinds are refused at the kind gate; their envelope/payload is not // judged, so the schema column is "not assessed" (null), never "valid". const schemaValid = v.verdict === "unsupported-kind" ? null : v.verdict === "valid"; const violations = v.verdict === "valid" ? v.profileViolations : []; return { parse: "ok", byteOffset: null, schemaValid, profileValid: v.verdict === "valid" && violations.length === 0, verdict: v.verdict, rule: v.rule ?? (violations.length > 0 ? "profile-pattern-mismatch" : null), path: v.path ?? (violations.length > 0 ? violations[0] : null), }; } function main() { const file = process.argv[2]; if (!file || process.argv.length !== 3) { process.stderr.write("usage: validate-record.mjs \n"); process.exit(2); } const corpus = JSON.parse(readFileSync(file, "utf8")); if (!Array.isArray(corpus)) throw new Error("corpus must be an array"); const out = corpus.map((c) => { if (typeof c.name !== "string" || typeof c.raw !== "string") throw new Error("corpus entry needs {name, raw}"); return { name: c.name, ...judgeRaw(c.raw) }; }); process.stdout.write(`${JSON.stringify(out)}\n`); } main();