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

1111 lines
71 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# DECOMP-OPUS — Adversarial Task Decomposition (ROBUSTNESS SIDE)
**Author:** `planner-opus` (web1), robustness/pessimist lane.
**Inputs read:** `MISSION.md`, `MACP-WIRING-SCOUT.md`, `BOARD.md`, plus first-hand inspection of
`/src/mosaic-stack` (`packages/{macp,coord,db,queue,forge,mosaic}`, `apps/`,
`packages/mosaic/framework/tools/orchestrator-matrix/controller/mosaic_orchestrator.py`),
`~/.config/mosaic/tools/{git,tmux,lease-broker}/`, and
`jarvis-brain/docs/scratchpads/postmortem/REMEDIATION-DISCUSSION-STATE.md`.
**Not read (by charter):** `DECOMP-SOL.md`.
**Output:** 38 tasks, 8 dissents.
---
## 0. Operating premise of this decomposition
Three assumptions drive every ordering choice below. All three are load-bearing; if you reject one,
the DAG changes.
**A1 — Every gate is inert until proven otherwise.** Gate-6 (`ci-queue-wait.sh`) passed green
fleet-wide while classifying `unknown``exit 0`, and its fix recursed the same defect. I verified
the defect is still live at `~/.config/mosaic/tools/git/ci-queue-wait.sh:282-288`:
```
terminal-success|terminal-failure|unknown) exit 0 # "unknown" == pass
*) echo "unrecognized state ... proceeding conservatively"; exit 0
```
`no-status` also exits 0 unless `--require-status` is passed, and nothing passes it
(`pr-merge.sh:98`). A gate whose failure path is unreachable is worse than no gate: it manufactures
evidence. **Therefore: no task in this plan may introduce a gate without a registered
must-exit-nonzero negative control, and the registry check itself is a gate (R-002).**
**A2 — Fail-open is the resting state of this system.** Confirmed instances, all live today:
| Site | Failure mode |
| ----------------------------------------- | --------------------------------------------------------------- |
| `ci-queue-wait.sh:282,286` | unknown/unrecognized CI state → proceed |
| `send-message.sh:111` | indeterminate submission → `✓ sent`, exit 0 |
| `packages/forge/src/cli.ts:13-31` | stub `TaskExecutor` reports completion with **empty gate list** |
| `lease-broker/mutator-gate.py:140` | broker socket unset → `return 0` (all tool calls allowed) |
| `lease-broker/revoke-lease.py:57` | broker socket unset → `return 0` (lease never revoked) |
| `lease-broker/receipt-observer-client.py` | entire client replaced by a `sys.exit(0)` stub |
Four of six are _silent_. Every task below names its fail-closed point explicitly (§4).
**A3 — The substrate is real, but "wire it in" understates the work.** `packages/db` already has
`missions`, `tasks`, `mission_tasks`, `agents`, `interaction_{sessions,inbox,outbox,checkpoints,handoffs}`
with idempotency-key unique indexes and a compaction-epoch column. `packages/queue` has a
`QueueAdapter` with bullmq + local adapters. That is genuinely more than the charter credits.
But see §6 — roughly a third of this plan is greenfield regardless of the slogan.
---
## 1. Phase map (ordered; hard barriers between phases marked ⛔)
```
P0 Make gates provable + stop the fleet re-bricking R-001..R-005
⛔ barrier: no gate-introducing task may merge before R-002
P1 Durable spine (PG) R-010..R-013
⛔ barrier: no migration may merge before R-010
P2 Single choke-point executor (MACP wire-in) R-020..R-030
⛔ barrier: R-025 (no-second-path) must land in the same milestone as R-020
P3 Rotation lifecycle (Mission Control Plane, finished) R-040..R-045
⛔ barrier: R-045 (delete /compact guidance) may not merge before R-042 is live
P4 Comms service R-050..R-055
⛔ barrier: R-060 (one socket per host) must precede R-052 identity addressing
P5 Retirements, hygiene, conformance R-060..R-065
```
**Deliberate divergence from `MISSION.md` sequencing:** the charter starts at "spine + choke-point
(builds 1+2)". I insert P0 ahead of it. Rationale in Dissent D1.
---
## 2. Master task table
`tier` = suggested runtime. `codex` = mechanical, spec is unambiguous. `sonnet` = normal feature work.
`opus` = security/integrity/design-ambiguity or cross-cutting invariant.
| id | title | build | depends_on | est tok | tier |
| ----- | ---------------------------------------------------------------------------------------- | ----- | -------------------------- | ------- | ------ |
| R-001 | Reproducible checkout + pre-push gate that fails on code, not env | 5 | — | 60k | codex |
| R-002 | Gate registry + negative-control CI check (anti-inert-gate harness) | 5 | R-001 | 120k | opus |
| R-003 | Queue-guard fail-closed rework (`unknown`/`no-status`/malformed) | 1 | R-002 | 100k | sonnet |
| R-004 | Break-glass replaces the three silent `MOSAIC BYPASS` fail-opens | 1 | R-005, R-002 | 120k | opus |
| R-005 | Activation/version coherence: block launch on host↔repo skew, fail SAFE | 5 | R-001 | 140k | sonnet |
| R-010 | Fix the Drizzle postgres-tier first-install migration defect | 2 | R-001 | 90k | sonnet |
| R-011 | Orchestration spine schema (macp_tasks, attempts, gate_results, ledger, claims) | 2 | R-010 | 160k | opus |
| R-012 | Spine client with fail-closed connection semantics (no silent PGlite in prod) | 2 | R-011 | 80k | sonnet |
| R-013 | Transactional outbox tables + reconciliation sweeper (PG-first) | 2 | R-012 | 140k | opus |
| R-020 | Production `TaskExecutor` backed by `@mosaicstack/macp` | 1 | R-012, R-002 | 220k | opus |
| R-021 | Gate-runner hardening: structured `GateEntry`, `fail_on`, timeout, empty-gate-set = fail | 1 | R-020 | 120k | sonnet |
| R-022 | Hash-chained append-only MACPEvent ledger in PG; lifecycle EventType extension | 1 | R-020, R-011 | 160k | opus |
| R-023 | `packages/coord` submits work through the executor (retire direct spawn) | 1 | R-020 | 140k | sonnet |
| R-024 | `mosaic yolo\|claude\|codex\|pi` launch path records a typed Task + events | 1 | R-020, R-022 | 160k | sonnet |
| R-025 | No-second-path gate: terminal status writable only by the executor | 1 | R-020, R-022 | 140k | opus |
| R-026 | Delete the Forge stub executor; Forge submits through the real one | 1 | R-020 | 90k | codex |
| R-027 | Seat identity from `MOSAIC_AGENT_NAME` + mandatory tri-state write outcomes | 1 | R-020 | 150k | opus |
| R-028 | Typed state claims (source/confidence/TTL) with HMAC integrity, fail-closed | 1 | R-011, R-020 | 170k | opus |
| R-029 | Contract-hash binding; stale generation loses mutation authority mechanically | 1 | R-020, R-028 | 180k | opus |
| R-030 | Retire the Python controller + `plugins/macp` island (island count 3 → 1) | 1 | R-023, R-024, R-025, R-026 | 110k | codex |
| R-040 | Durable compaction/token sensor (per-runtime thresholds, PreCompact event) | 3 | R-022, R-029 | 130k | sonnet |
| R-041 | Typed checkpoint writer (structured claims, not transcript) + digest | 3 | R-028, R-040 | 150k | opus |
| R-042 | Rotation daemon: watch → checkpoint → kill → relaunch → rehydrate | 3 | R-041, R-023 | 240k | opus |
| R-043 | Rehydration attestation gate: refuse to act on an incomplete claim set | 3 | R-041 | 130k | opus |
| R-044 | Fault-injected rotation soak (100× incl. kill-at-worst-moment) | 3 | R-042, R-043 | 180k | sonnet |
| R-045 | Delete `/compact and continue` from the persistent-seat path (substitution) | 3 | R-042, R-044 | 60k | codex |
| R-050 | `comms/v1` envelope + protocol-version negotiation, LOUD reject | 4 | R-011 | 140k | opus |
| R-051 | Comms service: PG state machine PENDING→RECEIVED→CONSUMED→DEAD-LETTER | 4 | R-050, R-013 | 200k | opus |
| R-052 | tmux transport adapter behind the service; durable retry before cursor advance | 4 | R-051, R-060 | 160k | sonnet |
| R-053 | Per-class coalescing + supersede (stale-consumed-as-live fix) | 4 | R-051 | 130k | sonnet |
| R-054 | Redis hot path + provenance guard (Redis may never be read as authority) | 4 | R-051, R-013 | 170k | opus |
| R-055 | Retire direct tmux sends; only the service may write a pane | 4 | R-052, R-053 | 100k | codex |
| R-060 | One roster-owned socket/host, quarantine unmanaged, stale-session GC | 5 | R-005 | 150k | sonnet |
| R-061 | Auto-sync allowlist (never auto-stage unknown paths) + worktree isolation | 5 | R-002 | 110k | sonnet |
| R-062 | Flat-file orchestration tracking → DB cutover, with rollback artifact | 2/5 | R-024, R-028, R-042 | 200k | opus |
| R-063 | Conformance harness: fault injection of the six live failure classes | 5 | R-044, R-051, R-062 | 260k | opus |
| R-064 | Fleet-wide inert-gate audit against the R-002 registry | 5 | R-002 | 120k | sonnet |
| R-065 | Retirement proof: CI asserts all three retirements are complete and stay complete | 5 | R-030, R-055, R-062 | 90k | codex |
**Totals:** 38 tasks, ~5.3M estimated worker tokens. Critical path (longest chain):
`R-001 → R-010 → R-011 → R-012 → R-020 → R-022 → R-029 → R-040 → R-041 → R-042 → R-044 → R-062 → R-063`
= 13 tasks, ~2.2M tokens. Everything else parallelizes against it.
---
## 3. Task detail
Acceptance criteria are written **diff-blind**: each is a command a reviewer can pre-register and run
against the merged branch without having read the implementation. `⇒0` / `⇒≠0` denote required exit
codes. Every gate-introducing task carries at least one **must-fail** case — that is the whole point.
### P0 — Make gates provable
---
**R-001 — Reproducible checkout + pre-push gate that fails on code, not env**
_build 5 · depends: — · 60k · codex_
The mosaic-stack checkout on web1 cannot push: `pre-push` typecheck fails on `Cannot find module
commander/@mosaicstack/*` because deps were never installed. A gate that reds on environment is
indistinguishable from a gate that reds on defect, so the fleet learns to bypass it. This is the
mission's own TASK-0 and it blocks literally every other PR.
- AC1: from a clean clone in the CI container, `pnpm install --frozen-lockfile && pnpm -w typecheck` ⇒0.
- AC2: `scripts/preflight.sh` (or equivalent named in the PR) run with node_modules removed ⇒≠0 with a
message containing `run pnpm install`, and ⇒0 after install. **Negative control: the missing-deps
case must be distinguishable by exit code or a machine-greppable marker from a real type error.**
- AC3: introduce `export const x: number = "s"` in any package → gate ⇒≠0. Revert → ⇒0.
- AC4: `git status --porcelain` in a fresh clone after a full build is empty (no generated residue).
- Dogfood: live finding 2026-07-31 — "pre-push gate red on env not code"; `.bak`/`.mosaic-bak` and
untracked `apps/coordinator` residue in the checkout.
- Fail-closed point: none introduced; this restores the _ability_ to fail closed.
---
**R-002 — Gate registry + negative-control CI check (anti-inert-gate harness)** ★ keystone
_build 5 · depends: R-001 · 120k · opus_
The generalization of P-QUEUE-001. A machine-readable registry (`gates.manifest.json` or equivalent)
where every deterministic gate in the repo declares: invocation, the input classes it must
distinguish, and for **each** class the required exit code. A CI job runs every declared case and
fails if (a) any must-fail case exits 0, (b) any gate in the tools tree is absent from the registry,
or (c) a gate has zero must-fail cases registered.
- AC1: `pnpm gate:verify` ⇒0 on main.
- AC2: mutate any registered gate's failure branch to `exit 0``pnpm gate:verify` ⇒≠0 naming that
gate. **This is the meta-negative-control; a reviewer must run it.**
- AC3: add a new executable under the gates path without a registry entry → ⇒≠0 with `unregistered gate`.
- AC4: registry entry with zero must-fail cases → ⇒≠0 with `no negative control`.
- AC5: the check runs in CI on every PR, not only on gate-file changes (an inert gate is usually made
inert by a change _elsewhere_, e.g. an env var going unset).
- Dogfood: gate-6 inert fleet-wide; #1019 fix (PR #1023 @f6334080) recursed the same defect —
rev-974 review id 58 finding (3): "tests don't assert exit outcomes."
- Fail-closed point: **unregistered gate ⇒ build fails.** Not "warn".
- Secretly greenfield: yes, small. Accept it — it is the cheapest insurance in the plan.
---
**R-003 — Queue-guard fail-closed rework**
_build 1 · depends: R-002 · 100k · sonnet_
`ci-queue-wait.sh` must stop treating `unknown`, unrecognized, and `no-status` as pass. Spec is
rev-974 review id 58: (1) `unknown` must not exit 0; (2) payload via stdin/temp file, never argv
(ARG_MAX rc126 at ~150 KiB); (3) tests assert exit outcomes.
- AC1: synthetic status payloads for each class ⇒ `pending` (loops then times out ⇒124),
`terminal-success` ⇒0, `terminal-failure` ⇒≠0, `no-status` ⇒≠0, `malformed JSON` ⇒≠0,
`unknown` ⇒≠0. Each is a registered R-002 case.
- AC2: a 150 KiB status payload ⇒ not 126, and classification unchanged.
- AC3: `--force-proceed` (if provided) requires a reason, emits a WARN to stderr, and is a registered
break-glass per R-004 — never a silent default.
- AC4: `pr-merge.sh` invokes the guard with `--require-status`; removing that flag ⇒ R-002 fails.
- Dogfood: gate-6 inert + #1019 recursion.
- Fail-closed point: **every non-green classification blocks the merge.**
-**Authority conflict:** #1019/PR #1023 is explicitly PARKED under Mos ("delivery stack still
parked"), while Build 1 absorbs QUEUE and HOLD is lifted for this workstream. The orchestrator must
resolve ownership with Mos _before dispatch_, or two lanes will rework the same branch. Flagged, not
assumed.
---
**R-004 — Break-glass replaces the three silent `MOSAIC BYPASS` fail-opens**
_build 1 · depends: R-005, R-002 · 120k · opus_
Convert `mutator-gate.py:140`, `revoke-lease.py:57`, and the `receipt-observer-client.py` stub from
permanent silent fail-open into LOUD + AUDITED + TEMPORARY break-glass: explicit opt-in token with an
expiry timestamp, a WARN on stderr every invocation, an audit line written on every use, and refusal
once expired.
- AC1: with break-glass unset and the broker absent → gate ⇒≠0 (denies) with `GATE_UNAVAILABLE`.
- AC2: with a valid unexpired break-glass token → ⇒0, **and** an audit record exists containing seat,
reason, expiry, and invocation count.
- AC3: with an expired token → ⇒≠0. (Registered must-fail case.)
- AC4: `grep -rn "MOSAIC BYPASS" ~/.config/mosaic/tools/` returns nothing after deploy; a CI check
fails if the marker reappears without a matching registry entry.
- AC5: the Stop-hook client never blocks a turn — observational hooks exit 0 on transport failure
**but record a delivery-failure event**; silence is the bug, not the exit code.
- Dogfood: Pi brick (fail-closed gate whose recovery needed the gate it was blocked by) + the
2026-07-22 fleet incident that motivated all three bypasses.
- Fail-closed point: **broker absent + no break-glass = deny.**
-**Ordering hazard (severe):** merging R-004 before R-005 re-bricks the fleet exactly as on
2026-07-22. The bypasses exist because the broker daemon was never _deployed_ on this host, not
because the code was wrong. Removing the fail-open before deployment coherence is real recreates the
incident. **Hard edge: R-005 → R-004.**
---
**R-005 — Activation/version coherence: block launch on host↔repo skew, fail SAFE**
_build 5 · depends: R-001 · 140k · sonnet_
P-ACTIVATION-001. Transactional install of CLI + hooks + broker + version; launch refuses to start a
seat whose deployed framework version disagrees with the repo/roster expectation. "Fail SAFE" per
Jason's caveat = clear message + exact fix command, not a bare hard stop.
- AC1: with matching versions, `mosaic claude` launches ⇒0.
- AC2: with a deliberately skewed deployed version, launch ⇒≠0 and stdout contains both versions and a
copy-pasteable remediation command. (Registered must-fail case.)
- AC3: interrupted install (kill mid-way) leaves the previous version fully functional — no
half-installed hook set. Verified by: install, `kill -9` at a scripted point, then run the previous
version's smoke test ⇒0.
- AC4: `mosaic doctor` reports broker deployed/undeployed **honestly** — the label must change when
the daemon is stopped. (Direct fix for P-RECOVERY-001's "honest capability labeling".)
- Dogfood: the 2026-07-22 incident root cause ("#828 lease-broker daemon + launch-time env injection
were never deployed on this host"); the messy-checkout finding.
- Fail-closed point: **version skew blocks launch.**
### P1 — Durable spine
---
**R-010 — Fix the Drizzle postgres-tier first-install migration defect** ★ hidden blocker
_build 2 · depends: R-001 · 90k · sonnet_
`packages/db/src/migrate.ts:30-38` carries a TODO admitting that **postgres-tier first-install fails
today**: Drizzle wraps all migrations in one transaction (breaking 0009's `ALTER TYPE ADD VALUE`
`SET DEFAULT` sequence) and `drizzle/meta/_journal.json` orders 0009 before 0008, which the
postgres-js migrator silently skips by `created_at < folderMillis`. The PGlite path sidesteps both —
which means the spine has only ever been proven on the embedded tier.
- AC1: against a **fresh empty** Postgres 16 container, `runMigrations()` ⇒0 and every table in
`schema.ts` exists (assert by count and by name list).
- AC2: `drizzle.__drizzle_migrations` contains one row per migration file, in file order.
- AC3: re-running `runMigrations()` on the migrated DB ⇒0 and is a no-op (row count unchanged).
- AC4: a deliberately corrupted journal ⇒≠0 (must not silently skip). Registered must-fail case.
- Dogfood: synthetic — no live incident yet, because nobody has first-installed the PG tier. That is
precisely the danger: the first real deploy is the discovery event.
- Fail-closed point: **skipped migration ⇒ error, never silent skip.**
-**Ordering hazard:** every later task that adds a migration (R-011, R-013, R-022, R-028, R-050,
R-051) silently depends on this. Without it they pass on PGlite in CI and fail on the real deploy.
---
**R-011 — Orchestration spine schema**
_build 2 · depends: R-010 · 160k · opus_
New tables, **not** overloads of existing ones: `macp_tasks` (typed MACP `Task`), `task_attempts`,
`gate_results`, `macp_events` (append-only, monotonic sequence, `prev_hash`/`hash` chain),
`state_claims` (typed claim with source/confidence/TTL + HMAC), `seat_identities`,
`contract_bindings`. Reuse `missions`/`mission_tasks` for mission linkage.
- AC1: migration applies to fresh PG ⇒0 (per R-010 AC1).
- AC2: `UPDATE macp_events SET ...` or `DELETE FROM macp_events` ⇒ error (append-only enforced by
trigger/rule or role grants, not convention). Registered must-fail case.
- AC3: inserting an event whose `prev_hash` does not match the current head ⇒ error.
- AC4: a verification function reports gap/tamper on an artificially altered chain ⇒≠0.
- AC5: `state_claims` rows carry a non-null TTL; a query helper returns expired claims as _expired_,
never as valid.
- Dogfood: P-AUDIT-001's duplicate ledger (`.mosaic/orchestrator/events.ndjson` written by
`mosaic_orchestrator.py:129-133` and only _read_ by `plugins/mosaic-framework`).
- Fail-closed point: **ledger mutation attempt = hard DB error.**
- Design note (deliberate divergence): the existing `events` table (`schema.ts:264`) is a generic
`type/title/description/date` record — a calendar-shaped table. Overloading it as the audit ledger
would defeat append-only enforcement. Separate table.
---
**R-012 — Spine client with fail-closed connection semantics**
_build 2 · depends: R-011 · 80k · sonnet_
`packages/db` exposes both `createDb` (postgres) and `createPgliteDb` (embedded). If production can
ever silently land on PGlite, two seats will happily write to two different "systems of record."
- AC1: with `NODE_ENV=production` and no `DATABASE_URL`, client construction ⇒ throws (not a fallback).
Registered must-fail case.
- AC2: with `DATABASE_URL` set to an unreachable host, the first operation ⇒ throws within the
configured timeout; no in-memory substitute is created.
- AC3: PGlite construction in production requires an explicit `MOSAIC_ALLOW_EMBEDDED_DB=1`; absent it,
⇒ throws.
- AC4: a health endpoint/CLI reports which tier is in use, and the string differs between tiers.
- Dogfood: synthetic (the split-brain this prevents has not happened yet _because_ the spine is not
wired — it would happen on week one of Build 2).
- Fail-closed point: **no DB ⇒ no work.** The choke point must not degrade to local files.
---
**R-013 — Transactional outbox + reconciliation sweeper**
_build 2 · depends: R-012 · 140k · opus_
PG commit first, then enqueue. A sweeper deterministically re-enqueues rows that committed in PG but
never reached the hot queue. `interaction_outbox` already has the right shape
(`idempotency_key` unique per session, status enum, `content_digest`) — reuse it rather than adding a
fourth queue concept.
- AC1: kill the process between PG commit and enqueue → sweeper re-enqueues within N seconds; the
consumer sees the message exactly once (idempotency key enforced).
- AC2: enqueue the same idempotency key twice → one delivery, one PG row. Registered must-fail case is
the inverse: a duplicate delivery ⇒ test fails.
- AC3: with Redis unavailable, writes still commit to PG and are marked `pending`; nothing is lost and
nothing reports delivered.
- AC4: sweeper is idempotent — running it twice concurrently produces no duplicates (advisory lock).
- Dogfood: MACP scout notification BOUNCE ("tmux target not found") — never RECEIVED, no retry, found
only by manual liveness check.
- Fail-closed point: **an un-acked message is never marked delivered.**
### P2 — The single choke point
---
**R-020 — Production `TaskExecutor` backed by `@mosaicstack/macp`** ★ keystone
_build 1 · depends: R-012, R-002 · 220k · opus_
One Node executor: validate a typed `Task` (schemas exist at `packages/macp/src/schemas`), call
`resolveCredentials`, run the process, call `runGates`, emit `MACPEvent`s, persist a `TaskResult`.
Exposed as a programmatic API **and** a CLI. Note the scout's observation that there is no exported
programmatic `submit` — that is this task.
- AC1: submitting a Task that fails schema validation ⇒≠0 before any process spawn; assert no child
process was created and no event row exists. Registered must-fail case.
- AC2: a Task whose provider credentials cannot be resolved ⇒≠0 with `CredentialError`; **assert the
credential value never appears in stdout, stderr, the event ledger, or the task row.**
- AC3: a successful Task produces, in order: `task.assigned`, `task.started`, `rail.check.*` per gate,
`task.completed` — all in `macp_events`, chain-valid.
- AC4: a Task whose gate fails is recorded `failed`/`gated`, never `completed`.
- AC5: killing the executor mid-run leaves the task in a non-terminal state that the sweeper can
reclaim — **never** `completed`.
- AC6: two executors racing on the same task id → exactly one runs (row-level claim).
- Dogfood: the entire "stranded MACP" finding — zero production calls to
`runGates`/`emitEvent`/`resolveCredentials`.
- Fail-closed points: schema-invalid ⇒ reject; credentials unresolved ⇒ reject; crash ⇒ non-terminal.
-**I dissent from the scout's chosen wire-in point.** See Dissent D2 — wiring into
`mosaic_orchestrator.py::run_single_task` puts the choke point inside a controller that is
`"enabled": false`. Wire the live path instead (R-023/R-024) and delete the Python rail (R-030).
---
**R-021 — Gate-runner hardening**
_build 1 · depends: R-020 · 120k · sonnet_
The Python rail runs raw string gates (`mosaic_orchestrator.py:213-235`) with no `type`/`fail_on`
support. The package runner supports structured `GateEntry`. Hardening: honor `fail_on`, enforce
per-gate timeouts distinct from the task timeout, and **treat an empty gate list as failure for
gate-required task types** rather than as vacuous success.
- AC1: `quality_gates: []` on a `coding` task ⇒ task not `completed`; error names `no gates
configured`. Registered must-fail case — this is the Forge-stub bug generalized.
- AC2: a gate exceeding its timeout ⇒ `timed_out: true` and `passed: false` (never `passed: true`).
- AC3: `fail_on: blocker` with 3 non-blocking findings ⇒ pass; with 1 blocker ⇒ fail.
- AC4: a gate command that does not exist (rc 127) ⇒ fail, not skip.
- Dogfood: `packages/forge/src/cli.ts:13-31` stub executor "immediately reports completion with empty
gates" — an inert gate by construction.
- Fail-closed point: **absent/unrunnable gates fail the task.**
---
**R-022 — Hash-chained MACPEvent ledger + lifecycle EventType extension**
_build 1 · depends: R-020, R-011 · 160k · opus_
Emitter targets PG (via R-011), not a caller-supplied NDJSON path. Extend `EventType` from
task-centric to lifecycle-first-class: `session.launched`, `mission.generated`,
`compaction.detected`, `session.rotated`, `recovery.started`, `breakglass.used`,
`inbox.received`, `inbox.consumed`, `terminal.disposition`. Executor-emitted, so lines are identical
across Claude/Codex/Pi.
- AC1: run the same Task under two runtimes → event sequences are byte-identical modulo
`event_id`/`timestamp`/`source`. **This is the runtime-neutrality claim; assert it, don't assume it.**
- AC2: chain verification ⇒0 on a healthy ledger, ⇒≠0 after any row alteration. Registered must-fail case.
- AC3: an emit that fails to persist ⇒ the operation it describes fails (no fire-and-forget logging on
the authority path).
- AC4: `grep -rn "events.ndjson"` in production paths returns nothing (the duplicate island is gone —
enforced permanently by R-065).
- Dogfood: P-AUDIT-001 duplicate Python ledger; `#1018` reconstruction required manual archaeology.
- Fail-closed point: **unpersistable event ⇒ operation fails.** (Yes, this trades availability for
auditability. That is the decision on record.)
---
**R-023 — `packages/coord` submits through the executor**
_build 1 · depends: R-020 · 140k · sonnet_
`packages/coord/src/runner.ts:397-427` spawns a child process directly and tracks tasks in
`docs/TASKS.md` + `.mosaic/orchestrator/mission.json`. Replace the spawn with an executor submit;
keep coord's session-lock/crash-recovery, which are good and already durable.
- AC1: `mosaic coord run` on a fixture mission produces `macp_tasks` + `macp_events` rows; no direct
`spawn` of the runtime remains in `runner.ts` (assert by AST/grep in the R-025 check).
- AC2: crash recovery still works — kill mid-task, resume, and the task is reclaimed exactly once.
- AC3: coord no longer _writes_ `docs/TASKS.md` as authority (read-only mirror at most, per R-062).
- Dogfood: coord/macp "disconnected islands" finding.
- Fail-closed point: **coord cannot complete a task the executor did not run.**
---
**R-024 — Launch path records a typed Task**
_build 1 · depends: R-020, R-022 · 160k · sonnet_
`packages/mosaic/src/commands/launch.ts:730-843,1102-1167` dispatches `mosaic yolo|claude|codex|pi`
straight to harness launch with no Task, no gates, no events. This is the path humans and the fleet
actually use — leaving it outside the choke point leaves the choke point decorative.
- AC1: `mosaic yolo <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].