docs: fix capitalization losses + one text corruption from the contractions pass

Review pass on top of the previous commit (85a94671) turned up three
real issues its own vale-only verification didn't catch:

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

Re-verified with a fresh vale run: the remaining 17 hits are exactly
the union of the 14 legitimate ALL-CAPS/bold-emphasis skips from the
first commit and these 3 false positives. Full accounting in
/agents/iris/state/haiku-batches/contractions-report.md.
This commit is contained in:
iris 2026-09-07 14:56:04 +02:00 committed by mara
commit ff377b0dee
15 changed files with 47 additions and 47 deletions

View file

@ -58,7 +58,7 @@ declarations.
the PAT injected?** → [`integrations/github.md`](integrations/github.md)
(operator content up top; the `gh`/git-push + notification-poller
mechanics are in a collapsed "Implementation" section at the bottom).
- **What is `/knowledge`? How does the hive-wide knowledge repo sync,
- **What's `/knowledge`? How does the hive-wide knowledge repo sync,
and how do I contribute a document?** → [`integrations/knowledge.md`](integrations/knowledge.md).
- **What does `hivectl` do? Provisioning, gateway users, container
shells?** → [`tools/hivectl.md`](tools/hivectl.md) (the curated guide);

View file

@ -476,9 +476,9 @@ Only present on the one host running
so it survives restarts and redeploys.
- `webhook-secret` — the HMAC key the swarm's forge webhooks are signed
with. **Keep it.** it's handed to Forgejo when a hook is registered,
with. **Keep it.** It's handed to Forgejo when a hook is registered,
so replacing the file means every subsequent delivery fails
verification until the hook is re-registered with the new value. it's
verification until the hook is re-registered with the new value. It's
generated automatically on first start; there is nothing to configure.
If the file is unreadable at startup the daemon still starts and logs

View file

@ -206,7 +206,7 @@ On by default, and listens on `httpsPort` (default 443) on every vhost beside th
The issuer is a **host-held hive CA**, not a bare self-signed leaf. A host service (`hive-tls-ca.service`, from the `hive-tls` module) generates a long-lived CA (`services.hyperhive.deploy.hive-controller.tls.caValidityDays`, default ~20y) under `services.hyperhive.deploy.hive-controller.tls.stateDir` (default `/var/lib/hive-tls`), then signs a gateway **leaf** (`leafValidityDays`, default 30d) with it. `hive-gateway-self-signed-cert` then imports the leaf into nginx's state dir (`/var/lib/hive-gateway/tls/{cert,key}.pem`).
⚠️ **don't collapse that import unit into pointing nginx at the CA dir.**
⚠️ **Don't collapse that import unit into pointing nginx at the CA dir.**
It does two jobs, and skipping it has taken the gateway down in production
before. It re-modes the leaf (`hive-tls-ca` writes the key `0600
root:root`; nginx's pre-start `nginx -t` runs as the *nginx user*, so a
@ -455,7 +455,7 @@ legacy single-proxy block and all traffic is forwarded to the agent
daemon as before.
**`extraFiles`**: per-agent `hyperhive.frontend.extraFiles` are in
`mergedDist`, not in the base `cfg.frontend` dist. they're not under
`mergedDist`, not in the base `cfg.frontend` dist. They're not under
the nix-store `alias` path, so requests for them fall through
`try_files` to `@<name>_dynamic` and are served by the agent daemon
as before.
@ -542,7 +542,7 @@ The daemon hashes passwords with BCrypt (cost 12) and writes
`$2y$`-prefixed hashes that nginx accepts natively. No external
`htpasswd` binary is required.
**what's not gated:** per-agent UI routes emitted into `agents.conf`
**What's not gated:** per-agent UI routes emitted into `agents.conf`
(served under `/agent/<name>/`) inherit no auth from `/` — nginx
applies `auth_basic` per-location. Full per-agent coverage is a
follow-up.

View file

@ -236,7 +236,7 @@ to all containers — agents and service containers alike.
`HOST_ADDRESS` is the bridge gateway IP (the address part of
`HIVE_NETWORK_SUBNET`, via `lifecycle::bridge_gateway_ip` — taken verbatim
so a non-`.1` operator override still resolves to wherever the bridge
actually lives). it's **load-bearing**: nixos-container's container-side
actually lives). It's **load-bearing**: nixos-container's container-side
network setup only installs a default route (`ip route add default via
$HOST_ADDRESS`) when `HOST_ADDRESS` is non-empty. In bridge mode the
host-side address/route setup is skipped, so writing it only affects the
@ -257,7 +257,7 @@ wiring is runtime:
gateway IP) into each container's `/etc`.
- the `hyperhive-isolated-dns` oneshot (`nix/agent-modules/network.nix`), gated on that
marker, rewrites `/etc/resolv.conf` to `nameserver <gateway-ip>` at boot.
it's ordered `before` the harness (`hive-ag3nt`), the matrix daemon, and
It's ordered `before` the harness (`hive-ag3nt`), the matrix daemon, and
`tea-login` so the resolver is correct before the first DNS lookup.
**Why isolation is safe**: hive-c0re's control-plane sockets are unix

