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.
This commit is contained in:
iris 2026-09-02 01:47:05 +02:00 committed by mara
commit 07b62612b0
124 changed files with 301 additions and 377 deletions

View file

@ -215,7 +215,7 @@ in
# Operators granted sudoless `hivectl`. Members of `hive-admin` can
# connect to the host admin socket (group-owned by hive-admin via the
# socket unit's `SocketGroup` below) without root. That socket is *full*
# hive control (spawn/kill/destroy/deploy, docs/boundary.md), so this is
# hive control (spawn/kill/destroy/deploy, docs/trust-boundary/boundary.md), so this is
# an explicit opt-in allowlist — empty by default (root-only).
users.groups.hive-admin = {
members = cfg.adminUsers;
@ -223,7 +223,7 @@ in
# The gateway nginx is always the sole external entry point (it runs
# alongside hyperhive), so the per-agent web-port range stays closed on
# the host firewall. See `docs/gateway.md::Firewall posture (host-level)`.
# the host firewall. See `docs/networking/gateway.md::Firewall posture (host-level)`.
# NB: `services.hyperhive.domain` is required when hyperhive is
# enabled — the canonical assertion lives in `hive-network.nix` (the
@ -368,7 +368,7 @@ in
# `0660 root:hive-admin` — group-owned by `hive-admin` so operators in
# that group (services.hyperhive.c0re.adminUsers) drive `hivectl`
# without root; an empty adminUsers list leaves the group memberless,
# so it stays effectively root-only. See docs/boundary.md.
# so it stays effectively root-only. See docs/trust-boundary/boundary.md.
SocketMode = "0660";
SocketGroup = "hive-admin";
# `0751` (traverse-only, no listing) so hive-admin can reach the socket

View file

@ -57,7 +57,7 @@ in
// {
# Identity env vars threaded into c0re's own service env and
# forwarded by meta.rs into every sub-agent's harness env —
# full chain in docs/conventions.md::Hive identity. `domain` is
# full chain in docs/process/conventions.md::Hive identity. `domain` is
# required (asserted in hive-network.nix), so it's always set.
HYPERHIVE_HIVE_DOMAIN = config.services.hyperhive.domain;
}
@ -141,7 +141,7 @@ in
# hive-c0re (forge/mod.rs `forge_http_base()`) and forwarded to
# agents via meta.rs for their forge-notify client. The forge is
# mandatory, so this is unconditional (the whole env block is already
# gated on hyperhive being enabled). See `docs/gateway.md::HIVE_FORGE_URL`.
# gated on hyperhive being enabled). See `docs/networking/gateway.md::HIVE_FORGE_URL`.
HIVE_FORGE_URL = "http://${config.services.hyperhive.swarm.forge.domain}";
# The one blacklist of names an agent may not take — see
@ -197,7 +197,7 @@ in
# Availability flags read by the dashboard's `/api/state`.
# Matrix GUI lives entirely on the gateway nginx (matrix tab
# only shows when both flags are on). Gateway routing detail:
# docs/gateway.md::Vhost map.
# docs/networking/gateway.md::Vhost map.
HIVE_MATRIX_GUI_ENABLED = "1";
}
// {

View file

@ -427,7 +427,7 @@
`hivectl` (and thus the whole hive) without `sudo`.
This is a real privilege grant: the admin socket is *full* hive
control spawn / kill / destroy / deploy see `docs/boundary.md`.
control spawn / kill / destroy / deploy see `docs/trust-boundary/boundary.md`.
Keep the list to trusted operators. Empty (the default) keeps the
socket root-only, as before.
'';

View file

