Twinned from https://github.com/cfsh/ninefold/issues/694 by tools/beads/import_github.py's reconcile pass.
Suggestions from ninefold-reviewer[bot]'s review of #692. Unclaimed, and none of it blocks that PR.
⚠️ Updated 2026-08-07 at aa5ca92 — items 7 and 8 were fixed in #692 itself rather than banked here. Item 6 was folded in at fb0c3cb. Outstanding: 3 and 9.
1. ~~test_the_re_read_happens_AFTER_the_build_not_before_it cannot fail for the mutation it names.~~ DONE in 7c9e600. drive now shares one runner between Git and verify, the assertion is against build Ninefold.sln, and the hoist mutation fails exactly that test.
2. ~~Dropped branches are still gated by the pre-push re-read.~~ DONE in 7c9e600, and folded in more directly by fb0c3cb — before is composed from the consumed set, so the subtraction is {b: sha for b, sha in consumed.items() if b not in dropped} rather than a filter at the push site.
3. acquire_publish_lock announces a seizure it does not perform. OPEN. (tools/pr/rebuild_f5.py.) On the attempt-2 path, if the successor lock also reads stale, ⚠ Taking the publish lock at <path> prints a second time on stderr and is then followed by → Another rebuild claimed the stale lock first — standing down. The seizure line is deliberately loud so a taken lock can be correlated later; a false one costs exactly that correlation. Reachable when the unlink fails, or when a rival re-takes the lock — the path test_losing_the_race_for_a_STALE_lock_stands_down_rather_than_re_stealing drives. Guard the second announcement on actually having taken the lock.
4. ~~notes/README.md's f5.md row was not updated.~~ DONE in 7c9e600.
5. ~~Doc pointer in lock_path: "(#492 F1, two functions up in merge_branch)".~~ DONE in 7c9e600.
6. ~~before is not the sha the build was actually made from.~~ DONE in fb0c3cb, in both directions. base now comes from the worktree's own HEAD after ensure_worktree's reset, and each head is read one call before its own git merge origin/<branch>. Inputs' docstring records the two reads as deliberately asymmetric, which is the part that stops it regressing as a tidy-up.
7. ~~test_a_head_that_moved_before_ITS_OWN_merge_does_not_refuse cannot fail for the mutation it names.~~ DONE in aa5ca92. Rewritten as test_each_head_is_read_AFTER_the_previous_branch_s_merge, an argv-order assertion (merge_a[0] < read_b[0] < merge_b[0], with exactly two git rev-parse origin/feat-b calls pinned so the pre-push re-read cannot stand in for the in-loop one) — the same move the file already makes for --path-format=absolute, and the only shape that can express a *when*. The dict-comprehension hoist now fails exactly that test. The PR body's mutation row was corrected from *"3 tests"* to *"1 test"*; the original sweep had mutated the read to pass, which empties consumed and is a different mutation.
8. ~~Two now-vestigial test setups from the fb0c3cb rewrite.~~ DONE in aa5ca92. test_the_refusal_leaves_f5_integration_ALONE_and_says_to_re_run moved off the shifting runner, so its stated cause is now its actual cause; and test_main_advancing_BETWEEN_the_reset_and_the_read_still_refuses was merged into test_MAIN_moving_under_the_build_refuses_the_push, which after G1 is one observable rather than two. Net 167 → 166 tests.
9. notes/f5.md wording, post-G1. OPEN, one clause. The refusal bullet says origin/main *"or one of the merged heads changed between the merge and the push"*. After fb0c3cb the origin/main half is measured from the worktree reset, which is earlier. The operator-facing advice (branch untouched, just re-run) is unaffected.
The set(integrated)-per-iteration comprehension noted here previously is gone — fb0c3cb removed it.
None.
No comments.