Files
stack/packages/mosaic/framework/tools/wake/manifest.txt
T
Jason WoltjeandClaude Fable 5 e11bc66223
ci/woodpecker/pr/ci Pipeline was successful
fix(wake): #946 digest ack watermark clamped at quarantined seqs — disclose AND clamp
The embedded ack suggestion covered dead-lettered entries: quarantine is a
render-time filter (0.6.14), so `ack.sh consumed --upto <observed_seq>`
stepped the cursor past quarantined seqs and _record_last_consumed wrote
consumed-hash witness rows for deliveries that never happened (live: mos-dt
seq 68 buried under five successive digests; Finding A: a false
9d0f639f…@63 witness row).

Fix, per the ruled disposition on #946:

- digest.sh: rendered digest gains a QUARANTINED section (seq + class +
  HELD only — ids and locator values stay withheld, preserving the
  exclusion property); embedded ack clamped to
  min(observed_seq, min quarantined seq - 1) with a loud
  "# ACK CLAMPED (#946)" note; render --from-store syncs the store-owned
  quarantined.set via `store.sh quarantine-sync` (full replace — a gate
  fix self-heals stale quarantine); --from-file/--stdin never touch the
  set.

- store.sh: consume REFUSES to cross an unconsumed quarantined seq;
  `--force-past-quarantine` is the ONLY way past, loud per-seq on stderr,
  and even the forced path never writes a consumed-hash witness for a
  quarantined seq; crossed seqs are pruned from the set after the cursor
  moves. New `quarantine-sync` (stdin seqs, full replace, invalid input is
  a loud no-op) and `quarantine-audit [--repair]` (sweeps consumed-hashes
  for rows provably contradicted by the dead-letter ledger; report exits
  1; --repair removes only provably-false rows; the ledger is history and
  never modified; rows whose dead-letter evidence was pruned are
  unprovable and untouched).

- ack.sh: plumbs --force-past-quarantine through to store consume; forced-
  path loudness is re-emitted on stderr while `CONSUMED <n>` stays clean.

Tests: test-wake-store-ack.sh T13-T16 (34 assertions RED at base),
test-wake-digest-quarantine.sh Q12-Q16 (10 RED at base; Q13/Q16
green-by-design controls). All nine wake suites green. version=0.6.15.

Closes #946

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 10:10:21 -05:00

