fix(wake): #908 unify observed_seq on a single store-side allocator (dissolve detector-private-counter seam) (#915)
All checks were successful
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful

Co-authored-by: jason.woltje <jason@diversecanvas.com>
Co-committed-by: jason.woltje <jason@diversecanvas.com>
This commit was merged in pull request #915.
This commit is contained in:
2026-07-26 06:02:02 +00:00
committed by Mos
parent 2378665eaf
commit e2ec927b1c
8 changed files with 487 additions and 157 deletions

View File

@@ -13,6 +13,9 @@
# observed_seq NOT advanced (the G2a invariant) (§4/G2a)
# D7 anchor-scoped hashing: edit OUTSIDE the anchor is a no-op;
# edit INSIDE the anchor is a delta (§1.1 (a))
# D8 MIGRATION-RESTART: post-migration first delta ALLOCATES a seq
# > consumed via the single store-side allocator — never the
# silent seq<=consumed no-op the old private counter caused (#908 arrow 3)
#
# Uses FAKE/STUB sources only (no live network). Isolated: every test runs
# against a fresh WAKE_STATE_HOME temp dir.
@@ -62,7 +65,12 @@ fresh_state() {
# depth — pending_depth reported by the store for the current namespace.
depth() { "$STORE" cursors | sed -n 's/pending_depth=//p'; }
det_seq() { "$DET" cursors | sed -n 's/detector_observed_seq=//p'; }
# observed_seq is the STORE's single source of truth now (#908): the detector
# keeps no private counter, so `detector.sh cursors` reports the store cursors.
# This asserts the SAME invariant the old detector-private counter did (a delta
# advances observed_seq by exactly one; no-change/first-seen/fail-loud do not) —
# just read from the unified store allocator.
det_seq() { "$DET" cursors | sed -n 's/^observed_seq=//p'; }
# write_watchlist FILE VERSION — a minimal valid watch-list with one repo source
# ("r1") plus optionally a board_file with an anchor ("b1").
@@ -332,6 +340,64 @@ echo "== D7: anchor-scoped hashing (edit outside anchor = no-op; inside = delta)
[ "$(depth)" -ge 1 ] || fail_msg "D7: in-anchor edit must enqueue, got depth $(depth)"
) && ok
echo "== D8: MIGRATION-RESTART — post-migration first delta ALLOCATES > consumed, never a silent no-op (#908, arrow #3) =="
(
WAKE_STATE_HOME="$(fresh_state d8)"
export WAKE_STATE_HOME
unset WAKE_AGENT
stub="$TMP_ROOT/d8stub"
mkdir -p "$stub"
make_stub "$stub"
export WAKE_DETECTOR_SOURCE_CMD="$stub/adapter.sh"
# A watch-list with a SINGLE repo source (clean seq accounting).
wl="$TMP_ROOT/d8.json"
cat >"$wl" <<'EOF'
{ "schema_version": 1,
"repos": [ { "id": "r1", "class": "actionable" } ],
"watches": [ { "lane": "L", "sources": [ { "kind": "repo", "id": "r1" } ] } ] }
EOF
export WAKE_WATCH_LIST="$wl"
# --- simulate adopt -> migrate -> restart --------------------------------
# §5 migration seeds the STORE cursors (consumed_seq=N, observed_seq=N) so the
# already-consumed prefix is preserved. It seeds NO detector-private counter —
# because after #908 the detector HAS none; the store cursor is the single SoT.
# This is the exact pilot state that used to silently swallow the first delta.
sd="$WAKE_STATE_HOME/default"
mkdir -p "$sd"
printf '5' >"$sd/consumed_seq"
printf '5' >"$sd/observed_seq"
: >"$sd/observed.set" # prefix <=5 fully consumed; live window empty
: >"$sd/pending.jsonl"
# "Restart" the detector: fresh detector-local state (no hash yet, and — the
# killer precondition — NO private observed_seq counter exists).
printf 'STATE-A\n' >"$stub/repo_r1"
"$DET" poll-once >/dev/null 2>&1 || fail_msg "D8: post-restart baseline pass failed"
# First-seen baselines silently (deliver-on-new); the store cursor is untouched.
[ "$(depth)" = "0" ] || fail_msg "D8: baseline must not enqueue, got depth $(depth)"
[ "$(det_seq)" = "5" ] || fail_msg "D8: migration-seeded observed_seq must be preserved at 5, got $(det_seq)"
# --- the real, un-consumed obligation: ONE delta -------------------------
printf 'STATE-B\n' >"$stub/repo_r1"
"$DET" poll-once >/dev/null 2>&1 || fail_msg "D8: post-migration delta pass failed"
# THE KILLER ASSERTION: the store (sole allocator) allocates 6 = observed(5)+1,
# which is > consumed(5). Under the OLD private-counter detector this delta got
# seq 1 (counter restarted at 0) which is <= consumed 5 and was SILENTLY
# swallowed by store enqueue's seq<=consumed no-op (depth stayed 0). With the
# unified allocator it CANNOT be swallowed: it enqueues and WOULD wake.
[ "$(det_seq)" = "6" ] || fail_msg "D8: post-migration first delta must ALLOCATE observed_seq 6 (>consumed 5), got $(det_seq) — a restart-at-0 private counter would give <=5 and be swallowed"
[ "$(depth)" = "1" ] || fail_msg "D8: post-migration delta must ENQUEUE a real obligation (depth 1), NOT be a silent no-op, got depth $(depth)"
# And it is genuinely deliverable (a locator-bearing pending entry the consumer
# would wake on), not swallowed.
entry="$("$STORE" drain)"
echo "$entry" | jq -e 'select(.observed_seq==6 and .locators.kind=="repo" and .locators.id=="r1")' >/dev/null \
|| fail_msg "D8: the enqueued post-migration obligation must be a real deliverable at observed_seq=6 [$entry]"
# Contiguous-prefix contract still holds: CONSUMED 6 succeeds (6 is observed,
# 5 is the consumed prefix — no interior gap).
"$STORE" consume --upto 6 >/dev/null 2>&1 || fail_msg "D8: CONSUMED 6 must succeed over the contiguous prefix after the delta"
) && ok
echo
if [ -s "$FAILFILE" ]; then
echo "wake detector harness: FAILED ($(grep -c . "$FAILFILE") assertion(s))" >&2