.
C One\swriter\sat\sa\stime,\sand\stests\sthat\sknow\sit
D 2026-09-28T03:17:20.747
E 2026-09-28T03:20:00 16e0ed3821b7909e8edda10d5b361186c713b657
N text/x-markdown
P c70857a1ec1d5c3e778b36d0c466fa463cf9a26bbf1ef4e98d4b472d6375c5a8
U dmestas
W 2590
# One writer at a time, and tests that know it
0.3.32 fixes one command that wrote to the repository without its lock,
moves that lock into the storage layer, and adds the tests that should have
caught the bug in the first place.
A zit repository is one append-only file. Every writer takes an exclusive
lock on it, finds the end, and appends there. Two writers that are not
serialised both find the same end and both append at it, so the second
writes over the first. The overwriting frames are complete and pass every
checksum, so neither `zit verify` nor crash recovery can tell anything
happened. One of the two writes is simply gone.
`zit purge undo` restores buried artifacts by appending them, and it opened
the store without that lock. Run while a commit, a pull or a server push
was writing the same repository, it could lose its own restore or the other
writer's check-in without a trace. It now opens the store the way every
other writer does and waits its turn.
That is the bug. The reason it existed is the more useful part. The lock
was taken by each command when it opened the file, so one command that
opened the file differently skipped it, and nothing noticed. The log now
takes the lock itself: opening a store for appending locks first, and only
then reads the file's length and scans for its end. A store opened for
reading cannot append at all, so a writer that opens the file the wrong way
fails loudly. A source check in the test suite also fails on any code that
opens the repository for writing outside the one shared opener.
The deterministic simulation already swept crashes, torn writes and faulty
disks, always with a single writer. A lock nobody contends for is never
exercised, so the whole class was invisible to it. A new sweep runs two to
four writers against one simulated log, interleaved step by step, through a
file that models the lock as the operating system provides it. Each writer
follows a real command's order, so another writer can run between finding
the file's length, opening the log, appending and committing. The file
reports an unlocked append, or one that does not land at the end, the
moment it happens. Each writer must see everything committed before it took
the lock and read back what it just wrote, including deltas against other
writers' artifacts. Removing the lock fails this sweep, and so does reading
the file's length before taking it.
An end-to-end test adds the real-process version: commits from two
worktrees, tag writes and user-table writes racing on one store, then a
count of what landed and a clean `verify`.
Z ce0c19e23c7fc5d0fff4b3cb08dae66e