Compare commits

..
Author SHA1 Message Date
mos-claudeandClaude Opus 5 1806b3a57a style(git): prettier-format push-guard README to the repo's format gate
ci/woodpecker/pr/ci Pipeline was successful
The prior commit passed a standalone 'prettier --check README.md' in the author's
environment but FAILED 'pnpm format:check' in the repository, which applies the
repo's own prettier configuration. Same tool, different configuration, opposite
answer — and I pushed past the warning rather than stopping at it.

Formatting only; no content change.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01YKj59Qadrb2WBLaePvkM7H
2026-07-30 22:15:40 -05:00
installer-7andClaude Opus 5 fc80138326 docs(git): correct push-guard README excluded-case provenance
ci/woodpecker/pr/ci Pipeline was canceled
The 46-vs-43 explanation named w2-fixture and g1-fixture as excluded. Neither runs
inside test-push-guard.sh — w2-fixture is in test-verify-clean-clone.sh and
g1-fixture is in test-mutate-push-guard.sh — so neither could contribute to its
tally. A reader auditing the denominator would have hunted them in the wrong files
with no way to tell whether the number or the explanation was wrong.

True excluded set, re-derived from the actual run output: the two z1 warning
assertions (which assert on a whole CLASS of output rather than an exit code, so
they carry no '(exit N)' for the parser) plus the e9-fixture precondition line
printed in 'ok' form. All three still RUN and still gate the suite — excluded from
ATTRIBUTION, not from EXECUTION. That distinction was missing and is what a reader
needs.

Blocking rather than cosmetic: this section exists to make a GENERATED coverage
number auditable, and every figure in the table is generated while the sentence
explaining it was typed from memory. The generated/typed boundary is where this
class of defect keeps landing.

Documentation only. All six script blobs are byte-identical to 8fdc8738ed, so the
guard's carried-forward clearance is unaffected.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01YKj59Qadrb2WBLaePvkM7H
2026-07-30 22:15:19 -05:00
installer-7andClaude Opus 5 8fdc8738ed fix(git): push-guard harness — resolve three re-review blockers + two corrections
ci/woodpecker/pr/ci Pipeline was successful
Authored by installer-7; committed by mos-claude (no credential for this remote).
push-guard.sh and test-push-guard.sh are BYTE-IDENTICAL to the previously cleared
versions — every change this round is in the harness, confirming the reviewer's
framing that none of the three blockers was in the guard itself.

B1 verify-clean-clone.sh could not verify the artifact in its real monorepo location:
  it resolved ROOT but kept artifacts as bare basenames, so running it in place
  reported all artifacts NOT TRACKED. Its own suite missed this because every
  fixture installed artifacts at fixture ROOT — a fixture encoding a layout the
  real subject does not have. PREFIX now comes from 'git rev-parse --show-prefix'
  and is threaded through the ls-tree pathspec, the cloned stat, and the suite cwd;
  the verifier PRINTS the prefix. Three needles: nested-layout pass, prefix-reported
  (else a green only means the prefix was ignored harmlessly), and mode-needle-still-
  bites-nested (a prefix threaded into the clone but not ls-tree would silently stop
  checking modes).
B2 the generator reported full coverage and exited 0 on a RED baseline — any
  pre-existing failure marked every mutant killed. Now refuses unless baseline is
  exit 0 with zero failures, prints the actual tally on refusal, emits no table, and
  scores kills by NAMED DELTA rather than a raw red count.
B3 the generator mutated the reviewed source in place; SIGKILL stranded a mutant and
  contaminated a following run. Guard and suite are now copied into a temp dir and
  mutations apply to that copy — no restore step to fail. The author's first control
  for this was itself vacuous (a 3s kill lands during baseline, before any mutation,
  so it passed against the unfixed mechanism too); the real control drives the kill
  from inside the run on the second suite invocation, plus a needle proving the
  reconstructed pre-fix mechanism DOES strand.
Corrections: 'shellcheck clean' had been measured at -S warning and published
  unqualified — a filtered measurement stated as an unfiltered claim. Now clean at
  DEFAULT severity across six files with one scoped, documented SC2016 disable.
  ARTIFACTS extended five -> six so the test files no longer omit themselves.

Verified before commit: six files sha256-matched to the author's hashes; shellcheck
exit 0 at default severity; push-guard 46/46 and verifier 9/9 run directly; the
in-place verifier resolved the real nested prefix against this tree.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01YKj59Qadrb2WBLaePvkM7H
2026-07-30 22:02:29 -05:00
installer-7andClaude Opus 5 8310075d33 fix(git): push-guard — resolve re-review blockers; mutation table now generated
ci/woodpecker/pr/ci Pipeline was successful
Authored by installer-7; committed by mos-claude (no credential for this remote).

BLOCKER 1 — verify-clean-clone.sh had B1's own defect: it copied WORKING-TREE files into a
  scratch repo and asserted the SCRATCH index, so a stale local exec bit was laundered in and it
  reported success while the committed artifact was still 100644. v2 reads mode from 'git ls-tree'
  of the SOURCE COMMIT and runs from 'git clone --no-local --no-hardlinks' of that commit; there is
  no 'cp' in the file. Proven by A/B against one laundered repo: v1 EXIT=0, v2 EXIT=1 (both observed,
  not asserted). New test-verify-clean-clone.sh 6/6, incl. a fixture that first proves it really is
  git=100644 disk=755 before testing it.
BLOCKER 2 — empty-merge needle + non-empty-merge control, both asserting on OUTPUT; the fixture
  first proves it IS a merge with a tree identical to both parents.

The 'non-blocking' README item was not documentation: regenerating its mutation table (now generated
by mutate-push-guard.sh, not hand-numbered) found TWO genuinely surviving mutants against a 46/46
green suite — staged-but-uncommitted opt-out honoured, and unparseable committed config ignored.
Both still exit 6 under mutation because control falls to a SIBLING refusal, so an exit-code-only
assertion would have been satisfied by the wrong branch. It also found a live instance of the
substring-anchor defect already in the suite: three branches print 'OPT-OUT IS NOT REVIEWABLE', so
deleting one let the needle RE-POINT to a sibling instead of failing. Re-anchored.
Suite 44 -> 46. Verifier 6/6. 13 mutants, 0 survived.

Also: README reformatted to satisfy 'pnpm format:check' (prettier), which was failing CI at both
prior heads — a gate neither author nor reviewer had exercised.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01YKj59Qadrb2WBLaePvkM7H
2026-07-30 21:06:55 -05:00
installer-7andClaude Opus 5 b0fb208b89 fix(git): push-guard — resolve 5 review blockers + a 6th self-found instance
ci/woodpecker/pr/ci Pipeline failed
Authored by installer-7; committed by mos-claude (installer-7 has no credential for this remote).
All five blockers from rev-974's review were REPRODUCED before any fix.

B1 mode 100644: the artifact was never executable, so an untouched clone could not run the
   suite at all (exit 126) — both prior 32/32 runs used a local exec bit set at creation.
   Fixed in the INDEX (git update-index --chmod=+x), plus verify-clean-clone.sh which asserts
   the mode via 'git ls-files -s' (not disk) and executes the suite DIRECTLY ('bash script'
   masks a missing bit). Negative control: chmod 644 makes the verifier refuse.
B2 mapfile < <(git diff) observed mapfile's status, not the producer's — a fatal pathspec
   error reported a clean scan, exit 0. pipefail governs PIPELINES; a process substitution is
   an async child whose status is never collected.
B3 OFF accepted from an untracked working-tree config, defeating the committed-artifact
   asymmetry — and write_config() wrote untracked configs, so every opt-out control asserted
   the forbidden provenance and passed green. OFF is now re-read from HEAD; untracked,
   staged-uncommitted, committed-symlink, and committed-ON-flipped-locally are all refused.
B4 --since-head proved inequality, not ancestry: an unrelated pre-existing commit passed as
   new work.
B5 empty ROOT commit exempted by parent-count, then reported PUSH CONFIRMED. Emptiness is now
   defined as 'tree identical to every parent' rather than exempted by category.
B6 (self-found by sweeping for the CONSTRUCT, not the report) same < <(git diff) in
   cmd_check_staged — fails closed but published a wrong diagnosis. Both callers now route
   through one staged_files_z().

