// Unit tests for the pure evaluation core (charter §4, §7, §8, §10.4). import { test } from "node:test"; import assert from "node:assert/strict"; import { readFileSync, readdirSync } from "node:fs"; import { join, dirname } from "node:path"; import { fileURLToPath } from "node:url"; import { findCycle, deepEqual, isValidTime, isValidRelativePath, intersectGrants, pathPermitted, layerFromRestrictions, UNRESTRICTED_LAYER, intersectLayers, buildResult, exitFor, Refusal, RULES, REASONS, RESULTS, PROPOSAL_STEPS, SUPPORTED_OPERATIONS, OPERATION_CATALOG, validateRecordShape, evaluate, inspect, DISCLAIMER, PREVIEW, RESULT_KEYS as IMPL_RESULT_KEYS, PINNED_PATTERNS, pinnedPatternMatches, strictPatternMatches, stableOrder, orderingForm, ORDER_KEYS, } from "./resolve.mjs"; import { parseStrict } from "./strict-json.mjs"; import { canonicalize, CanonicalError } from "./canonical.mjs"; import { createHash } from "node:crypto"; const HERE = dirname(fileURLToPath(import.meta.url)); const FIXTURES = join(HERE, "fixtures"); const CHARTER = join(HERE, "..", "..", "docs", "plans", "2026-09-06_foundation-inspector-charter.md"); const CHARTER_SHA256 = "19b6721128a627a2032ffdb95ece2d50abe69a8f6d521e9eff8bbdaff22798b6"; const INDEX = JSON.parse(readFileSync(join(FIXTURES, "index.json"), "utf8")); function loadBundle(name) { return parseStrict(new Uint8Array(readFileSync(join(FIXTURES, "bundles", `${name}.json`)))); } const CLOSED_KEYS = new Set([ "disclaimer", "preview", "bundleVersion", "authentication", "declarations", "result", "reason", "rule", "selection", "operation", "proposal", "diagnostic", "agentId", "projectId", "workspaceId", "assignmentRef", "kind", "id", "scope", "revision", "name", "target", "root", "path", "selectedAssignmentRef", "byteOffset", "inputPath", ]); /** Every key at any depth that is not part of the closed result vocabulary (§7). */ function unknownKeys(value, out = []) { if (Array.isArray(value)) { for (const v of value) unknownKeys(v, out); return out; } if (value && typeof value === "object") { for (const [k, v] of Object.entries(value)) { if (!CLOSED_KEYS.has(k)) out.push(k); unknownKeys(v, out); } } return out; } // Independent copy of the charter §7 closed field list (not the implementation export). const RESULT_KEYS = [ "disclaimer", "preview", "bundleVersion", "authentication", "declarations", "result", "reason", "rule", "selection", "operation", "proposal", "diagnostic", ]; /** The §7 "Closed result fields" bullet names parsed from the pinned charter text. */ function charterResultFields() { const bytes = readFileSync(CHARTER); const digest = createHash("sha256").update(bytes).digest("hex"); assert.equal(digest, CHARTER_SHA256, "charter candidate 3 must be the pinned text; rebind this test on amendment"); const text = bytes.toString("utf8"); const start = text.indexOf("Closed result fields, emitted on every outcome:"); assert.ok(start > 0, "charter §7 field list not found"); const block = text.slice(start, text.indexOf("\n\n", start)); return block.split("\n").map((l) => /^- ([a-zA-Z]+):/.exec(l)).filter(Boolean).map((m) => m[1]); } // --- graph ------------------------------------------------------------------------- test("findCycle: self-loop, pair, diamond, long chain without recursion", () => { assert.equal(findCycle(new Map([["a", ["a"]]])), "a"); assert.equal(findCycle(new Map([["a", ["b"]], ["b", ["a"]]])), "a"); assert.equal(findCycle(new Map([["a", ["b", "c"]], ["b", ["d"]], ["c", ["d"]], ["d", []]])), null); assert.equal(findCycle(new Map()), null); assert.equal(findCycle(new Map([["a", ["missing"]]])), null); const n = 20000; const chain = new Map(); for (let i = 0; i < n; i += 1) chain.set(`n${String(i).padStart(6, "0")}`, [`n${String(i + 1).padStart(6, "0")}`]); assert.equal(findCycle(chain), null); chain.set(`n${String(n).padStart(6, "0")}`, ["n000000"]); assert.equal(findCycle(chain), "n000000"); }); test("deepEqual: structural, key-order insensitive, type-strict", () => { assert.ok(deepEqual({ a: [1, { b: null }] }, { a: [1, { b: null }] })); assert.ok(deepEqual({ a: 1, b: 2 }, { b: 2, a: 1 })); assert.ok(!deepEqual({ a: 1 }, { a: "1" })); assert.ok(!deepEqual([1, 2], [2, 1])); assert.ok(!deepEqual({ a: 1 }, { a: 1, b: undefined })); assert.ok(!deepEqual(null, {})); assert.ok(!deepEqual([], {})); }); // --- formats (pinned checker semantics) ----------------------------------------------------- test("isValidTime follows the pinned checker's strptime round-trip", () => { const cases = [ ["2026-09-06T03:00:00.000Z", true], ["2026-02-30T00:00:00.000Z", false], ["2024-02-29T00:00:00.000Z", true], ["2023-02-29T00:00:00.000Z", false], ["2026-09-06T24:00:00.000Z", false], ["2026-09-06T03:00:60.000Z", false], ["2026-09-06T03:00:00Z", false], ["2026-09-06T03:00:00.0000Z", false], ["0000-01-01T00:00:00.000Z", false], ["9999-12-31T23:59:59.999Z", true], ["2026-09-06T03:00:00.000Z\n", false], ["2026-9-06T03:00:00.000Z", false], // pinned checker (check.py strftime round-trip, glibc %Y unpadded below 1000): 0001..0999 refused ["0001-01-01T00:00:00.000Z", false], ["0999-12-31T00:00:00.000Z", false], ["1000-01-01T00:00:00.000Z", true], ["2026-09-06T03:00:00.000+00:00", false], ["2026-13-01T00:00:00.000Z", false], ["2100-02-29T00:00:00.000Z", false], ["2000-02-29T00:00:00.000Z", true], [null, false], [20260906, false], ]; for (const [value, expected] of cases) assert.equal(isValidTime(value), expected, JSON.stringify(value)); }); test("isValidRelativePath: pinned checker categories and byte bounds", () => { const ok = ["src/main.mjs", "a", "a/b/c", "x".repeat(4096), "é".repeat(2048), "\u2028", "spa ce", "a.b", "...", "dot./x"]; const bad = ["", "/abs", "a//b", "./a", "a/../b", "a/.", "..", "a\\b", "a\u0001", "a\u200b", "\ud800", "a\u0085", "\u007f", "x".repeat(4097), "é".repeat(2049), "\ufeffx", "a/", null, 5]; for (const v of ok) assert.ok(isValidRelativePath(v), JSON.stringify(v)); for (const v of bad) assert.ok(!isValidRelativePath(v), JSON.stringify(v)); }); // --- restrictions calculus --------------------------------------------------------------- const ROOT = { root: "workspace", path: null }; const SRC = { root: "workspace", path: "src" }; const SRC_LIB = { root: "workspace", path: "src/lib" }; const DOCS = { root: "workspace", path: "docs" }; test("grant intersection keeps the narrower prefix and never widens", () => { assert.deepEqual(intersectGrants([ROOT], [SRC]), [SRC]); assert.deepEqual(intersectGrants([SRC], [ROOT]), [SRC]); assert.deepEqual(intersectGrants([SRC], [SRC_LIB]), [SRC_LIB]); assert.deepEqual(intersectGrants([SRC], [DOCS]), []); assert.deepEqual(intersectGrants([SRC, DOCS], [ROOT]), [SRC, DOCS]); assert.deepEqual(intersectGrants([], [ROOT]), []); assert.deepEqual(intersectGrants([ROOT], [ROOT]), [ROOT]); // segment prefixes, not string prefixes assert.deepEqual(intersectGrants([SRC], [{ root: "workspace", path: "srcx" }]), []); }); test("pathPermitted uses segment prefixes", () => { assert.ok(pathPermitted("src/a.mjs", [SRC])); assert.ok(pathPermitted("src", [SRC])); assert.ok(!pathPermitted("srcx/a.mjs", [SRC])); assert.ok(!pathPermitted("docs/a.md", [SRC])); assert.ok(pathPermitted("anything/at/all", [ROOT])); assert.ok(!pathPermitted("anything", [])); }); test("layer intersection narrows only; network none dominates; endpoints intersect structurally", () => { const full = layerFromRestrictions({ operations: [...OPERATION_CATALOG], readPaths: [ROOT], writePaths: [ROOT], network: "none", endpointRefs: [], }); assert.deepEqual(intersectLayers(UNRESTRICTED_LAYER, full), full); assert.deepEqual(intersectLayers(full, UNRESTRICTED_LAYER), full); const reader = layerFromRestrictions({ operations: ["work.read", "file.read"], readPaths: [SRC], writePaths: [], network: "none", endpointRefs: [] }); const both = intersectLayers(full, reader); assert.deepEqual(both.operations, ["work.read", "file.read"]); assert.deepEqual(both.readPaths, [SRC]); assert.deepEqual(both.writePaths, []); assert.equal(both.network, "none"); const ep1 = { registry: "endpoint", id: "e1", revision: 1, digest: `sha256:${"0".repeat(64)}` }; const ep2 = { ...ep1, id: "e2" }; const netA = { ...UNRESTRICTED_LAYER, network: "approved-endpoints", endpointRefs: [ep1, ep2] }; const netB = { ...UNRESTRICTED_LAYER, network: "approved-endpoints", endpointRefs: [{ ...ep2 }] }; const netC = { ...UNRESTRICTED_LAYER, network: "approved-endpoints", endpointRefs: [{ ...ep1, revision: 2 }] }; assert.deepEqual(intersectLayers(netA, netB), { ...UNRESTRICTED_LAYER, network: "approved-endpoints", endpointRefs: [ep2] }); assert.deepEqual(intersectLayers(netA, netC), { ...UNRESTRICTED_LAYER, network: "none", endpointRefs: [] }); assert.deepEqual(intersectLayers(netA, { ...UNRESTRICTED_LAYER, network: "none", endpointRefs: [] }), { ...UNRESTRICTED_LAYER, network: "none", endpointRefs: [] }); // delegated operation set narrows without touching paths const delegated = { ...UNRESTRICTED_LAYER, operations: ["assignment.change"] }; const narrowed = intersectLayers(full, delegated); assert.deepEqual(narrowed.operations, ["assignment.change"]); assert.deepEqual(narrowed.readPaths, [ROOT]); }); // --- closed vocabulary --------------------------------------------------------------------- test("vocabularies are closed and consistent with the fixture index", () => { assert.deepEqual([...RESULTS].sort(), ["allowed", "invalid", "refused", "unresolved"]); assert.deepEqual(SUPPORTED_OPERATIONS, ["work.read", "file.read", "file.change", "assignment.change"]); assert.equal(new Set(RULES).size, RULES.length); for (const [step, outcome] of Object.entries(PROPOSAL_STEPS)) { assert.ok(RULES.includes(step), `proposal step ${step} is a rule`); assert.ok(["refused", "unresolved"].includes(outcome)); } assert.equal(PROPOSAL_STEPS["runtime-reconciliation-required"], "unresolved"); const used = new Set(); for (const c of INDEX.cases) { assert.ok(RESULTS.includes(c.expect.result), c.name); assert.ok(REASONS.includes(c.expect.reason), c.name); if (c.expect.rule !== null) { assert.ok(RULES.includes(c.expect.rule), `${c.name} rule ${c.expect.rule}`); used.add(c.expect.rule); } if (c.expect.proposalRule !== null) assert.ok(RULES.includes(c.expect.proposalRule), c.name); } // Every stage-2..5 rule of the closed vocabulary is exercised by at least one fixture, // except CLI/I-O rules, which cli.test.mjs covers with a synthetic sandbox. const cliOnly = new Set(["usage-invalid", "open-flags-unavailable", "input-open-failed", "input-not-regular", "input-size-changed"]); const unexercised = RULES.filter((r) => !used.has(r) && !cliOnly.has(r) && !(r in PROPOSAL_STEPS)); assert.deepEqual(unexercised, [], "rules without a fixture"); }); test("closed result fields equal the pinned charter §7 list; exit is process metadata, not a field", () => { const fromCharter = charterResultFields(); assert.deepEqual(fromCharter, RESULT_KEYS, "test copy must equal the charter text"); assert.deepEqual([...IMPL_RESULT_KEYS], fromCharter, "implementation export must equal the charter text"); assert.ok(!fromCharter.includes("exit")); const r = buildResult({ exit: 0, reason: "allowed", rule: null, selection: null, operation: null, proposal: null, diagnostic: null }); assert.deepEqual(Object.keys(r), fromCharter); assert.ok(!("exit" in r)); assert.equal(exitFor(r), 0); assert.equal(exitFor({ result: "refused", reason: "not-authorized" }), 3); assert.equal(exitFor({ result: "unresolved", reason: "unknown-effects" }), 3); assert.equal(exitFor({ result: "invalid", reason: "invalid-request" }), 2); assert.equal(exitFor({ result: "invalid", reason: "missing-state" }), 2); assert.equal(exitFor({ result: "invalid", reason: "io-failure" }), 4); for (const [exit, reason] of [[3, "allowed"], [0, "not-authorized"], [2, "io-failure"], [4, "invalid-request"]]) { assert.throws(() => buildResult({ exit, reason, rule: null, selection: null, operation: null, proposal: null, diagnostic: null }), /inconsistent/, `${exit}/${reason}`); } }); test("buildResult: fixed key order, derived result, closed shape", () => { const r = buildResult({ exit: 0, reason: "allowed", rule: null, selection: null, operation: null, proposal: null, diagnostic: null }); assert.deepEqual(Object.keys(r), RESULT_KEYS); assert.equal(r.disclaimer, DISCLAIMER); assert.equal(r.preview, PREVIEW); assert.equal(r.bundleVersion, 1); assert.equal(r.authentication, "not-modelled"); assert.equal(r.declarations, "unverified-simulation"); assert.equal(r.result, "allowed"); assert.equal(buildResult({ exit: 3, reason: "not-authorized", rule: "operation-not-permitted", selection: null, operation: null, proposal: null, diagnostic: null }).result, "refused"); assert.equal(buildResult({ exit: 3, reason: "unknown-effects", rule: "runtime-reconciliation-required", selection: null, operation: null, proposal: null, diagnostic: null }).result, "unresolved"); assert.equal(buildResult({ exit: 2, reason: "invalid-request", rule: "usage-invalid", selection: null, operation: null, proposal: null, diagnostic: null }).result, "invalid"); assert.equal(buildResult({ exit: 4, reason: "io-failure", rule: "input-open-failed", selection: null, operation: null, proposal: null, diagnostic: { byteOffset: null, inputPath: "x" } }).result, "invalid"); const d = buildResult({ exit: 2, reason: "invalid-request", rule: "input-parse-failed", selection: null, operation: null, proposal: null, diagnostic: { byteOffset: 7 } }); assert.deepEqual(d.diagnostic, { byteOffset: 7, inputPath: null }); assert.throws(() => buildResult({ exit: 3, reason: "not-authorized", rule: "made-up", selection: null, operation: null, proposal: null, diagnostic: null }), /unknown rule/); assert.throws(() => buildResult({ exit: 3, reason: "made-up", rule: null, selection: null, operation: null, proposal: null, diagnostic: null }), /unknown reason/); }); test("Refusal rejects unknown rules and reasons at construction", () => { assert.throws(() => new Refusal(3, "not-authorized", "no-such-rule", null), /unknown rule/); assert.throws(() => new Refusal(3, "no-such-reason", "operation-not-permitted", null), /unknown reason/); const r = new Refusal(3, "not-authorized", "operation-not-permitted", "bundle.operation"); assert.equal(r.detail, "bundle.operation"); assert.equal(r.step, null); }); // --- record shape ---------------------------------------------------------------------------- test("validateRecordShape refuses unsupported kinds at the kind gate without judging payloads", () => { const base = loadBundle("demo-read-w1"); const project = base.records.find((r) => r.kind === "project"); assert.equal(validateRecordShape(project).verdict, "valid"); const alien = { ...project, kind: "session", payload: 42 }; const v = validateRecordShape(alien); assert.equal(v.verdict, "unsupported-kind"); assert.equal(v.rule, "record-kind-unsupported"); assert.equal(validateRecordShape({ ...project, kind: "nonsense" }).verdict, "invalid"); assert.equal(validateRecordShape(null).verdict, "invalid"); }); // --- evaluate over fixtures --------------------------------------------------------------------- test("§10.4 precedence: cycle rules fire before parent-scope and predecessor-continuity refusals", () => { assert.equal(evaluate(loadBundle("cycle-mission-self-parent")).result.rule, "mission-parent-cycle"); assert.equal(evaluate(loadBundle("cycle-supersedes-self-rev2")).result.rule, "supersedes-cycle"); }); test("evaluate over every bundle fixture: closed result, no locator leakage, deterministic", () => { const names = readdirSync(join(FIXTURES, "bundles")).filter((f) => f.endsWith(".json")).map((f) => f.slice(0, -5)); assert.equal(names.length, INDEX.cases.filter((c) => c.raw === false).length); for (const name of names) { const expect = INDEX.cases.find((c) => c.name === name).expect; const bundle = loadBundle(name); const { result, exit, detail } = evaluate(bundle); assert.deepEqual(Object.keys(result), RESULT_KEYS, name); assert.ok(!("exit" in result), name); assert.equal(exit, expect.exit, name); assert.equal(exitFor(result), expect.exit, name); assert.equal(result.result, expect.result, name); assert.equal(result.reason, expect.reason, name); assert.equal(result.rule, expect.rule, name); assert.equal(result.proposal === null ? null : result.proposal.rule, expect.proposalRule, name); assert.equal(result.diagnostic, null, name); assert.ok(!("detail" in result), name); if (detail !== null) assert.equal(typeof detail, "string", name); assert.deepEqual(unknownKeys(result), [], `${name}: keys outside the closed result vocabulary`); assert.deepEqual(inspect(loadBundle(name)), result, `${name}: nondeterministic`); if (result.proposal !== null) { assert.equal(result.proposal.result, PROPOSAL_STEPS[result.proposal.rule], name); assert.equal(result.result, result.proposal.result, name); } if (exit === 0) { assert.equal(result.rule, null, name); assert.notEqual(result.selection, null, name); assert.notEqual(result.operation, null, name); } } }); test("evaluate never mutates its input", () => { const bundle = loadBundle("demo-change-w1"); const before = JSON.stringify(bundle); evaluate(bundle); assert.equal(JSON.stringify(bundle), before); }); // --- static import boundary (bounded evidence, not a sandbox) ------------------------------------ function importsOf(source) { return [...source.matchAll(/^import\s[^;]*?from\s+"([^"]+)";?$/gm)].map((m) => m[1]); } function codeOnly(source) { // strip block and line comments before token scanning return source.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:])\/\/.*$/gm, "$1"); } const FORBIDDEN_PURE = /\b(process|Date|fetch|require|globalThis|setTimeout|setInterval|queueMicrotask|Intl|performance|Worker|Atomics|SharedArrayBuffer|WebAssembly)\b|Math\.random|import\s*\(/; test("pure modules import only node:crypto and each other, and contain no ambient-capability tokens", () => { const allowed = new Set(["node:crypto", "./canonical.mjs", "./strict-json.mjs"]); for (const file of ["strict-json.mjs", "canonical.mjs", "resolve.mjs"]) { const source = readFileSync(join(HERE, file), "utf8"); for (const spec of importsOf(source)) assert.ok(allowed.has(spec), `${file} imports ${spec}`); const code = codeOnly(source); const hit = FORBIDDEN_PURE.exec(code); assert.equal(hit, null, `${file} contains ${hit && hit[0]}`); assert.ok(!/\bnode:(fs|child_process|net|http|https|os|dns|tls|worker_threads|vm|readline|cluster|dgram)\b/.test(code), file); } }); test("CLI imports only node:fs/url/path and local modules; no process spawning or network modules", () => { const source = readFileSync(join(HERE, "..", "foundation-inspect.mjs"), "utf8"); const allowed = new Set(["node:fs", "node:url", "node:path", "./foundation/strict-json.mjs", "./foundation/resolve.mjs"]); const specs = importsOf(source); assert.ok(specs.length >= 4); for (const spec of specs) assert.ok(allowed.has(spec), `CLI imports ${spec}`); const code = codeOnly(source); assert.ok(!/\b(child_process|node:net|node:http|node:https|node:os|node:dns|node:tls|worker_threads|node:vm|fetch|require|Date|globalThis|setTimeout|readdirSync|readFileSync|writeFileSync|homedir|tmpdir)\b/.test(code)); assert.ok(!/process\.env|process\.cwd|import\s*\(|process\.chdir|process\.stdin/.test(code)); // Only stdout write, exitCode assignment and argv read are ambient uses of `process`. const uses = [...code.matchAll(/process\.(\w+)/g)].map((m) => m[1]); assert.deepEqual([...new Set(uses)].sort(), ["argv", "exitCode", "stdout"]); }); // --- schema column vs strict profile (addendum FI-C2-1; verdict FI-FILBERT-5) ------------- const REVIEWS = join(HERE, "..", "..", "docs", "plans", "reviews"); const ADDENDUM = join(REVIEWS, "2026-09-06_foundation-inspector-pattern-profile-addendum.md"); const ADDENDUM_SHA256 = "afe2980be2f91e701dae5af3018831ac5c300474f52bcc06e740ce5b5cc68ca5"; const VERDICT = join(REVIEWS, "2026-09-06_foundation-inspector-pattern-profile-verdict.md"); const VERDICT_SHA256 = "03c979b77cc6b03b6685ba51ed1ce24c3f1d7b274ce8f772263a103670050da7"; test("the unchanged charter, the exact addendum and its verdict are bound; the profile rule is in the closed vocabulary", () => { const sha = (file) => createHash("sha256").update(readFileSync(file)).digest("hex"); assert.equal(sha(CHARTER), CHARTER_SHA256, "charter candidate 3 unchanged"); assert.equal(sha(ADDENDUM), ADDENDUM_SHA256, "addendum FI-C2-1 must be the exact reviewed text; rebind on amendment"); assert.equal(sha(VERDICT), VERDICT_SHA256, "verdict FI-FILBERT-5 must be the exact text; rebind on amendment"); assert.ok(readFileSync(ADDENDUM, "utf8").includes("profile-pattern-mismatch"), "addendum names the fixed profile rule"); assert.ok(RULES.includes("profile-pattern-mismatch")); assert.ok(RULES.indexOf("profile-pattern-mismatch") < RULES.indexOf("duplicate-record-identity"), "stage-2 rule, before identity"); }); const SAMPLES = { id: "task-w1", runtimeId: "00000000-0000-4000-8000-000000000001", digest: `sha256:${"0".repeat(64)}` }; const NEWLINE_MATRIX = [ // label, mutation, pinned-schema verdict, strict-profile verdict (FI-FILBERT-5 table) ["valid", (v) => v, true, true], ["one-final-lf", (v) => `${v}\n`, true, false], ["two-final-lf", (v) => `${v}\n\n`, false, false], ["crlf", (v) => `${v}\r\n`, false, false], ["cr", (v) => `${v}\r`, false, false], ["u2028", (v) => `${v}\u2028`, false, false], ["u2029", (v) => `${v}\u2029`, false, false], ["interior-lf", (v) => `${v.slice(0, 3)}\n${v.slice(3)}`, false, false], ]; test("schema column reproduces the pinned checker's end-anchor semantics; the profile column is strict", () => { assert.deepEqual(Object.keys(PINNED_PATTERNS), ["id", "runtimeId", "digest"]); for (const [family, sample] of Object.entries(SAMPLES)) { const re = PINNED_PATTERNS[family]; assert.ok(re.source.endsWith("$") && !re.flags.includes("m"), `${family} is end-anchored without multiline`); for (const [label, mutate, schema, profile] of NEWLINE_MATRIX) { const v = mutate(sample); assert.equal(pinnedPatternMatches(re, v), schema, `${family}/${label} schema column`); assert.equal(strictPatternMatches(re, v), profile, `${family}/${label} profile column`); assert.ok(schema || !profile, "profile-valid implies schema-valid"); } } }); test("validateRecordShape reports schema and profile verdicts independently, in validation order, without altering the value", () => { const base = loadBundle("demo-read-w1").records.find((r) => r.kind === "task" && r.id === "task-w1"); const clone = () => JSON.parse(JSON.stringify(base)); assert.deepEqual(validateRecordShape(clone()), { verdict: "valid", profileViolations: [] }); const one = clone(); one.id = `${one.id}\n`; const v1 = validateRecordShape(one); assert.deepEqual(v1, { verdict: "valid", profileViolations: ["record.id"] }); assert.equal(one.id, `${base.id}\n`, "no trimming or normalization"); const two = clone(); two.id = `${two.id}\n\n`; assert.deepEqual(validateRecordShape(two), { verdict: "invalid", rule: "shape-pattern-mismatch", path: "record.id" }); const many = clone(); many.id = `${many.id}\n`; many.scope.projectId = `${many.scope.projectId}\n`; many.authorizationRef = `${many.authorizationRef}\n`; many.payload.dependencies[0].id = `${many.payload.dependencies[0].id}\n`; assert.deepEqual(validateRecordShape(many).profileViolations, ["record.id", "record.scope.projectId", "record.authorizationRef", "record.payload.dependencies[0].id"]); const text = clone(); text.payload.purpose = "line one\nline two\r\n"; assert.deepEqual(validateRecordShape(text), { verdict: "valid", profileViolations: [] }, "free-form text is not a typed grammar"); const time = clone(); time.createdAt = `${time.createdAt}\n`; assert.deepEqual(validateRecordShape(time), { verdict: "invalid", rule: "shape-pattern-mismatch", path: "record.createdAt" }); }); test("profile refusal happens before identity lookup and is distinguishable from a missing reference", () => { const missing = evaluate(loadBundle("ref-missing-dependency")); assert.equal(missing.result.rule, "record-reference-missing"); const lf = loadBundle("profile-record-dependency-ref-id-dangling-one-final-lf"); const r = evaluate(lf); assert.equal(r.exit, 2); assert.equal(r.result.result, "invalid"); assert.equal(r.result.reason, "invalid-request"); assert.equal(r.result.rule, "profile-pattern-mismatch"); assert.equal(r.result.selection, null, "safe pre-admission output"); assert.equal(r.result.operation, null); assert.equal(r.result.proposal, null); assert.equal(r.result.diagnostic, null); assert.deepEqual(Object.keys(r.result), [...IMPL_RESULT_KEYS], "closed fields unchanged"); assert.ok(/^bundle\.records\[\d+\]\.payload\.dependencies\[0\]\.id$/.test(r.detail), "internal locator is the path, not the value"); assert.ok(!JSON.stringify(r.result).includes("task-nope"), "the value never enters the result"); // Every profile fixture: selection/operation withheld, no typed value with a newline echoed. for (const c of INDEX.cases.filter((x) => x.expect.rule === "profile-pattern-mismatch")) { const out = evaluate(loadBundle(c.name)); assert.equal(out.exit, 2, c.name); assert.equal(out.result.selection, null, c.name); assert.equal(out.result.operation, null, c.name); assert.ok(!JSON.stringify(out.result).includes("\\n"), `${c.name}: newline-bearing value echoed`); } assert.ok(INDEX.cases.filter((x) => x.expect.rule === "profile-pattern-mismatch").length >= 30); }); // --- FI-FILBERT-6 corrections (F1–F5): witness recipes as regression tests ------------------- function firstFailure(bundle) { const { result, exit } = evaluate(bundle); return { exit, result: result.result, reason: result.reason, rule: result.rule, step: result.proposal === null ? null : result.proposal.rule }; } test("F1: consulted registry references resolve by exact four-field declaration at the admission stage", () => { assert.ok(RULES.includes("registry-declaration-missing")); const missing = { exit: 3, result: "refused", reason: "missing-state", rule: "registry-declaration-missing", step: null }; for (const name of [ "adm-registry-declaration-missing-agent-refs", "adm-registry-declaration-missing-settings", "adm-registry-declaration-missing-soul", "adm-registry-declaration-digest-mismatch", "adm-registry-declaration-revision-mismatch", "adm-registry-declaration-missing-instruction", "adm-registry-declaration-missing-skill", "adm-registry-declaration-missing-endpoint-execution", "adm-registry-declaration-missing-endpoint-task", ]) assert.deepEqual(firstFailure(loadBundle(name)), missing, name); // never the ordinary-record structural rule, never an allowed preview const b = loadBundle("demo-read-w1"); b.registries = b.registries.filter((e) => e.registry !== "harness"); const r = firstFailure(b); assert.notEqual(r.rule, "record-reference-missing"); assert.equal(r.exit, 3); // positive controls: declared endpoint, unconsulted record's dangling reference assert.equal(firstFailure(loadBundle("adm-registry-declaration-endpoint-declared")).exit, 0); assert.equal(firstFailure(loadBundle("adm-registry-declaration-unselected-agent-ignored")).exit, 0); }); test("F1: consulted dependency and mission authorizations must be declared", () => { const undeclared = { exit: 3, result: "refused", reason: "missing-state", rule: "authorization-undeclared", step: null }; assert.deepEqual(firstFailure(loadBundle("adm-dependency-authorization-undeclared")), undeclared); assert.deepEqual(firstFailure(loadBundle("adm-mission-authorization-undeclared")), undeclared); assert.equal(firstFailure(loadBundle("adm-unconsulted-authorization-ignored")).exit, 0); assert.deepEqual(firstFailure(loadBundle("prop-subject-task-authorization-undeclared")), { ...undeclared, step: "intent-not-current" }); }); test("F2: a bound task is consulted work; work.read is required even without mission/dependency references", () => { const notReadable = { exit: 3, result: "refused", reason: "not-authorized", rule: "consulted-work-not-readable", step: null }; assert.deepEqual(firstFailure(loadBundle("adm-assigned-task-without-work-read")), notReadable); assert.deepEqual(firstFailure(loadBundle("adm-assigned-task-without-work-read-read-op")), notReadable); assert.equal(firstFailure(loadBundle("assigned-task-only-with-work-read")).exit, 0); // genuinely taskless reads keep skipping the assignment layer assert.equal(firstFailure(loadBundle("taskless-work-read")).exit, 0); assert.equal(firstFailure(loadBundle("taskless-file-read")).exit, 0); assert.deepEqual(firstFailure(loadBundle("prop-task-only-contexts-without-work-read")), { ...notReadable, step: "requester-lacks-original-scope-authority" }); assert.deepEqual(firstFailure(loadBundle("prop-task-only-target-context-without-work-read")), { ...notReadable, step: "requester-lacks-target-scope-authority" }); assert.equal(firstFailure(loadBundle("prop-task-only-contexts-with-work-read")).rule, "runtime-reconciliation-required"); }); test("F3: the issuer ceiling narrows both requester calculations and work access is rechecked", () => { const expect = { exit: 3, result: "refused", reason: "not-authorized", rule: "consulted-work-not-readable", step: "requester-lacks-original-scope-authority" }; assert.deepEqual(firstFailure(loadBundle("prop-issuer-denies-work-read")), expect); assert.deepEqual(firstFailure(loadBundle("prop-issuer-denies-work-read-task-only")), expect); assert.equal(firstFailure(loadBundle("prop-issuer-grants-work-read")).rule, "runtime-reconciliation-required"); // the narrowing is the same in both scopes, so the original scope is always the first failure; // the layer algebra itself is scope-independent const issuer = layerFromRestrictions({ operations: ["assignment.change"], readPaths: [], writePaths: [], network: "none", endpointRefs: [] }); const ctx = layerFromRestrictions({ operations: ["work.read", "assignment.change"], readPaths: [], writePaths: [], network: "none", endpointRefs: [] }); assert.deepEqual(intersectLayers(ctx, issuer).operations, ["assignment.change"]); }); test("F4: cycle graphs are revision-exact; contiguous history with reversed edges is not a cycle", () => { assert.equal(firstFailure(loadBundle("cycle-acyclic-versioned-history")).exit, 0); assert.equal(firstFailure(loadBundle("cycle-acyclic-versioned-history-file-change")).exit, 0); assert.equal(firstFailure(loadBundle("cycle-dependency-cross-revision")).rule, "dependency-cycle"); assert.equal(firstFailure(loadBundle("cycle-mission-parent-old-revision-only")).rule, "mission-parent-cycle"); assert.equal(firstFailure(loadBundle("cycle-mission-parent-previous-revision-not-a-cycle")).rule, "mission-parent-scope-mismatch"); // detector-level witness: the exact graph of the history is acyclic, the identity-merged one is not const exact = new Map([["T#1", ["D#1"]], ["D#1", []], ["T#2", []], ["D#2", ["T#2"]]]); assert.equal(findCycle(exact), null); const merged = new Map([["T", ["D"]], ["D", ["T"]]]); assert.equal(findCycle(merged), "D"); }); test("F5: the first shape failure is independent of record input order, including malformed records", () => { const forward = loadBundle("shape-order-forward"); const reference = firstFailure(forward); assert.equal(reference.rule, "shape-enum-mismatch"); const permutations = [ (r) => [...r].reverse(), (r) => [...r.slice(7), ...r.slice(0, 7)], (r) => [...r].sort((a, b) => (a.id < b.id ? 1 : -1)), (r) => r.filter((_, i) => i % 2 === 1).concat(r.filter((_, i) => i % 2 === 0)), ]; for (const [i, perm] of permutations.entries()) { const b = loadBundle("shape-order-forward"); b.records = perm(b.records); assert.deepEqual(firstFailure(b), reference, `permutation ${i}`); } assert.deepEqual(firstFailure(loadBundle("shape-order-reversed")), reference); // malformed records (no readable identity) are validated after every identified record, wherever they sit const withMalformed = loadBundle("shape-order-malformed-record-sorts-last"); assert.equal(firstFailure(withMalformed).rule, "shape-enum-mismatch"); const moved = loadBundle("shape-order-malformed-record-sorts-last"); moved.records = [...moved.records.slice(1), moved.records[0]]; assert.deepEqual(firstFailure(moved), firstFailure(withMalformed)); assert.equal(firstFailure(loadBundle("shape-order-only-malformed-record")).rule, "shape-missing-field"); // two unidentified records: content decides, not position const twoBad = loadBundle("demo-read-w1"); const a = { schemaVersion: 1 }; const c = { schemaVersion: "1" }; twoBad.records = [a, c, ...twoBad.records]; const first = firstFailure(twoBad); twoBad.records = [c, ...twoBad.records.slice(2), a]; assert.deepEqual(firstFailure(twoBad), first); }); // --- FI-ROCKO-7: stable traversal of the declared inventories (§10.4) ------------------------- /** First failure plus its internal locator with the input index replaced by the entry's typed key. */ function firstLocated(bundle, family) { const { result, exit, detail } = evaluate(bundle); const m = typeof detail === "string" ? detail.match(new RegExp(`^bundle\\.${family}\\[(\\d+)\\]`)) : null; const entry = m === null ? null : bundle[family][Number(m[1])]; return { exit, result: result.result, reason: result.reason, rule: result.rule, step: result.proposal === null ? null : result.proposal.rule, tail: m === null ? detail : detail.slice(m[0].length), key: entry === null ? null : ORDER_KEYS[family](entry) }; } /** * Permutations of index array `arr`: every permutation when n ≤ 4, otherwise a fixed * deterministic family (identity, reverse, every rotation, odd/even interleave, a * descending sort and twelve seeded Fisher–Yates shuffles). Includes the identity. */ function permutations(arr) { if (arr.length <= 4) { if (arr.length <= 1) return [arr]; return arr.flatMap((x, i) => permutations([...arr.slice(0, i), ...arr.slice(i + 1)]).map((rest) => [x, ...rest])); } const out = [arr, [...arr].reverse(), [...arr].sort((x, y) => y - x), arr.filter((_, i) => i % 2 === 1).concat(arr.filter((_, i) => i % 2 === 0))]; for (let k = 1; k < arr.length; k += 1) out.push([...arr.slice(k), ...arr.slice(0, k)]); let seed = 0x9e3779b9; const next = () => { seed = (Math.imul(seed ^ (seed >>> 15), 0x2c1b3c6d) >>> 0); seed = (seed ^ (seed >>> 12)) >>> 0; return seed / 0x100000000; }; for (let s = 0; s < 12; s += 1) { const p = [...arr]; for (let i = p.length - 1; i > 0; i -= 1) { const j = Math.floor(next() * (i + 1)); [p[i], p[j]] = [p[j], p[i]]; } out.push(p); } return out; } /** Assert that every permutation of bundle[family] yields the same located first failure; returns it. */ function assertPermutationStable(name, family, expectRule, expectKey) { const reference = firstLocated(loadBundle(name), family); assert.equal(reference.rule, expectRule, name); if (expectKey !== undefined) assert.deepEqual(reference.key, expectKey, `${name}: first failure locator`); const base = loadBundle(name); const perms = permutations(base[family].map((_, i) => i)); assert.ok(perms.length >= 2, `${name}: ${family} has a permutable inventory`); for (const perm of perms) { const b = loadBundle(name); b[family] = perm.map((i) => base[family][i]); assert.deepEqual(firstLocated(b, family), reference, `${name}: permutation ${JSON.stringify(perm)}`); } return reference; } test("stableOrder: typed keys, null components after values, unkeyed items last, ordering-form tie-break, then index", () => { const items = [ { registry: "scope-role", id: "b", revision: 2, digest: "x" }, // 0 { registry: "scope-role", id: "b", revision: 1, digest: "x" }, // 1 { registry: "agent-policy", id: "z", revision: 1, digest: "x" }, // 2 "not-an-entry", // 3 (unkeyed) { registry: "scope-role", id: "b", revision: null, digest: "x" }, // 4 (null revision after integers) { registry: 7 }, // 5 (unkeyed) { registry: "scope-role", id: "b", revision: 1, digest: "x" }, // 6 (duplicate of 1: canonical tie, index decides) { registry: "scope-role", id: "b", revision: 1, digest: "a" }, // 7 (digest "a" before "x") ]; assert.deepEqual(stableOrder(items, ORDER_KEYS.registries), [2, 7, 1, 6, 0, 4, 3, 5]); // unkeyed items order by their ordering form: "not-an-entry" (string) vs {"registry":7} (object); reversing the input does not change it const rev = [...items].reverse(); const order = stableOrder(rev, ORDER_KEYS.registries).map((i) => rev[i]); assert.deepEqual(order, stableOrder(items, ORDER_KEYS.registries).map((i) => items[i])); // records: kind/id/scope/revision, unreadable identity last const recs = [{ kind: "task", id: "t", scope: { kind: "system" }, revision: 2 }, { schemaVersion: 1 }, { kind: "task", id: "t", scope: { kind: "system" }, revision: 1 }, { kind: "agent-definition", id: "a", scope: { kind: "project", projectId: "p" }, revision: 1 }]; assert.deepEqual(stableOrder(recs, ORDER_KEYS.records), [3, 2, 0, 1]); assert.deepEqual(stableOrder(["b", "a", 7, "a"], ORDER_KEYS.authorizations), [1, 3, 0, 2]); assert.deepEqual(stableOrder([{ runId: "r", artifactId: "b", digest: "d" }, { runId: "r", artifactId: "a" }, { runId: 1 }], ORDER_KEYS.artifacts), [1, 0, 2]); assert.deepEqual(stableOrder([{ decisionRef: { kind: "decision", id: "b", scope: { kind: "system" }, revision: 1 } }, { decisionRef: null }, { decisionRef: { kind: "decision", id: "a", scope: { kind: "system" }, revision: 1 } }], ORDER_KEYS.delegationInputs), [2, 0, 1]); // semantic sequence arrays are never reordered: the helper is only applied to inventories const b = loadBundle("demo-read-w1"); const before = JSON.stringify(b); evaluate(b); assert.equal(JSON.stringify(b), before, "evaluation does not mutate or reorder the input bundle"); }); test("ordering: registries — differing shape errors, permutation-stable first rule follows the typed key", () => { const apFull = ["agent-policy", "ap-full", 1, null]; assertPermutationStable("shape-order-registries-forward", "registries", "shape-missing-field", apFull); assertPermutationStable("shape-order-registries-reversed", "registries", "shape-missing-field", apFull); // swapping which entry carries which error changes the first rule, not the winning key const swapped = assertPermutationStable("shape-order-registries-swapped-forward", "registries", "shape-unknown-field"); assert.deepEqual(swapped.key.slice(0, 2), ["agent-policy", "ap-full"]); assertPermutationStable("shape-order-registries-swapped-reversed", "registries", "shape-unknown-field"); // an entry without a readable registry/id is validated after every keyed entry const malformed = assertPermutationStable("shape-order-registries-malformed-sorts-last", "registries", "shape-enum-mismatch"); assert.deepEqual(malformed.key.slice(0, 2), ["no-such-registry", "role-writer"]); assert.equal(malformed.tail, ".registry"); const only = assertPermutationStable("shape-order-registries-only-malformed", "registries", "shape-type-mismatch"); assert.equal(only.key, null); }); test("ordering: registries — profile then digest stages follow the same key; stage precedence is fixed", () => { const apReader = ["agent-policy", "ap-reader\n", 1]; const p = assertPermutationStable("profile-order-registries-forward", "registries", "profile-pattern-mismatch"); assert.deepEqual(p.key.slice(0, 3), apReader); assert.equal(p.tail, ".id"); assertPermutationStable("profile-order-registries-reversed", "registries", "profile-pattern-mismatch"); // shape (later key) precedes profile (earlier key) const sp = assertPermutationStable("profile-order-shape-precedes-profile-registries", "registries", "shape-unknown-field"); assert.deepEqual(sp.key.slice(0, 2), ["project-policy", "pp-p2"]); // digest stage: unsupported content on the lower key precedes a mismatch on the higher key, and vice versa const d = assertPermutationStable("shape-order-registry-digest-forward", "registries", "mock-content-unsupported"); assert.deepEqual(d.key.slice(0, 2), ["agent-policy", "ap-full"]); assert.equal(d.reason, "unsupported-capability"); assertPermutationStable("shape-order-registry-digest-reversed", "registries", "mock-content-unsupported"); const ds = assertPermutationStable("shape-order-registry-digest-swapped-forward", "registries", "registry-digest-mismatch"); assert.deepEqual(ds.key.slice(0, 2), ["agent-policy", "ap-full"]); assert.equal(ds.tail, ".digest"); assertPermutationStable("shape-order-registry-digest-swapped-reversed", "registries", "registry-digest-mismatch"); // profile (later key) precedes digest (earlier key) const pd = assertPermutationStable("shape-order-profile-precedes-digest-registries", "registries", "profile-pattern-mismatch"); assert.deepEqual(pd.key.slice(0, 2), ["project-policy", "pp-p2\n"]); // two competing digest mismatches: the lower key is reported in every order const two = assertPermutationStable("shape-order-two-digest-mismatches-reversed", "registries", "registry-digest-mismatch"); assert.deepEqual(two.key.slice(0, 2), ["scope-role", "role-reader"]); }); test("ordering: artifacts — differing shape errors and profile violations are permutation-stable", () => { const art1 = assertPermutationStable("shape-order-artifacts-forward", "artifacts", "shape-pattern-mismatch"); assert.deepEqual(art1.key.slice(0, 2), ["run-1", "art-1"]); assert.equal(art1.tail, ".digest"); assertPermutationStable("shape-order-artifacts-reversed", "artifacts", "shape-pattern-mismatch"); const sw = assertPermutationStable("shape-order-artifacts-swapped-forward", "artifacts", "shape-unknown-field"); assert.deepEqual(sw.key.slice(0, 2), ["run-1", "art-1"]); assertPermutationStable("shape-order-artifacts-swapped-reversed", "artifacts", "shape-unknown-field"); const mal = assertPermutationStable("shape-order-artifacts-malformed-sorts-last", "artifacts", "shape-pattern-mismatch"); assert.deepEqual(mal.key.slice(0, 2), ["run-1", "art-2"]); const prof = assertPermutationStable("profile-order-artifacts-reversed", "artifacts", "profile-pattern-mismatch"); assert.deepEqual(prof.key.slice(0, 2), ["run-1", "art-2\n"]); assert.equal(prof.tail, ".artifactId"); }); test("ordering: authorizations — the lower runtime id is the locator in every order; shape precedes profile", () => { const low = assertPermutationStable("shape-order-authorizations-forward", "authorizations", "shape-pattern-mismatch"); assert.deepEqual(low.key, ["00000000-0000-1000-8000-000000000002"]); assertPermutationStable("shape-order-authorizations-reversed", "authorizations", "shape-pattern-mismatch"); const mal = assertPermutationStable("shape-order-authorizations-malformed-sorts-last", "authorizations", "shape-pattern-mismatch"); assert.deepEqual(mal.key, ["00000000-0000-4000-8000-000000000001-x"]); const prof = assertPermutationStable("profile-order-authorizations-reversed", "authorizations", "profile-pattern-mismatch"); assert.deepEqual(prof.key, ["00000000-0000-4000-8000-000000000001\n"]); const sp = assertPermutationStable("profile-order-shape-precedes-profile-authorizations", "authorizations", "shape-pattern-mismatch"); assert.deepEqual(sp.key, ["00000000-0000-4000-8000-000000000002-x"]); }); test("ordering: delegationInputs — shape, identity and structural stages traverse by decisionRef key", () => { const delegB = ["decision", "d-deleg-b", "workspace:p1/w1", 1]; const f = assertPermutationStable("shape-order-delegation-inputs-forward", "delegationInputs", "shape-enum-mismatch", delegB); assert.equal(f.tail, ".mode"); assertPermutationStable("shape-order-delegation-inputs-reversed", "delegationInputs", "shape-enum-mismatch", delegB); assertPermutationStable("shape-order-delegation-inputs-swapped-forward", "delegationInputs", "shape-missing-field", delegB); assertPermutationStable("shape-order-delegation-inputs-swapped-reversed", "delegationInputs", "shape-missing-field", delegB); assertPermutationStable("shape-order-delegation-inputs-malformed-sorts-last", "delegationInputs", "shape-enum-mismatch", delegB); // structural: both inputs reference a missing revision; the lower key is reported wherever it sits const s = assertPermutationStable("struct-order-delegation-inputs-forward", "delegationInputs", "record-reference-missing", [...delegB.slice(0, 3), 9]); assert.equal(s.reason, "missing-state"); assert.equal(s.tail, ".decisionRef"); assertPermutationStable("struct-order-delegation-inputs-reversed", "delegationInputs", "record-reference-missing", [...delegB.slice(0, 3), 9]); // identity: a duplicated input is found after a valid lower-key input in every order const dup = assertPermutationStable("dup-order-delegation-inputs-reversed", "delegationInputs", "duplicate-delegation-input"); assert.deepEqual(dup.key.slice(0, 2), ["decision", "d-deleg-reg"]); }); test("ordering: family precedence and identity-stage order are fixed regardless of input positions", () => { assert.equal(firstFailure(loadBundle("shape-order-family-precedence-registries-before-artifacts")).rule, "shape-unknown-field"); assert.equal(firstFailure(loadBundle("shape-order-family-precedence-artifacts-before-authorizations")).rule, "shape-pattern-mismatch"); assert.equal(firstFailure(loadBundle("shape-order-family-precedence-authorizations-before-delegation-inputs")).rule, "shape-type-mismatch"); assert.equal(firstFailure(loadBundle("dup-order-registries-precede-artifacts")).rule, "duplicate-registry-identity"); // duplicate plain-registry identity with differing digests: the higher digest is the reported entry in every order const base = loadBundle("demo-read-w1"); const harness = base.registries.find((e) => e.registry === "harness"); base.registries.push({ ...harness, digest: "sha256:" + "f".repeat(64) }); const ref = firstLocated(base, "registries"); assert.equal(ref.rule, "duplicate-registry-identity"); assert.equal(ref.key[3], "sha256:" + "f".repeat(64)); const moved = { ...base, registries: [base.registries[base.registries.length - 1], ...base.registries.slice(0, -1)] }; assert.deepEqual(firstLocated(moved, "registries"), ref); const lower = { ...base, registries: base.registries.map((e) => (e.digest === "sha256:" + "f".repeat(64) ? { ...e, digest: "sha256:" + "0".repeat(64) } : e)) }; assert.equal(firstLocated(lower, "registries").key[3], harness.digest, "the original (now higher) digest is reported"); // artifacts: duplicate run/artifact with differing digests, permutation-stable locator const art = loadBundle("dup-artifact-identity"); const a = firstLocated(art, "artifacts"); assert.equal(a.rule, "duplicate-artifact-identity"); const artRev = { ...art, artifacts: [...art.artifacts].reverse() }; assert.deepEqual(firstLocated(artRev, "artifacts"), a); }); test("R5-1: the ordering form is total and injective over the strict-JSON domain, distinct from the mock digest", () => { // every parser-producible value has a form; canonicalize() refuses most of these const values = [null, true, false, 0, -1, 9007199254740991, "", "a", "é", "É", "é", "\u{1F600}", "café", "cafe", " ", " ", "\\", "\"", [], [null], [1, 2], [2, 1], [[1], 2], [1, [2]], {}, { a: 1 }, { a: 2 }, { b: 1 }, { a: 1, b: 2 }, { a: { b: 1 } }, { a: [1] }, { "é": 1 }, { "é": "é" }, { extra: "é" }, { x: "é", y: null }, [{ extra: "é" }], ["é"]]; const forms = values.map(orderingForm); for (const f of forms) assert.equal(typeof f, "string"); assert.equal(new Set(forms).size, values.length, "distinct values have distinct forms"); let refused = 0; for (const v of values) { try { canonicalize(v); } catch (e) { assert.ok(e instanceof CanonicalError); refused += 1; } } assert.ok(refused >= 12, "the mock digest domain is narrower than the ordering-form domain"); // key order is by UTF-16 code unit and does not depend on insertion order; array order is semantic assert.equal(orderingForm({ b: 1, a: 2 }), orderingForm({ a: 2, b: 1 })); assert.equal(orderingForm({ b: 1, a: 2 }), '{"a":2,"b":1}'); assert.equal(orderingForm({ "é": 1, e: 1, z: 1 }), '{"e":1,"z":1,"é":1}'); assert.notEqual(orderingForm([1, 2]), orderingForm([2, 1])); assert.equal(orderingForm("é"), JSON.stringify("é")); assert.equal(orderingForm([{ extra: "é" }, "é"]), '[{"extra":"é"},"é"]'); // no normalization or case folding; the form never throws for non-ASCII and is not a validity judgement assert.notEqual(orderingForm("é"), orderingForm("é")); assert.equal(orderingForm("é"), orderingForm("é")); assert.throws(() => orderingForm(undefined), /non-JSON/); assert.throws(() => orderingForm(() => 1), /non-JSON/); // stableOrder on two distinct unkeyed Unicode entries: string form before object form, in both input orders for (const family of ["records", "registries", "artifacts", "delegationInputs"]) { assert.deepEqual(stableOrder([{ extra: "é" }, "é"], ORDER_KEYS[family]), [1, 0], family); assert.deepEqual(stableOrder(["é", { extra: "é" }], ORDER_KEYS[family]), [0, 1], family); } // equal typed keys with distinct Unicode bodies: the form decides, never the index const a = { registry: "agent-policy", id: "ap", revision: 1, digest: "d", content: { name: "café", z: 1 } }; const b = { registry: "agent-policy", id: "ap", revision: 1, digest: "d", content: { name: "café", a: 1 } }; assert.deepEqual(stableOrder([a, b], ORDER_KEYS.registries), [1, 0]); assert.deepEqual(stableOrder([b, a], ORDER_KEYS.registries), [0, 1]); // only byte-identical entries fall back to the input index assert.deepEqual(stableOrder([a, structuredClone(a)], ORDER_KEYS.registries), [0, 1]); }); test("R5-1: unkeyed Unicode entries — the located first failure is permutation-stable in every family", () => { for (const [family, slug] of [["records", "records"], ["registries", "registries"], ["artifacts", "artifacts"], ["delegationInputs", "delegation-inputs"]]) { const f = assertPermutationStable(`shape-order-unkeyed-unicode-${slug}-forward`, family, "shape-type-mismatch"); assert.equal(f.key, null, family); const r = assertPermutationStable(`shape-order-unkeyed-unicode-${slug}-reversed`, family, "shape-type-mismatch"); assert.equal(r.key, null, family); // the two fixtures differ only in input order; the string entry is the located one in both const fb = loadBundle(`shape-order-unkeyed-unicode-${slug}-forward`); const rb = loadBundle(`shape-order-unkeyed-unicode-${slug}-reversed`); assert.deepEqual([...fb[family]].reverse(), rb[family], family); for (const bundle of [fb, rb]) { const m = new RegExp(`^bundle\\.${family}\\[(\\d+)\\]`).exec(evaluate(structuredClone(bundle)).detail); assert.ok(m, family); assert.equal(typeof bundle[family][Number(m[1])], "string", `${family}: the located entry is the string`); } } }); test("R5-1: equal-key Unicode entries — distinct bodies never collapse; first failure is permutation-stable", () => { // records: two agent-a copies (same kind/id/scope/revision, non-ASCII displayName); the copy with the unknown // field sorts first by form ("extra" < "harnessRef"), so shape-unknown-field is reported in every order const agentA = ["agent-definition", "agent-a", "system", 1]; const rf = assertPermutationStable("shape-order-equal-key-unicode-records-forward", "records", "shape-unknown-field", agentA); assert.equal(rf.tail, ".payload.extra"); assertPermutationStable("shape-order-equal-key-unicode-records-reversed", "records", "shape-unknown-field", agentA); // registries: two ap-full copies (same registry/id/revision/digest, non-ASCII path); same rule in every order const gf = assertPermutationStable("shape-order-equal-key-unicode-registries-forward", "registries", "shape-unknown-field"); assert.deepEqual(gf.key.slice(0, 3), ["agent-policy", "ap-full", 1]); assert.equal(gf.tail, ".content.restrictions.extra"); assertPermutationStable("shape-order-equal-key-unicode-registries-reversed", "registries", "shape-unknown-field"); // the sibling copy's failure (enum) is what remains once the unknown field is removed: the pair really carries two errors const b = loadBundle("shape-order-equal-key-unicode-records-forward"); const extra = b.records.find((r) => r.id === "agent-a" && "extra" in r.payload); delete extra.payload.extra; assert.equal(firstFailure(b).rule, "shape-enum-mismatch"); const g = loadBundle("shape-order-equal-key-unicode-registries-forward"); delete g.registries.find((e) => e.id === "ap-full" && "extra" in e.content.restrictions).content.restrictions.extra; assert.equal(firstFailure(g).rule, "shape-enum-mismatch"); // Unicode positives remain allowed assert.equal(firstFailure(loadBundle("positive-unicode-display-name-allowed")).result, "allowed"); }); test("F5: message-is-not-authority follows structural validation and precedes requester admission", () => { const message = { exit: 3, result: "refused", reason: "not-authorized", rule: "message-is-not-authority", step: "message-is-not-authority" }; assert.deepEqual(firstFailure(loadBundle("prop-message-precedes-requester-admission")), message); assert.deepEqual(firstFailure(loadBundle("prop-message-is-not-authority")), message); assert.deepEqual(firstFailure(loadBundle("prop-message-precedes-stale-subject")), message); assert.equal(firstFailure(loadBundle("prop-message-after-structural-failure")).rule, "requester-context-mismatch"); // without a message the missing delegation keeps its step-3 position assert.deepEqual(firstFailure(loadBundle("prop-delegation-missing-after-requester-admission")), { exit: 3, result: "refused", reason: "not-authorized", rule: "operation-not-permitted", step: "requester-lacks-original-scope-authority" }); assert.equal(firstFailure(loadBundle("prop-delegation-missing")).rule, "delegation-missing"); });