Compare commits

..
Author SHA1 Message Date
Jason WoltjeandClaude Fable 5 8aff7d8b47 fix(wake): #964 re-verdict — record-only polarity, both-forms deny, absent-ledger refusal
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
2026-07-30 16:29:33 -05:00
Jason WoltjeandClaude Fable 5 e07943d742 feat(wake): #958 A11 preimage.sh — durable provenance for the operator-side preimage definition
ci/woodpecker/pr/ci Pipeline was successful
The operator-owned bytes every observed_hash is computed FROM (the source
adapter, the watch-list) were unversioned: a byte change was attributable
only via an agent transcript, and reconcile surfaced it as N UNACCOUNTED
sources instead of one cause.

New tool preimage.sh (wake 0.6.15 -> 0.7.0):
- Derives the preimage set from the runtime env (resolved
  WAKE_DETECTOR_SOURCE_CMD file, WAKE_WATCH_LIST, WAKE_PREIMAGE_EXTRA);
  per file appends {ts,path,sha256,size,mtime,prev} to an append-only
  ledger and captures bytes content-addressed under
  $STATE_DIR/preimage/objects/<sha256> — prior bytes + change time from
  durable state alone (acceptance a).
- CREDENTIAL HARD GATE (acceptance b): byte capture is REFUSED — never
  redacted — for credential-store paths, secret-shaped content (digest's
  six scrub shapes as a content-deny), and files over
  WAKE_PREIMAGE_MAX_BYTES; the refused file still gets its
  hash/size/mtime row (captured:false) so change TIME survives.
- FIRST-CLASS CAUSE LINE (acceptance c): a change/deletion enqueues one
  class=actionable entry via the store allocator with path as its §2.1
  hard locator; detector poll-once and reconcile run the check as a
  PRE-step so the cause line lands at a LOWER observed_seq than the
  deltas/enumerations it explains.
