docs: migrate reminder/gateway/crash-watch/migrate prose to docs (#715)
This commit is contained in:
parent
34cc68bdfb
commit
12a26e6cb0
7 changed files with 75 additions and 123 deletions
|
|
@ -162,6 +162,31 @@ Transient broker errors (sqlite lock contention, etc.) get the same
|
|||
fan-out pass; a broker error on a one-shot is not retried (the
|
||||
operator advisory and `last_result` are the only audit trail).
|
||||
|
||||
### Reminder delivery: file-path semantics
|
||||
|
||||
A reminder may carry a `file_path` (the agent-visible path inside its
|
||||
container, e.g. `/agents/<name>/state/foo.md`). On delivery hive-c0re:
|
||||
|
||||
1. **Translates** the container path to the host path
|
||||
(`/var/lib/hyperhive/agents/<name>/state/foo.md`) so c0re can write
|
||||
from outside the container.
|
||||
2. **Validates** the path: rejects anything outside the agent's own state
|
||||
subtree, containing `..` (path traversal), or with an empty relative
|
||||
tail. On rejection the write is skipped and the original message is
|
||||
delivered inline with a warning — the reminder still fires.
|
||||
3. **Defends against symlink escape**: after `create_dir_all`, the parent
|
||||
dir is canonicalized and re-verified to live under the agent's host
|
||||
state root. The final file is opened with
|
||||
`O_NOFOLLOW | O_CREAT | O_TRUNC` so an existing symlink at the
|
||||
basename cannot redirect the write to an arbitrary host path.
|
||||
4. **Writes the body to disk** and delivers a short pointer message in its
|
||||
place, keeping the agent's inbox / wake-prompt small while the bulky
|
||||
payload is read out of band.
|
||||
|
||||
Atomicity of the inbox INSERT + `reminders.sent_at` UPDATE is handled
|
||||
inside `Broker::deliver_reminders_batch`; the scheduler only computes the
|
||||
body strings before calling it.
|
||||
|
||||
### Destroy semantics
|
||||
|
||||
`HostRequest::Destroy { name, purge }` is the lifecycle tear-down,
|
||||
|
|
|
|||
|
|
@ -114,6 +114,35 @@ TCP loopback upstream in `agents.conf` (deterministic port from
|
|||
`agent_web_port(name)`). A future cleanup will drop the TCP fallback
|
||||
once every agent's flipped.
|
||||
|
||||
## Agent port map (`agent-ports.json`)
|
||||
|
||||
`/var/lib/hyperhive/agent-ports.json` is a flat JSON object keyed by
|
||||
logical agent name → TCP web port:
|
||||
|
||||
```json
|
||||
{
|
||||
"iris": 8178,
|
||||
"atlas": 8304,
|
||||
"argus": 8267,
|
||||
"damocles": 8549
|
||||
}
|
||||
```
|
||||
|
||||
Written alongside `agents.conf` on every topology change. Ports come from
|
||||
`lifecycle::agent_web_port(name)` — a pure FNV-1a hash of the name,
|
||||
reproducible from the name alone. The manager is excluded: the gateway
|
||||
routes `/` directly to c0re's dashboard upstream, not through a
|
||||
per-agent `/agent/<name>/` prefix.
|
||||
|
||||
The file doubles as a human-readable audit artifact — `cat agent-ports.json`
|
||||
shows every registered sub-agent and its deterministic port assignment. TCP
|
||||
loopback upstreams in `agents.conf` reference these ports for agents that
|
||||
haven't opted into unix-socket mode yet.
|
||||
|
||||
Both `agent-ports.json` and `agents.conf` use atomic `<path>.tmp` +
|
||||
`rename()` writes so a crashing c0re process never leaves a partial or
|
||||
unparseable file behind.
|
||||
|
||||
## Dashboard link shape (gateway vs direct)
|
||||
|
||||
When the gateway is in front, the SW4RM tab builds per-agent links
|
||||
|
|
|
|||
|
|
@ -1,33 +1,7 @@
|
|||
//! `/var/lib/hyperhive/agent-ports.json` writer. Port map for
|
||||
//! per-agent `/agent/<name>/` TCP routing. Written alongside
|
||||
//! `agents.conf` (see `gateway_nginx.rs`) on every topology change;
|
||||
//! `gateway_nginx::render` reads it indirectly via
|
||||
//! `lifecycle::agent_web_port` to populate TCP upstreams for agents
|
||||
//! that haven't opted in to unix-socket mode yet. Also kept as a
|
||||
//! human-readable audit file — `cat agent-ports.json` shows every
|
||||
//! registered sub-agent and its deterministic port assignment.
|
||||
//!
|
||||
//! Shape (flat object keyed by logical agent name → web port):
|
||||
//!
|
||||
//! ```json
|
||||
//! {
|
||||
//! "iris": 8178,
|
||||
//! "atlas": 8304,
|
||||
//! "argus": 8267,
|
||||
//! "damocles": 8549
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! Ports come from [`crate::lifecycle::agent_web_port`] — pure
|
||||
//! FNV-1a(name) hash so the value is reproducible from the name
|
||||
//! alone. Manager is excluded from the map: the gateway routes `/`
|
||||
//! straight to it via the c0re dashboard upstream rather than a
|
||||
//! per-agent `/agent/<name>/` prefix.
|
||||
//!
|
||||
//! Atomicity: write to a sibling `.tmp` file + rename so a partial
|
||||
//! write never leaves an unparseable file in place. The gateway's
|
||||
//! `nginx` worker can read mid-write and Just Work because `rename()`
|
||||
//! is atomic on the same filesystem.
|
||||
//! `/var/lib/hyperhive/agent-ports.json` writer — flat map of
|
||||
//! agent name → TCP web port. Written alongside `agents.conf` on
|
||||
//! every topology change. JSON shape, port derivation (FNV-1a hash),
|
||||
//! atomicity, and manager exclusion: `docs/gateway.md::Agent port map`.
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::path::PathBuf;
|
||||
|
|
|
|||
|
|
@ -1,19 +1,7 @@
|
|||
//! Per-container state watcher. Polls every managed container on a
|
||||
//! fixed interval, tracks two orthogonal state-sets across ticks,
|
||||
//! and emits a `HelperEvent` to the manager on each transition:
|
||||
//!
|
||||
//! - **running**: container is up. running → stopped without an
|
||||
//! operator-initiated transient (`Stopping` / `Restarting` /
|
||||
//! `Destroying` / `Rebuilding`) → `ContainerCrash`.
|
||||
//! - **logged-in**: claude session dir is populated. ! → ✓ →
|
||||
//! `LoggedIn`; ✓ → ! → `NeedsLogin` (rare — usually only fires
|
||||
//! on a fresh spawn / purge).
|
||||
//!
|
||||
//! `NeedsUpdate` events are now fired from the apply-commit path
|
||||
//! directly rather than via rev-marker polling.
|
||||
//!
|
||||
//! D-Bus subscription would be lower-latency for the first axis,
|
||||
//! but polling is simpler and a 10s detection delay is fine.
|
||||
//! Per-container crash and login-state watcher. Polls every managed
|
||||
//! container on a 10s interval. Fires `ContainerCrash`, `LoggedIn`,
|
||||
//! and `NeedsLogin` helper events. Event semantics and the
|
||||
//! `RECENT_TRANSIENT_GRACE` window: `docs/approvals.md::Helper events`.
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::sync::Arc;
|
||||
|
|
|
|||
|
|
@ -1,30 +1,9 @@
|
|||
//! Runtime nginx include-file generator for the gateway's per-agent
|
||||
//! `/agent/<name>/` location blocks.
|
||||
//!
|
||||
//! Writes `/var/lib/hyperhive/gateway/agents.conf` on every topology change.
|
||||
//! The gateway container bind-mounts `/var/lib/hyperhive/gateway/` (NOT the
|
||||
//! whole parent dir) at `/run/hive-state/`; nginx includes
|
||||
//! `/run/hive-state/agents.conf`. After each write, c0re triggers
|
||||
//! `nginx -s reload` inside the gateway container via
|
||||
//! `systemd-run --machine=hive-gateway` from the host — no `nixos-rebuild
|
||||
//! switch` needed when agents start, stop, or flip `useUnixSocket`.
|
||||
//! (A path unit inside the container was tried first but `IN_MOVED_TO`
|
||||
//! from the atomic rename does not cross the nspawn mount-namespace
|
||||
//! boundary — see `docs/gateway.md` for the failure analysis.)
|
||||
//!
|
||||
//! Upstream selection mirrors `agent_sockets::build_map`: an agent
|
||||
//! gets a UDS upstream when its `.bound` marker exists (harness has
|
||||
//! bound the unix socket); otherwise falls back to the deterministic
|
||||
//! TCP port from `lifecycle::agent_web_port`. Proxy headers are
|
||||
//! emitted in full so the generated file is self-contained nginx
|
||||
//! config — no dependency on which `recommendedProxySettings` knobs
|
||||
//! the host config has on.
|
||||
//!
|
||||
//! `write()` is idempotent: if the rendered body equals what's already
|
||||
//! on disk, the rename is skipped and the path unit doesn't fire.
|
||||
//! Same atomic `<path>.tmp` + `rename()` shape as `agent_ports` /
|
||||
//! `agent_sockets` — a crashing c0re process never leaves a partial
|
||||
//! file the gateway's nginx would fail to parse.
|
||||
//! `/agent/<name>/` location blocks. Writes
|
||||
//! `/var/lib/hyperhive/gateway/agents.conf` on every topology change.
|
||||
//! UDS vs TCP upstream selection, reload trigger (`systemd-run
|
||||
//! --machine=hive-gateway`), and idempotency:
|
||||
//! `docs/gateway.md::Per-agent unix-socket upstream`.
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use std::fmt::Write as _;
|
||||
|
|
|
|||
|
|
@ -1,23 +1,7 @@
|
|||
//! Startup auto-migration from the pre-meta layout. Runs before
|
||||
//! `auto_update::run` and consists of four phases, each idempotent:
|
||||
//!
|
||||
//! 1. Per-agent applied repo: rewrite `flake.nix` to the module-only
|
||||
//! boilerplate if it isn't already, commit, relocate `deployed/0`
|
||||
//! to HEAD so `setup_applied`'s existence check passes.
|
||||
//! 2. Per-agent proposed repo: ensure the `applied` git remote
|
||||
//! points at `/applied/<n>/.git` (re-runs `setup_proposed`'s
|
||||
//! `ensure_applied_remote` indirectly via a host-side git call).
|
||||
//! 3. Meta repo: `meta::sync_agents` over the current agent list —
|
||||
//! init the repo on first call, rerender + relock if anything
|
||||
//! drifted.
|
||||
//! 4. Container repoint: for every existing container, run
|
||||
//! `nixos-container update <c> --flake meta#<name>` so it
|
||||
//! activates against the meta flake. Guarded by a marker file
|
||||
//! so the (expensive) phase 4 only runs once across hive-c0re
|
||||
//! restarts.
|
||||
//!
|
||||
//! Env kill-switch: `HIVE_SKIP_META_MIGRATION=1` skips the whole
|
||||
//! migration. Use when smoke-testing one agent at a time by hand.
|
||||
//! Startup auto-migration from the pre-meta layout. Four idempotent
|
||||
//! phases: applied repo, proposed repo, meta repo, container repoint.
|
||||
//! Kill-switch: `HIVE_SKIP_META_MIGRATION=1`. Full migration sequence
|
||||
//! and phase details: `docs/approvals.md::Migration from the pre-tag`.
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
|
|
|
|||
|
|
@ -1,34 +1,7 @@
|
|||
//! Background loop that drains due reminders out of the broker and
|
||||
//! delivers them as inbox messages. Mirrors the `events_vacuum` /
|
||||
//! `crash_watch` shape — a single `spawn(coord)` entry point started
|
||||
//! from `main.rs`.
|
||||
//!
|
||||
//! File-path semantics: a reminder may carry a `file_path` (the
|
||||
//! agent-visible path inside its container). On delivery we:
|
||||
//!
|
||||
//! - Translate the container path (`/agents/<agent>/state/foo.md`) to
|
||||
//! the host path (`/var/lib/hyperhive/agents/<agent>/state/foo.md`)
|
||||
//! so hive-c0re can write to it from outside the container.
|
||||
//! - Reject anything that isn't under the agent's own state subtree,
|
||||
//! contains `..` (path traversal), or has an empty relative tail.
|
||||
//! Falling outside the allowed prefix means the file write is
|
||||
//! skipped and the original message is delivered inline (with a
|
||||
//! noted warning) — the reminder still fires, just without the
|
||||
//! payload split.
|
||||
//! - Defend against symlink escape: after `create_dir_all`, the
|
||||
//! parent dir is canonicalized and re-verified to live under the
|
||||
//! agent's host state root. Then we open the final file with
|
||||
//! `O_NOFOLLOW | O_CREAT | O_TRUNC` so an existing-symlink basename
|
||||
//! can't redirect the write either. Without this an agent could
|
||||
//! `ln -s /etc /agents/foo/state/escape` and bounce a write to an
|
||||
//! arbitrary host path.
|
||||
//! - Write the reminder body to disk and deliver a short pointer
|
||||
//! message in its place, so the agent's inbox/wake-prompt stays
|
||||
//! small and the bulky payload can be read out of band.
|
||||
//!
|
||||
//! Atomicity of the inbox INSERT + `reminders.sent_at` UPDATE is handled
|
||||
//! inside `Broker::deliver_reminders_batch`; this module only computes the
|
||||
//! body strings before calling it.
|
||||
//! Background loop that drains due reminders from the broker and
|
||||
//! delivers them as inbox messages. 5s poll cadence, shutdown-aware.
|
||||
//! File-path semantics (path translation, traversal + symlink defense,
|
||||
//! pointer delivery): `docs/approvals.md::Reminder delivery`.
|
||||
|
||||
use std::io::Write;
|
||||
use std::os::unix::fs::OpenOptionsExt;
|
||||
|
|
|
|||
Loading…
Reference in a new issue