Watch
0
0
Fork
You've already forked hyperhive
0

Compare commits

..
2 changed files with 27 additions and 37 deletions

View file

@ -356,18 +356,12 @@ 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/`.
**Two hooks exist per swarm-wide repo: each hive's own, plus the **You will see two hooks where there used to be one**, and that is the
controller's.** Both are expected — **do not delete either.** Removing a intended state for now: each hive still registers and receives its own,
hive's hook stops that hive acting on knowledge pushes and config PRs; and the controller's is an additional copy that it currently logs and
removing the controller's just gets recreated on its next start. nothing more. **Do not delete either.** Removing a hive's hook stops that
hive acting on knowledge pushes and config PRs; removing the
The controller **interprets** a delivery and sends hives a specific controller's just gets recreated on its next start.
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,20 +6,22 @@
//! 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 daemon interprets a delivery; it does not forward it.** It parses //! **This relays; it does not interpret.** The payload stays opaque bytes,
//! the payload and emits a semantic message — *the knowledge repo changed*, //! keyed by a [`DeliveryKind`] from the URL path. The hives already know
//! *deploy agent X at rev Y* — addressed to the hives that need it. One place //! what a delivery *means* — which repo is the knowledge repo, which config
//! decides what a forge payload means, so no hive re-derives it. //! 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 //! **The HMAC code is not shared with `hive-c0re` because c0re's copy is
//! [`DeliveryKind`] from the URL path, and nothing consumes it until the //! leaving, not staying.** Once registration moves here, hives stop
//! swarm→hive channel exists. //! 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
//! The HMAC code is deliberately **not** shared with `hive-c0re`: that copy is //! departing. The two verifiers meanwhile check different hooks with
//! leaving, and a shared crate is right only when a second consumer arrives. //! different secrets and never need to agree.
//! //!
//! **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`]. //! [`crate::forge::Client::ensure_swarm_webhooks`], which carries why, and
//! why moving them cannot come before fan-out.
use anyhow::{Context as _, Result}; use anyhow::{Context as _, Result};
use axum::{ use axum::{
@ -212,14 +214,9 @@ impl DeliveryKind {
} }
} }
/// Stable string form, used for logging. Deliberately the same spelling /// Stable string form, used for logging and (once fan-out lands) as the
/// as the path segment so a journal line can be matched against a /// event's routing key. Deliberately the same spelling as the path
/// registered URL. /// 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 { fn as_str(self) -> &'static str {
match self { match self {
Self::Knowledge => "knowledge", Self::Knowledge => "knowledge",
@ -310,10 +307,9 @@ 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. Nothing consumes the delivery yet; /// Verifies the HMAC over the raw body, then (once fan-out lands) relays the
/// once the swarm→hive channel lands, this daemon **parses** it and emits a /// delivery to every hive over the swarm queue. The payload is never parsed
/// semantic message (*knowledge repo changed*, *deploy agent X at rev Y*) to /// here — see the module docs.
/// 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
@ -329,8 +325,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 rather \ verification runs over the exact wire bytes and the \
than over a reserialised copy" delivery can be relayed unmodified"
), ),
responses( responses(
(status = 200, description = "delivery accepted", body = String), (status = 200, description = "delivery accepted", body = String),