# DECOMP-OPUS — Adversarial Task Decomposition (ROBUSTNESS SIDE) **Author:** `planner-opus` (web1), robustness/pessimist lane. **Inputs read:** `MISSION.md`, `MACP-WIRING-SCOUT.md`, `BOARD.md`, plus first-hand inspection of `/src/mosaic-stack` (`packages/{macp,coord,db,queue,forge,mosaic}`, `apps/`, `packages/mosaic/framework/tools/orchestrator-matrix/controller/mosaic_orchestrator.py`), `~/.config/mosaic/tools/{git,tmux,lease-broker}/`, and `jarvis-brain/docs/scratchpads/postmortem/REMEDIATION-DISCUSSION-STATE.md`. **Not read (by charter):** `DECOMP-SOL.md`. **Output:** 38 tasks, 8 dissents. --- ## 0. Operating premise of this decomposition Three assumptions drive every ordering choice below. All three are load-bearing; if you reject one, the DAG changes. **A1 — Every gate is inert until proven otherwise.** Gate-6 (`ci-queue-wait.sh`) passed green fleet-wide while classifying `unknown` → `exit 0`, and its fix recursed the same defect. I verified the defect is still live at `~/.config/mosaic/tools/git/ci-queue-wait.sh:282-288`: ``` terminal-success|terminal-failure|unknown) exit 0 # "unknown" == pass *) echo "unrecognized state ... proceeding conservatively"; exit 0 ``` `no-status` also exits 0 unless `--require-status` is passed, and nothing passes it (`pr-merge.sh:98`). A gate whose failure path is unreachable is worse than no gate: it manufactures evidence. **Therefore: no task in this plan may introduce a gate without a registered must-exit-nonzero negative control, and the registry check itself is a gate (R-002).** **A2 — Fail-open is the resting state of this system.** Confirmed instances, all live today: | Site | Failure mode | | ----------------------------------------- | --------------------------------------------------------------- | | `ci-queue-wait.sh:282,286` | unknown/unrecognized CI state → proceed | | `send-message.sh:111` | indeterminate submission → `✓ sent`, exit 0 | | `packages/forge/src/cli.ts:13-31` | stub `TaskExecutor` reports completion with **empty gate list** | | `lease-broker/mutator-gate.py:140` | broker socket unset → `return 0` (all tool calls allowed) | | `lease-broker/revoke-lease.py:57` | broker socket unset → `return 0` (lease never revoked) | | `lease-broker/receipt-observer-client.py` | entire client replaced by a `sys.exit(0)` stub | Four of six are _silent_. Every task below names its fail-closed point explicitly (§4). **A3 — The substrate is real, but "wire it in" understates the work.** `packages/db` already has `missions`, `tasks`, `mission_tasks`, `agents`, `interaction_{sessions,inbox,outbox,checkpoints,handoffs}` with idempotency-key unique indexes and a compaction-epoch column. `packages/queue` has a `QueueAdapter` with bullmq + local adapters. That is genuinely more than the charter credits. But see §6 — roughly a third of this plan is greenfield regardless of the slogan. --- ## 1. Phase map (ordered; hard barriers between phases marked ⛔) ``` P0 Make gates provable + stop the fleet re-bricking R-001..R-005 ⛔ barrier: no gate-introducing task may merge before R-002 P1 Durable spine (PG) R-010..R-013 ⛔ barrier: no migration may merge before R-010 P2 Single choke-point executor (MACP wire-in) R-020..R-030 ⛔ barrier: R-025 (no-second-path) must land in the same milestone as R-020 P3 Rotation lifecycle (Mission Control Plane, finished) R-040..R-045 ⛔ barrier: R-045 (delete /compact guidance) may not merge before R-042 is live P4 Comms service R-050..R-055 ⛔ barrier: R-060 (one socket per host) must precede R-052 identity addressing P5 Retirements, hygiene, conformance R-060..R-065 ``` **Deliberate divergence from `MISSION.md` sequencing:** the charter starts at "spine + choke-point (builds 1+2)". I insert P0 ahead of it. Rationale in Dissent D1. --- ## 2. Master task table `tier` = suggested runtime. `codex` = mechanical, spec is unambiguous. `sonnet` = normal feature work. `opus` = security/integrity/design-ambiguity or cross-cutting invariant. | id | title | build | depends_on | est tok | tier | | ----- | ---------------------------------------------------------------------------------------- | ----- | -------------------------- | ------- | ------ | | R-001 | Reproducible checkout + pre-push gate that fails on code, not env | 5 | — | 60k | codex | | R-002 | Gate registry + negative-control CI check (anti-inert-gate harness) | 5 | R-001 | 120k | opus | | R-003 | Queue-guard fail-closed rework (`unknown`/`no-status`/malformed) | 1 | R-002 | 100k | sonnet | | R-004 | Break-glass replaces the three silent `MOSAIC BYPASS` fail-opens | 1 | R-005, R-002 | 120k | opus | | R-005 | Activation/version coherence: block launch on host↔repo skew, fail SAFE | 5 | R-001 | 140k | sonnet | | R-010 | Fix the Drizzle postgres-tier first-install migration defect | 2 | R-001 | 90k | sonnet | | R-011 | Orchestration spine schema (macp_tasks, attempts, gate_results, ledger, claims) | 2 | R-010 | 160k | opus | | R-012 | Spine client with fail-closed connection semantics (no silent PGlite in prod) | 2 | R-011 | 80k | sonnet | | R-013 | Transactional outbox tables + reconciliation sweeper (PG-first) | 2 | R-012 | 140k | opus | | R-020 | Production `TaskExecutor` backed by `@mosaicstack/macp` | 1 | R-012, R-002 | 220k | opus | | R-021 | Gate-runner hardening: structured `GateEntry`, `fail_on`, timeout, empty-gate-set = fail | 1 | R-020 | 120k | sonnet | | R-022 | Hash-chained append-only MACPEvent ledger in PG; lifecycle EventType extension | 1 | R-020, R-011 | 160k | opus | | R-023 | `packages/coord` submits work through the executor (retire direct spawn) | 1 | R-020 | 140k | sonnet | | R-024 | `mosaic yolo\|claude\|codex\|pi` launch path records a typed Task + events | 1 | R-020, R-022 | 160k | sonnet | | R-025 | No-second-path gate: terminal status writable only by the executor | 1 | R-020, R-022 | 140k | opus | | R-026 | Delete the Forge stub executor; Forge submits through the real one | 1 | R-020 | 90k | codex | | R-027 | Seat identity from `MOSAIC_AGENT_NAME` + mandatory tri-state write outcomes | 1 | R-020 | 150k | opus | | R-028 | Typed state claims (source/confidence/TTL) with HMAC integrity, fail-closed | 1 | R-011, R-020 | 170k | opus | | R-029 | Contract-hash binding; stale generation loses mutation authority mechanically | 1 | R-020, R-028 | 180k | opus | | R-030 | Retire the Python controller + `plugins/macp` island (island count 3 → 1) | 1 | R-023, R-024, R-025, R-026 | 110k | codex | | R-040 | Durable compaction/token sensor (per-runtime thresholds, PreCompact event) | 3 | R-022, R-029 | 130k | sonnet | | R-041 | Typed checkpoint writer (structured claims, not transcript) + digest | 3 | R-028, R-040 | 150k | opus | | R-042 | Rotation daemon: watch → checkpoint → kill → relaunch → rehydrate | 3 | R-041, R-023 | 240k | opus | | R-043 | Rehydration attestation gate: refuse to act on an incomplete claim set | 3 | R-041 | 130k | opus | | R-044 | Fault-injected rotation soak (100× incl. kill-at-worst-moment) | 3 | R-042, R-043 | 180k | sonnet | | R-045 | Delete `/compact and continue` from the persistent-seat path (substitution) | 3 | R-042, R-044 | 60k | codex | | R-050 | `comms/v1` envelope + protocol-version negotiation, LOUD reject | 4 | R-011 | 140k | opus | | R-051 | Comms service: PG state machine PENDING→RECEIVED→CONSUMED→DEAD-LETTER | 4 | R-050, R-013 | 200k | opus | | R-052 | tmux transport adapter behind the service; durable retry before cursor advance | 4 | R-051, R-060 | 160k | sonnet | | R-053 | Per-class coalescing + supersede (stale-consumed-as-live fix) | 4 | R-051 | 130k | sonnet | | R-054 | Redis hot path + provenance guard (Redis may never be read as authority) | 4 | R-051, R-013 | 170k | opus | | R-055 | Retire direct tmux sends; only the service may write a pane | 4 | R-052, R-053 | 100k | codex | | R-060 | One roster-owned socket/host, quarantine unmanaged, stale-session GC | 5 | R-005 | 150k | sonnet | | R-061 | Auto-sync allowlist (never auto-stage unknown paths) + worktree isolation | 5 | R-002 | 110k | sonnet | | R-062 | Flat-file orchestration tracking → DB cutover, with rollback artifact | 2/5 | R-024, R-028, R-042 | 200k | opus | | R-063 | Conformance harness: fault injection of the six live failure classes | 5 | R-044, R-051, R-062 | 260k | opus | | R-064 | Fleet-wide inert-gate audit against the R-002 registry | 5 | R-002 | 120k | sonnet | | R-065 | Retirement proof: CI asserts all three retirements are complete and stay complete | 5 | R-030, R-055, R-062 | 90k | codex | **Totals:** 38 tasks, ~5.3M estimated worker tokens. Critical path (longest chain): `R-001 → R-010 → R-011 → R-012 → R-020 → R-022 → R-029 → R-040 → R-041 → R-042 → R-044 → R-062 → R-063` = 13 tasks, ~2.2M tokens. Everything else parallelizes against it. --- ## 3. Task detail Acceptance criteria are written **diff-blind**: each is a command a reviewer can pre-register and run against the merged branch without having read the implementation. `⇒0` / `⇒≠0` denote required exit codes. Every gate-introducing task carries at least one **must-fail** case — that is the whole point. ### P0 — Make gates provable --- **R-001 — Reproducible checkout + pre-push gate that fails on code, not env** _build 5 · depends: — · 60k · codex_ The mosaic-stack checkout on web1 cannot push: `pre-push` typecheck fails on `Cannot find module commander/@mosaicstack/*` because deps were never installed. A gate that reds on environment is indistinguishable from a gate that reds on defect, so the fleet learns to bypass it. This is the mission's own TASK-0 and it blocks literally every other PR. - AC1: from a clean clone in the CI container, `pnpm install --frozen-lockfile && pnpm -w typecheck` ⇒0. - AC2: `scripts/preflight.sh` (or equivalent named in the PR) run with node_modules removed ⇒≠0 with a message containing `run pnpm install`, and ⇒0 after install. **Negative control: the missing-deps case must be distinguishable by exit code or a machine-greppable marker from a real type error.** - AC3: introduce `export const x: number = "s"` in any package → gate ⇒≠0. Revert → ⇒0. - AC4: `git status --porcelain` in a fresh clone after a full build is empty (no generated residue). - Dogfood: live finding 2026-07-31 — "pre-push gate red on env not code"; `.bak`/`.mosaic-bak` and untracked `apps/coordinator` residue in the checkout. - Fail-closed point: none introduced; this restores the _ability_ to fail closed. --- **R-002 — Gate registry + negative-control CI check (anti-inert-gate harness)** ★ keystone _build 5 · depends: R-001 · 120k · opus_ The generalization of P-QUEUE-001. A machine-readable registry (`gates.manifest.json` or equivalent) where every deterministic gate in the repo declares: invocation, the input classes it must distinguish, and for **each** class the required exit code. A CI job runs every declared case and fails if (a) any must-fail case exits 0, (b) any gate in the tools tree is absent from the registry, or (c) a gate has zero must-fail cases registered. - AC1: `pnpm gate:verify` ⇒0 on main. - AC2: mutate any registered gate's failure branch to `exit 0` → `pnpm gate:verify` ⇒≠0 naming that gate. **This is the meta-negative-control; a reviewer must run it.** - AC3: add a new executable under the gates path without a registry entry → ⇒≠0 with `unregistered gate`. - AC4: registry entry with zero must-fail cases → ⇒≠0 with `no negative control`. - AC5: the check runs in CI on every PR, not only on gate-file changes (an inert gate is usually made inert by a change _elsewhere_, e.g. an env var going unset). - Dogfood: gate-6 inert fleet-wide; #1019 fix (PR #1023 @f6334080) recursed the same defect — rev-974 review id 58 finding (3): "tests don't assert exit outcomes." - Fail-closed point: **unregistered gate ⇒ build fails.** Not "warn". - Secretly greenfield: yes, small. Accept it — it is the cheapest insurance in the plan. --- **R-003 — Queue-guard fail-closed rework** _build 1 · depends: R-002 · 100k · sonnet_ `ci-queue-wait.sh` must stop treating `unknown`, unrecognized, and `no-status` as pass. Spec is rev-974 review id 58: (1) `unknown` must not exit 0; (2) payload via stdin/temp file, never argv (ARG_MAX rc126 at ~150 KiB); (3) tests assert exit outcomes. - AC1: synthetic status payloads for each class ⇒ `pending` (loops then times out ⇒124), `terminal-success` ⇒0, `terminal-failure` ⇒≠0, `no-status` ⇒≠0, `malformed JSON` ⇒≠0, `unknown` ⇒≠0. Each is a registered R-002 case. - AC2: a 150 KiB status payload ⇒ not 126, and classification unchanged. - AC3: `--force-proceed` (if provided) requires a reason, emits a WARN to stderr, and is a registered break-glass per R-004 — never a silent default. - AC4: `pr-merge.sh` invokes the guard with `--require-status`; removing that flag ⇒ R-002 fails. - Dogfood: gate-6 inert + #1019 recursion. - Fail-closed point: **every non-green classification blocks the merge.** - ⚠ **Authority conflict:** #1019/PR #1023 is explicitly PARKED under Mos ("delivery stack still parked"), while Build 1 absorbs QUEUE and HOLD is lifted for this workstream. The orchestrator must resolve ownership with Mos _before dispatch_, or two lanes will rework the same branch. Flagged, not assumed. --- **R-004 — Break-glass replaces the three silent `MOSAIC BYPASS` fail-opens** _build 1 · depends: R-005, R-002 · 120k · opus_ Convert `mutator-gate.py:140`, `revoke-lease.py:57`, and the `receipt-observer-client.py` stub from permanent silent fail-open into LOUD + AUDITED + TEMPORARY break-glass: explicit opt-in token with an expiry timestamp, a WARN on stderr every invocation, an audit line written on every use, and refusal once expired. - AC1: with break-glass unset and the broker absent → gate ⇒≠0 (denies) with `GATE_UNAVAILABLE`. - AC2: with a valid unexpired break-glass token → ⇒0, **and** an audit record exists containing seat, reason, expiry, and invocation count. - AC3: with an expired token → ⇒≠0. (Registered must-fail case.) - AC4: `grep -rn "MOSAIC BYPASS" ~/.config/mosaic/tools/` returns nothing after deploy; a CI check fails if the marker reappears without a matching registry entry. - AC5: the Stop-hook client never blocks a turn — observational hooks exit 0 on transport failure **but record a delivery-failure event**; silence is the bug, not the exit code. - Dogfood: Pi brick (fail-closed gate whose recovery needed the gate it was blocked by) + the 2026-07-22 fleet incident that motivated all three bypasses. - Fail-closed point: **broker absent + no break-glass = deny.** - ⚠ **Ordering hazard (severe):** merging R-004 before R-005 re-bricks the fleet exactly as on 2026-07-22. The bypasses exist because the broker daemon was never _deployed_ on this host, not because the code was wrong. Removing the fail-open before deployment coherence is real recreates the incident. **Hard edge: R-005 → R-004.** --- **R-005 — Activation/version coherence: block launch on host↔repo skew, fail SAFE** _build 5 · depends: R-001 · 140k · sonnet_ P-ACTIVATION-001. Transactional install of CLI + hooks + broker + version; launch refuses to start a seat whose deployed framework version disagrees with the repo/roster expectation. "Fail SAFE" per Jason's caveat = clear message + exact fix command, not a bare hard stop. - AC1: with matching versions, `mosaic claude` launches ⇒0. - AC2: with a deliberately skewed deployed version, launch ⇒≠0 and stdout contains both versions and a copy-pasteable remediation command. (Registered must-fail case.) - AC3: interrupted install (kill mid-way) leaves the previous version fully functional — no half-installed hook set. Verified by: install, `kill -9` at a scripted point, then run the previous version's smoke test ⇒0. - AC4: `mosaic doctor` reports broker deployed/undeployed **honestly** — the label must change when the daemon is stopped. (Direct fix for P-RECOVERY-001's "honest capability labeling".) - Dogfood: the 2026-07-22 incident root cause ("#828 lease-broker daemon + launch-time env injection were never deployed on this host"); the messy-checkout finding. - Fail-closed point: **version skew blocks launch.** ### P1 — Durable spine --- **R-010 — Fix the Drizzle postgres-tier first-install migration defect** ★ hidden blocker _build 2 · depends: R-001 · 90k · sonnet_ `packages/db/src/migrate.ts:30-38` carries a TODO admitting that **postgres-tier first-install fails today**: Drizzle wraps all migrations in one transaction (breaking 0009's `ALTER TYPE ADD VALUE` → `SET DEFAULT` sequence) and `drizzle/meta/_journal.json` orders 0009 before 0008, which the postgres-js migrator silently skips by `created_at < folderMillis`. The PGlite path sidesteps both — which means the spine has only ever been proven on the embedded tier. - AC1: against a **fresh empty** Postgres 16 container, `runMigrations()` ⇒0 and every table in `schema.ts` exists (assert by count and by name list). - AC2: `drizzle.__drizzle_migrations` contains one row per migration file, in file order. - AC3: re-running `runMigrations()` on the migrated DB ⇒0 and is a no-op (row count unchanged). - AC4: a deliberately corrupted journal ⇒≠0 (must not silently skip). Registered must-fail case. - Dogfood: synthetic — no live incident yet, because nobody has first-installed the PG tier. That is precisely the danger: the first real deploy is the discovery event. - Fail-closed point: **skipped migration ⇒ error, never silent skip.** - ⚠ **Ordering hazard:** every later task that adds a migration (R-011, R-013, R-022, R-028, R-050, R-051) silently depends on this. Without it they pass on PGlite in CI and fail on the real deploy. --- **R-011 — Orchestration spine schema** _build 2 · depends: R-010 · 160k · opus_ New tables, **not** overloads of existing ones: `macp_tasks` (typed MACP `Task`), `task_attempts`, `gate_results`, `macp_events` (append-only, monotonic sequence, `prev_hash`/`hash` chain), `state_claims` (typed claim with source/confidence/TTL + HMAC), `seat_identities`, `contract_bindings`. Reuse `missions`/`mission_tasks` for mission linkage. - AC1: migration applies to fresh PG ⇒0 (per R-010 AC1). - AC2: `UPDATE macp_events SET ...` or `DELETE FROM macp_events` ⇒ error (append-only enforced by trigger/rule or role grants, not convention). Registered must-fail case. - AC3: inserting an event whose `prev_hash` does not match the current head ⇒ error. - AC4: a verification function reports gap/tamper on an artificially altered chain ⇒≠0. - AC5: `state_claims` rows carry a non-null TTL; a query helper returns expired claims as _expired_, never as valid. - Dogfood: P-AUDIT-001's duplicate ledger (`.mosaic/orchestrator/events.ndjson` written by `mosaic_orchestrator.py:129-133` and only _read_ by `plugins/mosaic-framework`). - Fail-closed point: **ledger mutation attempt = hard DB error.** - Design note (deliberate divergence): the existing `events` table (`schema.ts:264`) is a generic `type/title/description/date` record — a calendar-shaped table. Overloading it as the audit ledger would defeat append-only enforcement. Separate table. --- **R-012 — Spine client with fail-closed connection semantics** _build 2 · depends: R-011 · 80k · sonnet_ `packages/db` exposes both `createDb` (postgres) and `createPgliteDb` (embedded). If production can ever silently land on PGlite, two seats will happily write to two different "systems of record." - AC1: with `NODE_ENV=production` and no `DATABASE_URL`, client construction ⇒ throws (not a fallback). Registered must-fail case. - AC2: with `DATABASE_URL` set to an unreachable host, the first operation ⇒ throws within the configured timeout; no in-memory substitute is created. - AC3: PGlite construction in production requires an explicit `MOSAIC_ALLOW_EMBEDDED_DB=1`; absent it, ⇒ throws. - AC4: a health endpoint/CLI reports which tier is in use, and the string differs between tiers. - Dogfood: synthetic (the split-brain this prevents has not happened yet _because_ the spine is not wired — it would happen on week one of Build 2). - Fail-closed point: **no DB ⇒ no work.** The choke point must not degrade to local files. --- **R-013 — Transactional outbox + reconciliation sweeper** _build 2 · depends: R-012 · 140k · opus_ PG commit first, then enqueue. A sweeper deterministically re-enqueues rows that committed in PG but never reached the hot queue. `interaction_outbox` already has the right shape (`idempotency_key` unique per session, status enum, `content_digest`) — reuse it rather than adding a fourth queue concept. - AC1: kill the process between PG commit and enqueue → sweeper re-enqueues within N seconds; the consumer sees the message exactly once (idempotency key enforced). - AC2: enqueue the same idempotency key twice → one delivery, one PG row. Registered must-fail case is the inverse: a duplicate delivery ⇒ test fails. - AC3: with Redis unavailable, writes still commit to PG and are marked `pending`; nothing is lost and nothing reports delivered. - AC4: sweeper is idempotent — running it twice concurrently produces no duplicates (advisory lock). - Dogfood: MACP scout notification BOUNCE ("tmux target not found") — never RECEIVED, no retry, found only by manual liveness check. - Fail-closed point: **an un-acked message is never marked delivered.** ### P2 — The single choke point --- **R-020 — Production `TaskExecutor` backed by `@mosaicstack/macp`** ★ keystone _build 1 · depends: R-012, R-002 · 220k · opus_ One Node executor: validate a typed `Task` (schemas exist at `packages/macp/src/schemas`), call `resolveCredentials`, run the process, call `runGates`, emit `MACPEvent`s, persist a `TaskResult`. Exposed as a programmatic API **and** a CLI. Note the scout's observation that there is no exported programmatic `submit` — that is this task. - AC1: submitting a Task that fails schema validation ⇒≠0 before any process spawn; assert no child process was created and no event row exists. Registered must-fail case. - AC2: a Task whose provider credentials cannot be resolved ⇒≠0 with `CredentialError`; **assert the credential value never appears in stdout, stderr, the event ledger, or the task row.** - AC3: a successful Task produces, in order: `task.assigned`, `task.started`, `rail.check.*` per gate, `task.completed` — all in `macp_events`, chain-valid. - AC4: a Task whose gate fails is recorded `failed`/`gated`, never `completed`. - AC5: killing the executor mid-run leaves the task in a non-terminal state that the sweeper can reclaim — **never** `completed`. - AC6: two executors racing on the same task id → exactly one runs (row-level claim). - Dogfood: the entire "stranded MACP" finding — zero production calls to `runGates`/`emitEvent`/`resolveCredentials`. - Fail-closed points: schema-invalid ⇒ reject; credentials unresolved ⇒ reject; crash ⇒ non-terminal. - ⚠ **I dissent from the scout's chosen wire-in point.** See Dissent D2 — wiring into `mosaic_orchestrator.py::run_single_task` puts the choke point inside a controller that is `"enabled": false`. Wire the live path instead (R-023/R-024) and delete the Python rail (R-030). --- **R-021 — Gate-runner hardening** _build 1 · depends: R-020 · 120k · sonnet_ The Python rail runs raw string gates (`mosaic_orchestrator.py:213-235`) with no `type`/`fail_on` support. The package runner supports structured `GateEntry`. Hardening: honor `fail_on`, enforce per-gate timeouts distinct from the task timeout, and **treat an empty gate list as failure for gate-required task types** rather than as vacuous success. - AC1: `quality_gates: []` on a `coding` task ⇒ task not `completed`; error names `no gates configured`. Registered must-fail case — this is the Forge-stub bug generalized. - AC2: a gate exceeding its timeout ⇒ `timed_out: true` and `passed: false` (never `passed: true`). - AC3: `fail_on: blocker` with 3 non-blocking findings ⇒ pass; with 1 blocker ⇒ fail. - AC4: a gate command that does not exist (rc 127) ⇒ fail, not skip. - Dogfood: `packages/forge/src/cli.ts:13-31` stub executor "immediately reports completion with empty gates" — an inert gate by construction. - Fail-closed point: **absent/unrunnable gates fail the task.** --- **R-022 — Hash-chained MACPEvent ledger + lifecycle EventType extension** _build 1 · depends: R-020, R-011 · 160k · opus_ Emitter targets PG (via R-011), not a caller-supplied NDJSON path. Extend `EventType` from task-centric to lifecycle-first-class: `session.launched`, `mission.generated`, `compaction.detected`, `session.rotated`, `recovery.started`, `breakglass.used`, `inbox.received`, `inbox.consumed`, `terminal.disposition`. Executor-emitted, so lines are identical across Claude/Codex/Pi. - AC1: run the same Task under two runtimes → event sequences are byte-identical modulo `event_id`/`timestamp`/`source`. **This is the runtime-neutrality claim; assert it, don't assume it.** - AC2: chain verification ⇒0 on a healthy ledger, ⇒≠0 after any row alteration. Registered must-fail case. - AC3: an emit that fails to persist ⇒ the operation it describes fails (no fire-and-forget logging on the authority path). - AC4: `grep -rn "events.ndjson"` in production paths returns nothing (the duplicate island is gone — enforced permanently by R-065). - Dogfood: P-AUDIT-001 duplicate Python ledger; `#1018` reconstruction required manual archaeology. - Fail-closed point: **unpersistable event ⇒ operation fails.** (Yes, this trades availability for auditability. That is the decision on record.) --- **R-023 — `packages/coord` submits through the executor** _build 1 · depends: R-020 · 140k · sonnet_ `packages/coord/src/runner.ts:397-427` spawns a child process directly and tracks tasks in `docs/TASKS.md` + `.mosaic/orchestrator/mission.json`. Replace the spawn with an executor submit; keep coord's session-lock/crash-recovery, which are good and already durable. - AC1: `mosaic coord run` on a fixture mission produces `macp_tasks` + `macp_events` rows; no direct `spawn` of the runtime remains in `runner.ts` (assert by AST/grep in the R-025 check). - AC2: crash recovery still works — kill mid-task, resume, and the task is reclaimed exactly once. - AC3: coord no longer _writes_ `docs/TASKS.md` as authority (read-only mirror at most, per R-062). - Dogfood: coord/macp "disconnected islands" finding. - Fail-closed point: **coord cannot complete a task the executor did not run.** --- **R-024 — Launch path records a typed Task** _build 1 · depends: R-020, R-022 · 160k · sonnet_ `packages/mosaic/src/commands/launch.ts:730-843,1102-1167` dispatches `mosaic yolo|claude|codex|pi` straight to harness launch with no Task, no gates, no events. This is the path humans and the fleet actually use — leaving it outside the choke point leaves the choke point decorative. - AC1: `mosaic yolo …` creates a `macp_tasks` row and `session.launched` event before the harness starts. - AC2: the launch is refused if the spine is unreachable (per R-012). Registered must-fail case. - AC3: seat identity is bound at launch and present on every subsequent event from that seat. - AC4: an interactive/ephemeral launch class is supported explicitly and is _labeled_ as ungated — honest capability labeling, not a silent hole. - Dogfood: "direct `mosaic yolo|claude|codex|opencode|pi` also bypasses it" (scout, §2). - Fail-closed point: **no spine ⇒ no launch** (for managed seats). --- **R-025 — No-second-path gate** ★ the invariant that makes R-020 mean anything _build 1 · depends: R-020, R-022 · 140k · opus_ A choke point is only a choke point if nothing else can write a terminal outcome. Two layers: (1) DB — only the executor's role may insert/transition to terminal status; (2) CI — a static check that fails the build if any file outside the executor writes terminal task status, appends to the ledger, or spawns a runtime process directly. - AC1: a test that connects as a non-executor role and attempts to set `status='completed'` ⇒ error. Registered must-fail case. - AC2: add a file that writes terminal status → CI ⇒≠0 naming the file. **A reviewer must actually add such a file and watch it fail.** - AC3: the check enumerates its own coverage (which paths it scanned) so an empty scan is visible — an inert _checker_ is the same disease. - AC4: known-legitimate exceptions live in an explicit allowlist with a reason string; an empty-reason entry ⇒≠0. - Dogfood: three parallel MACP islands, each able to declare a task complete. - Fail-closed point: **unknown writer ⇒ build fails / DB rejects.** - ⚠ **Ordering hazard:** if R-025 lands a milestone _after_ R-020, the intervening period is when a fourth island gets built, and R-025 then arrives as a large adversarial refactor nobody wants to merge. Ship them together. --- **R-026 — Delete the Forge stub executor** _build 1 · depends: R-020 · 90k · codex_ - AC1: `mosaic forge run` on a fixture pipeline produces real gate results; a fixture with a failing gate ⇒≠0. Registered must-fail case (today it would exit 0 with empty gates). - AC2: `grep -n "stub" packages/forge/src/cli.ts` returns nothing; type-only MACP imports become value imports. - Dogfood: `packages/forge/src/cli.ts:13-31,167,185`. - Fail-closed point: **no executor injected ⇒ forge refuses to run** (not "runs with a stub"). --- **R-027 — Seat identity + mandatory tri-state write outcomes** _build 1 · depends: R-020 · 150k · opus_ Two halves of P-WRAPPER-001. (a) Identity derives from `MOSAIC_AGENT_NAME` at launch, survives respawn, resolved through the executor's credential binding, fail-closed. (b) Every write/send wrapper returns exactly one of `written+verified` / `written+unverified` / `not-written`, with distinct exit codes. Today `send-message.sh:111` prints `✓ sent … (submission state indeterminate)` and **exits 0** — a written+unverified reported as success, the discarded-measurement anti-pattern. - AC1: respawn a seat pane; the seat's identity is unchanged and `MOSAIC_GIT_IDENTITY` resolves without being inlined by hand. (rev-974's respawn dropped it → token-not-found → could not post.) - AC2: with identity unresolvable, the wrapper ⇒≠0 and performs no write. Registered must-fail case. - AC3: force an indeterminate submission (target pane exists, no verification possible) ⇒ exit code distinct from both success and hard failure, and the string `unverified` on stderr. - AC4: a caller that treats any nonzero as success is caught by an R-002 registry case. - AC5: safe target metadata (repo / id / head SHA) is named in the wrapper contract and verified by construction — a review posted to the wrong SHA ⇒≠0. - Dogfood: rev-974 identity drop (OpenBrain 33bef845); pepper written+verified; the written+unverified silent-success class. - Fail-closed point: **cannot verify ⇒ never report success.** --- **R-028 — Typed state claims with HMAC integrity** _build 1 · depends: R-011, R-020 · 170k · opus_ Replace the prose-blob checkpoint with typed claims: `{key, value, source, confidence, ttl, refreshed_at}`, signed. A corrupt or unsigned claim set refuses to rehydrate and forces refresh. - AC1: write a claim set, flip one byte, attempt rehydrate ⇒≠0 with `integrity`. Registered must-fail case. - AC2: an expired claim is returned as expired and cannot satisfy a required-claim check. - AC3: an unsigned claim set (legacy/prose) ⇒ refuse, with a named migration path. - AC4: the HMAC key is resolved through the credential path and never appears in any output. - Dogfood: `MOS-ORCHESTRATION-BOARD-LIVE.md` is the model-maintained manual prototype of this; Jason's "assuming data not corrupted" is the requirement being mechanized. - Fail-closed point: **corrupt checkpoint ⇒ refuse-rehydrate + force refresh.** --- **R-029 — Contract-hash binding; stale generation loses authority** _build 1 · depends: R-020, R-028 · 180k · opus_ Session binds a hash of (Constitution + AGENTS + runtime contract + skill set) at launch **and** at rotation. On policy change or compaction-detected, the seat must re-attest before acting. The executor refuses mutations carrying a stale generation. - AC1: submit a task with a stale contract hash ⇒ rejected with `stale_generation`. Registered must-fail case. - AC2: change any directive file → the next submit from an unrefreshed seat ⇒ rejected. - AC3: after re-anchor, the same submit ⇒0. - AC4: the rejection is _mechanical_ — assert it happens with the seat's LLM removed from the loop (a scripted client reproduces it). - Dogfood: the postmortem's opening incident — a compacted orchestrator that kept acting after its directives rotted; this very mission's `CLAUDE.md` context-rot guard is the prose version. - Fail-closed point: **stale hash ⇒ mutations rejected.** - Note: this is the single most likely task to produce fleet-wide breakage on rollout. Ship behind a report-only mode first (log rejections without enforcing) for one measured window, then flip. Report-only mode must itself be time-boxed and expire — see D5. --- **R-030 — Retire the Python controller + `plugins/macp` island** _build 1 · depends: R-023, R-024, R-025, R-026 · 110k · codex_ Delete `mosaic_orchestrator.py`'s exec/gate/event block and the redefined types in `plugins/macp/src/macp-runtime.ts:43-77`; repoint the OpenClaw ACP backend at the Node executor. Also removes the dangling reference to `tools/macp/dispatcher/pi_runner.ts`, which does not exist in this checkout. - AC1: island count is 1 — `grep -rn "def emit_event\|def append_event" packages/` returns nothing. - AC2: `plugins/macp` integration test drives a task end-to-end through the Node executor. - AC3: `.mosaic/orchestrator/{tasks,state}.json` and `events.ndjson` are no longer written by any code path (assert by running a full mission and checking mtimes). - AC4: a re-added duplicate emitter ⇒ R-025/R-065 check fails. - Dogfood: "three parallel islands, none wired." - Fail-closed point: n/a — this is a deletion; its guard is R-065. ### P3 — Rotation lifecycle --- **R-040 — Durable compaction/token sensor** _build 3 · depends: R-022, R-029 · 130k · sonnet_ Nothing durable tracks compactions today (the Claude compaction hook "revokes leases only"). Add a per-seat token/compaction counter persisted to the spine, with per-runtime thresholds (Claude ~200k, Codex/Pi ~372k) as configuration, not constants in code. - AC1: driving a seat past the configured threshold emits `compaction.pending` with the observed count. - AC2: an actual harness compaction emits `compaction.detected` — assert by triggering a real compaction, not by unit-mocking the hook. (A mocked sensor is an inert sensor.) - AC3: sensor failure (hook not installed) ⇒ the seat is reported _unmonitored_, and managed seats refuse to start unmonitored. Registered must-fail case. - AC4: counters survive a seat respawn. - Dogfood: this orchestrator lineage itself — a compacted session that kept operating. - Fail-closed point: **no sensor ⇒ seat not managed ⇒ launch refused.** --- **R-041 — Typed checkpoint writer** _build 3 · depends: R-028, R-040 · 150k · opus_ Structured essentials only — goal, current state, completed, blocked, next steps, constraints (mission-control FR-5) — as signed claims. **Never the transcript.** Atomic write + digest, into `interaction_checkpoints` (which already has `checkpoint_id`, `content_digest`, `compaction_epoch`). - AC1: checkpoint written; digest verifies; `compaction_epoch` increments monotonically. - AC2: kill the writer mid-write → no partial checkpoint is readable (atomic rename or tx). Registered must-fail case: a torn checkpoint must be rejected, not repaired-by-guess. - AC3: the checkpoint contains no transcript text — assert by size bound and by a content check that rejects raw conversational turns. - AC4: required claim classes are declared in a manifest; a checkpoint missing one is _invalid at write time_, not at read time. - Dogfood: the abandoned coordinator PoC "compacted THE CHAT (the anti-pattern) not canonical mission state." - Fail-closed point: **incomplete claim set ⇒ checkpoint rejected.** --- **R-042 — Rotation daemon** ★ the build-3 deliverable _build 3 · depends: R-041, R-023 · 240k · opus_ The missing piece: a deterministic program (no LLM in the loop) that watches the sensor, checkpoints atomically, kills the old session, launches a fresh one, and rehydrates. Reuse coord's `buildContinuationPrompt`, session lock, `writeAtomicJson`, and crash recovery. The abandoned `apps/coordinator` PoC stalled exactly here: `_spawn_agent()` was a stub and `_check_context()` only _logged_ "rotation needed" and never called `trigger_rotation()`. - AC1: a seat crossing threshold rotates without operator action; the new seat reports the same mission and next action. - AC2: **`_check_context`-equivalent regression test:** a test asserts that threshold-crossing actually _invokes_ rotation (not merely logs it). This is the specific historical failure — assert the call, not the log line. Registered must-fail case: stub out the rotation call → test ⇒≠0. - AC3: no LLM call occurs in the rotation path — assert by running the daemon with network/model access denied; rotation still completes. - AC4: rotation is idempotent under double-trigger (only one new session). - AC5: if checkpoint or rehydration fails, the daemon does **not** kill the old session. - Dogfood: `apps/coordinator` residue (Jan–Feb 2026, deliberately abandoned). - Fail-closed points: **checkpoint failure ⇒ no kill**; **rehydration failure ⇒ no promote** (R-043). - ⚠ Secretly greenfield: the daemon does not exist in any form. `packages/coord` has primitives, not a daemon. Budget accordingly. --- **R-043 — Rehydration attestation gate** _build 3 · depends: R-041 · 130k · opus_ The pessimist's core objection to rotation: an incomplete rehydration produces a clean-looking session with a silent gap — worse than a compacted one, because nothing signals the loss. A fresh seat must attest that every required claim class is present and digest-valid _before_ it is permitted to act. - AC1: with a complete claim set, the new seat's first mutation ⇒0. - AC2: with one required claim class removed, the new seat's first mutation ⇒ rejected with `rehydration_incomplete`, and the seat is quarantined, not silently continued. Registered must-fail case. - AC3: attestation is enforced executor-side (R-029 machinery), so a seat cannot self-certify. - AC4: the operator sees a distinct, greppable state for `rehydrated-degraded`. - Dogfood: this session lineage's hand-run `mosaic-context-refresh` "fail-closed residency attestation" — the prose version of exactly this gate. - Fail-closed point: **incomplete rehydration ⇒ seat may not mutate.** --- **R-044 — Fault-injected rotation soak** _build 3 · depends: R-042, R-043 · 180k · sonnet_ The "100 rotations lossless" bar, made adversarial. 100 clean rotations prove almost nothing; the failure mode is a kill at the worst moment. - AC1: 100 consecutive rotations, claim-set diff empty at every boundary (machine-compared, not eyeballed). - AC2: fault matrix, each ≥10 iterations: kill during checkpoint write; kill after checkpoint before old-session kill; kill after kill before relaunch; DB unavailable during checkpoint; corrupt checkpoint; two daemons racing; contract hash changed mid-rotation. **Each must end in a defined state — either fully rotated or fully not; never half.** - AC3: any iteration ending `rehydrated-degraded` fails the suite. - AC4: the suite runs against real runtime artifacts (real seats, real spine), not mocks — P-CONFORMANCE-001 criterion (1). - Dogfood: all of P-LIFECYCLE-001; the harness is the P-CONFORMANCE down payment. - Fail-closed point: **any half-state ⇒ suite red ⇒ rotation does not ship.** --- **R-045 — Delete `/compact and continue` from the persistent-seat path** _build 3 · depends: R-042, R-044 · 60k · codex_ Removal = substitution. Keep it for ephemeral seats (one doctrine per session class). - AC1: `grep -rn "compact and continue"` in orchestrator/persistent guidance returns nothing. - AC2: ephemeral-seat guidance still contains it, and a test asserts the two session classes are distinguished by a machine-readable attribute, not by prose. - AC3: a persistent seat attempting the old flow is redirected to rotation by the daemon. - Dogfood: P-GUIDE-001; the "live irony" that the deciding session was itself a hand-re-anchored compacted orchestrator. - Fail-closed point: n/a (guidance change) — but see the hazard. - ⚠ **Ordering hazard (severe):** merging this before R-042/R-044 are _live on the fleet_ removes the coping mechanism while leaving the failure in place. Hard edge, and the orchestrator should verify deployment, not just merge. ### P4 — Comms service --- **R-050 — `comms/v1` envelope + protocol-version negotiation** _build 4 · depends: R-011 · 140k · opus_ Version the protocol, not the participants. Envelope carries `comms/v1`; receiver rejects unsupported versions LOUDLY; N-version compatibility window; framework version is diagnostic-only (avoids the N² matrix). This is the first breaking comms change, so the negotiation must exist before anything rides on it. - AC1: a `comms/v1` envelope round-trips; digest and correlation id preserved. - AC2: a `comms/v99` envelope ⇒ rejected with a machine-readable `unsupported_protocol_version` and a visible operator message. Registered must-fail case. - AC3: a malformed/unsigned envelope ⇒ rejected (never best-effort parsed). - AC4: the supported-version window is data, not code branches; shrinking it in config immediately rejects the dropped version. - Dogfood: P-AUTHORITY-001; "bare tmux = comms P0/PoC" with no version field at all. - Fail-closed point: **unknown protocol version ⇒ reject, loudly.** --- **R-051 — Comms service: PG state machine** _build 4 · depends: R-050, R-013 · 200k · opus_ Sole-path service. `PENDING → RECEIVED → CONSUMED → DEAD-LETTER`, ack cursor, per-recipient filter. `interaction_inbox` already provides the unique `(session_id, idempotency_key)` index and the status enum — extend rather than fork. - AC1: a message with no consumer remains `PENDING` and is retried; it is never reported delivered. - AC2: a bounced transport attempt does not advance the cursor. Registered must-fail case: force a bounce, assert the cursor is unchanged and a retry occurs. - AC3: after N failed attempts the message goes `DEAD-LETTER` and raises a visible operator signal — dead-lettering silently is the same bug in a new hat. - AC4: `RECEIVED` and `CONSUMED` are distinct and both observable; a manual liveness check is never required to learn a message's fate. - Dogfood: the MACP scout bounce ("tmux target not found") — never RECEIVED, no retry, discovered only by hand. - Fail-closed point: **un-acked ⇒ not consumed ⇒ retried.** --- **R-052 — tmux transport adapter behind the service** _build 4 · depends: R-051, R-060 · 160k · sonnet_ tmux becomes the first dumb adapter, not the authority. Durable retry before cursor advance. - AC1: sending to a nonexistent pane ⇒ message stays `PENDING`, retried, and the sender receives `not-written` (R-027 tri-state), not `✓`. - AC2: sending to a busy pane ⇒ `written+unverified` until the receipt is observed, then `RECEIVED`. - AC3: adapter failure never loses the message — kill the adapter mid-send, assert redelivery. - AC4: the adapter cannot mark anything `CONSUMED`; only the recipient's ack can. Registered must-fail case. - Dogfood: scout-bounce; `send-message.sh:71` (`tmux target not found` → exit 1, no retry) and `:111`. - Fail-closed point: **transport cannot self-certify delivery.** - ⚠ Ordering: requires R-060 — identity addressing is meaningless while seats are split across the `default` and `mosaic-fleet` sockets (that split _was_ the bounce). --- **R-053 — Per-class coalescing + supersede** _build 4 · depends: R-051 · 130k · sonnet_ The `#1018` case: pepper's "awaiting your word" arrived _after_ the PR had merged — stale-consumed-as-live. - AC1: two messages of the same class for the same subject where the later supersedes → the recipient sees one, the newer, with the older marked superseded (not deleted — auditability). - AC2: a message whose subject has reached a terminal state is delivered _marked stale_, never as live actionable. Registered must-fail case: assert an actionable-class stale message does not present as actionable. - AC3: ordering within a class is monotonic per sender. - AC4: coalescing never drops a message with no successor. - Dogfood: `#1018` stale-consumed. - Fail-closed point: **ambiguous freshness ⇒ mark stale, never present as live.** --- **R-054 — Redis hot path + provenance guard** _build 4 · depends: R-051, R-013 · 170k · opus_ Redis Streams + PEL as a derived, rebuildable accelerator. PG remains the protected tier. - AC1: flush Redis entirely → the sweeper rebuilds the hot queue from PG with zero message loss and zero duplicates. This is the acceptance test that matters. - AC2: every read from Redis carries a provenance flag; a code path that returns Redis-derived data as authoritative ⇒ CI check fails (registered with R-025's machinery). Registered must-fail case: add such a path and watch the build go red. - AC3: Redis unavailable ⇒ service degrades to PG-only and _says so_; it does not fail open to "assume delivered". - AC4: split-brain injection (Redis has a message PG does not) → reconciliation resolves toward PG and emits an event. - Dogfood: synthetic. Justification: the moment a fast tier exists, someone reads it as truth — this guard is cheap now and impossible later. - Fail-closed point: **Redis is never authority.** --- **R-055 — Retire direct tmux sends** _build 4 · depends: R-052, R-053 · 100k · codex_ - AC1: a CI check fails on any direct `tmux send-keys` to an agent pane outside the adapter. - AC2: `agent-send.sh` becomes a thin client of the service; its exit codes remain tri-state per R-027. - AC3: existing fleet scripts are migrated; the check enumerates what it scanned (no silent empty scan). - Dogfood: the whole bare-tmux P0 comms substrate. - Fail-closed point: **out-of-band send ⇒ build fails.** ### P5 — Retirements, hygiene, conformance --- **R-060 — One roster-owned socket/host, quarantine, stale GC** _build 5 · depends: R-005 · 150k · sonnet_ - AC1: every roster seat resolves to the single roster-owned socket; a seat launched on another socket is quarantined and reported. Registered must-fail case. - AC2: `fleet status` lists unmanaged sessions distinctly; count is nonzero in a test that plants one. - AC3: stale-session GC reaps or rotates by max-age/max-context, with the decision recorded as an event. - AC4: GC never reaps a session holding an active mission claim. - Dogfood: scout-bounce root cause — "seats split default vs mosaic-fleet socket." - Fail-closed point: **unmanaged seat ⇒ quarantined, not addressed.** --- **R-061 — Auto-sync allowlist + worktree isolation** _build 5 · depends: R-002 · 110k · sonnet_ `brain-sync.sh:99` does `git add -- "$@"`; `session-end.sh:48` stages `data/ views/ domains/ docs/ '*.md'`. The measured incident (517bd5c26) staged agent-authored `docs/postmortem-spec/site/*` mid-write. - AC1: staging is allowlist-driven; a path outside the allowlist is never staged. Registered must-fail case: place an unknown path, run sync, assert it is untouched **and** reported. - AC2: a half-written agent-authored file under an agent-owned path is never swept (lease/worktree check). - AC3: the sync reports what it declined to stage (silence on decline is how this gets re-broken). - Dogfood: P-WORKFLOW-001, sweep 517bd5c26. - Fail-closed point: **unknown path ⇒ not staged, and said out loud.** - Note: criterion (1) of P-WORKFLOW-001 is partially obviated by R-062 (DB tracking), but (2)+(3) remain for code/docs in project repos. Do not cancel this task on the strength of R-062. --- **R-062 — Flat-file orchestration tracking → DB cutover, with rollback artifact** ★ highest data risk _build 2/5 · depends: R-024, R-028, R-042 · 200k · opus_ Jason's directive: hard cutover, no flat-file interim. I comply on the _write_ path and dissent on the unqualified form (D3). Scope: mosaic fleet orchestration state only — missions, agent-task assignments, `MOS-ORCHESTRATION-BOARD-{LIVE,LEDGER}`, `docs/TASKS.md`, `.mosaic/orchestrator/*`. **jarvis-brain PDA flat files (`data/projects`, `data/tasks`) are explicitly out of scope and must be proven untouched.** - AC1: a one-shot importer migrates the current board + ledger + `docs/TASKS.md` into the spine; re-running it is idempotent (no duplicate missions/tasks). - AC2: **in-flight safety** — a mission with a running task is migrated without the task being lost or double-claimed. Test by migrating while a task is mid-execution; assert exactly-once completion. - AC3: after cutover, flat files are no longer written (mtime unchanged over a full mission run). - AC4: a **read-only export** regenerates a human-readable board from the DB on demand. This is the rollback artifact, not a fallback tracking system — it is never read back as authority (enforced by R-025's writer check). - AC5: documented, tested rollback: restore the pre-cutover snapshot and resume flat-file tracking within one operator step. Exercised once in a rehearsal before the real cutover. - AC6: `git diff --stat` over `jarvis-brain/data/` across the cutover is empty. - Dogfood: the flat-file clobber class (P-WORKFLOW-001); the co-location of fleet state inside the brain repo, which the postmortem names as part of the bug. - Fail-closed points: **importer aborts on any ambiguity rather than guessing**; **cutover refuses to run while a rotation is in flight.** --- **R-063 — Conformance harness** _build 5 · depends: R-044, R-051, R-062 · 260k · opus_ Fault-inject the six live failure classes against real runtime artifacts on the DB substrate: compaction/rotation, broker-unavailable, delivery-bounce, identity-drop, stale-hash, queue-inert. Flat-file cases become regression guards. - AC1: each of the six classes has an injector, an expected-outcome assertion, and a _demonstrated red_ — the harness must be shown failing when the corresponding fix is reverted. **A harness that has never been red is an inert gate.** - AC2: the six seed incidents are reproduced by id: Pi brick, scout-bounce, gate-6 inert, #1019 recursion, identity drift, auto-sync sweep, #1018 stale-consumed. - AC3: harness runs in CI on a schedule and on release; results land in the event ledger. - AC4: harness self-reports coverage (which classes ran); a skipped class is a failure, not a silent pass. - Dogfood: it _is_ the dogfood — the automated form of the standing directive. - Fail-closed point: **skipped class ⇒ suite red.** --- **R-064 — Fleet-wide inert-gate audit** _build 5 · depends: R-002 · 120k · sonnet_ Apply the R-002 registry to every existing gate in `~/.config/mosaic/tools/` and the repo. Expect casualties beyond gate-6. - AC1: every gate is registered; the audit report lists each gate with its must-fail case and the observed exit code. - AC2: every gate found inert is either fixed in this PR or filed with an issue id in the registry; an unexplained inert gate ⇒ CI red. - AC3: the report is committed as evidence (this is the "prove it" artifact for P-QUEUE-001's interim doctrine: _queue-guard green = zero information until proven otherwise_). - Dogfood: gate-6 was inert **fleet-wide** and nobody knew; the base rate of inert gates is unknown and that is itself the finding. - Fail-closed point: **inert gate without a filed issue ⇒ red.** --- **R-065 — Retirement proof** _build 5 · depends: R-030, R-055, R-062 · 90k · codex_ A CI check asserting the three cross-cutting retirements are complete **and stay complete**: no flat-file orchestration tracking writes, exactly one MACP implementation, no silent bypass markers. - AC1: reintroduce each retired pattern in a scratch branch → check ⇒≠0, once per pattern. Registered must-fail cases (three of them). - AC2: the check enumerates what it scanned. - AC3: allowlisted exceptions carry an expiry date; an expired exception ⇒≠0. - Dogfood: the entire "built-but-unwired / retired-but-resurrected" disease. - Fail-closed point: **resurrection ⇒ build fails.** --- ## 4. Fail-closed register Every point where this plan chooses safety over availability. If any of these is later softened, the corresponding failure class returns. | # | Point | Task | What it costs when it fires | | ----- | ------------------------------------------------------------- | ------------ | ------------------------------------------------------- | | FC-1 | Unregistered gate ⇒ build fails | R-002 | New gates need a negative control before merge | | FC-2 | Unknown/no-status/malformed CI state ⇒ merge blocked | R-003 | Merges wait on flaky status APIs | | FC-3 | Broker absent + no break-glass ⇒ deny | R-004 | Requires R-005 first or the fleet bricks | | FC-4 | Version skew ⇒ launch blocked | R-005 | Seats refuse to start after a partial upgrade | | FC-5 | Skipped migration ⇒ error | R-010 | First-install must be fixed, not worked around | | FC-6 | Ledger mutation ⇒ DB error | R-011 | No "cleanup" of bad events; only compensating entries | | FC-7 | No DB ⇒ no managed work | R-012, R-024 | Spine outage stops the fleet. Accepted deliberately | | FC-8 | Schema-invalid / credentials unresolved ⇒ reject before spawn | R-020 | Bad task definitions surface loudly | | FC-9 | Executor crash ⇒ non-terminal state | R-020 | Requires a reclaim sweeper | | FC-10 | Empty/unrunnable gate set ⇒ task fails | R-021 | Gate-less task types must be declared explicitly | | FC-11 | Unpersistable event ⇒ operation fails | R-022 | Auditability outranks availability | | FC-12 | Unknown terminal-status writer ⇒ build/DB rejects | R-025 | Every new writer needs an allowlist entry with a reason | | FC-13 | Identity unresolvable ⇒ no write | R-027 | Respawned seats must re-derive identity or stop | | FC-14 | Cannot verify ⇒ never report success | R-027 | Callers must handle a third exit code | | FC-15 | Corrupt checkpoint ⇒ refuse rehydrate | R-028, R-041 | Forces a refresh cycle | | FC-16 | Stale contract hash ⇒ mutations rejected | R-029 | Highest blast radius; ship report-only first | | FC-17 | Unmonitored seat ⇒ launch refused | R-040 | Hook install becomes mandatory | | FC-18 | Checkpoint failure ⇒ old session not killed | R-042 | Rotation stalls rather than losing state | | FC-19 | Incomplete rehydration ⇒ seat may not mutate | R-043 | Quarantined seats need operator attention | | FC-20 | Unknown protocol version ⇒ reject loudly | R-050 | Rollout needs the N-version window honored | | FC-21 | Un-acked ⇒ not consumed ⇒ retried | R-051 | Duplicate-tolerant consumers required | | FC-22 | Transport cannot self-certify delivery | R-052 | tmux "✓" becomes tri-state | | FC-23 | Ambiguous freshness ⇒ marked stale | R-053 | Some genuinely-live messages arrive marked stale | | FC-24 | Redis never authority | R-054 | Extra PG round-trip on the read path | | FC-25 | Unmanaged seat ⇒ quarantined | R-060 | Ad-hoc tmux seats stop being addressable | | FC-26 | Unknown path ⇒ not staged, and reported | R-061 | Sync gets noisier, deliberately | | FC-27 | Importer aborts on ambiguity | R-062 | Cutover may need manual disambiguation | | FC-28 | Skipped conformance class ⇒ suite red | R-063 | No partial conformance claims | | FC-29 | Retirement resurrection ⇒ build fails | R-065 | Exceptions must carry expiry dates | --- ## 5. Ordering hazards (later task silently depends on an earlier invariant) **H-1 — R-004 before R-005 re-bricks the fleet.** The three bypasses are load-bearing _because the broker was never deployed_. Remove the fail-open before deployment coherence exists and you reproduce 2026-07-22 exactly, including the Pi brick. Hardest edge in the DAG. **H-2 — Every migration-adding task silently depends on R-010.** R-011/R-013/R-022/R-028/R-050/R-051 will all pass in CI on PGlite and fail on the first real Postgres install. The defect is documented in a TODO and has never been hit because nobody first-installed the PG tier. **H-3 — R-025 must ship with R-020, not after.** The choke point's only real property is exclusivity. The gap between "executor exists" and "executor is the only path" is exactly when island #4 gets built. **H-4 — R-045 before R-042/R-044 are _deployed_ removes the coping mechanism.** Merge ≠ live. The orchestrator must confirm rotation is running on the fleet, not just merged. **H-5 — R-052 depends on R-060.** Identity addressing across two tmux sockets is the bounce that motivated the whole comms build. Building the adapter first just relocates the bug. **H-6 — R-062 depends on R-042.** Cutting over tracking while rotation is half-built means a rotation lands mid-migration with state split across both substrates. R-062 must also refuse to run during an in-flight rotation (FC-27). **H-7 — R-029 depends on R-028's claim signing.** A contract hash that is not itself integrity-protected is a suggestion. Also: R-029 has the widest blast radius of any task here; report-only first. **H-8 — R-021's "empty gate set = failure" invariant is assumed by R-026.** If R-026 lands first, Forge's stub is replaced by a real executor that will happily run zero gates. **H-9 — R-063 assumes every earlier fix is _revertible in a test harness_.** If tasks land without feature flags or clean revert points, "demonstrate the harness red" (AC1) becomes impossible and the conformance suite silently becomes decorative. **Every P0–P4 task should land with a documented way to disable its fix in a test context.** This is a cross-cutting requirement the orchestrator should add to the charter, not a per-task note. **H-10 — R-002's registry is only as good as its enumeration.** If the registry is populated by hand from a partial list, unregistered gates keep passing. R-002 AC3 (unregistered-gate detection) is the load-bearing half; R-064 is its audit. --- ## 6. Secretly-greenfield register "Finish, don't re-spec" is accurate at the _specification_ level and misleading at the code level. Of 38 tasks, these have no existing implementation to finish: | Task | Reality | | ----------- | --------------------------------------------------------------------------------------------------- | | R-002 | Gate registry + negative-control runner — new, no precedent in repo | | R-011 | Hash-chained append-only ledger — the existing `events` table is calendar-shaped | | R-013 | Outbox sweeper — tables exist, sweeper does not | | R-020 | Executor — the package has **no exported programmatic submit** (scout §1); only a CLI placeholder | | R-025 | No-second-path enforcement — entirely new | | R-028 | HMAC claim integrity — new | | R-029 | Contract-hash binding — new | | R-040 | Durable compaction sensor — "nothing durable today" | | R-042 | Rotation daemon — the PoC's `_spawn_agent()` was a stub; `packages/coord` has primitives, no daemon | | R-043 | Rehydration attestation — new | | R-050/R-051 | comms/v1 envelope + service — tables exist, protocol and service do not | | R-054 | Provenance guard — new | | R-063 | Conformance harness — new | **Estimate: ~13 of 38 tasks (≈40% of tokens) are new construction.** What genuinely exists and should be _reused, not rebuilt_: `packages/macp` types/credential-resolver/gate-runner/risk-floor; `packages/db` schema + drizzle migrations (post-R-010); `packages/queue` `QueueAdapter`; `packages/coord` mission/session/capsule/crash-recovery primitives; `interaction_*` tables; PG + Redis already running in-stack. That is a real head start — it is just not "wiring." --- ## 7. Migration & rollback (the hard cutover) Per Jason: hard cutover, no flat-file interim. Concretely, R-062 executes in five steps, each of which must be reversible until the last: 1. **Rehearsal** (throwaway DB): run the importer against a copy of the current board/ledger/TASKS.md. Diff the reconstructed board against the flat original. Any unexplained delta ⇒ stop. 2. **Freeze window**: no rotation in flight (FC-27), no task mid-execution beyond a known set. 3. **Snapshot**: tag the flat-file state (git tag + tarball) — this is the rollback artifact. 4. **Import + flip**: importer runs; writers switch to the spine in one commit; flat files become read-only outputs. 5. **Verification window**: for one measured period, a scheduled job regenerates the human board from the DB and diffs it against the frozen snapshot's _shape_ (not content). Divergence in shape ⇒ investigate. **This is not a dual-write and not an interim tracking system** — it is a one-directional export that proves the DB is complete. **Rollback trigger:** any of — importer AC1 idempotency failure, a lost in-flight task (AC2), or spine unavailability exceeding the agreed window. **Rollback action:** restore the snapshot, revert the writer flip, resume flat-file tracking. Must be exercised once in rehearsal or it is not a rollback plan, it is a wish. --- ## 8. Dissent **D1 — The charter's sequencing starts one phase too late.** `MISSION.md` sequences "spine + choke-point first." I insert P0 (R-001…R-005) ahead of it. Reason: every task in Builds 1–4 introduces gates, and this fleet has demonstrated — twice, on the same defect — that it cannot detect an inert gate. Building the choke point first means its gates are unverifiable _by construction_, and we would be shipping the exact class of artifact the postmortem indicts. R-001 is also a hard blocker (nothing can be pushed today). Cost of the insertion: ~420k tokens, roughly 8% of the plan. I consider it the highest-leverage 8% here. **D2 — I disagree with the scout's chosen wire-in point.** ★ headline dissent `MACP-WIRING-SCOUT.md` names the "single integration point" as replacing the exec/gate/event block in `mosaic_orchestrator.py::run_single_task` (:126-276). But that controller is **disabled** (`.mosaic/orchestrator/config.json:2` — `"enabled": false`) and references a dispatcher path (`tools/macp/dispatcher/pi_runner.ts`) that does not exist in this checkout. Wiring the new choke point into a disabled rail produces a stranded executor — the identical disease, one layer up, and it would look "done" in a PR. The paths that actually carry work today are `packages/mosaic/src/commands/launch.ts` (`mosaic yolo|claude|codex|pi`, :730-843, :1102-1167) and `packages/coord/src/runner.ts` (:397-427). **Wire those (R-023, R-024) and delete the Python rail (R-030) rather than porting it.** The scout's analysis is excellent and its file:line evidence is what let me reach this conclusion — I am disagreeing with its recommendation, not its findings. Concrete consequence: R-030 becomes a deletion task, not an integration task, and Build 1's acceptance must be measured on a live `mosaic yolo` invocation. **D3 — "Hard cutover, no interim" is right about tracking and wrong about evidence.** I comply with the directive: one write path, no flat-file interim, no dual-write. But a hard cutover with **no rollback artifact** will, on the balance of this codebase's history, lose an in-flight mission. R-062 AC4/AC5 add a one-directional read-only export and a rehearsed rollback. These are not an interim tracking system — nothing reads them as authority (enforced by R-025) — they are the snapshot you need at 3am. If the orchestrator judges even this to violate the directive, escalate to Jason rather than silently dropping it; the difference between "no interim" and "no rollback" is worth one question. **D4 — The "100 rotations lossless" bar as written is not a test, it is a demo.** 100 clean rotations exercise the happy path 100 times. The failure mode is a kill at the worst moment. R-044 replaces the bar with a fault matrix and defines "lossless" as a machine-compared claim-set diff. I would not accept Build 3 on 100 clean rotations alone, and I would treat any suite that has never been observed red as unproven (R-063 AC1). **D5 — Report-only modes must expire, or they become the new fail-open.** I recommend report-only rollout for R-029 (contract hash) and R-025 (no-second-path) because their blast radius is fleet-wide. But a report-only gate is definitionally inert, and this organization has proven it will not notice. **Every report-only mode must carry a hard expiry timestamp after which it enforces or refuses to start** — same mechanism as R-004's break-glass. Without that clause I withdraw the report-only recommendation and prefer a hard flip with a scheduled window. **D6 — P-QUEUE-001 should not be scheduled inside Build 1, and its ownership is currently ambiguous.** The queue guard is a shell script in the shared tools path; it has no dependency on the spine or the executor, and it is P0. Scheduling it "inside Build 1" delays a P0 fix behind a 1M-token foundation. R-003 sits in P0 instead. Separately: PR #1023 is explicitly **parked under Mos** while HOLD is lifted for this workstream — two lanes can legitimately claim it. **The orchestrator must resolve this with Mos before dispatching R-003.** A third recursion on this defect would be the postmortem's own anti-pattern, performed by the remediation. **D7 — Build 4's adapter fan-out (Matrix/Discord/Slack/Telegram) does not belong in this mission.** The comms _service_, the envelope, the state machine, and one adapter (tmux) fix the observed failures. Additional adapters are reach, not correctness, and each one adds a delivery-semantics surface that R-063 must then cover. I recommend explicitly deferring adapters beyond tmux to a follow-on mission, and saying so in the charter so it is a decision rather than a slip. **D8 — FC-7 and FC-11 are real costs, and I want them acknowledged in writing, not discovered.** "No DB ⇒ no managed work" and "unpersistable event ⇒ operation fails" mean a Postgres outage stops the fleet, where today a flat-file fleet limps on. That is the correct trade for a system whose defining failure is _silent_ continuation — but it is a genuine availability regression, and the first time it fires at 2am someone will be tempted to add a fallback. **Pre-commit to the answer now:** the fallback is "the fleet stops," and the mitigation is spine availability (backups, restart policy, monitoring), not a degraded write path. If that is not acceptable to Jason, the right time to say so is before R-012, not during the incident. --- ## 9. Handoff notes for the orchestrator - **Dispatch order for the first wave (parallelizable):** R-001 → then R-002 and R-005 concurrently → then R-010 concurrently with R-003; R-004 waits on R-005. - **Do not dispatch any P2 task before R-002 is merged.** That is the whole argument of D1; if it gets compressed away under schedule pressure, the plan's central claim goes with it. - **Pre-registration:** every AC above is written to be committed _before_ the diff is read. They are drafts — the reviewer should tighten them per task, but the must-fail cases are non-negotiable. - **Charter addendum recommended (H-9):** every P0–P4 task lands with a documented way to disable its fix in a test context, so R-063 can demonstrate red. - **Three decisions needing a human or Mos:** (1) R-003 ownership vs. parked PR #1023 [D6]; (2) rollback artifact vs. "no interim" reading [D3]; (3) accepting the FC-7/FC-11 availability trade [D8].