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
25 changed files with 277 additions and 5274 deletions
@@ -109,55 +109,6 @@ else
detect_platform >/dev/null
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
# 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
@@ -199,7 +150,7 @@ print(json.dumps({"body": os.environ["COMMENT_BODY"]}))
return 1
fi
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
fi
@@ -228,7 +179,7 @@ PY
return 1
fi
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
fi
@@ -419,7 +370,7 @@ gitea_authenticated_login() {
return 1
fi
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
fi
@@ -456,7 +407,7 @@ gitea_read_pr_head_into() {
return 1
fi
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
fi
python3 - "$pr_file" <<'PY'
@@ -546,7 +497,7 @@ print(json.dumps({
fi
# Gitea returns 200 (occasionally 201) with the created review object.
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
fi
@@ -575,7 +526,7 @@ PY
return 1
fi
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
fi
@@ -7,40 +7,14 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
WORK_DIR="${MOSAIC_TEST_WORK_DIR:-$PWD/.mosaic-test-work/gitea-login-resolution}"
REPO_DIR="$WORK_DIR/repo"
BIN_DIR="$WORK_DIR/bin"
HOME_DIR="$WORK_DIR/home"
LOG_FILE="$WORK_DIR/calls.log"
CREDENTIALS_FILE="$WORK_DIR/credentials.json"
rm -rf "$WORK_DIR"
mkdir -p "$REPO_DIR" "$BIN_DIR" "$HOME_DIR"
mkdir -p "$REPO_DIR" "$BIN_DIR"
git -C "$REPO_DIR" init -q
git -C "$REPO_DIR" remote add origin https://git.uscllc.com/USC/uconnect.git
# HERMETICITY (#1007) — TWO mechanisms with DIFFERENT jobs; do not conflate them.
#
# OPERATIVE: the empty repo-local `mosaic.gitIdentity` below. 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 so 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. This suite is the one where the consequence is not subtle: it FAILS
# outright on a provisioned seat (rc=1 bare, rc=0 with $HOME sandboxed, one
# variable changed) and passes everywhere else, including CI, which has no
# per-agent token to leak.
#
# CONTAINMENT: the sandboxed HOME in the four run helpers below. It only has to
# bound a failure that the pin should already have prevented.
#
# NOTE FOR ANYONE AUDITING THIS SUITE: the sandboxed HOME is containment, NOT an
# assay. Running a suite under a decoy HOME to test for this defect REMOVES the
# trigger — ~/.gitconfig is where the global identity lives, so step 0 is skipped
# by construction and every suite reads clean however vulnerable it is. To measure,
# REPLICATE a seat (a decoy HOME whose .gitconfig sets mosaic.gitIdentity, with no
# per-slot token) so step 0 reaches its fail-loud branch.
#
# Note the env-var route does NOT work: detect-platform.sh reads
# "${MOSAIC_GIT_IDENTITY:-}", and `:-` treats set-but-empty identically to unset.
git -C "$REPO_DIR" config mosaic.gitIdentity ""
cat > "$CREDENTIALS_FILE" <<'JSON'
{
@@ -112,7 +86,6 @@ run_in_repo() {
(
cd "$REPO_DIR"
PATH="$BIN_DIR:$PATH" \
HOME="$HOME_DIR" \
MOSAIC_CREDENTIALS_FILE="$CREDENTIALS_FILE" \
MOSAIC_TEST_LOG="$LOG_FILE" \
"$@"
@@ -310,7 +283,6 @@ run_in_repo2() {
(
cd "$REPO_DIR"
PATH="$BIN_DIR2:$PATH" \
HOME="$HOME_DIR" \
MOSAIC_CREDENTIALS_FILE="$CREDENTIALS_FILE" \
MOSAIC_TEST_LOG="$LOG_FILE" \
"$@"
@@ -371,7 +343,7 @@ write_fixture() { printf '%s' "$1" > "$FIXTURE_XDG/tea/config.yml"; }
token_fallback() {
(
cd "$REPO_DIR"
HOME="$HOME_DIR" XDG_CONFIG_HOME="$FIXTURE_XDG" PYTHONPATH="$NOYAML_DIR" bash -c '
XDG_CONFIG_HOME="$FIXTURE_XDG" PYTHONPATH="$NOYAML_DIR" bash -c '
source "'"$SCRIPT_DIR"'/detect-platform.sh"
get_gitea_token_for_login "$1" "$2"
' _ "$1" "$2"
@@ -382,7 +354,7 @@ token_fallback() {
token_pyyaml() {
(
cd "$REPO_DIR"
HOME="$HOME_DIR" XDG_CONFIG_HOME="$FIXTURE_XDG" bash -c '
XDG_CONFIG_HOME="$FIXTURE_XDG" bash -c '
source "'"$SCRIPT_DIR"'/detect-platform.sh"
get_gitea_token_for_login "$1" "$2"
' _ "$1" "$2"
@@ -61,54 +61,15 @@ STATE_FILE="$WORK_DIR/comments.json"
# 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.
TMP_SCRATCH="$WORK_DIR/scratch"
HOME_DIR="$WORK_DIR/home"
cleanup() {
rm -rf "$WORK_DIR"
}
trap cleanup EXIT
mkdir -p "$REPO_DIR" "$BIN_DIR" "$XDG_DIR" "$TMP_SCRATCH" "$HOME_DIR"
mkdir -p "$REPO_DIR" "$BIN_DIR" "$XDG_DIR" "$TMP_SCRATCH"
git -C "$REPO_DIR" init -q
git -C "$REPO_DIR" remote add origin https://git.mosaicstack.dev/mosaicstack/stack.git
# HERMETICITY (#1007) — TWO mechanisms with DIFFERENT jobs; do not conflate them.
#
# OPERATIVE: the empty repo-local `mosaic.gitIdentity` below. 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 so 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 credential below is silently
# ignored. The stub curl then rejects the unrecognised bearer, and this suite
# fails at its FIRST case with `Gitea authenticated-identity read failed with
# HTTP 401`. An empty repo-local value shadows the global one and reads back
# empty at rc=0. Measured: without this pin the suite is RED on every seat.
#
# CONTAINMENT: the sandboxed HOME in run_comment(). It only has to bound a
# failure that the pin should already have prevented.
#
# THIS SUITE WAS THE HARDEST OF THE FIVE TO SEE, and the reason is worth stating
# because it generalises: run_comment() sends the wrapper's stdout AND stderr to
# $OUTPUT_FILE, and the EXIT trap above deletes $WORK_DIR. So the 401 — the only
# thing that says what went wrong — exists only inside a directory that is gone
# by the time anyone looks. The suite exits 1 with ZERO bytes on stdout and
# stderr. A suite that discards or deletes its own evidence turns any post-hoc
# assay into a non-measurement: "nothing found" there means "no surviving
# trace", never "clean". It was found by intercepting the identity read at its
# SOURCE (a PATH shim over `git` logging every `mosaic.gitIdentity` read to a
# file outside $WORK_DIR), which is deletion-proof by construction, rather than
# by grepping for the symptom.
#
# NOTE FOR ANYONE AUDITING THIS SUITE: the sandboxed HOME is containment, NOT an
# assay. Running a suite under a decoy HOME to test for this defect REMOVES the
# trigger — ~/.gitconfig is where the global identity lives, so step 0 is skipped
# by construction and every suite reads clean however vulnerable it is. To
# measure, REPLICATE a seat (a decoy HOME whose .gitconfig sets
# mosaic.gitIdentity, with no per-slot token) so step 0 reaches its fail-loud
# branch — or intercept the read as described above.
#
# Note the env-var route does NOT work: detect-platform.sh reads
# "${MOSAIC_GIT_IDENTITY:-}", and `:-` treats set-but-empty identically to unset.
git -C "$REPO_DIR" config mosaic.gitIdentity ""
ISSUE_NUMBER=7
REPO_SLUG="mosaicstack/stack"
@@ -405,7 +366,6 @@ run_comment() {
cd "$REPO_DIR"
PATH="$BIN_DIR:$PATH" \
TMPDIR="$TMP_SCRATCH" \
HOME="$HOME_DIR" \
XDG_CONFIG_HOME="$XDG_DIR" \
MOSAIC_CREDENTIALS_FILE="$CREDENTIALS_FILE" \
ISSUE_COMMENT_TEA_LOG="$TEA_LOG" \
@@ -7,38 +7,13 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
WORK_DIR="${MOSAIC_TEST_WORK_DIR:-$PWD/.mosaic-test-work/issue-create-interactive-auth}"
REPO_DIR="$WORK_DIR/repo"
BIN_DIR="$WORK_DIR/bin"
HOME_DIR="$WORK_DIR/home"
LOG_FILE="$WORK_DIR/calls.log"
CREDENTIALS_FILE="$WORK_DIR/credentials.json"
rm -rf "$WORK_DIR"
mkdir -p "$REPO_DIR" "$BIN_DIR" "$HOME_DIR"
mkdir -p "$REPO_DIR" "$BIN_DIR"
git -C "$REPO_DIR" init -q
git -C "$REPO_DIR" remote add origin https://git.mosaicstack.dev/mosaicstack/stack.git
# HERMETICITY (#1007) — TWO mechanisms with DIFFERENT jobs; do not conflate them.
#
# OPERATIVE: the empty repo-local `mosaic.gitIdentity` below. 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 so 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 credential below is silently
# ignored and the suite runs against a production credential. An empty repo-local
# value shadows the global one and reads back empty at rc=0. Measured: this suite
# resolves a per-slot token without it.
#
# CONTAINMENT: the sandboxed HOME in run_wrapper(). It only has to bound a failure
# that the pin should already have prevented.
#
# NOTE FOR ANYONE AUDITING THIS SUITE: the sandboxed HOME is containment, NOT an
# assay. Running a suite under a decoy HOME to test for this defect REMOVES the
# trigger — ~/.gitconfig is where the global identity lives, so step 0 is skipped
# by construction and every suite reads clean however vulnerable it is. To measure,
# REPLICATE a seat (a decoy HOME whose .gitconfig sets mosaic.gitIdentity, with no
# per-slot token) so step 0 reaches its fail-loud branch.
#
# Note the env-var route does NOT work: detect-platform.sh reads
# "${MOSAIC_GIT_IDENTITY:-}", and `:-` treats set-but-empty identically to unset.
git -C "$REPO_DIR" config mosaic.gitIdentity ""
cat > "$CREDENTIALS_FILE" <<'JSON'
{"gitea":{"mosaicstack":{"url":"https://git.mosaicstack.dev","token":"test-token"}}}
@@ -75,7 +50,6 @@ run_wrapper() {
(
cd "$REPO_DIR"
PATH="$BIN_DIR:$PATH" \
HOME="$HOME_DIR" \
MOSAIC_CREDENTIALS_FILE="$CREDENTIALS_FILE" \
MOSAIC_TEST_LOG="$LOG_FILE" \
"$@"
@@ -8,7 +8,6 @@ WORK_ROOT="${AGENT_WORK_ROOT:-${HOME:-/tmp}/mosaic/agent-work}"
SANDBOX="$WORK_ROOT/pr-merge-empty-uid-test-$$"
MOCK_BIN="$SANDBOX/bin"
REPO_DIR="$SANDBOX/repo"
HOME_DIR="$SANDBOX/home"
LOG_FILE="$SANDBOX/mock.log"
cleanup() {
@@ -16,7 +15,7 @@ cleanup() {
}
trap cleanup EXIT
mkdir -p "$MOCK_BIN" "$REPO_DIR" "$HOME_DIR"
mkdir -p "$MOCK_BIN" "$REPO_DIR"
: > "$LOG_FILE"
cat > "$MOCK_BIN/tea" <<'EOF'
@@ -100,48 +99,7 @@ chmod +x "$MOCK_BIN/curl"
cd "$REPO_DIR"
git init -q
git remote add origin https://git.mosaicstack.dev/mosaicstack/stack.git
# HERMETICITY (#1007) — TWO mechanisms with DIFFERENT jobs; do not conflate them.
#
# OPERATIVE: the empty repo-local `mosaic.gitIdentity` below. 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 so leaks into this fresh
# repo. Step 0 runs BEFORE the credential loader AND before the GITEA_TOKEN env
# check, so the `GITEA_TOKEN=redacted-test-token` exported below is silently
# overridden and a REAL per-slot token from $HOME is what flows through the
# wrapper. Measured on a provisioned seat before this pin: all 5 mock-curl calls
# carried the real per-slot token in argv and the fixture token was never used at
# ALL. Three consequences specific to this suite:
# 1. pr-merge.sh passes the token as `-H "Authorization: token $token"` and the
# mock curl logs full argv, so the real credential is written to $LOG_FILE
# on disk — transiently: the suite truncates that file between phases and
# the EXIT trap removes $SANDBOX, so it leaves NO post-hoc trace. That is
# why this suite was the hardest of the three to detect; observing it needs
# an instrument that captures argv while the run is live.
# 2. Every failure path dumps $OUTPUT/$LOG_FILE to stderr through
# `sed 's/redacted-test-token/***REDACTED***/g'` — a redaction pattern that
# is the literal fixture string and therefore CANNOT match the token
# actually in use.
# 3. The leak assertion at "Token leaked to pr-merge.sh output" greps for that
# same fixture string, so on a provisioned seat it passes vacuously: it is
# searching for a value the run never used.
# An empty repo-local value shadows the global one and reads back empty at rc=0.
#
# CONTAINMENT: the sandboxed HOME exported below. It only has to bound a failure
# that the pin should already have prevented.
#
# NOTE FOR ANYONE AUDITING THIS SUITE: the sandboxed HOME is containment, NOT an
# assay. Running a suite under a decoy HOME to test for this defect REMOVES the
# trigger — ~/.gitconfig is where the global identity lives, so step 0 is skipped
# by construction and every suite reads clean however vulnerable it is. To measure,
# REPLICATE a seat (a decoy HOME whose .gitconfig sets mosaic.gitIdentity, with no
# per-slot token) so step 0 reaches its fail-loud branch.
#
# Note the env-var route does NOT work: detect-platform.sh reads
# "${MOSAIC_GIT_IDENTITY:-}", and `:-` treats set-but-empty identically to unset.
git -C "$REPO_DIR" config mosaic.gitIdentity ""
# $SANDBOX/$HOME_DIR were derived from the real $HOME above, before this export.
export HOME="$HOME_DIR"
export PATH="$MOCK_BIN:$PATH"
export PR_MERGE_TEST_LOG="$LOG_FILE"
export GITEA_LOGIN="git.mosaicstack.dev"
@@ -8,68 +8,12 @@ WORK_DIR="${MOSAIC_TEST_WORK_DIR:-$PWD/.mosaic-test-work/pr-metadata-gitea}"
REPO_DIR="$WORK_DIR/repo"
FIXTURE_DIR="$WORK_DIR/fixtures"
STUB_DIR="$WORK_DIR/stubs"
HOME_DIR="$WORK_DIR/home"
CREDENTIALS_FILE="$WORK_DIR/credentials.json"
rm -rf "$WORK_DIR"
mkdir -p "$REPO_DIR" "$FIXTURE_DIR" "$STUB_DIR" "$HOME_DIR"
mkdir -p "$REPO_DIR" "$FIXTURE_DIR" "$STUB_DIR"
git -C "$REPO_DIR" init -q
git -C "$REPO_DIR" remote add origin https://git.uscllc.com/USC/uconnect.git
# HERMETICITY (#1007) — TWO mechanisms with DIFFERENT jobs; do not conflate them.
#
# OPERATIVE: the empty repo-local `mosaic.gitIdentity` below. 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 so leaks into this fresh
# repo. Step 0 runs BEFORE the credential loader AND before the GITEA_TOKEN env
# check, so the `GITEA_TOKEN="stub-token"` set in the run helpers below is
# silently overridden and a REAL per-slot token from $HOME is what reaches curl.
# Measured on a provisioned seat before this pin: both stub-curl calls carried
# the real token in argv. An empty repo-local value shadows the global one and
# reads back empty at rc=0.
#
# CONTAINMENT: the sandboxed HOME in the three run helpers below. It only has to
# bound a failure that the pin should already have prevented.
#
# NOTE FOR ANYONE AUDITING THIS SUITE: the sandboxed HOME is containment, NOT an
# assay. Running a suite under a decoy HOME to test for this defect REMOVES the
# trigger — ~/.gitconfig is where the global identity lives, so step 0 is skipped
# by construction and every suite reads clean however vulnerable it is. To measure,
# REPLICATE a seat (a decoy HOME whose .gitconfig sets mosaic.gitIdentity, with no
# per-slot token) so step 0 reaches its fail-loud branch. See
# test-gitea-token-identity.sh for the stronger `env -i HOME=…` form used where a
# suite's whole subject IS identity resolution.
#
# Note the env-var route does NOT work: detect-platform.sh reads
# "${MOSAIC_GIT_IDENTITY:-}", and `:-` treats set-but-empty identically to unset.
git -C "$REPO_DIR" config mosaic.gitIdentity ""
# The pin above removes step 0, but this suite has a SECOND, independent
# dependency on operator state, and closing only the first would leave the suite
# red on any hermetic environment. The `GITEA_TOKEN="stub-token"` /
# `GITEA_URL="https://git.example.test"` pair the run helpers set is INERT: step 2
# of get_gitea_token accepts GITEA_TOKEN only when GITEA_URL matches the remote
# host, and this repo's origin is git.uscllc.com, so that pair can never satisfy
# it. Before this fixture the only credential that could reach the authenticated
# curl branch was a REAL one — from step 0 on an agent seat, or from step 1
# reading the operator's own ~/.config/mosaic/credentials.json. That is why the
# "curl success path" case passed: not because the stub credential worked, but
# because a production credential was available.
#
# A fixture is used rather than relying on the sandboxed HOME making step 1 find
# nothing: a test that passes because production configuration is ABSENT fails
# the moment it is present. Step 1 now resolves deterministically to a value that
# is a fixture on every machine.
cat > "$CREDENTIALS_FILE" <<'JSON'
{
"gitea": {
"usc": {
"url": "https://git.uscllc.com",
"token": "stub-token"
}
}
}
JSON
cat > "$FIXTURE_DIR/gitea-standard.json" <<'JSON'
{
@@ -187,8 +131,6 @@ run_curl_success_case() {
set +e
output=$(cd "$REPO_DIR" && \
PATH="$STUB_DIR:$PATH" \
HOME="$HOME_DIR" \
MOSAIC_CREDENTIALS_FILE="$CREDENTIALS_FILE" \
TMPDIR="$tmpdir" \
GITEA_TOKEN="stub-token" \
GITEA_URL="https://git.example.test" \
@@ -228,8 +170,6 @@ run_curl_early_exit_cleanup_case() {
set +e
output=$(cd "$REPO_DIR" && \
PATH="$STUB_DIR:$PATH" \
HOME="$HOME_DIR" \
MOSAIC_CREDENTIALS_FILE="$CREDENTIALS_FILE" \
TMPDIR="$tmpdir" \
GITEA_TOKEN="stub-token" \
GITEA_URL="https://git.example.test" \
@@ -264,8 +204,7 @@ run_curl_early_exit_cleanup_case() {
run_case() {
local fixture="$1" expected_number="$2" expected_head="$3"
local output
output=$(cd "$REPO_DIR" && HOME="$HOME_DIR" MOSAIC_CREDENTIALS_FILE="$CREDENTIALS_FILE" \
MOSAIC_GITEA_PR_METADATA_RAW_FILE="$fixture" "$SCRIPT_DIR/pr-metadata.sh" -n "$expected_number")
output=$(cd "$REPO_DIR" && MOSAIC_GITEA_PR_METADATA_RAW_FILE="$fixture" "$SCRIPT_DIR/pr-metadata.sh" -n "$expected_number")
PR_METADATA_OUTPUT="$output" python3 - "$expected_number" "$expected_head" <<'PY'
import json
import os
@@ -58,9 +58,6 @@ CREDENTIALS_FILE="$WORK_DIR/credentials.json"
# 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.
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() {
rm -rf "$WORK_DIR"
@@ -81,18 +78,9 @@ OVERRIDE_TOKEN="override-token-placeholder"
CROSS_HOST_LOGIN="foreign-host-reviewer"
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" 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
# 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:
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
# 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.
@@ -547,8 +517,6 @@ run_review() {
cd "$REPO_DIR"
PATH="$BIN_DIR:$PATH" \
TMPDIR="$TMP_SCRATCH" \
HOME="$HOME_DIR" \
MOSAIC_GIT_IDENTITY="" \
XDG_CONFIG_HOME="$XDG_DIR" \
MOSAIC_CREDENTIALS_FILE="$CREDENTIALS_FILE" \
PR_REVIEW_TEA_LOG="$TEA_LOG" \
@@ -972,44 +940,4 @@ if grep -q 'Approved and verified' "$OUTPUT_FILE"; then
fi
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"
@@ -191,177 +191,3 @@ _wake_init_dir() {
[ -f "$dir/pending.jsonl" ] || printf '' | _atomic_write "$dir/pending.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
}
@@ -465,26 +465,8 @@
# never a hand-built flat dead-letter row, which would make the
# audit's correct non-conviction look exactly like the defect
# 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
version=0.7.2
version=0.7.1
# Watch-list schema this component consumes, and the INCLUSIVE range of
# schema_version values it supports. A wake-watch-list.json whose schema_version
@@ -31,13 +31,6 @@
set -uo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# #973: three-valued grep assertion helpers (has_match/count_lines); init saves real stderr for abort loudness.
# shellcheck disable=SC1091
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"
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)"
rc=$?
[ "$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)"
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).
@@ -157,13 +150,13 @@ echo "== B3: unconfigured OR unreachable ALARM target -> FAIL LOUD =="
out="$("$BEACON" check --slo-seconds 5 2>&1)"
rc=$?
[ "$rc" -eq 3 ] || fail_msg "B3a: unconfigured alarm target must FAIL LOUD (exit 3); got $rc [$out]"
echo "$out" | 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).
export WAKE_ALARM_SINK_CMD="false"
out2="$("$BEACON" check --slo-seconds 5 2>&1)"
rc2=$?
[ "$rc2" -eq 3 ] || fail_msg "B3b: unreachable alarm target must FAIL LOUD (exit 3); got $rc2 [$out2]"
echo "$out2" | 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
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=$?
err="$(cat "$TMP_ROOT/b4.err")"
[ "$rc" -eq 0 ] || fail_msg "B4: a different-supervision-root emit must still succeed (exit 0); got $rc [$out][$err]"
echo "$err" | has_match -qi 'DEGRADED' || fail_msg "B4: a different-supervision-root beacon must be FLAGGED degraded on stderr [$err]"
echo "$out" | has_match -q 'degraded=true' || fail_msg "B4: emit must NOT silently present a degraded beacon as healthy (degraded=true expected) [$out]"
echo "$err" | grep -qi 'DEGRADED' || fail_msg "B4: a different-supervision-root beacon must be FLAGGED degraded on stderr [$err]"
echo "$out" | grep -q 'degraded=true' || fail_msg "B4: emit must NOT silently present a degraded beacon as healthy (degraded=true expected) [$out]"
) && ok
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)"
rc=$?
[ "$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).
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)"
@@ -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)"
rc=$?
[ "$rc" -eq 1 ] || fail_msg "B6: absence via a by-name alarm adapter must fire (exit 1); got $rc [$out]"
head -n1 "$ALARM_OUT" 2>/dev/null | 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.
has_match -qF "$SECRET_TARGET" "$BEACON" && fail_msg "B6: beacon.sh must NOT inline the target endpoint/secret"
has_match -qF "$SECRET_TARGET" "$WAKE_ALARM_SINK_CMD" && fail_msg "B6: even the adapter must resolve by-name, not inline the secret"
grep -qF "$SECRET_TARGET" "$BEACON" && fail_msg "B6: beacon.sh must NOT inline the target endpoint/secret"
grep -qF "$SECRET_TARGET" "$WAKE_ALARM_SINK_CMD" && fail_msg "B6: even the adapter must resolve by-name, not inline the secret"
true
) && ok
@@ -225,7 +218,7 @@ echo "== B7: unconfigured OR unreachable BEACON sink on emit -> FAIL LOUD =="
out="$("$BEACON" emit 2>&1)"
rc=$?
[ "$rc" -eq 3 ] || fail_msg "B7a: unconfigured beacon sink must FAIL LOUD (exit 3); got $rc [$out]"
echo "$out" | 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.
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)"
@@ -234,7 +227,7 @@ echo "== B7: unconfigured OR unreachable BEACON sink on emit -> FAIL LOUD =="
out2="$("$BEACON" emit 2>&1)"
rc2=$?
[ "$rc2" -eq 1 ] || fail_msg "B7b: unreachable beacon sink must FAIL LOUD (exit 1); got $rc2 [$out2]"
echo "$out2" | 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
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)"
rc=$?
[ "$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
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)"
rc=$?
[ "$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"
) && 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"
out="$("$BEACON" check --slo-seconds 5 2>&1)"; rc=$?
[ "$rc" -eq 1 ] || fail_msg "B11: a far-future emit_ts must NOT defer staleness — receive-time governs (expected absence exit 1); got $rc [$out]"
echo "$out" | 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)]"
) && ok
@@ -336,7 +329,7 @@ else
jq -c '.beacon_seq = 99999 | .host_id = "attacker"' "$SHIPPED" >"$spoof"
out="$("$BEACON" record <"$spoof" 2>&1)"; rc=$?
[ "$rc" -ne 0 ] || fail_msg "B12: a spoofed beacon (altered fields) must be REJECTED at record; got rc=$rc [$out]"
echo "$out" | 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)"
# (c) an UNSIGNED beacon is rejected when signing is configured.
unsigned="$H/unsigned.json"
@@ -344,14 +337,14 @@ else
out2="$("$BEACON" record <"$unsigned" 2>&1)"; rc2=$?
[ "$rc2" -ne 0 ] || fail_msg "B12: an unsigned beacon must be REJECTED when signing is configured; got rc=$rc2 [$out2]"
# beacon.sh must not inline the key material.
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
) && ok
fi
echo
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
fi
echo "wake beacon harness: all invariants passed ($pass groups)"
@@ -29,13 +29,6 @@
set -uo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# #973: three-valued grep assertion helpers (has_match/count_lines); init saves real stderr for abort loudness.
# shellcheck disable=SC1091
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"
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)"
rc=$?
[ "$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" | has_match -qi 'range' || fail_msg "D5: the failure must state it is out of the supported range [$err]"
echo "$err" | grep -qi 'schema_version' || fail_msg "D5: the failure must name schema_version [$err]"
echo "$err" | grep -qi 'range' || fail_msg "D5: the failure must state it is out of the supported range [$err]"
# And nothing was enqueued / no cursor advance under a rejected watch-list.
[ "$(depth)" = "0" ] || fail_msg "D5: a rejected watch-list must not enqueue, got depth $(depth)"
[ "$(det_seq)" = "0" ] || fail_msg "D5: a rejected watch-list must not advance observed_seq, got $(det_seq)"
@@ -294,7 +287,7 @@ echo "== D6: source error / ambiguous-empty -> FAIL LOUD, observed_seq NOT advan
err="$("$DET" poll-once 2>&1)"
rc=$?
[ "$rc" -ne 0 ] || fail_msg "D6: a source error must FAIL LOUD (non-zero exit)"
echo "$err" | 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)"
[ "$(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)"
rc=$?
[ "$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))"
[ "$(depth)" = "0" ] || fail_msg "D6: ambiguous-empty must NOT enqueue, got depth $(depth)"
@@ -449,7 +442,7 @@ EOF
write_fc_watchlist "$wl" 999
err="$("$DET" poll-once 2>&1)"; rc=$?
[ "$rc" -ne 0 ] || fail_msg "D9: the new field must NOT weaken Gate B — an out-of-range schema_version must still FAIL LOUD (rc=$rc)"
echo "$err" | 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
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"
err="$("$DET" poll-once 2>&1 >/dev/null)"; rc=$?
[ "$rc" -eq 0 ] || fail_msg "D11: malformed metadata must NEVER fail the poll (rc=$rc)"
echo "$err" | 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)"
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]"
@@ -529,7 +522,7 @@ EOF
printf 'SHA-CCC\n' >"$stub/repo_r1"
err="$("$DET" poll-once 2>&1 >/dev/null)"; rc=$?
[ "$rc" -eq 0 ] || fail_msg "D11: rejected snapshot_sha must NEVER fail the poll (rc=$rc)"
echo "$err" | 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)"
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]"
@@ -567,7 +560,7 @@ EOF
printf 'SHA-BBB\n' >"$stub/repo_r1"
err="$("$DET" poll-once 2>&1 >/dev/null)"; rc=$?
[ "$rc" -eq 0 ] || fail_msg "D12: ts-without-sha must NEVER fail the poll (rc=$rc)"
echo "$err" | 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)"
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]"
@@ -578,7 +571,7 @@ EOF
printf 'SHA-CCC\n' >"$stub/repo_r1"
err="$("$DET" poll-once 2>&1 >/dev/null)"; rc=$?
[ "$rc" -eq 0 ] || fail_msg "D12: future ts must NEVER fail the poll (rc=$rc)"
echo "$err" | 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)"
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]"
@@ -588,7 +581,7 @@ EOF
printf 'SHA-DDD\n' >"$stub/repo_r1"
err="$("$DET" poll-once 2>&1 >/dev/null)"; rc=$?
[ "$rc" -eq 0 ] || fail_msg "D12: absurd ts must NEVER fail the poll (rc=$rc)"
echo "$err" | 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)"
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]"
@@ -628,7 +621,7 @@ EOF
printf 'SHA-BBB\n' >"$stub/repo_r1"
err="$(WAKE_SNAPSHOT_TS_FUTURE_SLACK='300s' "$DET" poll-once 2>&1 >/dev/null)"; rc=$?
[ "$rc" -eq 0 ] || fail_msg "D13: SLACK='300s' must NEVER fail the poll (rc=$rc)"
echo "$err" | 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)"
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]"
@@ -637,7 +630,7 @@ EOF
printf 'SHA-CCC\n' >"$stub/repo_r1"
err="$(WAKE_SNAPSHOT_TS_FUTURE_SLACK='abc' "$DET" poll-once 2>&1 >/dev/null)"; rc=$?
[ "$rc" -eq 0 ] || fail_msg "D13: SLACK='abc' must NEVER fail the poll (rc=$rc)"
echo "$err" | 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)"
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]"
@@ -647,7 +640,7 @@ EOF
printf 'SHA-DDD\n' >"$stub/repo_r1"
err="$(WAKE_SNAPSHOT_TS_FUTURE_SLACK='-99999999' "$DET" poll-once 2>&1 >/dev/null)"; rc=$?
[ "$rc" -eq 0 ] || fail_msg "D13: negative SLACK must NEVER fail the poll (rc=$rc)"
echo "$err" | 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)"
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]"
@@ -659,7 +652,7 @@ EOF
printf 'SHA-CC2\n' >"$stub/repo_r1"
err="$(WAKE_SNAPSHOT_TS_FUTURE_SLACK=$'300\n8' "$DET" poll-once 2>&1 >/dev/null)"; rc=$?
[ "$rc" -eq 0 ] || fail_msg "D13: multi-line SLACK (300\\n8) must NEVER fail the poll (rc=$rc) [$err]"
echo "$err" | 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)"
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]"
@@ -667,7 +660,7 @@ EOF
printf 'SHA-CC3\n' >"$stub/repo_r1"
err="$(WAKE_SNAPSHOT_TS_FUTURE_SLACK=$'300\nabc' "$DET" poll-once 2>&1 >/dev/null)"; rc=$?
[ "$rc" -eq 0 ] || fail_msg "D13: multi-line SLACK (300\\nabc) must NEVER fail the poll (rc=$rc) [$err]"
echo "$err" | 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)"
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]"
@@ -694,7 +687,7 @@ EOF
printf 'SHA-EEE\n' >"$stub/repo_r1"
err="$(WAKE_SNAPSHOT_TS_FUTURE_SLACK='0' "$DET" poll-once 2>&1 >/dev/null)"; rc=$?
[ "$rc" -eq 0 ] || fail_msg "D13: valid SLACK=0 must not fail the poll (rc=$rc)"
echo "$err" | 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)"
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]"
@@ -703,7 +696,7 @@ EOF
echo
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
fi
echo "wake detector harness: all invariants passed ($pass groups)"
@@ -35,13 +35,6 @@
set -uo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# #973: three-valued grep assertion helpers (has_match/count_lines); init saves real stderr for abort loudness.
# shellcheck disable=SC1091
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"
DIGEST="$SCRIPT_DIR/digest.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 2 --class actionable --locators '{"repo":"r","issue":22}' >/dev/null
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" | has_match -q 'issue=#22' || fail_msg "D1: newer change (issue #22) missing"
echo "$out" | has_match -q 'seq 1' || fail_msg "D1: seq 1 not listed in cumulative set"
echo "$out" | has_match -q 'seq 2' || fail_msg "D1: seq 2 not listed in cumulative set"
echo "$out" | grep -q 'issue=#11' || fail_msg "D1: OLDER change (issue #11) dropped — digest is a delta, not cumulative state"
echo "$out" | grep -q 'issue=#22' || fail_msg "D1: newer change (issue #22) missing"
echo "$out" | grep -q 'seq 1' || fail_msg "D1: seq 1 not listed in cumulative set"
echo "$out" | grep -q 'seq 2' || fail_msg "D1: seq 2 not listed in cumulative set"
# A coalescing digest-class entry is STATE (full), not a delta: a later digest
# subsumes the earlier, but the cumulative unacked set (both actionables) stays.
echo "$out" | 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
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
# preserved, now per-entry). The old exit-4 wedged the entire drain (#920).
[ "$rc" -eq 0 ] || fail_msg "D2: a malformed actionable must be quarantined (render exit 0, #920), got $rc"
printf '%s' "$out" | has_match -q 'mergeable=true' && fail_msg "D2: an unlocated actionable claim must NOT be delivered as a valid CLAIM"
has_match -q 'mergeable=true' "$h/default/dead-letter.jsonl" 2>/dev/null || fail_msg "D2: the malformed claim must be DEAD-LETTERED (fail-loud preserved, per-entry)"
has_match -qi 'QUARANTINE' "$err" || fail_msg "D2: a LOUD per-entry alarm must fire for the malformed claim"
printf '%s' "$out" | grep -q 'mergeable=true' && fail_msg "D2: an unlocated actionable claim must NOT be delivered as a valid CLAIM"
grep -q 'mergeable=true' "$h/default/dead-letter.jsonl" 2>/dev/null || fail_msg "D2: the malformed claim must be DEAD-LETTERED (fail-loud preserved, per-entry)"
grep -qi 'QUARANTINE' "$err" || fail_msg "D2: a LOUD per-entry alarm must fire for the malformed claim"
# A present-but-imprecise sha (not 40 hex) does NOT satisfy the hard locator ->
# quarantined, not delivered.
@@ -147,8 +140,8 @@ echo "== D2: HARD-LOCATOR enforcement — a malformed actionable claim is QUARAN
rc=0
out="$("$DIGEST" render 2>/dev/null)" || rc=$?
[ "$rc" -eq 0 ] || fail_msg "D2: imprecise-sha entry must be quarantined (render exit 0), got $rc"
printf '%s' "$out" | has_match -q 'ci=green' && fail_msg "D2: imprecise sha (not 40-hex) must not satisfy the hard-locator gate (claim must not deliver)"
has_match -q 'ci=green' "$h/default/dead-letter.jsonl" 2>/dev/null || fail_msg "D2: imprecise-sha claim must be dead-lettered"
printf '%s' "$out" | grep -q 'ci=green' && fail_msg "D2: imprecise sha (not 40-hex) must not satisfy the hard-locator gate (claim must not deliver)"
grep -q 'ci=green' "$h/default/dead-letter.jsonl" 2>/dev/null || fail_msg "D2: imprecise-sha claim must be dead-lettered"
# The SAME claim WITH a precise 40-hex sha renders fine (delivered, not quarantined).
h="$(fresh_state d2c)"
@@ -156,7 +149,7 @@ echo "== D2: HARD-LOCATOR enforcement — a malformed actionable claim is QUARAN
export WAKE_STATE_HOME
"$STORE" enqueue --seq 1 --class actionable --locators "$(jq -cn --arg s "$SHA40" '{claim:"ci=green",sha:$s}')" >/dev/null
out="$("$DIGEST" render 2>/dev/null)" || fail_msg "D2: a well-located actionable claim must render"
printf '%s' "$out" | 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"
# --- #905 bypass-prevention (STILL enforced, now via quarantine): a NON-
# 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
out="$(printf '%s\n' "$toplevel_claim_entry" | "$DIGEST" render --stdin 2>/dev/null)" || rc=$?
[ "$rc" -eq 0 ] || fail_msg "D2(#905): top-level .claim must be quarantined via --stdin (exit 0), got $rc"
printf '%s' "$out" | has_match -q 'mergeable=true' && fail_msg "D2(#905): --stdin top-level-.claim must NOT be delivered (gate not bypassed)"
has_match -q 'mergeable=true' "$h/default/dead-letter.jsonl" 2>/dev/null || fail_msg "D2(#905): --stdin top-level .claim must be dead-lettered (gate enforced)"
printf '%s' "$out" | grep -q 'mergeable=true' && fail_msg "D2(#905): --stdin top-level-.claim must NOT be delivered (gate not bypassed)"
grep -q 'mergeable=true' "$h/default/dead-letter.jsonl" 2>/dev/null || fail_msg "D2(#905): --stdin top-level .claim must be dead-lettered (gate enforced)"
ff="$TMP_ROOT/d2d-entry.jsonl"
printf '%s\n' "$toplevel_claim_entry" >"$ff"
h="$(fresh_state d2e)"
@@ -181,8 +174,8 @@ echo "== D2: HARD-LOCATOR enforcement — a malformed actionable claim is QUARAN
rc=0
out2="$("$DIGEST" render --from-file "$ff" 2>/dev/null)" || rc=$?
[ "$rc" -eq 0 ] || fail_msg "D2(#905): top-level .claim must be quarantined via --from-file (exit 0), got $rc"
printf '%s' "$out2" | has_match -q 'mergeable=true' && fail_msg "D2(#905): --from-file top-level-.claim must NOT be delivered (gate not bypassed)"
has_match -q 'mergeable=true' "$h/default/dead-letter.jsonl" 2>/dev/null || fail_msg "D2(#905): --from-file top-level .claim must be dead-lettered (gate enforced)"
printf '%s' "$out2" | grep -q 'mergeable=true' && fail_msg "D2(#905): --from-file top-level-.claim must NOT be delivered (gate not bypassed)"
grep -q 'mergeable=true' "$h/default/dead-letter.jsonl" 2>/dev/null || fail_msg "D2(#905): --from-file top-level .claim must be dead-lettered (gate enforced)"
) && ok
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
# No pending obligations => no-op common case.
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)"
# Now an actionable, consequential fact. It must be a CLAIM-TO-VERIFY, never
# 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"
# Rendering itself still makes ZERO live calls (it only lays out claims).
[ -e "$WAKE_STATE_HOME/TOOL_CALLED" ] && fail_msg "D3: rendering an actionable claim made a live call (must defer to the consumer's gate)"
echo "$out2" | has_match -q 'CLAIM@seq' || fail_msg "D3: consequential fact not framed as CLAIM@seq"
echo "$out2" | has_match -qi 'VERIFY LIVE' || fail_msg "D3: claim not marked for live verification"
echo "$out2" | has_match -qi 'do NOT act on this line' || fail_msg "D3: claim missing do-not-auto-action framing"
echo "$out2" | grep -q 'CLAIM@seq' || fail_msg "D3: consequential fact not framed as CLAIM@seq"
echo "$out2" | grep -qi 'VERIFY LIVE' || fail_msg "D3: claim not marked for live verification"
echo "$out2" | grep -qi 'do NOT act on this line' || fail_msg "D3: claim missing do-not-auto-action framing"
# 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)"
# 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
) && 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
out="$("$DIGEST" render)" || fail_msg "D4: render failed"
# 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.
# #912: patterns are LITERAL bytes + `grep -E`, NOT PCRE `grep -P`. BusyBox
# 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')"
_baa="$(printf '%b' '\xaa')"; _bae="$(printf '%b' '\xae')"
_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"
# C0 control bytes (except tab/newline) must be gone.
_c00="$(printf '%b' '\x01')"; _c08="$(printf '%b' '\x08')"
_c0e="$(printf '%b' '\x0e')"; _c1f="$(printf '%b' '\x1f')"; _c7f="$(printf '%b' '\x7f')"
printf '%s' "$out" | LC_ALL=C 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"
# 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" | has_match -q 'AKIAIOSFODNN7EXAMPLE' && fail_msg "D4: AWS-key canary LEAKED into the digest"
printf '%s' "$out" | has_match -q 'REDACTED-SECRET' || fail_msg "D4: secret redaction marker absent — canary may not have been scrubbed"
printf '%s' "$out" | grep -q 'ghp_0123456789' && fail_msg "D4: GitHub-token canary LEAKED into the digest"
printf '%s' "$out" | grep -q 'AKIAIOSFODNN7EXAMPLE' && fail_msg "D4: AWS-key canary LEAKED into the digest"
printf '%s' "$out" | grep -q 'REDACTED-SECRET' || fail_msg "D4: secret redaction marker absent — canary may not have been scrubbed"
# 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.
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
) && 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"
echo "$env1" | jq -e .wake_mac >/dev/null || fail_msg "H2: no MAC produced from by-name key"
# The key VALUE must never appear in the signed output.
echo "$env1" | 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.
entry="$(printf '{"observed_seq":1,"locators":{"repo":"r","issue":1},"class":"actionable","emit_ts":1700000000,"hmac":""}' |
WAKE_HMAC_KEY_NAME=signing-key "$SIGN" sign-entry)"
echo "$entry" | 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"
# 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).
@@ -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"
fi
# 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"
has_match -qi 'off-uid' "$SIGN" || fail_msg "H2: off-uid future signer not named in sign.sh"
grep -qi 'same-uid' "$SIGN" || fail_msg "H2: same-uid threat boundary not documented in sign.sh"
grep -qi 'off-uid' "$SIGN" || fail_msg "H2: off-uid future signer not named in sign.sh"
) && ok
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"
ack_line="$(printf '%s\n' "$out" | awk '/^-- ACK/{f=1;next} f && NF {print; exit}')"
[ -n "$ack_line" ] || fail_msg "D5: no embedded ack line extracted from the digest"
printf '%s' "$ack_line" | 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]"
# Actually RUN it with NO WAKE_AGENT in the environment (the real failure
# 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"}')"
"$STORE" enqueue --seq 1 --class digest --locators "$loc" >/dev/null
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"
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]"
# 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]"
# --- 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
out="$("$DIGEST" render 2>"$err")" || rc=$?
[ "$rc" -eq 0 ] || fail_msg "D6: a malformed actionable is now per-entry quarantined (render exit 0, #920), got $rc"
printf '%s' "$out" | has_match -q 'mergeable=true' && fail_msg "D6: a malformed actionable claim must NOT be delivered as valid (fail-loud preserved)"
has_match -q 'mergeable=true' "$h/default/dead-letter.jsonl" 2>/dev/null || fail_msg "D6: a malformed actionable must be DEAD-LETTERED (fail-loud preserved, per-entry)"
has_match -qi 'QUARANTINE' "$err" || fail_msg "D6: a malformed actionable must raise a loud per-entry alarm"
printf '%s' "$out" | grep -q 'mergeable=true' && fail_msg "D6: a malformed actionable claim must NOT be delivered as valid (fail-loud preserved)"
grep -q 'mergeable=true' "$h/default/dead-letter.jsonl" 2>/dev/null || fail_msg "D6: a malformed actionable must be DEAD-LETTERED (fail-loud preserved, per-entry)"
grep -qi 'QUARANTINE' "$err" || fail_msg "D6: a malformed actionable must raise a loud per-entry alarm"
) && ok
echo
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
fi
echo "wake digest/hmac harness: all invariants passed ($pass groups)"
@@ -117,13 +117,6 @@
set -uo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# #973: three-valued grep assertion helpers (has_match/count_lines); init saves real stderr for abort loudness.
# shellcheck disable=SC1091
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"
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")"
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" | has_match -q 'MALFORMED-Q' && fail_msg "Q1: the quarantined entry must be EXCLUDED from the rendered digest"
printf '%s' "$out" | grep -q "$SHA40" || fail_msg "Q1: the clean sibling (sha $SHA40) must STILL be delivered in the same drain [head-of-line block]"
printf '%s' "$out" | grep -q 'MALFORMED-Q' && fail_msg "Q1: the quarantined entry must be EXCLUDED from the rendered digest"
[ -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)"
has_match -qi 'QUARANTINE' "$err" || fail_msg "Q1: a LOUD per-entry alarm must fire on stderr"
has_match -q 'observed_seq=1' "$err" || fail_msg "Q1: the alarm must identify the offending entry (observed_seq=1)"
grep -q 'MALFORMED-Q' "$(dlq "$home")" 2>/dev/null || fail_msg "Q1: the malformed entry must be DEAD-LETTERED (accounted-for, not silently dropped)"
grep -qi 'QUARANTINE' "$err" || fail_msg "Q1: a LOUD per-entry alarm must fire on stderr"
grep -q 'observed_seq=1' "$err" || fail_msg "Q1: the alarm must identify the offending entry (observed_seq=1)"
) && ok
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")"
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" | has_match -q 'CLAIM@seq' && fail_msg "Q2: an enumeration must NOT render as an ACTIONABLE CLAIM@seq"
printf '%s' "$out" | grep -q 'id=ENUM-B' || fail_msg "Q2: the enumeration must render as an ORIENTATION pointer (id=ENUM-B via _locator_line)"
printf '%s' "$out" | grep -q 'CLAIM@seq' && fail_msg "Q2: an enumeration must NOT render as an ACTIONABLE CLAIM@seq"
[ -s "$(dlq "$home")" ] && fail_msg "Q2: an ORIENTATION-tier enumeration must NOT be quarantined/dead-lettered"
true
) && 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")"
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"
printf '%s' "$out" | has_match -q 'id=ENUM-C1' || fail_msg "Q3: enumeration ENUM-C1 must be present"
printf '%s' "$out" | has_match -q 'id=ENUM-C2' || fail_msg "Q3: enumeration ENUM-C2 must be present"
printf '%s' "$out" | grep -q 'id=ENUM-C1' || fail_msg "Q3: enumeration ENUM-C1 must be present"
printf '%s' "$out" | grep -q 'id=ENUM-C2' || fail_msg "Q3: enumeration ENUM-C2 must be present"
[ -s "$(dlq "$home")" ] && fail_msg "Q3: enumerations must NOT be quarantined"
true
) && 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")"
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" | has_match -q '(none) — no consequential claims pending' || fail_msg "Q4: with the only claim quarantined, the ACTIONABLE section must show (none)"
has_match -q 'CLAIM-KEEP' "$(dlq "$home")" 2>/dev/null || fail_msg "Q4: the malformed actionable must be DEAD-LETTERED (loudly surfaced, not silent)"
has_match -qi 'QUARANTINE' "$err" || fail_msg "Q4: the malformed actionable must raise a LOUD alarm"
printf '%s' "$out" | grep -q 'CLAIM-KEEP' && fail_msg "Q4: a malformed actionable must NOT be delivered as if valid"
printf '%s' "$out" | grep -q '(none) — no consequential claims pending' || fail_msg "Q4: with the only claim quarantined, the ACTIONABLE section must show (none)"
grep -q 'CLAIM-KEEP' "$(dlq "$home")" 2>/dev/null || fail_msg "Q4: the malformed actionable must be DEAD-LETTERED (loudly surfaced, not silent)"
grep -qi 'QUARANTINE' "$err" || fail_msg "Q4: the malformed actionable must raise a LOUD alarm"
) && ok
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")"
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)"
printf '%s' "$out" | has_match -q 'CI is green' && fail_msg "Q5: the un-verifiable claim must NOT be delivered"
grep -q 'CLAIMY' "$(dlq "$home")" 2>/dev/null || fail_msg "Q5: a claim-carrying entry with no hard locator must be quarantined (claim precedence, §2.1)"
printf '%s' "$out" | grep -q 'CI is green' && fail_msg "Q5: the un-verifiable claim must NOT be delivered"
true
) && 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")"
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)]"
has_match -q '"observed_seq":21' "$ALARM_OUT" 2>/dev/null || fail_msg "Q6: the routed alarm payload must name the entry's observed_seq (21)"
has_match -qi 'QUARANTINE' "$err" || fail_msg "Q6: the existing #920 stderr diagnostic must STILL fire (local + off-host, not either/or)"
printf '%s' "$out" | has_match -q 'DLQ-Q6' && fail_msg "Q6: the quarantined entry must still be EXCLUDED from the rendered digest"
grep -q '"observed_seq":21' "$ALARM_OUT" 2>/dev/null || fail_msg "Q6: the routed alarm payload must name the entry's observed_seq (21)"
grep -qi 'QUARANTINE' "$err" || fail_msg "Q6: the existing #920 stderr diagnostic must STILL fire (local + off-host, not either/or)"
printf '%s' "$out" | grep -q 'DLQ-Q6' && fail_msg "Q6: the quarantined entry must still be EXCLUDED from the rendered digest"
true
) && 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
"$DIGEST" render --from-file "$f" --agent default >/dev/null 2>"$TMP_ROOT/q7.err.$i"
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)]"
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
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 # re-drain seq 31 -> must NOT re-alarm
"$DIGEST" render --from-file "$f2" --agent default >/dev/null 2>/dev/null # NEW distinct seq 32 -> its own alarm
n="$(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)]"
has_match -q '"observed_seq":31' "$ALARM_OUT" 2>/dev/null || fail_msg "Q8: seq 31's alarm must be present"
has_match -q '"observed_seq":32' "$ALARM_OUT" 2>/dev/null || fail_msg "Q8: seq 32's (the new distinct entry's) OWN alarm must be present"
grep -q '"observed_seq":31' "$ALARM_OUT" 2>/dev/null || fail_msg "Q8: seq 31's alarm must be present"
grep -q '"observed_seq":32' "$ALARM_OUT" 2>/dev/null || fail_msg "Q8: seq 32's (the new distinct entry's) OWN alarm must be present"
) && ok
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")"
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")]"
has_match -Eqi 'silent no-alarm|silent-miss|PERMANENTLY miss|permanent silent miss' "$err_a" || fail_msg "Q9a: the diagnostic must name the silent-miss hazard (G2a), mirroring beacon.sh's fail-closed wording [$(cat "$err_a")]"
printf '%s' "$out_a" | has_match -q 'DLQ-Q9' && fail_msg "Q9a: the quarantined entry must still be EXCLUDED from the rendered digest"
grep -qi 'FAIL LOUD' "$err_a" || fail_msg "Q9a: an unconfigured off-host alarm target must FAIL LOUD on stderr [$(cat "$err_a")]"
grep -Eqi 'silent no-alarm|silent-miss|PERMANENTLY miss|permanent silent miss' "$err_a" || fail_msg "Q9a: the diagnostic must name the silent-miss hazard (G2a), mirroring beacon.sh's fail-closed wording [$(cat "$err_a")]"
printf '%s' "$out_a" | grep -q 'DLQ-Q9' && fail_msg "Q9a: the quarantined entry must still be EXCLUDED from the rendered digest"
true
) && 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")"
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")]"
has_match -qi 'UNREACHABLE' "$err_b" || fail_msg "Q9b: the diagnostic must name the unreachable target [$(cat "$err_b")]"
printf '%s' "$out_b" | has_match -q 'DLQ-Q9' && fail_msg "Q9b: the quarantined entry must still be EXCLUDED from the rendered digest"
grep -qi 'FAIL LOUD' "$err_b" || fail_msg "Q9b: an unreachable off-host alarm target must FAIL LOUD on stderr [$(cat "$err_b")]"
grep -qi 'UNREACHABLE' "$err_b" || fail_msg "Q9b: the diagnostic must name the unreachable target [$(cat "$err_b")]"
printf '%s' "$out_b" | grep -q 'DLQ-Q9' && fail_msg "Q9b: the quarantined entry must still be EXCLUDED from the rendered digest"
true
) && 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)"
rc=$?
[ "$rc" -eq 0 ] || fail_msg "Q10: render must succeed (rc=$rc)"
snapa_line="$(printf '%s\n' "$out" | has_match 'id=SNAP-A' || true)"
printf '%s' "$snapa_line" | has_match -q 'snapshot_sha=0123abc4567890def0123abc4567890def012345' \
snapa_line="$(printf '%s\n' "$out" | grep 'id=SNAP-A' || true)"
printf '%s' "$snapa_line" | grep -q 'snapshot_sha=0123abc4567890def0123abc4567890def012345' \
|| fail_msg "Q10: snapshot_sha must render on the ORIENTATION pointer line [$snapa_line]"
printf '%s' "$snapa_line" | 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]"
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"
# The sibling without metadata must not grow empty snapshot_ fields.
snapb_line="$(printf '%s\n' "$out" | has_match 'id=SNAP-B' || true)"
printf '%s' "$snapb_line" | has_match -q 'snapshot_' \
snapb_line="$(printf '%s\n' "$out" | grep 'id=SNAP-B' || true)"
printf '%s' "$snapb_line" | grep -q 'snapshot_' \
&& fail_msg "Q10: an entry without metadata must render NO snapshot_ fields [$snapb_line]"
true
) && ok
@@ -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"
# (a) POSITIVE: the live entry RENDERS as an actionable claim (not merely
# passes the predicate) with the one-call git-show re-verify hint.
printf '%s' "$out" | has_match -q 'seq 68 — CLAIM@seq' || fail_msg "Q11a: the live seq-68 detector-shape entry must RENDER as CLAIM@seq (positive control)"
printf '%s' "$out" | has_match -q 'git show 55d4909569d2b5fbccfec49ec9ca83db5049f3ce:docs/scratchpads/heartbeat-planning' \
printf '%s' "$out" | grep -q 'seq 68 — CLAIM@seq' || fail_msg "Q11a: the live seq-68 detector-shape entry must RENDER as CLAIM@seq (positive control)"
printf '%s' "$out" | grep -q 'git show 55d4909569d2b5fbccfec49ec9ca83db5049f3ce:docs/scratchpads/heartbeat-planning' \
|| fail_msg "Q11a: the rendered claim must carry the one-call re-verify hint git show <snapshot_sha>:<path>"
# (b) POSITIVE: path arm alone suffices; hint degrades to one-call re-read.
printf '%s' "$out" | has_match -q 'seq 69 — CLAIM@seq' || fail_msg "Q11b: a path-only detector-shape entry must RENDER as CLAIM@seq (path arm)"
printf '%s' "$out" | has_match -q 're-read BOARD.md' || fail_msg "Q11b: the path-only claim must carry the one-call re-read <path> hint"
n_claims="$(printf '%s\n' "$out" | count_lines 'CLAIM@seq' || true)"
printf '%s' "$out" | grep -q 'seq 69 — CLAIM@seq' || fail_msg "Q11b: a path-only detector-shape entry must RENDER as CLAIM@seq (path arm)"
printf '%s' "$out" | grep -q 're-read BOARD.md' || fail_msg "Q11b: the path-only claim must carry the one-call re-read <path> hint"
n_claims="$(printf '%s\n' "$out" | grep -c 'CLAIM@seq' || true)"
[ "$n_claims" = "2" ] || fail_msg "Q11: EXACTLY the two valid entries must render as CLAIM@seq (got $n_claims)"
# (c)+(d) NEGATIVES retained: both quarantine, each with its OWN alarm.
printf '%s' "$out" | has_match -q 'ADDR-FREE' && fail_msg "Q11c: the address-free entry must be EXCLUDED from the digest"
printf '%s' "$out" | has_match -q 'SNAP-ONLY' && fail_msg "Q11d: no bare path-less snapshot_sha entry may appear in the digest (gate must not widen past board_file)"
has_match -q 'ADDR-FREE' "$(dlq "$home")" 2>/dev/null || fail_msg "Q11c: the address-free entry must be DEAD-LETTERED"
printf '%s' "$out" | grep -q 'ADDR-FREE' && fail_msg "Q11c: the address-free entry must be EXCLUDED from the digest"
printf '%s' "$out" | grep -q 'SNAP-ONLY' && fail_msg "Q11d: no bare path-less snapshot_sha entry may appear in the digest (gate must not widen past board_file)"
grep -q 'ADDR-FREE' "$(dlq "$home")" 2>/dev/null || fail_msg "Q11c: the address-free entry must be DEAD-LETTERED"
for snap_id in SNAP-ONLY-40 SNAP-ONLY-7 SNAP-ONLY-64; do
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
has_match -q 'heartbeat-planning' "$(dlq "$home")" 2>/dev/null && fail_msg "Q11a: the valid live entry must NOT be dead-lettered"
has_match -q 'PILOT-LOCAL' "$(dlq "$home")" 2>/dev/null && fail_msg "Q11b: the valid path-only entry must NOT be dead-lettered"
n_alarms="$(count_lines . "$ALARM_OUT" 2>/dev/null || true)"
grep -q 'heartbeat-planning' "$(dlq "$home")" 2>/dev/null && fail_msg "Q11a: the valid live entry must NOT be dead-lettered"
grep -q 'PILOT-LOCAL' "$(dlq "$home")" 2>/dev/null && fail_msg "Q11b: the valid path-only entry must NOT be dead-lettered"
n_alarms="$(grep -c . "$ALARM_OUT" 2>/dev/null || true)"
[ "$n_alarms" = "4" ] || fail_msg "Q11: exactly the four invalid entries must alarm (got $n_alarms) [$(cat "$ALARM_OUT" 2>/dev/null)]"
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
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
true
) && 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"
# DISCLOSURE: a held entry must be VISIBLE in the digest it was held from —
# a silent hold is how five successive digests each stepped past seq 68...
printf '%s' "$out" | has_match -q 'QUARANTINED' || fail_msg "Q12: the digest must carry a QUARANTINED disclosure section (no silent hold)"
printf '%s' "$out" | has_match -q 'seq 2 .*HELD' || fail_msg "Q12: the disclosure must name the held seq (2) as HELD"
printf '%s' "$out" | grep -q 'QUARANTINED' || fail_msg "Q12: the digest must carry a QUARANTINED disclosure section (no silent hold)"
printf '%s' "$out" | grep -q 'seq 2 .*HELD' || fail_msg "Q12: the disclosure must name the held seq (2) as HELD"
# ...but WITHOUT re-injecting the refused content: disclosure is by seq only;
# the Q1/Q4/Q5/Q6/Q9/Q11 exclusion property stands.
printf '%s' "$out" | 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.
printf '%s' "$out" | has_match -Eq 'consumed --upto 1$' || fail_msg "Q12: the embedded ack must be CLAMPED to --upto 1 (below quarantined seq 2)"
printf '%s' "$out" | has_match -Eq 'consumed --upto 2( |$)' && fail_msg "Q12: the raw observed cursor (2) must NOT be embedded while seq 2 is quarantined"
printf '%s' "$out" | has_match -q 'ACK CLAMPED' || fail_msg "Q12: the clamp must be LOUDLY disclosed in the ACK section"
printf '%s' "$out" | grep -Eq 'consumed --upto 1$' || fail_msg "Q12: the embedded ack must be CLAMPED to --upto 1 (below quarantined seq 2)"
printf '%s' "$out" | grep -Eq 'consumed --upto 2( |$)' && fail_msg "Q12: the raw observed cursor (2) must NOT be embedded while seq 2 is quarantined"
printf '%s' "$out" | grep -q 'ACK CLAMPED' || fail_msg "Q12: the clamp must be LOUDLY disclosed in the ACK section"
# A foreign-data render must NOT rewrite the lane's quarantine truth.
[ -e "$home/default/quarantined.set" ] && fail_msg "Q12: a --from-file render must NOT write the store's quarantined.set (lane truth is store-mode only)"
true
@@ -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)"
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" | has_match -q 'QUARANTINED' && fail_msg "Q13: no disclosure section when nothing is quarantined"
printf '%s' "$out" | has_match -q 'ACK CLAMPED' && fail_msg "Q13: no clamp note when nothing is quarantined"
printf '%s' "$out" | grep -Eq 'consumed --upto 1$' || fail_msg "Q13: with nothing quarantined the ack must embed the observed cursor (1) unchanged"
printf '%s' "$out" | grep -q 'QUARANTINED' && fail_msg "Q13: no disclosure section when nothing is quarantined"
printf '%s' "$out" | grep -q 'ACK CLAMPED' && fail_msg "Q13: no clamp note when nothing is quarantined"
true
) && 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)"
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" | has_match -Eq 'consumed --upto 1$' || fail_msg "Q14: the embedded ack must clamp to 1 (below quarantined seq 2)"
printf '%s' "$out" | grep -q 'QUARANTINED' || fail_msg "Q14: the store-mode digest must disclose the held entry"
printf '%s' "$out" | grep -Eq 'consumed --upto 1$' || fail_msg "Q14: the embedded ack must clamp to 1 (below quarantined seq 2)"
qf="$home/default/quarantined.set"
[ "$(cat "$qf" 2>/dev/null)" = "2" ] || fail_msg "Q14: a store-mode render must sync quarantined.set to exactly {2}, got [$(cat "$qf" 2>/dev/null)]"
# END-TO-END: even a hand-typed upto past the held seq is refused at the
@@ -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)"
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" | 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 -q 'QUARANTINED' && fail_msg "Q15: nothing quarantines under the fixed gate — no disclosure section"
printf '%s' "$out" | grep -Eq 'consumed --upto 2$' || fail_msg "Q15: the ack must embed the full observed cursor (2) once the gate is fixed"
[ -s "$home/default/quarantined.set" ] && fail_msg "Q15: the clean render must REPLACE (clear) the stale quarantined.set — a cumulative-forever set would block acks on entries that now render, got [$(cat "$home/default/quarantined.set")]"
"$STORE" consume --upto 2 >/dev/null 2>&1 || fail_msg "Q15: the ordinary consume must succeed after the clamp self-heals"
) && ok
@@ -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
# literal fixture id and cannot self-match.
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)"
fixture_json="$(printf '%s' "$fixture_line" | sed "s/.*'\({.*}\)'.*/\1/")"
# 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
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
fi
echo "wake digest-quarantine harness: all invariants passed ($pass groups)"
@@ -25,13 +25,6 @@
set -uo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# #973: three-valued grep assertion helpers (has_match/count_lines); init saves real stderr for abort loudness.
# shellcheck disable=SC1091
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"
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)"
rc=$?
[ "$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" | has_match -q 'VERDICT = PASS' || fail_msg "O1: verdict must be PASS 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" | grep -q 'VERDICT = PASS' || fail_msg "O1: verdict must be PASS on a healthy pipeline [$out]"
) && ok
echo "== O2: DISABLED detector (perfect no-op) -> FN-DETECTED (blindspot killer) =="
@@ -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)"
rc=$?
[ "$rc" -ne 0 ] || fail_msg "O2: a detector that drops a KNOWN delta MUST fail the oracle (non-zero exit), even at a perfect no-op rate"
echo "$out" | has_match -q 'FN-RATE = 3/3' || fail_msg "O2: every dropped canary must count as a false-negative (FN-RATE 3/3) [$out]"
echo "$out" | has_match -q 'VERDICT = FN-DETECTED' || fail_msg "O2: verdict must be FN-DETECTED for a dropping detector [$out]"
echo "$out" | has_match -qi 'never OBSERVED' || fail_msg "O2: the failure must name the dropped (never-observed) delta [$out]"
echo "$out" | grep -q 'FN-RATE = 3/3' || fail_msg "O2: every dropped canary must count as a false-negative (FN-RATE 3/3) [$out]"
echo "$out" | grep -q 'VERDICT = FN-DETECTED' || fail_msg "O2: verdict must be FN-DETECTED for a dropping detector [$out]"
echo "$out" | grep -qi 'never OBSERVED' || fail_msg "O2: the failure must name the dropped (never-observed) delta [$out]"
) && ok
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)"
rc=$?
[ "$rc" -ne 0 ] || fail_msg "O3: a canary that reaches CONSUMED past its SLO must be a false-negative (non-zero exit)"
echo "$out" | 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,
# 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]"
fi
) && 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)"
rc=$?
[ "$rc" -ne 0 ] || fail_msg "O4: a drive that exits 0 but delivers nothing must still be FN-DETECTED (verdict is off-domain)"
echo "$out" | 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
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)"
rc=$?
[ "$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
echo
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
fi
echo "wake fn-oracle harness: all invariants passed ($pass groups)"
@@ -34,13 +34,6 @@
set -uo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# #973: three-valued grep assertion helpers (has_match/count_lines); init saves real stderr for abort loudness.
# shellcheck disable=SC1091
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"
BEACON="$SCRIPT_DIR/beacon.sh"
FRAMEWORK_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
@@ -101,7 +94,7 @@ EOF
export WAKE_INSTALL_MANIFEST="$BAD_MANIFEST"
out="$(bash "$WI" install 2>&1)"; rc=$?
[ "$rc" -ne 0 ] || fail_msg "I2: install must FAIL when a wake candidate is not framework-owned (got rc=$rc)"
echo "$out" | 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.
[ ! -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.
@@ -128,7 +121,7 @@ EOF
|| fail_msg "I3: blank-reset drop-in write failed"
out="$(bash "$WI" verify-single "$UNIT" 2>&1)"; rc=$?
[ "$rc" -eq 0 ] || fail_msg "I3: blank-reset must resolve exactly one OnUnitActiveUSec (rc=$rc) [$out]"
echo "$out" | 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.
cat >"$UD/$UNIT.d/interval.conf" <<'EOF'
[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.
out="$(bash "$WI" reap-unit "$UNIT" 2>&1)"; rc=$?
[ "$rc" -ne 0 ] || fail_msg "I4: reap without a snapshot must be REFUSED (rc=$rc)"
echo "$out" | 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"
# (b) snapshot, then reap -> allowed, unit gone, snapshot retained.
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"
out="$(bash "$WI" validate-hmac-key 2>&1)"; rc=$?
[ "$rc" -ne 0 ] || fail_msg "I5: missing credential store must FAIL LOUD for the HMAC key (rc=$rc)"
echo "$out" | 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.
jq -cn --arg k "$SECRET_KEY" '{wake:{hmac_keys:{"primary":$k}}}' >"$CRED"
out2="$(bash "$WI" validate-hmac-key 2>&1)"; rc2=$?
[ "$rc2" -eq 0 ] || fail_msg "I5: a by-name-resolvable HMAC key must pass (rc=$rc2) [$out2]"
echo "$out2" | 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).
unset WAKE_ALARM_SINK_CMD
out3="$(bash "$WI" validate-alarm-target 2>&1)"; rc3=$?
[ "$rc3" -ne 0 ] || fail_msg "I5: unconfigured alarm target must FAIL LOUD (rc=$rc3)"
echo "$out3" | 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.
export WAKE_ALARM_SINK_CMD="false"
out4="$(bash "$WI" validate-alarm-target 2>&1)"; rc4=$?
[ "$rc4" -ne 0 ] || fail_msg "I5: unreachable alarm sink must FAIL LOUD (rc=$rc4)"
echo "$out4" | 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.
export WAKE_ALARM_SINK_CMD="cat >/dev/null"
bash "$WI" validate-alarm-target >/dev/null 2>&1 || fail_msg "I5: a configured+reachable alarm sink must pass"
# The installer file must inline NO secret/endpoint.
has_match -qF "$SECRET_ENDPOINT" "$WI" && fail_msg "I5: wake-install.sh must NOT inline any alarm endpoint"
has_match -qE 'hmac_keys[^A-Za-z_].*=[^=].*[A-Za-z0-9]{8,}' "$WI" && fail_msg "I5: wake-install.sh must NOT inline key material"
grep -qF "$SECRET_ENDPOINT" "$WI" && fail_msg "I5: wake-install.sh must NOT inline any alarm endpoint"
grep -qE 'hmac_keys[^A-Za-z_].*=[^=].*[A-Za-z0-9]{8,}' "$WI" && fail_msg "I5: wake-install.sh must NOT inline key material"
true
) && 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]"
[ -f "$UD/$TIMER" ] || fail_msg "I6: overlap phase must LEAVE the legacy timer running (retire withheld)"
[ -f "$SNAP/$TIMER" ] || fail_msg "I6: the legacy timer must be snapshotted before any retire"
echo "$out" | 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).
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]"
@@ -290,11 +283,11 @@ echo "== I9: dep-check — a host seed missing _lib/manifest.sh FAILS LOUD (name
TGT="$(fresh i9-target)"
out="$(WAKE_INSTALL_SOURCE="$FAKE" WAKE_INSTALL_TARGET="$TGT" bash "$WI_FAKE" install 2>&1)"; rc=$?
[ "$rc" -ne 0 ] || fail_msg "I9: install MUST fail when _lib/manifest.sh is missing (rc=$rc)"
echo "$out" | has_match -qi 'manifest.sh' || fail_msg "I9: the failure must NAME the missing library (manifest.sh) [$out]"
echo "$out" | has_match -qiE 'REMEDY|mosaic update|re-seed|framework install' \
echo "$out" | grep -qi 'manifest.sh' || fail_msg "I9: the failure must NAME the missing library (manifest.sh) [$out]"
echo "$out" | grep -qiE 'REMEDY|mosaic update|re-seed|framework install' \
|| fail_msg "I9: the failure must give an actionable REMEDY, not just an error [$out]"
# It must be a CLEAR fail-loud, NOT the obscure bare `source: No such file or directory`.
echo "$out" | 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]"
# Positive control: with the helper present (real framework root) install succeeds.
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"
out2="$(bash "$WI" validate-systemd-path 2>&1)"; rc2=$?
[ "$rc2" -ne 0 ] || fail_msg "I10: validate must FAIL when the unit is not in the search path (rc=$rc2) [$out2]"
echo "$out2" | 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.
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)"
bash "$WI" validate-systemd-path >/dev/null 2>&1 || fail_msg "I10: re-install must keep the unit resolvable"
) && 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).
out0="$(bash "$WI" fallback-proven-live 2>&1)"; rc0=$?
[ "$rc0" -ne 0 ] || fail_msg "I11: fallback-proven-live must FAIL when the fallback wake is not installed (rc=$rc0)"
echo "$out0" | 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
# legacy timer LEFT RUNNING (never a coverage gap).
out="$(bash "$WI" reset-verify-retire f --interval 30min --vector-passed 2>&1)"; rc=$?
[ "$rc" -ne 0 ] || fail_msg "I11: reap must be REFUSED without a proven-live fallback (rc=$rc)"
echo "$out" | 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)"
# (c) POSITIVE — provision the fallback at the schedulable floor, then the reap
# 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.
out="$(bash "$DIGEST" render --from-store 2>/dev/null)"; rc=$?
[ "$rc" -eq 0 ] || fail_msg "I12: the canon fallback drain must exit 0 (rc=$rc)"
echo "$out" | 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]"
# Bind the invariant to the SHIPPED unit: its ExecStart must be this canon drain.
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)"
) && ok
@@ -398,11 +391,11 @@ echo "== I13: install links + validates the canon fallback timer+service into th
rm -f "$UD/mosaic-wake-fallback.timer"
out="$(bash "$WI" validate-fallback-units 2>&1)"; rc=$?
[ "$rc" -ne 0 ] || fail_msg "I13: validate must FAIL when a fallback unit is not in the search path (rc=$rc) [$out]"
echo "$out" | 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.
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 .)"
ns="$(find "$UD" -maxdepth 1 -name 'mosaic-wake-fallback.service' | count_lines .)"
nt="$(find "$UD" -maxdepth 1 -name 'mosaic-wake-fallback.timer' | grep -c .)"
ns="$(find "$UD" -maxdepth 1 -name 'mosaic-wake-fallback.service' | grep -c .)"
[ "$nt" -eq 1 ] && [ "$ns" -eq 1 ] || fail_msg "I13: re-install must not duplicate the fallback entries (timer=$nt service=$ns)"
bash "$WI" validate-fallback-units >/dev/null 2>&1 || fail_msg "I13: re-install must keep the fallback units resolvable"
) && ok
@@ -414,17 +407,17 @@ echo "== I14: per-class fallback cadence — blank-reset yields EXACTLY ONE OnUn
TIMER="mosaic-wake-fallback.timer"
# The shipped timer carries the base OnUnitActiveSec placeholder (=1h).
cp "$FRAMEWORK_ROOT/systemd/user/$TIMER" "$UD/$TIMER"
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.
out="$(bash "$WI" write-fallback-cadence 30min 2>&1)"; rc=$?
[ "$rc" -eq 0 ] || fail_msg "I14: write-fallback-cadence must succeed + verify single (rc=$rc) [$out]"
echo "$out" | 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/"
# Assert the blank-reset FORM: an empty `OnUnitActiveSec=` reset line IMMEDIATELY
# FOLLOWED by the new value. Portable across GNU and BusyBox grep (Alpine CI) —
# `grep -z` (NUL-data) is a GNU-only extension BusyBox grep does NOT support, so
# match the reset line and require the value on the very next line (-A1) instead.
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)"
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.
@@ -435,7 +428,7 @@ echo "== I14: per-class fallback cadence — blank-reset yields EXACTLY ONE OnUn
echo
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
fi
echo "wake install harness: all invariants passed ($pass groups)"
@@ -46,13 +46,6 @@
set -uo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# #973: three-valued grep assertion helpers (has_match/count_lines); init saves real stderr for abort loudness.
# shellcheck disable=SC1091
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"
STORE="$SCRIPT_DIR/store.sh"
DET="$SCRIPT_DIR/detector.sh"
@@ -209,9 +202,9 @@ echo "== P4: credential-store PATH refused — bytes never captured (acceptance
sd="$(state_dir)"
row="$(tail -n1 "$sd/preimage/preimage-ledger.jsonl")"
[ "$(jq -r '.captured' <<<"$row")" = "false" ] || fail_msg "P4: row must record captured=false [$row]"
jq -r '.refused // ""' <<<"$row" | 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.
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"
fi
) && ok
@@ -232,8 +225,8 @@ echo "== P5: secret-SHAPED content refused (refusal, not redaction) =="
sd="$(state_dir)"
row="$(tail -n1 "$sd/preimage/preimage-ledger.jsonl")"
[ "$(jq -r '.captured' <<<"$row")" = "false" ] || fail_msg "P5: row must record captured=false [$row]"
jq -r '.refused // ""' <<<"$row" | has_match -qi 'secret' || fail_msg "P5: refusal must name secret-shaped content [$row]"
if has_match -rq "$tok" "$sd" 2>/dev/null; then
jq -r '.refused // ""' <<<"$row" | grep -qi 'secret' || fail_msg "P5: refusal must name secret-shaped content [$row]"
if grep -rq "$tok" "$sd" 2>/dev/null; then
fail_msg "P5: secret-shaped bytes leaked into the wake state dir"
fi
) && 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
out="$("$PRE" check --enqueue 2>&1)"; rc=$?
[ "$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
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"
out="$("$PRE" check --enqueue 2>&1)"; rc=$?
[ "$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"
) && ok
@@ -321,7 +314,7 @@ echo "== P10: oversized file -> bytes refused, hash still recorded =="
sd="$(state_dir)"
row="$(tail -n1 "$sd/preimage/preimage-ledger.jsonl")"
[ "$(jq -r '.captured' <<<"$row")" = "false" ] || fail_msg "P10: row must record captured=false [$row]"
jq -r '.refused // ""' <<<"$row" | 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" = "$(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"
@@ -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"
out="$("$DET" poll-once 2>&1)"; rc=$?
[ "$rc" -ne 0 ] || fail_msg "P12: the pass must exit non-zero on preimage infra failure"
echo "$out" | 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" -ge 1 ] || fail_msg "P12: source observation must still proceed (no watch hash baselined)"
) && 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" = "2" ] || fail_msg "P13: both decoys must record captured=false (got $n)"
# 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"
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"
fi
) && ok
@@ -430,7 +423,7 @@ echo "== P14: detector.env-class key — safe WITHOUT opt-in by polarity, refuse
sd="$(state_dir)"
row="$(tail -n1 "$sd/preimage/preimage-ledger.jsonl")"
[ "$(jq -r '.captured' <<<"$row")" = "false" ] || fail_msg "P14a: extra must be record-only without opt-in [$row]"
if 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"
fi
# (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]"
row="$(tail -n1 "$sd/preimage/preimage-ledger.jsonl")"
[ "$(jq -r '.captured' <<<"$row")" = "false" ] || fail_msg "P14b: opted-in key material must still refuse [$row]"
jq -r '.refused // ""' <<<"$row" | has_match -qi 'secret' || fail_msg "P14b: refusal must name secret-shaped content [$row]"
if has_match -rq "$hm" "$sd" 2>/dev/null; then
jq -r '.refused // ""' <<<"$row" | grep -qi 'secret' || fail_msg "P14b: refusal must name secret-shaped content [$row]"
if grep -rq "$hm" "$sd" 2>/dev/null; then
fail_msg "P14b: key bytes leaked despite refusal"
fi
) && ok
@@ -495,7 +488,7 @@ echo "== P16: deleted ledger over surviving objects/ — REFUSE to re-baseline (
printf '# v3\n' >>"$fx/adapter.sh"
out="$("$PRE" check --enqueue 2>&1)"; rc=$?
[ "$rc" -ne 0 ] || fail_msg "P16: absent ledger over prior objects must FAIL LOUD (rc=0) [$out]"
echo "$out" | 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"
[ "$(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))"
@@ -527,7 +520,7 @@ echo "== P17: allowlist symmetry — symlink to a DENIED target refuses; symlink
sd="$(state_dir)"
crow="$(jq -c --arg p "$(realpath "$fx/settings.json")" 'select(.path == $p)' "$sd/preimage/preimage-ledger.jsonl" | tail -n1)"
[ "$(jq -r '.captured' <<<"$crow")" = "false" ] || fail_msg "P17: allowlisted symlink to a denied target must refuse [$crow]"
if 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"
fi
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
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"
STORE="$SCRIPT_DIR/store.sh"
DET="$SCRIPT_DIR/detector.sh"
@@ -94,7 +87,7 @@ EOF
out="$("$RECON" inventory 2>&1)"
rc=$?
[ "$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
echo "== R2: OMITTED source -> FLAG (vacuous-pass prevented, not silently green) =="
@@ -113,8 +106,8 @@ EOF
out="$("$RECON" inventory 2>&1)"
rc=$?
[ "$rc" -ne 0 ] || fail_msg "R2: an omitted (declared-but-unwatched) source must FLAG (non-zero), not silently pass"
echo "$out" | has_match -qi 'r2' || fail_msg "R2: the flag must name the omitted source r2 [$out]"
echo "$out" | has_match -qi 'vacuous' || fail_msg "R2: the flag must state the vacuous-pass is prevented [$out]"
echo "$out" | grep -qi 'r2' || fail_msg "R2: the flag must name the omitted source r2 [$out]"
echo "$out" | grep -qi 'vacuous' || fail_msg "R2: the flag must state the vacuous-pass is prevented [$out]"
) && ok
echo "== R3: required_sources omission -> FLAG =="
@@ -133,7 +126,7 @@ EOF
out="$("$RECON" inventory 2>&1)"
rc=$?
[ "$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
echo "== R4: dangling watch reference -> FLAG =="
@@ -151,7 +144,7 @@ EOF
out="$("$RECON" inventory 2>&1)"
rc=$?
[ "$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
echo "== R5/R6: pre-existing state -> UNACCOUNTED flag + enumerated into store; re-run 0 =="
@@ -180,14 +173,14 @@ EOF
out="$("$RECON" reconcile 2>&1)"
rc=$?
[ "$rc" -ne 0 ] || fail_msg "R5: pre-existing unaccounted state must FLAG (non-zero) on first reconcile"
echo "$out" | 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.
[ "$(depth)" = "2" ] || fail_msg "R6: pre-existing state must be ENUMERATED into the store (depth 2), got $(depth)"
# A follow-up reconcile now finds everything accounted -> 0 unaccounted.
out2="$("$RECON" reconcile 2>&1)"
rc2=$?
[ "$rc2" -eq 0 ] || fail_msg "R6: a second reconcile must report 0 unaccounted (exit 0) [$out2]"
echo "$out2" | 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)"
) && ok
@@ -218,7 +211,7 @@ EOF
out="$("$RECON" reconcile 2>&1)"
rc=$?
[ "$rc" -eq 0 ] || fail_msg "R7: state reflected in the inbox must be ACCOUNTED (exit 0) [$out]"
echo "$out" | 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)"
) && ok
@@ -243,7 +236,7 @@ EOF
out="$("$RECON" reconcile 2>&1)"
rc=$?
[ "$rc" -ne 0 ] || fail_msg "R8: a source error must FAIL LOUD (non-zero exit)"
echo "$out" | 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)"
) && ok
@@ -285,7 +278,7 @@ EOF
out="$("$RECON" reconcile 2>&1)"
rc=$?
[ "$rc" -ne 0 ] || fail_msg "R9: pre-existing beta is unaccounted on this pass -> must FLAG (non-zero) [$out]"
echo "$out" | 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)"
# 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).
seqs="$("$STORE" drain | jq -r '.observed_seq' | sort -n | tr '\n' ' ')"
[ "$seqs" = "1 2 3 " ] || fail_msg "R9: co-fed observed_seqs must be distinct+gapless '1 2 3', got '$seqs' (a duplicate = the aliasing #908 dissolved)"
ndistinct="$("$STORE" drain | jq -r '.observed_seq' | sort -nu | count_lines .)"
ntotal="$("$STORE" drain | jq -r '.observed_seq' | count_lines .)"
ndistinct="$("$STORE" drain | jq -r '.observed_seq' | sort -nu | grep -c .)"
ntotal="$("$STORE" drain | jq -r '.observed_seq' | grep -c .)"
[ "$ndistinct" = "$ntotal" ] || fail_msg "R9: aliasing detected — $ntotal entries but only $ndistinct distinct observed_seq"
# The contiguous-prefix CONSUMED contract holds over the co-fed seqs.
"$STORE" consume --upto 3 >/dev/null 2>&1 || fail_msg "R9: CONSUMED 3 must succeed over the gapless co-fed prefix"
@@ -340,7 +333,7 @@ EOF
out="$("$RECON" reconcile 2>&1)"
rc=$?
[ "$rc" -eq 0 ] || fail_msg "R10: a consumed state must be ACCOUNTED (exit 0, no spurious rc=1 CRITICAL) [$out]"
echo "$out" | 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)"
) && ok
@@ -379,7 +372,7 @@ EOF
[ "$rc" -ne 0 ] || fail_msg "R11: a genuine gap (r2) must still FLAG (non-zero) [$out]"
# After #932: ONLY r2 is unaccounted (r1 suppressed by the store record). On
# BASE both r1 and r2 re-enumerate (UNACCOUNTED=2) -> this assertion is red-first.
echo "$out" | 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
# 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.)
@@ -390,7 +383,7 @@ EOF
echo
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
fi
echo "wake reconcile harness: all invariants passed ($pass groups)"
@@ -42,13 +42,6 @@
set -uo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# #973: three-valued grep assertion helpers (has_match/count_lines); init saves real stderr for abort loudness.
# shellcheck disable=SC1091
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"
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 2 --class actionable --locators '{"repo":"r","issue":2}' >/dev/null
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" | has_match -q 'consumed_seq=0' || fail_msg "T1: consumed_seq must NOT advance on enqueue (only on CONSUMED ack) [$cur]"
echo "$cur" | has_match -q 'pending_depth=2' || fail_msg "T1: pending depth should be 2 [$cur]"
echo "$cur" | grep -q 'observed_seq=2' || fail_msg "T1: observed_seq should be 2 after enqueue 1,2 [$cur]"
echo "$cur" | grep -q 'consumed_seq=0' || fail_msg "T1: consumed_seq must NOT advance on enqueue (only on CONSUMED ack) [$cur]"
echo "$cur" | grep -q 'pending_depth=2' || fail_msg "T1: pending depth should be 2 [$cur]"
# consumed_seq advances only on CONSUMED.
"$STORE" consume --upto 2 >/dev/null
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" | has_match -q 'pending_depth=0' || fail_msg "T1: pending drained after CONSUMED [$cur]"
echo "$cur" | grep -q 'consumed_seq=2' || fail_msg "T1: consumed_seq should be 2 after CONSUMED 2 [$cur]"
echo "$cur" | grep -q 'pending_depth=0' || fail_msg "T1: pending drained after CONSUMED [$cur]"
) && ok
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 4 --class human --locators '{"from":"peer"}' >/dev/null
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"
head="$(printf '%s\n' "$out" | jq -r 'select(.class=="digest") | .locators.head')"
[ "$head" = "bbb" ] || fail_msg "T2: newest digest must REPLACE prior (expected bbb, got $head)"
nactionable="$(printf '%s\n' "$out" | jq -c 'select(.class=="actionable")' | count_lines . || true)"
nhuman="$(printf '%s\n' "$out" | jq -c 'select(.class=="human")' | 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")' | grep -c . || true)"
[ "$nactionable" = "1" ] || fail_msg "T2: actionable must APPEND (never replaced), got $nactionable"
[ "$nhuman" = "1" ] || fail_msg "T2: human must APPEND / stay durable, got $nhuman"
# Durability never bypassed: all classes present in the durable store.
total="$(printf '%s\n' "$out" | 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"
) && 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)"
fi
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.
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"
@@ -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
"$ACK" consumed --upto 3 --no-sync >/dev/null 2>&1 || fail_msg "T3: CONSUMED 3 should succeed once gap at 2 is filled"
cur="$("$STORE" cursors)"
echo "$cur" | 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.
"$ACK" consumed --upto 2 --no-sync >/dev/null 2>&1 || fail_msg "T3: cumulative CONSUMED 2 (<=3) should be an idempotent success"
cur="$("$STORE" cursors)"
echo "$cur" | 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
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'"
# RECEIVED (delivery) must NEVER advance consumed_seq (delivery != consumption).
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
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)"
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" | 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.
"$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
@@ -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.
[ -e "$STATE_DIR/.wake.tmp.crash12" ] || fail_msg "T5: the enqueue hot path must NOT delete tmp files off its own write (that hot-path reap was the #927 clobber)"
d2="$("$STORE" drain)"
[ "$(printf '%s\n' "$d2" | 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 /
# 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
@@ -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"
[ -e "$STATE_DIR/.wake.tmp.crash12" ] && fail_msg "T5: maintenance reap (store.sh init) must remove a demonstrably-orphaned stale temp (bounded accumulation)"
d3="$("$STORE" drain)"
[ "$(printf '%s\n' "$d3" | 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"
) && 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
# see the persisted entries — nothing was held in memory.
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)"
# Retained UNTIL consumed: after CONSUMED they are released.
"$STORE" consume --upto 2 >/dev/null
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)"
) && ok
@@ -303,7 +296,7 @@ EOF
end="$(date +%s.%N)"
elapsed_ms="$(awk -v s="$start" -v e="$end" 'BEGIN { printf "%d", (e - s) * 1000 }')"
[ "$elapsed_ms" -lt 1500 ] || fail_msg "T7: ack path blocked ${elapsed_ms}ms — must return without waiting on the background sync"
echo "$out" | 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).
STATE_DIR="$WAKE_STATE_HOME/default"
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'"
[ "$s2" = "2" ] || fail_msg "T8: second store-allocated observed_seq must be 2 (contiguous), got '$s2'"
cur="$("$STORE" cursors)"
echo "$cur" | has_match -q 'observed_seq=2' || fail_msg "T8: observed_seq cursor should be 2 [$cur]"
echo "$cur" | has_match -q 'pending_depth=2' || fail_msg "T8: both allocations must be durably enqueued [$cur]"
echo "$cur" | grep -q 'observed_seq=2' || fail_msg "T8: observed_seq cursor should be 2 [$cur]"
echo "$cur" | grep -q 'pending_depth=2' || fail_msg "T8: both allocations must be durably enqueued [$cur]"
# ANTI-SWALLOW: consume the prefix, then a LEGACY explicit --seq inside the
# consumed prefix must FAIL LOUD (never the old silent seq<=consumed no-op).
"$STORE" consume --upto 2 >/dev/null
@@ -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"
fi
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')"
[ "$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.
@@ -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')"
[ "$a" != "$b" ] || fail_msg "T10: two concurrent enqueues must get DISTINCT seqs, both got '$a' (aliasing / lost write without the lock)"
cur="$("$STORE" cursors)"
echo "$cur" | has_match -q 'observed_seq=2' || fail_msg "T10: observed_seq must reach 2 after two enqueues [$cur]"
echo "$cur" | has_match -q 'pending_depth=2' || fail_msg "T10: both entries must be durably stored (no lost write) [$cur]"
echo "$cur" | grep -q 'observed_seq=2' || fail_msg "T10: observed_seq must reach 2 after two enqueues [$cur]"
echo "$cur" | grep -q 'pending_depth=2' || fail_msg "T10: both entries must be durably stored (no lost write) [$cur]"
# The two allocated seqs are exactly {1,2} (distinct, contiguous, gapless).
lo="$a"; hi="$b"; [ "$a" -gt "$b" ] && { lo="$b"; hi="$a"; }
{ [ "$lo" = "1" ] && [ "$hi" = "2" ]; } || fail_msg "T10: concurrent seqs must be {1,2}, got {$lo,$hi}"
@@ -499,7 +492,7 @@ echo "== T11: final observed_seq cursor write FAILURE — fail loud + observed.s
# the _atomic_write failure and returns 0).
[ "$rc" -ne 0 ] || fail_msg "T11: an enqueue whose FINAL cursor write fails must EXIT NON-ZERO (fail loud), got rc=$rc"
# The loud diagnostic names the cursor write (not a generic error).
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.
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" = "HASH-C" ] || fail_msg "T12: repo/r2 must record HASH-C, got '$r2h'"
# ADDITIVE: existing on-disk state files are unchanged/consistent post-consume.
"$STORE" cursors | 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
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"
fi
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" | has_match -q -- '--force-past-quarantine' || fail_msg "T13: the refusal must NAME the force flag [$err]"
echo "$err" | grep -q 'quarantined seq(s): 2' || fail_msg "T13: the refusal must NAME the quarantined seq [$err]"
echo "$err" | grep -q -- '--force-past-quarantine' || fail_msg "T13: the refusal must NAME the force flag [$err]"
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.
"$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.
@@ -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)"
fi
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
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" -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'"
has_match -q 'FORCED PAST QUARANTINE' "$errf" || fail_msg "T14: the forced path must be LOUD on stderr [$(cat "$errf")]"
has_match -q 'seq 2' "$errf" || fail_msg "T14: the forced-path diagnostic must name the stepped-over seq 2 [$(cat "$errf")]"
"$STORE" cursors | has_match -q 'consumed_seq=3' || fail_msg "T14: forced consume must advance the cursor to 3"
grep -q 'FORCED PAST QUARANTINE' "$errf" || fail_msg "T14: the forced path must be LOUD on stderr [$(cat "$errf")]"
grep -q 'seq 2' "$errf" || fail_msg "T14: the forced-path diagnostic must name the stepped-over seq 2 [$(cat "$errf")]"
"$STORE" cursors | grep -q 'consumed_seq=3' || fail_msg "T14: forced consume must advance the cursor to 3"
# NO FALSE WITNESS: the quarantined entry (repo/b) was NEVER delivered, so no
# consumed-hash row may exist for it — even on the forced path (the reconciler
# re-enumerating it once is safe-but-noisy; a false witness silences it
@@ -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)"
# The stepped-over seq is PRUNED from the set (it is consumed now; a stale
# 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
# still reports a CLEAN cursor line on stdout.
"$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")"
rc2=$?
[ "$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'"
has_match -q 'FORCED PAST QUARANTINE' "$errf2" || fail_msg "T14: the forced-path loudness must survive the ack wrapper (stderr) [$(cat "$errf2")]"
echo "$out2" | grep -q '^CONSUMED 5$' || fail_msg "T14: forced ack must report a CLEAN cursor line 'CONSUMED 5', got '$out2'"
grep -q 'FORCED PAST QUARANTINE' "$errf2" || fail_msg "T14: the forced-path loudness must survive the ack wrapper (stderr) [$(cat "$errf2")]"
jq_any "$rec" '.kind=="repo" and .id=="e"' && fail_msg "T14: the quarantined repo/e must have NO consumed-hash row via the forced ack path either"
true
) && ok
@@ -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
fail_msg "T16: report-mode audit must exit NON-ZERO when false rows exist"
fi
has_match -q 'FALSE WITNESS' "$rep" || fail_msg "T16: the audit must name the false row loudly [$(cat "$rep")]"
has_match -q '"id":"X"' "$rep" || fail_msg "T16: the audit must identify the false row (repo/X@1) [$(cat "$rep")]"
has_match -q '"id":"Y"' "$rep" && fail_msg "T16: the clean row repo/Y must NOT be flagged"
has_match -q '"id":"Z"' "$rep" && fail_msg "T16: the HEALED row repo/Z@4 must NOT be flagged (its dead-letter evidence is seq 3 with a different hash)"
grep -q 'FALSE WITNESS' "$rep" || fail_msg "T16: the audit must name the false row loudly [$(cat "$rep")]"
grep -q '"id":"X"' "$rep" || fail_msg "T16: the audit must identify the false row (repo/X@1) [$(cat "$rep")]"
grep -q '"id":"Y"' "$rep" && fail_msg "T16: the clean row repo/Y must NOT be flagged"
grep -q '"id":"Z"' "$rep" && fail_msg "T16: the HEALED row repo/Z@4 must NOT be flagged (its dead-letter evidence is seq 3 with a different hash)"
# Report mode modifies nothing.
jq_any "$rec" '.id=="X"' || fail_msg "T16: report mode must not modify the record"
# REPAIR: exactly the false row is removed; the dead-letter LEDGER is history
@@ -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=="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"
[ "$(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.
"$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
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.
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")]"
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.
has_match -qi 'pruned' "$rep" || fail_msg "T17: clean sweep must name residual class 1 — evidence pruned away [$(cat "$rep")]"
has_match -qi 'empty observed_hash' "$rep" || fail_msg "T17: clean sweep must name residual class 2 — surviving evidence with an empty observed_hash [$(cat "$rep")]"
grep -qi 'pruned' "$rep" || fail_msg "T17: clean sweep must name residual class 1 — evidence pruned away [$(cat "$rep")]"
grep -qi 'empty observed_hash' "$rep" || fail_msg "T17: clean sweep must name residual class 2 — surviving evidence with an empty observed_hash [$(cat "$rep")]"
# --repair on a clean sweep removes nothing: the unprovable row survives.
"$STORE" quarantine-audit --repair >"$TMP_ROOT/t17.fix" 2>&1 || fail_msg "T17: --repair on a clean sweep must exit 0 [$(cat "$TMP_ROOT/t17.fix")]"
jq_any "$rec" '.kind=="bench" and .observed_seq==13 and .observed_hash=="H-REAL-CONSUMED"' ||
fail_msg "T17: --repair must NOT remove the unprovable bench@13 row — the audit only removes what the ledger can convict"
[ "$(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
echo
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
fi
echo "wake store/ack harness: all invariants passed ($pass groups)"
@@ -32,13 +32,6 @@
set -uo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# #973: three-valued grep assertion helpers (has_match/count_lines); init saves real stderr for abort loudness.
# shellcheck disable=SC1091
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"
command -v jq >/dev/null 2>&1 || {
@@ -154,7 +147,7 @@ EOF
wait "$pa" 2>/dev/null || true
else
# Confirm the window is genuinely open: A holds a live in-flight tmp now.
n_tmp="$(find "$STATE_DIR" -maxdepth 1 -name '.wake.tmp.*' -type f | 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)"
# 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
fail_msg "T-RACE: enqueue A was SPURIOUSLY ABORTED (rc=$ra) — its live in-flight tmp was deleted by B's pre-lock cleanup [#927]. stderr: $(tr '\n' ' ' <"$a_err")"
fi
if 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)."
fi
[ "$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.
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" | has_match -q 'pending_depth=2' || fail_msg "T-RACE: both entries must be durably stored — no lost/aborted write [$cur]"
echo "$cur" | has_match -q 'consumed_seq=0' || fail_msg "T-RACE: enqueue must never advance consumed_seq [$cur]"
echo "$cur" | grep -q 'observed_seq=2' || fail_msg "T-RACE: observed_seq must reach 2 (both allocations committed) [$cur]"
echo "$cur" | grep -q 'pending_depth=2' || fail_msg "T-RACE: both entries must be durably stored — no lost/aborted write [$cur]"
echo "$cur" | grep -q 'consumed_seq=0' || fail_msg "T-RACE: enqueue must never advance consumed_seq [$cur]"
# Gapless: the contiguous prefix 1..2 is consumable (no interior gap/burn).
"$STORE" consume --upto 2 >/dev/null 2>&1 || fail_msg "T-RACE: CONSUMED 2 must succeed — seqs {1,2} are gapless (no burned seq)"
# No stale tmp left leaking either.
n_leak="$(find "$STATE_DIR" -maxdepth 1 -name '.wake.tmp.*' -type f | 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"
fi
) && ok
echo
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
fi
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"