Commit graph hyperhive/docs
Author SHA1 Message Date
atlas
f0e3ed04d3 hive-forge, hivectl, swarmctl: fix clap help passive voice, regen docs
Rewrites every write-good.Passive hit in the hive-forge clap help text
into terse, imperative, active voice (meaning unchanged) and drops
clap-markdown's own fixed footer ('This document was generated
automatically by...') via MarkdownOptions::show_footer(false), since
that string isn't ours to reword and vale flagged it too.

docs/tools/{hivectl,swarmctl,forge}-cli.md are generated from each
crate's clap tree (see hive-forge/src/main.rs's MarkdownDocs verb) —
regenerated here from the fixed source, not hand-edited.

Refs #4549
2026-09-20 13:49:39 +02:00
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
atlas
f304fd3069 docs/process: keep 'automatically' when fixing the auto-deployed hyphen 2026-09-20 05:36:49 +02:00
atlas
729c5b4f42 hive-agent-mcp, hive-c0re, hive-sh4re: drop agent param from get_loose_ends
get_loose_ends now always returns the caller's own loose ends, for every
caller including the manager (ruth) — there is no separate manager
surface, ruth is a normal agent with different default capabilities.

- AgentGetLooseEndsArgs removed; get_loose_ends takes no args.
- Wire Request::GetLooseEnds collapses from an Option<String> target to
  a unit variant.
- hive-c0re's handle_get_loose_ends drops the "*" hive-wide branch and
  the subtree/capability resolver (resolve_agent_state_target); both
  are gone since there is no longer a target to resolve.
- loose_ends::hive_wide and Capability::QueryAgentState removed as
  dead code — their only callers were the two functions above.
- is_descendant_of is untouched (still used by lifecycle_handlers.rs
  and schedules.rs independently of this change).
- Docs updated: docs/turn-loop/mcp.md, docs/web-ui/dashboard.md,
  docs/process/conventions.md (Loose-ends wire shape + capabilities
  table), plus the doc comments in hive-core-agent-sock, mcp_config.rs
  and capabilities.rs that described the old shape.

