feat(webhook): declared watch policy — rules instead of muting humans - #199
Open
defangdevs wants to merge 1 commit into
Open
feat(webhook): declared watch policy — rules instead of muting humans#199defangdevs wants to merge 1 commit into
defangdevs wants to merge 1 commit into
Conversation
…#197) A standing watch spawned a session for every non-CI event from an unmuted sender, and ignore_senders was the only lever — sender-granular, so stopping a human's close/merge echoes meant muting the human, which also swallowed the new issues the watch exists to catch. This box mutes its owner today for exactly that reason, and echo spawns eat hook- session slots that drop at the cap (#170). local-webhook 0.11.0 (local-channels#15, spun out of local-channels#14 after #13 settled that the upstream repo is mechanism only) adds per- subscription when/drop payload predicates: an entry carrying them owns its whole spawn policy — the failures-only CI brake steps aside, sender muting moves inside the rules where it can be scoped to echo-shaped events, and every declined event is logged. This PR is the policy half: - webhook.watchPolicy option: declarative rules per topic, enforced onto the matching filter.dispatch.json entry by an ExecStartPre on each user's receiver daemon. Upgrade-only: the module governs watches that sessions created, it never creates one, so the default is inert on a box without such a watch. The declaration replaces the managed fields (when/drop/ignoreSenders/note) wholesale; runtime fields (ttl, timestamps) stay the entry's own. - Default policy for the box's own-repos watch (github:defangdevs/*): spawn for issues/PRs opened or reopened by anyone but the box's own login, and for terminal CI failures whoever triggered the run; closes, merges, pushes, green and in-flight CI spawn nothing. The ignoreSenders on the governed entry is CLEARED — deploying this un-mutes the human owner, whose new issues then get triaged again. - Pin bump to local-channels 74a99dd (0.11.0), superseding the pin half of #196 (its green-run VM test remains valid: rule-less watches keep the 0.10.1 failures-only brake). - CLI + built-in AGENTS.md documentation for --when/--drop and the governed-entry rule (edit the NixOS config, not the entry). - VM test: a governed watch gets its rules and loses its ad-hoc sender mute on daemon restart while a rule-less sibling entry is untouched; a close echo is declined (and the decline logged); an outsider's opened issue still spawns. Verification: - nix run .#assemble; checks.aarch64-linux module-generated-up-to-date, multi-user, module-single-file, webhook-route, download-route all pass natively. - tests/webhook.nix passes nix-instantiate --parse and the ty/ruff testscript gates (the VM test itself is x86_64-only; not run here). - The reconcile jq was exercised against a fixture mirroring this box's live filter.dispatch.json (entry with ignoreSenders ["defangdevs","lionello"]): rules applied, mute cleared, timestamps/ ttl preserved, sibling entries untouched. The governed entry was then routed through the pinned webhook.py 0.11.0 directly: human's opened issue spawns, box echo / PR close / merge push / green run do not, box's own failing run does. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SDHb5tyTqZwzKM6Qdr8cxv
This was referenced Aug 5, 2026
Owner
Author
|
CI note: the failing check is |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #197. Companion to defangdevs/local-channels#15 (local-webhook 0.11.0, merged), which added the mechanism this PR writes policy into — per-subscription
when/droppayload predicates, the event-agnostic shape the owner asked for on local-channels#14 after #13 settled that the upstream repo carries mechanism only.Motivation
A standing watch spawns a session for every non-CI event from an unmuted sender, and
ignore_sendersis sender-granular — so the only way to stop a human's close/merge echoes was to mute the human, losing their new issues as collateral. This box does exactly that today: its dispatch watch mutes bothdefangdevs(the box) andlionello(the human owner), so a human-filed issue spawns no triage session. Echo spawns also eat hook-session slots that drop at the cap instead of queueing (#170), so they starve real failures.What this adds
services.agent-box.webhook.watchPolicy— declarative per-topic rules (when/drop/ignoreSenders/note), enforced onto the matchingfilter.dispatch.jsonentry by anExecStartPreon each user's receiver daemon. Upgrade-only: the module governs watches sessions created; it never creates one, so the default matches nothing on a box without such a watch. The declaration replaces the managed fields wholesale (config and state must not drift); runtime fields (ttl, timestamps) stay the entry's own. Every start logs what was governed — or that the policy is idle — because a rule that silently applied to nothing reads like a rule that worked (Standing watches go silently inert once the hook-session cap is hit #170).github:defangdevs/*): spawn for issues/PRsopened/reopenedby anyone but the box's login, and for terminal CI failures (failure,timed_out,action_required,startup_failure,stale,error) whoever triggered the run. Closes, merges, pushes, green/in-flight CI spawn nothing. Sender policy lives inside the rules, so the governed entry'signoreSendersis cleared — deploying this un-mutes lionello, and their new issues get triaged again while their close buttons stay quiet.74a99dd(0.11.0). Supersedes the pin half of chore(webhook): pin local-webhook 0.10.1 so green CI stops spawning sessions #196; its green-run VM test stays valid, because a rule-less watch keeps the 0.10.1 failures-only brake unchanged.agent-box-webhook --helpand the built-in AGENTS.md now cover--when/--dropand the governed-entry rule (change the NixOS config, not the entry — the entry's note says so).tests/webhook.nix): a second watch with declared policy gets its rules and loses its ad-hoc sender mute on daemon restart while the rule-less first watch is untouched; a signed close-echo delivery is declined and the decline is journaled; an outsider's opened issue still spawns a hook session.Deployment note
Nothing here restarts anything by itself — the rules and the un-mute both land when the box is next switched (the daemon restart runs the reconcile, and the new pinned webhook.py evaluates the rules; there is no version skew window because both arrive in the same switch). Until then the live watch keeps today's behavior, mutes included. Sessions load the plugin from the marketplace clone tracking local-channels main, so they already speak 0.11.0; only the daemon runs the pin.
Checks run
nix run .#assemble(module regenerated;module-generated-up-to-datepasses)nix build .#checks.aarch64-linux.{module-generated-up-to-date,multi-user,module-single-file,webhook-route,download-route}— all pass natively (VM tests are x86_64-only and not runnable on this box)tests/webhook.nix:nix-instantiate --parse+ the ty/ruff testscript gatesfilter.dispatch.json(entry withignoreSenders: ["defangdevs","lionello"]): rules applied, mute cleared, timestamps/ttl preserved, sibling entries untouched. The governed entry was then driven through the pinned webhook.py 0.11.0 itself: human's opened issue → spawn; box echo, PR close, merge push, green run → no spawn; box's own failing run → spawn.Refs: #197 (closes), #196 (pin superseded), #170 (cap wedge / logging), #192, local-channels#13/#14/#15.
🤖 Generated with Claude Code
https://claude.ai/code/session_01SDHb5tyTqZwzKM6Qdr8cxv