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
`https://<swarm.ui.domain>/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.
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
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
//! 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.
//! **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.
//!
//! **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.
//! 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.
//!
//! **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::{
@ -214,9 +212,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",
@ -307,9 +310,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
@ -325,8 +329,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),