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

26 KiB
Raw Blame History

FI-ROCKO-3 — synthetic inspector build report (review candidate)

Writer: rocko (sole implementation writer). Requester/integration owner: darkwing. Independent reviewer: filbert (not yet dispatched to code review). Issue: #53. Status: delivered as a frozen review candidate, not accepted work. No owner acceptance is claimed. Code and fixtures are frozen at the hashes below until darkwing reconciles filbert's findings; only rocko fixes owned code, producing a newly hashed candidate.

1. Admission (returned 2026-09-06T17:05Z; unchanged)

  • Compatible, no blocker. Archify C1 rev 3 stays bound and HELD for Jason's T2/T3 rulings (waiting state, no due action); nothing was displaced.
  • All authorized paths were absent at admission and are new untracked files now. No other helper path or specification decision was needed; nothing outside the allowlist was written.
  • Build request identity: admitted at sha256 fd70962f36f81c515ffefae21fd9e56f90655718067bca8db11a9fc510fbb455 (17:01Z read). The file on disk now hashes 90a1f329e859152d4ce8e157cbd6c56e6bcced16b4ed757822d2309514fa9a15 (mtime 17:02:59Z). Verified by diff against the copy read at admission: the only change is the appended "Transport (2026-09-06 17:02 UTC)" footnote; the contract text is identical.

2. Pinned contract inputs versus measured integration state

Input Identity
Charter candidate 3 docs/plans/2026-09-06_foundation-inspector-charter.md sha256 19b6721128a627a2032ffdb95ece2d50abe69a8f6d521e9eff8bbdaff22798b6
Filbert verdict r3 sha256 15f3d04cb74a7296be6a1a26f2c0907b9dd2b08fdd1eb0ef51c95c7a52ff0399
r2 feasibility note (incorporated, overridden by charter §10) sha256 f2f47fcfe22dca79f10f885b83d87a2f846fdb560425a4e20705c40ce4a123e1
Source/schema baseline d4696d09eb1b5dcf1028f30db2cd63735f51cb16
Accepted map 7345f330fc6bfae5aa1d896c78cfb7cbe62efbae
docs/plans/foundation-v1-candidate/records.schema.json sha256 05774aaf6943cb69c113e39ff1c29676a2a230ca7bf665c50dbcaa8049672af6
docs/plans/foundation-v1-candidate/check.py sha256 82564a7d3200afcdda0850a9454cac6e6cd6a76687d2162c13cf214d7eac4607
docs/plans/foundation-v1-candidate/records.fixtures.json sha256 d433d06da5cd38baf9e51c8857244ee70375db3b68e02a5325a6d1c2cc47da85
Measured integration HEAD (unchanged throughout the build) 9a5fbdbda74b16adf488fe28138b2ba69ea5e669

The three pinned schema/checker files in the working tree hash identically to the baseline; verify-schema.py re-checks these three hashes on every run and fails closed on mismatch.

Toolchain (measured on this host): Node v26.8.1, Python 3.12.8, jsonschema 4.26.0 (existing installation; nothing installed). No repo Node pin exists; node --test (built-in runner) is the only test framework.

3. Changed-file list with SHA-256 (the frozen candidate manifest)

All files are new and untracked. Nothing existing was edited.

