12 KiB
Wake/Heartbeat Canon — FRAMEWORK PACKAGING PLAN (MS-LEAD)
Author: MS-LEAD (framework packaging role, named in CONVERGED-DESIGN footer) · Date: 2026-07-25
Input: jarvis-brain@31f1f6fd5 docs/scratchpads/heartbeat-planning/CONVERGED-DESIGN.md (GO-WITH-GATES,
RATIFIED) + ENHANCE-CADENCE-WRITER-FIX.md + ENHANCE-BASELINE.md.
Scope: §6 category-A framework-component items (A1–A11) + WAKE-DOCTRINE guide. NOT pilot (B*, mos-dt-0)
or operator-config values (C*). Proposal only — no framework PR cut until Mos acks this plan (per Mos).
Repo facts verified against mosaicstack/stack origin/main (read-only ls-tree) — not assumed.
Rev 2 (2026-07-25): addresses W-jarvis review at cd11bbb — 4 gaps resolved (component manifest/version,
schema ordering ≤ W4, blank-reset target/lifecycle, framework/operator boundaries) + #889 minor.
Rev 3 (2026-07-25): W-jarvis Rev-2 re-review = CONDITIONAL APPROVE; adds two acceptance gates — Gate A
(wake manifest is VERSION metadata only; path-ownership stays the single framework-manifest.txt SSOT +
Bash/TS parity tests — no second ownership truth) and Gate B (component manifest declares a supported
schema RANGE; detector fails loud outside it; upgrade/downgrade boundary tests). Still PROPOSED / PENDING-AUTHORITY.
1. Repo layout (grounded in verified paths)
Framework source lives in packages/mosaic/framework/ and installs to ~/.config/mosaic/. Verified:
tools/<subdir>/ (git, tmux, fleet, orchestrator, lease-broker, context, …); guides/*.md;
install.sh; systemd/; tools/tmux/agent-send.sh (+ agent-send.test.sh).
| Item | Source path (new unless noted) | Installs to |
|---|---|---|
| A1 detector daemon | framework/tools/wake/detector.sh |
~/.config/mosaic/tools/wake/ |
| A2 store+drain lib | framework/tools/wake/store.sh (three-cursor, atomic write-tmp+rename) |
XDG state ~/.local/state/mosaic/wake/ |
| A3 digest renderer | framework/tools/wake/digest.sh |
" |
| A4 ack-wrapper | framework/tools/wake/ack.sh (RECEIVED/CONSUMED, local-write+async) |
ledger under XDG state |
| A5 HMAC signer | framework/tools/wake/sign.sh (non-circular tuple, load_credentials by-name) |
" |
| A6 FN-oracle · A7 reconciler | framework/tools/wake/{fn-oracle,reconcile}.sh |
" |
| A8 off-host beacon | framework/tools/wake/beacon.sh + alarm integration |
off-host monitor |
A9 digest class validator |
MODIFY framework/tools/tmux/agent-send.sh (+ .test.sh) |
— |
| A10 idempotent installer | MODIFY framework/install.sh + framework/systemd/user/mosaic-wake.service |
~/.config/systemd/user/ |
| A11 WAKE-DOCTRINE | framework/guides/WAKE-DOCTRINE.md (lift §"WAKE-DOCTRINE" verbatim) |
~/.config/mosaic/guides/ |
| C1 watch-list SCHEMA (framework) | framework/defaults/wake-watch-list.schema.json |
operator supplies values |
Note: gitea-pr-watch / mos-comms (the pollers the detector generalizes) are NOT under
framework/tools/ on main — they are host/operator artifacts today. A1 becomes the canonical framework
generalization; it does not depend on those existing in-repo. The detector runs as a long-lived
service (not a timer) — the whole point is retiring unconditional timers; fallback cadence is a
per-class SLO bound inside the daemon, not a systemd interval.
2. PR slicing (dependency-ordered, ≤2 concurrent builders, each builder→indep-review→Mos id-11)
Dependency spine: store/ack (A2/A4) → digest/sign (A3/A5) → detector (A1) → oracle/reconciler (A6/A7); beacon (A8), A9, A11 are near-independent; installer (A10) lands last (needs all files present).
| PR | Contents | Notes |
|---|---|---|
| W0 | A11 WAKE-DOCTRINE.md | docs-only, independent; lands first as normative reference. Supersedes/informs #889; closure Mos-owned (see §5). |
| W1 | A9 agent-send.sh digest-class validator + test |
tiny, independent, red-first (like Patch 2b). Good opener. |
| W2 | A2 store+drain lib + A4 ack-wrapper + watch-list SCHEMA contract (framework .schema.json + schema_version) |
core three-cursor transaction + the public schema W4/W5 validate against (W-jarvis gap 2 — schema lands ≤ its first consumer, not W7); red-first heavy. |
| W3 | A3 digest renderer + A5 HMAC signer | depends on W2 store; hard-locator + scrub + non-circular MAC tests. |
| W4 | A1 detector daemon | depends on W2 (incl. schema)/W3; checks watch-list schema_version ∈ component supported-range → FAIL-LOUD (Gate B) + boundary tests; repo-section/anchor-scoped hashing; flock; in mos-comms loop. |
| W5 | A6 FN-oracle + A7 reconciler | depends on W4; the non-optional G3/§4 cost, built explicitly. |
| W6 | A8 off-host beacon + pluggable alarm-sink adapter | semi-independent; independent liveness leg (G1). Framework = emitter+adapter iface; operator = target (§5 boundaries). |
| W7 | A10 idempotent install.sh (component-manifest install, metadata-only; path-ownership stays framework-manifest SSOT + parity tests — Gate A + snapshot-guard + blank-reset on legacy timer drop-ins during retire) + mosaic-wake.service |
LAST; absorbs batch (d); #869 publish-gate. Operator schema VALUES deploy here; schema CONTRACT is W2. |
~8 PRs pipelined; at ≤2 lanes this is several convergence cycles. Each: fresh-clone builder
(mosaic-coder), author≠reviewer independent review at exact head, RoR packet, Mos id-11 stamp+merge.
Red-first mandatory on all code PRs; test:framework-shell chain extended per PR.
3. Versioning + #869 compatibility
- Two independent version axes (resolves the inherit-vs-adopt-independently contradiction — W-jarvis gap 1):
- Component artifact version — metadata only (Gate A). The wake component ships its own
manifest+semver (
framework/tools/wake/manifest.txt) as VERSION metadata ONLY. Path-ownership stays the single SSOTframework/framework-manifest.txt(consumed by BOTH the Bash and TS installers with parity tests) — the wake manifest does NOT independently authorize file writes/prunes (no second ownership truth). A component-scoped install/update (install.sh --component wake) selects the component's file set intersected-and-validated against framework-manifest ownership, and extends the Bash/TS parity tests. Net: independent addressability/versioning of the component WITHOUT a second path-ownership authority (design §5.4 enabler). Acceptance: parity tests green + no wake-manifest-authorized write outside framework-manifest ownership. - Watch-list
schema_version— orthogonal; compat via explicit RANGE (Gate B). Component semver andschema_versionare different axes and are NOT compared directly. The component manifest declares a supported schema range (schema_min/schema_max, or a supported list); the detector checks the watch-listschema_version∈ range → else FAIL LOUD (#869 discipline, Patch-2b posture). Acceptance: upgrade/downgrade boundary tests (in-range pass, out-of-range fail-loud).
- Component artifact version — metadata only (Gate A). The wake component ships its own
manifest+semver (
- WAKE-DOCTRINE is version-agnostic doctrine.
- A10 installer is an enforcement path → #869 publish-gate: fail-closed, idempotent, snapshot-guard
blocks a reap/clean-checkout without a snapshot; post-apply verify = exactly one
OnUnitActiveUSec. - web1 rides #869 Point-2 (currently HELD/non-autonomous). dragon-lin pilots NOW (separate host, outside the #869 freeze) — so canon PRs can land + be piloted without touching the web1 release hold.
4. Rollout sequence (§5, retire-net-LAST)
- Snapshot-first (B5, mos-dt-0/pilot): dragon-lin
jarvis-heartbeat.{service,timer}— snapshot already present atheartbeat-planning/pilot/snapshot-dragon-lin/. Canon is version-controlled from day one (the failure class — deployed-from-uncommitted — cannot recur). - Land W0→W7 to main (reviewed framework PRs).
- dragon-lin: enable canon alongside ad-hoc (B1) → dual-ledger compare (B2) → run §4 vector (B3) → delete ad-hoc units after pass (B4). Every step leaves old-or-new live (sol F7).
- web1: adopt component + watch-list on #869 resolution.
- Do NOT land the ad-hoc
infra/fleet/*subsystem to main first (§5.1, Mos ruling) — it is exactly what canon retires.
5. Disposition of adjacent items
- Batch (d) — ABSORBED into A10. The blank-reset bug is in
infra/fleet/install.sh(deployed-from-uncommitted, NOT on main — confirms my "could not verify on main"). Host drop-ins already corrected + live-verified 2026-07-25 (planner-opus/planner-sol now fire 30min not 15min). Nothing standalone to land; the proven idiom (byte-identical toinstaller-7) becomes an A10 requirement + a post-apply test. Batch item (d) is therefore closed-as-absorbed, not a separate PR. Blank-reset TARGET + lifecycle (W-jarvis gap 3): the component is a long-livedmosaic-wake.service(NOT a timer), so blank-reset does NOT apply to the service. It applies to the legacymosaic-heartbeat@<agent>.timercadence drop-ins (.../interval.conf) during the §5 overlap→retire lifecycle: A10 installer (1) snapshots the legacy unit; (2) IF a per-class fallback-cadence timer is used, writes its drop-in in the blank-reset form (byte-identical toinstaller-7; verify = exactly oneOnUnitActiveUSec); (3) on §4-vector pass, removes the legacymosaic-heartbeat@units (retire-last). Named units + reset→verify→retire steps make the invariant a testable acceptance criterion, not an orphaned idiom. - Framework/operator boundaries + fail-closed criteria (W-jarvis gap 4):
- W6 off-host alarm: framework ships the beacon emitter + a pluggable alarm-sink adapter
interface; the operator owns the target endpoint (off-host monitor address/credential, by-name
via
load_credentials). Fail-closed: an unconfigured/unreachable alarm target fails loud at install — no silent no-alarm host (G1/G2a). - W3/W7 HMAC key: operator-owned secret, provisioned by-name via
load_credentials, NEVER inlined, NEVER in units or digests. Framework ships the signer using the named key. Fail-closed: missing key → fail loud, no unsigned wake emitted. Same-uid key-read boundary documented (§2.5/G6); off-uid signer tracked as a gate, not assumed away.
- W6 off-host alarm: framework ships the beacon emitter + a pluggable alarm-sink adapter
interface; the operator owns the target endpoint (off-host monitor address/credential, by-name
via
- PR #889 (my layering note) — W0 supersedes/informs it; CLOSURE remains Mos-owned. Its constraints
(one liveness substrate, framework-vs-product split, state location, blank-reset invariant) are now
canonized in CONVERGED-DESIGN §0/§1.1/§1.4/§5.2. The canonical heartbeat docs live in jarvis-brain
heartbeat-planning/; amosaicstack/stack docs/scratchpads/duplicate is cross-repo drift. My rec: close #889 without merge (design input delivered + absorbed) — but W0 does not itself resolve #889; Mos decides close-superseded vs merge-as-record.
6. Open questions for Mos (before I cut W0)
- Repo home —
framework/tools/wake/for A1–A8 +framework/defaults/for C1 schema: confirm names. - #889 — close-superseded (my rec) vs merge-as-record?
- PR granularity — ~8 pipelined PRs OK, or coarser (fewer, larger) / finer?
- Pilot boundary — design assigns pilot (B1–B5) to mos-dt-0; I own framework packaging (A*). I do NOT build pilot items unless you reassign. Confirm.
- Sequencing vs tooling batch — #890 (a) in flight for id-11; (b) held on scopes; (c) HALTED. Does W0 start now (dragon-lin outside #869), or after the tooling batch clears? My lean: W0/W1 can start now (doc + tiny validator, no #869 dependency), heavier W2+ after (b)/(c) resolve to keep ≤2 lanes.
- Tracking issue/EPIC — create a
mosaicstack/stackEPIC for the canon component (parent for W0–W7), analogous to Matrix EPIC #887 / tooling umbrella #891? I'll open it on your nod.