Operator decision at review: 15 min is aggressive for config churn; hourly keeps the stale-window acceptable (<=1 h) with a fifth of the wakeups. Timer unit, tier law, and failure-table wording aligned.
76 lines
3.1 KiB
Markdown
76 lines
3.1 KiB
Markdown
---
|
|
type: reference
|
|
title: live-sync heartbeat
|
|
description: Auto-commit daemon for machine-written paths in the command-center checkout — the deployment is live, so symlinks make config churn dirty tracked files continuously.
|
|
tags:
|
|
- git
|
|
- automation
|
|
- systemd
|
|
- deployment
|
|
- fleet
|
|
verified: 2026-10-18
|
|
timestamp: 2026-10-18
|
|
---
|
|
|
|
# live-sync heartbeat
|
|
|
|
The command-center checkout IS the deployment, and several tracked files are
|
|
live state: the harness writes memories and managed-skills, the operator
|
|
edits `config.yml`/`models.yml` through the `cfg://` surface, and desktop
|
|
GUIs (Noctalia, niri) rewrite dotfiles through symlinks. Before Refs #11 this
|
|
machine churn just piled up unpushed — one disk holding the only copy of ~61
|
|
auto-learned skills, a settings fix stranded locally, and merge-refusals
|
|
because the dirty tree collided with incoming work.
|
|
|
|
`scripts/live-sync.sh` is the pressure valve: a heartbeat that commits the
|
|
machine's own writes on a schedule. Tier law (which paths it may touch) lives
|
|
in [AGENTS.md](../../AGENTS.md) § Live-sync tiers.
|
|
|
|
## Contract (black-box)
|
|
|
|
Run = one pass: flock → branch guard (`main` only) → stage allowlisted dirty
|
|
tiers → commit (hooks armed) → fetch + merge `origin/main` → push → reap
|
|
stale `wt/*` worktrees. Exit `0` = quiet heartbeat (synced/no-op/reaped);
|
|
exit `1` = an alert was raised. Pinned by `tests/live-sync.bats`.
|
|
|
|
Hard properties:
|
|
|
|
- **Allowlist only.** Task dirt (`plugins/`, `omp-docs/`, prose) is never
|
|
staged — merge conflicts with in-flight work become impossible by
|
|
construction.
|
|
- **Hooks stay armed.** A secret-gate refusal aborts the commit, leaves
|
|
changes staged for a human, and alerts. Never `--no-verify`, never force.
|
|
- **Never fights the remote.** Merge conflict → `merge --abort`, local
|
|
commit kept, alert. Retries are automatic next tick.
|
|
- **Branch guard.** On a non-`main` checkout it refuses and alerts — same
|
|
rule as the doctrine ("never end a session off main").
|
|
- **Polite reaper.** `wt/wt-*` worktrees untouched for 7+ days are removed
|
|
with their `wt/*` branch; dirty or process-occupied worktrees are skipped.
|
|
|
|
## Alerts
|
|
|
|
`notify()` writes stderr always; pushes to ntfy when the topic file
|
|
`~/.config/ntfy/live-sync` (mode 600, topic text only) exists, throttled to
|
|
one push per hour. Test seams: `LIVE_SYNC_REPO`, `LIVE_SYNC_NOTIFY`,
|
|
`LIVE_SYNC_NTFY_TOPIC_FILE`, `LIVE_SYNC_AGE_DAYS`.
|
|
|
|
## Install
|
|
|
|
Units ship in `dotfiles/systemd-user/.config/systemd/user/` and propagate
|
|
with the usual dotfiles chain:
|
|
|
|
```bash
|
|
systemctl --user enable --now live-sync.timer # after the unit is linked
|
|
systemctl --user start live-sync.service # one manual heartbeat
|
|
journalctl --user -u live-sync.service -n 20 # observe
|
|
```
|
|
|
|
## Failure modes
|
|
|
|
| Event | Result |
|
|
|-------|--------|
|
|
| Harness mid-write | commits a valid snapshot; remainder lands next heartbeat |
|
|
| Gitea down | commit safe locally; push retries next tick (≤1 h stale window) |
|
|
| Feature branch checked out in main checkout | refuses + alerts; protects the never-end-off-main rule |
|
|
| Secret shape in machine-written content | hook blocks, changes stay staged, alert |
|