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

@@ -398,6 +398,53 @@ EOF
"$STORE" consume --upto 6 >/dev/null 2>&1 || fail_msg "D8: CONSUMED 6 must succeed over the contiguous prefix after the delta"
) && ok
echo "== D9: schema (#925) — optional per-class fallback_cadence is BACKWARD-COMPAT (in-range accepted; out-of-range still FAILS LOUD, Gate B intact) =="
(
WAKE_STATE_HOME="$(fresh_state d9)"
export WAKE_STATE_HOME
unset WAKE_AGENT
stub="$TMP_ROOT/d9stub"
mkdir -p "$stub"
make_stub "$stub"
export WAKE_DETECTOR_SOURCE_CMD="$stub/adapter.sh"
printf 'SHA-D9\n' >"$stub/repo_r1"
printf '## LANE-X\nx\n' >"$stub/board_file_b1"
# A watch-list that EXERCISES the new optional additive field: a per-class
# `fallback_cadence` under an slos tier, at schema_version 1. Because the field is
# OPTIONAL + additive, an existing schema_version-1 watch-list stays valid and the
# detector's Gate B range [schema_min, schema_max] is UNCHANGED — the new field
# must NOT push the watch-list out of range.
wl="$TMP_ROOT/d9.json"
write_fc_watchlist() {
local file="$1" ver="$2"
cat >"$file" <<EOF
{
"schema_version": $ver,
"slos": {
"actionable-tier": { "class": "actionable", "fallback_bound": "30m", "fallback_cadence": "1h" }
},
"repos": [{ "id": "r1", "remote": "example/repo", "class": "digest" }],
"board_files": [{ "id": "b1", "path": "BOARD.md", "anchor": "## LANE-X" }],
"watches": [
{ "lane": "lane-x", "sources": [
{ "kind": "repo", "id": "r1" },
{ "kind": "board_file", "id": "b1" }
] }
]
}
EOF
}
# (a) in-range (schema_version 1) WITH fallback_cadence -> ACCEPTED.
write_fc_watchlist "$wl" 1
export WAKE_WATCH_LIST="$wl"
"$DET" poll-once >/dev/null 2>&1 || fail_msg "D9: an in-range watch-list carrying the optional fallback_cadence must be ACCEPTED (additive/backward-compatible)"
# (b) out-of-range (schema_version 999) WITH the SAME field -> STILL FAILS LOUD.
write_fc_watchlist "$wl" 999
err="$("$DET" poll-once 2>&1)"; rc=$?
[ "$rc" -ne 0 ] || fail_msg "D9: the new field must NOT weaken Gate B — an out-of-range schema_version must still FAIL LOUD (rc=$rc)"
echo "$err" | grep -qi 'range' || fail_msg "D9: the out-of-range failure must still state it is out of the supported range [$err]"
) && ok
echo
if [ -s "$FAILFILE" ]; then
echo "wake detector harness: FAILED ($(grep -c . "$FAILFILE") assertion(s))" >&2