gate: intuitive wake-rule composition (closes #105) - #106
Merged
Conversation
… lint, dry-run probes, anchored positions
Grew out of the 2026-08-08 Mythos stand-back-density incident: a
prepended {scope:[mcpl:channel-incoming]} passive_sample, intended as
an every-150-events reflection governor, consumed the entire incoming
lane and silently made the DM/mention wake rules unreachable for 12h.
Nothing malfunctioned; the syntax faithfully did something nobody
intended, and nothing at install time said so. Four fixes (af#105):
- passthrough: true on counting behaviors (passive_sample, rate_limit)
gives observer semantics — a matched-but-not-firing rule counts the
event and falls through to later policies instead of returning
trigger:false. "Additionally wake me every Nth event" is now
expressible and safe by construction. Non-counting behaviors reject
the flag at validation.
- Shadow lint: conservative per-event-type subsumption over the finite
match language proves when an earlier rule makes a later rule
unreachable. Surfaced in the wake_add_rule result, a
gate:shadow-warnings trace on any config change, and gate_status.
Every warning is a true statement; absence of warnings is not proof.
- Dry-run probes: EventGate.probe()/probeTable() evaluate canonical
chat/heartbeat shapes with zero side effects (no counters, stats,
traces, debounce batches; sleep and gate.js ignored). wake_add_rule
now returns a before/after winners table with changed rows flagged —
the incident would have printed "dm: discord-direct-address →
stand-back-density" at install time.
- Anchored positions: addPolicy position {before:name}/{after:name}
(tool fields insertBefore/insertAfter), replacement-with-position
moves the rule (the repair path), and the wake_add_rule description
no longer recommends blanket prepend.
Full suite 558 tests, 0 fail; new event-gate-passthrough suite covers
all four pieces including the exact incident shape and its repair.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Bugbot is not enabled for your account, so this pull request was not reviewed. Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs. |
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 #105. Implements all four pieces, authorized by antra in #operating_room tonight ("Yes, please implement and deploy") after the Mythos stand-back-density incident.
What changed
①
passthrough: trueobserver rules — on the counting behaviors (passive_sample,rate_limit): a matched rule that doesn't fire counts the event and evaluation continues to later policies; when it fires it consumes as usual.evaluate()now runs an inline match/decide loop (first terminal match wins; passthrough non-fires accumulate indecision.observedand thegate:decisiontrace). Validation rejects the flag onalways/defer/skip/debounce.② Shadow lint —
findShadowedPolicies()does conservative per-event-type subsumption over the match language (scope overlap, glob equal-or-absent-or-*, OR-list subset formetadataTrue/tagsAny, constraint subset fortagsAll/tagsNone, identical filter). Every warning is a provably true "rule X makes rule Y unreachable for "; no warning ≠ proven safe. Surfaced in thewake_add_ruleresult, as agate:shadow-warningstrace on any config change (startup/reload/mutation, fingerprinted so unchanged sets don't re-trace), and ingate_status.③ Dry-run probes —
EventGate.probe()/probeTable(): side-effect-free evaluation (no counter/bucket mutation, no stats/traces, no debounce batching; sleep and gate.js deliberately ignored).wake_add_rulereturns a before/after winners table over canonical shapes (dm open/closed, mention open/closed, reply, ambient human/bot, heartbeat) with changed rows flagged. The incident would have showndm (open channel): discord-direct-address → stand-back-density, wouldWake falseat install time.④ Anchored positions —
addPolicyaccepts{before: name}/{after: name}(tool fieldsinsertBefore/insertAfter, mutually exclusive withposition); a missing anchor throws naming the available rules. Replacement without placement stays in place (unchanged); replacement WITH placement moves the rule — the repair path for a mis-placed rule. Tool description rewritten: ordering-is-semantics warning, steer to anchors/passthrough, prepend demoted to "only when it must beat everything".Compat
addPolicy/addGatePolicysignatures widen (existing'append'/'prepend'callers unaffected; ChannelModeModule untouched).GateStatusgainsshadowWarnings: string[];GateDecisiongains optionalobserved.passthrough— the evaluate loop is first-match-wins exactly as before.Tests
New
test/event-gate-passthrough.test.ts(17 tests) covering all four pieces, including the exact incident config shape, the passthrough fix, the probe-table diff that makes it visible, and the anchored-reissue repair. All 7 pre-existing gate suites green individually (100 tests); full suite 558 tests, 0 fail.🤖 Generated with Claude Code