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
This commit is contained in:
Jason Woltje
2026-07-30 16:29:33 -05:00
co-authored by Claude Fable 5
parent e07943d742
commit 8aff7d8b47
3 changed files with 427 additions and 91 deletions
@@ -390,15 +390,33 @@
# (acceptance a). A git-repo-in-operator-dir design was REJECTED:
# its who/why claim is false under shared-author fleets, and
# .gitignore is pattern-based where acceptance (b) demands
# fail-closed. (2) CREDENTIAL HARD GATE (acceptance b): byte
# capture is REFUSED — never redacted — for (i) credential-store
# PATHS (mosaic-home credentials.json, tools/_lib/credentials.json,
# credentials/**, any basename credentials.json), (ii)
# secret-SHAPED content (digest.sh's six scrub shapes reused as a
# content-deny grep), (iii) files over WAKE_PREIMAGE_MAX_BYTES
# (default 1 MiB). A refused file still gets its hash/size/mtime
# row (captured:false + refused reason) so change TIME survives
# even when content must not. (3) FIRST-CLASS CAUSE LINE
# fail-closed. (2) CREDENTIAL HARD GATE (acceptance b), FOUR
# LAYERS (#964 re-verdict: B2 cases C+D): (i) POLARITY — extras
# (WAKE_PREIMAGE_EXTRA) are RECORD-ONLY by default (ledger row +
# cause line, NO bytes); byte capture for an extra requires the
# path listed in WAKE_PREIMAGE_CAPTURE (colon-separated opt-in);
# only the core set (adapter, watch-list) is capture-eligible by
# default — a shape list can only refuse the secrets someone
# already enumerated, so safety may not rest on one (#964-D).
# (ii) PATH DENY evaluated on BOTH the raw and realpath-resolved
# candidate forms against BOTH unresolved and resolved
# mosaic-home anchors (credentials.json,
# tools/_lib/credentials.json, credentials/**, any basename
# credentials.json) — a deny list written in unresolved paths
# cannot match a path resolved before it arrived (#964-C), and a
# symlinked opt-in entry cannot smuggle a denied target (deny
# runs FIRST, independent of what the opt-in matched). (iii)
# CONTENT DENY on the opt-in path only, defense-in-depth NOT the
# safety mechanism: digest.sh's six scrub shapes + a
# named-assignment probe (key/token/secret/passw/hmac/credential/
# bearer = unbroken value >=16 chars); probe ERROR fails TOWARD
# refusal (an error exit is not a negative result); false
# positives are acceptable — a false match only withholds byte
# capture, never tracking. (iv) size cap WAKE_PREIMAGE_MAX_BYTES
# (default 1 MiB). Deny ALWAYS wins over the opt-in. A refused
# file still gets its hash/size/mtime row (captured:false +
# refused reason) so change TIME survives even when content must
# not. (3) FIRST-CLASS CAUSE LINE
# (acceptance c): on a change (or deletion — ABSENT is a state,
# not an error), one class=actionable entry is enqueued via the
# store allocator with locators {kind:preimage, path (§2.1 hard
@@ -414,13 +432,20 @@
# object/ledger write, or failed enqueue is a loud non-zero —
# never read as "no change"; in both integrations the pass exits
# non-zero but source observation still proceeds (no starvation).
# First-seen is a SILENT baseline (detector first-poll idiom).
# An ABSENT/EMPTY ledger with objects/ NON-empty is a loud
# non-zero REFUSAL to re-baseline (#964-B11: objects with no
# ledger cannot be a first install — history was deleted; the
# first-install path must not silently absorb it). First-seen on
# a genuinely clean state dir is a SILENT baseline (detector
# first-poll idiom).
# The installer is UNCHANGED — Gate A auto-enumerates the new
# file from the filesystem; the recording site is the runtime
# tick, which is where the env-derived preimage set exists.
# store.sh/digest.sh/beacon.sh UNCHANGED; watch-list schema
# UNTOUCHED ([1,1]). Changed: preimage.sh (new), detector.sh,
# reconcile.sh (+ test-wake-preimage.sh P1-P12).
# reconcile.sh (+ test-wake-preimage.sh P1-P17; P13-P17 are the
# #964 re-verdict regression needles: symlinked store, live
# secret value, polarity, ledger deletion, allowlist-symlink).
component=wake
version=0.7.0