forge_notify: skip-reasons drop-list filter, configurable via agent.nix
This commit is contained in:
parent
b0f6bd8ece
commit
a94b504883
3 changed files with 72 additions and 4 deletions
|
|
@ -104,8 +104,6 @@ pub async fn run(socket: PathBuf, is_manager: bool) {
|
||||||
debug!(%own_login, "forge_notify: own login resolved");
|
debug!(%own_login, "forge_notify: own login resolved");
|
||||||
}
|
}
|
||||||
|
|
||||||
info!(forge_url = %forge_url, "forge_notify: polling started");
|
|
||||||
|
|
||||||
let mut interval = tokio::time::interval(Duration::from_secs(POLL_INTERVAL_SECS));
|
let mut interval = tokio::time::interval(Duration::from_secs(POLL_INTERVAL_SECS));
|
||||||
interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
|
interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
|
||||||
// First tick fires immediately — skip it so we don't race the broker
|
// First tick fires immediately — skip it so we don't race the broker
|
||||||
|
|
@ -118,6 +116,29 @@ pub async fn run(socket: PathBuf, is_manager: bool) {
|
||||||
.map(|v| v == "1" || v.eq_ignore_ascii_case("true"))
|
.map(|v| v == "1" || v.eq_ignore_ascii_case("true"))
|
||||||
.unwrap_or(false);
|
.unwrap_or(false);
|
||||||
|
|
||||||
|
// Optional reason drop-list. `HIVE_FORGE_NOTIFY_SKIP_REASONS` is a
|
||||||
|
// comma-separated list of Forgejo notification `reason` values to
|
||||||
|
// suppress (e.g. `subscribed,participating`). Notifications with
|
||||||
|
// those reasons are marked read and silently dropped; everything
|
||||||
|
// else -- including notifications with a null/unrecognised reason --
|
||||||
|
// is delivered. Drop-list is safer than an allow-list: it kills the
|
||||||
|
// firehose without risking silent misses of directed signals
|
||||||
|
// (review_requested, assigned) or future unknown reason strings.
|
||||||
|
// Configurable per-agent via `hyperhive.forge.skipNotifyReasons` in agent.nix.
|
||||||
|
let skip_reasons: Vec<String> = std::env::var("HIVE_FORGE_NOTIFY_SKIP_REASONS")
|
||||||
|
.unwrap_or_default()
|
||||||
|
.split(',')
|
||||||
|
.map(str::trim)
|
||||||
|
.filter(|s| !s.is_empty())
|
||||||
|
.map(str::to_owned)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
if skip_reasons.is_empty() {
|
||||||
|
info!(forge_url = %forge_url, "forge_notify: polling started (all reasons)");
|
||||||
|
} else {
|
||||||
|
info!(forge_url = %forge_url, skip = ?skip_reasons, "forge_notify: polling started");
|
||||||
|
}
|
||||||
|
|
||||||
// Repos we have already unsubscribed this process lifetime. Persists
|
// Repos we have already unsubscribed this process lifetime. Persists
|
||||||
// across polls so we don't hammer DELETE on every cycle.
|
// across polls so we don't hammer DELETE on every cycle.
|
||||||
let mut unsubbed_repos: HashSet<String> = HashSet::new();
|
let mut unsubbed_repos: HashSet<String> = HashSet::new();
|
||||||
|
|
@ -133,6 +154,7 @@ pub async fn run(socket: PathBuf, is_manager: bool) {
|
||||||
keep_subscriptions,
|
keep_subscriptions,
|
||||||
&mut unsubbed_repos,
|
&mut unsubbed_repos,
|
||||||
&own_login,
|
&own_login,
|
||||||
|
&skip_reasons,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
@ -444,6 +466,7 @@ async fn poll_once(
|
||||||
keep_subscriptions: bool,
|
keep_subscriptions: bool,
|
||||||
unsubbed_repos: &mut HashSet<String>,
|
unsubbed_repos: &mut HashSet<String>,
|
||||||
own_login: &str,
|
own_login: &str,
|
||||||
|
skip_reasons: &[String],
|
||||||
) {
|
) {
|
||||||
let url = format!("{forge_url}/api/v1/notifications?all=false&limit=50");
|
let url = format!("{forge_url}/api/v1/notifications?all=false&limit=50");
|
||||||
let resp = match client
|
let resp = match client
|
||||||
|
|
@ -481,6 +504,18 @@ async fn poll_once(
|
||||||
for notif in ¬ifications {
|
for notif in ¬ifications {
|
||||||
let Some(id) = notif["id"].as_u64() else { continue };
|
let Some(id) = notif["id"].as_u64() else { continue };
|
||||||
|
|
||||||
|
// Reason drop-list: suppress noisy reasons (subscribed/participating).
|
||||||
|
// null/unknown reasons pass through — directed signals are never
|
||||||
|
// silently dropped even if Forgejo returns an unexpected value.
|
||||||
|
if !skip_reasons.is_empty() {
|
||||||
|
let reason = notif["reason"].as_str().unwrap_or("");
|
||||||
|
if !reason.is_empty() && skip_reasons.iter().any(|r| r == reason) {
|
||||||
|
debug!(%id, %reason, "forge_notify: skipping (reason in drop-list)");
|
||||||
|
mark_read(client, forge_url, token, id).await;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let body_opt = format_notification(client, token, notif, own_login).await;
|
let body_opt = format_notification(client, token, notif, own_login).await;
|
||||||
|
|
||||||
// None means self-echo — mark read silently, no delivery.
|
// None means self-echo — mark read silently, no delivery.
|
||||||
|
|
|
||||||
|
|
@ -173,6 +173,31 @@
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
options.hyperhive.forge.skipNotifyReasons = lib.mkOption {
|
||||||
|
type = lib.types.listOf lib.types.str;
|
||||||
|
default = [ ];
|
||||||
|
example = [
|
||||||
|
"subscribed"
|
||||||
|
"participating"
|
||||||
|
];
|
||||||
|
description = ''
|
||||||
|
Forgejo notification `reason` values to suppress in the forge
|
||||||
|
notification poller. Notifications with these reasons are marked
|
||||||
|
read and silently dropped; all others — including notifications
|
||||||
|
with a null or unrecognised reason — are delivered.
|
||||||
|
|
||||||
|
Drop-list is safer than an allow-list: directed signals
|
||||||
|
(`review_requested`, `assigned`, `mention`) are never silently
|
||||||
|
missed even if Forgejo returns an unexpected reason string.
|
||||||
|
|
||||||
|
Empty list (the default) delivers all notifications. Set to
|
||||||
|
`[ "subscribed" "participating" ]` for agents like the manager
|
||||||
|
that want only direct mentions and reviews, not the full repo
|
||||||
|
firehose. Rendered to the `HIVE_FORGE_NOTIFY_SKIP_REASONS`
|
||||||
|
environment variable consumed by the harness poller at runtime.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
options.hyperhive.dashboardLinks = lib.mkOption {
|
options.hyperhive.dashboardLinks = lib.mkOption {
|
||||||
type = lib.types.listOf (lib.types.submodule {
|
type = lib.types.listOf (lib.types.submodule {
|
||||||
options = {
|
options = {
|
||||||
|
|
@ -332,6 +357,8 @@
|
||||||
HIVE_COMPACT_WATERMARK_TOKENS = "0";
|
HIVE_COMPACT_WATERMARK_TOKENS = "0";
|
||||||
} // lib.optionalAttrs config.hyperhive.forge.keepSubscriptions {
|
} // lib.optionalAttrs config.hyperhive.forge.keepSubscriptions {
|
||||||
HIVE_FORGE_KEEP_SUBSCRIPTIONS = "1";
|
HIVE_FORGE_KEEP_SUBSCRIPTIONS = "1";
|
||||||
|
} // lib.optionalAttrs (config.hyperhive.forge.skipNotifyReasons != [ ]) {
|
||||||
|
HIVE_FORGE_NOTIFY_SKIP_REASONS = lib.concatStringsSep "," config.hyperhive.forge.skipNotifyReasons;
|
||||||
};
|
};
|
||||||
|
|
||||||
boot.isNspawnContainer = true;
|
boot.isNspawnContainer = true;
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,15 @@
|
||||||
{
|
{
|
||||||
imports = [ ./harness-base.nix ];
|
imports = [ ./harness-base.nix ];
|
||||||
|
|
||||||
# Manager auto-unsubscribes from repo watches (uses mention-only filtering
|
# Manager auto-unsubscribes from repo watches and skips the subscription/
|
||||||
# via HIVE_FORGE_NOTIFY_REASONS). Sub-agents default to keepSubscriptions=true.
|
# participation firehose — only direct mentions, reviews, and assignments
|
||||||
|
# land in the inbox. Sub-agents default to keepSubscriptions=true and
|
||||||
|
# skipNotifyReasons=[].
|
||||||
hyperhive.forge.keepSubscriptions = false;
|
hyperhive.forge.keepSubscriptions = false;
|
||||||
|
hyperhive.forge.skipNotifyReasons = [
|
||||||
|
"subscribed"
|
||||||
|
"participating"
|
||||||
|
];
|
||||||
|
|
||||||
# HIVE_PORT/HIVE_LABEL/gitconfig are also injected by the generated
|
# HIVE_PORT/HIVE_LABEL/gitconfig are also injected by the generated
|
||||||
# `applied/hm1nd/flake.nix` (see `lifecycle::setup_applied`); the values
|
# `applied/hm1nd/flake.nix` (see `lifecycle::setup_applied`); the values
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue