# Queue A2 (#1508), candidate for review Darkwing, 2026-09-27. A2 is migration, render and dispatch (lead decision 20), plus every item Sage carried forward from A1's review (decision 26: N5, N8, N10, N11, N12, P1, P2, P3). Base is HEAD c9539baa; A1 is 34a72af9. Filbert reviews. Sage commits, then installs the hook and runs genesis. Nothing is committed, staged or pushed. ## Files `build.patch` (sha256 `dc0be7ce74aaaaaf43deebe68af439d1b2b80c77aad6538420e929de35e2f21f`) changes 13 files and adds 8. `build-manifest.sha256` (sha256 `782bcb62e555659333a35888d418d986da63bdf522f38cafa447db7ddd0074b7`) pins all 21 after the patch. In a fresh clone at c9539baa the patch applies and the result matches the manifest 21/21, file modes included. `git apply` warns about one blank line at the end of `fixtures/genesis-render.md`. It belongs there: the render ends with one, and the golden has to match byte for byte. - `packages/queue/src/`: `queue.mjs` (N8, N10, N11, P2), `store.mjs` (N12, N11's shared check, P1's helper, P3's caller), `lock.mjs` (P1, P3), `io.mjs` (N5), `cli.mjs` (usage comment). - `scripts/mosaic`: `queue` execs `packages/queue/src/cli.mjs` with the remaining arguments. Every other call reaches the seat CLI as before. - `scripts/test-queue.sh`: syntax checks for `scripts/mosaic` and the new fixture; `scripts/mosaic queue help` always; after genesis, `verify` and `render --check` on the live queue through `scripts/mosaic queue`. - `packages/queue/README.md`: the dispatch, N5, N7, N8, N10, N12, P2, the map and `map-check.mjs`, two new test files. - Tests: 107 in A1, 122 now. New: `dispatch.test.mjs` (3), `migration.test.mjs` (3), and 9 more in data, lock, store and write. Fixtures: `mosaic-pre-a2.sh` (the script before A2), `queue-marked.md` (QUEUE.md with the markers), `genesis-render.md` (the golden render). - `agents/darkwing/work/queue-migration-map.md`: the genesis input. - `agents/darkwing/work/queue-a2/map-check.mjs`: the drift check. - `agents/darkwing/work/queue-a2/carry-forward.md`: the item list Sage confirmed (773dbd75), with the r1 section added after it. Outside the patch, for Sage to apply (condition 2 keeps them out of A2): - `queue-md.patch` (sha256 `2ca8f689e1dcda5bb30e9af4c3f867242d5239d72e13001369c7b7aca7956402`): the two markers, a header that points seats at `scripts/mosaic queue next`, and a line freezing the old log of table changes. It applies to HEAD. - `tools-md.patch` (sha256 `c53aea1f6e871e17d04335ef60250adcdbe64f6466d9ff641042c00094d1c3a8`): a "Work queue" section in `docs/TOOLS.md`. It applies to HEAD. Optional; the README already has the detail. ## The five conditions 1. **Nothing ran against the canonical `.git`.** Tests use scratch repositories under the temp directory. The dry run below used `/tmp/qa2-dry`. Checked after all runs: no `mosaic-queue*` file in `.git/`, `.git/hooks/pre-commit` absent, `core.hooksPath` unset in every scope, nothing staged. 2. **No QUEUE.md, AGENTS.md or TOOLS.md edits.** The two proposals are patch files. `git status` shows none of the three modified. 3. **`scripts/test-queue.sh` is green at a HEAD with no `queue.json`:** 24/24, the live checks skipped. After the dry-run genesis it ran 26/26, with `verify` and `render --check` on the live queue. 4. **H is recorded before the canary.** `queue-commit.sh` is unchanged from A1, and its test "F1: H is recorded before the canary, so HEAD moving during the canary makes update-ref fail" passes. 5. **The fault layer is reachable only from tests.** N5 narrows this further: tmpfs was the one fault-free path open to the CLI, and now only a layer with `allowTmpfs: true` gets it. The test asserts `realIo` has no such key and that `"yes"` doesn't count. ## Carried-forward items | Item | Change | Test | |---|---|---| | N8 | `piece`, `gate`, `note`, move `reason`, brief anchor and `blockedReason` refuse `\` and `<`, at the CLI and in replay | "text the table shows refuses \ and <, everywhere it enters"; "every accepted text renders to nine cells on every row" (GFM's cell rule, no `marked` import) | | N11 | Replay holds every log entry, round op and claim op to `CALLER_OP_RE` and refuses `.outcome`, genesis and `accept-history` included. `LOG_OP_RE` stays for Piece D's derived ids; no verb derives one yet | "replay holds every op id to the caller's rule" | | N10 | `set issues` moves `closes` only if it equalled the old issues; a narrowed `closes` keeps its intersection, and the receipt says `(kept narrowed)` | "set issues keeps a logged narrowing of closes" | | P2 | Each round records `issue`; `review` is `{rounds}` only. A later round keeps the last round's issue | "the row schema refuses a round with a null issue, and the A1 review shape", and A1's R2 tests updated | | P1 | Both gate paths in `acquire` release through `releaseOrWarn`; a failed release is a message naming the lock, not a stack trace | "a release that fails on a gate path is reported, never a stack trace" | | P3 | `lock.unlock` returns `{result, warning}`; nothing splits on newlines | "unlock keeps a multi-line lock record on stdout" | | N5 | tmpfs left `FS_TYPES`; only `allowTmpfs === true` admits it | "tmpfs passes only a test layer that allows it" | | N12 | `--by` still wins; a different non-empty `MOSAIC_AGENT_NAME` adds a stderr warning on success and on refusal. Nothing is logged | "--by that differs from MOSAIC_AGENT_NAME warns on stderr and logs nothing more" | The rest, as `carry-forward.md` records and decision 26 confirmed: N7 is a README line (the leftover `.git/mosaic-queue.lock...tmp` is removed by hand). N15, N16, N13-a and ext2/ext3 are won't-do. ## Migration The map (`queue-migration-map.md`) is built from QUEUE.md blob c8e3d34e, which HEAD has. `node agents/darkwing/work/queue-a2/map-check.mjs` prints `ok: QUEUE.md matches the map (blob c8e3d34e)`. Run it again just before genesis. It exits 1 and lists the rows if QUEUE.md moved. map-check did its job once already. HEAD moved from 8dba3ff7 to c9539baa while I worked, and it reported row 5 (the CHAT-03 brief pinned, lead decision 33). I rebuilt row 5's note, `queue-md.patch`, the marked fixture and the golden render against the new blob. No other row changed. The map's "Choices Sage should check" has eight items. None blocks review. The ones that change what genesis writes: row 7 stays a live row (decision 27), row 10's owner stays `coordinator` with `queue assign 10 sage` recommended as the first op, rows 12 and 13 have Sage as gate owner, row 16 stays `waiting-on-jason` unless #1510 is closed, and rows 9 to 12 close nothing so row 13 closes #1508. Rows 9 to 13 have no `after`: decision 29 took the wait on row 6 off them, and `after: 9 done` on row 10 would wait on a gate that needs row 10. Dry run in a shared clone (`/tmp/qa2-dry`), the candidate plus `queue-md.patch` and `tools-md.patch` committed on top of c9539baa. I reran it from scratch after the rebase: 0. `map-check.mjs`: `ok: QUEUE.md matches the map (blob c8e3d34e)`. 1. `scripts/test-queue.sh`: 24/24, live checks skipped. 2. `scripts/queue-commit.sh --install-hook --by sage`: installed; canary passed. 3. `scripts/mosaic queue genesis --root /tmp/qa2-dry --branch a2-dry --map … --op genesis-2026-09-27 --by sage`: `ok genesis-2026-09-27 rev 0 genesis 30 rows`. 4. `scripts/queue-commit.sh --genesis`: committed. 5. `verify --current`: briefs match HEAD. `render --check`: current. `legacyView` equals the marked body byte for byte, and `mapBlob` is the map's blob. 6. `next`: darkwing resumes 9, sage resumes 7, dewey resumes 5, filbert and rocko have nothing. 7. `scripts/test-queue.sh`: 26/26 with the live checks. 8. `queue assign 10 sage --op assign-10-dry --by sage`: `ok … rev 1 row 10 owner: coordinator→sage`. The dry clone's hook is `.git/hooks/pre-commit`, mode 0755, blob abdf14e7. `core.hooksPath` is unset there too. ## Choices I made - **N8 refuses instead of escaping.** An escape has to be right for every Markdown renderer that reads QUEUE.md; a refusal doesn't. No line in today's table has either character. - **P2 drops `review.issue`.** The last round's issue is the kept one, so a second copy could only disagree. Piece D reads `rounds[].issue`. - **N10's edge.** A `closes` narrowed to nothing stays empty whatever the new issues are. `set closes` with a reason is the way to widen it again. - **N12 warns on a refusal too,** so a seat that mistyped `--by` sees it on the error it was reading anyway. - **`scripts/mosaic queue` uses `exec`,** so exit codes and signals are the queue CLI's own. Any first argument other than exactly `queue` goes to the seat CLI, as before; the dispatch test compares argv, cwd, environment and stdin against the pre-A2 script. ## Mutations Each mutation ran alone in a shared clone of the candidate, with `node --test packages/queue/tests/`. A killed mutation fails at least one test. 46 mutations. Each one below failed at least one test; the number is how many. | Item | Mutations | |---|---| | N8 | backslash 2, lt 2, noteVerb 1, rowNote 2, anchor 1, reason 1 | | N11 | entryShape 1, outcome 2, genesisOnly 1, roundOp 1, claimOp 1 | | N10 | always 2, noIntersect 1, receipt 1 | | P2 | keptFirst 1, noCheck 1, reviewIssue 10 | | P1 | gateErr 1, gatePresent 1, gatePresentMsg 1, withLock 1 | | P3 | joined 2, storeSplit 1 | | N5 | inTypes 1, truthy 1, anyType 1 | | N12 | noWarnOk 1, noWarnErr 1, envWins 1, emptyEnv 1 | | Dispatch | noShift 1, unsetArg 1, prefix 1, noExec 1 | | Golden render | mapGate 1, mapState 1 | | map-check | fixed26 1, owner 1, issues 1, lineDiff 1, parkedPiece 1 | Five of them survived the first pass. Each now has a test that kills it: - N11 roundOp and claimOp (replay stops checking a review round's op or a claim's op). The N11 test now builds a claimed row and a reviewed row and refuses an op of 73 characters and one ending `.outcome` in each. - P1 withLock (the post-op release goes back to the throwing `release`). New write test: the lock's unlink fails with EACCES after the op, and the receipt and a refusal both carry "cannot release the queue lock (EACCES)". - N12 emptyEnv (an empty `MOSAIC_AGENT_NAME` warns). The N12 test now runs with it empty and expects no stderr. - Dispatch noExec (`node` without `exec`, so a successful queue call falls through to the seat CLI). The dispatch test now runs a queue call that exits 0 and checks that only the queue CLI ran. The map and golden-render mutations ran again after the rebase and were still killed. ## Suites All nine green at the final candidate: config 24, task 90, foundation 44, conductor 17, release 14, auth 15, discord 64, extension-package 18, queue 24 (no `queue.json` at HEAD). `node --test packages/queue/tests/`: 122/122. ## After approval (Sage) 1. Check the canonical tree against `build-manifest.sha256`, then commit the 21 files by path. 2. Apply `queue-md.patch` (and `tools-md.patch` if wanted) and commit. 3. `node agents/darkwing/work/queue-a2/map-check.mjs` must print `ok`. 4. `scripts/queue-commit.sh --install-hook --by sage`. 5. `scripts/mosaic queue genesis --root /mnt/storage/src/mosaic-stack --branch refactor --map agents/darkwing/work/queue-migration-map.md --op --by sage`. 6. `scripts/queue-commit.sh --genesis -m MSG`. 7. `scripts/test-queue.sh` runs the live checks from here on.