@ -58,7 +58,7 @@ in
# gateway at `http://${forgeCfg.domain}` (resolved to the bridge IP
# via `networking.extraHosts`; gateway port 80 is always open on the
# bridge; `addSSL = true` means HTTP is served alongside HTTPS without
# a redirect). See docs/network.md.
# a redirect). See docs/networking/network.md.
# Container is non-ephemeral: the runner's registered credentials
# survive restarts (gitea-actions-runner writes them to its stateDir
# on first registration and reuses them on every subsequent start).
@ -74,7 +74,7 @@ in
# Nix builds inside the container use the shared /nix/store (standard
# nixos-container behaviour) with sandbox-fallback = true, because
# nspawn containers can't create the user-namespaces that nix sandboxing
# requires. See docs/gotchas.md.
# requires. See docs/process/gotchas.md.
options.services.hyperhive.deploy.forgejo.ci = {
enable = lib.mkOption {
@ -218,7 +218,7 @@ in
# runner reaches the forge via the gateway — and cannot reach
# host-loopback (127.0.0.1:7000 dashboard, raw forge port, etc.).
# Requires `forge.behindGateway = true` (asserted in the options
# block above). See docs/network.md.
# block above). See docs/networking/network.md.
privateNetwork = true;
hostBridge = networkCfg.bridgeName;
@ -285,7 +285,7 @@ in
# sandboxing always fails. Fall back to unsandboxed builds.
# Moot once every nix invocation in the container routes
# through the host daemon (the daemon governs sandboxing).
# See docs/gotchas.md and nix/agent-modules/default.nix.
# See docs/process/gotchas.md and nix/agent-modules/default.nix.
nix.settings.sandbox-fallback = lib.mkForce true;
# Degrade to a local build when a remote builder is unreachable
# rather than failing the check. `fallback` is a client-side

View file

@ -107,7 +107,7 @@ in
# Private Forgejo in a `hive-forge` nixos-container, shared host
# netns. Agents reach it at `forge.<domain>` via the gateway. State
# at `/var/lib/nixos-containers/hive-forge/var/lib/forgejo/` survives
# restart. See `docs/gateway.md::hive-forge container shape`.
# restart. See `docs/networking/gateway.md::hive-forge container shape`.
# External Forgejo/Gitea/Codeberg-compatible forges (beyond the mandatory
# internal one) are entirely dashboard-provisioned — no nix config here.
@ -179,7 +179,7 @@ in
Public hostname for the forge. Doubles as both the forgejo
`DOMAIN` setting (clone URLs forgejo advertises) AND the
gateway vhost server-name when `behindGateway = true`
(sub-domain routing see `docs/gateway.md`).
(sub-domain routing see `docs/networking/gateway.md`).
Defaults to `forge.''${services.hyperhive.swarm.domain}` the
swarm's domain, not this hive's, because a swarm runs **one**
@ -251,7 +251,7 @@ in
description = ''
Serve forgejo through the hive-gateway nginx as a sub-domain
vhost (`server_name = cfg.domain`) instead of directly on
`httpPort` (sub-domain routing see `docs/gateway.md`).
`httpPort` (sub-domain routing see `docs/networking/gateway.md`).
When `true`:
- The gateway adds a `server { server_name = ''${cfg.domain}; }`
@ -473,7 +473,7 @@ in
# `server_name = forge.domain`, proxies all `/` → forgejo. Tuned for
# git: `client_max_body_size 1G`, `proxy_read_timeout 1h` (multi-GB
# clones). SSH stays direct on `forge.sshPort`. See
# `docs/gateway.md`.
# `docs/networking/gateway.md`.
services.nginx.virtualHosts = lib.optionalAttrs cfg.behindGateway {
"${cfg.domain}" = (gatewayCfg.lib.tlsFor cfg.domain) // {
listen = gatewayCfg.lib.listen;

View file

@ -5,7 +5,7 @@
# answers on the hive bridge, so neither can be confined to a network
# namespace of its own.
# Full vhost map + discovery flow + design rationale in
# `docs/gateway.md`. Layout: ./options.nix (option declarations),
# `docs/networking/gateway.md`. Layout: ./options.nix (option declarations),
# ./vhosts.nix (the nginx virtual-host tree), ./error-pages.nix
# (styled static pages), ./dnsmasq.nix (resolver + DHCP config).
{
@ -313,7 +313,7 @@ in
# from an interrupted oneshot, etc.). The leaf covers the bare
# hive domain plus `forge.`, `matrix.` and `*.${hyperhiveDomain}`
# so all sub-domains validate under the same cert + the hive CA.
# See `docs/gateway.md` ("Self-signed TLS").
# See `docs/networking/gateway.md` ("Self-signed TLS").
systemd.services.hive-gateway-self-signed-cert = lib.mkIf useSelfSigned {
description = "Import host-generated TLS leaf for hive-gateway";
wantedBy = [ "multi-user.target" ];
@ -444,7 +444,7 @@ in
};
# `/etc/hosts` entries for local dev — the bare hive domain plus
# every name a service module contributed. See `docs/gateway.md`
# every name a service module contributed. See `docs/networking/gateway.md`
# ("Local dev").
#
# This used to restate the per-service list a THIRD time (after the

View file

@ -1,7 +1,7 @@
# Static error/help pages the gateway serves for routes it has
# special-cased, all rendered from one Catppuccin-styled template.
# Useful pages instead of nginx's default 404/502 — see
# `docs/gateway.md::Per-agent error pages` for the design rationale +
# `docs/networking/gateway.md::Per-agent error pages` for the design rationale +
# page-vs-status semantics. Consumed by ./vhosts.nix.
{ pkgs }:
let

View file

@ -53,7 +53,7 @@ in
# The gateway always terminates TLS: self-signed is the implicit
# floor when neither `tls.certDir` nor ACME is set, so there is no
# http-only mode. Listen addresses every vhost shares — plain http
# on `cfg.port` plus TLS on `cfg.httpsPort`. See `docs/gateway.md`
# on `cfg.port` plus TLS on `cfg.httpsPort`. See `docs/networking/gateway.md`
# ("TLS modes").
listen = [
{
@ -92,7 +92,7 @@ in
# — invisibly, it works and keeps working against any certificate at
# all. Full reasoning for every directive here (plus two real
# footguns — session-cache keying, and a `Host`-header clobber that
# can recurse a subrequest into itself) is in docs/gateway.md's
# can recurse a subrequest into itself) is in docs/networking/gateway.md's
# "Dialing another vhost by name" section — read it before touching
# this.
verifiedProxyTo = name: ''

View file

@ -36,13 +36,13 @@ let
# mints into responses (well-known JSON, the deprecated
# `<hive>/matrix/*` 301 redirect, future absolute-URL needs):
# always `https://<host>` (matrix-spec compliance) — the canonical
# 443 elides the port. See `docs/gateway.md` ("Self-signed TLS").
# 443 elides the port. See `docs/networking/gateway.md` ("Self-signed TLS").
publicScheme = "https";
publicPort = cfg.httpsPort;
publicPortSuffix = if publicPort == 443 then "" else ":${toString publicPort}";
# `<hive>/matrix/*` → 301 → `matrix.<hive>/$1` (legacy deep-link
# shim during the fluffychat sub-domain move). See `docs/gateway.md`.
# shim during the fluffychat sub-domain move). See `docs/networking/gateway.md`.
matrixRedirectLocations =
lib.optionalAttrs (matrixCfg.enable && matrixCfg.gui.enable && matrixCfg.gatewayHost != null)
(
@ -61,7 +61,7 @@ let
# `.well-known/matrix/{client,server}` discovery JSON. Points
# clients at `matrixCfg.gatewayHost` when set; falls back to direct
# `<hive>:<httpPort>`. CORS `*` per matrix spec. The `m.server`
# port-8448 carve-out is documented inline. See `docs/gateway.md`.
# port-8448 carve-out is documented inline. See `docs/networking/gateway.md`.
wellKnownLocations = lib.optionalAttrs matrixCfg.enable (
let
clientBaseUrl =
@ -77,7 +77,7 @@ let
# (which drops :443) is right for the client base_url above but
# wrong for federation delegation. Without this, peers federate to
# <gatewayHost>:8448 (closed) while the endpoint actually lives on
# the gateway's 443 vhost. See docs/gateway.md discovery flow.
# the gateway's 443 vhost. See docs/networking/gateway.md discovery flow.
serverHostPort =
if matrixCfg.gatewayHost != null then
"${matrixCfg.gatewayHost}:${toString publicPort}"

View file

@ -84,7 +84,7 @@ let
# skips the dart web-worker compile + the emscripten native_imaging
# build. Two derivations below cover both. Full rationale (why
# passthru.pubspecLock.dependencySources, why `dontConfigure`, why
# `make -C js`, why build-CWD-relative dart path): docs/matrix.md::
# `make -C js`, why build-CWD-relative dart path): docs/integrations/matrix.md::
# fluffychat-web build fixes.
fluffychat-web-imaging = pkgs.stdenv.mkDerivation {
@ -153,8 +153,8 @@ in
# optional fluffychat-web client at matrix.<hive>/. Container shape,
# serverName vs gatewayHost split, provisioning flow (registration
# token + LoadCredential), assertion rationale, initial rollout
# settings: docs/matrix.md. Vhost map + discovery flow + tuning
# knobs: docs/gateway.md.
# settings: docs/integrations/matrix.md. Vhost map + discovery flow + tuning
# knobs: docs/networking/gateway.md.
# Matrix moved under `swarm` when the swarm-global services were
# consolidated. One rename for the namespace: the subtree comes with it,
@ -291,7 +291,7 @@ in
Defaults to `chat.''${services.hyperhive.swarm.domain}` the
swarm's domain, because a swarm runs **one** homeserver. Set to
`null` to skip the gateway vhost (tuwunel stays direct on
`httpPort`). See `docs/gateway.md` for the vhost map + matrix
`httpPort`). See `docs/networking/gateway.md` for the vhost map + matrix
discovery flow, and the federation port-8448 caveat at the
bottom of that doc.
@ -410,7 +410,7 @@ in
Requires `matrix.gatewayHost != null` (default `matrix.<hive>`
when hive-domain set); the gateway itself always runs. When
off, the dashboard's `M4TR1X ` tab is hidden. See
`docs/gateway.md` for the discovery flow that lets clients
`docs/networking/gateway.md` for the discovery flow that lets clients
auto-find the sub-domain.
'';
};
@ -528,7 +528,7 @@ in
};
# Accept-header SPA map, used only by the `/` location below (see
# docs/gateway.md "SPA fallback"): text/html → index.html, else a
# docs/networking/gateway.md "SPA fallback"): text/html → index.html, else a
# sentinel so `try_files` falls through to 404. `appendHttpConfig`
# is a `lines` option, so this merges with anything else the host
# contributes instead of replacing it.
@ -601,7 +601,7 @@ in
# hive-network.nix) when not set explicitly, so no separate
# domain/serverName assertion is needed here. gatewayHost may not be
# "" (same footgun as forge.domain — nginx rejects an empty
# server_name). docs/matrix.md::Assertion rationale.
# server_name). docs/integrations/matrix.md::Assertion rationale.
assertions = [
{
assertion = cfg.gatewayHost == null || cfg.gatewayHost != "";
@ -764,7 +764,7 @@ in
# Activation-time token generation — without this the bind-mount
# would hand tuwunel an empty file on first boot and break every
# registration until restart. Idempotent;
# docs/matrix.md::Provisioning flow.
# docs/integrations/matrix.md::Provisioning flow.
# Tell an operator whose homeserver already exists that `serverName` is
# unpinned, at the one moment they are looking: the rebuild.
#
@ -895,7 +895,7 @@ in
# `environment.etc."resolv.conf".text` is `nameserver <bridgeIp>`.
# This container always shares the host netns
# (`privateNetwork = false`), so it reaches `bridgeIp` regardless
# of agent-container isolation. See `docs/network.md`.
# of agent-container isolation. See `docs/networking/network.md`.
networking = {
# resolvconf is taken out of the loop entirely; the static
# `environment.etc."resolv.conf"` below is the sole source of

View file

@ -31,7 +31,7 @@ in
{
# Hive-internal network — host-side bridge + per-agent DNS resolver.
# Always active when hyperhive is enabled: agent containers run in
# private netns behind the bridge. Full design: docs/network.md.
# private netns behind the bridge. Full design: docs/networking/network.md.
imports = [
(lib.mkRemovedOptionModule [ "services" "hyperhive" "network" "enable" ] ''
@ -234,7 +234,7 @@ in
})
# Container isolation overlay — now unconditional (the shared-netns
# mode was removed). See docs/network.md#container-isolation.
# mode was removed). See docs/networking/network.md#container-isolation.
(lib.mkIf config.services.hyperhive.enable {
# Agents route internet traffic via the bridge; NAT masquerades their RFC-1918 IPs.

View file

@ -1,5 +1,5 @@
# hive-priv — the narrow root privileged helper hive-c0re delegates
# to, socket-activated at /run/hive/priv.sock. See docs/boundary.md
# to, socket-activated at /run/hive/priv.sock. See docs/trust-boundary/boundary.md
# for the operator/agent trust-boundary design.
{
pkgs,

View file

@ -276,7 +276,7 @@ in
# So the issuer is a long-lived **hive CA** held on the host. The
# gateway serves a **leaf** signed by that CA (via the `tls.certDir`
# bind-mount path); agents and federation peers trust the *CA* once,
# and leaf rotation never re-breaks them. See `docs/gateway.md`
# and leaf rotation never re-breaks them. See `docs/networking/gateway.md`
# ("Self-signed TLS").
#
# That CA is self-signed by default. Under

View file

@ -33,7 +33,7 @@ in
# matrix bakes it in on first boot and the gateway/forge/agent URLs all
# derive from it (no safe default). Full identity-surface
# context (HYPERHIVE_HIVE_DOMAIN / HIVE_NAME / SWARM_NAME env-var
# chain → identity.rs → claude prompt): docs/conventions.md::
# chain → identity.rs → claude prompt): docs/process/conventions.md::
# Hive identity (label + domain + display names).
options.services.hyperhive.domain = lib.mkOption {
type = lib.types.nullOr lib.types.str;
@ -133,7 +133,7 @@ in
};
# Human display name for this hive. Distinct from the DNS domain
# above (machine-readable) — see docs/conventions.md::Hive identity
# above (machine-readable) — see docs/process/conventions.md::Hive identity
# for the domain-vs-name-vs-swarm distinction + the env-var
# propagation chain. The swarm's display name is
# `services.hyperhive.swarm.name`, one level out: this hive is named

View file

@ -42,7 +42,7 @@ let
# `swarmctl user add --group <this>` is what grants it — the two must
# agree, and one constant is how they stay agreeing.
#
# ⚠️ `admins` and not a new word, because `docs/setup.md` and
# ⚠️ `admins` and not a new word, because `docs/getting-started/setup.md` and
# `docs/swarm/sso.md` have been telling every operator to create
# exactly that group since the bootstrap step existed. This is the
# first rule that CONSUMES a group name; picking a different one would

View file

@ -16,7 +16,7 @@
# Confinement is a property of the DEPLOYMENT, not of this unit: in a
# real swarm the store is its own small VM (the machine is the
# boundary); in the all-local case it's a container on the c0re host.
# The module hardcodes neither. docs/snapshot-store.md covers what the
# The module hardcodes neither. docs/networking/snapshot-store.md covers what the
# deployment is expected to provide.
{
pkgs,
@ -201,7 +201,7 @@ in
# nature. The hardening below is defence in depth and NOT a
# boundary: a process holding CAP_SYS_ADMIN can call mount(2) and
# undo the namespace these directives set up. The real boundary is
# the deployment --- see docs/snapshot-store.md.
# the deployment --- see docs/networking/snapshot-store.md.
systemd.services."swarm-snapshot-store@" = {
description = "hyperhive swarm snapshot store receiver";
after = [ "swarm-snapshot-store.socket" ];