Watch
0
0
Fork
You've already forked hyperhive
0

Compare commits

...
Author SHA1 Message Date
iris
2aa9f19ccb docs: clear write-good.Passive hits in docs/trust-boundary/security.md
2 genuine passive-voice rewrites (actor named — a caller, the
hard-coded unit name); 5 false positives left untouched — a
definitional/classificatory statement ('an agent is trusted code'),
a predicate-adjective state description ('is privileged'), two
quoted-literal framing phrases ('can the agent be stopped...', 'the
whole hive is affected'), and the already-established 'is tracked as
a TODO' pattern.
2026-09-20 13:24:24 +02:00
iris
6f0985824c docs: clear write-good.Passive hits in docs/web-ui/shape.md
5 genuine passive-voice rewrites (actor named — DOMPurify.sanitize()
x2, paintAtomic, the keyed pattern, the operator); 2 false positives
left untouched — predicate-adjective state descriptions ('are
unaffected', 'is gone'), same category as several already-established
in this sweep.
2026-09-20 13:24:24 +02:00
iris
ad16f3dba5 docs: clear write-good.Passive hits in docs/agent-lifecycle/agent-hierarchy.md
3 genuine passive-voice rewrites (actor named — the operator,
hive-c0re, the harness); 5 false positives left untouched — a
design-intent idiom ('are meant to run'), a predicate-adjective state
description ('is finished'), a predicate-adjective necessity
description ('is required', the original established example for this
category), a project-planning-state description ('is planned'), and a
quoted literal error string ('must be owned by uid 0...').
2026-09-20 13:24:24 +02:00
iris
c75604aa42 docs: clear write-good.Passive hits in docs/swarm/sso.md
6 genuine passive-voice rewrites (actor named — the operator,
secrets.md, swarm-authelia.nix, the person who introduced the split,
swarmctl); 1 false positive left untouched — a quoted phrase +
predicate adjective ('presenting as "the forge is broken"').
2026-09-20 13:24:24 +02:00
4 changed files with 39 additions and 36 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
structural manager that everything hangs under. Hierarchy is built
explicitly: an agent gets a parent edge written before its first spawn,
or the operator reparents it afterwards (so `bob` above sits under
structural manager that everything hangs under. The operator builds
hierarchy explicitly: an agent gets a parent edge written before its
first spawn, or the operator reparents it afterwards (so `bob` above sits under
`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
"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
deploy log). Planned: each agent gets RW to `/agents/<descendant>/`
for just its own subtree — the manager's full-forest RW becomes the
"root's subtree is everything" case of that same rule. RO `/meta`
access will be gated on a "meta read" capability; only
"root's subtree is everything" case of that same rule. hive-c0re will
gate RO `/meta` access on a "meta read" capability; only
`request_update_meta_inputs` writes `flake.lock`, gated by its own
capability.
- **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
module docs for `loose_ends.rs`, `stores/broker.rs`, `actions.rs`,
and `workers/crash_watch.rs` for the current owner-check logic in
each. (Reminder cancellation is handled fully in-agent — see the note
on `CancelLooseEndKind::Reminder` in
each. (The harness handles reminder cancellation fully in-agent — see
the note on `CancelLooseEndKind::Reminder` in
`hive-c0re/src/socket_server/mod.rs`.)
None of the above is a stable interface — treat the module doc

View file

@ -1,9 +1,9 @@
# Swarm SSO
The swarm runs one authelia, and it's two things at once: the **session
provider** every protected vhost checks (`auth_request`), and — once any
client is declared — an **OIDC provider** issuing tokens to relying
parties: the forge and the matrix homeserver.
provider** every protected vhost checks (`auth_request`), and — once an
operator declares any client — an **OIDC provider** issuing tokens to
relying parties: the forge and the matrix homeserver.
The second role derives rather than switches:
`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
```
It then exits 1 and systemd restarts it, so a swarm that has been
It then exits 1 and systemd restarts it, so a swarm the operator has
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.
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
Every secret in the swarm, with its generator and its path, is tabulated
in one place: [`secrets.md`](secrets.md), including authelia's own keys
[`secrets.md`](secrets.md) tabulates every secret in the swarm, with its
generator and its path, in one place — including authelia's own keys
(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
a path, never a value, and the generator and the reader typically live in
@ -115,9 +115,11 @@ 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
entry — callback URL included — to authelia's client list.
The callback is built once and read twice, so the redirect URI authelia
accepts and the one the service actually sends can't drift apart. A
mismatch there is a rejected login with no error text worth reading.
Each relying service's own glue module builds its callback once, and
both authelia's client list and the service's own OIDC client config
read that same value, so the redirect URI authelia accepts and the one
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:
nixos-container refuses to start a container whose bind source is
@ -238,7 +240,7 @@ upstream and compare three requests, not one:
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
page-only check. This was verified that way when the split was introduced.
page-only check. The person who introduced the split verified it this way.
## What this doesn't do
@ -254,6 +256,6 @@ page-only check. This was verified that way when the split was introduced.
door, and a dead provider locks everyone out, the built-in admin
included. That's why `swarm.grafana.oidc.role` defaults to `Admin`; see
[`services.md`](services.md#metrics-victoriametrics--grafana).
- **It doesn't provision users.** Agents are created and destroyed
continuously, so the subject set belongs to a program rather than to a
config file; today that program is `swarmctl`.
- **It doesn't provision users.** `swarmctl` creates and destroys agents
continuously today, so the subject set belongs to a program rather
than to a config file.

View file

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