Files
stack/docs/remediation/DECOMP-OPUS.md
T
mos-dt-0andMos 01e966f36d
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
docs(remediation): mission charter + reconciled execution backlog (#1026)
Co-authored-by: mos-dt-0 <[email protected]>
2026-07-31 22:47:03 +00:00

71 KiB
Raw Blame History

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 unknownexit 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 0pnpm 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 VALUESET 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 MACPEvents, 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 <runtime> … 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 (JanFeb 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 P0P4 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 14 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 P0P4 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].