feat(roles): M18 seat-role progressive capability restriction (#45)

Role contracts (roles/<role>.json): roleVersion, name bound to filename,
tools ceiling (subset of pi built-ins), network declared (none|api-only|
open; enforced when network policy lands). Strict schema, fail closed -
a non-role document refuses resolution.

mosaic-task.mjs resolve-role: config-free contract validation, emits
MOSAIC_ROLE_TOOLS / MOSAIC_ROLE_NETWORK.

agent.sh: a declared role binds to its contract. Missing/invalid contract
refuses the launch (exit 2, names the role - the under-equipped-seat
failure mode, mirroring M17 skills). Effective tools = ceiling ∩ requested
(CLI --tools or agent.json caps); no request -> ceiling stands; narrowing
and tool-free outcomes loud on stderr. Adapters unchanged; headless M9
chain (mission ∩ task) untouched.

Ships roles/researcher.json (existing seat declares the role; without the
contract the fail-closed gate would refuse its launch).

Task suite 74 -> 88: contract resolution, wrong-kind/name/network/
duplicate/unsupported/missing refusals, ceiling narrowing E2E (mock
adapter), tool-free E2E, missing-contract refusal. Test-authoring
correction recorded in BUILD-LOG (a check that registered on one path
only, caught by count arithmetic).

Suites 24/88/14/17 + verify green.
This commit is contained in:
2026-09-03 17:25:17 -05:00
parent bf56583a49
commit ca8135d70c
9 changed files with 172 additions and 6 deletions
+32
View File
@@ -467,3 +467,35 @@ Release self-determination live: the system aligns itself to RELEASE without man
The conductor loop is proven end to end on a live seat; TOOLS.md covers the
tools/ tree; suites green at every gate.
## Phase 23: M18 — seat-role progressive capability restriction
- Role contracts: `roles/<role>.json` — roleVersion, name (must match
filename), tools ceiling (subset of pi built-ins), network declared
(none | api-only | open; enforcement is a later milestone). Strict
schema, fail closed; a non-role document (e.g. conductor-policy.json)
refuses.
- `mosaic-task.mjs resolve-role <file>`: config-free validation, emits
MOSAIC_ROLE_TOOLS / MOSAIC_ROLE_NETWORK for agent.sh to consume.
- `agent.sh`: a declared role binds to its contract. Missing/invalid
contract refuses the launch (exit 2, names the role) — the
under-equipped-seat failure mode, mirroring M17 skills. Effective tools
= ceiling ∩ requested (CLI --tools or agent.json caps); no request →
ceiling stands; narrowing and tool-free outcomes are loud on stderr.
Adapters unchanged (MOSAIC_TOOLS carries the effective set); headless
M9 chain (mission ∩ task) untouched.
- Ships roles/researcher.json — the existing researcher seat declares the
role; without the contract the fail-closed gate would refuse its launch.
- Suite additions (14 cases, task suite 74 → 88): contract resolution,
wrong-kind/name/network/duplicate/unsupported/missing refusals, seat
narrowing E2E via mock adapter, tool-free E2E, missing-contract refusal.
- Test-authoring correction: the first version of the missing-contract
case registered its check only on the failure path (a `|| RC=$?` chain
swallowed it on success); caught by count arithmetic (74 + 15 ≠ 88),
restructured so the case always registers.
## Result (M18)
Seat roles are ceilings, not labels: the M15 role field now resolves to a
versioned contract that seats cannot escalate past. Suites 24/88/14/17 +
verify green.