# FI-ROCKO-1 — Foundation synthetic inspector: measured feasibility note Author: rocko (Claude Fable 5.1, Archify lane author seat). Requested by: darkwing (FI-ROCKO-1, 2026-09-06). Issue: #53. Status: planning evidence only. This note proposes; it does not approve, implement, or bind. Every choice below is a recommendation for darkwing to accept, alter, or reject in the frozen charter. ## 0. Admission **FI-ROCKO-1 is admitted. No blocker.** - Compatibility: my current owner goal (Archify C1 rev 3, HELD for Jason's T2/T3 rulings) is a waiting state with no lane action due; this note displaces nothing. I take no supervisory authority over darkwing, filbert, or Jason's queue; Jason may veto or discard this note at any time. - Authority basis observed, not assumed: `docs/plans/CURRENT.md` (working tree) records Jason's acceptance of the map and "continue with filbert/rocko"; the charter draft restates it. I did not verify this with Jason directly. - Scope honoured: this file is the only repository write. No commit, push, checkout, reset, live/runtime test, credential read, or `~/.mosaic` investigation was performed. Dewey's `extensions/**`, `.pi/**`, and scripts were not touched. Filbert's verdict files were not opened for editing. ## 1. Pinned inputs | Input | Identity | How read | |---|---|---| | Accepted map `docs/plans/2026-09-06_agent-project-workspace-foundation.md` | commit `7345f330fc6bfae5aa1d896c78cfb7cbe62efbae` | `git show 7345f33:…` | | Source/plan baseline (candidate schema, semantic-model.py, check.py, README, REVIEW, RUNTIME, phase-2 contract, ROADMAP, foundation plan) | commit `d4696d09eb1b5dcf1028f30db2cd63735f51cb16` | `git show d4696d09:…` | | Charter draft 1 `docs/plans/2026-09-06_foundation-inspector-charter.md` | untracked; SHA-256 `fdcdf4df8bf30b9fbfff960f9b1d4160ce7e6bbc4b8bb601a93cda13b56c8996` at write time | working tree | | Filbert map verdict (FM-FILBERT-1) | SHA-256 `6b08c6fac0718d3db527cf9ffbfab49407e7b289d09782f5d1d0e26493eaabb3` (as cited in the charter) | read only | The charter is a moving draft: an earlier read in this session hashed to `9c065ef1…`. Recommendations below target the `fdcdf4df…` text. If the frozen charter differs materially, this note must be re-checked, not assumed valid. Citations use `commit:path:line`. `cand/` abbreviates `docs/plans/foundation-v1-candidate/`. ## 2. Repository facts that constrain the design - Host toolchain: Node v26.8.1 with `node:test` available; Python 3.12.8 with `jsonschema` 4.26.0 importable. `package.json` declares one dependency (pi 0.84.4); no `node_modules` is present in the tree. `RELEASE` = 0.0.12. - `scripts/` is flat; no `packages/` tree exists yet. ROADMAP places the package layout under M20 (`d4696d09:docs/plans/ROADMAP.md:114`) with the layout doctrine and phase-1 sequencing at `:142-145` and the target tree at `:152-159` (`packages/config` at `:159`). - The map's recommended increment forbids npm or Turbo changes (`7345f33:docs/plans/2026-09-06_agent-project-workspace-foundation.md:278`) and proposes exit classes 0/2/3/4 (`:275`). - Existing validation idiom is hand-rolled and closed: `scripts/mosaic-task.mjs` (`rejectUnknownKeys`, `fail(2, …)`). `scripts/mosaic-config.mjs` reads `$MOSAIC_CONFIG` (`:42-43`) and refuses symlinks (`:54`); importing it runs config resolution, which the charter forbids for the inspector. - Existing suite style: `scripts/test-config.sh` uses a `mktemp` sandbox, `check`/`expect_exit` helpers, and `NO_COLOR`. - `cand/FINDINGS.md` records an `ajv`/`fast-uri` audit concern; the candidate's own checker uses Python `jsonschema` (`cand/check.py`). - The candidate's fingerprint rule is restricted-domain canonicalization, not full RFC 8785 (`d4696d09:cand/README.md:265-288`, ASCII-only vectors `:281-282`; `check.py` sorts keys, uses compact separators, ASCII-only, sorts skills by id, digest = `"sha256:" + hex(SHA-256(UTF-8))`). ## 3. Language and dependency pin (recommendation) **Recommend: Node ESM (`.mjs`), zero new dependencies, hand-rolled closed validation.** Pin: the repository's existing Node engine (v26.8.1 on host); no `package.json` change; no `ajv`, no `jsonschema` at runtime. Why: - Matches the map (`:278`, no npm/Turbo change) and the repo's validation idiom. - Avoids the `ajv`/`fast-uri` audit issue and avoids adding a runtime dependency for a preview tool that must be reproducible offline. - Node gives `TextDecoder('utf-8', { fatal: true })` for malformed-UTF-8 refusal, `node:crypto` for SHA-256, and `node:test` for units, all built in. - A hand-rolled closed validator is smaller than the candidate schema but must be **derived from** `cand/records.schema.json` field-by-field. To keep it honest, the test suite should run the Python candidate checker over every fixture bundle's records as a cross-check when `python3 -c "import jsonschema"` succeeds, and skip with a visible notice otherwise (see §9). Alternative (viable, not preferred): Python 3.12 stdlib only, reusing `check.py`'s canonicalizer verbatim. Rejected because the CLI surface of the stack is Node/bash and because the charter says not to promote the author-only Python model into the inspector. This is unresolved decision D-A. Explicitly not recommended: JSON Schema at runtime via `ajv` (audit issue, dependency) or via Python `jsonschema` in a Node CLI (two runtimes). ## 4. Physical source paths (recommendation, M20-consistent) No `packages/` scaffolding in this increment. Files land in `scripts/`, which is the existing flat layout, with a comment header naming the logical owner so M20 phase 1 can move them without rewriting. | Path | Logical owner (M20 target) | Contents | |---|---|---| | `scripts/foundation-inspect.mjs` | `packages/mosaic` (CLI presentation) | argv parsing, file read, output rendering, exit classes | | `scripts/foundation/strict-json.mjs` | `packages/config` | strict JSON reader: fatal UTF-8, duplicate-key refusal, depth/size limits, byte-offset-only errors | | `scripts/foundation/canonical.mjs` | `packages/config` | restricted-domain canonicalizer + `sha256:` digest, out-of-domain refusal | | `scripts/foundation/resolve.mjs` | `packages/config` | bundle validation, reference resolution, graph checks, permission intersection, proposal evaluation. Pure: no I/O, no env, no clock | | `scripts/foundation/fixtures/*.json` | test data | demo bundle plus negative fixtures | | `scripts/test-foundation.sh` | suite | sandboxed CLI/privacy/non-effect suite, invokes `node --test scripts/foundation/*.test.mjs` | | `scripts/foundation/*.test.mjs` | unit tests | `node:test` units for the three pure modules | Rules: - `resolve.mjs`, `canonical.mjs`, `strict-json.mjs` import only each other and `node:crypto`. Never `mosaic-config.mjs`, never `process.env`, never `Date`. - `foundation-inspect.mjs` is the only file that touches `process.argv`, `fs`, `stdout`, `stderr`, and `process.exit`. It reads `process.env` only to honour `NO_COLOR`; it never reads `MOSAIC_CONFIG`, `HOME`, or the data root. - Root stays bootstrap-only (AGENTS.md invariant 1): nothing is added to root. - The command name `mosaic foundation inspect ` from REVIEW (`d4696d09:cand/REVIEW.md:123`) is the eventual `packages/mosaic` surface. In this increment the wrapper is `scripts/foundation-inspect.mjs`; a `foundation-inspect.sh` shim is unnecessary. Naming is decision D-H. ## 5. Bundle contract (exact fields, proposed to freeze) One file, one closed object. Every object is closed: unknown keys refuse. Duplicate keys refuse at parse time. All strings must be ASCII printable when they participate in digests (§6); identifiers are `^[a-z0-9][a-z0-9-]{0,63}$`. ``` { "bundleVersion": 1, "kind": "foundation-inspector-bundle", "simulation": true, // must be literally true; anything else refuses "records": [ , ... ], // candidate envelope records, ≤256 "registries": [ , ... ], // ≤256 "selection": { "agentId": "", "projectId": "", "workspaceId": "", "assignmentRef": | null, "launchRestrictions": | null // simulated execution layer, D-F }, "operation": { "name": "", "target": | null }, "proposal": null | { "kind": "assignment-change", "requesterAgentId": "", "subjectAssignmentRef": , "targetTaskRef": , "delegationRef": | null, // decision, decisionKind delegation "changeDecisionRef": | null, // decision, decisionKind assignment-change "message": "" | absent, // informational only; never authority "runtimeObservations": { "originalExecutionStopped": true|false|null, "effectsReconciled": true|false|null } } } ``` `` is exactly the candidate envelope (`d4696d09:cand/records.schema.json`): `{schemaVersion:1, kind, id, scope, revision, createdAt, createdBy, supersedes, authorizationRef, payload}`, closed. `createdAt` is carried and validated as a string but never used in calculations (charter: no timestamps in results). `` = `{kind, id, scope, revision}`; `` = `{registry, id, revision, digest}`; `` = `{operations[], readPaths[], writePaths[], network, endpointRefs[]}` with `pathGrant` `{root:"workspace", path|null}`; all as defined in the candidate schema. `` for file operations: `{root:"workspace", path:""}`. For record operations: a ``. For operations that take no target: `null`. An operation whose target shape does not match its name refuses `invalid-request`. `` has two shapes: - Declared: `{registry, id, revision, digest}` for harness, settings, context-content, endpoint. Existence and exact-match only. - Content-bearing: `{registry, id, revision, digest, content}` for `scope-role`, `agent-policy`, `project-policy`. `content` is `{restrictions: }`; `digest` must equal the canonical digest of `content` (§6) or the bundle refuses `invalid-request`. These are the "separately identified mock policy inputs" the charter requires. The candidate does not define registry content shapes; this is decision D-E. Bounds (refuse `invalid-request` before any calculation): | Bound | Value | |---|---| | File size | ≤ 1 MiB (aligns with RUNTIME frame limit `d4696d09:cand/RUNTIME.md:41` and contract `…phase2-contract.md:116`) | | `records` | ≤ 256 | | `registries` | ≤ 256 | | Any array | ≤ 1024 items (contract `:116`) | | Nesting depth | ≤ 32 | | Any string | ≤ 4096 bytes; path strings ≤ 4096 bytes | | Input file | regular file only; symlink, directory, FIFO, device refuse `invalid-request` (matches `mosaic-config.mjs:54` doctrine) | | Path argument | exactly one positional bundle path; relative paths resolved against cwd but never searched; no defaults | Explicitly absent by design: no `$ref`, no includes, no URLs, no `env`, no `credentials`, no `authenticated`/`authorized` booleans anywhere. Presence of any of those keys is an unknown-key refusal, not a warning. ## 6. Reference and digest algorithm ### 6.1 Canonical digest (restricted domain) Implement `canonical.mjs` as the candidate's rule, not JCS: 1. Domain: objects, arrays, strings, booleans, null, and integers within `Number.isSafeInteger`. Floats, `-0`, non-ASCII strings, control characters other than those JSON must escape, and values outside the domain **refuse** with `unsupported-capability`. This is stricter than JCS and is the documented restricted domain (`d4696d09:cand/README.md:265-288`). 2. Object keys sorted by UTF-16 code unit order (same as `sort_keys=True` on ASCII keys), compact separators, no whitespace. 3. Set-valued arrays are sorted by a stable key before serialization: `skillRefs` by `id`, `instructionRefs` by `id`, `operations` lexically, `readPaths`/`writePaths` by `path` with `null` first, `endpointRefs` by `id`. Sequence-valued arrays (`dependencies`, `subjectRefs`, `evidenceRefs`) keep order. The list of set-valued fields is enumerated in code and in the frozen charter; anything not listed is a sequence. 4. Digest = `"sha256:" + lowercase hex SHA-256 of the UTF-8 bytes`. 5. Test vectors: reproduce the README's vectors (`:281-282`) and add one out-of-domain vector per rejected class. No claim of RFC 8785 conformance is made in code, docs, or output. ### 6.2 Reference resolution (bounded resolver, `resolve.mjs`) Index build: - Key `(kind, id, scope)` → list of revisions. Two records with the same `(kind, id, scope, revision)` refuse `invalid-request` (duplicate identity, A3). - `supersedes` chains must be contiguous from revision 1: revision *n* must supersede exactly `(kind, id, scope, n-1)`; gaps, forks (two records superseding the same predecessor), or a supersedes pointing outside the identity refuse `invalid-request`. The bundle may carry only the head revision if it carries no history at all for that identity (D-J). - `id` must agree with `scope` per README `:64`; mismatch refuses. Reference check (every `recordRef` and `registryRef` in every payload): - Exact match on all fields; `digest` compared byte-for-byte after §6.1 recomputation for content-bearing entries. A ref to a superseded revision refuses `stale-revision`. A ref to an absent identity refuses `missing-state`. Kind mismatch (e.g. `policyRef` not pointing at `agent-policy`) refuses `invalid-request`. Graph rules (all refuse without repair; the violated rule name is emitted): - `workspace.payload` must reference exactly one active `project` (README `:65`; charter "exactly one project owns a workspace"). - A workspace `registration` for agent X must reference, via `projectRegistrationRef`, an **active** project `registration` for the same agent X in the owning project (README `:65`). Missing or revoked → refuse. - `mission.parentMissionRef` must resolve to a mission in the same project; a task's `missionRef` must resolve to a mission in the selected project. - `assignment` must bind one task and one agent; `intentRef` must resolve to a `decision` with `decisionKind ∈ {plan-approval, delegation, assignment-change}` whose `subjectRefs` include the task. Otherwise `not-authorized`. - Cycle checks (A3, "document which edges participate"): DFS over exactly three edge sets, evaluated independently: (i) `task.dependencies`, (ii) `mission.parentMissionRef`, (iii) `supersedes`. A cycle in any of them refuses `invalid-request`. Edges deliberately excluded from cycle checks: `intentRef`, `authorizationRef`, `evidenceRefs`, `subjectRefs`, all `registryRef`s (they point outside the record graph). - Cross-project execution selection refuses (README `:71`). ## 7. Permission algorithm Inputs: the resolved graph, `selection`, `operation`. Output: `allowed` or `refused` + reason code + violated rule + the identities consulted. Never the payload bodies. Taskless set: `{work.read, file.read}` (from `semantic-model.py`). Every other operation requires `selection.assignmentRef` to be non-null (D-D asks whether `work.propose` belongs in the taskless set; recommendation: no, keep the candidate's set). Layers, in fixed order, each producing a `` view or a refusal: | # | Layer | Source | Missing → | |---|---|---|---| | L1 | agent policy | `agent-definition.policyRef` → registry `agent-policy.content.restrictions` | refuse `missing-state` | | L2 | project registration | active `registration` for agent in `projectId`, `restrictions` | refuse `not-authorized` (A2) | | L3 | workspace registration | active `registration` for agent in `workspaceId` whose `projectRegistrationRef` is L2's record | refuse `not-authorized` (A2) | | L4 | project policy | `project.policyRef` → registry `project-policy` | refuse `missing-state` | | L5 | workspace policy | `workspace.policyRef` if present in candidate payload; else scope-role of L3 | refuse `missing-state` | | L6 | assignment | `selection.assignmentRef`; must be `status: selected`, agent = `agentId`, task in `projectId` | non-taskless op → refuse `not-authorized` | | L7 | task | `task.restrictions` | null = no narrowing | | L8 | mission chain | each mission up the `parentMissionRef` chain, `restrictions` | null = no narrowing | | L9 | simulated execution layer | `selection.launchRestrictions` | null = no narrowing; when present, narrows only (D-F) | Intersection semantics (README `:96-142`, contract `:158-193`): - `operations`: set intersection. An explicit empty array is **none** (`:101`). A `null` restrictions object at L7–L9 means "no narrowing"; at L1–L5 a `null` refuses (required layer, `:133`). - `readPaths`/`writePaths`: a grant `{root:"workspace", path:null}` is the whole root (`:102`). Intersection of two grant lists = the set of pairs where one is a component-prefix of the other, keeping the narrower. Component-prefix means path segments, not string prefix (`a/b` covers `a/b/c`, not `a/bc`). Target paths must be relative, normalized, contain no `..`, no empty segment, no leading `/`, no `\`, no NUL; otherwise refuse `invalid-request` before any permission result (A5). - `network`: `none` ∧ anything = `none`; `approved-endpoints` ∧ `approved-endpoints` = intersection of `endpointRefs` by exact ref. - Never union across assignments (contract `:172`); exactly one assignment is consulted (README `:134`). A second `selected` assignment for the same agent+task in the bundle is not an error, but only the selected one is used. - Result for the operation: allowed iff `operation.name ∈ operations` and, for file operations, the target path is covered by the corresponding `readPaths`/`writePaths` after intersection, and for network operations the endpoint is covered. Refusal precedence (deterministic): parse/shape errors (exit 2) → graph incoherence (exit 2, D-C) → missing required layer (exit 3, `missing-state`) → registration absent (exit 3, `not-authorized`) → operation not in intersection (exit 3, `not-authorized`). The first failing check ends evaluation; the output names only that rule. ## 8. Proposal mode (cross-lane reassignment, map `:290-314`) Evaluated only when `proposal !== null`, after the base permission result: 1. `requesterAgentId` must hold an active registration in the target task's project (authority in target scope) — else refuse `not-authorized`. 2. `delegationRef` must resolve to a `decision` with `decisionKind: delegation`, outcome approved, `delegatedOperations` including `assignment.change`, and `subjectRefs` covering the subject assignment or its task — else refuse `not-authorized`. A `message` with no delegation refuses with the same code and the violated rule "message-is-not-authority" (A7). 3. `changeDecisionRef` must resolve to a `decision` with `decisionKind: assignment-change` whose `subjectRefs` include both the subject assignment and the target task and whose `basisRef` is the delegation decision — else refuse `not-authorized`. 4. `runtimeObservations`: if either field is `null`, result is **unresolved** with reason `unknown-effects` (charter: report reconciliation-required rather than simulate success). If `originalExecutionStopped` is `false` or `effectsReconciled` is `false`, refuse `unsafe-replacement`. 5. On any outcome, the output's `selection.assignmentRef` is echoed unchanged and the bundle bytes are never written (A6, A7). Exit class for unresolved: 3 (it is a refusal to affirm), with `result: "unresolved"` in JSON and text so it cannot be read as a permission refusal. Requester model in increment 1 is agent-only (D-G). ## 9. Output and exit classes - Text (default) and `--json` must be generated from the same result object; the text renderer is a pure function of the JSON. First line of both, always: `SYNTHETIC PREVIEW — NO LIVE EFFECTS` (text) / `"disclaimer": "SYNTHETIC PREVIEW — NO LIVE EFFECTS"` (JSON), including on every refusal and on exit 2/4. REVIEW's phrasing "preview: no live registrations or permission grants" (`d4696d09:cand/REVIEW.md:126`) is emitted as the second line. - JSON result shape (closed): `{disclaimer, bundleVersion, result: allowed | refused | unresolved, reason: , rule: , selection: {agentId, projectId, workspaceId, assignmentRef}, operation, consulted: [], proposal: null | {result, reason, rule, selectedAssignmentRef}}`. No payload bodies, no paths other than the requested target, no input snippets, no timestamps, no random ids, keys sorted. - Reason codes: the RUNTIME closed set (`d4696d09:cand/RUNTIME.md:97`: allowed, invalid-request, not-authorized, stale-revision, missing-state, already-active, control-conflict, retired, unsupported-capability, audit-unavailable, unknown-effects, unsafe-replacement, request-id-conflict, expired) plus inspector-only codes `unsupported-kind`, `unresolved`, `io-failure`. Inspector-only codes are documented as not being runtime API codes (charter). - Exit classes: 0 allowed; 2 malformed input (parse, shape, bounds, graph incoherence, unsupported kind/operation); 3 simulated refusal or unresolved; 4 I/O failure (missing file, unreadable, not a regular file). Exit 4 output names the path as given on argv and nothing else; no bootstrap, no config read, no directory creation (A8). - Errors from `strict-json.mjs` report byte offset and error class only, never the surrounding bytes. ## 10. Supported record kinds and operations Supported kinds in increment 1: `agent-definition`, `project`, `workspace`, `registration`, `mission`, `task`, `assignment`, `decision`. The remaining candidate kinds refuse `unsupported-kind` (exit 2) when present in `records`, rather than being ignored. Operation vocabulary is the candidate's 29-name enum (`d4696d09:cand/RUNTIME.md:55`); a name outside it refuses `invalid-request`. Operations whose semantics need runtime state the inspector cannot model (stop, durability, audit) are accepted as names for permission intersection only; the output states `"scope": "permission-only"` for them so a `0` is not read as an executability claim. ## 11. Fixtures and the first demonstration `scripts/foundation/fixtures/demo.bundle.json` implements the charter's scenario: project P1 with workspaces W1 and W2; project P2; agent A registered in P1 and W1 only; one mission, one task in W1, one selected assignment for A; agent B registered in P2 with a delegation decision that does **not** cover `assignment.change`. Derived bundles (same records, different selection or proposal) cover: | Fixture | Expectation | |---|---| | `demo-read-w1` | exit 0, allowed, `file.read` on `docs/notes.md` | | `demo-read-w2` | exit 3, not-authorized, rule workspace-registration-missing, no W2 payloads in output | | `demo-cross-project-exec` | exit 3, not-authorized, cross-project selection | | `demo-reassign-message-only` | exit 3, not-authorized, message-is-not-authority, selection unchanged | | `demo-reassign-no-observations` | exit 3, unresolved, unknown-effects | | `neg-duplicate-identity`, `neg-stale-ref`, `neg-missing-parent`, `neg-cycle-task`, `neg-cycle-mission`, `neg-fork-supersedes` | exit 2, named rule | | `neg-unknown-key`, `neg-dup-json-key`, `neg-bad-utf8.bin`, `neg-bad-path` | exit 2, before any permission text | | `neg-empty-ops-layer` | exit 3, explicit empty = none | | `neg-union-two-assignments` | exit 3, second assignment does not widen | Fixture digests are generated by a checked-in helper (`node scripts/foundation/canonical.mjs --digest `) and the suite fails if a stored digest disagrees with a recomputed one, so fixtures cannot drift silently. ## 12. Test harness (isolated, non-effect, privacy) `scripts/test-foundation.sh`, same shape as `scripts/test-config.sh`: - Runs under `env -i PATH=… HOME= NO_COLOR=1 node …`; `MOSAIC_CONFIG` unset; no `~/.config/mosaic-dev` or data root exists in the sandbox. A canary `HOME/.config/mosaic-dev/config.json` is **absent** and the test asserts it stays absent (no bootstrap on missing input, A8). - Non-effect: before and after every CLI invocation, `find -newer ` must be empty and `sha256sum` of each bundle must be unchanged (A6). Also asserts no `.pi`, `roles`, or `state` directory appears under the sandbox. - Privacy: fixture payloads contain a canary string (`CANARY-DO-NOT-PRINT-`) in every unrelated record body and in W2's payload; every stdout/stderr capture is grepped for it and must be clean (A2, charter diagnostics rule). Exit-2 parse errors are checked for absence of any input bytes beyond the byte offset. - Determinism: each fixture runs twice; outputs must be byte-identical; `--json` and text must agree on `result`/`reason`/`rule` (A8). - Golden files: `scripts/foundation/fixtures/expected/.{txt,json}`, compared exactly. - Units (`node --test scripts/foundation/*.test.mjs`): canonicalizer vectors and out-of-domain refusals; strict-json duplicate key, depth, size, fatal UTF-8; resolver graph rules one per test; permission intersection tables (empty=none, null=whole root, component-prefix, no union); proposal rules. - Cross-check (optional, skipped visibly when Python `jsonschema` is absent): `python3 cand/check.py` over each fixture's `records` to confirm the hand-rolled validator has not drifted from the candidate schema. - The suite does not touch Dewey's extension installation, `~/.mosaic`, the network, or any engine/process launch. It states this in its header. The existing required suites remain untouched and must stay green. ## 13. Unresolved decisions for the frozen charter | Id | Decision | Recommendation | |---|---|---| | D-A | Language: Node ESM vs Python stdlib | Node ESM, zero deps (§3) | | D-B | Directory layout before M20 | `scripts/foundation/` with owner headers, no `packages/` (§4) | | D-C | Exit class for graph incoherence (cycle, stale, duplicate) | 2 (malformed input), not 3, so it cannot be mistaken for a policy refusal | | D-D | Taskless operation set | `{work.read, file.read}` exactly; `work.propose` stays assigned-only | | D-E | Registry content shapes for scope-role / agent-policy / project-policy | `{restrictions}` only, declared simulation-only in the charter; candidate schema does not define these | | D-F | Simulated execution layer | `selection.launchRestrictions`, narrowing-only, default null | | D-G | Requester model in proposal mode | agent-only (agentId + registration); no human/operator principal in increment 1 | | D-H | Wrapper naming | `scripts/foundation-inspect.mjs`, no `.sh` shim | | D-I | Delegation enforcement depth | require `delegatedOperations ∋ assignment.change` and subject coverage; do not model delegation chains | | D-J | Whether bundles carry revision history | permit either head-only or contiguous full history per identity; forbid partial history | One further point the charter should settle explicitly: whether a `0` result for stop/durability/audit-class operations is acceptable at all in increment 1, or whether those names should refuse `unsupported-capability` outright. My recommendation is the latter for `execution.stop`-class names, to keep A9's "understands the limitation" honest. ## 14. Feasibility verdict Feasible as a bounded increment with no new dependencies, no package migration, and no runtime coupling. Estimated size: three pure modules of a few hundred lines each, one CLI wrapper, roughly twenty fixtures, one bash suite, and unit tests. The main risk is drift between the hand-rolled validator and the candidate schema; the Python cross-check in §12 and the enumerated set-valued field list in §6.1 are the mitigations. Nothing here weakens the accepted semantics; where the candidate is silent (D-E, D-F), the proposal is narrowing-only and simulation-labelled. This note is evidence for darkwing's charter. It is not approval, and it authorizes no implementation.