# Foundation inspector — rocko build report r2 (FI-ROCKO-4 dispositions) Author: rocko (Claude Fable 5.1). Coordinator: darkwing. Task: FI-ROCKO-4. Written 2026-09-06 18:35 UTC. Supersedes nothing: the original report `docs/plans/reviews/2026-09-06_foundation-inspector-rocko-build.md` is preserved unedited (sha256 `857470d97af6aeae4e7d7942c2a1d8455bfcda74ff68b8e1dc4ba8d066521fa4`, unchanged since delivery); this revision records the admission findings, what changed, and the new frozen candidate. Inputs bound to this revision: | Input | sha256 | |---|---| | Admission findings `2026-09-06_foundation-inspector-build-admission-findings.md` (as received, 18:04Z) | `59233c6126c2355df5a367f0e61df7798e6cccfaa682454992fcd0d0df39c82c` | | Same file now (darkwing's "Transport (2026-09-06 18:17 UTC)" footnote appended; no other change) | `8d9f7aab3b3bdf79345a07ba3595b966982b7667714ac7e3f669d65e328ca3d5` | | Charter candidate 3 `docs/plans/2026-09-06_foundation-inspector-charter.md` | `19b6721128a627a2032ffdb95ece2d50abe69a8f6d521e9eff8bbdaff22798b6` | | `records.schema.json` / `check.py` / `records.fixtures.json` | `05774aaf…72af6` / `82564a7d…4607` / `d433d06d…da85` (full values re-verified by the oracle gate 1 and listed in the manifest) | | Build request `2026-09-06_foundation-inspector-rocko-build-request.md` (now, transport footnote included) | `90a1f329e859152d4ce8e157cbd6c56e6bcced16b4ed757822d2309514fa9a15` | | Integration HEAD (unchanged, nothing staged) | `9a5fbdbda74b16adf488fe28138b2ba69ea5e669` | | Machine-generated manifest `2026-09-06_foundation-inspector-rocko-build-manifest-r2.json` | `2e8b5f1998a3be6cc7fcc3fba308724d2a29ca166c08f005e1bca88ddbc14196` | Toolchain measured: Node v26.8.1, CPython 3.12.8, jsonschema 4.26.0, Linux 7.2.2-1-cachyos x86_64, glibc 2.44 (`strftime("%Y")` for year 999 → `999`). ## 1. Per-finding dispositions ### C1 — demo hash mismatch: transcription error, no drift Disposition: **typo in the original report, not evidence drift.** Corrected; no evidence was regenerated to make anything match. - Advertised (report line 96): `…548fc023…`; measured then and now: `f1c6bd523cdff161e51da156fd00cfaa652d193f41541686548cf023378fd8ab`. The only difference is the transposition `fc`→`cf` at positions 51–52; the other 62 hex characters are identical. - Evidence the file never changed: my scratch hash listing taken at 18:03:52 UTC (before the report was written) already contains the measured value for `demo/prop-message-is-not-authority.json`; the file's mtime is 17:52:58 UTC, earlier than that listing; the demo copy is byte-identical to `bundles/prop-message-is-not-authority.json` (same hash, 49,975 bytes; asserted by `fixtures.test.mjs` "demo copy drifted"); and both aggregate manifests reproduce the original report's values with the original method: bundles 185 files 9,235,245 bytes `a804e4ab253839ba2bb8ad7475d27512b2ef88e70a9779f9093fee5478de4823`, raw 19 files 1,336,985 bytes `508ad85208f4b69676f3d041faa4ff4cd4a2f8de74b7bde9b0b89123a8485cc4`. - Root cause: the original §3 tables were hand-transcribed. The r2 manifest is written by a script (`manifest-r2.json`: path, mode, size, sha256 for all 239 candidate files, aggregates, HEAD, toolchain, pinned inputs). No hash in this report or in the manifest is typed by hand except where a value is quoted from the original report to show the error. ### C2 — oracle divergences were waived: waiver removed, one class remains as a spec blocker Disposition: **fixed for the calendar class; precise blocker for the pattern class.** The oracle no longer has a `KNOWN_DIVERGENCES` list; every schema-column disagreement is a problem and the oracle FAILS. Calendar class (2 cases, fixed): the pinned checker refuses years 0001–0999 on this platform because `strftime("%Y")` is unpadded below 1000, so its strptime/strftime round-trip fails. The inspector now refuses them identically (`isValidTime`: year < 1000 refused; charter §8 "calendar semantics from the pinned checker"). Witness: `python3 -c 'from datetime import datetime; print(datetime(999,1,1).strftime("%Y"))'` → `999`. The oracle asserts this witness as gate 3 before comparing and fails with an explicit "platform boundary" message on a platform that pads. Cases `calendar-year-0001`, `calendar-year-0999`, `calendar-year-0100` now agree (both refuse). `resolve.test.mjs` adds the three boundary vectors. Pattern class (4 cases, blocker, unchanged verdicts): the pinned checker evaluates `pattern` through Python `re.search`, whose `$` also matches immediately before a trailing `\n`; ECMAScript `$` does not. Consequently the pinned checker ACCEPTS `id`, `scope.projectId`, `authorizationRef` (runtimeId pattern) and `policyRef.digest` values that carry a trailing newline, and the inspector REFUSES them (`shape-pattern-mismatch`). Witnesses: ``` python3 -c 'import re; print(bool(re.search(r"^[a-z0-9][a-z0-9._-]{0,63}$", "abc\n")))' # True node -e 'console.log(/^[a-z0-9][a-z0-9._-]{0,63}$/.test("abc\n"))' # false ``` Current oracle output (verbatim, exit 1): ``` platform witness: strftime('%Y') for year 999 -> '999' (pinned checker refuses years 0001..0999) node v26.8.1; corpus 1432 records (38 pinned fixtures, 410 unique bundle records, 984 typeCase/mutation/lexical cases) schema column: agree-valid 476, agree-invalid 915, DISAGREEMENTS 4; strict-only (parser-bound) cases: 27; unsupported-kind records not schema-assessed by the inspector: 10 profile column (schema-valid records only): profile-valid 476, profile-invalid 0 problem: DISAGREEMENT pattern-authorization-ref-trailing-newline: pinned-checker schema True / inspector schema False (inspector profile False, rule shape-pattern-mismatch) problem: DISAGREEMENT pattern-digest-trailing-newline: pinned-checker schema True / inspector schema False (inspector profile False, rule shape-pattern-mismatch) problem: DISAGREEMENT pattern-id-trailing-newline: pinned-checker schema True / inspector schema False (inspector profile False, rule shape-pattern-mismatch) problem: DISAGREEMENT pattern-scope-project-id-trailing-newline: pinned-checker schema True / inspector schema False (inspector profile False, rule shape-pattern-mismatch) FAIL: 4 differential problems ``` Why I did not resolve it myself: the findings forbid implementing a waiver or changing the pinned schema/checker, and the charter forbids weakening safe input handling. Making the inspector accept an identifier with an embedded newline would relax input handling merely to turn the count green (the newline would then be carried into `selection.projectId` and the text rendering, and a `scope.projectId` of `p1\n` would be a distinct identity from `p1` that renders almost identically). The schema-column comparison is therefore left honestly red, and the suite reports `selftest: 36 passed, 1 failed` until this is ruled. Proposed amendment (for darkwing/Jason; I implement nothing until ruled): - (a) **Recommended.** Add to charter §8 (or the incorporated notes) a "checker-dialect artifact" clause: "The pinned checker's `pattern` semantics are Python `re.search`; its `$` accepts one trailing newline. This is a dialect artifact, not an intended grammar. The inspector applies the pattern with anchored (ECMAScript) semantics and refuses a trailing newline. The differential oracle records the cases in this class by name, with the two witnesses above, and reports them as an expected artifact-class difference separately from agreement counts; any other disagreement fails." Under (a) the oracle would carry a named, witnessed artifact class with exact direction (pinned True / inspector False) and fail on any other direction or any other case. This is the amendment I would implement; it needs a coordinator ruling because it is a policy choice about which side is authoritative. - (b) Against: relax the inspector to Python semantics. Rejected for the input handling reason above and because §8 keeps "record-schema verdict distinct from … refusal"; the inspector would have to accept an identifier the runtime would never mint. - (c) Against: drop the four mutations from the corpus. Rejected: it hides a real, reproducible difference between the two implementations. Until ruled, the candidate's differential gate is **red by design**, and this report does not claim oracle PASS. ### C3 — `exit` outside the closed §7 fields: removed from the serialized result Disposition: **fixed; no charter amendment needed.** Exit status is process metadata derived from the emitted result (`exitFor(result)`), never a field of it and never a text line. - `resolve.mjs`: `RESULT_KEYS` is exactly the 12 charter §7 names in charter order; `buildResult` builds no `exit` member and throws if the requested exit is inconsistent with the derived result/reason; new export `exitFor(result)` (allowed→0; refused/unresolved→3; invalid+io-failure→4; other invalid→2). `evaluate()` returns `{ result, exit, detail }` with `exit` outside `result`. - `foundation-inspect.mjs`: `renderText` ends with the `diagnostic:` line (no `exit:` line); `run()` returns `{ json, result, exit }`; `main()` writes the rendering and sets `process.exitCode = exit`. - Tests whose expected keys come from the approved contract, not from the implementation: `resolve.test.mjs` "closed result fields equal the pinned charter §7 list" reads the charter file, asserts its sha256 is `19b67211…798b6`, parses the twelve `- name:` bullets under "Closed result fields, emitted on every outcome:", and asserts they equal both an independent literal copy in the test and the implementation's `RESULT_KEYS`; it also asserts `exit` is absent, checks `exitFor` on all four classes, and that `buildResult` throws for four exit/reason inconsistencies. `cli.test.mjs` and `fixtures.test.mjs` assert `!("exit" in parsed)` for every spawned run and derive the expected process status from the parsed result; text renderings are asserted to end with the diagnostic line and to contain no `exit` line for all 204 cases. `test-foundation.sh` asserts the `--json` key list equals the twelve names literally. - Consequence: **the ten demo goldens (`*.expected.txt`, `*.expected.json`) were regenerated** with the README loop because the contract fix removed one line / one member from each; the five `*.expected.exit` files are byte-identical to before (hashes `9a271f2a…86aa` for 0 and `1121cfcc…02a2` for 3, as in the original table). `index.json` is unchanged (its `expect.exit` was always the process status). Demo tree total bytes 249,096 → 248,991 (−105 = 5 × (8-byte text line + 13-byte JSON member)). ### C4 — demo step 2 could not show unchanged bytes: corrected Disposition: **fixed.** The BEFORE manifest is saved in a separate scratch location first, the preview runs, then `sha256sum -c` verifies against that SAME manifest (plus an inventory of path/type/size/mode/inode/mtime and a file count). Corrected commands are in §4; the receipt from running them is in §3. ### C5 — report omitted O_NONBLOCK: prose corrected, flag kept Disposition: **fixed in prose only; no code change.** The CLI opens the input with `O_RDONLY | O_NOFOLLOW | O_NONBLOCK` (`REQUIRED_FLAGS` = those three names) and refuses with `open-flags-unavailable` (exit 4) if any is not a number on the platform. O_CLOEXEC is not used (charter §6). The original report's lines 142 and 270 said `O_RDONLY|O_NOFOLLOW` only; that text was wrong and is superseded by this paragraph. The FIFO test (`cli.test.mjs` "FIFO must be refused after O_NONBLOCK open, not read") exercises the flag. ## 2. Changed files (old → new sha256; everything else byte-identical) All under the FI-ROCKO-3 allowlist plus the two paths offered in the findings. Nothing else in the working tree was touched; `git status` shows the same pre-existing modified/untracked entries as before plus the two r2 documents. | File | Lines | Original report sha256 | r2 sha256 | |---|---|---|---| | `scripts/foundation-inspect.mjs` | 239→244 | `bfa1b78007c772c54fe1dab90a53e19e6bce7e4888a5adb2de0b2cfe43c5cddd` | `dad369490ca8396d3b4a7bd4a1b7fa87b677a2b8cf991bf61672d446e5aa3c93` | | `scripts/foundation/resolve.mjs` | 1625→1646 | `9ae8b4255b5dc1f1e9b0d36549865268c12dc53f010984eb1e5616b127d1d8a2` | `aa842898ebb0bb2a911406314c4c3a2c4079b46bfd674c4ffba0176a1e32766c` | | `scripts/foundation/verify-schema.py` | 453→467 | `7e3098655b2e5f261391d788c2112a6ffb055895e357b427bc68e5f2fa7b52d7` | `b61031b6c2cea126133afda70894e7f2429b08bf2cd50fc766ec6b99f348c966` | | `scripts/foundation/resolve.test.mjs` | 294→333 | `05f27aa6c8a1b8ff2ee45e5de03a9aea5ed13cf011b2464c5865022dc9781449` | `6bb6d40fa6ec90fb52db658f77ca9fd27cc3eb8673f040f52fc126e67983a571` | | `scripts/foundation/cli.test.mjs` | 279→281 | `b7e9a13c11e3e6f487ada2779c892cdf848526de1ef8b21387d15d4a461ddb62` | `ccec06c266e03d878c1ecd9e8c20ee3f1fb82f478daead25efa4661b7090d83f` | | `scripts/foundation/fixtures.test.mjs` | 93→98 | `2827de36214c8014471fed3478c42bf48eddb3fbdbff82cac9e27851646b15d2` | `94e7508717efcafd11585a288a5d791e1e48a993794e2d470c6097f904befdb3` | | `scripts/test-foundation.sh` | 132 | `76fb7ba9da4345271b6ba93228a9da69c7e55c4e44b9f4033e84a8f4b430e8ec` | `fdfa13ce247bc9ad8f3617e248913aef384029198924685ada3431006ffdf114` | | `fixtures/demo/demo-read-w1.expected.txt` | | `8bfff519ce8275cfe59562efd04647c242c328a09b301b91d70491f030cf1600` | `8ba4bbe92819ac8912c72e7c635730e99eac7916c0d924fc81b75115b5939b53` | | `fixtures/demo/demo-read-w1.expected.json` | | `55e10c1693787cd8e39d3a66841d30e2336080d2ae4450a806a7ae8373db476b` | `42055bda71075ba5c710f011f83d470228f18097d93ac5585ba1b534cba0ef2a` | | `fixtures/demo/demo-file-change-src.expected.txt` | | `f5387217ca0a41d437468af33b7d87d06c0c5535ab5490b568f9866a13b2732e` | `2cdd6d4e162fa904b03e8f8a88553a03065ac7fb646baabf21625d4f5962a79b` | | `fixtures/demo/demo-file-change-src.expected.json` | | `88c7793c6af17ca255525451a499d9f8bb165155e50d2ee936bd68ba82a876a7` | `7ea412916404ec948c94e929cc7b2ce120b394944c66c7bfcc811d0721dd82db` | | `fixtures/demo/demo-change-w1.expected.txt` | | `2a1090e5f5ad08e2175830943a7c81928de970470893e803b1e440843152c348` | `0e2807ab56d15670c51c8ad4a43f33b36c5672799937561f39e20c6e93be1dda` | | `fixtures/demo/demo-change-w1.expected.json` | | `36c287cba327070f7ff3dd9846421bee3ebd0dd52e9c602b68e84fa620ef4fc5` | `ecf8313993ca27450d1753c6178be1a063fb0637f14be8f091de923b8b722516` | | `fixtures/demo/adm-registration-revoked.expected.txt` | | `624c9e5991597d59fc3379343b16e7eab9cb6434f054da3a4df7518742e3b36c` | `c2fd118d44d21cbd709d30a2c2be2603740a81e628aa9d32f95d6eaf06d3a9a6` | | `fixtures/demo/adm-registration-revoked.expected.json` | | `b966d939e0d2851ef731462a0fda932e75f53b9aac406216ad440f781e036d0f` | `c44b0935ed329c1a381f986997b1bc4be702fc4229166d8a0b49e4e80f9b2fa8` | | `fixtures/demo/prop-message-is-not-authority.expected.txt` | | `e7b0e9fa64abe0c0f8c91fbc9aa565988ce76cb3967672bfacafab350e20d8be` | `388983ad6e4e31db6f5a688773ff2bf07d44d7ffe530eb7870e729a0f546278d` | | `fixtures/demo/prop-message-is-not-authority.expected.json` | | `53884bfdea21185141b9a06d52aa1c0fe8e7bfae6627b465929403521f9bc4d8` | `742e479ddbb4cf2138d49ec0ac40cd038b6c20f8b9c0e2daffc5a859db9f4145` | | `fixtures/demo/prop-message-is-not-authority.json` | | (report typo `…548fc023…`) | `f1c6bd523cdff161e51da156fd00cfaa652d193f41541686548cf023378fd8ab` (file unchanged) | Unchanged (same hash as the original table): `strict-json.mjs`, `canonical.mjs`, `validate-record.mjs`, `strict-json.test.mjs`, `canonical.test.mjs`, `build-fixtures.mjs`, `index.json`, `fixtures/README.md`, all five `*.expected.exit`, all five demo `*.json` bundles, all 185 bundles and 19 raw fixtures (aggregates above). Full per-file listing with modes: `manifest-r2.json`. Aggregates (r2 manifest method: sha256 of the LC_ALL=C sorted `sha256sum` listing with repo-relative paths): `scripts/foundation/fixtures/` 227 files `36d99b91065ed7af03f17b7b9cc309b3f7805ca5e3a337ddb5f3536f2ccaa563`; `scripts/foundation/` 237 files `6c111ad1165e0da219d566f919e25005af47fa004257a11e7e2750a4010b9ecb`; all 239 candidate files `e0fe89d9cc5245031ce484e9e0fe15b93319dd2cdf040800c52555a26fb1c1a6`. Original-method demo aggregate (run in `scripts/foundation/fixtures/`, `sha256sum demo/* | sha256sum`): `7000fdd9b2a5e92256429e148240541fab3f93d2ef009e47b08baddcbcef5216`. Source changes in words: `resolve.mjs` (isValidTime year ≥ 1000; `exitFor`; `RESULT_KEYS` without exit; `buildResult` consistency guard; `evaluate` returns exit beside result). `foundation-inspect.mjs` (no exit line; `withExit`; exit via `process.exitCode` only). `verify-schema.py` (waiver list and waiver comparison deleted; gate 3 platform witness; trailing-newline cases renamed `pattern-*-trailing-newline` and kept; calendar boundary cases; separate schema/profile column summary; every disagreement is a problem). `test-foundation.sh` (JSON key-list assertion; oracle summary grep lines). Tests as described under C3 and C2. No new dependency, no new file outside the allowlist, no `__pycache__` (verified absent after all runs). ## 3. Receipts (all run 2026-09-06 18:2x–18:30 UTC at HEAD 9a5fbdbd) | Check | Result | |---|---| | `node --check` on all 10 `.mjs` files; `ast.parse` on `verify-schema.py` | OK | | `node --test scripts/foundation/` | 58 tests, 58 pass, 0 fail (was 57; +1 charter-derived field test) | | `python3 scripts/foundation/verify-schema.py` | **FAIL, exit 1: exactly the 4 `pattern-*-trailing-newline` disagreements** (output quoted under C2); gates 1–3 PASS; 38 pinned fixtures agree | | `NO_COLOR=1 scripts/test-foundation.sh` | **`selftest: 36 passed, 1 failed`**; the one failure is the oracle gate above; all other checks OK (syntax, fixture regeneration equality, node tests, demo exits 0/0/3/3/3, usage 2, I/O 4 ×3, oversize 2, disclaimer, JSON key list, golden byte-match, sandboxed inventory unchanged, canaries never printed) | | `python3 docs/plans/foundation-v1-candidate/check.py` | exit 0 | | `scripts/test-config.sh` / `test-task.sh` / `test-release.sh` / `test-conductor.sh` / `test-auth.sh` | 24/0, 90/0, 14/0, 17/0, 15/0 passed/failed | | `git rev-parse HEAD`; `git status` | `9a5fbdbd…`; nothing staged; only the pre-existing entries plus the two r2 documents | | `scripts/foundation/__pycache__`, `docs/plans/foundation-v1-candidate/__pycache__` | absent | Owner-demo receipt (corrected C4 procedure, verbatim outcome lines): ``` step 1 demo-read-w1.json → result: allowed … operation: work.read target null rc=0 step 2 before manifest: 227 files, sha256 cf2eed27c5e5b311d40e5346af89fbc5fdc7781488bf8ea9d663e66e7dd50d59 demo-file-change-src.json → result: allowed … operation: file.change target workspace:src/main.mjs rc=0 sha256sum -c against saved BEFORE manifest: all 227 OK inventory (path/type/size/mode/inode/mtime) unchanged file count unchanged: no file added step 3 adm-workspace-registration-missing.json → refused / not-authorized / workspace-registration-missing, assignment null rc=3 step 4 demo-change-w1.json → unresolved / unknown-effects / runtime-reconciliation-required; selection keeps asg-a-w1 rev 1 rc=3 prop-all-pass-restricted-contexts.json → unresolved / unknown-effects / runtime-reconciliation-required rc=3 extra adm-registration-revoked → refused/registration-revoked rc=3; prop-message-is-not-authority → refused/message-is-not-authority rc=3 json keys = [disclaimer, preview, bundleVersion, authentication, declarations, result, reason, rule, selection, operation, proposal, diagnostic] rc=0 ``` Every text rendering begins `SYNTHETIC PREVIEW — NO LIVE EFFECTS` and ends with the `diagnostic:` line. These are bounded evidence (finite tests, inventory comparison), not an OS-sandbox proof; the original report's §8 qualifications stand. ## 4. Corrected owner-demo commands (A9; replaces original §9 step 2) ```sh # 1. selected agent/project/workspace with a permitted read preview (exit 0) node scripts/foundation-inspect.mjs scripts/foundation/fixtures/demo/demo-read-w1.json # 2. assigned file.change preview (exit 0); prove no fixture file changed: # save the BEFORE manifest elsewhere first, run, then verify against that SAME manifest BEFORE="$(mktemp)" ( cd scripts/foundation/fixtures && find . -type f | LC_ALL=C sort | xargs sha256sum ) > "$BEFORE" node scripts/foundation-inspect.mjs scripts/foundation/fixtures/demo/demo-file-change-src.json ( cd scripts/foundation/fixtures && sha256sum -c --quiet "$BEFORE" ) && echo "all $(wc -l < "$BEFORE") files unchanged" [ "$(cd scripts/foundation/fixtures && find . -type f | wc -l)" = "$(wc -l < "$BEFORE")" ] && echo "no file added" # 3. unregistered workspace selected: refusal without fallback (exit 3, assignment null) node scripts/foundation-inspect.mjs scripts/foundation/fixtures/bundles/adm-workspace-registration-missing.json # 4. cross-lane reassignment preview: original assignment stays selected; even fully # supplied mock declarations cannot yield allowed (exit 3, unresolved) node scripts/foundation-inspect.mjs scripts/foundation/fixtures/demo/demo-change-w1.json node scripts/foundation-inspect.mjs scripts/foundation/fixtures/bundles/prop-all-pass-restricted-contexts.json # machine-readable form; process exit status is the only exit signal node scripts/foundation-inspect.mjs --json scripts/foundation/fixtures/demo/demo-read-w1.json; echo "exit $?" ``` Step 5 (Jason's answer on clarity and acceptance) is darkwing's to record; this report claims no acceptance. ## 5. Open item and freeze Open: the C2 pattern-class ruling (§1 C2, options a/b/c). Until ruled the oracle and therefore `test-foundation.sh` are red on exactly those four named cases; I have not weakened, waived or removed them. Frozen at the r2 manifest (`manifest-r2.json`, sha256 `2e8b5f19…4196`; 239 files) for independent review. Rocko changes nothing under the allowlist until darkwing returns a ruling or reconciled findings. No staging, commit, checkout/reset, push, charter/schema/verdict/coordinator-record edit, live or native/sync work, credential or `~/.mosaic` read, network access or engine launch occurred. C1 (Archify rev 3) remains HELD; no lane action taken.