feat(fleet): add shared role semantics (#768)
All checks were successful
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful

This commit was merged in pull request #768.
This commit is contained in:
2026-07-15 00:53:47 +00:00
parent eb4e14ae5c
commit a5e8e55401
19 changed files with 1650 additions and 177 deletions

View File

@@ -52,20 +52,20 @@ Active workstream is **W1 — Federation v1**. Workers should:
> the repository quality gates, independent code and security review, terminal-green CI, and
> the applicable acceptance evidence before merge. Issue #758 remains open until M5 closes.
| id | status | description | issue | agent | repo | branch | depends_on | estimate | notes |
| ---------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | ------ | ----------------- | --------------------------------------- | ---------------------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------- |
| FCM-M0-001 | in-progress | Publish normative PRD requirements/acceptance criteria, this M0M5 DAG, docs-IA checklist, and legacy example/profile disposition inventory; no implementation changes | #758 | sonnet | mosaicstack/stack | `docs/758-fleet-config-management` | — | 18K | M0 exit: approved docs; every shipped example/profile/service preset classified; docs-only PR |
| FCM-M1-001 | not-started | Implement narrow local-tmux v2 roster structural contract/compiler with YAML/JSON canonicalization and schema/parser parity tests | #758 | codex | mosaicstack/stack | `feat/758-roster-v2-compiler` | FCM-M0-001 | 30K | No lifecycle, remote, connector, secret, channel, or gateway work |
| FCM-M1-002 | not-started | Reuse existing profile/persona/provision resolver for roster semantics; add canonical class/authority validation and approved aliases | #758 | codex | mosaicstack/stack | `feat/758-shared-role-resolution` | FCM-M0-001 | 25K | Validator is certificate-only; merge-gate remains sole merge authority |
| FCM-M1-003 | not-started | Convert the M0 legacy inventory into executable example/profile/service-preset validation and explicit v1-version/retirement checks | #758 | codex | mosaicstack/stack | `test/758-example-profile-dispositions` | FCM-M1-001, FCM-M1-002 | 20K | Every shipped artifact must validate, be versioned v1, or be retired with replacement |
| FCM-M2-001 | not-started | Migrate generic launch chain to deterministic `.env.generated` plus strict data-only `.env.local`; quarantine forbidden legacy keys | #758 | codex | mosaicstack/stack | `feat/758-generated-env-boundary` | FCM-M1-001, FCM-M1-002 | 30K | No arbitrary command compatibility path; diagnostics expose key names/hashes only |
| FCM-M2-002 | not-started | Add generation-guarded local fleet agent create/get/update/delete mutations with plan/dry-run, atomic roster writes, and recovery output | #758 | codex | mosaicstack/stack | `feat/758-fleet-agent-crud` | FCM-M1-001, FCM-M2-001 | 30K | Fresh create persists stopped unless explicit persisted start |
| FCM-M3-001 | not-started | Implement local roster-owned reconcile/apply plus lifecycle/status/verify/doctor contracts and stable JSON/exit codes | #758 | codex | mosaicstack/stack | `feat/758-local-reconciler` | FCM-M2-001, FCM-M2-002 | 35K | Exact systemd/tmux ownership; remote/schema-only entries are inventory only |
| FCM-M3-002 | not-started | Add isolated systemd/tmux lifecycle, drift, socket, unmanaged-session, crash, and rollback acceptance coverage | #758 | sonnet | mosaicstack/stack | `test/758-reconciler-lifecycle-gates` | FCM-M3-001 | 25K | Proves stopped-state preservation and zero fuzzy destructive targeting |
| FCM-M4-001 | not-started | Implement field-complete v1-to-v2 inventory/preview/migrator with alias, lifecycle, env-quarantine, and remote/connector disposition evidence | #758 | codex | mosaicstack/stack | `feat/758-v1-v2-migrator` | FCM-M1-003, FCM-M3-001 | 35K | Preview first; no unreviewed lifecycle inference |
| FCM-M4-002 | not-started | Add reversible canary migration, rollback, stale-projection/orphan classification, and current-host 9-managed/3-unmanaged fixture coverage | #758 | sonnet | mosaicstack/stack | `test/758-migration-rollback-gates` | FCM-M4-001, FCM-M3-002 | 25K | Never starts a previously stopped agent or kills an unproven unmanaged session |
| FCM-M5-001 | not-started | Deliver the accepted fleet documentation IA, how-to/operations/migration references, and link/example validation | #758 | haiku | mosaicstack/stack | `docs/758-fleet-config-operator-docs` | FCM-M1-003, FCM-M2-002, FCM-M3-001, FCM-M4-001 | 24K | Must close every checklist item or record an approved deferral |
| FCM-M5-002 | not-started | Package/update asset-drift checks, rolling local canary, independent validation certificate, and release evidence | #758 | sonnet | mosaicstack/stack | `feat/758-fleet-config-release-gate` | FCM-M3-002, FCM-M4-002, FCM-M5-001 | 30K | Final #758 gate: quality, independent code/security review, validator certificate, merge-gate approval, green CI |
| id | status | description | issue | agent | repo | branch | depends_on | estimate | notes |
| ---------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | ------------- | ----------------- | --------------------------------------- | ---------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------ |
| FCM-M0-001 | done | Publish normative PRD requirements/acceptance criteria, this M0M5 DAG, docs-IA checklist, and legacy example/profile disposition inventory; no implementation changes | #758 | sonnet | mosaicstack/stack | `docs/758-fleet-config-management` | — | 18K | Merged via #760 (`c32d85a`); parent #758 intentionally remains open through M5 |
| FCM-M1-001 | done | Implement narrow local-tmux v2 roster structural contract/compiler with YAML/JSON canonicalization and schema/parser parity tests | #758 | coder0 | mosaicstack/stack | `feat/758-roster-v2-compiler` | FCM-M0-001 | 30K | #764 squash `aa5b43b`; exact-head RoR and PR/main terminal-green CI; no lifecycle or live mutation |
| FCM-M1-002 | in-progress | Reuse existing profile/persona/provision resolver for roster semantics; add canonical class/authority validation and approved aliases | #758 | native-sonnet | mosaicstack/stack | `feat/758-shared-role-resolution` | FCM-M0-001 | 25K | Started 2026-07-14 from `aa5b43b`; one shared resolver only; validator certificate-only; merge-gate sole merge authority |
| FCM-M1-003 | not-started | Convert the M0 legacy inventory into executable example/profile/service-preset validation and explicit v1-version/retirement checks | #758 | codex | mosaicstack/stack | `test/758-example-profile-dispositions` | FCM-M1-001, FCM-M1-002 | 20K | Every shipped artifact must validate, be versioned v1, or be retired with replacement |
| FCM-M2-001 | not-started | Migrate generic launch chain to deterministic `.env.generated` plus strict data-only `.env.local`; quarantine forbidden legacy keys | #758 | codex | mosaicstack/stack | `feat/758-generated-env-boundary` | FCM-M1-001, FCM-M1-002 | 30K | No arbitrary command compatibility path; diagnostics expose key names/hashes only |
| FCM-M2-002 | not-started | Add generation-guarded local fleet agent create/get/update/delete mutations with plan/dry-run, atomic roster writes, and recovery output | #758 | codex | mosaicstack/stack | `feat/758-fleet-agent-crud` | FCM-M1-001, FCM-M2-001 | 30K | Fresh create persists stopped unless explicit persisted start |
| FCM-M3-001 | not-started | Implement local roster-owned reconcile/apply plus lifecycle/status/verify/doctor contracts and stable JSON/exit codes | #758 | codex | mosaicstack/stack | `feat/758-local-reconciler` | FCM-M2-001, FCM-M2-002 | 35K | Exact systemd/tmux ownership; remote/schema-only entries are inventory only |
| FCM-M3-002 | not-started | Add isolated systemd/tmux lifecycle, drift, socket, unmanaged-session, crash, and rollback acceptance coverage | #758 | sonnet | mosaicstack/stack | `test/758-reconciler-lifecycle-gates` | FCM-M3-001 | 25K | Proves stopped-state preservation and zero fuzzy destructive targeting |
| FCM-M4-001 | not-started | Implement field-complete v1-to-v2 inventory/preview/migrator with alias, lifecycle, env-quarantine, and remote/connector disposition evidence | #758 | codex | mosaicstack/stack | `feat/758-v1-v2-migrator` | FCM-M1-003, FCM-M3-001 | 35K | Preview first; no unreviewed lifecycle inference |
| FCM-M4-002 | not-started | Add reversible canary migration, rollback, stale-projection/orphan classification, and current-host 9-managed/3-unmanaged fixture coverage | #758 | sonnet | mosaicstack/stack | `test/758-migration-rollback-gates` | FCM-M4-001, FCM-M3-002 | 25K | Never starts a previously stopped agent or kills an unproven unmanaged session |
| FCM-M5-001 | not-started | Deliver the accepted fleet documentation IA, how-to/operations/migration references, and link/example validation | #758 | haiku | mosaicstack/stack | `docs/758-fleet-config-operator-docs` | FCM-M1-003, FCM-M2-002, FCM-M3-001, FCM-M4-001 | 24K | Must close every checklist item or record an approved deferral |
| FCM-M5-002 | not-started | Package/update asset-drift checks, rolling local canary, independent validation certificate, and release evidence | #758 | sonnet | mosaicstack/stack | `feat/758-fleet-config-release-gate` | FCM-M3-002, FCM-M4-002, FCM-M5-001 | 30K | Final #758 gate: quality, independent code/security review, validator certificate, merge-gate approval, green CI |
## Thin-core prompt diet (#528) — feat/contract-thin-core

View File

@@ -0,0 +1,54 @@
# Customize Fleet Roles
Mosaic resolves persona contracts through two layers:
1. `fleet/roles/<canonical-class>.md` — seeded baseline contract.
2. `fleet/roles.local/<canonical-class>.md` — operator override or custom role; this layer wins.
The same shared resolver is used by profile validation, provisioning, roster-v2 semantic validation,
and launch-time persona injection.
## Override a baseline role
Create a readable Markdown contract under `roles.local` with the canonical filename and class marker:
```markdown
# Code — local role definition
The local code role (`class: code`) follows the operator's repository conventions.
```
Save it as `fleet/roles.local/code.md`. Do not edit generated or seeded baseline assets when the goal
is a durable local customization.
Legacy aliases canonicalize before lookup. Therefore `roles.local/implementer.md` does not override
`code`; use `roles.local/code.md`. See [Legacy Fleet Class Aliases](../migration/legacy-class-aliases.md).
## Add a custom class
A custom class remains supported when a readable contract exists for the exact identifier:
```markdown
# Release notes — local role definition
The release-notes role (`class: release-notes`) prepares operator-reviewed release copy.
```
Save it as `fleet/roles.local/release-notes.md`, then reference `class: release-notes` and a matching
`tool_policy: release-notes` in roster v2. Adding only a `LIBRARY.md` row is insufficient.
Names such as `worker`, `analyst`, and `canary` are not built-in aliases; they need genuine custom
contracts. `agents[].alias`, Tess, and Ultron are display names and cannot select a class.
## Validation and authority boundaries
Semantic validation reads the winning contract and rejects missing, unreadable, or empty files.
Protected authority is derived from canonical class metadata in code, never from role prose. A custom
contract cannot claim merge, validation-certificate, orchestration, lease, or interaction authority.
Roster v2 also fails closed when a protected class and tool policy do not match after canonicalization,
or when an unprotected class claims a protected tool policy. The legacy `operator-interaction` policy
canonicalizes to `interaction`.
Role customization does not issue leases, store validation certificates, mutate credentials, or
change lifecycle state.

View File

@@ -0,0 +1,40 @@
# Legacy Fleet Class Aliases
Fleet class compatibility is intentionally narrow. The shared resolver accepts exactly three legacy
class names and converts them to canonical classes before persona lookup:
| Legacy value | Canonical value | Migration action |
| ---------------------- | --------------- | ---------------------------------------------------------------------------------------------------------------------- |
| `implementer` | `code` | Replace class and tool-policy references with `code`. |
| `reviewer` | `review` | Replace class and tool-policy references with `review`. |
| `operator-interaction` | `interaction` | Replace class and roster-v2 tool-policy references with `interaction`. The legacy service artifact remains compatible. |
Alias support preserves existing inputs while provisioning and typed semantic output use canonical
identities. Requested and canonical class values remain separately observable during semantic
validation.
## Lookup and override behavior
Canonicalization precedes baseline and `roles.local` lookup. A legacy-named override such as
`roles.local/implementer.md` is not a separate authority and is not selected for an `implementer`
request. Customize the canonical role instead, for example `roles.local/code.md`.
The compatibility file `operator-interaction.md` remains shipped, but `interaction` is the canonical
role class. Tess is an example display name only.
## Unresolved and custom classes
No names are inferred from historical usage, instance names, or similar wording. `worker`, `analyst`,
`canary`, Tess, and Ultron are not aliases. An otherwise unknown class is accepted only if the shared
resolver can read an actual baseline or `roles.local` contract for that exact class. A `LIBRARY.md`
row without a readable contract fails semantic validation.
Custom classes receive no protected authority implicitly. Protected class/tool-policy mismatches
fail closed.
## Retirement guidance
New configuration should emit canonical values. Existing inputs may use the three aliases during the
compatibility period, but operators should migrate class and tool-policy fields together. Do not
create new legacy-named role overrides; move their intended content to the canonical filename and
validate the roster/profile before removing the old artifact.

View File

@@ -0,0 +1,45 @@
# Fleet Role Classes and Authority
A fleet role class is a machine identity resolved from the persona library. Resolution uses the
canonical class before consulting the baseline `fleet/roles/` and operator `fleet/roles.local/`
layers. A readable role contract is required; an index entry alone is not semantic success.
## Canonicalization
Only these legacy class aliases are recognized:
| Requested class | Canonical class |
| ---------------------- | --------------- |
| `implementer` | `code` |
| `reviewer` | `review` |
| `operator-interaction` | `interaction` |
No other alias is inferred. In particular, `worker`, `analyst`, and `canary` are custom classes only
when an operator supplies a readable contract for that exact class. Tess and Ultron are instance
names, not classes. `agents[].alias` is display-only and cannot grant authority.
Canonicalization happens before role lookup. For example, requesting `implementer` resolves
`code.md`; a separate `roles.local/implementer.md` cannot redefine the legacy alias. A canonical
`roles.local/code.md` still overrides the baseline `roles/code.md` contract.
## Protected authority
Protected authority is immutable metadata derived only from canonical class. Role prose, instance
name, display alias, tool policy, runtime, and custom role files cannot grant it.
| Canonical class | Granted authority | Explicit limits |
| ----------------- | -------------------------------------------------- | --------------------------------------------------------------------------------- |
| `merge-gate` | Sole approve-to-land and merge authority | No authority is inferred by similarly named custom roles or policies. |
| `validator` | May issue a validation certificate | Cannot approve-to-land or merge. |
| `orchestrator` | May orchestrate, manage topology, and issue leases | Cannot approve-to-land or merge. |
| `team-leader` | May use orchestrator-leased capacity | Cannot issue leases or mutate roster, configuration, credentials, or merge state. |
| `interaction` | Request and status surface | Cannot orchestrate, issue leases, mutate roster/configuration, or merge. |
| all other classes | No protected authority implicitly | Custom contracts do not acquire protected powers from prose. |
Roster-v2 semantic validation requires a protected class and its canonical tool policy to match. It
also rejects an unprotected class paired with a protected tool policy. The legacy tool-policy name
`operator-interaction` canonicalizes to `interaction`.
This mapping describes authority metadata only. Lease issuance, validation-certificate storage or
workflow, lifecycle reconciliation, credentials, roster mutation, and merge execution are outside
this resolver contract.

View File

@@ -81,6 +81,35 @@ agents:
| `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,

View File

@@ -0,0 +1,148 @@
# FCM-M1-002 — Shared role resolution
- **Task:** `FCM-M1-002`
- **Issue:** `mosaicstack/stack#758`
- **Branch:** `feat/758-shared-role-resolution`
- **Starting head:** `32e75c67b094de443d37fe7d5ff8d25cdfc8b39d`
- **Role:** implementation worker; independent review and merge remain outside this worker
## Objective
Reuse the existing baseline-plus-`roles.local` persona resolver as the sole class authority for roster-v2 semantics, profile validation, provisioning, and launch/persona resolution. Add exact approved alias canonicalization, fail-closed semantic validation, immutable canonical-class authority contracts, required baseline roles, and operator documentation without implementing lifecycle, mutation, credentials, certificate workflow, or later FCM cards.
## Budget
- Soft budget: **25K tokens**.
- Strategy: inspect once, implement in small TDD units, run focused suites before the full package gate, and avoid unrelated refactors or M1-003/M2/M4 scope.
## Plan
1. Map the existing persona resolver, roster-v2 compiler, profile/provision consumers, launch resolution, role library, and focused tests.
2. Write denial/invariant tests first for aliases, canonicalization-before-override, unreadable roles, authority boundaries, policy mismatch, canonical provision output, and resolver parity.
3. Run the focused suites and record the expected red evidence.
4. Implement one shared canonical resolution and authority contract in/through `fleet-personas.ts`; delegate roster semantic validation and profile/provision paths to it.
5. Add baseline `validator`, `team-leader`, and `interaction` role contracts plus `LIBRARY.md` entries while retaining `operator-interaction` compatibility.
6. Add the required role reference, alias migration, customization guide, and roster-v2 semantic handoff documentation.
7. Run focused tests, the full `@mosaicstack/mosaic` suite, typecheck, lint, Prettier, `git diff --check`, situational verification, independent code/security review, and remediation.
8. Commit with the required co-author trailer, run the CI queue guard, push the existing branch, and create/update exactly one PR to `main` with `Refs #758`.
## TDD evidence
### Red
After installing worktree-local dependencies and building `@mosaicstack/db`, the pre-implementation
focused run collected the intended tests and failed as expected:
```text
2 test files failed; 32 tests failed; 32 tests passed
```
Expected failures named the missing `canonicalizeRoleClass`,
`authorityForCanonicalClass`, and `validateRosterV2Semantics` APIs, absent requested/canonical typed
output, and unresolved required canonical role contracts. An earlier run that failed before test
collection on an unresolved `yaml` dependency was treated as environment setup, not TDD evidence.
### Green
Focused role-resolution and affected service fixtures:
```text
6 test files passed; 109 tests passed
```
The focused set covers personas, profiles, provision, launch persona contract, roster-v2 semantics,
and the operator-interaction service fixture. The final profile tests also cover readable lead/floor
compatibility and canonical collision denial.
## Tests and gates
- Focused suites: pass, **6 files / 109 tests**.
- Full `@mosaicstack/mosaic` suite: pass, **50 files / 713 tests**. Workspace package build outputs
were prepared first because a clean worktree has no dependency `dist` entries.
- `pnpm --filter @mosaicstack/mosaic typecheck`: pass.
- `pnpm --filter @mosaicstack/mosaic lint`: pass.
- Prettier check over every changed file: pass.
- `git diff --check`: pass.
- Runtime/file-boundary evidence: real role library, profile/provision filesystem integration,
launch-time synchronous contract injection, v1 roster parser round-trip, roster-v2 semantic
filesystem checks, and operator-interaction service fixtures all pass without live mutation.
- Independent code review: **APPROVE**, no blocking or non-blocking findings; reviewed complete
tracked/untracked delta including the canonical collision guard. Residual: roster-v2 semantic
validation is an explicit async handoff with production caller wiring owned by later work.
- Independent security review: **APPROVE**, no verified authority/security findings on the final
delta.
### Post-PR fail-closed remediation
Independent rereview found resolver fail-open edges that the original green PR head did not cover. The
remediation remained uncommitted until every finding was reproduced red-first and the same reviewer
approved the complete two-file delta.
Final regression evidence:
```text
persona resolver: 47/47
focused affected suites: 6 files / 138 tests
root-container resolver suites: 86/86
full canonical run: 42/42 Turbo tasks; Mosaic 50 files / 733 tests
```
DB migration, typecheck, lint, Prettier, and `git diff --check` also passed. Coverage now proves:
- unreadable, unscannable, direct-dangling, ancestor-dangling, and literal `..` traversal override paths
fail closed across async, sync, listing, and status APIs;
- genuinely missing override directories still permit baseline fallback;
- cached missing scans are revalidated before fallback;
- marker-defined identity and domain metadata are revalidated on the second read;
- `LIBRARY.md` rows and incidental later markers cannot define, shadow, or advertise personas.
Exact-head pipeline `1819` passed for rebased head `4d990eee…`, but the independent reviewer-of-record
returned **REQUEST CHANGES** after reproducing three additional edge failures: a canonical filename could
inherit protected authority despite a conflicting explicit first marker, cached `scanned` absence could
miss an override created before baseline fallback, and inherited plain-object names such as `constructor`
could corrupt alias/authority lookup. Merge remained held.
Each failure was reproduced red-first in the persona suite (4 failing assertions), then remediated without
expanding card scope. Explicit first markers now own identity and filename fallback applies only to
markerless contracts; every second read rejects a newly introduced conflicting marker regardless of
cached classification; cached async resolution re-scans the override layer immediately before every
baseline fallback; alias and authority registries require own-property matches. Current uncommitted
evidence is persona **52/52**, focused affected suites **6 files / 143 tests**, and full Mosaic package
**50 files / 738 tests**, plus typecheck, lint, Prettier, and `git diff --check`. Independent
finding-specific rereview **APPROVED** the complete uncommitted three-file remediation after direct
adversarial reproduction of all three findings and the follow-up markerless TOCTOU. All post-commit
exact-head gates remain required.
## Risks and boundaries
- **Security-sensitive authority:** authority must derive only from canonical class, never role prose, aliases, display names, or tool-policy text.
- **Resolver divergence:** no second regex, registry, scanner, or prose parser may be introduced.
- **Alias capture:** aliases must canonicalize before baseline/`roles.local` lookup so local files cannot redefine legacy aliases as separate authority.
- **Readable persona requirement:** semantic success requires a resolved readable persona, not class-set membership.
- **Scope control:** no roster mutation, lifecycle, lease issuance, certificate workflow/storage, credentials, remote reconciliation, provision-v2 conversion, or shipped-example disposition execution.
- **Coordination:** `docs/TASKS.md` is read-only and remains orchestrator-owned.
## Acceptance-evidence mapping
| Requirement / criterion | Verification evidence |
| --- | --- |
| `FCM-REQ-02` shared semantic resolver | Async/sync resolver parity; roster-v2 delegates batched scans and resolution; profiles/provision and launch reuse `fleet-personas.ts`; no second scanner or class-marker regex added. |
| `FCM-REQ-07` canonical classes and authority boundaries | Exact alias and non-alias tests; immutable authority invariant tests; all required canonical contracts resolve through the real role library. |
| `AC-FCM-01` structural + semantic roster validation | Synchronous parser/normalizer tests remain intact; async semantic tests cover aliases, custom roles, unreadable/unresolved roles, `LIBRARY`-only rejection, and bidirectional protected policy mismatch. |
| `AC-FCM-07` protected authority invariants | Denial tests prove merge-gate-only merge, validator certificate-only, orchestrator/team-leader/interaction limits, no implicit custom-role authority, and canonical tool-policy matching. |
## Documentation
- `docs/fleet/reference/role-classes.md`
- `docs/fleet/migration/legacy-class-aliases.md`
- `docs/fleet/how-to/customize-roles.md`
- `docs/fleet/reference/roster-v2-fields.md` semantic handoff
- Baseline role contracts and `LIBRARY.md` rows for `validator`, `team-leader`, and `interaction`
## Residual risks
- Provisioning remains intentionally v1 and does not emit `reports_to`; canonical topology is retained
in its typed seat/summary path only, matching the existing v1 parser boundary.
- Alias support remains for compatibility; new configuration should emit canonical identities.
- This card defines authority metadata and validation only. Enforcement workflows for leases,
certificates, lifecycle, and mutation remain owned by later FCM cards.