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

260 lines
20 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 r5 (FI-ROCKO-7: §10.4 stable traversal of the declared inventories)
Author: rocko (Claude Fable 5.1). Coordinator: darkwing. Reviewer: filbert. Task: FI-ROCKO-7.
Written 2026-09-07 15:03 UTC. Supersedes nothing: the original report, the r2/r3/r4
reports and manifests, the charter, the addendum, both verdicts, the corrections
request, the ordering follow-up and the demo test-gate decision are preserved unedited.
This revision closes the F5 remainder disclosed in r4 §3(b) (declared inventories were
still shape-validated in input order; the registry digest traversal at r4
`resolve.mjs:831` used input order), records what changed r4 → r5, the per-family
permutation evidence, the owner's demo test-gate decision, and the frozen candidate
for independent re-review.
## 0. Admission, compatibility and blockers
FI-ROCKO-7 is **admitted and compatible** with the standing chain, the FI-ROCKO-3
build request, the corrections request, the ordering follow-up and the demo test-gate
decision; the code/test work is complete under the original allowlist plus the two r5
receipt paths named in the follow-up. C1 (Archify rev 3) remains HELD with no lane action.
**No open blocker.** The r4 §7 integration-gate blocker (`scripts/test-task.sh` and
`scripts/test-release.sh` not runnable here without executing Docker/live branches) is
now governed by the owner decision recorded in
`docs/plans/reviews/2026-09-07_foundation-inspector-demo-test-gate-decision.md`
(sha256 `62cea74b…4898`, 2026-09-07 14:47 UTC): both suites are **NOT RUN / DEFERRED**
for this offline demo (§7). They are not reported green and no live test authority is
claimed. The decision changes only the offline-demo test prerequisite; full
deployment/integration testing remains a later gate.
Inputs bound to this revision (all hashes machine-read; full list in the r5 manifest
`pinnedInputs`):
| Input | sha256 |
|---|---|
| charter `docs/plans/2026-09-06_foundation-inspector-charter.md` | `19b6721128a627a2032ffdb95ece2d50abe69a8f6d521e9eff8bbdaff22798b6` |
| addendum FI-C2-1 | `afe2980be2f91e701dae5af3018831ac5c300474f52bcc06e740ce5b5cc68ca5` |
| code verdict FI-FILBERT-6 | `e4cc5970aab20240e6ea3a9a1513ef942ff167fefbb03356b9a32c89999adb3d` |
| corrections request | `35c237f5c001832a0cfed23b03ff7530ffa78443612ffe11130fe456b968bd61` |
| r4 report | `dff9f676ad0a97f6333a59d527e02ed55e7bcdca986fe9c1488280b84d4e677f` |
| r4 manifest (331 files) | `2a2e885f9df3dd626a09fe5db6e28af67c289e54660872031c18f8aa40cb0826` |
| ordering follow-up `…_foundation-inspector-ordering-followup.md` | `e93a65b94fd4df8bd6e74c2f38afaf02e0f86b36632ff5a820fd8ac464c22101` |
| demo test-gate decision `…_foundation-inspector-demo-test-gate-decision.md` | `62cea74bea59e69f29c8a29a9c0311fdc6feffa2c337bc6a141db55149204898` |
| pinned schema / checker / fixtures | `05774aaf…2af6` / `82564a7d…4607` / `d433d06d…da85` |
Git: HEAD `9a5fbdbda74b16adf488fe28138b2ba69ea5e669` (main), unchanged throughout;
nothing staged; the only tracked modifications are the pre-existing ones
(`BUILD-LOG.md`, `docs/SESSIONS.md`, `docs/plans/CURRENT.md`, not touched by rocko).
All candidate files remain untracked. Toolchain: node v26.8.1, Python 3.12.8,
jsonschema 4.26.0, Linux 7.2.2-1-cachyos.
## 1. Evidence preservation
- r4 report and manifest: unedited, hashes above re-verified after the r5 build.
- r3 report (`ec0444c9…5508`) and manifest (`3c2253b6…8547`): unedited. **The r4 §3(g)
correction stands and is repeated here so it stays visible in history:** the r3
`test-task.sh` 90/0 receipt did execute the suite's Docker and live branches (Docker
was reachable), contradicting r3 §6 "no engine launch". r3 is not relabeled; those
actions were not repeated in r4 or r5 (§7).
- Charter, addendum, verdicts, corrections request, follow-up, decision doc, pinned
schema/checker/fixtures: unedited (manifest `pinnedInputs`).
- Every pre-existing fixture bundle, raw case, demo copy and golden is byte-identical
to r4 (manifest diff: 5 files changed, 38 bundles added, 0 removed; §4). Every prior
`index.json` expectation is unchanged; only 38 cases were appended.
## 2. Closure of the ordering follow-up
### 2.1 What §10.4 requires and what r4 still did
Charter §10.4: "Within a stage use stable kind/id/scope/revision and field ordering,
not input order." r4 applied this to `bundle.records` only. Registries, artifacts,
authorizations and delegationInputs were traversed in input order at the shape stage
(`checkArray` per family), at the profile stage (the profile sink is filled in
validation order, so it inherited input order for these families), at the
content-digest check (r4 `resolve.mjs:831`, `b.registries.forEach`), at the identity
stage (duplicate checks per family) and at the structural stage (delegationInputs
`decisionRef` existence). The first failure within any of those traversals therefore
depended on input position.
### 2.2 Implementation (`scripts/foundation/resolve.mjs`)
- `stableOrder(items, keyOf)` (exported, r5 lines 730767): the generic stable
traversal. `keyOf` returns a typed key (array of `string | integer | null`
components) or `null` when the item has no readable identity. Order: keyed items
before unkeyed; keys component-wise (a `null` component after every readable
value; strings by code unit; integers numerically); equal keys tie-break on
canonical content (`canonicalize`, `CanonicalError` → empty string); then input
index. Returns input indices, so every locator path keeps the input index (privacy
and locator semantics unchanged; `detail` remains internal).
- `ORDER_KEYS` (exported, 801807), the typed identity key per family:
- records: `[kind, id, scopeKey, revision]` (tolerant, exactly the r4 rule:
unreadable kind/id/scope kind → unkeyed; non-integer revision → `null` component);
`shapeValidationOrder` now delegates to `stableOrder`, behaviour-preserving (all
r4 F5 tests and fixtures unchanged);
- registries: `[registry, id, revision, digest]`; unkeyed unless `registry` and
`id` are strings;
- artifacts: `[runId, artifactId, digest]`; unkeyed unless `runId`/`artifactId` are strings;
- authorizations: `[runtimeId]`; unkeyed unless a string;
- delegationInputs: the `decisionRef` kind/id/scope/revision key; unkeyed unless
the ref is readable.
- `checkInventory(v, path, itemCheck, keyOf)` (813818): bounds first
(`shape-type-mismatch`, `shape-bound-exceeded` at the array path, as before), then
each item in stable order with its input-index path. Used for the four declared
inventories in `validateBundleShapeScoped` (821823 and 857864). Family
precedence is unchanged: records → registries → artifacts → authorizations →
selection → operation → proposal → delegationInputs.
- Profile stage: unchanged code; `profileSink[0]` is now the first violation in
stable order for every family because the sink is filled in traversal order.
- Digest stage (871884): content-digest traversal iterates
`stableOrder(b.registries, ORDER_KEYS.registries)`; rules, reasons and paths unchanged.
- `Model` constructor (10841089): computes `this.order` (stable index lists for the
four families) once; `indexIdentities` (stage 3) walks each family's duplicate
check in that order; `checkStructuralReferences` (stage 4, 1181) walks
delegationInputs in that order. Lookup-based consumers (`requireRegistryDeclaration`,
`registryContent`, `requireArtifacts`, the authorization `Set`, `delegationInputs.find`)
are order-independent once duplicates are rejected and are untouched.
- **Semantic sequence arrays are not reordered.** `checkArray` is unchanged and still
used, in input order, for `operations`, `readPaths`, `writePaths`, `endpointRefs`,
instruction/skill/context-content lists, `dependencies`, `subjectRefs`,
`evidenceRefs` and every other ordered array; canonical content is never
reordered; `evaluate` does not mutate or reorder the input bundle (asserted).
- Stage/field precedence preserved: every shape failure before any profile
violation, every profile violation before any digest failure, identity before
structural; within an entry the field order of the existing checkers is unchanged.
- No new rule, reason or exit; the closed vocabulary is unchanged. No input file is
mutated; no exception is invented for declaration lists.
### 2.3 Per-family evidence (fixtures in `build-fixtures.mjs` 10761230; tests in `resolve.test.mjs` 548712)
Each fixture pair carries two *different* failures on two entries; the expected first
rule is determined by the typed key, and the "swapped" pairs move the errors between
the same two entries so the expected rule changes with the key, not the position.
`assertPermutationStable` re-evaluates every permutation of the family (all
permutations for inventories of ≤ 4 entries; for larger inventories a fixed
deterministic family: identity, reverse, descending sort, odd/even interleave, every
rotation and twelve seeded FisherYates shuffles) and asserts the identical located
first failure (rule, reason, exit, proposal step, and the locator resolved to the
entry's typed key plus field tail). Expected keys are asserted explicitly, so the
tests demonstrate intended first-rule stability, not merely a consistent nonzero exit.
| Family / stage | Fixtures (group) | Expected first rule and locator |
|---|---|---|
| registries, shape | `shape-order-registries-{forward,reversed}` (shape) | `shape-missing-field` at agent-policy/ap-full (missing `digest`) before `shape-unknown-field` at project-policy/pp-p1 |
| registries, shape, swapped | `shape-order-registries-swapped-{forward,reversed}` | `shape-unknown-field` at ap-full (errors exchanged) |
| registries, shape, malformed | `shape-order-registries-malformed-sorts-last`, `-only-malformed` | `{registry: 7}` (unkeyed) sorts last → `shape-enum-mismatch` at the keyed bad entry `.registry`; a lone `"not-an-entry"``shape-type-mismatch` |
| registries, profile | `profile-order-registries-{forward,reversed}` (profile) | `profile-pattern-mismatch` at agent-policy/ap-reader `.id` before scope-role/role-reader |
| registries, shape > profile | `profile-order-shape-precedes-profile-registries` (shape) | `shape-unknown-field` at project-policy/pp-p2 although ap-reader carries the (earlier-key) profile violation |
| registries, digest | `shape-order-registry-digest-{forward,reversed}` (profile) | `mock-content-unsupported` (unsupported-capability) at ap-full `.content` before `registry-digest-mismatch` at role-writer |
| registries, digest, swapped | `shape-order-registry-digest-swapped-{forward,reversed}` | `registry-digest-mismatch` at ap-full `.digest` |
| registries, profile > digest | `shape-order-profile-precedes-digest-registries` | `profile-pattern-mismatch` at pp-p2 `.id` although ap-full carries the digest mismatch |
| registries, two digest mismatches | `shape-order-two-digest-mismatches-reversed` | `registry-digest-mismatch` at scope-role/role-reader (lower key) in every order |
| artifacts, shape | `shape-order-artifacts-{forward,reversed}`, `-swapped-{forward,reversed}`, `-malformed-sorts-last` | `shape-pattern-mismatch` at run-1/art-1 `.digest` before `shape-unknown-field` at art-2; swapped → `shape-unknown-field` at art-1; `{runId: 1}` sorts last |
| artifacts, profile | `profile-order-artifacts-reversed` | `profile-pattern-mismatch` at run-1/art-2 `.artifactId` (art-1's violation is on `.runId`, a higher key after the LF) |
| authorizations, shape | `shape-order-authorizations-{forward,reversed}`, `-malformed-sorts-last` | `shape-pattern-mismatch` at the lower runtime id `…-1000-…0002` in every order; a non-string entry sorts last |
| authorizations, profile and shape > profile | `profile-order-authorizations-reversed`, `profile-order-shape-precedes-profile-authorizations` | `profile-pattern-mismatch` at `…0001\n`; a pattern failure on the higher id precedes a profile violation on the lower one |
| delegationInputs, shape | `shape-order-delegation-inputs-{forward,reversed}`, `-swapped-{forward,reversed}`, `-malformed-sorts-last` | `shape-enum-mismatch` at decision/d-deleg-b `.mode` before `shape-missing-field` at d-deleg-reg; swapped → `shape-missing-field` at d-deleg-b; `{decisionRef: null}` sorts last |
| delegationInputs, structural | `struct-order-delegation-inputs-{forward,reversed}` (structure) | `record-reference-missing` (missing-state) at d-deleg-b revision 9 `.decisionRef` in every order |
| delegationInputs, identity | `dup-order-delegation-inputs-reversed` (identity) | `duplicate-delegation-input` located at the d-deleg-reg duplicate although a valid lower-key input is listed after it |
| family precedence | `shape-order-family-precedence-{registries-before-artifacts,artifacts-before-authorizations,authorizations-before-delegation-inputs}` | the earlier family's failure wins regardless of positions |
| identity precedence | `dup-order-registries-precede-artifacts` (identity) | `duplicate-registry-identity` before `duplicate-artifact-identity` |
| identity, differing digests (unit only) | `resolve.test.mjs` "family precedence and identity-stage order" | duplicate plain-registry and artifact identities with differing digests: the higher digest is the reported entry in every order |
Unit test `stableOrder: typed keys …` pins the helper itself on hand-built inventories
(integer-before-null revision, digest tie-break, duplicate index tie-break, unkeyed
items ordered by canonical content, records/artifacts/authorizations/delegationInputs
keys) and asserts `evaluate` leaves the bundle byte-identical.
Mutation witness (scratchpad only, discarded): with `stableOrder` short-circuited to
input order for the four families, the seven new r5 tests fail (`stableOrder`, the
five per-family tests and the precedence test) while all r4 tests still pass; the
r5 tests therefore detect the r4 behaviour.
## 3. Disclosures for the reviewer
- (a) **Tie-break for byte-identical duplicates.** Two entries with equal typed key
and equal canonical content are indistinguishable; the duplicate reported at the
identity stage is then the higher input index. This is position-dependent only in
the degenerate case where the two positions hold identical bytes, so no observable
result changes. Entries with equal key but different content (e.g. duplicate
registry identity with a different digest) are ordered by content and are
permutation-stable (asserted).
- (b) **`registry-digest-mismatch` / `mock-content-unsupported` share one traversal.**
Both are raised in a single stable pass over registries after the profile stage; an
unsupported content on a lower key precedes a mismatch on a higher key and vice
versa. This is the intended first-rule order (key order, not rule severity).
- (c) **`registry-declaration-missing` semantics.** Unchanged from r4 (accepted by
darkwing as diagnostic naming only); the reviewer's semantic confirmation is still
open and nothing in r5 alters that path.
- (d) **Permutation family for large inventories** is a fixed deterministic subset
(§2.3), not all n! orders; the base bundle carries ten registries. All-permutation
coverage applies to the artifact, authorization and delegation-input inventories
(23 entries). The helper's own ordering is asserted directly on hand-built lists.
- (e) **Locator (`detail`) remains internal.** The tests read it through `evaluate`
only; `inspect`/CLI output still exposes no path or value (fixtures.test and the
suite's privacy checks unchanged and green).
- (f) **r3 live-branch correction** (r4 §3(g)) remains in force and unrelabeled (§1, §7).
## 4. Changed files (r4 → r5 sha256; everything else byte-identical, 38 fixtures added, 0 removed)
| File | r5 sha256 | Change |
|---|---|---|
| `scripts/foundation/resolve.mjs` | `892f95ab8a971d93060353e43d53cb6988e7e61f2a5bae71f6f2aaf2b7c1f09f` | `stableOrder`, `ORDER_KEYS`, `checkInventory`; stable traversal at shape/profile/digest/identity/structural stages (§2.2) |
| `scripts/foundation/resolve.test.mjs` | `0fdcbf80edfc35e877132566b9af5b1256d5efd3e69d4fc809eb0ca259a5bba3` | seven FI-ROCKO-7 tests, `firstLocated`, `permutations`, `assertPermutationStable` |
| `scripts/foundation/fixtures/build-fixtures.mjs` | `273f3f12d1911aaa067a1046bbea20c5eb27b5b80c9913a833c0863b8e06ab7c` | 38 ordering cases (§2.3) |
| `scripts/foundation/fixtures/index.json` | `31c6fab4d27f3e9c3e26cdcf343171f9c744404d359d042c80dc5a8566a57ea0` | 334 cases (was 296) |
| `scripts/foundation/fixtures/README.md` | `f3d548908f8bde629525ac0be90df40bbfaf01faff79980474ef4164f306ec7e` | FI-ROCKO-7 paragraph |
| `scripts/foundation/fixtures/bundles/*` | 38 added (manifest) | none modified |
Group counts (`index.json`): admission 62, continuity 2, cycle 6, identity 7 (+2),
lexical 19, operation 7, ownership 10, positive 21, profile 47 (+10), proposal 48,
registration-delegation 13, shape 85 (+24), structure 7 (+2).
## 5. Receipts (run 2026-09-07 15:0x UTC at HEAD 9a5fbdbd; logs kept in the session scratchpad)
| Check | Result |
|---|---|
| `NO_COLOR=1 PYTHONDONTWRITEBYTECODE=1 scripts/test-foundation.sh` | 43 passed, 0 failed (syntax, fixture regeneration equality for bundles/raw/index/demo, node tests, oracle, demo exits, goldens, sandbox inventory incl. uid/gid, canaries) |
| `node --test scripts/foundation/` | 77 tests, 77 pass, 0 fail (was 70) |
| `python3 scripts/foundation/verify-schema.py` (jsonschema 4.26.0, pinned hashes) | PASS; corpus 1563 records; schema column agree-valid 539, agree-invalid 987, DISAGREEMENTS 0; profile refusals asserted 30 |
| `python3 docs/plans/foundation-v1-candidate/check.py` | PASS (155 shape fixtures, 5 obligations, 35 model cases) |
| `scripts/test-config.sh` | 24 passed, 0 failed |
| `scripts/test-conductor.sh` | 17 passed, 0 failed (disposable test copies only) |
| `scripts/test-auth.sh` | 15 passed, 0 failed |
| `scripts/test-task.sh` | **NOT RUN / DEFERRED** (owner decision, §7) |
| `scripts/test-release.sh` | **NOT RUN / DEFERRED** (owner decision, §7) |
| every new fixture through the CLI (`env -i`-style PATH/HOME) | 38/38 match `index.json`; every pre-existing case unchanged |
| git | HEAD unchanged, nothing staged, no candidate file tracked; no `__pycache__` created (the one present under `.pi/evidence/goal56/` predates this work, 2026-09-06) |
## 6. Constraints honoured
No edit to charter, addendum, verdicts, corrections request, follow-up, decision doc,
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; no container launch. Tests spawn the
inspector; the inspector spawns nothing. Finite tests and static flags are evidence,
not sandbox proof. No fake full-suite pass, no new waiver.
## 7. Deferred coverage (owner decision, recorded verbatim in scope)
Per `2026-09-07_foundation-inspector-demo-test-gate-decision.md` (Jason, 2026-09-07
14:47 UTC, after clarification): `scripts/test-task.sh` and `scripts/test-release.sh`
are deferred for this offline inspector demo and are reported **NOT RUN / DEFERRED**,
never passed or fully covered. The deferral changes only the test prerequisite for
the bounded offline demo; it authorizes no live model calls, deployment actions,
weaker inspector behaviour or permanent removal of tests. Still required and
delivered here: inspector unit/CLI/privacy/non-effect/differential checks, the
foundation checker and the authorized isolated non-live integration checks (§5).
Still required and **not** rocko's to grant: filbert's independent code approval and
Jason's separate synthetic demo acceptance. Full deployment/integration testing
remains a later gate before dependent live runtime work. The r3 execution of the
live branches (r4 §3(g)) is neither authorized nor relabeled by this decision.
## 8. Freeze
Frozen at the r5 manifest (`docs/plans/reviews/2026-09-07_foundation-inspector-rocko-build-manifest-r5.json`,
sha256 `a63bb103e4cfe51bbd8a42cd95545f935ac83c1da51cf469298375cf94ced135`; 369 files:
source 6, tests 5, suite 1, fixtures-meta 3, bundles 315, raw 19, demo 20; aggregate
`scripts/foundation/` 367 files `126b05e4…82ae`) 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.