Compare commits
11
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1806b3a57a | ||
|
|
fc80138326 | ||
|
|
8fdc8738ed | ||
|
|
8310075d33 | ||
|
|
b0fb208b89 | ||
|
|
62e3bf9e28 | ||
|
|
b7d391f9f4 | ||
|
|
1036449b57 | ||
|
|
63c5d2056f | ||
|
|
1288ce3721 | ||
|
|
92272a6400 |
@@ -41,11 +41,6 @@ steps:
|
|||||||
# (Constitution + dispatcher + each RUNTIME.md slice). See DESIGN §7 / R9.
|
# (Constitution + dispatcher + each RUNTIME.md slice). See DESIGN §7 / R9.
|
||||||
- bash packages/mosaic/framework/tools/quality/scripts/check-resident-budget.sh --self-test
|
- bash packages/mosaic/framework/tools/quality/scripts/check-resident-budget.sh --self-test
|
||||||
- bash packages/mosaic/framework/tools/quality/scripts/check-resident-budget.sh
|
- bash packages/mosaic/framework/tools/quality/scripts/check-resident-budget.sh
|
||||||
# Test-membership guard (#1017): also first link of test:framework-shell.
|
|
||||||
# Invoked from BOTH surfaces it audits (F2, PR #1018) — the guard is link
|
|
||||||
# [0] of the pnpm chain, so severing that chain would silence it together
|
|
||||||
# with everything it guards; this direct line keeps one instrument running.
|
|
||||||
- bash packages/mosaic/framework/tools/quality/scripts/check-test-enumeration.sh
|
|
||||||
|
|
||||||
# Blocking gate (#791): a framework upgrade must never write or delete an
|
# Blocking gate (#791): a framework upgrade must never write or delete an
|
||||||
# operator-owned path. The HARD GATE proves an unanticipated operator sentinel
|
# operator-owned path. The HARD GATE proves an unanticipated operator sentinel
|
||||||
|
|||||||
@@ -109,55 +109,6 @@ else
|
|||||||
detect_platform >/dev/null
|
detect_platform >/dev/null
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Render the provider's own explanation for a failed request, for appending to
|
|
||||||
# an error message (#1004). Every HTTP arm in this file already has the response
|
|
||||||
# body on disk; without this it was discarded unread at exactly the moment the
|
|
||||||
# caller needed it, which pushes an operator toward re-issuing the request by
|
|
||||||
# hand to find out what the server said. Gitea returns {"message": "..."} on a
|
|
||||||
# refusal; anything unparseable falls back to a truncated raw first line so a
|
|
||||||
# proxy's HTML error page still says something. Prints "" when there is nothing
|
|
||||||
# to add, so callers can interpolate unconditionally.
|
|
||||||
#
|
|
||||||
# Args: $1 = path to the response body file.
|
|
||||||
gitea_error_detail() {
|
|
||||||
local body_file="$1"
|
|
||||||
[[ -s "$body_file" ]] || return 0
|
|
||||||
python3 - "$body_file" <<'PY' 2>/dev/null || true
|
|
||||||
import json
|
|
||||||
import sys
|
|
||||||
|
|
||||||
LIMIT = 300
|
|
||||||
try:
|
|
||||||
with open(sys.argv[1], encoding="utf-8", errors="replace") as response:
|
|
||||||
raw = response.read().strip()
|
|
||||||
except OSError:
|
|
||||||
raise SystemExit(0)
|
|
||||||
if not raw:
|
|
||||||
raise SystemExit(0)
|
|
||||||
detail = ""
|
|
||||||
try:
|
|
||||||
parsed = json.loads(raw)
|
|
||||||
if isinstance(parsed, dict):
|
|
||||||
for key in ("message", "error", "errors"):
|
|
||||||
value = parsed.get(key)
|
|
||||||
if isinstance(value, str) and value.strip():
|
|
||||||
detail = value.strip()
|
|
||||||
break
|
|
||||||
if isinstance(value, list) and value:
|
|
||||||
detail = "; ".join(str(item) for item in value).strip()
|
|
||||||
break
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
if not detail:
|
|
||||||
detail = raw.splitlines()[0].strip()
|
|
||||||
if not detail:
|
|
||||||
raise SystemExit(0)
|
|
||||||
if len(detail) > LIMIT:
|
|
||||||
detail = detail[:LIMIT] + "..."
|
|
||||||
print(f" — provider said: {detail}")
|
|
||||||
PY
|
|
||||||
}
|
|
||||||
|
|
||||||
# Post a comment to a Gitea PR (PR comments ARE issue comments) via the
|
# Post a comment to a Gitea PR (PR comments ARE issue comments) via the
|
||||||
# supported REST API and verify it against a PROVIDER-RETURNED created id. The
|
# supported REST API and verify it against a PROVIDER-RETURNED created id. The
|
||||||
# write is a direct POST that returns the created comment object, so we learn
|
# write is a direct POST that returns the created comment object, so we learn
|
||||||
@@ -199,7 +150,7 @@ print(json.dumps({"body": os.environ["COMMENT_BODY"]}))
|
|||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
if [[ "$write_status" != "201" ]]; then
|
if [[ "$write_status" != "201" ]]; then
|
||||||
echo "Error: Gitea comment write failed with HTTP $write_status$(gitea_error_detail "$write_file")" >&2
|
echo "Error: Gitea comment write failed with HTTP $write_status" >&2
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -228,7 +179,7 @@ PY
|
|||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
if [[ "$readback_status" != "200" ]]; then
|
if [[ "$readback_status" != "200" ]]; then
|
||||||
echo "Error: Gitea comment read-back failed with HTTP $readback_status$(gitea_error_detail "$readback_file")" >&2
|
echo "Error: Gitea comment read-back failed with HTTP $readback_status" >&2
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -419,7 +370,7 @@ gitea_authenticated_login() {
|
|||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
if [[ "$status" != "200" ]]; then
|
if [[ "$status" != "200" ]]; then
|
||||||
echo "Error: Gitea authenticated-identity read failed with HTTP $status$(gitea_error_detail "$response_file")" >&2
|
echo "Error: Gitea authenticated-identity read failed with HTTP $status" >&2
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -456,7 +407,7 @@ gitea_read_pr_head_into() {
|
|||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
if [[ "$status" != "200" ]]; then
|
if [[ "$status" != "200" ]]; then
|
||||||
echo "Error: Gitea PR head read failed with HTTP $status$(gitea_error_detail "$pr_file")" >&2
|
echo "Error: Gitea PR head read failed with HTTP $status" >&2
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
python3 - "$pr_file" <<'PY'
|
python3 - "$pr_file" <<'PY'
|
||||||
@@ -546,7 +497,7 @@ print(json.dumps({
|
|||||||
fi
|
fi
|
||||||
# Gitea returns 200 (occasionally 201) with the created review object.
|
# Gitea returns 200 (occasionally 201) with the created review object.
|
||||||
if [[ "$write_status" != "200" && "$write_status" != "201" ]]; then
|
if [[ "$write_status" != "200" && "$write_status" != "201" ]]; then
|
||||||
echo "Error: Gitea review submit failed with HTTP $write_status$(gitea_error_detail "$write_file")" >&2
|
echo "Error: Gitea review submit failed with HTTP $write_status (#865: no durable review created)" >&2
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -575,7 +526,7 @@ PY
|
|||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
if [[ "$readback_status" != "200" ]]; then
|
if [[ "$readback_status" != "200" ]]; then
|
||||||
echo "Error: Gitea review read-back failed with HTTP $readback_status$(gitea_error_detail "$readback_file")" >&2
|
echo "Error: Gitea review read-back failed with HTTP $readback_status" >&2
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@@ -58,9 +58,6 @@ CREDENTIALS_FILE="$WORK_DIR/credentials.json"
|
|||||||
# A dedicated scratch dir the wrapper is pointed at via TMPDIR, so the leak
|
# A dedicated scratch dir the wrapper is pointed at via TMPDIR, so the leak
|
||||||
# check can assert every POST/GET body + metadata temp file is cleaned up.
|
# check can assert every POST/GET body + metadata temp file is cleaned up.
|
||||||
TMP_SCRATCH="$WORK_DIR/scratch"
|
TMP_SCRATCH="$WORK_DIR/scratch"
|
||||||
# Sandboxed HOME so nothing under the real $HOME (notably the per-slot Gitea token
|
|
||||||
# store at ~/.config/mosaic/secrets/gitea-tokens/) is reachable from the wrapper.
|
|
||||||
HOME_DIR="$WORK_DIR/home"
|
|
||||||
|
|
||||||
cleanup() {
|
cleanup() {
|
||||||
rm -rf "$WORK_DIR"
|
rm -rf "$WORK_DIR"
|
||||||
@@ -81,18 +78,9 @@ OVERRIDE_TOKEN="override-token-placeholder"
|
|||||||
CROSS_HOST_LOGIN="foreign-host-reviewer"
|
CROSS_HOST_LOGIN="foreign-host-reviewer"
|
||||||
CROSS_HOST_TOKEN="cross-host-token-placeholder"
|
CROSS_HOST_TOKEN="cross-host-token-placeholder"
|
||||||
|
|
||||||
mkdir -p "$REPO_DIR" "$BIN_DIR" "$XDG_DIR" "$STATE_DIR" "$TMP_SCRATCH" "$HOME_DIR"
|
mkdir -p "$REPO_DIR" "$BIN_DIR" "$XDG_DIR" "$STATE_DIR" "$TMP_SCRATCH"
|
||||||
git -C "$REPO_DIR" init -q
|
git -C "$REPO_DIR" init -q
|
||||||
git -C "$REPO_DIR" remote add origin https://git.mosaicstack.dev/mosaicstack/stack.git
|
git -C "$REPO_DIR" remote add origin https://git.mosaicstack.dev/mosaicstack/stack.git
|
||||||
# HERMETICITY: get_gitea_token() step 0 resolves a per-agent identity from
|
|
||||||
# `git config --get mosaic.gitIdentity`, which on a provisioned agent seat is set
|
|
||||||
# GLOBALLY and therefore leaks into this fresh repo. It then reads a REAL per-slot
|
|
||||||
# token from $HOME and returns it WITHOUT ever consulting MOSAIC_CREDENTIALS_FILE,
|
|
||||||
# so the fixture credentials below are silently ignored and the suite runs against
|
|
||||||
# production credentials. An empty repo-local value shadows the global one and reads
|
|
||||||
# back as empty at rc=0, restoring the shared-credential path this suite intends to
|
|
||||||
# exercise. Paired with the sandboxed HOME in run_review().
|
|
||||||
git -C "$REPO_DIR" config mosaic.gitIdentity ""
|
|
||||||
|
|
||||||
# tea config: the override login carries its own token here. The default login
|
# tea config: the override login carries its own token here. The default login
|
||||||
# name ("mosaicstack") is deliberately absent, so the no-override default path
|
# name ("mosaicstack") is deliberately absent, so the no-override default path
|
||||||
@@ -278,24 +266,6 @@ submitted = json.loads(os.environ["PR_REVIEW_PAYLOAD"])
|
|||||||
with open(state_path, encoding="utf-8") as handle:
|
with open(state_path, encoding="utf-8") as handle:
|
||||||
reviews = json.load(handle)
|
reviews = json.load(handle)
|
||||||
|
|
||||||
# review-refused-422 (#1004): the server REFUSES the submit outright with a
|
|
||||||
# definite, correct, machine-readable reason in the body — the shape Gitea
|
|
||||||
# returns when the acting credential authored the PR. Nothing is created. The
|
|
||||||
# wrapper must surface what the server said and must NOT relabel this as the
|
|
||||||
# #865 silent-no-op defect class, which is precisely what it is not.
|
|
||||||
if mode == "review-refused-422":
|
|
||||||
print("422")
|
|
||||||
print(json.dumps({"message": "Cannot approve your own pull request"}))
|
|
||||||
raise SystemExit(0)
|
|
||||||
|
|
||||||
# review-refused-html (#1004): a non-JSON error body, as a fronting proxy or
|
|
||||||
# gateway emits. The detail extraction must degrade to the first raw line rather
|
|
||||||
# than silently dropping the only explanation available.
|
|
||||||
if mode == "review-refused-html":
|
|
||||||
print("502")
|
|
||||||
print("<html><head><title>502 Bad Gateway</title></head>\n<body>nginx</body></html>")
|
|
||||||
raise SystemExit(0)
|
|
||||||
|
|
||||||
# no-op-concurrent-review: the wrapper's own submit is SUPPRESSED (200, no
|
# no-op-concurrent-review: the wrapper's own submit is SUPPRESSED (200, no
|
||||||
# created object) even though a concurrent same-identity, same-state review at
|
# created object) even though a concurrent same-identity, same-state review at
|
||||||
# the same head already exists. Nothing is persisted; no created id to verify.
|
# the same head already exists. Nothing is persisted; no created id to verify.
|
||||||
@@ -547,8 +517,6 @@ run_review() {
|
|||||||
cd "$REPO_DIR"
|
cd "$REPO_DIR"
|
||||||
PATH="$BIN_DIR:$PATH" \
|
PATH="$BIN_DIR:$PATH" \
|
||||||
TMPDIR="$TMP_SCRATCH" \
|
TMPDIR="$TMP_SCRATCH" \
|
||||||
HOME="$HOME_DIR" \
|
|
||||||
MOSAIC_GIT_IDENTITY="" \
|
|
||||||
XDG_CONFIG_HOME="$XDG_DIR" \
|
XDG_CONFIG_HOME="$XDG_DIR" \
|
||||||
MOSAIC_CREDENTIALS_FILE="$CREDENTIALS_FILE" \
|
MOSAIC_CREDENTIALS_FILE="$CREDENTIALS_FILE" \
|
||||||
PR_REVIEW_TEA_LOG="$TEA_LOG" \
|
PR_REVIEW_TEA_LOG="$TEA_LOG" \
|
||||||
@@ -972,44 +940,4 @@ if grep -q 'Approved and verified' "$OUTPUT_FILE"; then
|
|||||||
fi
|
fi
|
||||||
assert_no_temp_leak "review-body-null"
|
assert_no_temp_leak "review-body-null"
|
||||||
|
|
||||||
# Case 19 (#1004): an outright server REFUSAL must report the provider's own
|
|
||||||
# reason and must NOT be relabelled as the #865 silent-no-op defect class. The
|
|
||||||
# old arm hardcoded "(#865: no durable review created)" for EVERY non-2xx, so a
|
|
||||||
# 422/403/404 — all of them definite, correct refusals the server explained in
|
|
||||||
# the discarded body — arrived at the caller wearing the name of the one defect
|
|
||||||
# they are not. That misdirection is what makes an operator re-issue the request
|
|
||||||
# by hand against the live object to find out what actually happened.
|
|
||||||
if run_review review-refused-422 approve; then
|
|
||||||
echo "FAIL: approve reported success when the server refused the submit" >&2
|
|
||||||
cat "$OUTPUT_FILE" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
grep -q 'HTTP 422' "$OUTPUT_FILE"
|
|
||||||
if ! grep -q 'Cannot approve your own pull request' "$OUTPUT_FILE"; then
|
|
||||||
echo "FAIL: the provider's stated reason was discarded (#1004)" >&2
|
|
||||||
cat "$OUTPUT_FILE" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
if grep -q '#865: no durable review created' "$OUTPUT_FILE"; then
|
|
||||||
echo "FAIL: a server refusal was misattributed to the #865 defect class (#1004)" >&2
|
|
||||||
cat "$OUTPUT_FILE" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
assert_no_temp_leak "review-refused-422"
|
|
||||||
|
|
||||||
# Case 20 (#1004): a non-JSON error body (a fronting proxy's HTML page) must
|
|
||||||
# still yield something the caller can act on, rather than a bare status code.
|
|
||||||
if run_review review-refused-html approve; then
|
|
||||||
echo "FAIL: approve reported success on a 502" >&2
|
|
||||||
cat "$OUTPUT_FILE" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
grep -q 'HTTP 502' "$OUTPUT_FILE"
|
|
||||||
if ! grep -q '502 Bad Gateway' "$OUTPUT_FILE"; then
|
|
||||||
echo "FAIL: a non-JSON error body was dropped instead of degrading to its first line (#1004)" >&2
|
|
||||||
cat "$OUTPUT_FILE" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
assert_no_temp_leak "review-refused-html"
|
|
||||||
|
|
||||||
echo "pr-review.sh REST review + comment create/read-back regression passed"
|
echo "pr-review.sh REST review + comment create/read-back regression passed"
|
||||||
|
|||||||
@@ -1,165 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# check-test-enumeration.sh — CI test-membership guard (#1017).
|
|
||||||
#
|
|
||||||
# CI reaches shell suites through two hand-enumerated surfaces:
|
|
||||||
# S1 packages/mosaic/package.json scripts."test:framework-shell"
|
|
||||||
# S2 .woodpecker/ci.yml direct `bash packages/mosaic/framework/tools/...` commands
|
|
||||||
#
|
|
||||||
# A hand-enumerated allowlist re-arms its own gap: a new suite never auto-joins,
|
|
||||||
# so the list silently under-runs the disk (17 of 39 suites were invisible when
|
|
||||||
# #1017 was filed). This guard makes that under-run impossible to do silently:
|
|
||||||
#
|
|
||||||
# FAIL when a suite-shaped file exists on disk and is neither enumerated on
|
|
||||||
# the UNION of both surfaces nor listed in the exclusions file.
|
|
||||||
# ("Enumerated", deliberately — F1/F2 on PR #1018 proved this guard sees
|
|
||||||
# NAMING, not reachability, and its words must not claim otherwise.)
|
|
||||||
# FAIL when either surface names a path that does not exist on disk
|
|
||||||
# (a rename manufactures a stale entry silently — checked BOTH directions).
|
|
||||||
# FAIL when an exclusion entry has no reason, names a path that is gone,
|
|
||||||
# names a path that is also enumerated (contradiction), or names a path
|
|
||||||
# outside the population (dead weight that looks like coverage).
|
|
||||||
#
|
|
||||||
# POPULATION PATTERN — a deliberate decision, stated per #1017's record:
|
|
||||||
# basename matches *test*.sh (contains "test", ends ".sh"). Deliberately BROAD:
|
|
||||||
# the strict `test-*.sh` prefix cannot even name three real boundary files
|
|
||||||
# (tmux/agent-send.test.sh — CI-run; orchestrator/smoke-test.sh;
|
|
||||||
# wake/validate-973/microtest-wake-assert.sh), and three independent censuses
|
|
||||||
# handled that last file three different ways with no trace of the judgement.
|
|
||||||
# The broad pattern makes such files MEMBERS, so their disposition must be a
|
|
||||||
# signed exclusion, not an accident of the glob. The SAME pattern is applied to
|
|
||||||
# both sides of the comparison (disk and enumeration) — a comparison globbed two
|
|
||||||
# ways runs on two different populations. Scripts outside the pattern on both
|
|
||||||
# sides symmetrically (e.g. check-resident-budget.sh, verify-sanitized.sh) are
|
|
||||||
# check-scripts, not suites; their existence is still verified via the
|
|
||||||
# both-directions rule because every surface-named path must exist on disk.
|
|
||||||
#
|
|
||||||
# The surfaces are PARSED, never line-ranged: three seats independently
|
|
||||||
# mis-scoped hand-written line ranges against these files (#1017 thread). S1 is
|
|
||||||
# read via JSON + command-chain tokenization; S2 by extracting every
|
|
||||||
# packages/mosaic/framework/tools/ token wherever it appears in the file.
|
|
||||||
#
|
|
||||||
# Exclusions file format (framework/tools/quality/test-enumeration-exclusions.txt):
|
|
||||||
# <repo-relative-path> | <non-empty reason>
|
|
||||||
# Lines starting with # and blank lines are ignored. An exclusion is a recorded
|
|
||||||
# decision someone signed, not an omission nobody made.
|
|
||||||
|
|
||||||
set -uo pipefail
|
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
||||||
ROOT="$(cd "$SCRIPT_DIR/../../../../../.." && pwd)"
|
|
||||||
while (( $# )); do
|
|
||||||
case "$1" in
|
|
||||||
--root) ROOT="$(cd "$2" && pwd)"; shift 2 ;;
|
|
||||||
*) echo "usage: check-test-enumeration.sh [--root <repo-root>]" >&2; exit 2 ;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
|
|
||||||
PKG_JSON="$ROOT/packages/mosaic/package.json"
|
|
||||||
CI_YML="$ROOT/.woodpecker/ci.yml"
|
|
||||||
TOOLS_DIR="$ROOT/packages/mosaic/framework/tools"
|
|
||||||
EXCLUSIONS="$TOOLS_DIR/quality/test-enumeration-exclusions.txt"
|
|
||||||
|
|
||||||
for f in "$PKG_JSON" "$CI_YML"; do
|
|
||||||
[[ -f "$f" ]] || { echo "FAIL: required surface file missing: $f" >&2; exit 2; }
|
|
||||||
done
|
|
||||||
[[ -d "$TOOLS_DIR" ]] || { echo "FAIL: tools dir missing: $TOOLS_DIR" >&2; exit 2; }
|
|
||||||
|
|
||||||
fail_count=0
|
|
||||||
fail() { printf 'FAIL %s\n' "$1"; fail_count=$(( fail_count + 1 )); }
|
|
||||||
|
|
||||||
# in_population <repo-relative path> — the single pattern, used for BOTH sides.
|
|
||||||
in_population() {
|
|
||||||
local base; base="$(basename "$1")"
|
|
||||||
[[ "$base" == *test*.sh ]]
|
|
||||||
}
|
|
||||||
|
|
||||||
# --- Surface 1: package.json test:framework-shell, parsed, repo-relative -----
|
|
||||||
# Tokens are script paths iff they contain "/" and end .sh/.py; interpreter
|
|
||||||
# names and flags are skipped. Paths are relative to packages/mosaic/.
|
|
||||||
mapfile -t S1 < <(python3 - "$PKG_JSON" <<'PY'
|
|
||||||
import json, shlex, sys
|
|
||||||
cmd = json.load(open(sys.argv[1]))["scripts"].get("test:framework-shell", "")
|
|
||||||
seen = []
|
|
||||||
for seg in cmd.split("&&"):
|
|
||||||
for tok in shlex.split(seg):
|
|
||||||
if "/" in tok and (tok.endswith(".sh") or tok.endswith(".py")):
|
|
||||||
path = "packages/mosaic/" + tok
|
|
||||||
if path not in seen:
|
|
||||||
seen.append(path)
|
|
||||||
print("\n".join(seen))
|
|
||||||
PY
|
|
||||||
)
|
|
||||||
|
|
||||||
# --- Surface 2: ci.yml, every framework/tools token wherever it appears ------
|
|
||||||
# Comment lines (first non-whitespace char is #) are skipped BEFORE matching:
|
|
||||||
# commenting an invocation out is the most common way a suite actually gets
|
|
||||||
# disabled, and a raw-text regex would keep calling it enumerated (F1, 20155 on
|
|
||||||
# PR #1018 — demonstrated, not argued). Known residual limit: a path named only
|
|
||||||
# in a TRAILING comment on a live line still matches; no such line exists today
|
|
||||||
# and full fidelity would need a YAML parser the CI image does not ship.
|
|
||||||
mapfile -t S2 < <(grep -vE '^[[:space:]]*#' "$CI_YML" \
|
|
||||||
| grep -oE 'packages/mosaic/framework/tools/[A-Za-z0-9_./-]+\.(sh|py)' | sort -u)
|
|
||||||
|
|
||||||
# --- Union, and its population-restricted view -------------------------------
|
|
||||||
declare -A ENUM=() ENUM_POP=()
|
|
||||||
for p in "${S1[@]:-}" "${S2[@]:-}"; do
|
|
||||||
[[ -n "$p" ]] || continue
|
|
||||||
ENUM["$p"]=1
|
|
||||||
in_population "$p" && ENUM_POP["$p"]=1
|
|
||||||
done
|
|
||||||
|
|
||||||
# --- Direction B: every surface-named path must exist on disk ----------------
|
|
||||||
for p in "${!ENUM[@]}"; do
|
|
||||||
[[ -f "$ROOT/$p" ]] || fail "STALE ENUMERATION: surfaces name '$p' but it does not exist on disk"
|
|
||||||
done
|
|
||||||
|
|
||||||
# --- Exclusions: parsed with the same rigor the enumeration gets -------------
|
|
||||||
declare -A EXCLUDED=()
|
|
||||||
if [[ -f "$EXCLUSIONS" ]]; then
|
|
||||||
lineno=0
|
|
||||||
while IFS= read -r line; do
|
|
||||||
lineno=$(( lineno + 1 ))
|
|
||||||
[[ "$line" =~ ^[[:space:]]*(#|$) ]] && continue
|
|
||||||
path="${line%%|*}"; reason="${line#*|}"
|
|
||||||
path="$(echo "$path" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//')"
|
|
||||||
reason="$(echo "$reason" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//')"
|
|
||||||
if [[ "$line" != *"|"* || -z "$reason" ]]; then
|
|
||||||
fail "EXCLUSION MISSING REASON: line $lineno ('$path') — an exclusion is a recorded decision someone signed"
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
if [[ ! -f "$ROOT/$path" ]]; then
|
|
||||||
fail "STALE EXCLUSION: line $lineno excludes '$path' which does not exist on disk"
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
if ! in_population "$path"; then
|
|
||||||
fail "EXCLUSION OUTSIDE POPULATION: line $lineno excludes '$path' which the population pattern does not name — dead weight that reads as coverage"
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
if [[ -n "${ENUM[$path]:-}" ]]; then
|
|
||||||
fail "CONTRADICTORY EXCLUSION: line $lineno excludes '$path' which the surfaces already enumerate"
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
EXCLUDED["$path"]=1
|
|
||||||
done < "$EXCLUSIONS"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# --- Direction A: disk population must be enumerated or signed-excluded ------
|
|
||||||
disk_total=0
|
|
||||||
unlisted=0
|
|
||||||
while IFS= read -r f; do
|
|
||||||
rel="${f#"$ROOT"/}"
|
|
||||||
in_population "$rel" || continue
|
|
||||||
disk_total=$(( disk_total + 1 ))
|
|
||||||
if [[ -z "${ENUM_POP[$rel]:-}" && -z "${EXCLUDED[$rel]:-}" ]]; then
|
|
||||||
fail "UNENUMERATED: '$rel' exists on disk but is neither enumerated on any CI surface nor signed in the exclusions file"
|
|
||||||
unlisted=$(( unlisted + 1 ))
|
|
||||||
fi
|
|
||||||
done < <(find "$TOOLS_DIR" -type f -name '*.sh' | sort)
|
|
||||||
|
|
||||||
if (( fail_count > 0 )); then
|
|
||||||
printf 'enumeration guard: %d failure(s) — population %d, enumerated (in-population) %d, excluded %d\n' \
|
|
||||||
"$fail_count" "$disk_total" "${#ENUM_POP[@]}" "${#EXCLUDED[@]}"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
printf 'enumeration guard: OK — population %d, enumerated (in-population) %d, excluded (signed) %d, surfaces name %d path(s), all present on disk\n' \
|
|
||||||
"$disk_total" "${#ENUM_POP[@]}" "${#EXCLUDED[@]}" "${#ENUM[@]}"
|
|
||||||
@@ -1,166 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# test-check-test-enumeration.sh — needles for the enumeration guard (#1017).
|
|
||||||
#
|
|
||||||
# Every failure mode the guard promises gets BOTH polarities:
|
|
||||||
# NEEDLE a fixture that MUST trip the guard, asserted on the guard's OWN
|
|
||||||
# words (--out) — exit 1 alone cannot distinguish "caught the rogue
|
|
||||||
# file" from "choked on the fixture".
|
|
||||||
# CONTROL a fixture that MUST pass. A guard that failed unconditionally
|
|
||||||
# would satisfy every needle here — the null-case defect the guard's
|
|
||||||
# own subject matter (#1017) exists to make impossible.
|
|
||||||
#
|
|
||||||
# The needles encode the specific errors that produced #1017's thread:
|
|
||||||
# n6 is the 20124 boundary file (a suite the strict prefix cannot name);
|
|
||||||
# n2b proves surface 2 is PARSED, not line-ranged (three seats mis-scoped
|
|
||||||
# hand-written ranges against ci.yml);
|
|
||||||
# n5/n7 keep the exclusions file honest so it cannot become the next silent cap;
|
|
||||||
# n8/c4 are F1 (20155): a commented-out ci.yml line is NOT enumeration —
|
|
||||||
# commenting-out is the most common way a suite actually gets disabled,
|
|
||||||
# and it must fail loud in one direction without false-staling the other.
|
|
||||||
|
|
||||||
set -uo pipefail
|
|
||||||
|
|
||||||
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
||||||
GUARD="$HERE/check-test-enumeration.sh"
|
|
||||||
TMP="$(mktemp -d)"; trap 'rm -rf "$TMP"' EXIT
|
|
||||||
PASS=0; FAIL=0
|
|
||||||
|
|
||||||
# fixture <name> — a minimal repo root the guard accepts via --root:
|
|
||||||
# one suite enumerated on S1 (plus a naming-outlier suite, so the S1 parser's
|
|
||||||
# handling of non-prefix names is always exercised), one on S2, one check-script
|
|
||||||
# named on S2 that is outside the population, and an empty exclusions file.
|
|
||||||
fixture() {
|
|
||||||
local r="$TMP/$1"
|
|
||||||
mkdir -p "$r/packages/mosaic/framework/tools/git" \
|
|
||||||
"$r/packages/mosaic/framework/tools/tmux" \
|
|
||||||
"$r/packages/mosaic/framework/tools/quality/scripts" \
|
|
||||||
"$r/.woodpecker"
|
|
||||||
printf '#!/usr/bin/env bash\nexit 0\n' > "$r/packages/mosaic/framework/tools/git/test-a.sh"
|
|
||||||
printf '#!/usr/bin/env bash\nexit 0\n' > "$r/packages/mosaic/framework/tools/tmux/outlier.test.sh"
|
|
||||||
printf '#!/usr/bin/env bash\nexit 0\n' > "$r/packages/mosaic/framework/tools/quality/scripts/test-ci.sh"
|
|
||||||
printf '#!/usr/bin/env bash\nexit 0\n' > "$r/packages/mosaic/framework/tools/quality/scripts/verify-thing.sh"
|
|
||||||
cat > "$r/packages/mosaic/package.json" <<'JSON'
|
|
||||||
{"scripts": {"test:framework-shell": "bash framework/tools/git/test-a.sh && bash framework/tools/tmux/outlier.test.sh"}}
|
|
||||||
JSON
|
|
||||||
cat > "$r/.woodpecker/ci.yml" <<'YML'
|
|
||||||
steps:
|
|
||||||
sanitize:
|
|
||||||
commands:
|
|
||||||
- bash packages/mosaic/framework/tools/quality/scripts/verify-thing.sh
|
|
||||||
guard:
|
|
||||||
commands:
|
|
||||||
- bash packages/mosaic/framework/tools/quality/scripts/test-ci.sh
|
|
||||||
YML
|
|
||||||
: > "$r/packages/mosaic/framework/tools/quality/test-enumeration-exclusions.txt"
|
|
||||||
printf '%s' "$r"
|
|
||||||
}
|
|
||||||
|
|
||||||
# expect <kind> <want-exit> <desc> [--out <substring>] -- <root>
|
|
||||||
expect() {
|
|
||||||
local kind="$1" want="$2" desc="$3"; shift 3
|
|
||||||
local need_out=""
|
|
||||||
while (( $# )); do
|
|
||||||
case "$1" in
|
|
||||||
--out) need_out="$2"; shift 2 ;;
|
|
||||||
--) shift; break ;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
local root="$1" got=0 out
|
|
||||||
out="$(bash "$GUARD" --root "$root" 2>&1)" || got=$?
|
|
||||||
local why=""
|
|
||||||
[[ "$got" == "$want" ]] || why="wanted exit $want, got $got"
|
|
||||||
if [[ -z "$why" && -n "$need_out" && "$out" != *"$need_out"* ]]; then
|
|
||||||
why="exit $got as expected, but output never said: $need_out"
|
|
||||||
fi
|
|
||||||
if [[ -z "$why" ]]; then
|
|
||||||
printf ' PASS [%-7s] %s (exit %s)\n' "$kind" "$desc" "$got"
|
|
||||||
PASS=$(( PASS + 1 ))
|
|
||||||
else
|
|
||||||
printf ' FAIL [%-7s] %s — %s\n' "$kind" "$desc" "$why"
|
|
||||||
printf '%s\n' "$out" | sed 's/^/ | /'
|
|
||||||
FAIL=$(( FAIL + 1 ))
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
excl() { printf '%s\n' "$2" >> "$1/packages/mosaic/framework/tools/quality/test-enumeration-exclusions.txt"; }
|
|
||||||
|
|
||||||
echo "=== c1: a fully consistent fixture passes ==="
|
|
||||||
R="$(fixture c1)"
|
|
||||||
expect CONTROL 0 "consistent tree: both surfaces enumerated, nothing unlisted" \
|
|
||||||
--out "enumeration guard: OK" -- "$R"
|
|
||||||
|
|
||||||
echo "=== n1: an on-disk suite reachable from no surface must fail ==="
|
|
||||||
R="$(fixture n1)"
|
|
||||||
printf '#!/usr/bin/env bash\nexit 0\n' > "$R/packages/mosaic/framework/tools/git/test-rogue.sh"
|
|
||||||
expect NEEDLE 1 "unlisted suite is named in the failure" \
|
|
||||||
--out "UNENUMERATED: 'packages/mosaic/framework/tools/git/test-rogue.sh'" -- "$R"
|
|
||||||
|
|
||||||
echo "=== n6: the 20124 boundary file — a suite the strict prefix cannot name ==="
|
|
||||||
R="$(fixture n6)"
|
|
||||||
printf '#!/usr/bin/env bash\nexit 0\n' > "$R/packages/mosaic/framework/tools/git/rogue.test.sh"
|
|
||||||
expect NEEDLE 1 "naming-outlier suite (*.test.sh) is a population member, not invisible" \
|
|
||||||
--out "UNENUMERATED: 'packages/mosaic/framework/tools/git/rogue.test.sh'" -- "$R"
|
|
||||||
|
|
||||||
echo "=== c3: a non-suite script outside the pattern is outside it on BOTH sides ==="
|
|
||||||
R="$(fixture c3)"
|
|
||||||
printf '#!/usr/bin/env bash\nexit 0\n' > "$R/packages/mosaic/framework/tools/git/check-unrelated.sh"
|
|
||||||
expect CONTROL 0 "check-script on disk, unlisted, outside population: not the guard's business" \
|
|
||||||
--out "enumeration guard: OK" -- "$R"
|
|
||||||
|
|
||||||
echo "=== n2/n2b: a surface naming a path absent from disk must fail — both surfaces ==="
|
|
||||||
R="$(fixture n2)"
|
|
||||||
rm "$R/packages/mosaic/framework/tools/git/test-a.sh"
|
|
||||||
expect NEEDLE 1 "S1 (package.json) stale entry" \
|
|
||||||
--out "STALE ENUMERATION: surfaces name 'packages/mosaic/framework/tools/git/test-a.sh'" -- "$R"
|
|
||||||
R="$(fixture n2b)"
|
|
||||||
rm "$R/packages/mosaic/framework/tools/quality/scripts/test-ci.sh"
|
|
||||||
expect NEEDLE 1 "S2 (ci.yml) stale entry — proves ci.yml is parsed, not line-ranged" \
|
|
||||||
--out "STALE ENUMERATION: surfaces name 'packages/mosaic/framework/tools/quality/scripts/test-ci.sh'" -- "$R"
|
|
||||||
|
|
||||||
echo "=== c2: a rogue suite with a SIGNED exclusion passes, and is counted ==="
|
|
||||||
R="$(fixture c2)"
|
|
||||||
printf '#!/usr/bin/env bash\nexit 0\n' > "$R/packages/mosaic/framework/tools/git/test-rogue.sh"
|
|
||||||
excl "$R" "packages/mosaic/framework/tools/git/test-rogue.sh | non-hermetic pending fixture work (needle-suite specimen)"
|
|
||||||
expect CONTROL 0 "signed exclusion is honoured and visible in the summary" \
|
|
||||||
--out "excluded (signed) 1" -- "$R"
|
|
||||||
|
|
||||||
echo "=== n3: an exclusion with no reason is not a decision ==="
|
|
||||||
R="$(fixture n3)"
|
|
||||||
printf '#!/usr/bin/env bash\nexit 0\n' > "$R/packages/mosaic/framework/tools/git/test-rogue.sh"
|
|
||||||
excl "$R" "packages/mosaic/framework/tools/git/test-rogue.sh | "
|
|
||||||
expect NEEDLE 1 "empty reason rejected" --out "EXCLUSION MISSING REASON" -- "$R"
|
|
||||||
R="$(fixture n3b)"
|
|
||||||
printf '#!/usr/bin/env bash\nexit 0\n' > "$R/packages/mosaic/framework/tools/git/test-rogue.sh"
|
|
||||||
excl "$R" "packages/mosaic/framework/tools/git/test-rogue.sh"
|
|
||||||
expect NEEDLE 1 "missing separator rejected (the path alone is not a signature)" \
|
|
||||||
--out "EXCLUSION MISSING REASON" -- "$R"
|
|
||||||
|
|
||||||
echo "=== n4: an exclusion whose path is gone is stale, not satisfied ==="
|
|
||||||
R="$(fixture n4)"
|
|
||||||
excl "$R" "packages/mosaic/framework/tools/git/test-vanished.sh | was excluded once, then deleted"
|
|
||||||
expect NEEDLE 1 "stale exclusion rejected" --out "STALE EXCLUSION" -- "$R"
|
|
||||||
|
|
||||||
echo "=== n5: excluding an enumerated suite is a contradiction, not belt-and-braces ==="
|
|
||||||
R="$(fixture n5)"
|
|
||||||
excl "$R" "packages/mosaic/framework/tools/git/test-a.sh | already in CI but excluded anyway"
|
|
||||||
expect NEEDLE 1 "contradictory exclusion rejected" --out "CONTRADICTORY EXCLUSION" -- "$R"
|
|
||||||
|
|
||||||
echo "=== n8/c4: a commented-out ci.yml line is not enumeration (F1, 20155) ==="
|
|
||||||
R="$(fixture n8)"
|
|
||||||
printf '#!/usr/bin/env bash\nexit 0\n' > "$R/packages/mosaic/framework/tools/git/test-disabled.sh"
|
|
||||||
printf ' # - bash packages/mosaic/framework/tools/git/test-disabled.sh\n' >> "$R/.woodpecker/ci.yml"
|
|
||||||
expect NEEDLE 1 "suite named only in a commented-out invocation is UNENUMERATED" \
|
|
||||||
--out "UNENUMERATED: 'packages/mosaic/framework/tools/git/test-disabled.sh'" -- "$R"
|
|
||||||
R="$(fixture c4)"
|
|
||||||
printf ' # - bash packages/mosaic/framework/tools/git/test-vanished.sh\n' >> "$R/.woodpecker/ci.yml"
|
|
||||||
expect CONTROL 0 "comment naming an absent path raises no false stale-enumeration" \
|
|
||||||
--out "enumeration guard: OK" -- "$R"
|
|
||||||
|
|
||||||
echo "=== n7: excluding a file outside the population is dead weight, not coverage ==="
|
|
||||||
R="$(fixture n7)"
|
|
||||||
excl "$R" "packages/mosaic/framework/tools/quality/scripts/verify-thing.sh | not a suite but signing it anyway"
|
|
||||||
expect NEEDLE 1 "out-of-population exclusion rejected" --out "EXCLUSION OUTSIDE POPULATION" -- "$R"
|
|
||||||
|
|
||||||
echo
|
|
||||||
printf 'enumeration-guard needles: %d passed, %d failed\n' "$PASS" "$FAIL"
|
|
||||||
(( FAIL == 0 ))
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
# test-enumeration-exclusions.txt — signed exclusions for check-test-enumeration.sh (#1017).
|
|
||||||
#
|
|
||||||
# Every entry is a recorded decision: a suite-shaped file that exists on disk,
|
|
||||||
# is NOT reachable from any CI surface, and carries the reason someone signed
|
|
||||||
# for that. The guard FAILS on an entry with no reason, a stale path, or a path
|
|
||||||
# the surfaces already enumerate. Burning an entry down = making it CI-reachable
|
|
||||||
# (package.json test:framework-shell or a ci.yml step) and deleting its line.
|
|
||||||
#
|
|
||||||
# Format: <repo-relative path> | <reason>
|
|
||||||
# All entries below were signed at #1017's filing base (main 826a8b3b, 2026-07-31)
|
|
||||||
# by pepper (sb-it-1-dt); measurements cited are one-run assertions from that seat.
|
|
||||||
|
|
||||||
# --- tools/git: the #1007 five — non-hermetic, resolve real credentials ---
|
|
||||||
packages/mosaic/framework/tools/git/test-pr-merge-gitea-empty-uid.sh | resolves real credentials (#1007 census); joins CI after the wrapper-half hermeticity fix (git -C scoping)
|
|
||||||
packages/mosaic/framework/tools/git/test-issue-create-interactive-auth.sh | resolves real credentials (#1007 census); joins CI after the wrapper-half hermeticity fix
|
|
||||||
packages/mosaic/framework/tools/git/test-gitea-login-resolution.sh | resolves real credentials (#1007 census); joins CI after the wrapper-half hermeticity fix
|
|
||||||
packages/mosaic/framework/tools/git/test-pr-metadata-gitea.sh | resolves real credentials (#1007 census, fourth entry via family-grep); joins CI after the wrapper-half hermeticity fix
|
|
||||||
packages/mosaic/framework/tools/git/test-issue-comment-readback.sh | resolves real credentials (#1007 census, fifth entry); joins CI after the wrapper-half hermeticity fix
|
|
||||||
|
|
||||||
# --- tools/git: push guards — measured green locally, CI-image fitness unverified ---
|
|
||||||
packages/mosaic/framework/tools/git/test-push-guard.sh | measured green at 826a8b3b (46 passed / 0 failed, one run, 2026-07-31); CI-image fitness unverified; #1017 burndown
|
|
||||||
packages/mosaic/framework/tools/git/test-mutate-push-guard.sh | measured green at 826a8b3b (8/0, 13 mutants killed 0 survived, one run, 2026-07-31); requires setsid (util-linux), absent from the alpine base image; #1017 burndown
|
|
||||||
packages/mosaic/framework/tools/git/test-issue-create-body-safety.sh | hermeticity unaudited — the unprotected suite in #1007's protected/unprotected split; audit before CI; #1017 burndown
|
|
||||||
|
|
||||||
# --- tools/git: unmeasured ---
|
|
||||||
packages/mosaic/framework/tools/git/test-verify-clean-clone.sh | unmeasured in CI image; asserts git file-mode (100644/755) semantics that need verification on the CI filesystem first; #1017 burndown
|
|
||||||
packages/mosaic/framework/tools/git/test-help-exit-code.sh | unmeasured in CI image; stub-based (#701 regression harness), likely CI-fit; #1017 burndown
|
|
||||||
packages/mosaic/framework/tools/git/test-lane-brief-pr-linkage.sh | unmeasured in CI image; fixture-based (#546/#547 regression harness), likely CI-fit; #1017 burndown
|
|
||||||
|
|
||||||
# --- tools/tmux: require a live tmux server ---
|
|
||||||
packages/mosaic/framework/tools/tmux/test-send-message-socket.sh | requires a real tmux server on a throwaway socket; CI image ships no tmux; #1017 burndown (needs tmux in image or a signed permanent exclusion)
|
|
||||||
packages/mosaic/framework/tools/tmux/test-send-message-verdict.sh | requires real tmux-pane fixtures on a throwaway socket; CI image ships no tmux; #1017 burndown (same condition as its sibling)
|
|
||||||
|
|
||||||
# --- single-suite directories: unmeasured in CI ---
|
|
||||||
packages/mosaic/framework/tools/fleet/test-start-agent-session.sh | unmeasured in CI image; stubs tmux via a fake bin dir, likely CI-fit; #1017 burndown
|
|
||||||
packages/mosaic/framework/tools/glpi/test-list-http-status.sh | unmeasured in CI image; stub-based (#807 regression harness), likely CI-fit; #1017 burndown
|
|
||||||
packages/mosaic/framework/tools/orchestrator/test-board-roll.sh | unmeasured in CI image; file-fixture based, likely CI-fit; #1017 burndown
|
|
||||||
packages/mosaic/framework/tools/woodpecker/test-ci-wait-exit-matrix.sh | unmeasured in CI image; drives ci-wait.sh against a stub pipeline-status.sh, likely CI-fit; #1017 burndown
|
|
||||||
|
|
||||||
# --- naming-boundary files the strict test-*.sh prefix cannot even name ---
|
|
||||||
# (#1017: three independent censuses handled the microtest file three different
|
|
||||||
# ways — editorial drop, structural exclusion, accidental inclusion — with no
|
|
||||||
# recorded judgement. These lines ARE that judgement, signed.)
|
|
||||||
packages/mosaic/framework/tools/orchestrator/smoke-test.sh | behavior smoke checks for coord continue/run workflows, run manually by orchestrator seats; unmeasured in CI; #1017 burndown
|
|
||||||
packages/mosaic/framework/tools/wake/validate-973/microtest-wake-assert.sh | #973 instrument self-test, run as a precondition of the validate-973 evidence procedure rather than as a standing CI suite; #1017 burndown candidate
|
|
||||||
@@ -191,177 +191,3 @@ _wake_init_dir() {
|
|||||||
[ -f "$dir/pending.jsonl" ] || printf '' | _atomic_write "$dir/pending.jsonl"
|
[ -f "$dir/pending.jsonl" ] || printf '' | _atomic_write "$dir/pending.jsonl"
|
||||||
[ -f "$dir/ack-ledger.jsonl" ] || printf '' | _atomic_write "$dir/ack-ledger.jsonl"
|
[ -f "$dir/ack-ledger.jsonl" ] || printf '' | _atomic_write "$dir/ack-ledger.jsonl"
|
||||||
}
|
}
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# #973 — three-valued grep assertion helpers for the wake test suites.
|
|
||||||
#
|
|
||||||
# grep's exit contract is three-valued: 0 = match, 1 = no match, >1 = ERROR
|
|
||||||
# (bad file, bad pattern, resource failure). Every wake-suite assertion used
|
|
||||||
# to read all non-zero as "absent", so a grep that COULD NOT LOOK wore the
|
|
||||||
# colour of a verdict: OR-polarity sites (`|| fail`) went falsely red,
|
|
||||||
# AND-polarity sites (`&& fail` — including the credential canaries) went
|
|
||||||
# falsely green. The repair is to refuse to answer: rc 0 -> match, rc 1 -> no
|
|
||||||
# match, anything else -> loud abort naming the call site, the raw exit code,
|
|
||||||
# and the arguments. An error NEVER becomes a verdict.
|
|
||||||
#
|
|
||||||
# Production tools (store.sh, ack.sh) source this file but call none of the
|
|
||||||
# helpers below; they are inert outside the suites.
|
|
||||||
#
|
|
||||||
# Suite integration contract:
|
|
||||||
# - Call `wake_assert_init` ONCE at suite top level, right after sourcing.
|
|
||||||
# It dups the suite's real stderr to a saved fd BEFORE any call-site
|
|
||||||
# redirect exists, so an abort stays loud even at sites that append
|
|
||||||
# `2>/dev/null` (the preimage credential canaries pre-swallow stderr —
|
|
||||||
# exactly where a silent abort would recreate the defect being fixed).
|
|
||||||
# - Assertion sites live inside `( ... ) && ok` subshell blocks, pipelines,
|
|
||||||
# and `$(...)` substitutions, where a plain `exit` dies one layer deep and
|
|
||||||
# the suite would carry on to emit a verdict. The abort therefore signals
|
|
||||||
# the suite's MAIN shell ($$ is the main PID in every subshell) and then
|
|
||||||
# exits the current context: the suite dies by signal, non-zero, with NO
|
|
||||||
# verdict line emitted.
|
|
||||||
#
|
|
||||||
# Validation instrumentation (#973 evidence, not part of the assertion fix):
|
|
||||||
# - WAKE_ASSERT_LEDGER=<file>: every helper call appends
|
|
||||||
# "<helper> <caller-file>:<caller-line>" to <file>. That is the ONLY
|
|
||||||
# divergence from production behaviour — the suite otherwise runs its
|
|
||||||
# normal arms, so a validate run exercises exactly the shipped paths.
|
|
||||||
# - WAKE_ASSERT_FORCE_GREP_ERROR_AT=<caller-file>:<caller-line>: at exactly
|
|
||||||
# that call site, the invocation is routed through a REAL grep driven onto
|
|
||||||
# its real error path (unknown option -> rc 2) — a genuinely executed
|
|
||||||
# failing process, not a stubbed return — to prove per-site that the abort
|
|
||||||
# fires. Unset in production; matching no site is a no-op.
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
# wake_assert_init — dup the suite's real stderr once, for abort loudness.
|
|
||||||
# MUST be called at suite TOP LEVEL, immediately after sourcing and before any
|
|
||||||
# test block: a lazy (first-call) dup could capture an already-redirected
|
|
||||||
# stderr if the first executed helper call sat under a call-site 2>/dev/null,
|
|
||||||
# silencing every abort thereafter. The fd is allocated dynamically (>= 10),
|
|
||||||
# so it cannot collide with the wake lock fds (8) or the detector run-loop
|
|
||||||
# lock (9).
|
|
||||||
#
|
|
||||||
# Init also PINS the BASH_LINENO convention the site coordinates depend on:
|
|
||||||
# a helper call written across a backslash continuation must report at its
|
|
||||||
# FIRST physical line (the denominator artifact's convention). That was
|
|
||||||
# measured on a developer bash (5.3.x); CI runs whatever bash its base image
|
|
||||||
# baked in, and that version floats silently between image rebuilds. A bash
|
|
||||||
# that disagrees would shift every continuation-site coordinate by one line
|
|
||||||
# UNDER the validation instead of in front of it — so the convention is
|
|
||||||
# asserted at runtime, in the same bash binary that runs the suite, and a
|
|
||||||
# disagreeing bash aborts the suite loudly instead of skewing coordinates.
|
|
||||||
_wake_assert_lineno_pin() {
|
|
||||||
local _wa_pin_tmp _wa_pin_got
|
|
||||||
_wa_pin_tmp="$(mktemp)" || {
|
|
||||||
_wake_assert_err_note "WAKE-ASSERT INIT ABORT: mktemp failed; cannot pin the BASH_LINENO convention — a pin that silently does not run is not a pin (#973)"
|
|
||||||
exit 97
|
|
||||||
}
|
|
||||||
cat >"$_wa_pin_tmp" <<'WAKE_ASSERT_PIN'
|
|
||||||
_wap() { printf '%s\n' "${BASH_LINENO[0]}"; }
|
|
||||||
(
|
|
||||||
_wap simple
|
|
||||||
_wap \
|
|
||||||
continuation
|
|
||||||
)
|
|
||||||
WAKE_ASSERT_PIN
|
|
||||||
# WAKE_ASSERT_PIN_BASH: test-only interpreter override so the pin's abort
|
|
||||||
# arm can be PROVEN to fire (microtest C10) — bash resets $BASH at startup,
|
|
||||||
# so the real probe interpreter cannot be spoofed from the environment.
|
|
||||||
_wa_pin_got="$("${WAKE_ASSERT_PIN_BASH:-${BASH:-bash}}" "$_wa_pin_tmp" 2>/dev/null)"
|
|
||||||
rm -f "$_wa_pin_tmp"
|
|
||||||
if [ "$_wa_pin_got" != "$(printf '3\n4')" ]; then
|
|
||||||
_wake_assert_err_note "WAKE-ASSERT INIT ABORT: BASH_LINENO convention violated on bash ${BASH_VERSION}: probe reported [${_wa_pin_got:-<no output>}], expected [3 4] (simple call at own line, continuation call at FIRST physical line) — site coordinates are untrustworthy on this bash (#973)"
|
|
||||||
exit 97
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
wake_assert_init() {
|
|
||||||
if [ -z "${_wake_assert_err_fd:-}" ]; then
|
|
||||||
exec {_wake_assert_err_fd}>&2
|
|
||||||
_wake_assert_lineno_pin
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# _wake_assert_err_note MSG — write MSG to the saved real-stderr fd, falling
|
|
||||||
# back to the current stderr if init was never called.
|
|
||||||
_wake_assert_err_note() {
|
|
||||||
if [ -n "${_wake_assert_err_fd:-}" ]; then
|
|
||||||
printf '%s\n' "$1" >&"$_wake_assert_err_fd" 2>/dev/null ||
|
|
||||||
printf '%s\n' "$1" >&2
|
|
||||||
else
|
|
||||||
printf '%s\n' "$1" >&2
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# _wake_assert_abort HELPER SITE RC ARGS... — refuse to answer, loudly.
|
|
||||||
# Writes the named reason to the saved real-stderr fd (falling back to the
|
|
||||||
# current stderr), signals the suite's main shell, and exits this context.
|
|
||||||
_wake_assert_abort() {
|
|
||||||
local _wa_helper="$1" _wa_where="$2" _wa_code="$3"
|
|
||||||
shift 3
|
|
||||||
_wake_assert_err_note "WAKE-ASSERT ABORT: ${_wa_helper} at ${_wa_where}: grep exit ${_wa_code} is an error, not a verdict (args: $*) — refusing to answer (#973)"
|
|
||||||
if [ -n "${BASHPID:-}" ] && [ "$BASHPID" != "$$" ]; then
|
|
||||||
kill -TERM "$$" 2>/dev/null || true
|
|
||||||
fi
|
|
||||||
exit 97
|
|
||||||
}
|
|
||||||
|
|
||||||
# _wake_assert_armed SITE — true iff the forced-error arm targets SITE; on a
|
|
||||||
# match it emits a positive confirmation FIRST, so "site did not abort" can
|
|
||||||
# never conflate SITE NOT CONVERTED with ARM NEVER REACHED IT: an armed run
|
|
||||||
# with no ARMED line means the arm matched nothing (typo/renumber/drift), and
|
|
||||||
# an ARMED line with no abort means the site's error path is broken. The two
|
|
||||||
# defects are separable on stderr alone.
|
|
||||||
_wake_assert_armed() {
|
|
||||||
[ "${WAKE_ASSERT_FORCE_GREP_ERROR_AT:-}" = "$1" ] || return 1
|
|
||||||
_wake_assert_err_note "WAKE-ASSERT ARMED: forcing real grep error at $1 (#973)"
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
# has_match GREP_ARGS... — three-valued grep verdict.
|
|
||||||
# Drop-in for verdict-bearing `grep` calls (flags, files, stdin all pass
|
|
||||||
# through; stdout is not captured, so extract-form call sites may use it
|
|
||||||
# inside a substitution). Returns 0 on match, 1 on no-match; any other grep
|
|
||||||
# exit aborts the suite via _wake_assert_abort.
|
|
||||||
has_match() {
|
|
||||||
local _wa_site="${BASH_SOURCE[1]##*/}:${BASH_LINENO[0]}" _wa_rc=0
|
|
||||||
if [ -n "${WAKE_ASSERT_LEDGER:-}" ]; then
|
|
||||||
printf 'has_match %s\n' "$_wa_site" >>"$WAKE_ASSERT_LEDGER"
|
|
||||||
fi
|
|
||||||
if _wake_assert_armed "$_wa_site"; then
|
|
||||||
command grep --wake-assert-forced-error -- /dev/null
|
|
||||||
_wa_rc=$?
|
|
||||||
else
|
|
||||||
command grep "$@"
|
|
||||||
_wa_rc=$?
|
|
||||||
fi
|
|
||||||
case "$_wa_rc" in
|
|
||||||
0) return 0 ;;
|
|
||||||
1) return 1 ;;
|
|
||||||
*) _wake_assert_abort has_match "$_wa_site" "$_wa_rc" "$@" ;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
# count_lines GREP_ARGS... — `grep -c` with the same three-way discipline.
|
|
||||||
# Call sites drop their `-c` (the helper supplies it) and keep every other
|
|
||||||
# argument. Prints the count on rc 0 AND rc 1 (rc 1 is grep's "count is 0" —
|
|
||||||
# a valid measurement, not an error); any other exit aborts. The abort still
|
|
||||||
# kills the suite from inside a `$(...)` capture: the substitution subshell
|
|
||||||
# cannot exit the suite, but the signal to the main shell can — a count from
|
|
||||||
# a failed measurement is never printed.
|
|
||||||
count_lines() {
|
|
||||||
local _wa_site="${BASH_SOURCE[1]##*/}:${BASH_LINENO[0]}" _wa_rc=0 _wa_out=""
|
|
||||||
if [ -n "${WAKE_ASSERT_LEDGER:-}" ]; then
|
|
||||||
printf 'count_lines %s\n' "$_wa_site" >>"$WAKE_ASSERT_LEDGER"
|
|
||||||
fi
|
|
||||||
if _wake_assert_armed "$_wa_site"; then
|
|
||||||
_wa_out="$(command grep --wake-assert-forced-error -c -- /dev/null)"
|
|
||||||
_wa_rc=$?
|
|
||||||
else
|
|
||||||
_wa_out="$(command grep -c "$@")"
|
|
||||||
_wa_rc=$?
|
|
||||||
fi
|
|
||||||
case "$_wa_rc" in
|
|
||||||
0 | 1) printf '%s\n' "$_wa_out" ;;
|
|
||||||
*) _wake_assert_abort count_lines "$_wa_site" "$_wa_rc" "$@" ;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -529,19 +529,7 @@ cmd_run() {
|
|||||||
echo "detector.sh: WARN — off-host liveness beacon emit failed (see beacon.sh); the off-host absence check remains the authoritative dead-man." >&2
|
echo "detector.sh: WARN — off-host liveness beacon emit failed (see beacon.sh); the off-host absence check remains the authoritative dead-man." >&2
|
||||||
fi
|
fi
|
||||||
[ "$once" -eq 1 ] && break
|
[ "$once" -eq 1 ] && break
|
||||||
# Close the detector lock fd in the sleep child; otherwise an orphaned sleep
|
sleep "$interval"
|
||||||
# keeps the single-instance flock (fd 9, taken at exec 9> above) alive after
|
|
||||||
# the detector parent dies. The lock is non-blocking (`flock -n`, above), so
|
|
||||||
# for as long as that sleep survives a replacement instance is REFUSED and
|
|
||||||
# exits rather than queueing. This particular hold is BOUNDED by one poll
|
|
||||||
# interval (WAKE_DETECTOR_INTERVAL, default 30s): when the orphaned sleep
|
|
||||||
# exits its copy of fd 9 closes, ending this bounded sleep-child hold. It
|
|
||||||
# does NOT follow that the next start succeeds — other inheritors of fd 9
|
|
||||||
# (the M1 adapter, M2 sink grandchildren) are outside this patch's scope and
|
|
||||||
# can keep holding the flock. The cost this removes is a restart window in
|
|
||||||
# which every supervisor retry fails on the sleep child's account.
|
|
||||||
# `9>&-` closes ONLY the child's copy — the parent's lock is unaffected.
|
|
||||||
sleep "$interval" 9>&-
|
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -465,26 +465,8 @@
|
|||||||
# never a hand-built flat dead-letter row, which would make the
|
# never a hand-built flat dead-letter row, which would make the
|
||||||
# audit's correct non-conviction look exactly like the defect
|
# audit's correct non-conviction look exactly like the defect
|
||||||
# under hunt (the #951 review's false-defect near-miss).
|
# under hunt (the #951 review's false-defect near-miss).
|
||||||
# 0.7.2 #973 three-valued grep verdicts across ALL TEN wake test suites.
|
|
||||||
# grep's exit contract is three-valued (0 match / 1 no-match /
|
|
||||||
# >=2 ERROR); every suite assertion read non-zero as "absent",
|
|
||||||
# so a grep that COULD NOT LOOK wore the colour of a verdict —
|
|
||||||
# OR-polarity sites failed falsely RED, AND-polarity sites
|
|
||||||
# (including all 19 credential canaries) failed falsely GREEN
|
|
||||||
# under load. _wake-common.sh gains has_match/count_lines
|
|
||||||
# (rc 0/1 pass through; anything else LOUDLY ABORTS the whole
|
|
||||||
# suite naming file:line + raw rc — an error is never a
|
|
||||||
# verdict), wake_assert_init (saved-fd abort loudness that
|
|
||||||
# survives call-site 2>/dev/null + a runtime pin of the
|
|
||||||
# BASH_LINENO coordinate convention against CI bash drift),
|
|
||||||
# and 261 call sites converted mechanically from a frozen
|
|
||||||
# denominator artifact. Production tools source but never call
|
|
||||||
# the helpers; suite verdict semantics on rc 0/1 are UNCHANGED.
|
|
||||||
# Evidence chain in validate-973/ (microtest C1-C11, expected/
|
|
||||||
# static/trace set arithmetic, 21 forced-error arms, residual
|
|
||||||
# sweep with per-form plants).
|
|
||||||
component=wake
|
component=wake
|
||||||
version=0.7.2
|
version=0.7.1
|
||||||
|
|
||||||
# Watch-list schema this component consumes, and the INCLUSIVE range of
|
# Watch-list schema this component consumes, and the INCLUSIVE range of
|
||||||
# schema_version values it supports. A wake-watch-list.json whose schema_version
|
# schema_version values it supports. A wake-watch-list.json whose schema_version
|
||||||
|
|||||||
@@ -31,13 +31,6 @@
|
|||||||
set -uo pipefail
|
set -uo pipefail
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
# #973: three-valued grep assertion helpers (has_match/count_lines); init saves real stderr for abort loudness.
|
|
||||||
# shellcheck disable=SC1091
|
|
||||||
if ! . "$SCRIPT_DIR/_wake-common.sh"; then
|
|
||||||
echo "WAKE-ASSERT INIT ABORT: cannot source _wake-common.sh — suite ran ZERO wake assertions (#984)" >&2
|
|
||||||
exit 97
|
|
||||||
fi
|
|
||||||
wake_assert_init
|
|
||||||
BEACON="$SCRIPT_DIR/beacon.sh"
|
BEACON="$SCRIPT_DIR/beacon.sh"
|
||||||
|
|
||||||
command -v jq >/dev/null 2>&1 || {
|
command -v jq >/dev/null 2>&1 || {
|
||||||
@@ -136,7 +129,7 @@ echo "== B2: beacon ABSENCE past SLO -> alarm FIRES + ROUTES =="
|
|||||||
out="$("$BEACON" check --slo-seconds 5 2>&1)"
|
out="$("$BEACON" check --slo-seconds 5 2>&1)"
|
||||||
rc=$?
|
rc=$?
|
||||||
[ "$rc" -eq 1 ] || fail_msg "B2: a stale-past-SLO beacon must FIRE the absence alarm (exit 1); got $rc [$out]"
|
[ "$rc" -eq 1 ] || fail_msg "B2: a stale-past-SLO beacon must FIRE the absence alarm (exit 1); got $rc [$out]"
|
||||||
echo "$out" | has_match -qi 'ALARM FIRED' || fail_msg "B2: absence must announce the alarm fired [$out]"
|
echo "$out" | grep -qi 'ALARM FIRED' || fail_msg "B2: absence must announce the alarm fired [$out]"
|
||||||
[ -s "$ALARM_OUT" ] || fail_msg "B2: the alarm must actually ROUTE to the sink (payload not written)"
|
[ -s "$ALARM_OUT" ] || fail_msg "B2: the alarm must actually ROUTE to the sink (payload not written)"
|
||||||
jq -e '.kind == "beacon-absence-alarm"' "$ALARM_OUT" >/dev/null 2>&1 || fail_msg "B2: routed payload must be a beacon-absence-alarm [$(cat "$ALARM_OUT" 2>/dev/null)]"
|
jq -e '.kind == "beacon-absence-alarm"' "$ALARM_OUT" >/dev/null 2>&1 || fail_msg "B2: routed payload must be a beacon-absence-alarm [$(cat "$ALARM_OUT" 2>/dev/null)]"
|
||||||
# NEVER-received is also an absence (depends on nothing the dying host does).
|
# NEVER-received is also an absence (depends on nothing the dying host does).
|
||||||
@@ -157,13 +150,13 @@ echo "== B3: unconfigured OR unreachable ALARM target -> FAIL LOUD =="
|
|||||||
out="$("$BEACON" check --slo-seconds 5 2>&1)"
|
out="$("$BEACON" check --slo-seconds 5 2>&1)"
|
||||||
rc=$?
|
rc=$?
|
||||||
[ "$rc" -eq 3 ] || fail_msg "B3a: unconfigured alarm target must FAIL LOUD (exit 3); got $rc [$out]"
|
[ "$rc" -eq 3 ] || fail_msg "B3a: unconfigured alarm target must FAIL LOUD (exit 3); got $rc [$out]"
|
||||||
echo "$out" | has_match -qi 'silent no-alarm host' || fail_msg "B3a: the failure must name the silent-no-alarm-host hazard [$out]"
|
echo "$out" | grep -qi 'silent no-alarm host' || fail_msg "B3a: the failure must name the silent-no-alarm-host hazard [$out]"
|
||||||
# (b) UNREACHABLE alarm sink (non-zero exit).
|
# (b) UNREACHABLE alarm sink (non-zero exit).
|
||||||
export WAKE_ALARM_SINK_CMD="false"
|
export WAKE_ALARM_SINK_CMD="false"
|
||||||
out2="$("$BEACON" check --slo-seconds 5 2>&1)"
|
out2="$("$BEACON" check --slo-seconds 5 2>&1)"
|
||||||
rc2=$?
|
rc2=$?
|
||||||
[ "$rc2" -eq 3 ] || fail_msg "B3b: unreachable alarm target must FAIL LOUD (exit 3); got $rc2 [$out2]"
|
[ "$rc2" -eq 3 ] || fail_msg "B3b: unreachable alarm target must FAIL LOUD (exit 3); got $rc2 [$out2]"
|
||||||
echo "$out2" | has_match -qi 'UNREACHABLE' || fail_msg "B3b: the failure must name the unreachable target [$out2]"
|
echo "$out2" | grep -qi 'UNREACHABLE' || fail_msg "B3b: the failure must name the unreachable target [$out2]"
|
||||||
) && ok
|
) && ok
|
||||||
|
|
||||||
echo "== B4: isolated host -> DEGRADED different-supervision-root beacon FLAGGED =="
|
echo "== B4: isolated host -> DEGRADED different-supervision-root beacon FLAGGED =="
|
||||||
@@ -176,8 +169,8 @@ echo "== B4: isolated host -> DEGRADED different-supervision-root beacon FLAGGED
|
|||||||
rc=$?
|
rc=$?
|
||||||
err="$(cat "$TMP_ROOT/b4.err")"
|
err="$(cat "$TMP_ROOT/b4.err")"
|
||||||
[ "$rc" -eq 0 ] || fail_msg "B4: a different-supervision-root emit must still succeed (exit 0); got $rc [$out][$err]"
|
[ "$rc" -eq 0 ] || fail_msg "B4: a different-supervision-root emit must still succeed (exit 0); got $rc [$out][$err]"
|
||||||
echo "$err" | has_match -qi 'DEGRADED' || fail_msg "B4: a different-supervision-root beacon must be FLAGGED degraded on stderr [$err]"
|
echo "$err" | grep -qi 'DEGRADED' || fail_msg "B4: a different-supervision-root beacon must be FLAGGED degraded on stderr [$err]"
|
||||||
echo "$out" | has_match -q 'degraded=true' || fail_msg "B4: emit must NOT silently present a degraded beacon as healthy (degraded=true expected) [$out]"
|
echo "$out" | grep -q 'degraded=true' || fail_msg "B4: emit must NOT silently present a degraded beacon as healthy (degraded=true expected) [$out]"
|
||||||
) && ok
|
) && ok
|
||||||
|
|
||||||
echo "== B5: same-host-sibling -> REJECTED as non-independent, seq NOT advanced =="
|
echo "== B5: same-host-sibling -> REJECTED as non-independent, seq NOT advanced =="
|
||||||
@@ -189,7 +182,7 @@ echo "== B5: same-host-sibling -> REJECTED as non-independent, seq NOT advanced
|
|||||||
out="$("$BEACON" emit 2>&1)"
|
out="$("$BEACON" emit 2>&1)"
|
||||||
rc=$?
|
rc=$?
|
||||||
[ "$rc" -ne 0 ] || fail_msg "B5: a same-host-sibling beacon must be REJECTED (non-zero exit) [$out]"
|
[ "$rc" -ne 0 ] || fail_msg "B5: a same-host-sibling beacon must be REJECTED (non-zero exit) [$out]"
|
||||||
echo "$out" | has_match -qi 'not an independent' || fail_msg "B5: the rejection must explain it is NOT an independent leg [$out]"
|
echo "$out" | grep -qi 'not an independent' || fail_msg "B5: the rejection must explain it is NOT an independent leg [$out]"
|
||||||
# A rejected emit must not have minted a seq (rejection precedes counter bump).
|
# A rejected emit must not have minted a seq (rejection precedes counter bump).
|
||||||
seq_after="$("$BEACON" status 2>/dev/null | sed -n 's/^beacon_emitter_seq=//p')"
|
seq_after="$("$BEACON" status 2>/dev/null | sed -n 's/^beacon_emitter_seq=//p')"
|
||||||
[ "${seq_after:-0}" -eq 0 ] || fail_msg "B5: a rejected emit must NOT advance the monotonic seq (got $seq_after)"
|
[ "${seq_after:-0}" -eq 0 ] || fail_msg "B5: a rejected emit must NOT advance the monotonic seq (got $seq_after)"
|
||||||
@@ -208,10 +201,10 @@ echo "== B6: alarm target resolved BY NAME; beacon.sh inlines no endpoint/secret
|
|||||||
out="$("$BEACON" check --slo-seconds 5 2>&1)"
|
out="$("$BEACON" check --slo-seconds 5 2>&1)"
|
||||||
rc=$?
|
rc=$?
|
||||||
[ "$rc" -eq 1 ] || fail_msg "B6: absence via a by-name alarm adapter must fire (exit 1); got $rc [$out]"
|
[ "$rc" -eq 1 ] || fail_msg "B6: absence via a by-name alarm adapter must fire (exit 1); got $rc [$out]"
|
||||||
head -n1 "$ALARM_OUT" 2>/dev/null | has_match -qF "$SECRET_TARGET" || fail_msg "B6: the adapter must resolve+route to the BY-NAME target [$(cat "$ALARM_OUT" 2>/dev/null)]"
|
head -n1 "$ALARM_OUT" 2>/dev/null | grep -qF "$SECRET_TARGET" || fail_msg "B6: the adapter must resolve+route to the BY-NAME target [$(cat "$ALARM_OUT" 2>/dev/null)]"
|
||||||
# The framework file must NOT inline the endpoint/secret: it only knows a NAME.
|
# The framework file must NOT inline the endpoint/secret: it only knows a NAME.
|
||||||
has_match -qF "$SECRET_TARGET" "$BEACON" && fail_msg "B6: beacon.sh must NOT inline the target endpoint/secret"
|
grep -qF "$SECRET_TARGET" "$BEACON" && fail_msg "B6: beacon.sh must NOT inline the target endpoint/secret"
|
||||||
has_match -qF "$SECRET_TARGET" "$WAKE_ALARM_SINK_CMD" && fail_msg "B6: even the adapter must resolve by-name, not inline the secret"
|
grep -qF "$SECRET_TARGET" "$WAKE_ALARM_SINK_CMD" && fail_msg "B6: even the adapter must resolve by-name, not inline the secret"
|
||||||
true
|
true
|
||||||
) && ok
|
) && ok
|
||||||
|
|
||||||
@@ -225,7 +218,7 @@ echo "== B7: unconfigured OR unreachable BEACON sink on emit -> FAIL LOUD =="
|
|||||||
out="$("$BEACON" emit 2>&1)"
|
out="$("$BEACON" emit 2>&1)"
|
||||||
rc=$?
|
rc=$?
|
||||||
[ "$rc" -eq 3 ] || fail_msg "B7a: unconfigured beacon sink must FAIL LOUD (exit 3); got $rc [$out]"
|
[ "$rc" -eq 3 ] || fail_msg "B7a: unconfigured beacon sink must FAIL LOUD (exit 3); got $rc [$out]"
|
||||||
echo "$out" | has_match -qi 'silent no-alarm host' || fail_msg "B7a: the failure must name the silent-no-alarm-host hazard [$out]"
|
echo "$out" | grep -qi 'silent no-alarm host' || fail_msg "B7a: the failure must name the silent-no-alarm-host hazard [$out]"
|
||||||
# A refused emit (no sink) must not have minted a seq.
|
# A refused emit (no sink) must not have minted a seq.
|
||||||
seq_after="$("$BEACON" status 2>/dev/null | sed -n 's/^beacon_emitter_seq=//p')"
|
seq_after="$("$BEACON" status 2>/dev/null | sed -n 's/^beacon_emitter_seq=//p')"
|
||||||
[ "${seq_after:-0}" -eq 0 ] || fail_msg "B7a: an unconfigured-sink emit must NOT advance the seq (got $seq_after)"
|
[ "${seq_after:-0}" -eq 0 ] || fail_msg "B7a: an unconfigured-sink emit must NOT advance the seq (got $seq_after)"
|
||||||
@@ -234,7 +227,7 @@ echo "== B7: unconfigured OR unreachable BEACON sink on emit -> FAIL LOUD =="
|
|||||||
out2="$("$BEACON" emit 2>&1)"
|
out2="$("$BEACON" emit 2>&1)"
|
||||||
rc2=$?
|
rc2=$?
|
||||||
[ "$rc2" -eq 1 ] || fail_msg "B7b: unreachable beacon sink must FAIL LOUD (exit 1); got $rc2 [$out2]"
|
[ "$rc2" -eq 1 ] || fail_msg "B7b: unreachable beacon sink must FAIL LOUD (exit 1); got $rc2 [$out2]"
|
||||||
echo "$out2" | has_match -qi 'UNREACHABLE' || fail_msg "B7b: the failure must name the unreachable sink [$out2]"
|
echo "$out2" | grep -qi 'UNREACHABLE' || fail_msg "B7b: the failure must name the unreachable sink [$out2]"
|
||||||
) && ok
|
) && ok
|
||||||
|
|
||||||
echo "== B8: no invented SLO -> check --slo-seconds is REQUIRED (fail-loud) =="
|
echo "== B8: no invented SLO -> check --slo-seconds is REQUIRED (fail-loud) =="
|
||||||
@@ -245,7 +238,7 @@ echo "== B8: no invented SLO -> check --slo-seconds is REQUIRED (fail-loud) =="
|
|||||||
out="$("$BEACON" check 2>&1)"
|
out="$("$BEACON" check 2>&1)"
|
||||||
rc=$?
|
rc=$?
|
||||||
[ "$rc" -eq 3 ] || fail_msg "B8: check without an SLO must fail loud (exit 3); got $rc [$out]"
|
[ "$rc" -eq 3 ] || fail_msg "B8: check without an SLO must fail loud (exit 3); got $rc [$out]"
|
||||||
echo "$out" | has_match -qi 'slo' || fail_msg "B8: the usage error must name the missing SLO [$out]"
|
echo "$out" | grep -qi 'slo' || fail_msg "B8: the usage error must name the missing SLO [$out]"
|
||||||
) && ok
|
) && ok
|
||||||
|
|
||||||
echo "== B9: capture-pane hint is a liveness HINT ONLY (does NOT suppress absence) =="
|
echo "== B9: capture-pane hint is a liveness HINT ONLY (does NOT suppress absence) =="
|
||||||
@@ -274,7 +267,7 @@ echo "== B10: fresh beacon within SLO -> ALIVE (exit 0, no alarm) =="
|
|||||||
out="$("$BEACON" check --slo-seconds 60 2>&1)"
|
out="$("$BEACON" check --slo-seconds 60 2>&1)"
|
||||||
rc=$?
|
rc=$?
|
||||||
[ "$rc" -eq 0 ] || fail_msg "B10: a fresh beacon within SLO must be ALIVE (exit 0); got $rc [$out]"
|
[ "$rc" -eq 0 ] || fail_msg "B10: a fresh beacon within SLO must be ALIVE (exit 0); got $rc [$out]"
|
||||||
echo "$out" | has_match -qi 'ALIVE' || fail_msg "B10: a fresh beacon must report ALIVE [$out]"
|
echo "$out" | grep -qi 'ALIVE' || fail_msg "B10: a fresh beacon must report ALIVE [$out]"
|
||||||
[ ! -s "$ALARM_OUT" ] || fail_msg "B10: a fresh beacon must NOT fire an alarm"
|
[ ! -s "$ALARM_OUT" ] || fail_msg "B10: a fresh beacon must NOT fire an alarm"
|
||||||
) && ok
|
) && ok
|
||||||
|
|
||||||
@@ -298,7 +291,7 @@ echo "== B11: staleness from monitor ingested_ts -> a far-future emit_ts STILL g
|
|||||||
jq -c --argjson ing "$((now - 100))" '.ingested_ts = $ing' "$WAKE_BEACON_RECEIVED" >"$H/tmp" && mv "$H/tmp" "$WAKE_BEACON_RECEIVED"
|
jq -c --argjson ing "$((now - 100))" '.ingested_ts = $ing' "$WAKE_BEACON_RECEIVED" >"$H/tmp" && mv "$H/tmp" "$WAKE_BEACON_RECEIVED"
|
||||||
out="$("$BEACON" check --slo-seconds 5 2>&1)"; rc=$?
|
out="$("$BEACON" check --slo-seconds 5 2>&1)"; rc=$?
|
||||||
[ "$rc" -eq 1 ] || fail_msg "B11: a far-future emit_ts must NOT defer staleness — receive-time governs (expected absence exit 1); got $rc [$out]"
|
[ "$rc" -eq 1 ] || fail_msg "B11: a far-future emit_ts must NOT defer staleness — receive-time governs (expected absence exit 1); got $rc [$out]"
|
||||||
echo "$out" | has_match -qi 'ALARM FIRED' || fail_msg "B11: the receive-time-stale beacon must fire the absence alarm [$out]"
|
echo "$out" | grep -qi 'ALARM FIRED' || fail_msg "B11: the receive-time-stale beacon must fire the absence alarm [$out]"
|
||||||
jq -e '.age_seconds >= 100' "$ALARM_OUT" >/dev/null 2>&1 || fail_msg "B11: staleness age must be measured from ingested_ts (>=100s), not emit_ts [$(cat "$ALARM_OUT" 2>/dev/null)]"
|
jq -e '.age_seconds >= 100' "$ALARM_OUT" >/dev/null 2>&1 || fail_msg "B11: staleness age must be measured from ingested_ts (>=100s), not emit_ts [$(cat "$ALARM_OUT" 2>/dev/null)]"
|
||||||
) && ok
|
) && ok
|
||||||
|
|
||||||
@@ -336,7 +329,7 @@ else
|
|||||||
jq -c '.beacon_seq = 99999 | .host_id = "attacker"' "$SHIPPED" >"$spoof"
|
jq -c '.beacon_seq = 99999 | .host_id = "attacker"' "$SHIPPED" >"$spoof"
|
||||||
out="$("$BEACON" record <"$spoof" 2>&1)"; rc=$?
|
out="$("$BEACON" record <"$spoof" 2>&1)"; rc=$?
|
||||||
[ "$rc" -ne 0 ] || fail_msg "B12: a spoofed beacon (altered fields) must be REJECTED at record; got rc=$rc [$out]"
|
[ "$rc" -ne 0 ] || fail_msg "B12: a spoofed beacon (altered fields) must be REJECTED at record; got rc=$rc [$out]"
|
||||||
echo "$out" | has_match -qi 'spoofed beacon' || fail_msg "B12: the rejection must name the spoof [$out]"
|
echo "$out" | grep -qi 'spoofed beacon' || fail_msg "B12: the rejection must name the spoof [$out]"
|
||||||
[ ! -s "$WAKE_BEACON_RECEIVED" ] || fail_msg "B12: a rejected spoof must NOT be stored (dead-man clock not advanced)"
|
[ ! -s "$WAKE_BEACON_RECEIVED" ] || fail_msg "B12: a rejected spoof must NOT be stored (dead-man clock not advanced)"
|
||||||
# (c) an UNSIGNED beacon is rejected when signing is configured.
|
# (c) an UNSIGNED beacon is rejected when signing is configured.
|
||||||
unsigned="$H/unsigned.json"
|
unsigned="$H/unsigned.json"
|
||||||
@@ -344,14 +337,14 @@ else
|
|||||||
out2="$("$BEACON" record <"$unsigned" 2>&1)"; rc2=$?
|
out2="$("$BEACON" record <"$unsigned" 2>&1)"; rc2=$?
|
||||||
[ "$rc2" -ne 0 ] || fail_msg "B12: an unsigned beacon must be REJECTED when signing is configured; got rc=$rc2 [$out2]"
|
[ "$rc2" -ne 0 ] || fail_msg "B12: an unsigned beacon must be REJECTED when signing is configured; got rc=$rc2 [$out2]"
|
||||||
# beacon.sh must not inline the key material.
|
# beacon.sh must not inline the key material.
|
||||||
has_match -qF "test-beacon-hmac-key-do-not-echo" "$BEACON" && fail_msg "B12: beacon.sh must NOT inline the HMAC key"
|
grep -qF "test-beacon-hmac-key-do-not-echo" "$BEACON" && fail_msg "B12: beacon.sh must NOT inline the HMAC key"
|
||||||
true
|
true
|
||||||
) && ok
|
) && ok
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo
|
echo
|
||||||
if [ -s "$FAILFILE" ]; then
|
if [ -s "$FAILFILE" ]; then
|
||||||
echo "wake beacon harness: FAILED ($(count_lines . "$FAILFILE") assertion(s))" >&2
|
echo "wake beacon harness: FAILED ($(grep -c . "$FAILFILE") assertion(s))" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
echo "wake beacon harness: all invariants passed ($pass groups)"
|
echo "wake beacon harness: all invariants passed ($pass groups)"
|
||||||
|
|||||||
@@ -29,13 +29,6 @@
|
|||||||
set -uo pipefail
|
set -uo pipefail
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
# #973: three-valued grep assertion helpers (has_match/count_lines); init saves real stderr for abort loudness.
|
|
||||||
# shellcheck disable=SC1091
|
|
||||||
if ! . "$SCRIPT_DIR/_wake-common.sh"; then
|
|
||||||
echo "WAKE-ASSERT INIT ABORT: cannot source _wake-common.sh — suite ran ZERO wake assertions (#984)" >&2
|
|
||||||
exit 97
|
|
||||||
fi
|
|
||||||
wake_assert_init
|
|
||||||
DET="$SCRIPT_DIR/detector.sh"
|
DET="$SCRIPT_DIR/detector.sh"
|
||||||
STORE="$SCRIPT_DIR/store.sh"
|
STORE="$SCRIPT_DIR/store.sh"
|
||||||
|
|
||||||
@@ -260,8 +253,8 @@ echo "== D5: watch-list schema_version out of manifest range -> FAIL LOUD =="
|
|||||||
err="$("$DET" poll-once 2>&1)"
|
err="$("$DET" poll-once 2>&1)"
|
||||||
rc=$?
|
rc=$?
|
||||||
[ "$rc" -ne 0 ] || fail_msg "D5: an out-of-range schema_version must FAIL LOUD (non-zero exit)"
|
[ "$rc" -ne 0 ] || fail_msg "D5: an out-of-range schema_version must FAIL LOUD (non-zero exit)"
|
||||||
echo "$err" | has_match -qi 'schema_version' || fail_msg "D5: the failure must name schema_version [$err]"
|
echo "$err" | grep -qi 'schema_version' || fail_msg "D5: the failure must name schema_version [$err]"
|
||||||
echo "$err" | has_match -qi 'range' || fail_msg "D5: the failure must state it is out of the supported range [$err]"
|
echo "$err" | grep -qi 'range' || fail_msg "D5: the failure must state it is out of the supported range [$err]"
|
||||||
# And nothing was enqueued / no cursor advance under a rejected watch-list.
|
# And nothing was enqueued / no cursor advance under a rejected watch-list.
|
||||||
[ "$(depth)" = "0" ] || fail_msg "D5: a rejected watch-list must not enqueue, got depth $(depth)"
|
[ "$(depth)" = "0" ] || fail_msg "D5: a rejected watch-list must not enqueue, got depth $(depth)"
|
||||||
[ "$(det_seq)" = "0" ] || fail_msg "D5: a rejected watch-list must not advance observed_seq, got $(det_seq)"
|
[ "$(det_seq)" = "0" ] || fail_msg "D5: a rejected watch-list must not advance observed_seq, got $(det_seq)"
|
||||||
@@ -294,7 +287,7 @@ echo "== D6: source error / ambiguous-empty -> FAIL LOUD, observed_seq NOT advan
|
|||||||
err="$("$DET" poll-once 2>&1)"
|
err="$("$DET" poll-once 2>&1)"
|
||||||
rc=$?
|
rc=$?
|
||||||
[ "$rc" -ne 0 ] || fail_msg "D6: a source error must FAIL LOUD (non-zero exit)"
|
[ "$rc" -ne 0 ] || fail_msg "D6: a source error must FAIL LOUD (non-zero exit)"
|
||||||
echo "$err" | has_match -qi 'FAIL LOUD' || fail_msg "D6: the source error must be loud [$err]"
|
echo "$err" | grep -qi 'FAIL LOUD' || fail_msg "D6: the source error must be loud [$err]"
|
||||||
[ "$(det_seq)" = "$seq_before" ] || fail_msg "D6: a source error must NOT advance observed_seq (got $(det_seq), was $seq_before)"
|
[ "$(det_seq)" = "$seq_before" ] || fail_msg "D6: a source error must NOT advance observed_seq (got $(det_seq), was $seq_before)"
|
||||||
[ "$(depth)" = "0" ] || fail_msg "D6: a source error must NOT enqueue, got depth $(depth)"
|
[ "$(depth)" = "0" ] || fail_msg "D6: a source error must NOT enqueue, got depth $(depth)"
|
||||||
|
|
||||||
@@ -305,7 +298,7 @@ echo "== D6: source error / ambiguous-empty -> FAIL LOUD, observed_seq NOT advan
|
|||||||
err="$("$DET" poll-once 2>&1)"
|
err="$("$DET" poll-once 2>&1)"
|
||||||
rc=$?
|
rc=$?
|
||||||
[ "$rc" -ne 0 ] || fail_msg "D6: an ambiguous-empty response must FAIL LOUD (non-zero exit)"
|
[ "$rc" -ne 0 ] || fail_msg "D6: an ambiguous-empty response must FAIL LOUD (non-zero exit)"
|
||||||
echo "$err" | has_match -qi 'AMBIGUOUS-EMPTY' || fail_msg "D6: ambiguous-empty must be named in the loud failure [$err]"
|
echo "$err" | grep -qi 'AMBIGUOUS-EMPTY' || fail_msg "D6: ambiguous-empty must be named in the loud failure [$err]"
|
||||||
[ "$(det_seq)" = "$seq_before" ] || fail_msg "D6: ambiguous-empty must NOT advance observed_seq (got $(det_seq))"
|
[ "$(det_seq)" = "$seq_before" ] || fail_msg "D6: ambiguous-empty must NOT advance observed_seq (got $(det_seq))"
|
||||||
[ "$(depth)" = "0" ] || fail_msg "D6: ambiguous-empty must NOT enqueue, got depth $(depth)"
|
[ "$(depth)" = "0" ] || fail_msg "D6: ambiguous-empty must NOT enqueue, got depth $(depth)"
|
||||||
|
|
||||||
@@ -449,7 +442,7 @@ EOF
|
|||||||
write_fc_watchlist "$wl" 999
|
write_fc_watchlist "$wl" 999
|
||||||
err="$("$DET" poll-once 2>&1)"; rc=$?
|
err="$("$DET" poll-once 2>&1)"; rc=$?
|
||||||
[ "$rc" -ne 0 ] || fail_msg "D9: the new field must NOT weaken Gate B — an out-of-range schema_version must still FAIL LOUD (rc=$rc)"
|
[ "$rc" -ne 0 ] || fail_msg "D9: the new field must NOT weaken Gate B — an out-of-range schema_version must still FAIL LOUD (rc=$rc)"
|
||||||
echo "$err" | has_match -qi 'range' || fail_msg "D9: the out-of-range failure must still state it is out of the supported range [$err]"
|
echo "$err" | grep -qi 'range' || fail_msg "D9: the out-of-range failure must still state it is out of the supported range [$err]"
|
||||||
) && ok
|
) && ok
|
||||||
|
|
||||||
echo "== D10: snapshot metadata (fd 3, #940) — adapter-attested sha/ts land in the enqueued locators =="
|
echo "== D10: snapshot metadata (fd 3, #940) — adapter-attested sha/ts land in the enqueued locators =="
|
||||||
@@ -520,7 +513,7 @@ EOF
|
|||||||
printf 'SHA-BBB\n' >"$stub/repo_r1"
|
printf 'SHA-BBB\n' >"$stub/repo_r1"
|
||||||
err="$("$DET" poll-once 2>&1 >/dev/null)"; rc=$?
|
err="$("$DET" poll-once 2>&1 >/dev/null)"; rc=$?
|
||||||
[ "$rc" -eq 0 ] || fail_msg "D11: malformed metadata must NEVER fail the poll (rc=$rc)"
|
[ "$rc" -eq 0 ] || fail_msg "D11: malformed metadata must NEVER fail the poll (rc=$rc)"
|
||||||
echo "$err" | has_match -qi 'snapshot' || fail_msg "D11: dropping malformed metadata must be LOUD on stderr [$err]"
|
echo "$err" | grep -qi 'snapshot' || fail_msg "D11: dropping malformed metadata must be LOUD on stderr [$err]"
|
||||||
entry="$("$STORE" drain | tail -1)"
|
entry="$("$STORE" drain | tail -1)"
|
||||||
echo "$entry" | jq -e '.locators | has("snapshot_sha") or has("snapshot_ts")' >/dev/null 2>&1 \
|
echo "$entry" | jq -e '.locators | has("snapshot_sha") or has("snapshot_ts")' >/dev/null 2>&1 \
|
||||||
&& fail_msg "D11: malformed metadata must emit NO snapshot fields [$entry]"
|
&& fail_msg "D11: malformed metadata must emit NO snapshot fields [$entry]"
|
||||||
@@ -529,7 +522,7 @@ EOF
|
|||||||
printf 'SHA-CCC\n' >"$stub/repo_r1"
|
printf 'SHA-CCC\n' >"$stub/repo_r1"
|
||||||
err="$("$DET" poll-once 2>&1 >/dev/null)"; rc=$?
|
err="$("$DET" poll-once 2>&1 >/dev/null)"; rc=$?
|
||||||
[ "$rc" -eq 0 ] || fail_msg "D11: rejected snapshot_sha must NEVER fail the poll (rc=$rc)"
|
[ "$rc" -eq 0 ] || fail_msg "D11: rejected snapshot_sha must NEVER fail the poll (rc=$rc)"
|
||||||
echo "$err" | has_match -qi 'snapshot' || fail_msg "D11: rejecting a bad snapshot_sha must be LOUD on stderr [$err]"
|
echo "$err" | grep -qi 'snapshot' || fail_msg "D11: rejecting a bad snapshot_sha must be LOUD on stderr [$err]"
|
||||||
entry="$("$STORE" drain | tail -1)"
|
entry="$("$STORE" drain | tail -1)"
|
||||||
echo "$entry" | jq -e '.locators | has("snapshot_sha") or has("snapshot_ts")' >/dev/null 2>&1 \
|
echo "$entry" | jq -e '.locators | has("snapshot_sha") or has("snapshot_ts")' >/dev/null 2>&1 \
|
||||||
&& fail_msg "D11: rejected metadata must emit NO snapshot fields [$entry]"
|
&& fail_msg "D11: rejected metadata must emit NO snapshot fields [$entry]"
|
||||||
@@ -567,7 +560,7 @@ EOF
|
|||||||
printf 'SHA-BBB\n' >"$stub/repo_r1"
|
printf 'SHA-BBB\n' >"$stub/repo_r1"
|
||||||
err="$("$DET" poll-once 2>&1 >/dev/null)"; rc=$?
|
err="$("$DET" poll-once 2>&1 >/dev/null)"; rc=$?
|
||||||
[ "$rc" -eq 0 ] || fail_msg "D12: ts-without-sha must NEVER fail the poll (rc=$rc)"
|
[ "$rc" -eq 0 ] || fail_msg "D12: ts-without-sha must NEVER fail the poll (rc=$rc)"
|
||||||
echo "$err" | has_match -qi 'without a valid snapshot_sha' || fail_msg "D12: dropping ts-without-sha must be LOUD on stderr [$err]"
|
echo "$err" | grep -qi 'without a valid snapshot_sha' || fail_msg "D12: dropping ts-without-sha must be LOUD on stderr [$err]"
|
||||||
entry="$("$STORE" drain | tail -1)"
|
entry="$("$STORE" drain | tail -1)"
|
||||||
echo "$entry" | jq -e '.locators | has("snapshot_sha") or has("snapshot_ts")' >/dev/null 2>&1 \
|
echo "$entry" | jq -e '.locators | has("snapshot_sha") or has("snapshot_ts")' >/dev/null 2>&1 \
|
||||||
&& fail_msg "D12: ts-without-sha must emit NO snapshot fields [$entry]"
|
&& fail_msg "D12: ts-without-sha must emit NO snapshot fields [$entry]"
|
||||||
@@ -578,7 +571,7 @@ EOF
|
|||||||
printf 'SHA-CCC\n' >"$stub/repo_r1"
|
printf 'SHA-CCC\n' >"$stub/repo_r1"
|
||||||
err="$("$DET" poll-once 2>&1 >/dev/null)"; rc=$?
|
err="$("$DET" poll-once 2>&1 >/dev/null)"; rc=$?
|
||||||
[ "$rc" -eq 0 ] || fail_msg "D12: future ts must NEVER fail the poll (rc=$rc)"
|
[ "$rc" -eq 0 ] || fail_msg "D12: future ts must NEVER fail the poll (rc=$rc)"
|
||||||
echo "$err" | has_match -qi 'future-skew' || fail_msg "D12: dropping a future ts must be LOUD on stderr [$err]"
|
echo "$err" | grep -qi 'future-skew' || fail_msg "D12: dropping a future ts must be LOUD on stderr [$err]"
|
||||||
entry="$("$STORE" drain | tail -1)"
|
entry="$("$STORE" drain | tail -1)"
|
||||||
echo "$entry" | jq -e --arg s "$goodsha" 'select(.locators.snapshot_sha==$s) | .locators | has("snapshot_ts") | not' >/dev/null 2>&1 \
|
echo "$entry" | jq -e --arg s "$goodsha" 'select(.locators.snapshot_sha==$s) | .locators | has("snapshot_ts") | not' >/dev/null 2>&1 \
|
||||||
|| fail_msg "D12: future ts must drop ts but KEEP the sha [$entry]"
|
|| fail_msg "D12: future ts must drop ts but KEEP the sha [$entry]"
|
||||||
@@ -588,7 +581,7 @@ EOF
|
|||||||
printf 'SHA-DDD\n' >"$stub/repo_r1"
|
printf 'SHA-DDD\n' >"$stub/repo_r1"
|
||||||
err="$("$DET" poll-once 2>&1 >/dev/null)"; rc=$?
|
err="$("$DET" poll-once 2>&1 >/dev/null)"; rc=$?
|
||||||
[ "$rc" -eq 0 ] || fail_msg "D12: absurd ts must NEVER fail the poll (rc=$rc)"
|
[ "$rc" -eq 0 ] || fail_msg "D12: absurd ts must NEVER fail the poll (rc=$rc)"
|
||||||
echo "$err" | has_match -qi 'sane positive epoch' || fail_msg "D12: rejecting an absurd ts must be LOUD on stderr [$err]"
|
echo "$err" | grep -qi 'sane positive epoch' || fail_msg "D12: rejecting an absurd ts must be LOUD on stderr [$err]"
|
||||||
entry="$("$STORE" drain | tail -1)"
|
entry="$("$STORE" drain | tail -1)"
|
||||||
echo "$entry" | jq -e --arg s "$goodsha" 'select(.locators.snapshot_sha==$s) | .locators | has("snapshot_ts") | not' >/dev/null 2>&1 \
|
echo "$entry" | jq -e --arg s "$goodsha" 'select(.locators.snapshot_sha==$s) | .locators | has("snapshot_ts") | not' >/dev/null 2>&1 \
|
||||||
|| fail_msg "D12: absurd ts must drop ts but KEEP the sha [$entry]"
|
|| fail_msg "D12: absurd ts must drop ts but KEEP the sha [$entry]"
|
||||||
@@ -628,7 +621,7 @@ EOF
|
|||||||
printf 'SHA-BBB\n' >"$stub/repo_r1"
|
printf 'SHA-BBB\n' >"$stub/repo_r1"
|
||||||
err="$(WAKE_SNAPSHOT_TS_FUTURE_SLACK='300s' "$DET" poll-once 2>&1 >/dev/null)"; rc=$?
|
err="$(WAKE_SNAPSHOT_TS_FUTURE_SLACK='300s' "$DET" poll-once 2>&1 >/dev/null)"; rc=$?
|
||||||
[ "$rc" -eq 0 ] || fail_msg "D13: SLACK='300s' must NEVER fail the poll (rc=$rc)"
|
[ "$rc" -eq 0 ] || fail_msg "D13: SLACK='300s' must NEVER fail the poll (rc=$rc)"
|
||||||
echo "$err" | has_match -qi 'falling back to 300' || fail_msg "D13: malformed slack must be LOUD on stderr [$err]"
|
echo "$err" | grep -qi 'falling back to 300' || fail_msg "D13: malformed slack must be LOUD on stderr [$err]"
|
||||||
entry="$("$STORE" drain | tail -1)"
|
entry="$("$STORE" drain | tail -1)"
|
||||||
echo "$entry" | jq -e --arg s "$goodsha" 'select(.locators.snapshot_sha==$s) | .locators | has("snapshot_ts")' >/dev/null 2>&1 \
|
echo "$entry" | jq -e --arg s "$goodsha" 'select(.locators.snapshot_sha==$s) | .locators | has("snapshot_ts")' >/dev/null 2>&1 \
|
||||||
|| fail_msg "D13: valid metadata must SURVIVE a malformed knob (fallback, not drop) [$entry]"
|
|| fail_msg "D13: valid metadata must SURVIVE a malformed knob (fallback, not drop) [$entry]"
|
||||||
@@ -637,7 +630,7 @@ EOF
|
|||||||
printf 'SHA-CCC\n' >"$stub/repo_r1"
|
printf 'SHA-CCC\n' >"$stub/repo_r1"
|
||||||
err="$(WAKE_SNAPSHOT_TS_FUTURE_SLACK='abc' "$DET" poll-once 2>&1 >/dev/null)"; rc=$?
|
err="$(WAKE_SNAPSHOT_TS_FUTURE_SLACK='abc' "$DET" poll-once 2>&1 >/dev/null)"; rc=$?
|
||||||
[ "$rc" -eq 0 ] || fail_msg "D13: SLACK='abc' must NEVER fail the poll (rc=$rc)"
|
[ "$rc" -eq 0 ] || fail_msg "D13: SLACK='abc' must NEVER fail the poll (rc=$rc)"
|
||||||
echo "$err" | has_match -qi 'falling back to 300' || fail_msg "D13: non-numeric slack must be LOUD on stderr [$err]"
|
echo "$err" | grep -qi 'falling back to 300' || fail_msg "D13: non-numeric slack must be LOUD on stderr [$err]"
|
||||||
entry="$("$STORE" drain | tail -1)"
|
entry="$("$STORE" drain | tail -1)"
|
||||||
echo "$entry" | jq -e --arg s "$goodsha" 'select(.locators.snapshot_sha==$s) | .locators | has("snapshot_ts")' >/dev/null 2>&1 \
|
echo "$entry" | jq -e --arg s "$goodsha" 'select(.locators.snapshot_sha==$s) | .locators | has("snapshot_ts")' >/dev/null 2>&1 \
|
||||||
|| fail_msg "D13: valid metadata must SURVIVE a non-numeric knob [$entry]"
|
|| fail_msg "D13: valid metadata must SURVIVE a non-numeric knob [$entry]"
|
||||||
@@ -647,7 +640,7 @@ EOF
|
|||||||
printf 'SHA-DDD\n' >"$stub/repo_r1"
|
printf 'SHA-DDD\n' >"$stub/repo_r1"
|
||||||
err="$(WAKE_SNAPSHOT_TS_FUTURE_SLACK='-99999999' "$DET" poll-once 2>&1 >/dev/null)"; rc=$?
|
err="$(WAKE_SNAPSHOT_TS_FUTURE_SLACK='-99999999' "$DET" poll-once 2>&1 >/dev/null)"; rc=$?
|
||||||
[ "$rc" -eq 0 ] || fail_msg "D13: negative SLACK must NEVER fail the poll (rc=$rc)"
|
[ "$rc" -eq 0 ] || fail_msg "D13: negative SLACK must NEVER fail the poll (rc=$rc)"
|
||||||
echo "$err" | has_match -qi 'falling back to 300' || fail_msg "D13: negative slack must be LOUD on stderr [$err]"
|
echo "$err" | grep -qi 'falling back to 300' || fail_msg "D13: negative slack must be LOUD on stderr [$err]"
|
||||||
entry="$("$STORE" drain | tail -1)"
|
entry="$("$STORE" drain | tail -1)"
|
||||||
echo "$entry" | jq -e --arg s "$goodsha" 'select(.locators.snapshot_sha==$s) | .locators | has("snapshot_ts")' >/dev/null 2>&1 \
|
echo "$entry" | jq -e --arg s "$goodsha" 'select(.locators.snapshot_sha==$s) | .locators | has("snapshot_ts")' >/dev/null 2>&1 \
|
||||||
|| fail_msg "D13: negative slack must NOT invert the guard into deny-all [$entry]"
|
|| fail_msg "D13: negative slack must NOT invert the guard into deny-all [$entry]"
|
||||||
@@ -659,7 +652,7 @@ EOF
|
|||||||
printf 'SHA-CC2\n' >"$stub/repo_r1"
|
printf 'SHA-CC2\n' >"$stub/repo_r1"
|
||||||
err="$(WAKE_SNAPSHOT_TS_FUTURE_SLACK=$'300\n8' "$DET" poll-once 2>&1 >/dev/null)"; rc=$?
|
err="$(WAKE_SNAPSHOT_TS_FUTURE_SLACK=$'300\n8' "$DET" poll-once 2>&1 >/dev/null)"; rc=$?
|
||||||
[ "$rc" -eq 0 ] || fail_msg "D13: multi-line SLACK (300\\n8) must NEVER fail the poll (rc=$rc) [$err]"
|
[ "$rc" -eq 0 ] || fail_msg "D13: multi-line SLACK (300\\n8) must NEVER fail the poll (rc=$rc) [$err]"
|
||||||
echo "$err" | has_match -qi 'falling back to 300' || fail_msg "D13: multi-line slack must be LOUD on stderr [$err]"
|
echo "$err" | grep -qi 'falling back to 300' || fail_msg "D13: multi-line slack must be LOUD on stderr [$err]"
|
||||||
entry="$("$STORE" drain | tail -1)"
|
entry="$("$STORE" drain | tail -1)"
|
||||||
echo "$entry" | jq -e --arg s "$goodsha" 'select(.locators.snapshot_sha==$s) | .locators | has("snapshot_ts")' >/dev/null 2>&1 \
|
echo "$entry" | jq -e --arg s "$goodsha" 'select(.locators.snapshot_sha==$s) | .locators | has("snapshot_ts")' >/dev/null 2>&1 \
|
||||||
|| fail_msg "D13: valid metadata must SURVIVE a multi-line knob [$entry]"
|
|| fail_msg "D13: valid metadata must SURVIVE a multi-line knob [$entry]"
|
||||||
@@ -667,7 +660,7 @@ EOF
|
|||||||
printf 'SHA-CC3\n' >"$stub/repo_r1"
|
printf 'SHA-CC3\n' >"$stub/repo_r1"
|
||||||
err="$(WAKE_SNAPSHOT_TS_FUTURE_SLACK=$'300\nabc' "$DET" poll-once 2>&1 >/dev/null)"; rc=$?
|
err="$(WAKE_SNAPSHOT_TS_FUTURE_SLACK=$'300\nabc' "$DET" poll-once 2>&1 >/dev/null)"; rc=$?
|
||||||
[ "$rc" -eq 0 ] || fail_msg "D13: multi-line SLACK (300\\nabc) must NEVER fail the poll (rc=$rc) [$err]"
|
[ "$rc" -eq 0 ] || fail_msg "D13: multi-line SLACK (300\\nabc) must NEVER fail the poll (rc=$rc) [$err]"
|
||||||
echo "$err" | has_match -qi 'falling back to 300' || fail_msg "D13: multi-line non-numeric slack must be LOUD on stderr [$err]"
|
echo "$err" | grep -qi 'falling back to 300' || fail_msg "D13: multi-line non-numeric slack must be LOUD on stderr [$err]"
|
||||||
entry="$("$STORE" drain | tail -1)"
|
entry="$("$STORE" drain | tail -1)"
|
||||||
echo "$entry" | jq -e --arg s "$goodsha" 'select(.locators.snapshot_sha==$s) | .locators | has("snapshot_ts")' >/dev/null 2>&1 \
|
echo "$entry" | jq -e --arg s "$goodsha" 'select(.locators.snapshot_sha==$s) | .locators | has("snapshot_ts")' >/dev/null 2>&1 \
|
||||||
|| fail_msg "D13: valid metadata must SURVIVE a multi-line non-numeric knob [$entry]"
|
|| fail_msg "D13: valid metadata must SURVIVE a multi-line non-numeric knob [$entry]"
|
||||||
@@ -694,7 +687,7 @@ EOF
|
|||||||
printf 'SHA-EEE\n' >"$stub/repo_r1"
|
printf 'SHA-EEE\n' >"$stub/repo_r1"
|
||||||
err="$(WAKE_SNAPSHOT_TS_FUTURE_SLACK='0' "$DET" poll-once 2>&1 >/dev/null)"; rc=$?
|
err="$(WAKE_SNAPSHOT_TS_FUTURE_SLACK='0' "$DET" poll-once 2>&1 >/dev/null)"; rc=$?
|
||||||
[ "$rc" -eq 0 ] || fail_msg "D13: valid SLACK=0 must not fail the poll (rc=$rc)"
|
[ "$rc" -eq 0 ] || fail_msg "D13: valid SLACK=0 must not fail the poll (rc=$rc)"
|
||||||
echo "$err" | has_match -qi 'future-skew' || fail_msg "D13: a valid tightened slack must still reject a future ts [$err]"
|
echo "$err" | grep -qi 'future-skew' || fail_msg "D13: a valid tightened slack must still reject a future ts [$err]"
|
||||||
entry="$("$STORE" drain | tail -1)"
|
entry="$("$STORE" drain | tail -1)"
|
||||||
echo "$entry" | jq -e --arg s "$goodsha" 'select(.locators.snapshot_sha==$s) | .locators | has("snapshot_ts") | not' >/dev/null 2>&1 \
|
echo "$entry" | jq -e --arg s "$goodsha" 'select(.locators.snapshot_sha==$s) | .locators | has("snapshot_ts") | not' >/dev/null 2>&1 \
|
||||||
|| fail_msg "D13: valid SLACK=0 must drop the future ts but keep the sha [$entry]"
|
|| fail_msg "D13: valid SLACK=0 must drop the future ts but keep the sha [$entry]"
|
||||||
@@ -703,7 +696,7 @@ EOF
|
|||||||
|
|
||||||
echo
|
echo
|
||||||
if [ -s "$FAILFILE" ]; then
|
if [ -s "$FAILFILE" ]; then
|
||||||
echo "wake detector harness: FAILED ($(count_lines . "$FAILFILE") assertion(s))" >&2
|
echo "wake detector harness: FAILED ($(grep -c . "$FAILFILE") assertion(s))" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
echo "wake detector harness: all invariants passed ($pass groups)"
|
echo "wake detector harness: all invariants passed ($pass groups)"
|
||||||
|
|||||||
@@ -35,13 +35,6 @@
|
|||||||
set -uo pipefail
|
set -uo pipefail
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
# #973: three-valued grep assertion helpers (has_match/count_lines); init saves real stderr for abort loudness.
|
|
||||||
# shellcheck disable=SC1091
|
|
||||||
if ! . "$SCRIPT_DIR/_wake-common.sh"; then
|
|
||||||
echo "WAKE-ASSERT INIT ABORT: cannot source _wake-common.sh — suite ran ZERO wake assertions (#984)" >&2
|
|
||||||
exit 97
|
|
||||||
fi
|
|
||||||
wake_assert_init
|
|
||||||
STORE="$SCRIPT_DIR/store.sh"
|
STORE="$SCRIPT_DIR/store.sh"
|
||||||
DIGEST="$SCRIPT_DIR/digest.sh"
|
DIGEST="$SCRIPT_DIR/digest.sh"
|
||||||
SIGN="$SCRIPT_DIR/sign.sh"
|
SIGN="$SCRIPT_DIR/sign.sh"
|
||||||
@@ -109,13 +102,13 @@ echo "== D1: CUMULATIVE-STATE — two pending changes BOTH render (not just late
|
|||||||
"$STORE" enqueue --seq 1 --class actionable --locators '{"repo":"r","issue":11}' >/dev/null
|
"$STORE" enqueue --seq 1 --class actionable --locators '{"repo":"r","issue":11}' >/dev/null
|
||||||
"$STORE" enqueue --seq 2 --class actionable --locators '{"repo":"r","issue":22}' >/dev/null
|
"$STORE" enqueue --seq 2 --class actionable --locators '{"repo":"r","issue":22}' >/dev/null
|
||||||
out="$("$DIGEST" render)" || fail_msg "D1: render exited non-zero"
|
out="$("$DIGEST" render)" || fail_msg "D1: render exited non-zero"
|
||||||
echo "$out" | has_match -q 'issue=#11' || fail_msg "D1: OLDER change (issue #11) dropped — digest is a delta, not cumulative state"
|
echo "$out" | grep -q 'issue=#11' || fail_msg "D1: OLDER change (issue #11) dropped — digest is a delta, not cumulative state"
|
||||||
echo "$out" | has_match -q 'issue=#22' || fail_msg "D1: newer change (issue #22) missing"
|
echo "$out" | grep -q 'issue=#22' || fail_msg "D1: newer change (issue #22) missing"
|
||||||
echo "$out" | has_match -q 'seq 1' || fail_msg "D1: seq 1 not listed in cumulative set"
|
echo "$out" | grep -q 'seq 1' || fail_msg "D1: seq 1 not listed in cumulative set"
|
||||||
echo "$out" | has_match -q 'seq 2' || fail_msg "D1: seq 2 not listed in cumulative set"
|
echo "$out" | grep -q 'seq 2' || fail_msg "D1: seq 2 not listed in cumulative set"
|
||||||
# A coalescing digest-class entry is STATE (full), not a delta: a later digest
|
# A coalescing digest-class entry is STATE (full), not a delta: a later digest
|
||||||
# subsumes the earlier, but the cumulative unacked set (both actionables) stays.
|
# subsumes the earlier, but the cumulative unacked set (both actionables) stays.
|
||||||
echo "$out" | has_match -q 'pending=2' || fail_msg "D1: cumulative pending count wrong (expected 2)"
|
echo "$out" | grep -q 'pending=2' || fail_msg "D1: cumulative pending count wrong (expected 2)"
|
||||||
) && ok
|
) && ok
|
||||||
|
|
||||||
echo "== D2: HARD-LOCATOR enforcement — a malformed actionable claim is QUARANTINED (fail-loud PER-ENTRY, #920); never delivered as valid =="
|
echo "== D2: HARD-LOCATOR enforcement — a malformed actionable claim is QUARANTINED (fail-loud PER-ENTRY, #920); never delivered as valid =="
|
||||||
@@ -134,9 +127,9 @@ echo "== D2: HARD-LOCATOR enforcement — a malformed actionable claim is QUARAN
|
|||||||
# but the unlocated claim is NEVER delivered as a valid CLAIM (fail-loud is
|
# but the unlocated claim is NEVER delivered as a valid CLAIM (fail-loud is
|
||||||
# preserved, now per-entry). The old exit-4 wedged the entire drain (#920).
|
# preserved, now per-entry). The old exit-4 wedged the entire drain (#920).
|
||||||
[ "$rc" -eq 0 ] || fail_msg "D2: a malformed actionable must be quarantined (render exit 0, #920), got $rc"
|
[ "$rc" -eq 0 ] || fail_msg "D2: a malformed actionable must be quarantined (render exit 0, #920), got $rc"
|
||||||
printf '%s' "$out" | has_match -q 'mergeable=true' && fail_msg "D2: an unlocated actionable claim must NOT be delivered as a valid CLAIM"
|
printf '%s' "$out" | grep -q 'mergeable=true' && fail_msg "D2: an unlocated actionable claim must NOT be delivered as a valid CLAIM"
|
||||||
has_match -q 'mergeable=true' "$h/default/dead-letter.jsonl" 2>/dev/null || fail_msg "D2: the malformed claim must be DEAD-LETTERED (fail-loud preserved, per-entry)"
|
grep -q 'mergeable=true' "$h/default/dead-letter.jsonl" 2>/dev/null || fail_msg "D2: the malformed claim must be DEAD-LETTERED (fail-loud preserved, per-entry)"
|
||||||
has_match -qi 'QUARANTINE' "$err" || fail_msg "D2: a LOUD per-entry alarm must fire for the malformed claim"
|
grep -qi 'QUARANTINE' "$err" || fail_msg "D2: a LOUD per-entry alarm must fire for the malformed claim"
|
||||||
|
|
||||||
# A present-but-imprecise sha (not 40 hex) does NOT satisfy the hard locator ->
|
# A present-but-imprecise sha (not 40 hex) does NOT satisfy the hard locator ->
|
||||||
# quarantined, not delivered.
|
# quarantined, not delivered.
|
||||||
@@ -147,8 +140,8 @@ echo "== D2: HARD-LOCATOR enforcement — a malformed actionable claim is QUARAN
|
|||||||
rc=0
|
rc=0
|
||||||
out="$("$DIGEST" render 2>/dev/null)" || rc=$?
|
out="$("$DIGEST" render 2>/dev/null)" || rc=$?
|
||||||
[ "$rc" -eq 0 ] || fail_msg "D2: imprecise-sha entry must be quarantined (render exit 0), got $rc"
|
[ "$rc" -eq 0 ] || fail_msg "D2: imprecise-sha entry must be quarantined (render exit 0), got $rc"
|
||||||
printf '%s' "$out" | has_match -q 'ci=green' && fail_msg "D2: imprecise sha (not 40-hex) must not satisfy the hard-locator gate (claim must not deliver)"
|
printf '%s' "$out" | grep -q 'ci=green' && fail_msg "D2: imprecise sha (not 40-hex) must not satisfy the hard-locator gate (claim must not deliver)"
|
||||||
has_match -q 'ci=green' "$h/default/dead-letter.jsonl" 2>/dev/null || fail_msg "D2: imprecise-sha claim must be dead-lettered"
|
grep -q 'ci=green' "$h/default/dead-letter.jsonl" 2>/dev/null || fail_msg "D2: imprecise-sha claim must be dead-lettered"
|
||||||
|
|
||||||
# The SAME claim WITH a precise 40-hex sha renders fine (delivered, not quarantined).
|
# The SAME claim WITH a precise 40-hex sha renders fine (delivered, not quarantined).
|
||||||
h="$(fresh_state d2c)"
|
h="$(fresh_state d2c)"
|
||||||
@@ -156,7 +149,7 @@ echo "== D2: HARD-LOCATOR enforcement — a malformed actionable claim is QUARAN
|
|||||||
export WAKE_STATE_HOME
|
export WAKE_STATE_HOME
|
||||||
"$STORE" enqueue --seq 1 --class actionable --locators "$(jq -cn --arg s "$SHA40" '{claim:"ci=green",sha:$s}')" >/dev/null
|
"$STORE" enqueue --seq 1 --class actionable --locators "$(jq -cn --arg s "$SHA40" '{claim:"ci=green",sha:$s}')" >/dev/null
|
||||||
out="$("$DIGEST" render 2>/dev/null)" || fail_msg "D2: a well-located actionable claim must render"
|
out="$("$DIGEST" render 2>/dev/null)" || fail_msg "D2: a well-located actionable claim must render"
|
||||||
printf '%s' "$out" | has_match -q "$SHA40" || fail_msg "D2: a well-located actionable claim must be DELIVERED"
|
printf '%s' "$out" | grep -q "$SHA40" || fail_msg "D2: a well-located actionable claim must be DELIVERED"
|
||||||
[ -s "$h/default/dead-letter.jsonl" ] && fail_msg "D2: a well-located claim must NOT be quarantined"
|
[ -s "$h/default/dead-letter.jsonl" ] && fail_msg "D2: a well-located claim must NOT be quarantined"
|
||||||
# --- #905 bypass-prevention (STILL enforced, now via quarantine): a NON-
|
# --- #905 bypass-prevention (STILL enforced, now via quarantine): a NON-
|
||||||
# CANONICAL entry with a TOP-LEVEL `.claim` (store.sh never emits this shape;
|
# CANONICAL entry with a TOP-LEVEL `.claim` (store.sh never emits this shape;
|
||||||
@@ -171,8 +164,8 @@ echo "== D2: HARD-LOCATOR enforcement — a malformed actionable claim is QUARAN
|
|||||||
rc=0
|
rc=0
|
||||||
out="$(printf '%s\n' "$toplevel_claim_entry" | "$DIGEST" render --stdin 2>/dev/null)" || rc=$?
|
out="$(printf '%s\n' "$toplevel_claim_entry" | "$DIGEST" render --stdin 2>/dev/null)" || rc=$?
|
||||||
[ "$rc" -eq 0 ] || fail_msg "D2(#905): top-level .claim must be quarantined via --stdin (exit 0), got $rc"
|
[ "$rc" -eq 0 ] || fail_msg "D2(#905): top-level .claim must be quarantined via --stdin (exit 0), got $rc"
|
||||||
printf '%s' "$out" | has_match -q 'mergeable=true' && fail_msg "D2(#905): --stdin top-level-.claim must NOT be delivered (gate not bypassed)"
|
printf '%s' "$out" | grep -q 'mergeable=true' && fail_msg "D2(#905): --stdin top-level-.claim must NOT be delivered (gate not bypassed)"
|
||||||
has_match -q 'mergeable=true' "$h/default/dead-letter.jsonl" 2>/dev/null || fail_msg "D2(#905): --stdin top-level .claim must be dead-lettered (gate enforced)"
|
grep -q 'mergeable=true' "$h/default/dead-letter.jsonl" 2>/dev/null || fail_msg "D2(#905): --stdin top-level .claim must be dead-lettered (gate enforced)"
|
||||||
ff="$TMP_ROOT/d2d-entry.jsonl"
|
ff="$TMP_ROOT/d2d-entry.jsonl"
|
||||||
printf '%s\n' "$toplevel_claim_entry" >"$ff"
|
printf '%s\n' "$toplevel_claim_entry" >"$ff"
|
||||||
h="$(fresh_state d2e)"
|
h="$(fresh_state d2e)"
|
||||||
@@ -181,8 +174,8 @@ echo "== D2: HARD-LOCATOR enforcement — a malformed actionable claim is QUARAN
|
|||||||
rc=0
|
rc=0
|
||||||
out2="$("$DIGEST" render --from-file "$ff" 2>/dev/null)" || rc=$?
|
out2="$("$DIGEST" render --from-file "$ff" 2>/dev/null)" || rc=$?
|
||||||
[ "$rc" -eq 0 ] || fail_msg "D2(#905): top-level .claim must be quarantined via --from-file (exit 0), got $rc"
|
[ "$rc" -eq 0 ] || fail_msg "D2(#905): top-level .claim must be quarantined via --from-file (exit 0), got $rc"
|
||||||
printf '%s' "$out2" | has_match -q 'mergeable=true' && fail_msg "D2(#905): --from-file top-level-.claim must NOT be delivered (gate not bypassed)"
|
printf '%s' "$out2" | grep -q 'mergeable=true' && fail_msg "D2(#905): --from-file top-level-.claim must NOT be delivered (gate not bypassed)"
|
||||||
has_match -q 'mergeable=true' "$h/default/dead-letter.jsonl" 2>/dev/null || fail_msg "D2(#905): --from-file top-level .claim must be dead-lettered (gate enforced)"
|
grep -q 'mergeable=true' "$h/default/dead-letter.jsonl" 2>/dev/null || fail_msg "D2(#905): --from-file top-level .claim must be dead-lettered (gate enforced)"
|
||||||
) && ok
|
) && ok
|
||||||
|
|
||||||
echo "== D3: TWO-TIER — orientation no-op with ZERO tool calls; actionable = claim-to-verify =="
|
echo "== D3: TWO-TIER — orientation no-op with ZERO tool calls; actionable = claim-to-verify =="
|
||||||
@@ -204,7 +197,7 @@ echo "== D3: TWO-TIER — orientation no-op with ZERO tool calls; actionable = c
|
|||||||
done
|
done
|
||||||
# No pending obligations => no-op common case.
|
# No pending obligations => no-op common case.
|
||||||
out="$(PATH="$bin:$PATH" "$DIGEST" render --lane build)" || fail_msg "D3: no-op render failed"
|
out="$(PATH="$bin:$PATH" "$DIGEST" render --lane build)" || fail_msg "D3: no-op render failed"
|
||||||
echo "$out" | has_match -q 'NO-OP' || fail_msg "D3: empty inbox must render an explicit NO-OP orientation"
|
echo "$out" | grep -q 'NO-OP' || fail_msg "D3: empty inbox must render an explicit NO-OP orientation"
|
||||||
[ -e "$WAKE_STATE_HOME/TOOL_CALLED" ] && fail_msg "D3: orientation no-op made a live tool call (must be ZERO)"
|
[ -e "$WAKE_STATE_HOME/TOOL_CALLED" ] && fail_msg "D3: orientation no-op made a live tool call (must be ZERO)"
|
||||||
# Now an actionable, consequential fact. It must be a CLAIM-TO-VERIFY, never
|
# Now an actionable, consequential fact. It must be a CLAIM-TO-VERIFY, never
|
||||||
# rendered as a trusted assertion or an auto-action.
|
# rendered as a trusted assertion or an auto-action.
|
||||||
@@ -213,14 +206,14 @@ echo "== D3: TWO-TIER — orientation no-op with ZERO tool calls; actionable = c
|
|||||||
out2="$(PATH="$bin:$PATH" "$DIGEST" render)" || fail_msg "D3: actionable render failed"
|
out2="$(PATH="$bin:$PATH" "$DIGEST" render)" || fail_msg "D3: actionable render failed"
|
||||||
# Rendering itself still makes ZERO live calls (it only lays out claims).
|
# Rendering itself still makes ZERO live calls (it only lays out claims).
|
||||||
[ -e "$WAKE_STATE_HOME/TOOL_CALLED" ] && fail_msg "D3: rendering an actionable claim made a live call (must defer to the consumer's gate)"
|
[ -e "$WAKE_STATE_HOME/TOOL_CALLED" ] && fail_msg "D3: rendering an actionable claim made a live call (must defer to the consumer's gate)"
|
||||||
echo "$out2" | has_match -q 'CLAIM@seq' || fail_msg "D3: consequential fact not framed as CLAIM@seq"
|
echo "$out2" | grep -q 'CLAIM@seq' || fail_msg "D3: consequential fact not framed as CLAIM@seq"
|
||||||
echo "$out2" | has_match -qi 'VERIFY LIVE' || fail_msg "D3: claim not marked for live verification"
|
echo "$out2" | grep -qi 'VERIFY LIVE' || fail_msg "D3: claim not marked for live verification"
|
||||||
echo "$out2" | has_match -qi 'do NOT act on this line' || fail_msg "D3: claim missing do-not-auto-action framing"
|
echo "$out2" | grep -qi 'do NOT act on this line' || fail_msg "D3: claim missing do-not-auto-action framing"
|
||||||
# The consequential fact must NOT appear as a bare trusted directive.
|
# The consequential fact must NOT appear as a bare trusted directive.
|
||||||
echo "$out2" | has_match -qiE 'merge now|go ahead and (merge|deploy)|safe to merge' &&
|
echo "$out2" | grep -qiE 'merge now|go ahead and (merge|deploy)|safe to merge' &&
|
||||||
fail_msg "D3: digest auto-actioned a consequential fact (imperative present)"
|
fail_msg "D3: digest auto-actioned a consequential fact (imperative present)"
|
||||||
# And its hard locator + one-call re-verify hint are present.
|
# And its hard locator + one-call re-verify hint are present.
|
||||||
echo "$out2" | has_match -q "re-verify (ONE call)" || fail_msg "D3: actionable claim missing one-call re-verify locator"
|
echo "$out2" | grep -q "re-verify (ONE call)" || fail_msg "D3: actionable claim missing one-call re-verify locator"
|
||||||
true
|
true
|
||||||
) && ok
|
) && ok
|
||||||
|
|
||||||
@@ -236,7 +229,7 @@ echo "== D4: SCRUB — secret-canary + ANSI/bidi/zero-width in source free-text
|
|||||||
"$STORE" enqueue --seq 1 --class actionable --locators "$loc" >/dev/null
|
"$STORE" enqueue --seq 1 --class actionable --locators "$loc" >/dev/null
|
||||||
out="$("$DIGEST" render)" || fail_msg "D4: render failed"
|
out="$("$DIGEST" render)" || fail_msg "D4: render failed"
|
||||||
# ANSI escape / CSI must be gone.
|
# ANSI escape / CSI must be gone.
|
||||||
printf '%s' "$out" | LC_ALL=C has_match -q "$(printf '\x1b')" && fail_msg "D4: ANSI ESC survived the scrub"
|
printf '%s' "$out" | LC_ALL=C grep -q "$(printf '\x1b')" && fail_msg "D4: ANSI ESC survived the scrub"
|
||||||
# bidi/zero-width/BOM UTF-8 sequences must be gone.
|
# bidi/zero-width/BOM UTF-8 sequences must be gone.
|
||||||
# #912: patterns are LITERAL bytes + `grep -E`, NOT PCRE `grep -P`. BusyBox
|
# #912: patterns are LITERAL bytes + `grep -E`, NOT PCRE `grep -P`. BusyBox
|
||||||
# grep (Alpine/musl CI) has no `-P` — a `grep -qP` there errors
|
# grep (Alpine/musl CI) has no `-P` — a `grep -qP` there errors
|
||||||
@@ -251,21 +244,21 @@ echo "== D4: SCRUB — secret-canary + ANSI/bidi/zero-width in source free-text
|
|||||||
_b8b="$(printf '%b' '\x8b')"; _b8f="$(printf '%b' '\x8f')"
|
_b8b="$(printf '%b' '\x8b')"; _b8f="$(printf '%b' '\x8f')"
|
||||||
_baa="$(printf '%b' '\xaa')"; _bae="$(printf '%b' '\xae')"
|
_baa="$(printf '%b' '\xaa')"; _bae="$(printf '%b' '\xae')"
|
||||||
_bbom="$(printf '%b' '\xef\xbb\xbf')"
|
_bbom="$(printf '%b' '\xef\xbb\xbf')"
|
||||||
printf '%s' "$out" | LC_ALL=C has_match -qE "${_b280}[${_b8b}-${_b8f}${_baa}-${_bae}]|${_bbom}" &&
|
printf '%s' "$out" | LC_ALL=C grep -qE "${_b280}[${_b8b}-${_b8f}${_baa}-${_bae}]|${_bbom}" &&
|
||||||
fail_msg "D4: bidi/zero-width/BOM survived the scrub"
|
fail_msg "D4: bidi/zero-width/BOM survived the scrub"
|
||||||
# C0 control bytes (except tab/newline) must be gone.
|
# C0 control bytes (except tab/newline) must be gone.
|
||||||
_c00="$(printf '%b' '\x01')"; _c08="$(printf '%b' '\x08')"
|
_c00="$(printf '%b' '\x01')"; _c08="$(printf '%b' '\x08')"
|
||||||
_c0e="$(printf '%b' '\x0e')"; _c1f="$(printf '%b' '\x1f')"; _c7f="$(printf '%b' '\x7f')"
|
_c0e="$(printf '%b' '\x0e')"; _c1f="$(printf '%b' '\x1f')"; _c7f="$(printf '%b' '\x7f')"
|
||||||
printf '%s' "$out" | LC_ALL=C has_match -qE "[${_c00}-${_c08}${_c0e}-${_c1f}${_c7f}]" &&
|
printf '%s' "$out" | LC_ALL=C grep -qE "[${_c00}-${_c08}${_c0e}-${_c1f}${_c7f}]" &&
|
||||||
fail_msg "D4: a C0 control byte survived the scrub"
|
fail_msg "D4: a C0 control byte survived the scrub"
|
||||||
# Secret canaries must be redacted, never inlined.
|
# Secret canaries must be redacted, never inlined.
|
||||||
printf '%s' "$out" | has_match -q 'ghp_0123456789' && fail_msg "D4: GitHub-token canary LEAKED into the digest"
|
printf '%s' "$out" | grep -q 'ghp_0123456789' && fail_msg "D4: GitHub-token canary LEAKED into the digest"
|
||||||
printf '%s' "$out" | has_match -q 'AKIAIOSFODNN7EXAMPLE' && fail_msg "D4: AWS-key canary LEAKED into the digest"
|
printf '%s' "$out" | grep -q 'AKIAIOSFODNN7EXAMPLE' && fail_msg "D4: AWS-key canary LEAKED into the digest"
|
||||||
printf '%s' "$out" | has_match -q 'REDACTED-SECRET' || fail_msg "D4: secret redaction marker absent — canary may not have been scrubbed"
|
printf '%s' "$out" | grep -q 'REDACTED-SECRET' || fail_msg "D4: secret redaction marker absent — canary may not have been scrubbed"
|
||||||
# Free-text is quoted inside a DELIMITED untrusted block, framed as NOT instructions.
|
# Free-text is quoted inside a DELIMITED untrusted block, framed as NOT instructions.
|
||||||
printf '%s' "$out" | has_match -q 'BEGIN UNTRUSTED DATA' || fail_msg "D4: source free-text not placed in a delimited untrusted block"
|
printf '%s' "$out" | grep -q 'BEGIN UNTRUSTED DATA' || fail_msg "D4: source free-text not placed in a delimited untrusted block"
|
||||||
# The 40-hex git SHA locator must NOT be mangled by the secret scrubber.
|
# The 40-hex git SHA locator must NOT be mangled by the secret scrubber.
|
||||||
printf '%s' "$out" | has_match -q "$SHA40" || fail_msg "D4: legitimate 40-hex SHA locator was wrongly scrubbed"
|
printf '%s' "$out" | grep -q "$SHA40" || fail_msg "D4: legitimate 40-hex SHA locator was wrongly scrubbed"
|
||||||
true
|
true
|
||||||
) && ok
|
) && ok
|
||||||
|
|
||||||
@@ -330,11 +323,11 @@ echo "== H2: KEY BY-NAME, never inline; no key leak; same-uid boundary documente
|
|||||||
fail_msg "H2: by-name key resolution failed"
|
fail_msg "H2: by-name key resolution failed"
|
||||||
echo "$env1" | jq -e .wake_mac >/dev/null || fail_msg "H2: no MAC produced from by-name key"
|
echo "$env1" | jq -e .wake_mac >/dev/null || fail_msg "H2: no MAC produced from by-name key"
|
||||||
# The key VALUE must never appear in the signed output.
|
# The key VALUE must never appear in the signed output.
|
||||||
echo "$env1" | has_match -q 'SUPERSECRET-KEYVALUE-XYZ' && fail_msg "H2: key material LEAKED into the signed envelope"
|
echo "$env1" | grep -q 'SUPERSECRET-KEYVALUE-XYZ' && fail_msg "H2: key material LEAKED into the signed envelope"
|
||||||
# A signed store-entry (hmac placeholder filled) must not leak the key either.
|
# A signed store-entry (hmac placeholder filled) must not leak the key either.
|
||||||
entry="$(printf '{"observed_seq":1,"locators":{"repo":"r","issue":1},"class":"actionable","emit_ts":1700000000,"hmac":""}' |
|
entry="$(printf '{"observed_seq":1,"locators":{"repo":"r","issue":1},"class":"actionable","emit_ts":1700000000,"hmac":""}' |
|
||||||
WAKE_HMAC_KEY_NAME=signing-key "$SIGN" sign-entry)"
|
WAKE_HMAC_KEY_NAME=signing-key "$SIGN" sign-entry)"
|
||||||
echo "$entry" | has_match -q 'SUPERSECRET-KEYVALUE-XYZ' && fail_msg "H2: key material LEAKED into the signed entry"
|
echo "$entry" | grep -q 'SUPERSECRET-KEYVALUE-XYZ' && fail_msg "H2: key material LEAKED into the signed entry"
|
||||||
echo "$entry" | jq -e '.hmac != "" and .hmac != null' >/dev/null || fail_msg "H2: sign-entry did not fill the hmac placeholder"
|
echo "$entry" | jq -e '.hmac != "" and .hmac != null' >/dev/null || fail_msg "H2: sign-entry did not fill the hmac placeholder"
|
||||||
# No flag may accept key MATERIAL inline — only a key NAME. An attempt to pass
|
# No flag may accept key MATERIAL inline — only a key NAME. An attempt to pass
|
||||||
# a literal key must be rejected as an unknown option (never silently honored).
|
# a literal key must be rejected as an unknown option (never silently honored).
|
||||||
@@ -348,8 +341,8 @@ echo "== H2: KEY BY-NAME, never inline; no key leak; same-uid boundary documente
|
|||||||
fail_msg "H2: signing with an unresolvable key name must FAIL LOUD"
|
fail_msg "H2: signing with an unresolvable key name must FAIL LOUD"
|
||||||
fi
|
fi
|
||||||
# The same-uid threat boundary + off-uid follow-up must be DOCUMENTED in the tool.
|
# The same-uid threat boundary + off-uid follow-up must be DOCUMENTED in the tool.
|
||||||
has_match -qi 'same-uid' "$SIGN" || fail_msg "H2: same-uid threat boundary not documented in sign.sh"
|
grep -qi 'same-uid' "$SIGN" || fail_msg "H2: same-uid threat boundary not documented in sign.sh"
|
||||||
has_match -qi 'off-uid' "$SIGN" || fail_msg "H2: off-uid future signer not named in sign.sh"
|
grep -qi 'off-uid' "$SIGN" || fail_msg "H2: off-uid future signer not named in sign.sh"
|
||||||
) && ok
|
) && ok
|
||||||
|
|
||||||
echo "== D5 (#914a): EMBEDDED ACK NAMESPACE — env-less copy-run targets the RENDER-TIME agent, not 'default' =="
|
echo "== D5 (#914a): EMBEDDED ACK NAMESPACE — env-less copy-run targets the RENDER-TIME agent, not 'default' =="
|
||||||
@@ -362,7 +355,7 @@ echo "== D5 (#914a): EMBEDDED ACK NAMESPACE — env-less copy-run targets the RE
|
|||||||
out="$(WAKE_AGENT=someagent "$DIGEST" render)" || fail_msg "D5: render (WAKE_AGENT=someagent) failed"
|
out="$(WAKE_AGENT=someagent "$DIGEST" render)" || fail_msg "D5: render (WAKE_AGENT=someagent) failed"
|
||||||
ack_line="$(printf '%s\n' "$out" | awk '/^-- ACK/{f=1;next} f && NF {print; exit}')"
|
ack_line="$(printf '%s\n' "$out" | awk '/^-- ACK/{f=1;next} f && NF {print; exit}')"
|
||||||
[ -n "$ack_line" ] || fail_msg "D5: no embedded ack line extracted from the digest"
|
[ -n "$ack_line" ] || fail_msg "D5: no embedded ack line extracted from the digest"
|
||||||
printf '%s' "$ack_line" | has_match -q 'WAKE_AGENT=someagent' ||
|
printf '%s' "$ack_line" | grep -q 'WAKE_AGENT=someagent' ||
|
||||||
fail_msg "D5: embedded ack line has no explicit WAKE_AGENT=someagent prefix — an env-less copy-run silently resolves to 'default' [$ack_line]"
|
fail_msg "D5: embedded ack line has no explicit WAKE_AGENT=someagent prefix — an env-less copy-run silently resolves to 'default' [$ack_line]"
|
||||||
# Actually RUN it with NO WAKE_AGENT in the environment (the real failure
|
# Actually RUN it with NO WAKE_AGENT in the environment (the real failure
|
||||||
# mode: a consumer copy-pastes the line into a fresh shell).
|
# mode: a consumer copy-pastes the line into a fresh shell).
|
||||||
@@ -408,12 +401,12 @@ echo "== D6 (#914b): DIGEST-CLASS LOCATOR THREADING — ORIENTATION pointer carr
|
|||||||
loc="$(jq -cn '{kind:"repo", id:"r1", observed_hash:"deadbeefcafe0123456789abcdef0123456789abcdef0123456789abcdef01", remote:"example/repo"}')"
|
loc="$(jq -cn '{kind:"repo", id:"r1", observed_hash:"deadbeefcafe0123456789abcdef0123456789abcdef0123456789abcdef01", remote:"example/repo"}')"
|
||||||
"$STORE" enqueue --seq 1 --class digest --locators "$loc" >/dev/null
|
"$STORE" enqueue --seq 1 --class digest --locators "$loc" >/dev/null
|
||||||
out="$("$DIGEST" render)" || fail_msg "D6: render failed"
|
out="$("$DIGEST" render)" || fail_msg "D6: render failed"
|
||||||
orientline="$(printf '%s\n' "$out" | has_match -E '^\s*\* seq 1 \[digest\]')"
|
orientline="$(printf '%s\n' "$out" | grep -E '^\s*\* seq 1 \[digest\]')"
|
||||||
[ -n "$orientline" ] || fail_msg "D6: no ORIENTATION line found for seq 1"
|
[ -n "$orientline" ] || fail_msg "D6: no ORIENTATION line found for seq 1"
|
||||||
printf '%s' "$orientline" | has_match -qE 'locator: *$' &&
|
printf '%s' "$orientline" | grep -qE 'locator: *$' &&
|
||||||
fail_msg "D6: digest-class ORIENTATION pointer rendered an EMPTY locator despite a populated .locators field [$orientline]"
|
fail_msg "D6: digest-class ORIENTATION pointer rendered an EMPTY locator despite a populated .locators field [$orientline]"
|
||||||
# Must surface something a consumer can act on to re-verify.
|
# Must surface something a consumer can act on to re-verify.
|
||||||
printf '%s' "$orientline" | has_match -qE 'remote=example/repo|id=r1|kind=repo' ||
|
printf '%s' "$orientline" | grep -qE 'remote=example/repo|id=r1|kind=repo' ||
|
||||||
fail_msg "D6: digest-class ORIENTATION pointer does not carry a usable locator [$orientline]"
|
fail_msg "D6: digest-class ORIENTATION pointer does not carry a usable locator [$orientline]"
|
||||||
|
|
||||||
# --- ACTIONABLE-tier hard-locator FAIL-LOUD must be PRESERVED (now PER-ENTRY
|
# --- ACTIONABLE-tier hard-locator FAIL-LOUD must be PRESERVED (now PER-ENTRY
|
||||||
@@ -428,14 +421,14 @@ echo "== D6 (#914b): DIGEST-CLASS LOCATOR THREADING — ORIENTATION pointer carr
|
|||||||
rc=0
|
rc=0
|
||||||
out="$("$DIGEST" render 2>"$err")" || rc=$?
|
out="$("$DIGEST" render 2>"$err")" || rc=$?
|
||||||
[ "$rc" -eq 0 ] || fail_msg "D6: a malformed actionable is now per-entry quarantined (render exit 0, #920), got $rc"
|
[ "$rc" -eq 0 ] || fail_msg "D6: a malformed actionable is now per-entry quarantined (render exit 0, #920), got $rc"
|
||||||
printf '%s' "$out" | has_match -q 'mergeable=true' && fail_msg "D6: a malformed actionable claim must NOT be delivered as valid (fail-loud preserved)"
|
printf '%s' "$out" | grep -q 'mergeable=true' && fail_msg "D6: a malformed actionable claim must NOT be delivered as valid (fail-loud preserved)"
|
||||||
has_match -q 'mergeable=true' "$h/default/dead-letter.jsonl" 2>/dev/null || fail_msg "D6: a malformed actionable must be DEAD-LETTERED (fail-loud preserved, per-entry)"
|
grep -q 'mergeable=true' "$h/default/dead-letter.jsonl" 2>/dev/null || fail_msg "D6: a malformed actionable must be DEAD-LETTERED (fail-loud preserved, per-entry)"
|
||||||
has_match -qi 'QUARANTINE' "$err" || fail_msg "D6: a malformed actionable must raise a loud per-entry alarm"
|
grep -qi 'QUARANTINE' "$err" || fail_msg "D6: a malformed actionable must raise a loud per-entry alarm"
|
||||||
) && ok
|
) && ok
|
||||||
|
|
||||||
echo
|
echo
|
||||||
if [ -s "$FAILFILE" ]; then
|
if [ -s "$FAILFILE" ]; then
|
||||||
echo "wake digest/hmac harness: FAILED ($(count_lines . "$FAILFILE") assertion(s))" >&2
|
echo "wake digest/hmac harness: FAILED ($(grep -c . "$FAILFILE") assertion(s))" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
echo "wake digest/hmac harness: all invariants passed ($pass groups)"
|
echo "wake digest/hmac harness: all invariants passed ($pass groups)"
|
||||||
|
|||||||
@@ -117,13 +117,6 @@
|
|||||||
set -uo pipefail
|
set -uo pipefail
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
# #973: three-valued grep assertion helpers (has_match/count_lines); init saves real stderr for abort loudness.
|
|
||||||
# shellcheck disable=SC1091
|
|
||||||
if ! . "$SCRIPT_DIR/_wake-common.sh"; then
|
|
||||||
echo "WAKE-ASSERT INIT ABORT: cannot source _wake-common.sh — suite ran ZERO wake assertions (#984)" >&2
|
|
||||||
exit 97
|
|
||||||
fi
|
|
||||||
wake_assert_init
|
|
||||||
DIGEST="$SCRIPT_DIR/digest.sh"
|
DIGEST="$SCRIPT_DIR/digest.sh"
|
||||||
|
|
||||||
command -v jq >/dev/null 2>&1 || {
|
command -v jq >/dev/null 2>&1 || {
|
||||||
@@ -183,12 +176,12 @@ echo "== Q1 (a): malformed address-free {kind,id,observed_hash} QUARANTINES; cle
|
|||||||
out="$("$DIGEST" render --from-file "$f" --agent default 2>"$err")"
|
out="$("$DIGEST" render --from-file "$f" --agent default 2>"$err")"
|
||||||
rc=$?
|
rc=$?
|
||||||
[ "$rc" -eq 0 ] || fail_msg "Q1: render must EXIT 0 (per-entry quarantine, not whole-digest exit-4), got rc=$rc"
|
[ "$rc" -eq 0 ] || fail_msg "Q1: render must EXIT 0 (per-entry quarantine, not whole-digest exit-4), got rc=$rc"
|
||||||
printf '%s' "$out" | has_match -q "$SHA40" || fail_msg "Q1: the clean sibling (sha $SHA40) must STILL be delivered in the same drain [head-of-line block]"
|
printf '%s' "$out" | grep -q "$SHA40" || fail_msg "Q1: the clean sibling (sha $SHA40) must STILL be delivered in the same drain [head-of-line block]"
|
||||||
printf '%s' "$out" | has_match -q 'MALFORMED-Q' && fail_msg "Q1: the quarantined entry must be EXCLUDED from the rendered digest"
|
printf '%s' "$out" | grep -q 'MALFORMED-Q' && fail_msg "Q1: the quarantined entry must be EXCLUDED from the rendered digest"
|
||||||
[ -f "$(dlq "$home")" ] || fail_msg "Q1: a durable dead-letter file must be written"
|
[ -f "$(dlq "$home")" ] || fail_msg "Q1: a durable dead-letter file must be written"
|
||||||
has_match -q 'MALFORMED-Q' "$(dlq "$home")" 2>/dev/null || fail_msg "Q1: the malformed entry must be DEAD-LETTERED (accounted-for, not silently dropped)"
|
grep -q 'MALFORMED-Q' "$(dlq "$home")" 2>/dev/null || fail_msg "Q1: the malformed entry must be DEAD-LETTERED (accounted-for, not silently dropped)"
|
||||||
has_match -qi 'QUARANTINE' "$err" || fail_msg "Q1: a LOUD per-entry alarm must fire on stderr"
|
grep -qi 'QUARANTINE' "$err" || fail_msg "Q1: a LOUD per-entry alarm must fire on stderr"
|
||||||
has_match -q 'observed_seq=1' "$err" || fail_msg "Q1: the alarm must identify the offending entry (observed_seq=1)"
|
grep -q 'observed_seq=1' "$err" || fail_msg "Q1: the alarm must identify the offending entry (observed_seq=1)"
|
||||||
) && ok
|
) && ok
|
||||||
|
|
||||||
echo "== Q2 (b): reconciler enumeration (reconciled:true) renders ORIENTATION-tier, NO exit-4 =="
|
echo "== Q2 (b): reconciler enumeration (reconciled:true) renders ORIENTATION-tier, NO exit-4 =="
|
||||||
@@ -207,8 +200,8 @@ echo "== Q2 (b): reconciler enumeration (reconciled:true) renders ORIENTATION-ti
|
|||||||
out="$("$DIGEST" render --from-file "$f" --agent default 2>"$err")"
|
out="$("$DIGEST" render --from-file "$f" --agent default 2>"$err")"
|
||||||
rc=$?
|
rc=$?
|
||||||
[ "$rc" -eq 0 ] || fail_msg "Q2: a reconciler enumeration must NOT exit-4 (it is ORIENTATION-tier), got rc=$rc"
|
[ "$rc" -eq 0 ] || fail_msg "Q2: a reconciler enumeration must NOT exit-4 (it is ORIENTATION-tier), got rc=$rc"
|
||||||
printf '%s' "$out" | has_match -q 'id=ENUM-B' || fail_msg "Q2: the enumeration must render as an ORIENTATION pointer (id=ENUM-B via _locator_line)"
|
printf '%s' "$out" | grep -q 'id=ENUM-B' || fail_msg "Q2: the enumeration must render as an ORIENTATION pointer (id=ENUM-B via _locator_line)"
|
||||||
printf '%s' "$out" | has_match -q 'CLAIM@seq' && fail_msg "Q2: an enumeration must NOT render as an ACTIONABLE CLAIM@seq"
|
printf '%s' "$out" | grep -q 'CLAIM@seq' && fail_msg "Q2: an enumeration must NOT render as an ACTIONABLE CLAIM@seq"
|
||||||
[ -s "$(dlq "$home")" ] && fail_msg "Q2: an ORIENTATION-tier enumeration must NOT be quarantined/dead-lettered"
|
[ -s "$(dlq "$home")" ] && fail_msg "Q2: an ORIENTATION-tier enumeration must NOT be quarantined/dead-lettered"
|
||||||
true
|
true
|
||||||
) && ok
|
) && ok
|
||||||
@@ -227,10 +220,10 @@ echo "== Q3 (c): TWO DISTINCT enumerations BOTH survive as SEPARATE orientation
|
|||||||
out="$("$DIGEST" render --from-file "$f" --agent default 2>"$err")"
|
out="$("$DIGEST" render --from-file "$f" --agent default 2>"$err")"
|
||||||
rc=$?
|
rc=$?
|
||||||
[ "$rc" -eq 0 ] || fail_msg "Q3: two enumerations must render (exit 0), got rc=$rc"
|
[ "$rc" -eq 0 ] || fail_msg "Q3: two enumerations must render (exit 0), got rc=$rc"
|
||||||
n="$(printf '%s\n' "$out" | count_lines 'id=ENUM-C[12]' || true)"
|
n="$(printf '%s\n' "$out" | grep -c 'id=ENUM-C[12]' || true)"
|
||||||
[ "$n" = "2" ] || fail_msg "Q3: BOTH distinct enumerations must survive as SEPARATE orientation pointers (expected 2, got $n) — neither coalesced away"
|
[ "$n" = "2" ] || fail_msg "Q3: BOTH distinct enumerations must survive as SEPARATE orientation pointers (expected 2, got $n) — neither coalesced away"
|
||||||
printf '%s' "$out" | has_match -q 'id=ENUM-C1' || fail_msg "Q3: enumeration ENUM-C1 must be present"
|
printf '%s' "$out" | grep -q 'id=ENUM-C1' || fail_msg "Q3: enumeration ENUM-C1 must be present"
|
||||||
printf '%s' "$out" | has_match -q 'id=ENUM-C2' || fail_msg "Q3: enumeration ENUM-C2 must be present"
|
printf '%s' "$out" | grep -q 'id=ENUM-C2' || fail_msg "Q3: enumeration ENUM-C2 must be present"
|
||||||
[ -s "$(dlq "$home")" ] && fail_msg "Q3: enumerations must NOT be quarantined"
|
[ -s "$(dlq "$home")" ] && fail_msg "Q3: enumerations must NOT be quarantined"
|
||||||
true
|
true
|
||||||
) && ok
|
) && ok
|
||||||
@@ -246,10 +239,10 @@ echo "== Q4: PRESERVED — a genuine malformed ACTIONABLE claim is STILL loud (d
|
|||||||
out="$("$DIGEST" render --from-file "$f" --agent default 2>"$err")"
|
out="$("$DIGEST" render --from-file "$f" --agent default 2>"$err")"
|
||||||
rc=$?
|
rc=$?
|
||||||
[ "$rc" -eq 0 ] || fail_msg "Q4: render must exit 0 (per-entry quarantine), got rc=$rc"
|
[ "$rc" -eq 0 ] || fail_msg "Q4: render must exit 0 (per-entry quarantine), got rc=$rc"
|
||||||
printf '%s' "$out" | has_match -q 'CLAIM-KEEP' && fail_msg "Q4: a malformed actionable must NOT be delivered as if valid"
|
printf '%s' "$out" | grep -q 'CLAIM-KEEP' && fail_msg "Q4: a malformed actionable must NOT be delivered as if valid"
|
||||||
printf '%s' "$out" | has_match -q '(none) — no consequential claims pending' || fail_msg "Q4: with the only claim quarantined, the ACTIONABLE section must show (none)"
|
printf '%s' "$out" | grep -q '(none) — no consequential claims pending' || fail_msg "Q4: with the only claim quarantined, the ACTIONABLE section must show (none)"
|
||||||
has_match -q 'CLAIM-KEEP' "$(dlq "$home")" 2>/dev/null || fail_msg "Q4: the malformed actionable must be DEAD-LETTERED (loudly surfaced, not silent)"
|
grep -q 'CLAIM-KEEP' "$(dlq "$home")" 2>/dev/null || fail_msg "Q4: the malformed actionable must be DEAD-LETTERED (loudly surfaced, not silent)"
|
||||||
has_match -qi 'QUARANTINE' "$err" || fail_msg "Q4: the malformed actionable must raise a LOUD alarm"
|
grep -qi 'QUARANTINE' "$err" || fail_msg "Q4: the malformed actionable must raise a LOUD alarm"
|
||||||
) && ok
|
) && ok
|
||||||
|
|
||||||
echo "== Q5: claim-precedence gated — a non-actionable-class entry carrying a claim (no hard locator) is STILL quarantined =="
|
echo "== Q5: claim-precedence gated — a non-actionable-class entry carrying a claim (no hard locator) is STILL quarantined =="
|
||||||
@@ -265,8 +258,8 @@ echo "== Q5: claim-precedence gated — a non-actionable-class entry carrying a
|
|||||||
out="$("$DIGEST" render --from-file "$f" --agent default 2>"$err")"
|
out="$("$DIGEST" render --from-file "$f" --agent default 2>"$err")"
|
||||||
rc=$?
|
rc=$?
|
||||||
[ "$rc" -eq 0 ] || fail_msg "Q5: render must exit 0, got rc=$rc"
|
[ "$rc" -eq 0 ] || fail_msg "Q5: render must exit 0, got rc=$rc"
|
||||||
has_match -q 'CLAIMY' "$(dlq "$home")" 2>/dev/null || fail_msg "Q5: a claim-carrying entry with no hard locator must be quarantined (claim precedence, §2.1)"
|
grep -q 'CLAIMY' "$(dlq "$home")" 2>/dev/null || fail_msg "Q5: a claim-carrying entry with no hard locator must be quarantined (claim precedence, §2.1)"
|
||||||
printf '%s' "$out" | has_match -q 'CI is green' && fail_msg "Q5: the un-verifiable claim must NOT be delivered"
|
printf '%s' "$out" | grep -q 'CI is green' && fail_msg "Q5: the un-verifiable claim must NOT be delivered"
|
||||||
true
|
true
|
||||||
) && ok
|
) && ok
|
||||||
|
|
||||||
@@ -285,11 +278,11 @@ echo "== Q6 (a): dead-lettered entry routes EXACTLY ONE off-host alarm (payload
|
|||||||
out="$("$DIGEST" render --from-file "$f" --agent default 2>"$err")"
|
out="$("$DIGEST" render --from-file "$f" --agent default 2>"$err")"
|
||||||
rc=$?
|
rc=$?
|
||||||
[ "$rc" -eq 0 ] || fail_msg "Q6: render must still EXIT 0 (per-entry quarantine, not whole-drain wedge), got rc=$rc"
|
[ "$rc" -eq 0 ] || fail_msg "Q6: render must still EXIT 0 (per-entry quarantine, not whole-drain wedge), got rc=$rc"
|
||||||
n="$(count_lines . "$ALARM_OUT" 2>/dev/null || true)"
|
n="$(grep -c . "$ALARM_OUT" 2>/dev/null || true)"
|
||||||
[ "$n" = "1" ] || fail_msg "Q6: EXACTLY ONE off-host alarm must route for the dead-lettered entry (got $n) [$(cat "$ALARM_OUT" 2>/dev/null)]"
|
[ "$n" = "1" ] || fail_msg "Q6: EXACTLY ONE off-host alarm must route for the dead-lettered entry (got $n) [$(cat "$ALARM_OUT" 2>/dev/null)]"
|
||||||
has_match -q '"observed_seq":21' "$ALARM_OUT" 2>/dev/null || fail_msg "Q6: the routed alarm payload must name the entry's observed_seq (21)"
|
grep -q '"observed_seq":21' "$ALARM_OUT" 2>/dev/null || fail_msg "Q6: the routed alarm payload must name the entry's observed_seq (21)"
|
||||||
has_match -qi 'QUARANTINE' "$err" || fail_msg "Q6: the existing #920 stderr diagnostic must STILL fire (local + off-host, not either/or)"
|
grep -qi 'QUARANTINE' "$err" || fail_msg "Q6: the existing #920 stderr diagnostic must STILL fire (local + off-host, not either/or)"
|
||||||
printf '%s' "$out" | has_match -q 'DLQ-Q6' && fail_msg "Q6: the quarantined entry must still be EXCLUDED from the rendered digest"
|
printf '%s' "$out" | grep -q 'DLQ-Q6' && fail_msg "Q6: the quarantined entry must still be EXCLUDED from the rendered digest"
|
||||||
true
|
true
|
||||||
) && ok
|
) && ok
|
||||||
|
|
||||||
@@ -307,9 +300,9 @@ echo "== Q7 (b): re-draining the SAME still-dead-lettered entry N times routes Z
|
|||||||
for i in 1 2 3 4; do
|
for i in 1 2 3 4; do
|
||||||
"$DIGEST" render --from-file "$f" --agent default >/dev/null 2>"$TMP_ROOT/q7.err.$i"
|
"$DIGEST" render --from-file "$f" --agent default >/dev/null 2>"$TMP_ROOT/q7.err.$i"
|
||||||
done
|
done
|
||||||
n="$(count_lines . "$ALARM_OUT" 2>/dev/null || true)"
|
n="$(grep -c . "$ALARM_OUT" 2>/dev/null || true)"
|
||||||
[ "$n" = "1" ] || fail_msg "Q7: re-draining the SAME dead-lettered entry 4x must route ONLY ONE off-host alarm total (durable dedup by observed_seq); got $n [$(cat "$ALARM_OUT" 2>/dev/null)]"
|
[ "$n" = "1" ] || fail_msg "Q7: re-draining the SAME dead-lettered entry 4x must route ONLY ONE off-host alarm total (durable dedup by observed_seq); got $n [$(cat "$ALARM_OUT" 2>/dev/null)]"
|
||||||
has_match -qi 'QUARANTINE' "$TMP_ROOT/q7.err.4" || fail_msg "Q7: the #920 stderr diagnostic must STILL fire on every re-drain (only the off-host route is deduped)"
|
grep -qi 'QUARANTINE' "$TMP_ROOT/q7.err.4" || fail_msg "Q7: the #920 stderr diagnostic must STILL fire on every re-drain (only the off-host route is deduped)"
|
||||||
) && ok
|
) && ok
|
||||||
|
|
||||||
echo "== Q8 (c): a NEW distinct dead-lettered entry routes its OWN one alarm (dedup is per-entry, not a global latch) =="
|
echo "== Q8 (c): a NEW distinct dead-lettered entry routes its OWN one alarm (dedup is per-entry, not a global latch) =="
|
||||||
@@ -328,10 +321,10 @@ echo "== Q8 (c): a NEW distinct dead-lettered entry routes its OWN one alarm (de
|
|||||||
"$DIGEST" render --from-file "$f1" --agent default >/dev/null 2>/dev/null
|
"$DIGEST" render --from-file "$f1" --agent default >/dev/null 2>/dev/null
|
||||||
"$DIGEST" render --from-file "$f1" --agent default >/dev/null 2>/dev/null # re-drain seq 31 -> must NOT re-alarm
|
"$DIGEST" render --from-file "$f1" --agent default >/dev/null 2>/dev/null # re-drain seq 31 -> must NOT re-alarm
|
||||||
"$DIGEST" render --from-file "$f2" --agent default >/dev/null 2>/dev/null # NEW distinct seq 32 -> its own alarm
|
"$DIGEST" render --from-file "$f2" --agent default >/dev/null 2>/dev/null # NEW distinct seq 32 -> its own alarm
|
||||||
n="$(count_lines . "$ALARM_OUT" 2>/dev/null || true)"
|
n="$(grep -c . "$ALARM_OUT" 2>/dev/null || true)"
|
||||||
[ "$n" = "2" ] || fail_msg "Q8: two DISTINCT dead-lettered entries must together route exactly 2 off-host alarms total (got $n) [$(cat "$ALARM_OUT" 2>/dev/null)]"
|
[ "$n" = "2" ] || fail_msg "Q8: two DISTINCT dead-lettered entries must together route exactly 2 off-host alarms total (got $n) [$(cat "$ALARM_OUT" 2>/dev/null)]"
|
||||||
has_match -q '"observed_seq":31' "$ALARM_OUT" 2>/dev/null || fail_msg "Q8: seq 31's alarm must be present"
|
grep -q '"observed_seq":31' "$ALARM_OUT" 2>/dev/null || fail_msg "Q8: seq 31's alarm must be present"
|
||||||
has_match -q '"observed_seq":32' "$ALARM_OUT" 2>/dev/null || fail_msg "Q8: seq 32's (the new distinct entry's) OWN alarm must be present"
|
grep -q '"observed_seq":32' "$ALARM_OUT" 2>/dev/null || fail_msg "Q8: seq 32's (the new distinct entry's) OWN alarm must be present"
|
||||||
) && ok
|
) && ok
|
||||||
|
|
||||||
echo "== Q9 (d): WAKE_ALARM_SINK_CMD unconfigured OR unreachable -> FAIL LOUD (never silent no-alarm); per-entry, render still exits 0 =="
|
echo "== Q9 (d): WAKE_ALARM_SINK_CMD unconfigured OR unreachable -> FAIL LOUD (never silent no-alarm); per-entry, render still exits 0 =="
|
||||||
@@ -347,9 +340,9 @@ echo "== Q9 (d): WAKE_ALARM_SINK_CMD unconfigured OR unreachable -> FAIL LOUD (n
|
|||||||
out_a="$("$DIGEST" render --from-file "$f" --agent default 2>"$err_a")"
|
out_a="$("$DIGEST" render --from-file "$f" --agent default 2>"$err_a")"
|
||||||
rc_a=$?
|
rc_a=$?
|
||||||
[ "$rc_a" -eq 0 ] || fail_msg "Q9a: per-entry quarantine must still exit 0 even when the off-host alarm sink is unconfigured (no whole-drain wedge), got rc=$rc_a"
|
[ "$rc_a" -eq 0 ] || fail_msg "Q9a: per-entry quarantine must still exit 0 even when the off-host alarm sink is unconfigured (no whole-drain wedge), got rc=$rc_a"
|
||||||
has_match -qi 'FAIL LOUD' "$err_a" || fail_msg "Q9a: an unconfigured off-host alarm target must FAIL LOUD on stderr [$(cat "$err_a")]"
|
grep -qi 'FAIL LOUD' "$err_a" || fail_msg "Q9a: an unconfigured off-host alarm target must FAIL LOUD on stderr [$(cat "$err_a")]"
|
||||||
has_match -Eqi 'silent no-alarm|silent-miss|PERMANENTLY miss|permanent silent miss' "$err_a" || fail_msg "Q9a: the diagnostic must name the silent-miss hazard (G2a), mirroring beacon.sh's fail-closed wording [$(cat "$err_a")]"
|
grep -Eqi 'silent no-alarm|silent-miss|PERMANENTLY miss|permanent silent miss' "$err_a" || fail_msg "Q9a: the diagnostic must name the silent-miss hazard (G2a), mirroring beacon.sh's fail-closed wording [$(cat "$err_a")]"
|
||||||
printf '%s' "$out_a" | has_match -q 'DLQ-Q9' && fail_msg "Q9a: the quarantined entry must still be EXCLUDED from the rendered digest"
|
printf '%s' "$out_a" | grep -q 'DLQ-Q9' && fail_msg "Q9a: the quarantined entry must still be EXCLUDED from the rendered digest"
|
||||||
true
|
true
|
||||||
) && ok
|
) && ok
|
||||||
(
|
(
|
||||||
@@ -362,9 +355,9 @@ echo "== Q9 (d): WAKE_ALARM_SINK_CMD unconfigured OR unreachable -> FAIL LOUD (n
|
|||||||
out_b="$("$DIGEST" render --from-file "$f" --agent default 2>"$err_b")"
|
out_b="$("$DIGEST" render --from-file "$f" --agent default 2>"$err_b")"
|
||||||
rc_b=$?
|
rc_b=$?
|
||||||
[ "$rc_b" -eq 0 ] || fail_msg "Q9b: per-entry quarantine must still exit 0 even when the off-host alarm sink is unreachable, got rc=$rc_b"
|
[ "$rc_b" -eq 0 ] || fail_msg "Q9b: per-entry quarantine must still exit 0 even when the off-host alarm sink is unreachable, got rc=$rc_b"
|
||||||
has_match -qi 'FAIL LOUD' "$err_b" || fail_msg "Q9b: an unreachable off-host alarm target must FAIL LOUD on stderr [$(cat "$err_b")]"
|
grep -qi 'FAIL LOUD' "$err_b" || fail_msg "Q9b: an unreachable off-host alarm target must FAIL LOUD on stderr [$(cat "$err_b")]"
|
||||||
has_match -qi 'UNREACHABLE' "$err_b" || fail_msg "Q9b: the diagnostic must name the unreachable target [$(cat "$err_b")]"
|
grep -qi 'UNREACHABLE' "$err_b" || fail_msg "Q9b: the diagnostic must name the unreachable target [$(cat "$err_b")]"
|
||||||
printf '%s' "$out_b" | has_match -q 'DLQ-Q9' && fail_msg "Q9b: the quarantined entry must still be EXCLUDED from the rendered digest"
|
printf '%s' "$out_b" | grep -q 'DLQ-Q9' && fail_msg "Q9b: the quarantined entry must still be EXCLUDED from the rendered digest"
|
||||||
true
|
true
|
||||||
) && ok
|
) && ok
|
||||||
|
|
||||||
@@ -390,16 +383,16 @@ echo "== Q10: snapshot metadata (#940) — snapshot_sha/snapshot_ts render on th
|
|||||||
out="$("$DIGEST" render --from-file "$f" --agent default 2>/dev/null)"
|
out="$("$DIGEST" render --from-file "$f" --agent default 2>/dev/null)"
|
||||||
rc=$?
|
rc=$?
|
||||||
[ "$rc" -eq 0 ] || fail_msg "Q10: render must succeed (rc=$rc)"
|
[ "$rc" -eq 0 ] || fail_msg "Q10: render must succeed (rc=$rc)"
|
||||||
snapa_line="$(printf '%s\n' "$out" | has_match 'id=SNAP-A' || true)"
|
snapa_line="$(printf '%s\n' "$out" | grep 'id=SNAP-A' || true)"
|
||||||
printf '%s' "$snapa_line" | has_match -q 'snapshot_sha=0123abc4567890def0123abc4567890def012345' \
|
printf '%s' "$snapa_line" | grep -q 'snapshot_sha=0123abc4567890def0123abc4567890def012345' \
|
||||||
|| fail_msg "Q10: snapshot_sha must render on the ORIENTATION pointer line [$snapa_line]"
|
|| fail_msg "Q10: snapshot_sha must render on the ORIENTATION pointer line [$snapa_line]"
|
||||||
printf '%s' "$snapa_line" | has_match -q 'snapshot_ts=1753850000' \
|
printf '%s' "$snapa_line" | grep -q 'snapshot_ts=1753850000' \
|
||||||
|| fail_msg "Q10: snapshot_ts must render on the ORIENTATION pointer line (age = emit_ts - snapshot_ts, local arithmetic) [$snapa_line]"
|
|| fail_msg "Q10: snapshot_ts must render on the ORIENTATION pointer line (age = emit_ts - snapshot_ts, local arithmetic) [$snapa_line]"
|
||||||
printf '%s' "$out" | has_match -q 'git show 0123abc4567890def0123abc4567890def012345:BOARD.md' \
|
printf '%s' "$out" | grep -q 'git show 0123abc4567890def0123abc4567890def012345:BOARD.md' \
|
||||||
|| fail_msg "Q10: snapshot_sha+path must upgrade the actionable re-verify hint to a one-call git show"
|
|| fail_msg "Q10: snapshot_sha+path must upgrade the actionable re-verify hint to a one-call git show"
|
||||||
# The sibling without metadata must not grow empty snapshot_ fields.
|
# The sibling without metadata must not grow empty snapshot_ fields.
|
||||||
snapb_line="$(printf '%s\n' "$out" | has_match 'id=SNAP-B' || true)"
|
snapb_line="$(printf '%s\n' "$out" | grep 'id=SNAP-B' || true)"
|
||||||
printf '%s' "$snapb_line" | has_match -q 'snapshot_' \
|
printf '%s' "$snapb_line" | grep -q 'snapshot_' \
|
||||||
&& fail_msg "Q10: an entry without metadata must render NO snapshot_ fields [$snapb_line]"
|
&& fail_msg "Q10: an entry without metadata must render NO snapshot_ fields [$snapb_line]"
|
||||||
true
|
true
|
||||||
) && ok
|
) && ok
|
||||||
@@ -441,28 +434,28 @@ echo "== Q11 (#944): REAL detector-shape actionable RENDERS as CLAIM@seq; addres
|
|||||||
[ "$rc" -eq 0 ] || fail_msg "Q11: render must exit 0, got rc=$rc"
|
[ "$rc" -eq 0 ] || fail_msg "Q11: render must exit 0, got rc=$rc"
|
||||||
# (a) POSITIVE: the live entry RENDERS as an actionable claim (not merely
|
# (a) POSITIVE: the live entry RENDERS as an actionable claim (not merely
|
||||||
# passes the predicate) with the one-call git-show re-verify hint.
|
# passes the predicate) with the one-call git-show re-verify hint.
|
||||||
printf '%s' "$out" | has_match -q 'seq 68 — CLAIM@seq' || fail_msg "Q11a: the live seq-68 detector-shape entry must RENDER as CLAIM@seq (positive control)"
|
printf '%s' "$out" | grep -q 'seq 68 — CLAIM@seq' || fail_msg "Q11a: the live seq-68 detector-shape entry must RENDER as CLAIM@seq (positive control)"
|
||||||
printf '%s' "$out" | has_match -q 'git show 55d4909569d2b5fbccfec49ec9ca83db5049f3ce:docs/scratchpads/heartbeat-planning' \
|
printf '%s' "$out" | grep -q 'git show 55d4909569d2b5fbccfec49ec9ca83db5049f3ce:docs/scratchpads/heartbeat-planning' \
|
||||||
|| fail_msg "Q11a: the rendered claim must carry the one-call re-verify hint git show <snapshot_sha>:<path>"
|
|| fail_msg "Q11a: the rendered claim must carry the one-call re-verify hint git show <snapshot_sha>:<path>"
|
||||||
# (b) POSITIVE: path arm alone suffices; hint degrades to one-call re-read.
|
# (b) POSITIVE: path arm alone suffices; hint degrades to one-call re-read.
|
||||||
printf '%s' "$out" | has_match -q 'seq 69 — CLAIM@seq' || fail_msg "Q11b: a path-only detector-shape entry must RENDER as CLAIM@seq (path arm)"
|
printf '%s' "$out" | grep -q 'seq 69 — CLAIM@seq' || fail_msg "Q11b: a path-only detector-shape entry must RENDER as CLAIM@seq (path arm)"
|
||||||
printf '%s' "$out" | has_match -q 're-read BOARD.md' || fail_msg "Q11b: the path-only claim must carry the one-call re-read <path> hint"
|
printf '%s' "$out" | grep -q 're-read BOARD.md' || fail_msg "Q11b: the path-only claim must carry the one-call re-read <path> hint"
|
||||||
n_claims="$(printf '%s\n' "$out" | count_lines 'CLAIM@seq' || true)"
|
n_claims="$(printf '%s\n' "$out" | grep -c 'CLAIM@seq' || true)"
|
||||||
[ "$n_claims" = "2" ] || fail_msg "Q11: EXACTLY the two valid entries must render as CLAIM@seq (got $n_claims)"
|
[ "$n_claims" = "2" ] || fail_msg "Q11: EXACTLY the two valid entries must render as CLAIM@seq (got $n_claims)"
|
||||||
# (c)+(d) NEGATIVES retained: both quarantine, each with its OWN alarm.
|
# (c)+(d) NEGATIVES retained: both quarantine, each with its OWN alarm.
|
||||||
printf '%s' "$out" | has_match -q 'ADDR-FREE' && fail_msg "Q11c: the address-free entry must be EXCLUDED from the digest"
|
printf '%s' "$out" | grep -q 'ADDR-FREE' && fail_msg "Q11c: the address-free entry must be EXCLUDED from the digest"
|
||||||
printf '%s' "$out" | has_match -q 'SNAP-ONLY' && fail_msg "Q11d: no bare path-less snapshot_sha entry may appear in the digest (gate must not widen past board_file)"
|
printf '%s' "$out" | grep -q 'SNAP-ONLY' && fail_msg "Q11d: no bare path-less snapshot_sha entry may appear in the digest (gate must not widen past board_file)"
|
||||||
has_match -q 'ADDR-FREE' "$(dlq "$home")" 2>/dev/null || fail_msg "Q11c: the address-free entry must be DEAD-LETTERED"
|
grep -q 'ADDR-FREE' "$(dlq "$home")" 2>/dev/null || fail_msg "Q11c: the address-free entry must be DEAD-LETTERED"
|
||||||
for snap_id in SNAP-ONLY-40 SNAP-ONLY-7 SNAP-ONLY-64; do
|
for snap_id in SNAP-ONLY-40 SNAP-ONLY-7 SNAP-ONLY-64; do
|
||||||
has_match -q "$snap_id" "$(dlq "$home")" 2>/dev/null || fail_msg "Q11d: the bare snapshot_sha entry ($snap_id) must be DEAD-LETTERED"
|
grep -q "$snap_id" "$(dlq "$home")" 2>/dev/null || fail_msg "Q11d: the bare snapshot_sha entry ($snap_id) must be DEAD-LETTERED"
|
||||||
done
|
done
|
||||||
has_match -q 'heartbeat-planning' "$(dlq "$home")" 2>/dev/null && fail_msg "Q11a: the valid live entry must NOT be dead-lettered"
|
grep -q 'heartbeat-planning' "$(dlq "$home")" 2>/dev/null && fail_msg "Q11a: the valid live entry must NOT be dead-lettered"
|
||||||
has_match -q 'PILOT-LOCAL' "$(dlq "$home")" 2>/dev/null && fail_msg "Q11b: the valid path-only entry must NOT be dead-lettered"
|
grep -q 'PILOT-LOCAL' "$(dlq "$home")" 2>/dev/null && fail_msg "Q11b: the valid path-only entry must NOT be dead-lettered"
|
||||||
n_alarms="$(count_lines . "$ALARM_OUT" 2>/dev/null || true)"
|
n_alarms="$(grep -c . "$ALARM_OUT" 2>/dev/null || true)"
|
||||||
[ "$n_alarms" = "4" ] || fail_msg "Q11: exactly the four invalid entries must alarm (got $n_alarms) [$(cat "$ALARM_OUT" 2>/dev/null)]"
|
[ "$n_alarms" = "4" ] || fail_msg "Q11: exactly the four invalid entries must alarm (got $n_alarms) [$(cat "$ALARM_OUT" 2>/dev/null)]"
|
||||||
has_match -q '"observed_seq":70' "$ALARM_OUT" 2>/dev/null || fail_msg "Q11c: seq 70's own alarm must be present"
|
grep -q '"observed_seq":70' "$ALARM_OUT" 2>/dev/null || fail_msg "Q11c: seq 70's own alarm must be present"
|
||||||
for snap_seq in 71 72 73; do
|
for snap_seq in 71 72 73; do
|
||||||
has_match -q "\"observed_seq\":$snap_seq" "$ALARM_OUT" 2>/dev/null || fail_msg "Q11d: seq $snap_seq's own alarm must be present"
|
grep -q "\"observed_seq\":$snap_seq" "$ALARM_OUT" 2>/dev/null || fail_msg "Q11d: seq $snap_seq's own alarm must be present"
|
||||||
done
|
done
|
||||||
true
|
true
|
||||||
) && ok
|
) && ok
|
||||||
@@ -484,15 +477,15 @@ echo "== Q12 (#946): quarantined entries are DISCLOSED (by seq, content withheld
|
|||||||
[ "$rc" -eq 0 ] || fail_msg "Q12: render must exit 0, got rc=$rc"
|
[ "$rc" -eq 0 ] || fail_msg "Q12: render must exit 0, got rc=$rc"
|
||||||
# DISCLOSURE: a held entry must be VISIBLE in the digest it was held from —
|
# DISCLOSURE: a held entry must be VISIBLE in the digest it was held from —
|
||||||
# a silent hold is how five successive digests each stepped past seq 68...
|
# a silent hold is how five successive digests each stepped past seq 68...
|
||||||
printf '%s' "$out" | has_match -q 'QUARANTINED' || fail_msg "Q12: the digest must carry a QUARANTINED disclosure section (no silent hold)"
|
printf '%s' "$out" | grep -q 'QUARANTINED' || fail_msg "Q12: the digest must carry a QUARANTINED disclosure section (no silent hold)"
|
||||||
printf '%s' "$out" | has_match -q 'seq 2 .*HELD' || fail_msg "Q12: the disclosure must name the held seq (2) as HELD"
|
printf '%s' "$out" | grep -q 'seq 2 .*HELD' || fail_msg "Q12: the disclosure must name the held seq (2) as HELD"
|
||||||
# ...but WITHOUT re-injecting the refused content: disclosure is by seq only;
|
# ...but WITHOUT re-injecting the refused content: disclosure is by seq only;
|
||||||
# the Q1/Q4/Q5/Q6/Q9/Q11 exclusion property stands.
|
# the Q1/Q4/Q5/Q6/Q9/Q11 exclusion property stands.
|
||||||
printf '%s' "$out" | has_match -q 'ADDR-Q12' && fail_msg "Q12: the quarantined entry's content/locators must stay EXCLUDED from the digest"
|
printf '%s' "$out" | grep -q 'ADDR-Q12' && fail_msg "Q12: the quarantined entry's content/locators must stay EXCLUDED from the digest"
|
||||||
# CLAMP: the embedded ack stops BELOW the quarantined seq, and says so loudly.
|
# CLAMP: the embedded ack stops BELOW the quarantined seq, and says so loudly.
|
||||||
printf '%s' "$out" | has_match -Eq 'consumed --upto 1$' || fail_msg "Q12: the embedded ack must be CLAMPED to --upto 1 (below quarantined seq 2)"
|
printf '%s' "$out" | grep -Eq 'consumed --upto 1$' || fail_msg "Q12: the embedded ack must be CLAMPED to --upto 1 (below quarantined seq 2)"
|
||||||
printf '%s' "$out" | has_match -Eq 'consumed --upto 2( |$)' && fail_msg "Q12: the raw observed cursor (2) must NOT be embedded while seq 2 is quarantined"
|
printf '%s' "$out" | grep -Eq 'consumed --upto 2( |$)' && fail_msg "Q12: the raw observed cursor (2) must NOT be embedded while seq 2 is quarantined"
|
||||||
printf '%s' "$out" | has_match -q 'ACK CLAMPED' || fail_msg "Q12: the clamp must be LOUDLY disclosed in the ACK section"
|
printf '%s' "$out" | grep -q 'ACK CLAMPED' || fail_msg "Q12: the clamp must be LOUDLY disclosed in the ACK section"
|
||||||
# A foreign-data render must NOT rewrite the lane's quarantine truth.
|
# A foreign-data render must NOT rewrite the lane's quarantine truth.
|
||||||
[ -e "$home/default/quarantined.set" ] && fail_msg "Q12: a --from-file render must NOT write the store's quarantined.set (lane truth is store-mode only)"
|
[ -e "$home/default/quarantined.set" ] && fail_msg "Q12: a --from-file render must NOT write the store's quarantined.set (lane truth is store-mode only)"
|
||||||
true
|
true
|
||||||
@@ -510,9 +503,9 @@ echo "== Q13 (#946): nothing quarantined -> ack UNCLAMPED at the observed cursor
|
|||||||
out="$("$DIGEST" render --from-file "$f" --agent default 2>/dev/null)"
|
out="$("$DIGEST" render --from-file "$f" --agent default 2>/dev/null)"
|
||||||
rc=$?
|
rc=$?
|
||||||
[ "$rc" -eq 0 ] || fail_msg "Q13: render must exit 0, got rc=$rc"
|
[ "$rc" -eq 0 ] || fail_msg "Q13: render must exit 0, got rc=$rc"
|
||||||
printf '%s' "$out" | has_match -Eq 'consumed --upto 1$' || fail_msg "Q13: with nothing quarantined the ack must embed the observed cursor (1) unchanged"
|
printf '%s' "$out" | grep -Eq 'consumed --upto 1$' || fail_msg "Q13: with nothing quarantined the ack must embed the observed cursor (1) unchanged"
|
||||||
printf '%s' "$out" | has_match -q 'QUARANTINED' && fail_msg "Q13: no disclosure section when nothing is quarantined"
|
printf '%s' "$out" | grep -q 'QUARANTINED' && fail_msg "Q13: no disclosure section when nothing is quarantined"
|
||||||
printf '%s' "$out" | has_match -q 'ACK CLAMPED' && fail_msg "Q13: no clamp note when nothing is quarantined"
|
printf '%s' "$out" | grep -q 'ACK CLAMPED' && fail_msg "Q13: no clamp note when nothing is quarantined"
|
||||||
true
|
true
|
||||||
) && ok
|
) && ok
|
||||||
|
|
||||||
@@ -527,8 +520,8 @@ echo "== Q14 (#946): store-mode render SYNCS quarantine truth into the store —
|
|||||||
out="$("$DIGEST" render --from-store --agent default 2>/dev/null)"
|
out="$("$DIGEST" render --from-store --agent default 2>/dev/null)"
|
||||||
rc=$?
|
rc=$?
|
||||||
[ "$rc" -eq 0 ] || fail_msg "Q14: render must exit 0, got rc=$rc"
|
[ "$rc" -eq 0 ] || fail_msg "Q14: render must exit 0, got rc=$rc"
|
||||||
printf '%s' "$out" | has_match -q 'QUARANTINED' || fail_msg "Q14: the store-mode digest must disclose the held entry"
|
printf '%s' "$out" | grep -q 'QUARANTINED' || fail_msg "Q14: the store-mode digest must disclose the held entry"
|
||||||
printf '%s' "$out" | has_match -Eq 'consumed --upto 1$' || fail_msg "Q14: the embedded ack must clamp to 1 (below quarantined seq 2)"
|
printf '%s' "$out" | grep -Eq 'consumed --upto 1$' || fail_msg "Q14: the embedded ack must clamp to 1 (below quarantined seq 2)"
|
||||||
qf="$home/default/quarantined.set"
|
qf="$home/default/quarantined.set"
|
||||||
[ "$(cat "$qf" 2>/dev/null)" = "2" ] || fail_msg "Q14: a store-mode render must sync quarantined.set to exactly {2}, got [$(cat "$qf" 2>/dev/null)]"
|
[ "$(cat "$qf" 2>/dev/null)" = "2" ] || fail_msg "Q14: a store-mode render must sync quarantined.set to exactly {2}, got [$(cat "$qf" 2>/dev/null)]"
|
||||||
# END-TO-END: even a hand-typed upto past the held seq is refused at the
|
# END-TO-END: even a hand-typed upto past the held seq is refused at the
|
||||||
@@ -552,8 +545,8 @@ echo "== Q15 (#946): gate-fix RECOVERY — a stale quarantined.set is REPLACED b
|
|||||||
out="$("$DIGEST" render --from-store --agent default 2>/dev/null)"
|
out="$("$DIGEST" render --from-store --agent default 2>/dev/null)"
|
||||||
rc=$?
|
rc=$?
|
||||||
[ "$rc" -eq 0 ] || fail_msg "Q15: render must exit 0, got rc=$rc"
|
[ "$rc" -eq 0 ] || fail_msg "Q15: render must exit 0, got rc=$rc"
|
||||||
printf '%s' "$out" | has_match -q 'QUARANTINED' && fail_msg "Q15: nothing quarantines under the fixed gate — no disclosure section"
|
printf '%s' "$out" | grep -q 'QUARANTINED' && fail_msg "Q15: nothing quarantines under the fixed gate — no disclosure section"
|
||||||
printf '%s' "$out" | has_match -Eq 'consumed --upto 2$' || fail_msg "Q15: the ack must embed the full observed cursor (2) once the gate is fixed"
|
printf '%s' "$out" | grep -Eq 'consumed --upto 2$' || fail_msg "Q15: the ack must embed the full observed cursor (2) once the gate is fixed"
|
||||||
[ -s "$home/default/quarantined.set" ] && fail_msg "Q15: the clean render must REPLACE (clear) the stale quarantined.set — a cumulative-forever set would block acks on entries that now render, got [$(cat "$home/default/quarantined.set")]"
|
[ -s "$home/default/quarantined.set" ] && fail_msg "Q15: the clean render must REPLACE (clear) the stale quarantined.set — a cumulative-forever set would block acks on entries that now render, got [$(cat "$home/default/quarantined.set")]"
|
||||||
"$STORE" consume --upto 2 >/dev/null 2>&1 || fail_msg "Q15: the ordinary consume must succeed after the clamp self-heals"
|
"$STORE" consume --upto 2 >/dev/null 2>&1 || fail_msg "Q15: the ordinary consume must succeed after the clamp self-heals"
|
||||||
) && ok
|
) && ok
|
||||||
@@ -564,7 +557,7 @@ echo "== Q16 (guard): Q2's ENUM-B fixture must STAY address-free — the reconci
|
|||||||
# Token concatenated so THIS guard's own source lines never contain the
|
# Token concatenated so THIS guard's own source lines never contain the
|
||||||
# literal fixture id and cannot self-match.
|
# literal fixture id and cannot self-match.
|
||||||
enum_id='ENUM''-B'
|
enum_id='ENUM''-B'
|
||||||
fixture_line="$(has_match -F "\"id\":\"$enum_id\"" "$self" | has_match -F '"observed_seq":5' | head -n1)"
|
fixture_line="$(grep -F "\"id\":\"$enum_id\"" "$self" | grep -F '"observed_seq":5' | head -n1)"
|
||||||
[ -n "$fixture_line" ] || fail_msg "Q16: could not locate Q2's $enum_id fixture line (renamed/renumbered? update this guard)"
|
[ -n "$fixture_line" ] || fail_msg "Q16: could not locate Q2's $enum_id fixture line (renamed/renumbered? update this guard)"
|
||||||
fixture_json="$(printf '%s' "$fixture_line" | sed "s/.*'\({.*}\)'.*/\1/")"
|
fixture_json="$(printf '%s' "$fixture_line" | sed "s/.*'\({.*}\)'.*/\1/")"
|
||||||
# Positive controls FIRST (blind-instrument rule): the extraction must yield
|
# Positive controls FIRST (blind-instrument rule): the extraction must yield
|
||||||
@@ -585,7 +578,7 @@ echo "== Q16 (guard): Q2's ENUM-B fixture must STAY address-free — the reconci
|
|||||||
|
|
||||||
echo
|
echo
|
||||||
if [ -s "$FAILFILE" ]; then
|
if [ -s "$FAILFILE" ]; then
|
||||||
echo "wake digest-quarantine harness: FAILED ($(count_lines . "$FAILFILE") assertion(s))" >&2
|
echo "wake digest-quarantine harness: FAILED ($(grep -c . "$FAILFILE") assertion(s))" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
echo "wake digest-quarantine harness: all invariants passed ($pass groups)"
|
echo "wake digest-quarantine harness: all invariants passed ($pass groups)"
|
||||||
|
|||||||
@@ -25,13 +25,6 @@
|
|||||||
set -uo pipefail
|
set -uo pipefail
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
# #973: three-valued grep assertion helpers (has_match/count_lines); init saves real stderr for abort loudness.
|
|
||||||
# shellcheck disable=SC1091
|
|
||||||
if ! . "$SCRIPT_DIR/_wake-common.sh"; then
|
|
||||||
echo "WAKE-ASSERT INIT ABORT: cannot source _wake-common.sh — suite ran ZERO wake assertions (#984)" >&2
|
|
||||||
exit 97
|
|
||||||
fi
|
|
||||||
wake_assert_init
|
|
||||||
ORACLE="$SCRIPT_DIR/fn-oracle.sh"
|
ORACLE="$SCRIPT_DIR/fn-oracle.sh"
|
||||||
DET="$SCRIPT_DIR/detector.sh"
|
DET="$SCRIPT_DIR/detector.sh"
|
||||||
|
|
||||||
@@ -69,8 +62,8 @@ echo "== O1: healthy pipeline -> synthetic-canary FN-rate = 0 =="
|
|||||||
out="$("$ORACLE" run --slo-seconds 120 --count 3 2>&1)"
|
out="$("$ORACLE" run --slo-seconds 120 --count 3 2>&1)"
|
||||||
rc=$?
|
rc=$?
|
||||||
[ "$rc" -eq 0 ] || fail_msg "O1: a healthy pipeline must exit 0 (got $rc) [$out]"
|
[ "$rc" -eq 0 ] || fail_msg "O1: a healthy pipeline must exit 0 (got $rc) [$out]"
|
||||||
echo "$out" | has_match -q 'FN-RATE = 0/3' || fail_msg "O1: FN-rate must be 0/3 on a healthy pipeline [$out]"
|
echo "$out" | grep -q 'FN-RATE = 0/3' || fail_msg "O1: FN-rate must be 0/3 on a healthy pipeline [$out]"
|
||||||
echo "$out" | has_match -q 'VERDICT = PASS' || fail_msg "O1: verdict must be PASS on a healthy pipeline [$out]"
|
echo "$out" | grep -q 'VERDICT = PASS' || fail_msg "O1: verdict must be PASS on a healthy pipeline [$out]"
|
||||||
) && ok
|
) && ok
|
||||||
|
|
||||||
echo "== O2: DISABLED detector (perfect no-op) -> FN-DETECTED (blindspot killer) =="
|
echo "== O2: DISABLED detector (perfect no-op) -> FN-DETECTED (blindspot killer) =="
|
||||||
@@ -83,9 +76,9 @@ echo "== O2: DISABLED detector (perfect no-op) -> FN-DETECTED (blindspot killer)
|
|||||||
out="$("$ORACLE" run --slo-seconds 120 --count 3 2>&1)"
|
out="$("$ORACLE" run --slo-seconds 120 --count 3 2>&1)"
|
||||||
rc=$?
|
rc=$?
|
||||||
[ "$rc" -ne 0 ] || fail_msg "O2: a detector that drops a KNOWN delta MUST fail the oracle (non-zero exit), even at a perfect no-op rate"
|
[ "$rc" -ne 0 ] || fail_msg "O2: a detector that drops a KNOWN delta MUST fail the oracle (non-zero exit), even at a perfect no-op rate"
|
||||||
echo "$out" | has_match -q 'FN-RATE = 3/3' || fail_msg "O2: every dropped canary must count as a false-negative (FN-RATE 3/3) [$out]"
|
echo "$out" | grep -q 'FN-RATE = 3/3' || fail_msg "O2: every dropped canary must count as a false-negative (FN-RATE 3/3) [$out]"
|
||||||
echo "$out" | has_match -q 'VERDICT = FN-DETECTED' || fail_msg "O2: verdict must be FN-DETECTED for a dropping detector [$out]"
|
echo "$out" | grep -q 'VERDICT = FN-DETECTED' || fail_msg "O2: verdict must be FN-DETECTED for a dropping detector [$out]"
|
||||||
echo "$out" | has_match -qi 'never OBSERVED' || fail_msg "O2: the failure must name the dropped (never-observed) delta [$out]"
|
echo "$out" | grep -qi 'never OBSERVED' || fail_msg "O2: the failure must name the dropped (never-observed) delta [$out]"
|
||||||
) && ok
|
) && ok
|
||||||
|
|
||||||
echo "== O3: reached CONSUMED but too slow -> FN-DETECTED (within-SLO clause) =="
|
echo "== O3: reached CONSUMED but too slow -> FN-DETECTED (within-SLO clause) =="
|
||||||
@@ -98,10 +91,10 @@ echo "== O3: reached CONSUMED but too slow -> FN-DETECTED (within-SLO clause) ==
|
|||||||
out="$("$ORACLE" run --slo-seconds 1 --count 1 2>&1)"
|
out="$("$ORACLE" run --slo-seconds 1 --count 1 2>&1)"
|
||||||
rc=$?
|
rc=$?
|
||||||
[ "$rc" -ne 0 ] || fail_msg "O3: a canary that reaches CONSUMED past its SLO must be a false-negative (non-zero exit)"
|
[ "$rc" -ne 0 ] || fail_msg "O3: a canary that reaches CONSUMED past its SLO must be a false-negative (non-zero exit)"
|
||||||
echo "$out" | has_match -qi 'per-class SLO' || fail_msg "O3: the failure must attribute to the per-class SLO, not a drop [$out]"
|
echo "$out" | grep -qi 'per-class SLO' || fail_msg "O3: the failure must attribute to the per-class SLO, not a drop [$out]"
|
||||||
# It must NOT be the 'never observed' branch: the delta WAS observed/delivered,
|
# It must NOT be the 'never observed' branch: the delta WAS observed/delivered,
|
||||||
# just too slowly. This proves O3 exercises the SLO clause specifically.
|
# just too slowly. This proves O3 exercises the SLO clause specifically.
|
||||||
if echo "$out" | has_match -qi 'never OBSERVED'; then
|
if echo "$out" | grep -qi 'never OBSERVED'; then
|
||||||
fail_msg "O3: an SLO breach must NOT be misreported as a dropped delta [$out]"
|
fail_msg "O3: an SLO breach must NOT be misreported as a dropped delta [$out]"
|
||||||
fi
|
fi
|
||||||
) && ok
|
) && ok
|
||||||
@@ -117,7 +110,7 @@ echo "== O4: verdict is OFF-DOMAIN (from terminal consumed_seq, not detector sel
|
|||||||
out="$("$ORACLE" run --slo-seconds 120 --count 1 2>&1)"
|
out="$("$ORACLE" run --slo-seconds 120 --count 1 2>&1)"
|
||||||
rc=$?
|
rc=$?
|
||||||
[ "$rc" -ne 0 ] || fail_msg "O4: a drive that exits 0 but delivers nothing must still be FN-DETECTED (verdict is off-domain)"
|
[ "$rc" -ne 0 ] || fail_msg "O4: a drive that exits 0 but delivers nothing must still be FN-DETECTED (verdict is off-domain)"
|
||||||
echo "$out" | has_match -q 'VERDICT = FN-DETECTED' || fail_msg "O4: off-domain verdict must not be fooled by a clean exit code [$out]"
|
echo "$out" | grep -q 'VERDICT = FN-DETECTED' || fail_msg "O4: off-domain verdict must not be fooled by a clean exit code [$out]"
|
||||||
) && ok
|
) && ok
|
||||||
|
|
||||||
echo "== O5: no invented SLO -> --slo-seconds is REQUIRED (fail-loud) =="
|
echo "== O5: no invented SLO -> --slo-seconds is REQUIRED (fail-loud) =="
|
||||||
@@ -128,12 +121,12 @@ echo "== O5: no invented SLO -> --slo-seconds is REQUIRED (fail-loud) =="
|
|||||||
err="$("$ORACLE" run --count 1 2>&1)"
|
err="$("$ORACLE" run --count 1 2>&1)"
|
||||||
rc=$?
|
rc=$?
|
||||||
[ "$rc" -ne 0 ] || fail_msg "O5: run without an SLO must fail loud (no invented default)"
|
[ "$rc" -ne 0 ] || fail_msg "O5: run without an SLO must fail loud (no invented default)"
|
||||||
echo "$err" | has_match -qi 'slo' || fail_msg "O5: the usage error must name the missing SLO [$err]"
|
echo "$err" | grep -qi 'slo' || fail_msg "O5: the usage error must name the missing SLO [$err]"
|
||||||
) && ok
|
) && ok
|
||||||
|
|
||||||
echo
|
echo
|
||||||
if [ -s "$FAILFILE" ]; then
|
if [ -s "$FAILFILE" ]; then
|
||||||
echo "wake fn-oracle harness: FAILED ($(count_lines . "$FAILFILE") assertion(s))" >&2
|
echo "wake fn-oracle harness: FAILED ($(grep -c . "$FAILFILE") assertion(s))" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
echo "wake fn-oracle harness: all invariants passed ($pass groups)"
|
echo "wake fn-oracle harness: all invariants passed ($pass groups)"
|
||||||
|
|||||||
@@ -34,13 +34,6 @@
|
|||||||
set -uo pipefail
|
set -uo pipefail
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
# #973: three-valued grep assertion helpers (has_match/count_lines); init saves real stderr for abort loudness.
|
|
||||||
# shellcheck disable=SC1091
|
|
||||||
if ! . "$SCRIPT_DIR/_wake-common.sh"; then
|
|
||||||
echo "WAKE-ASSERT INIT ABORT: cannot source _wake-common.sh — suite ran ZERO wake assertions (#984)" >&2
|
|
||||||
exit 97
|
|
||||||
fi
|
|
||||||
wake_assert_init
|
|
||||||
WI="$SCRIPT_DIR/wake-install.sh"
|
WI="$SCRIPT_DIR/wake-install.sh"
|
||||||
BEACON="$SCRIPT_DIR/beacon.sh"
|
BEACON="$SCRIPT_DIR/beacon.sh"
|
||||||
FRAMEWORK_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
FRAMEWORK_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||||
@@ -101,7 +94,7 @@ EOF
|
|||||||
export WAKE_INSTALL_MANIFEST="$BAD_MANIFEST"
|
export WAKE_INSTALL_MANIFEST="$BAD_MANIFEST"
|
||||||
out="$(bash "$WI" install 2>&1)"; rc=$?
|
out="$(bash "$WI" install 2>&1)"; rc=$?
|
||||||
[ "$rc" -ne 0 ] || fail_msg "I2: install must FAIL when a wake candidate is not framework-owned (got rc=$rc)"
|
[ "$rc" -ne 0 ] || fail_msg "I2: install must FAIL when a wake candidate is not framework-owned (got rc=$rc)"
|
||||||
echo "$out" | has_match -qi 'Gate A VIOLATION' || fail_msg "I2: refusal must name the Gate A violation [$out]"
|
echo "$out" | grep -qi 'Gate A VIOLATION' || fail_msg "I2: refusal must name the Gate A violation [$out]"
|
||||||
# No partial write: the target must have received NO wake tool file.
|
# No partial write: the target must have received NO wake tool file.
|
||||||
[ ! -e "$TGT/tools/wake/beacon.sh" ] || fail_msg "I2: a refused install must not have written any wake file (partial write leaked)"
|
[ ! -e "$TGT/tools/wake/beacon.sh" ] || fail_msg "I2: a refused install must not have written any wake file (partial write leaked)"
|
||||||
# And the positive control: with the REAL manifest, the same candidates install.
|
# And the positive control: with the REAL manifest, the same candidates install.
|
||||||
@@ -128,7 +121,7 @@ EOF
|
|||||||
|| fail_msg "I3: blank-reset drop-in write failed"
|
|| fail_msg "I3: blank-reset drop-in write failed"
|
||||||
out="$(bash "$WI" verify-single "$UNIT" 2>&1)"; rc=$?
|
out="$(bash "$WI" verify-single "$UNIT" 2>&1)"; rc=$?
|
||||||
[ "$rc" -eq 0 ] || fail_msg "I3: blank-reset must resolve exactly one OnUnitActiveUSec (rc=$rc) [$out]"
|
[ "$rc" -eq 0 ] || fail_msg "I3: blank-reset must resolve exactly one OnUnitActiveUSec (rc=$rc) [$out]"
|
||||||
echo "$out" | has_match -qi 'EXACTLY ONE' || fail_msg "I3: verify must confirm exactly-one [$out]"
|
echo "$out" | grep -qi 'EXACTLY ONE' || fail_msg "I3: verify must confirm exactly-one [$out]"
|
||||||
# NEGATIVE CONTROL: a drop-in WITHOUT the reset line appends -> TWO values -> fail.
|
# NEGATIVE CONTROL: a drop-in WITHOUT the reset line appends -> TWO values -> fail.
|
||||||
cat >"$UD/$UNIT.d/interval.conf" <<'EOF'
|
cat >"$UD/$UNIT.d/interval.conf" <<'EOF'
|
||||||
[Timer]
|
[Timer]
|
||||||
@@ -148,7 +141,7 @@ echo "== I4: snapshot-guard — reap without a snapshot is REFUSED; allowed afte
|
|||||||
# (a) reap WITHOUT snapshot -> refuse, unit still present.
|
# (a) reap WITHOUT snapshot -> refuse, unit still present.
|
||||||
out="$(bash "$WI" reap-unit "$UNIT" 2>&1)"; rc=$?
|
out="$(bash "$WI" reap-unit "$UNIT" 2>&1)"; rc=$?
|
||||||
[ "$rc" -ne 0 ] || fail_msg "I4: reap without a snapshot must be REFUSED (rc=$rc)"
|
[ "$rc" -ne 0 ] || fail_msg "I4: reap without a snapshot must be REFUSED (rc=$rc)"
|
||||||
echo "$out" | has_match -qi 'snapshot-guard' || fail_msg "I4: refusal must name the snapshot-guard [$out]"
|
echo "$out" | grep -qi 'snapshot-guard' || fail_msg "I4: refusal must name the snapshot-guard [$out]"
|
||||||
[ -f "$UD/$UNIT" ] || fail_msg "I4: a refused reap must NOT delete the unit"
|
[ -f "$UD/$UNIT" ] || fail_msg "I4: a refused reap must NOT delete the unit"
|
||||||
# (b) snapshot, then reap -> allowed, unit gone, snapshot retained.
|
# (b) snapshot, then reap -> allowed, unit gone, snapshot retained.
|
||||||
bash "$WI" snapshot-unit "$UNIT" >/dev/null 2>&1 || fail_msg "I4: snapshot-unit failed"
|
bash "$WI" snapshot-unit "$UNIT" >/dev/null 2>&1 || fail_msg "I4: snapshot-unit failed"
|
||||||
@@ -168,28 +161,28 @@ echo "== I5: fail-closed install-validate — unconfigured HMAC/alarm FAIL LOUD;
|
|||||||
export WAKE_HMAC_KEY_NAME="primary"
|
export WAKE_HMAC_KEY_NAME="primary"
|
||||||
out="$(bash "$WI" validate-hmac-key 2>&1)"; rc=$?
|
out="$(bash "$WI" validate-hmac-key 2>&1)"; rc=$?
|
||||||
[ "$rc" -ne 0 ] || fail_msg "I5: missing credential store must FAIL LOUD for the HMAC key (rc=$rc)"
|
[ "$rc" -ne 0 ] || fail_msg "I5: missing credential store must FAIL LOUD for the HMAC key (rc=$rc)"
|
||||||
echo "$out" | has_match -qi 'UNSIGNED' || fail_msg "I5: HMAC failure must warn about unsigned wakes [$out]"
|
echo "$out" | grep -qi 'UNSIGNED' || fail_msg "I5: HMAC failure must warn about unsigned wakes [$out]"
|
||||||
# Now provide the key by-name -> pass, and the value must NEVER be echoed.
|
# Now provide the key by-name -> pass, and the value must NEVER be echoed.
|
||||||
jq -cn --arg k "$SECRET_KEY" '{wake:{hmac_keys:{"primary":$k}}}' >"$CRED"
|
jq -cn --arg k "$SECRET_KEY" '{wake:{hmac_keys:{"primary":$k}}}' >"$CRED"
|
||||||
out2="$(bash "$WI" validate-hmac-key 2>&1)"; rc2=$?
|
out2="$(bash "$WI" validate-hmac-key 2>&1)"; rc2=$?
|
||||||
[ "$rc2" -eq 0 ] || fail_msg "I5: a by-name-resolvable HMAC key must pass (rc=$rc2) [$out2]"
|
[ "$rc2" -eq 0 ] || fail_msg "I5: a by-name-resolvable HMAC key must pass (rc=$rc2) [$out2]"
|
||||||
echo "$out2" | has_match -qF "$SECRET_KEY" && fail_msg "I5: validate must NEVER echo the HMAC key material"
|
echo "$out2" | grep -qF "$SECRET_KEY" && fail_msg "I5: validate must NEVER echo the HMAC key material"
|
||||||
# --- alarm target: unconfigured -> fail loud (silent no-alarm host).
|
# --- alarm target: unconfigured -> fail loud (silent no-alarm host).
|
||||||
unset WAKE_ALARM_SINK_CMD
|
unset WAKE_ALARM_SINK_CMD
|
||||||
out3="$(bash "$WI" validate-alarm-target 2>&1)"; rc3=$?
|
out3="$(bash "$WI" validate-alarm-target 2>&1)"; rc3=$?
|
||||||
[ "$rc3" -ne 0 ] || fail_msg "I5: unconfigured alarm target must FAIL LOUD (rc=$rc3)"
|
[ "$rc3" -ne 0 ] || fail_msg "I5: unconfigured alarm target must FAIL LOUD (rc=$rc3)"
|
||||||
echo "$out3" | has_match -qi 'silent no-alarm host' || fail_msg "I5: alarm failure must name the silent-no-alarm hazard [$out3]"
|
echo "$out3" | grep -qi 'silent no-alarm host' || fail_msg "I5: alarm failure must name the silent-no-alarm hazard [$out3]"
|
||||||
# unreachable -> fail loud.
|
# unreachable -> fail loud.
|
||||||
export WAKE_ALARM_SINK_CMD="false"
|
export WAKE_ALARM_SINK_CMD="false"
|
||||||
out4="$(bash "$WI" validate-alarm-target 2>&1)"; rc4=$?
|
out4="$(bash "$WI" validate-alarm-target 2>&1)"; rc4=$?
|
||||||
[ "$rc4" -ne 0 ] || fail_msg "I5: unreachable alarm sink must FAIL LOUD (rc=$rc4)"
|
[ "$rc4" -ne 0 ] || fail_msg "I5: unreachable alarm sink must FAIL LOUD (rc=$rc4)"
|
||||||
echo "$out4" | has_match -qi 'UNREACHABLE' || fail_msg "I5: alarm failure must name the unreachable target [$out4]"
|
echo "$out4" | grep -qi 'UNREACHABLE' || fail_msg "I5: alarm failure must name the unreachable target [$out4]"
|
||||||
# reachable -> pass.
|
# reachable -> pass.
|
||||||
export WAKE_ALARM_SINK_CMD="cat >/dev/null"
|
export WAKE_ALARM_SINK_CMD="cat >/dev/null"
|
||||||
bash "$WI" validate-alarm-target >/dev/null 2>&1 || fail_msg "I5: a configured+reachable alarm sink must pass"
|
bash "$WI" validate-alarm-target >/dev/null 2>&1 || fail_msg "I5: a configured+reachable alarm sink must pass"
|
||||||
# The installer file must inline NO secret/endpoint.
|
# The installer file must inline NO secret/endpoint.
|
||||||
has_match -qF "$SECRET_ENDPOINT" "$WI" && fail_msg "I5: wake-install.sh must NOT inline any alarm endpoint"
|
grep -qF "$SECRET_ENDPOINT" "$WI" && fail_msg "I5: wake-install.sh must NOT inline any alarm endpoint"
|
||||||
has_match -qE 'hmac_keys[^A-Za-z_].*=[^=].*[A-Za-z0-9]{8,}' "$WI" && fail_msg "I5: wake-install.sh must NOT inline key material"
|
grep -qE 'hmac_keys[^A-Za-z_].*=[^=].*[A-Za-z0-9]{8,}' "$WI" && fail_msg "I5: wake-install.sh must NOT inline key material"
|
||||||
true
|
true
|
||||||
) && ok
|
) && ok
|
||||||
|
|
||||||
@@ -211,7 +204,7 @@ echo "== I6: reset->verify->retire — overlap keeps legacy running; only §4-ve
|
|||||||
[ "$rc" -eq 0 ] || fail_msg "I6: overlap reset-verify must succeed (rc=$rc) [$out]"
|
[ "$rc" -eq 0 ] || fail_msg "I6: overlap reset-verify must succeed (rc=$rc) [$out]"
|
||||||
[ -f "$UD/$TIMER" ] || fail_msg "I6: overlap phase must LEAVE the legacy timer running (retire withheld)"
|
[ -f "$UD/$TIMER" ] || fail_msg "I6: overlap phase must LEAVE the legacy timer running (retire withheld)"
|
||||||
[ -f "$SNAP/$TIMER" ] || fail_msg "I6: the legacy timer must be snapshotted before any retire"
|
[ -f "$SNAP/$TIMER" ] || fail_msg "I6: the legacy timer must be snapshotted before any retire"
|
||||||
echo "$out" | has_match -qi 'LEFT RUNNING' || fail_msg "I6: overlap must announce retire is withheld [$out]"
|
echo "$out" | grep -qi 'LEFT RUNNING' || fail_msg "I6: overlap must announce retire is withheld [$out]"
|
||||||
# (b) §4-vector pass: retire LAST, snapshot-guarded (fallback proven live above).
|
# (b) §4-vector pass: retire LAST, snapshot-guarded (fallback proven live above).
|
||||||
out2="$(bash "$WI" reset-verify-retire c --interval 30min --vector-passed 2>&1)"; rc2=$?
|
out2="$(bash "$WI" reset-verify-retire c --interval 30min --vector-passed 2>&1)"; rc2=$?
|
||||||
[ "$rc2" -eq 0 ] || fail_msg "I6: vector-passed retire must succeed (rc=$rc2) [$out2]"
|
[ "$rc2" -eq 0 ] || fail_msg "I6: vector-passed retire must succeed (rc=$rc2) [$out2]"
|
||||||
@@ -290,11 +283,11 @@ echo "== I9: dep-check — a host seed missing _lib/manifest.sh FAILS LOUD (name
|
|||||||
TGT="$(fresh i9-target)"
|
TGT="$(fresh i9-target)"
|
||||||
out="$(WAKE_INSTALL_SOURCE="$FAKE" WAKE_INSTALL_TARGET="$TGT" bash "$WI_FAKE" install 2>&1)"; rc=$?
|
out="$(WAKE_INSTALL_SOURCE="$FAKE" WAKE_INSTALL_TARGET="$TGT" bash "$WI_FAKE" install 2>&1)"; rc=$?
|
||||||
[ "$rc" -ne 0 ] || fail_msg "I9: install MUST fail when _lib/manifest.sh is missing (rc=$rc)"
|
[ "$rc" -ne 0 ] || fail_msg "I9: install MUST fail when _lib/manifest.sh is missing (rc=$rc)"
|
||||||
echo "$out" | has_match -qi 'manifest.sh' || fail_msg "I9: the failure must NAME the missing library (manifest.sh) [$out]"
|
echo "$out" | grep -qi 'manifest.sh' || fail_msg "I9: the failure must NAME the missing library (manifest.sh) [$out]"
|
||||||
echo "$out" | has_match -qiE 'REMEDY|mosaic update|re-seed|framework install' \
|
echo "$out" | grep -qiE 'REMEDY|mosaic update|re-seed|framework install' \
|
||||||
|| fail_msg "I9: the failure must give an actionable REMEDY, not just an error [$out]"
|
|| fail_msg "I9: the failure must give an actionable REMEDY, not just an error [$out]"
|
||||||
# It must be a CLEAR fail-loud, NOT the obscure bare `source: No such file or directory`.
|
# It must be a CLEAR fail-loud, NOT the obscure bare `source: No such file or directory`.
|
||||||
echo "$out" | has_match -qi 'No such file or directory' \
|
echo "$out" | grep -qi 'No such file or directory' \
|
||||||
&& fail_msg "I9: must not fail with a bare source error (obscure) [$out]"
|
&& fail_msg "I9: must not fail with a bare source error (obscure) [$out]"
|
||||||
# Positive control: with the helper present (real framework root) install succeeds.
|
# Positive control: with the helper present (real framework root) install succeeds.
|
||||||
TGT_OK="$(fresh i9-target-ok)"
|
TGT_OK="$(fresh i9-target-ok)"
|
||||||
@@ -320,10 +313,10 @@ echo "== I10: systemd search-path — install links mosaic-wake.service into the
|
|||||||
rm -f "$UD/mosaic-wake.service"
|
rm -f "$UD/mosaic-wake.service"
|
||||||
out2="$(bash "$WI" validate-systemd-path 2>&1)"; rc2=$?
|
out2="$(bash "$WI" validate-systemd-path 2>&1)"; rc2=$?
|
||||||
[ "$rc2" -ne 0 ] || fail_msg "I10: validate must FAIL when the unit is not in the search path (rc=$rc2) [$out2]"
|
[ "$rc2" -ne 0 ] || fail_msg "I10: validate must FAIL when the unit is not in the search path (rc=$rc2) [$out2]"
|
||||||
echo "$out2" | has_match -qi 'search path' || fail_msg "I10: validate failure must name the search-path miss [$out2]"
|
echo "$out2" | grep -qi 'search path' || fail_msg "I10: validate failure must name the search-path miss [$out2]"
|
||||||
# (c) idempotent re-install: exactly one search-path entry, still resolvable.
|
# (c) idempotent re-install: exactly one search-path entry, still resolvable.
|
||||||
bash "$WI" install >/dev/null 2>&1 || fail_msg "I10: re-run install must succeed"
|
bash "$WI" install >/dev/null 2>&1 || fail_msg "I10: re-run install must succeed"
|
||||||
n="$(find "$UD" -maxdepth 1 -name 'mosaic-wake.service' | count_lines .)"
|
n="$(find "$UD" -maxdepth 1 -name 'mosaic-wake.service' | grep -c .)"
|
||||||
[ "$n" -eq 1 ] || fail_msg "I10: re-install must not duplicate the search-path entry (found $n)"
|
[ "$n" -eq 1 ] || fail_msg "I10: re-install must not duplicate the search-path entry (found $n)"
|
||||||
bash "$WI" validate-systemd-path >/dev/null 2>&1 || fail_msg "I10: re-install must keep the unit resolvable"
|
bash "$WI" validate-systemd-path >/dev/null 2>&1 || fail_msg "I10: re-install must keep the unit resolvable"
|
||||||
) && ok
|
) && ok
|
||||||
@@ -341,12 +334,12 @@ echo "== I11: F7 — the legacy reap REFUSES unless the canon fallback wake is p
|
|||||||
# it must REFUSE (schedulable floor not met).
|
# it must REFUSE (schedulable floor not met).
|
||||||
out0="$(bash "$WI" fallback-proven-live 2>&1)"; rc0=$?
|
out0="$(bash "$WI" fallback-proven-live 2>&1)"; rc0=$?
|
||||||
[ "$rc0" -ne 0 ] || fail_msg "I11: fallback-proven-live must FAIL when the fallback wake is not installed (rc=$rc0)"
|
[ "$rc0" -ne 0 ] || fail_msg "I11: fallback-proven-live must FAIL when the fallback wake is not installed (rc=$rc0)"
|
||||||
echo "$out0" | has_match -qi 'F7' || fail_msg "I11: the refusal must name the F7 precondition [$out0]"
|
echo "$out0" | grep -qi 'F7' || fail_msg "I11: the refusal must name the F7 precondition [$out0]"
|
||||||
# (b) NEGATIVE — vector-passed reap with NO live fallback must be REFUSED and the
|
# (b) NEGATIVE — vector-passed reap with NO live fallback must be REFUSED and the
|
||||||
# legacy timer LEFT RUNNING (never a coverage gap).
|
# legacy timer LEFT RUNNING (never a coverage gap).
|
||||||
out="$(bash "$WI" reset-verify-retire f --interval 30min --vector-passed 2>&1)"; rc=$?
|
out="$(bash "$WI" reset-verify-retire f --interval 30min --vector-passed 2>&1)"; rc=$?
|
||||||
[ "$rc" -ne 0 ] || fail_msg "I11: reap must be REFUSED without a proven-live fallback (rc=$rc)"
|
[ "$rc" -ne 0 ] || fail_msg "I11: reap must be REFUSED without a proven-live fallback (rc=$rc)"
|
||||||
echo "$out" | has_match -qi 'FALLBACK WAKE is not proven live' || fail_msg "I11: refusal must name the un-proven fallback [$out]"
|
echo "$out" | grep -qi 'FALLBACK WAKE is not proven live' || fail_msg "I11: refusal must name the un-proven fallback [$out]"
|
||||||
[ -f "$UD/$TIMER" ] || fail_msg "I11: a refused reap must LEAVE the legacy timer running (F7 — no coverage gap)"
|
[ -f "$UD/$TIMER" ] || fail_msg "I11: a refused reap must LEAVE the legacy timer running (F7 — no coverage gap)"
|
||||||
# (c) POSITIVE — provision the fallback at the schedulable floor, then the reap
|
# (c) POSITIVE — provision the fallback at the schedulable floor, then the reap
|
||||||
# proceeds (F7 satisfied) and the legacy timer is retired.
|
# proceeds (F7 satisfied) and the legacy timer is retired.
|
||||||
@@ -374,11 +367,11 @@ echo "== I12: fallback drain — a STALLED detector still gets delivery via the
|
|||||||
# even with no detector running — no starvation of the drain.
|
# even with no detector running — no starvation of the drain.
|
||||||
out="$(bash "$DIGEST" render --from-store 2>/dev/null)"; rc=$?
|
out="$(bash "$DIGEST" render --from-store 2>/dev/null)"; rc=$?
|
||||||
[ "$rc" -eq 0 ] || fail_msg "I12: the canon fallback drain must exit 0 (rc=$rc)"
|
[ "$rc" -eq 0 ] || fail_msg "I12: the canon fallback drain must exit 0 (rc=$rc)"
|
||||||
echo "$out" | has_match -q 'STALLED-DRAIN-MARK' \
|
echo "$out" | grep -q 'STALLED-DRAIN-MARK' \
|
||||||
|| fail_msg "I12: the fallback drain must DELIVER the pending obligation a stalled detector left (no delivery starvation) [$out]"
|
|| fail_msg "I12: the fallback drain must DELIVER the pending obligation a stalled detector left (no delivery starvation) [$out]"
|
||||||
# Bind the invariant to the SHIPPED unit: its ExecStart must be this canon drain.
|
# Bind the invariant to the SHIPPED unit: its ExecStart must be this canon drain.
|
||||||
UNIT="$FRAMEWORK_ROOT/systemd/user/mosaic-wake-fallback.service"
|
UNIT="$FRAMEWORK_ROOT/systemd/user/mosaic-wake-fallback.service"
|
||||||
has_match -qE '^ExecStart=.*digest\.sh render --from-store' "$UNIT" \
|
grep -qE '^ExecStart=.*digest\.sh render --from-store' "$UNIT" \
|
||||||
|| fail_msg "I12: mosaic-wake-fallback.service ExecStart must fire the canon drain (digest.sh render --from-store)"
|
|| fail_msg "I12: mosaic-wake-fallback.service ExecStart must fire the canon drain (digest.sh render --from-store)"
|
||||||
) && ok
|
) && ok
|
||||||
|
|
||||||
@@ -398,11 +391,11 @@ echo "== I13: install links + validates the canon fallback timer+service into th
|
|||||||
rm -f "$UD/mosaic-wake-fallback.timer"
|
rm -f "$UD/mosaic-wake-fallback.timer"
|
||||||
out="$(bash "$WI" validate-fallback-units 2>&1)"; rc=$?
|
out="$(bash "$WI" validate-fallback-units 2>&1)"; rc=$?
|
||||||
[ "$rc" -ne 0 ] || fail_msg "I13: validate must FAIL when a fallback unit is not in the search path (rc=$rc) [$out]"
|
[ "$rc" -ne 0 ] || fail_msg "I13: validate must FAIL when a fallback unit is not in the search path (rc=$rc) [$out]"
|
||||||
echo "$out" | has_match -qi 'search path' || fail_msg "I13: validate failure must name the search-path miss [$out]"
|
echo "$out" | grep -qi 'search path' || fail_msg "I13: validate failure must name the search-path miss [$out]"
|
||||||
# (c) idempotent re-install: exactly one entry per unit, still resolvable.
|
# (c) idempotent re-install: exactly one entry per unit, still resolvable.
|
||||||
bash "$WI" install >/dev/null 2>&1 || fail_msg "I13: re-run install must succeed"
|
bash "$WI" install >/dev/null 2>&1 || fail_msg "I13: re-run install must succeed"
|
||||||
nt="$(find "$UD" -maxdepth 1 -name 'mosaic-wake-fallback.timer' | count_lines .)"
|
nt="$(find "$UD" -maxdepth 1 -name 'mosaic-wake-fallback.timer' | grep -c .)"
|
||||||
ns="$(find "$UD" -maxdepth 1 -name 'mosaic-wake-fallback.service' | count_lines .)"
|
ns="$(find "$UD" -maxdepth 1 -name 'mosaic-wake-fallback.service' | grep -c .)"
|
||||||
[ "$nt" -eq 1 ] && [ "$ns" -eq 1 ] || fail_msg "I13: re-install must not duplicate the fallback entries (timer=$nt service=$ns)"
|
[ "$nt" -eq 1 ] && [ "$ns" -eq 1 ] || fail_msg "I13: re-install must not duplicate the fallback entries (timer=$nt service=$ns)"
|
||||||
bash "$WI" validate-fallback-units >/dev/null 2>&1 || fail_msg "I13: re-install must keep the fallback units resolvable"
|
bash "$WI" validate-fallback-units >/dev/null 2>&1 || fail_msg "I13: re-install must keep the fallback units resolvable"
|
||||||
) && ok
|
) && ok
|
||||||
@@ -414,17 +407,17 @@ echo "== I14: per-class fallback cadence — blank-reset yields EXACTLY ONE OnUn
|
|||||||
TIMER="mosaic-wake-fallback.timer"
|
TIMER="mosaic-wake-fallback.timer"
|
||||||
# The shipped timer carries the base OnUnitActiveSec placeholder (=1h).
|
# The shipped timer carries the base OnUnitActiveSec placeholder (=1h).
|
||||||
cp "$FRAMEWORK_ROOT/systemd/user/$TIMER" "$UD/$TIMER"
|
cp "$FRAMEWORK_ROOT/systemd/user/$TIMER" "$UD/$TIMER"
|
||||||
has_match -q '^OnUnitActiveSec=1h' "$UD/$TIMER" || fail_msg "I14: shipped fallback timer must carry a base OnUnitActiveSec placeholder"
|
grep -q '^OnUnitActiveSec=1h' "$UD/$TIMER" || fail_msg "I14: shipped fallback timer must carry a base OnUnitActiveSec placeholder"
|
||||||
# write-fallback-cadence writes the per-class cadence in BLANK-RESET form.
|
# write-fallback-cadence writes the per-class cadence in BLANK-RESET form.
|
||||||
out="$(bash "$WI" write-fallback-cadence 30min 2>&1)"; rc=$?
|
out="$(bash "$WI" write-fallback-cadence 30min 2>&1)"; rc=$?
|
||||||
[ "$rc" -eq 0 ] || fail_msg "I14: write-fallback-cadence must succeed + verify single (rc=$rc) [$out]"
|
[ "$rc" -eq 0 ] || fail_msg "I14: write-fallback-cadence must succeed + verify single (rc=$rc) [$out]"
|
||||||
echo "$out" | has_match -qi 'exactly one' || fail_msg "I14: the cadence write must confirm exactly-one OnUnitActiveUSec [$out]"
|
echo "$out" | grep -qi 'exactly one' || fail_msg "I14: the cadence write must confirm exactly-one OnUnitActiveUSec [$out]"
|
||||||
[ -f "$UD/$TIMER.d/cadence.conf" ] || fail_msg "I14: the per-class cadence drop-in must be written under <timer>.d/"
|
[ -f "$UD/$TIMER.d/cadence.conf" ] || fail_msg "I14: the per-class cadence drop-in must be written under <timer>.d/"
|
||||||
# Assert the blank-reset FORM: an empty `OnUnitActiveSec=` reset line IMMEDIATELY
|
# Assert the blank-reset FORM: an empty `OnUnitActiveSec=` reset line IMMEDIATELY
|
||||||
# FOLLOWED by the new value. Portable across GNU and BusyBox grep (Alpine CI) —
|
# FOLLOWED by the new value. Portable across GNU and BusyBox grep (Alpine CI) —
|
||||||
# `grep -z` (NUL-data) is a GNU-only extension BusyBox grep does NOT support, so
|
# `grep -z` (NUL-data) is a GNU-only extension BusyBox grep does NOT support, so
|
||||||
# match the reset line and require the value on the very next line (-A1) instead.
|
# match the reset line and require the value on the very next line (-A1) instead.
|
||||||
has_match -A1 '^OnUnitActiveSec=$' "$UD/$TIMER.d/cadence.conf" | has_match -qx 'OnUnitActiveSec=30min' \
|
grep -A1 '^OnUnitActiveSec=$' "$UD/$TIMER.d/cadence.conf" | grep -qx 'OnUnitActiveSec=30min' \
|
||||||
|| fail_msg "I14: the drop-in must use the blank-reset form (empty reset line, then the value)"
|
|| fail_msg "I14: the drop-in must use the blank-reset form (empty reset line, then the value)"
|
||||||
bash "$WI" verify-single "$TIMER" >/dev/null 2>&1 || fail_msg "I14: base(1h)+blank-reset(30min) must resolve exactly one OnUnitActiveUSec"
|
bash "$WI" verify-single "$TIMER" >/dev/null 2>&1 || fail_msg "I14: base(1h)+blank-reset(30min) must resolve exactly one OnUnitActiveUSec"
|
||||||
# NEGATIVE CONTROL: a drop-in WITHOUT the reset line appends -> base 1h + 30min -> two -> fail.
|
# NEGATIVE CONTROL: a drop-in WITHOUT the reset line appends -> base 1h + 30min -> two -> fail.
|
||||||
@@ -435,7 +428,7 @@ echo "== I14: per-class fallback cadence — blank-reset yields EXACTLY ONE OnUn
|
|||||||
|
|
||||||
echo
|
echo
|
||||||
if [ -s "$FAILFILE" ]; then
|
if [ -s "$FAILFILE" ]; then
|
||||||
echo "wake install harness: FAILED ($(count_lines . "$FAILFILE") assertion(s))" >&2
|
echo "wake install harness: FAILED ($(grep -c . "$FAILFILE") assertion(s))" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
echo "wake install harness: all invariants passed ($pass groups)"
|
echo "wake install harness: all invariants passed ($pass groups)"
|
||||||
|
|||||||
@@ -46,13 +46,6 @@
|
|||||||
set -uo pipefail
|
set -uo pipefail
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
# #973: three-valued grep assertion helpers (has_match/count_lines); init saves real stderr for abort loudness.
|
|
||||||
# shellcheck disable=SC1091
|
|
||||||
if ! . "$SCRIPT_DIR/_wake-common.sh"; then
|
|
||||||
echo "WAKE-ASSERT INIT ABORT: cannot source _wake-common.sh — suite ran ZERO wake assertions (#984)" >&2
|
|
||||||
exit 97
|
|
||||||
fi
|
|
||||||
wake_assert_init
|
|
||||||
PRE="$SCRIPT_DIR/preimage.sh"
|
PRE="$SCRIPT_DIR/preimage.sh"
|
||||||
STORE="$SCRIPT_DIR/store.sh"
|
STORE="$SCRIPT_DIR/store.sh"
|
||||||
DET="$SCRIPT_DIR/detector.sh"
|
DET="$SCRIPT_DIR/detector.sh"
|
||||||
@@ -209,9 +202,9 @@ echo "== P4: credential-store PATH refused — bytes never captured (acceptance
|
|||||||
sd="$(state_dir)"
|
sd="$(state_dir)"
|
||||||
row="$(tail -n1 "$sd/preimage/preimage-ledger.jsonl")"
|
row="$(tail -n1 "$sd/preimage/preimage-ledger.jsonl")"
|
||||||
[ "$(jq -r '.captured' <<<"$row")" = "false" ] || fail_msg "P4: row must record captured=false [$row]"
|
[ "$(jq -r '.captured' <<<"$row")" = "false" ] || fail_msg "P4: row must record captured=false [$row]"
|
||||||
jq -r '.refused // ""' <<<"$row" | has_match -qi 'path' || fail_msg "P4: refusal must name the path deny [$row]"
|
jq -r '.refused // ""' <<<"$row" | grep -qi 'path' || fail_msg "P4: refusal must name the path deny [$row]"
|
||||||
# THE invariant: the secret bytes exist NOWHERE under the wake state dir.
|
# THE invariant: the secret bytes exist NOWHERE under the wake state dir.
|
||||||
if has_match -rq "$secret" "$sd" 2>/dev/null; then
|
if grep -rq "$secret" "$sd" 2>/dev/null; then
|
||||||
fail_msg "P4: credential bytes leaked into the wake state dir"
|
fail_msg "P4: credential bytes leaked into the wake state dir"
|
||||||
fi
|
fi
|
||||||
) && ok
|
) && ok
|
||||||
@@ -232,8 +225,8 @@ echo "== P5: secret-SHAPED content refused (refusal, not redaction) =="
|
|||||||
sd="$(state_dir)"
|
sd="$(state_dir)"
|
||||||
row="$(tail -n1 "$sd/preimage/preimage-ledger.jsonl")"
|
row="$(tail -n1 "$sd/preimage/preimage-ledger.jsonl")"
|
||||||
[ "$(jq -r '.captured' <<<"$row")" = "false" ] || fail_msg "P5: row must record captured=false [$row]"
|
[ "$(jq -r '.captured' <<<"$row")" = "false" ] || fail_msg "P5: row must record captured=false [$row]"
|
||||||
jq -r '.refused // ""' <<<"$row" | has_match -qi 'secret' || fail_msg "P5: refusal must name secret-shaped content [$row]"
|
jq -r '.refused // ""' <<<"$row" | grep -qi 'secret' || fail_msg "P5: refusal must name secret-shaped content [$row]"
|
||||||
if has_match -rq "$tok" "$sd" 2>/dev/null; then
|
if grep -rq "$tok" "$sd" 2>/dev/null; then
|
||||||
fail_msg "P5: secret-shaped bytes leaked into the wake state dir"
|
fail_msg "P5: secret-shaped bytes leaked into the wake state dir"
|
||||||
fi
|
fi
|
||||||
) && ok
|
) && ok
|
||||||
@@ -282,7 +275,7 @@ echo "== P8: unresolvable adapter -> FAIL LOUD, never 'no change' (D2 class) =="
|
|||||||
unset WAKE_WATCH_LIST WAKE_PREIMAGE_EXTRA MOSAIC_HOME
|
unset WAKE_WATCH_LIST WAKE_PREIMAGE_EXTRA MOSAIC_HOME
|
||||||
out="$("$PRE" check --enqueue 2>&1)"; rc=$?
|
out="$("$PRE" check --enqueue 2>&1)"; rc=$?
|
||||||
[ "$rc" -ne 0 ] || fail_msg "P8: an unobservable preimage definition must FAIL LOUD (rc=0)"
|
[ "$rc" -ne 0 ] || fail_msg "P8: an unobservable preimage definition must FAIL LOUD (rc=0)"
|
||||||
echo "$out" | has_match -qi 'does not resolve' || fail_msg "P8: the failure must name the unresolvable adapter [$out]"
|
echo "$out" | grep -qi 'does not resolve' || fail_msg "P8: the failure must name the unresolvable adapter [$out]"
|
||||||
) && ok
|
) && ok
|
||||||
|
|
||||||
echo "== P9: corrupt ledger -> REFUSE to compare/re-baseline =="
|
echo "== P9: corrupt ledger -> REFUSE to compare/re-baseline =="
|
||||||
@@ -301,7 +294,7 @@ echo "== P9: corrupt ledger -> REFUSE to compare/re-baseline =="
|
|||||||
printf 'v2 changed\n' >"$fx/f.env"
|
printf 'v2 changed\n' >"$fx/f.env"
|
||||||
out="$("$PRE" check --enqueue 2>&1)"; rc=$?
|
out="$("$PRE" check --enqueue 2>&1)"; rc=$?
|
||||||
[ "$rc" -ne 0 ] || fail_msg "P9: a corrupt ledger must FAIL LOUD (rc=0)"
|
[ "$rc" -ne 0 ] || fail_msg "P9: a corrupt ledger must FAIL LOUD (rc=0)"
|
||||||
echo "$out" | has_match -qi 'unparseable' || fail_msg "P9: the failure must name the corrupt ledger [$out]"
|
echo "$out" | grep -qi 'unparseable' || fail_msg "P9: the failure must name the corrupt ledger [$out]"
|
||||||
[ "$(wc -l <"$sd/preimage/preimage-ledger.jsonl")" = "$r1" ] || fail_msg "P9: nothing may be appended over corrupt history"
|
[ "$(wc -l <"$sd/preimage/preimage-ledger.jsonl")" = "$r1" ] || fail_msg "P9: nothing may be appended over corrupt history"
|
||||||
) && ok
|
) && ok
|
||||||
|
|
||||||
@@ -321,7 +314,7 @@ echo "== P10: oversized file -> bytes refused, hash still recorded =="
|
|||||||
sd="$(state_dir)"
|
sd="$(state_dir)"
|
||||||
row="$(tail -n1 "$sd/preimage/preimage-ledger.jsonl")"
|
row="$(tail -n1 "$sd/preimage/preimage-ledger.jsonl")"
|
||||||
[ "$(jq -r '.captured' <<<"$row")" = "false" ] || fail_msg "P10: row must record captured=false [$row]"
|
[ "$(jq -r '.captured' <<<"$row")" = "false" ] || fail_msg "P10: row must record captured=false [$row]"
|
||||||
jq -r '.refused // ""' <<<"$row" | has_match -qi 'MAX_BYTES' || fail_msg "P10: refusal must name the size cap [$row]"
|
jq -r '.refused // ""' <<<"$row" | grep -qi 'MAX_BYTES' || fail_msg "P10: refusal must name the size cap [$row]"
|
||||||
sha="$(jq -r '.sha256' <<<"$row")"
|
sha="$(jq -r '.sha256' <<<"$row")"
|
||||||
[ "$sha" = "$(sha256sum "$fx/big.bin" | awk '{print $1}')" ] || fail_msg "P10: hash must still be recorded [$row]"
|
[ "$sha" = "$(sha256sum "$fx/big.bin" | awk '{print $1}')" ] || fail_msg "P10: hash must still be recorded [$row]"
|
||||||
[ ! -f "$sd/preimage/objects/$sha" ] || fail_msg "P10: oversized bytes must NOT be stored"
|
[ ! -f "$sd/preimage/objects/$sha" ] || fail_msg "P10: oversized bytes must NOT be stored"
|
||||||
@@ -369,7 +362,7 @@ echo "== P12: detector — preimage infra failure is LOUD but does not starve ob
|
|||||||
printf 'NOT-JSON-GARBAGE{{{\n' >"$sd/preimage/preimage-ledger.jsonl"
|
printf 'NOT-JSON-GARBAGE{{{\n' >"$sd/preimage/preimage-ledger.jsonl"
|
||||||
out="$("$DET" poll-once 2>&1)"; rc=$?
|
out="$("$DET" poll-once 2>&1)"; rc=$?
|
||||||
[ "$rc" -ne 0 ] || fail_msg "P12: the pass must exit non-zero on preimage infra failure"
|
[ "$rc" -ne 0 ] || fail_msg "P12: the pass must exit non-zero on preimage infra failure"
|
||||||
echo "$out" | has_match -qi 'preimage' || fail_msg "P12: the failure must name the preimage check [$out]"
|
echo "$out" | grep -qi 'preimage' || fail_msg "P12: the failure must name the preimage check [$out]"
|
||||||
n="$(find "$sd/detector" -name 'watch-*.hash' 2>/dev/null | wc -l | tr -d '[:space:]')"
|
n="$(find "$sd/detector" -name 'watch-*.hash' 2>/dev/null | wc -l | tr -d '[:space:]')"
|
||||||
[ "$n" -ge 1 ] || fail_msg "P12: source observation must still proceed (no watch hash baselined)"
|
[ "$n" -ge 1 ] || fail_msg "P12: source observation must still proceed (no watch hash baselined)"
|
||||||
) && ok
|
) && ok
|
||||||
@@ -404,10 +397,10 @@ echo "== P13: symlinked/renamed credential store refused on BOTH path forms (#96
|
|||||||
n="$(jq -r 'select(.captured == false) | .path' "$sd/preimage/preimage-ledger.jsonl" | wc -l | tr -d '[:space:]')"
|
n="$(jq -r 'select(.captured == false) | .path' "$sd/preimage/preimage-ledger.jsonl" | wc -l | tr -d '[:space:]')"
|
||||||
[ "$n" = "2" ] || fail_msg "P13: both decoys must record captured=false (got $n)"
|
[ "$n" = "2" ] || fail_msg "P13: both decoys must record captured=false (got $n)"
|
||||||
# THE invariant (decoy method): the planted bytes exist NOWHERE in state.
|
# THE invariant (decoy method): the planted bytes exist NOWHERE in state.
|
||||||
if has_match -rq "$s1" "$sd" 2>/dev/null; then
|
if grep -rq "$s1" "$sd" 2>/dev/null; then
|
||||||
fail_msg "P13: renamed-target store bytes leaked into the wake state dir"
|
fail_msg "P13: renamed-target store bytes leaked into the wake state dir"
|
||||||
fi
|
fi
|
||||||
if has_match -rq "$s2" "$sd" 2>/dev/null; then
|
if grep -rq "$s2" "$sd" 2>/dev/null; then
|
||||||
fail_msg "P13: prefix-less key bytes leaked into the wake state dir"
|
fail_msg "P13: prefix-less key bytes leaked into the wake state dir"
|
||||||
fi
|
fi
|
||||||
) && ok
|
) && ok
|
||||||
@@ -430,7 +423,7 @@ echo "== P14: detector.env-class key — safe WITHOUT opt-in by polarity, refuse
|
|||||||
sd="$(state_dir)"
|
sd="$(state_dir)"
|
||||||
row="$(tail -n1 "$sd/preimage/preimage-ledger.jsonl")"
|
row="$(tail -n1 "$sd/preimage/preimage-ledger.jsonl")"
|
||||||
[ "$(jq -r '.captured' <<<"$row")" = "false" ] || fail_msg "P14a: extra must be record-only without opt-in [$row]"
|
[ "$(jq -r '.captured' <<<"$row")" = "false" ] || fail_msg "P14a: extra must be record-only without opt-in [$row]"
|
||||||
if has_match -rq "$hm" "$sd" 2>/dev/null; then
|
if grep -rq "$hm" "$sd" 2>/dev/null; then
|
||||||
fail_msg "P14a: key bytes leaked without any opt-in"
|
fail_msg "P14a: key bytes leaked without any opt-in"
|
||||||
fi
|
fi
|
||||||
# (b) The operator opts the env file in (the exact error the old usage text
|
# (b) The operator opts the env file in (the exact error the old usage text
|
||||||
@@ -441,8 +434,8 @@ echo "== P14: detector.env-class key — safe WITHOUT opt-in by polarity, refuse
|
|||||||
[ "$rc" -eq 0 ] || fail_msg "P14b: refusal is not an infra failure (rc=$rc) [$out]"
|
[ "$rc" -eq 0 ] || fail_msg "P14b: refusal is not an infra failure (rc=$rc) [$out]"
|
||||||
row="$(tail -n1 "$sd/preimage/preimage-ledger.jsonl")"
|
row="$(tail -n1 "$sd/preimage/preimage-ledger.jsonl")"
|
||||||
[ "$(jq -r '.captured' <<<"$row")" = "false" ] || fail_msg "P14b: opted-in key material must still refuse [$row]"
|
[ "$(jq -r '.captured' <<<"$row")" = "false" ] || fail_msg "P14b: opted-in key material must still refuse [$row]"
|
||||||
jq -r '.refused // ""' <<<"$row" | has_match -qi 'secret' || fail_msg "P14b: refusal must name secret-shaped content [$row]"
|
jq -r '.refused // ""' <<<"$row" | grep -qi 'secret' || fail_msg "P14b: refusal must name secret-shaped content [$row]"
|
||||||
if has_match -rq "$hm" "$sd" 2>/dev/null; then
|
if grep -rq "$hm" "$sd" 2>/dev/null; then
|
||||||
fail_msg "P14b: key bytes leaked despite refusal"
|
fail_msg "P14b: key bytes leaked despite refusal"
|
||||||
fi
|
fi
|
||||||
) && ok
|
) && ok
|
||||||
@@ -495,7 +488,7 @@ echo "== P16: deleted ledger over surviving objects/ — REFUSE to re-baseline (
|
|||||||
printf '# v3\n' >>"$fx/adapter.sh"
|
printf '# v3\n' >>"$fx/adapter.sh"
|
||||||
out="$("$PRE" check --enqueue 2>&1)"; rc=$?
|
out="$("$PRE" check --enqueue 2>&1)"; rc=$?
|
||||||
[ "$rc" -ne 0 ] || fail_msg "P16: absent ledger over prior objects must FAIL LOUD (rc=0) [$out]"
|
[ "$rc" -ne 0 ] || fail_msg "P16: absent ledger over prior objects must FAIL LOUD (rc=0) [$out]"
|
||||||
echo "$out" | has_match -qi 'REFUSING to re-baseline' || fail_msg "P16: the failure must name the refusal [$out]"
|
echo "$out" | grep -qi 'REFUSING to re-baseline' || fail_msg "P16: the failure must name the refusal [$out]"
|
||||||
[ ! -e "$sd/preimage/preimage-ledger.jsonl" ] || fail_msg "P16: the ledger must NOT be silently recreated over deleted history"
|
[ ! -e "$sd/preimage/preimage-ledger.jsonl" ] || fail_msg "P16: the ledger must NOT be silently recreated over deleted history"
|
||||||
[ "$(ls "$sd/preimage/objects" | wc -l | tr -d '[:space:]')" = "$nobj" ] || fail_msg "P16: prior objects must remain untouched"
|
[ "$(ls "$sd/preimage/objects" | wc -l | tr -d '[:space:]')" = "$nobj" ] || fail_msg "P16: prior objects must remain untouched"
|
||||||
[ "$(depth)" = "0" ] || fail_msg "P16: the v2->v3 change must NOT be absorbed or enqueued from an unverifiable baseline (depth=$(depth))"
|
[ "$(depth)" = "0" ] || fail_msg "P16: the v2->v3 change must NOT be absorbed or enqueued from an unverifiable baseline (depth=$(depth))"
|
||||||
@@ -527,7 +520,7 @@ echo "== P17: allowlist symmetry — symlink to a DENIED target refuses; symlink
|
|||||||
sd="$(state_dir)"
|
sd="$(state_dir)"
|
||||||
crow="$(jq -c --arg p "$(realpath "$fx/settings.json")" 'select(.path == $p)' "$sd/preimage/preimage-ledger.jsonl" | tail -n1)"
|
crow="$(jq -c --arg p "$(realpath "$fx/settings.json")" 'select(.path == $p)' "$sd/preimage/preimage-ledger.jsonl" | tail -n1)"
|
||||||
[ "$(jq -r '.captured' <<<"$crow")" = "false" ] || fail_msg "P17: allowlisted symlink to a denied target must refuse [$crow]"
|
[ "$(jq -r '.captured' <<<"$crow")" = "false" ] || fail_msg "P17: allowlisted symlink to a denied target must refuse [$crow]"
|
||||||
if has_match -rq "$s" "$sd" 2>/dev/null; then
|
if grep -rq "$s" "$sd" 2>/dev/null; then
|
||||||
fail_msg "P17: credential bytes leaked via an allowlisted alias"
|
fail_msg "P17: credential bytes leaked via an allowlisted alias"
|
||||||
fi
|
fi
|
||||||
brow="$(jq -c --arg p "$(realpath "$fx/alias.cfg")" 'select(.path == $p)' "$sd/preimage/preimage-ledger.jsonl" | tail -n1)"
|
brow="$(jq -c --arg p "$(realpath "$fx/alias.cfg")" 'select(.path == $p)' "$sd/preimage/preimage-ledger.jsonl" | tail -n1)"
|
||||||
|
|||||||
@@ -29,13 +29,6 @@
|
|||||||
set -uo pipefail
|
set -uo pipefail
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
# #973: three-valued grep assertion helpers (has_match/count_lines); init saves real stderr for abort loudness.
|
|
||||||
# shellcheck disable=SC1091
|
|
||||||
if ! . "$SCRIPT_DIR/_wake-common.sh"; then
|
|
||||||
echo "WAKE-ASSERT INIT ABORT: cannot source _wake-common.sh — suite ran ZERO wake assertions (#984)" >&2
|
|
||||||
exit 97
|
|
||||||
fi
|
|
||||||
wake_assert_init
|
|
||||||
RECON="$SCRIPT_DIR/reconcile.sh"
|
RECON="$SCRIPT_DIR/reconcile.sh"
|
||||||
STORE="$SCRIPT_DIR/store.sh"
|
STORE="$SCRIPT_DIR/store.sh"
|
||||||
DET="$SCRIPT_DIR/detector.sh"
|
DET="$SCRIPT_DIR/detector.sh"
|
||||||
@@ -94,7 +87,7 @@ EOF
|
|||||||
out="$("$RECON" inventory 2>&1)"
|
out="$("$RECON" inventory 2>&1)"
|
||||||
rc=$?
|
rc=$?
|
||||||
[ "$rc" -eq 0 ] || fail_msg "R1: a complete inventory must PASS (exit 0) [$out]"
|
[ "$rc" -eq 0 ] || fail_msg "R1: a complete inventory must PASS (exit 0) [$out]"
|
||||||
echo "$out" | has_match -qi 'COMPLETE' || fail_msg "R1: a complete inventory must report COMPLETE [$out]"
|
echo "$out" | grep -qi 'COMPLETE' || fail_msg "R1: a complete inventory must report COMPLETE [$out]"
|
||||||
) && ok
|
) && ok
|
||||||
|
|
||||||
echo "== R2: OMITTED source -> FLAG (vacuous-pass prevented, not silently green) =="
|
echo "== R2: OMITTED source -> FLAG (vacuous-pass prevented, not silently green) =="
|
||||||
@@ -113,8 +106,8 @@ EOF
|
|||||||
out="$("$RECON" inventory 2>&1)"
|
out="$("$RECON" inventory 2>&1)"
|
||||||
rc=$?
|
rc=$?
|
||||||
[ "$rc" -ne 0 ] || fail_msg "R2: an omitted (declared-but-unwatched) source must FLAG (non-zero), not silently pass"
|
[ "$rc" -ne 0 ] || fail_msg "R2: an omitted (declared-but-unwatched) source must FLAG (non-zero), not silently pass"
|
||||||
echo "$out" | has_match -qi 'r2' || fail_msg "R2: the flag must name the omitted source r2 [$out]"
|
echo "$out" | grep -qi 'r2' || fail_msg "R2: the flag must name the omitted source r2 [$out]"
|
||||||
echo "$out" | has_match -qi 'vacuous' || fail_msg "R2: the flag must state the vacuous-pass is prevented [$out]"
|
echo "$out" | grep -qi 'vacuous' || fail_msg "R2: the flag must state the vacuous-pass is prevented [$out]"
|
||||||
) && ok
|
) && ok
|
||||||
|
|
||||||
echo "== R3: required_sources omission -> FLAG =="
|
echo "== R3: required_sources omission -> FLAG =="
|
||||||
@@ -133,7 +126,7 @@ EOF
|
|||||||
out="$("$RECON" inventory 2>&1)"
|
out="$("$RECON" inventory 2>&1)"
|
||||||
rc=$?
|
rc=$?
|
||||||
[ "$rc" -ne 0 ] || fail_msg "R3: a required_sources omission must FLAG (non-zero)"
|
[ "$rc" -ne 0 ] || fail_msg "R3: a required_sources omission must FLAG (non-zero)"
|
||||||
echo "$out" | has_match -qi "requires source 'r2'" || fail_msg "R3: the flag must name the required-but-omitted source r2 [$out]"
|
echo "$out" | grep -qi "requires source 'r2'" || fail_msg "R3: the flag must name the required-but-omitted source r2 [$out]"
|
||||||
) && ok
|
) && ok
|
||||||
|
|
||||||
echo "== R4: dangling watch reference -> FLAG =="
|
echo "== R4: dangling watch reference -> FLAG =="
|
||||||
@@ -151,7 +144,7 @@ EOF
|
|||||||
out="$("$RECON" inventory 2>&1)"
|
out="$("$RECON" inventory 2>&1)"
|
||||||
rc=$?
|
rc=$?
|
||||||
[ "$rc" -ne 0 ] || fail_msg "R4: a dangling reference must FLAG (non-zero)"
|
[ "$rc" -ne 0 ] || fail_msg "R4: a dangling reference must FLAG (non-zero)"
|
||||||
echo "$out" | has_match -qi 'dangling' || fail_msg "R4: the flag must state it is dangling [$out]"
|
echo "$out" | grep -qi 'dangling' || fail_msg "R4: the flag must state it is dangling [$out]"
|
||||||
) && ok
|
) && ok
|
||||||
|
|
||||||
echo "== R5/R6: pre-existing state -> UNACCOUNTED flag + enumerated into store; re-run 0 =="
|
echo "== R5/R6: pre-existing state -> UNACCOUNTED flag + enumerated into store; re-run 0 =="
|
||||||
@@ -180,14 +173,14 @@ EOF
|
|||||||
out="$("$RECON" reconcile 2>&1)"
|
out="$("$RECON" reconcile 2>&1)"
|
||||||
rc=$?
|
rc=$?
|
||||||
[ "$rc" -ne 0 ] || fail_msg "R5: pre-existing unaccounted state must FLAG (non-zero) on first reconcile"
|
[ "$rc" -ne 0 ] || fail_msg "R5: pre-existing unaccounted state must FLAG (non-zero) on first reconcile"
|
||||||
echo "$out" | has_match -qi 'UNACCOUNTED=2' || fail_msg "R5: both pre-existing sources must be UNACCOUNTED [$out]"
|
echo "$out" | grep -qi 'UNACCOUNTED=2' || fail_msg "R5: both pre-existing sources must be UNACCOUNTED [$out]"
|
||||||
# R6: enumerated into the durable store — one entry per pre-existing source.
|
# R6: enumerated into the durable store — one entry per pre-existing source.
|
||||||
[ "$(depth)" = "2" ] || fail_msg "R6: pre-existing state must be ENUMERATED into the store (depth 2), got $(depth)"
|
[ "$(depth)" = "2" ] || fail_msg "R6: pre-existing state must be ENUMERATED into the store (depth 2), got $(depth)"
|
||||||
# A follow-up reconcile now finds everything accounted -> 0 unaccounted.
|
# A follow-up reconcile now finds everything accounted -> 0 unaccounted.
|
||||||
out2="$("$RECON" reconcile 2>&1)"
|
out2="$("$RECON" reconcile 2>&1)"
|
||||||
rc2=$?
|
rc2=$?
|
||||||
[ "$rc2" -eq 0 ] || fail_msg "R6: a second reconcile must report 0 unaccounted (exit 0) [$out2]"
|
[ "$rc2" -eq 0 ] || fail_msg "R6: a second reconcile must report 0 unaccounted (exit 0) [$out2]"
|
||||||
echo "$out2" | has_match -qi 'UNACCOUNTED=0' || fail_msg "R6: second reconcile must be 0 unaccounted [$out2]"
|
echo "$out2" | grep -qi 'UNACCOUNTED=0' || fail_msg "R6: second reconcile must be 0 unaccounted [$out2]"
|
||||||
[ "$(depth)" = "2" ] || fail_msg "R6: a clean reconcile must NOT re-enumerate (depth still 2), got $(depth)"
|
[ "$(depth)" = "2" ] || fail_msg "R6: a clean reconcile must NOT re-enumerate (depth still 2), got $(depth)"
|
||||||
) && ok
|
) && ok
|
||||||
|
|
||||||
@@ -218,7 +211,7 @@ EOF
|
|||||||
out="$("$RECON" reconcile 2>&1)"
|
out="$("$RECON" reconcile 2>&1)"
|
||||||
rc=$?
|
rc=$?
|
||||||
[ "$rc" -eq 0 ] || fail_msg "R7: state reflected in the inbox must be ACCOUNTED (exit 0) [$out]"
|
[ "$rc" -eq 0 ] || fail_msg "R7: state reflected in the inbox must be ACCOUNTED (exit 0) [$out]"
|
||||||
echo "$out" | has_match -qi 'UNACCOUNTED=0' || fail_msg "R7: inbox-reflected state must be 0 unaccounted [$out]"
|
echo "$out" | grep -qi 'UNACCOUNTED=0' || fail_msg "R7: inbox-reflected state must be 0 unaccounted [$out]"
|
||||||
[ "$(depth)" = "1" ] || fail_msg "R7: reconciler must NOT double-enumerate inbox-reflected state (depth still 1), got $(depth)"
|
[ "$(depth)" = "1" ] || fail_msg "R7: reconciler must NOT double-enumerate inbox-reflected state (depth still 1), got $(depth)"
|
||||||
) && ok
|
) && ok
|
||||||
|
|
||||||
@@ -243,7 +236,7 @@ EOF
|
|||||||
out="$("$RECON" reconcile 2>&1)"
|
out="$("$RECON" reconcile 2>&1)"
|
||||||
rc=$?
|
rc=$?
|
||||||
[ "$rc" -ne 0 ] || fail_msg "R8: a source error must FAIL LOUD (non-zero exit)"
|
[ "$rc" -ne 0 ] || fail_msg "R8: a source error must FAIL LOUD (non-zero exit)"
|
||||||
echo "$out" | has_match -qi 'FAIL LOUD' || fail_msg "R8: the source error must be loud [$out]"
|
echo "$out" | grep -qi 'FAIL LOUD' || fail_msg "R8: the source error must be loud [$out]"
|
||||||
[ "$(depth)" = "0" ] || fail_msg "R8: a failed observation must NOT enumerate anything, got depth $(depth)"
|
[ "$(depth)" = "0" ] || fail_msg "R8: a failed observation must NOT enumerate anything, got depth $(depth)"
|
||||||
) && ok
|
) && ok
|
||||||
|
|
||||||
@@ -285,7 +278,7 @@ EOF
|
|||||||
out="$("$RECON" reconcile 2>&1)"
|
out="$("$RECON" reconcile 2>&1)"
|
||||||
rc=$?
|
rc=$?
|
||||||
[ "$rc" -ne 0 ] || fail_msg "R9: pre-existing beta is unaccounted on this pass -> must FLAG (non-zero) [$out]"
|
[ "$rc" -ne 0 ] || fail_msg "R9: pre-existing beta is unaccounted on this pass -> must FLAG (non-zero) [$out]"
|
||||||
echo "$out" | has_match -qi 'UNACCOUNTED=1' || fail_msg "R9: only beta should be unaccounted (alpha is inbox-accounted) [$out]"
|
echo "$out" | grep -qi 'UNACCOUNTED=1' || fail_msg "R9: only beta should be unaccounted (alpha is inbox-accounted) [$out]"
|
||||||
[ "$(depth)" = "2" ] || fail_msg "R9: reconciler must ENUMERATE beta into the co-fed store (depth 2), got $(depth)"
|
[ "$(depth)" = "2" ] || fail_msg "R9: reconciler must ENUMERATE beta into the co-fed store (depth 2), got $(depth)"
|
||||||
|
|
||||||
# A further detector delta on beta must allocate 3 — the unified allocator
|
# A further detector delta on beta must allocate 3 — the unified allocator
|
||||||
@@ -299,8 +292,8 @@ EOF
|
|||||||
# already used, so this set would contain a duplicate (alias).
|
# already used, so this set would contain a duplicate (alias).
|
||||||
seqs="$("$STORE" drain | jq -r '.observed_seq' | sort -n | tr '\n' ' ')"
|
seqs="$("$STORE" drain | jq -r '.observed_seq' | sort -n | tr '\n' ' ')"
|
||||||
[ "$seqs" = "1 2 3 " ] || fail_msg "R9: co-fed observed_seqs must be distinct+gapless '1 2 3', got '$seqs' (a duplicate = the aliasing #908 dissolved)"
|
[ "$seqs" = "1 2 3 " ] || fail_msg "R9: co-fed observed_seqs must be distinct+gapless '1 2 3', got '$seqs' (a duplicate = the aliasing #908 dissolved)"
|
||||||
ndistinct="$("$STORE" drain | jq -r '.observed_seq' | sort -nu | count_lines .)"
|
ndistinct="$("$STORE" drain | jq -r '.observed_seq' | sort -nu | grep -c .)"
|
||||||
ntotal="$("$STORE" drain | jq -r '.observed_seq' | count_lines .)"
|
ntotal="$("$STORE" drain | jq -r '.observed_seq' | grep -c .)"
|
||||||
[ "$ndistinct" = "$ntotal" ] || fail_msg "R9: aliasing detected — $ntotal entries but only $ndistinct distinct observed_seq"
|
[ "$ndistinct" = "$ntotal" ] || fail_msg "R9: aliasing detected — $ntotal entries but only $ndistinct distinct observed_seq"
|
||||||
# The contiguous-prefix CONSUMED contract holds over the co-fed seqs.
|
# The contiguous-prefix CONSUMED contract holds over the co-fed seqs.
|
||||||
"$STORE" consume --upto 3 >/dev/null 2>&1 || fail_msg "R9: CONSUMED 3 must succeed over the gapless co-fed prefix"
|
"$STORE" consume --upto 3 >/dev/null 2>&1 || fail_msg "R9: CONSUMED 3 must succeed over the gapless co-fed prefix"
|
||||||
@@ -340,7 +333,7 @@ EOF
|
|||||||
out="$("$RECON" reconcile 2>&1)"
|
out="$("$RECON" reconcile 2>&1)"
|
||||||
rc=$?
|
rc=$?
|
||||||
[ "$rc" -eq 0 ] || fail_msg "R10: a consumed state must be ACCOUNTED (exit 0, no spurious rc=1 CRITICAL) [$out]"
|
[ "$rc" -eq 0 ] || fail_msg "R10: a consumed state must be ACCOUNTED (exit 0, no spurious rc=1 CRITICAL) [$out]"
|
||||||
echo "$out" | has_match -qi 'UNACCOUNTED=0' || fail_msg "R10: a consumed state must be 0 unaccounted (no re-enumeration) [$out]"
|
echo "$out" | grep -qi 'UNACCOUNTED=0' || fail_msg "R10: a consumed state must be 0 unaccounted (no re-enumeration) [$out]"
|
||||||
[ "$(depth)" = "0" ] || fail_msg "R10: reconciler must NOT re-enumerate a consumed state (depth still 0 = no duplicate wake), got $(depth)"
|
[ "$(depth)" = "0" ] || fail_msg "R10: reconciler must NOT re-enumerate a consumed state (depth still 0 = no duplicate wake), got $(depth)"
|
||||||
) && ok
|
) && ok
|
||||||
|
|
||||||
@@ -379,7 +372,7 @@ EOF
|
|||||||
[ "$rc" -ne 0 ] || fail_msg "R11: a genuine gap (r2) must still FLAG (non-zero) [$out]"
|
[ "$rc" -ne 0 ] || fail_msg "R11: a genuine gap (r2) must still FLAG (non-zero) [$out]"
|
||||||
# After #932: ONLY r2 is unaccounted (r1 suppressed by the store record). On
|
# After #932: ONLY r2 is unaccounted (r1 suppressed by the store record). On
|
||||||
# BASE both r1 and r2 re-enumerate (UNACCOUNTED=2) -> this assertion is red-first.
|
# BASE both r1 and r2 re-enumerate (UNACCOUNTED=2) -> this assertion is red-first.
|
||||||
echo "$out" | has_match -qi 'UNACCOUNTED=1' || fail_msg "R11: exactly ONE source (the gap r2) must be unaccounted; the consumed r1 must be suppressed [$out]"
|
echo "$out" | grep -qi 'UNACCOUNTED=1' || fail_msg "R11: exactly ONE source (the gap r2) must be unaccounted; the consumed r1 must be suppressed [$out]"
|
||||||
# Prove precisely WHICH state re-enumerated: the gap r2 IS enumerated, the
|
# Prove precisely WHICH state re-enumerated: the gap r2 IS enumerated, the
|
||||||
# consumed r1 is NOT. (base re-enumerates r1 too -> the r1-absent assertion is
|
# consumed r1 is NOT. (base re-enumerates r1 too -> the r1-absent assertion is
|
||||||
# red-first; the r2-present assertion holds both before and after = no over-suppression.)
|
# red-first; the r2-present assertion holds both before and after = no over-suppression.)
|
||||||
@@ -390,7 +383,7 @@ EOF
|
|||||||
|
|
||||||
echo
|
echo
|
||||||
if [ -s "$FAILFILE" ]; then
|
if [ -s "$FAILFILE" ]; then
|
||||||
echo "wake reconcile harness: FAILED ($(count_lines . "$FAILFILE") assertion(s))" >&2
|
echo "wake reconcile harness: FAILED ($(grep -c . "$FAILFILE") assertion(s))" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
echo "wake reconcile harness: all invariants passed ($pass groups)"
|
echo "wake reconcile harness: all invariants passed ($pass groups)"
|
||||||
|
|||||||
@@ -42,13 +42,6 @@
|
|||||||
set -uo pipefail
|
set -uo pipefail
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
# #973: three-valued grep assertion helpers (has_match/count_lines); init saves real stderr for abort loudness.
|
|
||||||
# shellcheck disable=SC1091
|
|
||||||
if ! . "$SCRIPT_DIR/_wake-common.sh"; then
|
|
||||||
echo "WAKE-ASSERT INIT ABORT: cannot source _wake-common.sh — suite ran ZERO wake assertions (#984)" >&2
|
|
||||||
exit 97
|
|
||||||
fi
|
|
||||||
wake_assert_init
|
|
||||||
STORE="$SCRIPT_DIR/store.sh"
|
STORE="$SCRIPT_DIR/store.sh"
|
||||||
ACK="$SCRIPT_DIR/ack.sh"
|
ACK="$SCRIPT_DIR/ack.sh"
|
||||||
|
|
||||||
@@ -103,14 +96,14 @@ echo "== T1: three-cursor advancement =="
|
|||||||
"$STORE" enqueue --seq 1 --class actionable --locators '{"repo":"r","issue":1}' >/dev/null
|
"$STORE" enqueue --seq 1 --class actionable --locators '{"repo":"r","issue":1}' >/dev/null
|
||||||
"$STORE" enqueue --seq 2 --class actionable --locators '{"repo":"r","issue":2}' >/dev/null
|
"$STORE" enqueue --seq 2 --class actionable --locators '{"repo":"r","issue":2}' >/dev/null
|
||||||
cur="$("$STORE" cursors)"
|
cur="$("$STORE" cursors)"
|
||||||
echo "$cur" | has_match -q 'observed_seq=2' || fail_msg "T1: observed_seq should be 2 after enqueue 1,2 [$cur]"
|
echo "$cur" | grep -q 'observed_seq=2' || fail_msg "T1: observed_seq should be 2 after enqueue 1,2 [$cur]"
|
||||||
echo "$cur" | has_match -q 'consumed_seq=0' || fail_msg "T1: consumed_seq must NOT advance on enqueue (only on CONSUMED ack) [$cur]"
|
echo "$cur" | grep -q 'consumed_seq=0' || fail_msg "T1: consumed_seq must NOT advance on enqueue (only on CONSUMED ack) [$cur]"
|
||||||
echo "$cur" | has_match -q 'pending_depth=2' || fail_msg "T1: pending depth should be 2 [$cur]"
|
echo "$cur" | grep -q 'pending_depth=2' || fail_msg "T1: pending depth should be 2 [$cur]"
|
||||||
# consumed_seq advances only on CONSUMED.
|
# consumed_seq advances only on CONSUMED.
|
||||||
"$STORE" consume --upto 2 >/dev/null
|
"$STORE" consume --upto 2 >/dev/null
|
||||||
cur="$("$STORE" cursors)"
|
cur="$("$STORE" cursors)"
|
||||||
echo "$cur" | has_match -q 'consumed_seq=2' || fail_msg "T1: consumed_seq should be 2 after CONSUMED 2 [$cur]"
|
echo "$cur" | grep -q 'consumed_seq=2' || fail_msg "T1: consumed_seq should be 2 after CONSUMED 2 [$cur]"
|
||||||
echo "$cur" | has_match -q 'pending_depth=0' || fail_msg "T1: pending drained after CONSUMED [$cur]"
|
echo "$cur" | grep -q 'pending_depth=0' || fail_msg "T1: pending drained after CONSUMED [$cur]"
|
||||||
) && ok
|
) && ok
|
||||||
|
|
||||||
echo "== T2: digest coalesce-REPLACE vs actionable APPEND =="
|
echo "== T2: digest coalesce-REPLACE vs actionable APPEND =="
|
||||||
@@ -123,16 +116,16 @@ echo "== T2: digest coalesce-REPLACE vs actionable APPEND =="
|
|||||||
"$STORE" enqueue --seq 3 --class digest --locators '{"head":"bbb"}' >/dev/null
|
"$STORE" enqueue --seq 3 --class digest --locators '{"head":"bbb"}' >/dev/null
|
||||||
"$STORE" enqueue --seq 4 --class human --locators '{"from":"peer"}' >/dev/null
|
"$STORE" enqueue --seq 4 --class human --locators '{"from":"peer"}' >/dev/null
|
||||||
out="$("$STORE" drain)"
|
out="$("$STORE" drain)"
|
||||||
ndigest="$(printf '%s\n' "$out" | jq -c 'select(.class=="digest")' | count_lines . || true)"
|
ndigest="$(printf '%s\n' "$out" | jq -c 'select(.class=="digest")' | grep -c . || true)"
|
||||||
[ "$ndigest" = "1" ] || fail_msg "T2: digest must COALESCE to a single pending entry, got $ndigest"
|
[ "$ndigest" = "1" ] || fail_msg "T2: digest must COALESCE to a single pending entry, got $ndigest"
|
||||||
head="$(printf '%s\n' "$out" | jq -r 'select(.class=="digest") | .locators.head')"
|
head="$(printf '%s\n' "$out" | jq -r 'select(.class=="digest") | .locators.head')"
|
||||||
[ "$head" = "bbb" ] || fail_msg "T2: newest digest must REPLACE prior (expected bbb, got $head)"
|
[ "$head" = "bbb" ] || fail_msg "T2: newest digest must REPLACE prior (expected bbb, got $head)"
|
||||||
nactionable="$(printf '%s\n' "$out" | jq -c 'select(.class=="actionable")' | count_lines . || true)"
|
nactionable="$(printf '%s\n' "$out" | jq -c 'select(.class=="actionable")' | grep -c . || true)"
|
||||||
nhuman="$(printf '%s\n' "$out" | jq -c 'select(.class=="human")' | count_lines . || true)"
|
nhuman="$(printf '%s\n' "$out" | jq -c 'select(.class=="human")' | grep -c . || true)"
|
||||||
[ "$nactionable" = "1" ] || fail_msg "T2: actionable must APPEND (never replaced), got $nactionable"
|
[ "$nactionable" = "1" ] || fail_msg "T2: actionable must APPEND (never replaced), got $nactionable"
|
||||||
[ "$nhuman" = "1" ] || fail_msg "T2: human must APPEND / stay durable, got $nhuman"
|
[ "$nhuman" = "1" ] || fail_msg "T2: human must APPEND / stay durable, got $nhuman"
|
||||||
# Durability never bypassed: all classes present in the durable store.
|
# Durability never bypassed: all classes present in the durable store.
|
||||||
total="$(printf '%s\n' "$out" | count_lines . || true)"
|
total="$(printf '%s\n' "$out" | grep -c . || true)"
|
||||||
[ "$total" = "3" ] || fail_msg "T2: durable store should hold digest+actionable+human = 3, got $total"
|
[ "$total" = "3" ] || fail_msg "T2: durable store should hold digest+actionable+human = 3, got $total"
|
||||||
) && ok
|
) && ok
|
||||||
|
|
||||||
@@ -148,7 +141,7 @@ echo "== T3: contiguous-prefix CONSUMED (reject ack N while N-1 unconsumed) =="
|
|||||||
fail_msg "T3: CONSUMED 3 must be REJECTED while seq 2 is a gap (unconsumed)"
|
fail_msg "T3: CONSUMED 3 must be REJECTED while seq 2 is a gap (unconsumed)"
|
||||||
fi
|
fi
|
||||||
cur="$("$STORE" cursors)"
|
cur="$("$STORE" cursors)"
|
||||||
echo "$cur" | has_match -q 'consumed_seq=0' || fail_msg "T3: rejected CONSUMED must NOT advance the cursor [$cur]"
|
echo "$cur" | grep -q 'consumed_seq=0' || fail_msg "T3: rejected CONSUMED must NOT advance the cursor [$cur]"
|
||||||
# Also reject via the ack wrapper.
|
# Also reject via the ack wrapper.
|
||||||
if "$ACK" consumed --upto 3 --no-sync >/dev/null 2>&1; then
|
if "$ACK" consumed --upto 3 --no-sync >/dev/null 2>&1; then
|
||||||
fail_msg "T3: ack.sh CONSUMED 3 must be REJECTED over a gap"
|
fail_msg "T3: ack.sh CONSUMED 3 must be REJECTED over a gap"
|
||||||
@@ -157,11 +150,11 @@ echo "== T3: contiguous-prefix CONSUMED (reject ack N while N-1 unconsumed) =="
|
|||||||
"$STORE" enqueue --seq 2 --class actionable --locators '{}' >/dev/null
|
"$STORE" enqueue --seq 2 --class actionable --locators '{}' >/dev/null
|
||||||
"$ACK" consumed --upto 3 --no-sync >/dev/null 2>&1 || fail_msg "T3: CONSUMED 3 should succeed once gap at 2 is filled"
|
"$ACK" consumed --upto 3 --no-sync >/dev/null 2>&1 || fail_msg "T3: CONSUMED 3 should succeed once gap at 2 is filled"
|
||||||
cur="$("$STORE" cursors)"
|
cur="$("$STORE" cursors)"
|
||||||
echo "$cur" | has_match -q 'consumed_seq=3' || fail_msg "T3: consumed_seq should be 3 after contiguous prefix filled [$cur]"
|
echo "$cur" | grep -q 'consumed_seq=3' || fail_msg "T3: consumed_seq should be 3 after contiguous prefix filled [$cur]"
|
||||||
# Cumulative + can't regress: CONSUMED 2 after 3 is an idempotent no-op.
|
# Cumulative + can't regress: CONSUMED 2 after 3 is an idempotent no-op.
|
||||||
"$ACK" consumed --upto 2 --no-sync >/dev/null 2>&1 || fail_msg "T3: cumulative CONSUMED 2 (<=3) should be an idempotent success"
|
"$ACK" consumed --upto 2 --no-sync >/dev/null 2>&1 || fail_msg "T3: cumulative CONSUMED 2 (<=3) should be an idempotent success"
|
||||||
cur="$("$STORE" cursors)"
|
cur="$("$STORE" cursors)"
|
||||||
echo "$cur" | has_match -q 'consumed_seq=3' || fail_msg "T3: cursor must not regress below 3 [$cur]"
|
echo "$cur" | grep -q 'consumed_seq=3' || fail_msg "T3: cursor must not regress below 3 [$cur]"
|
||||||
) && ok
|
) && ok
|
||||||
|
|
||||||
echo "== T4: wake_id DELIVERY-dedup (dup delivery = re-RECEIVE, never re-action) =="
|
echo "== T4: wake_id DELIVERY-dedup (dup delivery = re-RECEIVE, never re-action) =="
|
||||||
@@ -176,7 +169,7 @@ echo "== T4: wake_id DELIVERY-dedup (dup delivery = re-RECEIVE, never re-action)
|
|||||||
[ "$r2" = "DUP" ] || fail_msg "T4: duplicate delivery of same wake_id should be DUP (re-RECEIVE), got '$r2'"
|
[ "$r2" = "DUP" ] || fail_msg "T4: duplicate delivery of same wake_id should be DUP (re-RECEIVE), got '$r2'"
|
||||||
# RECEIVED (delivery) must NEVER advance consumed_seq (delivery != consumption).
|
# RECEIVED (delivery) must NEVER advance consumed_seq (delivery != consumption).
|
||||||
cur="$("$STORE" cursors)"
|
cur="$("$STORE" cursors)"
|
||||||
echo "$cur" | has_match -q 'consumed_seq=0' || fail_msg "T4: RECEIVED must not advance consumed_seq [$cur]"
|
echo "$cur" | grep -q 'consumed_seq=0' || fail_msg "T4: RECEIVED must not advance consumed_seq [$cur]"
|
||||||
) && ok
|
) && ok
|
||||||
|
|
||||||
echo "== T5: atomic write-tmp+rename survives crash mid-write =="
|
echo "== T5: atomic write-tmp+rename survives crash mid-write =="
|
||||||
@@ -196,7 +189,7 @@ echo "== T5: atomic write-tmp+rename survives crash mid-write =="
|
|||||||
drained="$("$STORE" drain)"
|
drained="$("$STORE" drain)"
|
||||||
printf '%s\n' "$drained" | jq -e . >/dev/null 2>&1 || fail_msg "T5: drain returned non-JSON — garbage temp corrupted the store"
|
printf '%s\n' "$drained" | jq -e . >/dev/null 2>&1 || fail_msg "T5: drain returned non-JSON — garbage temp corrupted the store"
|
||||||
printf '%s\n' "$drained" | stream_any '.observed_seq==1' || fail_msg "T5: committed entry (seq 1) lost after simulated crash"
|
printf '%s\n' "$drained" | stream_any '.observed_seq==1' || fail_msg "T5: committed entry (seq 1) lost after simulated crash"
|
||||||
printf '%s\n' "$drained" | has_match -q 999 && fail_msg "T5: uncommitted garbage (seq 999) leaked into the live store"
|
printf '%s\n' "$drained" | grep -q 999 && fail_msg "T5: uncommitted garbage (seq 999) leaked into the live store"
|
||||||
# A subsequent real mutation must succeed DESPITE the stale temp present.
|
# A subsequent real mutation must succeed DESPITE the stale temp present.
|
||||||
"$STORE" enqueue --seq 2 --class actionable --locators '{"issue":2}' >/dev/null || fail_msg "T5: enqueue after crash temp failed"
|
"$STORE" enqueue --seq 2 --class actionable --locators '{"issue":2}' >/dev/null || fail_msg "T5: enqueue after crash temp failed"
|
||||||
# #927: the enqueue HOT PATH must NOT reap tmp files — an unconditional delete
|
# #927: the enqueue HOT PATH must NOT reap tmp files — an unconditional delete
|
||||||
@@ -205,7 +198,7 @@ echo "== T5: atomic write-tmp+rename survives crash mid-write =="
|
|||||||
# untouched by an enqueue; reaping it on the hot path is exactly the bug.
|
# untouched by an enqueue; reaping it on the hot path is exactly the bug.
|
||||||
[ -e "$STATE_DIR/.wake.tmp.crash12" ] || fail_msg "T5: the enqueue hot path must NOT delete tmp files off its own write (that hot-path reap was the #927 clobber)"
|
[ -e "$STATE_DIR/.wake.tmp.crash12" ] || fail_msg "T5: the enqueue hot path must NOT delete tmp files off its own write (that hot-path reap was the #927 clobber)"
|
||||||
d2="$("$STORE" drain)"
|
d2="$("$STORE" drain)"
|
||||||
[ "$(printf '%s\n' "$d2" | has_match -c .)" = "2" ] || fail_msg "T5: store not healthy after crash+recovery (expected 2 entries)"
|
[ "$(printf '%s\n' "$d2" | grep -c .)" = "2" ] || fail_msg "T5: store not healthy after crash+recovery (expected 2 entries)"
|
||||||
# Bounded accumulation is preserved via a MAINTENANCE reap (store.sh init /
|
# Bounded accumulation is preserved via a MAINTENANCE reap (store.sh init /
|
||||||
# detector tick), age-scoped so it only removes DEMONSTRABLY-orphaned tmps
|
# detector tick), age-scoped so it only removes DEMONSTRABLY-orphaned tmps
|
||||||
# (older than any plausible in-flight write) and never a live one. Age the
|
# (older than any plausible in-flight write) and never a live one. Age the
|
||||||
@@ -216,7 +209,7 @@ echo "== T5: atomic write-tmp+rename survives crash mid-write =="
|
|||||||
"$STORE" init >/dev/null 2>&1 || fail_msg "T5: store.sh init (maintenance) failed"
|
"$STORE" init >/dev/null 2>&1 || fail_msg "T5: store.sh init (maintenance) failed"
|
||||||
[ -e "$STATE_DIR/.wake.tmp.crash12" ] && fail_msg "T5: maintenance reap (store.sh init) must remove a demonstrably-orphaned stale temp (bounded accumulation)"
|
[ -e "$STATE_DIR/.wake.tmp.crash12" ] && fail_msg "T5: maintenance reap (store.sh init) must remove a demonstrably-orphaned stale temp (bounded accumulation)"
|
||||||
d3="$("$STORE" drain)"
|
d3="$("$STORE" drain)"
|
||||||
[ "$(printf '%s\n' "$d3" | has_match -c .)" = "2" ] || fail_msg "T5: store not healthy after maintenance reap (expected 2 entries)"
|
[ "$(printf '%s\n' "$d3" | grep -c .)" = "2" ] || fail_msg "T5: store not healthy after maintenance reap (expected 2 entries)"
|
||||||
printf '%s\n' "$d3" | jq -e . >/dev/null 2>&1 || fail_msg "T5: drain returned non-JSON after maintenance reap"
|
printf '%s\n' "$d3" | jq -e . >/dev/null 2>&1 || fail_msg "T5: drain returned non-JSON after maintenance reap"
|
||||||
) && ok
|
) && ok
|
||||||
|
|
||||||
@@ -261,12 +254,12 @@ echo "== T6: durability survives restart (retain until CONSUMED) =="
|
|||||||
# "Session 2": a brand-new process (this subshell invocation of store.sh) must
|
# "Session 2": a brand-new process (this subshell invocation of store.sh) must
|
||||||
# see the persisted entries — nothing was held in memory.
|
# see the persisted entries — nothing was held in memory.
|
||||||
d="$("$STORE" drain)"
|
d="$("$STORE" drain)"
|
||||||
[ "$(printf '%s\n' "$d" | has_match -c .)" = "2" ] || fail_msg "T6: entries not retained across restart (expected 2)"
|
[ "$(printf '%s\n' "$d" | grep -c .)" = "2" ] || fail_msg "T6: entries not retained across restart (expected 2)"
|
||||||
printf '%s\n' "$d" | stream_any '.class=="human"' || fail_msg "T6: a human message was lost across restart (durability bypassed)"
|
printf '%s\n' "$d" | stream_any '.class=="human"' || fail_msg "T6: a human message was lost across restart (durability bypassed)"
|
||||||
# Retained UNTIL consumed: after CONSUMED they are released.
|
# Retained UNTIL consumed: after CONSUMED they are released.
|
||||||
"$STORE" consume --upto 2 >/dev/null
|
"$STORE" consume --upto 2 >/dev/null
|
||||||
d2="$("$STORE" drain)"
|
d2="$("$STORE" drain)"
|
||||||
n2="$(printf '%s\n' "$d2" | count_lines . || true)"
|
n2="$(printf '%s\n' "$d2" | grep -c . || true)"
|
||||||
[ "$n2" = "0" ] || fail_msg "T6: entries should be released only AFTER CONSUMED (still $n2 pending)"
|
[ "$n2" = "0" ] || fail_msg "T6: entries should be released only AFTER CONSUMED (still $n2 pending)"
|
||||||
) && ok
|
) && ok
|
||||||
|
|
||||||
@@ -303,7 +296,7 @@ EOF
|
|||||||
end="$(date +%s.%N)"
|
end="$(date +%s.%N)"
|
||||||
elapsed_ms="$(awk -v s="$start" -v e="$end" 'BEGIN { printf "%d", (e - s) * 1000 }')"
|
elapsed_ms="$(awk -v s="$start" -v e="$end" 'BEGIN { printf "%d", (e - s) * 1000 }')"
|
||||||
[ "$elapsed_ms" -lt 1500 ] || fail_msg "T7: ack path blocked ${elapsed_ms}ms — must return without waiting on the background sync"
|
[ "$elapsed_ms" -lt 1500 ] || fail_msg "T7: ack path blocked ${elapsed_ms}ms — must return without waiting on the background sync"
|
||||||
echo "$out" | has_match -q 'CONSUMED 1' || fail_msg "T7: CONSUMED not reported [$out]"
|
echo "$out" | grep -q 'CONSUMED 1' || fail_msg "T7: CONSUMED not reported [$out]"
|
||||||
# Local write is durable IMMEDIATELY (no network needed to record the ack).
|
# Local write is durable IMMEDIATELY (no network needed to record the ack).
|
||||||
STATE_DIR="$WAKE_STATE_HOME/default"
|
STATE_DIR="$WAKE_STATE_HOME/default"
|
||||||
jq_any "$STATE_DIR/ack-ledger.jsonl" '.type=="CONSUMED" and .upto==1' ||
|
jq_any "$STATE_DIR/ack-ledger.jsonl" '.type=="CONSUMED" and .upto==1' ||
|
||||||
@@ -328,8 +321,8 @@ echo "== T8: SOLE store-side allocator — enqueue (no --seq) allocates contiguo
|
|||||||
[ "$s1" = "1" ] || fail_msg "T8: first store-allocated observed_seq must be 1, got '$s1'"
|
[ "$s1" = "1" ] || fail_msg "T8: first store-allocated observed_seq must be 1, got '$s1'"
|
||||||
[ "$s2" = "2" ] || fail_msg "T8: second store-allocated observed_seq must be 2 (contiguous), got '$s2'"
|
[ "$s2" = "2" ] || fail_msg "T8: second store-allocated observed_seq must be 2 (contiguous), got '$s2'"
|
||||||
cur="$("$STORE" cursors)"
|
cur="$("$STORE" cursors)"
|
||||||
echo "$cur" | has_match -q 'observed_seq=2' || fail_msg "T8: observed_seq cursor should be 2 [$cur]"
|
echo "$cur" | grep -q 'observed_seq=2' || fail_msg "T8: observed_seq cursor should be 2 [$cur]"
|
||||||
echo "$cur" | has_match -q 'pending_depth=2' || fail_msg "T8: both allocations must be durably enqueued [$cur]"
|
echo "$cur" | grep -q 'pending_depth=2' || fail_msg "T8: both allocations must be durably enqueued [$cur]"
|
||||||
# ANTI-SWALLOW: consume the prefix, then a LEGACY explicit --seq inside the
|
# ANTI-SWALLOW: consume the prefix, then a LEGACY explicit --seq inside the
|
||||||
# consumed prefix must FAIL LOUD (never the old silent seq<=consumed no-op).
|
# consumed prefix must FAIL LOUD (never the old silent seq<=consumed no-op).
|
||||||
"$STORE" consume --upto 2 >/dev/null
|
"$STORE" consume --upto 2 >/dev/null
|
||||||
@@ -338,7 +331,7 @@ echo "== T8: SOLE store-side allocator — enqueue (no --seq) allocates contiguo
|
|||||||
fail_msg "T8: explicit --seq 1 <= consumed_seq 2 must FAIL LOUD (anti-swallow), not be a silent no-op"
|
fail_msg "T8: explicit --seq 1 <= consumed_seq 2 must FAIL LOUD (anti-swallow), not be a silent no-op"
|
||||||
fi
|
fi
|
||||||
err="$("$STORE" enqueue --seq 1 --class actionable --locators '{}' 2>&1 || true)"
|
err="$("$STORE" enqueue --seq 1 --class actionable --locators '{}' 2>&1 || true)"
|
||||||
echo "$err" | has_match -qi 'anti-swallow' || fail_msg "T8: the refusal must name the anti-swallow guarantee [$err]"
|
echo "$err" | grep -qi 'anti-swallow' || fail_msg "T8: the refusal must name the anti-swallow guarantee [$err]"
|
||||||
after_depth="$("$STORE" cursors | sed -n 's/pending_depth=//p')"
|
after_depth="$("$STORE" cursors | sed -n 's/pending_depth=//p')"
|
||||||
[ "$before_depth" = "$after_depth" ] || fail_msg "T8: a refused enqueue must not mutate the store (depth $before_depth->$after_depth)"
|
[ "$before_depth" = "$after_depth" ] || fail_msg "T8: a refused enqueue must not mutate the store (depth $before_depth->$after_depth)"
|
||||||
# And the NEXT real allocation is > consumed (2) — never inside the prefix.
|
# And the NEXT real allocation is > consumed (2) — never inside the prefix.
|
||||||
@@ -450,8 +443,8 @@ if command -v flock >/dev/null 2>&1; then
|
|||||||
[ -n "$a" ] && [ -n "$b" ] || fail_msg "T10: both concurrent enqueues must print an allocated seq (got '$a','$b')"
|
[ -n "$a" ] && [ -n "$b" ] || fail_msg "T10: both concurrent enqueues must print an allocated seq (got '$a','$b')"
|
||||||
[ "$a" != "$b" ] || fail_msg "T10: two concurrent enqueues must get DISTINCT seqs, both got '$a' (aliasing / lost write without the lock)"
|
[ "$a" != "$b" ] || fail_msg "T10: two concurrent enqueues must get DISTINCT seqs, both got '$a' (aliasing / lost write without the lock)"
|
||||||
cur="$("$STORE" cursors)"
|
cur="$("$STORE" cursors)"
|
||||||
echo "$cur" | has_match -q 'observed_seq=2' || fail_msg "T10: observed_seq must reach 2 after two enqueues [$cur]"
|
echo "$cur" | grep -q 'observed_seq=2' || fail_msg "T10: observed_seq must reach 2 after two enqueues [$cur]"
|
||||||
echo "$cur" | has_match -q 'pending_depth=2' || fail_msg "T10: both entries must be durably stored (no lost write) [$cur]"
|
echo "$cur" | grep -q 'pending_depth=2' || fail_msg "T10: both entries must be durably stored (no lost write) [$cur]"
|
||||||
# The two allocated seqs are exactly {1,2} (distinct, contiguous, gapless).
|
# The two allocated seqs are exactly {1,2} (distinct, contiguous, gapless).
|
||||||
lo="$a"; hi="$b"; [ "$a" -gt "$b" ] && { lo="$b"; hi="$a"; }
|
lo="$a"; hi="$b"; [ "$a" -gt "$b" ] && { lo="$b"; hi="$a"; }
|
||||||
{ [ "$lo" = "1" ] && [ "$hi" = "2" ]; } || fail_msg "T10: concurrent seqs must be {1,2}, got {$lo,$hi}"
|
{ [ "$lo" = "1" ] && [ "$hi" = "2" ]; } || fail_msg "T10: concurrent seqs must be {1,2}, got {$lo,$hi}"
|
||||||
@@ -499,7 +492,7 @@ echo "== T11: final observed_seq cursor write FAILURE — fail loud + observed.s
|
|||||||
# the _atomic_write failure and returns 0).
|
# the _atomic_write failure and returns 0).
|
||||||
[ "$rc" -ne 0 ] || fail_msg "T11: an enqueue whose FINAL cursor write fails must EXIT NON-ZERO (fail loud), got rc=$rc"
|
[ "$rc" -ne 0 ] || fail_msg "T11: an enqueue whose FINAL cursor write fails must EXIT NON-ZERO (fail loud), got rc=$rc"
|
||||||
# The loud diagnostic names the cursor write (not a generic error).
|
# The loud diagnostic names the cursor write (not a generic error).
|
||||||
has_match -qi 'cursor' "$errfile" || fail_msg "T11: the failure diagnostic must name the observed_seq cursor write [$(cat "$errfile")]"
|
grep -qi 'cursor' "$errfile" || fail_msg "T11: the failure diagnostic must name the observed_seq cursor write [$(cat "$errfile")]"
|
||||||
|
|
||||||
# (2) The cursor did NOT advance — the allocation is NOT committed.
|
# (2) The cursor did NOT advance — the allocation is NOT committed.
|
||||||
obs_after="$("$STORE" cursors | sed -n 's/^observed_seq=//p')"
|
obs_after="$("$STORE" cursors | sed -n 's/^observed_seq=//p')"
|
||||||
@@ -543,7 +536,7 @@ echo "== T12: #932 — consume records the last-consumed observed_hash per (kind
|
|||||||
r2h="$(jq -sr '[ .[] | select(.kind=="repo" and .id=="r2") ] | .[0].observed_hash' "$rec" 2>/dev/null)"
|
r2h="$(jq -sr '[ .[] | select(.kind=="repo" and .id=="r2") ] | .[0].observed_hash' "$rec" 2>/dev/null)"
|
||||||
[ "$r2h" = "HASH-C" ] || fail_msg "T12: repo/r2 must record HASH-C, got '$r2h'"
|
[ "$r2h" = "HASH-C" ] || fail_msg "T12: repo/r2 must record HASH-C, got '$r2h'"
|
||||||
# ADDITIVE: existing on-disk state files are unchanged/consistent post-consume.
|
# ADDITIVE: existing on-disk state files are unchanged/consistent post-consume.
|
||||||
"$STORE" cursors | has_match -q 'consumed_seq=3' || fail_msg "T12: consumed_seq must be 3 after CONSUMED 3"
|
"$STORE" cursors | grep -q 'consumed_seq=3' || fail_msg "T12: consumed_seq must be 3 after CONSUMED 3"
|
||||||
) && ok
|
) && ok
|
||||||
|
|
||||||
echo "== T13: #946 — ordinary consume REFUSES to advance past a quarantined seq (store + ack paths) =="
|
echo "== T13: #946 — ordinary consume REFUSES to advance past a quarantined seq (store + ack paths) =="
|
||||||
@@ -562,10 +555,10 @@ echo "== T13: #946 — ordinary consume REFUSES to advance past a quarantined se
|
|||||||
fail_msg "T13: ordinary consume --upto 3 must be REFUSED while seq 2 is quarantined"
|
fail_msg "T13: ordinary consume --upto 3 must be REFUSED while seq 2 is quarantined"
|
||||||
fi
|
fi
|
||||||
err="$("$STORE" consume --upto 3 2>&1 >/dev/null || true)"
|
err="$("$STORE" consume --upto 3 2>&1 >/dev/null || true)"
|
||||||
echo "$err" | has_match -q 'quarantined seq(s): 2' || fail_msg "T13: the refusal must NAME the quarantined seq [$err]"
|
echo "$err" | grep -q 'quarantined seq(s): 2' || fail_msg "T13: the refusal must NAME the quarantined seq [$err]"
|
||||||
echo "$err" | has_match -q -- '--force-past-quarantine' || fail_msg "T13: the refusal must NAME the force flag [$err]"
|
echo "$err" | grep -q -- '--force-past-quarantine' || fail_msg "T13: the refusal must NAME the force flag [$err]"
|
||||||
cur="$("$STORE" cursors)"
|
cur="$("$STORE" cursors)"
|
||||||
echo "$cur" | has_match -q 'consumed_seq=0' || fail_msg "T13: a refused consume must NOT advance the cursor [$cur]"
|
echo "$cur" | grep -q 'consumed_seq=0' || fail_msg "T13: a refused consume must NOT advance the cursor [$cur]"
|
||||||
# BELOW the quarantined seq the ordinary path is unaffected.
|
# BELOW the quarantined seq the ordinary path is unaffected.
|
||||||
"$STORE" consume --upto 1 >/dev/null 2>&1 || fail_msg "T13: consume --upto 1 (below the quarantined seq) must succeed"
|
"$STORE" consume --upto 1 >/dev/null 2>&1 || fail_msg "T13: consume --upto 1 (below the quarantined seq) must succeed"
|
||||||
# The ack wrapper propagates the refusal — no ordinary-path bypass exists.
|
# The ack wrapper propagates the refusal — no ordinary-path bypass exists.
|
||||||
@@ -573,7 +566,7 @@ echo "== T13: #946 — ordinary consume REFUSES to advance past a quarantined se
|
|||||||
fail_msg "T13: ack.sh consumed --upto 3 must be REFUSED while seq 2 is quarantined (ordinary-path bypass)"
|
fail_msg "T13: ack.sh consumed --upto 3 must be REFUSED while seq 2 is quarantined (ordinary-path bypass)"
|
||||||
fi
|
fi
|
||||||
cur="$("$STORE" cursors)"
|
cur="$("$STORE" cursors)"
|
||||||
echo "$cur" | has_match -q 'consumed_seq=1' || fail_msg "T13: cursor must still be 1 after the refused ack [$cur]"
|
echo "$cur" | grep -q 'consumed_seq=1' || fail_msg "T13: cursor must still be 1 after the refused ack [$cur]"
|
||||||
) && ok
|
) && ok
|
||||||
|
|
||||||
echo "== T14: #946 — FORCED step-over is LOUD, prunes the set, and NEVER fabricates a consumed-hash row for the quarantined entry =="
|
echo "== T14: #946 — FORCED step-over is LOUD, prunes the set, and NEVER fabricates a consumed-hash row for the quarantined entry =="
|
||||||
@@ -592,9 +585,9 @@ echo "== T14: #946 — FORCED step-over is LOUD, prunes the set, and NEVER fabri
|
|||||||
rc=$?
|
rc=$?
|
||||||
[ "$rc" -eq 0 ] || fail_msg "T14: forced consume must succeed (rc=$rc) [$(cat "$errf")]"
|
[ "$rc" -eq 0 ] || fail_msg "T14: forced consume must succeed (rc=$rc) [$(cat "$errf")]"
|
||||||
[ "$out" = "3" ] || fail_msg "T14: forced consume must print the new cursor 3, got '$out'"
|
[ "$out" = "3" ] || fail_msg "T14: forced consume must print the new cursor 3, got '$out'"
|
||||||
has_match -q 'FORCED PAST QUARANTINE' "$errf" || fail_msg "T14: the forced path must be LOUD on stderr [$(cat "$errf")]"
|
grep -q 'FORCED PAST QUARANTINE' "$errf" || fail_msg "T14: the forced path must be LOUD on stderr [$(cat "$errf")]"
|
||||||
has_match -q 'seq 2' "$errf" || fail_msg "T14: the forced-path diagnostic must name the stepped-over seq 2 [$(cat "$errf")]"
|
grep -q 'seq 2' "$errf" || fail_msg "T14: the forced-path diagnostic must name the stepped-over seq 2 [$(cat "$errf")]"
|
||||||
"$STORE" cursors | has_match -q 'consumed_seq=3' || fail_msg "T14: forced consume must advance the cursor to 3"
|
"$STORE" cursors | grep -q 'consumed_seq=3' || fail_msg "T14: forced consume must advance the cursor to 3"
|
||||||
# NO FALSE WITNESS: the quarantined entry (repo/b) was NEVER delivered, so no
|
# NO FALSE WITNESS: the quarantined entry (repo/b) was NEVER delivered, so no
|
||||||
# consumed-hash row may exist for it — even on the forced path (the reconciler
|
# consumed-hash row may exist for it — even on the forced path (the reconciler
|
||||||
# re-enumerating it once is safe-but-noisy; a false witness silences it
|
# re-enumerating it once is safe-but-noisy; a false witness silences it
|
||||||
@@ -604,7 +597,7 @@ echo "== T14: #946 — FORCED step-over is LOUD, prunes the set, and NEVER fabri
|
|||||||
jq_any "$rec" '.kind=="repo" and .id=="b"' && fail_msg "T14: the quarantined entry repo/b must have NO consumed-hash row (a row would witness a delivery that never happened)"
|
jq_any "$rec" '.kind=="repo" and .id=="b"' && fail_msg "T14: the quarantined entry repo/b must have NO consumed-hash row (a row would witness a delivery that never happened)"
|
||||||
# The stepped-over seq is PRUNED from the set (it is consumed now; a stale
|
# The stepped-over seq is PRUNED from the set (it is consumed now; a stale
|
||||||
# entry would re-refuse forever).
|
# entry would re-refuse forever).
|
||||||
has_match -qxF '2' "$qf" 2>/dev/null && fail_msg "T14: seq 2 must be PRUNED from quarantined.set after the forced step-over"
|
grep -qxF '2' "$qf" 2>/dev/null && fail_msg "T14: seq 2 must be PRUNED from quarantined.set after the forced step-over"
|
||||||
# The ack wrapper's force flag passes through, stays LOUD on stderr, and
|
# The ack wrapper's force flag passes through, stays LOUD on stderr, and
|
||||||
# still reports a CLEAN cursor line on stdout.
|
# still reports a CLEAN cursor line on stdout.
|
||||||
"$STORE" enqueue --class actionable --locators '{"kind":"repo","id":"d","observed_hash":"HD"}' >/dev/null
|
"$STORE" enqueue --class actionable --locators '{"kind":"repo","id":"d","observed_hash":"HD"}' >/dev/null
|
||||||
@@ -614,8 +607,8 @@ echo "== T14: #946 — FORCED step-over is LOUD, prunes the set, and NEVER fabri
|
|||||||
out2="$("$ACK" consumed --upto 5 --no-sync --force-past-quarantine 2>"$errf2")"
|
out2="$("$ACK" consumed --upto 5 --no-sync --force-past-quarantine 2>"$errf2")"
|
||||||
rc2=$?
|
rc2=$?
|
||||||
[ "$rc2" -eq 0 ] || fail_msg "T14: forced ack must succeed (rc=$rc2) [$(cat "$errf2")]"
|
[ "$rc2" -eq 0 ] || fail_msg "T14: forced ack must succeed (rc=$rc2) [$(cat "$errf2")]"
|
||||||
echo "$out2" | has_match -q '^CONSUMED 5$' || fail_msg "T14: forced ack must report a CLEAN cursor line 'CONSUMED 5', got '$out2'"
|
echo "$out2" | grep -q '^CONSUMED 5$' || fail_msg "T14: forced ack must report a CLEAN cursor line 'CONSUMED 5', got '$out2'"
|
||||||
has_match -q 'FORCED PAST QUARANTINE' "$errf2" || fail_msg "T14: the forced-path loudness must survive the ack wrapper (stderr) [$(cat "$errf2")]"
|
grep -q 'FORCED PAST QUARANTINE' "$errf2" || fail_msg "T14: the forced-path loudness must survive the ack wrapper (stderr) [$(cat "$errf2")]"
|
||||||
jq_any "$rec" '.kind=="repo" and .id=="e"' && fail_msg "T14: the quarantined repo/e must have NO consumed-hash row via the forced ack path either"
|
jq_any "$rec" '.kind=="repo" and .id=="e"' && fail_msg "T14: the quarantined repo/e must have NO consumed-hash row via the forced ack path either"
|
||||||
true
|
true
|
||||||
) && ok
|
) && ok
|
||||||
@@ -678,10 +671,10 @@ echo "== T16: #946 — quarantine-audit: a consumed-hash row matching a dead-let
|
|||||||
if "$STORE" quarantine-audit >"$rep" 2>&1; then
|
if "$STORE" quarantine-audit >"$rep" 2>&1; then
|
||||||
fail_msg "T16: report-mode audit must exit NON-ZERO when false rows exist"
|
fail_msg "T16: report-mode audit must exit NON-ZERO when false rows exist"
|
||||||
fi
|
fi
|
||||||
has_match -q 'FALSE WITNESS' "$rep" || fail_msg "T16: the audit must name the false row loudly [$(cat "$rep")]"
|
grep -q 'FALSE WITNESS' "$rep" || fail_msg "T16: the audit must name the false row loudly [$(cat "$rep")]"
|
||||||
has_match -q '"id":"X"' "$rep" || fail_msg "T16: the audit must identify the false row (repo/X@1) [$(cat "$rep")]"
|
grep -q '"id":"X"' "$rep" || fail_msg "T16: the audit must identify the false row (repo/X@1) [$(cat "$rep")]"
|
||||||
has_match -q '"id":"Y"' "$rep" && fail_msg "T16: the clean row repo/Y must NOT be flagged"
|
grep -q '"id":"Y"' "$rep" && fail_msg "T16: the clean row repo/Y must NOT be flagged"
|
||||||
has_match -q '"id":"Z"' "$rep" && fail_msg "T16: the HEALED row repo/Z@4 must NOT be flagged (its dead-letter evidence is seq 3 with a different hash)"
|
grep -q '"id":"Z"' "$rep" && fail_msg "T16: the HEALED row repo/Z@4 must NOT be flagged (its dead-letter evidence is seq 3 with a different hash)"
|
||||||
# Report mode modifies nothing.
|
# Report mode modifies nothing.
|
||||||
jq_any "$rec" '.id=="X"' || fail_msg "T16: report mode must not modify the record"
|
jq_any "$rec" '.id=="X"' || fail_msg "T16: report mode must not modify the record"
|
||||||
# REPAIR: exactly the false row is removed; the dead-letter LEDGER is history
|
# REPAIR: exactly the false row is removed; the dead-letter LEDGER is history
|
||||||
@@ -690,10 +683,10 @@ echo "== T16: #946 — quarantine-audit: a consumed-hash row matching a dead-let
|
|||||||
jq_any "$rec" '.id=="X"' && fail_msg "T16: --repair must REMOVE the provably-false X row"
|
jq_any "$rec" '.id=="X"' && fail_msg "T16: --repair must REMOVE the provably-false X row"
|
||||||
jq_any "$rec" '.id=="Y" and .observed_hash=="HY"' || fail_msg "T16: --repair must keep the clean Y row"
|
jq_any "$rec" '.id=="Y" and .observed_hash=="HY"' || fail_msg "T16: --repair must keep the clean Y row"
|
||||||
jq_any "$rec" '.id=="Z" and .observed_hash=="HZ-NEW" and .observed_seq==4' || fail_msg "T16: --repair must keep the healed Z@4 row"
|
jq_any "$rec" '.id=="Z" and .observed_hash=="HZ-NEW" and .observed_seq==4' || fail_msg "T16: --repair must keep the healed Z@4 row"
|
||||||
[ "$(has_match -c . "$dl")" = "2" ] || fail_msg "T16: the dead-letter LEDGER must be untouched by --repair"
|
[ "$(grep -c . "$dl")" = "2" ] || fail_msg "T16: the dead-letter LEDGER must be untouched by --repair"
|
||||||
# Clean re-audit: OK, exit 0.
|
# Clean re-audit: OK, exit 0.
|
||||||
"$STORE" quarantine-audit >"$TMP_ROOT/t16.ok" 2>&1 || fail_msg "T16: a clean audit must exit 0 [$(cat "$TMP_ROOT/t16.ok")]"
|
"$STORE" quarantine-audit >"$TMP_ROOT/t16.ok" 2>&1 || fail_msg "T16: a clean audit must exit 0 [$(cat "$TMP_ROOT/t16.ok")]"
|
||||||
has_match -qi 'OK' "$TMP_ROOT/t16.ok" || fail_msg "T16: a clean audit must say OK [$(cat "$TMP_ROOT/t16.ok")]"
|
grep -qi 'OK' "$TMP_ROOT/t16.ok" || fail_msg "T16: a clean audit must say OK [$(cat "$TMP_ROOT/t16.ok")]"
|
||||||
) && ok
|
) && ok
|
||||||
|
|
||||||
echo "== T17: #952 — the clean-sweep message names BOTH unprovable residual classes; a surviving-but-empty-hash dead-letter row is correctly NOT convicted =="
|
echo "== T17: #952 — the clean-sweep message names BOTH unprovable residual classes; a surviving-but-empty-hash dead-letter row is correctly NOT convicted =="
|
||||||
@@ -729,20 +722,20 @@ echo "== T17: #952 — the clean-sweep message names BOTH unprovable residual cl
|
|||||||
# match can never fire: this is unprovable class 2, NOT a false witness.
|
# match can never fire: this is unprovable class 2, NOT a false witness.
|
||||||
rep="$TMP_ROOT/t17.rep"
|
rep="$TMP_ROOT/t17.rep"
|
||||||
"$STORE" quarantine-audit >"$rep" 2>&1 || fail_msg "T17: the audit must exit 0 — nothing here is provable [$(cat "$rep")]"
|
"$STORE" quarantine-audit >"$rep" 2>&1 || fail_msg "T17: the audit must exit 0 — nothing here is provable [$(cat "$rep")]"
|
||||||
has_match -q 'FALSE WITNESS' "$rep" && fail_msg "T17: the empty-hash evidence must NOT convict (the predicate is correct and must not change) [$(cat "$rep")]"
|
grep -q 'FALSE WITNESS' "$rep" && fail_msg "T17: the empty-hash evidence must NOT convict (the predicate is correct and must not change) [$(cat "$rep")]"
|
||||||
# The wording under test (#952): BOTH residual classes, named.
|
# The wording under test (#952): BOTH residual classes, named.
|
||||||
has_match -qi 'pruned' "$rep" || fail_msg "T17: clean sweep must name residual class 1 — evidence pruned away [$(cat "$rep")]"
|
grep -qi 'pruned' "$rep" || fail_msg "T17: clean sweep must name residual class 1 — evidence pruned away [$(cat "$rep")]"
|
||||||
has_match -qi 'empty observed_hash' "$rep" || fail_msg "T17: clean sweep must name residual class 2 — surviving evidence with an empty observed_hash [$(cat "$rep")]"
|
grep -qi 'empty observed_hash' "$rep" || fail_msg "T17: clean sweep must name residual class 2 — surviving evidence with an empty observed_hash [$(cat "$rep")]"
|
||||||
# --repair on a clean sweep removes nothing: the unprovable row survives.
|
# --repair on a clean sweep removes nothing: the unprovable row survives.
|
||||||
"$STORE" quarantine-audit --repair >"$TMP_ROOT/t17.fix" 2>&1 || fail_msg "T17: --repair on a clean sweep must exit 0 [$(cat "$TMP_ROOT/t17.fix")]"
|
"$STORE" quarantine-audit --repair >"$TMP_ROOT/t17.fix" 2>&1 || fail_msg "T17: --repair on a clean sweep must exit 0 [$(cat "$TMP_ROOT/t17.fix")]"
|
||||||
jq_any "$rec" '.kind=="bench" and .observed_seq==13 and .observed_hash=="H-REAL-CONSUMED"' ||
|
jq_any "$rec" '.kind=="bench" and .observed_seq==13 and .observed_hash=="H-REAL-CONSUMED"' ||
|
||||||
fail_msg "T17: --repair must NOT remove the unprovable bench@13 row — the audit only removes what the ledger can convict"
|
fail_msg "T17: --repair must NOT remove the unprovable bench@13 row — the audit only removes what the ledger can convict"
|
||||||
[ "$(count_lines . "$dl")" = "1" ] || fail_msg "T17: the dead-letter LEDGER must be untouched"
|
[ "$(grep -c . "$dl")" = "1" ] || fail_msg "T17: the dead-letter LEDGER must be untouched"
|
||||||
) && ok
|
) && ok
|
||||||
|
|
||||||
echo
|
echo
|
||||||
if [ -s "$FAILFILE" ]; then
|
if [ -s "$FAILFILE" ]; then
|
||||||
echo "wake store/ack harness: FAILED ($(count_lines . "$FAILFILE") assertion(s))" >&2
|
echo "wake store/ack harness: FAILED ($(grep -c . "$FAILFILE") assertion(s))" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
echo "wake store/ack harness: all invariants passed ($pass groups)"
|
echo "wake store/ack harness: all invariants passed ($pass groups)"
|
||||||
|
|||||||
@@ -32,13 +32,6 @@
|
|||||||
set -uo pipefail
|
set -uo pipefail
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
# #973: three-valued grep assertion helpers (has_match/count_lines); init saves real stderr for abort loudness.
|
|
||||||
# shellcheck disable=SC1091
|
|
||||||
if ! . "$SCRIPT_DIR/_wake-common.sh"; then
|
|
||||||
echo "WAKE-ASSERT INIT ABORT: cannot source _wake-common.sh — suite ran ZERO wake assertions (#984)" >&2
|
|
||||||
exit 97
|
|
||||||
fi
|
|
||||||
wake_assert_init
|
|
||||||
STORE="$SCRIPT_DIR/store.sh"
|
STORE="$SCRIPT_DIR/store.sh"
|
||||||
|
|
||||||
command -v jq >/dev/null 2>&1 || {
|
command -v jq >/dev/null 2>&1 || {
|
||||||
@@ -154,7 +147,7 @@ EOF
|
|||||||
wait "$pa" 2>/dev/null || true
|
wait "$pa" 2>/dev/null || true
|
||||||
else
|
else
|
||||||
# Confirm the window is genuinely open: A holds a live in-flight tmp now.
|
# Confirm the window is genuinely open: A holds a live in-flight tmp now.
|
||||||
n_tmp="$(find "$STATE_DIR" -maxdepth 1 -name '.wake.tmp.*' -type f | count_lines . || true)"
|
n_tmp="$(find "$STATE_DIR" -maxdepth 1 -name '.wake.tmp.*' -type f | grep -c . || true)"
|
||||||
[ "$n_tmp" -ge 1 ] || fail_msg "T-RACE: expected A's live in-flight tmp to exist while A holds the lock (got $n_tmp)"
|
[ "$n_tmp" -ge 1 ] || fail_msg "T-RACE: expected A's live in-flight tmp to exist while A holds the lock (got $n_tmp)"
|
||||||
|
|
||||||
# B: its PRE-LOCK _wake_init_dir runs now (pre-fix: deletes A's live tmp),
|
# B: its PRE-LOCK _wake_init_dir runs now (pre-fix: deletes A's live tmp),
|
||||||
@@ -180,7 +173,7 @@ EOF
|
|||||||
if [ "$ra" -ne 0 ]; then
|
if [ "$ra" -ne 0 ]; then
|
||||||
fail_msg "T-RACE: enqueue A was SPURIOUSLY ABORTED (rc=$ra) — its live in-flight tmp was deleted by B's pre-lock cleanup [#927]. stderr: $(tr '\n' ' ' <"$a_err")"
|
fail_msg "T-RACE: enqueue A was SPURIOUSLY ABORTED (rc=$ra) — its live in-flight tmp was deleted by B's pre-lock cleanup [#927]. stderr: $(tr '\n' ' ' <"$a_err")"
|
||||||
fi
|
fi
|
||||||
if has_match -q 'durable pending write FAILED' "$a_err" 2>/dev/null; then
|
if grep -q 'durable pending write FAILED' "$a_err" 2>/dev/null; then
|
||||||
fail_msg "T-RACE: enqueue A reported 'durable pending write FAILED' — the exact #927 spurious abort (its tmp was clobbered mid-write by a concurrent pre-lock cleanup)."
|
fail_msg "T-RACE: enqueue A reported 'durable pending write FAILED' — the exact #927 spurious abort (its tmp was clobbered mid-write by a concurrent pre-lock cleanup)."
|
||||||
fi
|
fi
|
||||||
[ "$rb" -eq 0 ] || fail_msg "T-RACE: enqueue B should also succeed (rc=$rb). stderr: $(tr '\n' ' ' <"$b_err")"
|
[ "$rb" -eq 0 ] || fail_msg "T-RACE: enqueue B should also succeed (rc=$rb). stderr: $(tr '\n' ' ' <"$b_err")"
|
||||||
@@ -196,20 +189,20 @@ EOF
|
|||||||
|
|
||||||
# #908 store invariants: both durably landed, cursor reached 2, no burned seq.
|
# #908 store invariants: both durably landed, cursor reached 2, no burned seq.
|
||||||
cur="$("$STORE" cursors)"
|
cur="$("$STORE" cursors)"
|
||||||
echo "$cur" | has_match -q 'observed_seq=2' || fail_msg "T-RACE: observed_seq must reach 2 (both allocations committed) [$cur]"
|
echo "$cur" | grep -q 'observed_seq=2' || fail_msg "T-RACE: observed_seq must reach 2 (both allocations committed) [$cur]"
|
||||||
echo "$cur" | has_match -q 'pending_depth=2' || fail_msg "T-RACE: both entries must be durably stored — no lost/aborted write [$cur]"
|
echo "$cur" | grep -q 'pending_depth=2' || fail_msg "T-RACE: both entries must be durably stored — no lost/aborted write [$cur]"
|
||||||
echo "$cur" | has_match -q 'consumed_seq=0' || fail_msg "T-RACE: enqueue must never advance consumed_seq [$cur]"
|
echo "$cur" | grep -q 'consumed_seq=0' || fail_msg "T-RACE: enqueue must never advance consumed_seq [$cur]"
|
||||||
# Gapless: the contiguous prefix 1..2 is consumable (no interior gap/burn).
|
# Gapless: the contiguous prefix 1..2 is consumable (no interior gap/burn).
|
||||||
"$STORE" consume --upto 2 >/dev/null 2>&1 || fail_msg "T-RACE: CONSUMED 2 must succeed — seqs {1,2} are gapless (no burned seq)"
|
"$STORE" consume --upto 2 >/dev/null 2>&1 || fail_msg "T-RACE: CONSUMED 2 must succeed — seqs {1,2} are gapless (no burned seq)"
|
||||||
# No stale tmp left leaking either.
|
# No stale tmp left leaking either.
|
||||||
n_leak="$(find "$STATE_DIR" -maxdepth 1 -name '.wake.tmp.*' -type f | count_lines . || true)"
|
n_leak="$(find "$STATE_DIR" -maxdepth 1 -name '.wake.tmp.*' -type f | grep -c . || true)"
|
||||||
[ "$n_leak" = "0" ] || fail_msg "T-RACE: $n_leak stale tmp file(s) leaked after both enqueues committed"
|
[ "$n_leak" = "0" ] || fail_msg "T-RACE: $n_leak stale tmp file(s) leaked after both enqueues committed"
|
||||||
fi
|
fi
|
||||||
) && ok
|
) && ok
|
||||||
|
|
||||||
echo
|
echo
|
||||||
if [ -s "$FAILFILE" ]; then
|
if [ -s "$FAILFILE" ]; then
|
||||||
echo "wake store enqueue-race harness: FAILED ($(count_lines . "$FAILFILE") assertion(s)) — #927 TOCTOU reproduced (RED)" >&2
|
echo "wake store enqueue-race harness: FAILED ($(grep -c . "$FAILFILE") assertion(s)) — #927 TOCTOU reproduced (RED)" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
echo "wake store enqueue-race harness: all invariants passed ($pass group) — #927 race closed (GREEN)"
|
echo "wake store enqueue-race harness: all invariants passed ($pass group) — #927 race closed (GREEN)"
|
||||||
|
|||||||
@@ -1,318 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""check-973.py — computation backend for the #973 validation harness.
|
|
||||||
|
|
||||||
Everything here derives from exactly two inputs: the frozen denominator
|
|
||||||
artifact (denominator-089615f.json) and the SOURCE TEXT of the ten converted
|
|
||||||
suites at the current tree. It never reads the ledger — set arithmetic against
|
|
||||||
the runtime trace belongs to validate-973.sh, so the two legs of the
|
|
||||||
comparison come from independent code paths.
|
|
||||||
|
|
||||||
Subcommands (all print sorted, stable output; non-zero exit on any failure):
|
|
||||||
|
|
||||||
expected The expected coordinate set from the ARTIFACT: one
|
|
||||||
"<helper> <file>:<line+7>" row per denominator row (+7 = 3
|
|
||||||
converter header lines + 4 lines from the #984 source guard,
|
|
||||||
uniform across all ten suites).
|
|
||||||
Multi-grep lines stay ONE coordinate.
|
|
||||||
|
|
||||||
static The converted-site inventory from the SOURCE TEXT at the current
|
|
||||||
tree: every non-comment line bearing a has_match/count_lines
|
|
||||||
token, as "<helper> <file>:<line>". Independent of the artifact
|
|
||||||
row list, so `expected == static` is a real check on the
|
|
||||||
conversion, not a tautology. (Amendment ONE, leg 1: the ledger is
|
|
||||||
an execution trace, not an inventory — the inventory must come
|
|
||||||
from the text.)
|
|
||||||
|
|
||||||
arms The forced-error arm list: the 19 denominator canaries plus one
|
|
||||||
E-form arm (store-ack:733→740, a $(count_lines) capture compared
|
|
||||||
afterward — the A6 shape) plus one F-form arm (quarantine:560→567,
|
|
||||||
the multi-grep pipeline capture), as "<helper> <file>:<line+7>
|
|
||||||
<form>". Both extras are asserted to exist in the artifact with
|
|
||||||
the expected form — a renumber that moved them fails here, not
|
|
||||||
silently downstream.
|
|
||||||
|
|
||||||
sweep Residual sweep: the denominator's own classifier (ported from the
|
|
||||||
frozen derivation) over the ten suites at the current tree must
|
|
||||||
find ZERO unconverted verdict-form grep sites; and, IN THE SAME
|
|
||||||
RUN, eight specimens (six per-form + two absorb-branch probes, #985)
|
|
||||||
planted into a temp copy of a real
|
|
||||||
suite must ALL be found with their correct forms — an instrument
|
|
||||||
that reports zero must first be seen finding what it claims to
|
|
||||||
find (A5).
|
|
||||||
"""
|
|
||||||
|
|
||||||
import json
|
|
||||||
import re
|
|
||||||
import shutil
|
|
||||||
import sys
|
|
||||||
import tempfile
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
HERE = Path(__file__).resolve().parent
|
|
||||||
WAKE = HERE.parent
|
|
||||||
ART = HERE / "denominator-089615f.json"
|
|
||||||
|
|
||||||
HEADER_SHIFT = 7 # 3 converter header lines after SCRIPT_DIR + 4 lines from the
|
|
||||||
# #984 source guard (1-line `. _wake-common.sh && wake_assert_init` became a 5-line
|
|
||||||
# guarded block) — both uniform across all ten suites, both above every site.
|
|
||||||
|
|
||||||
# The two hand-picked extra arms (base coordinates; forms asserted at load).
|
|
||||||
EXTRA_ARMS = [
|
|
||||||
("test-wake-store-ack.sh", 733, "E-count-capture"),
|
|
||||||
("test-wake-digest-quarantine.sh", 560, "F-extract-capture"),
|
|
||||||
]
|
|
||||||
|
|
||||||
RX_HELPER = re.compile(r"(^|[^A-Za-z0-9_.-])(has_match|count_lines)([^A-Za-z0-9_.-]|$)")
|
|
||||||
|
|
||||||
# ---- classifier, ported verbatim in logic from the frozen denominator
|
|
||||||
# ---- derivation (docs/journal/fleet/drift-derive-089615f__pepper.py)
|
|
||||||
RX_FAIL_SAME = re.compile(r"(\|\||&&)\s*fail")
|
|
||||||
RX_COUNT_SUB = re.compile(r"\$\(.*grep\s+[^)]*-c|\$\(\s*grep\s+-c")
|
|
||||||
RX_ASSIGN_SUB = re.compile(r'=\s*"?\$\(.*grep')
|
|
||||||
RX_IF = re.compile(r"^\s*(el)?if\s+.*grep")
|
|
||||||
RX_GREP = re.compile(r"(^|[^A-Za-z0-9_.-])grep([^A-Za-z0-9_.-]|$)")
|
|
||||||
|
|
||||||
# grep in COMMAND position: at line start or after a command separator / subshell
|
|
||||||
# opener / shell keyword / `!`. Quote-unaware by design — a quoted "grep" after a
|
|
||||||
# separator reads as a command and lands the line in residual, which fails LOUD;
|
|
||||||
# the absorb direction (note) is the one that must never fire on a real verdict.
|
|
||||||
RX_GREP_CMD = re.compile(
|
|
||||||
r"(?:^|[;|&(`]|\$\(|\bif\b|\belif\b|\bthen\b|\belse\b|\bdo\b|\bwhile\b|\buntil\b|!)"
|
|
||||||
r"\s*grep(?:\s|$)"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def classify(lines):
|
|
||||||
"""Return (sites, dispo). Every line containing the word grep gets a row."""
|
|
||||||
sites, dispo = [], []
|
|
||||||
n = len(lines)
|
|
||||||
for i, raw in enumerate(lines):
|
|
||||||
line = raw
|
|
||||||
ln = i + 1
|
|
||||||
if not RX_GREP.search(line):
|
|
||||||
continue
|
|
||||||
stripped = line.strip()
|
|
||||||
if stripped.startswith("#"):
|
|
||||||
dispo.append((ln, "comment", stripped))
|
|
||||||
continue
|
|
||||||
nxt = ""
|
|
||||||
for j in range(i + 1, min(i + 3, n)):
|
|
||||||
if lines[j].strip():
|
|
||||||
nxt = lines[j].strip()
|
|
||||||
break
|
|
||||||
if "$(" in line and RX_COUNT_SUB.search(line):
|
|
||||||
sites.append((ln, "E-count-capture", stripped))
|
|
||||||
continue
|
|
||||||
if RX_ASSIGN_SUB.search(line) and "grep -c" not in line:
|
|
||||||
sites.append((ln, "F-extract-capture", stripped))
|
|
||||||
continue
|
|
||||||
if RX_FAIL_SAME.search(line):
|
|
||||||
sites.append((ln, "A-same-line", stripped))
|
|
||||||
continue
|
|
||||||
if stripped.endswith("\\"):
|
|
||||||
k = i + 1
|
|
||||||
joined = stripped[:-1]
|
|
||||||
while k < n:
|
|
||||||
cont = lines[k].strip()
|
|
||||||
joined += " " + (cont[:-1] if cont.endswith("\\") else cont)
|
|
||||||
if not cont.endswith("\\"):
|
|
||||||
break
|
|
||||||
k += 1
|
|
||||||
if re.search(r"(\|\||&&)\s*fail", joined) or (
|
|
||||||
joined.rstrip().endswith(("||", "&&"))
|
|
||||||
and k + 1 < n
|
|
||||||
and lines[k + 1].strip().startswith("fail")
|
|
||||||
):
|
|
||||||
sites.append((ln, "C-cont-backslash", stripped))
|
|
||||||
continue
|
|
||||||
dispo.append((ln, "backslash-no-fail-continuation", stripped))
|
|
||||||
continue
|
|
||||||
if stripped.endswith(("||", "&&")) and nxt.startswith("fail"):
|
|
||||||
sites.append((ln, "B-cont-operator", stripped))
|
|
||||||
continue
|
|
||||||
if RX_IF.search(line):
|
|
||||||
window = " ".join(lines[j] for j in range(i, min(i + 5, n)))
|
|
||||||
if "fail" in window:
|
|
||||||
sites.append((ln, "D-if-form", stripped))
|
|
||||||
continue
|
|
||||||
dispo.append((ln, "if-grep-no-fail-window", stripped))
|
|
||||||
continue
|
|
||||||
win = " ".join(lines[j] for j in range(max(0, i - 2), min(i + 3, n)))
|
|
||||||
if re.search(r"fail", win, re.I):
|
|
||||||
dispo.append((ln, "BACKSTOP-HAND-REVIEW", stripped))
|
|
||||||
else:
|
|
||||||
dispo.append((ln, "no-verdict-context", stripped))
|
|
||||||
return sites, dispo
|
|
||||||
|
|
||||||
|
|
||||||
def residual_sites(lines):
|
|
||||||
"""classify() plus the absorb decision — the ONE path both sweep legs share.
|
|
||||||
|
|
||||||
A classified site is absorbed as a note only when its line carries a wake
|
|
||||||
helper token AND the line shows no grep in command position: a converted
|
|
||||||
line whose PATTERN argument merely contains the word grep. A helper line
|
|
||||||
that also runs a real grep verdict (has_match ... && grep -q SECRET ... &&
|
|
||||||
fail) stays residual (#985). Multi-line forms anchor the site at the line
|
|
||||||
containing grep, so a command-position grep on a continuation line never
|
|
||||||
shares its line with the helper token and stays residual by construction.
|
|
||||||
"""
|
|
||||||
sites, dispo = classify(lines)
|
|
||||||
residual, notes = [], []
|
|
||||||
for ln, form, text in sites:
|
|
||||||
line = lines[ln - 1]
|
|
||||||
if RX_HELPER.search(line) and not RX_GREP_CMD.search(line):
|
|
||||||
notes.append((ln, form, text))
|
|
||||||
else:
|
|
||||||
residual.append((ln, form, text))
|
|
||||||
return residual, notes, dispo
|
|
||||||
|
|
||||||
|
|
||||||
def load_art():
|
|
||||||
art = json.loads(ART.read_text())
|
|
||||||
assert art["total"] == 261 == len(art["rows"]), "artifact self-consistency"
|
|
||||||
return art
|
|
||||||
|
|
||||||
|
|
||||||
def helper_for(row):
|
|
||||||
return "count_lines" if row["form"].startswith("E") else "has_match"
|
|
||||||
|
|
||||||
|
|
||||||
def suite_files(art):
|
|
||||||
return sorted({r["file"] for r in art["rows"]})
|
|
||||||
|
|
||||||
|
|
||||||
def cmd_expected():
|
|
||||||
art = load_art()
|
|
||||||
out = sorted(
|
|
||||||
f"{helper_for(r)} {r['file']}:{r['line'] + HEADER_SHIFT}" for r in art["rows"]
|
|
||||||
)
|
|
||||||
assert len(out) == len(set(out)) == 261, "expected set must be 261 distinct rows"
|
|
||||||
print("\n".join(out))
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
def cmd_static():
|
|
||||||
art = load_art()
|
|
||||||
rows = []
|
|
||||||
for f in suite_files(art):
|
|
||||||
for i, line in enumerate((WAKE / f).read_text().split("\n"), start=1):
|
|
||||||
if line.strip().startswith("#"):
|
|
||||||
continue
|
|
||||||
m = RX_HELPER.search(line)
|
|
||||||
if not m:
|
|
||||||
continue
|
|
||||||
helper = (
|
|
||||||
"count_lines"
|
|
||||||
if RX_HELPER.search(line).group(2) == "count_lines"
|
|
||||||
else "has_match"
|
|
||||||
)
|
|
||||||
rows.append(f"{helper} {f}:{i}")
|
|
||||||
print("\n".join(sorted(rows)))
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
def cmd_arms():
|
|
||||||
art = load_art()
|
|
||||||
by_key = {(r["file"], r["line"]): r for r in art["rows"]}
|
|
||||||
rows = []
|
|
||||||
canaries = [r for r in art["rows"] if r.get("canary")]
|
|
||||||
assert len(canaries) == 19, f"expected 19 canaries, artifact has {len(canaries)}"
|
|
||||||
for r in canaries:
|
|
||||||
rows.append(f"{helper_for(r)} {r['file']}:{r['line'] + HEADER_SHIFT} {r['form']}")
|
|
||||||
for f, ln, want_form in EXTRA_ARMS:
|
|
||||||
r = by_key.get((f, ln))
|
|
||||||
assert r is not None, f"extra arm {f}:{ln} not in artifact — renumbered?"
|
|
||||||
assert r["form"] == want_form, f"extra arm {f}:{ln} form {r['form']} != {want_form}"
|
|
||||||
rows.append(f"{helper_for(r)} {f}:{ln + HEADER_SHIFT} {r['form']}")
|
|
||||||
assert len(rows) == 21
|
|
||||||
print("\n".join(rows))
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
# (expected classify form, expected disposition through residual_sites, snippet)
|
|
||||||
PLANTS = [
|
|
||||||
("A-same-line", "residual", ['grep -q needle haystack || fail "plant-A"']),
|
|
||||||
("B-cont-operator", "residual", ["grep -q needle haystack ||", ' fail "plant-B"']),
|
|
||||||
("C-cont-backslash", "residual", ["grep -q needle \\", ' haystack || fail "plant-C"']),
|
|
||||||
("D-if-form", "residual", ["if ! grep -q needle haystack; then", ' fail "plant-D"', "fi"]),
|
|
||||||
("E-count-capture", "residual", ['[ "$(grep -c needle haystack)" = "1" ] || fail "plant-E"']),
|
|
||||||
("F-extract-capture", "residual", ['val="$(grep needle haystack)"']),
|
|
||||||
# G: a converted line that ALSO runs a raw grep verdict — the helper token
|
|
||||||
# must not absorb it (#985)
|
|
||||||
("A-same-line", "residual", ['has_match -q needle "$F" && grep -q SECRET "$F" && fail "plant-G"']),
|
|
||||||
# H: negative control — helper whose PATTERN argument is the word grep;
|
|
||||||
# must be absorbed as a note, never residual
|
|
||||||
("A-same-line", "note", ['has_match -q "grep" haystack || fail "plant-H"']),
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def cmd_sweep():
|
|
||||||
art = load_art()
|
|
||||||
bad = 0
|
|
||||||
|
|
||||||
# leg 1: real suites at the current tree must be residual-free
|
|
||||||
for f in suite_files(art):
|
|
||||||
residual, notes, _dispo = residual_sites((WAKE / f).read_text().split("\n"))
|
|
||||||
for ln, form, text in notes:
|
|
||||||
# converted line whose PATTERN argument contains the word grep:
|
|
||||||
# not an unconverted site, but never silently absorbed either
|
|
||||||
print(f"SWEEP-NOTE {f}:{ln} converted line matches grep-token ({form}): {text[:80]}")
|
|
||||||
for ln, form, text in residual:
|
|
||||||
print(f"SWEEP-RESIDUAL {f}:{ln} {form}: {text[:100]}")
|
|
||||||
bad += 1
|
|
||||||
print(f"SWEEP {f}: {len(residual)} residual verdict site(s)")
|
|
||||||
|
|
||||||
# leg 2, SAME RUN, SAME PATH as leg 1: the instrument must find every plant
|
|
||||||
# with the right form AND the right absorb disposition — plants G/H exercise
|
|
||||||
# the absorb branch itself, so this leg must go through residual_sites(),
|
|
||||||
# not raw classify()
|
|
||||||
donor = suite_files(art)[0]
|
|
||||||
with tempfile.TemporaryDirectory() as td:
|
|
||||||
planted = Path(td) / donor
|
|
||||||
shutil.copy(WAKE / donor, planted)
|
|
||||||
base_lines = planted.read_text().split("\n")
|
|
||||||
offset = len(base_lines)
|
|
||||||
expect = {}
|
|
||||||
for form, dispo, snippet in PLANTS:
|
|
||||||
expect[offset + 1] = (form, dispo) # first physical line of each plant
|
|
||||||
base_lines.extend(snippet)
|
|
||||||
offset = len(base_lines)
|
|
||||||
planted.write_text("\n".join(base_lines))
|
|
||||||
residual, notes, _ = residual_sites(planted.read_text().split("\n"))
|
|
||||||
found = {ln: (form, "residual") for ln, form, _t in residual}
|
|
||||||
found.update({ln: (form, "note") for ln, form, _t in notes})
|
|
||||||
unexpected = [(ln, form) for ln, form, _t in residual if ln not in expect]
|
|
||||||
hits = sum(1 for ln, want in expect.items() if found.get(ln) == want)
|
|
||||||
n_plants = len(PLANTS)
|
|
||||||
print(f"SWEEP-PLANTS found={hits}/{n_plants} in planted copy of {donor}")
|
|
||||||
if hits != n_plants:
|
|
||||||
for ln, want in sorted(expect.items()):
|
|
||||||
got = found.get(ln, ("<missed>", "<missed>"))
|
|
||||||
if got != want:
|
|
||||||
print(f"SWEEP-PLANT-MISS line {ln}: expected {want}, got {got}")
|
|
||||||
bad += 1
|
|
||||||
if unexpected:
|
|
||||||
# the donor is a converted suite: any non-plant RESIDUAL site in the
|
|
||||||
# copy contradicts the zero leg 1 just reported on the original
|
|
||||||
# (non-plant notes mirror leg 1's treatment: printed there, not bad)
|
|
||||||
for ln, form in unexpected:
|
|
||||||
print(f"SWEEP-PLANT-UNEXPECTED {donor}(copy):{ln} {form}")
|
|
||||||
bad += 1
|
|
||||||
|
|
||||||
return 1 if bad else 0
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
cmds = {
|
|
||||||
"expected": cmd_expected,
|
|
||||||
"static": cmd_static,
|
|
||||||
"arms": cmd_arms,
|
|
||||||
"sweep": cmd_sweep,
|
|
||||||
}
|
|
||||||
if len(sys.argv) != 2 or sys.argv[1] not in cmds:
|
|
||||||
sys.exit(f"usage: check-973.py {{{'|'.join(cmds)}}}")
|
|
||||||
sys.exit(cmds[sys.argv[1]]())
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
@@ -1,131 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""convert-973.py — mechanical #973 site conversion, driven by the frozen
|
|
||||||
denominator artifact (denominator-089615f.json), never by ad-hoc grepping.
|
|
||||||
|
|
||||||
For every row in the artifact it FIRST verifies the worktree line still says
|
|
||||||
what the artifact froze (exact match after whitespace strip, artifact-side
|
|
||||||
truncation as prefix match, or the normalized suite-summary template), and
|
|
||||||
aborts before touching anything on the first verification failure — a
|
|
||||||
conversion applied to a line the denominator did not measure would be the
|
|
||||||
umbrella defect wearing the converter's clothes.
|
|
||||||
|
|
||||||
Transforms (behaviour-preserving; verdict semantics unchanged on grep rc 0/1):
|
|
||||||
E-count-capture `grep -c ARGS` -> `count_lines ARGS` (helper adds -c)
|
|
||||||
all other forms `grep ARGS` -> `has_match ARGS` (drop-in)
|
|
||||||
env prefixes (`LC_ALL=C grep`) are kept — the prefix reaches the grep child
|
|
||||||
through the function (microtest C8).
|
|
||||||
|
|
||||||
The two multi-grep pipeline sites (digest-quarantine:560, install:420) convert
|
|
||||||
BOTH greps: each is measurement-bearing, and under pipefail an rc=2 in the
|
|
||||||
left element is masked by an rc=1 in the right — the same defect one pipe
|
|
||||||
deeper. They stay ONE denominator site each (one coordinate); the ledger
|
|
||||||
records helper calls, so those coordinates appear twice per execution and the
|
|
||||||
equality check compares SETS of coordinates.
|
|
||||||
|
|
||||||
Finally each suite gains three header lines directly after its SCRIPT_DIR
|
|
||||||
assignment (source + wake_assert_init + comment), shifting every site by +3
|
|
||||||
lines exactly; the validation harness maps base coordinates accordingly.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import json
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
HERE = Path(__file__).resolve().parent
|
|
||||||
WAKE = HERE.parent
|
|
||||||
ART = HERE / "denominator-089615f.json"
|
|
||||||
|
|
||||||
RX_GREP_TOKEN = re.compile(r"(^|[^A-Za-z0-9_.-])grep([^A-Za-z0-9_.-]|$)")
|
|
||||||
|
|
||||||
HEADER = [
|
|
||||||
"# #973: three-valued grep assertion helpers (has_match/count_lines); init saves real stderr for abort loudness.\n",
|
|
||||||
"# shellcheck disable=SC1091\n",
|
|
||||||
'. "$SCRIPT_DIR/_wake-common.sh" && wake_assert_init\n',
|
|
||||||
]
|
|
||||||
|
|
||||||
MULTI_GREP_CONVERT_BOTH = {
|
|
||||||
("test-wake-digest-quarantine.sh", 560),
|
|
||||||
("test-wake-install.sh", 420),
|
|
||||||
}
|
|
||||||
|
|
||||||
SUMMARY_TEMPLATE_MARK = '$(grep -c . "$FAILFILE")'
|
|
||||||
|
|
||||||
|
|
||||||
def verify(row, actual):
|
|
||||||
a = actual.strip()
|
|
||||||
t = row["text"].strip()
|
|
||||||
if a == t:
|
|
||||||
return True
|
|
||||||
if t and a.startswith(t): # artifact-side truncation
|
|
||||||
return True
|
|
||||||
# normalized suite-summary template rows
|
|
||||||
if t.startswith('echo "wake ') and SUMMARY_TEMPLATE_MARK in actual and a.startswith('echo "wake '):
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def convert_line(row, line):
|
|
||||||
key = (row["file"], row["line"])
|
|
||||||
n_grep = len(RX_GREP_TOKEN.findall(line))
|
|
||||||
if key in MULTI_GREP_CONVERT_BOTH:
|
|
||||||
assert n_grep == 2, f"{key}: expected 2 grep tokens, found {n_grep}"
|
|
||||||
else:
|
|
||||||
assert n_grep == 1, f"{key}: expected 1 grep token, found {n_grep}: {line!r}"
|
|
||||||
|
|
||||||
if row["form"].startswith("E"):
|
|
||||||
assert line.count("grep -c ") == 1, f"{key}: E row without single 'grep -c ': {line!r}"
|
|
||||||
return line.replace("grep -c ", "count_lines ", 1)
|
|
||||||
|
|
||||||
def repl(m):
|
|
||||||
return m.group(1) + "has_match" + m.group(2)
|
|
||||||
|
|
||||||
count = 2 if key in MULTI_GREP_CONVERT_BOTH else 1
|
|
||||||
return RX_GREP_TOKEN.sub(repl, line, count=count)
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
art = json.loads(ART.read_text())
|
|
||||||
rows = art["rows"]
|
|
||||||
by_file = {}
|
|
||||||
for r in rows:
|
|
||||||
by_file.setdefault(r["file"], []).append(r)
|
|
||||||
|
|
||||||
# pass 1: verify every row before touching any file
|
|
||||||
bad = 0
|
|
||||||
texts = {}
|
|
||||||
for f, frs in by_file.items():
|
|
||||||
lines = (WAKE / f).read_text().split("\n")
|
|
||||||
texts[f] = lines
|
|
||||||
for r in frs:
|
|
||||||
if not verify(r, lines[r["line"] - 1]):
|
|
||||||
bad += 1
|
|
||||||
print(f"VERIFY-FAIL {f}:{r['line']}\n artifact: {r['text']!r}\n worktree: {lines[r['line'] - 1]!r}")
|
|
||||||
if bad:
|
|
||||||
sys.exit(f"ABORT: {bad} row(s) failed verification; nothing was modified.")
|
|
||||||
|
|
||||||
# pass 2: convert + insert header
|
|
||||||
total = {"has_match": 0, "count_lines": 0}
|
|
||||||
for f, frs in sorted(by_file.items()):
|
|
||||||
lines = texts[f]
|
|
||||||
for r in frs:
|
|
||||||
i = r["line"] - 1
|
|
||||||
new = convert_line(r, lines[i])
|
|
||||||
assert new != lines[i], f"{f}:{r['line']}: no-op conversion"
|
|
||||||
lines[i] = new
|
|
||||||
total["count_lines" if r["form"].startswith("E") else "has_match"] += 1
|
|
||||||
# header insertion after the SCRIPT_DIR= line
|
|
||||||
sd = [i for i, ln in enumerate(lines) if ln.startswith('SCRIPT_DIR="$(')]
|
|
||||||
assert len(sd) == 1, f"{f}: expected exactly one SCRIPT_DIR line, found {len(sd)}"
|
|
||||||
first_site = min(r["line"] for r in frs) - 1
|
|
||||||
assert sd[0] < first_site, f"{f}: SCRIPT_DIR line {sd[0] + 1} not before first site {first_site + 1}"
|
|
||||||
lines[sd[0] + 1 : sd[0] + 1] = [h.rstrip("\n") for h in HEADER]
|
|
||||||
(WAKE / f).write_text("\n".join(lines))
|
|
||||||
print(f"{f}: {len(frs)} sites converted, header at line {sd[0] + 2}")
|
|
||||||
|
|
||||||
print(f"TOTAL: {total['has_match']} has_match + {total['count_lines']} count_lines = {sum(total.values())} sites in {len(by_file)} files")
|
|
||||||
assert sum(total.values()) == art["total"] == 261, "site count mismatch vs artifact"
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,299 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# microtest-wake-assert.sh — #973 instrument self-test. Run BEFORE trusting any
|
|
||||||
# validate-run evidence: it proves the counted ledger and the abort mechanics on
|
|
||||||
# two generated mini-suites, so a defect in the instrument cannot silently wear
|
|
||||||
# the colour of a clean validation.
|
|
||||||
#
|
|
||||||
# What it proves (each check named C1..C8 below):
|
|
||||||
# C1 green run: ledger set EQUALS a text-derived expected set spanning TWO
|
|
||||||
# files (file-field discrimination), row count > 1, both sentinels emitted,
|
|
||||||
# exit 0. Also pins the BASH_LINENO convention for backslash-continuation
|
|
||||||
# call sites against the first-physical-line convention the denominator
|
|
||||||
# artifact uses.
|
|
||||||
# C2 early-exit truncation: a suite that exits before its later site yields a
|
|
||||||
# SHORT ledger, and the expected-set comparison catches it — a counted
|
|
||||||
# ledger must report its own truncation, never a smaller total.
|
|
||||||
# C3 abort from inside a `( ... )` test subshell kills the WHOLE suite: no
|
|
||||||
# sentinel, non-zero exit, loud named reason (file:line + raw rc).
|
|
||||||
# C4 abort stays loud at a call site that appends 2>/dev/null (the preimage
|
|
||||||
# canary shape) — the saved-fd path.
|
|
||||||
# C5 abort escapes a `$( count_lines ... )` substitution (A6 shape): the
|
|
||||||
# count from a failed measurement is never compared and the suite dies.
|
|
||||||
# C6 abort escapes a pipeline tail (`printf | has_match`).
|
|
||||||
# C7 count_lines prints 0 on grep rc 1 (zero matches is a measurement, not an
|
|
||||||
# error) — implicit in C1's green run via the delta-count site.
|
|
||||||
# C8 an env-prefix on the helper (`LC_ALL=C has_match ...`) reaches the grep
|
|
||||||
# child — pins the conversion shape for the digest-hmac LC_ALL site.
|
|
||||||
# C9 an arm that matches NO site is loud about it by omission: green run,
|
|
||||||
# sentinel present, and NO "WAKE-ASSERT ARMED" line — so "did not abort"
|
|
||||||
# is separable into arm-never-matched (no ARMED line) vs error-path-
|
|
||||||
# broken (ARMED line, no abort). C3..C6 require the ARMED line AND the
|
|
||||||
# aborting site's ledger row (append lands BEFORE the grep runs, so an
|
|
||||||
# abort can never shorten the count it is part of).
|
|
||||||
# C10 the BASH_LINENO pin's abort arm fires: under a probe interpreter that
|
|
||||||
# misreports the continuation line, wake_assert_init aborts loudly and
|
|
||||||
# nothing past init executes — a pin whose failure arm was never seen
|
|
||||||
# firing is an undertaking, not a control.
|
|
||||||
# C11 the FAILED-summary template executes on the red path: a mini-suite
|
|
||||||
# driven deterministically red emits the exact converted summary shape
|
|
||||||
# (`FAILED ($(count_lines . "$FAILFILE") assertion(s))`) with the right
|
|
||||||
# count, exits 1, and the summary site's ledger row lands. The nine
|
|
||||||
# real-suite summary sites are structurally unreachable in a green run
|
|
||||||
# (guarded by [ -s "$FAILFILE" ]); their dispositions cite THIS check as
|
|
||||||
# the measured execution of the same template, so "unexecuted in the
|
|
||||||
# green run" never silently means "never executed anywhere".
|
|
||||||
set -uo pipefail
|
|
||||||
|
|
||||||
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
||||||
export WAKE_COMMON="$HERE/../_wake-common.sh"
|
|
||||||
[ -f "$WAKE_COMMON" ] || {
|
|
||||||
echo "microtest: _wake-common.sh not found at $WAKE_COMMON" >&2
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
TMP="$(mktemp -d)"
|
|
||||||
trap 'rm -rf "$TMP"' EXIT
|
|
||||||
|
|
||||||
fails=0
|
|
||||||
check() { # check NAME COND-DESCRIPTION (pass/fail already decided by caller: $1=name $2=0|1 $3=detail)
|
|
||||||
if [ "$2" -eq 0 ]; then
|
|
||||||
echo " PASS $1"
|
|
||||||
else
|
|
||||||
echo " FAIL $1 — $3"
|
|
||||||
fails=$((fails + 1))
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# --- fixture data ----------------------------------------------------------
|
|
||||||
printf 'alpha\nbeta\nbeta\ngamma-unused\n' >"$TMP/data.txt"
|
|
||||||
|
|
||||||
# --- mini-suite A: six helper sites across every converted form ------------
|
|
||||||
cat >"$TMP/mini-a.sh" <<'MINI_A'
|
|
||||||
#!/usr/bin/env bash
|
|
||||||
set -uo pipefail
|
|
||||||
. "$WAKE_COMMON"
|
|
||||||
wake_assert_init
|
|
||||||
TMP="$1"
|
|
||||||
FAILFILE="$TMP/failures-a"
|
|
||||||
: >"$FAILFILE"
|
|
||||||
fail_msg() { echo " FAIL: $*" >&2; echo x >>"$FAILFILE"; }
|
|
||||||
ok() { :; }
|
|
||||||
(
|
|
||||||
has_match -q alpha "$TMP/data.txt" || fail_msg "alpha missing" # SITE:or-subshell
|
|
||||||
) && ok
|
|
||||||
(
|
|
||||||
has_match -q FORBIDDEN "$TMP/data.txt" 2>/dev/null && fail_msg "forbidden present" # SITE:and-swallow
|
|
||||||
) && ok
|
|
||||||
(
|
|
||||||
[ "$(count_lines beta "$TMP/data.txt")" = "2" ] || fail_msg "beta count" # SITE:count-capture
|
|
||||||
) && ok
|
|
||||||
(
|
|
||||||
printf 'gamma\n' | has_match -q gamma || fail_msg "gamma pipeline" # SITE:pipeline
|
|
||||||
) && ok
|
|
||||||
(
|
|
||||||
has_match -q \
|
|
||||||
alpha "$TMP/data.txt" || fail_msg "continuation" # SITE:continuation
|
|
||||||
) && ok
|
|
||||||
(
|
|
||||||
[ "$(count_lines delta "$TMP/data.txt")" = "0" ] || fail_msg "delta zero" # SITE:count-zero
|
|
||||||
) && ok
|
|
||||||
if [ -s "$FAILFILE" ]; then
|
|
||||||
echo "mini-a: FAILED" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
echo "mini-a: OK" >&2
|
|
||||||
MINI_A
|
|
||||||
|
|
||||||
# --- mini-suite B: second file, one site behind an early exit --------------
|
|
||||||
cat >"$TMP/mini-b.sh" <<'MINI_B'
|
|
||||||
#!/usr/bin/env bash
|
|
||||||
set -uo pipefail
|
|
||||||
. "$WAKE_COMMON"
|
|
||||||
wake_assert_init
|
|
||||||
TMP="$1"
|
|
||||||
(
|
|
||||||
has_match -q alpha "$TMP/data.txt" || echo "b1 missing" >&2 # SITE:b-first
|
|
||||||
)
|
|
||||||
if [ "${MINI_B_EARLY_EXIT:-}" = "1" ]; then
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
(
|
|
||||||
has_match -q beta "$TMP/data.txt" || echo "b2 missing" >&2 # SITE:b-second
|
|
||||||
)
|
|
||||||
echo "mini-b: OK" >&2
|
|
||||||
MINI_B
|
|
||||||
chmod +x "$TMP/mini-a.sh" "$TMP/mini-b.sh"
|
|
||||||
|
|
||||||
# Text-derived expected set: helper-name + basename:line for every SITE-marked
|
|
||||||
# call, taken from the generated files' TEXT (independent of BASH_LINENO), with
|
|
||||||
# the continuation site expected at its FIRST physical line — the denominator
|
|
||||||
# artifact's convention.
|
|
||||||
expected_set() { # expected_set FILE
|
|
||||||
local f="$1" base
|
|
||||||
base="$(basename "$f")"
|
|
||||||
awk '
|
|
||||||
/# SITE:/ {
|
|
||||||
line = NR
|
|
||||||
if ($0 !~ /has_match|count_lines/) line = NR - 1 # marker on the continuation tail
|
|
||||||
print line
|
|
||||||
}
|
|
||||||
' "$f" | while read -r ln; do
|
|
||||||
txt="$(sed -n "${ln}p" "$f")"
|
|
||||||
case "$txt" in
|
|
||||||
*count_lines*) printf 'count_lines %s:%s\n' "$base" "$ln" ;;
|
|
||||||
*) printf 'has_match %s:%s\n' "$base" "$ln" ;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
site_line() { # site_line FILE MARKER -> first physical line of that call
|
|
||||||
local f="$1" marker="$2" ln
|
|
||||||
ln="$(grep -n "# SITE:${marker}\$" "$f" | cut -d: -f1)"
|
|
||||||
# continuation marker sits on the tail line; the call starts one line up
|
|
||||||
if ! sed -n "${ln}p" "$f" | grep -Eq 'has_match|count_lines'; then
|
|
||||||
ln=$((ln - 1))
|
|
||||||
fi
|
|
||||||
printf '%s' "$ln"
|
|
||||||
}
|
|
||||||
|
|
||||||
# --- C1: green run, two files, set equality --------------------------------
|
|
||||||
LEDGER="$TMP/ledger-c1"
|
|
||||||
: >"$LEDGER"
|
|
||||||
outA="$(WAKE_ASSERT_LEDGER="$LEDGER" bash "$TMP/mini-a.sh" "$TMP" 2>&1)"
|
|
||||||
rcA=$?
|
|
||||||
outB="$(WAKE_ASSERT_LEDGER="$LEDGER" bash "$TMP/mini-b.sh" "$TMP" 2>&1)"
|
|
||||||
rcB=$?
|
|
||||||
{ expected_set "$TMP/mini-a.sh"; expected_set "$TMP/mini-b.sh"; } | sort >"$TMP/expected-c1"
|
|
||||||
sort "$LEDGER" >"$TMP/got-c1"
|
|
||||||
n_expected="$(grep -c . "$TMP/expected-c1")"
|
|
||||||
if [ "$rcA" -eq 0 ] && [ "$rcB" -eq 0 ] &&
|
|
||||||
printf '%s' "$outA" | grep -q 'mini-a: OK' &&
|
|
||||||
printf '%s' "$outB" | grep -q 'mini-b: OK' &&
|
|
||||||
[ "$n_expected" -gt 1 ] &&
|
|
||||||
cmp -s "$TMP/expected-c1" "$TMP/got-c1"; then
|
|
||||||
check C1 0 ""
|
|
||||||
else
|
|
||||||
check C1 1 "rcA=$rcA rcB=$rcB expected($n_expected)/got diff: $(diff "$TMP/expected-c1" "$TMP/got-c1" 2>&1 | head -n 10 | tr '\n' ' ')"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# --- C2: early exit -> short ledger, comparison catches it -----------------
|
|
||||||
LEDGER="$TMP/ledger-c2"
|
|
||||||
: >"$LEDGER"
|
|
||||||
WAKE_ASSERT_LEDGER="$LEDGER" MINI_B_EARLY_EXIT=1 bash "$TMP/mini-b.sh" "$TMP" >/dev/null 2>&1
|
|
||||||
expected_set "$TMP/mini-b.sh" | sort >"$TMP/expected-c2"
|
|
||||||
sort "$LEDGER" >"$TMP/got-c2"
|
|
||||||
if ! cmp -s "$TMP/expected-c2" "$TMP/got-c2" &&
|
|
||||||
grep -q "has_match mini-b.sh:$(site_line "$TMP/mini-b.sh" b-first)" "$TMP/got-c2" &&
|
|
||||||
! grep -q "mini-b.sh:$(site_line "$TMP/mini-b.sh" b-second)" "$TMP/got-c2"; then
|
|
||||||
check C2 0 ""
|
|
||||||
else
|
|
||||||
check C2 1 "truncated ledger was not detected as short"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# --- C3..C6: per-shape abort proofs ----------------------------------------
|
|
||||||
abort_case() { # abort_case NAME MARKER HELPER
|
|
||||||
local name="$1" marker="$2" helper="$3" ln site out rc ledger
|
|
||||||
ln="$(site_line "$TMP/mini-a.sh" "$marker")"
|
|
||||||
site="mini-a.sh:${ln}"
|
|
||||||
ledger="$TMP/ledger-${name}"
|
|
||||||
: >"$ledger"
|
|
||||||
out="$(WAKE_ASSERT_LEDGER="$ledger" WAKE_ASSERT_FORCE_GREP_ERROR_AT="$site" \
|
|
||||||
bash "$TMP/mini-a.sh" "$TMP" 2>&1)"
|
|
||||||
rc=$?
|
|
||||||
if [ "$rc" -ne 0 ] &&
|
|
||||||
! printf '%s' "$out" | grep -q 'mini-a: OK' &&
|
|
||||||
! printf '%s' "$out" | grep -q 'mini-a: FAILED' &&
|
|
||||||
printf '%s' "$out" | grep -q "WAKE-ASSERT ARMED: forcing real grep error at $site" &&
|
|
||||||
printf '%s' "$out" | grep -q "WAKE-ASSERT ABORT" &&
|
|
||||||
printf '%s' "$out" | grep -q "$site" &&
|
|
||||||
printf '%s' "$out" | grep -q "grep exit 2" &&
|
|
||||||
grep -q "^${helper} ${site}\$" "$ledger"; then
|
|
||||||
check "$name" 0 ""
|
|
||||||
else
|
|
||||||
check "$name" 1 "rc=$rc site=$site ledger=$(grep -c . "$ledger") out=$(printf '%s' "$out" | tail -n 3 | tr '\n' ' ')"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
abort_case C3 or-subshell has_match
|
|
||||||
abort_case C4 and-swallow has_match
|
|
||||||
abort_case C5 count-capture count_lines
|
|
||||||
abort_case C6 pipeline has_match
|
|
||||||
|
|
||||||
# --- C7: covered by C1 (delta-count site prints 0 on grep rc 1) ------------
|
|
||||||
check C7 0 ""
|
|
||||||
|
|
||||||
# --- C8: env-prefix on a function reaches the grep child -------------------
|
|
||||||
envprobe() { command env | command grep -c '^LC_ALL=xx_wake_test$'; }
|
|
||||||
got="$(LC_ALL=xx_wake_test envprobe 2>/dev/null)" # bash's setlocale warning about the fake locale is itself proof the prefix landed
|
|
||||||
if [ "$got" = "1" ]; then check C8 0 ""; else check C8 1 "env-prefix did not reach child (got=$got)"; fi
|
|
||||||
|
|
||||||
# --- C9: arm matching NO site -> green run, no ARMED line ------------------
|
|
||||||
out="$(WAKE_ASSERT_FORCE_GREP_ERROR_AT="mini-a.sh:9999" bash "$TMP/mini-a.sh" "$TMP" 2>&1)"
|
|
||||||
rc=$?
|
|
||||||
if [ "$rc" -eq 0 ] &&
|
|
||||||
printf '%s' "$out" | grep -q 'mini-a: OK' &&
|
|
||||||
! printf '%s' "$out" | grep -q 'WAKE-ASSERT ARMED'; then
|
|
||||||
check C9 0 ""
|
|
||||||
else
|
|
||||||
check C9 1 "rc=$rc out=$(printf '%s' "$out" | tail -n 3 | tr '\n' ' ')"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# --- C10: lineno pin aborts under an interpreter that breaks the convention -
|
|
||||||
cat >"$TMP/fake-bash" <<'FAKE'
|
|
||||||
#!/usr/bin/env bash
|
|
||||||
# stand-in for a bash whose BASH_LINENO convention differs: misreports the
|
|
||||||
# continuation call one line low (the exact skew the pin exists to catch)
|
|
||||||
printf '3\n5\n'
|
|
||||||
FAKE
|
|
||||||
chmod +x "$TMP/fake-bash"
|
|
||||||
out="$(WAKE_ASSERT_PIN_BASH="$TMP/fake-bash" bash -c '. "$WAKE_COMMON" && wake_assert_init && echo REACHED-PAST-INIT' 2>&1)"
|
|
||||||
rc=$?
|
|
||||||
if [ "$rc" -ne 0 ] &&
|
|
||||||
! printf '%s' "$out" | grep -q 'REACHED-PAST-INIT' &&
|
|
||||||
printf '%s' "$out" | grep -q 'WAKE-ASSERT INIT ABORT: BASH_LINENO convention violated'; then
|
|
||||||
check C10 0 ""
|
|
||||||
else
|
|
||||||
check C10 1 "rc=$rc out=$(printf '%s' "$out" | tail -n 2 | tr '\n' ' ')"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# --- C11: red path executes the converted FAILED-summary template -----------
|
|
||||||
cat >"$TMP/mini-c.sh" <<'MINI_C'
|
|
||||||
#!/usr/bin/env bash
|
|
||||||
set -uo pipefail
|
|
||||||
. "$WAKE_COMMON"
|
|
||||||
wake_assert_init
|
|
||||||
TMP="$1"
|
|
||||||
FAILFILE="$TMP/failures-c"
|
|
||||||
: >"$FAILFILE"
|
|
||||||
fail_msg() { echo " FAIL: $*" >&2; echo x >>"$FAILFILE"; }
|
|
||||||
ok() { :; }
|
|
||||||
(
|
|
||||||
has_match -q alpha "$TMP/data.txt" && fail_msg "alpha present" # SITE:c-inverted (deterministically red: alpha IS in the fixture)
|
|
||||||
) && ok
|
|
||||||
echo
|
|
||||||
if [ -s "$FAILFILE" ]; then
|
|
||||||
echo "wake mini-c harness: FAILED ($(count_lines . "$FAILFILE") assertion(s))" >&2 # SITE:c-summary
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
echo "wake mini-c harness: all invariants passed (1 group)"
|
|
||||||
MINI_C
|
|
||||||
chmod +x "$TMP/mini-c.sh"
|
|
||||||
LEDGER="$TMP/ledger-c11"
|
|
||||||
: >"$LEDGER"
|
|
||||||
out="$(WAKE_ASSERT_LEDGER="$LEDGER" bash "$TMP/mini-c.sh" "$TMP" 2>&1)"
|
|
||||||
rc=$?
|
|
||||||
summary_ln="$(site_line "$TMP/mini-c.sh" c-summary)"
|
|
||||||
if [ "$rc" -eq 1 ] &&
|
|
||||||
printf '%s' "$out" | grep -q 'wake mini-c harness: FAILED (1 assertion(s))' &&
|
|
||||||
! printf '%s' "$out" | grep -q 'all invariants passed' &&
|
|
||||||
grep -q "^count_lines mini-c.sh:${summary_ln}\$" "$LEDGER"; then
|
|
||||||
check C11 0 ""
|
|
||||||
else
|
|
||||||
check C11 1 "rc=$rc summary_ln=$summary_ln ledger=$(tr '\n' ' ' <"$LEDGER") out=$(printf '%s' "$out" | tail -n 2 | tr '\n' ' ')"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo
|
|
||||||
if [ "$fails" -gt 0 ]; then
|
|
||||||
echo "microtest-wake-assert: FAILED ($fails check(s))" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
echo "microtest-wake-assert: OK (all checks passed)"
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
# unexecuted-sites-dispositions.txt — #973 validation, amendment ONE leg 3.
|
|
||||||
#
|
|
||||||
# The ledger is an execution trace, not an inventory: a green instrumented run
|
|
||||||
# cannot execute a site that only lives on a suite's red path. Every converted
|
|
||||||
# site that did NOT appear in the green-run trace is enumerated here with an
|
|
||||||
# individual disposition; validate-973.sh fails if any unexecuted site lacks a
|
|
||||||
# row here, and ALSO fails if a row here names a site that DID execute (stale
|
|
||||||
# disposition). Key = first two whitespace-separated fields; text after "—" is
|
|
||||||
# the adjudication.
|
|
||||||
#
|
|
||||||
# All nine sites below are the same structural shape, adjudicated one by one
|
|
||||||
# from source text: the suite's FAILED-branch summary line,
|
|
||||||
# echo "wake <name> harness: FAILED ($(count_lines . "$FAILFILE") assertion(s))" >&2
|
|
||||||
# guarded by `if [ -s "$FAILFILE" ]` — structurally unreachable while every
|
|
||||||
# assertion passes, which is precisely the state a green validation run is
|
|
||||||
# required to be in. (The tenth suite, test-wake-preimage.sh, uses its own
|
|
||||||
# X/Y summary format with no grep in the red branch, so it has no row here.)
|
|
||||||
#
|
|
||||||
# The disposition is NOT "it would work": the exact template is EXECUTED red
|
|
||||||
# in microtest C11 (deterministically failed mini-suite, same
|
|
||||||
# count_lines-in-substitution summary shape → right count, exit 1, ledger row
|
|
||||||
# at the summary coordinate), and the E-in-substitution abort path is proven
|
|
||||||
# by microtest C5 plus the forced-error arm at test-wake-store-ack.sh:736.
|
|
||||||
# Each site's conversion text is independently verified by the static
|
|
||||||
# inventory (check-973.py static == expected, all 261 rows).
|
|
||||||
#
|
|
||||||
# Verified guard per site (line numbers at branch tip, +3 header shift):
|
|
||||||
|
|
||||||
count_lines test-wake-beacon.sh:354 — red-path summary; guard `[ -s "$FAILFILE" ]` at line 353; template execution measured by microtest C11; text verified by static inventory
|
|
||||||
count_lines test-wake-detector.sh:706 — red-path summary; guard `[ -s "$FAILFILE" ]` at line 705; template execution measured by microtest C11; text verified by static inventory
|
|
||||||
count_lines test-wake-digest-hmac.sh:438 — red-path summary; guard `[ -s "$FAILFILE" ]` at line 437; template execution measured by microtest C11; text verified by static inventory
|
|
||||||
count_lines test-wake-digest-quarantine.sh:588 — red-path summary; guard `[ -s "$FAILFILE" ]` at line 587; template execution measured by microtest C11; text verified by static inventory
|
|
||||||
count_lines test-wake-fn-oracle.sh:136 — red-path summary; guard `[ -s "$FAILFILE" ]` at line 135; template execution measured by microtest C11; text verified by static inventory
|
|
||||||
count_lines test-wake-install.sh:438 — red-path summary; guard `[ -s "$FAILFILE" ]` at line 437; template execution measured by microtest C11; text verified by static inventory
|
|
||||||
count_lines test-wake-reconcile.sh:393 — red-path summary; guard `[ -s "$FAILFILE" ]` at line 392; template execution measured by microtest C11; text verified by static inventory
|
|
||||||
count_lines test-wake-store-ack.sh:745 — red-path summary; guard `[ -s "$FAILFILE" ]` at line 744; template execution measured by microtest C11; text verified by static inventory
|
|
||||||
count_lines test-wake-store-enqueue-race.sh:212 — red-path summary (with "#927 TOCTOU reproduced (RED)" tail); guard `[ -s "$FAILFILE" ]` at line 211; template execution measured by microtest C11; text verified by static inventory
|
|
||||||
@@ -1,218 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# validate-973.sh — #973 validation driver. One run produces the complete
|
|
||||||
# evidence chain for the 261-site conversion:
|
|
||||||
#
|
|
||||||
# 0. instrument self-test (microtest) — no validate evidence is trusted
|
|
||||||
# before the instrument itself has been proven, including its abort arms.
|
|
||||||
# 1. expected set: 261 coordinates from the FROZEN artifact (+7 header
|
|
||||||
# shift: 3 converter lines + 4 #984 guard lines), count asserted against the number declared below BEFORE any
|
|
||||||
# suite runs.
|
|
||||||
# 2. static inventory: converted call sites re-derived from SOURCE TEXT,
|
|
||||||
# must equal the expected set exactly (amendment ONE, leg 1 — the
|
|
||||||
# inventory comes from the text, never from the ledger).
|
|
||||||
# 3. green instrumented run: all ten suites with WAKE_ASSERT_LEDGER; each
|
|
||||||
# must exit 0 AND emit its own sentinel (per-suite formats differ and are
|
|
||||||
# pinned here — a suite that died early must never pass on another
|
|
||||||
# suite's output).
|
|
||||||
# 4. trace arithmetic on coordinate SETS (loops re-execute sites and the
|
|
||||||
# multi-grep lines append twice per pass, so counts are meaningless;
|
|
||||||
# sets are not):
|
|
||||||
# trace − expected MUST be empty (a helper ran at a coordinate the
|
|
||||||
# denominator never measured);
|
|
||||||
# expected − trace = converted-but-never-executed: enumerated, and
|
|
||||||
# every entry must carry a disposition in the
|
|
||||||
# committed unexecuted-sites-dispositions.txt, with
|
|
||||||
# no stale dispositions the other way (amendment
|
|
||||||
# ONE, legs 2+3 — the ledger is an execution trace,
|
|
||||||
# not an inventory; the difference is enumerated and
|
|
||||||
# individually dispositioned, never silently absent).
|
|
||||||
# 5. forced-error arms: the 19 denominator canaries plus one E-form and one
|
|
||||||
# F-form site, each run with WAKE_ASSERT_FORCE_GREP_ERROR_AT: the suite
|
|
||||||
# must emit the ARMED line (the arm proved it fired), the ABORT line
|
|
||||||
# naming the site, exit non-zero, emit NO sentinel, and the aborting
|
|
||||||
# site's ledger row must already be present (the append lands before the
|
|
||||||
# grep).
|
|
||||||
# 6. residual sweep: the denominator's own classifier finds zero unconverted
|
|
||||||
# verdict greps in the suites — and eight plants (six per-form + two
|
|
||||||
# absorb-branch probes, #985) in the same run.
|
|
||||||
#
|
|
||||||
# Output discipline (A10): every line that reports on a suite names the file
|
|
||||||
# under test; exit codes are reported before failure counts.
|
|
||||||
set -uo pipefail
|
|
||||||
|
|
||||||
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
||||||
WAKE="$(cd "$HERE/.." && pwd)"
|
|
||||||
CHECK="$HERE/check-973.py"
|
|
||||||
DISPO="$HERE/unexecuted-sites-dispositions.txt"
|
|
||||||
TMP="$(mktemp -d)"
|
|
||||||
trap 'rm -rf "$TMP"' EXIT
|
|
||||||
|
|
||||||
# Declared BEFORE any suite runs (A2): the run must produce THESE numbers,
|
|
||||||
# not be described by whatever numbers it produced.
|
|
||||||
EXPECTED_SUITES=10
|
|
||||||
EXPECTED_SITES=261
|
|
||||||
EXPECTED_ARMS=21
|
|
||||||
|
|
||||||
fails=0
|
|
||||||
flag() {
|
|
||||||
printf 'FAIL %s\n' "$*"
|
|
||||||
fails=$((fails + 1))
|
|
||||||
}
|
|
||||||
|
|
||||||
SUITES=(
|
|
||||||
test-wake-beacon.sh
|
|
||||||
test-wake-detector.sh
|
|
||||||
test-wake-digest-hmac.sh
|
|
||||||
test-wake-digest-quarantine.sh
|
|
||||||
test-wake-fn-oracle.sh
|
|
||||||
test-wake-install.sh
|
|
||||||
test-wake-preimage.sh
|
|
||||||
test-wake-reconcile.sh
|
|
||||||
test-wake-store-ack.sh
|
|
||||||
test-wake-store-enqueue-race.sh
|
|
||||||
)
|
|
||||||
[ "${#SUITES[@]}" -eq "$EXPECTED_SUITES" ] ||
|
|
||||||
flag "suite list has ${#SUITES[@]} entries, declared $EXPECTED_SUITES"
|
|
||||||
|
|
||||||
# Per-suite sentinel patterns, pinned: nine suites share the harness template
|
|
||||||
# (enqueue-race appends a tail after it); preimage uses its own format.
|
|
||||||
sentinel_for() {
|
|
||||||
case "$1" in
|
|
||||||
test-wake-preimage.sh) printf '%s' '^== test-wake-preimage: 17/17 passed ==$' ;;
|
|
||||||
*) printf '%s' 'harness: all invariants passed' ;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
# --- 0: instrument self-test ------------------------------------------------
|
|
||||||
if bash "$HERE/microtest-wake-assert.sh" >"$TMP/microtest.out" 2>&1; then
|
|
||||||
echo "MICROTEST microtest-wake-assert.sh exit=0 (instrument proven)"
|
|
||||||
else
|
|
||||||
rc=$?
|
|
||||||
echo "MICROTEST microtest-wake-assert.sh exit=$rc"
|
|
||||||
sed 's/^/ /' "$TMP/microtest.out" | tail -n 15
|
|
||||||
flag "instrument self-test failed — no validate evidence below is trustworthy"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# --- 1+2: expected set (artifact) vs static inventory (source text) ---------
|
|
||||||
python3 "$CHECK" expected | sort >"$TMP/expected.txt" ||
|
|
||||||
flag "check-973.py expected failed"
|
|
||||||
n_expected="$(grep -c . "$TMP/expected.txt")"
|
|
||||||
echo "EXPECTED-SET $n_expected coordinates (declared: $EXPECTED_SITES)"
|
|
||||||
[ "$n_expected" -eq "$EXPECTED_SITES" ] ||
|
|
||||||
flag "expected set has $n_expected coordinates, declared $EXPECTED_SITES"
|
|
||||||
|
|
||||||
python3 "$CHECK" static | sort >"$TMP/static.txt" ||
|
|
||||||
flag "check-973.py static failed"
|
|
||||||
if cmp -s "$TMP/expected.txt" "$TMP/static.txt"; then
|
|
||||||
echo "STATIC-INVENTORY equals expected set ($(grep -c . "$TMP/static.txt") rows from source text)"
|
|
||||||
else
|
|
||||||
flag "static inventory (source text) differs from expected set (artifact):"
|
|
||||||
diff "$TMP/expected.txt" "$TMP/static.txt" | head -n 20 | sed 's/^/ /'
|
|
||||||
fi
|
|
||||||
|
|
||||||
# --- 3: green instrumented run ----------------------------------------------
|
|
||||||
LEDGER="$TMP/ledger"
|
|
||||||
: >"$LEDGER"
|
|
||||||
for s in "${SUITES[@]}"; do
|
|
||||||
out="$(WAKE_ASSERT_LEDGER="$LEDGER" bash "$WAKE/$s" 2>&1)"
|
|
||||||
rc=$?
|
|
||||||
if printf '%s\n' "$out" | grep -Eq "$(sentinel_for "$s")"; then
|
|
||||||
sent="present"
|
|
||||||
else
|
|
||||||
sent="ABSENT"
|
|
||||||
fi
|
|
||||||
echo "SUITE $s exit=$rc sentinel=$sent"
|
|
||||||
[ "$rc" -eq 0 ] || flag "$s exited $rc in the green instrumented run"
|
|
||||||
[ "$sent" = "present" ] || flag "$s did not emit its sentinel"
|
|
||||||
done
|
|
||||||
|
|
||||||
# --- 4: trace arithmetic on coordinate sets ---------------------------------
|
|
||||||
sort -u "$LEDGER" >"$TMP/trace.txt"
|
|
||||||
echo "TRACE $(grep -c . "$TMP/trace.txt") distinct coordinates from $(grep -c . "$LEDGER") ledger rows"
|
|
||||||
|
|
||||||
comm -13 "$TMP/expected.txt" "$TMP/trace.txt" >"$TMP/rogue.txt"
|
|
||||||
if [ -s "$TMP/rogue.txt" ]; then
|
|
||||||
flag "trace contains coordinates OUTSIDE the frozen denominator:"
|
|
||||||
sed 's/^/ ROGUE /' "$TMP/rogue.txt"
|
|
||||||
else
|
|
||||||
echo "TRACE-MINUS-EXPECTED empty (no helper ran at an unmeasured coordinate)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
comm -23 "$TMP/expected.txt" "$TMP/trace.txt" >"$TMP/unexec.txt"
|
|
||||||
n_unexec="$(grep -c . "$TMP/unexec.txt" || true)"
|
|
||||||
echo "UNEXECUTED $n_unexec of $EXPECTED_SITES converted sites did not execute in the green run"
|
|
||||||
if [ ! -f "$DISPO" ]; then
|
|
||||||
flag "disposition file missing: $DISPO — every unexecuted site must be individually dispositioned"
|
|
||||||
sed 's/^/ UNDISPOSITIONED /' "$TMP/unexec.txt"
|
|
||||||
else
|
|
||||||
awk '!/^#/ && NF >= 2 {print $1, $2}' "$DISPO" | sort -u >"$TMP/dispo-keys.txt"
|
|
||||||
comm -23 "$TMP/unexec.txt" "$TMP/dispo-keys.txt" >"$TMP/undispo.txt"
|
|
||||||
comm -13 "$TMP/unexec.txt" "$TMP/dispo-keys.txt" >"$TMP/stale-dispo.txt"
|
|
||||||
if [ -s "$TMP/undispo.txt" ]; then
|
|
||||||
flag "unexecuted sites WITHOUT a disposition:"
|
|
||||||
sed 's/^/ UNDISPOSITIONED /' "$TMP/undispo.txt"
|
|
||||||
fi
|
|
||||||
if [ -s "$TMP/stale-dispo.txt" ]; then
|
|
||||||
flag "dispositions for sites that DID execute (stale — the file no longer matches the run):"
|
|
||||||
sed 's/^/ STALE-DISPO /' "$TMP/stale-dispo.txt"
|
|
||||||
fi
|
|
||||||
if [ ! -s "$TMP/undispo.txt" ] && [ ! -s "$TMP/stale-dispo.txt" ]; then
|
|
||||||
echo "DISPOSITIONS all $n_unexec unexecuted sites individually dispositioned, none stale"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# --- 5: forced-error arms ---------------------------------------------------
|
|
||||||
python3 "$CHECK" arms >"$TMP/arms.txt" || flag "check-973.py arms failed"
|
|
||||||
n_arms="$(grep -c . "$TMP/arms.txt")"
|
|
||||||
echo "ARMS $n_arms forced-error arms (declared: $EXPECTED_ARMS)"
|
|
||||||
[ "$n_arms" -eq "$EXPECTED_ARMS" ] ||
|
|
||||||
flag "arm list has $n_arms entries, declared $EXPECTED_ARMS"
|
|
||||||
|
|
||||||
while read -r helper site form; do
|
|
||||||
f="${site%%:*}"
|
|
||||||
aled="$TMP/ledger-arm"
|
|
||||||
: >"$aled"
|
|
||||||
out="$(WAKE_ASSERT_LEDGER="$aled" WAKE_ASSERT_FORCE_GREP_ERROR_AT="$site" \
|
|
||||||
bash "$WAKE/$f" 2>&1)"
|
|
||||||
rc=$?
|
|
||||||
bad=""
|
|
||||||
[ "$rc" -ne 0 ] || bad="$bad exit=0"
|
|
||||||
printf '%s\n' "$out" | grep -q "WAKE-ASSERT ARMED: forcing real grep error at $site" ||
|
|
||||||
bad="$bad no-ARMED-line"
|
|
||||||
printf '%s\n' "$out" | grep -q "WAKE-ASSERT ABORT: ${helper} at ${site}: grep exit" ||
|
|
||||||
bad="$bad no-ABORT-line"
|
|
||||||
# AND-polarity check (a match is the defect): a grep error (rc>=2) must be
|
|
||||||
# its own loud arm — it cannot fall through as "no sentinel = pass".
|
|
||||||
rc_sent=0
|
|
||||||
printf '%s\n' "$out" | grep -Eq "$(sentinel_for "$f")" || rc_sent=$?
|
|
||||||
case "$rc_sent" in
|
|
||||||
0) bad="$bad sentinel-emitted" ;;
|
|
||||||
1) : ;;
|
|
||||||
*) bad="$bad sentinel-grep-error-rc=$rc_sent" ;;
|
|
||||||
esac
|
|
||||||
grep -q "^${helper} ${site}\$" "$aled" ||
|
|
||||||
bad="$bad no-ledger-row"
|
|
||||||
if [ -z "$bad" ]; then
|
|
||||||
echo "ARM $site ($form) exit=$rc armed+abort+no-sentinel+ledger-row"
|
|
||||||
else
|
|
||||||
echo "ARM $site ($form) exit=$rc DEFECTS:$bad"
|
|
||||||
flag "arm $site ($form) failed:$bad"
|
|
||||||
fi
|
|
||||||
done <"$TMP/arms.txt"
|
|
||||||
|
|
||||||
# --- 6: residual sweep ------------------------------------------------------
|
|
||||||
if python3 "$CHECK" sweep >"$TMP/sweep.out" 2>&1; then
|
|
||||||
echo "SWEEP exit=0"
|
|
||||||
else
|
|
||||||
echo "SWEEP exit=$?"
|
|
||||||
flag "residual sweep failed"
|
|
||||||
fi
|
|
||||||
sed 's/^/ /' "$TMP/sweep.out"
|
|
||||||
|
|
||||||
# --- summary (exit codes above, failure count last — A10) --------------------
|
|
||||||
echo
|
|
||||||
if [ "$fails" -gt 0 ]; then
|
|
||||||
echo "validate-973: FAILED ($fails failure(s))" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
echo "validate-973: OK — $EXPECTED_SUITES suites, $EXPECTED_SITES sites, $EXPECTED_ARMS arms, sweep clean"
|
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
"lint": "eslint src",
|
"lint": "eslint src",
|
||||||
"typecheck": "tsc --noEmit",
|
"typecheck": "tsc --noEmit",
|
||||||
"test": "vitest run --passWithNoTests && pnpm run test:framework-shell",
|
"test": "vitest run --passWithNoTests && pnpm run test:framework-shell",
|
||||||
"test:framework-shell": "bash framework/tools/quality/scripts/check-test-enumeration.sh && bash framework/tools/quality/scripts/test-check-test-enumeration.sh && python3 src/lease-broker/daemon_deadline_unittest.py && python3 src/lease-broker/normative_fragments_unittest.py && python3 src/lease-broker/receipt_challenge_unittest.py && python3 src/lease-broker/context_recovery_unittest.py && python3 src/lease-broker/recovery_runtime_unittest.py && python3 src/lease-broker/recovery_b1_adversarial_unittest.py && python3 src/lease-broker/framework_skill_portability_unittest.py && python3 src/mutator-gate/runtime_tools_unittest.py && python3 src/mutator-gate/runtime_launch_guard_unittest.py && python3 src/mutator-gate/version_coupling_unittest.py && python3 framework/tools/lease-broker/check-runtime-launches.py --root ../.. && bash framework/tools/codex/test-pr-diff-context.sh && bash framework/tools/qa/test-deps-preflight.sh && bash framework/tools/git/test-pr-review-gitea-comment.sh && bash framework/tools/git/test-pr-review-repo-host-override.sh && bash framework/tools/git/test-ci-queue-wait-branch-absent.sh && bash framework/tools/git/test-git-credential-mosaic.sh && bash framework/tools/git/test-gitea-token-identity.sh && bash framework/tools/_scripts/test-install-ordering-guard.sh && bash framework/tools/tmux/agent-send.test.sh && bash framework/tools/wake/test-wake-store-ack.sh && bash framework/tools/wake/test-wake-store-enqueue-race.sh && bash framework/tools/wake/test-wake-digest-hmac.sh && bash framework/tools/wake/test-wake-digest-quarantine.sh && bash framework/tools/wake/test-wake-detector.sh && bash framework/tools/wake/test-wake-fn-oracle.sh && bash framework/tools/wake/test-wake-reconcile.sh && bash framework/tools/wake/test-wake-beacon.sh && bash framework/tools/wake/test-wake-preimage.sh && bash framework/tools/wake/test-wake-install.sh"
|
"test:framework-shell": "python3 src/lease-broker/daemon_deadline_unittest.py && python3 src/lease-broker/normative_fragments_unittest.py && python3 src/lease-broker/receipt_challenge_unittest.py && python3 src/lease-broker/context_recovery_unittest.py && python3 src/lease-broker/recovery_runtime_unittest.py && python3 src/lease-broker/recovery_b1_adversarial_unittest.py && python3 src/lease-broker/framework_skill_portability_unittest.py && python3 src/mutator-gate/runtime_tools_unittest.py && python3 src/mutator-gate/runtime_launch_guard_unittest.py && python3 src/mutator-gate/version_coupling_unittest.py && python3 framework/tools/lease-broker/check-runtime-launches.py --root ../.. && bash framework/tools/codex/test-pr-diff-context.sh && bash framework/tools/qa/test-deps-preflight.sh && bash framework/tools/git/test-pr-review-gitea-comment.sh && bash framework/tools/git/test-pr-review-repo-host-override.sh && bash framework/tools/git/test-ci-queue-wait-branch-absent.sh && bash framework/tools/git/test-git-credential-mosaic.sh && bash framework/tools/git/test-gitea-token-identity.sh && bash framework/tools/_scripts/test-install-ordering-guard.sh && bash framework/tools/tmux/agent-send.test.sh && bash framework/tools/wake/test-wake-store-ack.sh && bash framework/tools/wake/test-wake-store-enqueue-race.sh && bash framework/tools/wake/test-wake-digest-hmac.sh && bash framework/tools/wake/test-wake-digest-quarantine.sh && bash framework/tools/wake/test-wake-detector.sh && bash framework/tools/wake/test-wake-fn-oracle.sh && bash framework/tools/wake/test-wake-reconcile.sh && bash framework/tools/wake/test-wake-beacon.sh && bash framework/tools/wake/test-wake-preimage.sh && bash framework/tools/wake/test-wake-install.sh"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mosaicstack/brain": "workspace:*",
|
"@mosaicstack/brain": "workspace:*",
|
||||||
|
|||||||
Reference in New Issue
Block a user