feat(agent): fail-closed seat resolution under MOSAIC_AGENTS_DIR override (#46)

Owner decision after live verification of M18: an explicit agents-dir
override that cannot resolve the named seat now refuses the launch
(exit 4, names the seat and dir) instead of launching seatless and
unbounded. Unsetting the override keeps the M13 plain governed TUI.
MOSAIC_ROLES_DIR needs no symmetric change - the M18 gate already
refuses unresolvable role contracts.

Task suite 88 -> 90 (refusal + refusal-names-the-seat). TOOLS.md Agent
section documents the refusal.

Suites 24/90/14/17 + verify green.
This commit is contained in:
2026-09-03 19:45:10 -05:00
parent ca8135d70c
commit d1d7b5598d
7 changed files with 52 additions and 2 deletions
+23
View File
@@ -499,3 +499,26 @@ tools/ tree; suites green at every gate.
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.
## Phase 23 follow-up: fail-closed seat resolution under override (#46)
- Owner live-verified M18 (2026-09-03): ceiling narrowing note with the
correct narrowed set, missing-contract refusal message, clean researcher
launch, validator positives and negatives, tool-free note — all as
expected.
- The verification surfaced a governance gap: an explicit
MOSAIC_AGENTS_DIR override that cannot resolve the named seat still
launched — seatless, unbounded, no role ceiling to bind.
- Owner decision: fail closed. agent.sh now refuses (exit 4, names the
seat and the dir) when the override is set and no seat definition
resolves; unsetting the override keeps the M13 plain governed TUI.
MOSAIC_ROLES_DIR needs no symmetric change — the M18 gate already
refuses unresolvable contracts.
- Task suite 88 → 90 (refusal + refusal-names-the-seat); TOOLS.md Agent
section documents the refusal.
## Result
Suites 24/90/14/17 + verify green. Seat resolution is now fail closed in
every direction: unknown seat under override, declared role without
contract, invalid contract, empty ceiling intersection.
+1
View File
@@ -12,3 +12,4 @@ are never rewritten or removed; corrections are new entries.
| 2026-09-03 | assistant (conductor) + ms-test collaborator (worker, glm-5.3-flash) | Conductor-loop calibration (#43): decompose → dispatch via agent-send.sh → receipt → line-by-line diff review (claims verified vs tool source) → suite-gated integration; CURRENT.md staleness corrected (M16/M17 late-logged, next action → M18) | docs/TOOLS.md tools/ section + suite-count fix; issue #43 closed; suites 24/74/14/17 + verify green |
| 2026-09-03 | owner + assistant (conductor) + ms-test collaborator | Skill revisions adjudicated (#44): ms-communications integrated as-authored (owner preamble restructure + collaborator delivery-discipline hunks); ms-conductor collaborator redraft integrated with conductor remediation (step 3 refusal-vs-outage distinction; preserves owner's outage-dispatch intent inside fail-closed canon); TOOLS.md gains release.sh ensure row | skills/ms-communications/SKILL.md, skills/ms-conductor/SKILL.md, docs/TOOLS.md; suites 24/74/14/17 + verify green; unslop clean ×3 |
| 2026-09-03 | assistant (conductor) | M18 seat-role progressive capability restriction (#45): roles/<role>.json contracts (strict schema, name-filename binding, network declared), mosaic-task.mjs resolve-role, agent.sh ceiling intersection with fail-closed refusals, roles/researcher.json shipped, 14 suite cases (task 74 → 88) | scripts/mosaic-task.mjs, scripts/agent.sh, scripts/test-task.sh, roles/researcher.json, docs; suites 24/88/14/17 + verify green |
| 2026-09-03 | owner (decision + live verification) + assistant (conductor) | M18 live verification + follow-up (#46): owner confirmed narrowing/refusal/tool-free live; seatless launch under AGENTS_DIR override discovered and made fail-closed (exit 4); task suite 88 → 90 | scripts/agent.sh, scripts/test-task.sh, docs/TOOLS.md; suites 24/90/14/17 + verify green |
+4 -2
View File
@@ -52,7 +52,9 @@ persistent named session, optional workspace. Exit with `/quit`.
A seat role (`agent.json` `role`) binds to `roles/<role>.json` (M18): the
contract's tools are a ceiling the seat definition or `--tools` may narrow,
never escalate past. Missing/invalid contract refuses the launch; empty
intersection = loud tool-free seat.
intersection = loud tool-free seat. An explicit `MOSAIC_AGENTS_DIR` override
that cannot resolve the named seat also refuses (#46) — unset the override
for the M13 plain governed TUI.
## Release
@@ -81,7 +83,7 @@ Push is never automatic.
|---|---|---|
| `scripts/reset.sh` | Delete the data root | Triple-safety-checked (path, symlink, ownership marker) |
| `scripts/test-config.sh` | Config selftests (no Docker) | 24 cases |
| `scripts/test-task.sh` | Task selftests + live cases | 88 cases |
| `scripts/test-task.sh` | Task selftests + live cases | 90 cases |
| `scripts/test-release.sh` | Release selftests | 14 cases |
| `scripts/test-conductor.sh` | Auto-apply selftests (sandboxed) | 17 cases |
| `scripts/gitea-api.sh <METHOD> <path> [body]` | Gitea API helper | Token never on argv/stdout |
+1
View File
@@ -51,3 +51,4 @@ git history + Gitea issues.
- 2026-09-03 — conductor-loop calibration with live collaborator (#43): dispatch via agent-send.sh → receipt → line-by-line diff review → suite-gated integration; docs/TOOLS.md gains Tools (host-side) section + corrected suite counts
- 2026-09-03 — skill revisions adjudicated (#44): ms-communications integrated as-authored; ms-conductor redraft + conductor remediation (refusal vs outage); TOOLS.md release.sh ensure row
- 2026-09-03 — M18 seat-role progressive capability restriction (#45; roles resolve to contracts, ceiling ∩ seat grant, fail-closed refusals, roles/researcher.json); task suite 74 → 88
- 2026-09-03 — M18 follow-up: fail-closed seat resolution under MOSAIC_AGENTS_DIR override (#46, owner decision after live verification); task suite 88 → 90; next action M19
+8
View File
@@ -57,6 +57,14 @@ fi
# The seat's SOUL.md overrides the contract persona; governance contracts
# are never overridden.
AGENTS_DIR="${MOSAIC_AGENTS_DIR:-agents}"
# Fail closed (owner decision, #46): an explicit agents-dir override that
# cannot resolve the named seat refuses the launch - a seatless launch has
# no identity, no SOUL, and no role ceiling to bind. Unset the override for
# the M13 plain governed TUI.
if [ -n "${MOSAIC_AGENTS_DIR:-}" ] && [ ! -f "$AGENTS_DIR/$NAME/agent.json" ]; then
echo "agent: no seat definition for '$NAME' in overridden agents dir: $AGENTS_DIR (unset MOSAIC_AGENTS_DIR for a plain governed TUI)" >&2
exit 4
fi
ROLE=""
DEFCAPS=""
AGENT_DEF_SKILLS=""
+14
View File
@@ -299,6 +299,20 @@ EOF
grep -q "nothing in common -> tool-free seat" "$SANDBOX/roleseat-stderr.txt" 2>/dev/null \
&& check "tool-free outcome recorded loudly" 0 || check "tool-free outcome recorded loudly" 1
# fail-closed seat resolution (#46, owner decision): an explicit
# agents-dir override that cannot resolve the named seat refuses.
env MOSAIC_CONFIG="$SANDBOX/mock-config.json" MOSAIC_AGENTS_DIR="$SANDBOX/agents" \
scripts/agent.sh nobody </dev/null >"$SANDBOX/nobody-stdout.txt" 2>"$SANDBOX/nobody-stderr.txt"
RC=$?
if [ "$RC" -eq 4 ]; then
PASS=$((PASS+1)); echo "${C_OK}OK${C_RESET} overridden agents dir without seat definition refuses (exit 4)"
else
FAIL=$((FAIL+1)); echo "${C_FAIL}FAIL${C_RESET} overridden agents dir without seat definition refuses (exit $RC)" >&2
echo "NOBODY stderr:" >&2; cat "$SANDBOX/nobody-stderr.txt" >&2
fi
grep -q "no seat definition for 'nobody'" "$SANDBOX/nobody-stderr.txt" 2>/dev/null \
&& check "seat-resolution refusal names the seat" 0 || check "seat-resolution refusal names the seat" 1
# capability policy (M9): least-privilege intersection
POL="$SANDBOX/data/workspaces"; mkdir -p "$POL"
pol_run() { # missionTools(ABSENT|json) taskTools(ABSENT|json) -> stderr MOSAIC_TOOLS value
+1
View File
@@ -27,3 +27,4 @@ Agent operation cycles: launch, work, verify, persist, hand off.
overridden.
- If a capability you need is missing, say so. Do not improvise a capability
you were not granted.
- End your turn with a timestamp.