{"type":"meta","slug":"overdeck-v1","topic":"Overdeck v1 remaining waves: adapters A5-A7 + wiring, Inbox/Decisions/Map pages, action gateway, deck-tui, alerter+digest, e2e verify. Waves 0-2 (S1,S2,A1-A4,U1) already landed on main at 60073a9.","created":"2026-07-17","exec_mode":"ship","scheduler":"dag-parallel","land_mode":"merge-to-main","base_branch":"main","gate0_mode":"strict","preset":"codex-cursor"}
{"type":"task","id":"a5","wave":1,"seat":"coder","tier":"medium","deps":[],"files":["collector/src/adapters/cluster.ts","collector/src/adapters/cluster.test.ts"],"desc":"Read docs/plans/2026-07-17-overdeck-v1.md Task A5 (Cluster + agent-guard adapter) for the full contract. Create collector/src/adapters/cluster.ts exporting createClusterAdapter(opts) implementing the Adapter contract in collector/src/adapter.ts (id, interval, poll). Sources are ALL read-only: agent-guard unix socket /tmp/system-monitor/agent-guard.sock (events, culprit candidates), ~/.claude/run/buildbox-watch/state, ~/.claude/run/ci-fallback/{idle,pressure}, ~/.cache/buildslot queue dir, and an orphan scan over `rtk proxy ps` output (etime>30m AND pcpu>80 AND an agent-class regex pinned in config, never hardcoded). Emits Panel{id:'cluster'} (debian1 online/offline, autoscaler state, buildslot running/queued/p95), Panel{id:'agents'} (fleet by class, CPU, slices), and Item{kind:'alert',severity:'act'} per orphan candidate carrying ActionRef{verb:'reap'}. DECLARATIVE RECONCILIATION CONTRACT — read collector/src/adapter.ts and collector/src/state.ts BEFORE writing any code: poll() returns this adapter's COMPLETE CURRENT snapshot; an item present means the condition holds NOW; an item absent from a SUCCESSFUL poll is auto-resolved by the reconciler (tombstoned + item-resolved delta). NEVER write emit-once / suppress / seen-set / already-reported logic — under this contract that is a BUG. A poll that cannot read its source MUST throw (a failed poll retains all prior state); NEVER swallow an error and return a partial snapshot, which would falsely resolve live items. Model the file's shape on the existing collector/src/adapters/prometheus.ts. Record fixtures into collector/test/fixtures/cluster/. Do NOT edit collector/src/adapters/index.ts or collector/src/config.ts — registration is task w1's scope. Acceptance: cd collector && bun test src/adapters/cluster.test.ts — expect PASS against fixture socket transcript, state files and ps output. Commit only: collector/src/adapters/cluster.ts, collector/src/adapters/cluster.test.ts, collector/test/fixtures/cluster/.","requires_decision":null,"verify":["cd collector && bun test src/adapters/cluster.test.ts"],"acceptance":["cd collector && bun test src/adapters/cluster.test.ts — expect PASS against fixture socket transcript, state files and ps output"]}
{"type":"task","id":"a6","wave":1,"seat":"coder","tier":"medium","deps":[],"files":["collector/src/adapters/gates.ts","collector/src/adapters/gates.test.ts"],"desc":"Read docs/plans/2026-07-17-overdeck-v1.md Task A6 (Gates adapter) for the full contract. Create collector/src/adapters/gates.ts exporting createGatesAdapter(opts) implementing the Adapter contract in collector/src/adapter.ts (id, interval, poll). Read-only sources, all paths from config with no hardcoded absolute paths: security-gate prevent/confirmed.json plus its precision records; and for each configured repo, that repo's .slopgate/ ratchet baseline giving a per-repo debt count plus a 7-day delta computed from git log of the baseline file. Emits Panel{id:'gates'}; Item{kind:'gate'} for unresolved prevent-band findings and for .warnignore additions awaiting sign-off (these surface on the Decisions page). DECLARATIVE RECONCILIATION CONTRACT — read collector/src/adapter.ts and collector/src/state.ts BEFORE writing any code: poll() returns this adapter's COMPLETE CURRENT snapshot; an item present means the condition holds NOW; an item absent from a SUCCESSFUL poll is auto-resolved by the reconciler. NEVER write emit-once / suppress / seen-set logic — under this contract that is a BUG. A poll that cannot read its source MUST throw so prior state is retained; NEVER swallow an error and return a partial snapshot. Model the file's shape on the existing collector/src/adapters/golive.ts (it already reads repo files + git log). Record fixture trees into collector/test/fixtures/gates/. Do NOT edit collector/src/adapters/index.ts or collector/src/config.ts — registration is task w1's scope. Acceptance: cd collector && bun test src/adapters/gates.test.ts — expect PASS on fixture trees. Commit only: collector/src/adapters/gates.ts, collector/src/adapters/gates.test.ts, collector/test/fixtures/gates/.","requires_decision":null,"verify":["cd collector && bun test src/adapters/gates.test.ts"],"acceptance":["cd collector && bun test src/adapters/gates.test.ts — expect PASS on fixture trees"]}
{"type":"task","id":"a7","wave":1,"seat":"coder","tier":"medium","deps":[],"files":["collector/src/adapters/botmaster.ts","collector/src/adapters/botmaster.test.ts"],"desc":"Read docs/plans/2026-07-17-overdeck-v1.md Task A7 (Botmaster adapter) for the full contract. Create collector/src/adapters/botmaster.ts exporting createBotmasterAdapter(opts) implementing the Adapter contract in collector/src/adapter.ts (id, interval, poll). Poll Botmaster's existing read-only API routes (/api/metrics, and /api/bots/:id/logs for error counts); base URL and auth token come from config, never hardcoded, never committed. Botmaster itself is NOT modified. Emits Panel{id:'bots'} (per-bot status, msgs24h, errors, cost) and Item{kind:'alert'} when a bot is down. Inject the fetch implementation via the FetchLike type exported from collector/src/adapter.ts so tests pass a mock — do NOT call global fetch directly; model this on collector/src/adapters/harness.ts which already does exactly that. DECLARATIVE RECONCILIATION CONTRACT — read collector/src/adapter.ts and collector/src/state.ts BEFORE writing any code: poll() returns this adapter's COMPLETE CURRENT snapshot; an item present means the condition holds NOW; an item absent from a SUCCESSFUL poll is auto-resolved by the reconciler, so a recovered bot needs NO explicit resolve path. NEVER write emit-once / suppress / seen-set logic — under this contract that is a BUG. A poll whose API call fails MUST throw so prior state is retained; NEVER swallow the error and return a partial snapshot, which would falsely resolve live alerts. Record API fixtures into collector/test/fixtures/botmaster/. Do NOT edit collector/src/adapters/index.ts or collector/src/config.ts — registration is task w1's scope. Acceptance: cd collector && bun test src/adapters/botmaster.test.ts — expect PASS on recorded API fixtures, including a test that a failing API call rejects. Commit only: collector/src/adapters/botmaster.ts, collector/src/adapters/botmaster.test.ts, collector/test/fixtures/botmaster/.","requires_decision":null,"verify":["cd collector && bun test src/adapters/botmaster.test.ts"],"acceptance":["cd collector && bun test src/adapters/botmaster.test.ts — expect PASS on recorded API fixtures, including a test that a failing API call rejects"]}
{"type":"task","id":"w1","wave":2,"seat":"coder","tier":"medium","deps":["a5","a6","a7"],"files":["collector/src/adapters/index.ts","collector/src/config.ts","collector/test/adapters-wiring.test.ts"],"desc":"Register the three adapters landed in wave 1 (cluster, gates, botmaster) into the collector. In collector/src/adapters/index.ts extend buildAdapters(config) to construct createClusterAdapter, createGatesAdapter and createBotmasterAdapter following the EXACT pattern the existing golive and ghci blocks already use in that file (isEnabled(id) guard, intervalOf(id), settings(id) passthrough) — read the file first and copy its shape, do not invent a new registration mechanism. In collector/src/config.ts extend AdapterConfigSchema with whatever per-adapter settings those three need (every new field OPTIONAL, no secrets, no hardcoded absolute paths). Extend collector/test/adapters-wiring.test.ts so the expected adapter id list becomes the full set including the three new ids, and so each new adapter is proven to respect its config-disable flag. Acceptance: cd collector && bun test test/adapters-wiring.test.ts — expect PASS. Commit only: collector/src/adapters/index.ts, collector/src/config.ts, collector/test/adapters-wiring.test.ts.","requires_decision":null,"verify":["cd collector && bun test test/adapters-wiring.test.ts"],"acceptance":["cd collector && bun test test/adapters-wiring.test.ts — expect PASS"]}
{"type":"task","id":"u2","wave":3,"seat":"coder","tier":"medium","deps":["w1"],"files":["apps/web/src/pages/inbox.astro","apps/web/tests/inbox.spec.ts"],"desc":"Read docs/plans/2026-07-17-overdeck-v1.md Task U2 (Inbox page) for the full contract, and use docs/mockups/deck-overview-v2.html as the render oracle. Build apps/web/src/pages/inbox.astro: full triage list with a filter rail (kind counts), severity sort, project color tags, inline ActionRef buttons rendered DISABLED with tooltip text stating actions land in wave 4 (task x1 enables them; decision answers are already live), snooze (local state — Item hidden until ts+duration), and an inbox-zero empty state. DATA FLOW — read apps/web/src/pages/index.astro (Overview) FIRST and follow it exactly: all collector reads go through the same-origin server-side proxy route apps/web/src/pages/api/collector/[...path].ts. NEVER fetch the collector directly from client JS and NEVER put the bearer token in a PUBLIC_ env var or client bundle — the token is server-side only. Reuse the existing packages/deck-ui components (InboxItem, SectionCard, StaleBadge); page-local composition lives under apps/web/src/components/inbox/. Do NOT edit packages/deck-ui/src/index.ts. Acceptance: pnpm exec playwright test apps/web/tests/inbox.spec.ts — expect PASS covering filters, snooze round-trip, severity ordering, and project colors matching settings. Commit only: apps/web/src/pages/inbox.astro, apps/web/src/components/inbox/, apps/web/tests/inbox.spec.ts.","requires_decision":null,"verify":["pnpm exec playwright test apps/web/tests/inbox.spec.ts"],"acceptance":["pnpm exec playwright test apps/web/tests/inbox.spec.ts — expect PASS covering filters, snooze round-trip, severity ordering, and project colors matching settings"]}
{"type":"task","id":"u3","wave":4,"seat":"coder","tier":"medium","deps":["u2"],"files":["apps/web/src/pages/decisions.astro","apps/web/tests/decisions.spec.ts","packages/deck-ui/src/DecisionRow.tsx"],"desc":"Read docs/plans/2026-07-17-overdeck-v1.md Task U3 (Decisions page) for the full contract, and use docs/mockups/deck-decisions-v3.html as the render oracle. Add a DecisionRow component to packages/deck-ui (typed props, no fetch inside — data via props, co-located test, exported from packages/deck-ui/src/index.ts following the existing barrel pattern) rendering a collapsed row: project edge + tag, title, waiting-age with old-threshold color, inline option buttons, free-text field. Clicking the title expands in place to show the full question, data block, context kvs and journal ref. Build apps/web/src/pages/decisions.astro consuming Item records where kind is decision or halt. Answering POSTs to /decisions/:id/answer THROUGH the same-origin proxy route apps/web/src/pages/api/collector/[...path].ts (read apps/web/src/pages/index.astro first for the data-flow pattern; the bearer token is server-side only and must never reach client JS). Note the proxy route currently allowlists only read paths — extend that allowlist to permit exactly this answer POST and nothing else, keeping it default-deny. Optimistic UI, journal-confirmed final state; on failure the row shows an error state and the typed answer is PRESERVED in the input. Acceptance: pnpm exec playwright test apps/web/tests/decisions.spec.ts — expect PASS covering expand/collapse, option answer posting the exact payload to a mock, free-text answer, and the failure path keeping the text. Commit only: packages/deck-ui/src/DecisionRow.tsx, packages/deck-ui/src/DecisionRow.test.tsx, packages/deck-ui/src/index.ts, apps/web/src/pages/decisions.astro, apps/web/src/pages/api/collector/[...path].ts, apps/web/tests/decisions.spec.ts.","requires_decision":null,"verify":["pnpm exec playwright test apps/web/tests/decisions.spec.ts"],"acceptance":["pnpm exec playwright test apps/web/tests/decisions.spec.ts — expect PASS covering expand/collapse, option answer posting the exact payload to a mock, free-text answer, and the failure path keeping the text"]}
{"type":"task","id":"u4","wave":5,"seat":"coder","tier":"medium","deps":["u3"],"files":["apps/web/src/pages/map.astro","apps/web/tests/map.spec.ts"],"desc":"Read docs/plans/2026-07-17-overdeck-v1.md Task U4 (Map page + remaining pages) for the full contract, and use docs/mockups/deck-overview-v2.html as the render oracle. Add MapCanvas and MapNode to packages/deck-ui (SVG edges, entity chips) plus an inspector side panel showing node kvs and ActionRefs; the node set DERIVES from the panels present — no hardcoded hosts. Build apps/web/src/pages/map.astro plus the remaining sidebar pages, each a filtered panel/table view REUSING existing deck-ui components with NO new one-off components: plans (runs list to per-run forensics), agents (fleet + orphans), ci, bots, limits, gates, scoreboard (full history + churn callouts), settings (project colors editor writing collector config via POST /config/projects, plus adapter status/intervals). All collector access goes through the same-origin proxy route apps/web/src/pages/api/collector/[...path].ts — read apps/web/src/pages/index.astro first and follow it; the bearer token is server-side only and must never reach client JS. Any new proxied path must be added to that route's default-deny allowlist explicitly. Acceptance: pnpm exec playwright test — expect the FULL suite to PASS: map renders fixture nodes and the inspector, every sidebar page is non-empty against the fixture collector, and the settings color override round-trips. Commit only: packages/deck-ui/src/MapCanvas.tsx, packages/deck-ui/src/MapNode.tsx, their co-located tests, packages/deck-ui/src/index.ts, apps/web/src/pages/, apps/web/src/components/, apps/web/tests/.","requires_decision":null,"verify":["pnpm exec playwright test"],"acceptance":["pnpm exec playwright test — expect the FULL suite to PASS: map renders fixture nodes and the inspector, every sidebar page is non-empty against the fixture collector, and the settings color override round-trips"]}
{"type":"task","id":"x1","wave":6,"seat":"coder","tier":"medium","deps":["u4"],"files":["collector/src/actions.ts","collector/src/actions.test.ts"],"desc":"Read docs/plans/2026-07-17-overdeck-v1.md Task X1 (Action gateway) for the full contract. THIS IS THE SECURITY-CRITICAL TASK — write the DENY-PATH TESTS FIRST, before any implementation. Add POST /actions/:verb to the collector with a DEFAULT-DENY allowlist of EXACTLY four verbs: reap (runs reaper-ctl kill <pid> --escalate), ci-rerun (runs gh run rerun <id>), steer (calls the harness steer endpoint via the harness adapter), snooze (collector-local, no subprocess). Any other verb MUST 404. Args are zod-validated per verb: for reap the pid MUST be an integer AND MUST appear in the CURRENT orphan candidate set from the cluster adapter, otherwise 400; for ci-rerun the run id MUST exist in the current ci panel, otherwise 400. NEVER concatenate args into a shell string and NEVER pass them through a shell — spawn with an argv ARRAY only. Every action appends one line to ~/.local/state/overdeck/actions.jsonl of exact shape {ts, verb, args, requestedBy, result, rc}. Journal the attempt whether it succeeds or fails. On failure the originating Item stays with an error note. Then enable the inline action buttons on the Inbox page (they were rendered disabled by task u2) and add a confirm dialog on reap; route the POST through the same-origin proxy route apps/web/src/pages/api/collector/[...path].ts, extending that route's default-deny allowlist to permit exactly the actions path. Acceptance: cd collector && bun test src/actions.test.ts && pnpm exec playwright test apps/web/tests/actions.spec.ts — expect PASS: a non-allowlisted verb returns 404; a pid absent from the candidate set returns 400; the journal line matches the exact shape; the UI confirm-to-toast round-trip works against a mock executor. Commit only: collector/src/actions.ts, collector/src/actions.test.ts, collector/src/index.ts, apps/web/src/pages/api/collector/[...path].ts, apps/web/src/components/, apps/web/tests/actions.spec.ts.","requires_decision":null,"verify":["cd collector && bun test src/actions.test.ts && pnpm exec playwright test apps/web/tests/actions.spec.ts"],"acceptance":["cd collector && bun test src/actions.test.ts && pnpm exec playwright test apps/web/tests/actions.spec.ts — expect PASS: a non-allowlisted verb returns 404; a pid absent from the candidate set returns 400; the journal line matches the exact shape; the UI confirm-to-toast round-trip works against a mock executor"]}
{"type":"task","id":"t1","wave":7,"seat":"coder","tier":"medium","deps":["x1"],"files":["tui/Cargo.toml","tui/src/main.rs"],"desc":"Read docs/plans/2026-07-17-overdeck-v1.md Task T1 (deck-tui) for the full contract. Render oracle: the section titled 'CONSOLE — Keyboard-First Web TUI' (design direction C) inside docs/mockups/deck-designs.html — that one file holds all five directions, so read ONLY the CONSOLE section and ignore TOWER/INBOX/SCOREBOARD/ATLAS. Create a Rust binary crate at tui/ in this repo. It depends on the tui-kit crate (package name tui-kit, version 0.1.0) which lives in the SEPARATE repo /home/user/Projects/mega-plan-harness at tui/crates/tui-kit and is already landed on that repo's main — declare it as a cargo git dependency against that repo (that repo has NO network remote, so use a file:// git URL) and pin a rev. READ that crate's actual public API before writing any code — do NOT guess widget or theme names. Also depends on reqwest against the collector on 127.0.0.1:4980. Views per the mockup, tabs 1:overview 2:inbox 3:decisions 4:plans 5:agents 6:ci 7:limits, a ':' command palette (kit widget; commands are the same ActionRef verbs plus navigation), and a status bar (collector health, top severities). Keymap: j/k move, Enter expand/answer, y/n confirm, ? help via the kit overlay. The TUI reads and acts THROUGH the collector ONLY — no direct access to any underlying source, so the collector stays the single seam. Acceptance: cargo test --manifest-path tui/Cargo.toml — expect PASS: projection tests per view against fixture collector JSON, and the palette executing reap against a mock gateway. Commit only: tui/.","requires_decision":null,"verify":["cargo test --manifest-path tui/Cargo.toml"],"acceptance":["cargo test --manifest-path tui/Cargo.toml — expect PASS: projection tests per view against fixture collector JSON, and the palette executing reap against a mock gateway"]}
{"type":"task","id":"n1","wave":8,"seat":"coder","tier":"medium","deps":["t1"],"files":["collector/src/alerter.ts","collector/src/alerter.test.ts"],"desc":"Read docs/plans/2026-07-17-overdeck-v1.md Task N1 (Alerter + morning digest) for the full contract. Add collector/src/alerter.ts: alert rules fire on severity 'act' Items ONLY, notifying the desktop via the existing gdbus notifier pattern used by system-monitor's notifier.py (read it, follow its conventions, do not invent a new notification mechanism), with a per-item cooldown and quiet hours from config. THRESHOLDS MUST COME FROM CONFIG seeded by the notification-mining results (ANNOYANCE_FATIGUE section 7, mine-all.prompt output) — do NOT invent thresholds. If that mining output is absent, the alerter MUST ship DISABLED and emit a named config error explaining exactly how to enable it; it must never guess a threshold and never silently self-enable. Add GET /digest plus an Overview banner shown on the first visit of a day: ONE generated paragraph from a fixed template (NOT an LLM call) covering overnight runs, scoreboard deltas, new failures and waiting decisions. Acceptance: cd collector && bun test src/alerter.test.ts — expect PASS: cooldown honored, quiet hours honored, digest string exact on a fixture day, and alerter disabled with the named error when mining output is absent. Commit only: collector/src/alerter.ts, collector/src/alerter.test.ts, collector/src/index.ts, apps/web/src/components/.","requires_decision":null,"verify":["cd collector && bun test src/alerter.test.ts"],"acceptance":["cd collector && bun test src/alerter.test.ts — expect PASS: cooldown honored, quiet hours honored, digest string exact on a fixture day, and alerter disabled with the named error when mining output is absent"]}
{"type":"task","id":"v1","wave":9,"seat":"coder","tier":"medium","deps":["n1"],"files":["GOLIVE.md"],"desc":"Read docs/plans/2026-07-17-overdeck-v1.md Task V1 (End-to-end verify + GOLIVE audit) for the full contract. Run the full automated gate and record the REAL results: pnpm -r typecheck && pnpm -r test must PASS with zero warnings (the .warnignore policy applies — fix a warning, never blanket-suppress it), pnpm exec playwright test must PASS, and cd collector && bun test must PASS. Then audit GOLIVE.md: flip an acceptance criterion to done ONLY where you have OBSERVED the behavior in this run's own output, and report works/total. NEVER flip an AC you have not observed, and NEVER flip one whose evidence is only that code exists. The live manual smoke described in the plan (start the collector unit, walk every sidebar page against LIVE data, answer one real decision end-to-end, run one staged reap) requires a human operator and live sources — do NOT fake it, do NOT mark those ACs done, and do NOT delete them: leave them unchecked and list them explicitly in your final report as the remaining human-verification step. Acceptance: the three commands above all PASS and GOLIVE.md reflects only observed behavior. Commit only: GOLIVE.md.","requires_decision":null,"verify":["pnpm -r typecheck && pnpm -r test","pnpm exec playwright test","cd collector && bun test"],"acceptance":["the three commands above all PASS and GOLIVE.md reflects only observed behavior"]}
