diff --git a/docs/swarm/README.md b/docs/swarm/README.md index 2338b0fd..5f6e7fda 100644 --- a/docs/swarm/README.md +++ b/docs/swarm/README.md @@ -356,18 +356,12 @@ itself — a `push` hook on `internal/knowledge` and a `pull_request` hook on the `agent-configs` org, both under `https:///webhook/forge/`. -**Two hooks exist per swarm-wide repo: each hive's own, plus the -controller's.** Both are expected — **do not delete either.** Removing a -hive's hook stops that hive acting on knowledge pushes and config PRs; -removing the controller's just gets recreated on its next start. - -The controller **interprets** a delivery and sends hives a specific -message — *the knowledge repo changed*, *deploy agent `foo` at rev -`abc123`* — rather than forwarding forge payloads for each hive to -re-derive. Approval happens once, at the swarm level: a hive receives a -decision, not an event to adjudicate. Today the controller logs each -verified delivery and sends nothing, because the swarm→hive channel does -not exist yet; the hive-side hooks are what act in the meantime. +**You will see two hooks where there used to be one**, and that is the +intended state for now: each hive still registers and receives its own, +and the controller's is an additional copy that it currently logs and +nothing more. **Do not delete either.** Removing a hive's hook stops that +hive acting on knowledge pushes and config PRs; removing the +controller's just gets recreated on its next start. Nothing to configure. The hooks are registered only when this host also serves the swarm UI vhost — that is what publishes the endpoint, and a diff --git a/swarm-controller/src/webhook.rs b/swarm-controller/src/webhook.rs index 5be60268..03efd5a5 100644 --- a/swarm-controller/src/webhook.rs +++ b/swarm-controller/src/webhook.rs @@ -6,20 +6,22 @@ //! deliveries. One owner is the only non-racing shape, and the only //! swarm-wide thing in the deployment is this daemon. //! -//! **This daemon interprets a delivery; it does not forward it.** It parses -//! the payload and emits a semantic message — *the knowledge repo changed*, -//! *deploy agent X at rev Y* — addressed to the hives that need it. One place -//! decides what a forge payload means, so no hive re-derives it. +//! **This relays; it does not interpret.** The payload stays opaque bytes, +//! keyed by a [`DeliveryKind`] from the URL path. The hives already know +//! what a delivery *means* — which repo is the knowledge repo, which config +//! PR action queues an approval — so parsing it here would be a second place +//! deciding that, and the two would drift. //! -//! Receipt is all that is wired today: the payload is opaque bytes keyed by a -//! [`DeliveryKind`] from the URL path, and nothing consumes it until the -//! swarm→hive channel exists. -//! -//! The HMAC code is deliberately **not** shared with `hive-c0re`: that copy is -//! leaving, and a shared crate is right only when a second consumer arrives. +//! **The HMAC code is not shared with `hive-c0re` because c0re's copy is +//! leaving, not staying.** Once registration moves here, hives stop +//! registering *and* receiving, and c0re's routes and secret go with it. A +//! shared crate is right when a second consumer *arrives*; here it is +//! departing. The two verifiers meanwhile check different hooks with +//! different secrets and never need to agree. //! //! **These hooks are registered ALONGSIDE the per-hive ones** — see -//! [`crate::forge::Client::ensure_swarm_webhooks`]. +//! [`crate::forge::Client::ensure_swarm_webhooks`], which carries why, and +//! why moving them cannot come before fan-out. use anyhow::{Context as _, Result}; use axum::{ @@ -212,14 +214,9 @@ impl DeliveryKind { } } - /// Stable string form, used for logging. Deliberately the same spelling - /// as the path segment so a journal line can be matched against a - /// registered URL. - /// - /// ⚠️ **Not the routing key for the swarm→hive message.** That message is - /// semantic (*knowledge repo changed*, *deploy agent X at rev Y*) and is - /// addressed to the hives that need it; which hook a delivery arrived on - /// is an input to deriving it, not the thing sent. + /// Stable string form, used for logging and (once fan-out lands) as the + /// event's routing key. Deliberately the same spelling as the path + /// segment so a journal line can be matched against a registered URL. fn as_str(self) -> &'static str { match self { Self::Knowledge => "knowledge", @@ -310,10 +307,9 @@ pub(super) fn verify( /// POST `/webhook/forge/{kind}` — a swarm-wide Forgejo delivery. /// -/// Verifies the HMAC over the raw body. Nothing consumes the delivery yet; -/// once the swarm→hive channel lands, this daemon **parses** it and emits a -/// semantic message (*knowledge repo changed*, *deploy agent X at rev Y*) to -/// the hives that need it — see the module docs. +/// Verifies the HMAC over the raw body, then (once fan-out lands) relays the +/// delivery to every hive over the swarm queue. The payload is never parsed +/// here — see the module docs. /// /// Returns 200 on an accepted delivery so Forgejo does not retry. A refused /// one answers 401 (bad signature) or 503 (this daemon has no secret), and @@ -329,8 +325,8 @@ pub(super) fn verify( content = String, content_type = "application/json", description = "Forgejo webhook payload, taken as raw bytes so HMAC \ - verification runs over the exact wire bytes rather \ - than over a reserialised copy" + verification runs over the exact wire bytes and the \ + delivery can be relayed unmodified" ), responses( (status = 200, description = "delivery accepted", body = String),