Skip to content

feat(capabilities): add the typed capability catalog of sixteen fleet capabilities (CFVC-09) - #1919

Closed
sbracewell64 wants to merge 2 commits into
kunchenguid:mainfrom
sbracewell64:fm/cfvc-09-capability-catalog
Closed

sbracewell64 wants to merge 2 commits into
kunchenguid:mainfrom
sbracewell64:fm/cfvc-09-capability-catalog

Conversation

@sbracewell64

Copy link
Copy Markdown

Intent

CFVC-09: build the capability catalog - typed definitions ONLY - per data/cfvc-synthesis-and-plan/report.md section 4's CFVC-09 table, which is the authoritative spec.

Goal: a harness-neutral catalog of typed capabilities, each naming an existing owner. Definitions only: nothing is bound, nothing executes, no runtime reads the file.

Context that explains the shape: every crewmate on every harness currently launches with all permission enforcement disabled (bin/fm-launch-lib.sh - four explicit bypass flags plus one harness with no permission system at all), so 'child_authority is a subset of parent_authority' is doctrinal only today. This increment NAMES capabilities. Enforcement arrives later via CFVC-10 (the capability binding record and issue-time authority law), which is deliberately split off and blocked on an open captain ownership decision (work-identity-owner). Building any binding record, broker, or enforcement point HERE was explicitly out of scope and was deliberately not done.

Accepted requirements, all from the spec:

  1. Each capability is {name, input_schema, output_schema, owner, authority_class, verifier}.
  2. authority_class REUSES loopspecs/schema.json's existing validated enum (read-only, firstmate-routine, captain-required) - never a new vocabulary. This is why the catalog documents the three classes rather than inventing worker-facing ones.
  3. Sixteen capabilities, each naming an existing owner - with network.request the single deliberate exception: defined, left UNOWNED, and captain-required. It is unowned on purpose because nothing in this repo mediates general outbound network access, so naming an owner would invent one; the row records that reason in an unowned_because field rather than leaving it to look like an oversight.
  4. Must NOT overload or merge into the platform's CapabilityRegistry (its scripts/runtime_capabilities.py, a separate repository): that answers a Kernel-side service-dispatch question, not an agent-side authority question. The catalog states this explicitly.
  5. Certification text written into the artifact itself: this catalog is a CAPABILITY boundary, NOT a security boundary - the host constrains a mistaken agent, not a hostile one. It is written in so it can never be quietly assumed away, and it names the enforcement point (CFVC-10) that would change that.
  6. Data only. Nothing changes for any agent; rollback is deleting the file.
  7. Tests required by the spec: every capability names an owner file that exists; network.request has no owner and is captain-required; every authority_class value is in the existing enum. Completion criteria: every row's owner resolves, no new authority_class, security-boundary disclaimer present.
  8. This is firstmate's own shared tracked material, so the firstmate-coding-guidelines skill was loaded and followed.

Decisions made while doing the work, which a reviewer reading only the diff would not know:

  • File sited at capabilities/catalog.json, a peer of the existing loopspecs/ registry, rather than inside loopspecs/ (a LoopSpec is temporal recurrence only) and rather than in docs/ (it is typed data, not prose).
  • The spec table does NOT enumerate the sixteen rows. It fixes the count, the field shape, the enum reuse and the network.request exception only, so which sixteen capabilities appear was the author's selection. No row was dropped from any authoritative list.
  • Owners are all real files under bin/ that already implement the named action, so 'owner' is a statement of current fact rather than an aspiration. The spec's example providers (workspace_provider.py, git_provider.py, ...) live in the platform repo and would not resolve from here, so repo-local owners were chosen deliberately to keep the owner-resolution test meaningful.
  • verifier was additionally constrained to name an existing check under tests/, and the test pins that too. The spec did not require it; it was added so no row can name a verifier that does not exist.
  • The test runs every check against a MUTATED copy of the catalog first and requires it to FAIL there, then runs the real catalog. This follows the brief's verification discipline: without the negative control, a passing data check could be vacuous.
  • Two one-line AGENTS.md updates (tracked-material list, layout tree) and one line in bin/fm-test-run.sh's family classifier, per the coding guidelines' size discipline: the substance lives in the artifact, not in AGENTS.md.
  • Verified locally before committing: new test green with every negative control firing, bin/fm-lint.sh clean, bin/fm-doc-audience-check.sh ok, tests/fm-test-run.test.sh and tests/fm-documentation-audiences.test.sh green, and the changed-file selector picks up the new test with nothing unmapped.

