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

22 KiB
Raw Permalink Blame History

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 fccf at positions 5152; 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 00010999 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:2x18: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 13 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)

# 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.