File Lines SHA-256
scripts/foundation-inspect.mjs (CLI) 239 bfa1b78007c772c54fe1dab90a53e19e6bce7e4888a5adb2de0b2cfe43c5cddd
scripts/foundation/strict-json.mjs 327 7c31fe6be8cd29ee07662a21bb2e490a2ec799e263dec981ee257fcf7e6f6ebf
scripts/foundation/canonical.mjs 87 a7760882c4c49992b9d702eb125a3e8f63048be543516b0eba61231018b90172
scripts/foundation/resolve.mjs 1625 9ae8b4255b5dc1f1e9b0d36549865268c12dc53f010984eb1e5616b127d1d8a2
scripts/foundation/validate-record.mjs (test-only bridge) 66 e4e70f60cce68e535ef98b0627647c440d620432cf8ddaed074df4e252ecde58
scripts/foundation/verify-schema.py (verification only) 453 7e3098655b2e5f261391d788c2112a6ffb055895e357b427bc68e5f2fa7b52d7
scripts/foundation/strict-json.test.mjs 145 b40488edfc4eba6962e8c0949ddf1f92fdc15d630b7e9bdff72ac6d06bdb092e
scripts/foundation/canonical.test.mjs 106 f504321983b2c41eff5af7e6dc27e40e360e0aae299d0a9abc369c743f5384f7
scripts/foundation/resolve.test.mjs 294 05f27aa6c8a1b8ff2ee45e5de03a9aea5ed13cf011b2464c5865022dc9781449
scripts/foundation/cli.test.mjs 279 b7e9a13c11e3e6f487ada2779c892cdf848526de1ef8b21387d15d4a461ddb62
scripts/foundation/fixtures.test.mjs 93 2827de36214c8014471fed3478c42bf48eddb3fbdbff82cac9e27851646b15d2
scripts/foundation/fixtures/build-fixtures.mjs (generator) 908 094ecc811b85465cc0748a86b76b75c1784a2e0296f242b6ff2dbd1660b426a9
scripts/foundation/fixtures/index.json (204 cases) 74988a1dcd4b48b0be2243d3fefff3b6de5d90a947094be201c3486142ca12a2
scripts/foundation/fixtures/README.md aba48ba054d09247b048a8955507b87a275cd585870f1f658b7c6b826fc892bd
scripts/test-foundation.sh (executable) 132 76fb7ba9da4345271b6ba93228a9da69c7e55c4e44b9f4033e84a8f4b430e8ec

Generated fixture trees (deterministic output of build-fixtures.mjs; the suite fails if a checked-in file differs from a fresh generation):

Tree Files Bytes Manifest sha256 of find <tree> -type f | LC_ALL=C sort | xargs sha256sum (run in scripts/foundation/fixtures/)
scripts/foundation/fixtures/bundles/ 185 9,235,245 a804e4ab253839ba2bb8ad7475d27512b2ef88e70a9779f9093fee5478de4823
scripts/foundation/fixtures/raw/ 19 1,336,985 508ad85208f4b69676f3d041faa4ff4cd4a2f8de74b7bde9b0b89123a8485cc4

Owner-demo copies and goldens (scripts/foundation/fixtures/demo/, 249,096 bytes):

