Compare commits

...
Author SHA1 Message Date
Jason WoltjeandClaude Fable 5 fa36da8087 fix(wake): #944 path becomes a hard-locator arm — detector-shape actionable entries can now pass the §2.1 gate + version 0.6.14
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). The file-level HARD LOCATORS doc block now
names all four arms (wake-judge R2(a): shipping the fix while leaving
doc-vs-producer drift in the same file would re-seed the defect class).

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 path-less snapshot_sha siblings
still quarantine — the snapshot_sha control at all THREE lengths the
detector's attestation validation ^[0-9a-f]{7,64}$ admits (7-char
abbreviation, 40-hex, 64-char sha-256), so it distinguishes "no arm"
from "arm present but length-gated" (review finding E1). Q1/Q6–Q9
fixtures moved off the now-valid path-bearing shape. Q2's ENUM-B is
ADDRESS-FREE (wake-judge F1): with `path` an arm, path-bearing
reconciled fixtures no longer exercise the exemption at the gate —
mutation test (exemption disabled) now dies on the gate-level
assertion again, not merely tier labels. RED at base: exactly the 8
Q11 assertions fail; Q1–Q10 pass under both digests.

Doc follow-up: #948 amends CONVERGED-DESIGN.md §2.1 (add `path`; state
the operative test as "one targeted call, never a search") — sequenced
after the reseed as a live delivery test of the fixed gate.

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:45:34 -05:00
mos-dt-0andMos 539b475a92 fix(wake): #943 whole-string validation for WAKE_SNAPSHOT_TS_FUTURE_SLACK + version 0.6.13
grep is line-oriented, so a multi-line knob value passed the per-line anchors and was still fatal in arithmetic. Replaced with a case pattern matching the whole string, so an embedded or leading newline rejects. Manifest bumped 0.6.12 -> 0.6.13: three materially different detectors had shipped under one version string, and version= is the component sole self-identity claim.

Authored-by: pepper
Reviewed-by: mos-dt (independent, at this head; transfer proven by blob-hash equality)
Merged-by: Mos
Co-authored-by: mos-dt-0 <[email protected]>
2026-07-30 11:56:18 +00:00
mos-dt-0andMos 3e47fc076f fix(wake): #942 harden WAKE_SNAPSHOT_TS_FUTURE_SLACK — validate shape AND force base-10
The slack knob was interpolated raw into $((...)) under set -u: a malformed value was FATAL to the poll, falsifying the poll-never-fails invariant, and a negative value inverted the guard to deny-all. Shape validation alone was insufficient — bash reads a leading zero as octal, so 08/09 passed the regex yet were fatal and 0300 silently meant 192. Now validated ^[0-9]{1,9}$ with a loud fallback to 300, then forced to base-10 via 10# so the knob means what the operator wrote.

Authored-by: pepper
Reviewed-by: mos-dt (independent, found both the original defect and the radix residual)
Merged-by: Mos
Co-authored-by: mos-dt-0 <[email protected]>
2026-07-30 11:17:51 +00:00
mos-dt-0andMos 8710d0f6d7 feat(wake): #940 snapshot-datable digests — fd-3 snapshot-metadata channel (#941)
Adapter emits snapshot sha/ts out-of-band on fd 3 so a changing value never enters the delta-gate hash. Detector validates advisorily (sha regex, epoch sanity before arithmetic, future-skew slack); malformed metadata is dropped loudly and never gates the wake. Digest renders snapshot_sha/snapshot_ts plus a git-show re-verify hint. Adapters that never write fd 3 are byte-identical.

