Skip to content

Keep the two claim-loop tests written during the #119 CI investigation - #124

Merged
stephane-segning merged 2 commits into
mainfrom
claude/claim-loop-regression-tests
Aug 8, 2026
Merged

Keep the two claim-loop tests written during the #119 CI investigation#124
stephane-segning merged 2 commits into
mainfrom
claude/claim-loop-regression-tests

Conversation

@stephane-segning

Copy link
Copy Markdown
Contributor

Summary

Promotes two tests written as throwaway diagnostics during the #119 CI investigation into standing regression tests in crates/sms-worker/tests/claim_live_postgres.rs. Renamed, reframed, and given the file's standard live-Postgres #[ignore] reason so they run like every other suite.

Test-only. No production code touched (crates/sms-worker/src/claim.rs is unchanged).

Intent

While #119 was blocked on CI, two_concurrent_claimers_never_both_win_the_same_row failed with wins: 2. That assertion's own doc comment says it exists to catch a CAS failure that would mean two workers submitting the same SMS — so it was investigated as a possible production double-send.

It was not one. take_lease's accepted branch deliberately leaves no real lease (claim.rs:205-226), so a row is immediately re-claimable, and dispatch.rs::tick() submits only rows in state routed. Two claim_batch calls can therefore both return one id — one doing the free accepted -> queued hop, one the real claim — with no CAS violation. The assertion was counting the wrong thing; #120 corrected it to assert on reaching routed.

The two tests written to establish that are worth more than the investigation that produced them. Source of truth: #119, #120, and claim.rs's own documented design.

Scope

  • a_second_claim_batch_call_picks_up_the_row_the_routing_hop_just_queued (was diagnostic_second_claim_batch_call_...) — pins the no-real-lease routing hop. Deterministic and not concurrent at all; it reproduces the "two wins" observation with two sequential calls. If a future change gives that hop a real lease, every accepted row would stall behind a lease it never needed and this fails loudly. It also stands as the written answer to a misreading that cost real time once.
  • concurrent_if_match_updates_never_both_win (was diagnostic_raw_cas_never_double_wins) — races 8 concurrent if_match updates against one row at one starting version, over 15 rounds, bypassing claim_batch/Claimable entirely. Asserts exactly one winner and that the rest get PreconditionFailed. This is the guarantee the whole claim loop rests on, one layer below it.

Why keep the second one specifically: cratestack is pinned exactly and moves fast (=0.6.7 today, and the family has been bumped repeatedly). If a future bump ever weakened if_match, every CAS claim in this system would begin double-claiming and no other test would notice — #87 is this repo's standing example of exactly that shape of silent regression.

A real defect in the tests as originally written: both carried #[ignore = "diagnostic — not for CI"], which does not exclude anything. The live job runs cargo test --workspace -- --ignored, which runs every ignored test regardless of the reason string — so they would have run in CI while claiming not to. Both now carry the file's standard reason, and are intended to run.

Verification

  • just lint green. Notably, the tests passed under cargo test while failing clippy -D warnings with 3 errors (a doc_markdown lint and two items_after_statements), since warnings are not errors under cargo test. Fixed; worth flagging as a reminder that a green test run does not imply a green lint job.
  • cargo test -p sms-worker --test claim_live_postgres -- --ignored — 9/9 passed, three consecutive runs (1.57s / 1.43s / 1.50s).
  • Full just test-live sweep green, all suites.
  • Container count returned to baseline (11), harness container only, nothing of the developer's touched.

Screenshots/Evidence

N/A — test-only change.

Risk Assessment

Low. Test-only, no production code. Adds ~1.5s to the claim suite. The concurrency test is bounded (15 rounds x 8 racers) and was stable across every run; if it ever proves flaky on a slower CI runner it should be investigated as a real finding rather than muted, since a flake there would mean if_match genuinely is not mutually exclusive.

AI Usage Declaration

Written by Claude Code (Claude Opus 5) under my direction. The underlying tests were authored by a subagent during the #119 investigation; I reviewed the diagnosis against claim.rs and dispatch.rs directly before accepting it, then rebased, renamed, and re-verified them here.

  • I confirmed the no-lease routing hop and the routed-only submit guard by reading the source, not by trusting the investigation's summary.
  • I ran the suite and the full live sweep myself.
  • I verified the original #[ignore] reason string would not have excluded these from CI.
  • I take responsibility for these being worth their runtime.

Reviewer Focus

Whether concurrent_if_match_updates_never_both_win belongs in the per-PR live job at all, versus a slower periodic run. It is cheap today, but it is a concurrency stress test and those tend to grow teeth on slower hardware.

Both were written as throwaway diagnostics while investigating a
`two_concurrent_claimers_never_both_win_the_same_row` CI failure that turned
out to be a bad assertion rather than a real double-claim. They are worth
keeping as standing regression tests, so this renames them, drops the
"diagnostic" framing, and states the invariant each one guards.

- `a_second_claim_batch_call_picks_up_the_row_the_routing_hop_just_queued`
  pins `take_lease`'s `accepted` branch leaving no real lease. If a future
  change gives that hop a real lease, every `accepted` row would stall
  behind a lease it never needed, and this fails instead of it going
  unnoticed. It also stands as the answer to a misreading that has already
  cost real time once: one message id appearing in two claimers' results is
  not evidence of a double-claim, which is exactly why the neighbouring test
  now asserts on reaching `routed`.