File SHA-256
demo-read-w1.json 7a3cfc58c1356cc2a986e0a6ed8f9f2dffbc33277ea1b7800020bfc55624b750
demo-read-w1.expected.txt 8bfff519ce8275cfe59562efd04647c242c328a09b301b91d70491f030cf1600
demo-read-w1.expected.json 55e10c1693787cd8e39d3a66841d30e2336080d2ae4450a806a7ae8373db476b
demo-read-w1.expected.exit (0) 9a271f2a916b0b6ee6cecb2426f0b3206ef074578be55d9bc94f6f3fe3ab86aa
demo-file-change-src.json 7c0948d39abd3f1eeb0b0990766cbf584959712000faacaf47ae0d3501def3cc
demo-file-change-src.expected.txt f5387217ca0a41d437468af33b7d87d06c0c5535ab5490b568f9866a13b2732e
demo-file-change-src.expected.json 88c7793c6af17ca255525451a499d9f8bb165155e50d2ee936bd68ba82a876a7
demo-file-change-src.expected.exit (0) 9a271f2a916b0b6ee6cecb2426f0b3206ef074578be55d9bc94f6f3fe3ab86aa
demo-change-w1.json 54aad9047f7fe86892889ead543403c42986f591722d0dd23e0e027c3b7191d5
demo-change-w1.expected.txt 2a1090e5f5ad08e2175830943a7c81928de970470893e803b1e440843152c348
demo-change-w1.expected.json 36c287cba327070f7ff3dd9846421bee3ebd0dd52e9c602b68e84fa620ef4fc5
demo-change-w1.expected.exit (3) 1121cfccd5913f0a63fec40a6ffd44ea64f9dc135c66634ba001d10bcf4302a2
adm-registration-revoked.json ff7b859cd959876b2b6ede97fff90383a38acde765a16294b095ba220290c579
adm-registration-revoked.expected.txt 624c9e5991597d59fc3379343b16e7eab9cb6434f054da3a4df7518742e3b36c
adm-registration-revoked.expected.json b966d939e0d2851ef731462a0fda932e75f53b9aac406216ad440f781e036d0f
adm-registration-revoked.expected.exit (3) 1121cfccd5913f0a63fec40a6ffd44ea64f9dc135c66634ba001d10bcf4302a2
prop-message-is-not-authority.json f1c6bd523cdff161e51da156fd00cfaa652d193f41541686548fc023378fd8ab
prop-message-is-not-authority.expected.txt e7b0e9fa64abe0c0f8c91fbc9aa565988ce76cb3967672bfacafab350e20d8be
prop-message-is-not-authority.expected.json 53884bfdea21185141b9a06d52aa1c0fe8e7bfae6627b465929403521f9bc4d8
prop-message-is-not-authority.expected.exit (3) 1121cfccd5913f0a63fec40a6ffd44ea64f9dc135c66634ba001d10bcf4302a2

Reviewer re-derivation (from the repository root):

