6.2 KiB
id, name, status, depends_on, scope, risk, impact, level, tags
| id | name | status | depends_on | scope | risk | impact | level | tags | ||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| pg-fix-bigint-stamp-overflow | Guard pg's server-side stamp arithmetic against i64 extremes (review 004 item 5, implementation) | completed |
|
moderate | medium | component | implementation |
|
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
- The decided guard lands at the decided sites; no re-deciding (any discrepancy recorded, not deviated around)
- 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 - The retention
i64::MINarm pinned (sweep behavior per the decided semantics) - The decision's annotation/contract text updated if the reachable failure set changed
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(<sum>, -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.