swarm: name the deploy event and grant the controller its publish

The subject and its payload live in `swarm-queue-client` for the reason
the knowledge event's already does: three crates have to agree on the
string, and the one that agrees hardest — the auth-callout responder,
which decides whether the publish is permitted at all — speaks neither
`jetstream` nor `kv`.

Swarm-wide rather than a `$SWARM.deploy.<hive>` family. That family
would look like isolation and provide none: this responder scopes
publish only, leaving `sub` unrestricted, so a hive could subscribe to
another's subject as easily as to its own. Until `sub` is scoped the
split costs a wider grant and buys nothing, so the addressing goes in
the payload and each hive filters on its own name.

Unlike the knowledge event the message is addressed, so it carries a
payload — a trigger, never the config. The hive already tracks the
agent's config repo; desired state on the wire would make this a second
source of truth for something git owns, and a hive that missed a
message would be wrong rather than late.

Both test arms mirrored from the knowledge event. The negative one
matters more here: a forged knowledge event makes a hive re-read a
repo, a forged deploy event makes it rebuild and restart a named agent.
This commit is contained in:
atlas 2026-08-30 22:36:19 +02:00 committed by mara
commit 93c7454bf5
2 changed files with 73 additions and 0 deletions

View file

@ -173,6 +173,40 @@ pub mod status;
/// permitted at all — speaks neither `jetstream` nor `kv`.
pub const KNOWLEDGE_SUBJECT: &str = "$SWARM.knowledge";
/// The subject the swarm controller publishes on to ask a hive to rebuild one
/// of its agents. Same shape as [`KNOWLEDGE_SUBJECT`]: one writer, every hive
/// subscribes, and it lives here for the same three-crate reason.
///
/// # Swarm-wide, not per-hive, and that is deliberate
///
/// A `$SWARM.deploy.<hive>` family would *look* like isolation and provide
/// none: the auth-callout responder scopes **publish** only, leaving `sub`
/// unrestricted, so any hive could subscribe to another's subject just as
/// easily as to its own. Until `sub` is scoped, the per-hive split costs a
/// wider grant and buys nothing — so the addressing lives in the payload and
/// each hive filters on its own name, exactly as the knowledge event fans out.
pub const DEPLOY_SUBJECT: &str = "$SWARM.deploy";
/// What a [`DEPLOY_SUBJECT`] message carries.
///
/// The knowledge event has no payload — every hive does the same thing on
/// receipt. This one is addressed, so both ends have to agree on the fields,
/// which is why the type lives beside the subject rather than in whichever
/// crate happened to need it first.
///
/// ⚠️ **A trigger, not the config.** The hive already tracks the agent's
/// config repo; putting desired state on the wire would make this message a
/// second source of truth for something git already owns, and a hive that
/// missed a message would then be wrong rather than merely late.
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct DeployRequest {
/// Which hive should act. Every hive receives the message; the one whose
/// own name this matches is the one that rebuilds.
pub hive: String,
/// The agent to rebuild, as the swarm knows it.
pub agent: String,
}
/// The hive-notices stream, shared by the hive that publishes and
/// whatever eventually consumes it. Behind the `notices` feature, same
/// reason `status` is behind `kv` — see the module doc.