fix(wake): #925 framework-ship canon fallback-wake (systemd timer + schema bound + A10 install/validate) — F7 out of the box #931

Merged
Mos merged 2 commits from fix/wake-925-fallback-wake into main 2026-07-26 13:44:52 +00:00
Owner

Closes #925; Part of #892.

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.

What

  1. 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 ownership authority.
  2. Schema bound: optional additive per-class fallback_cadence 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 (out-of-range still fails loud).
  3. A10 install/wire: wake-install.sh enumerates + links + validates both 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 (mirrors #913's opportunistic WAKE_VERIFY_USE_SYSTEMCTL). F7 is encoded in the installer, not operator memory.

Red-first

  • test-wake-install.sh I11 F7: reap refuses without a proven-live fallback (legacy left running — no coverage gap), succeeds once provisioned. I12 stalled detector still gets delivery via the fallback drain. I13 install links+validates the units. I14 per-class cadence blank-reset = exactly one OnUnitActiveUSec (negative control leaks two).
  • test-wake-detector.sh D9: in-range watch-list carrying fallback_cadence accepted; out-of-range schema_version still fails loud (Gate B intact).

#869 / Gate A/B

  • ADDITIVE only; path-ownership stays the single framework-manifest.txt SSOT — new units owned via the existing systemd/** glob (resolver confirmed framework-owned; parity 17 green). No second ownership authority.
  • Zero touch to install-ordering-guard / lease-broker enforcement wiring (guard spec 19 green).
  • Gate B: schema range unchanged at [1,1]; detector fails-loud out-of-range.

Gates

shellcheck 0.11 clean; verify-sanitized PASS; systemd-analyze verify rc=0 on both units; full test:framework-shell rc=0 (normal AND openssl-masked, I8 skips cleanly); vitest parity(17)/manifest(34)/install-ordering-guard(19) green; prettier clean; firewall grep=0 on diff; wake manifest.txt bumped 0.6.7 → 0.6.8.

Closes #925; Part of #892. 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. ## What 1. **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 ownership authority. 2. **Schema bound**: optional additive per-class `fallback_cadence` 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 (out-of-range still fails loud). 3. **A10 install/wire**: wake-install.sh enumerates + links + validates both 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 (mirrors #913's opportunistic `WAKE_VERIFY_USE_SYSTEMCTL`). F7 is encoded in the installer, not operator memory. ## Red-first - test-wake-install.sh **I11** F7: reap refuses without a proven-live fallback (legacy left running — no coverage gap), succeeds once provisioned. **I12** stalled detector still gets delivery via the fallback drain. **I13** install links+validates the units. **I14** per-class cadence blank-reset = exactly one OnUnitActiveUSec (negative control leaks two). - test-wake-detector.sh **D9**: in-range watch-list carrying `fallback_cadence` accepted; out-of-range schema_version still fails loud (Gate B intact). ## #869 / Gate A/B - ADDITIVE only; path-ownership stays the single framework-manifest.txt SSOT — new units owned via the existing `systemd/**` glob (resolver confirmed framework-owned; parity 17 green). No second ownership authority. - Zero touch to install-ordering-guard / lease-broker enforcement wiring (guard spec 19 green). - Gate B: schema range unchanged at [1,1]; detector fails-loud out-of-range. ## Gates shellcheck 0.11 clean; verify-sanitized PASS; systemd-analyze verify rc=0 on both units; full test:framework-shell rc=0 (normal AND openssl-masked, I8 skips cleanly); vitest parity(17)/manifest(34)/install-ordering-guard(19) green; prettier clean; firewall grep=0 on diff; wake manifest.txt bumped 0.6.7 → 0.6.8.
jason.woltje added 1 commit 2026-07-26 13:09:08 +00:00
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
jason.woltje added 1 commit 2026-07-26 13:34:17 +00:00
#931 CI (node:24-alpine, musl/BusyBox) went RED: the I14 blank-reset assertion
used `grep -qz` (NUL-data), a GNU-only extension BusyBox grep does NOT support
(its option list is -HhnlLoqvsrRiwFE -m -A -B -C -e -f — no -z). BusyBox grep
printed usage + exited non-zero, tripping `|| fail_msg`. Passed locally only
because GNU grep has -z. Toolchain-divergence class (cf. #912, BusyBox sed).

Fix (test-only; product drop-in is correct, verify-single already proves
exactly-one): assert the blank-reset FORM portably — `grep -A1 '^OnUnitActiveSec=$'
| grep -qx 'OnUnitActiveSec=30min'` still requires the empty reset line
IMMEDIATELY followed by the value, and still catches the negative control. Also
simplified a sibling `grep -qi 'EXACTLY ONE\|exactly one'` to `grep -qi 'exactly
one'` (drop the BRE alternation; -i already covers case).

Sweep of all packages/mosaic/framework/tools/wake/test-*.sh for BusyBox-
incompatible constructs (grep -z/-P, sed \xNN/\b/-i, gawk-isms, date -d/%N):
the only #931-introduced defect was this one. Verified in the real Alpine image
(node:24-alpine, BusyBox v1.37.0): all 9 wake harnesses + full test:framework-
shell green under BusyBox with CI=woodpecker (openssl present, I8 HMAC leg runs).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0158NZqN2n2ymKFeJAZ4GUCb
Author
Owner

Record of Review — PR #931 (issue #925): framework fallback-wake (F7) — FINAL, CI-GREEN (corrective head)

VERDICT: APPROVE — GO. REVIEWED-HEAD (full-40): a41662f03370349375020aee24e49d9be43c1f0a. CI: TERMINAL-GREEN (pipeline 2084, corrective head).

Review basis (product APPROVE 1-6 + delta-re-review resolving 7)

  • Product review: independent reviewer ae774fee (opus, ≠ builder mosaic-coder) at 8de52b7bcriteria 1-6 PASS, REPRODUCED (3/4/5 by hand): well-formed timer+service (systemd-analyze verify rc=0, no inlined secret/endpoint); ★ F7 install-validate — reap REFUSES without a live fallback (reproduced: no-fallback→rc=1 legacy-left-running; provisioned→retired; gate-bypass→RED); blank-reset drop-in = exactly one OnUnitActiveUSec (reproduced + negative control); stalled-detector still drains via the fallback (reproduced); Schema Gate B — optional additive fallback_cadence, schema_min/max UNCHANGED [1,1], out-of-range still fail-loud; #869 additive / Gate A — new units owned via the single framework-manifest.txt systemd/** glob (no 2nd authority), parity 17/17 + install-ordering-guard 19/19 green, install.sh additive-only. Reviewers ONLY blocker was criterion 7 (CI test-gate RED).
  • Criterion 7 fix + delta-re-review (MS-LEAD, git-verified): the RED was a PR-introduced TEST-portability defect — I14 used grep -qz, unsupported by the Alpine BusyBox grep on the CI image (toolchain-divergence, cf. OB bee9d29b / #912 BusyBox sed). PRODUCT CODE CORRECT (verify-single independently proves exactly-one). Corrective delta 8de52b7b..a41662f0 = ONE commit, ONLY test-wake-install.sh (I14 → grep -A1 ^OnUnitActiveSec=$ | grep -qx OnUnitActiveSec=30min, BusyBox-portable, still asserts the blank-reset ordering + catches the negative control). Criteria 1-6 UNCHANGED → carry. Builder verified in the REAL BusyBox image (node:24-alpine v1.37.0, CI=woodpecker): all 9 wake harnesses + full test:framework-shell GREEN. Sweep: the grep -qz was the only #931-introduced defect (digest-hmac grep -P hits are #912-handled comments); 2 pre-existing GUARDED date GNU-isms in test-wake-store-ack.sh reported (currently pass BusyBox, non-blocking).

6-check — GO

  1. open/mergeable base main ✓ · 2. CI TERMINAL-GREEN @ full-40 a41662f03370349375020aee24e49d9be43c1f0a ✓ · 3. reviewer(ae774fee)≠builder 1-6 + MS-LEAD delta-re-review resolving 7, durable RoR ✓ · 4. reviewed==CI==merge-head all a41662f0 ✓ · 5. body Closes #925 + Part of #892 ✓ · 6. queue-guard at merge.
    GO for id-11 + squash-merge at FULL-40 a41662f03370349375020aee24e49d9be43c1f0a. Closes #925 (framework fallback-wake, F7 replacement-before-retirement out of the box: timer+service + optional fallback_cadence schema bound + A10 install/link/validate with the reap-refuses-without-live-fallback F7 gate). Non-executor.
# Record of Review — PR #931 (issue #925): framework fallback-wake (F7) — FINAL, CI-GREEN (corrective head) **VERDICT: APPROVE — GO.** **REVIEWED-HEAD (full-40):** `a41662f03370349375020aee24e49d9be43c1f0a`. **CI: TERMINAL-GREEN** (pipeline 2084, corrective head). ## Review basis (product APPROVE 1-6 + delta-re-review resolving 7) - **Product review:** independent reviewer `ae774fee` (opus, ≠ builder `mosaic-coder`) at `8de52b7b` → **criteria 1-6 PASS, REPRODUCED** (3/4/5 by hand): well-formed timer+service (`systemd-analyze verify` rc=0, no inlined secret/endpoint); **★ F7 install-validate — reap REFUSES without a live fallback** (reproduced: no-fallback→rc=1 legacy-left-running; provisioned→retired; gate-bypass→RED); blank-reset drop-in = exactly one `OnUnitActiveUSec` (reproduced + negative control); stalled-detector still drains via the fallback (reproduced); Schema Gate B — optional additive `fallback_cadence`, schema_min/max UNCHANGED [1,1], out-of-range still fail-loud; **#869 additive / Gate A** — new units owned via the single `framework-manifest.txt` `systemd/**` glob (no 2nd authority), parity 17/17 + install-ordering-guard 19/19 green, install.sh additive-only. Reviewers ONLY blocker was criterion 7 (CI test-gate RED). - **Criterion 7 fix + delta-re-review (MS-LEAD, git-verified):** the RED was a PR-introduced TEST-portability defect — I14 used `grep -qz`, unsupported by the Alpine BusyBox grep on the CI image (toolchain-divergence, cf. OB `bee9d29b` / #912 BusyBox sed). PRODUCT CODE CORRECT (verify-single independently proves exactly-one). Corrective delta `8de52b7b..a41662f0` = ONE commit, ONLY `test-wake-install.sh` (I14 → `grep -A1 ^OnUnitActiveSec=$ | grep -qx OnUnitActiveSec=30min`, BusyBox-portable, still asserts the blank-reset ordering + catches the negative control). Criteria 1-6 UNCHANGED → carry. Builder verified in the REAL BusyBox image (node:24-alpine v1.37.0, CI=woodpecker): all 9 wake harnesses + full test:framework-shell GREEN. Sweep: the `grep -qz` was the only #931-introduced defect (digest-hmac `grep -P` hits are #912-handled comments); 2 pre-existing GUARDED `date` GNU-isms in test-wake-store-ack.sh reported (currently pass BusyBox, non-blocking). ## 6-check — GO 1. open/mergeable base main ✓ · 2. **CI TERMINAL-GREEN @ full-40 `a41662f03370349375020aee24e49d9be43c1f0a`** ✓ · 3. reviewer(`ae774fee`)≠builder 1-6 + MS-LEAD delta-re-review resolving 7, durable RoR ✓ · 4. reviewed==CI==merge-head all `a41662f0` ✓ · 5. body `Closes #925` + `Part of #892` ✓ · 6. queue-guard at merge. **GO for id-11 + squash-merge at FULL-40 `a41662f03370349375020aee24e49d9be43c1f0a`. Closes #925 (framework fallback-wake, F7 replacement-before-retirement out of the box: timer+service + optional `fallback_cadence` schema bound + A10 install/link/validate with the reap-refuses-without-live-fallback F7 gate). Non-executor.**
Mos approved these changes 2026-07-26 13:44:49 +00:00
Mos left a comment
First-time contributor

GO — Gate-16 id-11 stamp, pinned to exact FULL head a41662f033.
Basis: verbatim FINAL RoR (comment 19263) — product review ae774fee APPROVE on criteria 1-6 (reproduced: the F7 reap-gate REFUSES retirement without a live-proven fallback, blank-reset drop-in form, stalled-drain fallback, #869-additive, schema Gate-B) carrying through the delta-re-review that resolved criterion 7 (corrective delta = only the I14 BusyBox-portability test fix, verified green in the real node:24-alpine image — the fifth toolchain-divergence instance, caught because the review read real CI per doctrine). Closes #925: F7's replacement-before-retirement now ships out-of-the-box — future hosts get the fallback wake installed, validated, and proven-firing as a machine precondition of legacy reap, not operator memory. Part of #892. Named executor: Mos (id-11), squash pinned.

GO — Gate-16 id-11 stamp, pinned to exact FULL head a41662f03370349375020aee24e49d9be43c1f0a. Basis: verbatim FINAL RoR (comment 19263) — product review ae774fee APPROVE on criteria 1-6 (reproduced: the F7 reap-gate REFUSES retirement without a live-proven fallback, blank-reset drop-in form, stalled-drain fallback, #869-additive, schema Gate-B) carrying through the delta-re-review that resolved criterion 7 (corrective delta = only the I14 BusyBox-portability test fix, verified green in the real node:24-alpine image — the fifth toolchain-divergence instance, caught because the review read real CI per doctrine). Closes #925: F7's replacement-before-retirement now ships out-of-the-box — future hosts get the fallback wake installed, validated, and proven-firing as a machine precondition of legacy reap, not operator memory. Part of #892. Named executor: Mos (id-11), squash pinned.
Mos merged commit 17087efe15 into main 2026-07-26 13:44:52 +00:00
Mos deleted branch fix/wake-925-fallback-wake 2026-07-26 13:44:53 +00:00
Sign in to join this conversation.
No Reviewers
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: mosaicstack/stack#931