fix(wake): #925 framework-ship canon fallback-wake (systemd timer + schema bound + A10 install/validate) — F7 out of the box (#931)
All checks were successful
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful

Co-authored-by: jason.woltje <jason@diversecanvas.com>
Co-committed-by: jason.woltje <jason@diversecanvas.com>
This commit was merged in pull request #931.
This commit is contained in:
2026-07-26 13:44:51 +00:00
committed by Mos
parent 0ea41e848b
commit 17087efe15
7 changed files with 489 additions and 2 deletions

View File

@@ -0,0 +1,41 @@
[Unit]
# Mosaic wake FALLBACK safety drain (F7 replacement-before-retirement, EPIC #892,
# W7). This is the framework-shipped canon-side FALLBACK WAKE: a LOW-FREQUENCY
# SAFETY drain that fires the canon drain (digest.sh render --from-store) on a
# per-class cadence bound, INDEPENDENT of the event-driven detector daemon
# (mosaic-wake.service). Its whole reason to exist is that a stalled/dead detector
# or daemon can never SILENTLY STARVE delivery: even with nothing pushing, this
# oneshot periodically drains the durable pending-inbox so the cumulative unacked
# set still reaches the consumer. It is the §5 retirement precondition (F7) — it
# must be live + proven-firing BEFORE the legacy mosaic-heartbeat@ timer is reaped,
# so there is never a coverage gap. Fixed-interval heartbeats are forbidden as the
# PRIMARY wake mechanism (WAKE-DOCTRINE); they survive ONLY as this per-class
# fallback cadence, bounded by urgency SLO, never as the steady state.
#
# This is a oneshot SERVICE activated by mosaic-wake-fallback.timer; the cadence
# lives on the TIMER (OnUnitActiveSec), set per-class by the A10 installer via the
# blank-reset drop-in — never here. The service therefore carries NO [Install]
# section (the TIMER is what is enabled/wanted); it is triggered, not wanted.
Description=Mosaic wake fallback safety drain (canon drain: digest.sh render --from-store)
After=default.target
[Service]
Type=oneshot
# Strip loader / noninteractive-shell controls before ExecStart, matching the
# detector, lease-broker and tmux fleet units in this same directory (defense-in-
# depth against an injected BASH_ENV/ENV/LD_PRELOAD in the user manager environment).
UnsetEnvironment=LD_PRELOAD BASH_ENV ENV
# Operator-owned runtime configuration. This EnvironmentFile carries only WAKE_*
# NAMES (the per-agent namespace WAKE_AGENT, the lane WAKE_LANE, and — reused from
# the detector — the pluggable adapter COMMANDS resolved BY NAME at runtime). It
# carries NEVER any secret and NEVER any endpoint value. The '-' prefix keeps a
# missing file from masking the installer's dedicated fail-closed install-validate.
EnvironmentFile=-%h/.config/mosaic/wake/fallback.env
# THE CANON DRAIN. digest.sh render --from-store drains the durable pending-inbox
# (store.sh drain) and renders the cumulative-state digest. Running it here, on the
# timer cadence, is the safety net: it is the SAME drain the delivery path uses, so
# a stalled detector cannot starve it. Delivery/paste of the rendered digest is the
# same operator-wired send seam the detector path uses (out of framework scope);
# this unit guarantees the DRAIN fires on a bounded cadence regardless of detector
# health. digest render exits 0 on an empty inbox, so a quiet cycle is a clean no-op.
ExecStart=/bin/bash --noprofile --norc %h/.config/mosaic/tools/wake/digest.sh render --from-store

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