From 0ce6e8410ad397ce790ad1b9fad53366891e78ba Mon Sep 17 00:00:00 2001 From: atlas Date: Tue, 18 Aug 2026 12:30:27 +0200 Subject: [PATCH 1/3] docs(#3255): the controller is to interpret deliveries, not relay them mara's ruling: the controller should not forward received events, it should create specific messages from them ("knowledge repo changed", "deploy agent foo to rev abc123") and send those to whoever needs them. The module doc said the opposite as settled design. The argument it made was wrong in a specific way worth keeping: it counted this daemon as a *second* place deciding what a delivery means, while assuming the hives stay interpreters. They do not -- the parsing moves rather than duplicating, so the end state has one interpreter, not two. Doc-only. The behaviour is unchanged and still receipt-only; what changes is that the next reader is not told the wrong direction. --- swarm-controller/src/webhook.rs | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/swarm-controller/src/webhook.rs b/swarm-controller/src/webhook.rs index 03efd5a5..5af18ad1 100644 --- a/swarm-controller/src/webhook.rs +++ b/swarm-controller/src/webhook.rs @@ -6,22 +6,26 @@ //! deliveries. One owner is the only non-racing shape, and the only //! swarm-wide thing in the deployment is this daemon. //! -//! **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 only, for now — the interpretation lands here next.** The +//! payload is currently opaque bytes keyed by a [`DeliveryKind`] from the URL +//! path, because nothing consumes it yet. //! -//! **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. +//! ⚠️ This used to say *"this relays; it does not interpret"*, and that is no +//! longer the direction: this daemon is to **parse a delivery and emit a +//! semantic message** — *knowledge repo changed*, *deploy agent X at rev Y* — +//! addressed to whoever needs it, rather than forwarding forge bytes for each +//! hive to re-derive. The earlier reasoning ("parsing here would be a second +//! place deciding what a delivery means") counted this daemon as a *second* +//! interpreter while assuming the hives stay interpreters. They do not: the +//! parsing **moves** rather than duplicating, so one place decides. +//! +//! The HMAC code is deliberately **not** shared with `hive-c0re`: that copy +//! is leaving, not staying, and a shared crate is right only when a second +//! consumer *arrives*. Same arriving-or-leaving question as the paragraph +//! above, asked about code instead of about meaning. //! //! **These hooks are registered ALONGSIDE the per-hive ones** — see -//! [`crate::forge::Client::ensure_swarm_webhooks`], which carries why, and -//! why moving them cannot come before fan-out. +//! [`crate::forge::Client::ensure_swarm_webhooks`]. use anyhow::{Context as _, Result}; use axum::{ From b172e67fbdbf57a466d4087b90ba0b62ea9de4cd Mon Sep 17 00:00:00 2001 From: atlas Date: Tue, 18 Aug 2026 12:33:15 +0200 Subject: [PATCH 2/3] docs(#3255): correct the rest of the relay-shaped wording mara asked for the docs and comments to be fixed in one PR before the implementation, since a half-corrected description is what confuses a reader coming back after a context compact. Three more places said or implied "forward the payload": - post_webhook_forge's doc ("relays the delivery to every hive ... the payload is never parsed here") - its OpenAPI request_body description ("the delivery can be relayed unmodified") -- this one is published in the spec, so it was the most externally visible of them - DeliveryKind::as_str, which called the hook kind the event's routing key; the message is semantic, and which hook it arrived on is an input to deriving it rather than the thing sent docs/swarm/README.md gains the direction next to the interim state, so "two hooks, one of which only logs" reads as a step rather than a design. --- docs/swarm/README.md | 7 +++++++ swarm-controller/src/webhook.rs | 22 ++++++++++++++-------- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/docs/swarm/README.md b/docs/swarm/README.md index 5f6e7fda..6acb2ff7 100644 --- a/docs/swarm/README.md +++ b/docs/swarm/README.md @@ -363,6 +363,13 @@ 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. +Where this is going, so the interim state reads as interim: the +controller will **interpret** a delivery and send 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, before the message +is sent; a hive receives a decision, not an event to adjudicate. + 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 hook the forge cannot reach would collect failed deliveries while diff --git a/swarm-controller/src/webhook.rs b/swarm-controller/src/webhook.rs index 5af18ad1..f00bc1cc 100644 --- a/swarm-controller/src/webhook.rs +++ b/swarm-controller/src/webhook.rs @@ -218,9 +218,14 @@ impl DeliveryKind { } } - /// 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. + /// 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. fn as_str(self) -> &'static str { match self { Self::Knowledge => "knowledge", @@ -311,9 +316,10 @@ pub(super) fn verify( /// POST `/webhook/forge/{kind}` — a swarm-wide Forgejo delivery. /// -/// 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. +/// 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. /// /// 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 +335,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 and the \ - delivery can be relayed unmodified" + verification runs over the exact wire bytes rather \ + than over a reserialised copy" ), responses( (status = 200, description = "delivery accepted", body = String), From eb8387bd732e1fe45a3e45cdf2f5d884ed5a9136 Mon Sep 17 00:00:00 2001 From: atlas Date: Tue, 18 Aug 2026 12:35:27 +0200 Subject: [PATCH 3/3] docs(#3255): state the present, drop the changelog framing mara: "pls remove historical wording, only present pls". The correction was written as a diff against what the docs used to claim -- "this used to say X", "where this is going", "the intended state for now". That is a changelog, and a reader arriving cold has to reconstruct the current truth from it. The reasoning about why the old shape was wrong belongs in the PR that changed it, not in the file. Now says what is true: the controller interprets a delivery and emits a semantic message; receipt is all that is wired today because the swarm->hive channel does not exist yet. --- docs/swarm/README.md | 19 +++++++++---------- swarm-controller/src/webhook.rs | 24 +++++++++--------------- 2 files changed, 18 insertions(+), 25 deletions(-) diff --git a/docs/swarm/README.md b/docs/swarm/README.md index 6acb2ff7..2338b0fd 100644 --- a/docs/swarm/README.md +++ b/docs/swarm/README.md @@ -356,19 +356,18 @@ itself — a `push` hook on `internal/knowledge` and a `pull_request` hook on the `agent-configs` org, both under `https:///webhook/forge/`. -**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. +**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. -Where this is going, so the interim state reads as interim: the -controller will **interpret** a delivery and send hives a specific +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, before the message -is sent; a hive receives a decision, not an event to adjudicate. +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. 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 f00bc1cc..5be60268 100644 --- a/swarm-controller/src/webhook.rs +++ b/swarm-controller/src/webhook.rs @@ -6,23 +6,17 @@ //! deliveries. One owner is the only non-racing shape, and the only //! swarm-wide thing in the deployment is this daemon. //! -//! **Receipt only, for now — the interpretation lands here next.** The -//! payload is currently opaque bytes keyed by a [`DeliveryKind`] from the URL -//! path, because nothing consumes it yet. +//! **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 used to say *"this relays; it does not interpret"*, and that is no -//! longer the direction: this daemon is to **parse a delivery and emit a -//! semantic message** — *knowledge repo changed*, *deploy agent X at rev Y* — -//! addressed to whoever needs it, rather than forwarding forge bytes for each -//! hive to re-derive. The earlier reasoning ("parsing here would be a second -//! place deciding what a delivery means") counted this daemon as a *second* -//! interpreter while assuming the hives stay interpreters. They do not: the -//! parsing **moves** rather than duplicating, so one place decides. +//! 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, not staying, and a shared crate is right only when a second -//! consumer *arrives*. Same arriving-or-leaving question as the paragraph -//! above, asked about code instead of about meaning. +//! 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. //! //! **These hooks are registered ALONGSIDE the per-hive ones** — see //! [`crate::forge::Client::ensure_swarm_webhooks`].