Decisions already accepted on an earlier run of this same branch, which a fresh run should honour rather than re-litigate:

  • The rebase step is deliberately SKIPPED. This branch is based on the fork trunk on purpose; the commits it carries ahead of upstream are this home's fork landing queue and must never be replayed or bundled into contribution history. Ruled under the captain's standing fork-retarget rule.
  • Three review findings were raised and their fixes authorized, but the fix round died when the session limit hit, so none of them landed and the code still carries all three. If review raises them again, the authorized resolutions are:
    (a) network.request's purpose says it covers requests 'not already mediated by a named forge, quota, or vendor owner above', but there are no quota or vendor rows. That is stale drafting by the author, confirmed. Fix by dropping the words 'quota, or vendor'. Do NOT add quota or vendor rows: the catalog is fixed at sixteen.
    (b) field_contract claims every schema type comes from loopspecs/schema.json's type vocabulary, but 'bool' is a catalog-local extension used by six rows. The provenance claim is factually wrong in a fact-stating artifact. Fix the wording to say the catalog uses the LoopSpec type vocabulary plus 'bool', which extends it, and that the only thing this catalog must reuse from loopspecs/schema.json is the authority_class enum. Do NOT remove bool from the rows and do NOT add bool to loopspecs/schema.json.
    (c) tests/fm-capability-catalog.test.sh's mutate() ignores python3's exit status, so a mutation that failed to apply would leave the control passing on a 'catalog unreadable' violation - a broken negative control reading as proof the check fires, the exact vacuity the controls exist to prevent. Fix mutate() to fail loudly when the mutation does not apply, keeping every existing mutation body and all six cases green.

