Two issues raised on the first push:
1. **Rev advanced before send (Bug · Correctness).** The previous
diff advanced sessioninfo.rev/time inside the collect loop,
before any emit ran. A concurrent updatePadClients() could then
see the bumped rev and skip those revisions, and if the emit
threw later, the skipped revs were lost forever. The client
enforces strict newRev===rev+1 and silently stops applying on
mismatch — net effect was a possible pad desync under
concurrent fan-outs.
Fix: snapshot startRev/startTime once, claim the
(startRev, headRev] range by setting sessioninfo.rev = headRev
immediately (so a concurrent run skips it), build the pending
list against the local startTime, then emit. If the emit
throws, roll sessioninfo.rev back to startRev so the next
fan-out retries. Time is only committed after a successful
send.
2. **Test re-implemented the decision (Rule violation ·
Reliability).** The original test re-implemented the
NEW_CHANGES vs NEW_CHANGES_BATCH switch locally instead of
exercising the production code. Removing the production logic
would have left the test green.
Fix: extract the pure wire-format decision into
src/node/handler/NewChangesPacker.ts (no DB / pad dependency,
so the test can import it directly under vitest), and rewrite
the test to assert against the exported `buildNewChangesEmits`
function from that module. PadMessageHandler now calls the
same function; deleting it would fail the test.
9/9 tests across new-changes-batch + prom-instruments.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>