Files
stack/docs/plans/reviews/2026-09-07_foundation-inspector-rocko-build-r6.md
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

16 KiB
Raw Permalink Blame History

Foundation inspector — rocko build report r6 (FI-ROCKO-8: R5-1 total tie-break over the strict-JSON domain)

Author: rocko (Claude Fable 5.1). Coordinator: darkwing. Reviewer: filbert. Task: FI-ROCKO-8. Written 2026-09-07 15:35 UTC. Supersedes nothing: the original report, the r2r5 reports and manifests, the charter, the addendum, all verdicts, the corrections request, the ordering follow-up and the demo test-gate decision are preserved unedited. This revision corrects exactly the FI-FILBERT-7 finding R5-1 (verdict docs/plans/reviews/2026-09-07_foundation-inspector-code-verdict-r5.md, sha256 154e7b5d804c059d6fec4c936c78e4ae33bb7fc273ba3a5eec7b00d0a9ce1e59), records what changed r5 → r6, the new Unicode permutation regressions, and the frozen candidate for independent re-review.

0. Admission, compatibility and blockers

FI-ROCKO-8 is admitted and compatible with the standing chain, the FI-ROCKO-3 build request, the corrections request, the ordering follow-up, the demo test-gate decision and the FI-FILBERT-7 verdict; the work stays inside the original source/test allowlist plus the two r6 receipt paths named in the task. C1 (Archify rev 3) remains HELD with no lane action. No open blocker. scripts/test-task.sh and scripts/test-release.sh remain NOT RUN / DEFERRED under the owner decision (§7); no live test authority is claimed.

Inputs bound to this revision (all hashes machine-read; full list in the r6 manifest pinnedInputs, which adds the r5 report, the r5 manifest and the FI-FILBERT-7 verdict to the r5 list):

Input sha256
charter docs/plans/2026-09-06_foundation-inspector-charter.md 19b6721128a627a2032ffdb95ece2d50abe69a8f6d521e9eff8bbdaff22798b6
addendum FI-C2-1 afe2980be2f91e701dae5af3018831ac5c300474f52bcc06e740ce5b5cc68ca5
code verdict FI-FILBERT-6 e4cc5970aab20240e6ea3a9a1513ef942ff167fefbb03356b9a32c89999adb3d
code verdict FI-FILBERT-7 (r5, NOT APPROVED, R5-1) 154e7b5d804c059d6fec4c936c78e4ae33bb7fc273ba3a5eec7b00d0a9ce1e59
corrections request 35c237f5c001832a0cfed23b03ff7530ffa78443612ffe11130fe456b968bd61
ordering follow-up e93a65b94fd4df8bd6e74c2f38afaf02e0f86b36632ff5a820fd8ac464c22101
demo test-gate decision 62cea74bea59e69f29c8a29a9c0311fdc6feffa2c337bc6a141db55149204898
r5 report 17c67427dfdf1bc5dac491c4d0223928e87fdfc67a26175ac239eb2b2f7b7dec
r5 manifest (369 files) a63bb103e4cfe51bbd8a42cd95545f935ac83c1da51cf469298375cf94ced135
pinned schema / checker / fixtures 05774aaf…2af6 / 82564a7d…4607 / d433d06d…da85

Git: HEAD 9a5fbdbda74b16adf488fe28138b2ba69ea5e669 (main), unchanged throughout; nothing staged. Tracked modifications present in the worktree: the pre-existing BUILD-LOG.md, docs/SESSIONS.md, docs/plans/CURRENT.md, plus docs/TOOLS.md and scripts/agent.sh, which were modified by another session at 15:04 UTC (outside the allowlist; not touched, not inspected beyond git status, reported here only). All candidate files remain untracked. Toolchain: node v26.8.1, Python 3.12.8, jsonschema 4.26.0, Linux 7.2.2-1-cachyos.

