# 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).
#   0.7.0  #958 A11 preimage.sh — durable provenance for the OPERATOR-SIDE
#              preimage definition (wake-pilot finding: source-adapter.sh was
#              unversioned, so the operator-owned bytes every observed_hash is
#              computed FROM had thinner provenance than any hash they feed;
#              attribution required an agent transcript). NEW tool + two
#              pre-step integrations, ADDITIVE: (1) preimage.sh derives the
#              preimage set from the RUNTIME env (the resolved
#              WAKE_DETECTOR_SOURCE_CMD file, WAKE_WATCH_LIST, optional
#              WAKE_PREIMAGE_EXTRA paths) and, per file, records
#              {ts,path,sha256,size,mtime,prev} to an append-only ledger +
#              captures the bytes CONTENT-ADDRESSED under
#              $STATE_DIR/preimage/objects/<sha256> — prior bytes + change
#              time are answerable from durable state alone, no transcript
#              (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), 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
#              locator — never quarantined), observed_hash, prev_hash,
#              preimage:true, reason:preimage-definition-changed}. Both
#              detector.sh cmd_poll_once and reconcile.sh cmd_reconcile run
#              the check as a PRE-step, so the cause line lands at a LOWER
#              observed_seq than the N per-source deltas/enumerations it
#              explains — "preimage definition changed" reads first, not N
#              UNACCOUNTED lines. (4) FAIL-LOUD discipline (D2/#955 class):
#              an unresolvable adapter, unreadable watch-list, corrupt ledger
#              (REFUSES to compare or re-baseline over corrupt history), failed
#              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).
#              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-P17; P13-P17 are the
#              #964 re-verdict regression needles: symlinked store, live
#              secret value, polarity, ledger deletion, allowlist-symlink).
#   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):
#              THREE surfaces — the clean-sweep message, the PROVABILITY
#              BOUND comment, and (second commit, author-side self-catch)
#              the usage() help text — 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).
#   0.7.2  #973 three-valued grep verdicts across ALL TEN wake test suites.
#              grep's exit contract is three-valued (0 match / 1 no-match /
#              >=2 ERROR); every suite assertion read non-zero as "absent",
#              so a grep that COULD NOT LOOK wore the colour of a verdict —
#              OR-polarity sites failed falsely RED, AND-polarity sites
#              (including all 19 credential canaries) failed falsely GREEN
#              under load. _wake-common.sh gains has_match/count_lines
#              (rc 0/1 pass through; anything else LOUDLY ABORTS the whole
#              suite naming file:line + raw rc — an error is never a
#              verdict), wake_assert_init (saved-fd abort loudness that
#              survives call-site 2>/dev/null + a runtime pin of the
#              BASH_LINENO coordinate convention against CI bash drift),
#              and 261 call sites converted mechanically from a frozen
#              denominator artifact. Production tools source but never call
#              the helpers; suite verdict semantics on rc 0/1 are UNCHANGED.
#              Evidence chain in validate-973/ (microtest C1-C11, expected/
#              static/trace set arithmetic, 21 forced-error arms, residual
#              sweep with per-form plants).
component=wake
version=0.7.2

# 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)
#   preimage.sh A11 — operator-side PREIMAGE-DEFINITION provenance: derives the
#                    preimage set from the runtime env (resolved adapter file,
#                    watch-list, WAKE_PREIMAGE_EXTRA), appends
#                    {ts,path,sha256,size,mtime,prev} rows to an append-only
#                    ledger, captures bytes content-addressed under
#                    preimage/objects/<sha256>, and enqueues a FIRST-CLASS
#                    "preimage-definition-changed" actionable (path = §2.1 hard
#                    locator) BEFORE the deltas it explains (detector +
#                    reconcile pre-step). Credential HARD GATE: capture is
#                    REFUSED (hash/mtime still recorded) for credential-store
#                    paths, secret-shaped content, and oversized files —
#                    refusal, never redaction. Fail-loud on any infra failure;
#                    a corrupt ledger refuses re-baseline.        (#958)
# 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)
