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]>
This commit was merged in pull request #941.
This commit is contained in:
@@ -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,50 @@ _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
|
||||
local now_s
|
||||
now_s="$(date +%s)"
|
||||
if [ "$snap_ts" -gt $((now_s + ${WAKE_SNAPSHOT_TS_FUTURE_SLACK:-300})) ]; then
|
||||
echo "detector.sh: source '$kind/$id' snapshot_ts is beyond the ${WAKE_SNAPSHOT_TS_FUTURE_SLACK:-300}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 +341,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")
|
||||
|
||||
@@ -235,7 +235,7 @@ _has_hard_locator() {
|
||||
# ACTIONABLE-tier hard-locator FAIL-LOUD gate is untouched.
|
||||
_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 +247,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 +275,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
|
||||
|
||||
@@ -240,8 +240,50 @@
|
||||
# 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. 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, test-wake-digest-quarantine.sh Q10).
|
||||
component=wake
|
||||
version=0.6.11
|
||||
version=0.6.12
|
||||
|
||||
# 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,149 @@ 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
|
||||
if [ -s "$FAILFILE" ]; then
|
||||
echo "wake detector harness: FAILED ($(grep -c . "$FAILFILE") assertion(s))" >&2
|
||||
|
||||
@@ -301,6 +301,42 @@ 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
|
||||
if [ -s "$FAILFILE" ]; then
|
||||
echo "wake digest-quarantine harness: FAILED ($(grep -c . "$FAILFILE") assertion(s))" >&2
|
||||
|
||||
Reference in New Issue
Block a user