1. Evidence preservation

  • r5 report and manifest: unedited, hashes above re-verified after the r6 build. The r5 §3(a) claim that "two entries with equal typed key and equal canonical content are indistinguishable" was wrong for non-ASCII entries (R5-1); it is corrected here (§2), not by editing r5.
  • r4 report/manifest, r3 report (ec0444c9…5508) and manifest (3c2253b6…8547): unedited. The r4 §3(g) correction stands and is repeated so it stays visible: the r3 test-task.sh 90/0 receipt did execute the suite's Docker and live branches, contradicting r3 §6 "no engine launch". r3 is not relabeled; those actions were not repeated in r4, r5 or r6 (§7).
  • Charter, addendum, both verdicts, corrections request, follow-up, decision doc, pinned schema/checker/fixtures, canonical.mjs (a7760882…0172, the mock-digest canonicalizer): unedited.
  • Every pre-existing fixture bundle, raw case, demo copy and golden is byte-identical to r5 (manifest diff r5 → r6: 5 files changed, 13 bundles added, 0 removed; §4). Every prior index.json expectation is unchanged; only 13 cases were appended.

2. Correction of R5-1

2.1 The defect

r5 stableOrder broke equal-key and unkeyed ties on canonicalize(), the ASCII-only mock content-digest canonicalizer, and mapped its CanonicalError to the empty string. Every entry containing a non-ASCII string therefore had the same tie-break form, and two distinct such entries with equal typed keys (duplicates) or no readable key fell through to the input index, restoring input-order-dependent first failures — exactly filbert's six reversed-pair witnesses. The defect was a wrong choice of domain: the mock digest domain is deliberately narrower than the strict-JSON input domain (Unicode record values and logical paths are permitted input), so a digest canonicalizer can never be a total order over inputs.

2.2 Implementation (scripts/foundation/resolve.mjs, r6 lines 719771)

  • orderingForm(value) (exported, new): a total ordering form over the strict-JSON parser's output domain (null, booleans, safe integers, any well-formed Unicode string, arrays, objects): literals as JSON literals, strings and keys as JSON.stringify literals, arrays in order, objects with keys sorted by UTF-16 code unit, compact separators, no normalization or case folding, no length or depth limit beyond the parser's own bounds. Every parser-producible value has exactly one form and distinct values have distinct forms (injective), so the index fallback in stableOrder now decides only between byte-identical entries. It throws a plain Error only for non-JSON values, which parseStrict never yields; there is no CanonicalError path. It is not a digest, is never emitted or compared with a declared digest, and is not a validity judgement: canonical.mjs and the inspector-content-digest/1 domain are untouched and mock-content-unsupported semantics are unchanged.
  • stableOrder(items, keyOf): unchanged except that the memoized tie-break calls orderingForm(items[i]) instead of the canonicalize/CanonicalError fallback. Typed keys, keyed-before-unkeyed, component comparison and the input-index locator are exactly r5. ORDER_KEYS, checkInventory, shapeValidationOrder, the digest/identity/structural traversals and Model.order are unchanged.
  • Nothing else changed: no blanket Unicode rejection (the Unicode positive controls stay allowed), semantic sequence arrays are still never reordered, family and stage precedence are unchanged, detail remains internal and no value is echoed, the closed rule/reason/exit vocabulary is unchanged, no new dependency.

2.3 Regressions (fixtures in build-fixtures.mjs 12311266; tests in resolve.test.mjs 723801)

The fixtures rebuild filbert's witnesses from the base bundle. Each pair carries two different shape failures on two distinct entries with a legitimate non-ASCII value, forward and reversed; the expected first rule is the same in both orders and in every permutation (assertPermutationStable, all permutations for ≤ 4 entries, the fixed deterministic family beyond that).

