feat(botsfw): add SendGate so a platform can refuse a send#80
Merged
Conversation
bots-fw currently sends unconditionally, from code a platform cannot intercept.
That is safe on Telegram, where a bot may message any chat it knows at any time -
but that is a Telegram property, not a universal one.
WhatsApp permits free-form messages only within 24 hours of the recipient's last
reply. Outside that window a send fails with error 131047, and only a pre-approved
template may be delivered. Today a platform has no way to say "not now", so the
router would spend an API call to earn a rejection - or worse, deliver a billable
template the app never intended.
SendGate is an OPTIONAL interface on WebhookResponder:
type SendGate interface {
CanSend(c context.Context, m botmsg.MessageFromBot) error
}
A responder that does not implement it is treated as always permitting, so this
is additive rather than breaking. Verified: bots-fw-telegram builds and its tests
pass unchanged against this commit (via a temporary local replace).
This deliberately does NOT change WebhookResponder.SendMessage's signature, which
was the original proposal in the gap analysis. An optional interface achieves the
same refusal without breaking every existing responder, and gates BEFORE the send
rather than reporting after it - which matters when the attempt itself costs money.
Routed the three unconditional send sites through the seam:
- router.go processCommandResponse - every command response
- router.go - the "Unknown Type=%d" message
- driver.go - the panic handler, which pushes up to 3KB of stack trace into the
user's chat. Best-effort: the panic is already logged and sent to analytics, so
a refusal here is a warning, not an error.
Refusals log at Warning, not Error: a gated platform declining an unsolicited
message is the system working as designed, not a failure.
Tests: 8 new, covering the compatibility guarantee (ungated responders always
permit), that a refused send reaches no platform at all, and that refusals stay
classifiable through wrapping via errors.Is.
Background: spec/research/bots-fw-platform-neutrality-gap-analysis.md §1.1 in
bots-go-framework/backstage.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.
Why
bots-fwsends unconditionally, from code a platform cannot intercept. That's safe on Telegram, where a bot may message any chat it knows at any time — but that's a Telegram property, not a universal one.WhatsApp permits free-form messages only within 24 hours of the recipient's last reply. Outside that window the send fails with error
131047, and only a pre-approved template may be delivered. Meta's documented remediation is exact: "Send the recipient a template message instead."Today a platform has no way to say "not now". The router would spend an API call to earn a rejection — or worse, deliver a billable template message the app never intended.
This is §1.1 of the platform-neutrality gap analysis, the highest cost-of-delay item found while scoping the WhatsApp port, and the one blocker for
bots-fw-whatsapp.What
An optional interface on
WebhookResponder:A responder that doesn't implement it is treated as always permitting.
This is additive, not breaking — and that's verified
bots-fw-telegrambuilds and its tests pass unchanged against this branch (checked via a temporary localreplace; nothing in that repo was modified). Telegram's responder doesn't implementSendGate, so its behaviour is identical.Deliberate departure from the gap analysis
The gap analysis proposed getting a refusal into
WebhookResponder.SendMessage's return. Reading the actual code, an optional interface is better on two counts:Call sites routed through the seam
router.goprocessCommandResponserouter.go"Unknown Type=%d"messagedriver.goThe panic-handler send is best-effort: the panic is already logged and reported to analytics, so a refusal there is a warning rather than an error.
Refusals log at Warning, not Error throughout — a gated platform declining an unsolicited message is the system working as designed, not a failure.
Tests
8 new, pinning the things that actually matter:
SendMessageis never callederrors.Is/IsSendNotPermittedFull suite green;
gofmtandgo vetclean.What this does not do
Doesn't touch the other gap-analysis findings —
GetBotContextignoringplatformID, the deadBotAPISendMessageOverResponsedefault, or the integer-ID leaks inbotmsg. Those are separate changes.It also doesn't implement any gate:
bots-fw-whatsappwill supply the first realSendGate. This just creates somewhere for it to exist.🤖 Generated with Claude Code
https://claude.ai/code/session_01SE2shvgkXuR8fAzzMui4zN