460 lines
34 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Mosaic wake component — VERSION metadata manifest (Gate B).
#
# EPIC #892, W2 + W3 of the wake/heartbeat canon.
#
# SCOPE — THIS FILE IS VERSION METADATA ONLY. It declares the wake component's
# semantic version and the RANGE of watch-list schema versions it supports. It
# does NOT authorize file/path ownership: path-ownership remains the sole domain
# of packages/mosaic/framework/framework-manifest.txt (Gate A). Do not read any
# ownership meaning into this file.
#
# Format: KEY=VALUE, one per line. '#' and blank lines ignored.
# Component identity + semantic version.
# 0.1.0 W2 — store+drain lib + ack-wrapper.
# 0.2.0 W3 — cumulative-state digest renderer + non-circular HMAC signer.
# 0.3.0 W4 — per-host single-instance delta-gated detector daemon.
# 0.4.0 W5 — synthetic-canary FN-oracle + source-parity reconciler.
# 0.5.0 W6 — off-host dead-man beacon emitter + pluggable alarm-sink adapter
# + beacon-absence alarm (fail-loud on unconfigured/unreachable).
# 0.6.0 W7 — A10 idempotent, fail-closed component installer (Gate-A
# intersect+validate against the framework-manifest SSOT), the
# mosaic-wake.service detector daemon, the blank-reset retire idiom
# for the legacy heartbeat timer + snapshot-guard, and fail-closed
# alarm-target/HMAC-key install-validation. Also folds in the two W6
# monitor-integration observations (monitor-side ingested_ts
# staleness + beacon HMAC-verify at record).
# 0.6.1 #908 — UNIFY observed_seq on a SINGLE store-side allocator. store.sh
# enqueue is now the sole allocator (reads its own cursor, next=+1
# under an exclusive lock, prints the seq; commits IFF the durable
# write succeeds). The detector-private observed_seq_counter and its
# --seq hand-off are DELETED; the reconciler enumerates via the same
# store allocator (its dual-allocator fail-closed guard retired). This
# dissolves the three defects rooted in the private-counter seam:
# burn-before-enqueue (arrow 1), W5 co-feed aliasing (arrow 2), and
# the migration-restart silent-swallow (arrow 3, now structurally
# impossible — allocation is always > consumed or fails loud).
# 0.6.2 #914 digest.sh renderer fixes (live wake-pilot findings): (a) the
# embedded ack copy-run line now bakes an explicit
# WAKE_AGENT=<render-time-agent> prefix (shell-quoted) so an
# env-less copy-run resolves to the correct per-agent namespace
# instead of silently falling back to `default`; (b) the
# ORIENTATION locator renderer now also recognizes the locator
# vocabulary detector.sh (A1) actually emits for a digest-class
# entry (kind/id/observed_hash/remote/path), so a digest-class
# pointer carries a usable (soft) locator instead of rendering
# empty. Display-only: the ACTIONABLE-tier hard-locator FAIL-LOUD
# gate (_has_hard_locator, exit 4) is unchanged.
# 0.6.3 #912 digest.sh scrub PORTABILITY (no contract change): _scrub_ctrl's
# control/bidi/zero-width byte patterns are now LITERAL bytes (via
# printf %b) instead of GNU-sed `\xNN` hex escapes. BusyBox sed (the
# Alpine/musl CI runner, running as root) rejects a `\xNN` character
# range, which aborted the whole scrub sed and silently VOIDED the
# scrub in CI — collapsing every scrubbed value to empty and failing
# the digest suite's D1/D4/D5/D6 only in the Woodpecker runner. The
# scrub now renders byte-identically under GNU sed (glibc dev) and
# BusyBox sed (Alpine CI). The two-tier trust, exit-4 hard-locator
# FAIL-LOUD, and the secret-scrub/SHA-preservation contract are all
# unchanged — this makes the existing scrub deterministic across
# runners, it does not weaken it.
# 0.6.4 #920 digest.sh drain-quarantine + reconciler-enumeration render tier
# (live wake-pilot finding #6, BLOCKING). (a) PER-ENTRY
# QUARANTINE: a render-refused ACTIONABLE entry (no §2.1 hard
# locator) is now DEAD-LETTERED to $STATE_DIR/dead-letter.jsonl +
# a loud per-entry alarm and EXCLUDED, while the REST of the
# cumulative set still renders (exit 0). Replaces the whole-digest
# exit-4 that let ONE malformed entry wedge the entire drain (head-
# of-line blocking — 4 consecutive live timer failures, nothing
# delivered). Fail-loud is preserved, now per-entry; the bad entry
# is never silently dropped. (b) Reconciler ENUMERATIONS render
# ORIENTATION-tier: an entry whose locators carry reconciled==true
# (set only by reconcile.sh) is EXEMPT from the actionable hard-
# locator gate and renders as an orientation pointer via
# _locator_line's digest-class vocabulary — a RENDER-layer change
# only. reconcile.sh's STORE class is UNCHANGED (non-coalescing), so
# distinct enumerations never collapse (§2.3/T2/G3-R6 intact); the
# rejected class=digest alternative would have silently coalesced
# them. store.sh and reconcile.sh are UNCHANGED by 0.6.4.
# 0.6.5 #927 enqueue TOCTOU fix — move stale-tmp cleanup OFF the hot enqueue
# path (no concurrent in-flight-write clobber). cmd_enqueue called
# _wake_init_dir() (which reaped EVERY .wake.tmp.* unconditionally)
# BEFORE taking the enqueue lock, so a 2nd enqueue's PRE-LOCK cleanup
# deleted the LIVE in-flight tmp of a 1st enqueue holding the lock
# through its atomic write -> spurious "durable pending write FAILED"
# abort of a valid enqueue (reachable under live co-feed: detector +
# reconciler concurrently enqueue). FIX (_wake-common.sh): (a)
# _wake_init_dir no longer reaps tmps — it only ensures the layout,
# so nothing on the enqueue/consume/cursors/ack hot paths can clobber
# a concurrent live write; (b) _wake_clean_stale_tmp is AGE-SCOPED
# (mmin +${WAKE_TMP_STALE_MIN:-5}) so it can only remove demonstrably-
# orphaned crash-left tmps, never a live (ms-old) in-flight write.
# Reaping now runs as an explicit MAINTENANCE action at store.sh init
# (daemon-start) and the detector poll tick (detector.sh), keeping
# accumulation bounded once-per-pass instead of raced per-enqueue.
# #908 seq-integrity is UNCHANGED (single store-side allocator,
# atomic allocate+enqueue under flock, arrow-1 no-burn, anti-swallow
# fail-loud). reconcile.sh is UNCHANGED (its enumeration retry is the
# structural recovery net: an aborted enqueue advances neither the
# seen-ledger nor observed_seq, so the source is re-enumerated next
# cycle — no obligation loss). Files changed: _wake-common.sh,
# store.sh, detector.sh (+ tests).
# 0.6.6 #924 digest.sh dead-letter QUARANTINE alarm — G2a fix (wake-pilot
# cure-verification follow-up on #920/PR #922). The #920 per-entry
# quarantine alarm was stderr/journal-LOCAL only; a dead-lettered
# entry is STORE-ACCOUNTED (§2.3) so the reconciler never re-flags
# it, so journal-local-only visibility meant an unattended operator
# could PERMANENTLY MISS a real obligation (G2a silent-degradation).
# FIX: the SAME per-entry quarantine alarm now ALSO routes through
# WAKE_ALARM_SINK_CMD — REUSING beacon.sh's (W6/#910) exact
# pluggable off-host alarm-sink adapter contract (operator target
# resolved by-name inside the adapter, fail-closed) — IN ADDITION
# to (never instead of) the existing stderr diagnostic. Per-
# observed_seq DEDUP (entries carry no per-entry wake_id; the
# entry's durable identity is its store-allocated observed_seq,
# #908) via a durable alarmed-set file under STATE_DIR
# (dead-letter-alarmed.set, atomic-written) ensures a still-dead-
# lettered entry is alarmed off-host EXACTLY ONCE per drain/restart,
# never once per re-render; a NEW distinct dead-lettered entry
# still routes its own one alarm. An unconfigured/unreachable
# WAKE_ALARM_SINK_CMD is a LOUD per-entry stderr diagnostic
# (mirrors beacon.sh's fail-closed wording) but does NOT itself
# fail the whole render (per-entry fail-loud, never a whole-drain
# wedge — #920's core property is preserved). digest.sh is the
# ONLY file changed; store.sh/beacon.sh/reconcile.sh are
# UNCHANGED (beacon.sh's adapter contract is reused, not modified).
# 0.6.7 #913 wake-install.sh installer ADOPTION-GAP fixes (wake-pilot,
# non-blocking, ADDITIVE per #869). (a) DEP-CHECK: the installer
# sourced _lib/manifest.sh (the shared framework-manifest reader it
# needs for Gate A) unconditionally, so an older host seed that
# predates that helper aborted with a bare, obscure
# `source: No such file or directory`. It now checks the library
# FIRST and FAILS LOUD naming the missing file + the remedy (re-seed
# the framework, then retry --component wake) — the dependency is
# genuinely required (Gate A cannot be skipped on an enforcement
# path), so it fails loud rather than degrading. (b) SYSTEMD SEARCH
# PATH: wi_install copies mosaic-wake.service under mosaic home
# (systemd/user/, framework-owned) but `systemctl --user` searches
# ~/.config/systemd/user/, so the unit was invisible and could not be
# enabled/started. install now LINKS the unit into the user systemd
# search path (symlink -> the mosaic-home SSOT copy, so upgrades
# propagate) and VALIDATES it resolves (search-path entry exists,
# dereferences to a readable, well-formed unit; an opportunistic
# `systemctl --user cat` probe runs only behind a guard, since the
# installer may run where no user manager is live). Both steps are
# idempotent (a re-install neither duplicates nor breaks the link).
# #869 ADDITIVE: the link target lives OUTSIDE mosaic home, so it is
# not a framework-manifest path; ownership of the SSOT unit stays
# systemd/** in the single framework-manifest.txt authority — NO new
# owned path, NO second ownership authority. framework-manifest.txt,
# the install-ordering-guard, and the manifest parity contract are all
# UNCHANGED. Only wake-install.sh (+ test-wake-install.sh) changed.
# 0.6.8 #925 — framework-ship the canon-side FALLBACK WAKE (F7 replacement-
# before-retirement) so hosts get it OUT OF THE BOX rather than hand-
# wiring it per host. ADDITIVE, #869 / Gate-A/B discipline:
# (1) new framework units systemd/user/mosaic-wake-fallback.{timer,
# service}: a LOW-FREQUENCY SAFETY drain (oneshot service running the
# canon drain `digest.sh render --from-store`) fired by a per-class
# cadence timer, INDEPENDENT of the event-driven detector, so a stalled
# detector/daemon can never SILENTLY STARVE delivery. Both units are
# framework-owned via the EXISTING `systemd/**` glob in framework-
# manifest.txt (Gate A) — NO new owned path, NO second ownership
# authority. (2) an OPTIONAL, additive per-class `fallback_cadence`
# bound in wake-watch-list.schema.json (config, not code). It is
# backward-compatible within schema_version 1, so [schema_min,
# schema_max] stays [1,1] and the detector's Gate B range check is
# UNCHANGED (an out-of-range schema_version still fails loud). (3) A10
# install/wire: wi_install enumerates + LINKS + validates the two units
# into the user systemd search path (idempotent, fail-closed, same
# link-to-SSOT pattern as the detector unit); write-fallback-cadence
# writes the per-class cadence as a BLANK-RESET drop-in (exactly one
# effective OnUnitActiveUSec). (4) F7 install-validate: the §5 legacy
# reap now REFUSES unless the canon fallback wake is proven live
# (installed + schedulable floor always; enabled + proven-firing when a
# live user manager is probeable, mirroring #913's opportunistic
# WAKE_VERIFY_USE_SYSTEMCTL pattern) — F7 is encoded in the installer,
# not operator memory. framework-manifest.txt, the install-ordering-
# guard, and the manifest parity contract are all UNCHANGED.
# 0.6.9 #917 store.sh cmd_enqueue — HARDEN the final observed_seq cursor write
# (defense-in-depth, surfaced by the #915 review obs#2; non-blocking).
# The final cursor _atomic_write was the ONE durable write not wrapped
# in a failure check and was cross-file non-atomic with the observed.set
# write just before it. Now (a) the cursor write is GATED like the
# pending/observed.set writes (#908) — a cursor-write failure is
# FAIL-LOUD (non-zero + diagnostic), never silently swallowed into a
# spurious success while the allocation stayed uncommitted; and (b) on
# cursor-write failure observed.set is ROLLED BACK to its pre-write
# snapshot, so observed.set and the cursor can never be left cross-file
# inconsistent (observed.set ahead of a cursor that never committed) —
# they BOTH advance or NEITHER does. #908 is UNCHANGED: single store-side
# allocator, atomic allocate+enqueue under flock, arrow-1 no-burn
# (pending write still FIRST and its failure still aborts before any
# cursor advance), anti-swallow ≤consumed fail-loud, and the W2
# contiguous-prefix CONSUMED contract all intact. The cursor remains the
# sole COMMIT point (an uncommitted pending entry is re-derived/reconciled,
# never consumed), so a pending-ahead state is exactly the one #908 already
# tolerates on its observed.set-failure path. ON-DISK FORMAT UNCHANGED
# (read-compatible; a store written by older code reads identically). Only
# store.sh (+ test-wake-store-ack.sh T11) changed.
# 0.6.10 #932 reconciler RE-ENUMERATION of already-CONSUMED detector-observed
# state (wake-pilot finding #7 — safe-but-noisy G2a alarm-hygiene).
# After consume-truncation a consumed state matched NO accounting
# record (inbox truncated; the reconciler's seen-ledger only covers
# its OWN enumerations; the detector hash-file is correctly
# DISTRUSTED) -> the reconciler treated it as UNACCOUNTED and
# re-enumerated it: one DUPLICATE orientation wake + one SPURIOUS
# rc=1 CRITICAL per detector-active window per cycle (functionally
# safe — no lost obligation — but cry-wolf erosion of real alarms at
# fleet scale). FIX: (1) store.sh records the last-consumed
# observed_hash per (kind,id) at consume-truncation into a NEW
# store-owned durable record consumed-hashes.jsonl (atomic write;
# ADDITIVE — existing on-disk format unchanged/read-compatible; #908
# allocator untouched); (2) reconcile.sh adds a THIRD accounting
# source alongside the inbox and its seen-ledger: a detector-observed
# state whose observed_hash MATCHES the store's recorded last-consumed
# hash is ACCOUNTED (not re-enumerated — no dup wake, no spurious
# CRITICAL). TRUST BOUNDARY: the 3rd check consults ONLY the
# store-written record (its existence implies the state was durably
# enqueued+consumed, so it structurally cannot exhibit the §5
# hash-advance-without-enqueue swallow signature); trusting DETECTOR
# hash-files STAYS REJECTED. G3 is NOT weakened: only states the store
# RECORDED as consumed are suppressed — a genuinely-unaccounted state
# (enqueued-but-unconsumed, still in the inbox, OR a real gap) still
# re-enumerates + alarms. Changed: store.sh, reconcile.sh,
# _wake-common.sh (doc), test-wake-reconcile.sh (R10/R11),
# test-wake-store-ack.sh (T12).
# 0.6.11 #934 seq-integrity fault injection made MOUNT-FREE + privilege-invariant
# so the allocator's most safety-critical failure paths ACTUALLY RUN in
# the real NON-privileged CI runner (which denies mount-in-userns) instead
# of skipping. T9 (#908 arrow-1 no-burn) and T11 (#917 final-cursor gate +
# observed.set rollback) previously forced a write to fail via
# `unshare --mount --user --map-root-user` + a bind-mount EBUSY-on-mountpoint,
# which the non-priv runner DENIES -> both SKIPPED (skipped-trust-layer, the
# class #912 cured for digest). FIX: a single test-only, PROD-INERT fault
# seam in _wake-common.sh _atomic_write honored ONLY when the env var
# WAKE_TEST_FAULT explicitly names a write point (pending->pending.jsonl,
# cursor->observed_seq); it forces the ALREADY-EXISTING fail-loud/rollback
# PATH (#908/#917) to be taken for that one target and RUNS UNPRIVILEGED. No
# production input can set a process env var, so with it unset the seam is a
# no-op: on-disk format + allocator semantics are byte-for-byte unchanged in
# production. The unshare+bind-mount injection AND its skip-when-unavailable
# guard/witness-marker are REMOVED — T9/T11 now RUN and ASSERT their failure
# paths in every environment including non-priv CI. Changed: _wake-common.sh
# (seam), test-wake-store-ack.sh (T9/T11 conversion).
# 0.6.12 #940 snapshot-datable digests — the adapter-contract fd-3 snapshot-
# metadata channel (wake-pilot finding fw-wake-digest-snapshot-lag:
# a digest's locator carried observed_hash + emit_ts but nothing
# DATING the snapshot, so a consumer could not tell a fresh
# snapshot from one already superseded at delivery without a tool
# call). ADDITIVE + backward-compatible: (a) detector.sh invokes
# the W4 source adapter with fd 3 redirected to a temp file; the
# adapter MAY write one JSON object {"snapshot_sha": "<git commit
# sha>", "snapshot_ts": <epoch>} there. OUT-OF-BAND is load-
# bearing: everything on stdout is hashed by the delta gate, so an
# in-band tip-commit sha would advance observed_hash on every
# unrelated push (spurious delta wake per watched file). Metadata
# is ADVISORY and validated (sha ^[0-9a-f]{7,64}$, ts number):
# malformed metadata is dropped with a LOUD stderr diagnostic but
# NEVER fails the poll or suppresses the wake — the obligation
# never depends on optional dating. Valid fields join the enqueue
# locators; an adapter that never writes fd 3 is byte-identical
# legacy behavior. (b) digest.sh _locator_line renders
# snapshot_sha=/snapshot_ts= (scrubbed) beside observed_hash=, and
# snapshot_sha+path upgrades the one-call re-verify hint to
# `git show <snapshot_sha>:<path>` (snapshot_sha IS a commit sha,
# unlike observed_hash, so it may feed the git hint). With emit_ts
# already in the header, snapshot age becomes local arithmetic for
# the consumer — zero round trips. Watch-list schema UNTOUCHED
# ([1,1] unchanged — adapter contract + locator vocabulary, not
# watch-list config). store.sh/reconcile.sh/beacon.sh UNCHANGED.
# Review hardening (#941 §2): snapshot_ts additionally requires a
# VALID snapshot_sha (a bare number with no revision to re-verify
# against is the weakest attestation — dropped loudly), must be a
# sane positive epoch (^[0-9]{1,12}$ — validated BEFORE the shell
# integer comparison so an absurd value cannot error past it), and
# must not sit beyond a future-skew allowance
# (WAKE_SNAPSHOT_TS_FUTURE_SLACK, default 300 s): a future ts
# yields a NEGATIVE age — stale-reads-fresher-than-fresh, the
# exact failure class #940 fixes. The SLACK knob itself is
# operator input interpolated into arithmetic under set -u, so it
# gets the same discipline (#941 §2 round 2): shape-validated as
# a plain non-negative integer of at most 9 digits, else LOUD
# fallback to 300 — a malformed knob
# ('300s', '5m', 'abc') must never kill the poll, and a negative
# one must never invert the guard into deny-all. Shape validation
# is NOT radix validation (#942 review): bash reads leading zeros
# as OCTAL, so '08'/'09' pass the shape check yet are fatal in
# $((...)) and '0300' silently means 192 — the knob is therefore
# forced base-10 (10#) after validation, so it means what the
# operator wrote. The validator and the consumer must also agree
# on STRING EXTENT (#942 follow-up): grep's ^...$ anchors bind
# per LINE, so a multi-line value ($'300\n8') passed the regex
# whole yet was fatal in $((...)) — validation is a whole-string
# case pattern, not grep, so an embedded newline rejects.
# SKEW GUARANTEE
# (stated, not implied): the future-skew check runs against the
# DETECTOR's clock; consumer-side age arithmetic runs on the
# consumer's. A surviving snapshot_ts is therefore attested only
# to within SLACK seconds of the detector's clock, plus whatever
# skew the consumer's own clock adds — a small NEGATIVE age at
# render is bounded, not impossible; treat age <= 0 as
# "effectively current," never as proof of freshness.
# NOTE for consumers: these fields
# are ADVISORY and their ABSENCE IS DELIBERATELY NOT DIAGNOSTIC —
# a pre-#940 adapter and a dropped-as-malformed attestation render
# identically (no snapshot_* fields); the drop is loud only in the
# detector's own stderr. Do not build load-bearing logic on the
# absence of these fields.
# Changed: detector.sh, digest.sh (+ test-wake-detector.sh
# D10/D11/D12/D13, test-wake-digest-quarantine.sh Q10).
# 0.6.13 #942/#943 SLACK-knob validation hardening, split from 0.6.12 because
# version= is the component's SOLE self-identity claim (no per-file
# hashes here) and two detector-changing merges after the 0.6.12
# stamp had left three materially different detectors under one
# version string (#943 review §2). #942: the knob is resolved once,
# shape-validated, LOUD fallback 300, and forced base-10 (10#) so
# zero-padded values mean what the operator wrote instead of octal.
# #943: validation is a whole-string case pattern, not grep, so an
# embedded newline ($'300\n8' — accepted per-line by grep's ^...$
# anchors, fatal in $((...))) rejects. Full rationale in the knob
# paragraph of the 0.6.12 entry above.
# Changed: detector.sh (+ test-wake-detector.sh D13).
# 0.6.14 #944 the §2.1 hard-locator gate was UNSATISFIABLE for detector-built
# actionable board_file entries: _has_hard_locator tested only
# repo+issue / 40-hex sha / file, while detector.sh (A1) builds
# kind/id/observed_hash + path (+ snapshot_sha/_ts when attested,
# #940) — no key in common, so every class=actionable board_file
# delta was structurally guaranteed to dead-letter (live: mos-dt
# seqs 63/68, 2026-07-30; the only tier with a 30m SLO delivered
# nothing on its only actionable source). Fix: `path` becomes a
# hard-locator arm — and ONLY path: it mirrors `file`'s one-call
# "re-read X" precision, upgrading to one-call
# `git show <snapshot_sha>:<path>` when a snapshot is attested.
# observed_hash (content hash, not an address) and bare path-less
# snapshot_sha (would widen the gate past the board_file
# vocabulary — review-adopted criterion) remain NON-arms.
# Quarantine is a RENDER-TIME filter (entries never leave
# pending), so existing UNCONSUMED dead-letters re-deliver
# automatically on the first post-upgrade drain; consumed-past
# dead-letters are not requeued.
# Changed: digest.sh (+ test-wake-digest-quarantine.sh: Q11
# positive control — the live seq-68 entry verbatim must RENDER
# as CLAIM@seq — and Q1/Q6-Q9 fixtures moved off the now-valid
# path-bearing shape onto genuinely address-free shapes,
# amending the #920-era ruling that had pinned the live pilot's
# own locator shape as the malformed example). Doc follow-up:
# #948 amends CONVERGED-DESIGN.md §2.1 to add `path` to the
# hard-locator enumeration and to state the operative test as
# "one targeted call, never a search" (NOT "pins the observed
# state") — sequenced AFTER the reseed so the edit itself is a
# live delivery test of the fixed gate.
# 0.6.15 #946 the digest's embedded ack watermark covered quarantined
# entries: quarantine is a render-time filter (0.6.14), so the
# suggested `ack.sh consumed --upto <observed_seq>` stepped the
# cursor PAST dead-lettered seqs and _record_last_consumed then
# wrote consumed-hash witness rows for deliveries that never
# happened (live: mos-dt seq 68 buried under five digests;
# Finding A: a false 9d0f639f…@63 witness row). Fix — disclose
# AND clamp: (1) the rendered digest gains a QUARANTINED section
# (seq + class + HELD only; ids/locators stay withheld,
# preserving the exclusion property) and the embedded ack is
# clamped to min(observed_seq, min quarantined seq 1);
# (2) store.sh consume REFUSES to cross an unconsumed
# quarantined seq — `--force-past-quarantine` (plumbed through
# ack.sh consumed) is the ONLY way past, loud per-seq on stderr,
# and even the forced path never writes a consumed-hash witness
# for a quarantined seq; (3) render --from-store syncs the
# store-owned quarantined.set via new `store.sh quarantine-sync`
# (full-replace, so a gate fix self-heals stale quarantine;
# --from-file/--stdin never touch the set); (4) new
# `store.sh quarantine-audit [--repair]` sweeps consumed-hashes
# for rows provably contradicted by the dead-letter ledger
# (report exits 1; --repair removes only provably-false rows;
# the ledger itself is history and is never modified; rows whose
# dead-letter evidence was pruned are unprovable and untouched).
# Changed: store.sh, digest.sh, ack.sh
# (+ test-wake-store-ack.sh T13-T16,
# test-wake-digest-quarantine.sh Q12-Q16).
component=wake
version=0.6.15
# Watch-list schema this component consumes, and the INCLUSIVE range of
# schema_version values it supports. A wake-watch-list.json whose schema_version
# falls outside [schema_min, schema_max] is rejected by the component (fail-loud),
# never silently coerced.
#
# #925: the OPTIONAL per-class `fallback_cadence` bound is ADDITIVE and backward-
# compatible — an existing schema_version-1 watch-list stays valid (the field is
# omittable), so the supported range is UNCHANGED at [1, 1] and Gate B is intact.
schema=wake-watch-list
schema_min=1
schema_max=1
# Pieces shipped by this component version (informational):
# store.sh A2 — three-cursor durable store + drain lib. Stale-tmp reaping is
# OFF the hot enqueue path; `init` performs the age-scoped
# maintenance reap (#927). enqueue's final observed_seq cursor
# write is GATED fail-loud + rolls observed.set back on failure so
# the two never diverge (#917). (W2, #927, #917)
# ack.sh A4 — RECEIVED/CONSUMED ack-wrapper (local-write + ship). (W2)
# digest.sh A3 — cumulative-state digest renderer (hard locators,
# two-tier trust, injection/secret scrub). PER-ENTRY
# quarantine: a render-refused entry is dead-lettered +
# alarmed (stderr AND off-host via WAKE_ALARM_SINK_CMD,
# deduped by observed_seq, #924) + excluded, the rest still
# renders (no head-of-line block); reconciler enumerations
# (reconciled==true) render ORIENTATION-tier, gate-exempt.
# (W3, #920, #924)
# sign.sh A5 — non-circular HMAC signer (independent wake_id,
# load_credentials by-name; fills the hmac placeholder). (W3)
# detector.sh A1 — per-host single-instance delta-gated detector daemon
# (flock, anchor-scoped hashing, fail-loud source semantics;
# enqueues deltas to store.sh and captures the store-allocated
# observed_seq — no private counter, #908). Its poll tick also
# runs the age-scoped maintenance stale-tmp reap (#927). (W4)
# fn-oracle.sh A6 — synthetic-canary FN-oracle: injects a KNOWN delta at the
# source boundary, drives the pipeline through the detector's
# public poll-once, asserts CONSUMED within the per-class SLO
# (off-domain verdict from the terminal store cursor). §4
# requires FN-rate=0; a dropping/disabled detector FAILS. (W5)
# reconcile.sh A7 — source-parity reconciler: (i) source-coverage parity
# inventory (an omitted source cannot pass the vector
# vacuously) + (ii) periodic full reconcile to 0-unaccounted,
# enumerating pre-existing/startup state into the store via the
# SINGLE store-side allocator (co-feed is safe; the former
# dual-allocator fail-closed guard retired, #908). (W5)
# beacon.sh A8 — off-host DEAD-MAN liveness beacon: a monotonic beacon
# EMITTER (emit — the primitive the detector run-loop calls
# each cycle), the off-host monitor's RECEIVER + beacon-ABSENCE
# alarm (record, check), and a pluggable alarm-sink/beacon-sink
# ADAPTER INTERFACE. Liveness is SPLIT from work-triggering;
# the alarm fires on ABSENCE, routing to a human/other-host
# within its SLO (§4/G1). FAIL-CLOSED: an unconfigured OR
# unreachable target FAILS LOUD (no silent no-alarm host).
# A same-host sibling is REJECTED as non-independent; an
# isolated host degrades to a FLAGGED different-supervision-root
# beacon; capture-pane is a liveness HINT only. (W6)
# wake-install.sh A10 — idempotent, fail-closed COMPONENT installer. Selects the
# component file set and INTERSECTS-AND-VALIDATES it against the
# single SSOT framework-manifest.txt (Gate A) — this VERSION
# manifest authorizes no path. Ships the blank-reset retire
# idiom (exactly-one OnUnitActiveUSec) for the legacy heartbeat
# timer, the snapshot-guard (no reap without a snapshot), and
# fail-closed alarm-target + HMAC-key install-validation (the
# installer wires + install-validates the beacon target that
# beacon.sh's fail-loud primitive is designed for). Fails loud
# if the required _lib/manifest.sh helper is absent (older host
# seed) instead of a bare source error, and LINKS the unit into
# the user systemd search path + post-install-validates it
# resolves (#913). (W7, #913)
# Companion (framework subtree, not under tools/wake/): systemd/user/mosaic-wake.service
# — the long-lived detector daemon unit (per-class SLO lives in
# the daemon, NOT a systemd interval). (W7)
# Companion (framework subtree, not under tools/wake/):
# systemd/user/mosaic-wake-fallback.timer + mosaic-wake-fallback.service
# — the canon FALLBACK WAKE (F7): a per-class cadence timer firing
# a oneshot SAFETY drain (digest.sh render --from-store),
# INDEPENDENT of the detector, so a stalled detector cannot starve
# delivery. Owned via the existing systemd/** glob; the per-class
# cadence is a blank-reset drop-in; the §5 reap is F7-gated on this
# being proven live. (W7, #925)