docs/swarm/credentials.md: reword prose to pass vale

Fixes the Microsoft.Contractions/write-good.ThereIs/alex.Condescending/
Microsoft.Auto errors vale flagged in CI (prose-lint-errors, required):
contractions throughout, no sentences opening with 'There is', dropped
the 'auto-mints' hyphenation and the 'easily' condescension flag, and
renamed the credentials table's 'what it is' header to 'kind' (also
flagged once styles were resynced). No technical claim or file:line
citation changed. Same fix applied to the one new line in
docs/README.md's pointer to this page.
This commit is contained in:
atlas 2026-09-16 18:51:42 +02:00
commit b2c1378fd3
2 changed files with 34 additions and 34 deletions

View file

@ -77,7 +77,7 @@ declarations.
endpoint authenticate a pushing hive?** → endpoint authenticate a pushing hive?** →
[`networking/snapshot-store.md`](networking/snapshot-store.md). [`networking/snapshot-store.md`](networking/snapshot-store.md).
- **Who mints each credential, who reads it, and how does it rotate — and - **Who mints each credential, who reads it, and how does it rotate — and
where is that shape headed?** → [`swarm/credentials.md`](swarm/credentials.md) where's that shape headed?** → [`swarm/credentials.md`](swarm/credentials.md)
(current state, target state, and the progressive-enhancement rule); (current state, target state, and the progressive-enhancement rule);
[`swarm/secrets.md`](swarm/secrets.md) for where each file lives today. [`swarm/secrets.md`](swarm/secrets.md) for where each file lives today.

View file