Reviewed-by: Mos (design, independent)
Reviewed-by: mos-dt (artifact, hardening §2)
Co-authored-by: mos-dt-0 <[email protected]>
2026-07-30 10:55:18 +00:00
5 changed files with 633 additions and 26 deletions
@@ -225,15 +225,31 @@ _poll_source() {
# The source definition is handed to the adapter on STDIN via a temp FILE
# (not a pipe): an adapter that ignores stdin must not take SIGPIPE and read
# back as a spurious "source error" under `pipefail`.
local raw rc deftmp
local raw rc deftmp metatmp
mkdir -p "$DET_DIR"
# _wake_tmp_prefix is provided by _wake-common.sh (sourced above).
# shellcheck disable=SC2154
deftmp="$(mktemp "$DET_DIR/${_wake_tmp_prefix}defXXXXXX")" || return 1
printf '%s' "$def" >"$deftmp"
raw="$("$WAKE_DETECTOR_SOURCE_CMD" "$kind" "$id" <"$deftmp" 2>/dev/null)"
# fd 3 is the OUT-OF-BAND snapshot-metadata channel (#940): the adapter MAY
# write one JSON object {"snapshot_sha": "<git commit sha>", "snapshot_ts":
# <epoch>} there. It must stay out of stdout because EVERYTHING on stdout is
# hashed by the delta gate — an in-band tip-commit SHA would advance
# observed_hash on every unrelated push. Adapters that never write fd 3 leave
# the file empty: byte-identical legacy behavior.
metatmp="$(mktemp "$DET_DIR/${_wake_tmp_prefix}metaXXXXXX")" || {
rm -f "$deftmp"
return 1
}
raw="$("$WAKE_DETECTOR_SOURCE_CMD" "$kind" "$id" <"$deftmp" 3>"$metatmp" 2>/dev/null)"
rc=$?
rm -f "$deftmp"
# Slurp + remove the metadata file NOW so every return path below is clean;
# it is parsed only after the fail-loud gates (metadata from a FAILED
# observation is meaningless and must not be trusted or diagnosed).
local rawmeta=""
[ -s "$metatmp" ] && rawmeta="$(cat "$metatmp")"
rm -f "$metatmp"
if [ "$rc" -ne 0 ]; then
echo "detector.sh: FAIL LOUD (G2a) — source '$kind/$id' errored (adapter exit $rc: network/401/403/privacy-404/partial). observed_seq NOT advanced; NOT treated as 'no change'." >&2
return 1
@@ -243,6 +259,72 @@ _poll_source() {
return 1
fi
# --- snapshot metadata (fd 3, #940) — ADVISORY, validated, never gating ----
# Malformed metadata is dropped with a LOUD diagnostic but cannot fail the
# poll or suppress the wake: the obligation never depends on optional dating.
local snap_sha="" snap_ts="" snap_json=""
if [ -n "$rawmeta" ]; then
if snap_json="$(jq -ce '.' <<<"$rawmeta" 2>/dev/null)"; then
snap_sha="$(jq -r 'if (.snapshot_sha|type) == "string" then .snapshot_sha else "" end' <<<"$snap_json")"
snap_ts="$(jq -r 'if (.snapshot_ts|type) == "number" then (.snapshot_ts|floor|tostring) else "" end' <<<"$snap_json")"
if [ -n "$snap_sha" ] && ! printf '%s' "$snap_sha" | grep -Eq '^[0-9a-f]{7,64}$'; then
echo "detector.sh: source '$kind/$id' snapshot_sha rejected (not a 7-64 char lowercase-hex git sha) — snapshot metadata DROPPED, poll continues (#940)." >&2
snap_sha=""
snap_ts=""
fi
# A ts must be a sane positive epoch BEFORE any arithmetic touches it: a
# negative or absurdly large value would make the shell integer comparison
# below error out and silently KEEP the bad ts — validate first, compare after.
if [ -n "$snap_ts" ] && ! printf '%s' "$snap_ts" | grep -Eq '^[0-9]{1,12}$'; then
echo "detector.sh: source '$kind/$id' snapshot_ts rejected (not a sane positive epoch) — snapshot_ts DROPPED, poll continues (#940)." >&2
snap_ts=""
fi
# ts is only meaningful anchored to a revision the consumer can re-verify —
# a bare number with no sha behind it is the weakest possible attestation,
# so ts requires a valid sha (#940 review §2).
if [ -n "$snap_ts" ] && [ -z "$snap_sha" ]; then
echo "detector.sh: source '$kind/$id' snapshot_ts without a valid snapshot_sha — snapshot_ts DROPPED (unverifiable dating), poll continues (#940)." >&2
snap_ts=""
fi
# A FUTURE ts renders a stale snapshot fresher-than-fresh (negative age) —
# wrong in the reassuring direction, the exact failure class #940 fixes.
# Cross-host NTP skew of a few seconds is the NORMAL case, so allow a small
# slack; beyond it, drop the ts (the sha stays: independently verifiable).
if [ -n "$snap_ts" ]; then
# The OPERATOR's knob gets the same discipline as the adapter's ts: it
# is interpolated into $((...)) under set -u, so a non-numeric value
# ('300s', '5m', 'abc') would be FATAL to the poll — the one thing this
# block must never be. Resolve it ONCE, validate, fall back loudly.
local now_s slack slack_ok
slack="${WAKE_SNAPSHOT_TS_FUTURE_SLACK:-300}"
# NOT grep: grep is LINE-oriented, so ^...$ anchors bind per line and a
# multi-line value ($'300\n8') passes the regex yet is FATAL in $((...)).
# The case pattern matches the WHOLE string, newlines included. (snap_ts
# is immune: jq's number type-check above cannot emit an embedded newline.)
case "$slack" in
'' | *[!0-9]*) slack_ok=1 ;;
*) [ "${#slack}" -le 9 ] && slack_ok=0 || slack_ok=1 ;;
esac
if [ "$slack_ok" -ne 0 ]; then
echo "detector.sh: WAKE_SNAPSHOT_TS_FUTURE_SLACK='$slack' is not a plain non-negative integer of at most 9 digits (seconds) — falling back to 300, poll continues (#940)." >&2
slack=300
fi
# Shape validation is not radix validation: bash reads a leading zero as
# OCTAL, so '08'/'09' pass the shape check yet are FATAL in $((...)), and
# '0300' silently means 192. Force base-10 so the knob means what the
# operator wrote (safe: the case pattern above guarantees pure digits).
slack=$((10#$slack))
now_s="$(date +%s)"
if [ "$snap_ts" -gt $((now_s + slack)) ]; then
echo "detector.sh: source '$kind/$id' snapshot_ts is beyond the ${slack}s future-skew allowance (ts=$snap_ts now=$now_s) — snapshot_ts DROPPED, poll continues (#940)." >&2
snap_ts=""
fi
fi
else
echo "detector.sh: source '$kind/$id' wrote UNPARSEABLE snapshot metadata on fd 3 — DROPPED, poll continues (#940)." >&2
fi
fi
# --- anchor-scope (feature (a)) then hash ---------------------------------
local scoped
if [ -n "$anchor" ]; then
@@ -281,12 +363,17 @@ _poll_source() {
local locators emit_ts
emit_ts="$(date +%s)"
# NB: `def` is a reserved word in jq — the source-definition arg is `$sdef`.
# snapshot_sha/snapshot_ts (#940) join only when the adapter attested them.
locators="$(jq -cn \
--arg kind "$kind" \
--arg id "$id" \
--argjson sdef "$def" \
--arg hash "$h" \
--arg ssha "$snap_sha" \
--arg sts "$snap_ts" \
'{kind:$kind, id:$id, observed_hash:$hash}
+ (if $ssha != "" then {snapshot_sha: $ssha} else {} end)
+ (if $sts != "" then {snapshot_ts: ($sts|tonumber)} else {} end)
+ ( $sdef | {repo, path, anchor, remote, branches} | with_entries(select(.value != null)) )')"
local args=(enqueue --locators "$locators" --emit-ts "$emit_ts")
+38 -10
View File
@@ -25,8 +25,8 @@
# sufficiency NEVER exempts a consequential action from its live gate.
#
# HARD LOCATORS (§2.1): every actionable claim MUST carry a precise locator
# (repo + issue#, a 40-char SHA, or file:anchor) so re-verification is ONE
# targeted call. A missing locator = malformed ACTIONABLE entry = FAIL-LOUD.
# (repo + issue#, a 40-char SHA, file:anchor, or path — #944) so re-verification
# is ONE targeted call. A missing locator = malformed ACTIONABLE entry = FAIL-LOUD.
#
# #920 (per-entry quarantine — fail-loud WITHOUT head-of-line blocking): a
# render-refused entry (actionable-tier, no hard locator) is QUARANTINED — durably
@@ -209,13 +209,28 @@ _scrub_free() {
# ---------------------------------------------------------------------------
# _has_hard_locator LOCATORS_JSON — true iff the locators object carries at least
# one PRECISE locator sufficient for one-call re-verification:
# repo + issue (issue#) | 40-hex sha | file (file:anchor).
# one PRECISE locator sufficient for one-call re-verification. BOTH locator
# vocabularies in live use are accepted (the same two _locator_line documents
# under #914b):
# forge shape (§2.1): repo + issue (issue#) | 40-hex sha | file (file:anchor)
# detector shape (#944): path
# The `path` arm was added by #944: detector.sh (A1) builds board_file locators
# as kind/id/observed_hash + path (+ snapshot_sha when adapter-attested, #940)
# — none of which the gate tested — so a class=actionable board_file entry
# could NEVER pass and every heartbeat-planning delta dead-lettered (live seqs
# 63/68). `path` mirrors `file` exactly (same one-call "re-read X" hint below;
# with an attested snapshot_sha the hint upgrades to one-call
# `git show <snapshot_sha>:<path>`). DELIBERATELY NOT ARMS: `observed_hash`
# (a content hash, not an address) and bare `snapshot_sha` without `path`
# (a path-less 40-hex would widen the gate past the board_file vocabulary —
# review-adopted criterion on #944; snapshot_sha's precision is only reachable
# THROUGH a path, so path is the address and snapshot_sha stays a refinement).
_has_hard_locator() {
jq -e '
((.repo // "") != "" and ((.issue // "") | tostring) != "")
or (((.sha // "") | test("^[0-9a-f]{40}$")))
or ((.file // "") != "")
or ((.path // "") != "")
' >/dev/null 2>&1 <<<"$1"
}
@@ -223,19 +238,21 @@ _has_hard_locator() {
# one-targeted-call re-verify hint, where available.
#
# #914b: covers BOTH locator vocabularies actually in use:
# - the HARD-locator shape (§2.1, gated by _has_hard_locator, unchanged):
# - the HARD-locator shape (§2.1, gated by _has_hard_locator):
# repo+issue | 40-hex sha | file(:anchor).
# - the shape detector.sh (A1) actually builds for a `digest`-class entry
# (see detector.sh's enqueue-locators jq filter): kind/id/observed_hash +
# whichever of repo/path/anchor/remote/branches the source def declares.
# None of kind/id/observed_hash/remote/path/branches were recognized here
# before, so a real digest-class pointer rendered a bare empty "locator:"
# line despite the entry carrying real (soft, non-hard) locator data. This
# is display-only: it does NOT feed _has_hard_locator, so the
# ACTIONABLE-tier hard-locator FAIL-LOUD gate is untouched.
# line despite the entry carrying real (soft, non-hard) locator data.
# (At #914b this was display-only; #944 later promoted `path` — and ONLY
# `path` — into _has_hard_locator, since it carries the same one-call
# re-verify precision as `file`. kind/id/observed_hash/remote/branches
# and bare snapshot_sha remain soft/display-only.)
_locator_line() {
local loc="$1" repo issue sha file anchor head parts='' reverify=''
local remote path kind id ohash branches
local remote path kind id ohash branches snap_sha snap_ts
repo="$(jq -r '.repo // ""' <<<"$loc")"
issue="$(jq -r '(.issue // "") | tostring' <<<"$loc")"
sha="$(jq -r '.sha // ""' <<<"$loc")"
@@ -247,6 +264,8 @@ _locator_line() {
kind="$(jq -r '.kind // ""' <<<"$loc")"
id="$(jq -r '(.id // "") | tostring' <<<"$loc")"
ohash="$(jq -r '.observed_hash // ""' <<<"$loc")"
snap_sha="$(jq -r '.snapshot_sha // ""' <<<"$loc")"
snap_ts="$(jq -r '(.snapshot_ts // "") | tostring' <<<"$loc")"
branches="$(jq -r '(.branches // []) | if length > 0 then join(",") else "" end' <<<"$loc" 2>/dev/null || true)"
[ -n "$kind" ] && parts="$parts kind=$(_scrub_inline "$kind")"
[ "$id" != "" ] && parts="$parts id=$(_scrub_inline "$id")"
@@ -273,9 +292,18 @@ _locator_line() {
# git commit SHA — kept distinct from `sha` so it never impersonates one or
# feeds the `git show <sha>` re-verify hint below.
[ -n "$ohash" ] && parts="$parts observed_hash=$(_scrub_inline "$ohash")"
# snapshot_sha/snapshot_ts (#940): the SNAPSHOT'S git commit sha + commit
# epoch, attested by the source adapter at OBSERVE time (out-of-band fd 3,
# detector-validated). Unlike observed_hash this IS a commit sha, so it may
# feed the `git show` re-verify hint; with emit_ts already in the header,
# snapshot age becomes local arithmetic for the consumer.
[ -n "$snap_sha" ] && parts="$parts snapshot_sha=$(_scrub_inline "$snap_sha")"
[ "$snap_ts" != "" ] && parts="$parts snapshot_ts=$(_scrub_inline "$snap_ts")"
# One-targeted-call re-verify hint (best available, most-specific first).
if [ -n "$sha" ] && [ -n "$file" ]; then
reverify="git show $(_scrub_inline "$sha"):$(_scrub_inline "$file")"
elif [ -n "$snap_sha" ] && [ -n "$path" ]; then
reverify="git show $(_scrub_inline "$snap_sha"):$(_scrub_inline "$path")"
elif [ -n "$repo" ] && [ "$issue" != "" ]; then
reverify="issue $(_scrub_inline "$repo")#$(_scrub_inline "$issue")"
elif [ -n "$sha" ]; then
@@ -466,7 +494,7 @@ _quarantine_entry() {
return 0
fi
fi
echo "digest.sh: FAIL-LOUD QUARANTINE (#920) — malformed ACTIONABLE entry at observed_seq=$seq has no §2.1 hard locator (repo+issue#/40-char SHA/file:anchor). DEAD-LETTERED to $dlq and EXCLUDED from this digest; the rest of the cumulative set still renders (no head-of-line block). Re-feed the source with a valid hard locator." >&2
echo "digest.sh: FAIL-LOUD QUARANTINE (#920) — malformed ACTIONABLE entry at observed_seq=$seq has no §2.1 hard locator (repo+issue# / 40-hex sha / file:anchor / path). DEAD-LETTERED to $dlq and EXCLUDED from this digest; the rest of the cumulative set still renders (no head-of-line block). Re-feed the source with a valid hard locator." >&2
# #924 (G2a): route the SAME per-entry alarm off-host too, deduped by
# observed_seq so a still-dead-lettered entry re-drained every tick is
# alarmed off-host EXACTLY ONCE (never once per re-render).
@@ -240,8 +240,115 @@
# guard/witness-marker are REMOVED — T9/T11 now RUN and ASSERT their failure
# paths in every environment including non-priv CI. Changed: _wake-common.sh
# (seam), test-wake-store-ack.sh (T9/T11 conversion).
# 0.6.12 #940 snapshot-datable digests — the adapter-contract fd-3 snapshot-
# metadata channel (wake-pilot finding fw-wake-digest-snapshot-lag:
# a digest's locator carried observed_hash + emit_ts but nothing
# DATING the snapshot, so a consumer could not tell a fresh
# snapshot from one already superseded at delivery without a tool
# call). ADDITIVE + backward-compatible: (a) detector.sh invokes
# the W4 source adapter with fd 3 redirected to a temp file; the
# adapter MAY write one JSON object {"snapshot_sha": "<git commit
# sha>", "snapshot_ts": <epoch>} there. OUT-OF-BAND is load-
# bearing: everything on stdout is hashed by the delta gate, so an
# in-band tip-commit sha would advance observed_hash on every
# unrelated push (spurious delta wake per watched file). Metadata
# is ADVISORY and validated (sha ^[0-9a-f]{7,64}$, ts number):
# malformed metadata is dropped with a LOUD stderr diagnostic but
# NEVER fails the poll or suppresses the wake — the obligation
# never depends on optional dating. Valid fields join the enqueue
# locators; an adapter that never writes fd 3 is byte-identical
# legacy behavior. (b) digest.sh _locator_line renders
# snapshot_sha=/snapshot_ts= (scrubbed) beside observed_hash=, and
# snapshot_sha+path upgrades the one-call re-verify hint to
# `git show <snapshot_sha>:<path>` (snapshot_sha IS a commit sha,
# unlike observed_hash, so it may feed the git hint). With emit_ts
# already in the header, snapshot age becomes local arithmetic for
# the consumer — zero round trips. Watch-list schema UNTOUCHED
# ([1,1] unchanged — adapter contract + locator vocabulary, not
# watch-list config). store.sh/reconcile.sh/beacon.sh UNCHANGED.
# Review hardening (#941 §2): snapshot_ts additionally requires a
# VALID snapshot_sha (a bare number with no revision to re-verify
# against is the weakest attestation — dropped loudly), must be a
# sane positive epoch (^[0-9]{1,12}$ — validated BEFORE the shell
# integer comparison so an absurd value cannot error past it), and
# must not sit beyond a future-skew allowance
# (WAKE_SNAPSHOT_TS_FUTURE_SLACK, default 300 s): a future ts
# yields a NEGATIVE age — stale-reads-fresher-than-fresh, the
# exact failure class #940 fixes. The SLACK knob itself is
# operator input interpolated into arithmetic under set -u, so it
# gets the same discipline (#941 §2 round 2): shape-validated as
# a plain non-negative integer of at most 9 digits, else LOUD
# fallback to 300 — a malformed knob
# ('300s', '5m', 'abc') must never kill the poll, and a negative
# one must never invert the guard into deny-all. Shape validation
# is NOT radix validation (#942 review): bash reads leading zeros
# as OCTAL, so '08'/'09' pass the shape check yet are fatal in
# $((...)) and '0300' silently means 192 — the knob is therefore
# forced base-10 (10#) after validation, so it means what the
# operator wrote. The validator and the consumer must also agree
# on STRING EXTENT (#942 follow-up): grep's ^...$ anchors bind
# per LINE, so a multi-line value ($'300\n8') passed the regex
# whole yet was fatal in $((...)) — validation is a whole-string
# case pattern, not grep, so an embedded newline rejects.
# SKEW GUARANTEE
# (stated, not implied): the future-skew check runs against the
# DETECTOR's clock; consumer-side age arithmetic runs on the
# consumer's. A surviving snapshot_ts is therefore attested only
# to within SLACK seconds of the detector's clock, plus whatever
# skew the consumer's own clock adds — a small NEGATIVE age at
# render is bounded, not impossible; treat age <= 0 as
# "effectively current," never as proof of freshness.
# NOTE for consumers: these fields
# are ADVISORY and their ABSENCE IS DELIBERATELY NOT DIAGNOSTIC —
# a pre-#940 adapter and a dropped-as-malformed attestation render
# identically (no snapshot_* fields); the drop is loud only in the
# detector's own stderr. Do not build load-bearing logic on the
# absence of these fields.
# Changed: detector.sh, digest.sh (+ test-wake-detector.sh
# D10/D11/D12/D13, test-wake-digest-quarantine.sh Q10).
# 0.6.13 #942/#943 SLACK-knob validation hardening, split from 0.6.12 because
# version= is the component's SOLE self-identity claim (no per-file
# hashes here) and two detector-changing merges after the 0.6.12
# stamp had left three materially different detectors under one
# version string (#943 review §2). #942: the knob is resolved once,
# shape-validated, LOUD fallback 300, and forced base-10 (10#) so
# zero-padded values mean what the operator wrote instead of octal.
# #943: validation is a whole-string case pattern, not grep, so an
# embedded newline ($'300\n8' — accepted per-line by grep's ^...$
# anchors, fatal in $((...))) rejects. Full rationale in the knob
# paragraph of the 0.6.12 entry above.
# Changed: detector.sh (+ test-wake-detector.sh D13).
# 0.6.14 #944 the §2.1 hard-locator gate was UNSATISFIABLE for detector-built
# actionable board_file entries: _has_hard_locator tested only
# repo+issue / 40-hex sha / file, while detector.sh (A1) builds
# kind/id/observed_hash + path (+ snapshot_sha/_ts when attested,
# #940) — no key in common, so every class=actionable board_file
# delta was structurally guaranteed to dead-letter (live: mos-dt
# seqs 63/68, 2026-07-30; the only tier with a 30m SLO delivered
# nothing on its only actionable source). Fix: `path` becomes a
# hard-locator arm — and ONLY path: it mirrors `file`'s one-call
# "re-read X" precision, upgrading to one-call
# `git show <snapshot_sha>:<path>` when a snapshot is attested.
# 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) remain NON-arms.
# Quarantine is a RENDER-TIME filter (entries never leave
# pending), so existing UNCONSUMED dead-letters re-deliver
# automatically on the first post-upgrade drain; consumed-past
# dead-letters are not requeued.
# Changed: digest.sh (+ test-wake-digest-quarantine.sh: Q11
# positive control — the live seq-68 entry verbatim must RENDER
# as CLAIM@seq — and Q1/Q6-Q9 fixtures moved off the now-valid
# path-bearing shape onto genuinely address-free shapes,
# amending the #920-era ruling that had pinned the live pilot's
# own locator shape as the malformed example). Doc follow-up:
# #948 amends CONVERGED-DESIGN.md §2.1 to add `path` to the
# hard-locator enumeration and to state the operative test as
# "one targeted call, never a search" (NOT "pins the observed
# state") — sequenced AFTER the reseed so the edit itself is a
# live delivery test of the fixed gate.
component=wake
version=0.6.11
version=0.6.14
# Watch-list schema this component consumes, and the INCLUSIVE range of
# schema_version values it supports. A wake-watch-list.json whose schema_version
@@ -445,6 +445,255 @@ EOF
echo "$err" | grep -qi 'range' || fail_msg "D9: the out-of-range failure must still state it is out of the supported range [$err]"
) && ok
echo "== D10: snapshot metadata (fd 3, #940) — adapter-attested sha/ts land in the enqueued locators =="
(
WAKE_STATE_HOME="$(fresh_state d10)"
export WAKE_STATE_HOME
unset WAKE_AGENT
stub="$TMP_ROOT/d10stub"
mkdir -p "$stub"
# Stub adapter that ALSO writes snapshot metadata out-of-band on fd 3.
cat >"$stub/adapter.sh" <<EOF
#!/usr/bin/env bash
set -u
kind="\$1"; id="\$2"
base="$stub/\${kind}_\${id}"
[ -f "\$base" ] && cat "\$base"
[ -f "\$base.meta" ] && { cat "\$base.meta" >&3; } 2>/dev/null
exit 0
EOF
chmod +x "$stub/adapter.sh"
export WAKE_DETECTOR_SOURCE_CMD="$stub/adapter.sh"
wl="$TMP_ROOT/d10.json"
write_watchlist "$wl" 1
export WAKE_WATCH_LIST="$wl"
printf 'SHA-AAA\n' >"$stub/repo_r1"
printf '## LANE-X\ndecision: hold\n' >"$stub/board_file_b1"
printf '{"snapshot_sha":"0123abc4567890def0123abc4567890def012345","snapshot_ts":1753850000}\n' >"$stub/repo_r1.meta"
"$DET" poll-once >/dev/null || fail_msg "D10: baseline pass failed"
printf 'SHA-BBB\n' >"$stub/repo_r1"
"$DET" poll-once >/dev/null || fail_msg "D10: change pass failed"
entry="$("$STORE" drain)"
echo "$entry" | jq -e 'select(.locators.snapshot_sha=="0123abc4567890def0123abc4567890def012345" and .locators.snapshot_ts==1753850000)' >/dev/null \
|| fail_msg "D10: enqueued locators must carry the adapter-attested snapshot_sha + snapshot_ts [$entry]"
# And the metadata must NOT have leaked into the hashed content: an unchanged
# source with CHANGED metadata is still a no-op (delta gate intact).
d0="$(det_seq)"
printf '{"snapshot_sha":"ffff111122223333444455556666777788889999","snapshot_ts":1753860000}\n' >"$stub/repo_r1.meta"
"$DET" poll-once >/dev/null || fail_msg "D10: metadata-only pass failed"
[ "$(det_seq)" = "$d0" ] || fail_msg "D10: metadata is OUT-OF-BAND — a metadata-only change must NOT be a delta, got seq $(det_seq)"
) && ok
echo "== D11: snapshot metadata is ADVISORY — malformed metadata is dropped LOUDLY, the wake still fires, no fields emitted =="
(
WAKE_STATE_HOME="$(fresh_state d11)"
export WAKE_STATE_HOME
unset WAKE_AGENT
stub="$TMP_ROOT/d11stub"
mkdir -p "$stub"
cat >"$stub/adapter.sh" <<EOF
#!/usr/bin/env bash
set -u
kind="\$1"; id="\$2"
base="$stub/\${kind}_\${id}"
[ -f "\$base" ] && cat "\$base"
[ -f "\$base.meta" ] && { cat "\$base.meta" >&3; } 2>/dev/null
exit 0
EOF
chmod +x "$stub/adapter.sh"
export WAKE_DETECTOR_SOURCE_CMD="$stub/adapter.sh"
wl="$TMP_ROOT/d11.json"
write_watchlist "$wl" 1
export WAKE_WATCH_LIST="$wl"
printf 'SHA-AAA\n' >"$stub/repo_r1"
printf '## LANE-X\ndecision: hold\n' >"$stub/board_file_b1"
"$DET" poll-once >/dev/null 2>&1 || fail_msg "D11: baseline pass failed"
# (a) non-JSON garbage on fd 3.
printf 'this is not json\n' >"$stub/repo_r1.meta"
printf 'SHA-BBB\n' >"$stub/repo_r1"
err="$("$DET" poll-once 2>&1 >/dev/null)"; rc=$?
[ "$rc" -eq 0 ] || fail_msg "D11: malformed metadata must NEVER fail the poll (rc=$rc)"
echo "$err" | grep -qi 'snapshot' || fail_msg "D11: dropping malformed metadata must be LOUD on stderr [$err]"
entry="$("$STORE" drain | tail -1)"
echo "$entry" | jq -e '.locators | has("snapshot_sha") or has("snapshot_ts")' >/dev/null 2>&1 \
&& fail_msg "D11: malformed metadata must emit NO snapshot fields [$entry]"
# (b) valid JSON but a non-hex snapshot_sha -> same: loud drop, wake fires, no fields.
printf '{"snapshot_sha":"NOT-A-HEX-SHA","snapshot_ts":"also-not-a-number"}\n' >"$stub/repo_r1.meta"
printf 'SHA-CCC\n' >"$stub/repo_r1"
err="$("$DET" poll-once 2>&1 >/dev/null)"; rc=$?
[ "$rc" -eq 0 ] || fail_msg "D11: rejected snapshot_sha must NEVER fail the poll (rc=$rc)"
echo "$err" | grep -qi 'snapshot' || fail_msg "D11: rejecting a bad snapshot_sha must be LOUD on stderr [$err]"
entry="$("$STORE" drain | tail -1)"
echo "$entry" | jq -e '.locators | has("snapshot_sha") or has("snapshot_ts")' >/dev/null 2>&1 \
&& fail_msg "D11: rejected metadata must emit NO snapshot fields [$entry]"
[ "$(det_seq)" = "2" ] || fail_msg "D11: both real deltas must still have enqueued (advisory metadata never suppresses a wake), got seq $(det_seq)"
) && ok
echo "== D12: snapshot_ts guards (#940 review §2) — ts requires a sha, a future ts is dropped, an absurd ts cannot reach the comparison =="
(
WAKE_STATE_HOME="$(fresh_state d12)"
export WAKE_STATE_HOME
unset WAKE_AGENT
stub="$TMP_ROOT/d12stub"
mkdir -p "$stub"
cat >"$stub/adapter.sh" <<EOF
#!/usr/bin/env bash
set -u
kind="\$1"; id="\$2"
base="$stub/\${kind}_\${id}"
[ -f "\$base" ] && cat "\$base"
[ -f "\$base.meta" ] && { cat "\$base.meta" >&3; } 2>/dev/null
exit 0
EOF
chmod +x "$stub/adapter.sh"
export WAKE_DETECTOR_SOURCE_CMD="$stub/adapter.sh"
wl="$TMP_ROOT/d12.json"
write_watchlist "$wl" 1
export WAKE_WATCH_LIST="$wl"
printf 'SHA-AAA\n' >"$stub/repo_r1"
printf '## LANE-X\ndecision: hold\n' >"$stub/board_file_b1"
"$DET" poll-once >/dev/null 2>&1 || fail_msg "D12: baseline pass failed"
goodsha="0123abc4567890def0123abc4567890def012345"
# (a) ts WITHOUT sha — the weakest attestation: an unverifiable number with no
# revision to re-verify against. Dropped loudly; the wake still fires.
printf '{"snapshot_ts":1753850000}\n' >"$stub/repo_r1.meta"
printf 'SHA-BBB\n' >"$stub/repo_r1"
err="$("$DET" poll-once 2>&1 >/dev/null)"; rc=$?
[ "$rc" -eq 0 ] || fail_msg "D12: ts-without-sha must NEVER fail the poll (rc=$rc)"
echo "$err" | grep -qi 'without a valid snapshot_sha' || fail_msg "D12: dropping ts-without-sha must be LOUD on stderr [$err]"
entry="$("$STORE" drain | tail -1)"
echo "$entry" | jq -e '.locators | has("snapshot_sha") or has("snapshot_ts")' >/dev/null 2>&1 \
&& fail_msg "D12: ts-without-sha must emit NO snapshot fields [$entry]"
# (b) valid sha + FUTURE ts — a negative age would read fresher-than-fresh,
# wrong in the reassuring direction. ts dropped, sha kept (independently verifiable).
future=$(( $(date +%s) + 9999 ))
printf '{"snapshot_sha":"%s","snapshot_ts":%s}\n' "$goodsha" "$future" >"$stub/repo_r1.meta"
printf 'SHA-CCC\n' >"$stub/repo_r1"
err="$("$DET" poll-once 2>&1 >/dev/null)"; rc=$?
[ "$rc" -eq 0 ] || fail_msg "D12: future ts must NEVER fail the poll (rc=$rc)"
echo "$err" | grep -qi 'future-skew' || fail_msg "D12: dropping a future ts must be LOUD on stderr [$err]"
entry="$("$STORE" drain | tail -1)"
echo "$entry" | jq -e --arg s "$goodsha" 'select(.locators.snapshot_sha==$s) | .locators | has("snapshot_ts") | not' >/dev/null 2>&1 \
|| fail_msg "D12: future ts must drop ts but KEEP the sha [$entry]"
# (c) valid sha + absurdly large ts — must be rejected by the sanity regex BEFORE
# the shell integer comparison (which would error out and silently keep it).
printf '{"snapshot_sha":"%s","snapshot_ts":99999999999999999999}\n' "$goodsha" >"$stub/repo_r1.meta"
printf 'SHA-DDD\n' >"$stub/repo_r1"
err="$("$DET" poll-once 2>&1 >/dev/null)"; rc=$?
[ "$rc" -eq 0 ] || fail_msg "D12: absurd ts must NEVER fail the poll (rc=$rc)"
echo "$err" | grep -qi 'sane positive epoch' || fail_msg "D12: rejecting an absurd ts must be LOUD on stderr [$err]"
entry="$("$STORE" drain | tail -1)"
echo "$entry" | jq -e --arg s "$goodsha" 'select(.locators.snapshot_sha==$s) | .locators | has("snapshot_ts") | not' >/dev/null 2>&1 \
|| fail_msg "D12: absurd ts must drop ts but KEEP the sha [$entry]"
[ "$(det_seq)" = "3" ] || fail_msg "D12: all three real deltas must still have enqueued, got seq $(det_seq)"
) && ok
echo "== D13: WAKE_SNAPSHOT_TS_FUTURE_SLACK is operator input — a malformed knob must fall back to 300 loudly, never kill the poll or invert the guard =="
(
WAKE_STATE_HOME="$(fresh_state d13)"
export WAKE_STATE_HOME
unset WAKE_AGENT
stub="$TMP_ROOT/d13stub"
mkdir -p "$stub"
cat >"$stub/adapter.sh" <<EOF
#!/usr/bin/env bash
set -u
kind="\$1"; id="\$2"
base="$stub/\${kind}_\${id}"
[ -f "\$base" ] && cat "\$base"
[ -f "\$base.meta" ] && { cat "\$base.meta" >&3; } 2>/dev/null
exit 0
EOF
chmod +x "$stub/adapter.sh"
export WAKE_DETECTOR_SOURCE_CMD="$stub/adapter.sh"
wl="$TMP_ROOT/d13.json"
write_watchlist "$wl" 1
export WAKE_WATCH_LIST="$wl"
printf 'SHA-AAA\n' >"$stub/repo_r1"
printf '## LANE-X\ndecision: hold\n' >"$stub/board_file_b1"
"$DET" poll-once >/dev/null 2>&1 || fail_msg "D13: baseline pass failed"
goodsha="0123abc4567890def0123abc4567890def012345"
# Every sub-case ships a VALID sha + CURRENT ts: the metadata itself is good,
# only the operator's knob is broken, so the correct outcome is fallback-and-keep.
# (a) '300s' — the natural duration-suffix mistake. Under set -u this used to be
# FATAL inside \$((...)): rc=1, wake never fires. Now: loud fallback, ts kept.
printf '{"snapshot_sha":"%s","snapshot_ts":%s}\n' "$goodsha" "$(date +%s)" >"$stub/repo_r1.meta"
printf 'SHA-BBB\n' >"$stub/repo_r1"
err="$(WAKE_SNAPSHOT_TS_FUTURE_SLACK='300s' "$DET" poll-once 2>&1 >/dev/null)"; rc=$?
[ "$rc" -eq 0 ] || fail_msg "D13: SLACK='300s' must NEVER fail the poll (rc=$rc)"
echo "$err" | grep -qi 'falling back to 300' || fail_msg "D13: malformed slack must be LOUD on stderr [$err]"
entry="$("$STORE" drain | tail -1)"
echo "$entry" | jq -e --arg s "$goodsha" 'select(.locators.snapshot_sha==$s) | .locators | has("snapshot_ts")' >/dev/null 2>&1 \
|| fail_msg "D13: valid metadata must SURVIVE a malformed knob (fallback, not drop) [$entry]"
# (b) 'abc' — bare word: under set -u, arithmetic dies on 'abc: unbound variable'.
printf '{"snapshot_sha":"%s","snapshot_ts":%s}\n' "$goodsha" "$(date +%s)" >"$stub/repo_r1.meta"
printf 'SHA-CCC\n' >"$stub/repo_r1"
err="$(WAKE_SNAPSHOT_TS_FUTURE_SLACK='abc' "$DET" poll-once 2>&1 >/dev/null)"; rc=$?
[ "$rc" -eq 0 ] || fail_msg "D13: SLACK='abc' must NEVER fail the poll (rc=$rc)"
echo "$err" | grep -qi 'falling back to 300' || fail_msg "D13: non-numeric slack must be LOUD on stderr [$err]"
entry="$("$STORE" drain | tail -1)"
echo "$entry" | jq -e --arg s "$goodsha" 'select(.locators.snapshot_sha==$s) | .locators | has("snapshot_ts")' >/dev/null 2>&1 \
|| fail_msg "D13: valid metadata must SURVIVE a non-numeric knob [$entry]"
# (c) negative slack — arithmetic would ACCEPT it and silently invert the guard
# into deny-all (a CURRENT ts reads as 'future'). Must fall back and keep the ts.
printf '{"snapshot_sha":"%s","snapshot_ts":%s}\n' "$goodsha" "$(date +%s)" >"$stub/repo_r1.meta"
printf 'SHA-DDD\n' >"$stub/repo_r1"
err="$(WAKE_SNAPSHOT_TS_FUTURE_SLACK='-99999999' "$DET" poll-once 2>&1 >/dev/null)"; rc=$?
[ "$rc" -eq 0 ] || fail_msg "D13: negative SLACK must NEVER fail the poll (rc=$rc)"
echo "$err" | grep -qi 'falling back to 300' || fail_msg "D13: negative slack must be LOUD on stderr [$err]"
entry="$("$STORE" drain | tail -1)"
echo "$entry" | jq -e --arg s "$goodsha" 'select(.locators.snapshot_sha==$s) | .locators | has("snapshot_ts")' >/dev/null 2>&1 \
|| fail_msg "D13: negative slack must NOT invert the guard into deny-all [$entry]"
# (c2) MULTI-LINE knobs — grep's ^...$ anchors bind PER LINE, so a value with
# an embedded newline ($'300\n8') passed the old regex whole yet is FATAL in
# \$((...)) ('error token is "8"'; $'300\nabc' dies as unbound variable). The
# case pattern matches the WHOLE string: both must fall back loudly, keep the ts.
printf '{"snapshot_sha":"%s","snapshot_ts":%s}\n' "$goodsha" "$(date +%s)" >"$stub/repo_r1.meta"
printf 'SHA-CC2\n' >"$stub/repo_r1"
err="$(WAKE_SNAPSHOT_TS_FUTURE_SLACK=$'300\n8' "$DET" poll-once 2>&1 >/dev/null)"; rc=$?
[ "$rc" -eq 0 ] || fail_msg "D13: multi-line SLACK (300\\n8) must NEVER fail the poll (rc=$rc) [$err]"
echo "$err" | grep -qi 'falling back to 300' || fail_msg "D13: multi-line slack must be LOUD on stderr [$err]"
entry="$("$STORE" drain | tail -1)"
echo "$entry" | jq -e --arg s "$goodsha" 'select(.locators.snapshot_sha==$s) | .locators | has("snapshot_ts")' >/dev/null 2>&1 \
|| fail_msg "D13: valid metadata must SURVIVE a multi-line knob [$entry]"
printf '{"snapshot_sha":"%s","snapshot_ts":%s}\n' "$goodsha" "$(date +%s)" >"$stub/repo_r1.meta"
printf 'SHA-CC3\n' >"$stub/repo_r1"
err="$(WAKE_SNAPSHOT_TS_FUTURE_SLACK=$'300\nabc' "$DET" poll-once 2>&1 >/dev/null)"; rc=$?
[ "$rc" -eq 0 ] || fail_msg "D13: multi-line SLACK (300\\nabc) must NEVER fail the poll (rc=$rc) [$err]"
echo "$err" | grep -qi 'falling back to 300' || fail_msg "D13: multi-line non-numeric slack must be LOUD on stderr [$err]"
entry="$("$STORE" drain | tail -1)"
echo "$entry" | jq -e --arg s "$goodsha" 'select(.locators.snapshot_sha==$s) | .locators | has("snapshot_ts")' >/dev/null 2>&1 \
|| fail_msg "D13: valid metadata must SURVIVE a multi-line non-numeric knob [$entry]"
# (d2-pre) ZERO-PADDED knobs — shape-valid, radix-hostile. '08' passes the
# regex but is fatal octal in \$((...)) without the 10# normalization; '0300'
# silently means 192 (octal), so a ts +250s ahead would be WRONGLY dropped.
# With 10#: '08' means 8 and survives; '0300' means 300 and the +250s ts is KEPT.
printf '{"snapshot_sha":"%s","snapshot_ts":%s}\n' "$goodsha" "$(date +%s)" >"$stub/repo_r1.meta"
printf 'SHA-DD2\n' >"$stub/repo_r1"
err="$(WAKE_SNAPSHOT_TS_FUTURE_SLACK='08' "$DET" poll-once 2>&1 >/dev/null)"; rc=$?
[ "$rc" -eq 0 ] || fail_msg "D13: SLACK='08' (octal-fatal without 10#) must NEVER fail the poll (rc=$rc) [$err]"
entry="$("$STORE" drain | tail -1)"
echo "$entry" | jq -e --arg s "$goodsha" 'select(.locators.snapshot_sha==$s) | .locators | has("snapshot_ts")' >/dev/null 2>&1 \
|| fail_msg "D13: SLACK='08' with a current ts must keep the metadata [$entry]"
printf '{"snapshot_sha":"%s","snapshot_ts":%s}\n' "$goodsha" "$(( $(date +%s) + 250 ))" >"$stub/repo_r1.meta"
printf 'SHA-DD3\n' >"$stub/repo_r1"
err="$(WAKE_SNAPSHOT_TS_FUTURE_SLACK='0300' "$DET" poll-once 2>&1 >/dev/null)"; rc=$?
[ "$rc" -eq 0 ] || fail_msg "D13: SLACK='0300' must not fail the poll (rc=$rc)"
entry="$("$STORE" drain | tail -1)"
echo "$entry" | jq -e --arg s "$goodsha" 'select(.locators.snapshot_sha==$s) | .locators | has("snapshot_ts")' >/dev/null 2>&1 \
|| fail_msg "D13: SLACK='0300' must mean 300 (decimal), so a +250s ts is KEPT — octal 192 would have dropped it [$entry]"
# (d) a VALID knob is still honored: slack=0 with a ts 60s ahead -> future-skew drop.
printf '{"snapshot_sha":"%s","snapshot_ts":%s}\n' "$goodsha" "$(( $(date +%s) + 60 ))" >"$stub/repo_r1.meta"
printf 'SHA-EEE\n' >"$stub/repo_r1"
err="$(WAKE_SNAPSHOT_TS_FUTURE_SLACK='0' "$DET" poll-once 2>&1 >/dev/null)"; rc=$?
[ "$rc" -eq 0 ] || fail_msg "D13: valid SLACK=0 must not fail the poll (rc=$rc)"
echo "$err" | grep -qi 'future-skew' || fail_msg "D13: a valid tightened slack must still reject a future ts [$err]"
entry="$("$STORE" drain | tail -1)"
echo "$entry" | jq -e --arg s "$goodsha" 'select(.locators.snapshot_sha==$s) | .locators | has("snapshot_ts") | not' >/dev/null 2>&1 \
|| fail_msg "D13: valid SLACK=0 must drop the future ts but keep the sha [$entry]"
[ "$(det_seq)" = "8" ] || fail_msg "D13: all eight real deltas must still have enqueued, got seq $(det_seq)"
) && ok
echo
if [ -s "$FAILFILE" ]; then
echo "wake detector harness: FAILED ($(grep -c . "$FAILFILE") assertion(s))" >&2
@@ -4,12 +4,20 @@
# 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` is now a hard-locator arm — and ONLY path;
# bare snapshot_sha is deliberately NOT an arm, Q11(d) asserts it
# still quarantines — 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 +61,30 @@
# (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)
# 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
# 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 +137,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"
@@ -135,7 +168,11 @@ echo "== Q2 (b): reconciler enumeration (reconciled:true) renders ORIENTATION-ti
unset WAKE_AGENT
f="$TMP_ROOT/q2.jsonl"
# A reconciler enumeration: store class actionable (unchanged) + reconciled marker.
printf '%s\n' '{"observed_seq":5,"class":"actionable","locators":{"kind":"repo","id":"ENUM-B","path":"BOARD.md","observed_hash":"cafe1234","reconciled":true},"emit_ts":1}' >"$f"
# ADDRESS-FREE on purpose (#944 F1): no path/file/sha/repo+issue — the reconciled
# exemption must be the ONLY thing keeping this entry out of quarantine, so the
# exemption is proven load-bearing AT THE GATE (mutation-killable), not merely at
# the tier label. (Q3's ENUM-C* stay path-bearing: reconciled + valid-locator mix.)
printf '%s\n' '{"observed_seq":5,"class":"actionable","locators":{"kind":"repo","id":"ENUM-B","observed_hash":"cafe1234","reconciled":true},"emit_ts":1}' >"$f"
err="$TMP_ROOT/q2.err"
out="$("$DIGEST" render --from-file "$f" --agent default 2>"$err")"
rc=$?
@@ -209,7 +246,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 +269,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 +289,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 +310,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"
@@ -301,6 +338,105 @@ echo "== Q9 (d): WAKE_ALARM_SINK_CMD unconfigured OR unreachable -> FAIL LOUD (n
true
) && ok
echo "== Q10: snapshot metadata (#940) — snapshot_sha/snapshot_ts render on the locator line and feed the re-verify hint =="
(
home="$(fresh_home q10)"
export WAKE_STATE_HOME="$home"
unset WAKE_AGENT
f="$TMP_ROOT/q10.jsonl"
{
# (a) a digest-class ORIENTATION pointer WITH adapter-attested snapshot
# metadata — the fields must render on the pointer line (the tier where
# the snapshot-lag class actually bit; re-verify hints are actionable-
# tier-only by design and are asserted via (c) below).
printf '%s\n' '{"observed_seq":21,"class":"digest","locators":{"kind":"board_file","id":"SNAP-A","path":"BOARD.md","observed_hash":"aaaa1111","snapshot_sha":"0123abc4567890def0123abc4567890def012345","snapshot_ts":1753850000},"emit_ts":2}'
# (b) a sibling WITHOUT the fields — must render no snapshot vestige.
printf '%s\n' '{"observed_seq":22,"class":"digest","locators":{"kind":"board_file","id":"SNAP-B","path":"OTHER.md","observed_hash":"bbbb2222"},"emit_ts":2}'
# (c) an ACTIONABLE entry (hard locator: repo+issue) carrying path +
# snapshot_sha — its re-verify hint must upgrade to the one-call
# `git show <snapshot_sha>:<path>` (most-specific-first).
printf '%s\n' '{"observed_seq":23,"class":"actionable","locators":{"repo":"example/repo","issue":7,"path":"BOARD.md","observed_hash":"cccc3333","snapshot_sha":"0123abc4567890def0123abc4567890def012345","snapshot_ts":1753850000},"emit_ts":2}'
} >"$f"
out="$("$DIGEST" render --from-file "$f" --agent default 2>/dev/null)"
rc=$?
[ "$rc" -eq 0 ] || fail_msg "Q10: render must succeed (rc=$rc)"
snapa_line="$(printf '%s\n' "$out" | grep 'id=SNAP-A' || true)"
printf '%s' "$snapa_line" | grep -q 'snapshot_sha=0123abc4567890def0123abc4567890def012345' \
|| fail_msg "Q10: snapshot_sha must render on the ORIENTATION pointer line [$snapa_line]"
printf '%s' "$snapa_line" | grep -q 'snapshot_ts=1753850000' \
|| fail_msg "Q10: snapshot_ts must render on the ORIENTATION pointer line (age = emit_ts - snapshot_ts, local arithmetic) [$snapa_line]"
printf '%s' "$out" | grep -q 'git show 0123abc4567890def0123abc4567890def012345:BOARD.md' \
|| fail_msg "Q10: snapshot_sha+path must upgrade the actionable re-verify hint to a one-call git show"
# The sibling without metadata must not grow empty snapshot_ fields.
snapb_line="$(printf '%s\n' "$out" | grep 'id=SNAP-B' || true)"
printf '%s' "$snapb_line" | grep -q 'snapshot_' \
&& fail_msg "Q10: an entry without metadata must render NO snapshot_ fields [$snapb_line]"
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 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}'
} >"$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: no bare path-less snapshot_sha entry may appear in the digest (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 '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)]"
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
true
) && ok
echo
if [ -s "$FAILFILE" ]; then
echo "wake digest-quarantine harness: FAILED ($(grep -c . "$FAILFILE") assertion(s))" >&2