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.
This commit is contained in:
2026-09-07 14:06:35 -05:00
parent 127a54fdff
commit 8ebddd6f93
423 changed files with 665972 additions and 0 deletions
+87
View File
@@ -0,0 +1,87 @@
/**
* inspector-content-digest/1 — canonical form and digest for MOCK registry
* content only (charter candidate 3 §6; feasibility r2 §6.1).
*
* Domain: objects, arrays, ASCII strings, booleans, null and safe integers.
* Object keys are sorted by UTF-16 code unit; separators are compact; array
* order is preserved; the digest is "sha256:" + hex(SHA-256(UTF-8 bytes)).
* Anything outside the domain refuses with CanonicalError (reason
* unsupported-capability): non-ASCII strings, non-integral or unsafe numbers,
* negative zero, undefined, functions, symbols, bigints.
*
* This is NOT the candidate README launch-fingerprint projection and makes no
* JCS (RFC 8785) claim. Pure module: imports only node:crypto.
*/
import { createHash } from "node:crypto";
export const DIGEST_ALGORITHM = "inspector-content-digest/1";
export class CanonicalError extends Error {
constructor(code) {
super(code);
this.name = "CanonicalError";
this.code = code;
this.reason = "unsupported-capability";
}
}
function isAscii(text) {
for (let i = 0; i < text.length; i += 1) {
if (text.charCodeAt(i) > 0x7f) return false;
}
return true;
}
function encodeString(text) {
if (!isAscii(text)) throw new CanonicalError("string-not-ascii");
// JSON.stringify on an ASCII string escapes exactly ", \ and U+0000..U+001F
// (short forms \b \f \n \r \t, otherwise \u00XX lowercase), matching Python
// json.dumps for the ASCII domain.
return JSON.stringify(text);
}
function encodeValue(value, depth) {
if (depth > 64) throw new CanonicalError("depth-exceeded");
if (value === null) return "null";
switch (typeof value) {
case "boolean":
return value ? "true" : "false";
case "number":
if (!Number.isSafeInteger(value) || Object.is(value, -0)) {
throw new CanonicalError("number-not-safe-integer");
}
return String(value);
case "string":
return encodeString(value);
case "object":
break;
default:
throw new CanonicalError("value-not-json");
}
if (Array.isArray(value)) {
const parts = [];
for (const item of value) parts.push(encodeValue(item, depth + 1));
return `[${parts.join(",")}]`;
}
const keys = Object.keys(value);
keys.sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
const parts = [];
for (const key of keys) {
const item = value[key];
if (item === undefined) throw new CanonicalError("value-not-json");
parts.push(`${encodeString(key)}:${encodeValue(item, depth + 1)}`);
}
return `{${parts.join(",")}}`;
}
/** Canonical compact encoding, or throws CanonicalError. */
export function canonicalize(value) {
return encodeValue(value, 0);
}
/** "sha256:<hex>" of the canonical encoding, or throws CanonicalError. */
export function digestOf(value) {
const canonical = canonicalize(value);
return `sha256:${createHash("sha256").update(canonical, "utf8").digest("hex")}`;
}