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.
497 lines
31 KiB
Markdown
497 lines
31 KiB
Markdown
# 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": [ <envelope record>, ... ], // ≤256, supported kinds only (§10)
|
||
"registries": [ <registryEntry>, ... ], // ≤256
|
||
"artifacts": [ {runId, artifactId, digest}, ... ], // ≤256, declared only
|
||
"authorizations": [ <runtimeId>, ... ], // ≤256, declared only
|
||
"selection": {
|
||
"agentId": <id>, "projectId": <id>, "workspaceId": <id>,
|
||
"assignmentRef": <recordRef> | null,
|
||
"execution": { "kind": "unrestricted-simulation" }
|
||
| { "kind": "restrictions", "restrictions": <restrictions> }
|
||
},
|
||
"operation": { "name": <supported operation>, "target": <target> | null },
|
||
"proposal": null | {
|
||
"kind": "assignment-change",
|
||
"requesterAgentId": <id>,
|
||
"subjectAssignmentRef": <recordRef>,
|
||
"targetTaskRef": <recordRef>,
|
||
"delegationRef": <recordRef> | null,
|
||
"changeDecisionRef": <recordRef> | null,
|
||
"message": <string, ≤4000> | absent // informational, never authority
|
||
}
|
||
}
|
||
```
|
||
|
||
### 5.1 Type corrections (F2)
|
||
|
||
- `<envelope record>` 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.
|
||
- `<recordRef>` = `{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).
|
||
- `<registryRef>` = `{registry, id, revision, digest}`; `<artifactRef>` =
|
||
`{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": <count>, "authorizations": <count> },
|
||
"result": "allowed" | "refused" | "unresolved" | "invalid",
|
||
"reason": <reason code>,
|
||
"rule": <rule name> | null,
|
||
"selection": { "agentId", "projectId", "workspaceId", "assignmentRef" },
|
||
"operation": { "name", "target" },
|
||
"consulted": [ <recordRef | registryRef>, ... ], // 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.
|