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).
This commit is contained in:
iris 2026-09-20 01:51:20 +02:00 committed by mara
commit c985702bb0
2 changed files with 55 additions and 55 deletions

View file

@ -18,7 +18,7 @@ build .#docs-swarm` / `.#docs-deploy` or the website's `/options/swarm.html`
own `services.hyperhive.domain` DNS name and its own set of agent own `services.hyperhive.domain` DNS name and its own set of agent
containers. containers.
- **swarm** — one or more hives whose operators have declared them - **swarm** — one or more hives whose operators have declared them
as peers. Agents can be qualified as `agent@hive-domain`. as peers. You can qualify an agent as `agent@hive-domain`.
- **peer hive** — any hive in `services.hyperhive.swarm.hives` other - **peer hive** — any hive in `services.hyperhive.swarm.hives` other
than this one. Peers are _derived_, not declared: the directory lists than this one. Peers are _derived_, not declared: the directory lists
every hive including yourself, and `hiveName` says which one you are. every hive including yourself, and `hiveName` says which one you are.
@ -134,7 +134,7 @@ because `services.hyperhive.hiveName` says which entry is itself.
Empty (the default) means this host isn't in a swarm. Once non-empty it Empty (the default) means this host isn't in a swarm. Once non-empty it
**must** contain an entry for `hiveName`; eval fails naming the missing **must** contain an entry for `hiveName`; eval fails naming the missing
hive. That assertion is load-bearing rather than pedantic — "my peers" hive. That assertion is load-bearing rather than pedantic — "my peers"
is derived as _everything that isn't me_, so a directory that doesn't comes from _everything that isn't me_, so a directory that doesn't
contain you derives every hive as a peer and you peer with yourself. contain you derives every hive as a peer and you peer with yourself.
`domain` defaults to `<name>.<swarm.domain>`, the convention every hive `domain` defaults to `<name>.<swarm.domain>`, the convention every hive
@ -161,7 +161,7 @@ addressed by something else.
directory and serves it at `GET /api/hives`; `swarm-ui`'s overview directory and serves it at `GET /api/hives`; `swarm-ui`'s overview
page renders it (`docs/swarm/ui.md`). This is the operator-facing page renders it (`docs/swarm/ui.md`). This is the operator-facing
"what hives exist" surface — a per-hive dashboard "peer hives" "what hives exist" surface — a per-hive dashboard "peer hives"
display existed here once and was removed in favour of it. display existed here once; it no longer exists, in favour of this.
2. **Matrix federation** — when `matrix.enable` is on, tuwunel 2. **Matrix federation** — when `matrix.enable` is on, tuwunel
federates with the peer's matrix server (discovered via the peer's federates with the peer's matrix server (discovered via the peer's
@ -170,12 +170,12 @@ addressed by something else.
**container's** trust bundle, independent of this directory. **container's** trust bundle, independent of this directory.
⚠️ **That container currently trusts no swarm-internal CA**, so a ⚠️ **That container currently trusts no swarm-internal CA**, so a
self-signed gateway certificate doesn't federate. The swarm root self-signed gateway certificate doesn't federate. You can't list the
can't be listed there: `security.pki.certificateFiles` is swarm root there: `security.pki.certificateFiles` is
read when the system is _built_, and the root is a runtime file (its read when the system is _built_, and the root is a runtime file (its
key must never enter the store), so there is no build-time name for key must never enter the store), so there is no build-time name for
it. Bridging that needs a runtime mechanism and is tracked as its own it. Bridging that needs a runtime mechanism; a separate issue tracks
issue. Until then, federation needs CA-issued certs (ACME). See it. Until then, federation needs CA-issued certs (ACME). See
`docs/integrations/matrix.md` for federation firewall + TLS requirements. `docs/integrations/matrix.md` for federation firewall + TLS requirements.
3. **WireGuard mesh** (optional) — `deploy.wireguard.enable` reads each 3. **WireGuard mesh** (optional) — `deploy.wireguard.enable` reads each
@ -281,7 +281,7 @@ is required. Out of scope for v0.
## Snapshot store ## Snapshot store
One further option lives in this namespace but is documented with the One further option lives in this namespace but its docs live with the
service it points at: `services.hyperhive.swarm.snapshotStore.{address, service it points at: `services.hyperhive.swarm.snapshotStore.{address,
port}` tells this hive where the swarm's `btrfs receive` endpoint is, so port}` tells this hive where the swarm's `btrfs receive` endpoint is, so
`hivectl agent <name> subvol snapshot push` has somewhere to stream to. `hivectl agent <name> subvol snapshot push` has somewhere to stream to.
@ -296,8 +296,8 @@ migrating agent keeps one unbroken incremental chain. See
`services.hyperhive.deploy.swarm-controller.enable` runs the `swarm-controller` `services.hyperhive.deploy.swarm-controller.enable` runs the `swarm-controller`
daemon on this host. **Off by default and deliberately not derived from daemon on this host. **Off by default and deliberately not derived from
`services.hyperhive.enable`**: a swarm has one controller, so enabling it `services.hyperhive.enable`**: a swarm has one controller, so enabling it
is a statement about swarm topology, not about whether hyperhive is is a statement about swarm topology, not about whether the operator has
installed. Every hive runs `hive-c0re` (the agents on that host); one installed hyperhive. Every hive runs `hive-c0re` (the agents on that host); one
hive additionally runs this (what's true across hives). hive additionally runs this (what's true across hives).
What it serves, why it's a unix socket rather than a port, and the What it serves, why it's a unix socket rather than a port, and the
@ -380,8 +380,8 @@ with the credential itself and aren't configurable.
On a host that runs the queue, `agentNatsUrl` defaults to On a host that runs the queue, `agentNatsUrl` defaults to
`nats://<network.bridgeIp>:<swarm.nats.port>`, which is the only address that `nats://<network.bridgeIp>:<swarm.nats.port>`, which is the only address that
works from inside a container: the port is opened on the bridge interface and works from inside a container: the firewall opens the port on the bridge
nowhere else. ⚠️ **Never a loopback address here** — the hive's own interface and nowhere else. ⚠️ **Never a loopback address here** — the hive's own
`statusPublish.natsUrl` is loopback and correct, because `hive-c0re` shares the `statusPublish.natsUrl` is loopback and correct, because `hive-c0re` shares the
host's network namespace. An agent doesn't, so `127.0.0.1` reaches the agent. host's network namespace. An agent doesn't, so `127.0.0.1` reaches the agent.
@ -391,7 +391,7 @@ above, plus `HIVE_AGENT_OIDC_CLIENT_SECRET_FILE` and
`HIVE_AGENT_OIDC_CLIENT_ID_FILE`, which point into the unit's own credentials `HIVE_AGENT_OIDC_CLIENT_ID_FILE`, which point into the unit's own credentials
directory. The last two come from the delivered credential rather than from directory. The last two come from the delivered credential rather than from
config — see [`secrets.md`](secrets.md#hive-level--one-of-each-per-hive) for config — see [`secrets.md`](secrets.md#hive-level--one-of-each-per-hive) for
how it gets there. A hive that hasn't been given the queue's address for its how it gets there. A hive lacking the queue's address for its
agents sets none of the four and each agent logs that it has none; a half-set agents sets none of the four and each agent logs that it has none; a half-set
environment logs an error and the harness keeps serving. environment logs an error and the harness keeps serving.
@ -404,10 +404,10 @@ agent talks about itself here and reads nothing. Rows aren't retained — a
subscriber that wasn't listening missed them, the same as on the agent's own subscriber that wasn't listening missed them, the same as on the agent's own
live stream. live stream.
A row too large for the queue's `max_payload` would be refused outright and The queue would refuse a row too large for its `max_payload` outright and
take the connection down with it, so the harness drops such a row's body before take the connection down with it, so the harness drops such a row's body before
sending and leaves a marker in its place; the summary, level and icon still sending and leaves a marker in its place; the summary, level and icon still
arrive. A row that's too large even without its body is logged and skipped. arrive. The harness logs and skips a row that's too large even without its body.
The second thing an agent publishes is its **turn-state header**, on The second thing an agent publishes is its **turn-state header**, on
`$SWARM.agent-state.<hive>.<agent>` — same shape of subject, same grant `$SWARM.agent-state.<hive>.<agent>` — same shape of subject, same grant

View file

@ -1,7 +1,7 @@
# Swarm secrets: what exists, and where each one lives # Swarm secrets: what exists, and where each one lives
A swarm's credentials are generated in three different places and read in a A swarm generates its credentials in three different places and reads them
fourth, so "where does this file go" has a different answer per deployment. in a fourth, so "where does this file go" has a different answer per deployment.
This page is that answer, one row per secret. This page is that answer, one row per secret.
Two rules run through all of it. Two rules run through all of it.
@ -14,7 +14,7 @@ than a convenience.
The rule is about what must stay secret, not about credentials generally. The rule is about what must stay secret, not about credentials generally.
**Public material is a value**: a certificate, or a public nkey like **Public material is a value**: a certificate, or a public nkey like
`deploy.nats.calloutUserPublicKey`, is published to every client that connects, `deploy.nats.calloutUserPublicKey`, reaches every client that connects,
so the store is a perfectly good place for it. so the store is a perfectly good place for it.
**The generator and the reader typically live in different containers.** They share **The generator and the reader typically live in different containers.** They share
@ -34,16 +34,16 @@ file being there_ --- the two lists don't line up one-to-one, and
neither is a renaming of the other. neither is a renaming of the other.
| topology | what it means | who places secrets | | topology | what it means | who places secrets |
| ------------------ | ---------------------------------------------------------- | --------------------------------------------------------------------------- | | ------------------ | ---------------------------------------------------------- | -------------------------------------------------------------------------- |
| **all-local** | one host runs the swarm's shared services and its own hive | nobody — each secret is generated where it's read, or copied by a host unit | | **all-local** | one host runs the swarm's shared services and its own hive | nobody — the reader itself generates each secret, or a host unit copies it |
| **swarm-managed** | the swarm's services run on a host with `swarmctl` | `swarmctl` writes what it owns; the rest is still generated in place | | **swarm-managed** | the swarm's services run on a host with `swarmctl` | `swarmctl` writes what it owns; the rest is still generated in place |
| **hive elsewhere** | a hive that federates with a swarm it doesn't host | the operator provides the file and names it in config | | **hive elsewhere** | a hive that federates with a swarm it doesn't host | the operator provides the file and names it in config |
## Swarm-level — one of each per swarm ## Swarm-level — one of each per swarm
| secret | generated by | lives at | hive elsewhere | | secret | generated by | lives at | hive elsewhere |
| --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| swarm root CA cert | `swarm-ca.nix` first-boot unit, when `autoConfigure` is set | `/var/lib/swarm-ca/root.pem` | operator copies the **cert** in; it's public | | swarm root CA cert | `swarm-ca.nix` first-boot unit, when the operator sets `autoConfigure` | `/var/lib/swarm-ca/root.pem` | operator copies the **cert** in; it's public |
| swarm root CA key | same unit | `/var/lib/swarm-ca/root-key.pem`, `0600` | stays on whichever host holds it — see the constraint below | | swarm root CA key | same unit | `/var/lib/swarm-ca/root-key.pem`, `0600` | stays on whichever host holds it — see the constraint below |
| swarm-services sub-CA (cert + key) | `swarm-ca.nix`, signed by the root | `/var/lib/swarm-ca/services-ca{,-key}.pem` | issued where the root lives | | swarm-services sub-CA (cert + key) | `swarm-ca.nix`, signed by the root | `/var/lib/swarm-ca/services-ca{,-key}.pem` | issued where the root lives |
| authelia session, JWT and storage-encryption keys | authelia's first-boot unit, in-container | `/var/lib/authelia-swarm/{session,jwt,storage-encryption}.key` | generated in place; nothing outside that container reads them | | authelia session, JWT and storage-encryption keys | authelia's first-boot unit, in-container | `/var/lib/authelia-swarm/{session,jwt,storage-encryption}.key` | generated in place; nothing outside that container reads them |
@ -51,11 +51,11 @@ neither is a renaming of the other.
| authelia OIDC issuer key (RSA) | same unit | `/var/lib/authelia-swarm/oidc-issuer.key` | same — relying parties verify against the **public** half at `/jwks.json` | | authelia OIDC issuer key (RSA) | same unit | `/var/lib/authelia-swarm/oidc-issuer.key` | same — relying parties verify against the **public** half at `/jwks.json` |
| OIDC client secret, plaintext half | `authelia crypto hash generate --random` | `/var/lib/authelia-swarm/oidc-clients/<id>.secret` | operator provides the file and names it in whichever option reads it — `sso.clientSecretFile` for a service, `otel.clientSecretFile` for the hive's telemetry collector | | OIDC client secret, plaintext half | `authelia crypto hash generate --random` | `/var/lib/authelia-swarm/oidc-clients/<id>.secret` | operator provides the file and names it in whichever option reads it — `sso.clientSecretFile` for a service, `otel.clientSecretFile` for the hive's telemetry collector |
| OIDC client secret, digest half | the same mint | `oidc-clients/<id>.digest` | authelia's own half; merged at runtime via `settingsFiles` | | OIDC client secret, digest half | the same mint | `oidc-clients/<id>.digest` | authelia's own half; merged at runtime via `settingsFiles` |
| the swarm collector's copy of its OIDC secret | `swarm-bao-otel-oidc.service` reads it out of the swarm secret store, **on every host that runs the collector and holds a store identity** | `/var/lib/swarm-otel-oidc/<id>.secret` inside the `swarm-otel` container | same unit, same path — one route, co-located or not. A collector with no store identity is left with `clientSecretFile == null`, its already-supported unauthenticated-push degrade — see below | | the swarm collector's copy of its OIDC secret | `swarm-bao-otel-oidc.service` reads it out of the swarm secret store, **on every host that runs the collector and holds a store identity** | `/var/lib/swarm-otel-oidc/<id>.secret` inside the `swarm-otel` container | same unit, same path — one route, co-located or not. A collector with no store identity has `clientSecretFile == null`, its already-supported unauthenticated-push degrade — see below |
| Grafana's copy of its OIDC secret | `swarm-bao-grafana-oidc.service` reads it out of the swarm secret store, **on every host that runs Grafana** | `/var/lib/grafana-oidc/<id>.secret` inside the `swarm-grafana` container | same unit, same path — one route, co-located or not. Nothing for an operator to place beyond this host's store leaf, see below | | Grafana's copy of its OIDC secret | `swarm-bao-grafana-oidc.service` reads it out of the swarm secret store, **on every host that runs Grafana** | `/var/lib/grafana-oidc/<id>.secret` inside the `swarm-grafana` container | same unit, same path — one route, co-located or not. Nothing for an operator to place beyond this host's store leaf, see below |
| authelia subject store | `swarmctl` and `swarm-authelia-bridge` | `users.yml` — one file, read and written by both | `swarmctl`, on the host that runs authelia | | authelia subject store | `swarmctl` and `swarm-authelia-bridge` | `users.yml` — one file, read and written by both | `swarmctl`, on the host that runs authelia |
| wireguard private key | **the operator**`wg genkey` | whatever `deploy.wireguard.privateKeyFile` names | always operator-provided; nothing generates this for you | | wireguard private key | **the operator**`wg genkey` | whatever `deploy.wireguard.privateKeyFile` names | always operator-provided; nothing generates this for you |
| queue auth-callout nkeys (user seed + account seed) | `swarm-nats-callout-keys` first-boot unit, when `deploy.nats.autoGenerateCallout` is set | `/var/lib/swarm-nats-callout/{callout-user,issuer}.seed`, `0600` | operator mints both with `nk` and names them in `deploy.nats.calloutUserSeedFile` / `deploy.nats.calloutIssuerSeedFile` | | queue auth-callout nkeys (user seed + account seed) | `swarm-nats-callout-keys` first-boot unit, when the operator sets `deploy.nats.autoGenerateCallout` | `/var/lib/swarm-nats-callout/{callout-user,issuer}.seed`, `0600` | operator mints both with `nk` and names them in `deploy.nats.calloutUserSeedFile` / `deploy.nats.calloutIssuerSeedFile` |
| the secret store's own contents | openbao, on first `bao operator init`**an operator action, not a unit** | inside the `swarm-bao` container, at its own `/var/lib/openbao`, kept across rebuilds by `ephemeral = false`. ⚠️ **Not a host path**: `nixos-container destroy swarm-bao` takes the raft data with it, so back up the container's tree, not `/var/lib/`. Only the store's TLS material (`/var/lib/swarm-bao-tls`) and its PKCS11 token (`/var/lib/swarm-bao-token`) are host-level | n/a — there is one store; a hive elsewhere is a _client_ of it and holds none of this | | the secret store's own contents | openbao, on first `bao operator init`**an operator action, not a unit** | inside the `swarm-bao` container, at its own `/var/lib/openbao`, kept across rebuilds by `ephemeral = false`. ⚠️ **Not a host path**: `nixos-container destroy swarm-bao` takes the raft data with it, so back up the container's tree, not `/var/lib/`. Only the store's TLS material (`/var/lib/swarm-bao-tls`) and its PKCS11 token (`/var/lib/swarm-bao-token`) are host-level | n/a — there is one store; a hive elsewhere is a _client_ of it and holds none of this |
| 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 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 |
@ -68,8 +68,8 @@ Two telemetry collectors exist, and they land on opposite sides of that test.
The **hive's** collector needs no delivery step. It authenticates to the swarm's The **hive's** collector needs no delivery step. It authenticates to the swarm's
collector as its own hive, and it's a host unit rather than a container, so on collector as its own hive, and it's a host unit rather than a container, so on
an all-local swarm it reads authelia's file where it lies and no second copy is an all-local swarm it reads authelia's file where it lies and nothing makes a
made. On any other topology it's an ordinary "operator provides the file" second copy. On any other topology it's an ordinary "operator provides the file"
case — see `services.hyperhive.otel.clientSecretFile`. case — see `services.hyperhive.otel.clientSecretFile`.
The **swarm's** collector does need one, and its delivery route is the fourth The **swarm's** collector does need one, and its delivery route is the fourth
@ -77,8 +77,8 @@ store reader — the same shape as Grafana's, described in full below. In short:
`swarm-bao-otel-oidc.service` reads `swarm/services/<client-id>/oidc/client` `swarm-bao-otel-oidc.service` reads `swarm/services/<client-id>/oidc/client`
out of the store on every host that runs the collector and holds a store out of the store on every host that runs the collector and holds a store
identity, whether authelia is a network away or in the container next door. identity, whether authelia is a network away or in the container next door.
The client it reads for is registered unconditionally, the same call The same call `glue-grafana-oidc-client.nix` makes for Grafana's registers
`glue-grafana-oidc-client.nix` makes for Grafana's: authelia refuses a the client it reads for unconditionally: authelia refuses a
bearer-authz client with no audience, and the push audiences supply one even bearer-authz client with no audience, and the push audiences supply one even
for a collector with nothing published to scrape. for a collector with nothing published to scrape.
@ -90,9 +90,9 @@ there. `swarm-bao-otel-oidc.service` therefore doesn't render at all without
`deploy.bao.clientCertFile` / `clientKeyFile`, the same choice `deploy.bao.clientCertFile` / `clientKeyFile`, the same choice
`glue-matrix-bao-token.nix` and `glue-queue-agent-credential.nix` make for `glue-matrix-bao-token.nix` and `glue-queue-agent-credential.nix` make for
their own optional readers, rather than the hard assertion `swarm-grafana.nix` their own optional readers, rather than the hard assertion `swarm-grafana.nix`
uses for its non-optional one. The secret can still be delivered by hand, at uses for its non-optional one. The operator can still deliver the secret by
`services.hyperhive.deploy.swarm-otel.clientSecretFile`, on a collector this hand, at `services.hyperhive.deploy.swarm-otel.clientSecretFile`, on a
unit never reaches. collector this unit never reaches.
### Minting the queue's callout nkeys ### Minting the queue's callout nkeys
@ -178,8 +178,8 @@ Instead it keeps the value it minted under its `StateDirectory`,
`/var/lib/swarm-secret-publish/matrix-appservice-token/<hive>` (`0700` dir, `/var/lib/swarm-secret-publish/matrix-appservice-token/<hive>` (`0700` dir,
`0600` file), and mints only when that file is missing or empty. The `put` `0600` file), and mints only when that file is missing or empty. The `put`
itself runs every time: re-putting the same bytes changes nothing for any itself runs every time: re-putting the same bytes changes nothing for any
reader, while a mint whose publish failed must not be left as a token this reader, while a mint whose publish failed must not leave behind a token
host holds and no hive can reach. this host holds and no hive can reach.
Lose the state directory and the next run mints once more and republishes. Lose the state directory and the next run mints once more and republishes.
That rotates the token, which readers pick up on their next start — nothing That rotates the token, which readers pick up on their next start — nothing
@ -187,7 +187,7 @@ that already registered breaks, because the token authenticates the
appservice rather than living inside any account it created. appservice rather than living inside any account it created.
The path was `…/matrix/registration-token` while the homeserver still took a The path was `…/matrix/registration-token` while the homeserver still took a
shared registration secret; a value left at the old path is read by nothing. shared registration secret; nothing reads a value left at the old path.
The **second reader** is the agent containers' queue credential: The **second reader** is the agent containers' queue credential:
`glue-queue-agent-credential.nix` lands it as two files, the client secret and `glue-queue-agent-credential.nix` lands it as two files, the client secret and
@ -266,9 +266,9 @@ as it does for the other readers: a store that says "not here" leaves the
file alone and says so, since there is no such thing as a locally valid OIDC file alone and says so, since there is no such thing as a locally valid OIDC
client secret to stand in. client secret to stand in.
⚠️ **Service↔store mTLS is its own trust domain.** A credential you must ⚠️ **Service↔store mTLS is its own trust domain.** You can't fetch a
already hold to authenticate can't be fetched from the thing it authenticates credential you must already hold to authenticate from the thing it
you to, so the store's identity can't come from an authority the store authenticates you to, so the store's identity can't come from an authority the store
distributes — which excludes the hive CA and the swarm CA both, and has nothing distributes — which excludes the hive CA and the swarm CA both, and has nothing
to do with the gateway's HTTPS certificates either way. `glue-bao-tls.nix` to do with the gateway's HTTPS certificates either way. `glue-bao-tls.nix`
mints a CA that signs exactly two things, the store's server certificate and a mints a CA that signs exactly two things, the store's server certificate and a
@ -318,8 +318,8 @@ Whatever holds the swarm root is therefore what makes swarm-service certificates
possible at all. Two things follow: possible at all. Two things follow:
- **The root's private key is a runtime file and must never enter the nix - **The root's private key is a runtime file and must never enter the nix
store**, so nothing build-time can name it — `security.pki.certificateFiles` is store**, so nothing build-time can name it — `security.pki.certificateFiles`
read when the system is built, and is the wrong tool here. Trust reaches reads at system build time, and is the wrong tool here. Trust reaches
containers through a bind-mounted bundle assembled at boot instead. containers through a bind-mounted bundle assembled at boot instead.
- **On any topology other than all-local, placing that key is an operations - **On any topology other than all-local, placing that key is an operations
decision**, not something this module tree makes for you. A hive that hosts no decision**, not something this module tree makes for you. A hive that hosts no