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

135 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 home**`framework/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.