wake/#973: convert all 261 grep-verdict sites to has_match/count_lines
Mechanical conversion driven by the frozen denominator artifact (validate-973/convert-973.py, committed for auditability): every row is byte-verified against the worktree BEFORE any edit — a conversion applied to a line the denominator did not measure would be the umbrella defect wearing the converter's clothes — then 233 sites become has_match and 28 grep -c sites become count_lines, and each suite gains three header lines (source _wake-common.sh + wake_assert_init) directly after its SCRIPT_DIR line, a uniform +3 shift for every site coordinate. Env prefixes are kept (LC_ALL=C has_match — the prefix reaches the grep child, microtest C8). The two multi-grep pipeline sites (digest-quarantine:560, install:420) convert BOTH greps: each element is measurement-bearing, and under pipefail an rc=2 on the left is masked by an rc=1 on the right — the same defect one pipe deeper. Call-site 2>/dev/null redirects (the preimage canaries) are kept: ordinary grep noise stays suppressed, while an abort writes to the wake_assert_init-saved real stderr. Diff arithmetic: +291/-261 = 261 one-for-one line replacements + 30 header lines (10 suites x 3). Nothing in this commit is not the conversion. All ten suites run green after conversion. Per the review pre-registration (A9) that green is NON-EVIDENCE for this change: a suite that was green before conversion and green after witnesses nothing about whether the sites were converted — the counted-ledger equality in the validation harness is the evidence. Written-by: pepper (sb-it-1-dt) Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01NsKce8iZuSuRnu3gVMCBKB
This commit is contained in:
co-authored by
Claude Fable 5
parent
6109f81769
commit
10a7fe43c5
@@ -31,6 +31,9 @@
|
||||
set -uo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# #973: three-valued grep assertion helpers (has_match/count_lines); init saves real stderr for abort loudness.
|
||||
# shellcheck disable=SC1091
|
||||
. "$SCRIPT_DIR/_wake-common.sh" && wake_assert_init
|
||||
BEACON="$SCRIPT_DIR/beacon.sh"
|
||||
|
||||
command -v jq >/dev/null 2>&1 || {
|
||||
@@ -129,7 +132,7 @@ echo "== B2: beacon ABSENCE past SLO -> alarm FIRES + ROUTES =="
|
||||
out="$("$BEACON" check --slo-seconds 5 2>&1)"
|
||||
rc=$?
|
||||
[ "$rc" -eq 1 ] || fail_msg "B2: a stale-past-SLO beacon must FIRE the absence alarm (exit 1); got $rc [$out]"
|
||||
echo "$out" | grep -qi 'ALARM FIRED' || fail_msg "B2: absence must announce the alarm fired [$out]"
|
||||
echo "$out" | has_match -qi 'ALARM FIRED' || fail_msg "B2: absence must announce the alarm fired [$out]"
|
||||
[ -s "$ALARM_OUT" ] || fail_msg "B2: the alarm must actually ROUTE to the sink (payload not written)"
|
||||
jq -e '.kind == "beacon-absence-alarm"' "$ALARM_OUT" >/dev/null 2>&1 || fail_msg "B2: routed payload must be a beacon-absence-alarm [$(cat "$ALARM_OUT" 2>/dev/null)]"
|
||||
# NEVER-received is also an absence (depends on nothing the dying host does).
|
||||
@@ -150,13 +153,13 @@ echo "== B3: unconfigured OR unreachable ALARM target -> FAIL LOUD =="
|
||||
out="$("$BEACON" check --slo-seconds 5 2>&1)"
|
||||
rc=$?
|
||||
[ "$rc" -eq 3 ] || fail_msg "B3a: unconfigured alarm target must FAIL LOUD (exit 3); got $rc [$out]"
|
||||
echo "$out" | grep -qi 'silent no-alarm host' || fail_msg "B3a: the failure must name the silent-no-alarm-host hazard [$out]"
|
||||
echo "$out" | has_match -qi 'silent no-alarm host' || fail_msg "B3a: the failure must name the silent-no-alarm-host hazard [$out]"
|
||||
# (b) UNREACHABLE alarm sink (non-zero exit).
|
||||
export WAKE_ALARM_SINK_CMD="false"
|
||||
out2="$("$BEACON" check --slo-seconds 5 2>&1)"
|
||||
rc2=$?
|
||||
[ "$rc2" -eq 3 ] || fail_msg "B3b: unreachable alarm target must FAIL LOUD (exit 3); got $rc2 [$out2]"
|
||||
echo "$out2" | grep -qi 'UNREACHABLE' || fail_msg "B3b: the failure must name the unreachable target [$out2]"
|
||||
echo "$out2" | has_match -qi 'UNREACHABLE' || fail_msg "B3b: the failure must name the unreachable target [$out2]"
|
||||
) && ok
|
||||
|
||||
echo "== B4: isolated host -> DEGRADED different-supervision-root beacon FLAGGED =="
|
||||
@@ -169,8 +172,8 @@ echo "== B4: isolated host -> DEGRADED different-supervision-root beacon FLAGGED
|
||||
rc=$?
|
||||
err="$(cat "$TMP_ROOT/b4.err")"
|
||||
[ "$rc" -eq 0 ] || fail_msg "B4: a different-supervision-root emit must still succeed (exit 0); got $rc [$out][$err]"
|
||||
echo "$err" | grep -qi 'DEGRADED' || fail_msg "B4: a different-supervision-root beacon must be FLAGGED degraded on stderr [$err]"
|
||||
echo "$out" | grep -q 'degraded=true' || fail_msg "B4: emit must NOT silently present a degraded beacon as healthy (degraded=true expected) [$out]"
|
||||
echo "$err" | has_match -qi 'DEGRADED' || fail_msg "B4: a different-supervision-root beacon must be FLAGGED degraded on stderr [$err]"
|
||||
echo "$out" | has_match -q 'degraded=true' || fail_msg "B4: emit must NOT silently present a degraded beacon as healthy (degraded=true expected) [$out]"
|
||||
) && ok
|
||||
|
||||
echo "== B5: same-host-sibling -> REJECTED as non-independent, seq NOT advanced =="
|
||||
@@ -182,7 +185,7 @@ echo "== B5: same-host-sibling -> REJECTED as non-independent, seq NOT advanced
|
||||
out="$("$BEACON" emit 2>&1)"
|
||||
rc=$?
|
||||
[ "$rc" -ne 0 ] || fail_msg "B5: a same-host-sibling beacon must be REJECTED (non-zero exit) [$out]"
|
||||
echo "$out" | grep -qi 'not an independent' || fail_msg "B5: the rejection must explain it is NOT an independent leg [$out]"
|
||||
echo "$out" | has_match -qi 'not an independent' || fail_msg "B5: the rejection must explain it is NOT an independent leg [$out]"
|
||||
# A rejected emit must not have minted a seq (rejection precedes counter bump).
|
||||
seq_after="$("$BEACON" status 2>/dev/null | sed -n 's/^beacon_emitter_seq=//p')"
|
||||
[ "${seq_after:-0}" -eq 0 ] || fail_msg "B5: a rejected emit must NOT advance the monotonic seq (got $seq_after)"
|
||||
@@ -201,10 +204,10 @@ echo "== B6: alarm target resolved BY NAME; beacon.sh inlines no endpoint/secret
|
||||
out="$("$BEACON" check --slo-seconds 5 2>&1)"
|
||||
rc=$?
|
||||
[ "$rc" -eq 1 ] || fail_msg "B6: absence via a by-name alarm adapter must fire (exit 1); got $rc [$out]"
|
||||
head -n1 "$ALARM_OUT" 2>/dev/null | grep -qF "$SECRET_TARGET" || fail_msg "B6: the adapter must resolve+route to the BY-NAME target [$(cat "$ALARM_OUT" 2>/dev/null)]"
|
||||
head -n1 "$ALARM_OUT" 2>/dev/null | has_match -qF "$SECRET_TARGET" || fail_msg "B6: the adapter must resolve+route to the BY-NAME target [$(cat "$ALARM_OUT" 2>/dev/null)]"
|
||||
# The framework file must NOT inline the endpoint/secret: it only knows a NAME.
|
||||
grep -qF "$SECRET_TARGET" "$BEACON" && fail_msg "B6: beacon.sh must NOT inline the target endpoint/secret"
|
||||
grep -qF "$SECRET_TARGET" "$WAKE_ALARM_SINK_CMD" && fail_msg "B6: even the adapter must resolve by-name, not inline the secret"
|
||||
has_match -qF "$SECRET_TARGET" "$BEACON" && fail_msg "B6: beacon.sh must NOT inline the target endpoint/secret"
|
||||
has_match -qF "$SECRET_TARGET" "$WAKE_ALARM_SINK_CMD" && fail_msg "B6: even the adapter must resolve by-name, not inline the secret"
|
||||
true
|
||||
) && ok
|
||||
|
||||
@@ -218,7 +221,7 @@ echo "== B7: unconfigured OR unreachable BEACON sink on emit -> FAIL LOUD =="
|
||||
out="$("$BEACON" emit 2>&1)"
|
||||
rc=$?
|
||||
[ "$rc" -eq 3 ] || fail_msg "B7a: unconfigured beacon sink must FAIL LOUD (exit 3); got $rc [$out]"
|
||||
echo "$out" | grep -qi 'silent no-alarm host' || fail_msg "B7a: the failure must name the silent-no-alarm-host hazard [$out]"
|
||||
echo "$out" | has_match -qi 'silent no-alarm host' || fail_msg "B7a: the failure must name the silent-no-alarm-host hazard [$out]"
|
||||
# A refused emit (no sink) must not have minted a seq.
|
||||
seq_after="$("$BEACON" status 2>/dev/null | sed -n 's/^beacon_emitter_seq=//p')"
|
||||
[ "${seq_after:-0}" -eq 0 ] || fail_msg "B7a: an unconfigured-sink emit must NOT advance the seq (got $seq_after)"
|
||||
@@ -227,7 +230,7 @@ echo "== B7: unconfigured OR unreachable BEACON sink on emit -> FAIL LOUD =="
|
||||
out2="$("$BEACON" emit 2>&1)"
|
||||
rc2=$?
|
||||
[ "$rc2" -eq 1 ] || fail_msg "B7b: unreachable beacon sink must FAIL LOUD (exit 1); got $rc2 [$out2]"
|
||||
echo "$out2" | grep -qi 'UNREACHABLE' || fail_msg "B7b: the failure must name the unreachable sink [$out2]"
|
||||
echo "$out2" | has_match -qi 'UNREACHABLE' || fail_msg "B7b: the failure must name the unreachable sink [$out2]"
|
||||
) && ok
|
||||
|
||||
echo "== B8: no invented SLO -> check --slo-seconds is REQUIRED (fail-loud) =="
|
||||
@@ -238,7 +241,7 @@ echo "== B8: no invented SLO -> check --slo-seconds is REQUIRED (fail-loud) =="
|
||||
out="$("$BEACON" check 2>&1)"
|
||||
rc=$?
|
||||
[ "$rc" -eq 3 ] || fail_msg "B8: check without an SLO must fail loud (exit 3); got $rc [$out]"
|
||||
echo "$out" | grep -qi 'slo' || fail_msg "B8: the usage error must name the missing SLO [$out]"
|
||||
echo "$out" | has_match -qi 'slo' || fail_msg "B8: the usage error must name the missing SLO [$out]"
|
||||
) && ok
|
||||
|
||||
echo "== B9: capture-pane hint is a liveness HINT ONLY (does NOT suppress absence) =="
|
||||
@@ -267,7 +270,7 @@ echo "== B10: fresh beacon within SLO -> ALIVE (exit 0, no alarm) =="
|
||||
out="$("$BEACON" check --slo-seconds 60 2>&1)"
|
||||
rc=$?
|
||||
[ "$rc" -eq 0 ] || fail_msg "B10: a fresh beacon within SLO must be ALIVE (exit 0); got $rc [$out]"
|
||||
echo "$out" | grep -qi 'ALIVE' || fail_msg "B10: a fresh beacon must report ALIVE [$out]"
|
||||
echo "$out" | has_match -qi 'ALIVE' || fail_msg "B10: a fresh beacon must report ALIVE [$out]"
|
||||
[ ! -s "$ALARM_OUT" ] || fail_msg "B10: a fresh beacon must NOT fire an alarm"
|
||||
) && ok
|
||||
|
||||
@@ -291,7 +294,7 @@ echo "== B11: staleness from monitor ingested_ts -> a far-future emit_ts STILL g
|
||||
jq -c --argjson ing "$((now - 100))" '.ingested_ts = $ing' "$WAKE_BEACON_RECEIVED" >"$H/tmp" && mv "$H/tmp" "$WAKE_BEACON_RECEIVED"
|
||||
out="$("$BEACON" check --slo-seconds 5 2>&1)"; rc=$?
|
||||
[ "$rc" -eq 1 ] || fail_msg "B11: a far-future emit_ts must NOT defer staleness — receive-time governs (expected absence exit 1); got $rc [$out]"
|
||||
echo "$out" | grep -qi 'ALARM FIRED' || fail_msg "B11: the receive-time-stale beacon must fire the absence alarm [$out]"
|
||||
echo "$out" | has_match -qi 'ALARM FIRED' || fail_msg "B11: the receive-time-stale beacon must fire the absence alarm [$out]"
|
||||
jq -e '.age_seconds >= 100' "$ALARM_OUT" >/dev/null 2>&1 || fail_msg "B11: staleness age must be measured from ingested_ts (>=100s), not emit_ts [$(cat "$ALARM_OUT" 2>/dev/null)]"
|
||||
) && ok
|
||||
|
||||
@@ -329,7 +332,7 @@ else
|
||||
jq -c '.beacon_seq = 99999 | .host_id = "attacker"' "$SHIPPED" >"$spoof"
|
||||
out="$("$BEACON" record <"$spoof" 2>&1)"; rc=$?
|
||||
[ "$rc" -ne 0 ] || fail_msg "B12: a spoofed beacon (altered fields) must be REJECTED at record; got rc=$rc [$out]"
|
||||
echo "$out" | grep -qi 'spoofed beacon' || fail_msg "B12: the rejection must name the spoof [$out]"
|
||||
echo "$out" | has_match -qi 'spoofed beacon' || fail_msg "B12: the rejection must name the spoof [$out]"
|
||||
[ ! -s "$WAKE_BEACON_RECEIVED" ] || fail_msg "B12: a rejected spoof must NOT be stored (dead-man clock not advanced)"
|
||||
# (c) an UNSIGNED beacon is rejected when signing is configured.
|
||||
unsigned="$H/unsigned.json"
|
||||
@@ -337,14 +340,14 @@ else
|
||||
out2="$("$BEACON" record <"$unsigned" 2>&1)"; rc2=$?
|
||||
[ "$rc2" -ne 0 ] || fail_msg "B12: an unsigned beacon must be REJECTED when signing is configured; got rc=$rc2 [$out2]"
|
||||
# beacon.sh must not inline the key material.
|
||||
grep -qF "test-beacon-hmac-key-do-not-echo" "$BEACON" && fail_msg "B12: beacon.sh must NOT inline the HMAC key"
|
||||
has_match -qF "test-beacon-hmac-key-do-not-echo" "$BEACON" && fail_msg "B12: beacon.sh must NOT inline the HMAC key"
|
||||
true
|
||||
) && ok
|
||||
fi
|
||||
|
||||
echo
|
||||
if [ -s "$FAILFILE" ]; then
|
||||
echo "wake beacon harness: FAILED ($(grep -c . "$FAILFILE") assertion(s))" >&2
|
||||
echo "wake beacon harness: FAILED ($(count_lines . "$FAILFILE") assertion(s))" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "wake beacon harness: all invariants passed ($pass groups)"
|
||||
|
||||
@@ -29,6 +29,9 @@
|
||||
set -uo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# #973: three-valued grep assertion helpers (has_match/count_lines); init saves real stderr for abort loudness.
|
||||
# shellcheck disable=SC1091
|
||||
. "$SCRIPT_DIR/_wake-common.sh" && wake_assert_init
|
||||
DET="$SCRIPT_DIR/detector.sh"
|
||||
STORE="$SCRIPT_DIR/store.sh"
|
||||
|
||||
@@ -253,8 +256,8 @@ echo "== D5: watch-list schema_version out of manifest range -> FAIL LOUD =="
|
||||
err="$("$DET" poll-once 2>&1)"
|
||||
rc=$?
|
||||
[ "$rc" -ne 0 ] || fail_msg "D5: an out-of-range schema_version must FAIL LOUD (non-zero exit)"
|
||||
echo "$err" | grep -qi 'schema_version' || fail_msg "D5: the failure must name schema_version [$err]"
|
||||
echo "$err" | grep -qi 'range' || fail_msg "D5: the failure must state it is out of the supported range [$err]"
|
||||
echo "$err" | has_match -qi 'schema_version' || fail_msg "D5: the failure must name schema_version [$err]"
|
||||
echo "$err" | has_match -qi 'range' || fail_msg "D5: the failure must state it is out of the supported range [$err]"
|
||||
# And nothing was enqueued / no cursor advance under a rejected watch-list.
|
||||
[ "$(depth)" = "0" ] || fail_msg "D5: a rejected watch-list must not enqueue, got depth $(depth)"
|
||||
[ "$(det_seq)" = "0" ] || fail_msg "D5: a rejected watch-list must not advance observed_seq, got $(det_seq)"
|
||||
@@ -287,7 +290,7 @@ echo "== D6: source error / ambiguous-empty -> FAIL LOUD, observed_seq NOT advan
|
||||
err="$("$DET" poll-once 2>&1)"
|
||||
rc=$?
|
||||
[ "$rc" -ne 0 ] || fail_msg "D6: a source error must FAIL LOUD (non-zero exit)"
|
||||
echo "$err" | grep -qi 'FAIL LOUD' || fail_msg "D6: the source error must be loud [$err]"
|
||||
echo "$err" | has_match -qi 'FAIL LOUD' || fail_msg "D6: the source error must be loud [$err]"
|
||||
[ "$(det_seq)" = "$seq_before" ] || fail_msg "D6: a source error must NOT advance observed_seq (got $(det_seq), was $seq_before)"
|
||||
[ "$(depth)" = "0" ] || fail_msg "D6: a source error must NOT enqueue, got depth $(depth)"
|
||||
|
||||
@@ -298,7 +301,7 @@ echo "== D6: source error / ambiguous-empty -> FAIL LOUD, observed_seq NOT advan
|
||||
err="$("$DET" poll-once 2>&1)"
|
||||
rc=$?
|
||||
[ "$rc" -ne 0 ] || fail_msg "D6: an ambiguous-empty response must FAIL LOUD (non-zero exit)"
|
||||
echo "$err" | grep -qi 'AMBIGUOUS-EMPTY' || fail_msg "D6: ambiguous-empty must be named in the loud failure [$err]"
|
||||
echo "$err" | has_match -qi 'AMBIGUOUS-EMPTY' || fail_msg "D6: ambiguous-empty must be named in the loud failure [$err]"
|
||||
[ "$(det_seq)" = "$seq_before" ] || fail_msg "D6: ambiguous-empty must NOT advance observed_seq (got $(det_seq))"
|
||||
[ "$(depth)" = "0" ] || fail_msg "D6: ambiguous-empty must NOT enqueue, got depth $(depth)"
|
||||
|
||||
@@ -442,7 +445,7 @@ EOF
|
||||
write_fc_watchlist "$wl" 999
|
||||
err="$("$DET" poll-once 2>&1)"; rc=$?
|
||||
[ "$rc" -ne 0 ] || fail_msg "D9: the new field must NOT weaken Gate B — an out-of-range schema_version must still FAIL LOUD (rc=$rc)"
|
||||
echo "$err" | grep -qi 'range' || fail_msg "D9: the out-of-range failure must still state it is out of the supported range [$err]"
|
||||
echo "$err" | has_match -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 =="
|
||||
@@ -513,7 +516,7 @@ EOF
|
||||
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]"
|
||||
echo "$err" | has_match -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]"
|
||||
@@ -522,7 +525,7 @@ EOF
|
||||
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]"
|
||||
echo "$err" | has_match -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]"
|
||||
@@ -560,7 +563,7 @@ EOF
|
||||
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]"
|
||||
echo "$err" | has_match -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]"
|
||||
@@ -571,7 +574,7 @@ EOF
|
||||
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]"
|
||||
echo "$err" | has_match -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]"
|
||||
@@ -581,7 +584,7 @@ EOF
|
||||
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]"
|
||||
echo "$err" | has_match -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]"
|
||||
@@ -621,7 +624,7 @@ EOF
|
||||
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]"
|
||||
echo "$err" | has_match -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]"
|
||||
@@ -630,7 +633,7 @@ EOF
|
||||
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]"
|
||||
echo "$err" | has_match -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]"
|
||||
@@ -640,7 +643,7 @@ EOF
|
||||
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]"
|
||||
echo "$err" | has_match -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]"
|
||||
@@ -652,7 +655,7 @@ EOF
|
||||
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]"
|
||||
echo "$err" | has_match -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]"
|
||||
@@ -660,7 +663,7 @@ EOF
|
||||
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]"
|
||||
echo "$err" | has_match -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]"
|
||||
@@ -687,7 +690,7 @@ EOF
|
||||
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]"
|
||||
echo "$err" | has_match -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]"
|
||||
@@ -696,7 +699,7 @@ EOF
|
||||
|
||||
echo
|
||||
if [ -s "$FAILFILE" ]; then
|
||||
echo "wake detector harness: FAILED ($(grep -c . "$FAILFILE") assertion(s))" >&2
|
||||
echo "wake detector harness: FAILED ($(count_lines . "$FAILFILE") assertion(s))" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "wake detector harness: all invariants passed ($pass groups)"
|
||||
|
||||
@@ -35,6 +35,9 @@
|
||||
set -uo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# #973: three-valued grep assertion helpers (has_match/count_lines); init saves real stderr for abort loudness.
|
||||
# shellcheck disable=SC1091
|
||||
. "$SCRIPT_DIR/_wake-common.sh" && wake_assert_init
|
||||
STORE="$SCRIPT_DIR/store.sh"
|
||||
DIGEST="$SCRIPT_DIR/digest.sh"
|
||||
SIGN="$SCRIPT_DIR/sign.sh"
|
||||
@@ -102,13 +105,13 @@ echo "== D1: CUMULATIVE-STATE — two pending changes BOTH render (not just late
|
||||
"$STORE" enqueue --seq 1 --class actionable --locators '{"repo":"r","issue":11}' >/dev/null
|
||||
"$STORE" enqueue --seq 2 --class actionable --locators '{"repo":"r","issue":22}' >/dev/null
|
||||
out="$("$DIGEST" render)" || fail_msg "D1: render exited non-zero"
|
||||
echo "$out" | grep -q 'issue=#11' || fail_msg "D1: OLDER change (issue #11) dropped — digest is a delta, not cumulative state"
|
||||
echo "$out" | grep -q 'issue=#22' || fail_msg "D1: newer change (issue #22) missing"
|
||||
echo "$out" | grep -q 'seq 1' || fail_msg "D1: seq 1 not listed in cumulative set"
|
||||
echo "$out" | grep -q 'seq 2' || fail_msg "D1: seq 2 not listed in cumulative set"
|
||||
echo "$out" | has_match -q 'issue=#11' || fail_msg "D1: OLDER change (issue #11) dropped — digest is a delta, not cumulative state"
|
||||
echo "$out" | has_match -q 'issue=#22' || fail_msg "D1: newer change (issue #22) missing"
|
||||
echo "$out" | has_match -q 'seq 1' || fail_msg "D1: seq 1 not listed in cumulative set"
|
||||
echo "$out" | has_match -q 'seq 2' || fail_msg "D1: seq 2 not listed in cumulative set"
|
||||
# A coalescing digest-class entry is STATE (full), not a delta: a later digest
|
||||
# subsumes the earlier, but the cumulative unacked set (both actionables) stays.
|
||||
echo "$out" | grep -q 'pending=2' || fail_msg "D1: cumulative pending count wrong (expected 2)"
|
||||
echo "$out" | has_match -q 'pending=2' || fail_msg "D1: cumulative pending count wrong (expected 2)"
|
||||
) && ok
|
||||
|
||||
echo "== D2: HARD-LOCATOR enforcement — a malformed actionable claim is QUARANTINED (fail-loud PER-ENTRY, #920); never delivered as valid =="
|
||||
@@ -127,9 +130,9 @@ echo "== D2: HARD-LOCATOR enforcement — a malformed actionable claim is QUARAN
|
||||
# but the unlocated claim is NEVER delivered as a valid CLAIM (fail-loud is
|
||||
# preserved, now per-entry). The old exit-4 wedged the entire drain (#920).
|
||||
[ "$rc" -eq 0 ] || fail_msg "D2: a malformed actionable must be quarantined (render exit 0, #920), got $rc"
|
||||
printf '%s' "$out" | grep -q 'mergeable=true' && fail_msg "D2: an unlocated actionable claim must NOT be delivered as a valid CLAIM"
|
||||
grep -q 'mergeable=true' "$h/default/dead-letter.jsonl" 2>/dev/null || fail_msg "D2: the malformed claim must be DEAD-LETTERED (fail-loud preserved, per-entry)"
|
||||
grep -qi 'QUARANTINE' "$err" || fail_msg "D2: a LOUD per-entry alarm must fire for the malformed claim"
|
||||
printf '%s' "$out" | has_match -q 'mergeable=true' && fail_msg "D2: an unlocated actionable claim must NOT be delivered as a valid CLAIM"
|
||||
has_match -q 'mergeable=true' "$h/default/dead-letter.jsonl" 2>/dev/null || fail_msg "D2: the malformed claim must be DEAD-LETTERED (fail-loud preserved, per-entry)"
|
||||
has_match -qi 'QUARANTINE' "$err" || fail_msg "D2: a LOUD per-entry alarm must fire for the malformed claim"
|
||||
|
||||
# A present-but-imprecise sha (not 40 hex) does NOT satisfy the hard locator ->
|
||||
# quarantined, not delivered.
|
||||
@@ -140,8 +143,8 @@ echo "== D2: HARD-LOCATOR enforcement — a malformed actionable claim is QUARAN
|
||||
rc=0
|
||||
out="$("$DIGEST" render 2>/dev/null)" || rc=$?
|
||||
[ "$rc" -eq 0 ] || fail_msg "D2: imprecise-sha entry must be quarantined (render exit 0), got $rc"
|
||||
printf '%s' "$out" | grep -q 'ci=green' && fail_msg "D2: imprecise sha (not 40-hex) must not satisfy the hard-locator gate (claim must not deliver)"
|
||||
grep -q 'ci=green' "$h/default/dead-letter.jsonl" 2>/dev/null || fail_msg "D2: imprecise-sha claim must be dead-lettered"
|
||||
printf '%s' "$out" | has_match -q 'ci=green' && fail_msg "D2: imprecise sha (not 40-hex) must not satisfy the hard-locator gate (claim must not deliver)"
|
||||
has_match -q 'ci=green' "$h/default/dead-letter.jsonl" 2>/dev/null || fail_msg "D2: imprecise-sha claim must be dead-lettered"
|
||||
|
||||
# The SAME claim WITH a precise 40-hex sha renders fine (delivered, not quarantined).
|
||||
h="$(fresh_state d2c)"
|
||||
@@ -149,7 +152,7 @@ echo "== D2: HARD-LOCATOR enforcement — a malformed actionable claim is QUARAN
|
||||
export WAKE_STATE_HOME
|
||||
"$STORE" enqueue --seq 1 --class actionable --locators "$(jq -cn --arg s "$SHA40" '{claim:"ci=green",sha:$s}')" >/dev/null
|
||||
out="$("$DIGEST" render 2>/dev/null)" || fail_msg "D2: a well-located actionable claim must render"
|
||||
printf '%s' "$out" | grep -q "$SHA40" || fail_msg "D2: a well-located actionable claim must be DELIVERED"
|
||||
printf '%s' "$out" | has_match -q "$SHA40" || fail_msg "D2: a well-located actionable claim must be DELIVERED"
|
||||
[ -s "$h/default/dead-letter.jsonl" ] && fail_msg "D2: a well-located claim must NOT be quarantined"
|
||||
# --- #905 bypass-prevention (STILL enforced, now via quarantine): a NON-
|
||||
# CANONICAL entry with a TOP-LEVEL `.claim` (store.sh never emits this shape;
|
||||
@@ -164,8 +167,8 @@ echo "== D2: HARD-LOCATOR enforcement — a malformed actionable claim is QUARAN
|
||||
rc=0
|
||||
out="$(printf '%s\n' "$toplevel_claim_entry" | "$DIGEST" render --stdin 2>/dev/null)" || rc=$?
|
||||
[ "$rc" -eq 0 ] || fail_msg "D2(#905): top-level .claim must be quarantined via --stdin (exit 0), got $rc"
|
||||
printf '%s' "$out" | grep -q 'mergeable=true' && fail_msg "D2(#905): --stdin top-level-.claim must NOT be delivered (gate not bypassed)"
|
||||
grep -q 'mergeable=true' "$h/default/dead-letter.jsonl" 2>/dev/null || fail_msg "D2(#905): --stdin top-level .claim must be dead-lettered (gate enforced)"
|
||||
printf '%s' "$out" | has_match -q 'mergeable=true' && fail_msg "D2(#905): --stdin top-level-.claim must NOT be delivered (gate not bypassed)"
|
||||
has_match -q 'mergeable=true' "$h/default/dead-letter.jsonl" 2>/dev/null || fail_msg "D2(#905): --stdin top-level .claim must be dead-lettered (gate enforced)"
|
||||
ff="$TMP_ROOT/d2d-entry.jsonl"
|
||||
printf '%s\n' "$toplevel_claim_entry" >"$ff"
|
||||
h="$(fresh_state d2e)"
|
||||
@@ -174,8 +177,8 @@ echo "== D2: HARD-LOCATOR enforcement — a malformed actionable claim is QUARAN
|
||||
rc=0
|
||||
out2="$("$DIGEST" render --from-file "$ff" 2>/dev/null)" || rc=$?
|
||||
[ "$rc" -eq 0 ] || fail_msg "D2(#905): top-level .claim must be quarantined via --from-file (exit 0), got $rc"
|
||||
printf '%s' "$out2" | grep -q 'mergeable=true' && fail_msg "D2(#905): --from-file top-level-.claim must NOT be delivered (gate not bypassed)"
|
||||
grep -q 'mergeable=true' "$h/default/dead-letter.jsonl" 2>/dev/null || fail_msg "D2(#905): --from-file top-level .claim must be dead-lettered (gate enforced)"
|
||||
printf '%s' "$out2" | has_match -q 'mergeable=true' && fail_msg "D2(#905): --from-file top-level-.claim must NOT be delivered (gate not bypassed)"
|
||||
has_match -q 'mergeable=true' "$h/default/dead-letter.jsonl" 2>/dev/null || fail_msg "D2(#905): --from-file top-level .claim must be dead-lettered (gate enforced)"
|
||||
) && ok
|
||||
|
||||
echo "== D3: TWO-TIER — orientation no-op with ZERO tool calls; actionable = claim-to-verify =="
|
||||
@@ -197,7 +200,7 @@ echo "== D3: TWO-TIER — orientation no-op with ZERO tool calls; actionable = c
|
||||
done
|
||||
# No pending obligations => no-op common case.
|
||||
out="$(PATH="$bin:$PATH" "$DIGEST" render --lane build)" || fail_msg "D3: no-op render failed"
|
||||
echo "$out" | grep -q 'NO-OP' || fail_msg "D3: empty inbox must render an explicit NO-OP orientation"
|
||||
echo "$out" | has_match -q 'NO-OP' || fail_msg "D3: empty inbox must render an explicit NO-OP orientation"
|
||||
[ -e "$WAKE_STATE_HOME/TOOL_CALLED" ] && fail_msg "D3: orientation no-op made a live tool call (must be ZERO)"
|
||||
# Now an actionable, consequential fact. It must be a CLAIM-TO-VERIFY, never
|
||||
# rendered as a trusted assertion or an auto-action.
|
||||
@@ -206,14 +209,14 @@ echo "== D3: TWO-TIER — orientation no-op with ZERO tool calls; actionable = c
|
||||
out2="$(PATH="$bin:$PATH" "$DIGEST" render)" || fail_msg "D3: actionable render failed"
|
||||
# Rendering itself still makes ZERO live calls (it only lays out claims).
|
||||
[ -e "$WAKE_STATE_HOME/TOOL_CALLED" ] && fail_msg "D3: rendering an actionable claim made a live call (must defer to the consumer's gate)"
|
||||
echo "$out2" | grep -q 'CLAIM@seq' || fail_msg "D3: consequential fact not framed as CLAIM@seq"
|
||||
echo "$out2" | grep -qi 'VERIFY LIVE' || fail_msg "D3: claim not marked for live verification"
|
||||
echo "$out2" | grep -qi 'do NOT act on this line' || fail_msg "D3: claim missing do-not-auto-action framing"
|
||||
echo "$out2" | has_match -q 'CLAIM@seq' || fail_msg "D3: consequential fact not framed as CLAIM@seq"
|
||||
echo "$out2" | has_match -qi 'VERIFY LIVE' || fail_msg "D3: claim not marked for live verification"
|
||||
echo "$out2" | has_match -qi 'do NOT act on this line' || fail_msg "D3: claim missing do-not-auto-action framing"
|
||||
# The consequential fact must NOT appear as a bare trusted directive.
|
||||
echo "$out2" | grep -qiE 'merge now|go ahead and (merge|deploy)|safe to merge' &&
|
||||
echo "$out2" | has_match -qiE 'merge now|go ahead and (merge|deploy)|safe to merge' &&
|
||||
fail_msg "D3: digest auto-actioned a consequential fact (imperative present)"
|
||||
# And its hard locator + one-call re-verify hint are present.
|
||||
echo "$out2" | grep -q "re-verify (ONE call)" || fail_msg "D3: actionable claim missing one-call re-verify locator"
|
||||
echo "$out2" | has_match -q "re-verify (ONE call)" || fail_msg "D3: actionable claim missing one-call re-verify locator"
|
||||
true
|
||||
) && ok
|
||||
|
||||
@@ -229,7 +232,7 @@ echo "== D4: SCRUB — secret-canary + ANSI/bidi/zero-width in source free-text
|
||||
"$STORE" enqueue --seq 1 --class actionable --locators "$loc" >/dev/null
|
||||
out="$("$DIGEST" render)" || fail_msg "D4: render failed"
|
||||
# ANSI escape / CSI must be gone.
|
||||
printf '%s' "$out" | LC_ALL=C grep -q "$(printf '\x1b')" && fail_msg "D4: ANSI ESC survived the scrub"
|
||||
printf '%s' "$out" | LC_ALL=C has_match -q "$(printf '\x1b')" && fail_msg "D4: ANSI ESC survived the scrub"
|
||||
# bidi/zero-width/BOM UTF-8 sequences must be gone.
|
||||
# #912: patterns are LITERAL bytes + `grep -E`, NOT PCRE `grep -P`. BusyBox
|
||||
# grep (Alpine/musl CI) has no `-P` — a `grep -qP` there errors
|
||||
@@ -244,21 +247,21 @@ echo "== D4: SCRUB — secret-canary + ANSI/bidi/zero-width in source free-text
|
||||
_b8b="$(printf '%b' '\x8b')"; _b8f="$(printf '%b' '\x8f')"
|
||||
_baa="$(printf '%b' '\xaa')"; _bae="$(printf '%b' '\xae')"
|
||||
_bbom="$(printf '%b' '\xef\xbb\xbf')"
|
||||
printf '%s' "$out" | LC_ALL=C grep -qE "${_b280}[${_b8b}-${_b8f}${_baa}-${_bae}]|${_bbom}" &&
|
||||
printf '%s' "$out" | LC_ALL=C has_match -qE "${_b280}[${_b8b}-${_b8f}${_baa}-${_bae}]|${_bbom}" &&
|
||||
fail_msg "D4: bidi/zero-width/BOM survived the scrub"
|
||||
# C0 control bytes (except tab/newline) must be gone.
|
||||
_c00="$(printf '%b' '\x01')"; _c08="$(printf '%b' '\x08')"
|
||||
_c0e="$(printf '%b' '\x0e')"; _c1f="$(printf '%b' '\x1f')"; _c7f="$(printf '%b' '\x7f')"
|
||||
printf '%s' "$out" | LC_ALL=C grep -qE "[${_c00}-${_c08}${_c0e}-${_c1f}${_c7f}]" &&
|
||||
printf '%s' "$out" | LC_ALL=C has_match -qE "[${_c00}-${_c08}${_c0e}-${_c1f}${_c7f}]" &&
|
||||
fail_msg "D4: a C0 control byte survived the scrub"
|
||||
# Secret canaries must be redacted, never inlined.
|
||||
printf '%s' "$out" | grep -q 'ghp_0123456789' && fail_msg "D4: GitHub-token canary LEAKED into the digest"
|
||||
printf '%s' "$out" | grep -q 'AKIAIOSFODNN7EXAMPLE' && fail_msg "D4: AWS-key canary LEAKED into the digest"
|
||||
printf '%s' "$out" | grep -q 'REDACTED-SECRET' || fail_msg "D4: secret redaction marker absent — canary may not have been scrubbed"
|
||||
printf '%s' "$out" | has_match -q 'ghp_0123456789' && fail_msg "D4: GitHub-token canary LEAKED into the digest"
|
||||
printf '%s' "$out" | has_match -q 'AKIAIOSFODNN7EXAMPLE' && fail_msg "D4: AWS-key canary LEAKED into the digest"
|
||||
printf '%s' "$out" | has_match -q 'REDACTED-SECRET' || fail_msg "D4: secret redaction marker absent — canary may not have been scrubbed"
|
||||
# Free-text is quoted inside a DELIMITED untrusted block, framed as NOT instructions.
|
||||
printf '%s' "$out" | grep -q 'BEGIN UNTRUSTED DATA' || fail_msg "D4: source free-text not placed in a delimited untrusted block"
|
||||
printf '%s' "$out" | has_match -q 'BEGIN UNTRUSTED DATA' || fail_msg "D4: source free-text not placed in a delimited untrusted block"
|
||||
# The 40-hex git SHA locator must NOT be mangled by the secret scrubber.
|
||||
printf '%s' "$out" | grep -q "$SHA40" || fail_msg "D4: legitimate 40-hex SHA locator was wrongly scrubbed"
|
||||
printf '%s' "$out" | has_match -q "$SHA40" || fail_msg "D4: legitimate 40-hex SHA locator was wrongly scrubbed"
|
||||
true
|
||||
) && ok
|
||||
|
||||
@@ -323,11 +326,11 @@ echo "== H2: KEY BY-NAME, never inline; no key leak; same-uid boundary documente
|
||||
fail_msg "H2: by-name key resolution failed"
|
||||
echo "$env1" | jq -e .wake_mac >/dev/null || fail_msg "H2: no MAC produced from by-name key"
|
||||
# The key VALUE must never appear in the signed output.
|
||||
echo "$env1" | grep -q 'SUPERSECRET-KEYVALUE-XYZ' && fail_msg "H2: key material LEAKED into the signed envelope"
|
||||
echo "$env1" | has_match -q 'SUPERSECRET-KEYVALUE-XYZ' && fail_msg "H2: key material LEAKED into the signed envelope"
|
||||
# A signed store-entry (hmac placeholder filled) must not leak the key either.
|
||||
entry="$(printf '{"observed_seq":1,"locators":{"repo":"r","issue":1},"class":"actionable","emit_ts":1700000000,"hmac":""}' |
|
||||
WAKE_HMAC_KEY_NAME=signing-key "$SIGN" sign-entry)"
|
||||
echo "$entry" | grep -q 'SUPERSECRET-KEYVALUE-XYZ' && fail_msg "H2: key material LEAKED into the signed entry"
|
||||
echo "$entry" | has_match -q 'SUPERSECRET-KEYVALUE-XYZ' && fail_msg "H2: key material LEAKED into the signed entry"
|
||||
echo "$entry" | jq -e '.hmac != "" and .hmac != null' >/dev/null || fail_msg "H2: sign-entry did not fill the hmac placeholder"
|
||||
# No flag may accept key MATERIAL inline — only a key NAME. An attempt to pass
|
||||
# a literal key must be rejected as an unknown option (never silently honored).
|
||||
@@ -341,8 +344,8 @@ echo "== H2: KEY BY-NAME, never inline; no key leak; same-uid boundary documente
|
||||
fail_msg "H2: signing with an unresolvable key name must FAIL LOUD"
|
||||
fi
|
||||
# The same-uid threat boundary + off-uid follow-up must be DOCUMENTED in the tool.
|
||||
grep -qi 'same-uid' "$SIGN" || fail_msg "H2: same-uid threat boundary not documented in sign.sh"
|
||||
grep -qi 'off-uid' "$SIGN" || fail_msg "H2: off-uid future signer not named in sign.sh"
|
||||
has_match -qi 'same-uid' "$SIGN" || fail_msg "H2: same-uid threat boundary not documented in sign.sh"
|
||||
has_match -qi 'off-uid' "$SIGN" || fail_msg "H2: off-uid future signer not named in sign.sh"
|
||||
) && ok
|
||||
|
||||
echo "== D5 (#914a): EMBEDDED ACK NAMESPACE — env-less copy-run targets the RENDER-TIME agent, not 'default' =="
|
||||
@@ -355,7 +358,7 @@ echo "== D5 (#914a): EMBEDDED ACK NAMESPACE — env-less copy-run targets the RE
|
||||
out="$(WAKE_AGENT=someagent "$DIGEST" render)" || fail_msg "D5: render (WAKE_AGENT=someagent) failed"
|
||||
ack_line="$(printf '%s\n' "$out" | awk '/^-- ACK/{f=1;next} f && NF {print; exit}')"
|
||||
[ -n "$ack_line" ] || fail_msg "D5: no embedded ack line extracted from the digest"
|
||||
printf '%s' "$ack_line" | grep -q 'WAKE_AGENT=someagent' ||
|
||||
printf '%s' "$ack_line" | has_match -q 'WAKE_AGENT=someagent' ||
|
||||
fail_msg "D5: embedded ack line has no explicit WAKE_AGENT=someagent prefix — an env-less copy-run silently resolves to 'default' [$ack_line]"
|
||||
# Actually RUN it with NO WAKE_AGENT in the environment (the real failure
|
||||
# mode: a consumer copy-pastes the line into a fresh shell).
|
||||
@@ -401,12 +404,12 @@ echo "== D6 (#914b): DIGEST-CLASS LOCATOR THREADING — ORIENTATION pointer carr
|
||||
loc="$(jq -cn '{kind:"repo", id:"r1", observed_hash:"deadbeefcafe0123456789abcdef0123456789abcdef0123456789abcdef01", remote:"example/repo"}')"
|
||||
"$STORE" enqueue --seq 1 --class digest --locators "$loc" >/dev/null
|
||||
out="$("$DIGEST" render)" || fail_msg "D6: render failed"
|
||||
orientline="$(printf '%s\n' "$out" | grep -E '^\s*\* seq 1 \[digest\]')"
|
||||
orientline="$(printf '%s\n' "$out" | has_match -E '^\s*\* seq 1 \[digest\]')"
|
||||
[ -n "$orientline" ] || fail_msg "D6: no ORIENTATION line found for seq 1"
|
||||
printf '%s' "$orientline" | grep -qE 'locator: *$' &&
|
||||
printf '%s' "$orientline" | has_match -qE 'locator: *$' &&
|
||||
fail_msg "D6: digest-class ORIENTATION pointer rendered an EMPTY locator despite a populated .locators field [$orientline]"
|
||||
# Must surface something a consumer can act on to re-verify.
|
||||
printf '%s' "$orientline" | grep -qE 'remote=example/repo|id=r1|kind=repo' ||
|
||||
printf '%s' "$orientline" | has_match -qE 'remote=example/repo|id=r1|kind=repo' ||
|
||||
fail_msg "D6: digest-class ORIENTATION pointer does not carry a usable locator [$orientline]"
|
||||
|
||||
# --- ACTIONABLE-tier hard-locator FAIL-LOUD must be PRESERVED (now PER-ENTRY
|
||||
@@ -421,14 +424,14 @@ echo "== D6 (#914b): DIGEST-CLASS LOCATOR THREADING — ORIENTATION pointer carr
|
||||
rc=0
|
||||
out="$("$DIGEST" render 2>"$err")" || rc=$?
|
||||
[ "$rc" -eq 0 ] || fail_msg "D6: a malformed actionable is now per-entry quarantined (render exit 0, #920), got $rc"
|
||||
printf '%s' "$out" | grep -q 'mergeable=true' && fail_msg "D6: a malformed actionable claim must NOT be delivered as valid (fail-loud preserved)"
|
||||
grep -q 'mergeable=true' "$h/default/dead-letter.jsonl" 2>/dev/null || fail_msg "D6: a malformed actionable must be DEAD-LETTERED (fail-loud preserved, per-entry)"
|
||||
grep -qi 'QUARANTINE' "$err" || fail_msg "D6: a malformed actionable must raise a loud per-entry alarm"
|
||||
printf '%s' "$out" | has_match -q 'mergeable=true' && fail_msg "D6: a malformed actionable claim must NOT be delivered as valid (fail-loud preserved)"
|
||||
has_match -q 'mergeable=true' "$h/default/dead-letter.jsonl" 2>/dev/null || fail_msg "D6: a malformed actionable must be DEAD-LETTERED (fail-loud preserved, per-entry)"
|
||||
has_match -qi 'QUARANTINE' "$err" || fail_msg "D6: a malformed actionable must raise a loud per-entry alarm"
|
||||
) && ok
|
||||
|
||||
echo
|
||||
if [ -s "$FAILFILE" ]; then
|
||||
echo "wake digest/hmac harness: FAILED ($(grep -c . "$FAILFILE") assertion(s))" >&2
|
||||
echo "wake digest/hmac harness: FAILED ($(count_lines . "$FAILFILE") assertion(s))" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "wake digest/hmac harness: all invariants passed ($pass groups)"
|
||||
|
||||
@@ -117,6 +117,9 @@
|
||||
set -uo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# #973: three-valued grep assertion helpers (has_match/count_lines); init saves real stderr for abort loudness.
|
||||
# shellcheck disable=SC1091
|
||||
. "$SCRIPT_DIR/_wake-common.sh" && wake_assert_init
|
||||
DIGEST="$SCRIPT_DIR/digest.sh"
|
||||
|
||||
command -v jq >/dev/null 2>&1 || {
|
||||
@@ -176,12 +179,12 @@ echo "== Q1 (a): malformed address-free {kind,id,observed_hash} QUARANTINES; cle
|
||||
out="$("$DIGEST" render --from-file "$f" --agent default 2>"$err")"
|
||||
rc=$?
|
||||
[ "$rc" -eq 0 ] || fail_msg "Q1: render must EXIT 0 (per-entry quarantine, not whole-digest exit-4), got rc=$rc"
|
||||
printf '%s' "$out" | grep -q "$SHA40" || fail_msg "Q1: the clean sibling (sha $SHA40) must STILL be delivered in the same drain [head-of-line block]"
|
||||
printf '%s' "$out" | grep -q 'MALFORMED-Q' && fail_msg "Q1: the quarantined entry must be EXCLUDED from the rendered digest"
|
||||
printf '%s' "$out" | has_match -q "$SHA40" || fail_msg "Q1: the clean sibling (sha $SHA40) must STILL be delivered in the same drain [head-of-line block]"
|
||||
printf '%s' "$out" | has_match -q 'MALFORMED-Q' && fail_msg "Q1: the quarantined entry must be EXCLUDED from the rendered digest"
|
||||
[ -f "$(dlq "$home")" ] || fail_msg "Q1: a durable dead-letter file must be written"
|
||||
grep -q 'MALFORMED-Q' "$(dlq "$home")" 2>/dev/null || fail_msg "Q1: the malformed entry must be DEAD-LETTERED (accounted-for, not silently dropped)"
|
||||
grep -qi 'QUARANTINE' "$err" || fail_msg "Q1: a LOUD per-entry alarm must fire on stderr"
|
||||
grep -q 'observed_seq=1' "$err" || fail_msg "Q1: the alarm must identify the offending entry (observed_seq=1)"
|
||||
has_match -q 'MALFORMED-Q' "$(dlq "$home")" 2>/dev/null || fail_msg "Q1: the malformed entry must be DEAD-LETTERED (accounted-for, not silently dropped)"
|
||||
has_match -qi 'QUARANTINE' "$err" || fail_msg "Q1: a LOUD per-entry alarm must fire on stderr"
|
||||
has_match -q 'observed_seq=1' "$err" || fail_msg "Q1: the alarm must identify the offending entry (observed_seq=1)"
|
||||
) && ok
|
||||
|
||||
echo "== Q2 (b): reconciler enumeration (reconciled:true) renders ORIENTATION-tier, NO exit-4 =="
|
||||
@@ -200,8 +203,8 @@ echo "== Q2 (b): reconciler enumeration (reconciled:true) renders ORIENTATION-ti
|
||||
out="$("$DIGEST" render --from-file "$f" --agent default 2>"$err")"
|
||||
rc=$?
|
||||
[ "$rc" -eq 0 ] || fail_msg "Q2: a reconciler enumeration must NOT exit-4 (it is ORIENTATION-tier), got rc=$rc"
|
||||
printf '%s' "$out" | grep -q 'id=ENUM-B' || fail_msg "Q2: the enumeration must render as an ORIENTATION pointer (id=ENUM-B via _locator_line)"
|
||||
printf '%s' "$out" | grep -q 'CLAIM@seq' && fail_msg "Q2: an enumeration must NOT render as an ACTIONABLE CLAIM@seq"
|
||||
printf '%s' "$out" | has_match -q 'id=ENUM-B' || fail_msg "Q2: the enumeration must render as an ORIENTATION pointer (id=ENUM-B via _locator_line)"
|
||||
printf '%s' "$out" | has_match -q 'CLAIM@seq' && fail_msg "Q2: an enumeration must NOT render as an ACTIONABLE CLAIM@seq"
|
||||
[ -s "$(dlq "$home")" ] && fail_msg "Q2: an ORIENTATION-tier enumeration must NOT be quarantined/dead-lettered"
|
||||
true
|
||||
) && ok
|
||||
@@ -220,10 +223,10 @@ echo "== Q3 (c): TWO DISTINCT enumerations BOTH survive as SEPARATE orientation
|
||||
out="$("$DIGEST" render --from-file "$f" --agent default 2>"$err")"
|
||||
rc=$?
|
||||
[ "$rc" -eq 0 ] || fail_msg "Q3: two enumerations must render (exit 0), got rc=$rc"
|
||||
n="$(printf '%s\n' "$out" | grep -c 'id=ENUM-C[12]' || true)"
|
||||
n="$(printf '%s\n' "$out" | count_lines 'id=ENUM-C[12]' || true)"
|
||||
[ "$n" = "2" ] || fail_msg "Q3: BOTH distinct enumerations must survive as SEPARATE orientation pointers (expected 2, got $n) — neither coalesced away"
|
||||
printf '%s' "$out" | grep -q 'id=ENUM-C1' || fail_msg "Q3: enumeration ENUM-C1 must be present"
|
||||
printf '%s' "$out" | grep -q 'id=ENUM-C2' || fail_msg "Q3: enumeration ENUM-C2 must be present"
|
||||
printf '%s' "$out" | has_match -q 'id=ENUM-C1' || fail_msg "Q3: enumeration ENUM-C1 must be present"
|
||||
printf '%s' "$out" | has_match -q 'id=ENUM-C2' || fail_msg "Q3: enumeration ENUM-C2 must be present"
|
||||
[ -s "$(dlq "$home")" ] && fail_msg "Q3: enumerations must NOT be quarantined"
|
||||
true
|
||||
) && ok
|
||||
@@ -239,10 +242,10 @@ echo "== Q4: PRESERVED — a genuine malformed ACTIONABLE claim is STILL loud (d
|
||||
out="$("$DIGEST" render --from-file "$f" --agent default 2>"$err")"
|
||||
rc=$?
|
||||
[ "$rc" -eq 0 ] || fail_msg "Q4: render must exit 0 (per-entry quarantine), got rc=$rc"
|
||||
printf '%s' "$out" | grep -q 'CLAIM-KEEP' && fail_msg "Q4: a malformed actionable must NOT be delivered as if valid"
|
||||
printf '%s' "$out" | grep -q '(none) — no consequential claims pending' || fail_msg "Q4: with the only claim quarantined, the ACTIONABLE section must show (none)"
|
||||
grep -q 'CLAIM-KEEP' "$(dlq "$home")" 2>/dev/null || fail_msg "Q4: the malformed actionable must be DEAD-LETTERED (loudly surfaced, not silent)"
|
||||
grep -qi 'QUARANTINE' "$err" || fail_msg "Q4: the malformed actionable must raise a LOUD alarm"
|
||||
printf '%s' "$out" | has_match -q 'CLAIM-KEEP' && fail_msg "Q4: a malformed actionable must NOT be delivered as if valid"
|
||||
printf '%s' "$out" | has_match -q '(none) — no consequential claims pending' || fail_msg "Q4: with the only claim quarantined, the ACTIONABLE section must show (none)"
|
||||
has_match -q 'CLAIM-KEEP' "$(dlq "$home")" 2>/dev/null || fail_msg "Q4: the malformed actionable must be DEAD-LETTERED (loudly surfaced, not silent)"
|
||||
has_match -qi 'QUARANTINE' "$err" || fail_msg "Q4: the malformed actionable must raise a LOUD alarm"
|
||||
) && ok
|
||||
|
||||
echo "== Q5: claim-precedence gated — a non-actionable-class entry carrying a claim (no hard locator) is STILL quarantined =="
|
||||
@@ -258,8 +261,8 @@ echo "== Q5: claim-precedence gated — a non-actionable-class entry carrying a
|
||||
out="$("$DIGEST" render --from-file "$f" --agent default 2>"$err")"
|
||||
rc=$?
|
||||
[ "$rc" -eq 0 ] || fail_msg "Q5: render must exit 0, got rc=$rc"
|
||||
grep -q 'CLAIMY' "$(dlq "$home")" 2>/dev/null || fail_msg "Q5: a claim-carrying entry with no hard locator must be quarantined (claim precedence, §2.1)"
|
||||
printf '%s' "$out" | grep -q 'CI is green' && fail_msg "Q5: the un-verifiable claim must NOT be delivered"
|
||||
has_match -q 'CLAIMY' "$(dlq "$home")" 2>/dev/null || fail_msg "Q5: a claim-carrying entry with no hard locator must be quarantined (claim precedence, §2.1)"
|
||||
printf '%s' "$out" | has_match -q 'CI is green' && fail_msg "Q5: the un-verifiable claim must NOT be delivered"
|
||||
true
|
||||
) && ok
|
||||
|
||||
@@ -278,11 +281,11 @@ echo "== Q6 (a): dead-lettered entry routes EXACTLY ONE off-host alarm (payload
|
||||
out="$("$DIGEST" render --from-file "$f" --agent default 2>"$err")"
|
||||
rc=$?
|
||||
[ "$rc" -eq 0 ] || fail_msg "Q6: render must still EXIT 0 (per-entry quarantine, not whole-drain wedge), got rc=$rc"
|
||||
n="$(grep -c . "$ALARM_OUT" 2>/dev/null || true)"
|
||||
n="$(count_lines . "$ALARM_OUT" 2>/dev/null || true)"
|
||||
[ "$n" = "1" ] || fail_msg "Q6: EXACTLY ONE off-host alarm must route for the dead-lettered entry (got $n) [$(cat "$ALARM_OUT" 2>/dev/null)]"
|
||||
grep -q '"observed_seq":21' "$ALARM_OUT" 2>/dev/null || fail_msg "Q6: the routed alarm payload must name the entry's observed_seq (21)"
|
||||
grep -qi 'QUARANTINE' "$err" || fail_msg "Q6: the existing #920 stderr diagnostic must STILL fire (local + off-host, not either/or)"
|
||||
printf '%s' "$out" | grep -q 'DLQ-Q6' && fail_msg "Q6: the quarantined entry must still be EXCLUDED from the rendered digest"
|
||||
has_match -q '"observed_seq":21' "$ALARM_OUT" 2>/dev/null || fail_msg "Q6: the routed alarm payload must name the entry's observed_seq (21)"
|
||||
has_match -qi 'QUARANTINE' "$err" || fail_msg "Q6: the existing #920 stderr diagnostic must STILL fire (local + off-host, not either/or)"
|
||||
printf '%s' "$out" | has_match -q 'DLQ-Q6' && fail_msg "Q6: the quarantined entry must still be EXCLUDED from the rendered digest"
|
||||
true
|
||||
) && ok
|
||||
|
||||
@@ -300,9 +303,9 @@ echo "== Q7 (b): re-draining the SAME still-dead-lettered entry N times routes Z
|
||||
for i in 1 2 3 4; do
|
||||
"$DIGEST" render --from-file "$f" --agent default >/dev/null 2>"$TMP_ROOT/q7.err.$i"
|
||||
done
|
||||
n="$(grep -c . "$ALARM_OUT" 2>/dev/null || true)"
|
||||
n="$(count_lines . "$ALARM_OUT" 2>/dev/null || true)"
|
||||
[ "$n" = "1" ] || fail_msg "Q7: re-draining the SAME dead-lettered entry 4x must route ONLY ONE off-host alarm total (durable dedup by observed_seq); got $n [$(cat "$ALARM_OUT" 2>/dev/null)]"
|
||||
grep -qi 'QUARANTINE' "$TMP_ROOT/q7.err.4" || fail_msg "Q7: the #920 stderr diagnostic must STILL fire on every re-drain (only the off-host route is deduped)"
|
||||
has_match -qi 'QUARANTINE' "$TMP_ROOT/q7.err.4" || fail_msg "Q7: the #920 stderr diagnostic must STILL fire on every re-drain (only the off-host route is deduped)"
|
||||
) && ok
|
||||
|
||||
echo "== Q8 (c): a NEW distinct dead-lettered entry routes its OWN one alarm (dedup is per-entry, not a global latch) =="
|
||||
@@ -321,10 +324,10 @@ echo "== Q8 (c): a NEW distinct dead-lettered entry routes its OWN one alarm (de
|
||||
"$DIGEST" render --from-file "$f1" --agent default >/dev/null 2>/dev/null
|
||||
"$DIGEST" render --from-file "$f1" --agent default >/dev/null 2>/dev/null # re-drain seq 31 -> must NOT re-alarm
|
||||
"$DIGEST" render --from-file "$f2" --agent default >/dev/null 2>/dev/null # NEW distinct seq 32 -> its own alarm
|
||||
n="$(grep -c . "$ALARM_OUT" 2>/dev/null || true)"
|
||||
n="$(count_lines . "$ALARM_OUT" 2>/dev/null || true)"
|
||||
[ "$n" = "2" ] || fail_msg "Q8: two DISTINCT dead-lettered entries must together route exactly 2 off-host alarms total (got $n) [$(cat "$ALARM_OUT" 2>/dev/null)]"
|
||||
grep -q '"observed_seq":31' "$ALARM_OUT" 2>/dev/null || fail_msg "Q8: seq 31's alarm must be present"
|
||||
grep -q '"observed_seq":32' "$ALARM_OUT" 2>/dev/null || fail_msg "Q8: seq 32's (the new distinct entry's) OWN alarm must be present"
|
||||
has_match -q '"observed_seq":31' "$ALARM_OUT" 2>/dev/null || fail_msg "Q8: seq 31's alarm must be present"
|
||||
has_match -q '"observed_seq":32' "$ALARM_OUT" 2>/dev/null || fail_msg "Q8: seq 32's (the new distinct entry's) OWN alarm must be present"
|
||||
) && ok
|
||||
|
||||
echo "== Q9 (d): WAKE_ALARM_SINK_CMD unconfigured OR unreachable -> FAIL LOUD (never silent no-alarm); per-entry, render still exits 0 =="
|
||||
@@ -340,9 +343,9 @@ echo "== Q9 (d): WAKE_ALARM_SINK_CMD unconfigured OR unreachable -> FAIL LOUD (n
|
||||
out_a="$("$DIGEST" render --from-file "$f" --agent default 2>"$err_a")"
|
||||
rc_a=$?
|
||||
[ "$rc_a" -eq 0 ] || fail_msg "Q9a: per-entry quarantine must still exit 0 even when the off-host alarm sink is unconfigured (no whole-drain wedge), got rc=$rc_a"
|
||||
grep -qi 'FAIL LOUD' "$err_a" || fail_msg "Q9a: an unconfigured off-host alarm target must FAIL LOUD on stderr [$(cat "$err_a")]"
|
||||
grep -Eqi 'silent no-alarm|silent-miss|PERMANENTLY miss|permanent silent miss' "$err_a" || fail_msg "Q9a: the diagnostic must name the silent-miss hazard (G2a), mirroring beacon.sh's fail-closed wording [$(cat "$err_a")]"
|
||||
printf '%s' "$out_a" | grep -q 'DLQ-Q9' && fail_msg "Q9a: the quarantined entry must still be EXCLUDED from the rendered digest"
|
||||
has_match -qi 'FAIL LOUD' "$err_a" || fail_msg "Q9a: an unconfigured off-host alarm target must FAIL LOUD on stderr [$(cat "$err_a")]"
|
||||
has_match -Eqi 'silent no-alarm|silent-miss|PERMANENTLY miss|permanent silent miss' "$err_a" || fail_msg "Q9a: the diagnostic must name the silent-miss hazard (G2a), mirroring beacon.sh's fail-closed wording [$(cat "$err_a")]"
|
||||
printf '%s' "$out_a" | has_match -q 'DLQ-Q9' && fail_msg "Q9a: the quarantined entry must still be EXCLUDED from the rendered digest"
|
||||
true
|
||||
) && ok
|
||||
(
|
||||
@@ -355,9 +358,9 @@ echo "== Q9 (d): WAKE_ALARM_SINK_CMD unconfigured OR unreachable -> FAIL LOUD (n
|
||||
out_b="$("$DIGEST" render --from-file "$f" --agent default 2>"$err_b")"
|
||||
rc_b=$?
|
||||
[ "$rc_b" -eq 0 ] || fail_msg "Q9b: per-entry quarantine must still exit 0 even when the off-host alarm sink is unreachable, got rc=$rc_b"
|
||||
grep -qi 'FAIL LOUD' "$err_b" || fail_msg "Q9b: an unreachable off-host alarm target must FAIL LOUD on stderr [$(cat "$err_b")]"
|
||||
grep -qi 'UNREACHABLE' "$err_b" || fail_msg "Q9b: the diagnostic must name the unreachable target [$(cat "$err_b")]"
|
||||
printf '%s' "$out_b" | grep -q 'DLQ-Q9' && fail_msg "Q9b: the quarantined entry must still be EXCLUDED from the rendered digest"
|
||||
has_match -qi 'FAIL LOUD' "$err_b" || fail_msg "Q9b: an unreachable off-host alarm target must FAIL LOUD on stderr [$(cat "$err_b")]"
|
||||
has_match -qi 'UNREACHABLE' "$err_b" || fail_msg "Q9b: the diagnostic must name the unreachable target [$(cat "$err_b")]"
|
||||
printf '%s' "$out_b" | has_match -q 'DLQ-Q9' && fail_msg "Q9b: the quarantined entry must still be EXCLUDED from the rendered digest"
|
||||
true
|
||||
) && ok
|
||||
|
||||
@@ -383,16 +386,16 @@ echo "== Q10: snapshot metadata (#940) — snapshot_sha/snapshot_ts render on th
|
||||
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' \
|
||||
snapa_line="$(printf '%s\n' "$out" | has_match 'id=SNAP-A' || true)"
|
||||
printf '%s' "$snapa_line" | has_match -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' \
|
||||
printf '%s' "$snapa_line" | has_match -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' \
|
||||
printf '%s' "$out" | has_match -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_' \
|
||||
snapb_line="$(printf '%s\n' "$out" | has_match 'id=SNAP-B' || true)"
|
||||
printf '%s' "$snapb_line" | has_match -q 'snapshot_' \
|
||||
&& fail_msg "Q10: an entry without metadata must render NO snapshot_ fields [$snapb_line]"
|
||||
true
|
||||
) && ok
|
||||
@@ -434,28 +437,28 @@ echo "== Q11 (#944): REAL detector-shape actionable RENDERS as CLAIM@seq; addres
|
||||
[ "$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' \
|
||||
printf '%s' "$out" | has_match -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" | has_match -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)"
|
||||
printf '%s' "$out" | has_match -q 'seq 69 — CLAIM@seq' || fail_msg "Q11b: a path-only detector-shape entry must RENDER as CLAIM@seq (path arm)"
|
||||
printf '%s' "$out" | has_match -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" | count_lines '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"
|
||||
printf '%s' "$out" | has_match -q 'ADDR-FREE' && fail_msg "Q11c: the address-free entry must be EXCLUDED from the digest"
|
||||
printf '%s' "$out" | has_match -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)"
|
||||
has_match -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"
|
||||
has_match -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)"
|
||||
has_match -q 'heartbeat-planning' "$(dlq "$home")" 2>/dev/null && fail_msg "Q11a: the valid live entry must NOT be dead-lettered"
|
||||
has_match -q 'PILOT-LOCAL' "$(dlq "$home")" 2>/dev/null && fail_msg "Q11b: the valid path-only entry must NOT be dead-lettered"
|
||||
n_alarms="$(count_lines . "$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"
|
||||
has_match -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"
|
||||
has_match -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
|
||||
@@ -477,15 +480,15 @@ echo "== Q12 (#946): quarantined entries are DISCLOSED (by seq, content withheld
|
||||
[ "$rc" -eq 0 ] || fail_msg "Q12: render must exit 0, got rc=$rc"
|
||||
# DISCLOSURE: a held entry must be VISIBLE in the digest it was held from —
|
||||
# a silent hold is how five successive digests each stepped past seq 68...
|
||||
printf '%s' "$out" | grep -q 'QUARANTINED' || fail_msg "Q12: the digest must carry a QUARANTINED disclosure section (no silent hold)"
|
||||
printf '%s' "$out" | grep -q 'seq 2 .*HELD' || fail_msg "Q12: the disclosure must name the held seq (2) as HELD"
|
||||
printf '%s' "$out" | has_match -q 'QUARANTINED' || fail_msg "Q12: the digest must carry a QUARANTINED disclosure section (no silent hold)"
|
||||
printf '%s' "$out" | has_match -q 'seq 2 .*HELD' || fail_msg "Q12: the disclosure must name the held seq (2) as HELD"
|
||||
# ...but WITHOUT re-injecting the refused content: disclosure is by seq only;
|
||||
# the Q1/Q4/Q5/Q6/Q9/Q11 exclusion property stands.
|
||||
printf '%s' "$out" | grep -q 'ADDR-Q12' && fail_msg "Q12: the quarantined entry's content/locators must stay EXCLUDED from the digest"
|
||||
printf '%s' "$out" | has_match -q 'ADDR-Q12' && fail_msg "Q12: the quarantined entry's content/locators must stay EXCLUDED from the digest"
|
||||
# CLAMP: the embedded ack stops BELOW the quarantined seq, and says so loudly.
|
||||
printf '%s' "$out" | grep -Eq 'consumed --upto 1$' || fail_msg "Q12: the embedded ack must be CLAMPED to --upto 1 (below quarantined seq 2)"
|
||||
printf '%s' "$out" | grep -Eq 'consumed --upto 2( |$)' && fail_msg "Q12: the raw observed cursor (2) must NOT be embedded while seq 2 is quarantined"
|
||||
printf '%s' "$out" | grep -q 'ACK CLAMPED' || fail_msg "Q12: the clamp must be LOUDLY disclosed in the ACK section"
|
||||
printf '%s' "$out" | has_match -Eq 'consumed --upto 1$' || fail_msg "Q12: the embedded ack must be CLAMPED to --upto 1 (below quarantined seq 2)"
|
||||
printf '%s' "$out" | has_match -Eq 'consumed --upto 2( |$)' && fail_msg "Q12: the raw observed cursor (2) must NOT be embedded while seq 2 is quarantined"
|
||||
printf '%s' "$out" | has_match -q 'ACK CLAMPED' || fail_msg "Q12: the clamp must be LOUDLY disclosed in the ACK section"
|
||||
# A foreign-data render must NOT rewrite the lane's quarantine truth.
|
||||
[ -e "$home/default/quarantined.set" ] && fail_msg "Q12: a --from-file render must NOT write the store's quarantined.set (lane truth is store-mode only)"
|
||||
true
|
||||
@@ -503,9 +506,9 @@ echo "== Q13 (#946): nothing quarantined -> ack UNCLAMPED at the observed cursor
|
||||
out="$("$DIGEST" render --from-file "$f" --agent default 2>/dev/null)"
|
||||
rc=$?
|
||||
[ "$rc" -eq 0 ] || fail_msg "Q13: render must exit 0, got rc=$rc"
|
||||
printf '%s' "$out" | grep -Eq 'consumed --upto 1$' || fail_msg "Q13: with nothing quarantined the ack must embed the observed cursor (1) unchanged"
|
||||
printf '%s' "$out" | grep -q 'QUARANTINED' && fail_msg "Q13: no disclosure section when nothing is quarantined"
|
||||
printf '%s' "$out" | grep -q 'ACK CLAMPED' && fail_msg "Q13: no clamp note when nothing is quarantined"
|
||||
printf '%s' "$out" | has_match -Eq 'consumed --upto 1$' || fail_msg "Q13: with nothing quarantined the ack must embed the observed cursor (1) unchanged"
|
||||
printf '%s' "$out" | has_match -q 'QUARANTINED' && fail_msg "Q13: no disclosure section when nothing is quarantined"
|
||||
printf '%s' "$out" | has_match -q 'ACK CLAMPED' && fail_msg "Q13: no clamp note when nothing is quarantined"
|
||||
true
|
||||
) && ok
|
||||
|
||||
@@ -520,8 +523,8 @@ echo "== Q14 (#946): store-mode render SYNCS quarantine truth into the store —
|
||||
out="$("$DIGEST" render --from-store --agent default 2>/dev/null)"
|
||||
rc=$?
|
||||
[ "$rc" -eq 0 ] || fail_msg "Q14: render must exit 0, got rc=$rc"
|
||||
printf '%s' "$out" | grep -q 'QUARANTINED' || fail_msg "Q14: the store-mode digest must disclose the held entry"
|
||||
printf '%s' "$out" | grep -Eq 'consumed --upto 1$' || fail_msg "Q14: the embedded ack must clamp to 1 (below quarantined seq 2)"
|
||||
printf '%s' "$out" | has_match -q 'QUARANTINED' || fail_msg "Q14: the store-mode digest must disclose the held entry"
|
||||
printf '%s' "$out" | has_match -Eq 'consumed --upto 1$' || fail_msg "Q14: the embedded ack must clamp to 1 (below quarantined seq 2)"
|
||||
qf="$home/default/quarantined.set"
|
||||
[ "$(cat "$qf" 2>/dev/null)" = "2" ] || fail_msg "Q14: a store-mode render must sync quarantined.set to exactly {2}, got [$(cat "$qf" 2>/dev/null)]"
|
||||
# END-TO-END: even a hand-typed upto past the held seq is refused at the
|
||||
@@ -545,8 +548,8 @@ echo "== Q15 (#946): gate-fix RECOVERY — a stale quarantined.set is REPLACED b
|
||||
out="$("$DIGEST" render --from-store --agent default 2>/dev/null)"
|
||||
rc=$?
|
||||
[ "$rc" -eq 0 ] || fail_msg "Q15: render must exit 0, got rc=$rc"
|
||||
printf '%s' "$out" | grep -q 'QUARANTINED' && fail_msg "Q15: nothing quarantines under the fixed gate — no disclosure section"
|
||||
printf '%s' "$out" | grep -Eq 'consumed --upto 2$' || fail_msg "Q15: the ack must embed the full observed cursor (2) once the gate is fixed"
|
||||
printf '%s' "$out" | has_match -q 'QUARANTINED' && fail_msg "Q15: nothing quarantines under the fixed gate — no disclosure section"
|
||||
printf '%s' "$out" | has_match -Eq 'consumed --upto 2$' || fail_msg "Q15: the ack must embed the full observed cursor (2) once the gate is fixed"
|
||||
[ -s "$home/default/quarantined.set" ] && fail_msg "Q15: the clean render must REPLACE (clear) the stale quarantined.set — a cumulative-forever set would block acks on entries that now render, got [$(cat "$home/default/quarantined.set")]"
|
||||
"$STORE" consume --upto 2 >/dev/null 2>&1 || fail_msg "Q15: the ordinary consume must succeed after the clamp self-heals"
|
||||
) && ok
|
||||
@@ -557,7 +560,7 @@ echo "== Q16 (guard): Q2's ENUM-B fixture must STAY address-free — the reconci
|
||||
# Token concatenated so THIS guard's own source lines never contain the
|
||||
# literal fixture id and cannot self-match.
|
||||
enum_id='ENUM''-B'
|
||||
fixture_line="$(grep -F "\"id\":\"$enum_id\"" "$self" | grep -F '"observed_seq":5' | head -n1)"
|
||||
fixture_line="$(has_match -F "\"id\":\"$enum_id\"" "$self" | has_match -F '"observed_seq":5' | head -n1)"
|
||||
[ -n "$fixture_line" ] || fail_msg "Q16: could not locate Q2's $enum_id fixture line (renamed/renumbered? update this guard)"
|
||||
fixture_json="$(printf '%s' "$fixture_line" | sed "s/.*'\({.*}\)'.*/\1/")"
|
||||
# Positive controls FIRST (blind-instrument rule): the extraction must yield
|
||||
@@ -578,7 +581,7 @@ echo "== Q16 (guard): Q2's ENUM-B fixture must STAY address-free — the reconci
|
||||
|
||||
echo
|
||||
if [ -s "$FAILFILE" ]; then
|
||||
echo "wake digest-quarantine harness: FAILED ($(grep -c . "$FAILFILE") assertion(s))" >&2
|
||||
echo "wake digest-quarantine harness: FAILED ($(count_lines . "$FAILFILE") assertion(s))" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "wake digest-quarantine harness: all invariants passed ($pass groups)"
|
||||
|
||||
@@ -25,6 +25,9 @@
|
||||
set -uo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# #973: three-valued grep assertion helpers (has_match/count_lines); init saves real stderr for abort loudness.
|
||||
# shellcheck disable=SC1091
|
||||
. "$SCRIPT_DIR/_wake-common.sh" && wake_assert_init
|
||||
ORACLE="$SCRIPT_DIR/fn-oracle.sh"
|
||||
DET="$SCRIPT_DIR/detector.sh"
|
||||
|
||||
@@ -62,8 +65,8 @@ echo "== O1: healthy pipeline -> synthetic-canary FN-rate = 0 =="
|
||||
out="$("$ORACLE" run --slo-seconds 120 --count 3 2>&1)"
|
||||
rc=$?
|
||||
[ "$rc" -eq 0 ] || fail_msg "O1: a healthy pipeline must exit 0 (got $rc) [$out]"
|
||||
echo "$out" | grep -q 'FN-RATE = 0/3' || fail_msg "O1: FN-rate must be 0/3 on a healthy pipeline [$out]"
|
||||
echo "$out" | grep -q 'VERDICT = PASS' || fail_msg "O1: verdict must be PASS on a healthy pipeline [$out]"
|
||||
echo "$out" | has_match -q 'FN-RATE = 0/3' || fail_msg "O1: FN-rate must be 0/3 on a healthy pipeline [$out]"
|
||||
echo "$out" | has_match -q 'VERDICT = PASS' || fail_msg "O1: verdict must be PASS on a healthy pipeline [$out]"
|
||||
) && ok
|
||||
|
||||
echo "== O2: DISABLED detector (perfect no-op) -> FN-DETECTED (blindspot killer) =="
|
||||
@@ -76,9 +79,9 @@ echo "== O2: DISABLED detector (perfect no-op) -> FN-DETECTED (blindspot killer)
|
||||
out="$("$ORACLE" run --slo-seconds 120 --count 3 2>&1)"
|
||||
rc=$?
|
||||
[ "$rc" -ne 0 ] || fail_msg "O2: a detector that drops a KNOWN delta MUST fail the oracle (non-zero exit), even at a perfect no-op rate"
|
||||
echo "$out" | grep -q 'FN-RATE = 3/3' || fail_msg "O2: every dropped canary must count as a false-negative (FN-RATE 3/3) [$out]"
|
||||
echo "$out" | grep -q 'VERDICT = FN-DETECTED' || fail_msg "O2: verdict must be FN-DETECTED for a dropping detector [$out]"
|
||||
echo "$out" | grep -qi 'never OBSERVED' || fail_msg "O2: the failure must name the dropped (never-observed) delta [$out]"
|
||||
echo "$out" | has_match -q 'FN-RATE = 3/3' || fail_msg "O2: every dropped canary must count as a false-negative (FN-RATE 3/3) [$out]"
|
||||
echo "$out" | has_match -q 'VERDICT = FN-DETECTED' || fail_msg "O2: verdict must be FN-DETECTED for a dropping detector [$out]"
|
||||
echo "$out" | has_match -qi 'never OBSERVED' || fail_msg "O2: the failure must name the dropped (never-observed) delta [$out]"
|
||||
) && ok
|
||||
|
||||
echo "== O3: reached CONSUMED but too slow -> FN-DETECTED (within-SLO clause) =="
|
||||
@@ -91,10 +94,10 @@ echo "== O3: reached CONSUMED but too slow -> FN-DETECTED (within-SLO clause) ==
|
||||
out="$("$ORACLE" run --slo-seconds 1 --count 1 2>&1)"
|
||||
rc=$?
|
||||
[ "$rc" -ne 0 ] || fail_msg "O3: a canary that reaches CONSUMED past its SLO must be a false-negative (non-zero exit)"
|
||||
echo "$out" | grep -qi 'per-class SLO' || fail_msg "O3: the failure must attribute to the per-class SLO, not a drop [$out]"
|
||||
echo "$out" | has_match -qi 'per-class SLO' || fail_msg "O3: the failure must attribute to the per-class SLO, not a drop [$out]"
|
||||
# It must NOT be the 'never observed' branch: the delta WAS observed/delivered,
|
||||
# just too slowly. This proves O3 exercises the SLO clause specifically.
|
||||
if echo "$out" | grep -qi 'never OBSERVED'; then
|
||||
if echo "$out" | has_match -qi 'never OBSERVED'; then
|
||||
fail_msg "O3: an SLO breach must NOT be misreported as a dropped delta [$out]"
|
||||
fi
|
||||
) && ok
|
||||
@@ -110,7 +113,7 @@ echo "== O4: verdict is OFF-DOMAIN (from terminal consumed_seq, not detector sel
|
||||
out="$("$ORACLE" run --slo-seconds 120 --count 1 2>&1)"
|
||||
rc=$?
|
||||
[ "$rc" -ne 0 ] || fail_msg "O4: a drive that exits 0 but delivers nothing must still be FN-DETECTED (verdict is off-domain)"
|
||||
echo "$out" | grep -q 'VERDICT = FN-DETECTED' || fail_msg "O4: off-domain verdict must not be fooled by a clean exit code [$out]"
|
||||
echo "$out" | has_match -q 'VERDICT = FN-DETECTED' || fail_msg "O4: off-domain verdict must not be fooled by a clean exit code [$out]"
|
||||
) && ok
|
||||
|
||||
echo "== O5: no invented SLO -> --slo-seconds is REQUIRED (fail-loud) =="
|
||||
@@ -121,12 +124,12 @@ echo "== O5: no invented SLO -> --slo-seconds is REQUIRED (fail-loud) =="
|
||||
err="$("$ORACLE" run --count 1 2>&1)"
|
||||
rc=$?
|
||||
[ "$rc" -ne 0 ] || fail_msg "O5: run without an SLO must fail loud (no invented default)"
|
||||
echo "$err" | grep -qi 'slo' || fail_msg "O5: the usage error must name the missing SLO [$err]"
|
||||
echo "$err" | has_match -qi 'slo' || fail_msg "O5: the usage error must name the missing SLO [$err]"
|
||||
) && ok
|
||||
|
||||
echo
|
||||
if [ -s "$FAILFILE" ]; then
|
||||
echo "wake fn-oracle harness: FAILED ($(grep -c . "$FAILFILE") assertion(s))" >&2
|
||||
echo "wake fn-oracle harness: FAILED ($(count_lines . "$FAILFILE") assertion(s))" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "wake fn-oracle harness: all invariants passed ($pass groups)"
|
||||
|
||||
@@ -34,6 +34,9 @@
|
||||
set -uo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# #973: three-valued grep assertion helpers (has_match/count_lines); init saves real stderr for abort loudness.
|
||||
# shellcheck disable=SC1091
|
||||
. "$SCRIPT_DIR/_wake-common.sh" && wake_assert_init
|
||||
WI="$SCRIPT_DIR/wake-install.sh"
|
||||
BEACON="$SCRIPT_DIR/beacon.sh"
|
||||
FRAMEWORK_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
@@ -94,7 +97,7 @@ EOF
|
||||
export WAKE_INSTALL_MANIFEST="$BAD_MANIFEST"
|
||||
out="$(bash "$WI" install 2>&1)"; rc=$?
|
||||
[ "$rc" -ne 0 ] || fail_msg "I2: install must FAIL when a wake candidate is not framework-owned (got rc=$rc)"
|
||||
echo "$out" | grep -qi 'Gate A VIOLATION' || fail_msg "I2: refusal must name the Gate A violation [$out]"
|
||||
echo "$out" | has_match -qi 'Gate A VIOLATION' || fail_msg "I2: refusal must name the Gate A violation [$out]"
|
||||
# No partial write: the target must have received NO wake tool file.
|
||||
[ ! -e "$TGT/tools/wake/beacon.sh" ] || fail_msg "I2: a refused install must not have written any wake file (partial write leaked)"
|
||||
# And the positive control: with the REAL manifest, the same candidates install.
|
||||
@@ -121,7 +124,7 @@ EOF
|
||||
|| fail_msg "I3: blank-reset drop-in write failed"
|
||||
out="$(bash "$WI" verify-single "$UNIT" 2>&1)"; rc=$?
|
||||
[ "$rc" -eq 0 ] || fail_msg "I3: blank-reset must resolve exactly one OnUnitActiveUSec (rc=$rc) [$out]"
|
||||
echo "$out" | grep -qi 'EXACTLY ONE' || fail_msg "I3: verify must confirm exactly-one [$out]"
|
||||
echo "$out" | has_match -qi 'EXACTLY ONE' || fail_msg "I3: verify must confirm exactly-one [$out]"
|
||||
# NEGATIVE CONTROL: a drop-in WITHOUT the reset line appends -> TWO values -> fail.
|
||||
cat >"$UD/$UNIT.d/interval.conf" <<'EOF'
|
||||
[Timer]
|
||||
@@ -141,7 +144,7 @@ echo "== I4: snapshot-guard — reap without a snapshot is REFUSED; allowed afte
|
||||
# (a) reap WITHOUT snapshot -> refuse, unit still present.
|
||||
out="$(bash "$WI" reap-unit "$UNIT" 2>&1)"; rc=$?
|
||||
[ "$rc" -ne 0 ] || fail_msg "I4: reap without a snapshot must be REFUSED (rc=$rc)"
|
||||
echo "$out" | grep -qi 'snapshot-guard' || fail_msg "I4: refusal must name the snapshot-guard [$out]"
|
||||
echo "$out" | has_match -qi 'snapshot-guard' || fail_msg "I4: refusal must name the snapshot-guard [$out]"
|
||||
[ -f "$UD/$UNIT" ] || fail_msg "I4: a refused reap must NOT delete the unit"
|
||||
# (b) snapshot, then reap -> allowed, unit gone, snapshot retained.
|
||||
bash "$WI" snapshot-unit "$UNIT" >/dev/null 2>&1 || fail_msg "I4: snapshot-unit failed"
|
||||
@@ -161,28 +164,28 @@ echo "== I5: fail-closed install-validate — unconfigured HMAC/alarm FAIL LOUD;
|
||||
export WAKE_HMAC_KEY_NAME="primary"
|
||||
out="$(bash "$WI" validate-hmac-key 2>&1)"; rc=$?
|
||||
[ "$rc" -ne 0 ] || fail_msg "I5: missing credential store must FAIL LOUD for the HMAC key (rc=$rc)"
|
||||
echo "$out" | grep -qi 'UNSIGNED' || fail_msg "I5: HMAC failure must warn about unsigned wakes [$out]"
|
||||
echo "$out" | has_match -qi 'UNSIGNED' || fail_msg "I5: HMAC failure must warn about unsigned wakes [$out]"
|
||||
# Now provide the key by-name -> pass, and the value must NEVER be echoed.
|
||||
jq -cn --arg k "$SECRET_KEY" '{wake:{hmac_keys:{"primary":$k}}}' >"$CRED"
|
||||
out2="$(bash "$WI" validate-hmac-key 2>&1)"; rc2=$?
|
||||
[ "$rc2" -eq 0 ] || fail_msg "I5: a by-name-resolvable HMAC key must pass (rc=$rc2) [$out2]"
|
||||
echo "$out2" | grep -qF "$SECRET_KEY" && fail_msg "I5: validate must NEVER echo the HMAC key material"
|
||||
echo "$out2" | has_match -qF "$SECRET_KEY" && fail_msg "I5: validate must NEVER echo the HMAC key material"
|
||||
# --- alarm target: unconfigured -> fail loud (silent no-alarm host).
|
||||
unset WAKE_ALARM_SINK_CMD
|
||||
out3="$(bash "$WI" validate-alarm-target 2>&1)"; rc3=$?
|
||||
[ "$rc3" -ne 0 ] || fail_msg "I5: unconfigured alarm target must FAIL LOUD (rc=$rc3)"
|
||||
echo "$out3" | grep -qi 'silent no-alarm host' || fail_msg "I5: alarm failure must name the silent-no-alarm hazard [$out3]"
|
||||
echo "$out3" | has_match -qi 'silent no-alarm host' || fail_msg "I5: alarm failure must name the silent-no-alarm hazard [$out3]"
|
||||
# unreachable -> fail loud.
|
||||
export WAKE_ALARM_SINK_CMD="false"
|
||||
out4="$(bash "$WI" validate-alarm-target 2>&1)"; rc4=$?
|
||||
[ "$rc4" -ne 0 ] || fail_msg "I5: unreachable alarm sink must FAIL LOUD (rc=$rc4)"
|
||||
echo "$out4" | grep -qi 'UNREACHABLE' || fail_msg "I5: alarm failure must name the unreachable target [$out4]"
|
||||
echo "$out4" | has_match -qi 'UNREACHABLE' || fail_msg "I5: alarm failure must name the unreachable target [$out4]"
|
||||
# reachable -> pass.
|
||||
export WAKE_ALARM_SINK_CMD="cat >/dev/null"
|
||||
bash "$WI" validate-alarm-target >/dev/null 2>&1 || fail_msg "I5: a configured+reachable alarm sink must pass"
|
||||
# The installer file must inline NO secret/endpoint.
|
||||
grep -qF "$SECRET_ENDPOINT" "$WI" && fail_msg "I5: wake-install.sh must NOT inline any alarm endpoint"
|
||||
grep -qE 'hmac_keys[^A-Za-z_].*=[^=].*[A-Za-z0-9]{8,}' "$WI" && fail_msg "I5: wake-install.sh must NOT inline key material"
|
||||
has_match -qF "$SECRET_ENDPOINT" "$WI" && fail_msg "I5: wake-install.sh must NOT inline any alarm endpoint"
|
||||
has_match -qE 'hmac_keys[^A-Za-z_].*=[^=].*[A-Za-z0-9]{8,}' "$WI" && fail_msg "I5: wake-install.sh must NOT inline key material"
|
||||
true
|
||||
) && ok
|
||||
|
||||
@@ -204,7 +207,7 @@ echo "== I6: reset->verify->retire — overlap keeps legacy running; only §4-ve
|
||||
[ "$rc" -eq 0 ] || fail_msg "I6: overlap reset-verify must succeed (rc=$rc) [$out]"
|
||||
[ -f "$UD/$TIMER" ] || fail_msg "I6: overlap phase must LEAVE the legacy timer running (retire withheld)"
|
||||
[ -f "$SNAP/$TIMER" ] || fail_msg "I6: the legacy timer must be snapshotted before any retire"
|
||||
echo "$out" | grep -qi 'LEFT RUNNING' || fail_msg "I6: overlap must announce retire is withheld [$out]"
|
||||
echo "$out" | has_match -qi 'LEFT RUNNING' || fail_msg "I6: overlap must announce retire is withheld [$out]"
|
||||
# (b) §4-vector pass: retire LAST, snapshot-guarded (fallback proven live above).
|
||||
out2="$(bash "$WI" reset-verify-retire c --interval 30min --vector-passed 2>&1)"; rc2=$?
|
||||
[ "$rc2" -eq 0 ] || fail_msg "I6: vector-passed retire must succeed (rc=$rc2) [$out2]"
|
||||
@@ -283,11 +286,11 @@ echo "== I9: dep-check — a host seed missing _lib/manifest.sh FAILS LOUD (name
|
||||
TGT="$(fresh i9-target)"
|
||||
out="$(WAKE_INSTALL_SOURCE="$FAKE" WAKE_INSTALL_TARGET="$TGT" bash "$WI_FAKE" install 2>&1)"; rc=$?
|
||||
[ "$rc" -ne 0 ] || fail_msg "I9: install MUST fail when _lib/manifest.sh is missing (rc=$rc)"
|
||||
echo "$out" | grep -qi 'manifest.sh' || fail_msg "I9: the failure must NAME the missing library (manifest.sh) [$out]"
|
||||
echo "$out" | grep -qiE 'REMEDY|mosaic update|re-seed|framework install' \
|
||||
echo "$out" | has_match -qi 'manifest.sh' || fail_msg "I9: the failure must NAME the missing library (manifest.sh) [$out]"
|
||||
echo "$out" | has_match -qiE 'REMEDY|mosaic update|re-seed|framework install' \
|
||||
|| fail_msg "I9: the failure must give an actionable REMEDY, not just an error [$out]"
|
||||
# It must be a CLEAR fail-loud, NOT the obscure bare `source: No such file or directory`.
|
||||
echo "$out" | grep -qi 'No such file or directory' \
|
||||
echo "$out" | has_match -qi 'No such file or directory' \
|
||||
&& fail_msg "I9: must not fail with a bare source error (obscure) [$out]"
|
||||
# Positive control: with the helper present (real framework root) install succeeds.
|
||||
TGT_OK="$(fresh i9-target-ok)"
|
||||
@@ -313,10 +316,10 @@ echo "== I10: systemd search-path — install links mosaic-wake.service into the
|
||||
rm -f "$UD/mosaic-wake.service"
|
||||
out2="$(bash "$WI" validate-systemd-path 2>&1)"; rc2=$?
|
||||
[ "$rc2" -ne 0 ] || fail_msg "I10: validate must FAIL when the unit is not in the search path (rc=$rc2) [$out2]"
|
||||
echo "$out2" | grep -qi 'search path' || fail_msg "I10: validate failure must name the search-path miss [$out2]"
|
||||
echo "$out2" | has_match -qi 'search path' || fail_msg "I10: validate failure must name the search-path miss [$out2]"
|
||||
# (c) idempotent re-install: exactly one search-path entry, still resolvable.
|
||||
bash "$WI" install >/dev/null 2>&1 || fail_msg "I10: re-run install must succeed"
|
||||
n="$(find "$UD" -maxdepth 1 -name 'mosaic-wake.service' | grep -c .)"
|
||||
n="$(find "$UD" -maxdepth 1 -name 'mosaic-wake.service' | count_lines .)"
|
||||
[ "$n" -eq 1 ] || fail_msg "I10: re-install must not duplicate the search-path entry (found $n)"
|
||||
bash "$WI" validate-systemd-path >/dev/null 2>&1 || fail_msg "I10: re-install must keep the unit resolvable"
|
||||
) && ok
|
||||
@@ -334,12 +337,12 @@ echo "== I11: F7 — the legacy reap REFUSES unless the canon fallback wake is p
|
||||
# it must REFUSE (schedulable floor not met).
|
||||
out0="$(bash "$WI" fallback-proven-live 2>&1)"; rc0=$?
|
||||
[ "$rc0" -ne 0 ] || fail_msg "I11: fallback-proven-live must FAIL when the fallback wake is not installed (rc=$rc0)"
|
||||
echo "$out0" | grep -qi 'F7' || fail_msg "I11: the refusal must name the F7 precondition [$out0]"
|
||||
echo "$out0" | has_match -qi 'F7' || fail_msg "I11: the refusal must name the F7 precondition [$out0]"
|
||||
# (b) NEGATIVE — vector-passed reap with NO live fallback must be REFUSED and the
|
||||
# legacy timer LEFT RUNNING (never a coverage gap).
|
||||
out="$(bash "$WI" reset-verify-retire f --interval 30min --vector-passed 2>&1)"; rc=$?
|
||||
[ "$rc" -ne 0 ] || fail_msg "I11: reap must be REFUSED without a proven-live fallback (rc=$rc)"
|
||||
echo "$out" | grep -qi 'FALLBACK WAKE is not proven live' || fail_msg "I11: refusal must name the un-proven fallback [$out]"
|
||||
echo "$out" | has_match -qi 'FALLBACK WAKE is not proven live' || fail_msg "I11: refusal must name the un-proven fallback [$out]"
|
||||
[ -f "$UD/$TIMER" ] || fail_msg "I11: a refused reap must LEAVE the legacy timer running (F7 — no coverage gap)"
|
||||
# (c) POSITIVE — provision the fallback at the schedulable floor, then the reap
|
||||
# proceeds (F7 satisfied) and the legacy timer is retired.
|
||||
@@ -367,11 +370,11 @@ echo "== I12: fallback drain — a STALLED detector still gets delivery via the
|
||||
# even with no detector running — no starvation of the drain.
|
||||
out="$(bash "$DIGEST" render --from-store 2>/dev/null)"; rc=$?
|
||||
[ "$rc" -eq 0 ] || fail_msg "I12: the canon fallback drain must exit 0 (rc=$rc)"
|
||||
echo "$out" | grep -q 'STALLED-DRAIN-MARK' \
|
||||
echo "$out" | has_match -q 'STALLED-DRAIN-MARK' \
|
||||
|| fail_msg "I12: the fallback drain must DELIVER the pending obligation a stalled detector left (no delivery starvation) [$out]"
|
||||
# Bind the invariant to the SHIPPED unit: its ExecStart must be this canon drain.
|
||||
UNIT="$FRAMEWORK_ROOT/systemd/user/mosaic-wake-fallback.service"
|
||||
grep -qE '^ExecStart=.*digest\.sh render --from-store' "$UNIT" \
|
||||
has_match -qE '^ExecStart=.*digest\.sh render --from-store' "$UNIT" \
|
||||
|| fail_msg "I12: mosaic-wake-fallback.service ExecStart must fire the canon drain (digest.sh render --from-store)"
|
||||
) && ok
|
||||
|
||||
@@ -391,11 +394,11 @@ echo "== I13: install links + validates the canon fallback timer+service into th
|
||||
rm -f "$UD/mosaic-wake-fallback.timer"
|
||||
out="$(bash "$WI" validate-fallback-units 2>&1)"; rc=$?
|
||||
[ "$rc" -ne 0 ] || fail_msg "I13: validate must FAIL when a fallback unit is not in the search path (rc=$rc) [$out]"
|
||||
echo "$out" | grep -qi 'search path' || fail_msg "I13: validate failure must name the search-path miss [$out]"
|
||||
echo "$out" | has_match -qi 'search path' || fail_msg "I13: validate failure must name the search-path miss [$out]"
|
||||
# (c) idempotent re-install: exactly one entry per unit, still resolvable.
|
||||
bash "$WI" install >/dev/null 2>&1 || fail_msg "I13: re-run install must succeed"
|
||||
nt="$(find "$UD" -maxdepth 1 -name 'mosaic-wake-fallback.timer' | grep -c .)"
|
||||
ns="$(find "$UD" -maxdepth 1 -name 'mosaic-wake-fallback.service' | grep -c .)"
|
||||
nt="$(find "$UD" -maxdepth 1 -name 'mosaic-wake-fallback.timer' | count_lines .)"
|
||||
ns="$(find "$UD" -maxdepth 1 -name 'mosaic-wake-fallback.service' | count_lines .)"
|
||||
[ "$nt" -eq 1 ] && [ "$ns" -eq 1 ] || fail_msg "I13: re-install must not duplicate the fallback entries (timer=$nt service=$ns)"
|
||||
bash "$WI" validate-fallback-units >/dev/null 2>&1 || fail_msg "I13: re-install must keep the fallback units resolvable"
|
||||
) && ok
|
||||
@@ -407,17 +410,17 @@ echo "== I14: per-class fallback cadence — blank-reset yields EXACTLY ONE OnUn
|
||||
TIMER="mosaic-wake-fallback.timer"
|
||||
# The shipped timer carries the base OnUnitActiveSec placeholder (=1h).
|
||||
cp "$FRAMEWORK_ROOT/systemd/user/$TIMER" "$UD/$TIMER"
|
||||
grep -q '^OnUnitActiveSec=1h' "$UD/$TIMER" || fail_msg "I14: shipped fallback timer must carry a base OnUnitActiveSec placeholder"
|
||||
has_match -q '^OnUnitActiveSec=1h' "$UD/$TIMER" || fail_msg "I14: shipped fallback timer must carry a base OnUnitActiveSec placeholder"
|
||||
# write-fallback-cadence writes the per-class cadence in BLANK-RESET form.
|
||||
out="$(bash "$WI" write-fallback-cadence 30min 2>&1)"; rc=$?
|
||||
[ "$rc" -eq 0 ] || fail_msg "I14: write-fallback-cadence must succeed + verify single (rc=$rc) [$out]"
|
||||
echo "$out" | grep -qi 'exactly one' || fail_msg "I14: the cadence write must confirm exactly-one OnUnitActiveUSec [$out]"
|
||||
echo "$out" | has_match -qi 'exactly one' || fail_msg "I14: the cadence write must confirm exactly-one OnUnitActiveUSec [$out]"
|
||||
[ -f "$UD/$TIMER.d/cadence.conf" ] || fail_msg "I14: the per-class cadence drop-in must be written under <timer>.d/"
|
||||
# Assert the blank-reset FORM: an empty `OnUnitActiveSec=` reset line IMMEDIATELY
|
||||
# FOLLOWED by the new value. Portable across GNU and BusyBox grep (Alpine CI) —
|
||||
# `grep -z` (NUL-data) is a GNU-only extension BusyBox grep does NOT support, so
|
||||
# match the reset line and require the value on the very next line (-A1) instead.
|
||||
grep -A1 '^OnUnitActiveSec=$' "$UD/$TIMER.d/cadence.conf" | grep -qx 'OnUnitActiveSec=30min' \
|
||||
has_match -A1 '^OnUnitActiveSec=$' "$UD/$TIMER.d/cadence.conf" | has_match -qx 'OnUnitActiveSec=30min' \
|
||||
|| fail_msg "I14: the drop-in must use the blank-reset form (empty reset line, then the value)"
|
||||
bash "$WI" verify-single "$TIMER" >/dev/null 2>&1 || fail_msg "I14: base(1h)+blank-reset(30min) must resolve exactly one OnUnitActiveUSec"
|
||||
# NEGATIVE CONTROL: a drop-in WITHOUT the reset line appends -> base 1h + 30min -> two -> fail.
|
||||
@@ -428,7 +431,7 @@ echo "== I14: per-class fallback cadence — blank-reset yields EXACTLY ONE OnUn
|
||||
|
||||
echo
|
||||
if [ -s "$FAILFILE" ]; then
|
||||
echo "wake install harness: FAILED ($(grep -c . "$FAILFILE") assertion(s))" >&2
|
||||
echo "wake install harness: FAILED ($(count_lines . "$FAILFILE") assertion(s))" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "wake install harness: all invariants passed ($pass groups)"
|
||||
|
||||
@@ -46,6 +46,9 @@
|
||||
set -uo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# #973: three-valued grep assertion helpers (has_match/count_lines); init saves real stderr for abort loudness.
|
||||
# shellcheck disable=SC1091
|
||||
. "$SCRIPT_DIR/_wake-common.sh" && wake_assert_init
|
||||
PRE="$SCRIPT_DIR/preimage.sh"
|
||||
STORE="$SCRIPT_DIR/store.sh"
|
||||
DET="$SCRIPT_DIR/detector.sh"
|
||||
@@ -202,9 +205,9 @@ echo "== P4: credential-store PATH refused — bytes never captured (acceptance
|
||||
sd="$(state_dir)"
|
||||
row="$(tail -n1 "$sd/preimage/preimage-ledger.jsonl")"
|
||||
[ "$(jq -r '.captured' <<<"$row")" = "false" ] || fail_msg "P4: row must record captured=false [$row]"
|
||||
jq -r '.refused // ""' <<<"$row" | grep -qi 'path' || fail_msg "P4: refusal must name the path deny [$row]"
|
||||
jq -r '.refused // ""' <<<"$row" | has_match -qi 'path' || fail_msg "P4: refusal must name the path deny [$row]"
|
||||
# THE invariant: the secret bytes exist NOWHERE under the wake state dir.
|
||||
if grep -rq "$secret" "$sd" 2>/dev/null; then
|
||||
if has_match -rq "$secret" "$sd" 2>/dev/null; then
|
||||
fail_msg "P4: credential bytes leaked into the wake state dir"
|
||||
fi
|
||||
) && ok
|
||||
@@ -225,8 +228,8 @@ echo "== P5: secret-SHAPED content refused (refusal, not redaction) =="
|
||||
sd="$(state_dir)"
|
||||
row="$(tail -n1 "$sd/preimage/preimage-ledger.jsonl")"
|
||||
[ "$(jq -r '.captured' <<<"$row")" = "false" ] || fail_msg "P5: row must record captured=false [$row]"
|
||||
jq -r '.refused // ""' <<<"$row" | grep -qi 'secret' || fail_msg "P5: refusal must name secret-shaped content [$row]"
|
||||
if grep -rq "$tok" "$sd" 2>/dev/null; then
|
||||
jq -r '.refused // ""' <<<"$row" | has_match -qi 'secret' || fail_msg "P5: refusal must name secret-shaped content [$row]"
|
||||
if has_match -rq "$tok" "$sd" 2>/dev/null; then
|
||||
fail_msg "P5: secret-shaped bytes leaked into the wake state dir"
|
||||
fi
|
||||
) && ok
|
||||
@@ -275,7 +278,7 @@ echo "== P8: unresolvable adapter -> FAIL LOUD, never 'no change' (D2 class) =="
|
||||
unset WAKE_WATCH_LIST WAKE_PREIMAGE_EXTRA MOSAIC_HOME
|
||||
out="$("$PRE" check --enqueue 2>&1)"; rc=$?
|
||||
[ "$rc" -ne 0 ] || fail_msg "P8: an unobservable preimage definition must FAIL LOUD (rc=0)"
|
||||
echo "$out" | grep -qi 'does not resolve' || fail_msg "P8: the failure must name the unresolvable adapter [$out]"
|
||||
echo "$out" | has_match -qi 'does not resolve' || fail_msg "P8: the failure must name the unresolvable adapter [$out]"
|
||||
) && ok
|
||||
|
||||
echo "== P9: corrupt ledger -> REFUSE to compare/re-baseline =="
|
||||
@@ -294,7 +297,7 @@ echo "== P9: corrupt ledger -> REFUSE to compare/re-baseline =="
|
||||
printf 'v2 changed\n' >"$fx/f.env"
|
||||
out="$("$PRE" check --enqueue 2>&1)"; rc=$?
|
||||
[ "$rc" -ne 0 ] || fail_msg "P9: a corrupt ledger must FAIL LOUD (rc=0)"
|
||||
echo "$out" | grep -qi 'unparseable' || fail_msg "P9: the failure must name the corrupt ledger [$out]"
|
||||
echo "$out" | has_match -qi 'unparseable' || fail_msg "P9: the failure must name the corrupt ledger [$out]"
|
||||
[ "$(wc -l <"$sd/preimage/preimage-ledger.jsonl")" = "$r1" ] || fail_msg "P9: nothing may be appended over corrupt history"
|
||||
) && ok
|
||||
|
||||
@@ -314,7 +317,7 @@ echo "== P10: oversized file -> bytes refused, hash still recorded =="
|
||||
sd="$(state_dir)"
|
||||
row="$(tail -n1 "$sd/preimage/preimage-ledger.jsonl")"
|
||||
[ "$(jq -r '.captured' <<<"$row")" = "false" ] || fail_msg "P10: row must record captured=false [$row]"
|
||||
jq -r '.refused // ""' <<<"$row" | grep -qi 'MAX_BYTES' || fail_msg "P10: refusal must name the size cap [$row]"
|
||||
jq -r '.refused // ""' <<<"$row" | has_match -qi 'MAX_BYTES' || fail_msg "P10: refusal must name the size cap [$row]"
|
||||
sha="$(jq -r '.sha256' <<<"$row")"
|
||||
[ "$sha" = "$(sha256sum "$fx/big.bin" | awk '{print $1}')" ] || fail_msg "P10: hash must still be recorded [$row]"
|
||||
[ ! -f "$sd/preimage/objects/$sha" ] || fail_msg "P10: oversized bytes must NOT be stored"
|
||||
@@ -362,7 +365,7 @@ echo "== P12: detector — preimage infra failure is LOUD but does not starve ob
|
||||
printf 'NOT-JSON-GARBAGE{{{\n' >"$sd/preimage/preimage-ledger.jsonl"
|
||||
out="$("$DET" poll-once 2>&1)"; rc=$?
|
||||
[ "$rc" -ne 0 ] || fail_msg "P12: the pass must exit non-zero on preimage infra failure"
|
||||
echo "$out" | grep -qi 'preimage' || fail_msg "P12: the failure must name the preimage check [$out]"
|
||||
echo "$out" | has_match -qi 'preimage' || fail_msg "P12: the failure must name the preimage check [$out]"
|
||||
n="$(find "$sd/detector" -name 'watch-*.hash' 2>/dev/null | wc -l | tr -d '[:space:]')"
|
||||
[ "$n" -ge 1 ] || fail_msg "P12: source observation must still proceed (no watch hash baselined)"
|
||||
) && ok
|
||||
@@ -397,10 +400,10 @@ echo "== P13: symlinked/renamed credential store refused on BOTH path forms (#96
|
||||
n="$(jq -r 'select(.captured == false) | .path' "$sd/preimage/preimage-ledger.jsonl" | wc -l | tr -d '[:space:]')"
|
||||
[ "$n" = "2" ] || fail_msg "P13: both decoys must record captured=false (got $n)"
|
||||
# THE invariant (decoy method): the planted bytes exist NOWHERE in state.
|
||||
if grep -rq "$s1" "$sd" 2>/dev/null; then
|
||||
if has_match -rq "$s1" "$sd" 2>/dev/null; then
|
||||
fail_msg "P13: renamed-target store bytes leaked into the wake state dir"
|
||||
fi
|
||||
if grep -rq "$s2" "$sd" 2>/dev/null; then
|
||||
if has_match -rq "$s2" "$sd" 2>/dev/null; then
|
||||
fail_msg "P13: prefix-less key bytes leaked into the wake state dir"
|
||||
fi
|
||||
) && ok
|
||||
@@ -423,7 +426,7 @@ echo "== P14: detector.env-class key — safe WITHOUT opt-in by polarity, refuse
|
||||
sd="$(state_dir)"
|
||||
row="$(tail -n1 "$sd/preimage/preimage-ledger.jsonl")"
|
||||
[ "$(jq -r '.captured' <<<"$row")" = "false" ] || fail_msg "P14a: extra must be record-only without opt-in [$row]"
|
||||
if grep -rq "$hm" "$sd" 2>/dev/null; then
|
||||
if has_match -rq "$hm" "$sd" 2>/dev/null; then
|
||||
fail_msg "P14a: key bytes leaked without any opt-in"
|
||||
fi
|
||||
# (b) The operator opts the env file in (the exact error the old usage text
|
||||
@@ -434,8 +437,8 @@ echo "== P14: detector.env-class key — safe WITHOUT opt-in by polarity, refuse
|
||||
[ "$rc" -eq 0 ] || fail_msg "P14b: refusal is not an infra failure (rc=$rc) [$out]"
|
||||
row="$(tail -n1 "$sd/preimage/preimage-ledger.jsonl")"
|
||||
[ "$(jq -r '.captured' <<<"$row")" = "false" ] || fail_msg "P14b: opted-in key material must still refuse [$row]"
|
||||
jq -r '.refused // ""' <<<"$row" | grep -qi 'secret' || fail_msg "P14b: refusal must name secret-shaped content [$row]"
|
||||
if grep -rq "$hm" "$sd" 2>/dev/null; then
|
||||
jq -r '.refused // ""' <<<"$row" | has_match -qi 'secret' || fail_msg "P14b: refusal must name secret-shaped content [$row]"
|
||||
if has_match -rq "$hm" "$sd" 2>/dev/null; then
|
||||
fail_msg "P14b: key bytes leaked despite refusal"
|
||||
fi
|
||||
) && ok
|
||||
@@ -488,7 +491,7 @@ echo "== P16: deleted ledger over surviving objects/ — REFUSE to re-baseline (
|
||||
printf '# v3\n' >>"$fx/adapter.sh"
|
||||
out="$("$PRE" check --enqueue 2>&1)"; rc=$?
|
||||
[ "$rc" -ne 0 ] || fail_msg "P16: absent ledger over prior objects must FAIL LOUD (rc=0) [$out]"
|
||||
echo "$out" | grep -qi 'REFUSING to re-baseline' || fail_msg "P16: the failure must name the refusal [$out]"
|
||||
echo "$out" | has_match -qi 'REFUSING to re-baseline' || fail_msg "P16: the failure must name the refusal [$out]"
|
||||
[ ! -e "$sd/preimage/preimage-ledger.jsonl" ] || fail_msg "P16: the ledger must NOT be silently recreated over deleted history"
|
||||
[ "$(ls "$sd/preimage/objects" | wc -l | tr -d '[:space:]')" = "$nobj" ] || fail_msg "P16: prior objects must remain untouched"
|
||||
[ "$(depth)" = "0" ] || fail_msg "P16: the v2->v3 change must NOT be absorbed or enqueued from an unverifiable baseline (depth=$(depth))"
|
||||
@@ -520,7 +523,7 @@ echo "== P17: allowlist symmetry — symlink to a DENIED target refuses; symlink
|
||||
sd="$(state_dir)"
|
||||
crow="$(jq -c --arg p "$(realpath "$fx/settings.json")" 'select(.path == $p)' "$sd/preimage/preimage-ledger.jsonl" | tail -n1)"
|
||||
[ "$(jq -r '.captured' <<<"$crow")" = "false" ] || fail_msg "P17: allowlisted symlink to a denied target must refuse [$crow]"
|
||||
if grep -rq "$s" "$sd" 2>/dev/null; then
|
||||
if has_match -rq "$s" "$sd" 2>/dev/null; then
|
||||
fail_msg "P17: credential bytes leaked via an allowlisted alias"
|
||||
fi
|
||||
brow="$(jq -c --arg p "$(realpath "$fx/alias.cfg")" 'select(.path == $p)' "$sd/preimage/preimage-ledger.jsonl" | tail -n1)"
|
||||
|
||||
@@ -29,6 +29,9 @@
|
||||
set -uo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# #973: three-valued grep assertion helpers (has_match/count_lines); init saves real stderr for abort loudness.
|
||||
# shellcheck disable=SC1091
|
||||
. "$SCRIPT_DIR/_wake-common.sh" && wake_assert_init
|
||||
RECON="$SCRIPT_DIR/reconcile.sh"
|
||||
STORE="$SCRIPT_DIR/store.sh"
|
||||
DET="$SCRIPT_DIR/detector.sh"
|
||||
@@ -87,7 +90,7 @@ EOF
|
||||
out="$("$RECON" inventory 2>&1)"
|
||||
rc=$?
|
||||
[ "$rc" -eq 0 ] || fail_msg "R1: a complete inventory must PASS (exit 0) [$out]"
|
||||
echo "$out" | grep -qi 'COMPLETE' || fail_msg "R1: a complete inventory must report COMPLETE [$out]"
|
||||
echo "$out" | has_match -qi 'COMPLETE' || fail_msg "R1: a complete inventory must report COMPLETE [$out]"
|
||||
) && ok
|
||||
|
||||
echo "== R2: OMITTED source -> FLAG (vacuous-pass prevented, not silently green) =="
|
||||
@@ -106,8 +109,8 @@ EOF
|
||||
out="$("$RECON" inventory 2>&1)"
|
||||
rc=$?
|
||||
[ "$rc" -ne 0 ] || fail_msg "R2: an omitted (declared-but-unwatched) source must FLAG (non-zero), not silently pass"
|
||||
echo "$out" | grep -qi 'r2' || fail_msg "R2: the flag must name the omitted source r2 [$out]"
|
||||
echo "$out" | grep -qi 'vacuous' || fail_msg "R2: the flag must state the vacuous-pass is prevented [$out]"
|
||||
echo "$out" | has_match -qi 'r2' || fail_msg "R2: the flag must name the omitted source r2 [$out]"
|
||||
echo "$out" | has_match -qi 'vacuous' || fail_msg "R2: the flag must state the vacuous-pass is prevented [$out]"
|
||||
) && ok
|
||||
|
||||
echo "== R3: required_sources omission -> FLAG =="
|
||||
@@ -126,7 +129,7 @@ EOF
|
||||
out="$("$RECON" inventory 2>&1)"
|
||||
rc=$?
|
||||
[ "$rc" -ne 0 ] || fail_msg "R3: a required_sources omission must FLAG (non-zero)"
|
||||
echo "$out" | grep -qi "requires source 'r2'" || fail_msg "R3: the flag must name the required-but-omitted source r2 [$out]"
|
||||
echo "$out" | has_match -qi "requires source 'r2'" || fail_msg "R3: the flag must name the required-but-omitted source r2 [$out]"
|
||||
) && ok
|
||||
|
||||
echo "== R4: dangling watch reference -> FLAG =="
|
||||
@@ -144,7 +147,7 @@ EOF
|
||||
out="$("$RECON" inventory 2>&1)"
|
||||
rc=$?
|
||||
[ "$rc" -ne 0 ] || fail_msg "R4: a dangling reference must FLAG (non-zero)"
|
||||
echo "$out" | grep -qi 'dangling' || fail_msg "R4: the flag must state it is dangling [$out]"
|
||||
echo "$out" | has_match -qi 'dangling' || fail_msg "R4: the flag must state it is dangling [$out]"
|
||||
) && ok
|
||||
|
||||
echo "== R5/R6: pre-existing state -> UNACCOUNTED flag + enumerated into store; re-run 0 =="
|
||||
@@ -173,14 +176,14 @@ EOF
|
||||
out="$("$RECON" reconcile 2>&1)"
|
||||
rc=$?
|
||||
[ "$rc" -ne 0 ] || fail_msg "R5: pre-existing unaccounted state must FLAG (non-zero) on first reconcile"
|
||||
echo "$out" | grep -qi 'UNACCOUNTED=2' || fail_msg "R5: both pre-existing sources must be UNACCOUNTED [$out]"
|
||||
echo "$out" | has_match -qi 'UNACCOUNTED=2' || fail_msg "R5: both pre-existing sources must be UNACCOUNTED [$out]"
|
||||
# R6: enumerated into the durable store — one entry per pre-existing source.
|
||||
[ "$(depth)" = "2" ] || fail_msg "R6: pre-existing state must be ENUMERATED into the store (depth 2), got $(depth)"
|
||||
# A follow-up reconcile now finds everything accounted -> 0 unaccounted.
|
||||
out2="$("$RECON" reconcile 2>&1)"
|
||||
rc2=$?
|
||||
[ "$rc2" -eq 0 ] || fail_msg "R6: a second reconcile must report 0 unaccounted (exit 0) [$out2]"
|
||||
echo "$out2" | grep -qi 'UNACCOUNTED=0' || fail_msg "R6: second reconcile must be 0 unaccounted [$out2]"
|
||||
echo "$out2" | has_match -qi 'UNACCOUNTED=0' || fail_msg "R6: second reconcile must be 0 unaccounted [$out2]"
|
||||
[ "$(depth)" = "2" ] || fail_msg "R6: a clean reconcile must NOT re-enumerate (depth still 2), got $(depth)"
|
||||
) && ok
|
||||
|
||||
@@ -211,7 +214,7 @@ EOF
|
||||
out="$("$RECON" reconcile 2>&1)"
|
||||
rc=$?
|
||||
[ "$rc" -eq 0 ] || fail_msg "R7: state reflected in the inbox must be ACCOUNTED (exit 0) [$out]"
|
||||
echo "$out" | grep -qi 'UNACCOUNTED=0' || fail_msg "R7: inbox-reflected state must be 0 unaccounted [$out]"
|
||||
echo "$out" | has_match -qi 'UNACCOUNTED=0' || fail_msg "R7: inbox-reflected state must be 0 unaccounted [$out]"
|
||||
[ "$(depth)" = "1" ] || fail_msg "R7: reconciler must NOT double-enumerate inbox-reflected state (depth still 1), got $(depth)"
|
||||
) && ok
|
||||
|
||||
@@ -236,7 +239,7 @@ EOF
|
||||
out="$("$RECON" reconcile 2>&1)"
|
||||
rc=$?
|
||||
[ "$rc" -ne 0 ] || fail_msg "R8: a source error must FAIL LOUD (non-zero exit)"
|
||||
echo "$out" | grep -qi 'FAIL LOUD' || fail_msg "R8: the source error must be loud [$out]"
|
||||
echo "$out" | has_match -qi 'FAIL LOUD' || fail_msg "R8: the source error must be loud [$out]"
|
||||
[ "$(depth)" = "0" ] || fail_msg "R8: a failed observation must NOT enumerate anything, got depth $(depth)"
|
||||
) && ok
|
||||
|
||||
@@ -278,7 +281,7 @@ EOF
|
||||
out="$("$RECON" reconcile 2>&1)"
|
||||
rc=$?
|
||||
[ "$rc" -ne 0 ] || fail_msg "R9: pre-existing beta is unaccounted on this pass -> must FLAG (non-zero) [$out]"
|
||||
echo "$out" | grep -qi 'UNACCOUNTED=1' || fail_msg "R9: only beta should be unaccounted (alpha is inbox-accounted) [$out]"
|
||||
echo "$out" | has_match -qi 'UNACCOUNTED=1' || fail_msg "R9: only beta should be unaccounted (alpha is inbox-accounted) [$out]"
|
||||
[ "$(depth)" = "2" ] || fail_msg "R9: reconciler must ENUMERATE beta into the co-fed store (depth 2), got $(depth)"
|
||||
|
||||
# A further detector delta on beta must allocate 3 — the unified allocator
|
||||
@@ -292,8 +295,8 @@ EOF
|
||||
# already used, so this set would contain a duplicate (alias).
|
||||
seqs="$("$STORE" drain | jq -r '.observed_seq' | sort -n | tr '\n' ' ')"
|
||||
[ "$seqs" = "1 2 3 " ] || fail_msg "R9: co-fed observed_seqs must be distinct+gapless '1 2 3', got '$seqs' (a duplicate = the aliasing #908 dissolved)"
|
||||
ndistinct="$("$STORE" drain | jq -r '.observed_seq' | sort -nu | grep -c .)"
|
||||
ntotal="$("$STORE" drain | jq -r '.observed_seq' | grep -c .)"
|
||||
ndistinct="$("$STORE" drain | jq -r '.observed_seq' | sort -nu | count_lines .)"
|
||||
ntotal="$("$STORE" drain | jq -r '.observed_seq' | count_lines .)"
|
||||
[ "$ndistinct" = "$ntotal" ] || fail_msg "R9: aliasing detected — $ntotal entries but only $ndistinct distinct observed_seq"
|
||||
# The contiguous-prefix CONSUMED contract holds over the co-fed seqs.
|
||||
"$STORE" consume --upto 3 >/dev/null 2>&1 || fail_msg "R9: CONSUMED 3 must succeed over the gapless co-fed prefix"
|
||||
@@ -333,7 +336,7 @@ EOF
|
||||
out="$("$RECON" reconcile 2>&1)"
|
||||
rc=$?
|
||||
[ "$rc" -eq 0 ] || fail_msg "R10: a consumed state must be ACCOUNTED (exit 0, no spurious rc=1 CRITICAL) [$out]"
|
||||
echo "$out" | grep -qi 'UNACCOUNTED=0' || fail_msg "R10: a consumed state must be 0 unaccounted (no re-enumeration) [$out]"
|
||||
echo "$out" | has_match -qi 'UNACCOUNTED=0' || fail_msg "R10: a consumed state must be 0 unaccounted (no re-enumeration) [$out]"
|
||||
[ "$(depth)" = "0" ] || fail_msg "R10: reconciler must NOT re-enumerate a consumed state (depth still 0 = no duplicate wake), got $(depth)"
|
||||
) && ok
|
||||
|
||||
@@ -372,7 +375,7 @@ EOF
|
||||
[ "$rc" -ne 0 ] || fail_msg "R11: a genuine gap (r2) must still FLAG (non-zero) [$out]"
|
||||
# After #932: ONLY r2 is unaccounted (r1 suppressed by the store record). On
|
||||
# BASE both r1 and r2 re-enumerate (UNACCOUNTED=2) -> this assertion is red-first.
|
||||
echo "$out" | grep -qi 'UNACCOUNTED=1' || fail_msg "R11: exactly ONE source (the gap r2) must be unaccounted; the consumed r1 must be suppressed [$out]"
|
||||
echo "$out" | has_match -qi 'UNACCOUNTED=1' || fail_msg "R11: exactly ONE source (the gap r2) must be unaccounted; the consumed r1 must be suppressed [$out]"
|
||||
# Prove precisely WHICH state re-enumerated: the gap r2 IS enumerated, the
|
||||
# consumed r1 is NOT. (base re-enumerates r1 too -> the r1-absent assertion is
|
||||
# red-first; the r2-present assertion holds both before and after = no over-suppression.)
|
||||
@@ -383,7 +386,7 @@ EOF
|
||||
|
||||
echo
|
||||
if [ -s "$FAILFILE" ]; then
|
||||
echo "wake reconcile harness: FAILED ($(grep -c . "$FAILFILE") assertion(s))" >&2
|
||||
echo "wake reconcile harness: FAILED ($(count_lines . "$FAILFILE") assertion(s))" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "wake reconcile harness: all invariants passed ($pass groups)"
|
||||
|
||||
@@ -42,6 +42,9 @@
|
||||
set -uo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# #973: three-valued grep assertion helpers (has_match/count_lines); init saves real stderr for abort loudness.
|
||||
# shellcheck disable=SC1091
|
||||
. "$SCRIPT_DIR/_wake-common.sh" && wake_assert_init
|
||||
STORE="$SCRIPT_DIR/store.sh"
|
||||
ACK="$SCRIPT_DIR/ack.sh"
|
||||
|
||||
@@ -96,14 +99,14 @@ echo "== T1: three-cursor advancement =="
|
||||
"$STORE" enqueue --seq 1 --class actionable --locators '{"repo":"r","issue":1}' >/dev/null
|
||||
"$STORE" enqueue --seq 2 --class actionable --locators '{"repo":"r","issue":2}' >/dev/null
|
||||
cur="$("$STORE" cursors)"
|
||||
echo "$cur" | grep -q 'observed_seq=2' || fail_msg "T1: observed_seq should be 2 after enqueue 1,2 [$cur]"
|
||||
echo "$cur" | grep -q 'consumed_seq=0' || fail_msg "T1: consumed_seq must NOT advance on enqueue (only on CONSUMED ack) [$cur]"
|
||||
echo "$cur" | grep -q 'pending_depth=2' || fail_msg "T1: pending depth should be 2 [$cur]"
|
||||
echo "$cur" | has_match -q 'observed_seq=2' || fail_msg "T1: observed_seq should be 2 after enqueue 1,2 [$cur]"
|
||||
echo "$cur" | has_match -q 'consumed_seq=0' || fail_msg "T1: consumed_seq must NOT advance on enqueue (only on CONSUMED ack) [$cur]"
|
||||
echo "$cur" | has_match -q 'pending_depth=2' || fail_msg "T1: pending depth should be 2 [$cur]"
|
||||
# consumed_seq advances only on CONSUMED.
|
||||
"$STORE" consume --upto 2 >/dev/null
|
||||
cur="$("$STORE" cursors)"
|
||||
echo "$cur" | grep -q 'consumed_seq=2' || fail_msg "T1: consumed_seq should be 2 after CONSUMED 2 [$cur]"
|
||||
echo "$cur" | grep -q 'pending_depth=0' || fail_msg "T1: pending drained after CONSUMED [$cur]"
|
||||
echo "$cur" | has_match -q 'consumed_seq=2' || fail_msg "T1: consumed_seq should be 2 after CONSUMED 2 [$cur]"
|
||||
echo "$cur" | has_match -q 'pending_depth=0' || fail_msg "T1: pending drained after CONSUMED [$cur]"
|
||||
) && ok
|
||||
|
||||
echo "== T2: digest coalesce-REPLACE vs actionable APPEND =="
|
||||
@@ -116,16 +119,16 @@ echo "== T2: digest coalesce-REPLACE vs actionable APPEND =="
|
||||
"$STORE" enqueue --seq 3 --class digest --locators '{"head":"bbb"}' >/dev/null
|
||||
"$STORE" enqueue --seq 4 --class human --locators '{"from":"peer"}' >/dev/null
|
||||
out="$("$STORE" drain)"
|
||||
ndigest="$(printf '%s\n' "$out" | jq -c 'select(.class=="digest")' | grep -c . || true)"
|
||||
ndigest="$(printf '%s\n' "$out" | jq -c 'select(.class=="digest")' | count_lines . || true)"
|
||||
[ "$ndigest" = "1" ] || fail_msg "T2: digest must COALESCE to a single pending entry, got $ndigest"
|
||||
head="$(printf '%s\n' "$out" | jq -r 'select(.class=="digest") | .locators.head')"
|
||||
[ "$head" = "bbb" ] || fail_msg "T2: newest digest must REPLACE prior (expected bbb, got $head)"
|
||||
nactionable="$(printf '%s\n' "$out" | jq -c 'select(.class=="actionable")' | grep -c . || true)"
|
||||
nhuman="$(printf '%s\n' "$out" | jq -c 'select(.class=="human")' | grep -c . || true)"
|
||||
nactionable="$(printf '%s\n' "$out" | jq -c 'select(.class=="actionable")' | count_lines . || true)"
|
||||
nhuman="$(printf '%s\n' "$out" | jq -c 'select(.class=="human")' | count_lines . || true)"
|
||||
[ "$nactionable" = "1" ] || fail_msg "T2: actionable must APPEND (never replaced), got $nactionable"
|
||||
[ "$nhuman" = "1" ] || fail_msg "T2: human must APPEND / stay durable, got $nhuman"
|
||||
# Durability never bypassed: all classes present in the durable store.
|
||||
total="$(printf '%s\n' "$out" | grep -c . || true)"
|
||||
total="$(printf '%s\n' "$out" | count_lines . || true)"
|
||||
[ "$total" = "3" ] || fail_msg "T2: durable store should hold digest+actionable+human = 3, got $total"
|
||||
) && ok
|
||||
|
||||
@@ -141,7 +144,7 @@ echo "== T3: contiguous-prefix CONSUMED (reject ack N while N-1 unconsumed) =="
|
||||
fail_msg "T3: CONSUMED 3 must be REJECTED while seq 2 is a gap (unconsumed)"
|
||||
fi
|
||||
cur="$("$STORE" cursors)"
|
||||
echo "$cur" | grep -q 'consumed_seq=0' || fail_msg "T3: rejected CONSUMED must NOT advance the cursor [$cur]"
|
||||
echo "$cur" | has_match -q 'consumed_seq=0' || fail_msg "T3: rejected CONSUMED must NOT advance the cursor [$cur]"
|
||||
# Also reject via the ack wrapper.
|
||||
if "$ACK" consumed --upto 3 --no-sync >/dev/null 2>&1; then
|
||||
fail_msg "T3: ack.sh CONSUMED 3 must be REJECTED over a gap"
|
||||
@@ -150,11 +153,11 @@ echo "== T3: contiguous-prefix CONSUMED (reject ack N while N-1 unconsumed) =="
|
||||
"$STORE" enqueue --seq 2 --class actionable --locators '{}' >/dev/null
|
||||
"$ACK" consumed --upto 3 --no-sync >/dev/null 2>&1 || fail_msg "T3: CONSUMED 3 should succeed once gap at 2 is filled"
|
||||
cur="$("$STORE" cursors)"
|
||||
echo "$cur" | grep -q 'consumed_seq=3' || fail_msg "T3: consumed_seq should be 3 after contiguous prefix filled [$cur]"
|
||||
echo "$cur" | has_match -q 'consumed_seq=3' || fail_msg "T3: consumed_seq should be 3 after contiguous prefix filled [$cur]"
|
||||
# Cumulative + can't regress: CONSUMED 2 after 3 is an idempotent no-op.
|
||||
"$ACK" consumed --upto 2 --no-sync >/dev/null 2>&1 || fail_msg "T3: cumulative CONSUMED 2 (<=3) should be an idempotent success"
|
||||
cur="$("$STORE" cursors)"
|
||||
echo "$cur" | grep -q 'consumed_seq=3' || fail_msg "T3: cursor must not regress below 3 [$cur]"
|
||||
echo "$cur" | has_match -q 'consumed_seq=3' || fail_msg "T3: cursor must not regress below 3 [$cur]"
|
||||
) && ok
|
||||
|
||||
echo "== T4: wake_id DELIVERY-dedup (dup delivery = re-RECEIVE, never re-action) =="
|
||||
@@ -169,7 +172,7 @@ echo "== T4: wake_id DELIVERY-dedup (dup delivery = re-RECEIVE, never re-action)
|
||||
[ "$r2" = "DUP" ] || fail_msg "T4: duplicate delivery of same wake_id should be DUP (re-RECEIVE), got '$r2'"
|
||||
# RECEIVED (delivery) must NEVER advance consumed_seq (delivery != consumption).
|
||||
cur="$("$STORE" cursors)"
|
||||
echo "$cur" | grep -q 'consumed_seq=0' || fail_msg "T4: RECEIVED must not advance consumed_seq [$cur]"
|
||||
echo "$cur" | has_match -q 'consumed_seq=0' || fail_msg "T4: RECEIVED must not advance consumed_seq [$cur]"
|
||||
) && ok
|
||||
|
||||
echo "== T5: atomic write-tmp+rename survives crash mid-write =="
|
||||
@@ -189,7 +192,7 @@ echo "== T5: atomic write-tmp+rename survives crash mid-write =="
|
||||
drained="$("$STORE" drain)"
|
||||
printf '%s\n' "$drained" | jq -e . >/dev/null 2>&1 || fail_msg "T5: drain returned non-JSON — garbage temp corrupted the store"
|
||||
printf '%s\n' "$drained" | stream_any '.observed_seq==1' || fail_msg "T5: committed entry (seq 1) lost after simulated crash"
|
||||
printf '%s\n' "$drained" | grep -q 999 && fail_msg "T5: uncommitted garbage (seq 999) leaked into the live store"
|
||||
printf '%s\n' "$drained" | has_match -q 999 && fail_msg "T5: uncommitted garbage (seq 999) leaked into the live store"
|
||||
# A subsequent real mutation must succeed DESPITE the stale temp present.
|
||||
"$STORE" enqueue --seq 2 --class actionable --locators '{"issue":2}' >/dev/null || fail_msg "T5: enqueue after crash temp failed"
|
||||
# #927: the enqueue HOT PATH must NOT reap tmp files — an unconditional delete
|
||||
@@ -198,7 +201,7 @@ echo "== T5: atomic write-tmp+rename survives crash mid-write =="
|
||||
# untouched by an enqueue; reaping it on the hot path is exactly the bug.
|
||||
[ -e "$STATE_DIR/.wake.tmp.crash12" ] || fail_msg "T5: the enqueue hot path must NOT delete tmp files off its own write (that hot-path reap was the #927 clobber)"
|
||||
d2="$("$STORE" drain)"
|
||||
[ "$(printf '%s\n' "$d2" | grep -c .)" = "2" ] || fail_msg "T5: store not healthy after crash+recovery (expected 2 entries)"
|
||||
[ "$(printf '%s\n' "$d2" | has_match -c .)" = "2" ] || fail_msg "T5: store not healthy after crash+recovery (expected 2 entries)"
|
||||
# Bounded accumulation is preserved via a MAINTENANCE reap (store.sh init /
|
||||
# detector tick), age-scoped so it only removes DEMONSTRABLY-orphaned tmps
|
||||
# (older than any plausible in-flight write) and never a live one. Age the
|
||||
@@ -209,7 +212,7 @@ echo "== T5: atomic write-tmp+rename survives crash mid-write =="
|
||||
"$STORE" init >/dev/null 2>&1 || fail_msg "T5: store.sh init (maintenance) failed"
|
||||
[ -e "$STATE_DIR/.wake.tmp.crash12" ] && fail_msg "T5: maintenance reap (store.sh init) must remove a demonstrably-orphaned stale temp (bounded accumulation)"
|
||||
d3="$("$STORE" drain)"
|
||||
[ "$(printf '%s\n' "$d3" | grep -c .)" = "2" ] || fail_msg "T5: store not healthy after maintenance reap (expected 2 entries)"
|
||||
[ "$(printf '%s\n' "$d3" | has_match -c .)" = "2" ] || fail_msg "T5: store not healthy after maintenance reap (expected 2 entries)"
|
||||
printf '%s\n' "$d3" | jq -e . >/dev/null 2>&1 || fail_msg "T5: drain returned non-JSON after maintenance reap"
|
||||
) && ok
|
||||
|
||||
@@ -254,12 +257,12 @@ echo "== T6: durability survives restart (retain until CONSUMED) =="
|
||||
# "Session 2": a brand-new process (this subshell invocation of store.sh) must
|
||||
# see the persisted entries — nothing was held in memory.
|
||||
d="$("$STORE" drain)"
|
||||
[ "$(printf '%s\n' "$d" | grep -c .)" = "2" ] || fail_msg "T6: entries not retained across restart (expected 2)"
|
||||
[ "$(printf '%s\n' "$d" | has_match -c .)" = "2" ] || fail_msg "T6: entries not retained across restart (expected 2)"
|
||||
printf '%s\n' "$d" | stream_any '.class=="human"' || fail_msg "T6: a human message was lost across restart (durability bypassed)"
|
||||
# Retained UNTIL consumed: after CONSUMED they are released.
|
||||
"$STORE" consume --upto 2 >/dev/null
|
||||
d2="$("$STORE" drain)"
|
||||
n2="$(printf '%s\n' "$d2" | grep -c . || true)"
|
||||
n2="$(printf '%s\n' "$d2" | count_lines . || true)"
|
||||
[ "$n2" = "0" ] || fail_msg "T6: entries should be released only AFTER CONSUMED (still $n2 pending)"
|
||||
) && ok
|
||||
|
||||
@@ -296,7 +299,7 @@ EOF
|
||||
end="$(date +%s.%N)"
|
||||
elapsed_ms="$(awk -v s="$start" -v e="$end" 'BEGIN { printf "%d", (e - s) * 1000 }')"
|
||||
[ "$elapsed_ms" -lt 1500 ] || fail_msg "T7: ack path blocked ${elapsed_ms}ms — must return without waiting on the background sync"
|
||||
echo "$out" | grep -q 'CONSUMED 1' || fail_msg "T7: CONSUMED not reported [$out]"
|
||||
echo "$out" | has_match -q 'CONSUMED 1' || fail_msg "T7: CONSUMED not reported [$out]"
|
||||
# Local write is durable IMMEDIATELY (no network needed to record the ack).
|
||||
STATE_DIR="$WAKE_STATE_HOME/default"
|
||||
jq_any "$STATE_DIR/ack-ledger.jsonl" '.type=="CONSUMED" and .upto==1' ||
|
||||
@@ -321,8 +324,8 @@ echo "== T8: SOLE store-side allocator — enqueue (no --seq) allocates contiguo
|
||||
[ "$s1" = "1" ] || fail_msg "T8: first store-allocated observed_seq must be 1, got '$s1'"
|
||||
[ "$s2" = "2" ] || fail_msg "T8: second store-allocated observed_seq must be 2 (contiguous), got '$s2'"
|
||||
cur="$("$STORE" cursors)"
|
||||
echo "$cur" | grep -q 'observed_seq=2' || fail_msg "T8: observed_seq cursor should be 2 [$cur]"
|
||||
echo "$cur" | grep -q 'pending_depth=2' || fail_msg "T8: both allocations must be durably enqueued [$cur]"
|
||||
echo "$cur" | has_match -q 'observed_seq=2' || fail_msg "T8: observed_seq cursor should be 2 [$cur]"
|
||||
echo "$cur" | has_match -q 'pending_depth=2' || fail_msg "T8: both allocations must be durably enqueued [$cur]"
|
||||
# ANTI-SWALLOW: consume the prefix, then a LEGACY explicit --seq inside the
|
||||
# consumed prefix must FAIL LOUD (never the old silent seq<=consumed no-op).
|
||||
"$STORE" consume --upto 2 >/dev/null
|
||||
@@ -331,7 +334,7 @@ echo "== T8: SOLE store-side allocator — enqueue (no --seq) allocates contiguo
|
||||
fail_msg "T8: explicit --seq 1 <= consumed_seq 2 must FAIL LOUD (anti-swallow), not be a silent no-op"
|
||||
fi
|
||||
err="$("$STORE" enqueue --seq 1 --class actionable --locators '{}' 2>&1 || true)"
|
||||
echo "$err" | grep -qi 'anti-swallow' || fail_msg "T8: the refusal must name the anti-swallow guarantee [$err]"
|
||||
echo "$err" | has_match -qi 'anti-swallow' || fail_msg "T8: the refusal must name the anti-swallow guarantee [$err]"
|
||||
after_depth="$("$STORE" cursors | sed -n 's/pending_depth=//p')"
|
||||
[ "$before_depth" = "$after_depth" ] || fail_msg "T8: a refused enqueue must not mutate the store (depth $before_depth->$after_depth)"
|
||||
# And the NEXT real allocation is > consumed (2) — never inside the prefix.
|
||||
@@ -443,8 +446,8 @@ if command -v flock >/dev/null 2>&1; then
|
||||
[ -n "$a" ] && [ -n "$b" ] || fail_msg "T10: both concurrent enqueues must print an allocated seq (got '$a','$b')"
|
||||
[ "$a" != "$b" ] || fail_msg "T10: two concurrent enqueues must get DISTINCT seqs, both got '$a' (aliasing / lost write without the lock)"
|
||||
cur="$("$STORE" cursors)"
|
||||
echo "$cur" | grep -q 'observed_seq=2' || fail_msg "T10: observed_seq must reach 2 after two enqueues [$cur]"
|
||||
echo "$cur" | grep -q 'pending_depth=2' || fail_msg "T10: both entries must be durably stored (no lost write) [$cur]"
|
||||
echo "$cur" | has_match -q 'observed_seq=2' || fail_msg "T10: observed_seq must reach 2 after two enqueues [$cur]"
|
||||
echo "$cur" | has_match -q 'pending_depth=2' || fail_msg "T10: both entries must be durably stored (no lost write) [$cur]"
|
||||
# The two allocated seqs are exactly {1,2} (distinct, contiguous, gapless).
|
||||
lo="$a"; hi="$b"; [ "$a" -gt "$b" ] && { lo="$b"; hi="$a"; }
|
||||
{ [ "$lo" = "1" ] && [ "$hi" = "2" ]; } || fail_msg "T10: concurrent seqs must be {1,2}, got {$lo,$hi}"
|
||||
@@ -492,7 +495,7 @@ echo "== T11: final observed_seq cursor write FAILURE — fail loud + observed.s
|
||||
# the _atomic_write failure and returns 0).
|
||||
[ "$rc" -ne 0 ] || fail_msg "T11: an enqueue whose FINAL cursor write fails must EXIT NON-ZERO (fail loud), got rc=$rc"
|
||||
# The loud diagnostic names the cursor write (not a generic error).
|
||||
grep -qi 'cursor' "$errfile" || fail_msg "T11: the failure diagnostic must name the observed_seq cursor write [$(cat "$errfile")]"
|
||||
has_match -qi 'cursor' "$errfile" || fail_msg "T11: the failure diagnostic must name the observed_seq cursor write [$(cat "$errfile")]"
|
||||
|
||||
# (2) The cursor did NOT advance — the allocation is NOT committed.
|
||||
obs_after="$("$STORE" cursors | sed -n 's/^observed_seq=//p')"
|
||||
@@ -536,7 +539,7 @@ echo "== T12: #932 — consume records the last-consumed observed_hash per (kind
|
||||
r2h="$(jq -sr '[ .[] | select(.kind=="repo" and .id=="r2") ] | .[0].observed_hash' "$rec" 2>/dev/null)"
|
||||
[ "$r2h" = "HASH-C" ] || fail_msg "T12: repo/r2 must record HASH-C, got '$r2h'"
|
||||
# ADDITIVE: existing on-disk state files are unchanged/consistent post-consume.
|
||||
"$STORE" cursors | grep -q 'consumed_seq=3' || fail_msg "T12: consumed_seq must be 3 after CONSUMED 3"
|
||||
"$STORE" cursors | has_match -q 'consumed_seq=3' || fail_msg "T12: consumed_seq must be 3 after CONSUMED 3"
|
||||
) && ok
|
||||
|
||||
echo "== T13: #946 — ordinary consume REFUSES to advance past a quarantined seq (store + ack paths) =="
|
||||
@@ -555,10 +558,10 @@ echo "== T13: #946 — ordinary consume REFUSES to advance past a quarantined se
|
||||
fail_msg "T13: ordinary consume --upto 3 must be REFUSED while seq 2 is quarantined"
|
||||
fi
|
||||
err="$("$STORE" consume --upto 3 2>&1 >/dev/null || true)"
|
||||
echo "$err" | grep -q 'quarantined seq(s): 2' || fail_msg "T13: the refusal must NAME the quarantined seq [$err]"
|
||||
echo "$err" | grep -q -- '--force-past-quarantine' || fail_msg "T13: the refusal must NAME the force flag [$err]"
|
||||
echo "$err" | has_match -q 'quarantined seq(s): 2' || fail_msg "T13: the refusal must NAME the quarantined seq [$err]"
|
||||
echo "$err" | has_match -q -- '--force-past-quarantine' || fail_msg "T13: the refusal must NAME the force flag [$err]"
|
||||
cur="$("$STORE" cursors)"
|
||||
echo "$cur" | grep -q 'consumed_seq=0' || fail_msg "T13: a refused consume must NOT advance the cursor [$cur]"
|
||||
echo "$cur" | has_match -q 'consumed_seq=0' || fail_msg "T13: a refused consume must NOT advance the cursor [$cur]"
|
||||
# BELOW the quarantined seq the ordinary path is unaffected.
|
||||
"$STORE" consume --upto 1 >/dev/null 2>&1 || fail_msg "T13: consume --upto 1 (below the quarantined seq) must succeed"
|
||||
# The ack wrapper propagates the refusal — no ordinary-path bypass exists.
|
||||
@@ -566,7 +569,7 @@ echo "== T13: #946 — ordinary consume REFUSES to advance past a quarantined se
|
||||
fail_msg "T13: ack.sh consumed --upto 3 must be REFUSED while seq 2 is quarantined (ordinary-path bypass)"
|
||||
fi
|
||||
cur="$("$STORE" cursors)"
|
||||
echo "$cur" | grep -q 'consumed_seq=1' || fail_msg "T13: cursor must still be 1 after the refused ack [$cur]"
|
||||
echo "$cur" | has_match -q 'consumed_seq=1' || fail_msg "T13: cursor must still be 1 after the refused ack [$cur]"
|
||||
) && ok
|
||||
|
||||
echo "== T14: #946 — FORCED step-over is LOUD, prunes the set, and NEVER fabricates a consumed-hash row for the quarantined entry =="
|
||||
@@ -585,9 +588,9 @@ echo "== T14: #946 — FORCED step-over is LOUD, prunes the set, and NEVER fabri
|
||||
rc=$?
|
||||
[ "$rc" -eq 0 ] || fail_msg "T14: forced consume must succeed (rc=$rc) [$(cat "$errf")]"
|
||||
[ "$out" = "3" ] || fail_msg "T14: forced consume must print the new cursor 3, got '$out'"
|
||||
grep -q 'FORCED PAST QUARANTINE' "$errf" || fail_msg "T14: the forced path must be LOUD on stderr [$(cat "$errf")]"
|
||||
grep -q 'seq 2' "$errf" || fail_msg "T14: the forced-path diagnostic must name the stepped-over seq 2 [$(cat "$errf")]"
|
||||
"$STORE" cursors | grep -q 'consumed_seq=3' || fail_msg "T14: forced consume must advance the cursor to 3"
|
||||
has_match -q 'FORCED PAST QUARANTINE' "$errf" || fail_msg "T14: the forced path must be LOUD on stderr [$(cat "$errf")]"
|
||||
has_match -q 'seq 2' "$errf" || fail_msg "T14: the forced-path diagnostic must name the stepped-over seq 2 [$(cat "$errf")]"
|
||||
"$STORE" cursors | has_match -q 'consumed_seq=3' || fail_msg "T14: forced consume must advance the cursor to 3"
|
||||
# NO FALSE WITNESS: the quarantined entry (repo/b) was NEVER delivered, so no
|
||||
# consumed-hash row may exist for it — even on the forced path (the reconciler
|
||||
# re-enumerating it once is safe-but-noisy; a false witness silences it
|
||||
@@ -597,7 +600,7 @@ echo "== T14: #946 — FORCED step-over is LOUD, prunes the set, and NEVER fabri
|
||||
jq_any "$rec" '.kind=="repo" and .id=="b"' && fail_msg "T14: the quarantined entry repo/b must have NO consumed-hash row (a row would witness a delivery that never happened)"
|
||||
# The stepped-over seq is PRUNED from the set (it is consumed now; a stale
|
||||
# entry would re-refuse forever).
|
||||
grep -qxF '2' "$qf" 2>/dev/null && fail_msg "T14: seq 2 must be PRUNED from quarantined.set after the forced step-over"
|
||||
has_match -qxF '2' "$qf" 2>/dev/null && fail_msg "T14: seq 2 must be PRUNED from quarantined.set after the forced step-over"
|
||||
# The ack wrapper's force flag passes through, stays LOUD on stderr, and
|
||||
# still reports a CLEAN cursor line on stdout.
|
||||
"$STORE" enqueue --class actionable --locators '{"kind":"repo","id":"d","observed_hash":"HD"}' >/dev/null
|
||||
@@ -607,8 +610,8 @@ echo "== T14: #946 — FORCED step-over is LOUD, prunes the set, and NEVER fabri
|
||||
out2="$("$ACK" consumed --upto 5 --no-sync --force-past-quarantine 2>"$errf2")"
|
||||
rc2=$?
|
||||
[ "$rc2" -eq 0 ] || fail_msg "T14: forced ack must succeed (rc=$rc2) [$(cat "$errf2")]"
|
||||
echo "$out2" | grep -q '^CONSUMED 5$' || fail_msg "T14: forced ack must report a CLEAN cursor line 'CONSUMED 5', got '$out2'"
|
||||
grep -q 'FORCED PAST QUARANTINE' "$errf2" || fail_msg "T14: the forced-path loudness must survive the ack wrapper (stderr) [$(cat "$errf2")]"
|
||||
echo "$out2" | has_match -q '^CONSUMED 5$' || fail_msg "T14: forced ack must report a CLEAN cursor line 'CONSUMED 5', got '$out2'"
|
||||
has_match -q 'FORCED PAST QUARANTINE' "$errf2" || fail_msg "T14: the forced-path loudness must survive the ack wrapper (stderr) [$(cat "$errf2")]"
|
||||
jq_any "$rec" '.kind=="repo" and .id=="e"' && fail_msg "T14: the quarantined repo/e must have NO consumed-hash row via the forced ack path either"
|
||||
true
|
||||
) && ok
|
||||
@@ -671,10 +674,10 @@ echo "== T16: #946 — quarantine-audit: a consumed-hash row matching a dead-let
|
||||
if "$STORE" quarantine-audit >"$rep" 2>&1; then
|
||||
fail_msg "T16: report-mode audit must exit NON-ZERO when false rows exist"
|
||||
fi
|
||||
grep -q 'FALSE WITNESS' "$rep" || fail_msg "T16: the audit must name the false row loudly [$(cat "$rep")]"
|
||||
grep -q '"id":"X"' "$rep" || fail_msg "T16: the audit must identify the false row (repo/X@1) [$(cat "$rep")]"
|
||||
grep -q '"id":"Y"' "$rep" && fail_msg "T16: the clean row repo/Y must NOT be flagged"
|
||||
grep -q '"id":"Z"' "$rep" && fail_msg "T16: the HEALED row repo/Z@4 must NOT be flagged (its dead-letter evidence is seq 3 with a different hash)"
|
||||
has_match -q 'FALSE WITNESS' "$rep" || fail_msg "T16: the audit must name the false row loudly [$(cat "$rep")]"
|
||||
has_match -q '"id":"X"' "$rep" || fail_msg "T16: the audit must identify the false row (repo/X@1) [$(cat "$rep")]"
|
||||
has_match -q '"id":"Y"' "$rep" && fail_msg "T16: the clean row repo/Y must NOT be flagged"
|
||||
has_match -q '"id":"Z"' "$rep" && fail_msg "T16: the HEALED row repo/Z@4 must NOT be flagged (its dead-letter evidence is seq 3 with a different hash)"
|
||||
# Report mode modifies nothing.
|
||||
jq_any "$rec" '.id=="X"' || fail_msg "T16: report mode must not modify the record"
|
||||
# REPAIR: exactly the false row is removed; the dead-letter LEDGER is history
|
||||
@@ -683,10 +686,10 @@ echo "== T16: #946 — quarantine-audit: a consumed-hash row matching a dead-let
|
||||
jq_any "$rec" '.id=="X"' && fail_msg "T16: --repair must REMOVE the provably-false X row"
|
||||
jq_any "$rec" '.id=="Y" and .observed_hash=="HY"' || fail_msg "T16: --repair must keep the clean Y row"
|
||||
jq_any "$rec" '.id=="Z" and .observed_hash=="HZ-NEW" and .observed_seq==4' || fail_msg "T16: --repair must keep the healed Z@4 row"
|
||||
[ "$(grep -c . "$dl")" = "2" ] || fail_msg "T16: the dead-letter LEDGER must be untouched by --repair"
|
||||
[ "$(has_match -c . "$dl")" = "2" ] || fail_msg "T16: the dead-letter LEDGER must be untouched by --repair"
|
||||
# Clean re-audit: OK, exit 0.
|
||||
"$STORE" quarantine-audit >"$TMP_ROOT/t16.ok" 2>&1 || fail_msg "T16: a clean audit must exit 0 [$(cat "$TMP_ROOT/t16.ok")]"
|
||||
grep -qi 'OK' "$TMP_ROOT/t16.ok" || fail_msg "T16: a clean audit must say OK [$(cat "$TMP_ROOT/t16.ok")]"
|
||||
has_match -qi 'OK' "$TMP_ROOT/t16.ok" || fail_msg "T16: a clean audit must say OK [$(cat "$TMP_ROOT/t16.ok")]"
|
||||
) && ok
|
||||
|
||||
echo "== T17: #952 — the clean-sweep message names BOTH unprovable residual classes; a surviving-but-empty-hash dead-letter row is correctly NOT convicted =="
|
||||
@@ -722,20 +725,20 @@ echo "== T17: #952 — the clean-sweep message names BOTH unprovable residual cl
|
||||
# match can never fire: this is unprovable class 2, NOT a false witness.
|
||||
rep="$TMP_ROOT/t17.rep"
|
||||
"$STORE" quarantine-audit >"$rep" 2>&1 || fail_msg "T17: the audit must exit 0 — nothing here is provable [$(cat "$rep")]"
|
||||
grep -q 'FALSE WITNESS' "$rep" && fail_msg "T17: the empty-hash evidence must NOT convict (the predicate is correct and must not change) [$(cat "$rep")]"
|
||||
has_match -q 'FALSE WITNESS' "$rep" && fail_msg "T17: the empty-hash evidence must NOT convict (the predicate is correct and must not change) [$(cat "$rep")]"
|
||||
# The wording under test (#952): BOTH residual classes, named.
|
||||
grep -qi 'pruned' "$rep" || fail_msg "T17: clean sweep must name residual class 1 — evidence pruned away [$(cat "$rep")]"
|
||||
grep -qi 'empty observed_hash' "$rep" || fail_msg "T17: clean sweep must name residual class 2 — surviving evidence with an empty observed_hash [$(cat "$rep")]"
|
||||
has_match -qi 'pruned' "$rep" || fail_msg "T17: clean sweep must name residual class 1 — evidence pruned away [$(cat "$rep")]"
|
||||
has_match -qi 'empty observed_hash' "$rep" || fail_msg "T17: clean sweep must name residual class 2 — surviving evidence with an empty observed_hash [$(cat "$rep")]"
|
||||
# --repair on a clean sweep removes nothing: the unprovable row survives.
|
||||
"$STORE" quarantine-audit --repair >"$TMP_ROOT/t17.fix" 2>&1 || fail_msg "T17: --repair on a clean sweep must exit 0 [$(cat "$TMP_ROOT/t17.fix")]"
|
||||
jq_any "$rec" '.kind=="bench" and .observed_seq==13 and .observed_hash=="H-REAL-CONSUMED"' ||
|
||||
fail_msg "T17: --repair must NOT remove the unprovable bench@13 row — the audit only removes what the ledger can convict"
|
||||
[ "$(grep -c . "$dl")" = "1" ] || fail_msg "T17: the dead-letter LEDGER must be untouched"
|
||||
[ "$(count_lines . "$dl")" = "1" ] || fail_msg "T17: the dead-letter LEDGER must be untouched"
|
||||
) && ok
|
||||
|
||||
echo
|
||||
if [ -s "$FAILFILE" ]; then
|
||||
echo "wake store/ack harness: FAILED ($(grep -c . "$FAILFILE") assertion(s))" >&2
|
||||
echo "wake store/ack harness: FAILED ($(count_lines . "$FAILFILE") assertion(s))" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "wake store/ack harness: all invariants passed ($pass groups)"
|
||||
|
||||
@@ -32,6 +32,9 @@
|
||||
set -uo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# #973: three-valued grep assertion helpers (has_match/count_lines); init saves real stderr for abort loudness.
|
||||
# shellcheck disable=SC1091
|
||||
. "$SCRIPT_DIR/_wake-common.sh" && wake_assert_init
|
||||
STORE="$SCRIPT_DIR/store.sh"
|
||||
|
||||
command -v jq >/dev/null 2>&1 || {
|
||||
@@ -147,7 +150,7 @@ EOF
|
||||
wait "$pa" 2>/dev/null || true
|
||||
else
|
||||
# Confirm the window is genuinely open: A holds a live in-flight tmp now.
|
||||
n_tmp="$(find "$STATE_DIR" -maxdepth 1 -name '.wake.tmp.*' -type f | grep -c . || true)"
|
||||
n_tmp="$(find "$STATE_DIR" -maxdepth 1 -name '.wake.tmp.*' -type f | count_lines . || true)"
|
||||
[ "$n_tmp" -ge 1 ] || fail_msg "T-RACE: expected A's live in-flight tmp to exist while A holds the lock (got $n_tmp)"
|
||||
|
||||
# B: its PRE-LOCK _wake_init_dir runs now (pre-fix: deletes A's live tmp),
|
||||
@@ -173,7 +176,7 @@ EOF
|
||||
if [ "$ra" -ne 0 ]; then
|
||||
fail_msg "T-RACE: enqueue A was SPURIOUSLY ABORTED (rc=$ra) — its live in-flight tmp was deleted by B's pre-lock cleanup [#927]. stderr: $(tr '\n' ' ' <"$a_err")"
|
||||
fi
|
||||
if grep -q 'durable pending write FAILED' "$a_err" 2>/dev/null; then
|
||||
if has_match -q 'durable pending write FAILED' "$a_err" 2>/dev/null; then
|
||||
fail_msg "T-RACE: enqueue A reported 'durable pending write FAILED' — the exact #927 spurious abort (its tmp was clobbered mid-write by a concurrent pre-lock cleanup)."
|
||||
fi
|
||||
[ "$rb" -eq 0 ] || fail_msg "T-RACE: enqueue B should also succeed (rc=$rb). stderr: $(tr '\n' ' ' <"$b_err")"
|
||||
@@ -189,20 +192,20 @@ EOF
|
||||
|
||||
# #908 store invariants: both durably landed, cursor reached 2, no burned seq.
|
||||
cur="$("$STORE" cursors)"
|
||||
echo "$cur" | grep -q 'observed_seq=2' || fail_msg "T-RACE: observed_seq must reach 2 (both allocations committed) [$cur]"
|
||||
echo "$cur" | grep -q 'pending_depth=2' || fail_msg "T-RACE: both entries must be durably stored — no lost/aborted write [$cur]"
|
||||
echo "$cur" | grep -q 'consumed_seq=0' || fail_msg "T-RACE: enqueue must never advance consumed_seq [$cur]"
|
||||
echo "$cur" | has_match -q 'observed_seq=2' || fail_msg "T-RACE: observed_seq must reach 2 (both allocations committed) [$cur]"
|
||||
echo "$cur" | has_match -q 'pending_depth=2' || fail_msg "T-RACE: both entries must be durably stored — no lost/aborted write [$cur]"
|
||||
echo "$cur" | has_match -q 'consumed_seq=0' || fail_msg "T-RACE: enqueue must never advance consumed_seq [$cur]"
|
||||
# Gapless: the contiguous prefix 1..2 is consumable (no interior gap/burn).
|
||||
"$STORE" consume --upto 2 >/dev/null 2>&1 || fail_msg "T-RACE: CONSUMED 2 must succeed — seqs {1,2} are gapless (no burned seq)"
|
||||
# No stale tmp left leaking either.
|
||||
n_leak="$(find "$STATE_DIR" -maxdepth 1 -name '.wake.tmp.*' -type f | grep -c . || true)"
|
||||
n_leak="$(find "$STATE_DIR" -maxdepth 1 -name '.wake.tmp.*' -type f | count_lines . || true)"
|
||||
[ "$n_leak" = "0" ] || fail_msg "T-RACE: $n_leak stale tmp file(s) leaked after both enqueues committed"
|
||||
fi
|
||||
) && ok
|
||||
|
||||
echo
|
||||
if [ -s "$FAILFILE" ]; then
|
||||
echo "wake store enqueue-race harness: FAILED ($(grep -c . "$FAILFILE") assertion(s)) — #927 TOCTOU reproduced (RED)" >&2
|
||||
echo "wake store enqueue-race harness: FAILED ($(count_lines . "$FAILFILE") assertion(s)) — #927 TOCTOU reproduced (RED)" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "wake store enqueue-race harness: all invariants passed ($pass group) — #927 race closed (GREEN)"
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
#!/usr/bin/env python3
|
||||
"""convert-973.py — mechanical #973 site conversion, driven by the frozen
|
||||
denominator artifact (denominator-089615f.json), never by ad-hoc grepping.
|
||||
|
||||
For every row in the artifact it FIRST verifies the worktree line still says
|
||||
what the artifact froze (exact match after whitespace strip, artifact-side
|
||||
truncation as prefix match, or the normalized suite-summary template), and
|
||||
aborts before touching anything on the first verification failure — a
|
||||
conversion applied to a line the denominator did not measure would be the
|
||||
umbrella defect wearing the converter's clothes.
|
||||
|
||||
Transforms (behaviour-preserving; verdict semantics unchanged on grep rc 0/1):
|
||||
E-count-capture `grep -c ARGS` -> `count_lines ARGS` (helper adds -c)
|
||||
all other forms `grep ARGS` -> `has_match ARGS` (drop-in)
|
||||
env prefixes (`LC_ALL=C grep`) are kept — the prefix reaches the grep child
|
||||
through the function (microtest C8).
|
||||
|
||||
The two multi-grep pipeline sites (digest-quarantine:560, install:420) convert
|
||||
BOTH greps: each is measurement-bearing, and under pipefail an rc=2 in the
|
||||
left element is masked by an rc=1 in the right — the same defect one pipe
|
||||
deeper. They stay ONE denominator site each (one coordinate); the ledger
|
||||
records helper calls, so those coordinates appear twice per execution and the
|
||||
equality check compares SETS of coordinates.
|
||||
|
||||
Finally each suite gains three header lines directly after its SCRIPT_DIR
|
||||
assignment (source + wake_assert_init + comment), shifting every site by +3
|
||||
lines exactly; the validation harness maps base coordinates accordingly.
|
||||
"""
|
||||
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
WAKE = HERE.parent
|
||||
ART = HERE / "denominator-089615f.json"
|
||||
|
||||
RX_GREP_TOKEN = re.compile(r"(^|[^A-Za-z0-9_.-])grep([^A-Za-z0-9_.-]|$)")
|
||||
|
||||
HEADER = [
|
||||
"# #973: three-valued grep assertion helpers (has_match/count_lines); init saves real stderr for abort loudness.\n",
|
||||
"# shellcheck disable=SC1091\n",
|
||||
'. "$SCRIPT_DIR/_wake-common.sh" && wake_assert_init\n',
|
||||
]
|
||||
|
||||
MULTI_GREP_CONVERT_BOTH = {
|
||||
("test-wake-digest-quarantine.sh", 560),
|
||||
("test-wake-install.sh", 420),
|
||||
}
|
||||
|
||||
SUMMARY_TEMPLATE_MARK = '$(grep -c . "$FAILFILE")'
|
||||
|
||||
|
||||
def verify(row, actual):
|
||||
a = actual.strip()
|
||||
t = row["text"].strip()
|
||||
if a == t:
|
||||
return True
|
||||
if t and a.startswith(t): # artifact-side truncation
|
||||
return True
|
||||
# normalized suite-summary template rows
|
||||
if t.startswith('echo "wake ') and SUMMARY_TEMPLATE_MARK in actual and a.startswith('echo "wake '):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def convert_line(row, line):
|
||||
key = (row["file"], row["line"])
|
||||
n_grep = len(RX_GREP_TOKEN.findall(line))
|
||||
if key in MULTI_GREP_CONVERT_BOTH:
|
||||
assert n_grep == 2, f"{key}: expected 2 grep tokens, found {n_grep}"
|
||||
else:
|
||||
assert n_grep == 1, f"{key}: expected 1 grep token, found {n_grep}: {line!r}"
|
||||
|
||||
if row["form"].startswith("E"):
|
||||
assert line.count("grep -c ") == 1, f"{key}: E row without single 'grep -c ': {line!r}"
|
||||
return line.replace("grep -c ", "count_lines ", 1)
|
||||
|
||||
def repl(m):
|
||||
return m.group(1) + "has_match" + m.group(2)
|
||||
|
||||
count = 2 if key in MULTI_GREP_CONVERT_BOTH else 1
|
||||
return RX_GREP_TOKEN.sub(repl, line, count=count)
|
||||
|
||||
|
||||
def main():
|
||||
art = json.loads(ART.read_text())
|
||||
rows = art["rows"]
|
||||
by_file = {}
|
||||
for r in rows:
|
||||
by_file.setdefault(r["file"], []).append(r)
|
||||
|
||||
# pass 1: verify every row before touching any file
|
||||
bad = 0
|
||||
texts = {}
|
||||
for f, frs in by_file.items():
|
||||
lines = (WAKE / f).read_text().split("\n")
|
||||
texts[f] = lines
|
||||
for r in frs:
|
||||
if not verify(r, lines[r["line"] - 1]):
|
||||
bad += 1
|
||||
print(f"VERIFY-FAIL {f}:{r['line']}\n artifact: {r['text']!r}\n worktree: {lines[r['line'] - 1]!r}")
|
||||
if bad:
|
||||
sys.exit(f"ABORT: {bad} row(s) failed verification; nothing was modified.")
|
||||
|
||||
# pass 2: convert + insert header
|
||||
total = {"has_match": 0, "count_lines": 0}
|
||||
for f, frs in sorted(by_file.items()):
|
||||
lines = texts[f]
|
||||
for r in frs:
|
||||
i = r["line"] - 1
|
||||
new = convert_line(r, lines[i])
|
||||
assert new != lines[i], f"{f}:{r['line']}: no-op conversion"
|
||||
lines[i] = new
|
||||
total["count_lines" if r["form"].startswith("E") else "has_match"] += 1
|
||||
# header insertion after the SCRIPT_DIR= line
|
||||
sd = [i for i, ln in enumerate(lines) if ln.startswith('SCRIPT_DIR="$(')]
|
||||
assert len(sd) == 1, f"{f}: expected exactly one SCRIPT_DIR line, found {len(sd)}"
|
||||
first_site = min(r["line"] for r in frs) - 1
|
||||
assert sd[0] < first_site, f"{f}: SCRIPT_DIR line {sd[0] + 1} not before first site {first_site + 1}"
|
||||
lines[sd[0] + 1 : sd[0] + 1] = [h.rstrip("\n") for h in HEADER]
|
||||
(WAKE / f).write_text("\n".join(lines))
|
||||
print(f"{f}: {len(frs)} sites converted, header at line {sd[0] + 2}")
|
||||
|
||||
print(f"TOTAL: {total['has_match']} has_match + {total['count_lines']} count_lines = {sum(total.values())} sites in {len(by_file)} files")
|
||||
assert sum(total.values()) == art["total"] == 261, "site count mismatch vs artifact"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user