fix(wake): #925 framework-ship canon fallback-wake (systemd timer + schema bound + A10 install/validate) — F7 out of the box
Some checks failed
ci/woodpecker/pr/ci Pipeline failed

Framework-ship the canon-side FALLBACK WAKE (F7 replacement-before-retirement)
so hosts get it out of the box instead of hand-wiring it per host. ADDITIVE;
honors #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. Owned via the existing `systemd/**` glob in
   framework-manifest.txt (Gate A) — no new owned path, no second authority.
2. Optional additive per-class `fallback_cadence` bound in
   wake-watch-list.schema.json (config, not code). Backward-compatible within
   schema_version 1, so [schema_min, schema_max] stays [1,1] and the detector's
   Gate B range check is unchanged.
3. A10 wake-install.sh enumerates + links + validates the two units into the
   user systemd search path (idempotent, fail-closed); 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).

Red-first: test-wake-install.sh I11 (F7 reap-refuses-without-live-fallback),
I12 (stalled detector still drains), I13 (install links+validates units),
I14 (per-class cadence blank-reset = exactly one); test-wake-detector.sh D9
(optional fallback_cadence in-range accepted; out-of-range still fails loud).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0158NZqN2n2ymKFeJAZ4GUCb
This commit is contained in:
mosaic-coder
2026-07-26 08:07:03 -05:00
parent 0ea41e848b
commit 8de52b7b59
7 changed files with 485 additions and 2 deletions

View File

@@ -0,0 +1,30 @@
[Unit]
# Cadence timer for the Mosaic wake FALLBACK safety drain (F7, EPIC #892, W7).
# Drives mosaic-wake-fallback.service on a LOW-FREQUENCY per-class cadence bound,
# INDEPENDENT of the event-driven detector daemon, so a stalled detector can never
# silently starve delivery. This is the framework-shipped canon-side FALLBACK WAKE:
# a heartbeat-shaped timer that survives ONLY as the per-class fallback cadence
# (WAKE-DOCTRINE) bounded by urgency SLO — never the steady-state wake mechanism.
Description=Mosaic wake fallback cadence timer (per-class safety wake)
After=default.target
[Timer]
# BASE cadence placeholder. The A10 installer OVERRIDES this per-class from the
# watch-list schema's per-class `fallback_cadence` bound, via a BLANK-RESET drop-in
# (an empty OnUnitActiveSec= reset line, then the new value) written under
# mosaic-wake-fallback.timer.d/. systemd merges base + drop-ins so exactly ONE
# effective OnUnitActiveUSec results (wake-install.sh verify-single). The base value
# here is a conservative safety floor for a host installed before any per-class
# drop-in is written — it is deliberately low-frequency (never the primary wake).
OnUnitActiveSec=1h
# Also fire shortly after boot so a freshly-booted host does not wait a full cadence
# for its first safety drain. OnBootSec is a distinct key from OnUnitActiveSec and
# does NOT count toward the exactly-one-OnUnitActiveUSec blank-reset invariant.
OnBootSec=15min
# Catch up a missed elapse (host asleep/off) rather than silently skipping it — a
# fallback that silently skips is exactly the starvation this unit exists to prevent.
Persistent=true
Unit=mosaic-wake-fallback.service
[Install]
WantedBy=timers.target