swarm-secret-client: give the store one namespace instead of one prefix

The crate had a single path convention and it was per-agent:
`swarm/agents/<agent>/matrix/<account>`. The secrets still to move into the
store do not fit it — one belongs to a hive, one to a swarm service, one to
the controller itself — so each would have picked its own shape, and each
would have been a separate grant to get wrong.

mara ruled the scheme on the epic: `swarm/<kind>/<name>/<secret>`, over
`agents`, `hives`, `services` and `controller`. This lands it.

`Kind` is an enum rather than free strings for one reason: the store's grant
is written in nix and cannot be reached from Rust, so a misspelled kind is a
403 at provision time and not a compile error. `Kind::ALL` lets a test
enumerate the set instead of restating it, which is what makes adding a kind
a deliberate edit rather than an accidental grant.

Note `Kind` sits beside `checked_segment`'s existing `kind` argument, which
means something else entirely — the label of the name being validated. They
are not the same concept and should not be merged.

Nothing about the rendered policy changes. `policy::render` still grants read
on the agent kind alone; the other kinds are absent on purpose, because what a
hive may read of its own kind is a boundary question and not a consequence of
the namespace growing. The controller's write grant likewise stays scoped to
`agents/` — it widens when a path outside it gains a writer, not when the
kinds are declared.

Verified: `cargo test -p swarm-secret-client` 23 passed, 0 failed. The two
tests pinning the rendered strings (`the_document_grants_read_over_the_whole_agent_prefix`
and matrix's path assertion) still assert the same literals they did before,
which is what shows this is a faithful port rather than a reshape. `nix fmt`
710 emitted, 10 formatted, 0 changed; the three scripts/check-*.sh lints pass
with the change staged. No reference to the removed `path::AGENT_PREFIX`
survives in the crate or in nix — checked with a scoped pattern, because the
unqualified name also belongs to hive-host-sock's container prefix and greps
for it are answering a different question.
This commit is contained in:
atlas 2026-09-11 20:19:47 +02:00
commit 2979fcf5d5
5 changed files with 135 additions and 19 deletions

View file

@ -11,7 +11,7 @@ use serde::{Deserialize, Serialize};
use crate::{
Error,
path::{AGENT_PREFIX, checked_segment},
path::{Kind, checked_segment, principal_prefix},
};
/// The path holding `agent`'s token for the external matrix account `account`.
@ -21,9 +21,9 @@ use crate::{
/// `[A-Za-z0-9_-]`, which is what keeps one agent's name from addressing
/// another agent's secret.
pub fn account_path(agent: &str, account: &str) -> Result<String, Error> {
checked_segment("agent", agent)?;
let prefix = principal_prefix(Kind::Agent, agent)?;
checked_segment("account", account)?;
Ok(format!("{AGENT_PREFIX}/{agent}/matrix/{account}"))
Ok(format!("{prefix}/matrix/{account}"))
}
/// What an account's path holds: the token, plus the homeserver it belongs to.
@ -59,8 +59,10 @@ mod tests {
#[test]
fn a_well_formed_pair_lands_under_the_agent_prefix() {
let p = account_path("atlas", "ops-relay").expect("both segments are legal");
// Spelled out rather than rebuilt from the same pieces the code uses:
// a test that composes `ROOT` and `Kind::Agent` would keep passing
// through a rename that moves every stored credential.
assert_eq!(p, "swarm/agents/atlas/matrix/ops-relay");
assert!(p.starts_with(AGENT_PREFIX));
}
#[test]