Nobody has started this yet — be first.
Business impact
This is the other headline guarantee from the README, alongside the outbox crash-recovery story: "a timeout must be self-invalidating... the sweeper re-validates current state (and/or version) atomically at claim time." With that check broken, a timeout scheduled against one "generation" of a payment can fire against a later generation that's still in the same state -- e.g. an auto-void timeout firing after some other legitimate event has already moved the payment's version forward, incorrectly voiding an authorization that should have survived. This is exactly the class of bug a payments system cannot silently ship: money (or the promise of it) moving based on stale information.
Problem
applyFireAndPersist's version self-invalidation check -- if requireVersion != nil && p.Version != *requireVersion { return nil, false, nil } -- has been altered so its second condition compares p.Version against itself instead of against *requireVersion. That comparison is always false, so the check never rejects anything, regardless of what requireVersion actually holds.
Current behavior
ApplyTimeoutEvent fires a timeout event against a stale generation (an expectedVersion that no longer matches the payment's actual current version), even though the payment's state hasn't changed.
Expected behavior
ApplyTimeoutEvent (which passes a non-nil requireVersion) should reject firing when the payment's actual current version doesn't match the generation the timeout was scheduled for.
Steps to reproduce
TEST_DATABASE_URL=postgresql://... go test ./tickets/tests/... -run TestTicket22 -v -count=5
The project's own TestSweeper_DoesNotFire_IfPaymentAlreadyTransitionedOut still passes with this bug present, because in that test's scenario the payment moves to a different state before its timeout fires, so machine.Fire fails anyway for the unrelated, coincidental reason that the event isn't valid from that state. This ticket's test isolates the version check on its own: it advances the payment's version while deliberately leaving its state unchanged, so the timeout event remains a structurally valid transition. The only thing that should stop it from firing is the version check.
Why this matters
The project's own TestSweeper_DoesNotFire_IfPaymentAlreadyTransitionedOut (in internal/timeout/sweeper_test.go) still passes with this bug present, for the wrong reason -- it cannot tell "version check works" apart from "state check happens to also reject this."
Suggested approach
Read the comparison in applyFireAndPersist character by character: p.Version != p.Version. Compare it against the parameter the function actually received.
Acceptance criteria
Verification
TEST_DATABASE_URL=postgresql://... go test ./tickets/tests/... -run TestTicket22 -v -count=5
Hints (0/2)
Try it without hints first — the reading is the exercise.
Working on this ticket
Work on a branch named for the ticket — that's what you'll submit.
Branch off your fork
Sign in to create it automatically, or do it yourself:
$git checkout -b fix/timeout-self-invalidation-disabledFix it and commit
Meet every acceptance criterion, and add a test that would have caught this.
Push the branch
$git push -u origin fix/timeout-self-invalidation-disabledSubmit it below
Paste your fork URL and the branch name, with a short write-up of the root cause.
Questions
Ask about anything unclear in the ticket — the maintainer and anyone who has solved it can answer. Please don't post full solutions.
Sign in to ask a question or reply.
Sign indocker composepaystate/paystate/go.modreplace reliable => ../reliabledocker compose up --buildpaystate/context: ..docker-compose.ymlCREATED_TIMEOUTAUTHORIZED_TIMEOUTWork the tickets in tickets/docs/ (ticket-01 through ticket-25); each names one Go test in tickets/tests/:
TEST_DATABASE_URL=postgresql://... go test ./tickets/tests/... -run TestTicket01 -v # a single ticket
TEST_DATABASE_URL=postgresql://... ./tickets/run_tickets.sh # all 25, clean pass/fail summary
Tickets that don't touch Postgres (02, 03, 04, 05, 06, 14, 15, 24) run fine without TEST_DATABASE_URL set; the rest report FAIL with a "TEST_DATABASE_URL not set" message if it's missing. make test-race runs the full go test -race -p 1 ./... suite the same way.
Level 1
Fix a bug
Read existing behaviour, correct it.