Applies the document contract from
docs/plans/2026-08-20_stack-docs-flatten-and-alignment.md section 3, partially:
`kind` and `status` only. `parent` is deliberately held until the flatten in
section 4 lands, so that 127 documents do not have to be re-pointed by hand
when docs/fleet/NORTH_STAR.yaml moves to docs/NORTH_STAR.yaml.
Scope, measured on origin/next at 63069149:
127 live docs = all *.md under docs/ minus docs/archive/ minus docs/_old_structure/
104 stamped here
19 held operator judgement (plan section 9), worklist in the same PR
3 held the SUPERSEDED TASKS.md stamps, which cite the moving path
1 untouched docs/fleet/FLEET-DOCTRINE.md, already stamped in W1
Kinds applied: 54 guide, 34 record, 9 spec, 6 tracking, 1 projection.
Every row carries a confidence and a one-line rationale in the worklist.
Two collisions with the existing state, both flagged rather than resolved:
1. docs/README.md:150-160 already documents a front-matter convention
(title/type/audience/status/source_of_truth) with its own allowed values.
It is applied to 4 of 127 files. Its `status` vocabulary is
current|draft|deprecated|historical; the new contract's is active|superseded-by.
The key collides. This commit lets the new contract win and rewrites
`status: current` to `status: active` on those 4 files, keeping their other
legacy keys untouched. No code reads any of them: `git grep source_of_truth`
outside docs/ returns nothing. docs/README.md still prescribes the old
convention and is an operator row, so it is not edited here.
2. Two of the plan's 20 operator rows are YAML files, not markdown
(docs/fleet/examples/roster-v2.yaml, docs/openapi-tess.yaml), and the
contract's front-matter form has no defined meaning for a .yaml document.
That gap also applies to docs/fleet/NORTH_STAR.yaml, the source of truth
itself. Raised in the worklist.
A third row from the plan, docs/fleet/north-star.md, no longer exists: W1
renamed it to docs/fleet/FLEET-DOCTRINE.md.
Verification: 104/104 parse with the expected kind and status in front matter;
the check was shown to reject a wrong kind before it was trusted. The diff
removes 4 lines total, all of them `status: current`.
132 lines
8.8 KiB
Markdown
132 lines
8.8 KiB
Markdown
---
|
||
kind: guide
|
||
status: active
|
||
---
|
||
|
||
# Fleet Roster v2 Structural Contract
|
||
|
||
**Status:** FCM-M1-001 local-tmux structural compiler contract. This document describes parsing,
|
||
strict structural validation, normalized in-memory representation, and deterministic rendering only.
|
||
It does not authorize role resolution, lifecycle reconciliation, mutation, migration, remote
|
||
placement, connector configuration, secret references, arbitrary commands, channels, gateway
|
||
mapping, or any live-fleet change.
|
||
|
||
The executable schema is [`roster-v2.schema.json`](./roster-v2.schema.json). The compiler exports
|
||
the same schema and its test parses this file and compares it structurally with the executable contract.
|
||
|
||
## Format and canonical shape
|
||
|
||
The compiler accepts YAML or JSON. It reads only snake_case source fields and renders canonical,
|
||
snake_case YAML. Rendering sorts runtime keys and agents by stable name. Agent names, class names,
|
||
and tool-policy names are structural identifiers; whether a class or policy resolves is a later
|
||
shared-resolver concern.
|
||
|
||
```yaml
|
||
version: 2
|
||
generation: 1
|
||
transport: tmux
|
||
tmux:
|
||
socket_name: mosaic-fleet
|
||
holder_session: _holder
|
||
defaults:
|
||
working_directory: ~/src
|
||
runtime: pi
|
||
runtimes:
|
||
pi:
|
||
reset_command: /new
|
||
agents:
|
||
- name: coder0
|
||
alias: Coder 0
|
||
class: code
|
||
runtime: pi
|
||
provider: openai
|
||
model: gpt-5.6-sol
|
||
reasoning: high
|
||
tool_policy: code
|
||
working_directory: ~/src
|
||
persistent_persona: false
|
||
reset_between_tasks: true
|
||
lifecycle:
|
||
enabled: true
|
||
desired_state: stopped
|
||
launch:
|
||
yolo: true
|
||
```
|
||
|
||
## Root fields
|
||
|
||
| Field | Required | Default | Constraint | Meaning |
|
||
| ------------ | -------- | ------- | --------------------- | ------------------------------------------------------------------------------------------------- |
|
||
| `version` | yes | none | integer constant `2` | Identifies this contract. Version `1` stays on the compatibility path pending explicit migration. |
|
||
| `generation` | yes | none | positive safe integer | Desired-state generation and mutation/reconcile concurrency fence. |
|
||
| `transport` | yes | none | constant `tmux` | M1–M5 support local tmux only. |
|
||
| `tmux` | yes | none | strict object | Explicit local socket and holder-session configuration. |
|
||
| `defaults` | yes | none | strict object | Default work directory and one supported local runtime. |
|
||
| `runtimes` | yes | none | non-empty object | Declared local runtime reset policy map. |
|
||
| `agents` | yes | none | non-empty array | Local fleet entries. Duplicate stable names are rejected. |
|
||
|
||
## Nested fields
|
||
|
||
All nested fields in the v2 schema are required and have no implicit default. CRUD `create` is the only higher-level convenience: it records lifecycle.enabled: true and desired_state: stopped unless `--persisted-start` explicitly records running. That convenience still performs no runtime action.
|
||
|
||
| Path | Required | Constraint |
|
||
| -------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------ |
|
||
| `tmux.socket_name` | yes | [A-Za-z0-9_.-]\*; empty string means the literal default tmux server, while a non-empty value names a socket |
|
||
| `tmux.holder_session` | yes | non-empty [A-Za-z0-9_.-]+ |
|
||
| `defaults.working_directory` | yes | non-empty string |
|
||
| `defaults.runtime` | yes | `claude`, `codex`, `opencode`, or `pi`; it must be declared in `runtimes` |
|
||
| runtimes.<runtime>.reset_command | yes | non-empty string; runtime key must be a supported local runtime |
|
||
| agents[].name | yes | unique [A-Za-z0-9][A-Za-z0-9_.-]\* stable machine identity |
|
||
| agents[].alias | yes | non-empty display string |
|
||
| agents[].class | yes | [a-z][a-z0-9-]\*; structural only in M1, semantic role resolution is FCM-M1-002 |
|
||
| agents[].runtime | yes | `claude`, `codex`, `opencode`, or `pi`; it must be declared in `runtimes` |
|
||
| agents[].provider, `model`, `working_directory` | yes | non-empty strings; provider/model capability resolution is a later card |
|
||
| agents[].reasoning | yes | `low`, `medium`, or `high` |
|
||
| agents[].tool_policy | yes | [a-z][a-z0-9-]\*; structural only in M1 |
|
||
| agents[].persistent_persona, `reset_between_tasks` | yes | booleans |
|
||
| agents[].lifecycle.enabled | yes | boolean; stored now, reconciled in FCM-M3-001 |
|
||
| agents[].lifecycle.desired_state | yes | `running` or `stopped` |
|
||
| agents[].launch.yolo | yes | boolean; structured data only, not an arbitrary command escape hatch |
|
||
|
||
## Semantic handoff
|
||
|
||
`parseRosterV2` and `normalizeRosterV2` remain synchronous and structural. After structural success,
|
||
call the asynchronous `validateRosterV2Semantics` handoff before using persona identity or authority.
|
||
That validator batches the baseline `fleet/roles/` and operator `fleet/roles.local/` scans, then
|
||
delegates every agent to the shared persona resolver.
|
||
|
||
Semantic validation:
|
||
|
||
- requires the winning role contract to be readable and non-empty; `LIBRARY.md` membership alone does
|
||
not resolve a class;
|
||
- retains `requestedClass` separately from `canonicalClass` in typed output;
|
||
- canonicalizes only `implementer` to `code`, `reviewer` to `review`, and
|
||
`operator-interaction` to `interaction`;
|
||
- canonicalizes `tool_policy` with the same exact alias table;
|
||
- rejects protected class/tool-policy mismatches in either direction, while accepting
|
||
class: operator-interaction with tool_policy: operator-interaction as canonical
|
||
`interaction`;
|
||
- derives immutable protected authority only from canonical class; and
|
||
- accepts custom baseline or `roles.local` classes without granting protected authority.
|
||
|
||
agents[].alias remains display-only. Tess and Ultron are instance names, never semantic classes.
|
||
Canonicalization happens before role-layer lookup, so a legacy-named override cannot redefine an
|
||
alias as separate authority. See [Role Classes and Authority](./role-classes.md) and
|
||
[Customize Fleet Roles](../how-to/customize-roles.md).
|
||
|
||
This handoff performs no filesystem, systemd, tmux, roster, credential, lease, certificate, or
|
||
lifecycle mutation.
|
||
|
||
## Fail-closed boundary
|
||
|
||
Every object is additionalProperties: false. The compiler rejects unknown, missing, malformed,
|
||
and wrong-type fields before producing a model. It specifically rejects remote/SSH/host/socket
|
||
per-agent fields, connector blocks, secret references, channel fields, arbitrary command fields,
|
||
and gateway fields because they are unsupported in the local-tmux M1 contract. It does not silently
|
||
ignore v1 camelCase input, version `1`, or a source that does not parse to an object.
|
||
|
||
The v2 compiler is intentionally isolated from the existing v1 loader. Existing v1 rosters and
|
||
current examples/profiles continue on their current path; FCM-M4 owns explicit inventory, preview,
|
||
migration, and rollback. FCM-M2 owns generated-file/local-override quarantine, and FCM-M3 owns
|
||
runtime lifecycle and reconciliation.
|