Files
stack/scripts/foundation/canonical.test.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

107 lines
4.1 KiB
JavaScript

// Unit tests for inspector-content-digest/1 (charter §6, feasibility r2 §6.1 vectors).
import { test } from "node:test";
import assert from "node:assert/strict";
import { canonicalize, digestOf, CanonicalError, DIGEST_ALGORITHM } from "./canonical.mjs";
const V1 = {
restrictions: {
operations: ["work.read", "file.read"],
readPaths: [{ root: "workspace", path: null }],
writePaths: [],
network: "none",
endpointRefs: [],
},
};
function refuses(value, code) {
assert.throws(() => digestOf(value), (e) => e instanceof CanonicalError && e.code === code && e.reason === "unsupported-capability");
}
test("algorithm identifier is fixed", () => {
assert.equal(DIGEST_ALGORITHM, "inspector-content-digest/1");
});
test("V1: pinned digest of the reader restrictions", () => {
assert.equal(
canonicalize(V1),
'{"restrictions":{"endpointRefs":[],"network":"none","operations":["work.read","file.read"],"readPaths":[{"path":null,"root":"workspace"}],"writePaths":[]}}',
);
assert.equal(digestOf(V1), "sha256:0bc44e14fd8354a8a85be879306a54881da7a0cb4d3dcf1f88bc4bc08a40dc9c");
});
test("V2: array order is significant", () => {
const v2 = structuredClone(V1);
v2.restrictions.operations = ["file.read", "work.read"];
assert.equal(digestOf(v2), "sha256:63817bffc57803ca6ac5df971aa0a160d4be73646394b3bc536578bd66c82660");
assert.notEqual(digestOf(v2), digestOf(V1));
});
test("V3: writer restrictions", () => {
const v3 = structuredClone(V1);
v3.restrictions.operations = ["work.read", "file.read", "file.change"];
v3.restrictions.writePaths = [{ root: "workspace", path: "docs" }];
assert.equal(digestOf(v3), "sha256:d538c865808cfe7665956615fb48f69819baa0833626d1ef6e1cb3614acbc7da");
});
test("object key order is not significant", () => {
const reordered = {
restrictions: {
writePaths: [], endpointRefs: [], network: "none",
readPaths: [{ path: null, root: "workspace" }],
operations: ["work.read", "file.read"],
},
};
assert.equal(digestOf(reordered), digestOf(V1));
const nullProto = Object.create(null);
nullProto.restrictions = reordered.restrictions;
assert.equal(digestOf(nullProto), digestOf(V1));
});
test("V4: non-integral numbers refuse", () => {
const v4 = structuredClone(V1);
v4.restrictions.weight = 1.5;
refuses(v4, "number-not-safe-integer");
refuses({ a: -0 }, "number-not-safe-integer");
refuses({ a: 9007199254740992 }, "number-not-safe-integer");
refuses({ a: Number.NaN }, "number-not-safe-integer");
refuses({ a: Number.POSITIVE_INFINITY }, "number-not-safe-integer");
assert.equal(canonicalize({ a: 9007199254740991, b: -5, c: 0 }), '{"a":9007199254740991,"b":-5,"c":0}');
});
test("V5: non-ASCII strings and keys refuse", () => {
const v5 = structuredClone(V1);
v5.restrictions.note = "café";
refuses(v5, "string-not-ascii");
refuses({ "kéy": 1 }, "string-not-ascii");
refuses({ a: "\u{1F600}" }, "string-not-ascii");
});
test("ASCII control characters are escaped like Python json.dumps", () => {
assert.equal(canonicalize({ a: "x\ty\n\"\\" }), '{"a":"x\\ty\\n\\u0001\\"\\\\"}');
assert.equal(canonicalize({ a: "" }), '{"a":""}');
});
test("keys sort by UTF-16 code unit, not locale", () => {
assert.equal(canonicalize({ b: 1, B: 2, a: 3, A: 4, _: 5, "10": 6, "2": 7 }), '{"10":6,"2":7,"A":4,"B":2,"_":5,"a":3,"b":1}');
});
test("undefined, functions, symbols, bigints refuse; depth is bounded", () => {
refuses({ a: undefined }, "value-not-json");
refuses({ a: () => 1 }, "value-not-json");
refuses({ a: Symbol("s") }, "value-not-json");
refuses({ a: 1n }, "value-not-json");
refuses(undefined, "value-not-json");
let deep = 1;
for (let i = 0; i < 64; i += 1) deep = [deep];
assert.ok(canonicalize(deep));
deep = [deep];
refuses(deep, "depth-exceeded");
});
test("booleans, null and nesting encode compactly and deterministically", () => {
const value = { z: [true, false, null, { y: [], x: {} }] };
assert.equal(canonicalize(value), '{"z":[true,false,null,{"x":{},"y":[]}]}');
assert.equal(digestOf(value), digestOf(structuredClone(value)));
assert.match(digestOf(value), /^sha256:[0-9a-f]{64}$/);
});