Second doc-directory of hyperhive#4042's Passive pass (see #4098 for
the first batch's full read-every-hit reasoning). Only one hit in this
whole directory: "Every page is generated at build time
(nix/packages/reference-docs.nix, hyperhive#3051)" -- a genuine catch,
same shape as #4098's swarm-SSO rewrites -- the actor is already named
parenthetically in the same sentence, so naming it as the grammatical
subject too is strictly clearer: "nix/packages/reference-docs.nix
(hyperhive#3051) generates every page at build time".
Verified: vale docs/crates/README.md before/after -- 1 -> 0 warnings.
First doc-directory of hyperhive#4042's Passive pass (705+ hits across
docs/, genuinely mixed unlike TooWordy -- needs a real per-hit read,
not a dictionary shortcut, so this is going doc-directory by
doc-directory in small PRs, per the plan posted on the issue).
Read all 13 flagged hits in this file in context, not just the flagged
word. 3 were genuine catches with a real active-voice improvement and
a nameable actor:
- "the human operator's own forge/matrix account is created via swarm
SSO" -> "swarm SSO creates the human operator's own forge/matrix
account" (x2, identical sentence shape for both accounts) -- swarm
SSO is a nameable, specific actor already named later in the same
sentence, so naming it as the subject too is strictly clearer, not
just different.
- "Do this before anything is pointed at it" -> "Do this before you
point anything at it" -- matches the doc's own established
second-person imperative voice used throughout ("Do this", "Put the
token's value", "Delete the file"); the passive here was the odd one
out, not the house style.
The other 10 are legitimate passives, left alone:
- Generic/unspecified-actor statements ("is needed", "is bound", "is
issued", "been run") where forcing a subject would either invent an
actor the doc never established or read worse than the original.
- Security/architecture invariants ("No forge admin token is stored in
any agent state dir", "Telemetry ingest is authenticated per hive")
-- "No X is Y" / "X is Y" is the standard idiom for a guarantee
statement in security docs, not a clarity problem to fix.
- "operator-only surfaces ... are gated on that group" -- borderline
(could name the group as subject), judged idiomatic access-control
phrasing rather than genuinely clearer active, but flagged as the
closest call in this batch.
Verified: vale docs/getting-started/setup.md before/after -- 13 -> 10
write-good.Passive hits (exactly the 3 rewritten), the pre-existing
unrelated alex.Suicide hit on "hang" (line 51, untouched) still
present and correctly out of scope for this pass.
`Microsoft.Contractions` fires on "is not" and "it is", and both were in
the sentence I added about the role having no consumer yet. Reworded so
neither appears, and the passive construction goes with them.
The vale job is red on every PR by design -- ci.yml says so, the docs
tree has a standing backlog against these rules -- which is exactly why
"vale is red" was not a safe thing to wave through. Checking per line
rather than per job is what found these.
Verified with the invocation ci.yml uses:
`XDG_DATA_HOME=$PWD/.vale-data vale sync && vale docs/getting-started/setup.md`
-> synced 3 packages, 0 errors, 14 warnings. The 2 errors CI reported at
103:39 and 104:7 are the presence control: same rule, same file, prior
commit.
The policy the granting unit already writes grants paths under
`auth/cert/certs/*`, and nothing in the tree creates that mount. Every
certificate login therefore fails against a path that is not there --
the controller's own, and the per-hive ones it is meant to issue
against the same mount.
Same unit, same bootstrap token: check whether cert auth is mounted,
enable it if not, then write a role binding CN `swarm-controller` to
the `swarm-controller` policy.
Idempotency is a read rather than a tolerated error. `auth enable`
fails on an existing mount, and recognising that would tie a rebuild to
an error string no run of this store has ever produced, so the unit
asks `bao auth list` and mounts only on absence. That read is why the
token policy in setup.md gains `sys/auth`.
Each grant came from `bao <cmd> -output-policy`, which prints what a
command requires without running it -- the same way controllerPolicyText
was derived. Enabling an auth method needs `sudo` on `sys/auth/cert`,
which the documented token did not have.
Gated on `clientCaFile`, not on the token alone: `swarm-bao-certs`
installs `client-ca.pem` only under that condition, and a role's
`certificate=` has to name a real CA. The policy write, which needs no
CA, is unchanged in that case.
Nothing can present a certificate for this role yet -- the only client
leaf the tree mints carries CN = the hive's name -- and none of this has
been run against a live store. Both are stated in setup.md.
The last of the twelve. `swarm.forge.package` becomes
`deploy.forgejo.package`, beside the `behindGateway` / `mirrors` /
host-secret options that were already there.
⚠️ `deploy.forgejo`, not `deploy.forge`. The forge joins the controller
and the UI as a mover where the two sides simply do not share a name,
so the shim comment says which one.
Four readers, and one of them is why an anchored grep was the wrong
instrument: `:678` reads `cfg.package.data`, a sub-attribute, which
`cfg.package}` and `cfg.package/` both miss.
Three copies of the same sentence claimed the package stays under
`swarm.forge` — `deploy.nix`, this module's own `deploy.forgejo`
header, and `docs/integrations/forge.md`. The docs one is outside
`nix/` entirely. A namespace split gets described wherever the split is
explained, which is not only where the option is declared.
`forgeOldPath` gains the old path, and its comment's count of the
host-side options goes five → six with it.
Which build a service runs is a decision of the host that runs it, so
`*.package` belongs with `enable` rather than in the namespace every hive
must agree on byte for byte.
The `gui` pair was the sharpest case and the reason this starts here:
`gui.enable` moved to `deploy.*` in an earlier slice while `gui.package`
stayed behind, so one web client's two halves sat in two namespaces —
and hive-matrix.nix said so, in a comment directly above the option.
The diff removes a stated inconsistency rather than introducing a rule.
Both old paths keep working through `mkRenamedOptionModule`; both are
settable (neither is `readOnly`), so both get a shim. The `matrixOldPath`
fixture in module-eval.nix now sets eight options through their
pre-rename paths instead of six, so dropping either new shim fails the
eval rather than silently dropping a definition.
Prose swept in the same commit: three comments claimed the package was
swarm-wide or named the pre-rename path, and docs/web-ui/dashboard.md
named `swarm.matrix.gui.package` two lines above
`deploy.matrix.gui.enable` — the same asymmetry this commit exists to
remove, displayed in the docs. A shim keeps the old spelling valid, so
no build, lint or broken-reference search would have found that line.
Refs #3772.
Part of #4041's forgotten remainder (UIVerbs was scoped as pure
execution work, no decision needed -- see comment 71586).
29 genuine input-device-neutral rewrites ("click"/"clicking"/"clicked"
-> "select"/"selecting"/"selected", a few reworded where a direct verb
swap didn't read naturally: "one click from the hub" -> "one step from
the hub", "click straight from X into Y" -> "go straight from X into
Y", "the operator never clicked" -> "the operator never triggered").
Left 5 hits unfixed, each read in context and genuinely not a
device-assumption problem:
- docs/process/gotchas.md:439 -- "click-to-unlock screen" names a
specific OS lock-screen pattern, not an instruction to the reader.
- docs/web-ui/agent.md:377 -- "clicks" describes literal RFB pointer
events forwarded by hive-screen-mcp's sendPointer, genuinely
mouse-specific.
- docs/web-ui/agent.md:78 -- "ctrl/middle-click" names a specific
keyboard-modifier + mouse-button combination with no "select"
equivalent.
- docs/web-ui/dashboard.md:741 -- explicitly contrasts "click (or
Enter/Space)" as two named input methods; replacing "click" with
"select" would conflate the two the sentence is distinguishing.
- docs/web-ui/shape.md:253 -- "swipes in from the right" describes the
panel's own slide animation, not a user touch gesture.
Verified: fresh `vale --filter '.Name == "Microsoft.UIVerbs"' docs`
after the fix shows exactly those 5, `nix fmt` reports 0 changes.
Cert auth answers a role, so nothing in the swarm can authenticate to
the store until some role exists. Creating the first one therefore
cannot itself use a certificate — the credential has to come from
outside that cycle, and an operator places it.
`deploy.bao.bootstrapTokenFile` names that token. A one-shot unit inside
the store's container reads it and writes the `swarm-controller` policy;
`local-defaults.nix` supplies the path (never the file) on an all-local
deploy, since co-location makes only the location derivable. `bao
operator init` stays an operator action in every shape.
The unit runs in the container rather than on the host because writing
the first grant is a store-side operation: it reaches the store locally
and needs no client certificate. Gating it on the store being here is
therefore not the co-location assumption glue-matrix-bao-token.nix
warns about — a reader has to work from anywhere, the first write never
does.
Policy only, deliberately: a cert-auth role binds a certificate and the
controller has no bao identity yet — it holds no leaf and contains no
bao code at all. The two certificates that do exist are both wrong to
bind. `clientCertFile` is the store host's own reader leaf rather than
the controller's, and the CA that signed it also signs every other
reader's, so binding that would let any reader authenticate as the
controller. Whoever gives the controller an identity writes the role.
The grants are scoped to `hive-*`. Without the prefix the controller
could rewrite the policy that constrains it.
Two things the module-eval arms pin: the unit renders inside the
container with the token path in both its script and its
ConditionPathExists, and a host that names a token while running no
store gets neither the unit nor the host-side directory.
The capabilities were derived with `bao write -output-policy` rather
than written from memory, and the setup.md commands were run against a
real binary for shape before being written down.
Refs #3726.
Fixes the "obvious ones first" slice of #4042 (mara: do the obvious
ones first) -- 81 hits across write-good.So, write-good.ThereIs, and
write-good.Weasel, all in docs/. Each is a genuine sentence rewrite
(lead with the real subject instead of "There is/are", drop a
sentence-initial "So ", replace a vague intensifier), not a blind
regex substitution -- read every hit in its real file context before
touching it.
3 of the 81 hits were in CI-generated CLI docs (docs/tools/{hivectl,
swarmctl,forge}-cli.md) -- fixed at the clap #[arg(...)]/doc-comment
source in hivectl/src/cli.rs, swarmctl/src/main.rs, and
hive-forge/src/verbs/repo_add_collaborator.rs, then regenerated via
each crate's `markdown-docs` subcommand so CI's freshness check stays
green.
Verified: fresh vale re-run shows 0 remaining So/ThereIs/Weasel hits
and no new hits introduced (983->982, exactly the one incidental fix
this pass also picked up at docs/scheduler/observability.md:48).
cargo fmt --check and clippy clean on the three touched crates.
Remaining write-good backlog (Passive: 726, TooWordy: 207) is
judgment-heavy and left for a follow-up slice of #4042, not bulk-
rewritten here.
The guidance says to stagger two hosts' collections, and never says how
long one takes — so an operator picking `dates` has no way to know
whether their stagger is wide enough.
The number that matters is theirs, not mine: a collection's wall clock
is dominated by the sweep over unused `.links` entries, which scales
with the size of the store rather than with what the run deletes. So a
store's size, not its churn, decides the window, and `randomizedDelaySec`
spreads only the start.
Measured here for scale, and deliberately kept out of the doc since it
does not transfer: 11 runs over two weeks ranged 5min 16s to 51min 25s,
inversely — the 51-minute run deleted 732 paths, a 12-minute run deleted
21,901.
Refs #4019.
Slices 8 and 9 of the swarm/deploy split, and the last two.
statusPublish had three coordinates under one namespace. Two of them
are this machine's — where the queue listens *as seen from here*, and
where its client secret sits on this disk — so they move to
`deploy.hive-controller.statusPublish.*`, the namespace of the daemon
that is their only reader. `tokenEndpoint` is the swarm's one address,
so it stays. That leaves `swarm.statusPublish` holding a single option:
a legitimate split, not a botched move.
The all-or-nothing assertion now spans both namespaces. It is repointed
in both its condition and its message, and the message spells all three
paths in full so an operator is never told to set two options under a
path that only has one. `environment.nix`'s guard and the value beside
it likewise read different namespaces on purpose.
The collector's secret moves the same way, for the same reason, to
`deploy.swarm-otel.*` — `enable` already lives there. That also retires
one of the eight cross-namespace assignments tracked in #4048: the
delivery unit set a `swarm.*` value under a `deploy.*` gate, and now
sets a `deploy.*` value under one.
module-eval gets a fixture per slice. `otelRemoteAuthelia` already set
the collector secret through its pre-rename path, so it becomes slice
9's old-path case as it stands — left spelled that way deliberately,
with a comment, so it is not read later as a missed site.
That fixture also turned out to be describing an impossible hive: it
said authelia lives elsewhere without saying where, so the
authenticator interpolated a null `swarm.authelia.url` into its
`token_url`. Nothing to do with the rename, and invisible to the
existing case over the same fixture, which reads `? auth` and `elem` —
both stop at names and never force the extension's value. Given the
address a remote-IdP deployment has.
Verified: 49 -> 51 properties, all holding.
Resolving the 5 branches (contractions/foreign/auto/quotes/hyphens) onto
current main surfaced a few things beyond plain conflict markers:
- docs/scheduler/coordinator.md had 'auto-noops' turned into the
malformed 'autanoops' by the auto-hyphens batch -- fixed to
'autonoops'.
- main had moved (new prose, some option renames) since the batches
were generated against an older commit, so a handful of merge
conflicts needed both sides combined (keep main's updated content,
reapply the lint fix on top) rather than a plain pick-one resolution.
- A few genuinely new Microsoft.Contractions hits had appeared in the
moved-forward main and in content pulled in wholesale during
conflict resolution -- fixed the 2 real ones
(docs/integrations/forge.md, docs/turn-loop/claude-invocation.md).
- The other 'new' Contractions hits vale reports now
(docs/integrations/forge.md:281, docs/tools/forge.md:260,
docs/tools/hivectl.md:239, docs/tools/swarmctl-cli.md:28,
docs/web-ui/dashboard.md:457) are a side effect of the Foreign
batch's own 'i.e.' -> 'that is' substitution: vale's Contractions
rule can't tell 'that is' used as a copula (contractable) from 'that
is' used as a parenthetical clarifier (not contractable, would break
the sentence -- 'that's it raced the merge' isn't English). Left
those alone; noted in the PR description.
Final vale count on this branch: the only Microsoft.Contractions/
Foreign/Auto/Quotes/Hyphens hits left are the known legitimate skips
(ALL-CAPS/bold emphasis, vale's own false positives, and this
Foreign/Contractions interaction) -- verified with a fresh vale run.
Fixes 22 of 23 Microsoft.Hyphens hits: an adverb ending in -ly doesn't
need a hyphen to the word it modifies (minimally scoped, currently
running, deliberately stopped, ...) -- the -ly ending already
unambiguously signals it's modifying the following word, so the
hyphen is redundant per Microsoft style.
Left one hit alone: docs/web-ui/dashboard.md's 'checked-not-originally-
active' is a single 4-word compound designator (parallel structure
with 'unchecked-was-active' right before it in the same sentence,
describing a diffing algorithm's two possible states), not an
ordinary -ly-adverb modifier -- dropping just the one hyphen vale
flagged would have broken the compound into a dangling
'checked-not-originally active' fragment. Also rewrote
docs/scheduler/coordinator.md's 'still-fully-queued' as a predicate
('DAGs that are still fully queued') rather than leaving a similar
broken partial-hyphen chain, since 'still' and 'fully' are both plain
adverbs with no reason to hyphenate at all once dehyphenated
consistently.
Verified via vale: only the one deliberately-left hit remains.
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.
Fixed 72 hyphenated 'auto-X' compounds across the docs by deleting
the hyphen to match Microsoft style guide (autogenerated, autoregistration,
autodiscovery, etc). Preserved original capitalization throughout.
Covered 26 documentation files in docs/, including README.md, agent-lifecycle,
integrations, scheduler, networking, process, tools, trust-boundary,
turn-loop, and web-ui sections.
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.
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.
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.
docs/web-ui.md duplicated the web-ui/ directory name at the top level --
the only such collision in docs/ (every other subsystem has just a
directory, no sibling <dir>.md file). That's exactly why it rendered
outside the directory structure in the docs site nav (mara's report,
hyperhive#4054): the site build walks docs/ generically with no
special-casing, so a loose top-level file next to a same-named
directory shows up as its own flat top-level entry instead of nesting
under that directory's section.
web-ui.md's own first paragraph already said as much -- 'This doc has
been split for readability... start at web-ui/README.md instead.' It
was a leftover pointer from before the split, not a page carrying
unique content on its own merit.
Folded its two sections web-ui/README.md didn't already have (the
swarm-ui design-guide link, and the task-oriented 'reading paths'
quick-lookup list) into web-ui/README.md's existing 'More depth'
section, then deleted the stray file and repointed every real
reference at it: 3 in-tree doc cross-links, 3 doc prose mentions
(retargeted to the more specific dashboard.md/shape.md sub-page each
one was actually about), and ~28 frontend source comments
(dashboard/agent/shared packages) that cited it as
'docs/web-ui.md::<heading>' for implementation context -- retargeted
each to whichever of dashboard.md/shape.md/agent.md actually carries
that heading now, verified against each file's real heading list
rather than guessed.
Verified via scripts/check-doc-refs.sh (the same lint CI runs): 0 dead
pointers, both before write (confirming the tree was clean beforehand)
and after (confirming nothing broke).
`usersFile`, `hostClientSecretDir` and `hostUsersFile` are filesystem
paths that only exist on the machine running the `swarm-authelia`
container. A hive that does not run it has nothing at any of them, so
they fail the swarm-wide test the namespace's header states.
`usersFile` is a path *inside* the container and still belongs on the
deploy side: a path's scope is the scope of the filesystem it names, and
that filesystem is this host's container root.
The two `host*` options are `readOnly` and derived, so nothing can set
them through the rename shims — those serve readers. The shims are still
required: `mkRenamedOptionModule` is what keeps an out-of-tree module
reading the old path resolving at all.
Seven reads went through an alias rather than a full path
(`autheliaCfg.hostClientSecretDir` in five modules, `.hostUsersFile` in a
sixth, `swarmCfg.authelia.hostClientSecretDir` in a seventh). Every one
of those files already binds `deployCfg`, so the repoint needed no new
alias. Four more sites were prose, including one in `docs/`, which no
grep restricted to `nix/` would have found.
The nightly GC this page tells operators to configure is what #4019 is
about: a collection that lands on the same wall clock as CI. Two gaps in
the guidance rather than one.
`dates = "daily"` with no randomization puts the collection at the same
time every night, so whatever else runs nightly meets it every night.
`randomizedDelaySec` spreads it; the value is nixpkgs' own example for
that option, and the option feeds `RandomizedDelaySec` on the timer.
The overlap itself does not fail a build on a single host — the runner
builds through the host nix-daemon (`hive-ci.nix` disables its own and
bind-mounts the socket), so an in-flight build's temp roots are held by
the daemon a host GC talks to. What it does cost is wall time: the first
build after a collection rebuilds what the collection deleted. That is
the every-run version of the same cause and it is what the comment now
says.
The Remote builders paragraph covered disk space only. Temp-root
protection is per-daemon, so it does not span two stores: a collection
on the builder is not aware of a coordinator build waiting on a path
there. Stated as the reason to stagger the two hosts' windows rather
than give them the same `dates`.
Deliberately not claiming this fixes the failure in #4019. That one
observed a missing build input with a remote builder in play, and
distinguishing "the collector deleted it" from "it was never in that
store" needs a correlated reading on both hosts during a live window,
which has not been taken. This is the half that is knowable from the
tree: our own recommendation is what schedules the collision.
mara on #3923: "past tense is itself a smell for docs, we dont need any
'used to be somewhere else'" and "any gate we add should not have false
positives". Those are one instruction: the entire false-positive set of a
repo-path checker was prose naming files that are deliberately gone, so
removing the prose is what lets the gate be strict instead of carrying a
skip-list nobody maintains.
Three sites, and only two of them are past tense:
`agent-hierarchy.md` named two removed modules to explain that a code path
is gone. A reader cannot act on where it used to live; they can act on
where cancellation happens now, which is the half the sentence buried.
`term_msg.rs` carried a "Design history" paragraph whose live content was
three rules -- classification is server-side, `level` carries styling, four
named fields are deliberately absent -- wrapped in narration about a
redesign and a pointer to a deleted frontend file. Restated as the rules.
`gotchas.md` is NOT past tense: it correctly names a file in the website
repo. Qualifying it as `hyperhive/website:nix/options.nix` did not work --
the extractor still matches `nix/options.nix` as a substring, so the fix
has to be prose that contains no repo-relative path at all. Naming the
file alone does that and reads better.
Verified against this tree rather than the default checkout: the audit
script takes a directory argument and defaults elsewhere, so its first
(unchanged) DEAD:3 was a true statement about a different branch. On this
one: DEAD 3 -> 0, 141 distinct paths enumerated, and an injected dead path
is still caught.
Refs #3923
`swarm.*` is what a hive needs to be a *client* of the swarm. For Grafana
that is the package, the name it is served under, the port its `/metrics`
is re-served on, and the OIDC client it is registered as. Where its
datasources point, which plugins sit in its store path and the directory
it shares a socket with nginx through are decisions of the machine
running it, so datasourceUrl, logsDatasourceUrl, plugins and socketDir
move to `deploy.grafana.*`.
The two URLs are the interesting half. `swarm-grafana.nix`'s own summary
sentence said what stays is "its package, domain, and wiring" — and both
datasource URLs ARE wiring, so that sentence is rewritten with the move
rather than left asserting the opposite of what the module does. They
move because a URL's scope is the scope of what it ADDRESSES, not the
fact that it is a URL: docs/swarm/services.md already said datasourceUrl
"defaults to the store on this host, which is the only thing it can
reach", because that store binds loopback. The doc argues the move.
`socketDir` was already ruled host-side: the directory is shared between
the host's nginx and the container, and it is the same shape as the
options that moved in earlier slices.
Declared in swarm-grafana.nix under the `deploy.*` path, following
swarm-nats.nix; deploy.nix carries only the renames. One reader outside
the options block is prose: `metricsPort` STAYS and cross-referenced
`{option}services.hyperhive.swarm.grafana.socketDir`, which the split
makes a pointer to another rendered page — nixosOptionsDoc emits
swarm.md and deploy.md separately. Requalified, along with the one line
in docs/swarm/services.md that named a mover.
module-eval configures a hive through all four OLD paths and asserts a
rendered effect: the host tmpfiles rule that creates the socket
directory carries the fixture's custom path. The new paths evaluate fine
without the shims, so dropping them has to read as a clean tree; all
four are defined in the fixture, so removing any single shim fails the
eval rather than only the one the assertion reads.
`swarm.*` is what a hive needs to be a *client* of the swarm. For the
queue that is the ports it answers on, the client id it is registered
under, and the responder package. Whether this host mints its own
auth-callout keypairs and where the seeds sit are decisions of the
machine running it, so autoGenerateCallout, both seed files and both
public keys move to `deploy.nats.*`.
The two PUBLIC keys are the non-obvious half, so the reasoning is here
rather than in a comment nobody re-reads. A public key looks swarm-wide,
and docs/swarm/secrets.md says the user key "is published to every
client that connects" — which is true and does not make it swarm
config: peers RECEIVE it over the wire at connect time, they never
configure it. What decides the placement is that its seed is
unambiguously host-side, and splitting a keypair across two namespaces
is worse than either placement.
local-defaults.nix set `nats.autoGenerateCallout` from INSIDE
`config.services.hyperhive.swarm = { ... }`, so the bare prefix meant
`swarm.nats` and no full-path grep could see it. It moves out of that
block into a `deploy` statement rather than being rewritten in place.
swarm-controller.nix bound `natsCfg` and never used it — one mention,
no bare pass, while its sibling bindings have 5 and 10. Pre-existing
dead code, found by this slice's own alias sweep, removed with it.
Four assertion messages and five doc lines named moved options by a
short form (`nats.calloutUserSeedFile`) that is ambiguous now that both
`swarm.nats` and `deploy.nats` exist; one opened with
"services.hyperhive.swarm.nats has callout public keys", which the split
makes false. Prose that names a VALUE rather than a path — the `nk`
pipeline's `# -> calloutUserPublicKey` — is left bare on purpose.
module-eval configures a hive through all five OLD paths and asserts the
responder's delivery unit exists and interpolates the seed path.
`autoGenerateCallout` is deliberately FALSE there: that is what makes
the seed options decide `responderConfigured`, so the assertion is about
the seeds rather than about the auto-mint branch.
`swarm.*` is what a hive needs to be a *client* of the swarm. For the
homeserver that is what it IS from anywhere: its package, the name it
answers to, the ports and URLs it is reached on, and the client id it is
registered under. Whether it is exposed, which peers it trusts, how large
a request it accepts and where its host-local secrets sit are decisions
of the machine running it, so openFirewall, trustedServers,
maxRequestSize, registrationTokenFile, gui.enable and
sso.clientSecretFile move to `deploy.matrix.*`.
Two sub-blocks split rather than moving whole, on their own evidence.
`gui.enable` is whether THIS host serves the web client; `gui.package` is
which client, an artifact identity, and stays. `sso.clientSecretFile` is a
path on one host; `clientId` must match the id in authelia's register, so
it is swarm-wide. Each half now points at the other, because the rendered
docs put them on separate pages.
hive-gateway passed the whole `swarm.matrix` attrset into vhosts.nix, so
that file read a moving option through an argument with no option path
anywhere in it. It now takes `matrixDeployCfg` beside `matrixCfg` — the
only shape that carries a split namespace across that boundary.
While there: vhosts.nix read `matrixCfg.enable`, which has been a rename
alias for `deploy.matrix.enable` since the enable moved. Reading it made
the module system print `Obsolete option services.hyperhive.swarm.matrix.
enable is used` on EVERY evaluation of every host — a deprecation warning
no operator could silence, because the config tripping it was ours. That
shim lives in hive-matrix.nix rather than in this file's table, which is
why deploy.nix's header claim to be their single home is now qualified
in the new block's comment.
glue-matrix-bao-token.nix read the registration token through its own
`matrixCfg` alias; with that read repointed, the binding had no reader
left, so it goes, and the comment naming it is reworded.
module-eval gains a case configuring a hive through all six OLD paths and
asserting two rendered effects — the host firewall's port list and the
container's bind-mount table — because the new paths evaluate fine
without the shims. `gui.enable` is set to the opposite of its default so
the definition has to land rather than agreeing with it by accident.
`swarm.*` is what a hive needs to be a *client* of the swarm. For the
forge that is what it IS from any hive's point of view: its package, the
names and ports it answers on, the URLs it advertises, and the client id
it is registered under. How it is served, what it mirrors and where its
host-local secrets sit are decisions of the machine running it, so
behindGateway, openFirewall, mirrors, sso.clientSecretFile and
hostSwarmControllerTokenFile move to `deploy.forgejo.*`.
Unlike the wireguard mesh this SPLITS a module rather than relocating a
whole namespace. `sso` splits with it: `clientId` stays because it must
match the id in authelia's register, while the secret beside it is a
path on one host. Moving the whole `sso` block for symmetry with `ci`
was considered and rejected on exactly that asymmetry.
Declared in hive-forge/default.nix under the `deploy.*` path, following
swarm-victorialogs.nix; deploy.nix carries only the renames. `mirrors`
renames in one entry rather than one per field — it is a single option
of a list-of-submodule type, so the rename carries its whole value,
where `ci` needed five because it is a plain attrset of options.
Readers outside the module: hive-ci.nix binds `deploy.forgejo` for its
behindGateway assertion; swarm-authelia.nix and swarm-controller.nix
read theirs off the `deployCfg` they already bind. hivectl's `open`
printed `services.hyperhive.forge.behindGateway` in an operator-facing
hint — a path that never existed, missing `swarm.` — and hive-c0re's
state_snapshot doc comment carried the same defect; both now name the
new path. The rendered docs put the two halves on separate pages, so
the five descriptions of staying options that explain themselves in
terms of `behindGateway` now qualify it in full.
module-eval gains a forge case configured entirely through the old
paths, asserting the rendered firewall ports and the mirror env var
c0re seeds from: the new paths evaluate fine without the shims, so
dropping them reads as a clean tree. All five old paths are defined in
the fixture, so removing any single shim entry fails the eval rather
than only the two the assertion reads.
`swarm.*` is what a hive needs to be a *client* of the swarm; the mesh is
none of it. A peer needs this host's `wireguardEndpoint` -- the roster entry
in swarm.nix, which stays -- and nothing about the interface this host
brings up. The module already said so: "plain host networking that a machine
which runs no hive at all still needs."
All five options move, so the namespace relocates rather than splitting.
`listenPort` is the one that reads the other way: it is what this host
*binds*, while the port a peer *dials* lives inside `wireguardEndpoint`.
Declared in swarm-wireguard.nix under the `deploy.*` path, following
swarm-victorialogs.nix; deploy.nix carries only the renames, per its own
"a single file to delete when the deprecation window closes". Deliberately
NOT added to deploy.nix's own options block: every entry there is a swarm
service this host deploys, and the mesh is host networking.
hivectl/src/wg.rs generates the config snippet an operator pastes, so it
moves too -- otherwise the tool's own output trips the deprecation warning.
module-eval gains a case that configures a host through the OLD path and
asserts the rendered wg-hive interface, because the new path evaluates
fine without the shim: dropping it reads as a clean tree.
HeadingColons conflicts with this repo's sentence-case heading
convention -- same shape as the existing Dashes disable, a house-style
conflict rather than a defect.
Percentages had zero true positives on the full docs/ tree: it only
ever fired on identifier uses of "percent" (cpu.percent, percent
resolver), never on an actual spelled-out percentage.
Plurals: rewrote the two real hits to always use the plural form
instead of the parenthetical (s) construction, per house style.
`hive-matrix-daemon.path` globbed `/agents/*/state/matrix-token*`. Every
agent's state dir is visible from inside every container, so the
condition is satisfied by a sibling's token.
That is reachable, not cosmetic. The daemon deliberately exits 0 when it
has no token of its own — `Restart = "on-failure"` therefore does not
restart it, and the unit sits inactive, which is the state the path unit
exists for. In that state a sibling's token keeps the glob satisfied:
the path fires, the daemon exits 0, the unit deactivates, the path
re-arms, the condition is still true. systemd.path(5) activates a
`PathExists`-family condition that already holds immediately on arming,
so it repeats until the start limit stops it.
Scoped to this agent, the condition is false exactly when the daemon
would have nothing to do.
The glob is quoted in four other places, all of which would otherwise
name a pattern that no longer exists — a doc, a Rust doc-comment in
hive-c0re, a nix comment, and an assertion message an operator reads.
Each is reworded to the basename (`matrix-token*` in this agent's state
dir), which is what the assertion actually enforces via `baseNameOf`, so
they stay true wherever the directory moves.
Refs #4030.
Not condescending (they're negated -- non-obvious/not obvious is the
opposite claim, not the flagged pattern) but the negated-obvious
phrasing itself is worth a direct rewrite regardless of what triggered
the review: integrations/matrix.md (non-obvious -> subtle),
process/conventions.md (if non-obvious -> when the reasoning isn't
self-evident), swarm/ca.md (not obvious -> subtle).
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.
`docs/tools/forge.md` said the flat forms "still work as hidden
back-compat aliases" and named `pr-create` and `pr-status` as its
examples. Both refuse now: nine flat verbs parse only far enough to
print the namespaced form that replaced them.
The doc had no way to be read correctly, because the seven that do still
work are hidden too and `--help` shows neither group. Naming the split
here is the only place the difference is visible.
At the enum the kind-agnostic seven now read as staying rather than as
"still work", and `credential-helper` is marked as belonging to neither
group: it is hidden because git invokes it, so a sweep reading
`hide = true` as "deprecated" would remove checkout auth.
Refs #3974.
hyperhive#4008, mara: 'add a setting to not display verbose output...
like the grey colored debug stuff.'
Same shape as the existing expand-tool-output preference
(ExpandDetailsSetting.tsx): a new shared/src/prefs.ts key pair
(getHideDebugPref/setHideDebugPref), a new settings-menu-row component
owning its own state (HideDebugSetting.tsx, not a prop threaded through
the shared SettingsMenu component — per mara's earlier review on the
first one, more per-page options as props there is how that component
accumulates cruft), mounted next to ExpandDetailsSetting in Root.tsx.
Row.tsx skips (returns null for, not CSS display:none) any TermMsg
whose level is 'debug' when the pref is set — matches the muted 'debug'
row this issue is about (see docs/web-ui/terminal-rendering.md's Levels
table). Read live per-row, same as expand-details, so toggling applies
to newly streamed rows in an already-open tab without a reload; already
-rendered rows are unaffected either way, same non-retroactive
precedent the existing preference already sets.
hyperhive#3902, mara: option (a) - a content pass splitting mixed docs
into operator-facing content plus implementation detail. One file
first, to agree on the split pattern before doing the other ~19.
Went through 3 shapes on review before landing here: a sibling
-internals.md file (mara: clutters the navigation), then two
tree-precedent alternatives damocles raised (subdir+README like
web-ui/; or only split docs with a pre-existing boundary marker,
which would've covered 3-4 of the ~20 flagged docs and left the rest
untouched), then mara's own proposal - a collapsed <details> section
in the same file. Verified empirically (cmark-gfm --unsafe, the
website's own render pipeline) that markdown headings nested inside a
<details> block still parse as real headings with heading-id anchors
once separated from <summary> by a blank line, so anchor links into
the collapsed section keep working.
github.md keeps enabling/provisioning/security up top; its
'Implementation' section is now a <details> block holding what was
briefly a separate github-internals.md (deleted again) - how the
agent's gh/git-push actually authenticate, and the notification
poller's internals. Reverted the two cross-references + the
docs/README.md entry back to pointing at github.md now that the
content lives there again.
Added a short CLAUDE.md note recording the pattern per mara's ask,
including the one real caveat damocles flagged: <details> only
collapses in a rendered browser, a raw-text read (cat, the Read tool)
still sees everything, same as today.
An option name that is absent reads exactly like one that is present —
the reader has no way to tell, and the failure surfaces as a nixos eval
error at deploy time. Two namespace reorgs the prose never followed:
`forge.*` moved under `swarm.`, and several flat paths under `c0re.`.
docs/networking/gateway.md:335 services.hyperhive.forge = { … }
docs/networking/gateway.md:367 forge.rootUrl → swarm.forge.rootUrl
docs/networking/gateway.md:389 frontend → c0re.frontend
docs/tools/hivectl.md:193 agentCpuQuota → c0re.agentCpuQuota
docs/tools/hivectl.md:317 forge.behindGateway → swarm.forge.behindGateway
docs/agent-lifecycle/persistence.md:404 agentCpuQuota → c0re.agentCpuQuota
docs/web-ui/dashboard.md:610 modelPrices → c0re.modelPrices
docs/web-ui/dashboard.md:789 forge.publicUrl → swarm.forge.publicUrl
Both `gateway.md` blocks are fenced nix an operator is meant to paste.
Two sites needed more than a rename:
dashboard.md:781 cited `gateway.enable` as a live opt-in. It was
removed; `gateway.md:269` already says the gateway is unconditional,
and `hive-c0re/environment.nix:208` sets HIVE_GATEWAY_ENABLED = "1"
with no condition. Rewritten to say so, keeping the flag-absent
fallback the dashboard still implements.
turn-loop/config.md:4 attributed the page's knobs to
`services.hyperhive.agents.<name>`. No such option exists — every
knob the page documents is agent-side `hyperhive.*`, set in the
agent's own agent.nix.
Measured against the module system rather than a grep: `nix build
.#docs` renders nixosOptionsDoc over the evaluated trees (279 option
paths). That arm alone is wrong — it omits `internal = true` options
and called `gateway.localNames` and `swarm.peerHives` dead, so a second
arm requires the literal path to appear in nix/ and rescues exactly
those. Four remaining unresolved citations are correct prose saying
`gateway.enable` / `network.{enable,isolateContainers,upstreamDns}`
were removed, and are left alone.
Closes#3948
docs/tools/swarmctl-cli.md is rendered by `swarmctl markdown-docs`, and
nix/checks.nix's swarmctl-docs-fresh check diffs the committed copy
against fresh binary output. Enabling prettier on markdown rewrote its
list bullets and footer indentation, which no regeneration can settle:
formatting it fails the freshness check, not formatting it fails
treefmt. .prettierignore already carried hivectl-cli.md for exactly
this reason; swarmctl's doc was added later and the entry was not.
Restore the file to its generated bytes, list it alongside hivectl's,
and state the invariant where the next CLI doc gets added.
`docs/scheduler/ci.md` told the reader to run `nix develop -c treefmt`.
treefmt is not on the devshell PATH — `nix develop -c which treefmt` exits
1 while `which cargo` resolves, so the check discriminates and the
documented command has never worked from that shell.
The same line enumerated the formatter set as "rustfmt + nixfmt + taplo",
which was already wrong before this branch: keep-sorted has been enabled
all along, and the commit before this adds prettier. Replaced the list
with a pointer to nix/treefmt.nix so it can't drift out of sync again.
Also drops "+ rustfmt" from the nix-flake-check row: rustfmt is one of
treefmt's formatters, not a separate thing that runs alongside it.
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.
`docs/` was reorganised into topic subdirectories and the references to it
were not moved with it. Thirteen distinct paths and three relative links no
longer resolved, spread across nix, css, html, js, markdown and
.prettierignore — a stale pointer is not a markdown problem, it is wherever
someone wrote a path down.
Each mapping resolved to exactly one target. `docs/matrix.md` was the sole
ambiguous basename: .prettierignore lists `docs/tools/matrix.md` separately
and that entry still resolves, so the stale one is the integrations doc.
The three relative links were each one `../` too deep — from `<crate>/src/`
two levels reach the repo root. `hive-agent/src/login.rs` already had the
correct form, in the same crate, at the same depth.
.prettierignore is repointed rather than dropped, though nothing in the tree
runs prettier: no treefmt entry, no CI job, no package script. Whether that
config should exist at all is a separate question from whether it names
files that do.
`docs/swarm/ca.md` said it under *Constraints on the material* and again
under *Distributing the root*; this module's header said it a third time,
directly above a line already pointing at that doc.
Measured: 6-gram intersection between the module's comments and the doc,
32 -> 6. The sentence now appears once in the tree.
Module header keeps what a reader editing this file cannot see from the
config: that one root makes cross-hive trust O(1), that the two
provisioning modes differ only in who writes the artifacts, and the ⚠️
that both root key and root cert are runtime files — the cert as a
consequence, and that one costs something real, since nothing whose trust
store is built at build time can name it. Everything behind those is a
`docs/x.md::Section` pointer with the heading checked to exist.
Dropped the O(n²)-per-peer-pinning story: the live reason is that one root
is O(1), and how it used to be configured is history.
In the doc, *Distributing the root* now links to *Constraints on the
material* rather than restating it. Intra-doc `#anchor` links are already
the convention there — 85 of them across `docs/`, 8 in `gateway.md` alone.
Comments + one doc paragraph; no behaviour change. Refs #3901.
`docs/swarm/sso.md` described a person in a browser. The swarm's other
callers — the telemetry collector, the queue's auth-callout responder, each
hive's agents — hold no session and follow no redirect, and nothing operator-
facing said how they authenticate. Its relying-party table is forge and
matrix, both browser surfaces.
The new section carries what `swarm-authelia.nix` was holding in comments:
one client per hive because identity belongs to the directory, the audience
being that client id rather than a parallel naming scheme, and signed rather
than opaque tokens because the collector verifies offline against
`/jwks.json` while the queue introspects.
It also states the fail-closed rule once, in the place a reader looks before
touching a vhost: an error page answers 200, and `auth_request` reads any 2xx
as access granted. That shape has now appeared three times — this module's
`/api/` prefix and both of victorialogs' routes — which is what makes it
documentation rather than a comment.
The two comment blocks those replace shrink to the part that is genuinely
local: the submodule-typing reason these clients are a definition rather than
an append, and a loud warning against folding the machine prefix back into
`/`. The security warning stays at the site; only its consequence list moves.
Comments 495 -> 465 lines. Option `description` strings are untouched: they
are the source `pkgs.nixosOptionsDoc` renders into the operator's options
reference, so trimming one would delete published documentation rather than a
duplicate.
argus caught this on review: ci.md's own content never changed (0 diff on the
move), but it went from one level under docs/ to two (docs/scheduler/ci.md),
so its `../.forgejo/workflows/ci.yml` link now needs `../../`.
Also fixes the verification script itself — it only checked link targets
ending in .md or / (a systematic blind spot for a link to any other file
type, which is exactly how this one slipped through both the grep sweep and
the link-resolution check). Re-ran the corrected version repo-wide (93
tracked .md files, matches argus's own count) — zero broken links.
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.