fix(wake): #942 harden WAKE_SNAPSHOT_TS_FUTURE_SLACK — validate shape AND force base-10
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful

The slack knob was interpolated raw into $((...)) under set -u: a malformed value was FATAL to the poll, falsifying the poll-never-fails invariant, and a negative value inverted the guard to deny-all. Shape validation alone was insufficient — bash reads a leading zero as octal, so 08/09 passed the regex yet were fatal and 0300 silently meant 192. Now validated ^[0-9]{1,9}$ with a loud fallback to 300, then forced to base-10 via 10# so the knob means what the operator wrote.

Authored-by: pepper
Reviewed-by: mos-dt (independent, found both the original defect and the radix residual)
Merged-by: Mos
Co-authored-by: mos-dt-0 <[email protected]>
This commit was merged in pull request #942.
This commit is contained in:
mos-dt-0
2026-07-30 11:17:51 +00:00
committed by Mos
parent 8710d0f6d7
commit 3e47fc076f
3 changed files with 123 additions and 5 deletions
@@ -274,14 +274,32 @@
# must not sit beyond a future-skew allowance
# (WAKE_SNAPSHOT_TS_FUTURE_SLACK, default 300 s): a future ts
# yields a NEGATIVE age — stale-reads-fresher-than-fresh, the
# exact failure class #940 fixes. NOTE for consumers: these fields
# exact failure class #940 fixes. The SLACK knob itself is
# operator input interpolated into arithmetic under set -u, so it
# gets the same discipline (#941 §2 round 2): validated
# ^[0-9]{1,9}$, else LOUD fallback to 300 — a malformed knob
# ('300s', '5m', 'abc') must never kill the poll, and a negative
# one must never invert the guard into deny-all. Shape validation
# is NOT radix validation (#942 review): bash reads leading zeros
# as OCTAL, so '08'/'09' pass the regex yet are fatal in $((...))
# and '0300' silently means 192 — the knob is therefore forced
# base-10 (10#) after validation, so it means what the operator
# wrote. SKEW GUARANTEE
# (stated, not implied): the future-skew check runs against the
# DETECTOR's clock; consumer-side age arithmetic runs on the
# consumer's. A surviving snapshot_ts is therefore attested only
# to within SLACK seconds of the detector's clock, plus whatever
# skew the consumer's own clock adds — a small NEGATIVE age at
# render is bounded, not impossible; treat age <= 0 as
# "effectively current," never as proof of freshness.
# NOTE for consumers: these fields
# are ADVISORY and their ABSENCE IS DELIBERATELY NOT DIAGNOSTIC —
# a pre-#940 adapter and a dropped-as-malformed attestation render
# identically (no snapshot_* fields); the drop is loud only in the
# detector's own stderr. Do not build load-bearing logic on the
# absence of these fields.
# Changed: detector.sh, digest.sh (+ test-wake-detector.sh
# D10/D11/D12, test-wake-digest-quarantine.sh Q10).
# D10/D11/D12/D13, test-wake-digest-quarantine.sh Q10).
component=wake
version=0.6.12