fix(wake): #917 gate the final observed_seq cursor write + observed.set/cursor consistency (defense-in-depth)
Some checks failed
ci/woodpecker/pr/ci Pipeline failed

Defense-in-depth hardening of store.sh cmd_enqueue surfaced by the #915 review
(obs#2): the final observed_seq cursor _atomic_write was the ONE durable write not
wrapped in a failure check, and was cross-file non-atomic with the observed.set
write immediately before it. Practically unreachable today, but a cursor-write
failure after a successful observed.set write could (a) return spurious success
while the allocation stayed uncommitted, and (b) leave observed.set advanced while
the cursor lagged (cross-file inconsistent).

Fix (additive; preserves #908 exactly):
- GATE the final cursor write like the pending/observed.set writes (#908): a
  cursor-write failure is now FAIL-LOUD (non-zero + diagnostic), never swallowed.
- On cursor-write failure, ROLL observed.set BACK to its pre-write snapshot, so
  observed.set and the cursor either BOTH advance or NEITHER does — never a
  stranded observed.set entry above the cursor.

#908 invariants intact: single store-side allocator, atomic allocate+enqueue under
flock, arrow-1 no-burn (pending write still FIRST, still aborts before any cursor
advance), anti-swallow <=consumed fail-loud, W2 contiguous-prefix CONSUMED. The
cursor stays the sole COMMIT point, so a pending-ahead state is exactly the one
#908 already tolerates on its observed.set-failure path. On-disk format UNCHANGED
(read-compatible).

Test: new T11 in test-wake-store-ack.sh forces the final cursor _atomic_write to
fail (bind-mount EBUSY over observed_seq inside an unshare mount+user namespace,
seeded so the cursor READ still succeeds) after pending+observed.set succeed, and
asserts fail-loud + observed.set/cursor consistency + intact consumed prefix. RED
against pre-#917 code; all store-ack/race/reconcile/detector groups stay GREEN.
manifest bumped 0.6.8 -> 0.6.9.

Closes #917
Part of #892

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0158NZqN2n2ymKFeJAZ4GUCb
This commit is contained in:
mosaic-coder
2026-07-26 09:18:59 -05:00
parent 17087efe15
commit eb37eae1f8
3 changed files with 133 additions and 5 deletions

View File

@@ -174,8 +174,29 @@
# WAKE_VERIFY_USE_SYSTEMCTL pattern) — F7 is encoded in the installer,
# not operator memory. framework-manifest.txt, the install-ordering-
# guard, and the manifest parity contract are all UNCHANGED.
# 0.6.9 #917 store.sh cmd_enqueue — HARDEN the final observed_seq cursor write
# (defense-in-depth, surfaced by the #915 review obs#2; non-blocking).
# The final cursor _atomic_write was the ONE durable write not wrapped
# in a failure check and was cross-file non-atomic with the observed.set
# write just before it. Now (a) the cursor write is GATED like the
# pending/observed.set writes (#908) — a cursor-write failure is
# FAIL-LOUD (non-zero + diagnostic), never silently swallowed into a
# spurious success while the allocation stayed uncommitted; and (b) on
# cursor-write failure observed.set is ROLLED BACK to its pre-write
# snapshot, so observed.set and the cursor can never be left cross-file
# inconsistent (observed.set ahead of a cursor that never committed) —
# they BOTH advance or NEITHER does. #908 is UNCHANGED: single store-side
# allocator, atomic allocate+enqueue under flock, arrow-1 no-burn
# (pending write still FIRST and its failure still aborts before any
# cursor advance), anti-swallow ≤consumed fail-loud, and the W2
# contiguous-prefix CONSUMED contract all intact. The cursor remains the
# sole COMMIT point (an uncommitted pending entry is re-derived/reconciled,
# never consumed), so a pending-ahead state is exactly the one #908 already
# tolerates on its observed.set-failure path. ON-DISK FORMAT UNCHANGED
# (read-compatible; a store written by older code reads identically). Only
# store.sh (+ test-wake-store-ack.sh T11) changed.
component=wake
version=0.6.8
version=0.6.9
# Watch-list schema this component consumes, and the INCLUSIVE range of
# schema_version values it supports. A wake-watch-list.json whose schema_version
@@ -192,7 +213,9 @@ schema_max=1
# Pieces shipped by this component version (informational):
# store.sh A2 — three-cursor durable store + drain lib. Stale-tmp reaping is
# OFF the hot enqueue path; `init` performs the age-scoped
# maintenance reap (#927). (W2, #927)
# maintenance reap (#927). enqueue's final observed_seq cursor
# write is GATED fail-loud + rolls observed.set back on failure so
# the two never diverge (#917). (W2, #927, #917)
# ack.sh A4 — RECEIVED/CONSUMED ack-wrapper (local-write + ship). (W2)
# digest.sh A3 — cumulative-state digest renderer (hard locators,
# two-tier trust, injection/secret scrub). PER-ENTRY