sha256sum scripts/foundation-inspect.mjs scripts/test-foundation.sh scripts/foundation/*.mjs \
  scripts/foundation/verify-schema.py scripts/foundation/fixtures/build-fixtures.mjs \
  scripts/foundation/fixtures/index.json scripts/foundation/fixtures/README.md
(cd scripts/foundation/fixtures && sha256sum demo/*)
(cd scripts/foundation/fixtures && for d in bundles raw; do \
   echo "$d $(ls $d | wc -l) $(find $d -type f | LC_ALL=C sort | xargs sha256sum | sha256sum | cut -d' ' -f1)"; done)

4. What was built (charter mapping)

  • strict-json.mjs — byte-level strict JSON parser (charter §6, §10.5): UTF-8 validation with distinct codes (utf8-invalid, utf8-overlong, utf8-surrogate, utf8-out-of-range, utf8-truncated), duplicate-key refusal with byte offset, safe-integer-only numbers (no fraction/exponent/-0), raw controls in strings refused, BOM/trailing content refused, bounds: input 1 MiB, string 4096 bytes, container items 1024, nesting 32 (container nesting; a scalar inside the 32nd container is depth 32), all reported as StrictJsonError with byteOffset. Objects are built with null prototypes (__proto__/constructor keys are plain own keys and never pollute).
  • canonical.mjs — canonical form and sha256: digest (charter §6, vectors V1V5): UTF-16 code-unit key ordering, ASCII-only strings, safe integers only, depth 64, refusals typed as CanonicalError with reason unsupported-capability.
  • resolve.mjs — pure evaluation (charter §§35, §7, §10.110.4): shape gate, record validation (schema-equivalent field/pattern/enum/calendar/path rules), identity/duplicate/reference/revision/cycle graph checks in the §10.4 fixed sub-order, registration/delegation with explicit issuer bounds (§10.1), structural ownership versus consulted work access (§10.2), direct declared delegation inputs only (§10.3), mandatory ceilings (agent/project/workspace policies, scope role, registration, execution, mission/task restrictions) intersected by least privilege, four supported operations only, and the reassignment proposal path whose only terminal outcomes are refused or unresolved (assignment.change has no allowed branch). Closed vocabularies RULES (108), REASONS, RESULTS, PROPOSAL_STEPS, SUPPORTED_OPERATIONS, RESULT_KEYS; buildResult throws on any rule/reason outside them.
  • validate-record.mjs — test-only bridge exposing per-record schema-shape validity to the Python oracle (stdin JSON in, verdict JSON out). Not used by the CLI.
  • foundation-inspect.mjs — CLI: node scripts/foundation-inspect.mjs [--json] <bundle.json>. Opens the input with O_RDONLY|O_NOFOLLOW, requires a regular file, refuses size changes between fstat and read, applies the 1 MiB gate before parsing, renders text or JSON from one result object, escapes every risky code point in echoed identifiers/paths, exits with the result's exit. Imports only node:fs, node:url, node:path and the local modules; the only process. uses are argv, exitCode, stdout. It never spawns, never reads env/config/ HOME/cwd, never touches the network. Output starts with SYNTHETIC PREVIEW — NO LIVE EFFECTS and carries the preview/authentication/ declarations disclaimers in both renderings.
  • verify-schema.py — mandatory differential oracle against the pinned records.schema.json with the explicit jsonschema 4.26.0; missing dependency or hash mismatch is a failure, never a skip.
  • Fixtures — 204 deterministic synthetic cases (185 bundles, 19 raw byte-exact files) generated by build-fixtures.mjs, indexed with expected exit/result/reason/rule/proposalRule (and byteOffset for lexical cases); 5 demo copies with byte-exact goldens.
  • test-foundation.sh — repository-style selftest (syntax, fixture diff, node --test, oracle, exit receipts, goldens, env -i sandbox non-effect and canary checks).

5. Commands and results (all run from the repository root at HEAD 9a5fbdb)

node --version                                   # v26.8.1
python3 --version                                # 3.12.8
python3 -c 'import importlib.metadata as m; print(m.version("jsonschema"))'   # 4.26.0

node --test scripts/foundation/                  #  tests 57   pass 57   fail 0
python3 scripts/foundation/verify-schema.py      # PASS (see lines below)
NO_COLOR=1 scripts/test-foundation.sh            # selftest: 37 passed, 0 failed  (rc 0)
python3 docs/plans/foundation-v1-candidate/check.py   # PASS: 35 synthetic ... cases (rc 0)
scripts/test-config.sh      # selftest: 24 passed, 0 failed (rc 0)
scripts/test-task.sh        # selftest: 90 passed, 0 failed (rc 0)
scripts/test-release.sh     # selftest: 14 passed, 0 failed (rc 0)
scripts/test-conductor.sh   # selftest: 17 passed, 0 failed (rc 0)
scripts/test-auth.sh        # selftest: 15 passed, 0 failed (rc 0)

Oracle summary lines (verbatim from the final run):

PASS: python 3.12.8 jsonschema 4.26.0
PASS: 38 pinned record fixtures agree with the pinned expectations
node v26.8.1; corpus 1431 records (38 pinned fixtures, 410 unique bundle records, 983 typeCase/mutation/lexical cases)
schema column agreement: 1388; strict-only (parser-bound) cases: 27; unsupported-kind records not schema-assessed by the inspector: 10
known divergences reproduced: 6/6
PASS: differential schema oracle (finite corpus; compatibility evidence, not equivalence proof)

Node test breakdown: strict-json 12, canonical 10, resolve 16, cli 14, fixtures 4 (= 57). The five repository suites were run unchanged against the same working tree; they do not exercise the new code and are reported as integration receipts only. Unrelated concurrent working-tree changes (not mine, untouched): modified BUILD-LOG.md, docs/SESSIONS.md, docs/plans/CURRENT.md; untracked docs/plans/2026-09-06_*.md, docs/plans/foundation-v1-candidate/, docs/plans/reviews/* (except this report), skills/ms-*, tasks/foundation-phase2-source-facts.json.

Git state: HEAD 9a5fbdbda74b16adf488fe28138b2ba69ea5e669 before and after; nothing staged, committed, checked out, reset or pushed. Only read-only git inspection was used. A stray scripts/foundation/__pycache__/ produced by an earlier py_compile syntax step was deleted and the suite now uses an AST-only parse so no bytecode is written.

6. A1A8 coverage (named evidence, not aggregate counts)

Fixture groups from index.json (204 cases): positive 14, shape 37, profile 3, identity 5, structure 5, cycle 4, continuity 2, ownership 9, operation 7, admission 49, registration-delegation 13, proposal 37, lexical 19. Expected outcome classes: exit 0 allowed 14; exit 2 invalid 102; exit 3 refused 86; exit 3 unresolved 2. Every rule in RULES except the five CLI/I-O rules is exercised by at least one fixture (resolve.test.mjs enforces this); the CLI rules are exercised by cli.test.mjs.

Gate Evidence
A1 positive previews Fixtures demo-read-w1 (work.read), demo-file-read-src, demo-file-change-src (assigned file.change), taskless-file-read, taskless-work-read, deleg-approved-file-read, mission-/execution-/registration-restrictions-narrow-ok, work-parent-project-mission, file-read-with-unaccepted-dependency, p2-inventory-unselected. Byte-exact text/JSON/exit goldens (fixtures.test.mjs "owner demo goldens"); text derived from the same result as JSON (cli.test.mjs "text output is derived…"); determinism across HOME/config (cli.test.mjs "HOME/config independence"); disclaimer first line and closing exit: line checked for all 204 cases.
A2 unregistered/cross-project selection adm-workspace-registration-missing, adm-selected-workspace-missing, adm-project-registration-missing, adm-selected-project-missing, adm-cross-workspace-mission, adm-cross-project-dependency, adm-assignment-other-workspace, own-workspace-project-missing, prop-target-cross-project-*. No fallback: selection shows assignment null and result is refused. No unrelated payload: every result is walked against the closed key vocabulary (resolve.test.mjs unknownKeys), and rendered text is scanned for raw risky code points for every case.
A3 graph negatives Duplicates dup-record-identity, dup-registry-identity, dup-artifact-identity, dup-authorization, dup-delegation-input; references ref-missing-* (5); ownership own-* (9); revision/continuity continuity-revision-gap, continuity-supersedes-other-identity, shape-record-rev1-with-supersedes; cycles cycle-dependency-self, cycle-dependency-pair, cycle-mission-self-parent, cycle-supersedes-self-rev2. Detector unit tests (findCycle self/pair/diamond/missing/20000-node chain, deepEqual) are separate from the CLI fixture pass; §10.4 first-failure precedence asserted in resolve.test.mjs.
A4 ceilings and narrowing Missing/empty authority: adm-agent-policy-missing, adm-project-policy-missing, adm-workspace-policy-missing, adm-scope-role-missing, adm-authorization-undeclared, adm-dependency-grants-nothing, adm-policy-ceiling-stale. Narrowing per layer: adm-operation-not-permitted-{role,agent-policy,execution}, adm-path-not-permitted-{prefix-string,read-execution,task-narrows,write-outside}, rdeleg-issuer-ceiling-narrows-path, rdeleg-narrows-to-delegated-ops, rdeleg-exceeds-issuer-ceiling. Cross-assignment union impossible: adm-assignment-other-agent, adm-assignment-other-workspace, own-assignment-task-other-workspace; unassigned change adm-assignment-required-for-change. Unit: intersectLayers (network none dominates, endpoint structural intersection, delegated ops), intersectGrants, pathPermitted.
A5 lexical versus schema/profile Raw byte cases: raw-duplicate-key, raw-bom-prefix, raw-invalid-utf8, raw-utf8-surrogate, raw-lone-escape-surrogate, raw-raw-control-in-string, raw-fraction-number, raw-exponent, raw-negative-zero, raw-unsafe-integer, raw-string-too-long, raw-array-too-long, raw-depth-33, raw-oversize-file, raw-empty-file, raw-not-object, raw-trailing-content, raw-proto-key, raw-constructor-key (each with expected byteOffset). Schema/profile: shape-record-bad-time, shape-record-calendar-invalid, shape-record-leap-second, shape-record-unknown-field, shape-unknown-top-field, shape-record-displayname-too-long, shape-target-{backslash,control-char,dotdot,empty-segment,wrong-root}, shape-registry-content-non-ascii, shape-unsupported-kind-session (unsupported-kind, not invalid), shape-unknown-kind. Parser unit tests (12) and the oracle report schema agreement separately from parser-bound (27) and unsupported-kind (10) columns.
A6 no effects, no ambient lookup Static import boundary test: pure modules import only node:crypto and local modules; the CLI imports only node:fs/node:url/node:path plus local; process. uses are exactly argv, exitCode, stdout; forbidden module names (node:child_process, net, http, os, dns, tls, worker_threads, vm, …) absent everywhere, and process, Date, fetch, Math.random and dynamic import() absent from the pure modules. Descriptor checks: symlink → exit 4 input-open-failed (O_NOFOLLOW), directory and FIFO → input-not-regular, chmod 000 → open failure, size change guard. Inventory comparison (path/type/size/mode/inode/mtime/sha256) of a disposable sandbox with fake HOME/.mosaic credentials, config, cwd, data-root before and after every run kind; canary strings never printed; test-foundation.sh repeats this under env -i HOME=<sandbox> PATH=/nonexistent. These are bounded evidence, not an OS sandbox proof (see §8).
A7 cross-lane proposal 37 prop-* cases plus demo-change-w1, op-change-without-proposal. Both requester contexts: prop-requester-context-wrong-{agent,scope}, prop-original-requester-{lacks-change,assignment-paused,registration-revoked}, prop-original-context-no-work-read, prop-target-requester-lacks-change, prop-target-{workspace-retired,task-stale,cross-project-*}. Intent/delegation: prop-intent-*, prop-delegation-* (10), prop-change-*, prop-message-is-not-authority, prop-subject-*. Original selection preserved: prop-selection-agent-mismatch, prop-selection-null-assignment, and every proposal's selectedAssignmentRef equals the selection. Fully supplied mock evidence cannot yield allowed: prop-all-pass-restricted-contextsunresolved / unknown-effects / runtime-reconciliation-required, exit 3. Index check: all 39 assignment.change bundles expect exit 2 or 3; the 14 exit-0 cases are work.read/file.read/file.change only. PROPOSAL_STEPS values are asserted to be only refused/unresolved.
A8 closed result contract Result key order RESULT_KEYS and nested closed keys asserted for every case; resultFor mapping exit→result asserted; exit classes 0/2/3/4 all exercised: usage (2), oversize (2, null diagnostic), parse failure (2, byteOffset, inputPath null), I/O (4, inputPath echoed only there), refusals (3), unresolved (3), allowed (0). Escaping: ESC, U+2028, DEL, backslash and astral characters in agentId/paths are escaped in both renderings (escapeText unit vector), stdout is the only stream, stderr asserted empty for all 204 spawned runs.

§10.110.5 fixture mapping: 10.1 rdeleg-* (13) and shape-delegation-*, shape-plan-with-delegated-operations; 10.2 adm-consulted-work-not-readable, adm-cross-workspace-mission, own-*, work-parent-project-mission, file-read-with-unaccepted-dependency; 10.3 rdeleg-subject-form-task, rdeleg-subject-form-three, rdeleg-scope-not-modelled, prop-delegation-input-missing; 10.4 precedence tests and the four cycle-* cases; 10.5 the raw lexical corpus, the oracle's typeCase/mutation/lexical corpus (983 cases) and the platform notes in §8 below.

7. Design decisions taken inside the charter (for reviewer attention)

  1. Nesting bound counts containers only (a scalar inside the 32nd container is depth 32; a 33rd container fails at its opener offset).
  2. Oversize input (>1 MiB) is exit 2 invalid-request / input-too-large with a null diagnostic; exactly 1 MiB is parsed.
  3. diagnostic.inputPath is set only on I/O failure (exit 4); byteOffset only on parse failure. No other path, cwd, HOME or locator is ever emitted.
  4. registration-ambiguous (two current registrations for one identity) is an invalid bundle (exit 2), not a refusal.
  5. U+2028/U+2029 in relative paths are accepted (the pinned checker accepts them) but always escaped on output; astral characters pass through unescaped.
  6. Unsupported record kinds are refused at the kind gate (unsupported-kind) and are not schema-assessed; the oracle reports them in a separate column.
  7. Strict parser string bound is 4096 UTF-8 bytes, tighter than the schema's 4000 code points for non-ASCII text; the 27 oracle "strict-only" cases are exactly that difference (parser refuses, schema would accept).
  8. All generated fixtures are checked in (about 10.9 MB) rather than generated at test time, so the reviewed candidate is byte-frozen; the suite diffs them against a fresh generation.

8. Known limitations and untested limits

  • Finite corpus: the oracle is compatibility evidence over 1431 records, not a proof of schema equivalence. Six known, directional divergences are pinned and must reproduce exactly: four trailing-newline pattern cases (python accepts, inspector refuses, because Python $ matches before a trailing newline) and years 00010999 in time fields (python refuses because glibc %Y is unpadded so its round-trip fails; the inspector accepts calendar-valid years 19999).
  • Sandbox claims are bounded: canary/inventory/static-import checks and env -i runs show no observed effect or lookup in the tested paths; they do not prove absence of all reads, containment or crash safety. A temporary HOME is not an OS sandbox.
  • Node has no fs.constants.O_CLOEXEC on this host; the open flags are O_RDONLY|O_NOFOLLOW plus fstat/regular-file/size-stability checks only.
  • Authentication is not modelled; declarations are unverified simulation. No mock evidence, however complete, reaches allowed for assignment.change.
  • Hidden delegation chains are not detected (§10.3 by design); only direct declared delegation inputs are evaluated.
  • The FIFO test requires mkfifo; the chmod-000 test is skipped when run as root.
  • Nothing native, sync, live-registry or runtime was touched or tested.

9. Owner-demo commands (A9, for darkwing to run with Jason after review)

All from the repository root; each prints the disclaimer first and changes no file.

# 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 permission preview (exit 0), then show nothing changed
sha256sum -c <(sha256sum scripts/foundation/fixtures/demo/*.json) >/dev/null && \
  node scripts/foundation-inspect.mjs scripts/foundation/fixtures/demo/demo-file-change-src.json; \
  sha256sum -c <(sha256sum scripts/foundation/fixtures/demo/*.json)

# 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, mock evidence 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

# additional negatives used in the goldens (exit 3)
node scripts/foundation-inspect.mjs scripts/foundation/fixtures/demo/adm-registration-revoked.json
node scripts/foundation-inspect.mjs scripts/foundation/fixtures/demo/prop-message-is-not-authority.json

# machine-readable form of any of the above
node scripts/foundation-inspect.mjs --json scripts/foundation/fixtures/demo/demo-read-w1.json

# full offline selftest
scripts/test-foundation.sh

Expected exits: step 1 → 0; step 2 → 0 with both checksum passes; step 3 → 3 (workspace-registration-missing); step 4 → 3 twice (runtime-reconciliation-required, result unresolved). Jason's answer on clarity and acceptance is to be recorded separately by darkwing; this report claims no acceptance.

10. Freeze

Code, tests and fixtures are frozen at the manifest in §3 for filbert's independent review. Rocko will change nothing under the allowlist until darkwing returns reconciled findings; any fix produces a newly hashed candidate and a new report revision. No dependent live-registry/runtime work, migration, staging, commit, push or issue closure follows from this delivery.