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.
31 KiB
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_NONBLOCKopen thenfstat` |
| 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 ismap:244-283; exit classesmap:275-276; "No npm/Turbo change is needed"map:278; the inspector-must-not-bootstrap dispositionmap:240; the cross-lane retasking scenariomap:290-314with the adversarial acceptance case atmap: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.mddoes not exist (git cat-file -efails) and no file in the pinned tree or indocs/plans/*.mdmentionsajvorfast-uri. The audit rationale is withdrawn. The dependency-free recommendation now rests only on in-scope facts:package.jsondeclares 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
enginesfield, 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 --versionin its header and refuses to run below a stated minimum. The features used (node:test,node:crypto,TextDecoderwithfatal: 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 noenginesfield 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:authorizationRefis aruntimeId(UUID v4 pattern), not a recordRef;supersedesisnullat revision 1 and a recordRef otherwise (schemaallOf);createdAtis$defs/time(YYYY-MM-DDTHH:MM:SS.mmmZ) and must be a real calendar instant with the same round-trip rule ascheck.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.basisRefandevidenceRefsare artifactRefs and are evidence pointers, not authority.- Workspace ownership comes from
scope: a workspace record hasscope.kind = workspace,scope.projectIdnames the owning project, andidmust equalscope.workspaceId; a project'sidmust equalscope.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; workspaceid=scope.workspaceId; the workspace'sscope.projectIdmust resolve to an active project. - A workspace registration's
projectRegistrationRefmust resolve to an active project registration in the workspace's owning project for the sameagentId(README:65). - A workspace mission's
parentMissionRefmust be a project mission in that project; project missions have no parent (README:66-68). - A task's
missionRefresolves in the selected project;dependenciesare 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 everyproposalref. Naming a non-head revision refusesstale-revision. - Historical references may name any revision present in the bundle:
supersedesanddecision.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.
- Original scope authority: the requester holds an active L2/L3 registration
in the subject assignment's workspace whose role ceiling ∩ restrictions
include
assignment.change. Elsenot-authorized/requester-lacks-original-scope-authority. - Target scope authority: the same in the target task's project and
workspace. Else
not-authorized/requester-lacks-target-scope-authority. - Delegation:
delegationRef→ head decision,decisionKind: delegation,outcome: approved,delegatedOperations ∋ assignment.change,subjectRefsinclude the requester's agent-definition and the subject assignment or its task (recipient and subject bounds). Elsenot-authorized/delegation-not-applicable. Amessagewith no delegation refuses with rulemessage-is-not-authority(map:310). - Recorded change:
changeDecisionRef→ head decision,decisionKind: assignment-change,outcome: approved,subjectRefsinclude the subject assignment and the target task; itsbasisRefmust be a declared artifact but is not itself authorization. Elsenot-authorized/change-not-recorded. - Current intent: the subject assignment's
intentRefand the target task'sintentRefresolve to approved head decisions. Elsestale-revisionornot-authorizedas applicable. - If 1–5 pass: result
unresolved, reasonunknown-effects, ruleruntime-reconciliation-required. Stopping and reconciliation are runtime facts (map:311-314); the bundle has no field to assert them and the r1runtimeObservationsfield 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 withenv -i PATH=… HOME=$SANDBOX/home NO_COLOR=1, cwd$SANDBOX/cwd,MOSAIC_CONFIGunset. - Inventory before and after every invocation over each observed root:
find -printf '%p %y %s %m %U %i\n' | sortplus SHA-256 of every regular file; compared exactly. A canary config path underhome/.config/mosaic-devis 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, andDatedo not appear; a second asserts the CLI imports nomosaic-config. - Canaries (
CANARY-DO-NOT-PRINT-nin 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;
--jsonand text agree onresult/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.