hyperhive/swarm-matrix-minter/README.md
atlas fb9c6122df matrix: name the credential after the account it authenticates as
The store path and every identifier around it called this an admin
token. It is not one: of ~15 hive-c0re call sites only two need
homeserver admin, and the homeserver no longer promotes the account at
boot, so the name overstated both what the credential is and what it may
do.

Renaming it to the account was not enough either. "The `@hive:` token"
reads as the token of a hive user, and no such user is provisioned —
`@hive:<server_name>` is the appservice registration's own
`sender_localpart`, an account the homeserver creates for itself when it
loads the registration.

So it is the **sender token**: the matrix appservice sender account's
access token, at `swarm/services/matrix/sender-token`. The name says
what it authenticates as rather than what it may do, which is the part
that was wrong.

The path has one constructor, and the bao grant, the grant assertion and
three unit tests pin its literal independently — so a half-finished
rename fails a check rather than leaving the minter and its readers
disagreeing at runtime. `tracing` messages are renamed with the code, so
the journal reads the way the source does.

The host-side file keeps its name (`matrix/access-token`): it carried no
admin framing, and renaming it would orphan the file on every deployed
hive for nothing.

`docs/tools/hivectl-cli.md` is regenerated from the clap tree.
2026-09-20 22:07:16 +02:00

44 lines
2 KiB
Markdown

# swarm-matrix-minter
A boot-time oneshot that runs **inside `containers.hive-matrix`**, beside the
homeserver, and puts the appservice sender account's access token into the swarm's secret
store under an identity of its own.
## Why it lives in the matrix container
The credential it mints is authorised by the appservice `as_token`, and the
container already holds that: `nix/host-modules/hive-matrix.nix` bind-mounts the
rendered appservice registration into it read-only, because that is how tuwunel
itself is handed the registration. Minting anywhere else would mean copying the
`as_token` to a second holder — and the point of this component is that the hive
stops being one.
It is not the swarm controller for the same reason, plus a structural one: a
homeserver has exactly **one** `@hive:` account and a swarm runs one
homeserver, so "mint it once" needs no lock, no lease and no trigger surface — it
is a property of the thing being minted.
## Idempotency
The **store** is the key, not the homeserver. A run reads
`swarm/services/matrix/sender-token` first and returns without touching the
homeserver when something is already there. Only an empty path reaches the mint
ladder:
1. `POST /_matrix/client/v3/register` with `"type": "m.login.application_service"`
— one round trip, no UIAA.
2. `M_USER_IN_USE` (the expected arm on a homeserver that has already loaded the
registration, since the account is the appservice's own `sender_localpart`) →
`POST /_matrix/client/v3/login` as the appservice, same pinned `device_id`, so
the old device is replaced rather than duplicated.
3. Write the result to the store.
A crash between the homeserver call and the store write is recoverable: the next
run takes arm 2.
## 🩸 A secret is a path, never a value
Nothing here logs, prints or interpolates a token. The mint ladder's errors are
built from the homeserver's _status_ and its `errcode`, never its body, because a
`/login` response body is an access token. The one identifier this binary logs is
the store path it wrote.