fix(wake): #908 unify observed_seq on a single store-side allocator (dissolve detector-private-counter seam) (#915)
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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user