diff --git a/BUILD-LOG.md b/BUILD-LOG.md index 725d5780..b24dcd04 100644 --- a/BUILD-LOG.md +++ b/BUILD-LOG.md @@ -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. diff --git a/docs/SESSIONS.md b/docs/SESSIONS.md index 29578690..04f4bc7c 100644 --- a/docs/SESSIONS.md +++ b/docs/SESSIONS.md @@ -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/.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 | diff --git a/docs/TOOLS.md b/docs/TOOLS.md index 37e26adf..ebadb54a 100644 --- a/docs/TOOLS.md +++ b/docs/TOOLS.md @@ -52,7 +52,9 @@ persistent named session, optional workspace. Exit with `/quit`. A seat role (`agent.json` `role`) binds to `roles/.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 [body]` | Gitea API helper | Token never on argv/stdout | diff --git a/docs/plans/CURRENT.md b/docs/plans/CURRENT.md index 01c99acc..6c35ddf5 100644 --- a/docs/plans/CURRENT.md +++ b/docs/plans/CURRENT.md @@ -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 diff --git a/scripts/agent.sh b/scripts/agent.sh index 52e2b76e..a6169faf 100755 --- a/scripts/agent.sh +++ b/scripts/agent.sh @@ -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="" diff --git a/scripts/test-task.sh b/scripts/test-task.sh index ee153892..e157538a 100755 --- a/scripts/test-task.sh +++ b/scripts/test-task.sh @@ -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 "$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 diff --git a/skills/ms-agent/SKILL.md b/skills/ms-agent/SKILL.md index 69327b3f..ad159895 100644 --- a/skills/ms-agent/SKILL.md +++ b/skills/ms-agent/SKILL.md @@ -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.