@ -1,17 +1,17 @@
# Credentials: current state, target state, and the rule in between # Credentials: current state, target state, and the rule between the two
Where [`secrets.md`](secrets.md) is a map of every file a swarm holds, this Where [`secrets.md`](secrets.md) is a map of every file a swarm holds, this
page is the **direction of travel**: what the credential shape is today, what page is the **direction of travel**: what the credential shape is today, what
it is meant to become, and the test a change has to pass to count as movement it's on track to become, and the test a change has to pass to count as movement
toward it rather than away. toward it rather than away.
It is written to be the thing a discussion points at. **When the migration It's the thing a discussion should point at. **When the migration
below is complete, this page supersedes the old credential docs** — `secrets.md` below is complete, this page supersedes the old credential docs** — `secrets.md`
gets deleted and this file moves into its place. Until then the two coexist and gets deleted and this file moves into its place. Until then the two coexist and
neither is wrong: that one says where a file is, this one says whether it should neither is wrong: that one says where a file is, this one says whether it should
be a file at all. be a file at all.
Two conventions carry over unchanged, because the target does not relax them. Two conventions carry over unchanged, because the target doesn't relax them.
**A secret is a path, never a value.** Every option carrying key material or a **A secret is a path, never a value.** Every option carrying key material or a
token takes a file path, because a literal in a nix expression lands in the nix token takes a file path, because a literal in a nix expression lands in the nix
@ -25,10 +25,10 @@ is about those.
## Current state ## Current state
Four credential families live in the swarm's secret store, plus the mTLS Four credential families live in the swarm's secret store, plus the mTLS
identity that opens it. That fifth row is not a stored secret — it is the identity that opens it. That fifth row isn't a stored secret — it's the
credential that cannot be, being what authenticates the reader to the store. credential that can't be, being what authenticates the reader to the store.
| store path | what it is | minter | writer | reader | renewal | persisted outside the store | | store path | kind | minter | writer | reader | renewal | persisted outside the store |
| -------------------------------------------- | ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | -------------------------------------------- | ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `swarm/agents/<agent>/matrix/<account>` | one agent's matrix account credential | `swarm-controller`, via a caller-supplied bearer token or its own `m.login.password` (`swarm-controller/src/matrix_account.rs:14-21`) | same unit, `put_matrix_account`'s `store.write` call (`swarm-controller/src/matrix_account.rs:189-197`) | `hive-c0re`'s credential worker, under the **hive's** identity (`hive-c0re/src/workers/credential.rs:33-64`) | `NONE` | **yes**`hive-priv` writes `<agent-state>/matrix-token`, `0600`, chowned to the agent (`hive-priv/src/main.rs:535-556`) | | `swarm/agents/<agent>/matrix/<account>` | one agent's matrix account credential | `swarm-controller`, via a caller-supplied bearer token or its own `m.login.password` (`swarm-controller/src/matrix_account.rs:14-21`) | same unit, `put_matrix_account`'s `store.write` call (`swarm-controller/src/matrix_account.rs:189-197`) | `hive-c0re`'s credential worker, under the **hive's** identity (`hive-c0re/src/workers/credential.rs:33-64`) | `NONE` | **yes**`hive-priv` writes `<agent-state>/matrix-token`, `0600`, chowned to the agent (`hive-priv/src/main.rs:535-556`) |
| `swarm/hives/<hive>/matrix/appservice-token` | the appservice token authenticating one hive's homeserver | `swarm-secret-publisher.nix` itself, `head -c 32 /dev/urandom` on the authelia host (`nix/host-modules/swarm-secret-publisher.nix:271-278`) | same unit (`nix/host-modules/swarm-secret-publisher.nix:288-289`) | `swarm-bao-matrix-token.service` on the hive (`nix/host-modules/glue-matrix-bao-token.nix:159-167`) | `NONE` | **yes, twice** — the publisher's own state copy (`nix/host-modules/swarm-secret-publisher.nix:185`) and the hive's token file, `0600` (`nix/host-modules/glue-matrix-bao-token.nix:180-181`) | | `swarm/hives/<hive>/matrix/appservice-token` | the appservice token authenticating one hive's homeserver | `swarm-secret-publisher.nix` itself, `head -c 32 /dev/urandom` on the authelia host (`nix/host-modules/swarm-secret-publisher.nix:271-278`) | same unit (`nix/host-modules/swarm-secret-publisher.nix:288-289`) | `swarm-bao-matrix-token.service` on the hive (`nix/host-modules/glue-matrix-bao-token.nix:159-167`) | `NONE` | **yes, twice** — the publisher's own state copy (`nix/host-modules/swarm-secret-publisher.nix:185`) and the hive's token file, `0600` (`nix/host-modules/glue-matrix-bao-token.nix:180-181`) |
@ -36,7 +36,7 @@ credential that cannot be, being what authenticates the reader to the store.
| `swarm/services/<clientId>/oidc/client` | a swarm service's own OIDC client secret, one per service | authelia, on first boot | `swarm-secret-publish.service` (`nix/host-modules/swarm-secret-publisher.nix:245-248`) | whichever host runs that service — `swarm-bao-grafana-oidc.service` (`nix/host-modules/swarm-grafana.nix:565-684`), the collector's twin (`nix/host-modules/swarm-otel.nix:685-767`) | `NONE` | **yes** — Grafana's `0400` host file (`nix/host-modules/swarm-grafana.nix:678-682`), the collector's `0400` file handed in by `LoadCredential` (`nix/host-modules/swarm-otel.nix:764-768`) | | `swarm/services/<clientId>/oidc/client` | a swarm service's own OIDC client secret, one per service | authelia, on first boot | `swarm-secret-publish.service` (`nix/host-modules/swarm-secret-publisher.nix:245-248`) | whichever host runs that service — `swarm-bao-grafana-oidc.service` (`nix/host-modules/swarm-grafana.nix:565-684`), the collector's twin (`nix/host-modules/swarm-otel.nix:685-767`) | `NONE` | **yes** — Grafana's `0400` host file (`nix/host-modules/swarm-grafana.nix:678-682`), the collector's `0400` file handed in by `LoadCredential` (`nix/host-modules/swarm-otel.nix:764-768`) |
| _(not stored)_ the store's own mTLS leaves | the certificate a host presents to reach the store at all | `swarm-bao-pki.service`, self-signed, 10-year (`nix/host-modules/glue-bao-tls.nix:114-149`) | n/a | every client reads its own leaf off disk via `BAO_CLIENT_CERT` (`swarm-secret-client/src/client.rs:12-14`) | `NONE` — signed only when absent (`nix/host-modules/glue-bao-tls.nix:128-149`) | **always, by construction** — it has no other form | | _(not stored)_ the store's own mTLS leaves | the certificate a host presents to reach the store at all | `swarm-bao-pki.service`, self-signed, 10-year (`nix/host-modules/glue-bao-tls.nix:114-149`) | n/a | every client reads its own leaf off disk via `BAO_CLIENT_CERT` (`swarm-secret-client/src/client.rs:12-14`) | `NONE` — signed only when absent (`nix/host-modules/glue-bao-tls.nix:128-149`) | **always, by construction** — it has no other form |
Four things in that table are uncomfortable, and they are the reason this page Four things in that table are uncomfortable, and they're the reason this page
exists rather than a note in the margin of the old one. exists rather than a note in the margin of the old one.
**Every stored secret is persisted as a plaintext file outside the store.** All **Every stored secret is persisted as a plaintext file outside the store.** All
@ -44,15 +44,15 @@ four, without exception. The appservice token is persisted twice: once in the
publisher's own state directory on the authelia host, and again in the token publisher's own state directory on the authelia host, and again in the token
file on the hive that reads it. file on the hive that reads it.
**Nothing rotates.** Every renewal cell reads `NONE`. There is no rotation path **Nothing rotates.** Every renewal cell reads `NONE`. No rotation path exists
for any of the four, and none for the mTLS leaves either — those are signed once for any of the four, and none for the mTLS leaves either — those are signed once
if absent and carry ten years of validity, and deleting the CA to reissue if absent and carry ten years of validity, and deleting the CA to reissue
invalidates every client certificate already trusting it. invalidates every client certificate already trusting it.
**No agent container has a store identity at all.** There is not one **No agent container has a store identity at all.** Not one
`BAO_ADDR` or `BAO_CLIENT_CERT` anywhere under `nix/agent-modules/`. An agent `BAO_ADDR` or `BAO_CLIENT_CERT` exists anywhere under `nix/agent-modules/`. An agent
never talks to the store; the hive host reads on the agent's behalf and writes never talks to the store; the hive host reads on the agent's behalf and writes
a file into the container. That is the exact inverse of the target below. a file into the container. That's the exact inverse of the target below.
**The appservice token has a second, uncoordinated minter.** `hive-matrix.nix` **The appservice token has a second, uncoordinated minter.** `hive-matrix.nix`
keeps a local first-boot mint as a fallback for an unreachable store. Two keeps a local first-boot mint as a fallback for an unreachable store. Two
@ -68,22 +68,22 @@ fetched bytes to disk.
Two per-agent credential files — the forge token and the github token Two per-agent credential files — the forge token and the github token
(`hive-priv/src/main.rs`'s `write_forge_token`/`write_github_token`) — are deliberately absent from the table. (`hive-priv/src/main.rs`'s `write_forge_token`/`write_github_token`) — are deliberately absent from the table.
They are operator-supplied and never pass through the store at all, so they are They're operator-supplied and never pass through the store at all, so they're
not credentials this page governs. Their absence is not an oversight. not credentials this page governs. Their absence isn't an oversight.
## Target state ## Target state
⚠️ **This section describes what the swarm is moving toward. None of it is a ⚠️ **This section describes what the swarm is moving toward — not what exists
description of what exists today.** Read the current-state table above for today.** Read the current-state table above for that. In particular, the first
that. In particular, the first line below is the one most easily misread as a line below is the one most often misread as a statement of fact, so it's
statement of fact, so it is stated as a target twice. stated as a target twice.
**Every host needs a store mTLS certificate. Everything else is done through **Every host needs a store mTLS certificate. Everything else is done through
the store.** That is the target. Today only the host running the store itself the store.** That's the target. Today only the host running the store itself
auto-mints leaves — for itself, and pre-emptively for a controller and a mints its own leaves automatically — for itself, and pre-emptively for a controller and a
publisher that might be co-located. For every other host, `swarm-bao.nix:529-531` publisher that might be co-located. For every other host, `swarm-bao.nix:529-531`
says it outright: this is _"the credential an operator places by hand."_ There says it outright: this is _"the credential an operator places by hand."_ No
is no automated issuance path for an ordinary hive. The target is one automated issuance path exists for an ordinary hive. The target is one
out-of-band credential per host and zero out-of-band credentials anywhere else; out-of-band credential per host and zero out-of-band credentials anywhere else;
the current state is one out-of-band credential per host **plus** every file in the current state is one out-of-band credential per host **plus** every file in
the last column of the table above. the last column of the table above.
@ -100,38 +100,38 @@ one of the four is persisted, and one of them twice.
**Per secret, the docs specify minter, reader, and renewal strategy.** Those **Per secret, the docs specify minter, reader, and renewal strategy.** Those
three columns are the contract. A secret whose renewal strategy is `NONE` is three columns are the contract. A secret whose renewal strategy is `NONE` is
allowed to exist only as an honest record of where the swarm is — it is not a allowed to exist only as an honest record of where the swarm is — it's not a
strategy, and a new secret does not get to write it. strategy, and a new secret doesn't get to write it.
## Progressive enhancement ## Progressive enhancement
The rule has two halves, and the second is the one that makes the first The rule has two halves, and the second is the one that makes the first
survivable. survivable.
**New functionality matches the target shape immediately.** There is no **New functionality matches the target shape immediately.** No legacy clause
grandfather clause for code that does not exist yet. covers code that doesn't exist yet.
**Existing functionality moves step by step** — each individual step moving **Existing functionality moves step by step** — each individual step moving
toward the target shape, never sideways and never away. A step does not have to toward the target shape, never sideways and never away. A step doesn't have to
finish the migration. It has to not undo it. finish the migration. It has to not undo it.
That is checkable, so check it. Apply these to a pull request that touches a That's checkable, so check it. Apply these to a pull request that touches a
credential: credential:
| question | a `yes` means | | question | a `yes` means |
| ----------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | | ----------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| Does it add a minter outside the swarm's existing mint path? | **block** for new functionality. For existing functionality, only if it removes one too. | | Does it add a minter outside the swarm's existing mint path? | **block** for new functionality. For existing functionality, only if it removes one too. |
| Does it persist a store-provided secret to disk — a state directory, a bind mount, a rendered config? | **block** for new functionality. For existing functionality, only if the change removes a persisted copy elsewhere and nets out fewer. | | Does it persist a store-provided secret to disk — a state directory, a bind mount, a rendered config? | **block** for new functionality. For existing functionality, only if the change removes a persisted copy elsewhere and nets out fewer. |
| Does it add a credential whose renewal strategy is `NONE`? | **block** for new functionality. State the strategy, even if it is "operator reissues and restarts the reader." | | Does it add a credential whose renewal strategy is `NONE`? | **block** for new functionality. State the strategy, even if it's "operator reissues and restarts the reader." |
| Does it read a secret on another principal's behalf and hand it over? | **block** for new functionality. The principal that needs the value authenticates for it. | | Does it read a secret on another principal's behalf and hand it over? | **block** for new functionality. The principal that needs the value authenticates for it. |
| Does it give a host or container an out-of-band credential that is not the store mTLS leaf? | **block**, both. One out-of-band credential per principal is the whole point of the store. | | Does it give a host or container an out-of-band credential that's not the store mTLS leaf? | **block**, both. One out-of-band credential per principal is the whole point of the store. |
| Does it change the minter, reader, or renewal of an existing credential? | The current-state table above is **stale until updated in the same change.** | | Does it change the minter, reader, or renewal of an existing credential? | The current-state table above is **stale until updated in the same change.** |
For existing functionality the questions are not pass/fail — they are a For existing functionality the questions aren't pass/fail — they're a
direction check. A change that persists one fewer copy, or moves one reader from direction check. A change that persists one fewer copy, or moves one reader from
a host-written file to its own store read, is a step and lands. A change that a host-written file to its own store read, is a step and lands. A change that
leaves the count the same while moving the file somewhere tidier is not a step; leaves the count the same while moving the file somewhere tidier isn't a step;
it is churn with a migration story attached. it's churn with a migration story attached.
When the last `yes` in the current-state table's final column becomes a `no`, When the last `yes` in the current-state table's final column becomes a `no`,
this page replaces `secrets.md` and the current-state section goes with it. this page replaces `secrets.md` and the current-state section goes with it.