- FAIL-LOUD (D2/#955 class): unresolvable adapter, corrupt ledger
  (refuses re-baseline), failed object/ledger write, failed enqueue are
  loud non-zero, never "no change"; in both integrations the pass exits
  non-zero but source observation still proceeds.

Installer unchanged (Gate A auto-enumerates the new file; the recording
site is the runtime tick where the env-derived set exists). Watch-list
schema untouched ([1,1]).

Tests: test-wake-preimage.sh P1-P12 (red-first verified: P3/P11/P12 fail
with the integration edits reverted); all 9 existing wake suites green.

Refs #958

Agent: PEPPER (sb-it-1-dt)

Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01NsKce8iZuSuRnu3gVMCBKB
Written-by: pepper (sb-it-1-dt)
2026-07-30 15:48:50 -05:00
3 changed files with 9 additions and 90 deletions
@@ -447,24 +447,7 @@
# #964 re-verdict regression needles: symlinked store, live
# secret value, polarity, ledger deletion, allowlist-symlink).
component=wake
# 0.7.1 #952 quarantine-audit clean-sweep message named only ONE of the
# two unprovable residual classes ("rows without surviving
# dead-letter evidence"), so an operator reading the OK concluded
# NO evidence exists when evidence can exist and be UNUSABLE: a
# surviving dead-letter row whose locator extracts an empty
# observed_hash (live specimen: mos-dt seq 13,
# bench/malformed-locator-test, "deliberately non-conformant")
# can never satisfy the four-field conviction match, because
# _record_last_consumed only writes rows with a NON-empty hash.
# WORDING-ONLY fix (measurement defect, #951 review finding 1):
# the clean-sweep message + the PROVABILITY BOUND comment now
# name both classes; the conviction predicate is UNCHANGED.
# Test T17 drives the real writer flow and plants the verbatim
# live specimen (nested .locators.*, NO observed_hash key) —
# never a hand-built flat dead-letter row, which would make the
# audit's correct non-conviction look exactly like the defect
# under hunt (the #951 review's false-defect near-miss).
version=0.7.1
version=0.7.0
# Watch-list schema this component consumes, and the INCLUSIVE range of
# schema_version values it supports. A wake-watch-list.json whose schema_version
+8 -19
View File
@@ -110,13 +110,9 @@ Commands:
found); --repair removes exactly those
rows (atomic, loud). The dead-letter
ledger itself is NEVER modified (it is
history). TWO residual classes are
unprovable and never touched (#952):
rows whose dead-letter evidence was
pruned away, AND rows whose surviving
evidence extracts an empty
observed_hash (it can never satisfy
the four-field conviction match).
history). Rows whose dead-letter evidence
was pruned are NOT provable and are
never touched.
cursors Print observed_seq / consumed_seq / depth.
Environment:
@@ -567,17 +563,10 @@ cmd_quarantine_sync() {
# it on (kind, id, observed_seq, observed_hash) AND row.observed_seq <=
# consumed_seq: the per-key max_by merge means the surviving row's provenance IS
# that quarantined entry (a healed row differs in seq/hash and never matches).
# PROVABILITY BOUND — TWO residual classes, both unprovable (#952): (1) a row
# whose dead-letter evidence was pruned/rotated away — no evidence to convict
# on; (2) a row whose dead-letter evidence SURVIVES but extracts an empty
# observed_hash (e.g. a deliberately non-conformant locator: nested
# .locators.* with no observed_hash key) — evidence exists but can never
# satisfy the four-field match, because _record_last_consumed only ever writes
# rows with a NON-empty hash. Neither class is touched — this audit only ever
# removes what the ledger can convict, and the clean-sweep message names BOTH
# classes: "no evidence" and "evidence unusable" are different operator
# conclusions. The dead-letter ledger itself is history and is NEVER modified
# here.
# PROVABILITY BOUND: a row whose dead-letter evidence was pruned/rotated away is
# NOT provable and is never touched — this audit only ever removes what the
# ledger can convict. The dead-letter ledger itself is history and is NEVER
# modified here.
cmd_quarantine_audit() {
local repair=0
while [ $# -gt 0 ]; do
@@ -616,7 +605,7 @@ cmd_quarantine_audit() {
)) | length) > 0)
' "$rec" 2>/dev/null || true)"
if [ -z "$false_rows" ]; then
echo "store.sh quarantine-audit: OK — no provably-false consumed-hash rows. Two residual classes are unprovable and were NOT judged: rows whose dead-letter evidence was pruned/rotated away (no evidence to convict on), and rows whose surviving dead-letter evidence extracts an empty observed_hash (evidence exists but can never satisfy the four-field conviction match)."
echo "store.sh quarantine-audit: OK — no provably-false consumed-hash rows (rows without surviving dead-letter evidence are not provable and were not judged)"
return 0
fi
local n row
@@ -28,15 +28,6 @@
# T16 #946 quarantine-audit: consumed-hashes rows provably false against the
# dead-letter ledger are reported (exit 1) and removed only under --repair;
# healed rows and the ledger itself are untouched
# T17 #952 quarantine-audit clean sweep names BOTH unprovable residual
# classes: evidence pruned (no evidence) AND evidence surviving with an
# empty observed_hash (evidence unusable). Fixture is the VERBATIM live
# specimen (mos-dt lane dead-letter seq 13, bench/malformed-locator-test)
# — a nested .locators.* row with NO observed_hash key. Do NOT hand-build
# a FLAT dead-letter fixture here: consumed-hashes rows are genuinely
# flat, dead-letter rows are genuinely nested, and a flat fixture makes
# the audit's CORRECT non-conviction look exactly like the defect under
# hunt (this false-defect near-miss actually happened in the #951 review)
#
# Isolated: every test runs against a fresh WAKE_STATE_HOME temp dir.
set -uo pipefail
@@ -689,50 +680,6 @@ echo "== T16: #946 — quarantine-audit: a consumed-hash row matching a dead-let
grep -qi 'OK' "$TMP_ROOT/t16.ok" || fail_msg "T16: a clean audit must say OK [$(cat "$TMP_ROOT/t16.ok")]"
) && ok
echo "== T17: #952 — the clean-sweep message names BOTH unprovable residual classes; a surviving-but-empty-hash dead-letter row is correctly NOT convicted =="
(
WAKE_STATE_HOME="$(fresh_state t17)"
export WAKE_STATE_HOME
unset WAKE_AGENT
STATE_DIR="$WAKE_STATE_HOME/default"
rec="$STATE_DIR/consumed-hashes.jsonl"
dl="$STATE_DIR/dead-letter.jsonl"
# Drive the REAL flow so the consumed-hashes row is produced by the actual
# writer (_record_last_consumed), not hand-built: 12 fillers, then the bench
# key lands at observed_seq 13 — the same seq as the live specimen below.
i=1
while [ "$i" -le 12 ]; do
"$STORE" enqueue --class actionable --locators "{\"kind\":\"filler\",\"id\":\"f$i\",\"observed_hash\":\"HF$i\"}" >/dev/null || fail_msg "T17: filler enqueue $i failed"
i=$((i + 1))
done
"$STORE" enqueue --class actionable --locators '{"kind":"bench","id":"malformed-locator-test","observed_hash":"H-REAL-CONSUMED"}' >/dev/null
"$STORE" consume --upto 13 >/dev/null 2>&1 || fail_msg "T17: baseline consume failed"
jq_any "$rec" '.kind=="bench" and .id=="malformed-locator-test" and .observed_seq==13 and .observed_hash=="H-REAL-CONSUMED"' ||
fail_msg "T17: fixture broken — the real writer did not record the bench@13 row"
# VERBATIM live specimen: mos-dt lane dead-letter.jsonl line 1 (2026-07-26).
# Copied byte-for-byte per the #952 fixture constraint — nested .locators.*
# with NO observed_hash key, so the audit's extraction yields "".
printf '%s\n' '{"observed_seq":13,"locators":{"kind":"bench","id":"malformed-locator-test","note":"deliberately non-conformant"},"class":"actionable","emit_ts":1785055610,"hmac":""}' >"$dl"
# Guard the near-miss trap: the specimen must be genuinely NESTED (no
# top-level kind) and must extract an EMPTY hash via the audit's own paths.
jq -e '(has("kind") | not) and .locators.kind=="bench" and ((.locators.observed_hash // "") == "")' "$dl" >/dev/null ||
fail_msg "T17: specimen fixture is not the genuine nested shape — rebuild it from the live lane, not the schema"
# Evidence SURVIVES (kind/id/seq all match the row) but extracts hash "";
# _record_last_consumed never writes an empty-hash row, so the four-field
# match can never fire: this is unprovable class 2, NOT a false witness.
rep="$TMP_ROOT/t17.rep"
"$STORE" quarantine-audit >"$rep" 2>&1 || fail_msg "T17: the audit must exit 0 — nothing here is provable [$(cat "$rep")]"
grep -q 'FALSE WITNESS' "$rep" && fail_msg "T17: the empty-hash evidence must NOT convict (the predicate is correct and must not change) [$(cat "$rep")]"
# The wording under test (#952): BOTH residual classes, named.
grep -qi 'pruned' "$rep" || fail_msg "T17: clean sweep must name residual class 1 — evidence pruned away [$(cat "$rep")]"
grep -qi 'empty observed_hash' "$rep" || fail_msg "T17: clean sweep must name residual class 2 — surviving evidence with an empty observed_hash [$(cat "$rep")]"
# --repair on a clean sweep removes nothing: the unprovable row survives.
"$STORE" quarantine-audit --repair >"$TMP_ROOT/t17.fix" 2>&1 || fail_msg "T17: --repair on a clean sweep must exit 0 [$(cat "$TMP_ROOT/t17.fix")]"
jq_any "$rec" '.kind=="bench" and .observed_seq==13 and .observed_hash=="H-REAL-CONSUMED"' ||
fail_msg "T17: --repair must NOT remove the unprovable bench@13 row — the audit only removes what the ledger can convict"
[ "$(grep -c . "$dl")" = "1" ] || fail_msg "T17: the dead-letter LEDGER must be untouched"
) && ok
echo
if [ -s "$FAILFILE" ]; then
echo "wake store/ack harness: FAILED ($(grep -c . "$FAILFILE") assertion(s))" >&2