fix(wake): #944 path becomes a hard-locator arm — detector-shape actionable entries can now pass the §2.1 gate + version 0.6.14
ci/woodpecker/pr/ci Pipeline was canceled
ci/woodpecker/pr/ci Pipeline was canceled
The _has_hard_locator gate tested only repo+issue / 40-hex sha / file — the forge locator vocabulary. detector.sh (A1) builds board_file locators as kind/id/observed_hash + path (+ snapshot_sha/_ts when attested, #940): zero keys in common, so a class=actionable board_file entry could NEVER pass and every heartbeat-planning delta dead-lettered (live seqs 63/68, dragon-lin, 2026-07-30). Latent since #920; the #920 harness pinned the detector pilot shape as its malformed example, ratifying the gap. Fix: `path` becomes a hard-locator arm — and ONLY path. It carries the same one-call re-verify precision as `file` (re-read <path>; upgrades to git show <snapshot_sha>:<path> when snapshot-attested). Deliberate NON-arms: observed_hash (content hash, not an address) and bare path-less snapshot_sha (would widen the gate past the board_file vocabulary — review-adopted criterion). Quarantine is a render-time filter (entries never leave pending.jsonl; dead-letter.jsonl is ledger + alarm-dedup only), so existing UNCONSUMED dead-letters re-deliver automatically on the first post-upgrade drain. Consumed-past dead-letters are not requeued. Tests: Q11 positive control renders the live seq-68 entry VERBATIM as CLAIM@seq (real detector emission, not a hand-built dict); negative controls assert address-free and bare-snapshot_sha siblings still quarantine. Q1/Q6–Q9 fixtures moved off the now-valid path-bearing shape. RED at base: exactly the 8 Q11 assertions fail; Q1–Q10 pass under both. Closes #944 Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01NsKce8iZuSuRnu3gVMCBKB
This commit is contained in:
co-authored by
Claude Fable 5
parent
539b475a92
commit
e75db22972
@@ -4,12 +4,19 @@
|
||||
# blocking) + reconciler ENUMERATIONS render ORIENTATION-tier.
|
||||
#
|
||||
# Each test asserts ONE invariant and goes RED against the pre-#920 digest.sh:
|
||||
# Q1 (a) QUARANTINE + REST-DELIVERS: a malformed `actionable` carrying the live
|
||||
# pilot's EXACT locator shape {kind,id,path,observed_hash} (no hard
|
||||
# locator, no reconciled marker) is DEAD-LETTERED + alarmed AND EXCLUDED,
|
||||
# while a clean valid sibling in the SAME drain still renders (exit 0).
|
||||
# Q1 (a) QUARANTINE + REST-DELIVERS: a malformed `actionable` carrying NO hard
|
||||
# locator ({kind,id,observed_hash} — a content hash but no ADDRESS, no
|
||||
# reconciled marker) is DEAD-LETTERED + alarmed AND EXCLUDED, while a
|
||||
# clean valid sibling in the SAME drain still renders (exit 0).
|
||||
# RED baseline: the old whole-digest exit-4 delivered NOTHING (the live
|
||||
# head-of-line-blocking wedge). (#920 FIX1)
|
||||
# [#944 AMENDMENT: at #920 this fixture pinned the live pilot's shape
|
||||
# {kind,id,path,observed_hash} as the malformed case — ratifying a gate
|
||||
# the detector's own locators could never satisfy (every actionable
|
||||
# heartbeat-planning delta dead-lettered; live seqs 63/68). #944 amends
|
||||
# that ruling: `path` and 40-hex `snapshot_sha` are now hard-locator
|
||||
# arms (see Q11), so the quarantine fixtures here and in Q6-Q9 use
|
||||
# genuinely ADDRESS-FREE shapes instead.]
|
||||
# Q2 (b) ENUM-AS-ORIENTATION: a reconciler enumeration (locators.reconciled==
|
||||
# true) renders as an ORIENTATION-tier pointer and does NOT exit-4 / is
|
||||
# NOT quarantined. RED baseline: reconciled `actionable` + soft locators
|
||||
@@ -53,6 +60,26 @@
|
||||
# (Q6/Q7/Q8 all see 0 captured alarms) and no "FAIL LOUD ... alarm sink"
|
||||
# diagnostic exists to fire (Q9).
|
||||
#
|
||||
# #944 (unsatisfiable-gate fix): _has_hard_locator gains the `path` arm — and
|
||||
# ONLY that arm — covering the detector-built board_file vocabulary.
|
||||
# Q11 POSITIVE CONTROL + RETAINED NEGATIVES, one drain: (a) the live
|
||||
# seq-68 entry VERBATIM (the exact production entry that dead-lettered
|
||||
# under the unsatisfiable gate: kind/id/observed_hash + path +
|
||||
# snapshot_sha + snapshot_ts, class=actionable, as detector.sh
|
||||
# actually emits it — NOT a hand-built dict) must RENDER as a
|
||||
# CLAIM@seq with the one-call `git show <snapshot_sha>:<path>`
|
||||
# re-verify hint — the assertion is the rendered claim, not merely
|
||||
# the predicate returning true; (b) a path-only sibling (no snapshot
|
||||
# attestation — a pre-#940 adapter or a dropped attestation) must
|
||||
# ALSO render, with the "re-read <path>" hint; (c) an address-free
|
||||
# sibling ({kind,id,observed_hash}) must STILL quarantine + route its
|
||||
# own alarm — observed_hash is a content hash, not an address; (d) a
|
||||
# bare path-less 40-hex snapshot_sha sibling must ALSO still
|
||||
# quarantine — the widened gate must not widen PAST the board_file
|
||||
# vocabulary (review-adopted criterion). RED baseline: pre-#944
|
||||
# digest.sh dead-letters (a) and (b) — an assertion nobody has seen
|
||||
# succeed is as unproven as one nobody has seen fail.
|
||||
#
|
||||
# Hermetic: feeds controlled JSONL via `digest.sh render --from-file` — NO store,
|
||||
# NO network, NO openssl (so it runs identically under the CI openssl-mask).
|
||||
#
|
||||
@@ -105,15 +132,16 @@ fresh_home() {
|
||||
# dlq HOME — the dead-letter path for the default agent under HOME.
|
||||
dlq() { printf '%s/default/dead-letter.jsonl' "$1"; }
|
||||
|
||||
echo "== Q1 (a): malformed {kind,id,path,observed_hash} QUARANTINES; clean sibling STILL delivers =="
|
||||
echo "== Q1 (a): malformed address-free {kind,id,observed_hash} QUARANTINES; clean sibling STILL delivers =="
|
||||
(
|
||||
home="$(fresh_home q1)"
|
||||
export WAKE_STATE_HOME="$home"
|
||||
unset WAKE_AGENT
|
||||
f="$TMP_ROOT/q1.jsonl"
|
||||
# The live pilot's EXACT malformed shape (no reconciled marker) + a clean sibling.
|
||||
# An ADDRESS-FREE malformed shape (no reconciled marker) + a clean sibling.
|
||||
# [#944: the original fixture carried `path`, which is now a hard-locator arm.]
|
||||
{
|
||||
printf '%s\n' '{"observed_seq":1,"class":"actionable","locators":{"kind":"repo","id":"MALFORMED-Q","path":"docs/x.md","observed_hash":"deadbeef"},"emit_ts":1}'
|
||||
printf '%s\n' '{"observed_seq":1,"class":"actionable","locators":{"kind":"repo","id":"MALFORMED-Q","observed_hash":"deadbeef"},"emit_ts":1}'
|
||||
printf '{"observed_seq":2,"class":"actionable","locators":{"sha":"%s","file":"src/a.ts"},"emit_ts":1}\n' "$SHA40"
|
||||
} >"$f"
|
||||
err="$TMP_ROOT/q1.err"
|
||||
@@ -209,7 +237,7 @@ echo "== Q6 (a): dead-lettered entry routes EXACTLY ONE off-host alarm (payload
|
||||
export WAKE_STATE_HOME="$home"
|
||||
unset WAKE_AGENT
|
||||
f="$TMP_ROOT/q6.jsonl"
|
||||
printf '%s\n' '{"observed_seq":21,"class":"actionable","locators":{"kind":"repo","id":"DLQ-Q6","path":"x.md","observed_hash":"aaaa"},"emit_ts":1}' >"$f"
|
||||
printf '%s\n' '{"observed_seq":21,"class":"actionable","locators":{"kind":"repo","id":"DLQ-Q6","observed_hash":"aaaa"},"emit_ts":1}' >"$f"
|
||||
ALARM_OUT="$TMP_ROOT/q6.alarm.jsonl"
|
||||
export ALARM_OUT
|
||||
: >"$ALARM_OUT"
|
||||
@@ -232,7 +260,7 @@ echo "== Q7 (b): re-draining the SAME still-dead-lettered entry N times routes Z
|
||||
export WAKE_STATE_HOME="$home"
|
||||
unset WAKE_AGENT
|
||||
f="$TMP_ROOT/q7.jsonl"
|
||||
printf '%s\n' '{"observed_seq":22,"class":"actionable","locators":{"kind":"repo","id":"DLQ-Q7","path":"y.md","observed_hash":"bbbb"},"emit_ts":1}' >"$f"
|
||||
printf '%s\n' '{"observed_seq":22,"class":"actionable","locators":{"kind":"repo","id":"DLQ-Q7","observed_hash":"bbbb"},"emit_ts":1}' >"$f"
|
||||
ALARM_OUT="$TMP_ROOT/q7.alarm.jsonl"
|
||||
export ALARM_OUT
|
||||
: >"$ALARM_OUT"
|
||||
@@ -252,8 +280,8 @@ echo "== Q8 (c): a NEW distinct dead-lettered entry routes its OWN one alarm (de
|
||||
unset WAKE_AGENT
|
||||
f1="$TMP_ROOT/q8a.jsonl"
|
||||
f2="$TMP_ROOT/q8b.jsonl"
|
||||
printf '%s\n' '{"observed_seq":31,"class":"actionable","locators":{"kind":"repo","id":"DLQ-Q8A","path":"a.md","observed_hash":"c1"},"emit_ts":1}' >"$f1"
|
||||
printf '%s\n' '{"observed_seq":32,"class":"actionable","locators":{"kind":"repo","id":"DLQ-Q8B","path":"b.md","observed_hash":"c2"},"emit_ts":1}' >"$f2"
|
||||
printf '%s\n' '{"observed_seq":31,"class":"actionable","locators":{"kind":"repo","id":"DLQ-Q8A","observed_hash":"c1"},"emit_ts":1}' >"$f1"
|
||||
printf '%s\n' '{"observed_seq":32,"class":"actionable","locators":{"kind":"repo","id":"DLQ-Q8B","observed_hash":"c2"},"emit_ts":1}' >"$f2"
|
||||
ALARM_OUT="$TMP_ROOT/q8.alarm.jsonl"
|
||||
export ALARM_OUT
|
||||
: >"$ALARM_OUT"
|
||||
@@ -273,7 +301,7 @@ echo "== Q9 (d): WAKE_ALARM_SINK_CMD unconfigured OR unreachable -> FAIL LOUD (n
|
||||
export WAKE_STATE_HOME="$home"
|
||||
unset WAKE_AGENT
|
||||
f="$TMP_ROOT/q9.jsonl"
|
||||
printf '%s\n' '{"observed_seq":41,"class":"actionable","locators":{"kind":"repo","id":"DLQ-Q9","path":"z.md","observed_hash":"dddd"},"emit_ts":1}' >"$f"
|
||||
printf '%s\n' '{"observed_seq":41,"class":"actionable","locators":{"kind":"repo","id":"DLQ-Q9","observed_hash":"dddd"},"emit_ts":1}' >"$f"
|
||||
# (a) UNCONFIGURED alarm sink.
|
||||
unset WAKE_ALARM_SINK_CMD
|
||||
err_a="$TMP_ROOT/q9a.err"
|
||||
@@ -337,6 +365,59 @@ echo "== Q10: snapshot metadata (#940) — snapshot_sha/snapshot_ts render on th
|
||||
true
|
||||
) && ok
|
||||
|
||||
echo "== Q11 (#944): REAL detector-shape actionable RENDERS as CLAIM@seq; address-free + bare-snapshot_sha siblings STILL quarantine =="
|
||||
(
|
||||
home="$(fresh_home q11)"
|
||||
export WAKE_STATE_HOME="$home"
|
||||
unset WAKE_AGENT
|
||||
f="$TMP_ROOT/q11.jsonl"
|
||||
{
|
||||
# (a) the LIVE seq-68 entry VERBATIM — the exact production entry that
|
||||
# dead-lettered on the mos-dt lane under the unsatisfiable gate
|
||||
# (dragon-lin dead-letter.jsonl, 2026-07-30). Detector-emitted shape,
|
||||
# not a hand-built dict.
|
||||
printf '%s\n' '{"observed_seq":68,"locators":{"kind":"board_file","id":"heartbeat-planning","observed_hash":"2e85f2474001961e920976a91981eca5bb86a5ff0df044e082a1e1f2dc7493b5","snapshot_sha":"55d4909569d2b5fbccfec49ec9ca83db5049f3ce","snapshot_ts":1785414523,"path":"docs/scratchpads/heartbeat-planning"},"class":"actionable","emit_ts":1785415057,"hmac":""}'
|
||||
# (b) same vocabulary WITHOUT snapshot attestation (pre-#940 adapter or
|
||||
# dropped-as-malformed attestation) — must pass via the path arm alone.
|
||||
printf '%s\n' '{"observed_seq":69,"class":"actionable","locators":{"kind":"board_file","id":"PILOT-LOCAL","observed_hash":"abcd1234","path":"BOARD.md"},"emit_ts":2}'
|
||||
# (c) ADDRESS-FREE — the retained negative: a content hash is not an address.
|
||||
printf '%s\n' '{"observed_seq":70,"class":"actionable","locators":{"kind":"board_file","id":"ADDR-FREE","observed_hash":"ffff0000"},"emit_ts":2}'
|
||||
# (d) bare path-less 40-hex snapshot_sha — must NOT pass: the widened gate
|
||||
# must not widen past the board_file vocabulary.
|
||||
printf '%s\n' '{"observed_seq":71,"class":"actionable","locators":{"kind":"board_file","id":"SNAP-ONLY","observed_hash":"eeee1111","snapshot_sha":"55d4909569d2b5fbccfec49ec9ca83db5049f3ce"},"emit_ts":2}'
|
||||
} >"$f"
|
||||
ALARM_OUT="$TMP_ROOT/q11.alarm.jsonl"
|
||||
export ALARM_OUT
|
||||
: >"$ALARM_OUT"
|
||||
export WAKE_ALARM_SINK_CMD="$CAPTURE_ALARM"
|
||||
err="$TMP_ROOT/q11.err"
|
||||
out="$("$DIGEST" render --from-file "$f" --agent default 2>"$err")"
|
||||
rc=$?
|
||||
[ "$rc" -eq 0 ] || fail_msg "Q11: render must exit 0, got rc=$rc"
|
||||
# (a) POSITIVE: the live entry RENDERS as an actionable claim (not merely
|
||||
# passes the predicate) with the one-call git-show re-verify hint.
|
||||
printf '%s' "$out" | grep -q 'seq 68 — CLAIM@seq' || fail_msg "Q11a: the live seq-68 detector-shape entry must RENDER as CLAIM@seq (positive control)"
|
||||
printf '%s' "$out" | grep -q 'git show 55d4909569d2b5fbccfec49ec9ca83db5049f3ce:docs/scratchpads/heartbeat-planning' \
|
||||
|| fail_msg "Q11a: the rendered claim must carry the one-call re-verify hint git show <snapshot_sha>:<path>"
|
||||
# (b) POSITIVE: path arm alone suffices; hint degrades to one-call re-read.
|
||||
printf '%s' "$out" | grep -q 'seq 69 — CLAIM@seq' || fail_msg "Q11b: a path-only detector-shape entry must RENDER as CLAIM@seq (path arm)"
|
||||
printf '%s' "$out" | grep -q 're-read BOARD.md' || fail_msg "Q11b: the path-only claim must carry the one-call re-read <path> hint"
|
||||
n_claims="$(printf '%s\n' "$out" | grep -c 'CLAIM@seq' || true)"
|
||||
[ "$n_claims" = "2" ] || fail_msg "Q11: EXACTLY the two valid entries must render as CLAIM@seq (got $n_claims)"
|
||||
# (c)+(d) NEGATIVES retained: both quarantine, each with its OWN alarm.
|
||||
printf '%s' "$out" | grep -q 'ADDR-FREE' && fail_msg "Q11c: the address-free entry must be EXCLUDED from the digest"
|
||||
printf '%s' "$out" | grep -q 'SNAP-ONLY' && fail_msg "Q11d: the bare path-less snapshot_sha entry must be EXCLUDED (gate must not widen past board_file)"
|
||||
grep -q 'ADDR-FREE' "$(dlq "$home")" 2>/dev/null || fail_msg "Q11c: the address-free entry must be DEAD-LETTERED"
|
||||
grep -q 'SNAP-ONLY' "$(dlq "$home")" 2>/dev/null || fail_msg "Q11d: the bare snapshot_sha entry must be DEAD-LETTERED"
|
||||
grep -q 'heartbeat-planning' "$(dlq "$home")" 2>/dev/null && fail_msg "Q11a: the valid live entry must NOT be dead-lettered"
|
||||
grep -q 'PILOT-LOCAL' "$(dlq "$home")" 2>/dev/null && fail_msg "Q11b: the valid path-only entry must NOT be dead-lettered"
|
||||
n_alarms="$(grep -c . "$ALARM_OUT" 2>/dev/null || true)"
|
||||
[ "$n_alarms" = "2" ] || fail_msg "Q11: exactly the two invalid entries must alarm (got $n_alarms) [$(cat "$ALARM_OUT" 2>/dev/null)]"
|
||||
grep -q '"observed_seq":70' "$ALARM_OUT" 2>/dev/null || fail_msg "Q11c: seq 70's own alarm must be present"
|
||||
grep -q '"observed_seq":71' "$ALARM_OUT" 2>/dev/null || fail_msg "Q11d: seq 71's own alarm must be present"
|
||||
true
|
||||
) && ok
|
||||
|
||||
echo
|
||||
if [ -s "$FAILFILE" ]; then
|
||||
echo "wake digest-quarantine harness: FAILED ($(grep -c . "$FAILFILE") assertion(s))" >&2
|
||||
|
||||
Reference in New Issue
Block a user