ci/woodpecker/pr/ci Pipeline was successful
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
460 lines
34 KiB
Plaintext
460 lines
34 KiB
Plaintext
# 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)
|