Refs #4480
2026-09-20 05:36:49 +02:00
iris
67ffb124cb docs: clear write-good.Passive hits in docs/tools/matrix.md
6 genuine passive-voice rewrites (actor named — the operator,
hive-c0re, the daemon; two converted to active/elliptical-imperative
phrasing matching the bullet list's own established voice); 2 false
positives left untouched — 'been invited' (an agent-perspective
description of its own pending invites, no single inviter is the
point) and 'is keyed' (a structural/type-description idiom, same
category as coordinator.md's 'is typed').
2026-09-20 05:16:17 +02:00
iris
3ae8b081e0 docs: clear write-good.Passive hits in docs/trust-boundary/boundary.md
6 genuine passive-voice rewrites (actor named — the core daemon,
hyperhive, the adminUsers option; one flipped subject to match the
table it introduces; two converted to active/imperative phrasing);
2 false positives left untouched — the already-established 'is
tracked as [area:ops] issues' pattern, and a design-intent
contrastive idiom ('ownership is declared, not repaired') matching
the unflagged sibling phrase 'a DAG is declared, not described' in
coordinator.md.
2026-09-20 05:11:37 +02:00
iris
f047e509a7 docs: clear write-good.Passive hits in docs/scheduler/coordinator.md
5 genuine passive-voice rewrites (actor named — nixos-container
create; the passive construction replaced with an active
intransitive verb or 'nothing can X' phrasing matching the pattern
already established on credentials.md); 4 false positives left
untouched — a type-description idiom ('is typed'), a predicate-
adjective in a parallel triple ('is satisfied' alongside two
untouched siblings), and two already-established categories
('is unaffected', 'is tracked' as a follow-up).
2026-09-20 05:10:59 +02:00
iris
f6756de925 docs: clear write-good.Passive hits in docs/getting-started/setup.md
5 genuine passive-voice rewrites (actor named — pkcs11, the operator,
the minting policy; one converted to a runbook imperative; one
converted to an active 'gate on' verb); 5 false positives left
untouched — predicate-adjective necessity/security-property
descriptions ('is needed', 'is stored', 'is authenticated', same
category as the already-established 'is required'), a
project-tracking-state description ('is tracked'), and a topology
claim with no single correct actor to name ('is generated where it's
read' — which component mints a given credential varies per row in
the credentials table).
2026-09-20 05:10:26 +02:00
iris
63830e57b7 docs: clear write-good.Passive hits in docs/scheduler/observability.md
4 genuine passive-voice rewrites (actor named, or the passive
construction replaced with an active imperative); 7 false positives
left untouched — predicate-adjective state/necessity descriptions
('is unchanged'/'is needed'/'is enabled' x2/'is unauthenticated'/
'is unaffected', same category as the already-established 'is
required') and one quoted-literal example string ('this hive is
gone').
2026-09-20 04:40:43 +02:00
iris
04cac80c97 docs: clear write-good.Passive hits in docs/process/gotchas.md
7 genuine passive-voice rewrites (actor named, or the passive
construction replaced with a natural active verb/imperative); 5 false
positives left untouched — a temporal 'when it was written' reference,
two predicate-adjective necessity descriptions ('is needed'/'is
expected', same category as 'is required'), one predicate-adjective
state description ('are gone'), and one quoted literal + predicate
adjective ('these inputs are unused by rust').
2026-09-20 04:28:16 +02:00
iris
69b70a9c6f docs: clear write-good.Passive hits in docs/swarm/credentials.md
6 genuine passive-voice rewrites (actor named or replaced with an
active construction); 7 false positives left untouched — the
design-intent 'is meant to have' idiom, and the six identical
'must be stated' renewal-column table cells (a deliberate terse
placeholder directive, not a describable action with an actor).
2026-09-20 04:22:01 +02:00
iris
cb504f6863 docs: clear write-good.Passive hits in gateway/subagent/dashboard docs
Part of #4548 — rewriting the 353 hand-written-docs write-good.Passive
hits so #4546 can flip the rule to error. This batch: docs/networking/gateway.md
(27 hits), docs/tools/subagent.md (19), docs/web-ui/dashboard.md (18).

Minimal, meaning-preserving rewrites to active voice only — no
restructuring beyond what each flagged sentence needed. Genuine false
positives (predicate-adjective state descriptions like "is required",
"is broken", "is unchanged", adjectival past participles like "named
buckets", and one quoted literal UI string) are left in place; the
per-line reasoning is on the tracking issue.

Note: docs/tools/forge-cli.md (37 hits, would have been the single
biggest file) turned out to be generated from hive-forge's own clap
help text (see hive-forge/src/main.rs's `MarkdownDocs` verb) — same
category as the already-excluded hivectl-cli.md/swarmctl-cli.md, so
it's untouched here. Flagged on #4548.
2026-09-20 04:13:02 +02:00
iris
9fac33a026 docs/swarm/secrets.md: fix sense-mis-attribution in certificateFiles rewrite
argus caught it on PR #4557 review: 'certificateFiles reads at system
build time' hands the option itself a verb it can't perform. The
system reads the option's value at build time, not the other way
around.
2026-09-20 04:10:55 +02:00
iris
c985702bb0 docs: clear write-good.Passive hits in swarm secrets/README docs
Part of #4548 — third batch. This PR: docs/swarm/secrets.md (16 hits),
docs/swarm/README.md (15 hits) = 31 more of the 353 (130 total across
the three PRs so far).

Same method as #4551/#4553: minimal, meaning-preserving active-voice
rewrites, no restructuring beyond what each flagged sentence needed,
no vale-off silencing.

Genuine false positives left in place: predicate-adjective state
descriptions ("is required" x4, "is enabled", "is disabled"), one
systemd terminology reference ("is wanted (not required)" — literally
naming systemd's Wants=/Requires= dependency-type distinction, not
passive voice), and one design-intent idiom ("both are expected" —
same shape as earlier docs-lint work).
2026-09-20 04:10:55 +02:00
iris
18c5b5c5ad docs: clear write-good.Passive hits in matrix/persistence docs
Part of #4548 — second batch. This PR: docs/integrations/matrix.md
(18 hits), docs/agent-lifecycle/persistence.md (17 hits) = 35 more of
the 353 (99 total across the two PRs so far).

Same method as the first batch (#4551): minimal, meaning-preserving
active-voice rewrites only, no restructuring beyond what each flagged
sentence needed, no vale-off silencing.

Genuine false positives left in place, with per-file reasoning in the
PR body: predicate-adjective state descriptions ("is wedged", "is
stopped", "are unrelated", "is unaffected", "is needed"), one
design-intent idiom ("was meant to prevent" — same shape as earlier
docs-lint work), and one vale parsing artifact: "is read" flagged
inside the hyphenated compound adjective "read-only", not real passive
voice at all.
2026-09-20 04:10:41 +02:00
atlas
15424d3e49 hive-forge: move pr create --remote's origin default onto the clap arg
Puts the default on #[arg(long, default_value = "origin")] instead of
duplicating unwrap_or("origin") at both call sites (REST/--push path
and agit_create). clap now renders the default in --help itself, so
the hand-written doc-comment default is dropped in favor of it.
Regenerated docs/tools/forge-cli.md to match.
2026-09-19 20:33:48 +02:00
atlas
18f0199566 hive-forge: default pr create --push remote to origin
Fresh clones only have origin, not forge. The --remote override
already covers anyone using a different name.

Closes #4538
2026-09-19 18:16:55 +02:00
atlas
a358889384 docs(gateway): describe nginx as per-host, not a deployment-wide singleton
Fix wording flagged on #4521 review: "Single nginx in front of every
hyperhive web surface" and "Runs on the host" claimed a deployment-wide
topology. There's one nginx per host that has something on it, and
potentially more inside service containers. Reword to describe what
this module builds on this host, with no count claim swarm-wide.
2026-09-19 13:58:10 +02:00
atlas
d8e26a17bb nix: give the gateway, resolver and bridge their own enable
`services.hyperhive.gateway.enable`, `gateway.dns.enable` and
`network.enable` replace the `hyperhive.enable` gate on all three. Each
defaults to false; the modules that need one assert it with `mkDefault
true` from inside the guard their own deployment already carries, and
`swarm-required-services.nix` — the module that owns what the
swarm-services toggle implies — asserts all three explicitly.

hive-c0re asserts all three unconditionally, so an ordinary hive keeps
getting them with no opt-in: it is the host's only knowledge that agent
containers exist.

The resolver moves to its own `hive-gateway/dns.nix` so it can be gated
without reindenting the nginx half of the module.

Reinstates `network.enable`, dropping its `mkRemovedOptionModule` shim.
A config still carrying `network.enable = false` from before the removal
now switches the bridge off instead of failing eval.

Also deletes a duplicate `centralToggleOff` fixture in nix/module-eval.nix.
Two sibling slices added it independently (c5f60fd5, ce3b3d94); the merge
was textually clean and left `main` failing to evaluate at all, so this
file could not be gated without removing one.
2026-09-19 13:53:10 +02:00
atlas
87970a8c93 mcp: remove the restart/kill/start/update/get_logs agent verbs
Container lifecycle from inside an agent goes away: an agent no longer
starts, stops, restarts or rebuilds a container in its subtree, and no
longer reads another container's journal. Those are operator actions —
the dashboard and hivectl keep their own paths to the same job-queue
and hive-priv plumbing, which is why none of that machinery is removed
here, only the five MCP verbs and what they alone reached.

What went with them: the `Request` variants and `Response::Logs` on the
agent socket, the five tool definitions and their arg structs, the four
lifecycle handlers plus `handle_get_logs`, and `require_descendant` —
the topology guard those five were the only remaining callers of.
`ToolGroup::Diagnostics` goes too: `get_logs` was its only tool, so it
would otherwise be a grantable group that grants nothing. `lifecycle`
stays, now carrying `list_containers` alone.

An agent that gets a `needs_update` or `container_crash` helper event
has no remedy of its own left, so the system prompt and the docs now
send it to the operator instead of to a tool that no longer exists.

Refs #4480
2026-09-19 10:47:39 +02:00
atlas
837e658d4a swarm: courier an agent's store identity into its container, and log in with it
`swarm-controller` mints an agent's mTLS leaf at creation and publishes it
at `swarm/agents/<agent>/bao-mtls`. Nothing read it back. This adds the
hop that carries it the rest of the way, and the in-container consumer
that proves the hop works.

Host side, `lifecycle::agent_identity` reads the row under *this hive's*
own certificate — the hive is a principal the store already knows — and
stages the leaf and its key `0600` under a new `agent-identity/<name>`
state dir, deliberately outside every bind-mounted tree. Both files go in
as systemd credentials rather than binds, the same answer and the same
mode reason as the queue secret beside it: the staged key is unreadable
to the unprivileged agent user, and the container manager reads a
`--load-credential` source as root before re-exposing it under the
consuming unit's own `User=`. The agent is never asked to authenticate in
order to obtain the thing it authenticates with.

Container side, `hive-agent-bao-identity.service` logs in with that
certificate and reads the agent's own path back, failing the unit when
either step does not succeed. It fails loudly where the hive-side readers
degrade quietly, because a refused certificate means an agent that
believes it reaches the store and never does — a cause only the login
itself can name.

The address is the whole switch, no separate `enable`, matching how
`queue.nix` and `logs.nix` already gate themselves. A hive with a store
forwards `HIVE_AGENT_BAO_ADDR` and every agent on it gets the check; a
hive without one forwards nothing and no agent does. That is what keeps
the delivery from landing in a container with nothing to read it.

The hive can now reach an agent's identity, so hive privilege covers
agent privilege. Accepted, not mitigated: the alternative is an agent
fetching its own credential with a credential it does not yet have.

Refs #4137
2026-09-19 01:55:31 +02:00
atlas
4a1b5f5d51 docs(forge): drop the flat hidden-verb section from the verb reference
pr-create/pr-status/etc no longer parse -- clap deleted them along
with the renamed() helper. The remaining flat verbs (view, comment,
close, ...) are #[command(hide = true)] in clap; documenting a
deliberately hidden set as a group defeats the point of hiding it.
The examples further down that demonstrate individual hidden verbs
are unaffected.

Refs #4509
2026-09-18 22:43:35 +02:00
atlas
273b2895a1 docs: reword two vale-flagged 'is not' phrasings in credentials.md
Microsoft.Contractions wants 'isn't' over 'is not'; reworded the
mTLS-leaf-exception sentence to match without changing its meaning.

Refs #4137
2026-09-18 15:17:58 +02:00
atlas
676c45bc93 swarm: mint, publish and login-verify an agent's store identity at create
`swarm/agents/<agent>/bao-mtls` did not exist, and neither did any
per-agent identity at the secret store: `policy::agent_object_name`,
`render_agent` and `render_agent_with_queue` had been written and never
called outside their own tests. An agent's only "per-agent" secret today
is read under the HIVE's certificate, through a wide grant on
`swarm/agents/*` — so "per-agent" was presentational.

The swarm now mints the certificate, so no hive ever needs the capability
to mint one. `swarm-controller` is the service that does it: it already
logs in to the store, and its existing grant already covers exactly the
three objects written here (`create/update` on
`secret/data/swarm/agents/*`, `sys/policies/acl/hive-*` and
`auth/cert/certs/hive-*`). No new bao grant, and nothing co-located — a
cert-auth role pins its authority by value, per role, so the controller
issues from its own CA on its own host and pins that CA in the role it
writes. No existing role changes.

The mint node does not report success on a write. After publishing it
connects again, with the leaf it just issued and under the role it just
wrote, and reads the path back — so the policy, the role, the common name
and the leaf are exercised in production on every agent creation. A
certificate this code mints that the role this code writes will not accept
turns the job node red at creation time instead of surfacing later as an
agent container that cannot start.

`TriggerDeploy` gains an `after_any` edge on the mint, not `after_ok`: a
hive cannot pass down a certificate the swarm has not published, but a
host with no authority configured must still create agents exactly as it
does today.

The private key is generated in memory and never written to disk on the
controller — `SecretStore::connect_with_identity` takes the PEM the minter
is already holding, so nothing is written out purely to be logged in with.

Refs #4137
2026-09-18 15:05:24 +02:00
atlas
992468dccc docs: reword three vale-flagged sentences in matrix.md
Preserve meaning exactly: 'There is no' -> 'No ... exists', 'That is'
-> 'that's', 'is not' -> isn't.

Refs #4475
2026-09-18 10:35:16 +02:00
atlas
99b141f5f2 matrix: drop the per-agent matrix.enable; accounts are the enable signal
`services.hyperhive.agent.matrix.enable` was a second source of truth for
a fact the account set already carried: after ①-③ the hive-internal
`main` account is an ordinary `matrixAccounts` entry, so "does this agent
have matrix" and "does this agent have an account" were the same question
asked twice, with the boolean able to disagree.

The option is gone and a non-empty `matrixAccounts` now gates the daemon
unit, its token path-watcher and the injected `extraMcpServers.matrix`
entry.

That is only a real condition because `matrixAccounts.main` is itself
gated: it is declared when `matrix.url != null`, never unconditionally. A
`main` with no homeserver is an account the daemon can never log in as,
so declaring one always would have made the signal trivially true and
turned matrix on for every agent in every hive. With the URL gate, the
empty set is reachable exactly for an agent the hive gave no homeserver
and whose operator declared no account of its own — the state the old
`enable = false` expressed.

Assertions: "extras require enable" is deleted, having become the
definition of the thing it checked (an external-only account with its own
homeserver is now rendered rather than rejected). `main.tokenFile` stays
pinned, re-guarded on `? main` instead of on the flag, since `main` is
absent whenever the URL is null and an unguarded index would throw there.

Both spellings of the option get `mkRemovedOptionModule`, following
../host-modules/deploy.nix's registrationTokenFile pair rather than a
silent delete: the definition whose meaning changes is `false`, and left
undeclared it would be ignored and hand the agent the tools its operator
turned off. Failing the eval with the replacement spelling is the only
outcome that cannot.

module-eval gains the three arms — URL, nothing, external-only — with the
middle one carrying why it exists: it is the only thing in the suite that
would notice `main` becoming unconditional again.

Refs #4475
2026-09-18 10:35:16 +02:00
atlas
c74249f371 matrix: make the hive-internal main account an ordinary matrixAccounts entry
`matrixAccounts` is meant to be the agent's full account list, but the
hive-internal `main` account was outside it: the nix module emitted only
the extras and `hive-matrix-daemon` prepended a `main` it synthesized
from the per-agent paths, with the option schema forbidding the name
outright.

nix/agent-modules/matrix.nix now declares `main` itself, as an ordinary
entry under `matrix.enable`, from the state-dir paths the module already
used for its token path-watcher (now a shared `stateDir` binding) plus
`matrix.url`. The whole set, `main` included, is serialized to
HIVE_MATRIX_ACCOUNTS.

accounts::configured therefore synthesizes `main` only when the parsed
list carries none, and otherwise takes the declared one verbatim —
hoisting it to index 0, since the daemon reads index 0 as the primary
and nix serializes an attrset, so `main` sorts wherever its key falls.
Declared xor synthesized: an agent whose harness predates this entry
keeps working, a current one gets its own, and there is no arrangement
where `main` is duplicated or missing.

The reserved-name assertion is replaced rather than dropped: the name
must now be legal (the module uses it), but `main`'s tokenFile stays
pinned to `<state>/matrix-token`, since hive-c0re provisions the
hive-internal token there and nowhere else — a retarget would evaluate
fine and then never restore. The other two fields are mkDefault and free
to override.

Refs #4475
2026-09-18 09:34:44 +02:00
atlas
6050033fbc docs: fix stale hyperhive.* prose left by the repoint
Refs #4479
2026-09-18 03:05:43 +02:00
atlas
7570597190 docs: repoint agent-tier option paths to services.hyperhive.agent.*
Refs #4479
2026-09-18 03:05:43 +02:00
atlas
6e23af36c9 docs/swarm/credentials: a hive is an identity too
Line 26's list named two identity kinds; mara flagged it as
should-be-three and the third went unnamed. A hive holds its own mTLS
leaf (glue-bao-tls.nix:130-131, CN = hiveName from :49) and logs in
with it (glue-queue-agent-credential.nix:171, bao login
-method=cert); swarm-bao.nix mints matching cert-auth roles for it
alongside swarm-controller (:1049-1052) and swarm-secret-publisher
(:1101-1104), and swarm-controller/src/read_policy.rs:184-188 writes
the per-hive role dynamically. The table further down this page
already lists a hive as a reader (swarm/hives/<hive>/matrix/appservice-token),
so the line-26 list was inconsistent with the rest of the page.
2026-09-17 13:56:34 +02:00
atlas
42dcf10064 swarm-logs-cli.md: regenerate from the binary, prettierignore it
The committed doc was stale (missing help text under --limit/--format
that the binary's clap-markdown output now includes). Regenerated with
the documented command. While at it: docs/tools/swarm-logs-cli.md was
never added to .prettierignore alongside its three siblings, so nix fmt
silently reformatted it back to a stale layout right after
regeneration — the exact failure mode .prettierignore's own comment
warns about. Added it so the *-docs-fresh check in nix/checks.nix stays
satisfiable.
2026-09-17 01:02:14 +02:00
atlas
a39399f037 swarm-logs: an agent's CLI for the swarm log store
An agent can reach VictoriaLogs only through the gateway, and since the
machine query route landed the way to read it has been to hand-roll a
client_credentials token request and a curl, per query. This is the CLI
that closes that: `swarm-logs query '<LogsQL>'`, matched log lines on
stdout, so the answer pipes into grep like any other command's.

Built to the plan posted on the tracker thread: own crate, own
docs/tools reference generated off the clap tree, `query` as the one
verb, and the JSON error body surfaced on a non-200 rather than
swallowed. No `tail`: streaming is a different endpoint with a different
response shape, and folding it in here would be a fatter scope than the
ask.

Minting the token is NOT implemented here — swarm-queue-client already
owns the client_credentials request, its error type and its CA handling,
and a token-endpoint fix has to be findable in one place. What this crate
adds is the agent-shaped half: the client id arrives as a *file* beside
the secret, so nothing outside nix/agent-modules/queue.nix spells
`hive-<name>-agent` twice. That is the same problem hive-agent's
swarm_queue module solves, and swarm-logs/src/auth.rs is its `decide`
restated over this binary's inputs.

⚠️ The plan named one thing to verify empirically before calling the auth
settled: whether authelia's bearer policy for the logs vhost accepts the
agent client's audience. Measured from inside a container: it does not.
The client minted a token fine but with `aud: []` and `scp: []`, asking
for the logs URL as an audience answered `invalid_target`, and presenting
the audience-less token to the gateway answered a bare 401. So
swarm-authelia.nix's agentClients gains `authelia.bearer.authz` and the
query URL as a second audience — authelia authorises a bearer token by
the URL being requested, and that URL is now one binding read by three
places rather than three spellings of one address.

The URL reaches an agent the same way its queue coordinates do: computed
on the host (a container cannot derive a gateway address), forwarded by
hive_c0re::meta into the container's option set, and consumed by a new
agent module that installs the binary *wrapped* with its coordinates —
the shape swarm-controller.nix installs swarmctl in. Gated on the queue
credential as well as on the URL: a binary that can only answer 401 is
worse than no binary, because an agent reads a 401 as "no logs", which is
the exact confusion the store's machine route was added to end.
2026-09-17 01:02:14 +02:00
atlas
b71ff90661 docs/swarm/credentials.md: restore the new-vs-existing progressive-enhancement split
The rewrite in #4445 flattened this section's new-functionality-vs-
existing-functionality split into unconditional rules, because it had
been anchored to the current-state table that PR deleted. Restore the
split on its own terms, per #4445's own description of the rule: new
functionality has to match the target shape immediately; existing
functionality can move step by step, provided each step moves toward
the target shape.

Refs #4445
2026-09-17 00:02:00 +02:00
atlas
0c50913db9 docs/swarm/credentials.md: mTLS cert is per-identity, not per-host
Two lines still framed the invariant as one file total: line 19 ('plus
exactly one file') and line 21 ('the only credential on disk') implied
a single mTLS cert page-wide, contradicting the earlier fix that made
line 26 say 'every host has at least one'. Agents and swarm-level
services each need their own identity certificate, so a host running
several holds several. Restate both as per-identity, keep the target
shape (store + certs on disk, nothing else) unchanged.

Also relax the touching-a-credential rule: moving a secret into bao
without resolving its renewal strategy in the same change is
acceptable while the migration to this shape is in progress, provided
a follow-up issue is opened to settle renewal.

Refs #4445
2026-09-16 23:29:39 +02:00
atlas
596e8f006f docs/swarm/credentials.md: every host has at least one certificate
- Change 'Every host has one' to 'Every host has at least one' to clarify
  that a host may hold multiple mTLS client certificates.
2026-09-16 23:24:54 +02:00
atlas
972b80d7b8 docs/swarm/credentials.md: fix citation range to include 'A path, never a value'
The citation for the nix option description was 529-531, but the quoted
phrase 'A path, never a value.' appears on line 533. The correct range
529-533 includes both the 'operator places by hand' claim and the
'path, never a value' assertion that the doc attributes to that file.

Refs #4445
2026-09-16 22:46:29 +02:00
atlas
cc77db0e7e docs/swarm/credentials.md: nix fmt table alignment 2026-09-16 22:41:53 +02:00
atlas
940e11ecda docs/swarm/credentials.md: cut the page down to the target shape only
Drop the current-state table and the prose describing how the swarm
currently breaks the target (unrotated secrets persisted to disk outside
the store, no agent store identity, the appservice token's second
minter). Scope the path-never-value rule to the mTLS client certificate
— the one credential a nix option can carry, now that everything else
goes through bao. Trim the migration checklist to unconditional rules
for any credential-touching change, since there's no current-state table
left for the old existing-functionality carve-outs to reference.

Refs #4445
2026-09-16 22:41:23 +02:00
atlas
f1e5fd5592 docs/swarm/credentials.md: make the target section bao-shaped, not file-shaped
The target section described the target as a shorter list of secret files —
the intro promised "what changes is how many paths there are", and the
per-secret contract pointed at the current-state table's file-shaped columns.
That is today's shape with fewer rows, not the target.

Rewrite it around the invariant: no secret the store holds is ever written to
disk. The mTLS client certificate is the sole credential on disk, and the
section now says why — it authenticates the puller to the store, so it is the
one credential that cannot be fetched from it. Per secret the target still
states minter, reader and renewal, but the reader is now a process pulling a
store path at runtime under its own certificate, not a path being read off
disk.

The current-state section and all of its citations are untouched: the current
state genuinely is files, and that contrast is what the page is for.
2026-09-16 22:41:23 +02:00
atlas
ae388c48a7 docs/swarm/credentials.md: restore claims changed by vale rewrite
Two vale rewrites in c33cc441 changed meaning, not just wording:
- "on track to become" asserted a trajectory the page's own inventory
  contradicts (no bao identity anywhere, all renewals NONE, all
  secrets plaintext). Restored intent: "what it should become".
- "most often misread" claimed an observed reader frequency nobody
  measured. Dropped the claim about readers entirely; states the fact
  directly instead.

Both stay vale-clean; no suppressions.
2026-09-16 22:41:23 +02:00
atlas
b2c1378fd3 docs/swarm/credentials.md: reword prose to pass vale
Fixes the Microsoft.Contractions/write-good.ThereIs/alex.Condescending/
Microsoft.Auto errors vale flagged in CI (prose-lint-errors, required):
contractions throughout, no sentences opening with 'There is', dropped
the 'auto-mints' hyphenation and the 'easily' condescension flag, and
renamed the credentials table's 'what it is' header to 'kind' (also
flagged once styles were resynced). No technical claim or file:line
citation changed. Same fix applied to the one new line in
docs/README.md's pointer to this page.
2026-09-16 22:41:23 +02:00
atlas
52fabe7096 docs/swarm/credentials.md: fix 7 drifted/wrong file:line citations
Audited all 22 file:line citations against source on this head, per
argus's PR review. Fixed:

- matrix_account.rs writer cell pointed at path-computation line 163;
  the actual store.write() call is at 189-197 (now anchored to the
  put_matrix_account function name too).
- swarm-secret-publisher.nix appservice-token minter cited the
  "put on every run" comment (280-286) instead of the mint itself
  (271-278).
- glue-matrix-bao-token.nix persisted-token cell cited the
  registration-restamp comment (186-188) instead of the printf+chmod
  (180-181).
- glue-queue-agent-credential.nix persisted-files cell missed the
  secret's chmod 0600 at 219, citing 224-231; corrected to 218-224.
- swarm-otel.nix persisted-file cell cited 763-767, one line short of
  the chmod 0400 at 768; corrected to 764-768.
- hive-priv/src/main.rs:368,413 (forge/github token cell) landed in
  unrelated DestroyContainer/ListContainers/WriteAgentGithubToken match
  arms; replaced with the write_forge_token/write_github_token function
  names as a greppable, edit-resistant anchor.
- swarm-bao.nix:527-528 (the "operator places by hand" quote) missed
  the quoted text, which starts at 529; corrected to 529-531.

15 of 22 already resolved exactly; no claim lacked supporting code, so
nothing was removed. 22 of 22 citations checked.
2026-09-16 22:41:23 +02:00
atlas
937289383c Add a credential docs page: current state, target state, the rule between
The swarm's credential docs say where every file lives. They do not say
whether it should be a file at all, so a discussion about direction has
had nothing to point at and each one re-derived the same table.

This page carries that table with the three columns the target contract
is written in — minter, reader, renewal — plus the column the target is
really about: whether the value is persisted outside the store. Stating
it flatly is the point. All four stored families are plaintext files on
disk, the appservice token twice; every renewal cell reads NONE; no agent
container holds a store identity at all, so the hive reads on its behalf
and writes a file in; and the appservice token has a second, uncoordinated
local mint that can diverge from the published one.

The target section is marked as a target throughout, because its first
line is the one most easily misread as fact: every host needing a store
mTLS certificate is where this is going, while today only the store's own
host auto-mints and swarm-bao.nix calls it the credential an operator
places by hand everywhere else.

The progressive-enhancement rule is stated as a table of questions a
reviewer applies to a pull request rather than as prose, since a rule
nobody can check is a preference. New functionality matches the target
immediately; existing functionality moves stepwise, and the questions
distinguish a step from churn.

Indexed from the docs root and the swarm README. It supersedes
secrets.md when the migration completes — at which point that file is
deleted and this one moves into its place.
2026-09-16 22:41:23 +02:00
atlas
f45cecf08c ops: address argus review findings on PR #4433
Add attribution-trailer lint row to the ci.md checks table (modeled
on the vale-errors row: not yet a required check) so the doc doesn't
assert stale completeness once attribution-trailers merges. Delete
the dead CI_COMMIT_REF_NAME elif in check-attribution-trailers.sh —
byte-identical to the else arm, never exercised, and its comment
overclaimed GitLab/Forgejo support nothing here can test.

Refs #4432
2026-09-16 00:07:02 +02:00
atlas
199afa41c8 matrix: publish the appservice token from the swarm, not just read it
`glue-matrix-bao-token.nix` has read
`secret/swarm/hives/<hive>/matrix/appservice-token` since it landed, but
nothing ever wrote that path. The store was empty in every deployment, so
every read degraded to "keep what activation minted" and each hive stayed
the origin of a value the swarm has to agree on — two hives never
converged.

`swarm-secret-publish` is now the producer. It already holds a store
identity, already writes under the hive prefix, and already runs per
hive in the roster, so the mint is a third loop beside the two OIDC
copies rather than a second shape of this unit.

Idempotence comes from a record of its own, not from the store: this
principal is granted `create`/`update` with no `read`, so it cannot ask
whether a hive already has a token. It keeps what it minted under
`StateDirectory=` (0700 dir, 0600 file) and mints only when that file is
missing or empty; the `put` runs every time, because re-putting the same
bytes changes nothing for a reader while a mint whose publish failed must
not be left as a token this host holds and no hive can reach.

The token never becomes a nix literal and never reaches argv: the mint
redirects into a file, and the publish hands bao `value=@<path>` so bao
opens it itself — the same handling the OIDC loops use.

`hive-matrix.nix`'s activation mint stays as the genuine first-boot
fallback. It already fires only when the token file is absent, so it
cannot clobber a value the store delivered; `hs_token` has no swarm half
and is still minted there for real.

Refs #4402
2026-09-15 20:57:49 +02:00
atlas
7ee7080b21 matrix: remove the registration token
Nothing reads it any more: hive-c0re creates accounts as the hive's
appservice, so the mint, the host file, the bind mount, the
`LoadCredential` entry and tuwunel's `registration_token_file` all go.

⚠️ `allow_registration` has to go to `false` in the same change, and not
as hardening. tuwunel refuses to START when registration is allowed with
no token configured — it demands
`yes_i_am_very_very_sure_…_open_registration_…` instead — so dropping the
token and leaving the flag true is not a lax homeserver, it is one that
does not boot. The flag is checked only for requests arriving without an
appservice token, so hive-c0re provisions exactly as before and everyone
else is refused outright.

The swarm secret store keeps its role, repointed at the credential that
replaced the token (`swarm/hives/<hive>/matrix/appservice-token`). Its
unit now also re-runs hive-matrix's own registration renderer after
writing the file: the token is half an agreement, and a registration
still naming the previous value authenticates nobody. The renderer is
shared through an internal option rather than copied, so the
registration's shape has one home.

Both spellings of `registrationTokenFile` become
`mkRemovedOptionModule` with a message naming what replaced them. A hive
that never set the option — the default — is unaffected; one that pinned
it fails to evaluate with instructions instead of a silent no-op.

An upgraded hive needs no intervention: the activation script has both
halves in place before the homeserver restarts, existing agents keep the
tokens their devices already hold, and the old token file is left on
disk read by nothing. docs/integrations/matrix.md spells the path out.

Refs #4402
2026-09-15 19:58:10 +02:00
atlas
5ce0a357b4 subagent: grant claude's Bash when the agent holds the execution group
A subagent inherited the parent's built-in tool list, which correctly has
no `Bash` -- the agent reaches a shell through the `bash` MCP server, not
the built-in. Subagents get no such server, so the intersection was empty
and they could not run a command at all: no commits, no pushes, no gates.

Add `subagent_builtin_tools_for`/`_arg`, which reuse the shared resolver
and append `Bash` only when `Execution` -- the group that gates the `bash`
MCP server -- is present. Only the subagent spawn path calls them, so the
harness's own `--tools`/`--allowedTools` are unchanged.

The capability transfers; the mechanism does not.

Refs #4422
2026-09-15 18:48:51 +02:00