Wave 0: bootstrap coordinator scaffolding (AGENTS, SPEC, verify-stage, Makefile, workspaces)

This commit is contained in:
John Smith committed 2026-10-15 17:46:00 -04:00
commit 35f6bba98b
7 files changed
+782

No files matched your search

+181
View File
@@ -0,0 +1,181 @@
# AGENTS.md — Coordinator Harness for `agency-control-plane`
**Repo:** `/home/john/Projects/Heretek-AI-Rebuild/agency-control-plane`
**Remote:** `https://github.com/Heretek-AI/agency-control-plane`
**Package:** `@heretek-ai/agency-control-plane`
**Coordinator:** `agency-coordinator` (`opencode-go/muse-spark-1.3-contributor`)
**Model harness:** `opencode-go/muse-spark-1.3-contributor` — six non-negotiable rules apply on every turn.
---
## 1. Zero-Summarization Rule (Binding)
1. The complete specification lives in `docs/prompts/SPEC.md`. It contains verbatim
copies of `MASTER_BUILD_PROMPT.md` sections 0, 1, 2.1, 2.2, 2.3 plus the exact
module contracts.
2. Every subagent dispatch MUST instruct the subagent to read `AGENTS.md` and
`docs/prompts/SPEC.md` directly from disk with native `read` before doing any work.
3. Never paraphrase, compress, or re-dictate the spec inside a subagent prompt.
Point at the files; include only the wave assignment, worktree path, and branch name.
4. Violation = drift-guard `VERDICT: FAIL`.
## 2. Explicit Triad Roles
| Role | Agent ID | Responsibility |
|------|----------|----------------|
| Programmer | `agency-programmer` | Implements production code + unit tests + accept scripts. No stubs, no TODOs. Fixes all diagnostics immediately. |
| QA | `agency-qa` | Runs `make check`, accept scripts, and Red-Proof Sabotage. Records `docs/evidence/wave-<N>/red-proofs.log`. |
| Drift Guard | `agency-drift-guard` | Audits scope, spec fidelity, zero-stub grep, and issues binding `VERDICT: PASS/FAIL` in `docs/reports/wave-<N>-verification.md`. |
Always pass `"agent": "agency-programmer"`, `"agent": "agency-qa"`, or
`"agent": "agency-drift-guard"` when calling `subagent`. No other agent IDs are valid.
## 3. Vanilla Hygiene
- Use native `read`, `write`, `edit`, `glob`, `grep` — never `cat` or `sed`.
- Never include `read` line numbers (e.g. `12: `) in `edit` `oldString`/`newString`.
- Fix every `<diagnostics>` block immediately before proceeding.
- Bun-first toolchain: `bun install`, `bun run check`, `bun test`.
## 4. Strict Scope (ONLY `dispatcher/` + `web/`)
This repository is ONLY:
1. The **Fleet Dispatcher Daemon** (`dispatcher/`), and
2. The **unified High-Fidelity WebUI** (`web/`) with the 5 locked-in UX pillars:
1. **Kanban → worktrees**: 5-column board (`Backlog`, `Ready`, `In Progress`,
`Waiting Approval / QA`, `Done`) mapped to isolated `jj` / Git worktrees,
branch badges (`fleet/task-101`), owned-path chips, model-tier indicators.
2. **Director Chat sidebar**: persistent dockable chat with the Top-Level
Director Agent, `/ship <spec>`, mission charter preview, 1-click DAG injection.
3. **DAG & Worker Drill-Down SSE inspector**: click any card → live SSE tool-call
timeline, stage gates, token/cost telemetry, DAG dependency graph, Docker
Compose profile badge (`core` / `appsec` / `android` / `game` / `research`).
4. **Side-by-Side / Unified Diff viewer**: file-tree navigation,
addition/deletion counters, syntax-highlighted hunks.
5. **1-Click / passphrase Ed25519 Approval Drawer**: exact `subjectHash`, diff
summary, QA verdicts (`qa-functional` + `qa-adversarial`), Ed25519 approval gate.
**Invariant I1:** the passphrase is kept strictly in a local function closure
during the `fetch("/api/approvals/:id/sign")` call, zeroed immediately after,
and NEVER written to reactive stores (`fleet-store`), `localStorage`,
`sessionStorage`, or DOM data attributes.
### Forbidden in this repo (Drift Guard MUST fail on sight)
- Do NOT copy `SwarmOS` (`src/omni`, `swarmos-plugin.ts`).
- Do NOT implement AST parsers or domain MCP tools here.
- Scope refusal check: any file outside `dispatcher/`, `web/`, `docs/`, `scripts/`,
repo-root configs (`package.json`, `tsconfig.json`, `Makefile`, `AGENTS.md`) is rejected.
## 5. Critical Bug Fixes Enforced (Reports `01b`, `04a`, `04b`)
### Fix 1 — `dispatcher/src/limits.ts` 429 Global Freeze Bug
- Never pause all missions for 60s in an infinite loop on HTTP 429 (`gousagelimiterror`).
- `extractRetryAfterMs(headers?, defaultMs?)`: parse `retry-after-ms` (ms) and
`retry-after` (seconds or HTTP-Date), clamp strictly to `[1000, 300000]`.
- `classifyProviderFailure(failure, consecutiveFailures, currentTier)`:
- Quota shape: `failure.status === 402`, or code matches `gousagelimiterror` /
`freeusagelimiterror` / `insufficient_quota`, or 429 body matches
`/usage limit|quota exceeded|insufficient quota|credit balance/i`:
- `tier-1-opencode` → `{ action: "route-fallback", targetTier: "tier-2-commercial" }` (ZERO mission pause).
- `tier-2-commercial` → `{ action: "route-fallback", targetTier: "tier-3-local" }`.
- `tier-3-local` → `{ action: "pause-missions", reason, resumeAfterMs }`.
- Transient 429 (no quota shape) → `{ action: "retry-delay", delayMs: extractRetryAfterMs(headers) }`.
- Tier chain: `tier-1-opencode` → `tier-2-commercial` → `tier-3-local` via Maxim Bifrost.
### Fix 2 — OpenCode v2 SQLite Single-Writer & SSE Overflow
- Dispatcher SQLite store: `PRAGMA busy_timeout = 15000; PRAGMA journal_mode = WAL;`
- Isolate each worker's `.opencode` state per worktree.
- Multiplex worker events over a ring-buffered SSE / NATS JetStream bus
(bounded buffer, backpressure drop-oldest with sequence-gap marker, KV blackboard bridge).
## 6. Full Repository Layout
```text
agency-control-plane/
├── AGENTS.md
├── Makefile
├── package.json # Bun workspace root: ["dispatcher", "web"]
├── tsconfig.json
├── docs/
│ ├── prompts/
│ │ ├── SPEC.md
│ │ └── verify-stage.md
│ ├── reports/ # wave-<N>.md + wave-<N>-verification.md
│ └── evidence/ # wave-<N>/red-proofs.log
├── scripts/
│ └── accept/
│ ├── wave-1.sh
│ ├── wave-2.sh
│ └── wave-3.sh
├── dispatcher/ # Waves 1 & 2
│ ├── package.json # @heretek-ai/agency-dispatcher
│ ├── src/
│ │ ├── model.ts
│ │ ├── wildcard.ts
│ │ ├── scheduler.ts
│ │ ├── limits.ts
│ │ ├── audit.ts
│ │ ├── worktrees.ts
│ │ ├── landing.ts
│ │ ├── event-bus.ts
│ │ ├── approval-tickets.ts
│ │ ├── server.ts
│ │ └── index.ts
│ └── test/
│ ├── scheduler.test.ts
│ ├── limits.test.ts
│ ├── worktrees-landing.test.ts
│ └── server-api.test.ts
└── web/ # Wave 3
├── package.json # @heretek-ai/agency-web
├── index.html
├── src/
│ ├── state/fleet-store.ts
│ ├── security/approval-client.ts
│ ├── components/
│ │ ├── KanbanBoard.tsx
│ │ ├── DirectorChat.tsx
│ │ ├── WorkerDrillDown.tsx
│ │ ├── SplitDiffViewer.tsx
│ │ └── ApprovalDrawer.tsx
│ └── app.tsx
└── test/
├── approval-invariant.test.ts
└── ui-components.test.ts
```
## 7. API Contract (`dispatcher/src/server.ts`, Bun HTTP + SSE, loopback `127.0.0.1`)
| Method & Path | Purpose |
|---------------|---------|
| `GET /api/board` | Kanban columns, tasks, worktree statuses, active model tier (`tier-1-opencode` / `tier-2-commercial` / `tier-3-local`), DAG edges. |
| `POST /api/director/message` | Append steering message to Director session; decompose new task cards onto the board. |
| `GET /api/tasks/:id/drilldown` | Worker session metadata, stage gates, live tool-log buffer, git diff hunks (`[{ file, status, additions, deletions, patch }]`). |
| `POST /api/approvals/:taskId/ticket` | Mint single-use 60s-TTL preview ticket bound to `{ taskId, subjectHash }`. |
| `POST /api/approvals/:taskId/sign` | Verify single-use ticket + Ed25519 signature/passphrase in memory; transition `waitingHuman` → `ready`/`done`; append to audit ledger. |
| `GET /api/events` | SSE stream (ring-buffered, replayable sequence IDs). |
## 8. Commands
- `make check` — `bun install && bun run check && bun test` (typecheck + lint + full tests).
- `make accept` — `bash scripts/accept/wave-1.sh && bash scripts/accept/wave-2.sh && bash scripts/accept/wave-3.sh`.
- `make accept-1` / `make accept-2` / `make accept-3` — per-wave acceptance.
## 9. Triad Protocol (Binding Gate, Max 3 Rework Cycles)
1. **Programmer** implements in a wave worktree (`worktrees/wave-<N>`, branch
`wave-<N>-*`), writes code + tests + accept script + `docs/reports/wave-<N>.md`,
runs `bun test` + `bun run check` green, commits in the worktree.
2. **QA** runs `make check`, the wave accept script, and Red-Proof Sabotage
(break it → test fails → restore cleanly via `git checkout --`), recording
`docs/evidence/wave-<N>/red-proofs.log`.
3. **Drift Guard** audits scope/spec/stubs and issues `VERDICT: PASS` (or FAIL with
findings) in `docs/reports/wave-<N>-verification.md`.
4. Coordinator merges to `main` ONLY on `VERDICT: PASS` + green QA.
5. Max **3 rework cycles** per wave: Programmer → QA → Drift Guard counts as one.
After 3 failed cycles the coordinator halts the wave and escalates with evidence.
6. Zero stubs, zero TODOs, zero dummy passes, fail-closed security. No merge without
both QA red-proofs and Drift Guard `VERDICT: PASS`.
+176
View File
@@ -0,0 +1,176 @@
# MASTER COORDINATOR BUILD PROMPT: REPO 4 — `agency-control-plane`
**Target Repository:** `/home/john/Projects/Heretek-AI-Rebuild/agency-control-plane`
**GitHub Remote:** `https://github.com/Heretek-AI/agency-control-plane`
**Package Name:** `@heretek-ai/agency-control-plane`
**Primary Session Agent:** `agency-coordinator` (`opencode-go/muse-spark-1.3-contributor`)
**Required Triad Subagents:** `agency-programmer`, `agency-qa`, `agency-drift-guard`
---
## 0. CRITICAL MUSE 1.3 HARNESS SUPPLEMENT (READ FIRST — NON-NEGOTIABLE)
You are running on `opencode-go/muse-spark-1.3-contributor`. Obey these six rules on every turn:
1. **ZERO PROMPT SUMMARIZATION WHEN CALLING SUBAGENTS:**
- In Wave 0, write this entire specification into `docs/prompts/SPEC.md` and `AGENTS.md`. Every subagent call MUST instruct the subagent to `read` `AGENTS.md` and `docs/prompts/SPEC.md` directly from disk.
2. **EXPLICIT SUBAGENT ROLES:**
- Always pass `"agent": "agency-programmer"`, `"agent": "agency-qa"`, or `"agent": "agency-drift-guard"` when calling `subagent`.
3. **VANILLA `read` / `edit` / `write` HYGIENE:**
- Use native `read`, `write`, `edit`, `glob`, and `grep`—never `cat` or `sed`. Never include `read` line numbers (`12: `) in `edit` `oldString`/`newString`. Fix `<diagnostics>` immediately.
4. **STRICT SCOPE & REFUSAL BOUNDARY FOR `agency-control-plane`:**
- This repository is **ONLY** the Fleet Dispatcher Daemon (`dispatcher/`) and the unified High-Fidelity WebUI (`web/`) featuring your 5 locked-in UX pillars:
1. **Visual Kanban Board** mapped to isolated Jujutsu (`jj`) / Git worktrees,
2. **Persistent Top-Level Director Chat Sidebar** to plan and steer missions,
3. **Interactive DAG & Worker Drill-Down Inspector** (live SSE tool stream, stage gates, token/cost telemetry),
4. **Side-by-Side / Unified Git Diff Viewer** with file tree navigation, and
5. **1-Click / Passphrase Cryptographic Ed25519 Human Approval Drawer** (enforcing Invariant I1: passphrase kept strictly in local closure, never in reactive state stores).
- **FORBIDDEN IN THIS REPO:** Do NOT copy `SwarmOS` (`src/omni`, `swarmos-plugin.ts`) into this repo! Do NOT implement AST parsers or domain MCP tools here.
5. **CRITICAL BUG FIXES ENFORCED IN THIS REPO (FROM REPORTS `01b`, `04a`, `04b`):**
- **Fix 1 (`limits.ts` 429 Global Freeze Bug):** Never pause all missions for 60s in an infinite loop on HTTP 429 (`gousagelimiterror`). Parse `retry-after-ms` / `Retry-After` (clamped `1000ms..300000ms`) and immediately return `{ action: "route-fallback", targetTier: "tier-2-commercial" | "tier-3-local" }` via Maxim Bifrost!
- **Fix 2 (OpenCode v2 SQLite Single-Writer & SSE Overflow):** Set `PRAGMA busy_timeout = 15000; PRAGMA journal_mode = WAL;` on the dispatcher SQLite store, isolate each worker's `.opencode` state per worktree, and multiplex worker events over a ring-buffered SSE / NATS JetStream bus.
6. **BINDING TRIAD GATE:**
- Every wave requires `agency-qa` Red-Proof Sabotage (`docs/evidence/wave-<N>/red-proofs.log`) and `agency-drift-guard` `VERDICT: PASS` (`docs/reports/wave-<N>-verification.md`) before merging to `main`.
---
## 1. TARGET REPOSITORY ARCHITECTURE & FILE TREE
```text
/home/john/Projects/Heretek-AI-Rebuild/agency-control-plane/
├── AGENTS.md
├── Makefile
├── package.json # Bun workspace root: ["dispatcher", "web"]
├── tsconfig.json
├── docs/
│ ├── prompts/
│ │ ├── SPEC.md
│ │ └── verify-stage.md
│ ├── reports/
│ └── evidence/
├── scripts/
│ └── accept/
│ ├── wave-1.sh
│ ├── wave-2.sh
│ └── wave-3.sh
├── dispatcher/ # Wave 1 & 2: Fleet Dispatcher Daemon & HTTP/SSE Control API
│ ├── package.json # @heretek-ai/agency-dispatcher
│ ├── src/
│ │ ├── model.ts # TaskStatuses, Task, Mission, ApprovalTicket schemas
│ │ ├── wildcard.ts # Glob path intersection (`mayOverlap(pathA, pathB)`)
│ │ ├── scheduler.ts # Pure 186-line DAG scheduler (`all_success`, `one_success`, `pathsOverlap` lock, cascade cancel)
│ │ ├── limits.ts # Upgraded 3-tier failover & Retry-After classifier (Report 01b §9.1)
│ │ ├── audit.ts # O_APPEND + fsync SHA-256 hash-chained audit ledger with torn-tail check
│ │ ├── worktrees.ts # 2-phase crash-safe worktree coordinator supporting Jujutsu (`jj workspace add`) + git worktree fallback + `refs/fleet-salvage/<id>`
│ │ ├── landing.ts # Transactional landing in throwaway worktree (pre-verify -> merge -> post-verify -> rollback on conflict/failure)
│ │ ├── event-bus.ts # Ring-buffered SSE + NATS JetStream / KV Blackboard bridge
│ │ ├── approval-tickets.ts # Single-use preview tickets bound to subjectHash + loopback Ed25519 verification
│ │ ├── server.ts # Bun HTTP + SSE API serving Kanban state, Director chat, Worker drill-down, Diffs, and Approvals
│ │ └── index.ts
│ └── test/
│ ├── scheduler.test.ts # Tests all_success, one_success, cascade cancel, and pathsOverlap serialization
│ ├── limits.test.ts # Proves 429 gousagelimiterror triggers route-fallback instead of global freeze!
│ ├── worktrees-landing.test.ts # Tests 2-phase allocation, salvage ref anchoring, and transactional landing rollback
│ └── server-api.test.ts # Tests REST + SSE endpoints, approval tickets, and CSRF/loopback guards
└── web/ # Wave 3: Unified High-Fidelity WebUI (5 UX Pillars)
├── package.json # @heretek-ai/agency-web
├── index.html
├── src/
│ ├── state/
│ │ └── fleet-store.ts # Typed state store for Kanban columns, DAG nodes, active drill-down worker, and diffs
│ ├── security/
│ │ └── approval-client.ts # Ported from iumbtems approvals.ts (Invariant I1: passphrase held only in closure, never stored in state)
│ ├── components/
│ │ ├── KanbanBoard.tsx # Pillar 1: 5-column Worktree Kanban (Backlog, Ready, In Progress, Waiting Approval/QA, Done)
│ │ ├── DirectorChat.tsx # Pillar 2: Persistent Top-Level Director Chat sidebar with mission decomposition cards
│ │ ├── WorkerDrillDown.tsx # Pillar 3: Live worker subagent inspector (SSE tool stream, stage gates, token/tier badge, DAG view)
│ │ ├── SplitDiffViewer.tsx # Pillar 4: Side-by-side & unified Git diff viewer with file tree & line stats
│ │ └── ApprovalDrawer.tsx # Pillar 5: 1-click / passphrase Ed25519 approval & rejection gate
│ └── app.tsx # High-craft dark-mode layout satisfying WCAG 2.2 AA contrast & 24x24px target sizes
└── test/
├── approval-invariant.test.ts # Asserts passphrase never enters fleet-store or DOM attributes (Invariant I1)
└── ui-components.test.ts # Tests rendering and state transitions across all 5 UX pillars
```
---
## 2. EXACT MODULE SPECIFICATIONS (WRITE TO `docs/prompts/SPEC.md` IN WAVE 0)
### 2.1 Wave 1 Specification: Pure Scheduler, Path-Overlap Arbiter, 3-Tier Failover Limits & Audit Chain
1. **`dispatcher/src/wildcard.ts` & `dispatcher/src/scheduler.ts`:**
- Implement the exact salvaged `mayOverlap(patternA: string, patternB: string): boolean` and `schedule(tasks: Task[], runningIds: Iterable<string>, caps: ScheduleCaps): ScheduleResult` from `/home/john/Projects/The-Agency/dispatcher/src/scheduler.ts`:
- Under `all_success`, a task is ready when all `deps` are `done`; if any dep is `failed` or `cancelled`, it cascades to `cancelled` via fixpoint closure.
- Under `one_success`, a task is ready when at least one dep is `done`; it cancels only when all deps are terminal with zero `done`.
- Two `expert-run` tasks whose `input.paths` overlap (`mayOverlap(pa, pb) === true`) NEVER start simultaneously (`blockedByOverlap` / `collidesSelected`).
- `waitingHuman` tasks never start and never force downstream cancellation.
2. **`dispatcher/src/limits.ts` (Exact Patch from Report `01b` §9.1):**
- `extractRetryAfterMs(headers?: Record<string, string>, defaultMs?: number): number` — Parses `retry-after-ms` (ms) and `retry-after` (seconds or HTTP-Date), clamped strictly to `[1000, 300000]`.
- `classifyProviderFailure(failure: ProviderFailure, consecutiveFailures: number, currentTier: "tier-1-opencode" | "tier-2-commercial" | "tier-3-local"): ProviderDisposition`:
- When `failure.status === 402` or code matches `gousagelimiterror` / `freeusagelimiterror` / `insufficient_quota` or 429 body matches `/usage limit|quota exceeded|insufficient quota|credit balance/i`:
- From `tier-1-opencode` $\rightarrow$ returns `{ action: "route-fallback", targetTier: "tier-2-commercial" }` (ZERO mission pause!).
- From `tier-2-commercial` $\rightarrow$ returns `{ action: "route-fallback", targetTier: "tier-3-local" }`.
- From `tier-3-local` $\rightarrow$ returns `{ action: "pause-missions", reason: "...", resumeAfterMs }`.
- When `failure.status === 429` (transient rate spike without quota exhaustion) $\rightarrow$ returns `{ action: "retry-delay", delayMs: extractRetryAfterMs(failure.headers) }`.
3. **`dispatcher/src/audit.ts`:**
- Hash-chained JSONL audit log (`seq`, `timestamp`, `event`, `data`, `prevHash`, `hash`). Redacts secrets (`sk-*`, `Bearer *`, `OPENCODE_API_KEY`) before hashing. Detects any byte modification or equal-length rewrite via `checkAnchor`.
---
### 2.2 Wave 2 Specification: Crash-Safe Worktrees (`jj` / `git`), Transactional Landing & HTTP/SSE Control Server
1. **`dispatcher/src/worktrees.ts` & `dispatcher/src/landing.ts`:**
- Two-phase crash-safe worktree allocation (`allocating` recorded in SQLite before worktree creation, flipped to `allocated` after; `cleanupUnconfirmed` cleans orphans).
- Supports Jujutsu colocated workspaces (`jj workspace add`) when `.jj` is present, with seamless fallback to `git worktree add -b fleet/<taskId>`.
- `salvageWorktree` anchors `refs/fleet-salvage/<taskId>` before deleting abandoned task branches so agent work is never lost.
- `landTask` runs pre-checks in the task worktree, performs `--no-ff` merge onto `fleet/integration/<missionId>` inside a throwaway landing worktree, runs post-merge verification, and atomically rolls back to pre-merge SHA if verification or merge fails.
2. **`dispatcher/src/server.ts` & `dispatcher/src/approval-tickets.ts`:**
- Serves HTTP API + `/api/events` SSE stream on loopback (`127.0.0.1`):
- `GET /api/board` — Returns Kanban columns, tasks, worktree statuses, active model tier (`tier-1-opencode` / `tier-2-commercial` / `tier-3-local`), and DAG edges.
- `POST /api/director/message` — Appends steering message to Top-Level Director session and decomposes new task cards onto the board.
- `GET /api/tasks/:id/drilldown` — Returns worker session metadata, stage gates, live tool log buffer, and git diff hunks (`[{ file, status, additions, deletions, patch }]`).
- `POST /api/approvals/:taskId/ticket` — Mints a single-use, 60-second TTL preview ticket bound to `{ taskId, subjectHash }`.
- `POST /api/approvals/:taskId/sign` — Verifies the single-use ticket, verifies the Ed25519 signature / passphrase in memory, transitions task from `waitingHuman` to `ready`/`done`, and appends to `audit.ts`.
---
### 2.3 Wave 3 Specification: Unified High-Fidelity WebUI (`web/src/*`)
1. **`web/src/security/approval-client.ts` (Salvaged from `iumbtems/packages/web/src/approvals.ts`):**
- **Strict Security Invariant I1:** The human passphrase entered in the approval dialog is held ONLY in a lexical function closure variable during the `fetch("/api/approvals/:id/sign")` call, is zeroed out immediately after, and is **never** written to `fleet-store`, `localStorage`, `sessionStorage`, or DOM data attributes.
2. **Five High-Fidelity UX Components (`web/src/components/*`):**
- `KanbanBoard.tsx`: Renders the 5 Kanban columns (`Backlog`, `Ready`, `In Progress`, `Waiting Approval / QA`, `Done`) with worktree branch badges (`fleet/task-101`), owned-path chips, and model-tier indicators.
- `DirectorChat.tsx`: Persistent right/left dockable chat with the Top-Level Director Agent (`director`), supporting `/ship <spec>`, mission charter preview, and 1-click task DAG injection onto the Kanban board.
- `WorkerDrillDown.tsx`: Clicking any Kanban card opens the drill-down inspector showing the worker's live SSE tool-call timeline, active Docker Compose profile badge (`core` / `appsec` / `android` / `game` / `research`), and DAG dependency graph.
- `SplitDiffViewer.tsx`: Renders side-by-side and unified Git diffs for the selected worktree with file-tree navigation, addition/deletion counters, and syntax-highlighted hunk lines.
- `ApprovalDrawer.tsx`: Displays the exact `subjectHash`, diff summary, QA verdicts (`qa-functional` + `qa-adversarial`), and the 1-click / passphrase Ed25519 approval gate.
---
## 3. WAVE-BY-WAVE TRIAD EXECUTION PLAN FOR `agency-coordinator`
### Step 0 (Coordinator Bootstrap on `main`)
1. Write `AGENTS.md`, `docs/prompts/SPEC.md`, `docs/prompts/verify-stage.md`, `Makefile`, root `package.json`, and `tsconfig.json`.
2. Commit baseline scaffolding to `main`.
### Wave 1: Pure Scheduler, Path-Overlap Arbiter, 3-Tier Failover Limits & Audit Chain (`dispatcher/src/{model,wildcard,scheduler,limits,audit}.ts`)
1. Create worktree: `git worktree add -b wave-1-scheduler-limits worktrees/wave-1 main`
2. Dispatch **Programmer (`agency-programmer`)**:
```json
{
"agent": "agency-programmer",
"description": "Wave 1 Programmer: Implement scheduler, wildcard overlap, 3-tier limits, and audit log",
"prompt": "You are the Programmer Agent for Wave 1 in `/home/john/Projects/Heretek-AI-Rebuild/agency-control-plane/worktrees/wave-1` on branch `wave-1-scheduler-limits`.\n\nFIRST: Read `AGENTS.md` and `docs/prompts/SPEC.md` in full.\nSECOND: Implement in `dispatcher/src/`:\n1. `model.ts` and `wildcard.ts` (`mayOverlap`).\n2. `scheduler.ts` (`schedule`, `nextStatus` with `all_success`, `one_success`, fixpoint cancellation closure, and `pathsOverlap` serialization).\n3. `limits.ts` (`extractRetryAfterMs` clamped 1s..300s and `classifyProviderFailure` with 3-tier failover: `tier-1-opencode` -> `tier-2-commercial` -> `tier-3-local` on 429 quota errors).\n4. `audit.ts` (hash-chained JSONL ledger with secret redaction and equal-length rewrite detection).\n5. Write unit tests in `dispatcher/test/scheduler.test.ts` and `dispatcher/test/limits.test.ts`, acceptance script `scripts/accept/wave-1.sh`, and report `docs/reports/wave-1.md`.\n6. Run `bun test` and `bun run check` until green, then commit."
}
```
3. Dispatch **QA (`agency-qa`)** to run `make check`, `bash scripts/accept/wave-1.sh`, and perform Red-Proof Sabotage (revert `limits.ts` so 429 returns `pause-missions` on `tier-1-opencode` and confirm `limits.test.ts` fails; break `pathsOverlap` in `scheduler.ts` and confirm `scheduler.test.ts` fails; restore cleanly with `git checkout --`). Record in `docs/evidence/wave-1/red-proofs.log`.
4. Dispatch **Drift Guard (`agency-drift-guard`)** to audit Wave 1 and issue `VERDICT: PASS` in `docs/reports/wave-1-verification.md`.
5. Merge `wave-1-scheduler-limits` into `main` and remove worktree.
### Wave 2: Crash-Safe Worktrees, Transactional Landing & HTTP/SSE Control Server (`dispatcher/src/{worktrees,landing,event-bus,approval-tickets,server}.ts`)
1. Create worktree: `git worktree add -b wave-2-worktrees-server worktrees/wave-2 main`
2. Dispatch **Programmer (`agency-programmer`)** to implement `worktrees.ts`, `landing.ts`, `event-bus.ts`, `approval-tickets.ts`, `server.ts`, `dispatcher/test/worktrees-landing.test.ts`, `dispatcher/test/server-api.test.ts`, `scripts/accept/wave-2.sh`, and `docs/reports/wave-2.md`.
3. Dispatch **QA (`agency-qa`)** and **Drift Guard (`agency-drift-guard`)** to verify and red-proof Wave 2 (`VERDICT: PASS`).
4. Merge `wave-2-worktrees-server` into `main` and remove worktree.
### Wave 3: Unified High-Fidelity WebUI (`web/src/*` — Kanban, Director Chat, Worker Drill-Down, Split Diff & Ed25519 Approval Drawer)
1. Create worktree: `git worktree add -b wave-3-webui worktrees/wave-3 main`
2. Dispatch **Programmer (`agency-programmer`)** to implement `web/src/state/fleet-store.ts`, `web/src/security/approval-client.ts`, `KanbanBoard.tsx`, `DirectorChat.tsx`, `WorkerDrillDown.tsx`, `SplitDiffViewer.tsx`, `ApprovalDrawer.tsx`, `app.tsx`, `web/test/approval-invariant.test.ts`, `web/test/ui-components.test.ts`, `scripts/accept/wave-3.sh`, `README.md`, and `docs/reports/wave-3.md`.
3. Dispatch **QA (`agency-qa`)** and **Drift Guard (`agency-drift-guard`)** to verify and red-proof Wave 3 (`VERDICT: PASS`).
4. Merge `wave-3-webui` into `main`, remove worktree, and verify `make check && make accept` exits 0 on `main`.
+23
View File
@@ -0,0 +1,23 @@
.PHONY: check accept accept-1 accept-2 accept-3 install typecheck test
install:
bun install
typecheck:
bun run check
test:
bun test
check: install typecheck test
accept-1:
bash scripts/accept/wave-1.sh
accept-2:
bash scripts/accept/wave-2.sh
accept-3:
bash scripts/accept/wave-3.sh
accept: accept-1 accept-2 accept-3
+288
View File
@@ -0,0 +1,288 @@
# SPEC.md — Complete Build Specification for `agency-control-plane`
> This file contains VERBATIM copies of `MASTER_BUILD_PROMPT.md` sections 0, 1, 2.1,
> 2.2, 2.3, followed by exact module contracts the Programmer must implement.
> Subagents: read this file in full from disk before doing any work.
---
## 0. CRITICAL MUSE 1.3 HARNESS SUPPLEMENT (READ FIRST — NON-NEGOTIABLE)
You are running on `opencode-go/muse-spark-1.3-contributor`. Obey these six rules on every turn:
1. **ZERO PROMPT SUMMARIZATION WHEN CALLING SUBAGENTS:**
- In Wave 0, write this entire specification into `docs/prompts/SPEC.md` and `AGENTS.md`. Every subagent call MUST instruct the subagent to `read` `AGENTS.md` and `docs/prompts/SPEC.md` directly from disk.
2. **EXPLICIT SUBAGENT ROLES:**
- Always pass `"agent": "agency-programmer"`, `"agent": "agency-qa"`, or `"agent": "agency-drift-guard"` when calling `subagent`.
3. **VANILLA `read` / `edit` / `write` HYGIENE:**
- Use native `read`, `write`, `edit`, `glob`, and `grep`—never `cat` or `sed`. Never include `read` line numbers (`12: `) in `edit` `oldString`/`newString`. Fix `<diagnostics>` immediately.
4. **STRICT SCOPE & REFUSAL BOUNDARY FOR `agency-control-plane`:**
- This repository is **ONLY** the Fleet Dispatcher Daemon (`dispatcher/`) and the unified High-Fidelity WebUI (`web/`) featuring your 5 locked-in UX pillars:
1. **Visual Kanban Board** mapped to isolated Jujutsu (`jj`) / Git worktrees,
2. **Persistent Top-Level Director Chat Sidebar** to plan and steer missions,
3. **Interactive DAG & Worker Drill-Down Inspector** (live SSE tool stream, stage gates, token/cost telemetry),
4. **Side-by-Side / Unified Git Diff Viewer** with file tree navigation, and
5. **1-Click / Passphrase Cryptographic Ed25519 Human Approval Drawer** (enforcing Invariant I1: passphrase kept strictly in local closure, never in reactive state stores).
- **FORBIDDEN IN THIS REPO:** Do NOT copy `SwarmOS` (`src/omni`, `swarmos-plugin.ts`) into this repo! Do NOT implement AST parsers or domain MCP tools here.
5. **CRITICAL BUG FIXES ENFORCED IN THIS REPO (FROM REPORTS `01b`, `04a`, `04b`):**
- **Fix 1 (`limits.ts` 429 Global Freeze Bug):** Never pause all missions for 60s in an infinite loop on HTTP 429 (`gousagelimiterror`). Parse `retry-after-ms` / `Retry-After` (clamped `1000ms..300000ms`) and immediately return `{ action: "route-fallback", targetTier: "tier-2-commercial" | "tier-3-local" }` via Maxim Bifrost!
- **Fix 2 (OpenCode v2 SQLite Single-Writer & SSE Overflow):** Set `PRAGMA busy_timeout = 15000; PRAGMA journal_mode = WAL;` on the dispatcher SQLite store, isolate each worker's `.opencode` state per worktree, and multiplex worker events over a ring-buffered SSE / NATS JetStream bus.
6. **BINDING TRIAD GATE:**
- Every wave requires `agency-qa` Red-Proof Sabotage (`docs/evidence/wave-<N>/red-proofs.log`) and `agency-drift-guard` `VERDICT: PASS` (`docs/reports/wave-<N>-verification.md`) before merging to `main`.
---
## 1. TARGET REPOSITORY ARCHITECTURE & FILE TREE
```text
/home/john/Projects/Heretek-AI-Rebuild/agency-control-plane/
├── AGENTS.md
├── Makefile
├── package.json # Bun workspace root: ["dispatcher", "web"]
├── tsconfig.json
├── docs/
│ ├── prompts/
│ │ ├── SPEC.md
│ │ └── verify-stage.md
│ ├── reports/
│ └── evidence/
├── scripts/
│ └── accept/
│ ├── wave-1.sh
│ ├── wave-2.sh
│ └── wave-3.sh
├── dispatcher/ # Wave 1 & 2: Fleet Dispatcher Daemon & HTTP/SSE Control API
│ ├── package.json # @heretek-ai/agency-dispatcher
│ ├── src/
│ │ ├── model.ts # TaskStatuses, Task, Mission, ApprovalTicket schemas
│ │ ├── wildcard.ts # Glob path intersection (`mayOverlap(pathA, pathB)`)
│ │ ├── scheduler.ts # Pure 186-line DAG scheduler (`all_success`, `one_success`, `pathsOverlap` lock, cascade cancel)
│ │ ├── limits.ts # Upgraded 3-tier failover & Retry-After classifier (Report 01b §9.1)
│ │ ├── audit.ts # O_APPEND + fsync SHA-256 hash-chained audit ledger with torn-tail check
│ │ ├── worktrees.ts # 2-phase crash-safe worktree coordinator supporting Jujutsu (`jj workspace add`) + git worktree fallback + `refs/fleet-salvage/<id>`
│ │ ├── landing.ts # Transactional landing in throwaway worktree (pre-verify -> merge -> post-verify -> rollback on conflict/failure)
│ │ ├── event-bus.ts # Ring-buffered SSE + NATS JetStream / KV Blackboard bridge
│ │ ├── approval-tickets.ts # Single-use preview tickets bound to subjectHash + loopback Ed25519 verification
│ │ ├── server.ts # Bun HTTP + SSE API serving Kanban state, Director chat, Worker drill-down, Diffs, and Approvals
│ │ └── index.ts
│ └── test/
│ ├── scheduler.test.ts # Tests all_success, one_success, cascade cancel, and pathsOverlap serialization
│ ├── limits.test.ts # Proves 429 gousagelimiterror triggers route-fallback instead of global freeze!
│ ├── worktrees-landing.test.ts # Tests 2-phase allocation, salvage ref anchoring, and transactional landing rollback
│ └── server-api.test.ts # Tests REST + SSE endpoints, approval tickets, and CSRF/loopback guards
└── web/ # Wave 3: Unified High-Fidelity WebUI (5 UX Pillars)
├── package.json # @heretek-ai/agency-web
├── index.html
├── src/
│ ├── state/
│ │ └── fleet-store.ts # Typed state store for Kanban columns, DAG nodes, active drill-down worker, and diffs
│ ├── security/
│ │ └── approval-client.ts # Ported from iumbtems approvals.ts (Invariant I1: passphrase held only in closure, never stored in state)
│ ├── components/
│ │ ├── KanbanBoard.tsx # Pillar 1: 5-column Worktree Kanban (Backlog, Ready, In Progress, Waiting Approval/QA, Done)
│ │ ├── DirectorChat.tsx # Pillar 2: Persistent Top-Level Director Chat sidebar with mission decomposition cards
│ │ ├── WorkerDrillDown.tsx # Pillar 3: Live worker subagent inspector (SSE tool stream, stage gates, token/tier badge, DAG view)
│ │ ├── SplitDiffViewer.tsx # Pillar 4: Side-by-side & unified Git diff viewer with file tree & line stats
│ │ └── ApprovalDrawer.tsx # Pillar 5: 1-click / passphrase Ed25519 approval & rejection gate
│ └── app.tsx # High-craft dark-mode layout satisfying WCAG 2.2 AA contrast & 24x24px target sizes
└── test/
├── approval-invariant.test.ts # Asserts passphrase never enters fleet-store or DOM attributes (Invariant I1)
└── ui-components.test.ts # Tests rendering and state transitions across all 5 UX pillars
```
---
## 2. EXACT MODULE SPECIFICATIONS
### 2.1 Wave 1 Specification: Pure Scheduler, Path-Overlap Arbiter, 3-Tier Failover Limits & Audit Chain
1. **`dispatcher/src/wildcard.ts` & `dispatcher/src/scheduler.ts`:**
- Implement the exact salvaged `mayOverlap(patternA: string, patternB: string): boolean` and `schedule(tasks: Task[], runningIds: Iterable<string>, caps: ScheduleCaps): ScheduleResult` from `/home/john/Projects/The-Agency/dispatcher/src/scheduler.ts`:
- Under `all_success`, a task is ready when all `deps` are `done`; if any dep is `failed` or `cancelled`, it cascades to `cancelled` via fixpoint closure.
- Under `one_success`, a task is ready when at least one dep is `done`; it cancels only when all deps are terminal with zero `done`.
- Two `expert-run` tasks whose `input.paths` overlap (`mayOverlap(pa, pb) === true`) NEVER start simultaneously (`blockedByOverlap` / `collidesSelected`).
- `waitingHuman` tasks never start and never force downstream cancellation.
2. **`dispatcher/src/limits.ts` (Exact Patch from Report `01b` §9.1):**
- `extractRetryAfterMs(headers?: Record<string, string>, defaultMs?: number): number` — Parses `retry-after-ms` (ms) and `retry-after` (seconds or HTTP-Date), clamped strictly to `[1000, 300000]`.
- `classifyProviderFailure(failure: ProviderFailure, consecutiveFailures: number, currentTier: "tier-1-opencode" | "tier-2-commercial" | "tier-3-local"): ProviderDisposition`:
- When `failure.status === 402` or code matches `gousagelimiterror` / `freeusagelimiterror` / `insufficient_quota` or 429 body matches `/usage limit|quota exceeded|insufficient quota|credit balance/i`:
- From `tier-1-opencode` → returns `{ action: "route-fallback", targetTier: "tier-2-commercial" }` (ZERO mission pause!).
- From `tier-2-commercial` → returns `{ action: "route-fallback", targetTier: "tier-3-local" }`.
- From `tier-3-local` → returns `{ action: "pause-missions", reason: "...", resumeAfterMs }`.
- When `failure.status === 429` (transient rate spike without quota exhaustion) → returns `{ action: "retry-delay", delayMs: extractRetryAfterMs(failure.headers) }`.
3. **`dispatcher/src/audit.ts`:**
- Hash-chained JSONL audit log (`seq`, `timestamp`, `event`, `data`, `prevHash`, `hash`). Redacts secrets (`sk-*`, `Bearer *`, `OPENCODE_API_KEY`) before hashing. Detects any byte modification or equal-length rewrite via `checkAnchor`.
---
### 2.2 Wave 2 Specification: Crash-Safe Worktrees (`jj` / `git`), Transactional Landing & HTTP/SSE Control Server
1. **`dispatcher/src/worktrees.ts` & `dispatcher/src/landing.ts`:**
- Two-phase crash-safe worktree allocation (`allocating` recorded in SQLite before worktree creation, flipped to `allocated` after; `cleanupUnconfirmed` cleans orphans).
- Supports Jujutsu colocated workspaces (`jj workspace add`) when `.jj` is present, with seamless fallback to `git worktree add -b fleet/<taskId>`.
- `salvageWorktree` anchors `refs/fleet-salvage/<taskId>` before deleting abandoned task branches so agent work is never lost.
- `landTask` runs pre-checks in the task worktree, performs `--no-ff` merge onto `fleet/integration/<missionId>` inside a throwaway landing worktree, runs post-merge verification, and atomically rolls back to pre-merge SHA if verification or merge fails.
2. **`dispatcher/src/server.ts` & `dispatcher/src/approval-tickets.ts`:**
- Serves HTTP API + `/api/events` SSE stream on loopback (`127.0.0.1`):
- `GET /api/board` — Returns Kanban columns, tasks, worktree statuses, active model tier (`tier-1-opencode` / `tier-2-commercial` / `tier-3-local`), and DAG edges.
- `POST /api/director/message` — Appends steering message to Top-Level Director session and decomposes new task cards onto the board.
- `GET /api/tasks/:id/drilldown` — Returns worker session metadata, stage gates, live tool log buffer, and git diff hunks (`[{ file, status, additions, deletions, patch }]`).
- `POST /api/approvals/:taskId/ticket` — Mints a single-use, 60-second TTL preview ticket bound to `{ taskId, subjectHash }`.
- `POST /api/approvals/:taskId/sign` — Verifies the single-use ticket, verifies the Ed25519 signature / passphrase in memory, transitions task from `waitingHuman` to `ready`/`done`, and appends to `audit.ts`.
---
### 2.3 Wave 3 Specification: Unified High-Fidelity WebUI (`web/src/*`)
1. **`web/src/security/approval-client.ts` (Salvaged from `iumbtems/packages/web/src/approvals.ts`):**
- **Strict Security Invariant I1:** The human passphrase entered in the approval dialog is held ONLY in a lexical function closure variable during the `fetch("/api/approvals/:id/sign")` call, is zeroed out immediately after, and is **never** written to `fleet-store`, `localStorage`, `sessionStorage`, or DOM data attributes.
2. **Five High-Fidelity UX Components (`web/src/components/*`):**
- `KanbanBoard.tsx`: Renders the 5 Kanban columns (`Backlog`, `Ready`, `In Progress`, `Waiting Approval / QA`, `Done`) with worktree branch badges (`fleet/task-101`), owned-path chips, and model-tier indicators.
- `DirectorChat.tsx`: Persistent right/left dockable chat with the Top-Level Director Agent (`director`), supporting `/ship <spec>`, mission charter preview, and 1-click task DAG injection onto the Kanban board.
- `WorkerDrillDown.tsx`: Clicking any Kanban card opens the drill-down inspector showing the worker's live SSE tool-call timeline, active Docker Compose profile badge (`core` / `appsec` / `android` / `game` / `research`), and DAG dependency graph.
- `SplitDiffViewer.tsx`: Renders side-by-side and unified Git diffs for the selected worktree with file-tree navigation, addition/deletion counters, and syntax-highlighted hunk lines.
- `ApprovalDrawer.tsx`: Displays the exact `subjectHash`, diff summary, QA verdicts (`qa-functional` + `qa-adversarial`), and the 1-click / passphrase Ed25519 approval gate.
---
## 3. EXACT MODULE CONTRACTS (BINDING — PROGRAMMER MUST SATISFY)
### 3.1 `dispatcher/src/wildcard.ts` — `mayOverlap`
```ts
export function match(input: string, pattern: string): boolean;
export function literalPrefix(pattern: string): string;
export function mayOverlap(patternA: string, patternB: string): boolean;
```
- `match`: OpenCode dialect — `*` matches any chars including `/`, `?` matches one
char. Backslashes normalize to `/`. Trailing ` *` also matches the bare prefix.
- `literalPrefix`: text before the first wildcard (`*` or `?`).
- `mayOverlap`: conservative — true when either literal prefix is a prefix of the
other. Over-serializing is acceptable; colliding runs are not.
### 3.2 `dispatcher/src/scheduler.ts` — `schedule` / `nextStatus`
```ts
export interface ScheduleCaps { fleet: number; perExpert?: (expert: string) => number; }
export interface ScheduleResult { start: string[]; ready: string[]; cancel: string[]; }
export type TaskOutcome = "success" | "failure";
export function schedule(tasks: Task[], runningIds: Iterable<string>, caps: ScheduleCaps): ScheduleResult;
export function nextStatus(task: Task, outcome: TaskOutcome): TaskStatus;
```
- `all_success`: ready iff every dep is `done`. Cancel iff any dep is `failed`/`cancelled`.
Cascade to fixpoint (id-ordered, deterministic).
- `one_success`: ready iff ≥1 dep is `done`. Cancel iff deps non-empty and every dep is
terminal with zero `done`.
- Overlap lock: two `expert-run` tasks with `pathsOverlap` (any `mayOverlap(pa,pb)`
over `input.paths`) NEVER start together — checked against both `running`
(`blockedByOverlap`) and already-selected (`collidesSelected`).
- `waitingHuman` tasks never start and never force downstream cancellation (they are
neither `done` for readiness nor `failed`/`cancelled` for cascade).
- Starts ordered by id, bounded by fleet cap and per-expert cap; overflow → `ready`.
- `nextStatus`: `success` → `done`; `fail` strategy → `failed`; `replan` → `waitingHuman`
(`waiting-human`); `retry` → `pending` while `attempts <= maxAttempts`, else `failed`.
### 3.3 `dispatcher/src/limits.ts` — Retry-After + 3-Tier Failover
```ts
export type ModelTier = "tier-1-opencode" | "tier-2-commercial" | "tier-3-local";
export type ProviderDisposition =
| { action: "retry-delay"; delayMs: number }
| { action: "route-fallback"; targetTier: ModelTier }
| { action: "pause-missions"; reason: string; resumeAfterMs: number };
export function extractRetryAfterMs(headers?: Record<string, string>, defaultMs?: number): number;
export function classifyProviderFailure(
failure: ProviderFailure,
consecutiveFailures: number,
currentTier: ModelTier,
): ProviderDisposition;
```
- `extractRetryAfterMs`: `retry-after-ms` (raw ms) takes precedence; else `retry-after`
as delta-seconds or HTTP-Date; fallback `defaultMs ?? 5000`; clamp `[1000, 300000]`.
Header lookup is case-insensitive. Invalid/negative → default.
- `classifyProviderFailure`:
- Quota shape (`status === 402`, or code in `gousagelimiterror` / `freeusagelimiterror` /
`insufficient_quota`, or 429 body matching
`/usage limit|quota exceeded|insufficient quota|credit balance/i`):
tier-1 → `route-fallback` tier-2; tier-2 → `route-fallback` tier-3;
tier-3 → `pause-missions` with `resumeAfterMs = extractRetryAfterMs(headers)`.
- Plain 429 (no quota shape) → `retry-delay` with `delayMs = extractRetryAfterMs(headers)`.
- `ProviderFailure` shape: `{ status?: number; code?: string; message?: string; headers?: Record<string,string> }`.
### 3.4 `dispatcher/src/audit.ts` — Hash-Chained JSONL Ledger
```ts
export interface AuditRecord { seq: number; timestamp: string; event: string; data: unknown; prevHash: string; hash: string; }
export function appendAudit(logPath: string, event: string, data: unknown): AuditRecord;
export function checkAnchor(logPath: string): { ok: boolean; records: number; reason?: string };
```
- Append-only (`O_APPEND`) + `fsync` per record. `hash = SHA-256(prevHash + canonical(record sans hash))`.
- Redact before hashing: `sk-*`, `Bearer *`, `OPENCODE_API_KEY` values → `[REDACTED]`.
- `checkAnchor` replays the chain; any byte modification or equal-length rewrite fails
(`ok: false` with `reason`).
### 3.5 `dispatcher/src/worktrees.ts` — 2-Phase Allocation
- States `allocating` → `allocated`, recorded in SQLite (WAL + `busy_timeout=15000`)
BEFORE/AFTER the filesystem worktree creation.
- `cleanupUnconfirmed()`: removes `allocating` rows older than the confirmation window
and their orphaned directories.
- `jj workspace add` when `.jj` present; fallback `git worktree add -b fleet/<taskId>`.
- `.opencode` state isolated per worktree (never shared).
- `salvageWorktree(taskId)`: anchor `refs/fleet-salvage/<taskId>` at the abandoned
branch HEAD before deletion.
### 3.6 `dispatcher/src/landing.ts` — Transactional Landing
- `landTask(taskId, missionId)`: pre-checks in task worktree → `--no-ff` merge onto
`fleet/integration/<missionId>` inside a throwaway landing worktree → post-merge
verification → on conflict/failure, atomic rollback to pre-merge SHA; throwaway
worktree always removed.
### 3.7 `dispatcher/src/event-bus.ts` — Ring-Buffered SSE / NATS
- Bounded ring buffer (default 1000 events) with monotonic sequence IDs; overflow
drops oldest and emits a gap marker.
- `GET /api/events` SSE: `Last-Event-ID` replay, keepalive comments, loopback-only.
- NATS JetStream / KV blackboard bridge when `NATS_URL` set; otherwise in-process bus.
Same ordering + gap semantics either way.
### 3.8 `dispatcher/src/approval-tickets.ts` — Single-Use Tickets
- `mintTicket(taskId, subjectHash)`: random 256-bit ticket id, single-use, 60s TTL,
bound to `{ taskId, subjectHash }`.
- `redeemTicket(ticketId, taskId, subjectHash)`: fail-closed — unknown, expired,
reused, or mismatched binding → reject with descriptive error. Loopback Ed25519
verification of the signature over `subjectHash`.
### 3.9 `dispatcher/src/server.ts` — Bun HTTP + SSE
- Serves on `127.0.0.1` only (loopback guard; non-loopback `Host` → 403).
- Routes: `GET /api/board`, `POST /api/director/message`,
`GET /api/tasks/:id/drilldown`, `POST /api/approvals/:taskId/ticket`,
`POST /api/approvals/:taskId/sign`, `GET /api/events` (SSE).
- Every state-changing route appends to the audit ledger; every error is fail-closed
with a descriptive JSON error body.
### 3.10 `web/src/state/fleet-store.ts` — Typed Store
- Columns `Backlog | Ready | In Progress | Waiting Approval / QA | Done`, DAG nodes,
active drill-down worker id, diff cache, tier badge. No secrets, no passphrases —
Invariant I1 enforced by type + test.
### 3.11 `web/src/security/approval-client.ts` — Invariant I1
- Passphrase lives ONLY in a lexical closure variable during
`fetch("/api/approvals/:id/sign")`; zeroed (`fill(0)` / reassigned to `""`)
immediately after the call settles.
- NEVER written to `fleet-store`, `localStorage`, `sessionStorage`, or DOM attributes.
- `approval-invariant.test.ts` asserts this statically (source grep) and at runtime.
### 3.12 `web/src/app.tsx` — Dark-Mode WCAG 2.2 AA
- Dark-mode layout, contrast ≥ 4.5:1 body text, all interactive targets ≥ 24×24px,
visible focus rings, keyboard-navigable board/chat/drawer.
+77
View File
@@ -0,0 +1,77 @@
# verify-stage.md — Wave Verification Checklist
Every wave MUST satisfy ALL items below. QA and Drift Guard enforce this file.
No merge to `main` without every box checked and `VERDICT: PASS` recorded.
## 1. Green Build
- [ ] `make check` exits 0 (`bun install` + `bun run check` + `bun test`, zero failures).
- [ ] `bun run check` (typecheck + lint) reports zero warnings, zero errors.
- [ ] All `<diagnostics>` fixed immediately; none outstanding.
## 2. Acceptance Scripts
- [ ] Wave accept script exits 0:
- Wave 1 → `bash scripts/accept/wave-1.sh`
- Wave 2 → `bash scripts/accept/wave-2.sh`
- Wave 3 → `bash scripts/accept/wave-3.sh`
- [ ] Full `make accept` (all three waves) exits 0 before final Wave 3 merge.
## 3. QA Red-Proof Sabotage (`docs/evidence/wave-<N>/red-proofs.log`)
QA MUST break the code, prove the tests catch it, then restore cleanly.
Each sabotage entry records: what was broken, failing test output, restore command.
- [ ] Wave 1:
- [ ] Revert `limits.ts` so 429/`gousagelimiterror` on `tier-1-opencode` returns
`pause-missions` → `limits.test.ts` FAILS → restore with `git checkout --`.
- [ ] Break `pathsOverlap` in `scheduler.ts` (e.g. `return false`) →
`scheduler.test.ts` FAILS → restore cleanly.
- [ ] Wave 2:
- [ ] Break 2-phase allocation (skip `allocating` record) or salvage-ref anchoring →
`worktrees-landing.test.ts` FAILS → restore cleanly.
- [ ] Break ticket single-use/TTL or loopback guard → `server-api.test.ts` FAILS →
restore cleanly.
- [ ] Wave 3:
- [ ] Leak passphrase into `fleet-store`/`localStorage`/DOM → `approval-invariant.test.ts`
FAILS → restore cleanly.
- [ ] Log restored-cleanly proof (`git status --short` clean for sabotaged paths) in
`docs/evidence/wave-<N>/red-proofs.log`.
## 4. Drift-Guard Gate (Binding)
- [ ] `VERDICT: PASS` present in `docs/reports/wave-<N>-verification.md`.
- [ ] `VERDICT: FAIL` (or missing verdict) BLOCKS the merge unconditionally.
- [ ] Max 3 rework cycles per wave (Programmer → QA → Drift Guard = 1 cycle).
After 3 failed cycles, halt and escalate with evidence.
## 5. Zero-Stub Grep (must print nothing)
```sh
grep -rn --include='*.ts' --include='*.tsx' --include='*.sh' -E 'TODO|FIXME|XXX|HACK|return true;|echo "passed"|\|\| true|catch *\(.*\) *\{\}' dispatcher web scripts || true
```
- [ ] Grep output empty (no TODOs, no dummy passes, no swallowed errors, no `set +e`).
- [ ] Every interface/struct/function/route/test contains exact logic.
## 6. Scope Refusal Check
- [ ] No `SwarmOS` code (`src/omni`, `swarmos-plugin.ts`) present.
- [ ] No AST parsers or domain MCP tools present.
- [ ] All new files live under `dispatcher/`, `web/`, `docs/`, `scripts/`, or repo-root
configs (`package.json`, `tsconfig.json`, `Makefile`, `AGENTS.md`).
- [ ] Invariant I1 holds: `grep -rn 'passphrase' web/src/state web/src/components`
shows no storage of the passphrase outside `web/src/security/approval-client.ts`
closure usage.
## 7. Wave Deliverables Present
- [ ] Wave 1: `dispatcher/src/{model,wildcard,scheduler,limits,audit}.ts`,
`dispatcher/test/{scheduler.test.ts,limits.test.ts}`, `scripts/accept/wave-1.sh`,
`docs/reports/wave-1.md`.
- [ ] Wave 2: `dispatcher/src/{worktrees,landing,event-bus,approval-tickets,server}.ts`,
`dispatcher/test/{worktrees-landing.test.ts,server-api.test.ts}`,
`scripts/accept/wave-2.sh`, `docs/reports/wave-2.md`.
- [ ] Wave 3: `web/src/state/fleet-store.ts`, `web/src/security/approval-client.ts`,
all 5 components + `app.tsx`, `web/test/{approval-invariant.test.ts,ui-components.test.ts}`,
`scripts/accept/wave-3.sh`, `docs/reports/wave-3.md`.
+11
View File
@@ -0,0 +1,11 @@
{
"name": "@heretek-ai/agency-control-plane",
"private": true,
"version": "0.0.0",
"workspaces": ["dispatcher", "web"],
"scripts": {
"check": "bun run --filter './dispatcher' check && bun run --filter './web' check",
"accept": "bash scripts/accept/wave-1.sh && bash scripts/accept/wave-2.sh && bash scripts/accept/wave-3.sh",
"test": "bun test"
}
}
+26
View File
@@ -0,0 +1,26 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "Bundler",
"lib": ["ESNext", "DOM", "DOM.Iterable"],
"jsx": "react-jsx",
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"noImplicitOverride": true,
"noFallthroughCasesInSwitch": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"forceConsistentCasingInFileNames": true,
"skipLibCheck": true,
"resolveJsonModule": true,
"isolatedModules": true,
"declaration": true,
"sourceMap": true,
"types": ["bun"]
},
"include": ["dispatcher/src", "dispatcher/test", "web/src", "web/test"],
"exclude": ["node_modules", "worktrees"]
}