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

320 lines
23 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Foundation inspector — rocko build report r4 (FI-ROCKO-6: corrections for FI-FILBERT-6 findings F1F6)
Author: rocko (Claude Fable 5.1). Coordinator: darkwing. Reviewer: filbert. Task: FI-ROCKO-6.
Written 2026-09-07 14:40 UTC. Supersedes nothing: the original report, r2 and r3
reports/manifests, the charter, the addendum and both verdicts are preserved unedited.
This revision records the corrections to filbert's NOT APPROVED code verdict
(FI-FILBERT-6), what changed r3 → r4, the new witness receipts, and the frozen
candidate for independent re-review.
## 0. Admission, compatibility and blockers
FI-ROCKO-6 is **admitted and compatible** with the standing chain, the FI-ROCKO-3
build request and the corrections request; the code/test work is complete under the
original allowlist plus the two new r4 receipt paths. C1 (Archify rev 3) remains HELD
with no lane action.
**One test-authorization blocker is reported, not worked around (details in §7):**
`scripts/test-task.sh` and `scripts/test-release.sh` were **not run** in r4. In this
session `docker info` succeeds, so both suites would execute their Docker branches,
and `test-task.sh` would additionally execute its live branch (real `run-task.sh`
engine launches with the operator's configuration and credentials). The corrections
request forbids silently running legacy live branches and forbids substituting skips or
stubs to claim the gate green. Darkwing owns arranging that isolated integration gate.
The three suites without Docker/live/credential branches (`test-config.sh`,
`test-conductor.sh`, `test-auth.sh`) were run and pass (§6).
Inputs bound to this revision:
| Input | sha256 |
|---|---|
| Charter candidate 3 `docs/plans/2026-09-06_foundation-inspector-charter.md` (unchanged) | `19b6721128a627a2032ffdb95ece2d50abe69a8f6d521e9eff8bbdaff22798b6` |
| Addendum FI-C2-1 (unchanged) | `afe2980be2f91e701dae5af3018831ac5c300474f52bcc06e740ce5b5cc68ca5` |
| Filbert code verdict FI-FILBERT-6 `2026-09-06_foundation-inspector-code-verdict.md` | `e4cc5970aab20240e6ea3a9a1513ef942ff167fefbb03356b9a32c89999adb3d` |
| Corrections request `2026-09-07_foundation-inspector-corrections-request.md` | `35c237f5c001832a0cfed23b03ff7530ffa78443612ffe11130fe456b968bd61` |
| `records.schema.json` / `check.py` / `records.fixtures.json` (pinned, unchanged) | `05774aaf…72af6` / `82564a7d…4607` / `d433d06d…da85` (full values in the manifest; re-verified by oracle gate 1) |
| r3 report / r3 manifest (preserved) | `ec0444c94c9caf16472fdba1ff3fbc767dffd1a798b9a2da837598c9f4b25508` / `3c2253b6c9f31e448c77aaac53977d41baecfb30e15845dcfffd8dc694428547` |
| Integration HEAD (unchanged, nothing staged) | `9a5fbdbda74b16adf488fe28138b2ba69ea5e669` |
| Machine-generated manifest `2026-09-07_foundation-inspector-rocko-build-manifest-r4.json` | `2a2e885f9df3dd626a09fe5db6e28af67c289e54660872031c18f8aa40cb0826` (331 files) |
Toolchain measured: Node v26.8.1, CPython 3.12.8, jsonschema 4.26.0, Linux
7.2.2-1-cachyos x86_64.
## 1. Evidence preservation
The declared scratch evidence still exists at
`/tmp/fi-filbert-6.42ru903e-evidence/witnesses/`. All nine witness bundles named in
the verdict were re-hashed before use and match the verdict's listed values:
`missing-agent-registry-declarations` 78efd6fb…, `consulted-dependency-undeclared-authorization` cc78be28…,
`assigned-change-without-work-read` 35ee45e6…, `proposal-task-only-contexts-without-work-read` e1a9cd88…,
`proposal-issuer-denies-work-read` 34d1ed80…, `acyclic-versioned-history-false-cycle` e3dd1cd3…,
`shape-order-forward` 689f9aa9…, `shape-order-reversed` d14fca5e…,
`message-priority-after-structural-validation` 9e2ec77b…. Each recipe was rebuilt from
the frozen demo bundles inside `build-fixtures.mjs`; four rebuilt fixtures are
byte-identical to the corresponding witness files
(`adm-assigned-task-without-work-read`, `cycle-acyclic-versioned-history-file-change`,
`prop-issuer-denies-work-read`, `prop-task-only-contexts-without-work-read`), the
other five are semantically equivalent reconstructions. No other reviewer/fleet files
were inspected. Prior r1r3 evidence, fixtures and goldens are preserved: all 259
previously indexed cases keep their expectations, the five demo bundles and their
goldens are byte-identical to r3.
## 2. Per-finding closure
Pre-fix outcomes below were measured on the r3 candidate against the witness files;
post-fix outcomes are the r4 receipts (§6, all with empty stderr).
### F1 — referenced declarations incompletely enforced → CLOSED
Root cause: `harnessRef`, `settingsRef`, `soulRef`, `instructionRefs`, `skillRefs`
and `endpointRefs` were shape-checked but never resolved against the declared
`registries`; consulted dependency/mission authorizations were used without a
declaration check.
Change (`resolve.mjs`): new `Model.requireRegistryDeclaration(ref, where)` resolves a
reference by **exact four-field match** (registry, id, revision, digest) against the
declared registries and refuses `3 / missing-state / registry-declaration-missing`
otherwise; new `Model.declaredLayer(restrictions, where)` resolves every
`endpointRefs[i]` before computing a layer, and every layer computation in the Model
(registry content, registration, delegation issuer ceiling, task, mission, execution,
proposal issuer) now goes through it. `computeContext` resolves the selected agent's
harness/settings/soul/instruction/skill references at the admission stage (after the
agent-status check, before the mandatory policy ceiling) and calls
`requireAuthorization` for each consulted dependency (mission authorization was already
checked). `evaluateProposal` step 5 additionally requires the **subject** task's
authorization (previously only the target task's).
Witnesses: `missing-agent-registry-declarations` exit 0 allowed → **3 refused /
missing-state / registry-declaration-missing**; `consulted-dependency-undeclared-authorization`
exit 0 allowed → **3 refused / missing-state / authorization-undeclared**.
Fixtures (14): nine absence/mismatch negatives (`adm-registry-declaration-missing-agent-refs`,
`-missing-settings`, `-missing-soul`, `-missing-instruction`, `-missing-skill`,
`-missing-endpoint-execution`, `-missing-endpoint-task`, `-digest-mismatch`,
`-revision-mismatch`), `adm-dependency-authorization-undeclared`,
`adm-mission-authorization-undeclared`, `prop-subject-task-authorization-undeclared`,
and positive controls `adm-registry-declaration-unselected-agent-ignored`,
`adm-registry-declaration-endpoint-declared`, `adm-unconsulted-authorization-ignored`.
Unit tests: "F1: consulted registry references resolve by exact four-field declaration
at the admission stage", "F1: consulted dependency and mission authorizations must be
declared".
### F2 — task-only contexts evade required work.read → CLOSED
Root cause: `consultedWork` was set only when a mission or dependency was resolved, so
a bound task without those references was never treated as consulted work; the same
condition was in `checkWorkAccess`.
Change: the assigned task is consulted work by definition; `computeContext` sets
`consultedWork` when the task is bound (L7), and `checkWorkAccess` requires `work.read`
unconditionally after its scope guards. Genuinely taskless reads (no assignment) are
unchanged.
Witnesses: `assigned-change-without-work-read` exit 0 allowed → **3 refused /
not-authorized / consulted-work-not-readable**; `proposal-task-only-contexts-without-work-read`
exit 3 unresolved → **3 refused / consulted-work-not-readable**, proposal step
`requester-lacks-original-scope-authority`. Fixtures (6): `adm-assigned-task-without-work-read`,
`adm-assigned-task-without-work-read-read-op` (a `read` operation still needs
`work.read`), `prop-task-only-contexts-without-work-read` (original step),
`prop-task-only-target-context-without-work-read` (target step,
`requester-lacks-target-scope-authority`), positives `assigned-task-only-with-work-read`
(exit 0) and `prop-task-only-contexts-with-work-read` (unresolved). Unit test: "F2: a
bound task is consulted work; work.read is required even without mission/dependency
references".
### F3 — issuer narrowing does not recheck work access → CLOSED
Root cause: work access was checked before the issuer ceiling was applied; only
`assignment.change` was rechecked on the narrowed layers.
Change: `evaluateProposal` now runs `recheck(id, context, work, scope, label)` for
**both** requester contexts on the issuer-narrowed layer
(`intersectLayers(context.layer, issuer)`): `work.read` on the narrowed layer
(`consulted-work-not-readable`), `assignment.change` on the narrowed layer
(`operation-not-permitted`, where `<label>+issuer`), then the full `checkWorkAccess`
path/network intersection.
Witness: `proposal-issuer-denies-work-read` exit 3 unresolved → **3 refused /
not-authorized / consulted-work-not-readable**, step
`requester-lacks-original-scope-authority`. Fixtures (3): `prop-issuer-denies-work-read`,
`prop-issuer-denies-work-read-task-only` (F2+F3 combined), positive
`prop-issuer-grants-work-read` (unresolved). Unit test: "F3: the issuer ceiling
narrows both requester calculations and work access is rechecked" (includes an
`intersectLayers` algebra witness for the target scope; see §3(c)).
### F4 — cycle construction merges historical revisions → CLOSED
Root cause: `checkCycles` keyed dependency/parent/supersedes nodes by identity without
revision and unioned edges over every historical record.
Change: nodes are exact record keys (identity **and** revision) and edges are the
exact four-field references of each record; the identity-merged union and its
post-sort loop were removed. Cycle-before-continuity precedence is unchanged.
Witness: `acyclic-versioned-history-false-cycle` exit 2 dependency-cycle → **exit 0
allowed**. Fixtures (5): positives `cycle-acyclic-versioned-history` (read) and
`cycle-acyclic-versioned-history-file-change` (byte-identical to the witness);
`cycle-dependency-cross-revision` still detects a real cycle through exact revisions
(`dependency-cycle`); `cycle-mission-parent-old-revision-only` (self-parent in a
superseded revision remains `mission-parent-cycle`);
`cycle-mission-parent-previous-revision-not-a-cycle` (a workspace mission whose parent
is its own previous revision is acyclic in the exact graph and fails the ownership
stage as `mission-parent-scope-mismatch`). Unit test: "F4: cycle graphs are
revision-exact; contiguous history with reversed edges is not a cycle" (compares
`findCycle` on the exact graph versus the old merged graph).
### F5 — first-failure ordering not preserved → CLOSED
Root causes: (1) record shape validation walked the input array before the Model's
stable sort; (2) the `message-is-not-authority` check ran after requester admission.
Change: (1) new `shapeValidationOrder(records)` orders records **before** shape
validation using a tolerant key (kind, id, scope, revision when present; identified
records first, then a canonical-content tie-break, then input index) so the first
shape failure is independent of input order even when a record is too malformed to
key; (2) `evaluateProposal` performs the §5 message check immediately after both
requester contexts bind (structural validation) and before any head/authority step;
step 3 keeps only `delegation-missing`.
Witnesses: `shape-order-forward` / `shape-order-reversed` (`shape-enum-mismatch` /
`shape-unknown-field`) → **both `shape-enum-mismatch`**;
`message-priority-after-structural-validation` exit 3 operation-not-permitted /
`requester-lacks-original-scope-authority` → **3 refused / not-authorized /
message-is-not-authority**, proposal rule `message-is-not-authority`. Fixtures (8):
`shape-order-forward`, `shape-order-reversed`, `shape-order-malformed-record-sorts-last`,
`shape-order-only-malformed-record`, `prop-message-precedes-requester-admission`,
`prop-message-precedes-stale-subject`, `prop-message-after-structural-failure`
(structural failure still wins: `requester-context-mismatch`),
`prop-delegation-missing-after-requester-admission` (no message: requester admission
still precedes `delegation-missing`). Unit tests: "F5: the first shape failure is
independent of record input order, including malformed records" (four permutations,
malformed record moved, two unidentified records), "F5: message-is-not-authority
follows structural validation and precedes requester admission".
### F6 — inventories omit ownership → CLOSED
`cli.test.mjs` inventory rows now include `uid` and `gid`; `test-foundation.sh`
inventories with `stat -c '%n %F %s %a %u %g %i %Y'` and the check is labelled
"sandbox inventory (path/type/size/mode/uid/gid/inode/mtime/sha256) unchanged by runs".
## 3. Design decisions and disclosures for the reviewer
- (a) **Rule identifier `registry-declaration-missing` is writer-chosen.** The charter
names the outcome (missing declaration → missing-state admission refusal) but not an
identifier; the vocabulary entry sits in the stage-9 group next to
`authorization-undeclared`/`artifact-undeclared`. No new contract rule is claimed;
if darkwing/filbert prefer an existing identifier, the rename is mechanical.
- (b) **Declared lists are still shape-validated in input order.** `shapeValidationOrder`
applies to `bundle.records` only; `registries`, `artifacts`, `authorizations` and
`delegationInputs` remain in inventory order. The verdict's F5 witnesses concern
records; extending the sort to declaration lists is a small follow-up if required.
- (c) **The target-scope post-issuer failure is unreachable through the CLI.** Issuer
narrowing is scope-independent, so whenever the narrowed target layer lacks
`work.read` the original scope has already failed first. The target branch is covered
by the `intersectLayers` algebra assertion in the F3 unit test, not by a CLI fixture.
- (d) **Subject-task authorization** is now required at step 5 alongside the target
task's. This follows the F1 principle (consulted work must be declared); it was not an
explicit verdict witness and is flagged in case the reviewer reads §5 more narrowly.
- (e) **No currentness rule** was added for harness/settings/context-content/endpoint
references: they must match a declaration exactly (§10.1); only the mandatory policy
keeps its `policy-ceiling-stale` currentness check.
- (f) Agent declaration checks run after the agent-status check and before the policy
ceiling; a disabled agent therefore still refuses as `agent-disabled` first.
- (g) **Correction to r3 §6.** r3 stated "no engine launch". That was true of the
inspector work, but the r3 `test-task.sh` receipt (90/0) included the suite's Docker
and live branches (live hello, session fork, onboarding recall) because Docker was
reachable during that run. r3's receipt therefore rested on a live legacy branch that
FI-ROCKO-6 identifies as unauthorized; r4 does not repeat it (§7). The r3 report is
preserved unedited; this entry is the correction.
## 4. Changed files (r3 → r4 sha256; everything else byte-identical, 37 fixtures added)
| File | r3 | r4 |
|---|---|---|
| `scripts/foundation/resolve.mjs` | `eaa4a41961832891…` | `777dd415021a54a3…` |
| `scripts/foundation/resolve.test.mjs` | `d7de059015edcf0b…` | `cab963cd7b80957d…` |
| `scripts/foundation/cli.test.mjs` | `a0bda7779739021d…` | `566abb49770b5d23…` |
| `scripts/foundation/fixtures/build-fixtures.mjs` | `0386276f0e0ba8fb…` | `f731b1f1da04ecc2…` |
| `scripts/foundation/fixtures/index.json` | `c013090406bb6bbb…` | `9a963a0e58770e4f…` |
| `scripts/foundation/fixtures/README.md` | `ed0376589b203b1b…` | `9dc57df95eb6849d…` |
| `scripts/test-foundation.sh` | `73417979d1f93206…` | `6f808ea8f4a021e3…` |
Full hashes for every file, plus per-directory aggregates, are in the r4 manifest.
Untouched: `foundation-inspect.mjs`, `strict-json.mjs`, `canonical.mjs`,
`validate-record.mjs`, `verify-schema.py`, `fixtures.test.mjs`, all 259 prior
bundles/raw cases, all demo bundles and goldens.
## 5. New fixture inventory (37 cases; 296 total)
Groups after r4: admission 62, positive 21, continuity 2, cycle 6, ownership 10,
proposal 48, identity 5, operation 7, profile 37, shape 61, lexical 19,
registration-delegation 13, structure 5.
| Case | Group | exit result / reason / rule [/ proposal rule] |
|---|---|---|
| adm-registry-declaration-missing-agent-refs, -missing-settings, -missing-soul, -missing-instruction, -missing-skill, -missing-endpoint-execution, -missing-endpoint-task, -digest-mismatch, -revision-mismatch | admission | 3 refused / missing-state / registry-declaration-missing |
| adm-dependency-authorization-undeclared, adm-mission-authorization-undeclared | admission | 3 refused / missing-state / authorization-undeclared |
| adm-registry-declaration-unselected-agent-ignored, adm-registry-declaration-endpoint-declared, adm-unconsulted-authorization-ignored | positive | 0 allowed |
| adm-assigned-task-without-work-read, adm-assigned-task-without-work-read-read-op | admission | 3 refused / not-authorized / consulted-work-not-readable |
| assigned-task-only-with-work-read | positive | 0 allowed |
| prop-task-only-contexts-without-work-read, prop-issuer-denies-work-read, prop-issuer-denies-work-read-task-only | proposal | 3 refused / not-authorized / consulted-work-not-readable / requester-lacks-original-scope-authority |
| prop-task-only-target-context-without-work-read | proposal | 3 refused / not-authorized / consulted-work-not-readable / requester-lacks-target-scope-authority |
| prop-task-only-contexts-with-work-read, prop-issuer-grants-work-read | proposal | 3 unresolved / unknown-effects / runtime-reconciliation-required / runtime-reconciliation-required |
| prop-subject-task-authorization-undeclared | proposal | 3 refused / missing-state / authorization-undeclared / intent-not-current |
| cycle-acyclic-versioned-history, cycle-acyclic-versioned-history-file-change | positive | 0 allowed |
| cycle-dependency-cross-revision | cycle | 2 invalid / invalid-request / dependency-cycle |
| cycle-mission-parent-old-revision-only | cycle | 2 invalid / invalid-request / mission-parent-cycle |
| cycle-mission-parent-previous-revision-not-a-cycle | ownership | 2 invalid / invalid-request / mission-parent-scope-mismatch |
| shape-order-forward, shape-order-reversed, shape-order-malformed-record-sorts-last | shape | 2 invalid / invalid-request / shape-enum-mismatch |
| shape-order-only-malformed-record | shape | 2 invalid / invalid-request / shape-missing-field |
| prop-message-precedes-requester-admission, prop-message-precedes-stale-subject | proposal | 3 refused / not-authorized / message-is-not-authority / message-is-not-authority |
| prop-message-after-structural-failure | proposal | 2 invalid / invalid-request / requester-context-mismatch |
| prop-delegation-missing-after-requester-admission | proposal | 3 refused / not-authorized / operation-not-permitted / requester-lacks-original-scope-authority |
## 6. Receipts (run 2026-09-07 09:2x14:39 UTC at HEAD 9a5fbdbd)
| Check | Result |
|---|---|
| Verdict witness files (nine, hashes verified), run with `env -i HOME=/nonexistent PATH=/nonexistent` | missing-agent-registry-declarations → 3 refused/missing-state/registry-declaration-missing; consulted-dependency-undeclared-authorization → 3 refused/missing-state/authorization-undeclared; assigned-change-without-work-read → 3 refused/not-authorized/consulted-work-not-readable; proposal-task-only-contexts-without-work-read → 3 refused/consulted-work-not-readable (step requester-lacks-original-scope-authority); proposal-issuer-denies-work-read → same; acyclic-versioned-history-false-cycle → 0 allowed; shape-order-forward and shape-order-reversed → 2 shape-enum-mismatch; message-priority-after-structural-validation → 3 refused/not-authorized/message-is-not-authority (proposal rule message-is-not-authority); positive-control → 0 allowed. stderr empty for all. The three exploratory blocked-subject/target probes in the same directory remain unresolved/runtime-reconciliation-required, as the verdict §5 expects. |
| `node --check` on all 10 `.mjs` files; `ast.parse` on `verify-schema.py` | OK |
| `node --test scripts/foundation/` | 70 tests, 70 pass, 0 fail (was 63; +7 F1F5 tests) |
| `python3 scripts/foundation/verify-schema.py` | **PASS**, exit 0. Corpus 1563 records (38 pinned fixtures, 473 unique bundle records, 1052 typeCase/mutation/lexical). Schema column: agree-valid 539, agree-invalid 987, **DISAGREEMENTS 0**; strict-only 27; unsupported-kind not assessed 10. Profile column: profile-valid 509, profile-invalid 30; 30 refusals asserted, 73 named probes verified |
| `NO_COLOR=1 scripts/test-foundation.sh` | **`selftest: 43 passed, 0 failed`**, including "sandbox inventory (path/type/size/mode/uid/gid/inode/mtime/sha256) unchanged by runs" |
| `python3 docs/plans/foundation-v1-candidate/check.py` | exit 0, `PASS: 35` |
| `scripts/test-config.sh` / `test-conductor.sh` / `test-auth.sh` | 24/0, 17/0, 15/0 passed/failed (no Docker, no network, no credentials; conductor commits only inside its sandbox clones) |
| `scripts/test-task.sh` / `scripts/test-release.sh` | **NOT RUN — test-authorization blocker, §7** |
| Fixture regeneration | `build-fixtures.mjs` into a sandbox equals checked-in `bundles/`, `raw/`, `index.json`, demo bundles (296/296 cases match through the CLI) |
| `git rev-parse HEAD`; `git status` | `9a5fbdbd…`; nothing staged; only the pre-existing tracked modifications (BUILD-LOG.md, docs/SESSIONS.md, docs/plans/CURRENT.md) plus untracked candidate/review files |
| `__pycache__` directories | none created (`PYTHONDONTWRITEBYTECODE=1`) |
## 7. Integration-gate blocker (reported, not skipped)
- `scripts/test-task.sh` gates two sections on `docker info`: the adapter-seam mock
cases (container launches, no provider) and the live cases (`run-task.sh run` with
the real engine, provider credentials, session forks and onboarding recall). In this
session `docker info` returns 0, so an unmodified run executes both sections.
- `scripts/test-release.sh` gates its state-machine cases on `docker info` (package /
activate / rollback against Docker images).
- Running either suite would launch containers and, for `test-task.sh`, a live engine
under the operator's configuration: outside FI-ROCKO-6's authorization. Masking Docker
(for example an empty `PATH`) would print the suites' own "skip … (docker unavailable)"
lines and additionally fail `test-task.sh`'s unconditional onboarding recall; that is
the substitute-skip the request forbids. The suites are outside the allowlist, so no
fast-only entry point can be added by the writer.
- Required from darkwing: an explicitly authorized, isolated, non-live run of the two
suites (or a ruling that the fast sections are sufficient for this gate), as the
verdict §5 already identified. Nothing in r4 touches the code those suites exercise.
## 8. Constraints honoured and freeze
No edit to charter, addendum, verdicts, corrections request, pinned schema/checker/
fixtures, prior reports or manifests, CURRENT, shared logs, root/package files,
policies or runtime data. 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 launch in r4. Tests spawn the inspector; the inspector spawns
nothing. Finite tests and static flags are evidence, not sandbox proof.
Frozen at the r4 manifest (`…-rocko-build-manifest-r4.json`, sha256 `2a2e885f…0826`;
331 files) 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.