Case Content Expected first rule (both orders)
shape-order-unkeyed-unicode-{records,registries,artifacts,delegation-inputs}-{forward,reversed} (shape) [{"extra":"é"}, "é"] as the whole inventory: two unreadable-key entries shape-type-mismatch at the string entry (form "… sorts before {…); r5 flipped to shape-unknown-field/shape-missing-field in one order
shape-order-equal-key-unicode-records-{forward,reversed} (shape) two agent-definition/agent-a/system/1 copies, both displayName: "Café", one status: "invalid-status", the other an unknown payload.extra shape-unknown-field at .payload.extra (the copy with the extra key sorts first: "extra" < "harnessRef"); removing the extra key yields shape-enum-mismatch (the pair really carries two errors)
shape-order-equal-key-unicode-registries-{forward,reversed} (shape) two agent-policy/ap-full/1/<same digest> copies, readPaths[0].path: "café", one network: "everything", the other an unknown restrictions.extra shape-unknown-field at .content.restrictions.extra; removing the extra key yields shape-enum-mismatch
positive-unicode-display-name-allowed (positive) displayName: "Café 😀" on agent-a allowed

Unit tests: "R5-1: the ordering form is total and injective…" (37 hand-built values covering every parser type, NFC/NFD and case-distinct strings, NBSP/U+2028, escapes, nested arrays/objects, Unicode keys and values: 37 distinct forms; 12 of them (test bound ≥ 12) are refused by canonicalize() with CanonicalError, proving the domains differ; key order independent of insertion order and by code unit; array order semantic; string form equals JSON.stringify; stableOrder on the unkeyed and equal-key Unicode pairs in both input orders for all four families; only a structuredClone duplicate falls back to the index), "R5-1: unkeyed Unicode entries…" (per family: permutation-stable shape-type-mismatch with a null locator key, the two fixtures are exact reversals, and the located entry is the string in both), "R5-1: equal-key Unicode entries…" (permutation-stable shape-unknown-field with the asserted agent-a key and .payload.extra / .content.restrictions.extra tails, the sibling enum error is reachable, the Unicode positive stays allowed). The existing stableOrder unit test keeps its r5 expectations ("not-an-entry" still precedes {registry: 7}).

Filbert's own witness files (/tmp/fi-filbert-7.239u2bco-evidence/witnesses/, read only) through the r6 inspector: all six Unicode pairs now report the same rule in both orders (shape-type-mismatch for the four unkeyed pairs, shape-unknown-field for the two equal-key pairs).

Mutation witness (scratchpad copy only, discarded): with the r5 tie-break (canonicalize + CanonicalError → "") restored in the r6 tree, the three R5-1 unit tests and the "evaluate over every bundle fixture" index check fail (the 13 new index.json expectations flip), while all 34 other resolve tests pass; the r6 regressions therefore detect the r5 behaviour.

3. Disclosures for the reviewer

  • (a) Index fallback. a.i - b.i remains as the final comparator and now decides only between byte-identical entries (equal forms ⇒ equal values). The r5 §3(a) wording was wrong for non-ASCII entries; the corrected claim is asserted by the injectivity test and the equal-key fixtures.
  • (b) Ordering form vs. canonical digest. Two different serializations now exist in resolve.mjs/canonical.mjs on purpose: the digest form is the frozen mock domain (ASCII, CanonicalError for everything else, unchanged); the ordering form is total. They agree on the shared ASCII domain up to representation but are never compared with each other. The ordering form is never written to output.
  • (c) Cost. The form is computed at most once per entry per stableOrder call (memoized) and only for ties; inputs are already bounded by the strict parser (MAX_ITEMS/MAX_DEPTH/MAX_STRING_BYTES), so no new resource bound is introduced.
  • (d) registry-declaration-missing semantics: unchanged; the FI-FILBERT-7 verdict confirms them as consistent.
  • (e) Permutation family for large inventories remains the fixed deterministic subset (r5 §3(d)); the new unkeyed pairs have two entries (all permutations); the equal-key records pair sits in the 36-record inventory (35 base records plus the copy) (deterministic family).
  • (f) r3 live-branch correction (r4 §3(g)) remains in force and unrelabeled (§1, §7).

4. Changed files (r5 → r6 sha256; everything else byte-identical, 13 fixtures added, 0 removed)

File r6 sha256 Change
scripts/foundation/resolve.mjs c4951014828080caf9a5038845104afdac02465af10e94a2fddd3558b29621a3 orderingForm; stableOrder tie-break (§2.2)
scripts/foundation/resolve.test.mjs dca397f268cfd39feffae3839f57afc61fe74ca324645dd1d916219f38d4b15b three R5-1 tests; orderingForm/canonicalize imports; two comment/title words
scripts/foundation/fixtures/build-fixtures.mjs ea5a54028b75cc6f3fbce1666f6fad04b3df774a137b0fd281da0a1abb0d0cab 13 FI-ROCKO-8 cases (§2.3)
scripts/foundation/fixtures/index.json e5b2cf8d6ad0bab6aa232edf80492ca58dae260940f52cf87e34525b166b459a 347 cases (was 334)
scripts/foundation/fixtures/README.md c69c64a15b0a20105c46d6b8e04e32a80d8c9b025f9f47aa9e18679a69da8ed2 tie-break wording; FI-ROCKO-8 paragraph
scripts/foundation/fixtures/bundles/* 13 added (manifest) none modified

Group counts (index.json): admission 62, continuity 2, cycle 6, identity 7, lexical 19, operation 7, ownership 10, positive 22 (+1), profile 47, proposal 48, registration-delegation 13, shape 97 (+12), structure 7.

5. Receipts (run 2026-09-07 15:2x UTC at HEAD 9a5fbdbd; logs kept in the session scratchpad)

Check Result
NO_COLOR=1 PYTHONDONTWRITEBYTECODE=1 scripts/test-foundation.sh 43 passed, 0 failed
node --test scripts/foundation/ 80 tests, 80 pass, 0 fail (was 77)
python3 scripts/foundation/verify-schema.py (jsonschema 4.26.0, pinned hashes) PASS; corpus 1568 records; schema column agree-valid 540, agree-invalid 991, DISAGREEMENTS 0; profile refusals asserted 30
python3 docs/plans/foundation-v1-candidate/check.py PASS
scripts/test-config.sh / test-conductor.sh / test-auth.sh 24/0, 17/0, 15/0
scripts/test-task.sh, scripts/test-release.sh NOT RUN / DEFERRED (owner decision, §7)
every new fixture through the CLI (env -i, PATH=/nonexistent, HOME=/nonexistent) 13/13 match index.json; every pre-existing case unchanged; fresh generation equals the checked-in tree
filbert's six Unicode witness pairs through the r6 CLI same rule in both orders for all six (§2.3)
git HEAD unchanged, nothing staged, no candidate file tracked; no __pycache__ created

6. Constraints honoured

No edit to charter, addendum, verdicts, corrections request, follow-up, decision doc, pinned schema/checker/fixtures, canonical.mjs, prior reports or manifests, CURRENT, shared logs, root/package files, policies or runtime data. No blanket Unicode rejection. No new dependency. No staging, commit, checkout/reset or push (Dewey's index untouched). No live, native/sync, credential or ~/.mosaic work; no network; no engine or container launch. Tests spawn the inspector; the inspector spawns nothing. Finite tests and static flags are evidence, not sandbox proof. No fake full-suite pass, no new waiver.

7. Deferred coverage (owner decision, unchanged)

Per 2026-09-07_foundation-inspector-demo-test-gate-decision.md (Jason, 2026-09-07 14:47 UTC): scripts/test-task.sh and scripts/test-release.sh are deferred for this offline inspector demo and are reported NOT RUN / DEFERRED, never passed or fully covered; no live model calls, deployment actions, weaker inspector behaviour or permanent removal of tests are authorized. Still required and not rocko's to grant: filbert's independent code approval and Jason's separate synthetic demo acceptance. Full deployment/integration testing remains a later gate. The r3 execution of the live branches (r4 §3(g)) is neither authorized nor relabeled.

8. Freeze

Frozen at the r6 manifest (docs/plans/reviews/2026-09-07_foundation-inspector-rocko-build-manifest-r6.json, sha256 a4a4493000aff5905337a643886ca36e7c5377d52deed77b8aeab7174ca73dcf; 382 files: source 6, tests 5, suite 1, fixtures-meta 3, bundles 328, raw 19, demo 20; aggregate scripts/foundation/ 380 files e9d406b3…3dff) for filbert's independent re-review. Rocko changes nothing under the allowlist until darkwing returns reconciled findings. C1 (Archify rev 3) remains HELD; no lane action taken.