What Changed

  • Adds capabilities/catalog.json, a harness-neutral, data-only catalog of sixteen typed capabilities, each defined as {name, input_schema, output_schema, owner, authority_class, verifier}. Every owner is an existing script under bin/ and every verifier an existing check under tests/; authority_class reuses the validated enum from loopspecs/schema.json rather than inventing a new vocabulary. network.request is the single deliberate exception — defined, unowned, and captain-required, with the reason recorded in an unowned_because field. The artifact states in its own text that it is a capability boundary, not a security boundary, names CFVC-10 as the future enforcement point, and explicitly disclaims merging into the platform's CapabilityRegistry. No runtime reads the file; definitions only.
  • Adds tests/fm-capability-catalog.test.sh, which runs every check against a mutated catalog copy first and requires it to fail there before accepting the real catalog. The pipeline's review round landed three authorized fixes on this branch: dropped the stale "quota, or vendor" wording from network.request's purpose, corrected the field-contract text to state that bool is a catalog-local extension of the LoopSpec type vocabulary, and hardened the test's mutate() to fail loudly when a mutation does not apply so a broken negative control can't read as proof the check fires. The new test is wired into bin/fm-test-run.sh's family classifier and the artifact is listed in AGENTS.md's tracked-material list and layout tree.
  • The branch is deliberately based on the fork trunk (the pipeline's rebase step was skipped under the standing fork-retarget rule), so the full delta also carries the fork landing queue already merged there: the fleet launcher menu and launch library, admission control stages, model registry with the zero-budget spawn gate, LoopSpec schema and register, wake-outcome ledger, remote-secondmate tooling, and merge/teardown hardening.

Risk Assessment

✅ Low: The only delta since the last reviewed head is one commit that applies the three author-authorized fixes exactly as specified (two wording corrections in the data-only catalog and a hardened negative-control mutate() whose failure path was verified to propagate via stderr and a non-zero exit through the command substitution), with every round-1 acceptance-criterion verification still holding.

Testing

Ran the spec-required capability-catalog test (all six checks green), independently audited the catalog against every intent constraint (sixteen typed rows, resolving owners and verifiers, enum reuse, the unowned captain-required network.request exception, both written-in disclaimers), visibly demonstrated the negative controls firing on mutated copies and the hardened mutate() failing loudly, confirmed no runtime reads the file, verified the three previously-authorized review fixes landed in 04f99d0, and covered the peripheral classifier and AGENTS.md edits with their own focused tests — everything passed. No screenshot artifact because the change is a JSON data artifact plus a shell test with no rendered UI surface; CLI transcripts and the audit table are the end-user-facing evidence.

Evidence: Capability catalog test transcript (six checks green)

ok - capability catalog: every capability names an owner file that exists ok - capability catalog: every verifier names a check that exists ok - capability catalog: network.request is the only unowned row and is captain-required ok - capability catalog: every authority_class is in the existing LoopSpec enum ok - capability catalog: states it is a capability boundary, not a security boundary ok - capability catalog: sixteen capabilities with unique names

ok - capability catalog: every capability names an owner file that exists
ok - capability catalog: every verifier names a check that exists
ok - capability catalog: network.request is the only unowned row and is captain-required
ok - capability catalog: every authority_class is in the existing LoopSpec enum
ok - capability catalog: states it is a capability boundary, not a security boundary
ok - capability catalog: sixteen capabilities with unique names
Evidence: Catalog audit vs CFVC-09 spec (rows, owners, enum, exception, certifications)
=== capabilities/catalog.json audit (against the CFVC-09 spec) ===

row count: 16 (spec requires 16)
authority_class enum reused from loopspecs/schema.json: ['read-only', 'firstmate-routine', 'captain-required']

name               authority_class    owner                        owner?  verifier                                  verifier?
workspace.read     read-only          bin/fm-spawn.sh              EXISTS  tests/fm-spawn-worktree-settle.test.sh    EXISTS
workspace.write    firstmate-routine  bin/fm-spawn.sh              EXISTS  tests/fm-spawn-worktree-settle.test.sh    EXISTS
worktree.allocate  firstmate-routine  bin/fm-worktree-guard.sh     EXISTS  tests/fm-worktree-guard.test.sh           EXISTS
vcs.base.resolve   read-only          bin/fm-task-base-lib.sh      EXISTS  tests/fm-task-base.test.sh                EXISTS
vcs.landed.verify  read-only          bin/fm-landed-lib.sh         EXISTS  tests/fm-worktree-guard.test.sh           EXISTS
vcs.merge.local    captain-required   bin/fm-merge-local.sh        EXISTS  tests/fm-merge-local.test.sh              EXISTS
forge.pr.read      read-only          bin/fm-pr-lib.sh             EXISTS  tests/fm-pr-check-security.test.sh        EXISTS
forge.pr.merge     captain-required   bin/fm-pr-merge.sh           EXISTS  tests/fm-pr-merge.test.sh                 EXISTS
validation.run     firstmate-routine  bin/fm-nm-run-lib.sh         EXISTS  tests/fm-crew-state.test.sh               EXISTS
agent.spawn        firstmate-routine  bin/fm-launch-lib.sh         EXISTS  tests/fm-launch-lib.test.sh               EXISTS
agent.steer        firstmate-routine  bin/fm-send.sh               EXISTS  tests/fm-send-strict.test.sh              EXISTS
agent.state.read   read-only          bin/fm-crew-state.sh         EXISTS  tests/fm-crew-state.test.sh               EXISTS
agent.teardown     firstmate-routine  bin/fm-teardown.sh           EXISTS  tests/fm-teardown.test.sh                 EXISTS
backlog.update     firstmate-routine  bin/fm-tasks-axi-lib.sh      EXISTS  tests/fm-backlog-handoff.test.sh          EXISTS
decision.resolve   captain-required   bin/fm-decision-hold.sh      EXISTS  tests/fm-decision-hold-lifecycle.test.sh  EXISTS
network.request    captain-required   None                         n/a     None                                      n/a 

=== network.request (the single deliberate exception) ===
owner: None   authority_class: 'captain-required'
unowned_because present: True
purpose (fix a - no 'quota, or vendor'): 'Make an arbitrary outbound network request that is not already mediated by a named forge owner above.'

=== certification text written into the artifact ===
statement: This catalog is a CAPABILITY boundary, not a SECURITY boundary. It constrains a mistaken agent, never a hostile one.
names the enforcement point CFVC-10: True

=== field_contract provenance (fix b - 'bool' declared a catalog-local extension) ===
Typed inputs, using the LoopSpec type vocabulary (slug, string, string[], object, pos_int) plus bool, a catalog-local extension of it. The only vocabulary this catalog is required to reuse from loopspecs/schema.json is the authority_class enum.

=== platform CapabilityRegistry separation stated in the artifact ===
This is NOT the platform's CapabilityRegistry (its scripts/runtime_capabilities.py, a separate repository) and must never be merged into it. That registry answers a Kernel-side que...
Evidence: Negative controls firing on mutated copies + mutate() hardening demo
=== Negative control demo: same validator run against mutated copies must emit violations ===
[       owners] mutated  -> workspace.read owner does not exist: bin/fm-does-not-exist.sh
[       owners] real     -> (clean)
[      unowned] mutated  -> network.request must stay unowned, found owner: 'bin/fm-send.sh'
[      unowned] real     -> (clean)
[         enum] mutated  -> workspace.read has authority_class 'worker-routine', not in the LoopSpec enum ['read-only', 'firstmate-routine', 'captain-required']
[         enum] real     -> (clean)
[certification] mutated  -> the not-a-security-boundary certification is absent
[certification] real     -> (clean)

=== Fix (c) demo: a mutation that fails to apply now exits non-zero (test would fail loudly) ===
Traceback (most recent call last):
  File "<stdin>", line 5, in <module>
  File "<string>", line 1, in <module>
RuntimeError: mutation did not apply
python3 exit status on failed mutation: 1 (non-zero -> mutate() calls fail)
136:    fail "capability catalog: mutation did not apply: $body"
Evidence: Changed-file selector picks up the new test with nothing unmapped
tests/fm-admission.test.sh
tests/fm-arm-pretool-check.test.sh
tests/fm-ask-user-authority.test.sh
tests/fm-brief.test.sh
tests/fm-calm-pi-extension.test.sh
tests/fm-capability-catalog.test.sh
tests/fm-cd-pretool-check.test.sh
tests/fm-composer-ghost.test.sh
tests/fm-composer-lib.test.sh
tests/fm-context-statusline.test.sh
tests/fm-crew-state.test.sh
tests/fm-decision-hold-lifecycle.test.sh
tests/fm-documentation-audiences.test.sh
tests/fm-ensure-agents-md.test.sh
tests/fm-grok-harness.test.sh
tests/fm-herdr-lab.test.sh
tests/fm-kimi-harness.test.sh
tests/fm-launch-lib.test.sh
tests/fm-lint.test.sh
tests/fm-operational-input.test.sh
tests/fm-pi-primary-types.test.sh
tests/fm-send-popup-settle.test.sh
tests/fm-send-settle.test.sh
tests/fm-subagent-pretool-check.test.sh
tests/fm-supervision-instructions.test.sh
tests/fm-task-base.test.sh
tests/fm-task-delivery.test.sh
tests/fm-test-isolation-proof.test.sh
tests/fm-test-run.test.sh
tests/fm-tmux-submit-busy.test.sh
tests/fm-trace-context-lib.test.sh
tests/fm-transition-lib.test.sh
tests/fm-vendor-auth-probe.test.sh

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

⏭️ **Rebase** - skipped

Push main to origin, or rebase your branch onto origin/main, before gating.

🔧 **Review** - 3 issues found → auto-fixed ✅
  • ⚠️ capabilities/catalog.json:166 - network.request's purpose text says the capability covers requests "not already mediated by a named forge, quota, or vendor owner above", but the catalog contains no quota or vendor rows — stale drafting in a fact-stating artifact. Author-confirmed with an authorized resolution: drop the words "quota, or vendor" from the purpose text. Do NOT add quota or vendor rows; the catalog is fixed at sixteen.
  • ⚠️ capabilities/catalog.json:22 - field_contract's input_schema entry claims the types "(slug, string, string[], bool, object, pos_int)" are "the loopspecs/schema.json type vocabulary", but 'bool' appears zero times in loopspecs/schema.json — it is a catalog-local extension used by six rows, so the provenance claim is factually wrong in a fact-stating artifact. Author-confirmed with an authorized resolution: reword to say the catalog uses the LoopSpec type vocabulary plus 'bool', which extends it, and that the only thing this catalog must reuse from loopspecs/schema.json is the authority_class enum. Do NOT remove bool from the rows and do NOT add bool to loopspecs/schema.json.
  • ⚠️ tests/fm-capability-catalog.test.sh:124 - mutate() ignores python3's exit status: if exec(body) raises (mutation fails to apply), the destination file is never written, but printf still echoes its path; check() then reports "catalog unreadable" on the missing file and assert_control_fires treats that as the control firing — a broken negative control reading as proof the check works, the exact vacuity the controls exist to prevent. Latent today (all six current mutation bodies apply cleanly), but it silently degrades the verification discipline. Author-confirmed with an authorized resolution: make mutate() fail loudly when the mutation does not apply, keeping every existing mutation body and all six test cases green.

🔧 Fix: fix stale catalog wording and harden mutate negative controls
✅ Re-checked - no issues remain.

✅ **Test** - passed

✅ No issues found.

  • bash tests/fm-capability-catalog.test.sh — all six cases green (owners resolve, verifiers resolve, network.request unowned+captain-required, enum reuse, security-boundary disclaimer, sixteen unique rows)
  • Manual audit script over capabilities/catalog.json vs the CFVC-09 spec: 16 rows, six required fields per row, all 15 owners and verifiers exist on disk, all authority_class values in loopspecs/schema.json's enum, unowned_because present, certification names CFVC-10, CapabilityRegistry separation stated
  • Manual negative-control demo: ran the test's embedded validator against four mutated catalog copies (bad owner, owned network.request, invented authority class, deleted certification) — each emitted a violation while the real catalog ran clean
  • Manual fix-(c) demo: a mutation body that raises exits python3 with status 1, triggering mutate()'s new fail &#34;mutation did not apply&#34; guard
  • grep -rn &#34;capabilities/catalog&#34; across the repo — only the test references the file, confirming the data-only/no-runtime-reader claim
  • bash bin/fm-test-run.sh --changed --base 571c60c^ --list — exits 0 with nothing unmapped and selects tests/fm-capability-catalog.test.sh
  • bash tests/fm-test-run.test.sh — green, covering the one-line family-classifier edit in bin/fm-test-run.sh
  • bash tests/fm-documentation-audiences.test.sh — green, covering the two one-line AGENTS.md updates
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

…-09)

