Follow-up residual from PR #726 (the #286 + #469 ledger/board cross-process serialization
program). Filed rather than fixed there because the remedy contradicts an invariant that PR
deliberately pinned with a test.
The defect
sprintstatus.advance acquires the board lock before _advance_locked discovers there is
nothing to write. An idempotent bmad-loop confirm replay against a story already at or
beyond target — previously a successful no-op — can therefore fail when the lock is
unavailable, because it acquires before learning it had no work to do.
Reported by codex on PR #726 and verified correct against the code:
#726 (comment)
This is the third instance of one class
The general shape: a lock taken for an operation that will not write turns a previously
successful no-op into a failure. PR #726 fixed the other two instances on exactly that
reasoning:
archive_closed over a missing ledger (now skips the lock)
- empty
mark_done_many / mark_open_many batches (now skip the lock)
Consistency argues for fixing this site too. A fix should sweep the class rather than patch
this one report — a reviewer found the same shape at two sites across consecutive rounds, so
a per-report patch is likely to leave another instance standing.
Why it was not fixed in #726
The suggested remedy — probe for the already-at-target and absent-story cases before
acquiring — puts a board read above the lock. story_status goes through load, and
test_advance_holds_the_lock_across_every_read_and_the_write asserts
events[0] == "lock-enter" with events[1:-1] == ["load", "load", "write"].
That assertion is not incidental. It encodes #726's decision that all three reads move
under the hold, which is what closes the intra-call TOCTOU between the never-regress
decision and the bytes that decision is applied to. A pre-lock probe reopens a read outside
the lock and reddens that test by construction.
The actual trade-off
Either:
- Keep every read under the hold and accept that a no-op advance can fail when the lock
is unavailable (status quo), or
- Admit an advisory pre-lock probe as an early-out, with the authoritative never-regress
decision still made under the lock, and relax the pinned ordering to "the reads that
decide the published bytes are inside the lock".
Option 2 is defensible — the probe would be advisory only, and the published bytes would
still be decided under the hold — but it changes a reviewed decision and requires rewriting
the test that protects it. That is a design call, not a repair, which is why it is here
rather than in #726.
Related: #726, #286, #469, #735.
Follow-up residual from PR #726 (the #286 + #469 ledger/board cross-process serialization
program). Filed rather than fixed there because the remedy contradicts an invariant that PR
deliberately pinned with a test.
The defect
sprintstatus.advanceacquires the board lock before_advance_lockeddiscovers there isnothing to write. An idempotent
bmad-loop confirmreplay against a story already at orbeyond
target— previously a successful no-op — can therefore fail when the lock isunavailable, because it acquires before learning it had no work to do.
Reported by codex on PR #726 and verified correct against the code:
#726 (comment)
This is the third instance of one class
The general shape: a lock taken for an operation that will not write turns a previously
successful no-op into a failure. PR #726 fixed the other two instances on exactly that
reasoning:
archive_closedover a missing ledger (now skips the lock)mark_done_many/mark_open_manybatches (now skip the lock)Consistency argues for fixing this site too. A fix should sweep the class rather than patch
this one report — a reviewer found the same shape at two sites across consecutive rounds, so
a per-report patch is likely to leave another instance standing.
Why it was not fixed in #726
The suggested remedy — probe for the already-at-target and absent-story cases before
acquiring — puts a board read above the lock.
story_statusgoes throughload, andtest_advance_holds_the_lock_across_every_read_and_the_writeassertsevents[0] == "lock-enter"withevents[1:-1] == ["load", "load", "write"].That assertion is not incidental. It encodes #726's decision that all three reads move
under the hold, which is what closes the intra-call TOCTOU between the never-regress
decision and the bytes that decision is applied to. A pre-lock probe reopens a read outside
the lock and reddens that test by construction.
The actual trade-off
Either:
is unavailable (status quo), or
decision still made under the lock, and relax the pinned ordering to "the reads that
decide the published bytes are inside the lock".
Option 2 is defensible — the probe would be advisory only, and the published bytes would
still be decided under the hold — but it changes a reviewed decision and requires rewriting
the test that protects it. That is a design call, not a repair, which is why it is here
rather than in #726.
Related: #726, #286, #469, #735.