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")
|
||||
|
||||
Reference in New Issue
Block a user