View file

@ -7,11 +7,11 @@ complete a migration.
Two things it's not, both worth stating because both are common
assumptions:
- **it's not the swarm controller**, and doesn't depend on one. it's
- **It's not the swarm controller**, and doesn't depend on one. It's
a NixOS host role: a btrfs subvolume tree, a socket-activated
receiver, and the `wg-hive` interface the swarm module already brings
up. that's why it can be deployed before any controller exists.
- **it's not a backup product.** It happens to hold the data a backup
up. That's why it can be deployed before any controller exists.
- **It's not a backup product.** It happens to hold the data a backup
would hold, and it should be operated accordingly (see
[Operating it](#operating-it)) --- but nothing in it does scheduling,
verification, or restore orchestration.
@ -56,7 +56,7 @@ services.hyperhive.swarm.snapshotStore = {
Two deliberate asymmetries in that pair, both liable to be misread as
inconsistency:
- **`address` has no default.** it's a deployment fact a pushing hive
- **`address` has no default.** It's a deployment fact a pushing hive
can't derive, and a wrong guess means streaming an agent's state at
whatever happens to answer. Unset, a push fails naming this option.
- **`port` does default** (`51821`), because it's a convention both
@ -64,7 +64,7 @@ inconsistency:
coordination, not a guess.
Note the option lives under `swarm.*` while the receiving host's lives
under `services.hyperhive.snapshotStore`. that's the distinction the
under `services.hyperhive.snapshotStore`. That's the distinction the
two namespaces carry throughout: `swarm.*` describes _the swarm_ as seen
from this host, and a bare `services.hyperhive.<service>` describes _a
role this host performs_. A store host sets both --- one to run the
@ -78,7 +78,7 @@ and no credential argument, because the mesh is the authentication.
## The mesh is the authentication
There are no certificates here, and no key material of its own. that's
There are no certificates here, and no key material of its own. That's
deliberate rather than an omission.
WireGuard's cryptokey routing already binds a peer's source address to
@ -116,7 +116,7 @@ The rule that matters:
Validation is an allowlist --- `[A-Za-z0-9_-]+` and nothing else. No
slash and no dot means neither directory traversal nor an absolute path
can survive it. it's deliberately an allowlist and not a list of
can survive it. It's deliberately an allowlist and not a list of
forbidden characters: a blocklist only ever excludes the attacks
somebody already thought of.
@ -165,7 +165,7 @@ nobody runs it day to day, so it rots and is discovered broken in
production.
⚠️ **The assumption to keep true over time:** the store host runs
nothing else. that's true on day one and quietly false the day someone
nothing else. That's true on day one and quietly false the day someone
notices the box has spare disk. Nothing in the config objects when it
stops being true.

View file

@ -100,7 +100,7 @@ container build:
`DeployWindow` — the deploy subtree's root, which holds it across every
phase below it (it declares `Resource::MetaWindow`). Two meta
mutations can therefore never interleave, so no commit lands inside another
node's staged window. it's a queue resource rather than a runtime mutex
node's staged window. It's a queue resource rather than a runtime mutex
because a resource is held by a subtree root across its whole subtree, which
a `MutexGuard` (bounded by one executor fn) can't — that's what lets a
multi-node deploy own one window. For the same reason the window must stay
@ -237,7 +237,7 @@ than to one DAG shape it happens to appear in. A **brace** is the one sanctioned
exception: a pure-resource-holder root that declares on behalf of a subtree
coordinated with itself, whose members then declare nothing.
it's forced rather than stylistic. Declaring a resource means _"I need this
It's forced rather than stylistic. Declaring a resource means _"I need this
exclusively"_, and the agent lease is single-unit — so **two siblings that both
declared it could never run concurrently.** For a subtree whose whole point is
concurrency (`Prebuild` beside the `Signal``Drain` quiesce window), declaring
@ -349,7 +349,7 @@ the event's whole job.
## Container view
`container_view.rs` maintains an in-memory snapshot of every nixos-container's
systemd service state. it's polled on coordinator startup and re-scanned after
systemd service state. It's polled on coordinator startup and re-scanned after
every lifecycle operation (spawn, rebuild, kill) so the dashboard always reflects
the actual container status without a live `nixos-container list` call on each
render.
@ -527,7 +527,7 @@ wants it.
Two things to know about the weights:
- they're **hive-wide** — unlike the caps there is no per-agent
- They're **hive-wide** — unlike the caps there is no per-agent
override in `meta/resource-limits.json`, so every agent carries the
same value and the weight does _not_ rank agents against each other.
What `80` buys is that agents yield to everything **not** on this

View file

@ -54,7 +54,7 @@ credential.** Neither tier can tell a container's genuine Claude Code stats
from anything else shaped like OTLP arriving on that port — including data
smuggled out in resource attributes on an otherwise-legitimate export.
that's a **different risk from the one the collector fixes**, and strictly
That's a **different risk from the one the collector fixes**, and strictly
smaller than what preceded it: before, every agent held the upstream credential
itself, so it could do all of the above _and_ use the token anywhere else. The
collector removes the token and keeps the pipe. Agents are inside the trust
@ -122,7 +122,7 @@ read. `0600` protects it from other containers, not from the agent itself. As
long as the direct path stays _selectable_, that hole stays selectable; an
option that can reintroduce it's a hole with extra steps.
**Why the tiers stay separate on one box.** they're not collapsed when
**Why the tiers stay separate on one box.** They're not collapsed when
co-located: an all-local hive is a statement about _where_ processes run, not
about the shape of the deployment. A boundary that disappears locally is one
the local deployment stops testing.

View file

@ -120,7 +120,7 @@ services.hyperhive.swarm.hives = {
```
One attrset describing **every** hive in the swarm, **including this
one**, keyed by that hive's `hiveName`. it's meant to be _identical on
one**, keyed by that hive's `hiveName`. It's meant to be _identical on
every host_ — write it once, share it, and each host reads it correctly
because `services.hyperhive.hiveName` says which entry is itself.
@ -138,7 +138,7 @@ evaluates cleanly points at a real machine that isn't the one you meant.
> inside a swarm comes from the swarm root ([`ca.md`](ca.md)): every
> hive chains to it, so one anchor replaces per-hive pinning entirely.
> What that genuinely drops is trusting a hive whose root this swarm
> does _not_ own — another swarm's, or one keeping its own CA. that's
> does _not_ own — another swarm's, or one keeping its own CA. That's
> a cross-swarm problem and wants a mechanism designed for it. (An
> earlier `certFingerprint` field existed for exactly that gap, pinning
> a peer's TLS leaf for hive-c0re's own peer HTTPS checks — removed
@ -183,7 +183,7 @@ hiveName = "pr1ma"; hiveName = "edge";
swarm.hives = { … }; swarm.hives = { … }; # byte-identical
```
that's the point of the shape, and it removes a class of bug rather
That's the point of the shape, and it removes a class of bug rather
than saving typing: a per-host peer list let two hosts hold _different_
facts about the same third hive — a stale endpoint, a rotated
fingerprint — with nothing to detect the disagreement. One entry per
@ -276,7 +276,7 @@ service it points at: `services.hyperhive.swarm.snapshotStore.{address,
port}` tells this hive where the swarm's `btrfs receive` endpoint is, so
`hivectl agent <name> subvol snapshot push` has somewhere to stream to.
it's genuinely swarm-scoped rather than per-peer — a swarm has exactly
It's genuinely swarm-scoped rather than per-peer — a swarm has exactly
one store, because the receiver keys destinations by _agent_ so a
migrating agent keeps one unbroken incremental chain. See
[snapshot-store.md](../networking/snapshot-store.md).
@ -303,7 +303,7 @@ still knows its own state — you just can't see it from here.
A hive publishes only once it has been given the three
`swarm.statusPublish` coordinates below. A hive that has not reads
`never_reported` — it's not broken, it just hasn'thing to say upward.
`never_reported` — it's not broken, it just has nothing to say upward.
| freshness | what to do about it |
| ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |

View file

@ -5,7 +5,7 @@ hive's own CA, and that hive CA signs the gateway leaf. A peer that
trusts the root once validates every hive in the swarm, present and
future, instead of being pinned to each one by hand.
that's the whole point of the hierarchy — it turns per-peer trust from
That's the whole point of the hierarchy — it turns per-peer trust from
O(n²) hand-pinning into one anchor per swarm.
## Two provisioning modes, one structure
@ -49,7 +49,7 @@ swarm-services leaf can, and this host signs that one only under
and every client sees a name mismatch.
So `hive-tls` emits an eval-time **warning** naming the uncovered names.
it's deliberately not an assertion: this module can see what _it_ is
It's deliberately not an assertion: this module can see what _it_ is
able to issue, but not an operator-installed sub-CA in
`swarm.ca.stateDir`, an external ACME setup, or a certificate delivered
by any other means. If you have arranged one, the warning is expected
@ -89,7 +89,7 @@ host:
scp /var/lib/swarm-ca/root.pem <peer-host>:/var/lib/swarm-ca/root.pem
```
that's the point of the hierarchy: the root is installed **once per
That's the point of the hierarchy: the root is installed **once per
swarm host**, not once per peer, so a hive joining later needs no edit on
the hives already running. A hive whose cert already chains to a public
CA has nothing to install — `peer-config` says so instead.
@ -98,7 +98,7 @@ Handing a peer this hive's own `ca.pem` would not work even as a
one-off: once a hive CA is an intermediate under the swarm root, it's
no longer something a verifier can build a chain _to_ — OpenSSL won't
terminate a chain at a trusted non-self-signed certificate without
`-partial_chain`. that's why the tool distributes the root, not a
`-partial_chain`. That's why the tool distributes the root, not a
per-hive file.
## Adopting the hierarchy on an existing hive
@ -117,7 +117,7 @@ Adoption happens by itself, once. `hive-tls-ca.service` notices that
`ca-previous.pem`, and re-issues under the root; the next leaf is signed
by the new CA.
it's safe to automate here precisely because this is the
It's safe to automate here precisely because this is the
all-on-one-host shape: every consumer is on this box, so "when will they
have refreshed" is knowable rather than guessed.
@ -147,7 +147,7 @@ build log isn't something anyone reads twice.
To keep the current CA on purpose — a hive that deliberately stays
outside the hierarchy, or one mid-migration — `touch` the marker file
named in the message. that's a decision, and it's recorded as one.
named in the message. That's a decision, and it's recorded as one.
A hive with **no** root configured at all isn't affected by any of
this: it self-signs exactly as it always has.

View file

@ -19,7 +19,7 @@ so the store is a perfectly good place for it.
**The generator and the reader are usually in different containers.** They share
the host's network namespace, which makes them feel co-located, but their
filesystem roots are separate. that's why delivery is a **host-side copy rather
filesystem roots are separate. That's why delivery is a **host-side copy rather
than a bind mount**: `nixos-container` refuses to start when a bind source is
missing, and a secret minted on another container's first boot doesn't exist
yet. Binding it would make one container wait on a file that waits on a
@ -59,7 +59,7 @@ neither is a renaming of the other.
| the secret store's unseal material | the HSM/TPM under `deploy.bao.seal = "pkcs11"`; openbao itself under `"shamir"` | in the token; or held by whoever ran `bao operator init`, which is what `"shamir"` means and why it's stated rather than inferred | n/a — only the host running the store seals anything |
The three keys authelia mints for itself are generated in-container precisely
because nothing outside that container ever reads them. **that's the test worth
because nothing outside that container ever reads them. **That's the test worth
applying to any secret added here** — and the client secret's plaintext half is
the one row that fails it, which is the entire reason a delivery step exists.
@ -80,7 +80,7 @@ secret isn't the container that reads it.
The copy is only made when authelia is enabled **on this host** and something
published is being scraped; otherwise no secret is needed and none is placed.
⚠️ **don't read that delivery unit as the only way this collector is fed.**
⚠️ **Don't read that delivery unit as the only way this collector is fed.**
Whether it authenticates follows the _credential_, never another service's
placement: a swarm collector may run on a host that holds neither store and no
authelia, and then the secret is an ordinary operator-provided file named in
@ -108,12 +108,12 @@ admit anyone to the queue, so it belongs wherever the responder runs and nowhere
else.
A hive that sets neither the public keys nor `autoGenerateCallout` fails at
eval, naming the option it wants. that's deliberate: a queue that started
eval, naming the option it wants. That's deliberate: a queue that started
without them would accept `CONNECT {"user":"auth"}` from anyone sharing the
host's network namespace, and nothing would look wrong until somebody connected.
**All four or none** — the seed paths are required too, not just the public
keys. they're two halves of the same pair: the server verifies with the public
keys. They're two halves of the same pair: the server verifies with the public
half, the responder signs with the private one. Supplying only the public keys
used to pass eval and leave the queue with an auth-callout nobody answers, which
refuses every client rather than degrading — and a refusal reaches the client as
@ -163,7 +163,7 @@ internal CA deletes that file and names its own paths in
`deploy.bao.serverCertFile` / `clientCaFile`; the store itself has no opinion.
A hive that reads from a store on **another** machine names the reader's half —
`clientCertFile`, `clientKeyFile`, `serverCaFile` — and places that leaf by hand.
it's the one credential that can't come out of the store, being what opens it;
It's the one credential that can't come out of the store, being what opens it;
everything else a hive needs does.
## The constraint that decides where the root lives

View file

@ -11,7 +11,7 @@ services.hyperhive.deploy.allSwarmServices = true;
**`deploy.allSwarmServices` is what "the swarm's shared services run
here" means: every once-per-swarm service that's _optional_ takes its
`enable` from it.** that's the whole rule, stated once — the per-service
`enable` from it.** That's the whole rule, stated once — the per-service
sections below don't repeat it, so a service that stops deriving is a
visible difference rather than one more paragraph saying the same thing.

View file

@ -57,7 +57,7 @@ between "an operator's page" and "anyone with a session".
## What it costs to be reachable
The apex is published to the hive's resolver like every other swarm
service, so agent containers can resolve it. that's deliberate and it's
service, so agent containers can resolve it. That's deliberate and it's
not a hole: **reachability isn't the access control here.** An agent
that resolves the name and connects still has no operator session, and
the subrequest denies it.

View file

@ -110,7 +110,7 @@ agent's container uid/gid** — `o=--x` is traverse without listing, and
both sockets are `0666`, which is all a dialer needs.
**Ownership is declared, not repaired.** The tmpfiles.d entry written by
`SyncAgentTmpfiles` names the uid/gid directly. don't add a chown
`SyncAgentTmpfiles` names the uid/gid directly. Don't add a chown
alongside it: `d` re-applies on every boot _and_ every agent
spawn/destroy, so ownership set afterwards is reverted the next time any
agent changes — which is exactly how this dir spent a long time at

View file

@ -5,7 +5,7 @@
The sections below document specific mechanisms (the state-file endpoint,
nixbld isolation, privilege separation). This section frames the model they
serve: **what hyperhive defends, what it deliberately doesn't, and where the
operator is accepting risk.** it's the reference for "is it safe to give an
operator is accepting risk.** It's the reference for "is it safe to give an
agent capability X?".
### The trust boundary is the container, not credential storage
@ -66,7 +66,7 @@ checkpoints**, not about sandboxing the agent from its own tools:
merges restricted to the operators team + a required operators-team approval
(`apply_operator_branch_protection` / the config-repo equivalent) — and an
agent (a write collaborator, not a repo admin) can neither change those
settings nor merge its own PR. it's **not** set up for external VCS (GitHub
settings nor merge its own PR. It's **not** set up for external VCS (GitHub
etc.), though — there, operator-merge is process + accepted risk, not a
technical control.
- **Approvals** — config changes, schedule additions, and other
@ -161,7 +161,7 @@ services — including the core dashboard at `127.0.0.1:<dashboard_port>`, which
has no application-layer auth of its own (see [`docs/scheduler/ci.md`](../scheduler/ci.md)). The `0600`
token policy bounds file reads; network isolation bounds network reach.
**what's NOT exposed**:
**What's NOT exposed**:
- `/home/<name>/.claude/` — mode `0700`, owned by the per-agent
user `<name>`. nixbld users can't read it.
@ -171,7 +171,7 @@ token policy bounds file reads; network isolation bounds network reach.
can't read it.
**Policy**: all credential files written to agent state directories MUST be mode
`0600` or stricter. don't create world-readable secret files in agent state dirs.
`0600` or stricter. Don't create world-readable secret files in agent state dirs.
### Long-term fix

View file

@ -852,7 +852,7 @@ life). Two consequences for anything rendering it:
string; don't switch on specific values. `restarting` in
particular no longer exists, because no node kind is unique to a
restart.
- it's **not** exclusively operator-initiated, and **not** limited
- It's **not** exclusively operator-initiated, and **not** limited
to rebuild-shaped work — `running_transients()` on the backend is
a status-only test (any `Running` node whose payload names a
non-empty agent lights a pill), so work the operator never