# FI-ROCKO-2 — Foundation synthetic inspector: corrected feasibility note (r2) Author: rocko (Claude Fable 5.1, Archify lane author seat). Requested by darkwing (FI-ROCKO-2, 2026-09-06), reconciling the FI-ROCKO-1 note against darkwing's coordinator source check. Issue: #53. Status: planning evidence only. This note proposes; it does not approve, implement, or bind. It supersedes the r1 note for every point where the two differ; r1 is preserved unchanged as history. ## 0. Admission and scope **FI-ROCKO-2 is admitted. No blocker.** My held owner goal (Archify C1 rev 3, awaiting Jason's rulings) is not displaced. This file is the only repository write. No implementation, dependency, commit, push, checkout, live test, credential read, fleet investigation, or contact with filbert occurred. Every citation below was re-read from committed bytes with `git show` during this task; the only unpinned inputs are the untracked charter draft and darkwing's corrections note. ## 1. Pinned inputs | Input | Identity | |---|---| | Technical map `docs/plans/2026-09-06_foundation-technical-map.md` (the accepted map) and `docs/plans/2026-09-06_foundation-map-handoff.md` | commit `7345f330fc6bfae5aa1d896c78cfb7cbe62efbae` | | Requirements document `docs/plans/2026-09-06_agent-project-workspace-foundation.md` (R1–R34), candidate `docs/plans/foundation-v1-candidate/{README.md, REVIEW.md, RUNTIME.md, records.schema.json, records.fixtures.json, fingerprint-vectors.json, check.py, semantic-model.py}`, `docs/plans/2026-09-06_foundation-phase2-contract.md`, `docs/plans/ROADMAP.md` | commit `d4696d09eb1b5dcf1028f30db2cd63735f51cb16` | | Charter draft 1 `docs/plans/2026-09-06_foundation-inspector-charter.md` | untracked, SHA-256 `fdcdf4df8bf30b9fbfff960f9b1d4160ce7e6bbc4b8bb601a93cda13b56c8996` (unchanged since r1) | | Corrections `docs/plans/reviews/2026-09-06_foundation-inspector-rocko-corrections.md` | untracked, SHA-256 `fe672fd6f0b745b5f64042a094f31463064ca436aeaeba956f9c15228df5f716` | | r1 note `docs/plans/reviews/2026-09-06_foundation-inspector-rocko-feasibility.md` | untracked, SHA-256 `92fa7b3de2591ba24fb184ed8edbabedd30c54498fdaf101649e36aaebabe9b9` | Citation form: `commit:path:line`. `cand/` abbreviates `docs/plans/foundation-v1-candidate/`; `map` abbreviates `7345f33:docs/plans/2026-09-06_foundation-technical-map.md`. ## 2. Disposition of F1–F9 | Finding | Disposition | Where resolved | |---|---|---| | F1 wrong map path, nonexistent FINDINGS.md, phantom Node pin | **Accepted in full.** Citations corrected; AJV/fast-uri rationale withdrawn; measured host version separated from a proposed test baseline | §3, §4 | | F2 reference shapes (recordRef no digest, basisRef artifactRef, authorizationRef runtimeId, workspace parent from scope, createdAt calendar rule) | **Accepted in full.** Bundle gains declared `artifacts` and `authorizations` simulation inputs; resolver rules rewritten against the schema | §5, §6 | | F3 instruction-order digest | **Accepted.** New digest is named separately, preserves all array order, never claims the README 6.1 fingerprint rule; exact vectors supplied | §6.1 | | F4 mandatory role/policy layers, assignment has no restrictions, no launch default | **Accepted in full.** Layer table rebuilt; role ceilings mandatory; execution layer must be explicitly modelled | §7 | | F5 reassignment authority in both scopes; no success path | **Accepted.** Proposal mode yields only refused or unresolved; caller-supplied observations removed | §8 | | F6 bounded operations, no `execution.stop`, closed output | **Accepted.** Four supported operations; `execution.stop` withdrawn (catalog has `execution.control`); output schema enumerates every field | §9, §10 | | F7 required differential schema verification | **Accepted.** Mandatory Python/jsonschema harness; missing dependency fails, never skips | §12.1 | | F8 honest non-effect tests, descriptor-based open | **Accepted.** Inventory diff on separated roots, static import boundary, `O_NOFOLLOW|O_NONBLOCK` open then `fstat` | §12.2, §5.3 | | F9 revision handling | **Accepted.** Contiguous full history or nothing per identity; historical vs current-admission resolution classes | §6.3 | No finding is disputed. Two places where I go slightly beyond the finding are flagged inline (§9 exit class for unsupported operations; §6.1 ASCII limit on mock-content paths). ## 3. Corrected repository facts (F1) - The accepted map is the technical map at 7345f33. Its recommended increment is `map:244-283`; exit classes `map:275-276`; "No npm/Turbo change is needed" `map:278`; the inspector-must-not-bootstrap disposition `map:240`; the cross-lane retasking scenario `map:290-314` with the adversarial acceptance case at `map:305-314`. The document I cited in r1 under the map's name is the requirements document (R1–R34) at d4696d09; R-number citations stay valid against that file, the map citations did not. Corrected throughout. - `d4696d09:cand/FINDINGS.md` does not exist (`git cat-file -e` fails) and no file in the pinned tree or in `docs/plans/*.md` mentions `ajv` or `fast-uri`. **The audit rationale is withdrawn.** The dependency-free recommendation now rests only on in-scope facts: `package.json` declares one dependency (pi 0.84.4), the map forbids an npm change for this boundary (`map:278`), and the candidate's own checker states it "never installs dependencies" (`d4696d09:cand/check.py:4`). - There is no repository Node pin: no `engines` field, no `.nvmrc`, `.node-version`, or `.tool-versions`, and no Node image pin in the build scripts. The **measured host** runtime is Node v26.8.1, Python 3.12.8, jsonschema 4.26.0. These are measurements, not pins. - Proposed inspector test baseline (a new charter decision, not an existing pin): the suite records `node --version` in its header and refuses to run below a stated minimum. The features used (`node:test`, `node:crypto`, `TextDecoder` with `fatal: true`, `fs.constants.O_NOFOLLOW`) are all present in Node 22 LTS; I recommend the charter state "Node ≥ 22, measured on v26.8.1" and that no `engines` field be added by this increment. ## 4. Language and dependency recommendation (D-A resolved) Node ESM, zero new runtime dependencies, hand-rolled closed validation derived field-by-field from `d4696d09:cand/records.schema.json`, **verified differentially by a mandatory Python/jsonschema harness** (§12.1). The Python-stdlib runtime alternative is closed: the candidate's `check.py` is a script that executes assertions on import, not a validation API (`d4696d09:cand/check.py:66-96` run at module level), so neither language gets a free validator; Node keeps the CLI surface consistent with `scripts/`. ## 5. Bundle contract (exact fields, proposed to freeze) Closed objects throughout; unknown keys refuse; duplicate JSON keys refuse at parse time; identifiers follow `$defs/id` (`^[a-z0-9][a-z0-9._-]{0,63}$`). ``` { "bundleVersion": 1, "kind": "foundation-inspector-bundle", "simulation": true, // literal true or refuse "records": [ , ... ], // ≤256, supported kinds only (§10) "registries": [ , ... ], // ≤256 "artifacts": [ {runId, artifactId, digest}, ... ], // ≤256, declared only "authorizations": [ , ... ], // ≤256, declared only "selection": { "agentId": , "projectId": , "workspaceId": , "assignmentRef": | null, "execution": { "kind": "unrestricted-simulation" } | { "kind": "restrictions", "restrictions": } }, "operation": { "name": , "target": | null }, "proposal": null | { "kind": "assignment-change", "requesterAgentId": , "subjectAssignmentRef": , "targetTaskRef": , "delegationRef": | null, "changeDecisionRef": | null, "message": | absent // informational, never authority } } ``` ### 5.1 Type corrections (F2) - `` is exactly `$defs/envelope`: `authorizationRef` is a `runtimeId` (UUID v4 pattern), not a recordRef; `supersedes` is `null` at revision 1 and a recordRef otherwise (schema `allOf`); `createdAt` is `$defs/time` (`YYYY-MM-DDTHH:MM:SS.mmmZ`) **and** must be a real calendar instant with the same round-trip rule as `check.py:36-44` (2026-02-30 refuses). It still takes no part in calculations. - `` = `{kind, id, scope, revision}`. **No digest.** Record identity is exact-match on those four fields; record content integrity is not digest-checked in increment 1 (stated as a limitation in output docs). - `` = `{registry, id, revision, digest}`; `` = `{runId, artifactId, digest}`. `decision.basisRef` and `evidenceRefs` are artifactRefs and are **evidence pointers, not authority**. - Workspace ownership comes from `scope`: a workspace record has `scope.kind = workspace`, `scope.projectId` names the owning project, and `id` must equal `scope.workspaceId`; a project's `id` must equal `scope.projectId` (`d4696d09:cand/README.md:64`). Registrations carry project or workspace scope per the schema's conditional. ### 5.2 Simulation-only external inputs (F2) `artifacts` and `authorizations` are the only way the bundle can satisfy an `artifactRef` or `authorizationRef`. They are **declared, not verified**: the inspector checks presence and exact match only, never content, signature, or liveness, and the output labels them `declared-simulation-inputs`. A missing declaration refuses `missing-state`; it is never treated as valid. Nothing in the bundle asserts authentication; the inspector emits `"authentication": "not-modelled"` in every result. ### 5.3 Bounds and file handling (F8) | Bound | Value | |---|---| | File size | ≤ 1 MiB, checked from `fstat` **before** reading | | `records`, `registries`, `artifacts`, `authorizations` | ≤ 256 each | | Any array | ≤ 1024 (`d4696d09:docs/plans/2026-09-06_foundation-phase2-contract.md:116`) | | Nesting depth | ≤ 32 | | Strings | ≤ 4096 bytes; relative paths per `$defs/relativePath` and the `mosaic-relative-path` format (≤4096 UTF-8 bytes, no Cc/Cf/Cs, no backslash, no empty/`.`/`..` segment; `check.py:47-64`) | Open sequence (Linux, descriptor-based): `fs.openSync(path, O_RDONLY | O_NOFOLLOW | O_NONBLOCK | O_CLOEXEC)`; a symlink fails at open with `ELOOP` and a FIFO cannot block; then `fstatSync(fd)` must report a regular file and a size within bound; then read exactly `size` bytes from the descriptor and refuse if more arrive. Any failure is exit 4 `io-failure` naming only the path as given on argv. This is a design statement; no live test was run. ## 6. Digest and reference algorithms ### 6.1 Mock-content digest (F3): `inspector-content-digest/1` This is a **new, separately named** restricted algorithm for content-bearing registry entries only. It is not the README 6.1 launch-fingerprint projection, which preserves instruction order and ID-sorts skills (`d4696d09:cand/README.md:268-285`, `check.py:115`); the inspector computes no launch fingerprint in increment 1. Rule: domain = objects, arrays, ASCII strings, booleans, null, safe integers; anything else refuses `unsupported-capability`. Object keys sorted by code unit; compact separators; **all array order preserved**; digest = `"sha256:" + hex(SHA-256(UTF-8 bytes))`. Structural uniqueness (`uniqueItems`) and set intersection operate on parsed values and are independent of serialization. Consequence flagged: pathGrant paths inside mock content must be ASCII in increment 1, while record and target paths may be non-ASCII per the candidate regex (which accepts `é/x`); the charter should state this asymmetry. Exact vectors (computed with Python stdlib `json.dumps(sort_keys=True, separators=(",",":"))`, the same primitives as `check.py:116`): | Vector | Content | Canonical | Digest | |---|---|---|---| | V1 | `{"restrictions":{"operations":["work.read","file.read"],"readPaths":[{"root":"workspace","path":null}],"writePaths":[],"network":"none","endpointRefs":[]}}` | `{"restrictions":{"endpointRefs":[],"network":"none","operations":["work.read","file.read"],"readPaths":[{"path":null,"root":"workspace"}],"writePaths":[]}}` | `sha256:0bc44e14fd8354a8a85be879306a54881da7a0cb4d3dcf1f88bc4bc08a40dc9c` | | V2 | V1 with `operations` reversed | `…"operations":["file.read","work.read"]…` | `sha256:63817bffc57803ca6ac5df971aa0a160d4be73646394b3bc536578bd66c82660` (differs from V1: order is significant) | | V3 | V1 plus `file.change` and `writePaths:[{"root":"workspace","path":"docs"}]` | `{"restrictions":{"endpointRefs":[],"network":"none","operations":["work.read","file.read","file.change"],"readPaths":[{"path":null,"root":"workspace"}],"writePaths":[{"path":"docs","root":"workspace"}]}}` | `sha256:d538c865808cfe7665956615fb48f69819baa0833626d1ef6e1cb3614acbc7da` | | V4 | V1 with a float `1.0` anywhere | refuse | `unsupported-capability` | | V5 | V1 with a non-ASCII string | refuse | `unsupported-capability` | Registry entry shapes: declared `{registry, id, revision, digest}` for harness, settings, context-content, endpoint; content-bearing `{registry, id, revision, digest, content:{restrictions}}` for scope-role, agent-policy, project-policy, where `digest` must equal the V-rule digest of `content` or the bundle refuses `invalid-request`. The candidate defines no registry content shapes; `{restrictions}` is a simulation-only stand-in (D-E). ### 6.2 Reference resolution Index on `(kind, id, scope)`; duplicate `(kind, id, scope, revision)` refuses `invalid-request`. Every recordRef resolves by exact four-field match; every registryRef by exact four-field match including digest; every artifactRef and authorizationRef by exact match against the declared lists (§5.2). Absent → `missing-state`; kind mismatch against the schema's `const` constraints (for example `policyRef` not `agent-policy`) → `invalid-request`. Graph rules (all refuse without repair, naming the rule): - Project `id` = `scope.projectId`; workspace `id` = `scope.workspaceId`; the workspace's `scope.projectId` must resolve to an active project. - A workspace registration's `projectRegistrationRef` must resolve to an **active** project registration in the workspace's owning project for the same `agentId` (`README:65`). - A workspace mission's `parentMissionRef` must be a project mission in that project; project missions have no parent (`README:66-68`). - A task's `missionRef` resolves in the selected project; `dependencies` are tasks; cycle checks run over exactly three edge sets, independently: `task.dependencies`, `mission.parentMissionRef`, `supersedes`. Excluded from cycle checks: `intentRef`, `subjectRefs`, `projectRegistrationRef`, `delegationRef`, all registryRefs and artifactRefs. - Cross-project execution selection refuses (`README:71`). ### 6.3 Revision history (F9, D-J resolved) Per identity `(kind, id, scope)` the bundle carries either **exactly one record at revision 1** or a **complete contiguous chain** 1..n where each revision r > 1 has `supersedes = {kind, id, scope, revision: r-1}`. A chain with a missing predecessor, a skipped number, or two records superseding the same predecessor refuses `invalid-request`; the inspector never invents history. Head = highest revision. Two resolution classes: - **Current-admission references** must name the head: `selection.*`, `assignment.taskRef`, `assignment.intentRef`, `task.intentRef`, `task.missionRef`, `task.dependencies`, `mission.parentMissionRef`, `registration.projectRegistrationRef`, `registration.delegationRef`, and every `proposal` ref. Naming a non-head revision refuses `stale-revision`. - **Historical references** may name any revision present in the bundle: `supersedes` and `decision.subjectRefs`. Absent → `missing-state`. This satisfies charter A3's stale-reference case with a real fixture (task intent pointing at decision rev 1 while rev 2 exists) without treating all history as forbidden. ## 7. Permission algorithm (F4, D-D, D-F resolved) Supported operations for permission preview: `work.read`, `file.read`, `file.change` (§10). Taskless set = `{work.read, file.read}` exactly (`d4696d09:cand/semantic-model.py:24`); `file.change` requires `selection.assignmentRef`. | # | Layer | Source | Mandatory? | |---|---|---|---| | L1 | agent ceiling | `agent-definition.policyRef` → `agent-policy` content | yes; `agent-definition.status` must be `enabled` | | L2 | project registration | active registration in `projectScope(projectId)` for `agentId`: **`scopeRoleRef` → `scope-role` content is the ceiling**; `restrictions` (nullable) narrows it further | yes (`README:99-101`) | | L3 | workspace registration | active registration in `workspaceScope(projectId, workspaceId)` for `agentId`, `projectRegistrationRef` = L2's exact record; `scopeRoleRef` ceiling; `restrictions` nullable narrowing | yes | | L4 | project policy | `project.policyRef` → `project-policy` content | yes | | L5 | workspace policy | `workspace.policyRef` → `project-policy` content (schema `const`) | yes; **no fallback to L3's role** | | L6 | assignment binding | one `assignment` = `selection.assignmentRef`; `status: selected`, `agentId` = selected agent, `taskRef` head in the selected workspace scope, `intentRef` → approved decision (head) whose `subjectRefs` include the task | required for non-taskless operations; contributes checks only, **it has no restrictions field** | | L7 | task | `task.restrictions` (nullable = no narrowing); independently `task.status ∈ {ready, active}` and `task.intentRef` → approved head decision | status/intent checks mandatory whenever L6 applies | | L8 | mission chain | each mission from `task.missionRef` up the parent chain: `restrictions` (nullable = no narrowing); `status: active` mandatory | as L7 | | L9 | execution | `selection.execution`: `restrictions` narrows; `unrestricted-simulation` is an explicit no-narrowing marker | yes; absent or null refuses `missing-state` (`README:132`, `semantic-model.py:21`) | Intersection: operations as set intersection; explicit empty array = none (`README:101`); `restrictions: null` on L2/L3/L7/L8 = no additional narrowing (`README:100`) but never satisfies a mandatory ceiling or status/intent check. Path grants: `path: null` = whole root (`README:102`); component-prefix matching on parsed segments (`semantic-model.py:30-34`); the intersection of two grant lists keeps, for each pair where one grant is a segment-prefix of the other, the narrower. Network: `none` dominates; `approved-endpoints` intersect by exact endpoint registryRef. No union across assignments (`contract:172`); exactly one assignment consulted (`README:134`). Refusal precedence: parse/shape/bounds (exit 2) → graph incoherence (exit 2) → unsupported operation or kind (exit 2) → mandatory layer or declaration missing (exit 3, `missing-state`) → registration/role absent or revoked (exit 3, `not-authorized`) → status/intent not current (exit 3, `not-authorized`, rule named) → operation or target outside the intersection (exit 3, `not-authorized`). First failure ends evaluation. ## 8. Proposal mode: assignment-change negative check (F5, D-G, D-I resolved) Increment 1 implements **no success path**. The evaluation order is fixed and every branch ends in `refused` or `unresolved`; `allowed` is unreachable by construction, and a unit test asserts that. 1. Original scope authority: the requester holds an active L2/L3 registration in the subject assignment's workspace whose role ceiling ∩ restrictions include `assignment.change`. Else `not-authorized` / `requester-lacks-original-scope-authority`. 2. Target scope authority: the same in the target task's project and workspace. Else `not-authorized` / `requester-lacks-target-scope-authority`. 3. Delegation: `delegationRef` → head decision, `decisionKind: delegation`, `outcome: approved`, `delegatedOperations ∋ assignment.change`, `subjectRefs` include the requester's agent-definition **and** the subject assignment or its task (recipient and subject bounds). Else `not-authorized` / `delegation-not-applicable`. A `message` with no delegation refuses with rule `message-is-not-authority` (`map:310`). 4. Recorded change: `changeDecisionRef` → head decision, `decisionKind: assignment-change`, `outcome: approved`, `subjectRefs` include the subject assignment and the target task; its `basisRef` must be a declared artifact but **is not itself authorization**. Else `not-authorized` / `change-not-recorded`. 5. Current intent: the subject assignment's `intentRef` and the target task's `intentRef` resolve to approved head decisions. Else `stale-revision` or `not-authorized` as applicable. 6. If 1–5 pass: result `unresolved`, reason `unknown-effects`, rule `runtime-reconciliation-required`. Stopping and reconciliation are runtime facts (`map:311-314`); the bundle has no field to assert them and the r1 `runtimeObservations` field is withdrawn. In every branch the output echoes `selection.assignmentRef` unchanged as `selectedAssignmentRef` and the bundle bytes are untouched. Requester model is agent-only in increment 1 (D-G); delegation chains are not modelled (D-I). ## 9. Output and exit classes (F6) Closed result schema; every emitted field is listed here and nothing else is emitted: ``` { "disclaimer": "SYNTHETIC PREVIEW — NO LIVE EFFECTS", "preview": "preview: no live registrations or permission grants", // REVIEW.md:126 "bundleVersion": 1, "authentication": "not-modelled", "declaredSimulationInputs": { "artifacts": , "authorizations": }, "result": "allowed" | "refused" | "unresolved" | "invalid", "reason": , "rule": | null, "selection": { "agentId", "projectId", "workspaceId", "assignmentRef" }, "operation": { "name", "target" }, "consulted": [ , ... ], // identities only, sorted "proposal": null | { "result", "reason", "rule", "selectedAssignmentRef" } } ``` Text output is a pure rendering of this object; both begin with the disclaimer, on every exit class. No payload bodies, no parser input bytes (byte offset and error class only), no timestamps, no random ids, keys sorted. Reason codes: the RUNTIME closed set (`d4696d09:cand/RUNTIME.md:97`) plus inspector-only `unsupported-kind` and `io-failure`, documented as not runtime API codes. Exit classes (`map:275-276`, charter): 0 allowed; 2 malformed, graph-incoherent, unsupported kind or operation; 3 refused or unresolved; 4 I/O failure. Flag: I place unsupported operations in exit 2 with reason `unsupported-capability` because they are not simulated permission refusals; if darkwing prefers exit 3 for them, nothing else in this note changes (D-C). ## 10. Supported kinds and operations (F6) Record kinds: `agent-definition`, `project`, `workspace`, `registration`, `mission`, `task`, `assignment`, `decision`. `session`, `context-source`, `adapter-capability` present in `records` refuse `unsupported-kind`. Operations (from the 29-name catalog, `d4696d09:cand/records.schema.json` `$defs/operation`): permission previews for `work.read` (target `null`), `file.read` and `file.change` (target `{root:"workspace", path}`); proposal negative check for `assignment.change` (§8). The other 25 catalog names are recognized and refuse `unsupported-capability`; never exit 0. `execution.stop` does not exist in the catalog (`execution.control` does); the r1 remark is withdrawn. Names outside the catalog refuse `invalid-request`. ## 11. Physical paths (D-B, D-H resolved) Unchanged from r1: `scripts/foundation-inspect.mjs` (CLI, future `packages/mosaic`); `scripts/foundation/{strict-json,canonical,resolve}.mjs` (pure, future `packages/config`; import only each other and `node:crypto`); `scripts/foundation/fixtures/`; `scripts/foundation/*.test.mjs`; `scripts/test-foundation.sh`; plus the new verification harness `scripts/foundation/verify-schema.py` (§12.1). No `packages/` scaffolding, no root file, no `.sh` shim, nothing imported from `scripts/mosaic-config.mjs` (`map:240`). Layout follows the ROADMAP M20 doctrine (`d4696d09:docs/plans/ROADMAP.md:142-159`). ## 12. Test plan ### 12.1 Required differential schema verification (F7) `scripts/foundation/verify-schema.py`: Python 3 stdlib plus the already installed `jsonschema` (measured 4.26.0); **no installation**. If `import jsonschema` fails the suite exits non-zero with `verification dependency missing`; it never skips. The harness re-declares the two format checkers exactly as `check.py:36-64` (calendar round-trip `date-time`, `mosaic-relative-path`), loads the pinned `cand/records.schema.json` from the tree, and compares, for every document in a corpus, the jsonschema verdict with the Node validator's verdict (obtained by running `node scripts/foundation/validate-record.mjs` over the corpus and reading its JSON verdict list). Any disagreement fails. Corpus: every fixture record; the candidate's own `records.fixtures.json` cases (schema-valid and invalid) for the eight supported kinds; and a mutation set generated deterministically from each valid record: drop each required field; add one unknown key at each object level; `createdAt` = 2026-02-30 and 2028-02-29; paths `../x`, `a//b`, `a/./b`, `/abs`, backslash, a control character, 4097 bytes, `é/x` (must be **accepted** by both); revision 1 with non-null `supersedes`; revision 2 with null `supersedes`; approved delegation with empty `delegatedOperations`; non-delegation with non-empty `delegatedOperations`; `network: none` with an endpoint; `approved-endpoints` with none; `status: ended` with null `endReason` and vice versa; each typed reference with the wrong `kind`/`registry`. The runtime remains dependency-free; only verification needs Python. ### 12.2 Non-effect and privacy observations (F8) - Roots are separated: observed roots `$SANDBOX/home`, `$SANDBOX/cwd`, `$SANDBOX/fixtures`, `$SANDBOX/data-root`; harness writes go only to `$SANDBOX/harness`. The CLI runs with `env -i PATH=… HOME=$SANDBOX/home NO_COLOR=1`, cwd `$SANDBOX/cwd`, `MOSAIC_CONFIG` unset. - Inventory before and after every invocation over each observed root: `find -printf '%p %y %s %m %U %i\n' | sort` plus SHA-256 of every regular file; compared exactly. A canary config path under `home/.config/mosaic-dev` is asserted absent after a missing-file run (no bootstrap, A8). - Static boundary: a unit test reads the three pure modules and asserts their import specifiers are exactly the allowed set and that `process`, `fs`, and `Date` do not appear; a second asserts the CLI imports no `mosaic-config`. - Canaries (`CANARY-DO-NOT-PRINT-n` in every unrelated payload and in the second workspace's records) are grepped from all captured output. Stated limit: canaries and inventories demonstrate this tool's bounded behaviour on these inputs; they do not prove absence of all reads or OS-level containment (charter test-plan paragraph). - Determinism: each fixture runs twice, byte-identical; `--json` and text agree on `result`/`reason`/`rule`; golden files compared exactly. ### 12.3 Units (`node --test`) Canonicalizer V1–V5; strict JSON (duplicate key, depth, size, fatal UTF-8); each graph rule in §6.2–6.3; each layer in §7 including "null restrictions do not satisfy a mandatory ceiling", "empty array is none", "no union across assignments", "missing execution layer refuses"; §8 steps 1–6 and the `allowed`-is-unreachable assertion. ## 13. Fixtures and the first demonstration `demo.bundle.json`: project P1 with workspaces W1 and W2; project P2; agent A enabled, registered in P1 and W1 only, with scope-role V1 (reader) at W1; one active mission and one ready task in W1 with approved plan-approval intents; one selected assignment for A; agent B registered in P2 with a delegation decision that does not cover `assignment.change`; all revisions 1; a second variant carries decision rev 1→2 for the stale case. | Fixture | Expected | |---|---| | `demo-read-w1` | 0 allowed `file.read docs/notes.md` | | `demo-change-w1-refused` | 3 not-authorized, role ceiling V1 lacks `file.change` | | `demo-read-w2` | 3 not-authorized, `workspace-registration-missing`, no W2 payload text | | `demo-cross-project` | 3 not-authorized, cross-project selection | | `demo-reassign-message-only` | 3 not-authorized, `message-is-not-authority`, selection unchanged | | `demo-reassign-fully-recorded` | 3 unresolved, `unknown-effects`, `runtime-reconciliation-required` | | `neg-stale-intent` | 3 stale-revision (intent names decision rev 1, rev 2 present) | | `neg-missing-predecessor`, `neg-fork`, `neg-duplicate-identity`, `neg-cycle-{task,mission,supersedes}`, `neg-wrong-owner` | 2 invalid-request, rule named | | `neg-no-execution-layer`, `neg-missing-role-content`, `neg-undeclared-artifact` | 3 missing-state | | `neg-empty-ops`, `neg-two-assignments` | 3 not-authorized | | `neg-unsupported-op-execution.control`, `neg-unsupported-kind-session` | 2 unsupported-capability / unsupported-kind | | `neg-unknown-key`, `neg-dup-json-key`, `neg-bad-utf8.bin`, `neg-bad-path`, `neg-bad-calendar` | 2 invalid-request | | symlink, FIFO, missing file | 4 io-failure, no bootstrap | ## 14. Resolved decisions D-A..D-J | Id | Resolution | |---|---| | D-A | Node ESM, zero runtime deps; Python/jsonschema for mandatory verification only (§4, §12.1) | | D-B | `scripts/foundation/` with M20 owner headers; no `packages/` (§11) | | D-C | Graph incoherence and unsupported kind/operation → exit 2; permission refusal/unresolved → 3. Only open sub-choice: exit class for `unsupported-capability` operations (§9 flag) | | D-D | Taskless = `{work.read, file.read}`; `work.propose` is not implemented at all in increment 1 (§10) | | D-E | Registry content = `{restrictions}` simulation stand-in, digest per §6.1 | | D-F | `selection.execution` is mandatory and explicit; no null default (§7 L9) | | D-G | Requester is an agent with registrations in both scopes (§8) | | D-H | `scripts/foundation-inspect.mjs`, no `.sh` shim | | D-I | Delegation: recipient and subject bounds checked; no chains (§8 step 3) | | D-J | Revision-1-only or complete contiguous chain; historical vs current-admission classes (§6.3) | Remaining design choice for darkwing, stated precisely: whether unsupported catalog operations exit 2 or 3 (§9). Everything else in this note is a concrete recommendation with no open alternative. ## 15. Feasibility verdict Still feasible as a bounded, dependency-free increment. The corrections increase scope in three places: mandatory role ceilings and execution layer (§7), the full-history resolver with two reference classes (§6.3), and the required differential verification harness (§12.1). None requires an npm change, a package move, live state, or runtime coupling. The largest new risk is the differential harness itself drifting from the candidate checker's format semantics; re-declaring the two format checkers verbatim from `check.py:36-64` and asserting the same four sentinel cases (`check.py:66-69`) is the mitigation. This note is evidence for darkwing's charter. It is not approval, and it authorizes no implementation.