Skip to content

gate: intuitive wake-rule composition (closes #105) - #106

Merged
antra-tess merged 1 commit into
mainfrom
gate/intuitive-wake-rules
Aug 8, 2026
Merged

gate: intuitive wake-rule composition (closes #105)#106
antra-tess merged 1 commit into
mainfrom
gate/intuitive-wake-rules

Conversation

@antra-tess

Copy link
Copy Markdown
Collaborator

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: true observer 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 in decision.observed and the gate:decision trace). Validation rejects the flag on always/defer/skip/debounce.

② Shadow lintfindShadowedPolicies() does conservative per-event-type subsumption over the match language (scope overlap, glob equal-or-absent-or-*, OR-list subset for metadataTrue/tagsAny, constraint subset for tagsAll/tagsNone, identical filter). Every warning is a provably true "rule X makes rule Y unreachable for "; no warning ≠ proven safe. Surfaced in the wake_add_rule result, as a gate:shadow-warnings trace on any config change (startup/reload/mutation, fingerprinted so unchanged sets don't re-trace), and in gate_status.

③ Dry-run probesEventGate.probe() / probeTable(): side-effect-free evaluation (no counter/bucket mutation, no stats/traces, no debounce batching; sleep and gate.js deliberately ignored). wake_add_rule returns 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 shown dm (open channel): discord-direct-address → stand-back-density, wouldWake false at install time.

④ Anchored positionsaddPolicy accepts {before: name} / {after: name} (tool fields insertBefore / insertAfter, mutually exclusive with position); 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/addGatePolicy signatures widen (existing 'append'/'prepend' callers unaffected; ChannelModeModule untouched).
  • GateStatus gains shadowWarnings: string[]; GateDecision gains optional observed.
  • No behavior change for configs without 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

… 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>
@cursor

cursor Bot commented Aug 8, 2026

Copy link
Copy Markdown

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.

@antra-tess
antra-tess merged commit 1e7cfc2 into main Aug 8, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Wake gate: make rule composition intuitive — passthrough samplers, shadow lint, dry-run probe table, anchored positions

1 participant