Files
stack/scripts/foundation/validate-record.mjs
T
jason.woltje 8ebddd6f93 feat(foundation): offline synthetic scope/permission inspector (FI-FILBERT-8 APPROVED r6)
Rocko-authored, Filbert-reviewed inspector (r6 manifest
a4a44930...) with full review/build/verdict evidence under
docs/plans/reviews. 43/0 selftests, oracle zero-disagreement,
foundation checker PASS. Owner A9 acceptance recorded separately.
2026-09-07 14:06:35 -05:00

72 lines
3.0 KiB
JavaScript

#!/usr/bin/env node
/**
* Test-only differential bridge for scripts/foundation/verify-schema.py.
*
* node scripts/foundation/validate-record.mjs <corpus.json>
*
* 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" | <strict-json error code>, 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 <corpus.json>\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();