Compare commits

..
1 Commits
Author SHA1 Message Date
Jason WoltjeandClaude Fable 5 ac3b972273 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 successful
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
Written-by: pepper (sb-it-1-dt)
2026-07-30 08:07:31 -05:00
@@ -73,14 +73,10 @@
# 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)
# bare path-less snapshot_sha siblings must ALSO still quarantine —
# the widened gate must not widen PAST the board_file vocabulary
# (review-adopted criterion) — asserted at THREE lengths (7-char
# abbreviation, 40-hex, 64-char sha-256) spanning the detector's
# actual attestation validation ^[0-9a-f]{7,64}$ (detector.sh), so
# the assertion distinguishes "no snapshot_sha arm" from "an arm
# present but length-gated". RED baseline: pre-#944
# 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.
#
@@ -386,15 +382,9 @@ echo "== Q11 (#944): REAL detector-shape actionable RENDERS as CLAIM@seq; addres
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 snapshot_sha — must NOT pass: the widened gate must
# not widen past the board_file vocabulary. Asserted at all three
# lengths the detector's attestation validation ^[0-9a-f]{7,64}$
# admits: a 40-hex sha-1, a 7-char abbreviation, a 64-char sha-256.
# One length alone cannot distinguish "no arm" from "arm present but
# length-gated" (enumeration finding E1).
printf '%s\n' '{"observed_seq":71,"class":"actionable","locators":{"kind":"board_file","id":"SNAP-ONLY-40","observed_hash":"eeee1111","snapshot_sha":"55d4909569d2b5fbccfec49ec9ca83db5049f3ce"},"emit_ts":2}'
printf '%s\n' '{"observed_seq":72,"class":"actionable","locators":{"kind":"board_file","id":"SNAP-ONLY-7","observed_hash":"eeee2222","snapshot_sha":"55d4909"},"emit_ts":2}'
printf '%s\n' '{"observed_seq":73,"class":"actionable","locators":{"kind":"board_file","id":"SNAP-ONLY-64","observed_hash":"eeee3333","snapshot_sha":"55d4909569d2b5fbccfec49ec9ca83db5049f3ce55d4909569d2b5fbccfec49e"},"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
@@ -416,19 +406,15 @@ echo "== Q11 (#944): REAL detector-shape actionable RENDERS as CLAIM@seq; addres
[ "$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: no bare path-less snapshot_sha entry may appear in the digest (gate must not widen past board_file)"
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"
for snap_id in SNAP-ONLY-40 SNAP-ONLY-7 SNAP-ONLY-64; do
grep -q "$snap_id" "$(dlq "$home")" 2>/dev/null || fail_msg "Q11d: the bare snapshot_sha entry ($snap_id) must be DEAD-LETTERED"
done
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" = "4" ] || fail_msg "Q11: exactly the four invalid entries must alarm (got $n_alarms) [$(cat "$ALARM_OUT" 2>/dev/null)]"
[ "$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"
for snap_seq in 71 72 73; do
grep -q "\"observed_seq\":$snap_seq" "$ALARM_OUT" 2>/dev/null || fail_msg "Q11d: seq $snap_seq's own alarm must be present"
done
grep -q '"observed_seq":71' "$ALARM_OUT" 2>/dev/null || fail_msg "Q11d: seq 71's own alarm must be present"
true
) && ok