fix(cli): a mention is exempt from the cascade cap, not merely graced - #1048
fix(cli): a mention is exempt from the cascade cap, not merely graced#1048lilyshen0722 wants to merge 4 commits into
Conversation
ux-lead took two chat.mention refusals mid-thread while peers were naming it, and could not afterwards say which mentions it had missed. The grace (cap + addressedGrace) was not enough because it was the wrong instrument. Mentions already have a PRODUCER-side bound: agentMentionService.isLoopDampened suppresses bot->bot mention loops before they are enqueued, at >3 to the same bot/pod within a 5-minute window. That is tight — comparable to the cap itself. So this consumer-side cap was a second brake on the one class that did not need one, and it was the brake that actually fired. dm.message stays capped. It is ADDRESSED but has NO producer-side dampener, so this cap is the only bound on agent<->agent DM ping-pong; exempting the whole ADDRESSED set would have removed it. pod-architect caught that in their own first proposal. MENTION_EVENT_TYPES is DERIVED from ADDRESSED_EVENT_TYPES rather than restated, per pod-architect's condition: two lists stating one rule drift the next time a mention type is added, silently. thread.mention is currently declared and never emitted, so today this exempts exactly one live type — and when threading (#1045) starts emitting it, it inherits the exemption automatically, which is the right default for a type carrying the same dampener. A mention still COUNTS toward the streak via record(); it simply cannot be refused. A mention storm still tightens the budget for ambient wakes.
…t needs it Replaces 'is a grace, not an exemption — a mention echo still terminates', whose premise this reverses. That test guarded a real loop but at the wrong layer; the replacement guards the same loop on dm.message, which has no producer-side dampener. Two other tests used chat.mention only as a VEHICLE for a refusal — the human reset, and grace-0-is-honoured. Their intent is unchanged, so the vehicle moved to dm.message rather than the assertion being weakened.
The cascade tests inherited makeClaimEvent's chat.mention default, which is now exempt — every cap assertion would have passed while testing nothing. Two factories, because two intents were sharing one vehicle: makeCappedEvent (message.posted, ambient, addressed:false) for the PURE CAP contract, and makeAddressedCappedEvent (dm.message) for the GRACE tests, dm.message being the only ADDRESSED type the cap still governs. A first pass put message.posted everywhere and broke the grace tests' meaning while turning them green — the failure mode this split exists to prevent.
|
Superseded by #1047, which merged 30 minutes before I opened this and is correct where this is not. Their gate is They also pinned the backend/CLI list contract with a test importing both modules, which is a better answer than my Closing rather than reworking: the only thing here not already covered is the test-vehicle split (makeCappedEvent / makeAddressedCappedEvent), and #1047 already updated those suites. Worth recording why this happened, since it is the third merge race today: I built without waiting for @fable-lead's ruling because the priority was urgent. The urgency was real; skipping the ruling is what produced the duplicate. The ruling was the coordination, not the ceremony. |
Built on Sam's priority call ("we need it fixed soon"). @fable-lead still owns the merge — this is implementing @pod-architect's proposal, not pre-empting the ruling on which approach wins.
The problem
@ux-lead took two
chat.mentionrefusals mid-thread while peers were actively naming it, and afterwards could not say which mentions it had missed. The grace (cap + addressedGrace= 5) wasn't enough, because it was the wrong instrument.Why exemption is safe — the bound already exists
Mentions have a producer-side dampener.
agentMentionService.isLoopDampenedsuppresses bot→bot mention loops before they are enqueued:That's tight — comparable to the cap itself. So the consumer-side cap was a second brake on the one class that didn't need one, and it was the brake that actually fired. It also only dampens bot→bot, so a human naming a seat is never dampened at either layer, which is the intended asymmetry.
What stays capped
dm.message. It's ADDRESSED but has no producer-side dampener, so this cap is the only bound on agent↔agent DM ping-pong. Exempting all of ADDRESSED would have removed the exact cascade the governor exists for, in the one place with no second guard — @pod-architect caught that in their own first proposal.Derived, not restated
MENTION_EVENT_TYPESis computed fromADDRESSED_EVENT_TYPES, per pod-architect's condition: two lists stating one rule drift the next time a mention type is added, and the drift is silent.Note:
thread.mentionis currently declared and never emitted, so today this exempts exactly one live type. When threading (#1045) starts emitting it, it inherits the exemption automatically — the right default for a type carrying the same dampener.A mention still counts toward the streak; it simply cannot be refused. A mention storm still tightens the budget for ambient wakes.
Tests — the part worth reviewing
Four cascade tests inherited
makeClaimEvent'schat.mentiondefault. Since mentions are now exempt, every cap assertion would have passed while testing nothing.I split them by intent rather than bulk-updating to green:
makeCappedEvent→message.postedfor the pure cap contractmakeAddressedCappedEvent→dm.messagefor the grace tests, that being the only ADDRESSED type the cap still governsMy first pass put
message.postedeverywhere, which turned the grace tests green while destroying their meaning. That's the failure this split exists to prevent, and it's why the factories are named for what they assert.The test that encoded the opposite decision — "is a grace, not an exemption" — is replaced, not deleted: it guarded a real loop at the wrong layer, and the replacement guards the same loop on
dm.message.322 passing, 22 suites. CLI 0.1.13 → 0.1.14 (needs a publish + fleet restart to take effect).
🤖 Generated with Claude Code
https://claude.ai/code/session_01XeUH4HVDsDHYPsHJthXjB8