41/41 needles, executed directly from a clean clone. Known gap, stated: B2's second instance is
fixed but UNNEEDLED — a corrupt index aborts earlier at 128, so no fault injection reaches it.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01YKj59Qadrb2WBLaePvkM7H
2026-07-30 20:38:06 -05:00
installer-7andmos-claude 62e3bf9e28 fix(git): push-guard.README.md — required repo config (#975) + heredoc-in-substitution warning
ci/woodpecker/pr/ci Pipeline is pending
2026-07-31 00:22:47 +00:00
installer-7andmos-claude b7d391f9f4 fix(git): test-push-guard.sh — required repo config (#975) + heredoc-in-substitution warning
ci/woodpecker/pr/ci Pipeline was canceled
2026-07-31 00:22:45 +00:00
installer-7andmos-claude 1036449b57 fix(git): push-guard.sh — required repo config (#975) + heredoc-in-substitution warning 2026-07-31 00:22:43 +00:00
installer-7andmos-claude 63c5d2056f feat(git): add push-guard.README.md
ci/woodpecker/pr/ci Pipeline failed
Mechanical closure of 'a verification that passes when the thing it verifies never happened.'
2026-07-31 00:03:38 +00:00
installer-7andmos-claude 1288ce3721 feat(git): add test-push-guard.sh
Mechanical closure of 'a verification that passes when the thing it verifies never happened.'
2026-07-31 00:03:37 +00:00
installer-7andmos-claude 92272a6400 feat(git): add push-guard.sh
Mechanical closure of 'a verification that passes when the thing it verifies never happened.'
2026-07-31 00:03:36 +00:00
26 changed files with 271 additions and 5464 deletions
-5
View File
@@ -41,11 +41,6 @@ steps:
# (Constitution + dispatcher + each RUNTIME.md slice). See DESIGN §7 / R9. # (Constitution + dispatcher + each RUNTIME.md slice). See DESIGN §7 / R9.
- bash packages/mosaic/framework/tools/quality/scripts/check-resident-budget.sh --self-test - bash packages/mosaic/framework/tools/quality/scripts/check-resident-budget.sh --self-test
- bash packages/mosaic/framework/tools/quality/scripts/check-resident-budget.sh - bash packages/mosaic/framework/tools/quality/scripts/check-resident-budget.sh
# Test-membership guard (#1017): also first link of test:framework-shell.
# Invoked from BOTH surfaces it audits (F2, PR #1018) — the guard is link
# [0] of the pnpm chain, so severing that chain would silence it together
# with everything it guards; this direct line keeps one instrument running.
- bash packages/mosaic/framework/tools/quality/scripts/check-test-enumeration.sh
# Blocking gate (#791): a framework upgrade must never write or delete an # Blocking gate (#791): a framework upgrade must never write or delete an
# operator-owned path. The HARD GATE proves an unanticipated operator sentinel # operator-owned path. The HARD GATE proves an unanticipated operator sentinel
@@ -109,55 +109,6 @@ else
detect_platform >/dev/null detect_platform >/dev/null
fi fi
# Render the provider's own explanation for a failed request, for appending to
# an error message (#1004). Every HTTP arm in this file already has the response
# body on disk; without this it was discarded unread at exactly the moment the
# caller needed it, which pushes an operator toward re-issuing the request by
# hand to find out what the server said. Gitea returns {"message": "..."} on a
# refusal; anything unparseable falls back to a truncated raw first line so a
# proxy's HTML error page still says something. Prints "" when there is nothing
# to add, so callers can interpolate unconditionally.
#
# Args: $1 = path to the response body file.
gitea_error_detail() {
local body_file="$1"
[[ -s "$body_file" ]] || return 0
python3 - "$body_file" <<'PY' 2>/dev/null || true
import json
import sys
LIMIT = 300
try:
with open(sys.argv[1], encoding="utf-8", errors="replace") as response:
raw = response.read().strip()
except OSError:
raise SystemExit(0)
if not raw:
raise SystemExit(0)
detail = ""
try:
parsed = json.loads(raw)
if isinstance(parsed, dict):
for key in ("message", "error", "errors"):
value = parsed.get(key)
if isinstance(value, str) and value.strip():
detail = value.strip()
break
if isinstance(value, list) and value:
detail = "; ".join(str(item) for item in value).strip()
break
except ValueError:
pass
if not detail:
detail = raw.splitlines()[0].strip()
if not detail:
raise SystemExit(0)
if len(detail) > LIMIT:
detail = detail[:LIMIT] + "..."
print(f" — provider said: {detail}")
PY
}
# Post a comment to a Gitea PR (PR comments ARE issue comments) via the # Post a comment to a Gitea PR (PR comments ARE issue comments) via the
# supported REST API and verify it against a PROVIDER-RETURNED created id. The # supported REST API and verify it against a PROVIDER-RETURNED created id. The
# write is a direct POST that returns the created comment object, so we learn # write is a direct POST that returns the created comment object, so we learn
@@ -199,7 +150,7 @@ print(json.dumps({"body": os.environ["COMMENT_BODY"]}))
return 1 return 1
fi fi
if [[ "$write_status" != "201" ]]; then if [[ "$write_status" != "201" ]]; then
echo "Error: Gitea comment write failed with HTTP $write_status$(gitea_error_detail "$write_file")" >&2 echo "Error: Gitea comment write failed with HTTP $write_status" >&2
return 1 return 1
fi fi
@@ -228,7 +179,7 @@ PY
return 1 return 1
fi fi
if [[ "$readback_status" != "200" ]]; then if [[ "$readback_status" != "200" ]]; then
echo "Error: Gitea comment read-back failed with HTTP $readback_status$(gitea_error_detail "$readback_file")" >&2 echo "Error: Gitea comment read-back failed with HTTP $readback_status" >&2
return 1 return 1
fi fi
@@ -419,7 +370,7 @@ gitea_authenticated_login() {
return 1 return 1
fi fi
if [[ "$status" != "200" ]]; then if [[ "$status" != "200" ]]; then
echo "Error: Gitea authenticated-identity read failed with HTTP $status$(gitea_error_detail "$response_file")" >&2 echo "Error: Gitea authenticated-identity read failed with HTTP $status" >&2
return 1 return 1
fi fi
@@ -456,7 +407,7 @@ gitea_read_pr_head_into() {
return 1 return 1
fi fi
if [[ "$status" != "200" ]]; then if [[ "$status" != "200" ]]; then
echo "Error: Gitea PR head read failed with HTTP $status$(gitea_error_detail "$pr_file")" >&2 echo "Error: Gitea PR head read failed with HTTP $status" >&2
return 1 return 1
fi fi
python3 - "$pr_file" <<'PY' python3 - "$pr_file" <<'PY'
@@ -546,7 +497,7 @@ print(json.dumps({
fi fi
# Gitea returns 200 (occasionally 201) with the created review object. # Gitea returns 200 (occasionally 201) with the created review object.
if [[ "$write_status" != "200" && "$write_status" != "201" ]]; then if [[ "$write_status" != "200" && "$write_status" != "201" ]]; then
echo "Error: Gitea review submit failed with HTTP $write_status$(gitea_error_detail "$write_file")" >&2 echo "Error: Gitea review submit failed with HTTP $write_status (#865: no durable review created)" >&2
return 1 return 1
fi fi
@@ -575,7 +526,7 @@ PY
return 1 return 1
fi fi
if [[ "$readback_status" != "200" ]]; then if [[ "$readback_status" != "200" ]]; then
echo "Error: Gitea review read-back failed with HTTP $readback_status$(gitea_error_detail "$readback_file")" >&2 echo "Error: Gitea review read-back failed with HTTP $readback_status" >&2
return 1 return 1
fi fi
@@ -58,9 +58,6 @@ CREDENTIALS_FILE="$WORK_DIR/credentials.json"
# A dedicated scratch dir the wrapper is pointed at via TMPDIR, so the leak # A dedicated scratch dir the wrapper is pointed at via TMPDIR, so the leak
# check can assert every POST/GET body + metadata temp file is cleaned up. # check can assert every POST/GET body + metadata temp file is cleaned up.
TMP_SCRATCH="$WORK_DIR/scratch" TMP_SCRATCH="$WORK_DIR/scratch"
# Sandboxed HOME so nothing under the real $HOME (notably the per-slot Gitea token
# store at ~/.config/mosaic/secrets/gitea-tokens/) is reachable from the wrapper.
HOME_DIR="$WORK_DIR/home"
cleanup() { cleanup() {
rm -rf "$WORK_DIR" rm -rf "$WORK_DIR"
@@ -81,18 +78,9 @@ OVERRIDE_TOKEN="override-token-placeholder"
CROSS_HOST_LOGIN="foreign-host-reviewer" CROSS_HOST_LOGIN="foreign-host-reviewer"
CROSS_HOST_TOKEN="cross-host-token-placeholder" CROSS_HOST_TOKEN="cross-host-token-placeholder"
mkdir -p "$REPO_DIR" "$BIN_DIR" "$XDG_DIR" "$STATE_DIR" "$TMP_SCRATCH" "$HOME_DIR" mkdir -p "$REPO_DIR" "$BIN_DIR" "$XDG_DIR" "$STATE_DIR" "$TMP_SCRATCH"
git -C "$REPO_DIR" init -q git -C "$REPO_DIR" init -q
git -C "$REPO_DIR" remote add origin https://git.mosaicstack.dev/mosaicstack/stack.git git -C "$REPO_DIR" remote add origin https://git.mosaicstack.dev/mosaicstack/stack.git
# HERMETICITY: get_gitea_token() step 0 resolves a per-agent identity from
# `git config --get mosaic.gitIdentity`, which on a provisioned agent seat is set
# GLOBALLY and therefore leaks into this fresh repo. It then reads a REAL per-slot
# token from $HOME and returns it WITHOUT ever consulting MOSAIC_CREDENTIALS_FILE,
# so the fixture credentials below are silently ignored and the suite runs against
# production credentials. An empty repo-local value shadows the global one and reads
# back as empty at rc=0, restoring the shared-credential path this suite intends to
# exercise. Paired with the sandboxed HOME in run_review().
git -C "$REPO_DIR" config mosaic.gitIdentity ""
# tea config: the override login carries its own token here. The default login # tea config: the override login carries its own token here. The default login
# name ("mosaicstack") is deliberately absent, so the no-override default path # name ("mosaicstack") is deliberately absent, so the no-override default path
@@ -278,24 +266,6 @@ submitted = json.loads(os.environ["PR_REVIEW_PAYLOAD"])
with open(state_path, encoding="utf-8") as handle: with open(state_path, encoding="utf-8") as handle:
reviews = json.load(handle) reviews = json.load(handle)
# review-refused-422 (#1004): the server REFUSES the submit outright with a
# definite, correct, machine-readable reason in the body — the shape Gitea
# returns when the acting credential authored the PR. Nothing is created. The
# wrapper must surface what the server said and must NOT relabel this as the
# #865 silent-no-op defect class, which is precisely what it is not.
if mode == "review-refused-422":
print("422")
print(json.dumps({"message": "Cannot approve your own pull request"}))
raise SystemExit(0)
# review-refused-html (#1004): a non-JSON error body, as a fronting proxy or
# gateway emits. The detail extraction must degrade to the first raw line rather
# than silently dropping the only explanation available.
if mode == "review-refused-html":
print("502")
print("<html><head><title>502 Bad Gateway</title></head>\n<body>nginx</body></html>")
raise SystemExit(0)
# no-op-concurrent-review: the wrapper's own submit is SUPPRESSED (200, no # no-op-concurrent-review: the wrapper's own submit is SUPPRESSED (200, no
# created object) even though a concurrent same-identity, same-state review at # created object) even though a concurrent same-identity, same-state review at
# the same head already exists. Nothing is persisted; no created id to verify. # the same head already exists. Nothing is persisted; no created id to verify.
@@ -547,8 +517,6 @@ run_review() {
cd "$REPO_DIR" cd "$REPO_DIR"
PATH="$BIN_DIR:$PATH" \ PATH="$BIN_DIR:$PATH" \
TMPDIR="$TMP_SCRATCH" \ TMPDIR="$TMP_SCRATCH" \
HOME="$HOME_DIR" \
MOSAIC_GIT_IDENTITY="" \
XDG_CONFIG_HOME="$XDG_DIR" \ XDG_CONFIG_HOME="$XDG_DIR" \
MOSAIC_CREDENTIALS_FILE="$CREDENTIALS_FILE" \ MOSAIC_CREDENTIALS_FILE="$CREDENTIALS_FILE" \
PR_REVIEW_TEA_LOG="$TEA_LOG" \ PR_REVIEW_TEA_LOG="$TEA_LOG" \
@@ -972,44 +940,4 @@ if grep -q 'Approved and verified' "$OUTPUT_FILE"; then
fi fi
assert_no_temp_leak "review-body-null" assert_no_temp_leak "review-body-null"
# Case 19 (#1004): an outright server REFUSAL must report the provider's own
# reason and must NOT be relabelled as the #865 silent-no-op defect class. The
# old arm hardcoded "(#865: no durable review created)" for EVERY non-2xx, so a
# 422/403/404 — all of them definite, correct refusals the server explained in
# the discarded body — arrived at the caller wearing the name of the one defect
# they are not. That misdirection is what makes an operator re-issue the request
# by hand against the live object to find out what actually happened.
if run_review review-refused-422 approve; then
echo "FAIL: approve reported success when the server refused the submit" >&2
cat "$OUTPUT_FILE" >&2
exit 1
fi
grep -q 'HTTP 422' "$OUTPUT_FILE"
if ! grep -q 'Cannot approve your own pull request' "$OUTPUT_FILE"; then
echo "FAIL: the provider's stated reason was discarded (#1004)" >&2
cat "$OUTPUT_FILE" >&2
exit 1
fi
if grep -q '#865: no durable review created' "$OUTPUT_FILE"; then
echo "FAIL: a server refusal was misattributed to the #865 defect class (#1004)" >&2
cat "$OUTPUT_FILE" >&2
exit 1
fi
assert_no_temp_leak "review-refused-422"
# Case 20 (#1004): a non-JSON error body (a fronting proxy's HTML page) must
# still yield something the caller can act on, rather than a bare status code.
if run_review review-refused-html approve; then
echo "FAIL: approve reported success on a 502" >&2
cat "$OUTPUT_FILE" >&2
exit 1
fi
grep -q 'HTTP 502' "$OUTPUT_FILE"
if ! grep -q '502 Bad Gateway' "$OUTPUT_FILE"; then
echo "FAIL: a non-JSON error body was dropped instead of degrading to its first line (#1004)" >&2
cat "$OUTPUT_FILE" >&2
exit 1
fi
assert_no_temp_leak "review-refused-html"
echo "pr-review.sh REST review + comment create/read-back regression passed" echo "pr-review.sh REST review + comment create/read-back regression passed"
@@ -1,165 +0,0 @@
#!/usr/bin/env bash
# check-test-enumeration.sh — CI test-membership guard (#1017).
#
# CI reaches shell suites through two hand-enumerated surfaces:
# S1 packages/mosaic/package.json scripts."test:framework-shell"
# S2 .woodpecker/ci.yml direct `bash packages/mosaic/framework/tools/...` commands
#
# A hand-enumerated allowlist re-arms its own gap: a new suite never auto-joins,
# so the list silently under-runs the disk (17 of 39 suites were invisible when
# #1017 was filed). This guard makes that under-run impossible to do silently:
#
# FAIL when a suite-shaped file exists on disk and is neither enumerated on
# the UNION of both surfaces nor listed in the exclusions file.
# ("Enumerated", deliberately — F1/F2 on PR #1018 proved this guard sees
# NAMING, not reachability, and its words must not claim otherwise.)
# FAIL when either surface names a path that does not exist on disk
# (a rename manufactures a stale entry silently — checked BOTH directions).
# FAIL when an exclusion entry has no reason, names a path that is gone,
# names a path that is also enumerated (contradiction), or names a path
# outside the population (dead weight that looks like coverage).
#
# POPULATION PATTERN — a deliberate decision, stated per #1017's record:
# basename matches *test*.sh (contains "test", ends ".sh"). Deliberately BROAD:
# the strict `test-*.sh` prefix cannot even name three real boundary files
# (tmux/agent-send.test.sh — CI-run; orchestrator/smoke-test.sh;
# wake/validate-973/microtest-wake-assert.sh), and three independent censuses
# handled that last file three different ways with no trace of the judgement.
# The broad pattern makes such files MEMBERS, so their disposition must be a
# signed exclusion, not an accident of the glob. The SAME pattern is applied to
# both sides of the comparison (disk and enumeration) — a comparison globbed two
# ways runs on two different populations. Scripts outside the pattern on both
# sides symmetrically (e.g. check-resident-budget.sh, verify-sanitized.sh) are
# check-scripts, not suites; their existence is still verified via the
# both-directions rule because every surface-named path must exist on disk.
#
# The surfaces are PARSED, never line-ranged: three seats independently
# mis-scoped hand-written line ranges against these files (#1017 thread). S1 is
# read via JSON + command-chain tokenization; S2 by extracting every
# packages/mosaic/framework/tools/ token wherever it appears in the file.
#
# Exclusions file format (framework/tools/quality/test-enumeration-exclusions.txt):
# <repo-relative-path> | <non-empty reason>
# Lines starting with # and blank lines are ignored. An exclusion is a recorded
# decision someone signed, not an omission nobody made.
set -uo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT="$(cd "$SCRIPT_DIR/../../../../../.." && pwd)"
while (( $# )); do
case "$1" in
--root) ROOT="$(cd "$2" && pwd)"; shift 2 ;;
*) echo "usage: check-test-enumeration.sh [--root <repo-root>]" >&2; exit 2 ;;
esac
done
PKG_JSON="$ROOT/packages/mosaic/package.json"
CI_YML="$ROOT/.woodpecker/ci.yml"
TOOLS_DIR="$ROOT/packages/mosaic/framework/tools"
EXCLUSIONS="$TOOLS_DIR/quality/test-enumeration-exclusions.txt"
for f in "$PKG_JSON" "$CI_YML"; do
[[ -f "$f" ]] || { echo "FAIL: required surface file missing: $f" >&2; exit 2; }
done
[[ -d "$TOOLS_DIR" ]] || { echo "FAIL: tools dir missing: $TOOLS_DIR" >&2; exit 2; }
fail_count=0
fail() { printf 'FAIL %s\n' "$1"; fail_count=$(( fail_count + 1 )); }
# in_population <repo-relative path> — the single pattern, used for BOTH sides.
in_population() {
local base; base="$(basename "$1")"
[[ "$base" == *test*.sh ]]
}
# --- Surface 1: package.json test:framework-shell, parsed, repo-relative -----
# Tokens are script paths iff they contain "/" and end .sh/.py; interpreter
# names and flags are skipped. Paths are relative to packages/mosaic/.
mapfile -t S1 < <(python3 - "$PKG_JSON" <<'PY'
import json, shlex, sys
cmd = json.load(open(sys.argv[1]))["scripts"].get("test:framework-shell", "")
seen = []
for seg in cmd.split("&&"):
for tok in shlex.split(seg):
if "/" in tok and (tok.endswith(".sh") or tok.endswith(".py")):
path = "packages/mosaic/" + tok
if path not in seen:
seen.append(path)
print("\n".join(seen))
PY
)
# --- Surface 2: ci.yml, every framework/tools token wherever it appears ------
# Comment lines (first non-whitespace char is #) are skipped BEFORE matching:
# commenting an invocation out is the most common way a suite actually gets
# disabled, and a raw-text regex would keep calling it enumerated (F1, 20155 on
# PR #1018 — demonstrated, not argued). Known residual limit: a path named only
# in a TRAILING comment on a live line still matches; no such line exists today
# and full fidelity would need a YAML parser the CI image does not ship.
mapfile -t S2 < <(grep -vE '^[[:space:]]*#' "$CI_YML" \
| grep -oE 'packages/mosaic/framework/tools/[A-Za-z0-9_./-]+\.(sh|py)' | sort -u)
# --- Union, and its population-restricted view -------------------------------
declare -A ENUM=() ENUM_POP=()
for p in "${S1[@]:-}" "${S2[@]:-}"; do
[[ -n "$p" ]] || continue
ENUM["$p"]=1
in_population "$p" && ENUM_POP["$p"]=1
done
# --- Direction B: every surface-named path must exist on disk ----------------
for p in "${!ENUM[@]}"; do
[[ -f "$ROOT/$p" ]] || fail "STALE ENUMERATION: surfaces name '$p' but it does not exist on disk"
done
# --- Exclusions: parsed with the same rigor the enumeration gets -------------
declare -A EXCLUDED=()
if [[ -f "$EXCLUSIONS" ]]; then
lineno=0
while IFS= read -r line; do
lineno=$(( lineno + 1 ))
[[ "$line" =~ ^[[:space:]]*(#|$) ]] && continue
path="${line%%|*}"; reason="${line#*|}"
path="$(echo "$path" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//')"
reason="$(echo "$reason" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//')"
if [[ "$line" != *"|"* || -z "$reason" ]]; then
fail "EXCLUSION MISSING REASON: line $lineno ('$path') — an exclusion is a recorded decision someone signed"
continue
fi
if [[ ! -f "$ROOT/$path" ]]; then
fail "STALE EXCLUSION: line $lineno excludes '$path' which does not exist on disk"
continue
fi
if ! in_population "$path"; then
fail "EXCLUSION OUTSIDE POPULATION: line $lineno excludes '$path' which the population pattern does not name — dead weight that reads as coverage"
continue
fi
if [[ -n "${ENUM[$path]:-}" ]]; then
fail "CONTRADICTORY EXCLUSION: line $lineno excludes '$path' which the surfaces already enumerate"
continue
fi
EXCLUDED["$path"]=1
done < "$EXCLUSIONS"
fi
# --- Direction A: disk population must be enumerated or signed-excluded ------
disk_total=0
unlisted=0
while IFS= read -r f; do
rel="${f#"$ROOT"/}"
in_population "$rel" || continue
disk_total=$(( disk_total + 1 ))
if [[ -z "${ENUM_POP[$rel]:-}" && -z "${EXCLUDED[$rel]:-}" ]]; then
fail "UNENUMERATED: '$rel' exists on disk but is neither enumerated on any CI surface nor signed in the exclusions file"
unlisted=$(( unlisted + 1 ))
fi
done < <(find "$TOOLS_DIR" -type f -name '*.sh' | sort)
if (( fail_count > 0 )); then
printf 'enumeration guard: %d failure(s) — population %d, enumerated (in-population) %d, excluded %d\n' \
"$fail_count" "$disk_total" "${#ENUM_POP[@]}" "${#EXCLUDED[@]}"
exit 1
fi
printf 'enumeration guard: OK — population %d, enumerated (in-population) %d, excluded (signed) %d, surfaces name %d path(s), all present on disk\n' \
"$disk_total" "${#ENUM_POP[@]}" "${#EXCLUDED[@]}" "${#ENUM[@]}"
@@ -1,166 +0,0 @@
#!/usr/bin/env bash
# test-check-test-enumeration.sh — needles for the enumeration guard (#1017).
#
# Every failure mode the guard promises gets BOTH polarities:
# NEEDLE a fixture that MUST trip the guard, asserted on the guard's OWN
# words (--out) — exit 1 alone cannot distinguish "caught the rogue
# file" from "choked on the fixture".
# CONTROL a fixture that MUST pass. A guard that failed unconditionally
# would satisfy every needle here — the null-case defect the guard's
# own subject matter (#1017) exists to make impossible.
#
# The needles encode the specific errors that produced #1017's thread:
# n6 is the 20124 boundary file (a suite the strict prefix cannot name);
# n2b proves surface 2 is PARSED, not line-ranged (three seats mis-scoped
# hand-written ranges against ci.yml);
# n5/n7 keep the exclusions file honest so it cannot become the next silent cap;
# n8/c4 are F1 (20155): a commented-out ci.yml line is NOT enumeration —
# commenting-out is the most common way a suite actually gets disabled,
# and it must fail loud in one direction without false-staling the other.
set -uo pipefail
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
GUARD="$HERE/check-test-enumeration.sh"
TMP="$(mktemp -d)"; trap 'rm -rf "$TMP"' EXIT
PASS=0; FAIL=0
# fixture <name> — a minimal repo root the guard accepts via --root:
# one suite enumerated on S1 (plus a naming-outlier suite, so the S1 parser's
# handling of non-prefix names is always exercised), one on S2, one check-script
# named on S2 that is outside the population, and an empty exclusions file.
fixture() {
local r="$TMP/$1"
mkdir -p "$r/packages/mosaic/framework/tools/git" \
"$r/packages/mosaic/framework/tools/tmux" \
"$r/packages/mosaic/framework/tools/quality/scripts" \
"$r/.woodpecker"
printf '#!/usr/bin/env bash\nexit 0\n' > "$r/packages/mosaic/framework/tools/git/test-a.sh"
printf '#!/usr/bin/env bash\nexit 0\n' > "$r/packages/mosaic/framework/tools/tmux/outlier.test.sh"
printf '#!/usr/bin/env bash\nexit 0\n' > "$r/packages/mosaic/framework/tools/quality/scripts/test-ci.sh"
printf '#!/usr/bin/env bash\nexit 0\n' > "$r/packages/mosaic/framework/tools/quality/scripts/verify-thing.sh"
cat > "$r/packages/mosaic/package.json" <<'JSON'
{"scripts": {"test:framework-shell": "bash framework/tools/git/test-a.sh && bash framework/tools/tmux/outlier.test.sh"}}
JSON
cat > "$r/.woodpecker/ci.yml" <<'YML'
steps:
sanitize:
commands:
- bash packages/mosaic/framework/tools/quality/scripts/verify-thing.sh
guard:
commands:
- bash packages/mosaic/framework/tools/quality/scripts/test-ci.sh
YML
: > "$r/packages/mosaic/framework/tools/quality/test-enumeration-exclusions.txt"
printf '%s' "$r"
}
# expect <kind> <want-exit> <desc> [--out <substring>] -- <root>
expect() {
local kind="$1" want="$2" desc="$3"; shift 3
local need_out=""
while (( $# )); do
case "$1" in
--out) need_out="$2"; shift 2 ;;
--) shift; break ;;
esac
done
local root="$1" got=0 out
out="$(bash "$GUARD" --root "$root" 2>&1)" || got=$?
local why=""
[[ "$got" == "$want" ]] || why="wanted exit $want, got $got"
if [[ -z "$why" && -n "$need_out" && "$out" != *"$need_out"* ]]; then
why="exit $got as expected, but output never said: $need_out"
fi
if [[ -z "$why" ]]; then
printf ' PASS [%-7s] %s (exit %s)\n' "$kind" "$desc" "$got"
PASS=$(( PASS + 1 ))
else
printf ' FAIL [%-7s] %s — %s\n' "$kind" "$desc" "$why"
printf '%s\n' "$out" | sed 's/^/ | /'
FAIL=$(( FAIL + 1 ))
fi
}
excl() { printf '%s\n' "$2" >> "$1/packages/mosaic/framework/tools/quality/test-enumeration-exclusions.txt"; }
echo "=== c1: a fully consistent fixture passes ==="
R="$(fixture c1)"
expect CONTROL 0 "consistent tree: both surfaces enumerated, nothing unlisted" \
--out "enumeration guard: OK" -- "$R"
echo "=== n1: an on-disk suite reachable from no surface must fail ==="
R="$(fixture n1)"
printf '#!/usr/bin/env bash\nexit 0\n' > "$R/packages/mosaic/framework/tools/git/test-rogue.sh"
expect NEEDLE 1 "unlisted suite is named in the failure" \
--out "UNENUMERATED: 'packages/mosaic/framework/tools/git/test-rogue.sh'" -- "$R"
echo "=== n6: the 20124 boundary file — a suite the strict prefix cannot name ==="
R="$(fixture n6)"
printf '#!/usr/bin/env bash\nexit 0\n' > "$R/packages/mosaic/framework/tools/git/rogue.test.sh"
expect NEEDLE 1 "naming-outlier suite (*.test.sh) is a population member, not invisible" \
--out "UNENUMERATED: 'packages/mosaic/framework/tools/git/rogue.test.sh'" -- "$R"
echo "=== c3: a non-suite script outside the pattern is outside it on BOTH sides ==="
R="$(fixture c3)"
printf '#!/usr/bin/env bash\nexit 0\n' > "$R/packages/mosaic/framework/tools/git/check-unrelated.sh"
expect CONTROL 0 "check-script on disk, unlisted, outside population: not the guard's business" \
--out "enumeration guard: OK" -- "$R"
echo "=== n2/n2b: a surface naming a path absent from disk must fail — both surfaces ==="
R="$(fixture n2)"
rm "$R/packages/mosaic/framework/tools/git/test-a.sh"
expect NEEDLE 1 "S1 (package.json) stale entry" \
--out "STALE ENUMERATION: surfaces name 'packages/mosaic/framework/tools/git/test-a.sh'" -- "$R"
R="$(fixture n2b)"
rm "$R/packages/mosaic/framework/tools/quality/scripts/test-ci.sh"
expect NEEDLE 1 "S2 (ci.yml) stale entry — proves ci.yml is parsed, not line-ranged" \
--out "STALE ENUMERATION: surfaces name 'packages/mosaic/framework/tools/quality/scripts/test-ci.sh'" -- "$R"
echo "=== c2: a rogue suite with a SIGNED exclusion passes, and is counted ==="
R="$(fixture c2)"
printf '#!/usr/bin/env bash\nexit 0\n' > "$R/packages/mosaic/framework/tools/git/test-rogue.sh"
excl "$R" "packages/mosaic/framework/tools/git/test-rogue.sh | non-hermetic pending fixture work (needle-suite specimen)"
expect CONTROL 0 "signed exclusion is honoured and visible in the summary" \
--out "excluded (signed) 1" -- "$R"
echo "=== n3: an exclusion with no reason is not a decision ==="
R="$(fixture n3)"
printf '#!/usr/bin/env bash\nexit 0\n' > "$R/packages/mosaic/framework/tools/git/test-rogue.sh"
excl "$R" "packages/mosaic/framework/tools/git/test-rogue.sh | "
expect NEEDLE 1 "empty reason rejected" --out "EXCLUSION MISSING REASON" -- "$R"
R="$(fixture n3b)"
printf '#!/usr/bin/env bash\nexit 0\n' > "$R/packages/mosaic/framework/tools/git/test-rogue.sh"
excl "$R" "packages/mosaic/framework/tools/git/test-rogue.sh"
expect NEEDLE 1 "missing separator rejected (the path alone is not a signature)" \
--out "EXCLUSION MISSING REASON" -- "$R"
echo "=== n4: an exclusion whose path is gone is stale, not satisfied ==="
R="$(fixture n4)"
excl "$R" "packages/mosaic/framework/tools/git/test-vanished.sh | was excluded once, then deleted"
expect NEEDLE 1 "stale exclusion rejected" --out "STALE EXCLUSION" -- "$R"
echo "=== n5: excluding an enumerated suite is a contradiction, not belt-and-braces ==="
R="$(fixture n5)"
excl "$R" "packages/mosaic/framework/tools/git/test-a.sh | already in CI but excluded anyway"
expect NEEDLE 1 "contradictory exclusion rejected" --out "CONTRADICTORY EXCLUSION" -- "$R"
echo "=== n8/c4: a commented-out ci.yml line is not enumeration (F1, 20155) ==="
R="$(fixture n8)"
printf '#!/usr/bin/env bash\nexit 0\n' > "$R/packages/mosaic/framework/tools/git/test-disabled.sh"
printf ' # - bash packages/mosaic/framework/tools/git/test-disabled.sh\n' >> "$R/.woodpecker/ci.yml"
expect NEEDLE 1 "suite named only in a commented-out invocation is UNENUMERATED" \
--out "UNENUMERATED: 'packages/mosaic/framework/tools/git/test-disabled.sh'" -- "$R"
R="$(fixture c4)"
printf ' # - bash packages/mosaic/framework/tools/git/test-vanished.sh\n' >> "$R/.woodpecker/ci.yml"
expect CONTROL 0 "comment naming an absent path raises no false stale-enumeration" \
--out "enumeration guard: OK" -- "$R"
echo "=== n7: excluding a file outside the population is dead weight, not coverage ==="
R="$(fixture n7)"
excl "$R" "packages/mosaic/framework/tools/quality/scripts/verify-thing.sh | not a suite but signing it anyway"
expect NEEDLE 1 "out-of-population exclusion rejected" --out "EXCLUSION OUTSIDE POPULATION" -- "$R"
echo
printf 'enumeration-guard needles: %d passed, %d failed\n' "$PASS" "$FAIL"
(( FAIL == 0 ))
@@ -1,45 +0,0 @@
# test-enumeration-exclusions.txt — signed exclusions for check-test-enumeration.sh (#1017).
#
# Every entry is a recorded decision: a suite-shaped file that exists on disk,
# is NOT reachable from any CI surface, and carries the reason someone signed
# for that. The guard FAILS on an entry with no reason, a stale path, or a path
# the surfaces already enumerate. Burning an entry down = making it CI-reachable
# (package.json test:framework-shell or a ci.yml step) and deleting its line.
#
# Format: <repo-relative path> | <reason>
# All entries below were signed at #1017's filing base (main 826a8b3b, 2026-07-31)
# by pepper (sb-it-1-dt); measurements cited are one-run assertions from that seat.
# --- tools/git: the #1007 five — non-hermetic, resolve real credentials ---
packages/mosaic/framework/tools/git/test-pr-merge-gitea-empty-uid.sh | resolves real credentials (#1007 census); joins CI after the wrapper-half hermeticity fix (git -C scoping)
packages/mosaic/framework/tools/git/test-issue-create-interactive-auth.sh | resolves real credentials (#1007 census); joins CI after the wrapper-half hermeticity fix
packages/mosaic/framework/tools/git/test-gitea-login-resolution.sh | resolves real credentials (#1007 census); joins CI after the wrapper-half hermeticity fix
packages/mosaic/framework/tools/git/test-pr-metadata-gitea.sh | resolves real credentials (#1007 census, fourth entry via family-grep); joins CI after the wrapper-half hermeticity fix
packages/mosaic/framework/tools/git/test-issue-comment-readback.sh | resolves real credentials (#1007 census, fifth entry); joins CI after the wrapper-half hermeticity fix
# --- tools/git: push guards — measured green locally, CI-image fitness unverified ---
packages/mosaic/framework/tools/git/test-push-guard.sh | measured green at 826a8b3b (46 passed / 0 failed, one run, 2026-07-31); CI-image fitness unverified; #1017 burndown
packages/mosaic/framework/tools/git/test-mutate-push-guard.sh | measured green at 826a8b3b (8/0, 13 mutants killed 0 survived, one run, 2026-07-31); requires setsid (util-linux), absent from the alpine base image; #1017 burndown
packages/mosaic/framework/tools/git/test-issue-create-body-safety.sh | hermeticity unaudited — the unprotected suite in #1007's protected/unprotected split; audit before CI; #1017 burndown
# --- tools/git: unmeasured ---
packages/mosaic/framework/tools/git/test-verify-clean-clone.sh | unmeasured in CI image; asserts git file-mode (100644/755) semantics that need verification on the CI filesystem first; #1017 burndown
packages/mosaic/framework/tools/git/test-help-exit-code.sh | unmeasured in CI image; stub-based (#701 regression harness), likely CI-fit; #1017 burndown
packages/mosaic/framework/tools/git/test-lane-brief-pr-linkage.sh | unmeasured in CI image; fixture-based (#546/#547 regression harness), likely CI-fit; #1017 burndown
# --- tools/tmux: require a live tmux server ---
packages/mosaic/framework/tools/tmux/test-send-message-socket.sh | requires a real tmux server on a throwaway socket; CI image ships no tmux; #1017 burndown (needs tmux in image or a signed permanent exclusion)
packages/mosaic/framework/tools/tmux/test-send-message-verdict.sh | requires real tmux-pane fixtures on a throwaway socket; CI image ships no tmux; #1017 burndown (same condition as its sibling)
# --- single-suite directories: unmeasured in CI ---
packages/mosaic/framework/tools/fleet/test-start-agent-session.sh | unmeasured in CI image; stubs tmux via a fake bin dir, likely CI-fit; #1017 burndown
packages/mosaic/framework/tools/glpi/test-list-http-status.sh | unmeasured in CI image; stub-based (#807 regression harness), likely CI-fit; #1017 burndown
packages/mosaic/framework/tools/orchestrator/test-board-roll.sh | unmeasured in CI image; file-fixture based, likely CI-fit; #1017 burndown
packages/mosaic/framework/tools/woodpecker/test-ci-wait-exit-matrix.sh | unmeasured in CI image; drives ci-wait.sh against a stub pipeline-status.sh, likely CI-fit; #1017 burndown
# --- naming-boundary files the strict test-*.sh prefix cannot even name ---
# (#1017: three independent censuses handled the microtest file three different
# ways — editorial drop, structural exclusion, accidental inclusion — with no
# recorded judgement. These lines ARE that judgement, signed.)
packages/mosaic/framework/tools/orchestrator/smoke-test.sh | behavior smoke checks for coord continue/run workflows, run manually by orchestrator seats; unmeasured in CI; #1017 burndown
packages/mosaic/framework/tools/wake/validate-973/microtest-wake-assert.sh | #973 instrument self-test, run as a precondition of the validate-973 evidence procedure rather than as a standing CI suite; #1017 burndown candidate
@@ -191,177 +191,3 @@ _wake_init_dir() {
[ -f "$dir/pending.jsonl" ] || printf '' | _atomic_write "$dir/pending.jsonl" [ -f "$dir/pending.jsonl" ] || printf '' | _atomic_write "$dir/pending.jsonl"
[ -f "$dir/ack-ledger.jsonl" ] || printf '' | _atomic_write "$dir/ack-ledger.jsonl" [ -f "$dir/ack-ledger.jsonl" ] || printf '' | _atomic_write "$dir/ack-ledger.jsonl"
} }
# ---------------------------------------------------------------------------
# #973 — three-valued grep assertion helpers for the wake test suites.
#
# grep's exit contract is three-valued: 0 = match, 1 = no match, >1 = ERROR
# (bad file, bad pattern, resource failure). Every wake-suite assertion used
# to read all non-zero as "absent", so a grep that COULD NOT LOOK wore the
# colour of a verdict: OR-polarity sites (`|| fail`) went falsely red,
# AND-polarity sites (`&& fail` — including the credential canaries) went
# falsely green. The repair is to refuse to answer: rc 0 -> match, rc 1 -> no
# match, anything else -> loud abort naming the call site, the raw exit code,
# and the arguments. An error NEVER becomes a verdict.
#
# Production tools (store.sh, ack.sh) source this file but call none of the
# helpers below; they are inert outside the suites.
#
# Suite integration contract:
# - Call `wake_assert_init` ONCE at suite top level, right after sourcing.
# It dups the suite's real stderr to a saved fd BEFORE any call-site
# redirect exists, so an abort stays loud even at sites that append
# `2>/dev/null` (the preimage credential canaries pre-swallow stderr —
# exactly where a silent abort would recreate the defect being fixed).
# - Assertion sites live inside `( ... ) && ok` subshell blocks, pipelines,
# and `$(...)` substitutions, where a plain `exit` dies one layer deep and
# the suite would carry on to emit a verdict. The abort therefore signals
# the suite's MAIN shell ($$ is the main PID in every subshell) and then
# exits the current context: the suite dies by signal, non-zero, with NO
# verdict line emitted.
#
# Validation instrumentation (#973 evidence, not part of the assertion fix):
# - WAKE_ASSERT_LEDGER=<file>: every helper call appends
# "<helper> <caller-file>:<caller-line>" to <file>. That is the ONLY
# divergence from production behaviour — the suite otherwise runs its
# normal arms, so a validate run exercises exactly the shipped paths.
# - WAKE_ASSERT_FORCE_GREP_ERROR_AT=<caller-file>:<caller-line>: at exactly
# that call site, the invocation is routed through a REAL grep driven onto
# its real error path (unknown option -> rc 2) — a genuinely executed
# failing process, not a stubbed return — to prove per-site that the abort
# fires. Unset in production; matching no site is a no-op.
# ---------------------------------------------------------------------------
# wake_assert_init — dup the suite's real stderr once, for abort loudness.
# MUST be called at suite TOP LEVEL, immediately after sourcing and before any
# test block: a lazy (first-call) dup could capture an already-redirected
# stderr if the first executed helper call sat under a call-site 2>/dev/null,
# silencing every abort thereafter. The fd is allocated dynamically (>= 10),
# so it cannot collide with the wake lock fds (8) or the detector run-loop
# lock (9).
#
# Init also PINS the BASH_LINENO convention the site coordinates depend on:
# a helper call written across a backslash continuation must report at its
# FIRST physical line (the denominator artifact's convention). That was
# measured on a developer bash (5.3.x); CI runs whatever bash its base image
# baked in, and that version floats silently between image rebuilds. A bash
# that disagrees would shift every continuation-site coordinate by one line
# UNDER the validation instead of in front of it — so the convention is
# asserted at runtime, in the same bash binary that runs the suite, and a
# disagreeing bash aborts the suite loudly instead of skewing coordinates.
_wake_assert_lineno_pin() {
local _wa_pin_tmp _wa_pin_got
_wa_pin_tmp="$(mktemp)" || {
_wake_assert_err_note "WAKE-ASSERT INIT ABORT: mktemp failed; cannot pin the BASH_LINENO convention — a pin that silently does not run is not a pin (#973)"
exit 97
}
cat >"$_wa_pin_tmp" <<'WAKE_ASSERT_PIN'
_wap() { printf '%s\n' "${BASH_LINENO[0]}"; }
(
_wap simple
_wap \
continuation
)
WAKE_ASSERT_PIN
# WAKE_ASSERT_PIN_BASH: test-only interpreter override so the pin's abort
# arm can be PROVEN to fire (microtest C10) — bash resets $BASH at startup,
# so the real probe interpreter cannot be spoofed from the environment.
_wa_pin_got="$("${WAKE_ASSERT_PIN_BASH:-${BASH:-bash}}" "$_wa_pin_tmp" 2>/dev/null)"
rm -f "$_wa_pin_tmp"
if [ "$_wa_pin_got" != "$(printf '3\n4')" ]; then
_wake_assert_err_note "WAKE-ASSERT INIT ABORT: BASH_LINENO convention violated on bash ${BASH_VERSION}: probe reported [${_wa_pin_got:-<no output>}], expected [3 4] (simple call at own line, continuation call at FIRST physical line) — site coordinates are untrustworthy on this bash (#973)"
exit 97
fi
}
wake_assert_init() {
if [ -z "${_wake_assert_err_fd:-}" ]; then
exec {_wake_assert_err_fd}>&2
_wake_assert_lineno_pin
fi
}
# _wake_assert_err_note MSG — write MSG to the saved real-stderr fd, falling
# back to the current stderr if init was never called.
_wake_assert_err_note() {
if [ -n "${_wake_assert_err_fd:-}" ]; then
printf '%s\n' "$1" >&"$_wake_assert_err_fd" 2>/dev/null ||
printf '%s\n' "$1" >&2
else
printf '%s\n' "$1" >&2
fi
}
# _wake_assert_abort HELPER SITE RC ARGS... — refuse to answer, loudly.
# Writes the named reason to the saved real-stderr fd (falling back to the
# current stderr), signals the suite's main shell, and exits this context.
_wake_assert_abort() {
local _wa_helper="$1" _wa_where="$2" _wa_code="$3"
shift 3
_wake_assert_err_note "WAKE-ASSERT ABORT: ${_wa_helper} at ${_wa_where}: grep exit ${_wa_code} is an error, not a verdict (args: $*) — refusing to answer (#973)"
if [ -n "${BASHPID:-}" ] && [ "$BASHPID" != "$$" ]; then
kill -TERM "$$" 2>/dev/null || true
fi
exit 97
}
# _wake_assert_armed SITE — true iff the forced-error arm targets SITE; on a
# match it emits a positive confirmation FIRST, so "site did not abort" can
# never conflate SITE NOT CONVERTED with ARM NEVER REACHED IT: an armed run
# with no ARMED line means the arm matched nothing (typo/renumber/drift), and
# an ARMED line with no abort means the site's error path is broken. The two
# defects are separable on stderr alone.
_wake_assert_armed() {
[ "${WAKE_ASSERT_FORCE_GREP_ERROR_AT:-}" = "$1" ] || return 1
_wake_assert_err_note "WAKE-ASSERT ARMED: forcing real grep error at $1 (#973)"
return 0
}
# has_match GREP_ARGS... — three-valued grep verdict.
# Drop-in for verdict-bearing `grep` calls (flags, files, stdin all pass
# through; stdout is not captured, so extract-form call sites may use it
# inside a substitution). Returns 0 on match, 1 on no-match; any other grep
# exit aborts the suite via _wake_assert_abort.
has_match() {
local _wa_site="${BASH_SOURCE[1]##*/}:${BASH_LINENO[0]}" _wa_rc=0
if [ -n "${WAKE_ASSERT_LEDGER:-}" ]; then
printf 'has_match %s\n' "$_wa_site" >>"$WAKE_ASSERT_LEDGER"
fi
if _wake_assert_armed "$_wa_site"; then
command grep --wake-assert-forced-error -- /dev/null
_wa_rc=$?
else
command grep "$@"
_wa_rc=$?
fi
case "$_wa_rc" in
0) return 0 ;;
1) return 1 ;;
*) _wake_assert_abort has_match "$_wa_site" "$_wa_rc" "$@" ;;
esac
}
# count_lines GREP_ARGS... — `grep -c` with the same three-way discipline.
# Call sites drop their `-c` (the helper supplies it) and keep every other
# argument. Prints the count on rc 0 AND rc 1 (rc 1 is grep's "count is 0" —
# a valid measurement, not an error); any other exit aborts. The abort still
# kills the suite from inside a `$(...)` capture: the substitution subshell
# cannot exit the suite, but the signal to the main shell can — a count from
# a failed measurement is never printed.
count_lines() {
local _wa_site="${BASH_SOURCE[1]##*/}:${BASH_LINENO[0]}" _wa_rc=0 _wa_out=""
if [ -n "${WAKE_ASSERT_LEDGER:-}" ]; then
printf 'count_lines %s\n' "$_wa_site" >>"$WAKE_ASSERT_LEDGER"
fi
if _wake_assert_armed "$_wa_site"; then
_wa_out="$(command grep --wake-assert-forced-error -c -- /dev/null)"
_wa_rc=$?
else
_wa_out="$(command grep -c "$@")"
_wa_rc=$?
fi
case "$_wa_rc" in
0 | 1) printf '%s\n' "$_wa_out" ;;
*) _wake_assert_abort count_lines "$_wa_site" "$_wa_rc" "$@" ;;
esac
}
@@ -529,19 +529,7 @@ cmd_run() {
echo "detector.sh: WARN — off-host liveness beacon emit failed (see beacon.sh); the off-host absence check remains the authoritative dead-man." >&2 echo "detector.sh: WARN — off-host liveness beacon emit failed (see beacon.sh); the off-host absence check remains the authoritative dead-man." >&2
fi fi
[ "$once" -eq 1 ] && break [ "$once" -eq 1 ] && break
# Close the detector lock fd in the sleep child; otherwise an orphaned sleep sleep "$interval"
# keeps the single-instance flock (fd 9, taken at exec 9> above) alive after
# the detector parent dies. The lock is non-blocking (`flock -n`, above), so
# for as long as that sleep survives a replacement instance is REFUSED and
# exits rather than queueing. This particular hold is BOUNDED by one poll
# interval (WAKE_DETECTOR_INTERVAL, default 30s): when the orphaned sleep
# exits its copy of fd 9 closes, ending this bounded sleep-child hold. It
# does NOT follow that the next start succeeds — other inheritors of fd 9
# (the M1 adapter, M2 sink grandchildren) are outside this patch's scope and
# can keep holding the flock. The cost this removes is a restart window in
# which every supervisor retry fails on the sleep child's account.
# `9>&-` closes ONLY the child's copy — the parent's lock is unaffected.
sleep "$interval" 9>&-
done done
} }
@@ -465,26 +465,8 @@
# never a hand-built flat dead-letter row, which would make the # never a hand-built flat dead-letter row, which would make the
# audit's correct non-conviction look exactly like the defect # audit's correct non-conviction look exactly like the defect
# under hunt (the #951 review's false-defect near-miss). # under hunt (the #951 review's false-defect near-miss).
# 0.7.2 #973 three-valued grep verdicts across ALL TEN wake test suites.
# grep's exit contract is three-valued (0 match / 1 no-match /
# >=2 ERROR); every suite assertion read non-zero as "absent",
# so a grep that COULD NOT LOOK wore the colour of a verdict —
# OR-polarity sites failed falsely RED, AND-polarity sites
# (including all 19 credential canaries) failed falsely GREEN
# under load. _wake-common.sh gains has_match/count_lines
# (rc 0/1 pass through; anything else LOUDLY ABORTS the whole
# suite naming file:line + raw rc — an error is never a
# verdict), wake_assert_init (saved-fd abort loudness that
# survives call-site 2>/dev/null + a runtime pin of the
# BASH_LINENO coordinate convention against CI bash drift),
# and 261 call sites converted mechanically from a frozen
# denominator artifact. Production tools source but never call
# the helpers; suite verdict semantics on rc 0/1 are UNCHANGED.
# Evidence chain in validate-973/ (microtest C1-C11, expected/
# static/trace set arithmetic, 21 forced-error arms, residual
# sweep with per-form plants).
component=wake component=wake
version=0.7.2 version=0.7.1
# Watch-list schema this component consumes, and the INCLUSIVE range of # Watch-list schema this component consumes, and the INCLUSIVE range of
# schema_version values it supports. A wake-watch-list.json whose schema_version # schema_version values it supports. A wake-watch-list.json whose schema_version
@@ -31,13 +31,6 @@
set -uo pipefail set -uo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 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
if ! . "$SCRIPT_DIR/_wake-common.sh"; then
echo "WAKE-ASSERT INIT ABORT: cannot source _wake-common.sh — suite ran ZERO wake assertions (#984)" >&2
exit 97
fi
wake_assert_init
BEACON="$SCRIPT_DIR/beacon.sh" BEACON="$SCRIPT_DIR/beacon.sh"
command -v jq >/dev/null 2>&1 || { command -v jq >/dev/null 2>&1 || {
@@ -136,7 +129,7 @@ echo "== B2: beacon ABSENCE past SLO -> alarm FIRES + ROUTES =="
out="$("$BEACON" check --slo-seconds 5 2>&1)" out="$("$BEACON" check --slo-seconds 5 2>&1)"
rc=$? rc=$?
[ "$rc" -eq 1 ] || fail_msg "B2: a stale-past-SLO beacon must FIRE the absence alarm (exit 1); got $rc [$out]" [ "$rc" -eq 1 ] || fail_msg "B2: a stale-past-SLO beacon must FIRE the absence alarm (exit 1); got $rc [$out]"
echo "$out" | has_match -qi 'ALARM FIRED' || fail_msg "B2: absence must announce the alarm fired [$out]" echo "$out" | grep -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)" [ -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)]" 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). # NEVER-received is also an absence (depends on nothing the dying host does).
@@ -157,13 +150,13 @@ echo "== B3: unconfigured OR unreachable ALARM target -> FAIL LOUD =="
out="$("$BEACON" check --slo-seconds 5 2>&1)" out="$("$BEACON" check --slo-seconds 5 2>&1)"
rc=$? rc=$?
[ "$rc" -eq 3 ] || fail_msg "B3a: unconfigured alarm target must FAIL LOUD (exit 3); got $rc [$out]" [ "$rc" -eq 3 ] || fail_msg "B3a: unconfigured alarm target must FAIL LOUD (exit 3); got $rc [$out]"
echo "$out" | has_match -qi 'silent no-alarm host' || fail_msg "B3a: the failure must name the silent-no-alarm-host hazard [$out]" echo "$out" | grep -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). # (b) UNREACHABLE alarm sink (non-zero exit).
export WAKE_ALARM_SINK_CMD="false" export WAKE_ALARM_SINK_CMD="false"
out2="$("$BEACON" check --slo-seconds 5 2>&1)" out2="$("$BEACON" check --slo-seconds 5 2>&1)"
rc2=$? rc2=$?
[ "$rc2" -eq 3 ] || fail_msg "B3b: unreachable alarm target must FAIL LOUD (exit 3); got $rc2 [$out2]" [ "$rc2" -eq 3 ] || fail_msg "B3b: unreachable alarm target must FAIL LOUD (exit 3); got $rc2 [$out2]"
echo "$out2" | has_match -qi 'UNREACHABLE' || fail_msg "B3b: the failure must name the unreachable target [$out2]" echo "$out2" | grep -qi 'UNREACHABLE' || fail_msg "B3b: the failure must name the unreachable target [$out2]"
) && ok ) && ok
echo "== B4: isolated host -> DEGRADED different-supervision-root beacon FLAGGED ==" echo "== B4: isolated host -> DEGRADED different-supervision-root beacon FLAGGED =="
@@ -176,8 +169,8 @@ echo "== B4: isolated host -> DEGRADED different-supervision-root beacon FLAGGED
rc=$? rc=$?
err="$(cat "$TMP_ROOT/b4.err")" 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]" [ "$rc" -eq 0 ] || fail_msg "B4: a different-supervision-root emit must still succeed (exit 0); got $rc [$out][$err]"
echo "$err" | has_match -qi 'DEGRADED' || fail_msg "B4: a different-supervision-root beacon must be FLAGGED degraded on stderr [$err]" echo "$err" | grep -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]" echo "$out" | grep -q 'degraded=true' || fail_msg "B4: emit must NOT silently present a degraded beacon as healthy (degraded=true expected) [$out]"
) && ok ) && ok
echo "== B5: same-host-sibling -> REJECTED as non-independent, seq NOT advanced ==" echo "== B5: same-host-sibling -> REJECTED as non-independent, seq NOT advanced =="
@@ -189,7 +182,7 @@ echo "== B5: same-host-sibling -> REJECTED as non-independent, seq NOT advanced
out="$("$BEACON" emit 2>&1)" out="$("$BEACON" emit 2>&1)"
rc=$? rc=$?
[ "$rc" -ne 0 ] || fail_msg "B5: a same-host-sibling beacon must be REJECTED (non-zero exit) [$out]" [ "$rc" -ne 0 ] || fail_msg "B5: a same-host-sibling beacon must be REJECTED (non-zero exit) [$out]"
echo "$out" | has_match -qi 'not an independent' || fail_msg "B5: the rejection must explain it is NOT an independent leg [$out]" echo "$out" | grep -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). # 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="$("$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)" [ "${seq_after:-0}" -eq 0 ] || fail_msg "B5: a rejected emit must NOT advance the monotonic seq (got $seq_after)"
@@ -208,10 +201,10 @@ echo "== B6: alarm target resolved BY NAME; beacon.sh inlines no endpoint/secret
out="$("$BEACON" check --slo-seconds 5 2>&1)" out="$("$BEACON" check --slo-seconds 5 2>&1)"
rc=$? rc=$?
[ "$rc" -eq 1 ] || fail_msg "B6: absence via a by-name alarm adapter must fire (exit 1); got $rc [$out]" [ "$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 | has_match -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 | grep -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. # The framework file must NOT inline the endpoint/secret: it only knows a NAME.
has_match -qF "$SECRET_TARGET" "$BEACON" && fail_msg "B6: beacon.sh must NOT inline the target endpoint/secret" grep -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" grep -qF "$SECRET_TARGET" "$WAKE_ALARM_SINK_CMD" && fail_msg "B6: even the adapter must resolve by-name, not inline the secret"
true true
) && ok ) && ok
@@ -225,7 +218,7 @@ echo "== B7: unconfigured OR unreachable BEACON sink on emit -> FAIL LOUD =="
out="$("$BEACON" emit 2>&1)" out="$("$BEACON" emit 2>&1)"
rc=$? rc=$?
[ "$rc" -eq 3 ] || fail_msg "B7a: unconfigured beacon sink must FAIL LOUD (exit 3); got $rc [$out]" [ "$rc" -eq 3 ] || fail_msg "B7a: unconfigured beacon sink must FAIL LOUD (exit 3); got $rc [$out]"
echo "$out" | has_match -qi 'silent no-alarm host' || fail_msg "B7a: the failure must name the silent-no-alarm-host hazard [$out]" echo "$out" | grep -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. # 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="$("$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)" [ "${seq_after:-0}" -eq 0 ] || fail_msg "B7a: an unconfigured-sink emit must NOT advance the seq (got $seq_after)"
@@ -234,7 +227,7 @@ echo "== B7: unconfigured OR unreachable BEACON sink on emit -> FAIL LOUD =="
out2="$("$BEACON" emit 2>&1)" out2="$("$BEACON" emit 2>&1)"
rc2=$? rc2=$?
[ "$rc2" -eq 1 ] || fail_msg "B7b: unreachable beacon sink must FAIL LOUD (exit 1); got $rc2 [$out2]" [ "$rc2" -eq 1 ] || fail_msg "B7b: unreachable beacon sink must FAIL LOUD (exit 1); got $rc2 [$out2]"
echo "$out2" | has_match -qi 'UNREACHABLE' || fail_msg "B7b: the failure must name the unreachable sink [$out2]" echo "$out2" | grep -qi 'UNREACHABLE' || fail_msg "B7b: the failure must name the unreachable sink [$out2]"
) && ok ) && ok
echo "== B8: no invented SLO -> check --slo-seconds is REQUIRED (fail-loud) ==" echo "== B8: no invented SLO -> check --slo-seconds is REQUIRED (fail-loud) =="
@@ -245,7 +238,7 @@ echo "== B8: no invented SLO -> check --slo-seconds is REQUIRED (fail-loud) =="
out="$("$BEACON" check 2>&1)" out="$("$BEACON" check 2>&1)"
rc=$? rc=$?
[ "$rc" -eq 3 ] || fail_msg "B8: check without an SLO must fail loud (exit 3); got $rc [$out]" [ "$rc" -eq 3 ] || fail_msg "B8: check without an SLO must fail loud (exit 3); got $rc [$out]"
echo "$out" | has_match -qi 'slo' || fail_msg "B8: the usage error must name the missing SLO [$out]" echo "$out" | grep -qi 'slo' || fail_msg "B8: the usage error must name the missing SLO [$out]"
) && ok ) && ok
echo "== B9: capture-pane hint is a liveness HINT ONLY (does NOT suppress absence) ==" echo "== B9: capture-pane hint is a liveness HINT ONLY (does NOT suppress absence) =="
@@ -274,7 +267,7 @@ echo "== B10: fresh beacon within SLO -> ALIVE (exit 0, no alarm) =="
out="$("$BEACON" check --slo-seconds 60 2>&1)" out="$("$BEACON" check --slo-seconds 60 2>&1)"
rc=$? rc=$?
[ "$rc" -eq 0 ] || fail_msg "B10: a fresh beacon within SLO must be ALIVE (exit 0); got $rc [$out]" [ "$rc" -eq 0 ] || fail_msg "B10: a fresh beacon within SLO must be ALIVE (exit 0); got $rc [$out]"
echo "$out" | has_match -qi 'ALIVE' || fail_msg "B10: a fresh beacon must report ALIVE [$out]" echo "$out" | grep -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" [ ! -s "$ALARM_OUT" ] || fail_msg "B10: a fresh beacon must NOT fire an alarm"
) && ok ) && ok
@@ -298,7 +291,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" 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=$? 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]" [ "$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" | has_match -qi 'ALARM FIRED' || fail_msg "B11: the receive-time-stale beacon must fire the absence alarm [$out]" echo "$out" | grep -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)]" 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 ) && ok
@@ -336,7 +329,7 @@ else
jq -c '.beacon_seq = 99999 | .host_id = "attacker"' "$SHIPPED" >"$spoof" jq -c '.beacon_seq = 99999 | .host_id = "attacker"' "$SHIPPED" >"$spoof"
out="$("$BEACON" record <"$spoof" 2>&1)"; rc=$? 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]" [ "$rc" -ne 0 ] || fail_msg "B12: a spoofed beacon (altered fields) must be REJECTED at record; got rc=$rc [$out]"
echo "$out" | has_match -qi 'spoofed beacon' || fail_msg "B12: the rejection must name the spoof [$out]" echo "$out" | grep -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)" [ ! -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. # (c) an UNSIGNED beacon is rejected when signing is configured.
unsigned="$H/unsigned.json" unsigned="$H/unsigned.json"
@@ -344,14 +337,14 @@ else
out2="$("$BEACON" record <"$unsigned" 2>&1)"; rc2=$? 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]" [ "$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. # beacon.sh must not inline the key material.
has_match -qF "test-beacon-hmac-key-do-not-echo" "$BEACON" && fail_msg "B12: beacon.sh must NOT inline the HMAC key" grep -qF "test-beacon-hmac-key-do-not-echo" "$BEACON" && fail_msg "B12: beacon.sh must NOT inline the HMAC key"
true true
) && ok ) && ok
fi fi
echo echo
if [ -s "$FAILFILE" ]; then if [ -s "$FAILFILE" ]; then
echo "wake beacon harness: FAILED ($(count_lines . "$FAILFILE") assertion(s))" >&2 echo "wake beacon harness: FAILED ($(grep -c . "$FAILFILE") assertion(s))" >&2
exit 1 exit 1
fi fi
echo "wake beacon harness: all invariants passed ($pass groups)" echo "wake beacon harness: all invariants passed ($pass groups)"
@@ -29,13 +29,6 @@
set -uo pipefail set -uo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 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
if ! . "$SCRIPT_DIR/_wake-common.sh"; then
echo "WAKE-ASSERT INIT ABORT: cannot source _wake-common.sh — suite ran ZERO wake assertions (#984)" >&2
exit 97
fi
wake_assert_init
DET="$SCRIPT_DIR/detector.sh" DET="$SCRIPT_DIR/detector.sh"
STORE="$SCRIPT_DIR/store.sh" STORE="$SCRIPT_DIR/store.sh"
@@ -260,8 +253,8 @@ echo "== D5: watch-list schema_version out of manifest range -> FAIL LOUD =="
err="$("$DET" poll-once 2>&1)" err="$("$DET" poll-once 2>&1)"
rc=$? rc=$?
[ "$rc" -ne 0 ] || fail_msg "D5: an out-of-range schema_version must FAIL LOUD (non-zero exit)" [ "$rc" -ne 0 ] || fail_msg "D5: an out-of-range schema_version must FAIL LOUD (non-zero exit)"
echo "$err" | has_match -qi 'schema_version' || fail_msg "D5: the failure must name schema_version [$err]" echo "$err" | grep -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]" echo "$err" | grep -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. # 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)" [ "$(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)" [ "$(det_seq)" = "0" ] || fail_msg "D5: a rejected watch-list must not advance observed_seq, got $(det_seq)"
@@ -294,7 +287,7 @@ echo "== D6: source error / ambiguous-empty -> FAIL LOUD, observed_seq NOT advan
err="$("$DET" poll-once 2>&1)" err="$("$DET" poll-once 2>&1)"
rc=$? rc=$?
[ "$rc" -ne 0 ] || fail_msg "D6: a source error must FAIL LOUD (non-zero exit)" [ "$rc" -ne 0 ] || fail_msg "D6: a source error must FAIL LOUD (non-zero exit)"
echo "$err" | has_match -qi 'FAIL LOUD' || fail_msg "D6: the source error must be loud [$err]" echo "$err" | grep -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)" [ "$(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)" [ "$(depth)" = "0" ] || fail_msg "D6: a source error must NOT enqueue, got depth $(depth)"
@@ -305,7 +298,7 @@ echo "== D6: source error / ambiguous-empty -> FAIL LOUD, observed_seq NOT advan
err="$("$DET" poll-once 2>&1)" err="$("$DET" poll-once 2>&1)"
rc=$? rc=$?
[ "$rc" -ne 0 ] || fail_msg "D6: an ambiguous-empty response must FAIL LOUD (non-zero exit)" [ "$rc" -ne 0 ] || fail_msg "D6: an ambiguous-empty response must FAIL LOUD (non-zero exit)"
echo "$err" | has_match -qi 'AMBIGUOUS-EMPTY' || fail_msg "D6: ambiguous-empty must be named in the loud failure [$err]" echo "$err" | grep -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))" [ "$(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)" [ "$(depth)" = "0" ] || fail_msg "D6: ambiguous-empty must NOT enqueue, got depth $(depth)"
@@ -449,7 +442,7 @@ EOF
write_fc_watchlist "$wl" 999 write_fc_watchlist "$wl" 999
err="$("$DET" poll-once 2>&1)"; rc=$? 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)" [ "$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" | has_match -qi 'range' || fail_msg "D9: the out-of-range failure must still state it is out of the supported range [$err]" echo "$err" | grep -qi 'range' || fail_msg "D9: the out-of-range failure must still state it is out of the supported range [$err]"
) && ok ) && ok
echo "== D10: snapshot metadata (fd 3, #940) — adapter-attested sha/ts land in the enqueued locators ==" echo "== D10: snapshot metadata (fd 3, #940) — adapter-attested sha/ts land in the enqueued locators =="
@@ -520,7 +513,7 @@ EOF
printf 'SHA-BBB\n' >"$stub/repo_r1" printf 'SHA-BBB\n' >"$stub/repo_r1"
err="$("$DET" poll-once 2>&1 >/dev/null)"; rc=$? err="$("$DET" poll-once 2>&1 >/dev/null)"; rc=$?
[ "$rc" -eq 0 ] || fail_msg "D11: malformed metadata must NEVER fail the poll (rc=$rc)" [ "$rc" -eq 0 ] || fail_msg "D11: malformed metadata must NEVER fail the poll (rc=$rc)"
echo "$err" | has_match -qi 'snapshot' || fail_msg "D11: dropping malformed metadata must be LOUD on stderr [$err]" echo "$err" | grep -qi 'snapshot' || fail_msg "D11: dropping malformed metadata must be LOUD on stderr [$err]"
entry="$("$STORE" drain | tail -1)" entry="$("$STORE" drain | tail -1)"
echo "$entry" | jq -e '.locators | has("snapshot_sha") or has("snapshot_ts")' >/dev/null 2>&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]" && fail_msg "D11: malformed metadata must emit NO snapshot fields [$entry]"
@@ -529,7 +522,7 @@ EOF
printf 'SHA-CCC\n' >"$stub/repo_r1" printf 'SHA-CCC\n' >"$stub/repo_r1"
err="$("$DET" poll-once 2>&1 >/dev/null)"; rc=$? 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)" [ "$rc" -eq 0 ] || fail_msg "D11: rejected snapshot_sha must NEVER fail the poll (rc=$rc)"
echo "$err" | has_match -qi 'snapshot' || fail_msg "D11: rejecting a bad snapshot_sha must be LOUD on stderr [$err]" echo "$err" | grep -qi 'snapshot' || fail_msg "D11: rejecting a bad snapshot_sha must be LOUD on stderr [$err]"
entry="$("$STORE" drain | tail -1)" entry="$("$STORE" drain | tail -1)"
echo "$entry" | jq -e '.locators | has("snapshot_sha") or has("snapshot_ts")' >/dev/null 2>&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]" && fail_msg "D11: rejected metadata must emit NO snapshot fields [$entry]"
@@ -567,7 +560,7 @@ EOF
printf 'SHA-BBB\n' >"$stub/repo_r1" printf 'SHA-BBB\n' >"$stub/repo_r1"
err="$("$DET" poll-once 2>&1 >/dev/null)"; rc=$? 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)" [ "$rc" -eq 0 ] || fail_msg "D12: ts-without-sha must NEVER fail the poll (rc=$rc)"
echo "$err" | has_match -qi 'without a valid snapshot_sha' || fail_msg "D12: dropping ts-without-sha must be LOUD on stderr [$err]" echo "$err" | grep -qi 'without a valid snapshot_sha' || fail_msg "D12: dropping ts-without-sha must be LOUD on stderr [$err]"
entry="$("$STORE" drain | tail -1)" entry="$("$STORE" drain | tail -1)"
echo "$entry" | jq -e '.locators | has("snapshot_sha") or has("snapshot_ts")' >/dev/null 2>&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]" && fail_msg "D12: ts-without-sha must emit NO snapshot fields [$entry]"
@@ -578,7 +571,7 @@ EOF
printf 'SHA-CCC\n' >"$stub/repo_r1" printf 'SHA-CCC\n' >"$stub/repo_r1"
err="$("$DET" poll-once 2>&1 >/dev/null)"; rc=$? err="$("$DET" poll-once 2>&1 >/dev/null)"; rc=$?
[ "$rc" -eq 0 ] || fail_msg "D12: future ts must NEVER fail the poll (rc=$rc)" [ "$rc" -eq 0 ] || fail_msg "D12: future ts must NEVER fail the poll (rc=$rc)"
echo "$err" | has_match -qi 'future-skew' || fail_msg "D12: dropping a future ts must be LOUD on stderr [$err]" echo "$err" | grep -qi 'future-skew' || fail_msg "D12: dropping a future ts must be LOUD on stderr [$err]"
entry="$("$STORE" drain | tail -1)" 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 \ 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]" || fail_msg "D12: future ts must drop ts but KEEP the sha [$entry]"
@@ -588,7 +581,7 @@ EOF
printf 'SHA-DDD\n' >"$stub/repo_r1" printf 'SHA-DDD\n' >"$stub/repo_r1"
err="$("$DET" poll-once 2>&1 >/dev/null)"; rc=$? err="$("$DET" poll-once 2>&1 >/dev/null)"; rc=$?
[ "$rc" -eq 0 ] || fail_msg "D12: absurd ts must NEVER fail the poll (rc=$rc)" [ "$rc" -eq 0 ] || fail_msg "D12: absurd ts must NEVER fail the poll (rc=$rc)"
echo "$err" | has_match -qi 'sane positive epoch' || fail_msg "D12: rejecting an absurd ts must be LOUD on stderr [$err]" echo "$err" | grep -qi 'sane positive epoch' || fail_msg "D12: rejecting an absurd ts must be LOUD on stderr [$err]"
entry="$("$STORE" drain | tail -1)" 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 \ 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]" || fail_msg "D12: absurd ts must drop ts but KEEP the sha [$entry]"
@@ -628,7 +621,7 @@ EOF
printf 'SHA-BBB\n' >"$stub/repo_r1" printf 'SHA-BBB\n' >"$stub/repo_r1"
err="$(WAKE_SNAPSHOT_TS_FUTURE_SLACK='300s' "$DET" poll-once 2>&1 >/dev/null)"; rc=$? 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)" [ "$rc" -eq 0 ] || fail_msg "D13: SLACK='300s' must NEVER fail the poll (rc=$rc)"
echo "$err" | has_match -qi 'falling back to 300' || fail_msg "D13: malformed slack must be LOUD on stderr [$err]" echo "$err" | grep -qi 'falling back to 300' || fail_msg "D13: malformed slack must be LOUD on stderr [$err]"
entry="$("$STORE" drain | tail -1)" 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 \ 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]" || fail_msg "D13: valid metadata must SURVIVE a malformed knob (fallback, not drop) [$entry]"
@@ -637,7 +630,7 @@ EOF
printf 'SHA-CCC\n' >"$stub/repo_r1" printf 'SHA-CCC\n' >"$stub/repo_r1"
err="$(WAKE_SNAPSHOT_TS_FUTURE_SLACK='abc' "$DET" poll-once 2>&1 >/dev/null)"; rc=$? 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)" [ "$rc" -eq 0 ] || fail_msg "D13: SLACK='abc' must NEVER fail the poll (rc=$rc)"
echo "$err" | has_match -qi 'falling back to 300' || fail_msg "D13: non-numeric slack must be LOUD on stderr [$err]" echo "$err" | grep -qi 'falling back to 300' || fail_msg "D13: non-numeric slack must be LOUD on stderr [$err]"
entry="$("$STORE" drain | tail -1)" 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 \ 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]" || fail_msg "D13: valid metadata must SURVIVE a non-numeric knob [$entry]"
@@ -647,7 +640,7 @@ EOF
printf 'SHA-DDD\n' >"$stub/repo_r1" printf 'SHA-DDD\n' >"$stub/repo_r1"
err="$(WAKE_SNAPSHOT_TS_FUTURE_SLACK='-99999999' "$DET" poll-once 2>&1 >/dev/null)"; rc=$? 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)" [ "$rc" -eq 0 ] || fail_msg "D13: negative SLACK must NEVER fail the poll (rc=$rc)"
echo "$err" | has_match -qi 'falling back to 300' || fail_msg "D13: negative slack must be LOUD on stderr [$err]" echo "$err" | grep -qi 'falling back to 300' || fail_msg "D13: negative slack must be LOUD on stderr [$err]"
entry="$("$STORE" drain | tail -1)" 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 \ 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]" || fail_msg "D13: negative slack must NOT invert the guard into deny-all [$entry]"
@@ -659,7 +652,7 @@ EOF
printf 'SHA-CC2\n' >"$stub/repo_r1" printf 'SHA-CC2\n' >"$stub/repo_r1"
err="$(WAKE_SNAPSHOT_TS_FUTURE_SLACK=$'300\n8' "$DET" poll-once 2>&1 >/dev/null)"; rc=$? 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]" [ "$rc" -eq 0 ] || fail_msg "D13: multi-line SLACK (300\\n8) must NEVER fail the poll (rc=$rc) [$err]"
echo "$err" | has_match -qi 'falling back to 300' || fail_msg "D13: multi-line slack must be LOUD on stderr [$err]" echo "$err" | grep -qi 'falling back to 300' || fail_msg "D13: multi-line slack must be LOUD on stderr [$err]"
entry="$("$STORE" drain | tail -1)" 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 \ 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]" || fail_msg "D13: valid metadata must SURVIVE a multi-line knob [$entry]"
@@ -667,7 +660,7 @@ EOF
printf 'SHA-CC3\n' >"$stub/repo_r1" printf 'SHA-CC3\n' >"$stub/repo_r1"
err="$(WAKE_SNAPSHOT_TS_FUTURE_SLACK=$'300\nabc' "$DET" poll-once 2>&1 >/dev/null)"; rc=$? 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]" [ "$rc" -eq 0 ] || fail_msg "D13: multi-line SLACK (300\\nabc) must NEVER fail the poll (rc=$rc) [$err]"
echo "$err" | has_match -qi 'falling back to 300' || fail_msg "D13: multi-line non-numeric slack must be LOUD on stderr [$err]" echo "$err" | grep -qi 'falling back to 300' || fail_msg "D13: multi-line non-numeric slack must be LOUD on stderr [$err]"
entry="$("$STORE" drain | tail -1)" 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 \ 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]" || fail_msg "D13: valid metadata must SURVIVE a multi-line non-numeric knob [$entry]"
@@ -694,7 +687,7 @@ EOF
printf 'SHA-EEE\n' >"$stub/repo_r1" printf 'SHA-EEE\n' >"$stub/repo_r1"
err="$(WAKE_SNAPSHOT_TS_FUTURE_SLACK='0' "$DET" poll-once 2>&1 >/dev/null)"; rc=$? 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)" [ "$rc" -eq 0 ] || fail_msg "D13: valid SLACK=0 must not fail the poll (rc=$rc)"
echo "$err" | has_match -qi 'future-skew' || fail_msg "D13: a valid tightened slack must still reject a future ts [$err]" echo "$err" | grep -qi 'future-skew' || fail_msg "D13: a valid tightened slack must still reject a future ts [$err]"
entry="$("$STORE" drain | tail -1)" 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 \ 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]" || fail_msg "D13: valid SLACK=0 must drop the future ts but keep the sha [$entry]"
@@ -703,7 +696,7 @@ EOF
echo echo
if [ -s "$FAILFILE" ]; then if [ -s "$FAILFILE" ]; then
echo "wake detector harness: FAILED ($(count_lines . "$FAILFILE") assertion(s))" >&2 echo "wake detector harness: FAILED ($(grep -c . "$FAILFILE") assertion(s))" >&2
exit 1 exit 1
fi fi
echo "wake detector harness: all invariants passed ($pass groups)" echo "wake detector harness: all invariants passed ($pass groups)"
@@ -35,13 +35,6 @@
set -uo pipefail set -uo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 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
if ! . "$SCRIPT_DIR/_wake-common.sh"; then
echo "WAKE-ASSERT INIT ABORT: cannot source _wake-common.sh — suite ran ZERO wake assertions (#984)" >&2
exit 97
fi
wake_assert_init
STORE="$SCRIPT_DIR/store.sh" STORE="$SCRIPT_DIR/store.sh"
DIGEST="$SCRIPT_DIR/digest.sh" DIGEST="$SCRIPT_DIR/digest.sh"
SIGN="$SCRIPT_DIR/sign.sh" SIGN="$SCRIPT_DIR/sign.sh"
@@ -109,13 +102,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 1 --class actionable --locators '{"repo":"r","issue":11}' >/dev/null
"$STORE" enqueue --seq 2 --class actionable --locators '{"repo":"r","issue":22}' >/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" out="$("$DIGEST" render)" || fail_msg "D1: render exited non-zero"
echo "$out" | has_match -q 'issue=#11' || fail_msg "D1: OLDER change (issue #11) dropped — digest is a delta, not cumulative state" echo "$out" | grep -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" | grep -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" | grep -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" echo "$out" | grep -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 # 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. # subsumes the earlier, but the cumulative unacked set (both actionables) stays.
echo "$out" | has_match -q 'pending=2' || fail_msg "D1: cumulative pending count wrong (expected 2)" echo "$out" | grep -q 'pending=2' || fail_msg "D1: cumulative pending count wrong (expected 2)"
) && ok ) && ok
echo "== D2: HARD-LOCATOR enforcement — a malformed actionable claim is QUARANTINED (fail-loud PER-ENTRY, #920); never delivered as valid ==" echo "== D2: HARD-LOCATOR enforcement — a malformed actionable claim is QUARANTINED (fail-loud PER-ENTRY, #920); never delivered as valid =="
@@ -134,9 +127,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 # 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). # 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" [ "$rc" -eq 0 ] || fail_msg "D2: a malformed actionable must be quarantined (render exit 0, #920), got $rc"
printf '%s' "$out" | has_match -q 'mergeable=true' && fail_msg "D2: an unlocated actionable claim must NOT be delivered as a valid CLAIM" printf '%s' "$out" | grep -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)" 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)"
has_match -qi 'QUARANTINE' "$err" || fail_msg "D2: a LOUD per-entry alarm must fire for the malformed claim" grep -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 -> # A present-but-imprecise sha (not 40 hex) does NOT satisfy the hard locator ->
# quarantined, not delivered. # quarantined, not delivered.
@@ -147,8 +140,8 @@ echo "== D2: HARD-LOCATOR enforcement — a malformed actionable claim is QUARAN
rc=0 rc=0
out="$("$DIGEST" render 2>/dev/null)" || rc=$? out="$("$DIGEST" render 2>/dev/null)" || rc=$?
[ "$rc" -eq 0 ] || fail_msg "D2: imprecise-sha entry must be quarantined (render exit 0), got $rc" [ "$rc" -eq 0 ] || fail_msg "D2: imprecise-sha entry must be quarantined (render exit 0), got $rc"
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)" 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)"
has_match -q 'ci=green' "$h/default/dead-letter.jsonl" 2>/dev/null || fail_msg "D2: imprecise-sha claim must be dead-lettered" grep -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). # The SAME claim WITH a precise 40-hex sha renders fine (delivered, not quarantined).
h="$(fresh_state d2c)" h="$(fresh_state d2c)"
@@ -156,7 +149,7 @@ echo "== D2: HARD-LOCATOR enforcement — a malformed actionable claim is QUARAN
export WAKE_STATE_HOME export WAKE_STATE_HOME
"$STORE" enqueue --seq 1 --class actionable --locators "$(jq -cn --arg s "$SHA40" '{claim:"ci=green",sha:$s}')" >/dev/null "$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" out="$("$DIGEST" render 2>/dev/null)" || fail_msg "D2: a well-located actionable claim must render"
printf '%s' "$out" | has_match -q "$SHA40" || fail_msg "D2: a well-located actionable claim must be DELIVERED" printf '%s' "$out" | grep -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" [ -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- # --- #905 bypass-prevention (STILL enforced, now via quarantine): a NON-
# CANONICAL entry with a TOP-LEVEL `.claim` (store.sh never emits this shape; # CANONICAL entry with a TOP-LEVEL `.claim` (store.sh never emits this shape;
@@ -171,8 +164,8 @@ echo "== D2: HARD-LOCATOR enforcement — a malformed actionable claim is QUARAN
rc=0 rc=0
out="$(printf '%s\n' "$toplevel_claim_entry" | "$DIGEST" render --stdin 2>/dev/null)" || rc=$? 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" [ "$rc" -eq 0 ] || fail_msg "D2(#905): top-level .claim must be quarantined via --stdin (exit 0), got $rc"
printf '%s' "$out" | has_match -q 'mergeable=true' && fail_msg "D2(#905): --stdin top-level-.claim must NOT be delivered (gate not bypassed)" printf '%s' "$out" | grep -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)" 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)"
ff="$TMP_ROOT/d2d-entry.jsonl" ff="$TMP_ROOT/d2d-entry.jsonl"
printf '%s\n' "$toplevel_claim_entry" >"$ff" printf '%s\n' "$toplevel_claim_entry" >"$ff"
h="$(fresh_state d2e)" h="$(fresh_state d2e)"
@@ -181,8 +174,8 @@ echo "== D2: HARD-LOCATOR enforcement — a malformed actionable claim is QUARAN
rc=0 rc=0
out2="$("$DIGEST" render --from-file "$ff" 2>/dev/null)" || rc=$? 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" [ "$rc" -eq 0 ] || fail_msg "D2(#905): top-level .claim must be quarantined via --from-file (exit 0), got $rc"
printf '%s' "$out2" | has_match -q 'mergeable=true' && fail_msg "D2(#905): --from-file top-level-.claim must NOT be delivered (gate not bypassed)" printf '%s' "$out2" | grep -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)" 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)"
) && ok ) && ok
echo "== D3: TWO-TIER — orientation no-op with ZERO tool calls; actionable = claim-to-verify ==" echo "== D3: TWO-TIER — orientation no-op with ZERO tool calls; actionable = claim-to-verify =="
@@ -204,7 +197,7 @@ echo "== D3: TWO-TIER — orientation no-op with ZERO tool calls; actionable = c
done done
# No pending obligations => no-op common case. # No pending obligations => no-op common case.
out="$(PATH="$bin:$PATH" "$DIGEST" render --lane build)" || fail_msg "D3: no-op render failed" out="$(PATH="$bin:$PATH" "$DIGEST" render --lane build)" || fail_msg "D3: no-op render failed"
echo "$out" | has_match -q 'NO-OP' || fail_msg "D3: empty inbox must render an explicit NO-OP orientation" echo "$out" | grep -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)" [ -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 # Now an actionable, consequential fact. It must be a CLAIM-TO-VERIFY, never
# rendered as a trusted assertion or an auto-action. # rendered as a trusted assertion or an auto-action.
@@ -213,14 +206,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" out2="$(PATH="$bin:$PATH" "$DIGEST" render)" || fail_msg "D3: actionable render failed"
# Rendering itself still makes ZERO live calls (it only lays out claims). # 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)" [ -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" | has_match -q 'CLAIM@seq' || fail_msg "D3: consequential fact not framed as CLAIM@seq" echo "$out2" | grep -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" | grep -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" echo "$out2" | grep -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. # The consequential fact must NOT appear as a bare trusted directive.
echo "$out2" | has_match -qiE 'merge now|go ahead and (merge|deploy)|safe to merge' && echo "$out2" | grep -qiE 'merge now|go ahead and (merge|deploy)|safe to merge' &&
fail_msg "D3: digest auto-actioned a consequential fact (imperative present)" fail_msg "D3: digest auto-actioned a consequential fact (imperative present)"
# And its hard locator + one-call re-verify hint are present. # And its hard locator + one-call re-verify hint are present.
echo "$out2" | has_match -q "re-verify (ONE call)" || fail_msg "D3: actionable claim missing one-call re-verify locator" echo "$out2" | grep -q "re-verify (ONE call)" || fail_msg "D3: actionable claim missing one-call re-verify locator"
true true
) && ok ) && ok
@@ -236,7 +229,7 @@ echo "== D4: SCRUB — secret-canary + ANSI/bidi/zero-width in source free-text
"$STORE" enqueue --seq 1 --class actionable --locators "$loc" >/dev/null "$STORE" enqueue --seq 1 --class actionable --locators "$loc" >/dev/null
out="$("$DIGEST" render)" || fail_msg "D4: render failed" out="$("$DIGEST" render)" || fail_msg "D4: render failed"
# ANSI escape / CSI must be gone. # ANSI escape / CSI must be gone.
printf '%s' "$out" | LC_ALL=C has_match -q "$(printf '\x1b')" && fail_msg "D4: ANSI ESC survived the scrub" printf '%s' "$out" | LC_ALL=C grep -q "$(printf '\x1b')" && fail_msg "D4: ANSI ESC survived the scrub"
# bidi/zero-width/BOM UTF-8 sequences must be gone. # bidi/zero-width/BOM UTF-8 sequences must be gone.
# #912: patterns are LITERAL bytes + `grep -E`, NOT PCRE `grep -P`. BusyBox # #912: patterns are LITERAL bytes + `grep -E`, NOT PCRE `grep -P`. BusyBox
# grep (Alpine/musl CI) has no `-P` — a `grep -qP` there errors # grep (Alpine/musl CI) has no `-P` — a `grep -qP` there errors
@@ -251,21 +244,21 @@ echo "== D4: SCRUB — secret-canary + ANSI/bidi/zero-width in source free-text
_b8b="$(printf '%b' '\x8b')"; _b8f="$(printf '%b' '\x8f')" _b8b="$(printf '%b' '\x8b')"; _b8f="$(printf '%b' '\x8f')"
_baa="$(printf '%b' '\xaa')"; _bae="$(printf '%b' '\xae')" _baa="$(printf '%b' '\xaa')"; _bae="$(printf '%b' '\xae')"
_bbom="$(printf '%b' '\xef\xbb\xbf')" _bbom="$(printf '%b' '\xef\xbb\xbf')"
printf '%s' "$out" | LC_ALL=C has_match -qE "${_b280}[${_b8b}-${_b8f}${_baa}-${_bae}]|${_bbom}" && printf '%s' "$out" | LC_ALL=C grep -qE "${_b280}[${_b8b}-${_b8f}${_baa}-${_bae}]|${_bbom}" &&
fail_msg "D4: bidi/zero-width/BOM survived the scrub" fail_msg "D4: bidi/zero-width/BOM survived the scrub"
# C0 control bytes (except tab/newline) must be gone. # C0 control bytes (except tab/newline) must be gone.
_c00="$(printf '%b' '\x01')"; _c08="$(printf '%b' '\x08')" _c00="$(printf '%b' '\x01')"; _c08="$(printf '%b' '\x08')"
_c0e="$(printf '%b' '\x0e')"; _c1f="$(printf '%b' '\x1f')"; _c7f="$(printf '%b' '\x7f')" _c0e="$(printf '%b' '\x0e')"; _c1f="$(printf '%b' '\x1f')"; _c7f="$(printf '%b' '\x7f')"
printf '%s' "$out" | LC_ALL=C has_match -qE "[${_c00}-${_c08}${_c0e}-${_c1f}${_c7f}]" && printf '%s' "$out" | LC_ALL=C grep -qE "[${_c00}-${_c08}${_c0e}-${_c1f}${_c7f}]" &&
fail_msg "D4: a C0 control byte survived the scrub" fail_msg "D4: a C0 control byte survived the scrub"
# Secret canaries must be redacted, never inlined. # Secret canaries must be redacted, never inlined.
printf '%s' "$out" | has_match -q 'ghp_0123456789' && fail_msg "D4: GitHub-token canary LEAKED into the digest" printf '%s' "$out" | grep -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" | grep -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" printf '%s' "$out" | grep -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. # Free-text is quoted inside a DELIMITED untrusted block, framed as NOT instructions.
printf '%s' "$out" | has_match -q 'BEGIN UNTRUSTED DATA' || fail_msg "D4: source free-text not placed in a delimited untrusted block" printf '%s' "$out" | grep -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. # The 40-hex git SHA locator must NOT be mangled by the secret scrubber.
printf '%s' "$out" | has_match -q "$SHA40" || fail_msg "D4: legitimate 40-hex SHA locator was wrongly scrubbed" printf '%s' "$out" | grep -q "$SHA40" || fail_msg "D4: legitimate 40-hex SHA locator was wrongly scrubbed"
true true
) && ok ) && ok
@@ -330,11 +323,11 @@ echo "== H2: KEY BY-NAME, never inline; no key leak; same-uid boundary documente
fail_msg "H2: by-name key resolution failed" 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" 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. # The key VALUE must never appear in the signed output.
echo "$env1" | has_match -q 'SUPERSECRET-KEYVALUE-XYZ' && fail_msg "H2: key material LEAKED into the signed envelope" echo "$env1" | grep -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. # 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":""}' | 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)" WAKE_HMAC_KEY_NAME=signing-key "$SIGN" sign-entry)"
echo "$entry" | has_match -q 'SUPERSECRET-KEYVALUE-XYZ' && fail_msg "H2: key material LEAKED into the signed entry" echo "$entry" | grep -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" 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 # 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). # a literal key must be rejected as an unknown option (never silently honored).
@@ -348,8 +341,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" fail_msg "H2: signing with an unresolvable key name must FAIL LOUD"
fi fi
# The same-uid threat boundary + off-uid follow-up must be DOCUMENTED in the tool. # The same-uid threat boundary + off-uid follow-up must be DOCUMENTED in the tool.
has_match -qi 'same-uid' "$SIGN" || fail_msg "H2: same-uid threat boundary not documented in sign.sh" grep -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" grep -qi 'off-uid' "$SIGN" || fail_msg "H2: off-uid future signer not named in sign.sh"
) && ok ) && ok
echo "== D5 (#914a): EMBEDDED ACK NAMESPACE — env-less copy-run targets the RENDER-TIME agent, not 'default' ==" echo "== D5 (#914a): EMBEDDED ACK NAMESPACE — env-less copy-run targets the RENDER-TIME agent, not 'default' =="
@@ -362,7 +355,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" 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}')" 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" [ -n "$ack_line" ] || fail_msg "D5: no embedded ack line extracted from the digest"
printf '%s' "$ack_line" | has_match -q 'WAKE_AGENT=someagent' || printf '%s' "$ack_line" | grep -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]" 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 # Actually RUN it with NO WAKE_AGENT in the environment (the real failure
# mode: a consumer copy-pastes the line into a fresh shell). # mode: a consumer copy-pastes the line into a fresh shell).
@@ -408,12 +401,12 @@ echo "== D6 (#914b): DIGEST-CLASS LOCATOR THREADING — ORIENTATION pointer carr
loc="$(jq -cn '{kind:"repo", id:"r1", observed_hash:"deadbeefcafe0123456789abcdef0123456789abcdef0123456789abcdef01", remote:"example/repo"}')" loc="$(jq -cn '{kind:"repo", id:"r1", observed_hash:"deadbeefcafe0123456789abcdef0123456789abcdef0123456789abcdef01", remote:"example/repo"}')"
"$STORE" enqueue --seq 1 --class digest --locators "$loc" >/dev/null "$STORE" enqueue --seq 1 --class digest --locators "$loc" >/dev/null
out="$("$DIGEST" render)" || fail_msg "D6: render failed" out="$("$DIGEST" render)" || fail_msg "D6: render failed"
orientline="$(printf '%s\n' "$out" | has_match -E '^\s*\* seq 1 \[digest\]')" orientline="$(printf '%s\n' "$out" | grep -E '^\s*\* seq 1 \[digest\]')"
[ -n "$orientline" ] || fail_msg "D6: no ORIENTATION line found for seq 1" [ -n "$orientline" ] || fail_msg "D6: no ORIENTATION line found for seq 1"
printf '%s' "$orientline" | has_match -qE 'locator: *$' && printf '%s' "$orientline" | grep -qE 'locator: *$' &&
fail_msg "D6: digest-class ORIENTATION pointer rendered an EMPTY locator despite a populated .locators field [$orientline]" 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. # Must surface something a consumer can act on to re-verify.
printf '%s' "$orientline" | has_match -qE 'remote=example/repo|id=r1|kind=repo' || printf '%s' "$orientline" | grep -qE 'remote=example/repo|id=r1|kind=repo' ||
fail_msg "D6: digest-class ORIENTATION pointer does not carry a usable locator [$orientline]" 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 # --- ACTIONABLE-tier hard-locator FAIL-LOUD must be PRESERVED (now PER-ENTRY
@@ -428,14 +421,14 @@ echo "== D6 (#914b): DIGEST-CLASS LOCATOR THREADING — ORIENTATION pointer carr
rc=0 rc=0
out="$("$DIGEST" render 2>"$err")" || rc=$? 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" [ "$rc" -eq 0 ] || fail_msg "D6: a malformed actionable is now per-entry quarantined (render exit 0, #920), got $rc"
printf '%s' "$out" | has_match -q 'mergeable=true' && fail_msg "D6: a malformed actionable claim must NOT be delivered as valid (fail-loud preserved)" printf '%s' "$out" | grep -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)" 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)"
has_match -qi 'QUARANTINE' "$err" || fail_msg "D6: a malformed actionable must raise a loud per-entry alarm" grep -qi 'QUARANTINE' "$err" || fail_msg "D6: a malformed actionable must raise a loud per-entry alarm"
) && ok ) && ok
echo echo
if [ -s "$FAILFILE" ]; then if [ -s "$FAILFILE" ]; then
echo "wake digest/hmac harness: FAILED ($(count_lines . "$FAILFILE") assertion(s))" >&2 echo "wake digest/hmac harness: FAILED ($(grep -c . "$FAILFILE") assertion(s))" >&2
exit 1 exit 1
fi fi
echo "wake digest/hmac harness: all invariants passed ($pass groups)" echo "wake digest/hmac harness: all invariants passed ($pass groups)"
@@ -117,13 +117,6 @@
set -uo pipefail set -uo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 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
if ! . "$SCRIPT_DIR/_wake-common.sh"; then
echo "WAKE-ASSERT INIT ABORT: cannot source _wake-common.sh — suite ran ZERO wake assertions (#984)" >&2
exit 97
fi
wake_assert_init
DIGEST="$SCRIPT_DIR/digest.sh" DIGEST="$SCRIPT_DIR/digest.sh"
command -v jq >/dev/null 2>&1 || { command -v jq >/dev/null 2>&1 || {
@@ -183,12 +176,12 @@ echo "== Q1 (a): malformed address-free {kind,id,observed_hash} QUARANTINES; cle
out="$("$DIGEST" render --from-file "$f" --agent default 2>"$err")" out="$("$DIGEST" render --from-file "$f" --agent default 2>"$err")"
rc=$? rc=$?
[ "$rc" -eq 0 ] || fail_msg "Q1: render must EXIT 0 (per-entry quarantine, not whole-digest exit-4), got rc=$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" | 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" | 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" | has_match -q 'MALFORMED-Q' && fail_msg "Q1: the quarantined entry must be EXCLUDED from the rendered digest" printf '%s' "$out" | grep -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" [ -f "$(dlq "$home")" ] || fail_msg "Q1: a durable dead-letter file must be written"
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)" grep -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" grep -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)" grep -q 'observed_seq=1' "$err" || fail_msg "Q1: the alarm must identify the offending entry (observed_seq=1)"
) && ok ) && ok
echo "== Q2 (b): reconciler enumeration (reconciled:true) renders ORIENTATION-tier, NO exit-4 ==" echo "== Q2 (b): reconciler enumeration (reconciled:true) renders ORIENTATION-tier, NO exit-4 =="
@@ -207,8 +200,8 @@ echo "== Q2 (b): reconciler enumeration (reconciled:true) renders ORIENTATION-ti
out="$("$DIGEST" render --from-file "$f" --agent default 2>"$err")" out="$("$DIGEST" render --from-file "$f" --agent default 2>"$err")"
rc=$? rc=$?
[ "$rc" -eq 0 ] || fail_msg "Q2: a reconciler enumeration must NOT exit-4 (it is ORIENTATION-tier), got rc=$rc" [ "$rc" -eq 0 ] || fail_msg "Q2: a reconciler enumeration must NOT exit-4 (it is ORIENTATION-tier), got rc=$rc"
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" | 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" | has_match -q 'CLAIM@seq' && fail_msg "Q2: an enumeration must NOT render as an ACTIONABLE CLAIM@seq" printf '%s' "$out" | grep -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" [ -s "$(dlq "$home")" ] && fail_msg "Q2: an ORIENTATION-tier enumeration must NOT be quarantined/dead-lettered"
true true
) && ok ) && ok
@@ -227,10 +220,10 @@ echo "== Q3 (c): TWO DISTINCT enumerations BOTH survive as SEPARATE orientation
out="$("$DIGEST" render --from-file "$f" --agent default 2>"$err")" out="$("$DIGEST" render --from-file "$f" --agent default 2>"$err")"
rc=$? rc=$?
[ "$rc" -eq 0 ] || fail_msg "Q3: two enumerations must render (exit 0), got rc=$rc" [ "$rc" -eq 0 ] || fail_msg "Q3: two enumerations must render (exit 0), got rc=$rc"
n="$(printf '%s\n' "$out" | count_lines 'id=ENUM-C[12]' || true)" n="$(printf '%s\n' "$out" | grep -c '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" [ "$n" = "2" ] || fail_msg "Q3: BOTH distinct enumerations must survive as SEPARATE orientation pointers (expected 2, got $n) — neither coalesced away"
printf '%s' "$out" | has_match -q 'id=ENUM-C1' || fail_msg "Q3: enumeration ENUM-C1 must be present" printf '%s' "$out" | grep -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" printf '%s' "$out" | grep -q 'id=ENUM-C2' || fail_msg "Q3: enumeration ENUM-C2 must be present"
[ -s "$(dlq "$home")" ] && fail_msg "Q3: enumerations must NOT be quarantined" [ -s "$(dlq "$home")" ] && fail_msg "Q3: enumerations must NOT be quarantined"
true true
) && ok ) && ok
@@ -246,10 +239,10 @@ echo "== Q4: PRESERVED — a genuine malformed ACTIONABLE claim is STILL loud (d
out="$("$DIGEST" render --from-file "$f" --agent default 2>"$err")" out="$("$DIGEST" render --from-file "$f" --agent default 2>"$err")"
rc=$? rc=$?
[ "$rc" -eq 0 ] || fail_msg "Q4: render must exit 0 (per-entry quarantine), got rc=$rc" [ "$rc" -eq 0 ] || fail_msg "Q4: render must exit 0 (per-entry quarantine), got rc=$rc"
printf '%s' "$out" | has_match -q 'CLAIM-KEEP' && fail_msg "Q4: a malformed actionable must NOT be delivered as if valid" printf '%s' "$out" | grep -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)" printf '%s' "$out" | grep -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)" grep -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" grep -qi 'QUARANTINE' "$err" || fail_msg "Q4: the malformed actionable must raise a LOUD alarm"
) && ok ) && ok
echo "== Q5: claim-precedence gated — a non-actionable-class entry carrying a claim (no hard locator) is STILL quarantined ==" echo "== Q5: claim-precedence gated — a non-actionable-class entry carrying a claim (no hard locator) is STILL quarantined =="
@@ -265,8 +258,8 @@ echo "== Q5: claim-precedence gated — a non-actionable-class entry carrying a
out="$("$DIGEST" render --from-file "$f" --agent default 2>"$err")" out="$("$DIGEST" render --from-file "$f" --agent default 2>"$err")"
rc=$? rc=$?
[ "$rc" -eq 0 ] || fail_msg "Q5: render must exit 0, got rc=$rc" [ "$rc" -eq 0 ] || fail_msg "Q5: render must exit 0, got rc=$rc"
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)" 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" | has_match -q 'CI is green' && fail_msg "Q5: the un-verifiable claim must NOT be delivered" printf '%s' "$out" | grep -q 'CI is green' && fail_msg "Q5: the un-verifiable claim must NOT be delivered"
true true
) && ok ) && ok
@@ -285,11 +278,11 @@ echo "== Q6 (a): dead-lettered entry routes EXACTLY ONE off-host alarm (payload
out="$("$DIGEST" render --from-file "$f" --agent default 2>"$err")" out="$("$DIGEST" render --from-file "$f" --agent default 2>"$err")"
rc=$? rc=$?
[ "$rc" -eq 0 ] || fail_msg "Q6: render must still EXIT 0 (per-entry quarantine, not whole-drain wedge), got rc=$rc" [ "$rc" -eq 0 ] || fail_msg "Q6: render must still EXIT 0 (per-entry quarantine, not whole-drain wedge), got rc=$rc"
n="$(count_lines . "$ALARM_OUT" 2>/dev/null || true)" n="$(grep -c . "$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)]" [ "$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)]"
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)" 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)"
has_match -qi 'QUARANTINE' "$err" || fail_msg "Q6: the existing #920 stderr diagnostic must STILL fire (local + off-host, not either/or)" grep -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" printf '%s' "$out" | grep -q 'DLQ-Q6' && fail_msg "Q6: the quarantined entry must still be EXCLUDED from the rendered digest"
true true
) && ok ) && ok
@@ -307,9 +300,9 @@ echo "== Q7 (b): re-draining the SAME still-dead-lettered entry N times routes Z
for i in 1 2 3 4; do for i in 1 2 3 4; do
"$DIGEST" render --from-file "$f" --agent default >/dev/null 2>"$TMP_ROOT/q7.err.$i" "$DIGEST" render --from-file "$f" --agent default >/dev/null 2>"$TMP_ROOT/q7.err.$i"
done done
n="$(count_lines . "$ALARM_OUT" 2>/dev/null || true)" n="$(grep -c . "$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)]" [ "$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)]"
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)" 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)"
) && ok ) && ok
echo "== Q8 (c): a NEW distinct dead-lettered entry routes its OWN one alarm (dedup is per-entry, not a global latch) ==" echo "== Q8 (c): a NEW distinct dead-lettered entry routes its OWN one alarm (dedup is per-entry, not a global latch) =="
@@ -328,10 +321,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
"$DIGEST" render --from-file "$f1" --agent default >/dev/null 2>/dev/null # re-drain seq 31 -> must NOT re-alarm "$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 "$DIGEST" render --from-file "$f2" --agent default >/dev/null 2>/dev/null # NEW distinct seq 32 -> its own alarm
n="$(count_lines . "$ALARM_OUT" 2>/dev/null || true)" n="$(grep -c . "$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)]" [ "$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)]"
has_match -q '"observed_seq":31' "$ALARM_OUT" 2>/dev/null || fail_msg "Q8: seq 31's alarm must be present" grep -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" 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"
) && ok ) && ok
echo "== Q9 (d): WAKE_ALARM_SINK_CMD unconfigured OR unreachable -> FAIL LOUD (never silent no-alarm); per-entry, render still exits 0 ==" echo "== Q9 (d): WAKE_ALARM_SINK_CMD unconfigured OR unreachable -> FAIL LOUD (never silent no-alarm); per-entry, render still exits 0 =="
@@ -347,9 +340,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")" out_a="$("$DIGEST" render --from-file "$f" --agent default 2>"$err_a")"
rc_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" [ "$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"
has_match -qi 'FAIL LOUD' "$err_a" || fail_msg "Q9a: an unconfigured off-host alarm target must FAIL LOUD on stderr [$(cat "$err_a")]" grep -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")]" 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" | has_match -q 'DLQ-Q9' && fail_msg "Q9a: the quarantined entry must still be EXCLUDED from the rendered digest" printf '%s' "$out_a" | grep -q 'DLQ-Q9' && fail_msg "Q9a: the quarantined entry must still be EXCLUDED from the rendered digest"
true true
) && ok ) && ok
( (
@@ -362,9 +355,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")" out_b="$("$DIGEST" render --from-file "$f" --agent default 2>"$err_b")"
rc_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" [ "$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"
has_match -qi 'FAIL LOUD' "$err_b" || fail_msg "Q9b: an unreachable off-host alarm target must FAIL LOUD on stderr [$(cat "$err_b")]" grep -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")]" grep -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" printf '%s' "$out_b" | grep -q 'DLQ-Q9' && fail_msg "Q9b: the quarantined entry must still be EXCLUDED from the rendered digest"
true true
) && ok ) && ok
@@ -390,16 +383,16 @@ echo "== Q10: snapshot metadata (#940) — snapshot_sha/snapshot_ts render on th
out="$("$DIGEST" render --from-file "$f" --agent default 2>/dev/null)" out="$("$DIGEST" render --from-file "$f" --agent default 2>/dev/null)"
rc=$? rc=$?
[ "$rc" -eq 0 ] || fail_msg "Q10: render must succeed (rc=$rc)" [ "$rc" -eq 0 ] || fail_msg "Q10: render must succeed (rc=$rc)"
snapa_line="$(printf '%s\n' "$out" | has_match 'id=SNAP-A' || true)" snapa_line="$(printf '%s\n' "$out" | grep 'id=SNAP-A' || true)"
printf '%s' "$snapa_line" | has_match -q 'snapshot_sha=0123abc4567890def0123abc4567890def012345' \ printf '%s' "$snapa_line" | grep -q 'snapshot_sha=0123abc4567890def0123abc4567890def012345' \
|| fail_msg "Q10: snapshot_sha must render on the ORIENTATION pointer line [$snapa_line]" || fail_msg "Q10: snapshot_sha must render on the ORIENTATION pointer line [$snapa_line]"
printf '%s' "$snapa_line" | has_match -q 'snapshot_ts=1753850000' \ printf '%s' "$snapa_line" | grep -q 'snapshot_ts=1753850000' \
|| fail_msg "Q10: snapshot_ts must render on the ORIENTATION pointer line (age = emit_ts - snapshot_ts, local arithmetic) [$snapa_line]" || fail_msg "Q10: snapshot_ts must render on the ORIENTATION pointer line (age = emit_ts - snapshot_ts, local arithmetic) [$snapa_line]"
printf '%s' "$out" | has_match -q 'git show 0123abc4567890def0123abc4567890def012345:BOARD.md' \ printf '%s' "$out" | grep -q 'git show 0123abc4567890def0123abc4567890def012345:BOARD.md' \
|| fail_msg "Q10: snapshot_sha+path must upgrade the actionable re-verify hint to a one-call git show" || 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. # The sibling without metadata must not grow empty snapshot_ fields.
snapb_line="$(printf '%s\n' "$out" | has_match 'id=SNAP-B' || true)" snapb_line="$(printf '%s\n' "$out" | grep 'id=SNAP-B' || true)"
printf '%s' "$snapb_line" | has_match -q 'snapshot_' \ printf '%s' "$snapb_line" | grep -q 'snapshot_' \
&& fail_msg "Q10: an entry without metadata must render NO snapshot_ fields [$snapb_line]" && fail_msg "Q10: an entry without metadata must render NO snapshot_ fields [$snapb_line]"
true true
) && ok ) && ok
@@ -441,28 +434,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" [ "$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 # (a) POSITIVE: the live entry RENDERS as an actionable claim (not merely
# passes the predicate) with the one-call git-show re-verify hint. # passes the predicate) with the one-call git-show re-verify hint.
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" | 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" | has_match -q 'git show 55d4909569d2b5fbccfec49ec9ca83db5049f3ce:docs/scratchpads/heartbeat-planning' \ printf '%s' "$out" | grep -q 'git show 55d4909569d2b5fbccfec49ec9ca83db5049f3ce:docs/scratchpads/heartbeat-planning' \
|| fail_msg "Q11a: the rendered claim must carry the one-call re-verify hint git show <snapshot_sha>:<path>" || 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. # (b) POSITIVE: path arm alone suffices; hint degrades to one-call re-read.
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" | 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" | has_match -q 're-read BOARD.md' || fail_msg "Q11b: the path-only claim must carry the one-call re-read <path> hint" 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" | count_lines 'CLAIM@seq' || true)" n_claims="$(printf '%s\n' "$out" | grep -c 'CLAIM@seq' || true)"
[ "$n_claims" = "2" ] || fail_msg "Q11: EXACTLY the two valid entries must render as CLAIM@seq (got $n_claims)" [ "$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. # (c)+(d) NEGATIVES retained: both quarantine, each with its OWN alarm.
printf '%s' "$out" | has_match -q 'ADDR-FREE' && fail_msg "Q11c: the address-free entry must be EXCLUDED from the digest" printf '%s' "$out" | grep -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)" 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)"
has_match -q 'ADDR-FREE' "$(dlq "$home")" 2>/dev/null || fail_msg "Q11c: the address-free entry must be DEAD-LETTERED" grep -q 'ADDR-FREE' "$(dlq "$home")" 2>/dev/null || fail_msg "Q11c: the address-free entry must be DEAD-LETTERED"
for snap_id in SNAP-ONLY-40 SNAP-ONLY-7 SNAP-ONLY-64; do for snap_id in SNAP-ONLY-40 SNAP-ONLY-7 SNAP-ONLY-64; do
has_match -q "$snap_id" "$(dlq "$home")" 2>/dev/null || fail_msg "Q11d: the bare snapshot_sha entry ($snap_id) must be DEAD-LETTERED" grep -q "$snap_id" "$(dlq "$home")" 2>/dev/null || fail_msg "Q11d: the bare snapshot_sha entry ($snap_id) must be DEAD-LETTERED"
done done
has_match -q 'heartbeat-planning' "$(dlq "$home")" 2>/dev/null && fail_msg "Q11a: the valid live entry must NOT be dead-lettered" grep -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" grep -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="$(grep -c . "$ALARM_OUT" 2>/dev/null || true)"
[ "$n_alarms" = "4" ] || fail_msg "Q11: exactly the four invalid entries must alarm (got $n_alarms) [$(cat "$ALARM_OUT" 2>/dev/null)]" [ "$n_alarms" = "4" ] || fail_msg "Q11: exactly the four invalid entries must alarm (got $n_alarms) [$(cat "$ALARM_OUT" 2>/dev/null)]"
has_match -q '"observed_seq":70' "$ALARM_OUT" 2>/dev/null || fail_msg "Q11c: seq 70's own alarm must be present" grep -q '"observed_seq":70' "$ALARM_OUT" 2>/dev/null || fail_msg "Q11c: seq 70's own alarm must be present"
for snap_seq in 71 72 73; do for snap_seq in 71 72 73; do
has_match -q "\"observed_seq\":$snap_seq" "$ALARM_OUT" 2>/dev/null || fail_msg "Q11d: seq $snap_seq's own alarm must be present" grep -q "\"observed_seq\":$snap_seq" "$ALARM_OUT" 2>/dev/null || fail_msg "Q11d: seq $snap_seq's own alarm must be present"
done done
true true
) && ok ) && ok
@@ -484,15 +477,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" [ "$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 — # 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... # a silent hold is how five successive digests each stepped past seq 68...
printf '%s' "$out" | has_match -q 'QUARANTINED' || fail_msg "Q12: the digest must carry a QUARANTINED disclosure section (no silent hold)" printf '%s' "$out" | grep -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" printf '%s' "$out" | grep -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; # ...but WITHOUT re-injecting the refused content: disclosure is by seq only;
# the Q1/Q4/Q5/Q6/Q9/Q11 exclusion property stands. # the Q1/Q4/Q5/Q6/Q9/Q11 exclusion property stands.
printf '%s' "$out" | has_match -q 'ADDR-Q12' && fail_msg "Q12: the quarantined entry's content/locators must stay EXCLUDED from the digest" printf '%s' "$out" | grep -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. # CLAMP: the embedded ack stops BELOW the quarantined seq, and says so loudly.
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" | grep -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" | 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" | has_match -q 'ACK CLAMPED' || fail_msg "Q12: the clamp must be LOUDLY disclosed in the ACK section" printf '%s' "$out" | grep -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. # 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)" [ -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 true
@@ -510,9 +503,9 @@ echo "== Q13 (#946): nothing quarantined -> ack UNCLAMPED at the observed cursor
out="$("$DIGEST" render --from-file "$f" --agent default 2>/dev/null)" out="$("$DIGEST" render --from-file "$f" --agent default 2>/dev/null)"
rc=$? rc=$?
[ "$rc" -eq 0 ] || fail_msg "Q13: render must exit 0, got rc=$rc" [ "$rc" -eq 0 ] || fail_msg "Q13: render must exit 0, got rc=$rc"
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" | grep -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" | grep -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" printf '%s' "$out" | grep -q 'ACK CLAMPED' && fail_msg "Q13: no clamp note when nothing is quarantined"
true true
) && ok ) && ok
@@ -527,8 +520,8 @@ echo "== Q14 (#946): store-mode render SYNCS quarantine truth into the store —
out="$("$DIGEST" render --from-store --agent default 2>/dev/null)" out="$("$DIGEST" render --from-store --agent default 2>/dev/null)"
rc=$? rc=$?
[ "$rc" -eq 0 ] || fail_msg "Q14: render must exit 0, got rc=$rc" [ "$rc" -eq 0 ] || fail_msg "Q14: render must exit 0, got rc=$rc"
printf '%s' "$out" | has_match -q 'QUARANTINED' || fail_msg "Q14: the store-mode digest must disclose the held entry" printf '%s' "$out" | grep -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)" printf '%s' "$out" | grep -Eq 'consumed --upto 1$' || fail_msg "Q14: the embedded ack must clamp to 1 (below quarantined seq 2)"
qf="$home/default/quarantined.set" 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)]" [ "$(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 # END-TO-END: even a hand-typed upto past the held seq is refused at the
@@ -552,8 +545,8 @@ echo "== Q15 (#946): gate-fix RECOVERY — a stale quarantined.set is REPLACED b
out="$("$DIGEST" render --from-store --agent default 2>/dev/null)" out="$("$DIGEST" render --from-store --agent default 2>/dev/null)"
rc=$? rc=$?
[ "$rc" -eq 0 ] || fail_msg "Q15: render must exit 0, got rc=$rc" [ "$rc" -eq 0 ] || fail_msg "Q15: render must exit 0, got rc=$rc"
printf '%s' "$out" | has_match -q 'QUARANTINED' && fail_msg "Q15: nothing quarantines under the fixed gate — no disclosure section" printf '%s' "$out" | grep -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" 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"
[ -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")]" [ -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" "$STORE" consume --upto 2 >/dev/null 2>&1 || fail_msg "Q15: the ordinary consume must succeed after the clamp self-heals"
) && ok ) && ok
@@ -564,7 +557,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 # Token concatenated so THIS guard's own source lines never contain the
# literal fixture id and cannot self-match. # literal fixture id and cannot self-match.
enum_id='ENUM''-B' enum_id='ENUM''-B'
fixture_line="$(has_match -F "\"id\":\"$enum_id\"" "$self" | has_match -F '"observed_seq":5' | head -n1)" fixture_line="$(grep -F "\"id\":\"$enum_id\"" "$self" | grep -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)" [ -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/")" fixture_json="$(printf '%s' "$fixture_line" | sed "s/.*'\({.*}\)'.*/\1/")"
# Positive controls FIRST (blind-instrument rule): the extraction must yield # Positive controls FIRST (blind-instrument rule): the extraction must yield
@@ -585,7 +578,7 @@ echo "== Q16 (guard): Q2's ENUM-B fixture must STAY address-free — the reconci
echo echo
if [ -s "$FAILFILE" ]; then if [ -s "$FAILFILE" ]; then
echo "wake digest-quarantine harness: FAILED ($(count_lines . "$FAILFILE") assertion(s))" >&2 echo "wake digest-quarantine harness: FAILED ($(grep -c . "$FAILFILE") assertion(s))" >&2
exit 1 exit 1
fi fi
echo "wake digest-quarantine harness: all invariants passed ($pass groups)" echo "wake digest-quarantine harness: all invariants passed ($pass groups)"
@@ -25,13 +25,6 @@
set -uo pipefail set -uo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 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
if ! . "$SCRIPT_DIR/_wake-common.sh"; then
echo "WAKE-ASSERT INIT ABORT: cannot source _wake-common.sh — suite ran ZERO wake assertions (#984)" >&2
exit 97
fi
wake_assert_init
ORACLE="$SCRIPT_DIR/fn-oracle.sh" ORACLE="$SCRIPT_DIR/fn-oracle.sh"
DET="$SCRIPT_DIR/detector.sh" DET="$SCRIPT_DIR/detector.sh"
@@ -69,8 +62,8 @@ echo "== O1: healthy pipeline -> synthetic-canary FN-rate = 0 =="
out="$("$ORACLE" run --slo-seconds 120 --count 3 2>&1)" out="$("$ORACLE" run --slo-seconds 120 --count 3 2>&1)"
rc=$? rc=$?
[ "$rc" -eq 0 ] || fail_msg "O1: a healthy pipeline must exit 0 (got $rc) [$out]" [ "$rc" -eq 0 ] || fail_msg "O1: a healthy pipeline must exit 0 (got $rc) [$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" | grep -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]" echo "$out" | grep -q 'VERDICT = PASS' || fail_msg "O1: verdict must be PASS on a healthy pipeline [$out]"
) && ok ) && ok
echo "== O2: DISABLED detector (perfect no-op) -> FN-DETECTED (blindspot killer) ==" echo "== O2: DISABLED detector (perfect no-op) -> FN-DETECTED (blindspot killer) =="
@@ -83,9 +76,9 @@ echo "== O2: DISABLED detector (perfect no-op) -> FN-DETECTED (blindspot killer)
out="$("$ORACLE" run --slo-seconds 120 --count 3 2>&1)" out="$("$ORACLE" run --slo-seconds 120 --count 3 2>&1)"
rc=$? 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" [ "$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" | 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" | 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" | has_match -q 'VERDICT = FN-DETECTED' || fail_msg "O2: verdict must be FN-DETECTED for a dropping detector [$out]" echo "$out" | grep -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]" echo "$out" | grep -qi 'never OBSERVED' || fail_msg "O2: the failure must name the dropped (never-observed) delta [$out]"
) && ok ) && ok
echo "== O3: reached CONSUMED but too slow -> FN-DETECTED (within-SLO clause) ==" echo "== O3: reached CONSUMED but too slow -> FN-DETECTED (within-SLO clause) =="
@@ -98,10 +91,10 @@ echo "== O3: reached CONSUMED but too slow -> FN-DETECTED (within-SLO clause) ==
out="$("$ORACLE" run --slo-seconds 1 --count 1 2>&1)" out="$("$ORACLE" run --slo-seconds 1 --count 1 2>&1)"
rc=$? rc=$?
[ "$rc" -ne 0 ] || fail_msg "O3: a canary that reaches CONSUMED past its SLO must be a false-negative (non-zero exit)" [ "$rc" -ne 0 ] || fail_msg "O3: a canary that reaches CONSUMED past its SLO must be a false-negative (non-zero exit)"
echo "$out" | has_match -qi 'per-class SLO' || fail_msg "O3: the failure must attribute to the per-class SLO, not a drop [$out]" echo "$out" | grep -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, # It must NOT be the 'never observed' branch: the delta WAS observed/delivered,
# just too slowly. This proves O3 exercises the SLO clause specifically. # just too slowly. This proves O3 exercises the SLO clause specifically.
if echo "$out" | has_match -qi 'never OBSERVED'; then if echo "$out" | grep -qi 'never OBSERVED'; then
fail_msg "O3: an SLO breach must NOT be misreported as a dropped delta [$out]" fail_msg "O3: an SLO breach must NOT be misreported as a dropped delta [$out]"
fi fi
) && ok ) && ok
@@ -117,7 +110,7 @@ echo "== O4: verdict is OFF-DOMAIN (from terminal consumed_seq, not detector sel
out="$("$ORACLE" run --slo-seconds 120 --count 1 2>&1)" out="$("$ORACLE" run --slo-seconds 120 --count 1 2>&1)"
rc=$? rc=$?
[ "$rc" -ne 0 ] || fail_msg "O4: a drive that exits 0 but delivers nothing must still be FN-DETECTED (verdict is off-domain)" [ "$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" | has_match -q 'VERDICT = FN-DETECTED' || fail_msg "O4: off-domain verdict must not be fooled by a clean exit code [$out]" echo "$out" | grep -q 'VERDICT = FN-DETECTED' || fail_msg "O4: off-domain verdict must not be fooled by a clean exit code [$out]"
) && ok ) && ok
echo "== O5: no invented SLO -> --slo-seconds is REQUIRED (fail-loud) ==" echo "== O5: no invented SLO -> --slo-seconds is REQUIRED (fail-loud) =="
@@ -128,12 +121,12 @@ echo "== O5: no invented SLO -> --slo-seconds is REQUIRED (fail-loud) =="
err="$("$ORACLE" run --count 1 2>&1)" err="$("$ORACLE" run --count 1 2>&1)"
rc=$? rc=$?
[ "$rc" -ne 0 ] || fail_msg "O5: run without an SLO must fail loud (no invented default)" [ "$rc" -ne 0 ] || fail_msg "O5: run without an SLO must fail loud (no invented default)"
echo "$err" | has_match -qi 'slo' || fail_msg "O5: the usage error must name the missing SLO [$err]" echo "$err" | grep -qi 'slo' || fail_msg "O5: the usage error must name the missing SLO [$err]"
) && ok ) && ok
echo echo
if [ -s "$FAILFILE" ]; then if [ -s "$FAILFILE" ]; then
echo "wake fn-oracle harness: FAILED ($(count_lines . "$FAILFILE") assertion(s))" >&2 echo "wake fn-oracle harness: FAILED ($(grep -c . "$FAILFILE") assertion(s))" >&2
exit 1 exit 1
fi fi
echo "wake fn-oracle harness: all invariants passed ($pass groups)" echo "wake fn-oracle harness: all invariants passed ($pass groups)"
@@ -34,13 +34,6 @@
set -uo pipefail set -uo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 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
if ! . "$SCRIPT_DIR/_wake-common.sh"; then
echo "WAKE-ASSERT INIT ABORT: cannot source _wake-common.sh — suite ran ZERO wake assertions (#984)" >&2
exit 97
fi
wake_assert_init
WI="$SCRIPT_DIR/wake-install.sh" WI="$SCRIPT_DIR/wake-install.sh"
BEACON="$SCRIPT_DIR/beacon.sh" BEACON="$SCRIPT_DIR/beacon.sh"
FRAMEWORK_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" FRAMEWORK_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
@@ -101,7 +94,7 @@ EOF
export WAKE_INSTALL_MANIFEST="$BAD_MANIFEST" export WAKE_INSTALL_MANIFEST="$BAD_MANIFEST"
out="$(bash "$WI" install 2>&1)"; rc=$? 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)" [ "$rc" -ne 0 ] || fail_msg "I2: install must FAIL when a wake candidate is not framework-owned (got rc=$rc)"
echo "$out" | has_match -qi 'Gate A VIOLATION' || fail_msg "I2: refusal must name the Gate A violation [$out]" echo "$out" | grep -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. # 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)" [ ! -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. # And the positive control: with the REAL manifest, the same candidates install.
@@ -128,7 +121,7 @@ EOF
|| fail_msg "I3: blank-reset drop-in write failed" || fail_msg "I3: blank-reset drop-in write failed"
out="$(bash "$WI" verify-single "$UNIT" 2>&1)"; rc=$? 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]" [ "$rc" -eq 0 ] || fail_msg "I3: blank-reset must resolve exactly one OnUnitActiveUSec (rc=$rc) [$out]"
echo "$out" | has_match -qi 'EXACTLY ONE' || fail_msg "I3: verify must confirm exactly-one [$out]" echo "$out" | grep -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. # NEGATIVE CONTROL: a drop-in WITHOUT the reset line appends -> TWO values -> fail.
cat >"$UD/$UNIT.d/interval.conf" <<'EOF' cat >"$UD/$UNIT.d/interval.conf" <<'EOF'
[Timer] [Timer]
@@ -148,7 +141,7 @@ echo "== I4: snapshot-guard — reap without a snapshot is REFUSED; allowed afte
# (a) reap WITHOUT snapshot -> refuse, unit still present. # (a) reap WITHOUT snapshot -> refuse, unit still present.
out="$(bash "$WI" reap-unit "$UNIT" 2>&1)"; rc=$? out="$(bash "$WI" reap-unit "$UNIT" 2>&1)"; rc=$?
[ "$rc" -ne 0 ] || fail_msg "I4: reap without a snapshot must be REFUSED (rc=$rc)" [ "$rc" -ne 0 ] || fail_msg "I4: reap without a snapshot must be REFUSED (rc=$rc)"
echo "$out" | has_match -qi 'snapshot-guard' || fail_msg "I4: refusal must name the snapshot-guard [$out]" echo "$out" | grep -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" [ -f "$UD/$UNIT" ] || fail_msg "I4: a refused reap must NOT delete the unit"
# (b) snapshot, then reap -> allowed, unit gone, snapshot retained. # (b) snapshot, then reap -> allowed, unit gone, snapshot retained.
bash "$WI" snapshot-unit "$UNIT" >/dev/null 2>&1 || fail_msg "I4: snapshot-unit failed" bash "$WI" snapshot-unit "$UNIT" >/dev/null 2>&1 || fail_msg "I4: snapshot-unit failed"
@@ -168,28 +161,28 @@ echo "== I5: fail-closed install-validate — unconfigured HMAC/alarm FAIL LOUD;
export WAKE_HMAC_KEY_NAME="primary" export WAKE_HMAC_KEY_NAME="primary"
out="$(bash "$WI" validate-hmac-key 2>&1)"; rc=$? 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)" [ "$rc" -ne 0 ] || fail_msg "I5: missing credential store must FAIL LOUD for the HMAC key (rc=$rc)"
echo "$out" | has_match -qi 'UNSIGNED' || fail_msg "I5: HMAC failure must warn about unsigned wakes [$out]" echo "$out" | grep -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. # 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" jq -cn --arg k "$SECRET_KEY" '{wake:{hmac_keys:{"primary":$k}}}' >"$CRED"
out2="$(bash "$WI" validate-hmac-key 2>&1)"; rc2=$? 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]" [ "$rc2" -eq 0 ] || fail_msg "I5: a by-name-resolvable HMAC key must pass (rc=$rc2) [$out2]"
echo "$out2" | has_match -qF "$SECRET_KEY" && fail_msg "I5: validate must NEVER echo the HMAC key material" echo "$out2" | grep -qF "$SECRET_KEY" && fail_msg "I5: validate must NEVER echo the HMAC key material"
# --- alarm target: unconfigured -> fail loud (silent no-alarm host). # --- alarm target: unconfigured -> fail loud (silent no-alarm host).
unset WAKE_ALARM_SINK_CMD unset WAKE_ALARM_SINK_CMD
out3="$(bash "$WI" validate-alarm-target 2>&1)"; rc3=$? out3="$(bash "$WI" validate-alarm-target 2>&1)"; rc3=$?
[ "$rc3" -ne 0 ] || fail_msg "I5: unconfigured alarm target must FAIL LOUD (rc=$rc3)" [ "$rc3" -ne 0 ] || fail_msg "I5: unconfigured alarm target must FAIL LOUD (rc=$rc3)"
echo "$out3" | has_match -qi 'silent no-alarm host' || fail_msg "I5: alarm failure must name the silent-no-alarm hazard [$out3]" echo "$out3" | grep -qi 'silent no-alarm host' || fail_msg "I5: alarm failure must name the silent-no-alarm hazard [$out3]"
# unreachable -> fail loud. # unreachable -> fail loud.
export WAKE_ALARM_SINK_CMD="false" export WAKE_ALARM_SINK_CMD="false"
out4="$(bash "$WI" validate-alarm-target 2>&1)"; rc4=$? out4="$(bash "$WI" validate-alarm-target 2>&1)"; rc4=$?
[ "$rc4" -ne 0 ] || fail_msg "I5: unreachable alarm sink must FAIL LOUD (rc=$rc4)" [ "$rc4" -ne 0 ] || fail_msg "I5: unreachable alarm sink must FAIL LOUD (rc=$rc4)"
echo "$out4" | has_match -qi 'UNREACHABLE' || fail_msg "I5: alarm failure must name the unreachable target [$out4]" echo "$out4" | grep -qi 'UNREACHABLE' || fail_msg "I5: alarm failure must name the unreachable target [$out4]"
# reachable -> pass. # reachable -> pass.
export WAKE_ALARM_SINK_CMD="cat >/dev/null" 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" 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. # The installer file must inline NO secret/endpoint.
has_match -qF "$SECRET_ENDPOINT" "$WI" && fail_msg "I5: wake-install.sh must NOT inline any alarm endpoint" grep -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" grep -qE 'hmac_keys[^A-Za-z_].*=[^=].*[A-Za-z0-9]{8,}' "$WI" && fail_msg "I5: wake-install.sh must NOT inline key material"
true true
) && ok ) && ok
@@ -211,7 +204,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]" [ "$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 "$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" [ -f "$SNAP/$TIMER" ] || fail_msg "I6: the legacy timer must be snapshotted before any retire"
echo "$out" | has_match -qi 'LEFT RUNNING' || fail_msg "I6: overlap must announce retire is withheld [$out]" echo "$out" | grep -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). # (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=$? 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]" [ "$rc2" -eq 0 ] || fail_msg "I6: vector-passed retire must succeed (rc=$rc2) [$out2]"
@@ -290,11 +283,11 @@ echo "== I9: dep-check — a host seed missing _lib/manifest.sh FAILS LOUD (name
TGT="$(fresh i9-target)" TGT="$(fresh i9-target)"
out="$(WAKE_INSTALL_SOURCE="$FAKE" WAKE_INSTALL_TARGET="$TGT" bash "$WI_FAKE" install 2>&1)"; rc=$? 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)" [ "$rc" -ne 0 ] || fail_msg "I9: install MUST fail when _lib/manifest.sh is missing (rc=$rc)"
echo "$out" | has_match -qi 'manifest.sh' || fail_msg "I9: the failure must NAME the missing library (manifest.sh) [$out]" echo "$out" | grep -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' \ echo "$out" | grep -qiE 'REMEDY|mosaic update|re-seed|framework install' \
|| fail_msg "I9: the failure must give an actionable REMEDY, not just an error [$out]" || 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`. # It must be a CLEAR fail-loud, NOT the obscure bare `source: No such file or directory`.
echo "$out" | has_match -qi 'No such file or directory' \ echo "$out" | grep -qi 'No such file or directory' \
&& fail_msg "I9: must not fail with a bare source error (obscure) [$out]" && fail_msg "I9: must not fail with a bare source error (obscure) [$out]"
# Positive control: with the helper present (real framework root) install succeeds. # Positive control: with the helper present (real framework root) install succeeds.
TGT_OK="$(fresh i9-target-ok)" TGT_OK="$(fresh i9-target-ok)"
@@ -320,10 +313,10 @@ echo "== I10: systemd search-path — install links mosaic-wake.service into the
rm -f "$UD/mosaic-wake.service" rm -f "$UD/mosaic-wake.service"
out2="$(bash "$WI" validate-systemd-path 2>&1)"; rc2=$? 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]" [ "$rc2" -ne 0 ] || fail_msg "I10: validate must FAIL when the unit is not in the search path (rc=$rc2) [$out2]"
echo "$out2" | has_match -qi 'search path' || fail_msg "I10: validate failure must name the search-path miss [$out2]" echo "$out2" | grep -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. # (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" bash "$WI" install >/dev/null 2>&1 || fail_msg "I10: re-run install must succeed"
n="$(find "$UD" -maxdepth 1 -name 'mosaic-wake.service' | count_lines .)" n="$(find "$UD" -maxdepth 1 -name 'mosaic-wake.service' | grep -c .)"
[ "$n" -eq 1 ] || fail_msg "I10: re-install must not duplicate the search-path entry (found $n)" [ "$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" bash "$WI" validate-systemd-path >/dev/null 2>&1 || fail_msg "I10: re-install must keep the unit resolvable"
) && ok ) && ok
@@ -341,12 +334,12 @@ echo "== I11: F7 — the legacy reap REFUSES unless the canon fallback wake is p
# it must REFUSE (schedulable floor not met). # it must REFUSE (schedulable floor not met).
out0="$(bash "$WI" fallback-proven-live 2>&1)"; rc0=$? 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)" [ "$rc0" -ne 0 ] || fail_msg "I11: fallback-proven-live must FAIL when the fallback wake is not installed (rc=$rc0)"
echo "$out0" | has_match -qi 'F7' || fail_msg "I11: the refusal must name the F7 precondition [$out0]" echo "$out0" | grep -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 # (b) NEGATIVE — vector-passed reap with NO live fallback must be REFUSED and the
# legacy timer LEFT RUNNING (never a coverage gap). # legacy timer LEFT RUNNING (never a coverage gap).
out="$(bash "$WI" reset-verify-retire f --interval 30min --vector-passed 2>&1)"; rc=$? 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)" [ "$rc" -ne 0 ] || fail_msg "I11: reap must be REFUSED without a proven-live fallback (rc=$rc)"
echo "$out" | has_match -qi 'FALLBACK WAKE is not proven live' || fail_msg "I11: refusal must name the un-proven fallback [$out]" echo "$out" | grep -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)" [ -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 # (c) POSITIVE — provision the fallback at the schedulable floor, then the reap
# proceeds (F7 satisfied) and the legacy timer is retired. # proceeds (F7 satisfied) and the legacy timer is retired.
@@ -374,11 +367,11 @@ echo "== I12: fallback drain — a STALLED detector still gets delivery via the
# even with no detector running — no starvation of the drain. # even with no detector running — no starvation of the drain.
out="$(bash "$DIGEST" render --from-store 2>/dev/null)"; rc=$? 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)" [ "$rc" -eq 0 ] || fail_msg "I12: the canon fallback drain must exit 0 (rc=$rc)"
echo "$out" | has_match -q 'STALLED-DRAIN-MARK' \ echo "$out" | grep -q 'STALLED-DRAIN-MARK' \
|| fail_msg "I12: the fallback drain must DELIVER the pending obligation a stalled detector left (no delivery starvation) [$out]" || 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. # Bind the invariant to the SHIPPED unit: its ExecStart must be this canon drain.
UNIT="$FRAMEWORK_ROOT/systemd/user/mosaic-wake-fallback.service" UNIT="$FRAMEWORK_ROOT/systemd/user/mosaic-wake-fallback.service"
has_match -qE '^ExecStart=.*digest\.sh render --from-store' "$UNIT" \ grep -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)" || fail_msg "I12: mosaic-wake-fallback.service ExecStart must fire the canon drain (digest.sh render --from-store)"
) && ok ) && ok
@@ -398,11 +391,11 @@ echo "== I13: install links + validates the canon fallback timer+service into th
rm -f "$UD/mosaic-wake-fallback.timer" rm -f "$UD/mosaic-wake-fallback.timer"
out="$(bash "$WI" validate-fallback-units 2>&1)"; rc=$? 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]" [ "$rc" -ne 0 ] || fail_msg "I13: validate must FAIL when a fallback unit is not in the search path (rc=$rc) [$out]"
echo "$out" | has_match -qi 'search path' || fail_msg "I13: validate failure must name the search-path miss [$out]" echo "$out" | grep -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. # (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" 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' | count_lines .)" nt="$(find "$UD" -maxdepth 1 -name 'mosaic-wake-fallback.timer' | grep -c .)"
ns="$(find "$UD" -maxdepth 1 -name 'mosaic-wake-fallback.service' | count_lines .)" ns="$(find "$UD" -maxdepth 1 -name 'mosaic-wake-fallback.service' | grep -c .)"
[ "$nt" -eq 1 ] && [ "$ns" -eq 1 ] || fail_msg "I13: re-install must not duplicate the fallback entries (timer=$nt service=$ns)" [ "$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" bash "$WI" validate-fallback-units >/dev/null 2>&1 || fail_msg "I13: re-install must keep the fallback units resolvable"
) && ok ) && ok
@@ -414,17 +407,17 @@ echo "== I14: per-class fallback cadence — blank-reset yields EXACTLY ONE OnUn
TIMER="mosaic-wake-fallback.timer" TIMER="mosaic-wake-fallback.timer"
# The shipped timer carries the base OnUnitActiveSec placeholder (=1h). # The shipped timer carries the base OnUnitActiveSec placeholder (=1h).
cp "$FRAMEWORK_ROOT/systemd/user/$TIMER" "$UD/$TIMER" cp "$FRAMEWORK_ROOT/systemd/user/$TIMER" "$UD/$TIMER"
has_match -q '^OnUnitActiveSec=1h' "$UD/$TIMER" || fail_msg "I14: shipped fallback timer must carry a base OnUnitActiveSec placeholder" grep -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. # write-fallback-cadence writes the per-class cadence in BLANK-RESET form.
out="$(bash "$WI" write-fallback-cadence 30min 2>&1)"; rc=$? 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]" [ "$rc" -eq 0 ] || fail_msg "I14: write-fallback-cadence must succeed + verify single (rc=$rc) [$out]"
echo "$out" | has_match -qi 'exactly one' || fail_msg "I14: the cadence write must confirm exactly-one OnUnitActiveUSec [$out]" echo "$out" | grep -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/" [ -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 # Assert the blank-reset FORM: an empty `OnUnitActiveSec=` reset line IMMEDIATELY
# FOLLOWED by the new value. Portable across GNU and BusyBox grep (Alpine CI) — # 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 # `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. # match the reset line and require the value on the very next line (-A1) instead.
has_match -A1 '^OnUnitActiveSec=$' "$UD/$TIMER.d/cadence.conf" | has_match -qx 'OnUnitActiveSec=30min' \ grep -A1 '^OnUnitActiveSec=$' "$UD/$TIMER.d/cadence.conf" | grep -qx 'OnUnitActiveSec=30min' \
|| fail_msg "I14: the drop-in must use the blank-reset form (empty reset line, then the value)" || 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" 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. # NEGATIVE CONTROL: a drop-in WITHOUT the reset line appends -> base 1h + 30min -> two -> fail.
@@ -435,7 +428,7 @@ echo "== I14: per-class fallback cadence — blank-reset yields EXACTLY ONE OnUn
echo echo
if [ -s "$FAILFILE" ]; then if [ -s "$FAILFILE" ]; then
echo "wake install harness: FAILED ($(count_lines . "$FAILFILE") assertion(s))" >&2 echo "wake install harness: FAILED ($(grep -c . "$FAILFILE") assertion(s))" >&2
exit 1 exit 1
fi fi
echo "wake install harness: all invariants passed ($pass groups)" echo "wake install harness: all invariants passed ($pass groups)"
@@ -46,13 +46,6 @@
set -uo pipefail set -uo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 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
if ! . "$SCRIPT_DIR/_wake-common.sh"; then
echo "WAKE-ASSERT INIT ABORT: cannot source _wake-common.sh — suite ran ZERO wake assertions (#984)" >&2
exit 97
fi
wake_assert_init
PRE="$SCRIPT_DIR/preimage.sh" PRE="$SCRIPT_DIR/preimage.sh"
STORE="$SCRIPT_DIR/store.sh" STORE="$SCRIPT_DIR/store.sh"
DET="$SCRIPT_DIR/detector.sh" DET="$SCRIPT_DIR/detector.sh"
@@ -209,9 +202,9 @@ echo "== P4: credential-store PATH refused — bytes never captured (acceptance
sd="$(state_dir)" sd="$(state_dir)"
row="$(tail -n1 "$sd/preimage/preimage-ledger.jsonl")" row="$(tail -n1 "$sd/preimage/preimage-ledger.jsonl")"
[ "$(jq -r '.captured' <<<"$row")" = "false" ] || fail_msg "P4: row must record captured=false [$row]" [ "$(jq -r '.captured' <<<"$row")" = "false" ] || fail_msg "P4: row must record captured=false [$row]"
jq -r '.refused // ""' <<<"$row" | has_match -qi 'path' || fail_msg "P4: refusal must name the path deny [$row]" jq -r '.refused // ""' <<<"$row" | grep -qi 'path' || fail_msg "P4: refusal must name the path deny [$row]"
# THE invariant: the secret bytes exist NOWHERE under the wake state dir. # THE invariant: the secret bytes exist NOWHERE under the wake state dir.
if has_match -rq "$secret" "$sd" 2>/dev/null; then if grep -rq "$secret" "$sd" 2>/dev/null; then
fail_msg "P4: credential bytes leaked into the wake state dir" fail_msg "P4: credential bytes leaked into the wake state dir"
fi fi
) && ok ) && ok
@@ -232,8 +225,8 @@ echo "== P5: secret-SHAPED content refused (refusal, not redaction) =="
sd="$(state_dir)" sd="$(state_dir)"
row="$(tail -n1 "$sd/preimage/preimage-ledger.jsonl")" row="$(tail -n1 "$sd/preimage/preimage-ledger.jsonl")"
[ "$(jq -r '.captured' <<<"$row")" = "false" ] || fail_msg "P5: row must record captured=false [$row]" [ "$(jq -r '.captured' <<<"$row")" = "false" ] || fail_msg "P5: row must record captured=false [$row]"
jq -r '.refused // ""' <<<"$row" | has_match -qi 'secret' || fail_msg "P5: refusal must name secret-shaped content [$row]" jq -r '.refused // ""' <<<"$row" | grep -qi 'secret' || fail_msg "P5: refusal must name secret-shaped content [$row]"
if has_match -rq "$tok" "$sd" 2>/dev/null; then if grep -rq "$tok" "$sd" 2>/dev/null; then
fail_msg "P5: secret-shaped bytes leaked into the wake state dir" fail_msg "P5: secret-shaped bytes leaked into the wake state dir"
fi fi
) && ok ) && ok
@@ -282,7 +275,7 @@ echo "== P8: unresolvable adapter -> FAIL LOUD, never 'no change' (D2 class) =="
unset WAKE_WATCH_LIST WAKE_PREIMAGE_EXTRA MOSAIC_HOME unset WAKE_WATCH_LIST WAKE_PREIMAGE_EXTRA MOSAIC_HOME
out="$("$PRE" check --enqueue 2>&1)"; rc=$? out="$("$PRE" check --enqueue 2>&1)"; rc=$?
[ "$rc" -ne 0 ] || fail_msg "P8: an unobservable preimage definition must FAIL LOUD (rc=0)" [ "$rc" -ne 0 ] || fail_msg "P8: an unobservable preimage definition must FAIL LOUD (rc=0)"
echo "$out" | has_match -qi 'does not resolve' || fail_msg "P8: the failure must name the unresolvable adapter [$out]" echo "$out" | grep -qi 'does not resolve' || fail_msg "P8: the failure must name the unresolvable adapter [$out]"
) && ok ) && ok
echo "== P9: corrupt ledger -> REFUSE to compare/re-baseline ==" echo "== P9: corrupt ledger -> REFUSE to compare/re-baseline =="
@@ -301,7 +294,7 @@ echo "== P9: corrupt ledger -> REFUSE to compare/re-baseline =="
printf 'v2 changed\n' >"$fx/f.env" printf 'v2 changed\n' >"$fx/f.env"
out="$("$PRE" check --enqueue 2>&1)"; rc=$? out="$("$PRE" check --enqueue 2>&1)"; rc=$?
[ "$rc" -ne 0 ] || fail_msg "P9: a corrupt ledger must FAIL LOUD (rc=0)" [ "$rc" -ne 0 ] || fail_msg "P9: a corrupt ledger must FAIL LOUD (rc=0)"
echo "$out" | has_match -qi 'unparseable' || fail_msg "P9: the failure must name the corrupt ledger [$out]" echo "$out" | grep -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" [ "$(wc -l <"$sd/preimage/preimage-ledger.jsonl")" = "$r1" ] || fail_msg "P9: nothing may be appended over corrupt history"
) && ok ) && ok
@@ -321,7 +314,7 @@ echo "== P10: oversized file -> bytes refused, hash still recorded =="
sd="$(state_dir)" sd="$(state_dir)"
row="$(tail -n1 "$sd/preimage/preimage-ledger.jsonl")" row="$(tail -n1 "$sd/preimage/preimage-ledger.jsonl")"
[ "$(jq -r '.captured' <<<"$row")" = "false" ] || fail_msg "P10: row must record captured=false [$row]" [ "$(jq -r '.captured' <<<"$row")" = "false" ] || fail_msg "P10: row must record captured=false [$row]"
jq -r '.refused // ""' <<<"$row" | has_match -qi 'MAX_BYTES' || fail_msg "P10: refusal must name the size cap [$row]" jq -r '.refused // ""' <<<"$row" | grep -qi 'MAX_BYTES' || fail_msg "P10: refusal must name the size cap [$row]"
sha="$(jq -r '.sha256' <<<"$row")" sha="$(jq -r '.sha256' <<<"$row")"
[ "$sha" = "$(sha256sum "$fx/big.bin" | awk '{print $1}')" ] || fail_msg "P10: hash must still be recorded [$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" [ ! -f "$sd/preimage/objects/$sha" ] || fail_msg "P10: oversized bytes must NOT be stored"
@@ -369,7 +362,7 @@ echo "== P12: detector — preimage infra failure is LOUD but does not starve ob
printf 'NOT-JSON-GARBAGE{{{\n' >"$sd/preimage/preimage-ledger.jsonl" printf 'NOT-JSON-GARBAGE{{{\n' >"$sd/preimage/preimage-ledger.jsonl"
out="$("$DET" poll-once 2>&1)"; rc=$? out="$("$DET" poll-once 2>&1)"; rc=$?
[ "$rc" -ne 0 ] || fail_msg "P12: the pass must exit non-zero on preimage infra failure" [ "$rc" -ne 0 ] || fail_msg "P12: the pass must exit non-zero on preimage infra failure"
echo "$out" | has_match -qi 'preimage' || fail_msg "P12: the failure must name the preimage check [$out]" echo "$out" | grep -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="$(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)" [ "$n" -ge 1 ] || fail_msg "P12: source observation must still proceed (no watch hash baselined)"
) && ok ) && ok
@@ -404,10 +397,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="$(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)" [ "$n" = "2" ] || fail_msg "P13: both decoys must record captured=false (got $n)"
# THE invariant (decoy method): the planted bytes exist NOWHERE in state. # THE invariant (decoy method): the planted bytes exist NOWHERE in state.
if has_match -rq "$s1" "$sd" 2>/dev/null; then if grep -rq "$s1" "$sd" 2>/dev/null; then
fail_msg "P13: renamed-target store bytes leaked into the wake state dir" fail_msg "P13: renamed-target store bytes leaked into the wake state dir"
fi fi
if has_match -rq "$s2" "$sd" 2>/dev/null; then if grep -rq "$s2" "$sd" 2>/dev/null; then
fail_msg "P13: prefix-less key bytes leaked into the wake state dir" fail_msg "P13: prefix-less key bytes leaked into the wake state dir"
fi fi
) && ok ) && ok
@@ -430,7 +423,7 @@ echo "== P14: detector.env-class key — safe WITHOUT opt-in by polarity, refuse
sd="$(state_dir)" sd="$(state_dir)"
row="$(tail -n1 "$sd/preimage/preimage-ledger.jsonl")" 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]" [ "$(jq -r '.captured' <<<"$row")" = "false" ] || fail_msg "P14a: extra must be record-only without opt-in [$row]"
if has_match -rq "$hm" "$sd" 2>/dev/null; then if grep -rq "$hm" "$sd" 2>/dev/null; then
fail_msg "P14a: key bytes leaked without any opt-in" fail_msg "P14a: key bytes leaked without any opt-in"
fi fi
# (b) The operator opts the env file in (the exact error the old usage text # (b) The operator opts the env file in (the exact error the old usage text
@@ -441,8 +434,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]" [ "$rc" -eq 0 ] || fail_msg "P14b: refusal is not an infra failure (rc=$rc) [$out]"
row="$(tail -n1 "$sd/preimage/preimage-ledger.jsonl")" 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 '.captured' <<<"$row")" = "false" ] || fail_msg "P14b: opted-in key material must still refuse [$row]"
jq -r '.refused // ""' <<<"$row" | has_match -qi 'secret' || fail_msg "P14b: refusal must name secret-shaped content [$row]" jq -r '.refused // ""' <<<"$row" | grep -qi 'secret' || fail_msg "P14b: refusal must name secret-shaped content [$row]"
if has_match -rq "$hm" "$sd" 2>/dev/null; then if grep -rq "$hm" "$sd" 2>/dev/null; then
fail_msg "P14b: key bytes leaked despite refusal" fail_msg "P14b: key bytes leaked despite refusal"
fi fi
) && ok ) && ok
@@ -495,7 +488,7 @@ echo "== P16: deleted ledger over surviving objects/ — REFUSE to re-baseline (
printf '# v3\n' >>"$fx/adapter.sh" printf '# v3\n' >>"$fx/adapter.sh"
out="$("$PRE" check --enqueue 2>&1)"; rc=$? out="$("$PRE" check --enqueue 2>&1)"; rc=$?
[ "$rc" -ne 0 ] || fail_msg "P16: absent ledger over prior objects must FAIL LOUD (rc=0) [$out]" [ "$rc" -ne 0 ] || fail_msg "P16: absent ledger over prior objects must FAIL LOUD (rc=0) [$out]"
echo "$out" | has_match -qi 'REFUSING to re-baseline' || fail_msg "P16: the failure must name the refusal [$out]" echo "$out" | grep -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" [ ! -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" [ "$(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))" [ "$(depth)" = "0" ] || fail_msg "P16: the v2->v3 change must NOT be absorbed or enqueued from an unverifiable baseline (depth=$(depth))"
@@ -527,7 +520,7 @@ echo "== P17: allowlist symmetry — symlink to a DENIED target refuses; symlink
sd="$(state_dir)" sd="$(state_dir)"
crow="$(jq -c --arg p "$(realpath "$fx/settings.json")" 'select(.path == $p)' "$sd/preimage/preimage-ledger.jsonl" | tail -n1)" 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]" [ "$(jq -r '.captured' <<<"$crow")" = "false" ] || fail_msg "P17: allowlisted symlink to a denied target must refuse [$crow]"
if has_match -rq "$s" "$sd" 2>/dev/null; then if grep -rq "$s" "$sd" 2>/dev/null; then
fail_msg "P17: credential bytes leaked via an allowlisted alias" fail_msg "P17: credential bytes leaked via an allowlisted alias"
fi fi
brow="$(jq -c --arg p "$(realpath "$fx/alias.cfg")" 'select(.path == $p)' "$sd/preimage/preimage-ledger.jsonl" | tail -n1)" brow="$(jq -c --arg p "$(realpath "$fx/alias.cfg")" 'select(.path == $p)' "$sd/preimage/preimage-ledger.jsonl" | tail -n1)"
@@ -29,13 +29,6 @@
set -uo pipefail set -uo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 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
if ! . "$SCRIPT_DIR/_wake-common.sh"; then
echo "WAKE-ASSERT INIT ABORT: cannot source _wake-common.sh — suite ran ZERO wake assertions (#984)" >&2
exit 97
fi
wake_assert_init
RECON="$SCRIPT_DIR/reconcile.sh" RECON="$SCRIPT_DIR/reconcile.sh"
STORE="$SCRIPT_DIR/store.sh" STORE="$SCRIPT_DIR/store.sh"
DET="$SCRIPT_DIR/detector.sh" DET="$SCRIPT_DIR/detector.sh"
@@ -94,7 +87,7 @@ EOF
out="$("$RECON" inventory 2>&1)" out="$("$RECON" inventory 2>&1)"
rc=$? rc=$?
[ "$rc" -eq 0 ] || fail_msg "R1: a complete inventory must PASS (exit 0) [$out]" [ "$rc" -eq 0 ] || fail_msg "R1: a complete inventory must PASS (exit 0) [$out]"
echo "$out" | has_match -qi 'COMPLETE' || fail_msg "R1: a complete inventory must report COMPLETE [$out]" echo "$out" | grep -qi 'COMPLETE' || fail_msg "R1: a complete inventory must report COMPLETE [$out]"
) && ok ) && ok
echo "== R2: OMITTED source -> FLAG (vacuous-pass prevented, not silently green) ==" echo "== R2: OMITTED source -> FLAG (vacuous-pass prevented, not silently green) =="
@@ -113,8 +106,8 @@ EOF
out="$("$RECON" inventory 2>&1)" out="$("$RECON" inventory 2>&1)"
rc=$? rc=$?
[ "$rc" -ne 0 ] || fail_msg "R2: an omitted (declared-but-unwatched) source must FLAG (non-zero), not silently pass" [ "$rc" -ne 0 ] || fail_msg "R2: an omitted (declared-but-unwatched) source must FLAG (non-zero), not silently pass"
echo "$out" | has_match -qi 'r2' || fail_msg "R2: the flag must name the omitted source r2 [$out]" echo "$out" | grep -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]" echo "$out" | grep -qi 'vacuous' || fail_msg "R2: the flag must state the vacuous-pass is prevented [$out]"
) && ok ) && ok
echo "== R3: required_sources omission -> FLAG ==" echo "== R3: required_sources omission -> FLAG =="
@@ -133,7 +126,7 @@ EOF
out="$("$RECON" inventory 2>&1)" out="$("$RECON" inventory 2>&1)"
rc=$? rc=$?
[ "$rc" -ne 0 ] || fail_msg "R3: a required_sources omission must FLAG (non-zero)" [ "$rc" -ne 0 ] || fail_msg "R3: a required_sources omission must FLAG (non-zero)"
echo "$out" | has_match -qi "requires source 'r2'" || fail_msg "R3: the flag must name the required-but-omitted source r2 [$out]" echo "$out" | grep -qi "requires source 'r2'" || fail_msg "R3: the flag must name the required-but-omitted source r2 [$out]"
) && ok ) && ok
echo "== R4: dangling watch reference -> FLAG ==" echo "== R4: dangling watch reference -> FLAG =="
@@ -151,7 +144,7 @@ EOF
out="$("$RECON" inventory 2>&1)" out="$("$RECON" inventory 2>&1)"
rc=$? rc=$?
[ "$rc" -ne 0 ] || fail_msg "R4: a dangling reference must FLAG (non-zero)" [ "$rc" -ne 0 ] || fail_msg "R4: a dangling reference must FLAG (non-zero)"
echo "$out" | has_match -qi 'dangling' || fail_msg "R4: the flag must state it is dangling [$out]" echo "$out" | grep -qi 'dangling' || fail_msg "R4: the flag must state it is dangling [$out]"
) && ok ) && ok
echo "== R5/R6: pre-existing state -> UNACCOUNTED flag + enumerated into store; re-run 0 ==" echo "== R5/R6: pre-existing state -> UNACCOUNTED flag + enumerated into store; re-run 0 =="
@@ -180,14 +173,14 @@ EOF
out="$("$RECON" reconcile 2>&1)" out="$("$RECON" reconcile 2>&1)"
rc=$? rc=$?
[ "$rc" -ne 0 ] || fail_msg "R5: pre-existing unaccounted state must FLAG (non-zero) on first reconcile" [ "$rc" -ne 0 ] || fail_msg "R5: pre-existing unaccounted state must FLAG (non-zero) on first reconcile"
echo "$out" | has_match -qi 'UNACCOUNTED=2' || fail_msg "R5: both pre-existing sources must be UNACCOUNTED [$out]" echo "$out" | grep -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. # 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)" [ "$(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. # A follow-up reconcile now finds everything accounted -> 0 unaccounted.
out2="$("$RECON" reconcile 2>&1)" out2="$("$RECON" reconcile 2>&1)"
rc2=$? rc2=$?
[ "$rc2" -eq 0 ] || fail_msg "R6: a second reconcile must report 0 unaccounted (exit 0) [$out2]" [ "$rc2" -eq 0 ] || fail_msg "R6: a second reconcile must report 0 unaccounted (exit 0) [$out2]"
echo "$out2" | has_match -qi 'UNACCOUNTED=0' || fail_msg "R6: second reconcile must be 0 unaccounted [$out2]" echo "$out2" | grep -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)" [ "$(depth)" = "2" ] || fail_msg "R6: a clean reconcile must NOT re-enumerate (depth still 2), got $(depth)"
) && ok ) && ok
@@ -218,7 +211,7 @@ EOF
out="$("$RECON" reconcile 2>&1)" out="$("$RECON" reconcile 2>&1)"
rc=$? rc=$?
[ "$rc" -eq 0 ] || fail_msg "R7: state reflected in the inbox must be ACCOUNTED (exit 0) [$out]" [ "$rc" -eq 0 ] || fail_msg "R7: state reflected in the inbox must be ACCOUNTED (exit 0) [$out]"
echo "$out" | has_match -qi 'UNACCOUNTED=0' || fail_msg "R7: inbox-reflected state must be 0 unaccounted [$out]" echo "$out" | grep -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)" [ "$(depth)" = "1" ] || fail_msg "R7: reconciler must NOT double-enumerate inbox-reflected state (depth still 1), got $(depth)"
) && ok ) && ok
@@ -243,7 +236,7 @@ EOF
out="$("$RECON" reconcile 2>&1)" out="$("$RECON" reconcile 2>&1)"
rc=$? rc=$?
[ "$rc" -ne 0 ] || fail_msg "R8: a source error must FAIL LOUD (non-zero exit)" [ "$rc" -ne 0 ] || fail_msg "R8: a source error must FAIL LOUD (non-zero exit)"
echo "$out" | has_match -qi 'FAIL LOUD' || fail_msg "R8: the source error must be loud [$out]" echo "$out" | grep -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)" [ "$(depth)" = "0" ] || fail_msg "R8: a failed observation must NOT enumerate anything, got depth $(depth)"
) && ok ) && ok
@@ -285,7 +278,7 @@ EOF
out="$("$RECON" reconcile 2>&1)" out="$("$RECON" reconcile 2>&1)"
rc=$? rc=$?
[ "$rc" -ne 0 ] || fail_msg "R9: pre-existing beta is unaccounted on this pass -> must FLAG (non-zero) [$out]" [ "$rc" -ne 0 ] || fail_msg "R9: pre-existing beta is unaccounted on this pass -> must FLAG (non-zero) [$out]"
echo "$out" | has_match -qi 'UNACCOUNTED=1' || fail_msg "R9: only beta should be unaccounted (alpha is inbox-accounted) [$out]" echo "$out" | grep -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)" [ "$(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 # A further detector delta on beta must allocate 3 — the unified allocator
@@ -299,8 +292,8 @@ EOF
# already used, so this set would contain a duplicate (alias). # already used, so this set would contain a duplicate (alias).
seqs="$("$STORE" drain | jq -r '.observed_seq' | sort -n | tr '\n' ' ')" 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)" [ "$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 | count_lines .)" ndistinct="$("$STORE" drain | jq -r '.observed_seq' | sort -nu | grep -c .)"
ntotal="$("$STORE" drain | jq -r '.observed_seq' | count_lines .)" ntotal="$("$STORE" drain | jq -r '.observed_seq' | grep -c .)"
[ "$ndistinct" = "$ntotal" ] || fail_msg "R9: aliasing detected — $ntotal entries but only $ndistinct distinct observed_seq" [ "$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. # 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" "$STORE" consume --upto 3 >/dev/null 2>&1 || fail_msg "R9: CONSUMED 3 must succeed over the gapless co-fed prefix"
@@ -340,7 +333,7 @@ EOF
out="$("$RECON" reconcile 2>&1)" out="$("$RECON" reconcile 2>&1)"
rc=$? rc=$?
[ "$rc" -eq 0 ] || fail_msg "R10: a consumed state must be ACCOUNTED (exit 0, no spurious rc=1 CRITICAL) [$out]" [ "$rc" -eq 0 ] || fail_msg "R10: a consumed state must be ACCOUNTED (exit 0, no spurious rc=1 CRITICAL) [$out]"
echo "$out" | has_match -qi 'UNACCOUNTED=0' || fail_msg "R10: a consumed state must be 0 unaccounted (no re-enumeration) [$out]" echo "$out" | grep -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)" [ "$(depth)" = "0" ] || fail_msg "R10: reconciler must NOT re-enumerate a consumed state (depth still 0 = no duplicate wake), got $(depth)"
) && ok ) && ok
@@ -379,7 +372,7 @@ EOF
[ "$rc" -ne 0 ] || fail_msg "R11: a genuine gap (r2) must still FLAG (non-zero) [$out]" [ "$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 # 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. # BASE both r1 and r2 re-enumerate (UNACCOUNTED=2) -> this assertion is red-first.
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]" 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]"
# Prove precisely WHICH state re-enumerated: the gap r2 IS enumerated, the # 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 # 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.) # red-first; the r2-present assertion holds both before and after = no over-suppression.)
@@ -390,7 +383,7 @@ EOF
echo echo
if [ -s "$FAILFILE" ]; then if [ -s "$FAILFILE" ]; then
echo "wake reconcile harness: FAILED ($(count_lines . "$FAILFILE") assertion(s))" >&2 echo "wake reconcile harness: FAILED ($(grep -c . "$FAILFILE") assertion(s))" >&2
exit 1 exit 1
fi fi
echo "wake reconcile harness: all invariants passed ($pass groups)" echo "wake reconcile harness: all invariants passed ($pass groups)"
@@ -42,13 +42,6 @@
set -uo pipefail set -uo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 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
if ! . "$SCRIPT_DIR/_wake-common.sh"; then
echo "WAKE-ASSERT INIT ABORT: cannot source _wake-common.sh — suite ran ZERO wake assertions (#984)" >&2
exit 97
fi
wake_assert_init
STORE="$SCRIPT_DIR/store.sh" STORE="$SCRIPT_DIR/store.sh"
ACK="$SCRIPT_DIR/ack.sh" ACK="$SCRIPT_DIR/ack.sh"
@@ -103,14 +96,14 @@ echo "== T1: three-cursor advancement =="
"$STORE" enqueue --seq 1 --class actionable --locators '{"repo":"r","issue":1}' >/dev/null "$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 "$STORE" enqueue --seq 2 --class actionable --locators '{"repo":"r","issue":2}' >/dev/null
cur="$("$STORE" cursors)" cur="$("$STORE" cursors)"
echo "$cur" | has_match -q 'observed_seq=2' || fail_msg "T1: observed_seq should be 2 after enqueue 1,2 [$cur]" echo "$cur" | grep -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" | grep -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]" echo "$cur" | grep -q 'pending_depth=2' || fail_msg "T1: pending depth should be 2 [$cur]"
# consumed_seq advances only on CONSUMED. # consumed_seq advances only on CONSUMED.
"$STORE" consume --upto 2 >/dev/null "$STORE" consume --upto 2 >/dev/null
cur="$("$STORE" cursors)" cur="$("$STORE" cursors)"
echo "$cur" | has_match -q 'consumed_seq=2' || fail_msg "T1: consumed_seq should be 2 after CONSUMED 2 [$cur]" echo "$cur" | grep -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]" echo "$cur" | grep -q 'pending_depth=0' || fail_msg "T1: pending drained after CONSUMED [$cur]"
) && ok ) && ok
echo "== T2: digest coalesce-REPLACE vs actionable APPEND ==" echo "== T2: digest coalesce-REPLACE vs actionable APPEND =="
@@ -123,16 +116,16 @@ echo "== T2: digest coalesce-REPLACE vs actionable APPEND =="
"$STORE" enqueue --seq 3 --class digest --locators '{"head":"bbb"}' >/dev/null "$STORE" enqueue --seq 3 --class digest --locators '{"head":"bbb"}' >/dev/null
"$STORE" enqueue --seq 4 --class human --locators '{"from":"peer"}' >/dev/null "$STORE" enqueue --seq 4 --class human --locators '{"from":"peer"}' >/dev/null
out="$("$STORE" drain)" out="$("$STORE" drain)"
ndigest="$(printf '%s\n' "$out" | jq -c 'select(.class=="digest")' | count_lines . || true)" ndigest="$(printf '%s\n' "$out" | jq -c 'select(.class=="digest")' | grep -c . || true)"
[ "$ndigest" = "1" ] || fail_msg "T2: digest must COALESCE to a single pending entry, got $ndigest" [ "$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="$(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)" [ "$head" = "bbb" ] || fail_msg "T2: newest digest must REPLACE prior (expected bbb, got $head)"
nactionable="$(printf '%s\n' "$out" | jq -c 'select(.class=="actionable")' | count_lines . || true)" nactionable="$(printf '%s\n' "$out" | jq -c 'select(.class=="actionable")' | grep -c . || true)"
nhuman="$(printf '%s\n' "$out" | jq -c 'select(.class=="human")' | count_lines . || true)" nhuman="$(printf '%s\n' "$out" | jq -c 'select(.class=="human")' | grep -c . || true)"
[ "$nactionable" = "1" ] || fail_msg "T2: actionable must APPEND (never replaced), got $nactionable" [ "$nactionable" = "1" ] || fail_msg "T2: actionable must APPEND (never replaced), got $nactionable"
[ "$nhuman" = "1" ] || fail_msg "T2: human must APPEND / stay durable, got $nhuman" [ "$nhuman" = "1" ] || fail_msg "T2: human must APPEND / stay durable, got $nhuman"
# Durability never bypassed: all classes present in the durable store. # Durability never bypassed: all classes present in the durable store.
total="$(printf '%s\n' "$out" | count_lines . || true)" total="$(printf '%s\n' "$out" | grep -c . || true)"
[ "$total" = "3" ] || fail_msg "T2: durable store should hold digest+actionable+human = 3, got $total" [ "$total" = "3" ] || fail_msg "T2: durable store should hold digest+actionable+human = 3, got $total"
) && ok ) && ok
@@ -148,7 +141,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)" fail_msg "T3: CONSUMED 3 must be REJECTED while seq 2 is a gap (unconsumed)"
fi fi
cur="$("$STORE" cursors)" cur="$("$STORE" cursors)"
echo "$cur" | has_match -q 'consumed_seq=0' || fail_msg "T3: rejected CONSUMED must NOT advance the cursor [$cur]" echo "$cur" | grep -q 'consumed_seq=0' || fail_msg "T3: rejected CONSUMED must NOT advance the cursor [$cur]"
# Also reject via the ack wrapper. # Also reject via the ack wrapper.
if "$ACK" consumed --upto 3 --no-sync >/dev/null 2>&1; then 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" fail_msg "T3: ack.sh CONSUMED 3 must be REJECTED over a gap"
@@ -157,11 +150,11 @@ echo "== T3: contiguous-prefix CONSUMED (reject ack N while N-1 unconsumed) =="
"$STORE" enqueue --seq 2 --class actionable --locators '{}' >/dev/null "$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" "$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)" cur="$("$STORE" cursors)"
echo "$cur" | has_match -q 'consumed_seq=3' || fail_msg "T3: consumed_seq should be 3 after contiguous prefix filled [$cur]" echo "$cur" | grep -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. # 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" "$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)" cur="$("$STORE" cursors)"
echo "$cur" | has_match -q 'consumed_seq=3' || fail_msg "T3: cursor must not regress below 3 [$cur]" echo "$cur" | grep -q 'consumed_seq=3' || fail_msg "T3: cursor must not regress below 3 [$cur]"
) && ok ) && ok
echo "== T4: wake_id DELIVERY-dedup (dup delivery = re-RECEIVE, never re-action) ==" echo "== T4: wake_id DELIVERY-dedup (dup delivery = re-RECEIVE, never re-action) =="
@@ -176,7 +169,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'" [ "$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). # RECEIVED (delivery) must NEVER advance consumed_seq (delivery != consumption).
cur="$("$STORE" cursors)" cur="$("$STORE" cursors)"
echo "$cur" | has_match -q 'consumed_seq=0' || fail_msg "T4: RECEIVED must not advance consumed_seq [$cur]" echo "$cur" | grep -q 'consumed_seq=0' || fail_msg "T4: RECEIVED must not advance consumed_seq [$cur]"
) && ok ) && ok
echo "== T5: atomic write-tmp+rename survives crash mid-write ==" echo "== T5: atomic write-tmp+rename survives crash mid-write =="
@@ -196,7 +189,7 @@ echo "== T5: atomic write-tmp+rename survives crash mid-write =="
drained="$("$STORE" drain)" 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" | 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" | stream_any '.observed_seq==1' || fail_msg "T5: committed entry (seq 1) lost after simulated crash"
printf '%s\n' "$drained" | has_match -q 999 && fail_msg "T5: uncommitted garbage (seq 999) leaked into the live store" printf '%s\n' "$drained" | grep -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. # 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" "$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 # #927: the enqueue HOT PATH must NOT reap tmp files — an unconditional delete
@@ -205,7 +198,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. # 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)" [ -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)" d2="$("$STORE" drain)"
[ "$(printf '%s\n' "$d2" | has_match -c .)" = "2" ] || fail_msg "T5: store not healthy after crash+recovery (expected 2 entries)" [ "$(printf '%s\n' "$d2" | grep -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 / # Bounded accumulation is preserved via a MAINTENANCE reap (store.sh init /
# detector tick), age-scoped so it only removes DEMONSTRABLY-orphaned tmps # 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 # (older than any plausible in-flight write) and never a live one. Age the
@@ -216,7 +209,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" "$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)" [ -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)" d3="$("$STORE" drain)"
[ "$(printf '%s\n' "$d3" | has_match -c .)" = "2" ] || fail_msg "T5: store not healthy after maintenance reap (expected 2 entries)" [ "$(printf '%s\n' "$d3" | grep -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" printf '%s\n' "$d3" | jq -e . >/dev/null 2>&1 || fail_msg "T5: drain returned non-JSON after maintenance reap"
) && ok ) && ok
@@ -261,12 +254,12 @@ echo "== T6: durability survives restart (retain until CONSUMED) =="
# "Session 2": a brand-new process (this subshell invocation of store.sh) must # "Session 2": a brand-new process (this subshell invocation of store.sh) must
# see the persisted entries — nothing was held in memory. # see the persisted entries — nothing was held in memory.
d="$("$STORE" drain)" d="$("$STORE" drain)"
[ "$(printf '%s\n' "$d" | has_match -c .)" = "2" ] || fail_msg "T6: entries not retained across restart (expected 2)" [ "$(printf '%s\n' "$d" | grep -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)" 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. # Retained UNTIL consumed: after CONSUMED they are released.
"$STORE" consume --upto 2 >/dev/null "$STORE" consume --upto 2 >/dev/null
d2="$("$STORE" drain)" d2="$("$STORE" drain)"
n2="$(printf '%s\n' "$d2" | count_lines . || true)" n2="$(printf '%s\n' "$d2" | grep -c . || true)"
[ "$n2" = "0" ] || fail_msg "T6: entries should be released only AFTER CONSUMED (still $n2 pending)" [ "$n2" = "0" ] || fail_msg "T6: entries should be released only AFTER CONSUMED (still $n2 pending)"
) && ok ) && ok
@@ -303,7 +296,7 @@ EOF
end="$(date +%s.%N)" end="$(date +%s.%N)"
elapsed_ms="$(awk -v s="$start" -v e="$end" 'BEGIN { printf "%d", (e - s) * 1000 }')" 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" [ "$elapsed_ms" -lt 1500 ] || fail_msg "T7: ack path blocked ${elapsed_ms}ms — must return without waiting on the background sync"
echo "$out" | has_match -q 'CONSUMED 1' || fail_msg "T7: CONSUMED not reported [$out]" echo "$out" | grep -q 'CONSUMED 1' || fail_msg "T7: CONSUMED not reported [$out]"
# Local write is durable IMMEDIATELY (no network needed to record the ack). # Local write is durable IMMEDIATELY (no network needed to record the ack).
STATE_DIR="$WAKE_STATE_HOME/default" STATE_DIR="$WAKE_STATE_HOME/default"
jq_any "$STATE_DIR/ack-ledger.jsonl" '.type=="CONSUMED" and .upto==1' || jq_any "$STATE_DIR/ack-ledger.jsonl" '.type=="CONSUMED" and .upto==1' ||
@@ -328,8 +321,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'" [ "$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'" [ "$s2" = "2" ] || fail_msg "T8: second store-allocated observed_seq must be 2 (contiguous), got '$s2'"
cur="$("$STORE" cursors)" cur="$("$STORE" cursors)"
echo "$cur" | has_match -q 'observed_seq=2' || fail_msg "T8: observed_seq cursor should be 2 [$cur]" echo "$cur" | grep -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]" echo "$cur" | grep -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 # 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). # consumed prefix must FAIL LOUD (never the old silent seq<=consumed no-op).
"$STORE" consume --upto 2 >/dev/null "$STORE" consume --upto 2 >/dev/null
@@ -338,7 +331,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" fail_msg "T8: explicit --seq 1 <= consumed_seq 2 must FAIL LOUD (anti-swallow), not be a silent no-op"
fi fi
err="$("$STORE" enqueue --seq 1 --class actionable --locators '{}' 2>&1 || true)" err="$("$STORE" enqueue --seq 1 --class actionable --locators '{}' 2>&1 || true)"
echo "$err" | has_match -qi 'anti-swallow' || fail_msg "T8: the refusal must name the anti-swallow guarantee [$err]" echo "$err" | grep -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')" 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)" [ "$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. # And the NEXT real allocation is > consumed (2) — never inside the prefix.
@@ -450,8 +443,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')" [ -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)" [ "$a" != "$b" ] || fail_msg "T10: two concurrent enqueues must get DISTINCT seqs, both got '$a' (aliasing / lost write without the lock)"
cur="$("$STORE" cursors)" cur="$("$STORE" cursors)"
echo "$cur" | has_match -q 'observed_seq=2' || fail_msg "T10: observed_seq must reach 2 after two enqueues [$cur]" echo "$cur" | grep -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]" echo "$cur" | grep -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). # The two allocated seqs are exactly {1,2} (distinct, contiguous, gapless).
lo="$a"; hi="$b"; [ "$a" -gt "$b" ] && { lo="$b"; hi="$a"; } 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}" { [ "$lo" = "1" ] && [ "$hi" = "2" ]; } || fail_msg "T10: concurrent seqs must be {1,2}, got {$lo,$hi}"
@@ -499,7 +492,7 @@ echo "== T11: final observed_seq cursor write FAILURE — fail loud + observed.s
# the _atomic_write failure and returns 0). # 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" [ "$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). # The loud diagnostic names the cursor write (not a generic error).
has_match -qi 'cursor' "$errfile" || fail_msg "T11: the failure diagnostic must name the observed_seq cursor write [$(cat "$errfile")]" grep -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. # (2) The cursor did NOT advance — the allocation is NOT committed.
obs_after="$("$STORE" cursors | sed -n 's/^observed_seq=//p')" obs_after="$("$STORE" cursors | sed -n 's/^observed_seq=//p')"
@@ -543,7 +536,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="$(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'" [ "$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. # ADDITIVE: existing on-disk state files are unchanged/consistent post-consume.
"$STORE" cursors | has_match -q 'consumed_seq=3' || fail_msg "T12: consumed_seq must be 3 after CONSUMED 3" "$STORE" cursors | grep -q 'consumed_seq=3' || fail_msg "T12: consumed_seq must be 3 after CONSUMED 3"
) && ok ) && ok
echo "== T13: #946 — ordinary consume REFUSES to advance past a quarantined seq (store + ack paths) ==" echo "== T13: #946 — ordinary consume REFUSES to advance past a quarantined seq (store + ack paths) =="
@@ -562,10 +555,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" fail_msg "T13: ordinary consume --upto 3 must be REFUSED while seq 2 is quarantined"
fi fi
err="$("$STORE" consume --upto 3 2>&1 >/dev/null || true)" err="$("$STORE" consume --upto 3 2>&1 >/dev/null || true)"
echo "$err" | has_match -q 'quarantined seq(s): 2' || fail_msg "T13: the refusal must NAME the quarantined seq [$err]" echo "$err" | grep -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]" echo "$err" | grep -q -- '--force-past-quarantine' || fail_msg "T13: the refusal must NAME the force flag [$err]"
cur="$("$STORE" cursors)" cur="$("$STORE" cursors)"
echo "$cur" | has_match -q 'consumed_seq=0' || fail_msg "T13: a refused consume must NOT advance the cursor [$cur]" echo "$cur" | grep -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. # 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" "$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. # The ack wrapper propagates the refusal — no ordinary-path bypass exists.
@@ -573,7 +566,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)" fail_msg "T13: ack.sh consumed --upto 3 must be REFUSED while seq 2 is quarantined (ordinary-path bypass)"
fi fi
cur="$("$STORE" cursors)" cur="$("$STORE" cursors)"
echo "$cur" | has_match -q 'consumed_seq=1' || fail_msg "T13: cursor must still be 1 after the refused ack [$cur]" echo "$cur" | grep -q 'consumed_seq=1' || fail_msg "T13: cursor must still be 1 after the refused ack [$cur]"
) && ok ) && ok
echo "== T14: #946 — FORCED step-over is LOUD, prunes the set, and NEVER fabricates a consumed-hash row for the quarantined entry ==" echo "== T14: #946 — FORCED step-over is LOUD, prunes the set, and NEVER fabricates a consumed-hash row for the quarantined entry =="
@@ -592,9 +585,9 @@ echo "== T14: #946 — FORCED step-over is LOUD, prunes the set, and NEVER fabri
rc=$? rc=$?
[ "$rc" -eq 0 ] || fail_msg "T14: forced consume must succeed (rc=$rc) [$(cat "$errf")]" [ "$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'" [ "$out" = "3" ] || fail_msg "T14: forced consume must print the new cursor 3, got '$out'"
has_match -q 'FORCED PAST QUARANTINE' "$errf" || fail_msg "T14: the forced path must be LOUD on stderr [$(cat "$errf")]" grep -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")]" grep -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" "$STORE" cursors | grep -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 # 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 # 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 # re-enumerating it once is safe-but-noisy; a false witness silences it
@@ -604,7 +597,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)" 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 # The stepped-over seq is PRUNED from the set (it is consumed now; a stale
# entry would re-refuse forever). # entry would re-refuse forever).
has_match -qxF '2' "$qf" 2>/dev/null && fail_msg "T14: seq 2 must be PRUNED from quarantined.set after the forced step-over" grep -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 # The ack wrapper's force flag passes through, stays LOUD on stderr, and
# still reports a CLEAN cursor line on stdout. # still reports a CLEAN cursor line on stdout.
"$STORE" enqueue --class actionable --locators '{"kind":"repo","id":"d","observed_hash":"HD"}' >/dev/null "$STORE" enqueue --class actionable --locators '{"kind":"repo","id":"d","observed_hash":"HD"}' >/dev/null
@@ -614,8 +607,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")" out2="$("$ACK" consumed --upto 5 --no-sync --force-past-quarantine 2>"$errf2")"
rc2=$? rc2=$?
[ "$rc2" -eq 0 ] || fail_msg "T14: forced ack must succeed (rc=$rc2) [$(cat "$errf2")]" [ "$rc2" -eq 0 ] || fail_msg "T14: forced ack must succeed (rc=$rc2) [$(cat "$errf2")]"
echo "$out2" | has_match -q '^CONSUMED 5$' || fail_msg "T14: forced ack must report a CLEAN cursor line 'CONSUMED 5', got '$out2'" echo "$out2" | grep -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")]" grep -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" 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 true
) && ok ) && ok
@@ -678,10 +671,10 @@ echo "== T16: #946 — quarantine-audit: a consumed-hash row matching a dead-let
if "$STORE" quarantine-audit >"$rep" 2>&1; then if "$STORE" quarantine-audit >"$rep" 2>&1; then
fail_msg "T16: report-mode audit must exit NON-ZERO when false rows exist" fail_msg "T16: report-mode audit must exit NON-ZERO when false rows exist"
fi fi
has_match -q 'FALSE WITNESS' "$rep" || fail_msg "T16: the audit must name the false row loudly [$(cat "$rep")]" grep -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")]" grep -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" grep -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)" 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)"
# Report mode modifies nothing. # Report mode modifies nothing.
jq_any "$rec" '.id=="X"' || fail_msg "T16: report mode must not modify the record" 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 # REPAIR: exactly the false row is removed; the dead-letter LEDGER is history
@@ -690,10 +683,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=="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=="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" 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"
[ "$(has_match -c . "$dl")" = "2" ] || fail_msg "T16: the dead-letter LEDGER must be untouched by --repair" [ "$(grep -c . "$dl")" = "2" ] || fail_msg "T16: the dead-letter LEDGER must be untouched by --repair"
# Clean re-audit: OK, exit 0. # 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")]" "$STORE" quarantine-audit >"$TMP_ROOT/t16.ok" 2>&1 || fail_msg "T16: a clean audit must exit 0 [$(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")]" grep -qi 'OK' "$TMP_ROOT/t16.ok" || fail_msg "T16: a clean audit must say OK [$(cat "$TMP_ROOT/t16.ok")]"
) && 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 ==" echo "== T17: #952 — the clean-sweep message names BOTH unprovable residual classes; a surviving-but-empty-hash dead-letter row is correctly NOT convicted =="
@@ -729,20 +722,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. # match can never fire: this is unprovable class 2, NOT a false witness.
rep="$TMP_ROOT/t17.rep" 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")]" "$STORE" quarantine-audit >"$rep" 2>&1 || fail_msg "T17: the audit must exit 0 — nothing here is provable [$(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")]" 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")]"
# The wording under test (#952): BOTH residual classes, named. # The wording under test (#952): BOTH residual classes, named.
has_match -qi 'pruned' "$rep" || fail_msg "T17: clean sweep must name residual class 1 — evidence pruned away [$(cat "$rep")]" grep -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")]" 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")]"
# --repair on a clean sweep removes nothing: the unprovable row survives. # --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")]" "$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"' || 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" fail_msg "T17: --repair must NOT remove the unprovable bench@13 row — the audit only removes what the ledger can convict"
[ "$(count_lines . "$dl")" = "1" ] || fail_msg "T17: the dead-letter LEDGER must be untouched" [ "$(grep -c . "$dl")" = "1" ] || fail_msg "T17: the dead-letter LEDGER must be untouched"
) && ok ) && ok
echo echo
if [ -s "$FAILFILE" ]; then if [ -s "$FAILFILE" ]; then
echo "wake store/ack harness: FAILED ($(count_lines . "$FAILFILE") assertion(s))" >&2 echo "wake store/ack harness: FAILED ($(grep -c . "$FAILFILE") assertion(s))" >&2
exit 1 exit 1
fi fi
echo "wake store/ack harness: all invariants passed ($pass groups)" echo "wake store/ack harness: all invariants passed ($pass groups)"
@@ -32,13 +32,6 @@
set -uo pipefail set -uo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 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
if ! . "$SCRIPT_DIR/_wake-common.sh"; then
echo "WAKE-ASSERT INIT ABORT: cannot source _wake-common.sh — suite ran ZERO wake assertions (#984)" >&2
exit 97
fi
wake_assert_init
STORE="$SCRIPT_DIR/store.sh" STORE="$SCRIPT_DIR/store.sh"
command -v jq >/dev/null 2>&1 || { command -v jq >/dev/null 2>&1 || {
@@ -154,7 +147,7 @@ EOF
wait "$pa" 2>/dev/null || true wait "$pa" 2>/dev/null || true
else else
# Confirm the window is genuinely open: A holds a live in-flight tmp now. # 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 | count_lines . || true)" n_tmp="$(find "$STATE_DIR" -maxdepth 1 -name '.wake.tmp.*' -type f | grep -c . || 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)" [ "$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), # B: its PRE-LOCK _wake_init_dir runs now (pre-fix: deletes A's live tmp),
@@ -180,7 +173,7 @@ EOF
if [ "$ra" -ne 0 ]; then 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")" 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 fi
if has_match -q 'durable pending write FAILED' "$a_err" 2>/dev/null; then if grep -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)." 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 fi
[ "$rb" -eq 0 ] || fail_msg "T-RACE: enqueue B should also succeed (rc=$rb). stderr: $(tr '\n' ' ' <"$b_err")" [ "$rb" -eq 0 ] || fail_msg "T-RACE: enqueue B should also succeed (rc=$rb). stderr: $(tr '\n' ' ' <"$b_err")"
@@ -196,20 +189,20 @@ EOF
# #908 store invariants: both durably landed, cursor reached 2, no burned seq. # #908 store invariants: both durably landed, cursor reached 2, no burned seq.
cur="$("$STORE" cursors)" cur="$("$STORE" cursors)"
echo "$cur" | has_match -q 'observed_seq=2' || fail_msg "T-RACE: observed_seq must reach 2 (both allocations committed) [$cur]" echo "$cur" | grep -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" | grep -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]" echo "$cur" | grep -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). # 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)" "$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. # No stale tmp left leaking either.
n_leak="$(find "$STATE_DIR" -maxdepth 1 -name '.wake.tmp.*' -type f | count_lines . || true)" n_leak="$(find "$STATE_DIR" -maxdepth 1 -name '.wake.tmp.*' -type f | grep -c . || true)"
[ "$n_leak" = "0" ] || fail_msg "T-RACE: $n_leak stale tmp file(s) leaked after both enqueues committed" [ "$n_leak" = "0" ] || fail_msg "T-RACE: $n_leak stale tmp file(s) leaked after both enqueues committed"
fi fi
) && ok ) && ok
echo echo
if [ -s "$FAILFILE" ]; then if [ -s "$FAILFILE" ]; then
echo "wake store enqueue-race harness: FAILED ($(count_lines . "$FAILFILE") assertion(s)) — #927 TOCTOU reproduced (RED)" >&2 echo "wake store enqueue-race harness: FAILED ($(grep -c . "$FAILFILE") assertion(s)) — #927 TOCTOU reproduced (RED)" >&2
exit 1 exit 1
fi fi
echo "wake store enqueue-race harness: all invariants passed ($pass group) — #927 race closed (GREEN)" echo "wake store enqueue-race harness: all invariants passed ($pass group) — #927 race closed (GREEN)"
@@ -1,318 +0,0 @@
#!/usr/bin/env python3
"""check-973.py — computation backend for the #973 validation harness.
Everything here derives from exactly two inputs: the frozen denominator
artifact (denominator-089615f.json) and the SOURCE TEXT of the ten converted
suites at the current tree. It never reads the ledger — set arithmetic against
the runtime trace belongs to validate-973.sh, so the two legs of the
comparison come from independent code paths.
Subcommands (all print sorted, stable output; non-zero exit on any failure):
expected The expected coordinate set from the ARTIFACT: one
"<helper> <file>:<line+7>" row per denominator row (+7 = 3
converter header lines + 4 lines from the #984 source guard,
uniform across all ten suites).
Multi-grep lines stay ONE coordinate.
static The converted-site inventory from the SOURCE TEXT at the current
tree: every non-comment line bearing a has_match/count_lines
token, as "<helper> <file>:<line>". Independent of the artifact
row list, so `expected == static` is a real check on the
conversion, not a tautology. (Amendment ONE, leg 1: the ledger is
an execution trace, not an inventory — the inventory must come
from the text.)
arms The forced-error arm list: the 19 denominator canaries plus one
E-form arm (store-ack:733→740, a $(count_lines) capture compared
afterward — the A6 shape) plus one F-form arm (quarantine:560→567,
the multi-grep pipeline capture), as "<helper> <file>:<line+7>
<form>". Both extras are asserted to exist in the artifact with
the expected form — a renumber that moved them fails here, not
silently downstream.
sweep Residual sweep: the denominator's own classifier (ported from the
frozen derivation) over the ten suites at the current tree must
find ZERO unconverted verdict-form grep sites; and, IN THE SAME
RUN, eight specimens (six per-form + two absorb-branch probes, #985)
planted into a temp copy of a real
suite must ALL be found with their correct forms — an instrument
that reports zero must first be seen finding what it claims to
find (A5).
"""
import json
import re
import shutil
import sys
import tempfile
from pathlib import Path
HERE = Path(__file__).resolve().parent
WAKE = HERE.parent
ART = HERE / "denominator-089615f.json"
HEADER_SHIFT = 7 # 3 converter header lines after SCRIPT_DIR + 4 lines from the
# #984 source guard (1-line `. _wake-common.sh && wake_assert_init` became a 5-line
# guarded block) — both uniform across all ten suites, both above every site.
# The two hand-picked extra arms (base coordinates; forms asserted at load).
EXTRA_ARMS = [
("test-wake-store-ack.sh", 733, "E-count-capture"),
("test-wake-digest-quarantine.sh", 560, "F-extract-capture"),
]
RX_HELPER = re.compile(r"(^|[^A-Za-z0-9_.-])(has_match|count_lines)([^A-Za-z0-9_.-]|$)")
# ---- classifier, ported verbatim in logic from the frozen denominator
# ---- derivation (docs/journal/fleet/drift-derive-089615f__pepper.py)
RX_FAIL_SAME = re.compile(r"(\|\||&&)\s*fail")
RX_COUNT_SUB = re.compile(r"\$\(.*grep\s+[^)]*-c|\$\(\s*grep\s+-c")
RX_ASSIGN_SUB = re.compile(r'=\s*"?\$\(.*grep')
RX_IF = re.compile(r"^\s*(el)?if\s+.*grep")
RX_GREP = re.compile(r"(^|[^A-Za-z0-9_.-])grep([^A-Za-z0-9_.-]|$)")
# grep in COMMAND position: at line start or after a command separator / subshell
# opener / shell keyword / `!`. Quote-unaware by design — a quoted "grep" after a
# separator reads as a command and lands the line in residual, which fails LOUD;
# the absorb direction (note) is the one that must never fire on a real verdict.
RX_GREP_CMD = re.compile(
r"(?:^|[;|&(`]|\$\(|\bif\b|\belif\b|\bthen\b|\belse\b|\bdo\b|\bwhile\b|\buntil\b|!)"
r"\s*grep(?:\s|$)"
)
def classify(lines):
"""Return (sites, dispo). Every line containing the word grep gets a row."""
sites, dispo = [], []
n = len(lines)
for i, raw in enumerate(lines):
line = raw
ln = i + 1
if not RX_GREP.search(line):
continue
stripped = line.strip()
if stripped.startswith("#"):
dispo.append((ln, "comment", stripped))
continue
nxt = ""
for j in range(i + 1, min(i + 3, n)):
if lines[j].strip():
nxt = lines[j].strip()
break
if "$(" in line and RX_COUNT_SUB.search(line):
sites.append((ln, "E-count-capture", stripped))
continue
if RX_ASSIGN_SUB.search(line) and "grep -c" not in line:
sites.append((ln, "F-extract-capture", stripped))
continue
if RX_FAIL_SAME.search(line):
sites.append((ln, "A-same-line", stripped))
continue
if stripped.endswith("\\"):
k = i + 1
joined = stripped[:-1]
while k < n:
cont = lines[k].strip()
joined += " " + (cont[:-1] if cont.endswith("\\") else cont)
if not cont.endswith("\\"):
break
k += 1
if re.search(r"(\|\||&&)\s*fail", joined) or (
joined.rstrip().endswith(("||", "&&"))
and k + 1 < n
and lines[k + 1].strip().startswith("fail")
):
sites.append((ln, "C-cont-backslash", stripped))
continue
dispo.append((ln, "backslash-no-fail-continuation", stripped))
continue
if stripped.endswith(("||", "&&")) and nxt.startswith("fail"):
sites.append((ln, "B-cont-operator", stripped))
continue
if RX_IF.search(line):
window = " ".join(lines[j] for j in range(i, min(i + 5, n)))
if "fail" in window:
sites.append((ln, "D-if-form", stripped))
continue
dispo.append((ln, "if-grep-no-fail-window", stripped))
continue
win = " ".join(lines[j] for j in range(max(0, i - 2), min(i + 3, n)))
if re.search(r"fail", win, re.I):
dispo.append((ln, "BACKSTOP-HAND-REVIEW", stripped))
else:
dispo.append((ln, "no-verdict-context", stripped))
return sites, dispo
def residual_sites(lines):
"""classify() plus the absorb decision — the ONE path both sweep legs share.
A classified site is absorbed as a note only when its line carries a wake
helper token AND the line shows no grep in command position: a converted
line whose PATTERN argument merely contains the word grep. A helper line
that also runs a real grep verdict (has_match ... && grep -q SECRET ... &&
fail) stays residual (#985). Multi-line forms anchor the site at the line
containing grep, so a command-position grep on a continuation line never
shares its line with the helper token and stays residual by construction.
"""
sites, dispo = classify(lines)
residual, notes = [], []
for ln, form, text in sites:
line = lines[ln - 1]
if RX_HELPER.search(line) and not RX_GREP_CMD.search(line):
notes.append((ln, form, text))
else:
residual.append((ln, form, text))
return residual, notes, dispo
def load_art():
art = json.loads(ART.read_text())
assert art["total"] == 261 == len(art["rows"]), "artifact self-consistency"
return art
def helper_for(row):
return "count_lines" if row["form"].startswith("E") else "has_match"
def suite_files(art):
return sorted({r["file"] for r in art["rows"]})
def cmd_expected():
art = load_art()
out = sorted(
f"{helper_for(r)} {r['file']}:{r['line'] + HEADER_SHIFT}" for r in art["rows"]
)
assert len(out) == len(set(out)) == 261, "expected set must be 261 distinct rows"
print("\n".join(out))
return 0
def cmd_static():
art = load_art()
rows = []
for f in suite_files(art):
for i, line in enumerate((WAKE / f).read_text().split("\n"), start=1):
if line.strip().startswith("#"):
continue
m = RX_HELPER.search(line)
if not m:
continue
helper = (
"count_lines"
if RX_HELPER.search(line).group(2) == "count_lines"
else "has_match"
)
rows.append(f"{helper} {f}:{i}")
print("\n".join(sorted(rows)))
return 0
def cmd_arms():
art = load_art()
by_key = {(r["file"], r["line"]): r for r in art["rows"]}
rows = []
canaries = [r for r in art["rows"] if r.get("canary")]
assert len(canaries) == 19, f"expected 19 canaries, artifact has {len(canaries)}"
for r in canaries:
rows.append(f"{helper_for(r)} {r['file']}:{r['line'] + HEADER_SHIFT} {r['form']}")
for f, ln, want_form in EXTRA_ARMS:
r = by_key.get((f, ln))
assert r is not None, f"extra arm {f}:{ln} not in artifact — renumbered?"
assert r["form"] == want_form, f"extra arm {f}:{ln} form {r['form']} != {want_form}"
rows.append(f"{helper_for(r)} {f}:{ln + HEADER_SHIFT} {r['form']}")
assert len(rows) == 21
print("\n".join(rows))
return 0
# (expected classify form, expected disposition through residual_sites, snippet)
PLANTS = [
("A-same-line", "residual", ['grep -q needle haystack || fail "plant-A"']),
("B-cont-operator", "residual", ["grep -q needle haystack ||", ' fail "plant-B"']),
("C-cont-backslash", "residual", ["grep -q needle \\", ' haystack || fail "plant-C"']),
("D-if-form", "residual", ["if ! grep -q needle haystack; then", ' fail "plant-D"', "fi"]),
("E-count-capture", "residual", ['[ "$(grep -c needle haystack)" = "1" ] || fail "plant-E"']),
("F-extract-capture", "residual", ['val="$(grep needle haystack)"']),
# G: a converted line that ALSO runs a raw grep verdict — the helper token
# must not absorb it (#985)
("A-same-line", "residual", ['has_match -q needle "$F" && grep -q SECRET "$F" && fail "plant-G"']),
# H: negative control — helper whose PATTERN argument is the word grep;
# must be absorbed as a note, never residual
("A-same-line", "note", ['has_match -q "grep" haystack || fail "plant-H"']),
]
def cmd_sweep():
art = load_art()
bad = 0
# leg 1: real suites at the current tree must be residual-free
for f in suite_files(art):
residual, notes, _dispo = residual_sites((WAKE / f).read_text().split("\n"))
for ln, form, text in notes:
# converted line whose PATTERN argument contains the word grep:
# not an unconverted site, but never silently absorbed either
print(f"SWEEP-NOTE {f}:{ln} converted line matches grep-token ({form}): {text[:80]}")
for ln, form, text in residual:
print(f"SWEEP-RESIDUAL {f}:{ln} {form}: {text[:100]}")
bad += 1
print(f"SWEEP {f}: {len(residual)} residual verdict site(s)")
# leg 2, SAME RUN, SAME PATH as leg 1: the instrument must find every plant
# with the right form AND the right absorb disposition — plants G/H exercise
# the absorb branch itself, so this leg must go through residual_sites(),
# not raw classify()
donor = suite_files(art)[0]
with tempfile.TemporaryDirectory() as td:
planted = Path(td) / donor
shutil.copy(WAKE / donor, planted)
base_lines = planted.read_text().split("\n")
offset = len(base_lines)
expect = {}
for form, dispo, snippet in PLANTS:
expect[offset + 1] = (form, dispo) # first physical line of each plant
base_lines.extend(snippet)
offset = len(base_lines)
planted.write_text("\n".join(base_lines))
residual, notes, _ = residual_sites(planted.read_text().split("\n"))
found = {ln: (form, "residual") for ln, form, _t in residual}
found.update({ln: (form, "note") for ln, form, _t in notes})
unexpected = [(ln, form) for ln, form, _t in residual if ln not in expect]
hits = sum(1 for ln, want in expect.items() if found.get(ln) == want)
n_plants = len(PLANTS)
print(f"SWEEP-PLANTS found={hits}/{n_plants} in planted copy of {donor}")
if hits != n_plants:
for ln, want in sorted(expect.items()):
got = found.get(ln, ("<missed>", "<missed>"))
if got != want:
print(f"SWEEP-PLANT-MISS line {ln}: expected {want}, got {got}")
bad += 1
if unexpected:
# the donor is a converted suite: any non-plant RESIDUAL site in the
# copy contradicts the zero leg 1 just reported on the original
# (non-plant notes mirror leg 1's treatment: printed there, not bad)
for ln, form in unexpected:
print(f"SWEEP-PLANT-UNEXPECTED {donor}(copy):{ln} {form}")
bad += 1
return 1 if bad else 0
def main():
cmds = {
"expected": cmd_expected,
"static": cmd_static,
"arms": cmd_arms,
"sweep": cmd_sweep,
}
if len(sys.argv) != 2 or sys.argv[1] not in cmds:
sys.exit(f"usage: check-973.py {{{'|'.join(cmds)}}}")
sys.exit(cmds[sys.argv[1]]())
if __name__ == "__main__":
main()
@@ -1,131 +0,0 @@
#!/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()
File diff suppressed because it is too large Load Diff
@@ -1,299 +0,0 @@
#!/usr/bin/env bash
# microtest-wake-assert.sh — #973 instrument self-test. Run BEFORE trusting any
# validate-run evidence: it proves the counted ledger and the abort mechanics on
# two generated mini-suites, so a defect in the instrument cannot silently wear
# the colour of a clean validation.
#
# What it proves (each check named C1..C8 below):
# C1 green run: ledger set EQUALS a text-derived expected set spanning TWO
# files (file-field discrimination), row count > 1, both sentinels emitted,
# exit 0. Also pins the BASH_LINENO convention for backslash-continuation
# call sites against the first-physical-line convention the denominator
# artifact uses.
# C2 early-exit truncation: a suite that exits before its later site yields a
# SHORT ledger, and the expected-set comparison catches it — a counted
# ledger must report its own truncation, never a smaller total.
# C3 abort from inside a `( ... )` test subshell kills the WHOLE suite: no
# sentinel, non-zero exit, loud named reason (file:line + raw rc).
# C4 abort stays loud at a call site that appends 2>/dev/null (the preimage
# canary shape) — the saved-fd path.
# C5 abort escapes a `$( count_lines ... )` substitution (A6 shape): the
# count from a failed measurement is never compared and the suite dies.
# C6 abort escapes a pipeline tail (`printf | has_match`).
# C7 count_lines prints 0 on grep rc 1 (zero matches is a measurement, not an
# error) — implicit in C1's green run via the delta-count site.
# C8 an env-prefix on the helper (`LC_ALL=C has_match ...`) reaches the grep
# child — pins the conversion shape for the digest-hmac LC_ALL site.
# C9 an arm that matches NO site is loud about it by omission: green run,
# sentinel present, and NO "WAKE-ASSERT ARMED" line — so "did not abort"
# is separable into arm-never-matched (no ARMED line) vs error-path-
# broken (ARMED line, no abort). C3..C6 require the ARMED line AND the
# aborting site's ledger row (append lands BEFORE the grep runs, so an
# abort can never shorten the count it is part of).
# C10 the BASH_LINENO pin's abort arm fires: under a probe interpreter that
# misreports the continuation line, wake_assert_init aborts loudly and
# nothing past init executes — a pin whose failure arm was never seen
# firing is an undertaking, not a control.
# C11 the FAILED-summary template executes on the red path: a mini-suite
# driven deterministically red emits the exact converted summary shape
# (`FAILED ($(count_lines . "$FAILFILE") assertion(s))`) with the right
# count, exits 1, and the summary site's ledger row lands. The nine
# real-suite summary sites are structurally unreachable in a green run
# (guarded by [ -s "$FAILFILE" ]); their dispositions cite THIS check as
# the measured execution of the same template, so "unexecuted in the
# green run" never silently means "never executed anywhere".
set -uo pipefail
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
export WAKE_COMMON="$HERE/../_wake-common.sh"
[ -f "$WAKE_COMMON" ] || {
echo "microtest: _wake-common.sh not found at $WAKE_COMMON" >&2
exit 1
}
TMP="$(mktemp -d)"
trap 'rm -rf "$TMP"' EXIT
fails=0
check() { # check NAME COND-DESCRIPTION (pass/fail already decided by caller: $1=name $2=0|1 $3=detail)
if [ "$2" -eq 0 ]; then
echo " PASS $1"
else
echo " FAIL $1$3"
fails=$((fails + 1))
fi
}
# --- fixture data ----------------------------------------------------------
printf 'alpha\nbeta\nbeta\ngamma-unused\n' >"$TMP/data.txt"
# --- mini-suite A: six helper sites across every converted form ------------
cat >"$TMP/mini-a.sh" <<'MINI_A'
#!/usr/bin/env bash
set -uo pipefail
. "$WAKE_COMMON"
wake_assert_init
TMP="$1"
FAILFILE="$TMP/failures-a"
: >"$FAILFILE"
fail_msg() { echo " FAIL: $*" >&2; echo x >>"$FAILFILE"; }
ok() { :; }
(
has_match -q alpha "$TMP/data.txt" || fail_msg "alpha missing" # SITE:or-subshell
) && ok
(
has_match -q FORBIDDEN "$TMP/data.txt" 2>/dev/null && fail_msg "forbidden present" # SITE:and-swallow
) && ok
(
[ "$(count_lines beta "$TMP/data.txt")" = "2" ] || fail_msg "beta count" # SITE:count-capture
) && ok
(
printf 'gamma\n' | has_match -q gamma || fail_msg "gamma pipeline" # SITE:pipeline
) && ok
(
has_match -q \
alpha "$TMP/data.txt" || fail_msg "continuation" # SITE:continuation
) && ok
(
[ "$(count_lines delta "$TMP/data.txt")" = "0" ] || fail_msg "delta zero" # SITE:count-zero
) && ok
if [ -s "$FAILFILE" ]; then
echo "mini-a: FAILED" >&2
exit 1
fi
echo "mini-a: OK" >&2
MINI_A
# --- mini-suite B: second file, one site behind an early exit --------------
cat >"$TMP/mini-b.sh" <<'MINI_B'
#!/usr/bin/env bash
set -uo pipefail
. "$WAKE_COMMON"
wake_assert_init
TMP="$1"
(
has_match -q alpha "$TMP/data.txt" || echo "b1 missing" >&2 # SITE:b-first
)
if [ "${MINI_B_EARLY_EXIT:-}" = "1" ]; then
exit 0
fi
(
has_match -q beta "$TMP/data.txt" || echo "b2 missing" >&2 # SITE:b-second
)
echo "mini-b: OK" >&2
MINI_B
chmod +x "$TMP/mini-a.sh" "$TMP/mini-b.sh"
# Text-derived expected set: helper-name + basename:line for every SITE-marked
# call, taken from the generated files' TEXT (independent of BASH_LINENO), with
# the continuation site expected at its FIRST physical line — the denominator
# artifact's convention.
expected_set() { # expected_set FILE
local f="$1" base
base="$(basename "$f")"
awk '
/# SITE:/ {
line = NR
if ($0 !~ /has_match|count_lines/) line = NR - 1 # marker on the continuation tail
print line
}
' "$f" | while read -r ln; do
txt="$(sed -n "${ln}p" "$f")"
case "$txt" in
*count_lines*) printf 'count_lines %s:%s\n' "$base" "$ln" ;;
*) printf 'has_match %s:%s\n' "$base" "$ln" ;;
esac
done
}
site_line() { # site_line FILE MARKER -> first physical line of that call
local f="$1" marker="$2" ln
ln="$(grep -n "# SITE:${marker}\$" "$f" | cut -d: -f1)"
# continuation marker sits on the tail line; the call starts one line up
if ! sed -n "${ln}p" "$f" | grep -Eq 'has_match|count_lines'; then
ln=$((ln - 1))
fi
printf '%s' "$ln"
}
# --- C1: green run, two files, set equality --------------------------------
LEDGER="$TMP/ledger-c1"
: >"$LEDGER"
outA="$(WAKE_ASSERT_LEDGER="$LEDGER" bash "$TMP/mini-a.sh" "$TMP" 2>&1)"
rcA=$?
outB="$(WAKE_ASSERT_LEDGER="$LEDGER" bash "$TMP/mini-b.sh" "$TMP" 2>&1)"
rcB=$?
{ expected_set "$TMP/mini-a.sh"; expected_set "$TMP/mini-b.sh"; } | sort >"$TMP/expected-c1"
sort "$LEDGER" >"$TMP/got-c1"
n_expected="$(grep -c . "$TMP/expected-c1")"
if [ "$rcA" -eq 0 ] && [ "$rcB" -eq 0 ] &&
printf '%s' "$outA" | grep -q 'mini-a: OK' &&
printf '%s' "$outB" | grep -q 'mini-b: OK' &&
[ "$n_expected" -gt 1 ] &&
cmp -s "$TMP/expected-c1" "$TMP/got-c1"; then
check C1 0 ""
else
check C1 1 "rcA=$rcA rcB=$rcB expected($n_expected)/got diff: $(diff "$TMP/expected-c1" "$TMP/got-c1" 2>&1 | head -n 10 | tr '\n' ' ')"
fi
# --- C2: early exit -> short ledger, comparison catches it -----------------
LEDGER="$TMP/ledger-c2"
: >"$LEDGER"
WAKE_ASSERT_LEDGER="$LEDGER" MINI_B_EARLY_EXIT=1 bash "$TMP/mini-b.sh" "$TMP" >/dev/null 2>&1
expected_set "$TMP/mini-b.sh" | sort >"$TMP/expected-c2"
sort "$LEDGER" >"$TMP/got-c2"
if ! cmp -s "$TMP/expected-c2" "$TMP/got-c2" &&
grep -q "has_match mini-b.sh:$(site_line "$TMP/mini-b.sh" b-first)" "$TMP/got-c2" &&
! grep -q "mini-b.sh:$(site_line "$TMP/mini-b.sh" b-second)" "$TMP/got-c2"; then
check C2 0 ""
else
check C2 1 "truncated ledger was not detected as short"
fi
# --- C3..C6: per-shape abort proofs ----------------------------------------
abort_case() { # abort_case NAME MARKER HELPER
local name="$1" marker="$2" helper="$3" ln site out rc ledger
ln="$(site_line "$TMP/mini-a.sh" "$marker")"
site="mini-a.sh:${ln}"
ledger="$TMP/ledger-${name}"
: >"$ledger"
out="$(WAKE_ASSERT_LEDGER="$ledger" WAKE_ASSERT_FORCE_GREP_ERROR_AT="$site" \
bash "$TMP/mini-a.sh" "$TMP" 2>&1)"
rc=$?
if [ "$rc" -ne 0 ] &&
! printf '%s' "$out" | grep -q 'mini-a: OK' &&
! printf '%s' "$out" | grep -q 'mini-a: FAILED' &&
printf '%s' "$out" | grep -q "WAKE-ASSERT ARMED: forcing real grep error at $site" &&
printf '%s' "$out" | grep -q "WAKE-ASSERT ABORT" &&
printf '%s' "$out" | grep -q "$site" &&
printf '%s' "$out" | grep -q "grep exit 2" &&
grep -q "^${helper} ${site}\$" "$ledger"; then
check "$name" 0 ""
else
check "$name" 1 "rc=$rc site=$site ledger=$(grep -c . "$ledger") out=$(printf '%s' "$out" | tail -n 3 | tr '\n' ' ')"
fi
}
abort_case C3 or-subshell has_match
abort_case C4 and-swallow has_match
abort_case C5 count-capture count_lines
abort_case C6 pipeline has_match
# --- C7: covered by C1 (delta-count site prints 0 on grep rc 1) ------------
check C7 0 ""
# --- C8: env-prefix on a function reaches the grep child -------------------
envprobe() { command env | command grep -c '^LC_ALL=xx_wake_test$'; }
got="$(LC_ALL=xx_wake_test envprobe 2>/dev/null)" # bash's setlocale warning about the fake locale is itself proof the prefix landed
if [ "$got" = "1" ]; then check C8 0 ""; else check C8 1 "env-prefix did not reach child (got=$got)"; fi
# --- C9: arm matching NO site -> green run, no ARMED line ------------------
out="$(WAKE_ASSERT_FORCE_GREP_ERROR_AT="mini-a.sh:9999" bash "$TMP/mini-a.sh" "$TMP" 2>&1)"
rc=$?
if [ "$rc" -eq 0 ] &&
printf '%s' "$out" | grep -q 'mini-a: OK' &&
! printf '%s' "$out" | grep -q 'WAKE-ASSERT ARMED'; then
check C9 0 ""
else
check C9 1 "rc=$rc out=$(printf '%s' "$out" | tail -n 3 | tr '\n' ' ')"
fi
# --- C10: lineno pin aborts under an interpreter that breaks the convention -
cat >"$TMP/fake-bash" <<'FAKE'
#!/usr/bin/env bash
# stand-in for a bash whose BASH_LINENO convention differs: misreports the
# continuation call one line low (the exact skew the pin exists to catch)
printf '3\n5\n'
FAKE
chmod +x "$TMP/fake-bash"
out="$(WAKE_ASSERT_PIN_BASH="$TMP/fake-bash" bash -c '. "$WAKE_COMMON" && wake_assert_init && echo REACHED-PAST-INIT' 2>&1)"
rc=$?
if [ "$rc" -ne 0 ] &&
! printf '%s' "$out" | grep -q 'REACHED-PAST-INIT' &&
printf '%s' "$out" | grep -q 'WAKE-ASSERT INIT ABORT: BASH_LINENO convention violated'; then
check C10 0 ""
else
check C10 1 "rc=$rc out=$(printf '%s' "$out" | tail -n 2 | tr '\n' ' ')"
fi
# --- C11: red path executes the converted FAILED-summary template -----------
cat >"$TMP/mini-c.sh" <<'MINI_C'
#!/usr/bin/env bash
set -uo pipefail
. "$WAKE_COMMON"
wake_assert_init
TMP="$1"
FAILFILE="$TMP/failures-c"
: >"$FAILFILE"
fail_msg() { echo " FAIL: $*" >&2; echo x >>"$FAILFILE"; }
ok() { :; }
(
has_match -q alpha "$TMP/data.txt" && fail_msg "alpha present" # SITE:c-inverted (deterministically red: alpha IS in the fixture)
) && ok
echo
if [ -s "$FAILFILE" ]; then
echo "wake mini-c harness: FAILED ($(count_lines . "$FAILFILE") assertion(s))" >&2 # SITE:c-summary
exit 1
fi
echo "wake mini-c harness: all invariants passed (1 group)"
MINI_C
chmod +x "$TMP/mini-c.sh"
LEDGER="$TMP/ledger-c11"
: >"$LEDGER"
out="$(WAKE_ASSERT_LEDGER="$LEDGER" bash "$TMP/mini-c.sh" "$TMP" 2>&1)"
rc=$?
summary_ln="$(site_line "$TMP/mini-c.sh" c-summary)"
if [ "$rc" -eq 1 ] &&
printf '%s' "$out" | grep -q 'wake mini-c harness: FAILED (1 assertion(s))' &&
! printf '%s' "$out" | grep -q 'all invariants passed' &&
grep -q "^count_lines mini-c.sh:${summary_ln}\$" "$LEDGER"; then
check C11 0 ""
else
check C11 1 "rc=$rc summary_ln=$summary_ln ledger=$(tr '\n' ' ' <"$LEDGER") out=$(printf '%s' "$out" | tail -n 2 | tr '\n' ' ')"
fi
echo
if [ "$fails" -gt 0 ]; then
echo "microtest-wake-assert: FAILED ($fails check(s))" >&2
exit 1
fi
echo "microtest-wake-assert: OK (all checks passed)"
@@ -1,37 +0,0 @@
# unexecuted-sites-dispositions.txt — #973 validation, amendment ONE leg 3.
#
# The ledger is an execution trace, not an inventory: a green instrumented run
# cannot execute a site that only lives on a suite's red path. Every converted
# site that did NOT appear in the green-run trace is enumerated here with an
# individual disposition; validate-973.sh fails if any unexecuted site lacks a
# row here, and ALSO fails if a row here names a site that DID execute (stale
# disposition). Key = first two whitespace-separated fields; text after "—" is
# the adjudication.
#
# All nine sites below are the same structural shape, adjudicated one by one
# from source text: the suite's FAILED-branch summary line,
# echo "wake <name> harness: FAILED ($(count_lines . "$FAILFILE") assertion(s))" >&2
# guarded by `if [ -s "$FAILFILE" ]` — structurally unreachable while every
# assertion passes, which is precisely the state a green validation run is
# required to be in. (The tenth suite, test-wake-preimage.sh, uses its own
# X/Y summary format with no grep in the red branch, so it has no row here.)
#
# The disposition is NOT "it would work": the exact template is EXECUTED red
# in microtest C11 (deterministically failed mini-suite, same
# count_lines-in-substitution summary shape → right count, exit 1, ledger row
# at the summary coordinate), and the E-in-substitution abort path is proven
# by microtest C5 plus the forced-error arm at test-wake-store-ack.sh:736.
# Each site's conversion text is independently verified by the static
# inventory (check-973.py static == expected, all 261 rows).
#
# Verified guard per site (line numbers at branch tip, +3 header shift):
count_lines test-wake-beacon.sh:354 — red-path summary; guard `[ -s "$FAILFILE" ]` at line 353; template execution measured by microtest C11; text verified by static inventory
count_lines test-wake-detector.sh:706 — red-path summary; guard `[ -s "$FAILFILE" ]` at line 705; template execution measured by microtest C11; text verified by static inventory
count_lines test-wake-digest-hmac.sh:438 — red-path summary; guard `[ -s "$FAILFILE" ]` at line 437; template execution measured by microtest C11; text verified by static inventory
count_lines test-wake-digest-quarantine.sh:588 — red-path summary; guard `[ -s "$FAILFILE" ]` at line 587; template execution measured by microtest C11; text verified by static inventory
count_lines test-wake-fn-oracle.sh:136 — red-path summary; guard `[ -s "$FAILFILE" ]` at line 135; template execution measured by microtest C11; text verified by static inventory
count_lines test-wake-install.sh:438 — red-path summary; guard `[ -s "$FAILFILE" ]` at line 437; template execution measured by microtest C11; text verified by static inventory
count_lines test-wake-reconcile.sh:393 — red-path summary; guard `[ -s "$FAILFILE" ]` at line 392; template execution measured by microtest C11; text verified by static inventory
count_lines test-wake-store-ack.sh:745 — red-path summary; guard `[ -s "$FAILFILE" ]` at line 744; template execution measured by microtest C11; text verified by static inventory
count_lines test-wake-store-enqueue-race.sh:212 — red-path summary (with "#927 TOCTOU reproduced (RED)" tail); guard `[ -s "$FAILFILE" ]` at line 211; template execution measured by microtest C11; text verified by static inventory
@@ -1,218 +0,0 @@
#!/usr/bin/env bash
# validate-973.sh — #973 validation driver. One run produces the complete
# evidence chain for the 261-site conversion:
#
# 0. instrument self-test (microtest) — no validate evidence is trusted
# before the instrument itself has been proven, including its abort arms.
# 1. expected set: 261 coordinates from the FROZEN artifact (+7 header
# shift: 3 converter lines + 4 #984 guard lines), count asserted against the number declared below BEFORE any
# suite runs.
# 2. static inventory: converted call sites re-derived from SOURCE TEXT,
# must equal the expected set exactly (amendment ONE, leg 1 — the
# inventory comes from the text, never from the ledger).
# 3. green instrumented run: all ten suites with WAKE_ASSERT_LEDGER; each
# must exit 0 AND emit its own sentinel (per-suite formats differ and are
# pinned here — a suite that died early must never pass on another
# suite's output).
# 4. trace arithmetic on coordinate SETS (loops re-execute sites and the
# multi-grep lines append twice per pass, so counts are meaningless;
# sets are not):
# trace expected MUST be empty (a helper ran at a coordinate the
# denominator never measured);
# expected trace = converted-but-never-executed: enumerated, and
# every entry must carry a disposition in the
# committed unexecuted-sites-dispositions.txt, with
# no stale dispositions the other way (amendment
# ONE, legs 2+3 — the ledger is an execution trace,
# not an inventory; the difference is enumerated and
# individually dispositioned, never silently absent).
# 5. forced-error arms: the 19 denominator canaries plus one E-form and one
# F-form site, each run with WAKE_ASSERT_FORCE_GREP_ERROR_AT: the suite
# must emit the ARMED line (the arm proved it fired), the ABORT line
# naming the site, exit non-zero, emit NO sentinel, and the aborting
# site's ledger row must already be present (the append lands before the
# grep).
# 6. residual sweep: the denominator's own classifier finds zero unconverted
# verdict greps in the suites — and eight plants (six per-form + two
# absorb-branch probes, #985) in the same run.
#
# Output discipline (A10): every line that reports on a suite names the file
# under test; exit codes are reported before failure counts.
set -uo pipefail
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
WAKE="$(cd "$HERE/.." && pwd)"
CHECK="$HERE/check-973.py"
DISPO="$HERE/unexecuted-sites-dispositions.txt"
TMP="$(mktemp -d)"
trap 'rm -rf "$TMP"' EXIT
# Declared BEFORE any suite runs (A2): the run must produce THESE numbers,
# not be described by whatever numbers it produced.
EXPECTED_SUITES=10
EXPECTED_SITES=261
EXPECTED_ARMS=21
fails=0
flag() {
printf 'FAIL %s\n' "$*"
fails=$((fails + 1))
}
SUITES=(
test-wake-beacon.sh
test-wake-detector.sh
test-wake-digest-hmac.sh
test-wake-digest-quarantine.sh
test-wake-fn-oracle.sh
test-wake-install.sh
test-wake-preimage.sh
test-wake-reconcile.sh
test-wake-store-ack.sh
test-wake-store-enqueue-race.sh
)
[ "${#SUITES[@]}" -eq "$EXPECTED_SUITES" ] ||
flag "suite list has ${#SUITES[@]} entries, declared $EXPECTED_SUITES"
# Per-suite sentinel patterns, pinned: nine suites share the harness template
# (enqueue-race appends a tail after it); preimage uses its own format.
sentinel_for() {
case "$1" in
test-wake-preimage.sh) printf '%s' '^== test-wake-preimage: 17/17 passed ==$' ;;
*) printf '%s' 'harness: all invariants passed' ;;
esac
}
# --- 0: instrument self-test ------------------------------------------------
if bash "$HERE/microtest-wake-assert.sh" >"$TMP/microtest.out" 2>&1; then
echo "MICROTEST microtest-wake-assert.sh exit=0 (instrument proven)"
else
rc=$?
echo "MICROTEST microtest-wake-assert.sh exit=$rc"
sed 's/^/ /' "$TMP/microtest.out" | tail -n 15
flag "instrument self-test failed — no validate evidence below is trustworthy"
fi
# --- 1+2: expected set (artifact) vs static inventory (source text) ---------
python3 "$CHECK" expected | sort >"$TMP/expected.txt" ||
flag "check-973.py expected failed"
n_expected="$(grep -c . "$TMP/expected.txt")"
echo "EXPECTED-SET $n_expected coordinates (declared: $EXPECTED_SITES)"
[ "$n_expected" -eq "$EXPECTED_SITES" ] ||
flag "expected set has $n_expected coordinates, declared $EXPECTED_SITES"
python3 "$CHECK" static | sort >"$TMP/static.txt" ||
flag "check-973.py static failed"
if cmp -s "$TMP/expected.txt" "$TMP/static.txt"; then
echo "STATIC-INVENTORY equals expected set ($(grep -c . "$TMP/static.txt") rows from source text)"
else
flag "static inventory (source text) differs from expected set (artifact):"
diff "$TMP/expected.txt" "$TMP/static.txt" | head -n 20 | sed 's/^/ /'
fi
# --- 3: green instrumented run ----------------------------------------------
LEDGER="$TMP/ledger"
: >"$LEDGER"
for s in "${SUITES[@]}"; do
out="$(WAKE_ASSERT_LEDGER="$LEDGER" bash "$WAKE/$s" 2>&1)"
rc=$?
if printf '%s\n' "$out" | grep -Eq "$(sentinel_for "$s")"; then
sent="present"
else
sent="ABSENT"
fi
echo "SUITE $s exit=$rc sentinel=$sent"
[ "$rc" -eq 0 ] || flag "$s exited $rc in the green instrumented run"
[ "$sent" = "present" ] || flag "$s did not emit its sentinel"
done
# --- 4: trace arithmetic on coordinate sets ---------------------------------
sort -u "$LEDGER" >"$TMP/trace.txt"
echo "TRACE $(grep -c . "$TMP/trace.txt") distinct coordinates from $(grep -c . "$LEDGER") ledger rows"
comm -13 "$TMP/expected.txt" "$TMP/trace.txt" >"$TMP/rogue.txt"
if [ -s "$TMP/rogue.txt" ]; then
flag "trace contains coordinates OUTSIDE the frozen denominator:"
sed 's/^/ ROGUE /' "$TMP/rogue.txt"
else
echo "TRACE-MINUS-EXPECTED empty (no helper ran at an unmeasured coordinate)"
fi
comm -23 "$TMP/expected.txt" "$TMP/trace.txt" >"$TMP/unexec.txt"
n_unexec="$(grep -c . "$TMP/unexec.txt" || true)"
echo "UNEXECUTED $n_unexec of $EXPECTED_SITES converted sites did not execute in the green run"
if [ ! -f "$DISPO" ]; then
flag "disposition file missing: $DISPO — every unexecuted site must be individually dispositioned"
sed 's/^/ UNDISPOSITIONED /' "$TMP/unexec.txt"
else
awk '!/^#/ && NF >= 2 {print $1, $2}' "$DISPO" | sort -u >"$TMP/dispo-keys.txt"
comm -23 "$TMP/unexec.txt" "$TMP/dispo-keys.txt" >"$TMP/undispo.txt"
comm -13 "$TMP/unexec.txt" "$TMP/dispo-keys.txt" >"$TMP/stale-dispo.txt"
if [ -s "$TMP/undispo.txt" ]; then
flag "unexecuted sites WITHOUT a disposition:"
sed 's/^/ UNDISPOSITIONED /' "$TMP/undispo.txt"
fi
if [ -s "$TMP/stale-dispo.txt" ]; then
flag "dispositions for sites that DID execute (stale — the file no longer matches the run):"
sed 's/^/ STALE-DISPO /' "$TMP/stale-dispo.txt"
fi
if [ ! -s "$TMP/undispo.txt" ] && [ ! -s "$TMP/stale-dispo.txt" ]; then
echo "DISPOSITIONS all $n_unexec unexecuted sites individually dispositioned, none stale"
fi
fi
# --- 5: forced-error arms ---------------------------------------------------
python3 "$CHECK" arms >"$TMP/arms.txt" || flag "check-973.py arms failed"
n_arms="$(grep -c . "$TMP/arms.txt")"
echo "ARMS $n_arms forced-error arms (declared: $EXPECTED_ARMS)"
[ "$n_arms" -eq "$EXPECTED_ARMS" ] ||
flag "arm list has $n_arms entries, declared $EXPECTED_ARMS"
while read -r helper site form; do
f="${site%%:*}"
aled="$TMP/ledger-arm"
: >"$aled"
out="$(WAKE_ASSERT_LEDGER="$aled" WAKE_ASSERT_FORCE_GREP_ERROR_AT="$site" \
bash "$WAKE/$f" 2>&1)"
rc=$?
bad=""
[ "$rc" -ne 0 ] || bad="$bad exit=0"
printf '%s\n' "$out" | grep -q "WAKE-ASSERT ARMED: forcing real grep error at $site" ||
bad="$bad no-ARMED-line"
printf '%s\n' "$out" | grep -q "WAKE-ASSERT ABORT: ${helper} at ${site}: grep exit" ||
bad="$bad no-ABORT-line"
# AND-polarity check (a match is the defect): a grep error (rc>=2) must be
# its own loud arm — it cannot fall through as "no sentinel = pass".
rc_sent=0
printf '%s\n' "$out" | grep -Eq "$(sentinel_for "$f")" || rc_sent=$?
case "$rc_sent" in
0) bad="$bad sentinel-emitted" ;;
1) : ;;
*) bad="$bad sentinel-grep-error-rc=$rc_sent" ;;
esac
grep -q "^${helper} ${site}\$" "$aled" ||
bad="$bad no-ledger-row"
if [ -z "$bad" ]; then
echo "ARM $site ($form) exit=$rc armed+abort+no-sentinel+ledger-row"
else
echo "ARM $site ($form) exit=$rc DEFECTS:$bad"
flag "arm $site ($form) failed:$bad"
fi
done <"$TMP/arms.txt"
# --- 6: residual sweep ------------------------------------------------------
if python3 "$CHECK" sweep >"$TMP/sweep.out" 2>&1; then
echo "SWEEP exit=0"
else
echo "SWEEP exit=$?"
flag "residual sweep failed"
fi
sed 's/^/ /' "$TMP/sweep.out"
# --- summary (exit codes above, failure count last — A10) --------------------
echo
if [ "$fails" -gt 0 ]; then
echo "validate-973: FAILED ($fails failure(s))" >&2
exit 1
fi
echo "validate-973: OK — $EXPECTED_SUITES suites, $EXPECTED_SITES sites, $EXPECTED_ARMS arms, sweep clean"
+1 -1
View File
@@ -25,7 +25,7 @@
"lint": "eslint src", "lint": "eslint src",
"typecheck": "tsc --noEmit", "typecheck": "tsc --noEmit",
"test": "vitest run --passWithNoTests && pnpm run test:framework-shell", "test": "vitest run --passWithNoTests && pnpm run test:framework-shell",
"test:framework-shell": "bash framework/tools/quality/scripts/check-test-enumeration.sh && bash framework/tools/quality/scripts/test-check-test-enumeration.sh && python3 src/lease-broker/daemon_deadline_unittest.py && python3 src/lease-broker/normative_fragments_unittest.py && python3 src/lease-broker/receipt_challenge_unittest.py && python3 src/lease-broker/context_recovery_unittest.py && python3 src/lease-broker/recovery_runtime_unittest.py && python3 src/lease-broker/recovery_b1_adversarial_unittest.py && python3 src/lease-broker/framework_skill_portability_unittest.py && python3 src/mutator-gate/runtime_tools_unittest.py && python3 src/mutator-gate/runtime_launch_guard_unittest.py && python3 src/mutator-gate/version_coupling_unittest.py && python3 framework/tools/lease-broker/check-runtime-launches.py --root ../.. && bash framework/tools/codex/test-pr-diff-context.sh && bash framework/tools/qa/test-deps-preflight.sh && bash framework/tools/git/test-pr-review-gitea-comment.sh && bash framework/tools/git/test-pr-review-repo-host-override.sh && bash framework/tools/git/test-ci-queue-wait-branch-absent.sh && bash framework/tools/git/test-git-credential-mosaic.sh && bash framework/tools/git/test-gitea-token-identity.sh && bash framework/tools/_scripts/test-install-ordering-guard.sh && bash framework/tools/tmux/agent-send.test.sh && bash framework/tools/wake/test-wake-store-ack.sh && bash framework/tools/wake/test-wake-store-enqueue-race.sh && bash framework/tools/wake/test-wake-digest-hmac.sh && bash framework/tools/wake/test-wake-digest-quarantine.sh && bash framework/tools/wake/test-wake-detector.sh && bash framework/tools/wake/test-wake-fn-oracle.sh && bash framework/tools/wake/test-wake-reconcile.sh && bash framework/tools/wake/test-wake-beacon.sh && bash framework/tools/wake/test-wake-preimage.sh && bash framework/tools/wake/test-wake-install.sh" "test:framework-shell": "python3 src/lease-broker/daemon_deadline_unittest.py && python3 src/lease-broker/normative_fragments_unittest.py && python3 src/lease-broker/receipt_challenge_unittest.py && python3 src/lease-broker/context_recovery_unittest.py && python3 src/lease-broker/recovery_runtime_unittest.py && python3 src/lease-broker/recovery_b1_adversarial_unittest.py && python3 src/lease-broker/framework_skill_portability_unittest.py && python3 src/mutator-gate/runtime_tools_unittest.py && python3 src/mutator-gate/runtime_launch_guard_unittest.py && python3 src/mutator-gate/version_coupling_unittest.py && python3 framework/tools/lease-broker/check-runtime-launches.py --root ../.. && bash framework/tools/codex/test-pr-diff-context.sh && bash framework/tools/qa/test-deps-preflight.sh && bash framework/tools/git/test-pr-review-gitea-comment.sh && bash framework/tools/git/test-pr-review-repo-host-override.sh && bash framework/tools/git/test-ci-queue-wait-branch-absent.sh && bash framework/tools/git/test-git-credential-mosaic.sh && bash framework/tools/git/test-gitea-token-identity.sh && bash framework/tools/_scripts/test-install-ordering-guard.sh && bash framework/tools/tmux/agent-send.test.sh && bash framework/tools/wake/test-wake-store-ack.sh && bash framework/tools/wake/test-wake-store-enqueue-race.sh && bash framework/tools/wake/test-wake-digest-hmac.sh && bash framework/tools/wake/test-wake-digest-quarantine.sh && bash framework/tools/wake/test-wake-detector.sh && bash framework/tools/wake/test-wake-fn-oracle.sh && bash framework/tools/wake/test-wake-reconcile.sh && bash framework/tools/wake/test-wake-beacon.sh && bash framework/tools/wake/test-wake-preimage.sh && bash framework/tools/wake/test-wake-install.sh"
}, },
"dependencies": { "dependencies": {
"@mosaicstack/brain": "workspace:*", "@mosaicstack/brain": "workspace:*",