refactor(#3255): one knowledge subject, single writer and many readers
Review call: the event was addressed per hive — `$SWARM.events.<hive>.knowledge`, published in a loop over the roster, granted through a wildcard. It does not need to be. The payload is empty and the event means the same thing to every hive, so one publish to one subject delivers exactly what N publishes to N subjects did, and core NATS already fans out to whoever is subscribed. A hive that was down misses it either way and reconciles on its next periodic pull. That deletes rather than reshuffles: the roster loop, the wildcard, and the shared subject-building function whose entire purpose was keeping the grant and the publish from drifting apart. With one literal there is nothing to disagree about. The per-hive shape was justified by the callout policy's rule that an extra subject must contain the hive name. That rule governs `extra_hive_subjects` — what a HIVE may publish. This subject lives in the controller's reader grant, which the rule does not constrain, so a real rule was carried across into a decision it had no authority over. Knowledge becomes its own category rather than a leaf under a general event namespace, since a namespace shaped for events that do not exist yet is a decision made before there is anything to decide from. The empty config-PR match arm goes with it: an arm with no body claims this is where the deploy path is handled, and it is not. The deny test stays and matters more, not less: with one shared subject a forged event would reach the whole swarm where a per-hive one reached a single hive.
This commit is contained in:
parent
84125b0806
commit
9b939f4626
6 changed files with 104 additions and 156 deletions
|
|
@ -367,14 +367,14 @@ pub(super) async fn post_webhook_forge(
|
|||
"webhook: verified delivery"
|
||||
);
|
||||
|
||||
match kind {
|
||||
DeliveryKind::Knowledge => announce_knowledge_change(&state).await,
|
||||
// Deploy coordination is a separate concern with its own issue: a
|
||||
// hive does not want to hear that a config PR was opened, it wants
|
||||
// to be told when to rebuild from main, and that is a decision the
|
||||
// controller makes after a merge rather than a relay of this
|
||||
// delivery. Logged above and deliberately not forwarded.
|
||||
DeliveryKind::ConfigPr => {}
|
||||
// Only the knowledge delivery is acted on. Deploy coordination is a
|
||||
// separate concern with its own issue — a hive does not want to hear that a
|
||||
// config PR was opened, it wants to be told when to rebuild from main, and
|
||||
// that is a decision the controller makes after a merge rather than a relay
|
||||
// of this delivery. Written as a condition rather than a match arm holding
|
||||
// an empty body, which would claim this is where that path is handled.
|
||||
if kind == DeliveryKind::Knowledge {
|
||||
announce_knowledge_change(&state).await;
|
||||
}
|
||||
|
||||
(StatusCode::OK, "ok").into_response()
|
||||
|
|
@ -416,22 +416,23 @@ async fn announce_knowledge_change(state: &AppState) {
|
|||
return;
|
||||
};
|
||||
let client = status.queue_client();
|
||||
let subject = swarm_queue_client::knowledge::SUBJECT;
|
||||
|
||||
for hive in state.hives.iter() {
|
||||
let subject = swarm_queue_client::events::knowledge(&hive.name);
|
||||
if let Err(e) = client.publish(subject.clone(), Vec::new().into()).await {
|
||||
tracing::warn!(
|
||||
hive = %hive.name, %subject, error = %e,
|
||||
"webhook: publishing the knowledge event failed"
|
||||
);
|
||||
} else {
|
||||
tracing::info!(hive = %hive.name, %subject, "webhook: knowledge event published");
|
||||
}
|
||||
// One publish, not one per hive: every subscriber gets the same empty
|
||||
// event, so the roster is not consulted at all. The controller does not
|
||||
// need to know who the hives are in order to say the repository moved.
|
||||
if let Err(e) = client.publish(subject, Vec::new().into()).await {
|
||||
tracing::warn!(%subject, error = %e, "webhook: publishing the knowledge event failed");
|
||||
return;
|
||||
}
|
||||
|
||||
// Logged after the flush rather than after the publish: `publish` only
|
||||
// hands the message to the client's write buffer, so a line printed there
|
||||
// would claim delivery this end cannot yet know about.
|
||||
if let Err(e) = client.flush().await {
|
||||
tracing::warn!(error = %e, "webhook: flushing knowledge events failed");
|
||||
tracing::warn!(%subject, error = %e, "webhook: flushing the knowledge event failed");
|
||||
return;
|
||||
}
|
||||
tracing::info!(%subject, "webhook: knowledge event published");
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
|||
Loading…
Reference in a new issue