--- id: pg-fix-bigint-stamp-overflow name: Guard pg's server-side stamp arithmetic against i64 extremes (review 004 item 5, implementation) status: completed depends_on: [pg-decide-stamp-arithmetic-side] scope: moderate risk: medium impact: component level: implementation tags: [review-004-fixes, postgres-engine, queues] --- ## Description Implement the guard decided by `pg-decide-stamp-arithmetic-side`, per `docs/reviews/004-pre-release-general-review.md` §"Findings recorded, not fixed" item 5. The sites: the claim statement (`queue.rs:386`), heartbeat (`:912`), retry-reschedule (`:1000`), retention-sweep (`:714`), and the lock stamp (`lock.rs:91`) — server-side sums over consumer-supplied i64s whose overflow fails the UPDATE with "bigint out of range", sticky per row (one extreme-stamped row poisons every later claim/heartbeat/sweep that touches it). Per the decision of record: clamp/validate at the enqueue seam, or `LEAST`/`GREATEST`-guard the server expressions — do not re-deciding; if the implementation surfaces a reason the decision can't stand as written, stop and record the discrepancy instead of deviating. **Tests** (against the live harness server): a row stamped with `visibility_timeout_s = i64::MAX` must not poison claim/heartbeat — the row is claimable/heartbeatable per the decided semantics and *other rows are unaffected*; the retention `i64::MIN` arm pinned similarly. The sticky-failure property is the thing being retired: before the fix, one extreme row fails every claim UPDATE that sums against it. ## Acceptance Criteria - [x] The decided guard lands at the decided sites; no re-deciding (any discrepancy recorded, not deviated around) - [x] An `i64::MAX`-stamped row no longer makes claim/heartbeat UPDATEs fail — pinned by test against the live server, with the other-rows-unaffected assertion - [x] The retention `i64::MIN` arm pinned (sweep behavior per the decided semantics) - [x] The decision's annotation/contract text updated if the reachable failure set changed - [x] `cargo test -p alkstore-postgres` (harness server), clippy `-D warnings`, fmt clean; workspace gates green server-less ## References - docs/reviews/004-pre-release-general-review.md §"Findings recorded, not fixed" item 5 (the finding, the sticky-failure mechanism) - tasks/pg-decide-stamp-arithmetic-side.md (the decision of record this implements) - alkstore-postgres/src/queue.rs:386/714/912/1000, lock.rs:91 (the stamp sites) ## Notes **The decision stands as written; one form refinement, recorded here.** The decision's verbatim guard form writes the sum as `$N::numeric`, but a bare `$N::numeric` cast changes Postgres's parameter-type inference — the parameter infers `numeric` and tokio-postgres's `i64` binding fails with `WrongType { postgres: Numeric, rust: "i64" }` (caught by the first live test run, both tests). The realized form is the **`($N::bigint)::numeric` chain**: the inner `::bigint` cast pins the parameter's inferred type (the client keeps binding `i64`), the outer `::numeric` lifts the arithmetic into the overflow-free domain. Clamp domain, sites, and extreme-stamped observables are unchanged — this refines *how* the sum computes, not the decision. The pin lives in `resolution.rs`'s `saturating_stamp_expr` doc (the one owner both files call). **One owner for the guard.** The form lives in `resolution.rs`::`saturating_stamp_expr` (the engine's arithmetic home, "one owner per formula" — ADR-012 §2); the six sites call it: `queue.rs:385` (claim deadline), `:721` (retention sweep), `:927` (retry reschedule), `:1016` (heartbeat renewal), `lock.rs:95` (try_lock insert), `:222` (renew). Line numbers drifted a few lines from the decision's refs (the doc-only decision pinned decision-time lines); the site *set* is exactly the decision's six. **No contract/annotation text change.** The reachable-failure-set change was recorded by the decision itself (ADR-023 §2's 2026-10-11 annotation: "bigint out of range" leaves pg's reachable surface); the implementation changes nothing further — the acceptance criterion's "if changed" clause is satisfied vacuously. **Test coverage beyond the description's minimum**: the description pins claim/heartbeat (`i64::MAX` stamp) and the retention `i64::MIN` arm; the landed test also pins the retention `Some(i64::MAX)` delete-nothing arm, the retry override extremes (`Some(i64::MIN)` rides exact → ready-now reclaim; `Some(i64::MAX)` → `i64::MAX` ready-~never), and both lock arms (`ttl = i64::MAX` insert and renew → `i64::MAX` expiry, normal ttl rides exact). All six decision sites are behaviorally pinned, not just the four the review named. **One flake fixed before merge**: the first full-crate run failed once in the new queue test — the `heartbeat(i64::MIN)` exact-ride window was anchored to the test-start clock read and, under the loaded parallel suite, the server-side `now` inside the heartbeat drifted past `now + 2`. Re-anchored to a fresh read immediately before the heartbeat (the family's bounded-wait convention); two consecutive full-crate harness runs green after the fix. ## Summary The decision-of-record guard landed: every pg server-side stamp sum/subtraction computes in `numeric` and saturates to the i64 domain before the `::bigint` cast — `LEAST(GREATEST(, -9223372036854775808), 9223372036854775807)::bigint` — via one owner (`resolution.rs::saturating_stamp_expr`) called at all six sites of record (claim deadline, retry reschedule, heartbeat renewal, retention sweep; lock insert + renew). The sticky-poison property is retired: an `i64::MAX`-stamped row claims to the saturated `i64::MAX` deadline, heartbeats land, neighbors claim unaffected, `"bigint out of range"` is unreachable through every guarded op. Verified: `cargo test -p alkstore-postgres` against the live harness server (`pglo-poc` :15432) — 135 lib + 25 suite + 9 schema green, two consecutive full runs (the two new tests: `extreme_stamps_saturate_and_no_longer_poison_queue_ops`, `extreme_ttl_stamps_saturate_without_overflow`); workspace gates server-less green (build, full `cargo test` incl. the skipped-clean pg rows, clippy `-D warnings` all targets, fmt); fuzz corpus gate green (`fuzz/shared` 6 passed); `RUSTDOCFLAGS="-D warnings" cargo doc --no-deps` clean.