- `concurrent_if_match_updates_never_both_win` races N concurrent
  `if_match` updates against one row at one starting version, bypassing
  `claim_batch` entirely, and asserts exactly one wins. This is the
  guarantee the whole claim loop rests on, one layer below it. `cratestack`
  is pinned exactly and moves fast; if a future bump ever weakened
  `if_match`, every CAS claim in this system would begin double-claiming
  with nothing else noticing. Verified sound on the current pin (=0.6.7).

Both take the per-binary `TEST_MUTEX` and the standard live-Postgres
`#[ignore]` reason, so they run under `just test-live` and the CI live job
like every other suite. Their original `#[ignore = "diagnostic — not for
CI"]` would not have excluded them anyway: the live job runs `cargo test
--workspace -- --ignored`, which runs every ignored test regardless of the
reason string.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@changeset-bot

changeset-bot Bot commented Aug 8, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: d1e75a2

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@lightbridge-assistant lightbridge-assistant Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🅵 Fast automated pass — SAST + a quick, diff-scoped look (no repo-wide retrieval). For a deeper, repo-aware review, mention @lightbridge-assistant on this PR.

Test-only change (two #[ignore]d live-Postgres tests), no production code touched, SAST clean. The CAS regression test concurrent_if_match_updates_never_both_win is sound (8-racer if_match(version) race, wins==1 per round asserted). The one finding: a_second_claim_batch_call_picks_up_the_row_... doesn't actually assert its central claim — the second-call re-pickup and total_wins==2 are only printed, so it would pass even if take_lease regressed to leave a real lease, contradicting its stated purpose of failing loudly.


🤖 AI-generated review — treat it as untrusted, verify before acting; a human owns the final decision (AI governance).

let total_wins = first
.iter()
.chain(second.iter())
.filter(|m| m.id == seeded.id)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 quality

Central claim only printed, never asserted

The test's documented purpose ("Locks in take_lease's accepted branch ... so a future change to that hop fails loudly here instead of silently stalling") is not actually asserted. The only real assertion is after_first.state == queued. The test's central claim — that a second, separate claim_batch call genuinely picks up the same row (worker-b saw the same id again, total_wins == 2) — is computed and printed but never checked. If take_lease were changed to leave a real future lease (the exact regression this test exists to catch), the test would still pass.

Evidence: Lines 458–470: after_second and total_wins are derived but only consumed by println!; no assert!(after_second.is_some()) / assert_eq!(total_wins, 2). The only assert_eq! in this test is line 446 (after_first.state == queued).

Suggested change
.filter(|m| m.id == seeded.id)
assert!(after_second.is_some(), "second claim_batch must pick up the row the routing hop just queued");
assert_eq!(total_wins, 2);

Was this useful? React 👍/👎 to give us feedback

CI's live job failed on reclaims_a_routed_row_abandoned_by_a_crashed_worker
while both new tests (concurrent_if_match_updates_never_both_win,
a_second_claim_batch_call_picks_up_the_row_the_routing_hop_just_queued)
passed. Locally this suite passed 9/9 repeatedly, including a full
just test-live sweep — the failure was CI-only, a slower runner widening
a window that already existed.

Confirmed mechanism: candidates() orders by `priority DESC, createdAt ASC
LIMIT budget`, and every fixture in this file seeds priority: 1000 (the
max), so ties break purely by age. This binary's database is never reset
between tests or between cargo test invocations.
concurrent_if_match_updates_never_both_win alone leaves 15 accepted rows
behind every run (only the winning racer's stateReason changes; state
stays accepted, since that field is deliberately not what the race
updates), and a_second_claim_batch_call_picks_up_the_row_the_routing_hop_just_queued
leaves one more. That older-createdAt residue can fill budget=10 before
reclaims_a_routed_row_abandoned_by_a_crashed_worker's own
freshly-abandoned (and therefore newest-createdAt) row is reached — on a
slow enough runner, exactly the failure CI hit.

Same defect shape dispatch_live_postgres.rs already found and fixed
(#119). Reused its proven fix rather than inventing a second mechanism:
clear_claimable_backlog drains every accepted/queued/routed row to
cancelled (reachable from all three per §2.10) before a test seeds its
own fixture, through CrateStack delegates only (R1); isolated_db() wraps
db() with it so no new test can forget the call. The two tests that build
their own Cratestack pool directly (bigger connection limits) call
clear_claimable_backlog explicitly instead of going through isolated_db().

ca653a1's TEST_MUTEX is untouched and still required — it serializes
execution within this binary; it was never meant to, and doesn't, clean
up residue between tests.

Verified: cargo test -p sms-worker --test claim_live_postgres -- --ignored
10 consecutive runs, 9/9 green every time. Two full just test-live sweeps
green. just check, just lint, just parity, and
./ci/assert-no-raw-sqlx.sh all green. No production code touched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@stephane-segning
stephane-segning merged commit d1d8d29 into main Aug 8, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant