Files
stack/briefs/PACKAGING-PLAN-HEARTBEAT-CANON-MS-LEAD.md

11 KiB
Raw Blame History

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 (A1A11) + 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 independent review at cd11bbb — 4 gaps resolved (1 component manifest/version, 2 schema ordering ≤ W4, 3 blank-reset target/lifecycle, 4 framework/operator boundaries + fail-closed) + the #889 minor. 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; validates watch-list schema_version → FAIL-LOUD; 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 + 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):
    1. Component artifact version — the wake component ships its OWN manifest+semver (framework/tools/wake/manifest.txt, mirroring the existing framework/framework-manifest.txt pattern), independently addressable. A10's installer can install/update just the wake component subtree keyed to that version (install.sh --component wake / component-scoped copy), decoupled from a full framework-manifest bump — this is the concrete enabler that lets a host adopt the component alone (design §5.4). The component manifest is the artifact-selection/update unit; the framework version is NOT.
    2. Watch-list schema_version — config-compat only, orthogonal to axis 1. The detector validates the installed component version against the watch-list schema_version and FAILS LOUD on incompatible (no silent degrade — #869 discipline, Patch-2b posture).
  • 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)

  1. Snapshot-first (B5, mos-dt-0/pilot): dragon-lin jarvis-heartbeat.{service,timer} — snapshot already present at heartbeat-planning/pilot/snapshot-dragon-lin/. Canon is version-controlled from day one (the failure class — deployed-from-uncommitted — cannot recur).
  2. Land W0→W7 to main (reviewed framework PRs).
  3. 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).
  4. 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 to installer-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-lived mosaic-wake.service (NOT a timer), so blank-reset does NOT apply to the service. It applies to the legacy mosaic-heartbeat@<agent>.timer cadence 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 to installer-7; verify = exactly one OnUnitActiveUSec); (3) on §4-vector pass, removes the legacy mosaic-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.
  • 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/; a mosaicstack/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)

  1. Repo homeframework/tools/wake/ for A1A8 + framework/defaults/ for C1 schema: confirm names.
  2. #889 — close-superseded (my rec) vs merge-as-record?
  3. PR granularity — ~8 pipelined PRs OK, or coarser (fewer, larger) / finer?
  4. Pilot boundary — design assigns pilot (B1B5) to mos-dt-0; I own framework packaging (A*). I do NOT build pilot items unless you reassign. Confirm.
  5. 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.
  6. Tracking issue/EPIC — create a mosaicstack/stack EPIC for the canon component (parent for W0W7), analogous to Matrix EPIC #887 / tooling umbrella #891? I'll open it on your nod.