Compare commits
4 changed files with 36 additions and 39 deletions
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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`.
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue