fix(wake): #964 re-verdict — record-only polarity, both-forms deny, absent-ledger refusal
ci/woodpecker/pr/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful
Answers the NOT CLEAR verdict on #964 (B2 cases C+D, B11), new commit per the re-verdict terms. - B2-D (polarity): extras are RECORD-ONLY by default; byte capture for an extra requires WAKE_PREIMAGE_CAPTURE opt-in. Core set (adapter, watch-list) stays capture-eligible. Case D is safe by polarity alone — verified with the content shape list stubbed to always-allow. - B2-C (both forms): path deny evaluates raw AND realpath-resolved candidate forms against unresolved AND resolved mosaic-home anchors; deny runs first in the gate chain, so an allowlisted symlink cannot smuggle a denied target. - Content deny is defense-in-depth on the opt-in path only: six scrub shapes + named-assignment probe; probe errors fail TOWARD refusal (grep rc>=2 is a deny, not a fall-through). - B11: absent/empty ledger over non-empty objects/ is a loud non-zero refusal to re-baseline — that state cannot be a first install. - Suite: P2 moved to the core adapter; P4/P5/P10 exercise the opt-in path; new needles P13-P17 mirror every verdict probe (symlinked store, outside-store copy, live secret no-opt-in/opt-in, record-only tracking, ledger deletion, allowlist-symlink both halves). Red-first: pre-fix code fails exactly P13/P14/P15/P16 (14 assertions); fixed 17/17. P17's deny half is green pre-fix (basename rule) — kept as a guard needle, disclosed in the PR body. All 10 wake suites green. Written-by: pepper (sb-it-1-dt) Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01NsKce8iZuSuRnu3gVMCBKB
This commit is contained in:
co-authored by
Claude Fable 5
parent
e07943d742
commit
8aff7d8b47
@@ -23,6 +23,24 @@
|
||||
# P12 detector: preimage infra failure is LOUD (pass exits non-zero) but does
|
||||
# NOT starve source observation
|
||||
#
|
||||
# Regression needles from the #964 NOT-CLEAR review (decoy method: plant the
|
||||
# bytes, grep the WHOLE state dir — the decoy does not know what the code
|
||||
# believes):
|
||||
# P13 case C: symlinked/renamed credential store (mosaic-home itself behind
|
||||
# a symlink; a renamed target outside every path rule) — refused on BOTH
|
||||
# path forms / by content, bytes grep-absent (acceptance b)
|
||||
# P14 case D: prefix-less high-entropy key (detector.env class) — safe
|
||||
# WITHOUT opt-in by POLARITY alone (must hold with the shape list
|
||||
# deleted), and refused WITH opt-in by the named-assignment shape
|
||||
# P15 polarity: extras are RECORD-ONLY by default (change row + enqueue, no
|
||||
# bytes); byte capture requires explicit WAKE_PREIMAGE_CAPTURE opt-in
|
||||
# P16 case B11: ledger deleted while objects/ survives — LOUD refusal to
|
||||
# re-baseline; the v2->v3 change is NOT absorbed as first-seen
|
||||
# P17 allowlist symmetry: an allowlisted symlink whose TARGET is denied
|
||||
# refuses (deny runs first, on both forms — precedence is not path
|
||||
# agreement); a symlink to an ALLOWED target still captures (the fix
|
||||
# must not close case C by breaking every symlinked path)
|
||||
#
|
||||
# Uses FAKE/STUB sources only (no live network). Isolated per test.
|
||||
# shellcheck disable=SC2030,SC2031
|
||||
set -uo pipefail
|
||||
@@ -102,29 +120,31 @@ echo "== P1: first-seen -> SILENT baseline (rows, no enqueue) =="
|
||||
[ "$(depth)" = "0" ] || fail_msg "P1: first-seen must NOT enqueue (depth=$(depth))"
|
||||
) && ok
|
||||
|
||||
echo "== P2: change -> prior bytes + first-class hard-locator enqueue =="
|
||||
echo "== P2: adapter change -> prior bytes + first-class hard-locator enqueue =="
|
||||
(
|
||||
WAKE_STATE_HOME="$(fresh_state p2)"
|
||||
export WAKE_STATE_HOME
|
||||
unset WAKE_AGENT
|
||||
fx="$TMP_ROOT/p2fx"; mkdir -p "$fx"
|
||||
make_stub "$fx"; make_wl "$fx/wl.json"
|
||||
printf 'adapter preimage v1\n' >"$fx/extra.sh"
|
||||
export WAKE_DETECTOR_SOURCE_CMD="$fx/adapter.sh" WAKE_WATCH_LIST="$fx/wl.json" WAKE_PREIMAGE_EXTRA="$fx/extra.sh"
|
||||
# Acceptance (a) names the OPERATOR ADAPTER — a core member. Core members
|
||||
# capture by default (they ARE the preimage definition); extras do not (P15).
|
||||
export WAKE_DETECTOR_SOURCE_CMD="$fx/adapter.sh" WAKE_WATCH_LIST="$fx/wl.json"
|
||||
unset WAKE_PREIMAGE_EXTRA WAKE_PREIMAGE_CAPTURE MOSAIC_HOME
|
||||
"$PRE" check --enqueue >/dev/null 2>&1 || fail_msg "P2: baseline failed"
|
||||
old_sha="$(sha256sum "$fx/extra.sh" | awk '{print $1}')"
|
||||
printf 'adapter preimage v2 CHANGED\n' >"$fx/extra.sh"
|
||||
new_sha="$(sha256sum "$fx/extra.sh" | awk '{print $1}')"
|
||||
cp "$fx/adapter.sh" "$fx/adapter.v1"
|
||||
old_sha="$(sha256sum "$fx/adapter.sh" | awk '{print $1}')"
|
||||
printf '# adapter v2 CHANGED\n' >>"$fx/adapter.sh"
|
||||
new_sha="$(sha256sum "$fx/adapter.sh" | awk '{print $1}')"
|
||||
out="$("$PRE" check --enqueue 2>&1)"; rc=$?
|
||||
[ "$rc" -eq 0 ] || fail_msg "P2: change check must exit 0 (rc=$rc) [$out]"
|
||||
sd="$(state_dir)"
|
||||
row="$(jq -c --arg p "$(realpath "$fx/extra.sh")" 'select(.path == $p)' "$sd/preimage/preimage-ledger.jsonl" | tail -n1)"
|
||||
row="$(jq -c --arg p "$(realpath "$fx/adapter.sh")" 'select(.path == $p)' "$sd/preimage/preimage-ledger.jsonl" | tail -n1)"
|
||||
[ "$(jq -r '.prev // ""' <<<"$row")" = "$old_sha" ] || fail_msg "P2: new row must carry prev=<old sha> [$row]"
|
||||
[ "$(jq -r '.sha256' <<<"$row")" = "$new_sha" ] || fail_msg "P2: new row sha mismatch [$row]"
|
||||
# Acceptance (a): the PRIOR BYTES are retrievable from durable state alone.
|
||||
[ -f "$sd/preimage/objects/$old_sha" ] || fail_msg "P2: prior bytes object missing"
|
||||
[ "$(cat "$sd/preimage/objects/$old_sha")" = "adapter preimage v1" ] || fail_msg "P2: prior bytes not byte-exact"
|
||||
[ -f "$sd/preimage/objects/$new_sha" ] || fail_msg "P2: current bytes object missing"
|
||||
cmp -s "$sd/preimage/objects/$old_sha" "$fx/adapter.v1" || fail_msg "P2: prior bytes missing or not byte-exact"
|
||||
cmp -s "$sd/preimage/objects/$new_sha" "$fx/adapter.sh" || fail_msg "P2: current bytes object missing or mismatched"
|
||||
# Acceptance (c): ONE first-class actionable with a §2.1 hard locator (path).
|
||||
[ "$(depth)" = "1" ] || fail_msg "P2: exactly one enqueue expected (depth=$(depth))"
|
||||
ent="$(tail -n1 "$sd/pending.jsonl")"
|
||||
@@ -171,8 +191,11 @@ echo "== P4: credential-store PATH refused — bytes never captured (acceptance
|
||||
mkdir -p "$MOSAIC_HOME"
|
||||
secret='cred-value-P4-do-not-capture'
|
||||
printf '{"svc":{"token":"%s"}}\n' "$secret" >"$MOSAIC_HOME/credentials.json"
|
||||
# Operator error: the credential store listed as a preimage extra.
|
||||
# Operator error: the credential store listed as a preimage extra — AND
|
||||
# explicitly opted in to capture. Deny must win over the opt-in (#964: a
|
||||
# path both denied and allowlisted must refuse).
|
||||
export WAKE_PREIMAGE_EXTRA="$MOSAIC_HOME/credentials.json"
|
||||
export WAKE_PREIMAGE_CAPTURE="$MOSAIC_HOME/credentials.json"
|
||||
unset WAKE_DETECTOR_SOURCE_CMD WAKE_WATCH_LIST
|
||||
out="$("$PRE" check --enqueue 2>&1)"; rc=$?
|
||||
[ "$rc" -eq 0 ] || fail_msg "P4: refusal is not an infra failure (rc=$rc) [$out]"
|
||||
@@ -194,7 +217,8 @@ echo "== P5: secret-SHAPED content refused (refusal, not redaction) =="
|
||||
fx="$TMP_ROOT/p5fx"; mkdir -p "$fx"
|
||||
tok='ghp_abcdefghijklmnop0123456789ABCDEF'
|
||||
printf 'export MY_TOKEN=%s\n' "$tok" >"$fx/leaky.env"
|
||||
export WAKE_PREIMAGE_EXTRA="$fx/leaky.env"
|
||||
# Opted in, so the CONTENT gate (not record-only polarity) is what refuses.
|
||||
export WAKE_PREIMAGE_EXTRA="$fx/leaky.env" WAKE_PREIMAGE_CAPTURE="$fx/leaky.env"
|
||||
unset WAKE_DETECTOR_SOURCE_CMD WAKE_WATCH_LIST MOSAIC_HOME
|
||||
out="$("$PRE" check --enqueue 2>&1)"; rc=$?
|
||||
[ "$rc" -eq 0 ] || fail_msg "P5: refusal is not an infra failure (rc=$rc) [$out]"
|
||||
@@ -281,7 +305,9 @@ echo "== P10: oversized file -> bytes refused, hash still recorded =="
|
||||
unset WAKE_AGENT
|
||||
fx="$TMP_ROOT/p10fx"; mkdir -p "$fx"
|
||||
head -c 200 /dev/zero | tr '\0' 'A' >"$fx/big.bin"
|
||||
# Opted in, so the SIZE gate (not record-only polarity) is what refuses.
|
||||
export WAKE_PREIMAGE_EXTRA="$fx/big.bin" WAKE_PREIMAGE_MAX_BYTES=64
|
||||
export WAKE_PREIMAGE_CAPTURE="$fx/big.bin"
|
||||
unset WAKE_DETECTOR_SOURCE_CMD WAKE_WATCH_LIST MOSAIC_HOME
|
||||
out="$("$PRE" check --enqueue 2>&1)"; rc=$?
|
||||
[ "$rc" -eq 0 ] || fail_msg "P10: size refusal is not an infra failure (rc=$rc) [$out]"
|
||||
@@ -341,6 +367,166 @@ echo "== P12: detector — preimage infra failure is LOUD but does not starve ob
|
||||
[ "$n" -ge 1 ] || fail_msg "P12: source observation must still proceed (no watch hash baselined)"
|
||||
) && ok
|
||||
|
||||
echo "== P13: symlinked/renamed credential store refused on BOTH path forms (#964 C) =="
|
||||
(
|
||||
WAKE_STATE_HOME="$(fresh_state p13)"
|
||||
export WAKE_STATE_HOME
|
||||
unset WAKE_AGENT
|
||||
fx="$TMP_ROOT/p13fx"; mkdir -p "$fx"
|
||||
# The mosaic home is ITSELF behind a symlink: a deny list written in
|
||||
# unresolved paths cannot match a candidate that was realpath'd before the
|
||||
# deny saw it — unless the anchors are resolved too.
|
||||
mkdir -p "$fx/real-store/credentials"
|
||||
ln -s "$fx/real-store" "$fx/mh"
|
||||
export MOSAIC_HOME="$fx/mh"
|
||||
s1='decoy-P13a-renamed-target-under-store'
|
||||
printf 'x_token=%s\n' "$s1" >"$fx/mh/credentials/brain-creds.json"
|
||||
# And a renamed target OUTSIDE every known store location, reached via a
|
||||
# benign-looking symlink: no path rule can name it — the content gate must
|
||||
# hold on the resolved file.
|
||||
s2='0123456789abcdef0123456789abcdefP13b'
|
||||
printf 'service_hmac_key=%s\n' "$s2" >"$fx/renamed-anywhere.cfg"
|
||||
ln -s "$fx/renamed-anywhere.cfg" "$fx/link-b.env"
|
||||
export WAKE_PREIMAGE_EXTRA="$fx/mh/credentials/brain-creds.json:$fx/link-b.env"
|
||||
# Explicit opt-in for BOTH: deny must win over the allowlist.
|
||||
export WAKE_PREIMAGE_CAPTURE="$fx/mh/credentials/brain-creds.json:$fx/link-b.env"
|
||||
unset WAKE_DETECTOR_SOURCE_CMD WAKE_WATCH_LIST
|
||||
out="$("$PRE" check --enqueue 2>&1)"; rc=$?
|
||||
[ "$rc" -eq 0 ] || fail_msg "P13: refusal is not an infra failure (rc=$rc) [$out]"
|
||||
sd="$(state_dir)"
|
||||
n="$(jq -r 'select(.captured == false) | .path' "$sd/preimage/preimage-ledger.jsonl" | wc -l | tr -d '[:space:]')"
|
||||
[ "$n" = "2" ] || fail_msg "P13: both decoys must record captured=false (got $n)"
|
||||
# THE invariant (decoy method): the planted bytes exist NOWHERE in state.
|
||||
if grep -rq "$s1" "$sd" 2>/dev/null; then
|
||||
fail_msg "P13: renamed-target store bytes leaked into the wake state dir"
|
||||
fi
|
||||
if grep -rq "$s2" "$sd" 2>/dev/null; then
|
||||
fail_msg "P13: prefix-less key bytes leaked into the wake state dir"
|
||||
fi
|
||||
) && ok
|
||||
|
||||
echo "== P14: detector.env-class key — safe WITHOUT opt-in by polarity, refused WITH opt-in by shape (#964 D) =="
|
||||
(
|
||||
WAKE_STATE_HOME="$(fresh_state p14)"
|
||||
export WAKE_STATE_HOME
|
||||
unset WAKE_AGENT
|
||||
fx="$TMP_ROOT/p14fx"; mkdir -p "$fx"
|
||||
hm='9f8e7d6c5b4a39281706f5e4d3c2b1a09f8e7d6c'
|
||||
printf 'WAKE_HMAC_KEY=%s\n' "$hm" >"$fx/detector.env"
|
||||
export WAKE_PREIMAGE_EXTRA="$fx/detector.env"
|
||||
unset WAKE_DETECTOR_SOURCE_CMD WAKE_WATCH_LIST MOSAIC_HOME WAKE_PREIMAGE_CAPTURE
|
||||
# (a) NO opt-in: polarity alone keeps the bytes out. This leg must hold even
|
||||
# with the content shape list deleted — the shape is defense-in-depth for
|
||||
# the opt-in path, never the thing that makes case D read safe.
|
||||
out="$("$PRE" check --enqueue 2>&1)"; rc=$?
|
||||
[ "$rc" -eq 0 ] || fail_msg "P14a: record-only tracking must succeed (rc=$rc) [$out]"
|
||||
sd="$(state_dir)"
|
||||
row="$(tail -n1 "$sd/preimage/preimage-ledger.jsonl")"
|
||||
[ "$(jq -r '.captured' <<<"$row")" = "false" ] || fail_msg "P14a: extra must be record-only without opt-in [$row]"
|
||||
if grep -rq "$hm" "$sd" 2>/dev/null; then
|
||||
fail_msg "P14a: key bytes leaked without any opt-in"
|
||||
fi
|
||||
# (b) The operator opts the env file in (the exact error the old usage text
|
||||
# invited): the named-assignment shape must still refuse the bytes.
|
||||
export WAKE_PREIMAGE_CAPTURE="$fx/detector.env"
|
||||
printf 'WAKE_HMAC_KEY=%s\nrotated=1\n' "$hm" >"$fx/detector.env"
|
||||
out="$("$PRE" check --enqueue 2>&1)"; rc=$?
|
||||
[ "$rc" -eq 0 ] || fail_msg "P14b: refusal is not an infra failure (rc=$rc) [$out]"
|
||||
row="$(tail -n1 "$sd/preimage/preimage-ledger.jsonl")"
|
||||
[ "$(jq -r '.captured' <<<"$row")" = "false" ] || fail_msg "P14b: opted-in key material must still refuse [$row]"
|
||||
jq -r '.refused // ""' <<<"$row" | grep -qi 'secret' || fail_msg "P14b: refusal must name secret-shaped content [$row]"
|
||||
if grep -rq "$hm" "$sd" 2>/dev/null; then
|
||||
fail_msg "P14b: key bytes leaked despite refusal"
|
||||
fi
|
||||
) && ok
|
||||
|
||||
echo "== P15: extras are RECORD-ONLY by default; capture requires explicit opt-in =="
|
||||
(
|
||||
WAKE_STATE_HOME="$(fresh_state p15)"
|
||||
export WAKE_STATE_HOME
|
||||
unset WAKE_AGENT
|
||||
fx="$TMP_ROOT/p15fx"; mkdir -p "$fx"
|
||||
printf 'plain v1\n' >"$fx/notes.cfg"
|
||||
export WAKE_PREIMAGE_EXTRA="$fx/notes.cfg"
|
||||
unset WAKE_DETECTOR_SOURCE_CMD WAKE_WATCH_LIST MOSAIC_HOME WAKE_PREIMAGE_CAPTURE
|
||||
"$PRE" check --enqueue >/dev/null 2>&1 || fail_msg "P15: baseline failed"
|
||||
printf 'plain v2\n' >"$fx/notes.cfg"
|
||||
sha2="$(sha256sum "$fx/notes.cfg" | awk '{print $1}')"
|
||||
"$PRE" check --enqueue >/dev/null 2>&1 || fail_msg "P15: change check failed"
|
||||
sd="$(state_dir)"
|
||||
row="$(tail -n1 "$sd/preimage/preimage-ledger.jsonl")"
|
||||
[ "$(jq -r '.captured' <<<"$row")" = "false" ] || fail_msg "P15: default must be record-only [$row]"
|
||||
[ ! -f "$sd/preimage/objects/$sha2" ] || fail_msg "P15: bytes captured without opt-in"
|
||||
[ "$(depth)" = "1" ] || fail_msg "P15: record-only must still track the change (depth=$(depth))"
|
||||
# Opt in -> the next change captures.
|
||||
export WAKE_PREIMAGE_CAPTURE="$fx/notes.cfg"
|
||||
printf 'plain v3\n' >"$fx/notes.cfg"
|
||||
sha3="$(sha256sum "$fx/notes.cfg" | awk '{print $1}')"
|
||||
"$PRE" check --enqueue >/dev/null 2>&1 || fail_msg "P15: opted-in change check failed"
|
||||
row="$(tail -n1 "$sd/preimage/preimage-ledger.jsonl")"
|
||||
[ "$(jq -r '.captured' <<<"$row")" = "true" ] || fail_msg "P15: opt-in must capture [$row]"
|
||||
cmp -s "$sd/preimage/objects/$sha3" "$fx/notes.cfg" || fail_msg "P15: opted-in bytes missing or mismatched"
|
||||
) && ok
|
||||
|
||||
echo "== P16: deleted ledger over surviving objects/ — REFUSE to re-baseline (#964 B11) =="
|
||||
(
|
||||
WAKE_STATE_HOME="$(fresh_state p16)"
|
||||
export WAKE_STATE_HOME
|
||||
unset WAKE_AGENT
|
||||
fx="$TMP_ROOT/p16fx"; mkdir -p "$fx"
|
||||
make_stub "$fx"
|
||||
export WAKE_DETECTOR_SOURCE_CMD="$fx/adapter.sh"
|
||||
unset WAKE_WATCH_LIST WAKE_PREIMAGE_EXTRA WAKE_PREIMAGE_CAPTURE MOSAIC_HOME
|
||||
"$PRE" check >/dev/null 2>&1 || fail_msg "P16: v1 baseline failed"
|
||||
printf '# v2\n' >>"$fx/adapter.sh"
|
||||
"$PRE" check >/dev/null 2>&1 || fail_msg "P16: v2 change failed"
|
||||
sd="$(state_dir)"
|
||||
nobj="$(ls "$sd/preimage/objects" | wc -l | tr -d '[:space:]')"
|
||||
# Delete the ledger; objects/ survives. ABSENT is not CORRUPT — and it is
|
||||
# not a first install either: that asymmetry is locally detectable.
|
||||
rm -f "$sd/preimage/preimage-ledger.jsonl"
|
||||
printf '# v3\n' >>"$fx/adapter.sh"
|
||||
out="$("$PRE" check --enqueue 2>&1)"; rc=$?
|
||||
[ "$rc" -ne 0 ] || fail_msg "P16: absent ledger over prior objects must FAIL LOUD (rc=0) [$out]"
|
||||
echo "$out" | grep -qi 'REFUSING to re-baseline' || fail_msg "P16: the failure must name the refusal [$out]"
|
||||
[ ! -e "$sd/preimage/preimage-ledger.jsonl" ] || fail_msg "P16: the ledger must NOT be silently recreated over deleted history"
|
||||
[ "$(ls "$sd/preimage/objects" | wc -l | tr -d '[:space:]')" = "$nobj" ] || fail_msg "P16: prior objects must remain untouched"
|
||||
[ "$(depth)" = "0" ] || fail_msg "P16: the v2->v3 change must NOT be absorbed or enqueued from an unverifiable baseline (depth=$(depth))"
|
||||
) && ok
|
||||
|
||||
echo "== P17: allowlist symmetry — symlink to a DENIED target refuses; symlink to an allowed target captures =="
|
||||
(
|
||||
WAKE_STATE_HOME="$(fresh_state p17)"
|
||||
export WAKE_STATE_HOME
|
||||
unset WAKE_AGENT
|
||||
fx="$TMP_ROOT/p17fx"; mkdir -p "$fx"
|
||||
export MOSAIC_HOME="$fx/mh"
|
||||
mkdir -p "$MOSAIC_HOME"
|
||||
s='decoy-P17-cred-behind-benign-alias'
|
||||
printf '{"svc":{"token":"%s"}}\n' "$s" >"$MOSAIC_HOME/credentials.json"
|
||||
# A benign-looking alias whose TARGET is denied: the allowlist matches the
|
||||
# alias string, but deny runs FIRST and on BOTH forms — "deny wins over
|
||||
# opt-in" is a precedence rule, path agreement is what makes it hold.
|
||||
ln -s "$MOSAIC_HOME/credentials.json" "$fx/settings.json"
|
||||
# And a symlink to an ALLOWED target: the fix must not close case C by
|
||||
# breaking every symlinked path outright.
|
||||
printf 'benign payload v1\n' >"$fx/target.cfg"
|
||||
ln -s "$fx/target.cfg" "$fx/alias.cfg"
|
||||
export WAKE_PREIMAGE_EXTRA="$fx/settings.json:$fx/alias.cfg"
|
||||
export WAKE_PREIMAGE_CAPTURE="$fx/settings.json:$fx/alias.cfg"
|
||||
unset WAKE_DETECTOR_SOURCE_CMD WAKE_WATCH_LIST
|
||||
out="$("$PRE" check --enqueue 2>&1)"; rc=$?
|
||||
[ "$rc" -eq 0 ] || fail_msg "P17: check must exit 0 (rc=$rc) [$out]"
|
||||
sd="$(state_dir)"
|
||||
crow="$(jq -c --arg p "$(realpath "$fx/settings.json")" 'select(.path == $p)' "$sd/preimage/preimage-ledger.jsonl" | tail -n1)"
|
||||
[ "$(jq -r '.captured' <<<"$crow")" = "false" ] || fail_msg "P17: allowlisted symlink to a denied target must refuse [$crow]"
|
||||
if grep -rq "$s" "$sd" 2>/dev/null; then
|
||||
fail_msg "P17: credential bytes leaked via an allowlisted alias"
|
||||
fi
|
||||
brow="$(jq -c --arg p "$(realpath "$fx/alias.cfg")" 'select(.path == $p)' "$sd/preimage/preimage-ledger.jsonl" | tail -n1)"
|
||||
[ "$(jq -r '.captured' <<<"$brow")" = "true" ] || fail_msg "P17: symlink to an allowed target must still capture [$brow]"
|
||||
) && ok
|
||||
|
||||
echo
|
||||
total=$((pass + $(wc -l <"$FAILFILE")))
|
||||
echo "== test-wake-preimage: $pass/$total passed =="
|
||||
|
||||
Reference in New Issue
Block a user