Watch
0
0
Fork
You've already forked hyperhive
0
Commit graph hyperhive/docs/swarm/ui.md
Author SHA1 Message Date
atlas
8ad2af735e swarm-controller: refuse linking over an existing account
The matrix, forge and github link routes wrote their credential
unconditionally, so linking a name that was already linked replaced the
working account. For matrix that lost the device the agent's crypto store
belongs to (#4838).

Each route now reads the account's store path first and answers 409,
naming the existing account, when something is stored there. Nothing is
written. Replacing an account takes the delete from #4899, then a link.

The matrix route checks before password mode's login, so a refused link
mints no new device at the homeserver.

The check is a read then a write, not an atomic step; two concurrent
links to one name can still both pass it.

Closes #4856
2026-10-03 13:49:03 +02:00
atlas
318f67cda9 swarm-controller: create every agent's subagent stream
swarm-controller now creates `term-sub-<agent>` for every agent a hive is
declared to run, at start and every minute after, with the config
`swarm_queue_client::subagent_term::open_or_create` spells (subjects
`$SWARM.term.<agent>.sub.>`, max_age 24h). An existing stream is opened as
it is, as the controller does for its other streams and buckets, under the
`$JS.API.STREAM.CREATE.*` grant it already holds.

The agent no longer creates the stream: its token is granted publish on
`$SWARM.term.<agent>.sub.>` and no `$JS.API.STREAM.CREATE|INFO` subject,
and the subagent daemon only publishes. A `CREATE` carries the stream's
config in its payload, which no subject grant narrows, so the agent could
otherwise pick the stream's subjects and limits.
2026-10-03 01:34:01 +02:00
atlas
d6f94e5247 swarm: show subagent terminals in the swarm UI
An agent's subagent daemon publishes each subagent's output as terminal
rows on `$SWARM.term.<agent>.sub.<subagent>`, as the agent, into a
per-agent stream it creates itself; swarm-controller lists an agent's
subagents from that stream's subjects and relays one subagent's rows as
SSE; the swarm UI lists them under the agent's terminal preview and
reuses AgentTermPreview, full-screen tab included, with no input.

- swarm-nats.nix: the agent token may also publish
  `$SWARM.term.{agent}.sub.>` and `$JS.API.STREAM.CREATE|INFO` on
  `term-sub-{agent}`, and nothing else of JetStream. A module-eval arm
  pins the agent-token grant as an exact list.
- mcp.nix: hive-subagent-daemon loads the agent's store identity
  (`hive-agent-bao-cert/-key/-server-ca`, the ones hive-agent loads)
  whenever the agent has a store, not only on the opencode preset. The
  agent's own queue secret lives in the store, so this is the credential
  the harness connects with.
- hive-subagent-mcp: `swarm_term` reads the agent's queue secret under
  that identity, connects with the agent token, opens or creates
  `term-sub-<agent>` (max_age 24h), and publishes classified rows from
  the sink every subagent line already passes through. The sink only
  queues (bounded, drop-and-count); a missing store, refused credential,
  failed stream create or failed publish is a log line.
- The stream-json classifier (`stream_enrich`) and the `TermMsg` row
  types plus `fit` move from the hive-agent binary into hive-sh4re, so
  the subagent daemon publishes the rows AgentTermPreview already
  renders. hive-agent keeps its LiveEvent classifier on top.
- swarm-controller: `GET /api/agents/{name}/subagents` and
  `GET /api/agents/{name}/subagents/{subagent}/term/stream`.
- docs/swarm: what the UI shows and what the queue carries.

Closes #4827
2026-10-03 01:34:01 +02:00
atlas
b90be9e65e swarm UI: delete linked accounts — R2 fixes
Addresses argus review comment 90297 on PR #4899:

- swarm-controller/README.md: list the three DELETE routes (including
  matrix's ?revoke=true) beside the PUT/GET ones already documented.
- LinkedAccounts.tsx: a delete answering 404 means the account is
  already gone, so treat it as the delete's end state — re-fetch and
  close the dialog instead of showing an error.
- matrix_account.rs: matrix_logout treats a 401 M_UNKNOWN_TOKEN as the
  token already being revoked and proceeds with the delete; every
  other logout failure still keeps the account. Adds unit tests and
  updates docs/swarm/ui.md to match.
2026-10-03 00:56:39 +02:00
atlas
bbf931207f swarm UI: delete linked accounts
Each row of an agent's linked accounts, except its own `main` matrix
account, gets a delete action. swarm-controller serves DELETE beside each
PUT (matrix-accounts/{account}, forge-accounts/{label}, github-account),
answers 404 for an account the store does not hold, refuses `main`, and
removes every version through `delete_all_versions`.

The matrix confirmation has a revoke checkbox, off by default: the
controller logs the stored token out at its homeserver first, and keeps
the account when that fails or no homeserver is stored.

The controller's policy gains `delete` on each agent's
`metadata/.../matrix/+`, `forge/+` and `github-token`, pinned in
bao-grants.nix.

Refs #4855
2026-10-03 00:56:39 +02:00
atlas
9224c0bd15 swarm UI: fetch linked accounts only for the opened agent
The detail panel makes one request for the agent it shows,
GET /api/hives/{hive}/agents/{agent}/linked-accounts, which returns every
matrix, forge and github account of that agent as names and hosts. The
all-agents route and the table's matrix-column rows are removed, so the
table makes no linked-accounts request. The panel stays keyed by
hive/agent. The bao grant is unchanged.

Refs #4855
2026-10-02 22:36:11 +02:00
atlas
7ccde4647b swarm UI: one request for every agent's linked accounts
GET /api/agents/linked-accounts returns one entry per agent that
/api/agents/status has a row for, as {hive, agent, accounts}, from one
store login. The agents page fetches it once (and again when a link dialog
closes) and hands each table row and the detail panel its agent's slice,
so the page makes no per-agent request. The per-agent route had no caller
left and is removed. The bao grant is unchanged: the same list on each
agent's matrix and forge metadata directories.

Refs #4855
2026-10-02 21:20:40 +02:00
atlas
3380c1915f swarm UI: show the accounts linked to each agent
GET /api/hives/{hive}/agents/{agent}/linked-accounts returns one row per
account linked to the agent, as kind, name and host: each matrix account
under swarm/agents/<agent>/matrix (with its homeserver, and the agent's own
`main` marked reserved), each forge label under swarm/agents/<agent>/forge
(with its url), and github when swarm/agents/<agent>/github-token exists
(host github.com, which is not stored). No credential field is in the
response type.

Listing those two directories needs a new controller grant: `list` on
secret/metadata/swarm/agents/+/matrix and .../+/forge only, pinned in
bao-grants.nix as the only metadata stanzas under agents/ beside the queue
revocation. Checked against a dev OpenBao 2.6.3: the grant lists those two
directories and is refused on agents/, agents/<agent>/, and a leaf.

The swarm UI agent detail panel shows all rows under "accounts"; the table
view's matrix column shows the matrix rows. The link badges stay.

Refs #4855
2026-10-02 21:20:40 +02:00
atlas
6fd91b0e69 docs(swarm-ui): state that every quick-link's option defaults to a value
argus (PR #4895): the link table didn't say an entry can exist without
the service running anywhere in the swarm. Every option in the table
defaults to a value, so a reader shouldn't take an entry's presence as
proof the service is up.
2026-10-02 20:50:15 +02:00
atlas
72e9e1bb4a swarm-ui: build the forge link from swarm.forge.domain
The swarm-controller builds the Forge quick link from
services.hyperhive.swarm.forge.domain, replacing hive-forge/default.nix's
per-host entry, so all seven swarm-service links come from swarm-level
options.

Also drops the remaining references to the removed matrix GUI switch:
the HiveUrls / Urls / hive_urls docs, the hivectl.md `open` note and
the gateway.md vhost-map rows, which name `gatewayHost` instead. The
grafana, victoriametrics and victorialogs modules' comments no longer
mention a quick-link they do not define.

Refs #4885
2026-10-02 20:02:02 +02:00
atlas
6786d54e5a swarm-ui: link the secret store's web UI from swarm.bao.ui.domain
The swarm-controller adds a Bao quick link built from
services.hyperhive.swarm.bao.ui.domain, so the popover links the
store's browser UI whichever host runs bao.

Refs #4885
2026-10-02 20:02:02 +02:00
atlas
f60f8af33b matrix: serve the web client unconditionally; link it from the swarm domain
Removes services.hyperhive.deploy.matrix.gui.enable and its
swarm.matrix.gui.enable alias; both are mkRemovedOptionModule stubs. A
host running the homeserver serves fluffychat at gatewayHost's vhost,
and the hive's /matrix/ redirect follows the same condition.

The swarm-controller builds the Matrix quick link from
swarm.matrix.gatewayHost, replacing hive-matrix.nix's per-host entry.
HIVE_MATRIX_PUBLIC_URL is set on every hive with a gatewayHost, so
`hivectl open matrix` resolves off the homeserver's host too.

Drops HIVE_MATRIX_GUI_ENABLED and the dashboard's matrix_gui_enabled
field; nothing in the frontend reads it.

Refs #4885
2026-10-02 20:02:02 +02:00
atlas
8628e0ecdd swarm-ui: build authelia/grafana/metrics/logs links from swarm domains
The swarm-controller module builds the Authelia, Grafana, Metrics and
Logs quick links from services.hyperhive.swarm.<service>.domain, on the
controller's host, instead of each service module adding its entry only
on the host that runs it. A controller whose swarm runs those services
on other hosts lists them in its /api/links popover.

Forge, matrix and bao links are not moved yet: forge waits on #4891,
matrix and bao on whether their GUI gate becomes swarm-level.

Refs #4885
2026-10-02 19:45:02 +02:00
atlas
8e23feb01b github: PATs live in swarm bao; the agent fetches them itself
An operator links an agent's GitHub personal access token in the swarm UI
(LinkGithubAccountForm, "link github account" on /agents). swarm-controller's
PUT /api/hives/{hive}/agents/{agent}/github-account stores it at
swarm/agents/<agent>/github-token (swarm_secret_client::github), a flat leaf
under the agent's prefix that the agent's existing read grant already covers:
no policy change, and no list grant, since there is one token per agent.

In the agent, hive-agent-github-token (oneshot + 2-minute timer, as the agent
user, under its own store certificate, ordered before hive-github-notify)
reads that path and writes <state>/github-token, 0600 and agent-owned, the
file the gh wrapper, git credential helper and hive-github-notify already
read. It replaces the file by rename only when the bytes changed and never
deletes it: a hive-written github-token stays until a token is linked in the
swarm UI. It is installed only with a store address and
services.hyperhive.agent.github.enable.

Removed: the dashboard's CR3D3NTIALS page (credentials.html/js/css, its
build entries and H0M3 tile; GITHUB was its only tab), hive-c0re's
dashboard/matrix_accounts.rs with GET/POST /api/github-account,
priv_client::write_agent_github_token, the host socket's
SetAgentGithubToken and `hivectl github set-token`, and hive-priv's
WriteAgentGithubToken with write_agent_state_file, its only caller gone.

Docs: integrations/github.md and swarm/ui.md describe the swarm path,
swarm/credentials.md gains the store-path row, and the hive UI docs,
hivectl docs and security.md's hive-priv table drop the removed pieces.

Closes #4347
2026-10-02 17:48:27 +02:00
atlas
cabde572e9 docs(web-ui): scope dashboard.md to what the hive UI renders
Per mara's review: the hive UI doc covers only what hive-c0re's pages
render. Removed the M4TR1X page section (the hive gateway redirects
/matrix/ to the swarm matrix client, which the swarm UI's quick links
open), the swarm-UI forge/matrix account-linking lines from the
CR3D3NTIALS section, the infra-services hivectl paragraph, and the H0M3
Matrix/Forge absence line. Added a single pointer to docs/swarm/ui.md,
and stated the account-linking and Matrix quick-link facts there.

Refs #3902
2026-10-02 14:44:34 +02:00
atlas
270430a4b4 docs(swarm): facts + structure pass
swarm/README.md opens with the swarm and its control plane; hive identity
and the directory follow as the substrate. Upgrade notes move into a
<details> block, the per-agent queue publishing detail into another, and
the one-paragraph pointer sections collapse into a link list.

Fact fixes, checked against origin/main:
- an empty swarm.hives fails eval (swarm.nix:341-354); it does not mean
  "not in a swarm"
- swarm.domain is required with a hive (hive-network.nix:156,188), hiveName
  with a hive, store or homeserver (hyperhive.nix:161-166)
- the matrix container trusts the hive's trust-bundle.pem at runtime under
  self-signed certs (hive-matrix.nix:1046-1052, lib/hive-ca-trust.nix:76-85)
- singleHostSwarm also defaults the controller, localHostsEntry, the nats
  callout keys and the bao bootstrap token path (local-defaults.nix:72-129)
- swarm-controller serves far more than /health: roster, wanted state, job
  graph, agent creation and credential mints (main.rs:2874-2899)
- swarmctl user add needs --email for the forge account and refuses an
  existing user (setup.md:67-71, swarmctl/src/main.rs:425-430); document
  agent mint-identity and mint-forge-token
- agent creation also mints store identity, forge token and matrix
  account, and declares the agent paused (main.rs:1822-1920, 247-248)

Refs #3902
2026-10-02 12:50:34 +02:00
atlas
ed2ec52fe5 swarm-tls: narrow each gateway's services leaf to the names it fronts
Every gateway asked the store's `pki/issue/swarm-services` for the whole
swarm's service set, so a private key on any gateway host could serve a
valid certificate for services that host does not front and never has.

`swarm.localServiceDomains` derives the per-host subset by filtering
`swarm.serviceDomains` against the vhosts this host actually renders —
the deploy flags those vhosts are already guarded on, read once rather
than copied into a second filter. The leaf request and the coverage
guard that decides whether to re-issue both read it, so they cannot
disagree about which names the leaf owes.

The sub-CA's name constraint and the role's `allowed_domains` stay the
swarm-wide set: every host's subset is inside it, and narrowing the
constraint per host would turn one signing into N.
2026-09-25 23:41:10 +02:00
atlas
f4df4fc4a9 nix: issue the swarm-services leaf from bao's pki mount
The `pki` mount had no issuer and no principal could log in to it, so the
swarm's service certificates were still minted by two openssl hops from a
root key on disk. Close both halves and retire the openssl path with them.

The mount now generates its own root, once. The granting unit asks bao
whether an issuer already exists (`bao list pki/issuers`) before calling
`pki/root/generate/internal`, so a rebuild or a reboot re-asserts the role
and the grant without touching the anchor — a root that changed per boot
would invalidate every certificate issued under it and every browser
taught to trust it. The guard asks the store rather than looking for a
marker file on this host's disk: a file is a claim about a mount that may
have been restored from a snapshot or disabled and re-enabled underneath
it.

`swarm-services-issuer` stops being an inert policy. A fourth cert-auth
role attaches it, following the shape the controller, the publisher and
matrix-ctl already use, and glue-bao-tls.nix signs the leaf carrying its
CN — that credential is what opens the mount, so it cannot come out of it.

`swarm-services-cert.service` logs in with that leaf, calls
`pki/issue/swarm-services`, and writes the result to the path
hive-tls.nix already wrote and the gateway already copies from. The
sub-CA layer does not move; it stops existing. The role's
`allowed_domains`, read from the same `swarm.serviceDomains` the SANs
come from, enforces at issue time what the sub-CA encoded in x509
`nameConstraints`, and with the root inside the mount there is nothing
left for an intermediate to be an intermediate of.

Not a flag day: the issuing root is published beside the leaf as
`swarm-services-root.pem` (0644) and joins `trust-bundle.pem`, where the
swarm root still sits. A leaf chaining to the old sub-CA and one issued
by the store both verify against the same bundle, so hives can be
rebuilt in any order. The same file is what an operator hands a browser
— readable without a store login, which matters because every listener
demands a client certificate.

The eval-time warning about uncovered service names is gone rather than
reworded. It fired on "this host does not hold the swarm root key", which
was the reason a hive could end up serving its own leaf on a
swarm-service name. Every hive now asks the store with its own identity,
so that stopped being the thing that decides.

Closes #4586
2026-09-23 21:00:02 +02:00
iris
04e27c4fb6 docs: suppress reviewed write-good.Passive false positives
133 hits across 38 files, all previously classified during #4548's sweep
and deliberately left un-rewritten (predicate-adjective state/necessity
description, design-intent idiom, structural/type-description idiom,
no-single-actor topology claim, parallel-triple exception, vale
substring-match artifact — see hyperhive#4548's per-PR bodies for the
per-hit reasoning).

Wraps each one in a scoped <!-- vale write-good.Passive = NO/YES -->
pair (the supported mechanism — TokenIgnores has a known offset-drift
bug) rather than a blanket per-file or per-rule silence, so a *new*
passive-voice hit anywhere in these files still fails once the rule
gates CI (next commit). Table/list false positives (docs/swarm/credentials.md's
renewal-table cells) wrap the whole block, not each cell.

Part of #4546.
2026-09-20 16:24:11 +02:00
atlas
7570597190 docs: repoint agent-tier option paths to services.hyperhive.agent.*
Refs #4479
2026-09-18 03:05:43 +02:00
iris
fab2a0dedc docs: fix genuine passive-voice hits in docs/swarm
Read all 94 write-good.Passive hits across docs/swarm/ (ca.md,
README.md, secrets.md, services.md, sso.md, ui.md) in context. 44 are
genuine catches with a nameable, usually already-established actor
(swarm-controller, authelia, swarmctl, the controller, the gateway,
this module, hyperhive itself, or 'the operator' for manual actions) —
rewritten to active. 50 are legitimate passives or false catches, left
alone: predicate-adjective state descriptions (is expected/misconfigured/
broken), negative-capability idioms (no X is needed/placed, can't be
Yed/listed/fetched), config-state conditionals (whenever/when X is
enabled/configured/set), requirement-list labels (is required),
'is tracked as' idiom, backward-looking changelog facts with no actor
(was removed/verified/introduced), ambiguous-actor statements left
conservatively alone (agents are created and destroyed — could be
hive-c0re or swarm-controller, doc doesn't say), and a couple of
deliberately-parallel idiom pairs.

Several sibling-inconsistency fixes: a passive clause sitting next to
an already-active sibling describing the same fact/mechanism (ca.md's
two-bullet consumer list, README's 4-item WireGuard-mesh bullet list,
README's controller-registers-hooks paragraph, sso.md's followed-a-302
sentence).

Verified via vale on the whole directory, diffed against main's exact
baseline (not just the Passive count): write-good.Passive 94 -> 50
exactly, every other category unchanged (1 pre-existing
Microsoft.Contractions error at services... at secrets.md:182,
8 TooWordy, 1 Microsoft.We, 1 Microsoft.FirstPerson — same counts,
same locations).
2026-09-08 15:54:16 +02:00
iris
c20496604c fix Microsoft.Quotes vale findings - move punctuation inside quotes
Moved 27 instances of periods and commas from outside closing quote marks
to inside, following American-style punctuation placement. Changes span
across ~15 documentation files including integration, process, scheduler,
trust-boundary, and web-ui docs.
2026-09-07 16:28:06 +02:00
iris
4f2d1d281a docs: fix Microsoft.Foreign hits (e.g./i.e. -> for example/that is)
Substitutes 'for example' for 'e.g.' and 'that is' for 'i.e.' per
Microsoft style, with no comma after the replacement -- this repo
never puts a comma after e.g./i.e. either, so the substituted phrase
matches that house style instead of vale's literal 'for example,'
suggestion.

Covers all 77 real hits (72 from the initial vale pass + 5 more of the
same pattern found on a follow-up vale run). None of the flagged
instances were sentence-initial, so there's no capitalization concern
here.

Verified via a fresh vale run: 0 remaining Microsoft.Foreign hits.
Full accounting in /agents/iris/state/haiku-batches/foreign-report.md.
2026-09-07 16:28:06 +02:00
iris
ff377b0dee docs: fix capitalization losses + one text corruption from the contractions pass
Review pass on top of the previous commit (85a94671) turned up three
real issues its own vale-only verification didn't catch:

- 'has not' -> 'hasn't' fired inside 'has nothing', corrupting
  docs/swarm/README.md into 'hasn'thing to say upward'. Restored
  'has nothing'.
- ~44 hits where the original text was genuinely sentence-initial
  ('It is', 'That is', 'They are', 'What is', 'Do not', ...) got
  contracted in lowercase, silently de-capitalizing the start of a
  sentence. Checked each one's real surrounding text and capitalized
  the ones that needed it.
- Confirmed (by reading the actual text, not just trusting vale) that
  the 3 remaining 'is not'/'has not'/'it is' hits after those fixes are
  vale's own false positives: it matches these as a bare substring with
  no trailing word boundary, so it also fires on 'is nothing', 'has
  nothing', and the already-correct 'it isn't'. Left those alone --
  there's no real issue in the text.

Re-verified with a fresh vale run: the remaining 17 hits are exactly
the union of the 14 legitimate ALL-CAPS/bold-emphasis skips from the
first commit and these 3 false positives. Full accounting in
/agents/iris/state/haiku-batches/contractions-report.md.
2026-09-07 16:28:06 +02:00
iris
78021ce982 docs: fix 329 Microsoft.Contractions hits with proper safety checks
Apply contraction fixes across ~40 doc files (setup, integrations, lifecycle,
networking, scheduler, swarm, tools, trust-boundary, UI, etc.). Skipped 14 hits:
- 10 where words appear in ALL CAPS for deliberate emphasis (is NOT, do NOT, etc.)
- 4 where text could not be safely located due to markdown formatting or column position

Applied via systematic scan with checks for fenced code blocks, inline code spans,
and intentional caps. Preserves sentence-initial capitalization throughout.
2026-09-07 16:28:06 +02:00
iris
cd252ca6b2 docs: fix alex whitelist + condescending-word findings
Fixes the 'genuine' bucket from #4040's triage: whitelist/allowlist
(6 hits across 5 files, including a heading rename in turn-loop/mcp.md
-- checked no anchor links reference it first) and the
simply/obvious/easy condescending-word cluster (10 of 13 hits, real
sentence-level rewrites rather than mechanical deletion so nothing
reads awkwardly).

Left 3 alex.Condescending hits untouched on purpose:
integrations/matrix.md:271 and process/conventions.md:442 both say
"non-obvious", swarm/ca.md:162 says "not obvious" -- alex matched
the substring "obvious" inside a negated phrase, the literal opposite
of condescending. Flagging these on the issue rather than silently
leaving them out.
2026-09-07 13:25:09 +02:00
atlas
39b95c2ede treefmt: apply prettier
Pure `nix fmt` output from the commit before this one — no hand edits.
203 files: 52 md, 42 tsx, 32 js, 32 css, 21 ts, 13 html, 8 json, 3 mjs.

Reproduce with `nix develop -c nix fmt` on the parent commit; the result
should be byte-identical to this tree.

None of the 13 `.prettierignore` entries appears here — verified by
intersecting the changed-file list against the ignore file, with a
control proving the intersection finds a match when one exists.
2026-09-02 15:25:07 +02:00
iris
07b62612b0 docs: restructure into topic subdirectories, collapse duplicated index
Per mara's go-ahead on hyperhive#3902 ("getting started is good, but
terminal rendering does not go in there i think"):

Moved 21 top-level docs/*.md files into 7 new topic subdirectories
(existing web-ui/, turn-loop/, swarm/, tools/, crates/ untouched):
  getting-started/  setup.md
  agent-lifecycle/  agent-hierarchy.md, approvals.md, persistence.md
  trust-boundary/   boundary.md, security.md
  integrations/     forge.md, matrix.md, github.md, knowledge.md
  networking/       gateway.md, network.md, snapshot-store.md
  scheduler/        jobq.md, coordinator.md, ci.md, observability.md
  process/          conventions.md, gotchas.md, pr-review-gate.md
  web-ui/           terminal-rendering.md (moved into the EXISTING dir,
                    per mara's correction to the original getting-started
                    guess -- it's UI implementation detail, not onboarding)

The physical layout now matches docs/README.md's own topical headers,
which already amounted to this taxonomy -- see the scoping comment on
the issue for the two findings that motivated this (a genuine
duplication between CLAUDE.md's old "Reading paths" list and
docs/README.md's grouped one, since drifted out of sync with each
other; and the flat layout not matching the grouping we already had).

Fixed every cross-reference this moved across the whole repo (~120
files: docs/ internal links at every depth, Rust doc comments, nix
module option docs, crate READMEs) -- verified two ways: a grep sweep
confirming zero remaining references to any old path, and a script
that resolves every markdown link in docs/**/*.md + CLAUDE.md +
README.md against the filesystem and reports anything that doesn't
exist (zero broken links).

Collapsed CLAUDE.md's "Reading paths" section (the duplicate) down to
a pointer at docs/README.md, now the single index. Rewrote
docs/README.md itself to use the new subdirectory paths and added the
one doc it was missing that CLAUDE.md's old copy had (pr-review-gate.md).

Classified all 22 docs/*.md files first via a haiku subagent (mara's
suggestion) on two axes -- proposed grouping and operator-vs-
implementation focus -- before finalizing the taxonomy; spot-checked
the report and found internal inconsistencies (its classification
table disagreed with its own summary section for a few files), so this
taxonomy is my original proposal + the one correction mara gave
directly, not a blind application of the subagent's table. The
operator-focus data it gathered is still useful for a follow-up
content pass (docs skewing 'mixed' rather than pure operator-facing),
not addressed in this PR -- structure only.

nix fmt clean, both pre-push lints clean.
2026-09-02 01:55:37 +02:00
atlas
5aef2d1afc swarm: remove swarm.ui.domain, serve the UI on the swarm domain
The UI and the swarm-controller it fronts are one service to a reader and to a
certificate, so a separate option only ever created two spellings of one
address — and the controller's own public URL was built from one of them while
an allow-list entry named the other.

Removed rather than aliased. A config still setting it was pinning a name the
controller never used; an alias would preserve exactly that mismatch, so
`mkRemovedOptionModule` says so and fails the build.

⚠️ The option was TOTAL and `swarm.domain` is NULLABLE. That is not incidental:
`ui.domain` fell back to `swarm.invalid` so hive-network.nix's required-domain
assertion is what an operator sees, rather than a coercion error from a
consumer. Each site keeps that property its own way — swarm-ui binds a local
total (the idiom swarm-otel.nix already uses as `domainBase`), and the three
sites already gated on `deploy.swarm-ui.enable` gain `&& domain != null`, which
is stricter than a placeholder and puts no invented name into rendered config.

One behaviour change worth naming: `swarm.nix`'s certificate SAN list used to
receive the placeholder `swarm.invalid` on a null domain and now receives
nothing. `lib.optional` would otherwise have put a literal `null` in it.

Verified with `checks.module-eval` rather than `nix-instantiate --parse`: parse
proves syntax, and every failure this change could plausibly cause — a dead
binding, a renamed reference — is an evaluation error.
2026-08-31 19:00:52 +02:00
atlas
585269b8a3 deploy: rename swarm.enableRequiredServices to deploy.allSwarmServices
Both halves of the old name were wrong about the subject. The services
are required of the SWARM, not of the host, and the option says whether
THIS host runs them — so it described the wrong thing and sat in the
namespace that has to be identical on every host. The new name is mara's
own phrasing of what it means: "deploy all swarm level services on this
host".

mkRenamedOptionModule carries existing configs, read-side references
included, so this warns rather than failing to evaluate.

Three sites were not just the identifier:

- local-defaults.nix set it inside `config.services.hyperhive.swarm =
  { … }`. It moves out as a path beside the other deploy.* setter rather
  than into a second `deploy = { … }` attrset — the warning that file
  already carries about `swarm` applies to any second definition of the
  same parent.
- swarm-required-services.nix bound only `swarmCfg`, now unused; it binds
  and reads `deployCfg`.
- Two comments in that file described a half-migrated state, where the
  switch asserted some `swarm.*.enable` toggles and some `deploy.*` ones.
  Every one of them has been `deploy.*` for several slices now.
2026-08-30 20:12:16 +02:00
atlas
d3b40da1c8 deploy: give every option an enable, and name the controller one
Two corrections from review, applied forward on this branch rather than
by rewriting it.

`deploy.<service>` was a bare bool, which makes
`deploy.forgejo = { enable; ci; }` unrepresentable -- the nested
CI-runner sub-option this namespace was designed around. Every entry is
now an attrset with an `enable`, so a second per-host deployment
decision becomes an ordinary addition rather than a migration.

`deploy.controller` is now `deploy.swarm-controller`, consistent with
`deploy.swarm-ui`, which was introduced in the same commit.

89 references rewritten across 24 files -- nix, Rust, docs, and the
repo's own CLAUDE.md.

The prefix-anchored sweep missed exactly one, and it was live code:
hive-tls.nix spells it `hyperhiveCfg.deploy.controller` -- the only
`hyperhiveCfg` prefix among 45 references. A suffix grep
(`\.deploy\.<name>`) finds it; a path-anchored one cannot, because the
head of a reference is whatever alias the reading file happens to bind.
2026-08-30 04:23:22 +02:00
atlas
0b7357d4b8 deploy: move the controller and swarm-ui toggles
One commit rather than two because they are not independent: the UI's
`enable` had the controller's as its literal default, so moving the
controller alone would leave the UI's default naming an option that no
longer exists.

The UI keeps that derivation in its new home — it is a view onto the
controller's state and reaches it over that daemon's unix socket, so the
host running the controller is the host that can serve it.

Three spellings had to move together for the UI, not one: the `default`,
the `defaultText` shown in the options doc, and the description prose
that names the old path in words. A grep for the option path finds the
first two.

The sweep also reached outside nix: `swarm-controller`'s crate README and
its `//!` module doc both named the option, as did this repo's own
CLAUDE.md and four pages under docs/. An option's name is API, and its
documentation lives wherever someone thought to write it down.
2026-08-30 04:23:22 +02:00
atlas
6ca4887af4 docs(#3422): the user store is one file, not two
Six places asserted the old design as fact, and none of them mention the
change by name -- the class of doc breakage that is found by asking what
a diff made untrue, not by grepping for a feature:

- swarmctl/README.md and swarm-authelia-bridge/README.md both described
  their own private canonical store. The bridge's "known limitation"
  section described the seam as unsolved; it is what this fixes, so it
  becomes what both writers must uphold instead.
- docs/swarm/{sso,ui,secrets}.md described a rendered artifact.
- The repo CLAUDE.md entry for swarmctl said the same.
- docs/tools/swarmctl-cli.md is regenerated (CI diffs it against the
  clap tree), picking up the removed --store flag.

Operator-facing where it is read: the hand-editing consequence (values
survive a rewrite, comments do not) is stated in sso.md, where an
operator is being told to edit the file, rather than only in a module doc.
2026-08-18 10:34:00 +02:00
iris
8542f2ca42 swarm-ui: header links menu for swarm-wide services (hyperhive#3289)
Single 🔗 button in the Shell header (direction 1 of 3 proposed on the
issue) — one fixed element regardless of link count, so it can't
clutter as the list grows, and visible on every route since Shell
wraps all of them. Opens a popover listing whatever GET /api/links
returns (icon + label, opens in a new tab); hidden entirely when the
list is empty, same "don't render a dead affordance" rule the old
dashboard's H0M3 tiles follow for Forge/Matrix.

Docs: docs/swarm/ui.md gets a short section on the feature + the
contribute-your-own-entry idiom.

Verified: npm run build + npm run typecheck clean, headless-chromium
screenshots of both the closed and (temporarily forced open for the
screenshot only) open states, pixel-sampled to confirm the popover is
actually using the dark theme vars and not just looking that way in a
downscaled preview.
2026-08-15 14:24:52 +02:00
iris
3b24d08226 docs(swarm): fix stale four-wiring-sites table, verified against hive-gateway modules 2026-08-15 12:45:22 +02:00
atlas
4dd5093c6f docs(3167): the setup guide gains a swarm UI step, and one group name
Per mara on the PR: the guide someone follows on a fresh deploy is a
different page from the one they read while debugging, and only the
second existed.

Also fixes a contradiction the request surfaced: setup.md and
swarm/sso.md have told every operator to create '--group admins' since
the bootstrap step existed, while the new rule required 'operators'.
This is the first rule that CONSUMES a group name, so it takes the one
the guide already creates - inventing a second would have meant every
account made by following the guide silently failing the check it was
supposed to pass, and for mara specifically a migration step that now
may not be needed at all.

setup.md's step 3 says why --group is not decoration; the new step 4
says what decides whether the page opens (the group, and the name
resolving) rather than listing a command, because there is nothing to
run. Steps renumbered, including the matrix block's internal 4a-4e.
2026-08-12 17:57:52 +02:00
atlas
470d2ad845 docs(3167): the swarm UI page, and the group step that gates it
New docs/swarm/ui.md (split-page shape, per the docs rule), linked from
the swarm README and added to the gateway's vhost map.

Leads with the step that separates 'protected' from 'locked out':
swarmctl user add <you> --group operators. auth_request asks whether
there is a session; the access_control rule is what makes it mean
operator, and an account created before the rule existed has no groups.

Also records the four wiring sites a swarm service name needs, with the
certificate one called out - serviceDomains is both the sub-CA's
nameConstraints set and the leaf's SANs, and the apex is a sibling of
the other three rather than a parent, so nothing issues for it
implicitly.
2026-08-12 17:46:15 +02:00