Watch
0
0
Fork
You've already forked hyperhive
0

Compare commits

..
4 changed files with 36 additions and 39 deletions

View file

@ -20,9 +20,9 @@ Topology lives in the hive-c0re-owned **meta repo**, alongside
``` ```
`null` = root-level agent. New agents **default to root** — there is no `null` = root-level agent. New agents **default to root** — there is no
structural manager that everything hangs under. The operator builds structural manager that everything hangs under. Hierarchy is built
hierarchy explicitly: an agent gets a parent edge written before its explicitly: an agent gets a parent edge written before its first spawn,
first spawn, or the operator reparents it afterwards (so `bob` above sits under or the operator reparents it afterwards (so `bob` above sits under
`alice`). Any agent is reparentable, the bootstrap container (`ruth`) `alice`). Any agent is reparentable, the bootstrap container (`ruth`)
included — it's just another root. The manager is reparentable like any other agent; there's no included — it's just another root. The manager is reparentable like any other agent; there's no
"structurally root" carve-out. Its privileges live on its MCP socket, "structurally root" carve-out. Its privileges live on its MCP socket,
@ -128,8 +128,8 @@ other agents don't:
`/applied` (diff against what's deployed) and `/meta` (system-wide `/applied` (diff against what's deployed) and `/meta` (system-wide
deploy log). Planned: each agent gets RW to `/agents/<descendant>/` deploy log). Planned: each agent gets RW to `/agents/<descendant>/`
for just its own subtree — the manager's full-forest RW becomes the for just its own subtree — the manager's full-forest RW becomes the
"root's subtree is everything" case of that same rule. hive-c0re will "root's subtree is everything" case of that same rule. RO `/meta`
gate RO `/meta` access on a "meta read" capability; only access will be gated on a "meta read" capability; only
`request_update_meta_inputs` writes `flake.lock`, gated by its own `request_update_meta_inputs` writes `flake.lock`, gated by its own
capability. capability.
- **Prompt/tools** — the system prompt uses `<!-- role:agent -->` / - **Prompt/tools** — the system prompt uses `<!-- role:agent -->` /
@ -151,8 +151,8 @@ Manager}` switch picks the MCP tool allow-list claude sees. Both are
ancestor/descendant check instead of a manager-name check — see the ancestor/descendant check instead of a manager-name check — see the
module docs for `loose_ends.rs`, `stores/broker.rs`, `actions.rs`, module docs for `loose_ends.rs`, `stores/broker.rs`, `actions.rs`,
and `workers/crash_watch.rs` for the current owner-check logic in and `workers/crash_watch.rs` for the current owner-check logic in
each. (The harness handles reminder cancellation fully in-agent — see each. (Reminder cancellation is handled fully in-agent — see the note
the note on `CancelLooseEndKind::Reminder` in on `CancelLooseEndKind::Reminder` in
`hive-c0re/src/socket_server/mod.rs`.) `hive-c0re/src/socket_server/mod.rs`.)
None of the above is a stable interface — treat the module doc None of the above is a stable interface — treat the module doc

View file

@ -1,9 +1,9 @@
# Swarm SSO # Swarm SSO
The swarm runs one authelia, and it's two things at once: the **session The swarm runs one authelia, and it's two things at once: the **session
provider** every protected vhost checks (`auth_request`), and — once an provider** every protected vhost checks (`auth_request`), and — once any
operator declares any client — an **OIDC provider** issuing tokens to client is declared — an **OIDC provider** issuing tokens to relying
relying parties: the forge and the matrix homeserver. parties: the forge and the matrix homeserver.
The second role derives rather than switches: The second role derives rather than switches:
`services.hyperhive.swarm.authelia.oidc.clients` being non-empty turns it `services.hyperhive.swarm.authelia.oidc.clients` being non-empty turns it
@ -28,7 +28,7 @@ error reading the authentication database: could not validate the schema:
users: non zero value required users: non zero value required
``` ```
It then exits 1 and systemd restarts it, so a swarm the operator has It then exits 1 and systemd restarts it, so a swarm that has been
enabled but not bootstrapped shows a **crash-looping unit** and `502 Bad enabled but not bootstrapped shows a **crash-looping unit** and `502 Bad
Gateway` from the vhost — not a login page with nobody able to use it. Gateway` from the vhost — not a login page with nobody able to use it.
The gateway is working in that state; the upstream isn't up. The gateway is working in that state; the upstream isn't up.
@ -95,8 +95,8 @@ an attribute edit can invalidate a login by accident.
## What secrets exist, and where each one lives ## What secrets exist, and where each one lives
[`secrets.md`](secrets.md) tabulates every secret in the swarm, with its Every secret in the swarm, with its generator and its path, is tabulated
generator and its path, in one place — including authelia's own keys in one place: [`secrets.md`](secrets.md), including authelia's own keys
(session, JWT, storage-encryption, OIDC HMAC, OIDC issuer) and the two (session, JWT, storage-encryption, OIDC HMAC, OIDC issuer) and the two
halves of each client secret. That page's two rules — a secret is always halves of each client secret. That page's two rules — a secret is always
a path, never a value, and the generator and the reader typically live in a path, never a value, and the generator and the reader typically live in
@ -115,11 +115,9 @@ authelia's first boot to mint that client's secret and copies it into the
service's container, and the service's own module contributes its client service's container, and the service's own module contributes its client
entry — callback URL included — to authelia's client list. entry — callback URL included — to authelia's client list.
Each relying service's own glue module builds its callback once, and The callback is built once and read twice, so the redirect URI authelia
both authelia's client list and the service's own OIDC client config accepts and the one the service actually sends can't drift apart. A
read that same value, so the redirect URI authelia accepts and the one mismatch there is a rejected login with no error text worth reading.
the service actually sends can't drift apart. A mismatch there is a
rejected login with no error text worth reading.
⚠️ The delivery is a copy, not a `bindMounts` entry, and deliberately so: ⚠️ The delivery is a copy, not a `bindMounts` entry, and deliberately so:
nixos-container refuses to start a container whose bind source is nixos-container refuses to start a container whose bind source is
@ -240,7 +238,7 @@ upstream and compare three requests, not one:
All three matter. A change that silently deleted the browser page would All three matter. A change that silently deleted the browser page would
pass a deny-only check, and one that quietly stopped denying would pass a pass a deny-only check, and one that quietly stopped denying would pass a
page-only check. The person who introduced the split verified it this way. page-only check. This was verified that way when the split was introduced.
## What this doesn't do ## What this doesn't do
@ -256,6 +254,6 @@ page-only check. The person who introduced the split verified it this way.
door, and a dead provider locks everyone out, the built-in admin door, and a dead provider locks everyone out, the built-in admin
included. That's why `swarm.grafana.oidc.role` defaults to `Admin`; see included. That's why `swarm.grafana.oidc.role` defaults to `Admin`; see
[`services.md`](services.md#metrics-victoriametrics--grafana). [`services.md`](services.md#metrics-victoriametrics--grafana).
- **It doesn't provision users.** `swarmctl` creates and destroys agents - **It doesn't provision users.** Agents are created and destroyed
continuously today, so the subject set belongs to a program rather continuously, so the subject set belongs to a program rather than to a
than to a config file. config file; today that program is `swarmctl`.

View file

@ -236,7 +236,7 @@ this gap.
⚠️ **`ReloadGatewayNginx` acts on a host unit, so nothing implicitly ⚠️ **`ReloadGatewayNginx` acts on a host unit, so nothing implicitly
scopes it.** Its containment is the unit name hard-coded in `hive-priv`: scopes it.** Its containment is the unit name hard-coded in `hive-priv`:
a caller can't name the unit, so it can't steer the verb at another a caller can't name the unit, so the verb can't be steered at another
service. **A privileged verb needs something bounding what it can act service. **A privileged verb needs something bounding what it can act
on; when that isn't a namespace, it has to be a constant the caller on; when that isn't a namespace, it has to be a constant the caller
can't supply.** can't supply.**
@ -290,7 +290,7 @@ matching the agent-name convention (char-validated) or the known
sibling service containers (`hive-forge`, `hive-matrix`, `hive-ci`), sibling service containers (`hive-forge`, `hive-matrix`, `hive-ci`),
and rejects arbitrary container names. `hive-gateway` is a host unit, and rejects arbitrary container names. `hive-gateway` is a host unit,
not a container, so it's not in this list — see `ReloadGatewayNginx` not a container, so it's not in this list — see `ReloadGatewayNginx`
above for how the hard-coded unit name scopes its access instead. above for how its access is scoped instead.
**Socket-activated** — systemd starts `hive-priv` on the first **Socket-activated** — systemd starts `hive-priv` on the first
incoming connection (`LISTEN_FDS=1`); it's not running between calls. incoming connection (`LISTEN_FDS=1`); it's not running between calls.

View file

@ -37,10 +37,10 @@
`marked` (npm dep, replacing the vendored UMD bundle) and then `marked` (npm dep, replacing the vendored UMD bundle) and then
`DOMPurify.sanitize()` before hitting `innerHTML` — untrusted row `DOMPurify.sanitize()` before hitting `innerHTML` — untrusted row
text (peer-agent / matrix-relayed message bodies, agent-authored text (peer-agent / matrix-relayed message bodies, agent-authored
state files) can carry arbitrary HTML/script via markdown, so state files) can carry arbitrary HTML/script via markdown, so the
`DOMPurify.sanitize()` cleans the markdown path rather than it being markdown path is sanitized rather than XSS-safe by construction the
XSS-safe by construction the way the text-node `linkify` path is. way the text-node `linkify` path is. Rendered `<a>`s are rewritten
Rendered `<a>`s are rewritten to `target="_blank"`. to `target="_blank"`.
- `GET /api/state` → JSON snapshot the JS app renders into the - `GET /api/state` → JSON snapshot the JS app renders into the
DOM. Includes a top-level `seq` (the dashboard event channel's DOM. Includes a top-level `seq` (the dashboard event channel's
high-water mark at the moment the server assembled the snapshot); high-water mark at the moment the server assembled the snapshot);
@ -192,10 +192,9 @@ inside a fresh text node, so the autolinker never touches
trailing-punctuation strip keeps `.,;:` outside the link surface. trailing-punctuation strip keeps `.,;:` outside the link surface.
Markdown bodies go through `marked` separately, then Markdown bodies go through `marked` separately, then
`DOMPurify.sanitize()` on the resulting HTML before it's assigned to `DOMPurify.sanitize()` on the resulting HTML before it's assigned to
`innerHTML` (see `mdNode` in `app.js` / `common.js`) — `innerHTML` (see `mdNode` in `app.js` / `common.js`) — this path is
`DOMPurify.sanitize()` cleans this path rather than it being sanitized, not text-node-safe like `linkify`, since markdown can
text-node-safe like `linkify`, since markdown can carry raw HTML. carry raw HTML. Rendered `<a>`s get the same target rewrite.
Rendered `<a>`s get the same target rewrite.
The JS app handles all `form[data-async]` submissions via a delegated The JS app handles all `form[data-async]` submissions via a delegated
listener: read `data-confirm`, swap the button to a spinner, POST listener: read `data-confirm`, swap the button to a spinner, POST
@ -227,12 +226,12 @@ before returning.
**Keyed DOM caching:** for sections whose rows hold interactive state **Keyed DOM caching:** for sections whose rows hold interactive state
(textarea drafts, checkboxes, focused inputs) `paintAtomic` isn't (textarea drafts, checkboxes, focused inputs) `paintAtomic` isn't
enough — wiping and rebuilding still destroys the state even if enough — wiping and rebuilding still destroys the state even if the
`paintAtomic` hides the flash. The keyed pattern keeps a flash is hidden. The keyed pattern keeps a `Map<id, {el, fingerprint}>`
`Map<id, {el, fingerprint}>` where the fingerprint is where the fingerprint is `JSON.stringify({...visible fields...})`.
`JSON.stringify({...visible fields...})`. On each render, the pattern On each render: cache-hit rows are reused verbatim (preserving
reuses cache-hit rows verbatim (preserving textarea draft, checkbox textarea draft, checkbox state, and event listeners); only cache-miss
state, and event listeners) and rebuilds only cache-miss rows. Used for: containers (`containerRowCache`), rows are rebuilt and inserted. Used for: containers (`containerRowCache`),
rebuild-queue entries (`rebuildQueueRowCache`). The spawn-form input+focus and meta-input rebuild-queue entries (`rebuildQueueRowCache`). The spawn-form input+focus and meta-input
checkboxes use a lighter snapshot-then-restore pattern (snapshot checkboxes use a lighter snapshot-then-restore pattern (snapshot
before `replaceChildren`, restore after) since they're single before `replaceChildren`, restore after) since they're single
@ -299,7 +298,7 @@ flood the journal. The per-agent UI's `bind_with_retry`
than looping forever. than looping forever.
The per-agent UI optionally binds a `UnixListener` instead of The per-agent UI optionally binds a `UnixListener` instead of
TCP when the nix module sets `HIVE_WEB_SOCKET` — the unix-socket transition TCP when `HIVE_WEB_SOCKET` is set — the unix-socket transition
mechanics (per-agent `/run/hive-agent/<name>/` bind-mount, mechanics (per-agent `/run/hive-agent/<name>/` bind-mount,
`.bound` marker filtering, `agent-sockets.json` consumer on the `.bound` marker filtering, `agent-sockets.json` consumer on the
gateway side) live in [`docs/networking/gateway.md::Per-agent unix-socket gateway side) live in [`docs/networking/gateway.md::Per-agent unix-socket