Watch
0
0
Fork
You've already forked hyperhive
0

Compare commits

...
Author SHA1 Message Date
atlas
eb8387bd73 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.
2026-08-18 12:35:27 +02:00
atlas
b172e67fbd 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.
2026-08-18 12:33:15 +02:00
atlas
0ce6e8410a 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.
2026-08-18 12:30:53 +02:00
2 changed files with 37 additions and 27 deletions

View file

@ -356,12 +356,18 @@ itself — a `push` hook on `internal/knowledge` and a `pull_request` hook
on the `agent-configs` org, both under on the `agent-configs` org, both under
`https://<swarm.ui.domain>/webhook/forge/`. `https://<swarm.ui.domain>/webhook/forge/`.
**You will see two hooks where there used to be one**, and that is the **Two hooks exist per swarm-wide repo: each hive's own, plus the
intended state for now: each hive still registers and receives its own, controller's.** Both are expected — **do not delete either.** Removing a
and the controller's is an additional copy that it currently logs and hive's hook stops that hive acting on knowledge pushes and config PRs;
nothing more. **Do not delete either.** Removing a hive's hook stops that removing the controller's just gets recreated on its next start.
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.
Nothing to configure. The hooks are registered only when this host also 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 serves the swarm UI vhost — that is what publishes the endpoint, and a

View file

@ -6,22 +6,20 @@
//! deliveries. One owner is the only non-racing shape, and the only //! deliveries. One owner is the only non-racing shape, and the only
//! swarm-wide thing in the deployment is this daemon. //! swarm-wide thing in the deployment is this daemon.
//! //!
//! **This relays; it does not interpret.** The payload stays opaque bytes, //! **This daemon interprets a delivery; it does not forward it.** It parses
//! keyed by a [`DeliveryKind`] from the URL path. The hives already know //! the payload and emits a semantic message — *the knowledge repo changed*,
//! what a delivery *means* — which repo is the knowledge repo, which config //! *deploy agent X at rev Y* — addressed to the hives that need it. One place
//! PR action queues an approval — so parsing it here would be a second place //! decides what a forge payload means, so no hive re-derives it.
//! deciding that, and the two would drift.
//! //!
//! **The HMAC code is not shared with `hive-c0re` because c0re's copy is //! Receipt is all that is wired today: the payload is opaque bytes keyed by a
//! leaving, not staying.** Once registration moves here, hives stop //! [`DeliveryKind`] from the URL path, and nothing consumes it until the
//! registering *and* receiving, and c0re's routes and secret go with it. A //! swarm→hive channel exists.
//! shared crate is right when a second consumer *arrives*; here it is //!
//! departing. The two verifiers meanwhile check different hooks with //! The HMAC code is deliberately **not** shared with `hive-c0re`: that copy is
//! different secrets and never need to agree. //! leaving, and a shared crate is right only when a second consumer arrives.
//! //!
//! **These hooks are registered ALONGSIDE the per-hive ones** — see //! **These hooks are registered ALONGSIDE the per-hive ones** — see
//! [`crate::forge::Client::ensure_swarm_webhooks`], which carries why, and //! [`crate::forge::Client::ensure_swarm_webhooks`].
//! why moving them cannot come before fan-out.
use anyhow::{Context as _, Result}; use anyhow::{Context as _, Result};
use axum::{ use axum::{
@ -214,9 +212,14 @@ impl DeliveryKind {
} }
} }
/// Stable string form, used for logging and (once fan-out lands) as the /// Stable string form, used for logging. Deliberately the same spelling
/// event's routing key. Deliberately the same spelling as the path /// as the path segment so a journal line can be matched against a
/// segment so a journal line can be matched against a registered URL. /// 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 { fn as_str(self) -> &'static str {
match self { match self {
Self::Knowledge => "knowledge", Self::Knowledge => "knowledge",
@ -307,9 +310,10 @@ pub(super) fn verify(
/// POST `/webhook/forge/{kind}` — a swarm-wide Forgejo delivery. /// POST `/webhook/forge/{kind}` — a swarm-wide Forgejo delivery.
/// ///
/// Verifies the HMAC over the raw body, then (once fan-out lands) relays the /// Verifies the HMAC over the raw body. Nothing consumes the delivery yet;
/// delivery to every hive over the swarm queue. The payload is never parsed /// once the swarm→hive channel lands, this daemon **parses** it and emits a
/// here — see the module docs. /// 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 /// 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 /// one answers 401 (bad signature) or 503 (this daemon has no secret), and
@ -325,8 +329,8 @@ pub(super) fn verify(
content = String, content = String,
content_type = "application/json", content_type = "application/json",
description = "Forgejo webhook payload, taken as raw bytes so HMAC \ description = "Forgejo webhook payload, taken as raw bytes so HMAC \
verification runs over the exact wire bytes and the \ verification runs over the exact wire bytes rather \
delivery can be relayed unmodified" than over a reserialised copy"
), ),
responses( responses(
(status = 200, description = "delivery accepted", body = String), (status = 200, description = "delivery accepted", body = String),