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
|
fan-out pass; a broker error on a one-shot is not retried (the
|
||||||
operator advisory and `last_result` are the only audit trail).
|
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
|
### Destroy semantics
|
||||||
|
|
||||||
`HostRequest::Destroy { name, purge }` is the lifecycle tear-down,
|
`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
|
`agent_web_port(name)`). A future cleanup will drop the TCP fallback
|
||||||
once every agent's flipped.
|
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)
|
## Dashboard link shape (gateway vs direct)
|
||||||
|
|
||||||
When the gateway is in front, the SW4RM tab builds per-agent links
|
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
|
//! `/var/lib/hyperhive/agent-ports.json` writer — flat map of
|
||||||
//! per-agent `/agent/<name>/` TCP routing. Written alongside
|
//! agent name → TCP web port. Written alongside `agents.conf` on
|
||||||
//! `agents.conf` (see `gateway_nginx.rs`) on every topology change;
|
//! every topology change. JSON shape, port derivation (FNV-1a hash),
|
||||||
//! `gateway_nginx::render` reads it indirectly via
|
//! atomicity, and manager exclusion: `docs/gateway.md::Agent port map`.
|
||||||
//! `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.
|
|
||||||
|
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,7 @@
|
||||||
//! Per-container state watcher. Polls every managed container on a
|
//! Per-container crash and login-state watcher. Polls every managed
|
||||||
//! fixed interval, tracks two orthogonal state-sets across ticks,
|
//! container on a 10s interval. Fires `ContainerCrash`, `LoggedIn`,
|
||||||
//! and emits a `HelperEvent` to the manager on each transition:
|
//! and `NeedsLogin` helper events. Event semantics and the
|
||||||
//!
|
//! `RECENT_TRANSIENT_GRACE` window: `docs/approvals.md::Helper events`.
|
||||||
//! - **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.
|
|
||||||
|
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,9 @@
|
||||||
//! Runtime nginx include-file generator for the gateway's per-agent
|
//! Runtime nginx include-file generator for the gateway's per-agent
|
||||||
//! `/agent/<name>/` location blocks.
|
//! `/agent/<name>/` location blocks. Writes
|
||||||
//!
|
//! `/var/lib/hyperhive/gateway/agents.conf` on every topology change.
|
||||||
//! Writes `/var/lib/hyperhive/gateway/agents.conf` on every topology change.
|
//! UDS vs TCP upstream selection, reload trigger (`systemd-run
|
||||||
//! The gateway container bind-mounts `/var/lib/hyperhive/gateway/` (NOT the
|
//! --machine=hive-gateway`), and idempotency:
|
||||||
//! whole parent dir) at `/run/hive-state/`; nginx includes
|
//! `docs/gateway.md::Per-agent unix-socket upstream`.
|
||||||
//! `/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.
|
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use std::fmt::Write as _;
|
use std::fmt::Write as _;
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,7 @@
|
||||||
//! Startup auto-migration from the pre-meta layout. Runs before
|
//! Startup auto-migration from the pre-meta layout. Four idempotent
|
||||||
//! `auto_update::run` and consists of four phases, each idempotent:
|
//! phases: applied repo, proposed repo, meta repo, container repoint.
|
||||||
//!
|
//! Kill-switch: `HIVE_SKIP_META_MIGRATION=1`. Full migration sequence
|
||||||
//! 1. Per-agent applied repo: rewrite `flake.nix` to the module-only
|
//! and phase details: `docs/approvals.md::Migration from the pre-tag`.
|
||||||
//! 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.
|
|
||||||
|
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
|
||||||
|
|
@ -1,34 +1,7 @@
|
||||||
//! Background loop that drains due reminders out of the broker and
|
//! Background loop that drains due reminders from the broker and
|
||||||
//! delivers them as inbox messages. Mirrors the `events_vacuum` /
|
//! delivers them as inbox messages. 5s poll cadence, shutdown-aware.
|
||||||
//! `crash_watch` shape — a single `spawn(coord)` entry point started
|
//! File-path semantics (path translation, traversal + symlink defense,
|
||||||
//! from `main.rs`.
|
//! pointer delivery): `docs/approvals.md::Reminder delivery`.
|
||||||
//!
|
|
||||||
//! 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.
|
|
||||||
|
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::os::unix::fs::OpenOptionsExt;
|
use std::os::unix::fs::OpenOptionsExt;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue