# 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= 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. component=wake version=0.6.9 # 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)