pg: server-side stamp sums saturate to the i64 domain — the review-004 item-5 sticky-poison class dies (pg-fix-bigint-stamp-overflow): every stamp sum/subtraction computes in numeric via one owner (resolution::saturating_stamp_expr, the ADR-023 §2 2026-10-11 annotation's form) called at all six sites — claim deadline (queue.rs:385), retention sweep (:721), retry reschedule (:927), heartbeat renewal (:1016), lock insert + renew (lock.rs:95/222); the realized ($N::bigint)::numeric chain pins the params' inferred bigint type (a bare $N::numeric fails the i64 binding with WrongType — found live, recorded in the owner's doc); behavior pinned against the harness server per site (i64::MAX stamp claims to the i64::MAX deadline with neighbors unaffected, heartbeat extremes renew to the saturated edge and now+MIN rides exact then voids through the validity predicate, retry Some(i64::MIN)/(i64::MAX) reschedule, retention i64::MIN sweep-all + i64::MAX delete-nothing, ttl i64::MAX insert+renew); two consecutive full-crate harness runs green (135+25+9), workspace gates + fuzz corpus gate green server-less; no re-deciding, no contract-text change (the failure-set change was the decision's own annotation); task -> completed
This commit is contained in:
1 parent
60f7d02769
commit
ce5da852ca
6 files changed
+400
-25
No files matched your search
@@ -1,7 +1,7 @@
|
||||
---
|
||||
id: pg-fix-bigint-stamp-overflow
|
||||
name: Guard pg's server-side stamp arithmetic against i64 extremes (review 004 item 5, implementation)
|
||||
status: pending
|
||||
status: completed
|
||||
depends_on: [pg-decide-stamp-arithmetic-side]
|
||||
scope: moderate
|
||||
risk: medium
|
||||
@@ -22,7 +22,7 @@ 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-decide;
|
||||
`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.
|
||||
|
||||
@@ -36,16 +36,16 @@ against it.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] The decided guard lands at the decided sites; no re-deciding
|
||||
- [x] 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
|
||||
- [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
|
||||
- [ ] The retention `i64::MIN` arm pinned (sweep behavior per the
|
||||
- [x] The retention `i64::MIN` arm pinned (sweep behavior per the
|
||||
decided semantics)
|
||||
- [ ] The decision's annotation/contract text updated if the
|
||||
- [x] The decision's annotation/contract text updated if the
|
||||
reachable failure set changed
|
||||
- [ ] `cargo test -p alkstore-postgres` (harness server), clippy
|
||||
- [x] `cargo test -p alkstore-postgres` (harness server), clippy
|
||||
`-D warnings`, fmt clean; workspace gates green server-less
|
||||
|
||||
## References
|
||||
@@ -59,8 +59,74 @@ against it.
|
||||
|
||||
## Notes
|
||||
|
||||
> To be filled by implementation agent
|
||||
**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
|
||||
|
||||
> To be filled on completion
|
||||
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.
|
||||
Reference in new issue
Block a user