# Queue as data: the process stops being prose (#1508) Owner of the plan: Jason. Coordinator: the Claude session. Builder: darkwing. Tracking: #1508. Rows 9 to 13 in `docs/plans/QUEUE.md`, state `required`. Jason, 2026-09-13: "It all starts out great, then goes off the rails. I want this iron-clad." This page is the plan, the decomposition, and the briefs. It is not a design discussion. Each piece below has one owner, one gate, and one commit-sized scope. Nothing on this page is optional and nothing moves to DEFERRED.md; a piece that cannot ship gets a Blocked line in QUEUE.md with the reason. ## Problem, in one paragraph The entry point for "what is next" was prose in four files (AGENTS.md, CURRENT.md, ROADMAP.md, CONDUCTOR.md), edited by hand by any seat in one shared checkout. State lived in sentences, so it drifted: three "Next action" paragraphs, edits swept into other seats' commits, 160 review files invented as a side channel, and Jason unable to find the next step. The fix is the same fix already applied to decisions (`brain.py` is the only writer) and to seat identity (`registration.json` is the only record): the queue becomes a record with one writer and checks that fail loudly. ## Ordering and preconditions Starts after piece 5 (darkwing on point, QUEUE row 6) reaches Gate F or is blocked, whichever comes first. Runs before the fleet migration (row 8). Pieces A to E below ship in order; each is one issue comment on #1508 and one QUEUE row move. Nothing on this page waits for the WebUI, the comms MVP, or the orchestrator to be finished. ## Piece A: queue record and `mosaic queue` (QUEUE row 9) **Owner:** darkwing. **Reviewer:** filbert. **Package:** `packages/queue`, same shape as `packages/seat`. **Files owned:** `packages/queue/**`, `scripts/mosaic` (subcommand dispatch only), `docs/plans/QUEUE.md`. **What ships** - One file, `/docs/plans/queue.json`, committed. It is the queue. Array of rows: `id` (integer, never reused), `piece` (one line), `owner` (seat name, `jason`, `coordinator`, or `unassigned`), `issue` (integer or null), `state` (`queued` | `briefed` | `in-progress` | `in-review` | `waiting-on-jason` | `done` | `blocked` | `parked` | `required`), `gate` (one sentence), `brief` (repo path plus optional section), `updatedAt`, `updatedBy` (from `MOSAIC_AGENT_NAME`, or `--by`, else refused), `note` (one line, optional), `blockedReason` (required when state is blocked). - `scripts/mosaic queue list [--owner SEAT] [--json]`, `queue next [SEAT]` (prints the one row that seat should work on, or "nothing" with exit 0), `queue add --piece --owner --gate --brief [--issue]`, `queue move ID STATE [--reason]`, `queue assign ID SEAT`, `queue note ID TEXT`. All writes through the CLI; the CLI validates the transitions below and refuses anything else with one line on stderr and exit 2. - Transitions: queued→briefed→in-progress→in-review→waiting-on-jason→done; any→blocked (reason required) and blocked→previous; required→in-progress only; parked only from queued or briefed and only with `--by jason`; done and parked rows never change. A seat may move only rows it owns; `coordinator` and `jason` may move any row. - `docs/plans/QUEUE.md` becomes a rendered view: `mosaic queue render` writes the table between two marker comments and the CLI runs it after every write. The header text above the markers stays hand-written. - Tests: fixtures for every transition and every refusal; render is byte-stable; `queue next` picks the first row by id, not by file order. **Out of scope:** Gitea sync, board display, the ledger, hooks, any change under `packages/seat`, `packages/control-board`, `packages/ledger`. **Gate G (Jason):** a seat launched fresh, with no prior session, is told only "run `mosaic queue next` and do it". It finds its piece, opens the brief the row names, and starts, with zero further human messages. Jason watches from the board. Pass or fail, one sentence on #1508. ## Piece B: seats read the queue, not CURRENT.md (QUEUE row 10) **Owner:** coordinator. **Done 2026-09-13** for the cadence and pointer lines in AGENTS.md (cbf24b01). Remaining under this piece, after Piece A: the cadence line says `mosaic queue next` instead of naming the file, and `agents/*/AGENTS.md` or context files that still point at CURRENT.md for "what now" are changed to the command. One commit. No gate; verified by Gate G. ## Piece C: brief template (QUEUE row 11) **Owner:** darkwing, as part of Piece A's review round. **File:** `docs/plans/BRIEF-TEMPLATE.md`, one page, these headings only: Problem, Owner and reviewer, Files owned, What ships, Out of scope, Gate (who says yes, on what evidence). `mosaic queue add` refuses a `--brief` path that does not exist. The pieces on this page are written in that shape and are the first three uses. Passing: Piece A's brief and one later brief are accepted by Jason without a follow-up question about scope. ## Piece D: reviews through a channel, not files (QUEUE row 12) **Owner:** darkwing. **After Piece A.** Review requests and receipts go as issue comments on the piece's issue (`scripts/gitea-api.sh` exists) or as board messages once the comms MVP has a record; no new files under `docs/plans/reviews/`. The existing 160 files stay as history. `mosaic queue move ID in-review` posts the request comment itself from the row's data, so the seat does not write one. Passing: one full review round on a later piece with zero new files under `docs/plans/reviews/`. ## Piece E: ledger checks the queue (QUEUE row 13) **Owner:** darkwing. **Reviewer:** filbert. **After Piece A.** `packages/ ledger` gains a "queue" section: every open Gitea issue with a `#N` in a queue row has that row not done; every `in-progress` or `in-review` row has an owner seat with a live registration; every `done` row's issue is closed; every `required` row older than 14 days is listed by name. Violations print above the weekly table and in `--json`. First run Monday 2026-09-21 with the weekly number. Passing: zero violations, or every violation has a row moved within the day. ## What "iron-clad" means here, so it can be checked 1. The queue has one writer (`mosaic queue`) and refuses bad transitions. 2. Seats are told a command, not a file, so nothing to reinterpret. 3. Every row names an owner, a gate, and a brief that exists. 4. The ledger reports drift weekly, next to the number Jason already reads. 5. `required` rows cannot be parked; only Jason moves them. If any of the five stops being true, that is a finding on #1508, reopened. ## Log - 2026-09-13 — page written by the coordinator after Jason's "iron-clad" ruling; #1508 opened; QUEUE rows 9 to 13 point here.