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.
467 lines
28 KiB
Markdown
467 lines
28 KiB
Markdown
# 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 <bundle.json>` 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": [ <record>, ... ], // candidate envelope records, ≤256
|
||
"registries": [ <registryEntry>, ... ], // ≤256
|
||
"selection": {
|
||
"agentId": "<id>",
|
||
"projectId": "<id>",
|
||
"workspaceId": "<id>",
|
||
"assignmentRef": <recordRef> | null,
|
||
"launchRestrictions": <restrictions> | null // simulated execution layer, D-F
|
||
},
|
||
"operation": { "name": "<one of the 29 operation names>", "target": <target> | null },
|
||
"proposal": null | {
|
||
"kind": "assignment-change",
|
||
"requesterAgentId": "<id>",
|
||
"subjectAssignmentRef": <recordRef>,
|
||
"targetTaskRef": <recordRef>,
|
||
"delegationRef": <recordRef> | null, // decision, decisionKind delegation
|
||
"changeDecisionRef": <recordRef> | null, // decision, decisionKind assignment-change
|
||
"message": "<string>" | absent, // informational only; never authority
|
||
"runtimeObservations": {
|
||
"originalExecutionStopped": true|false|null,
|
||
"effectsReconciled": true|false|null
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
`<record>` 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).
|
||
|
||
`<recordRef>` = `{kind, id, scope, revision}`; `<registryRef>` =
|
||
`{registry, id, revision, digest}`; `<restrictions>` = `{operations[],
|
||
readPaths[], writePaths[], network, endpointRefs[]}` with `pathGrant`
|
||
`{root:"workspace", path|null}`; all as defined in the candidate schema.
|
||
|
||
`<target>` for file operations: `{root:"workspace", path:"<relative>"}`. For
|
||
record operations: a `<recordRef>`. For operations that take no target: `null`.
|
||
An operation whose target shape does not match its name refuses `invalid-request`.
|
||
|
||
`<registryEntry>` 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: <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 `<restrictions>` 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: <code>, rule: <rule-name>, selection: {agentId,
|
||
projectId, workspaceId, assignmentRef}, operation, consulted: [<recordRef |
|
||
registryRef>], 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 <file>`) 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=<mktemp> 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 <sandbox> -newer
|
||
<stamp>` 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-<n>`) 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/<name>.{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.
|