Add capabilities/catalog.json: a harness-neutral catalog of sixteen typed
capabilities, each naming the file that already implements it. Definitions
only - nothing is bound, nothing executes, and no runtime reads the file, so
this changes nothing for any agent and rolls back by deleting it.

Every row is {name, input_schema, output_schema, owner, authority_class,
verifier}. authority_class reuses loopspecs/schema.json's existing validated
enum rather than growing a second vocabulary. network.request is defined and
deliberately left unowned and captain-required, because nothing here mediates
general outbound network access and naming an owner would invent one.

The catalog certifies in its own text that it is a capability boundary and
not a security boundary: crewmates launch with permission enforcement
disabled and the harnesses do not share a sandbox, so it constrains a
mistaken agent, never a hostile one. Enforcement would need an issue-time
binding record, which is separate work blocked on an open ownership
decision.

This is not the platform's CapabilityRegistry and states so: that answers a
Kernel-side service-dispatch question, not an agent-side authority question.

tests/fm-capability-catalog.test.sh pins the contract - every owner and
verifier resolves, network.request is the only unowned row and is
captain-required, every authority_class is in the existing enum, and the
security-boundary certification is present. Each check runs against a mutated
copy first and must fail there, so a pass is evidence the check fires.
@sbracewell64
sbracewell64 force-pushed the fm/cfvc-09-capability-catalog branch from 04f99d0 to 829eb36 Compare August 7, 2026 22:34
sbracewell64 added a commit to sbracewell64/firstmate that referenced this pull request Aug 11, 2026
… of upstream kunchenguid#1919) (#78)

Land CFVC-09 on the fork trunk, which is the code this fleet actually
runs. The same change was contributed upstream as kunchenguid#1919 and merged there,
but a merged upstream PR is a contribution rather than a landing, so
capabilities/catalog.json was still absent from the running trunk.

The catalog is harness-neutral and definitions ONLY: nothing is bound,
nothing executes, and no runtime reads the file. Each of the sixteen rows
names an existing owner in this repo plus the existing check that pins
that owner's behavior. network.request is deliberately unowned and
captain-required, because nothing here mediates general outbound network
access and naming an owner would invent one.

authority_class reuses loopspecs/schema.json's already-validated enum
verbatim rather than growing a second vocabulary, and the file certifies
in its own text that it is a capability boundary and NOT a security
boundary: it constrains a mistaken agent, never a hostile one. Binding
and enforcement are CFVC-10's work, deliberately not built here.

Rollback is deleting the file.
@kunchenguid

Copy link
Copy Markdown
Owner

Speaking as Kun's firstmate: closing this as stale. It has been waiting on a contributor update for 14+ days with no author push or comment. Reopen if you want to pick it back up.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants