71 KiB
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 containingrun 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 --porcelainin 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-bakand untrackedapps/coordinatorresidue 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.shinvokes 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 claudelaunches ⇒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 -9at a scripted point, then run the previous version's smoke test ⇒0. - AC4:
mosaic doctorreports 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 inschema.tsexists (assert by count and by name list). - AC2:
drizzle.__drizzle_migrationscontains 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 ...orDELETE 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_hashdoes not match the current head ⇒ error. - AC4: a verification function reports gap/tamper on an artificially altered chain ⇒≠0.
- AC5:
state_claimsrows 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.ndjsonwritten bymosaic_orchestrator.py:129-133and only read byplugins/mosaic-framework). - Fail-closed point: ledger mutation attempt = hard DB error.
- Design note (deliberate divergence): the existing
eventstable (schema.ts:264) is a generictype/title/description/daterecord — 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=productionand noDATABASE_URL, client construction ⇒ throws (not a fallback). Registered must-fail case. - AC2: with
DATABASE_URLset 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 inmacp_events, chain-valid. - AC4: a Task whose gate fails is recorded
failed/gated, nevercompleted. - 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_taskputs 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 acodingtask ⇒ task notcompleted; error namesno gates configured. Registered must-fail case — this is the Forge-stub bug generalized. - AC2: a gate exceeding its timeout ⇒
timed_out: trueandpassed: false(neverpassed: true). - AC3:
fail_on: blockerwith 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-31stub 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;
#1018reconstruction 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 runon a fixture mission producesmacp_tasks+macp_eventsrows; no directspawnof the runtime remains inrunner.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.mdas 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 amacp_tasksrow andsession.launchedevent 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|pialso 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 runon 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.tsreturns 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_IDENTITYresolves 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
unverifiedon 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.mdis 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.mdcontext-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/macpintegration test drives a task end-to-end through the Node executor. - AC3:
.mosaic/orchestrator/{tasks,state}.jsonandevents.ndjsonare 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.pendingwith 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_epochincrements 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/coordinatorresidue (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/coordhas 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-degradedfails 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/v1envelope round-trips; digest and correlation id preserved. - AC2: a
comms/v99envelope ⇒ rejected with a machine-readableunsupported_protocol_versionand 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
PENDINGand 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-LETTERand raises a visible operator signal — dead-lettering silently is the same bug in a new hat. - AC4:
RECEIVEDandCONSUMEDare 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 receivesnot-written(R-027 tri-state), not✓. - AC2: sending to a busy pane ⇒
written+unverifieduntil the receipt is observed, thenRECEIVED. - 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
defaultandmosaic-fleetsockets (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:
#1018stale-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-keysto an agent pane outside the adapter. - AC2:
agent-send.shbecomes 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 statuslists 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.mdinto 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 --statoverjarvis-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:
- 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.
- Freeze window: no rotation in flight (FC-27), no task mid-execution beyond a known set.
- Snapshot: tag the flat-file state (git tag + tarball) — this is the rollback artifact.
- Import + flip: importer runs; writers switch to the spine in one commit; flat files become read-only outputs.
- 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].