packages/queue, scripts/queue-commit.sh, scripts/git-hooks and scripts/test-queue.sh, plus docs/plans/BRIEF-TEMPLATE.md. There is no queue.json yet, so verify skips until the genesis commit after A2. Darkwing built it, and Filbert reviewed R0 (6933b885, changes requested) and r1 (e464be6c, approved). The 20 files match manifest 85a8a453. The nine suites passed on an index export, including the new queue suite. test-queue.sh joins the suite list in AGENTS.md. Lead decisions 20, 23 and 26. Co-Authored-By: Claude Opus 5.5 <[email protected]>
8.8 KiB
Queue A1 build (#1508), candidate for review
Darkwing built this on 2026-09-26 from section 8 of
agents/filbert/work/queue-as-data-plan-2026-09-26.md (sha256 282fabbb,
the only spec), split as Sage approved: render is in A1, move in-review
needs --candidate until piece D, and a round's issue is the row's first
issue. Filbert reviews the code; Sage commits after the suites. Base is HEAD
3a209eea. Nothing is committed, staged or pushed.
A stray pkill at 22:02:32Z stopped my first turn with only
src/errors.mjs and src/io.mjs on disk. I reread both against what I had
meant them to be. They match: the exit-code class, and the file layer with
realIo as the only layer the CLI uses. Everything else was written after
the restart.
Files
build-manifest.sha256 pins the 20 files. build.patch (sha256 419804f2)
adds all 20 as new files with their modes. It applies cleanly to 3a209eea,
and the applied tree matches the manifest and passes scripts/test-queue.sh.
packages/queue/src/:errors.mjs,io.mjs(the fault-injectable file layer),lock.mjs(lock and unlock gate, 8.4),queue.mjs(serialization, replay, the transition matrix,next, render),store.mjs(canonical checks, the write path, witness, views, snapshot, verify),cli.mjs.packages/queue/tests/: data 19, lock 17, store 18, write 20, commit 21 tests, plushelpers.mjsand two child fixtures.packages/queue/package.jsonandREADME.md. The package has no dependencies.scripts/queue-commit.sh(0755): the 8.12 procedure and--install-hook.scripts/git-hooks/pre-commit(0755, POSIX sh): the queue guard.scripts/test-queue.sh(0755): the suite, in the style oftest-discord.sh.docs/plans/BRIEF-TEMPLATE.md: the 8.13 template.
Which path runs verify once genesis is in
scripts/test-queue.sh runs node packages/queue/src/cli.mjs verify when
git cat-file -e HEAD:docs/plans/queue.json succeeds. At HEAD today there is
no queue.json, so it prints skip queue verify: HEAD has no docs/plans/queue.json (before the genesis commit) and stays green. A2 moves
that call to scripts/mosaic queue verify when it adds the dispatch.
queue-commit.sh also calls node packages/queue/src/cli.mjs directly
(snapshot, then verify --snapshot from HEAD's archive) until A2.
Sage's five conditions
- Nothing ran against the canonical
.git. After all runs,.git/hooksholds only the samples,.githas nomosaic-queue*file, andgit config --show-scope --get-all core.hooksPathreturns nothing in any scope (rc 1). Every hook install, genesis, lock and gate test runs in a scratch repo under the system temp directory. Suite runs used a--sharedclone at/tmp/qa1-verify. docs/plans/QUEUE.md,AGENTS.mdanddocs/TOOLS.mdare unedited.docs/SESSIONS.mdshows as modified in the working tree, but that was someone else's edit before my session began; I didn't touch it.scripts/test-queue.shis green at HEAD with noqueue.json: 19 checks passed,verifyskipped as above.- H is recorded before the canary.
commit.test.mjshas "H recorded before the canary": a shim commits on the firstgit hook run, andqueue-commit.shexits 1 withrefs/heads/<branch> moved since <H>; nothing published. A second test moves HEAD aftercommit-treewith the same result. Mutation M1 (read H after the canary) fails the first test. - The fault file layer is reachable only from tests. Faults enter through
the options the API takes (
io,proc,hook,now,readOrder,lockWaitMs);cli.mjspasses none. The onlyprocess.envread insrc/is the defaultenvinstore.mjs's context.
Bugs found while building
- A nested
node --testinheritsNODE_TEST_CONTEXTand exits 0 whatever its tests do. Step 4's run of HEAD's archived tests therefore passed with a failing test in the archive.queue-commit.shandtest-queue.shnow run it underenv -u NODE_TEST_CONTEXT, and a test commits an archive with a failing test and expects a refusal (mutation M4). Other suites in this repo that nestnode --testmay have the same blind spot. I haven't checked them. - git 2.55 does not hold
index.lockwhile the commit editor is open. The plan expected a pausedgit commit -eto block step 8. It doesn't: the paused commit loses later at its own HEAD update withcannot lock ref 'HEAD': is at C but expected H. The test now asserts that outcome. Nothing is lost, but the reason differs from the plan's. - ext4 hands a freed inode number straight back. My first test for release's inode check wrote a byte-identical lock after unlinking the original and got the same inode back, so it proved nothing. It now writes a copy and renames it over the lock, which guarantees a new inode.
Choices the spec left open
- Verb names
releaseandset.addrequires--gate. A null brief is allowed only on rows that genesis creates as done.sync --opis optional. - Reads need no actor.
nextwith no seat and no$MOSAIC_AGENT_NAMErefuses. accept-historyaccepts a stale table but not an unknown one. When a write succeeds but the table write is skipped, the CLI warns and exits 0.- An invalid witness is treated as absent.
GIT_DIR,GIT_WORK_TREEandGIT_COMMON_DIRrefuse in the CLI.queue-commit.shalso refusesGIT_INDEX_FILE,GIT_OBJECT_DIRECTORYandGIT_ALTERNATE_OBJECT_DIRECTORIES.- Leftover temp files are unlinked. Genesis uses
link, so it cannot replace an existing file. unlockworks on a missing or invalid lock file.noteon a blocked row editsblockedReason.- Messages already name
scripts/mosaic queue. The lost-history refusal namessyncwhen the file holds genesis alone. --candidateauto-detects: an existing file is a manifest, anything else is a commit reachable fromrefs/headsorrefs/tags.- Only
--install-hookis privileged inqueue-commit.sh(jason or sage). The commit itself relies on the protocol that the lead runs it. - After the snapshot,
queue-commit.shchecks the genesis entry's branch and root against the current branch and root. For--genesisit also checks thatH:<map>is the map blob genesis read. - Step 8 compares the index's two queue entries with H's before it looks
for
index.lock, so "someone staged a queue path" is reported ahead of a lock.
Deferred
- 8.12's test of
verify-commiton a prospective tree belongs to piece D, which addsverify-commit. It is not in A1. - A2 holds the real migration map, the QUEUE.md markers and header, the
row-7 pointer, the golden render,
scripts/mosaicdispatch anddocs/TOOLS.md. - Sage adds
queueto the suite list when A1 lands (lead decision 20). - Bootstrap happens after A2's map and markers land:
scripts/queue-commit.sh --install-hook --by sage, thenqueue genesis, thenscripts/queue-commit.sh --genesis -m MSG.
Verification
In /tmp/qa1-verify (HEAD 3a209eea plus the 20 files):
| Suite | Result |
|---|---|
| config | 24/24 |
| task | 90/90 |
| foundation | 43/43 |
| conductor | 17/17 |
| release | 14/14 |
| auth | 15/15 |
| discord | 63/63 |
| extension-package | 18/18 |
| queue | 19 checks; node --test 95/95 |
The queue tests take about 18 s and were stable over two runs. A combined
node --test run over every package came to 474/474.
I also ran the post-genesis path in a scratch repo: install the hook,
genesis, --genesis commit, then test-queue.sh. verify printed ok verify rev 1: file valid, witness matches, view current. After a hand edit
to one table cell it failed with view unknown.
Mutations
Each mutation went into the verify clone, the queue tests ran, and the original was restored. Every one was caught; the number is how many tests failed.
| Id | Mutation | Failing tests |
|---|---|---|
| M1 | read H after the canary | 1 |
| M2 | drop the step-7 guard recheck | 2 |
| M3 | drop step 8's entry comparison | 1 |
| M4 | keep NODE_TEST_CONTEXT |
1 |
| M5 | drop step 1's staged-path check | 1 |
| M6 | update-ref without the old value |
2 |
| M7 | skip the canary | 2 |
| M8 | guard hook always passes | 21 |
| M9 | drop step 8's index.lock check |
1 |
| M10 | drop the exec-bit check | 2 |
| M11 | drop the core.hooksPath check |
1 |
| M12 | drop the map-blob check | 1 |
| S1 | drop the "unchanged since read" check | 1 |
| S2 | swallow the directory fsync error | 2 |
| S3 | drop the recheck under the lock for unlocked reads | 2 |
| S4 | drop the unlock-gate check | 2 |
| S5a | release ignores the inode | 1 |
| S5b | release ignores the record bytes | 1 |
| S6 | write the witness before the rename | 10 |
| S7 | drop genesis's fsync | 1 |
| S8 | treat a reused pid as dead | 11 |
S5 survived at first: the delayed-release test was caught by the byte
comparison alone. The inode test in lock.test.mjs closes that gap.