feat(whatsapp): access control (WHATSAPP-001 Phase 2, #467) - #469
Merged
Merged
Conversation
Wire WhatsApp into the unified cross-channel access control system (#311) so WhatsApp identities can be gated by the same `agent_sharing` / `access_requests` primitives as Telegram/Slack/web. Pure application-only code — Phase 1 (#299) shipped the schema columns up-front. What ships: - `/login` / `/logout` / `/whoami` command handlers, dispatched by the Twilio webhook transport (short-circuiting the router gate so verification- state-changing commands aren't themselves gated on verification) - Redis-backed pending-login state with 10-minute TTL (`whatsapp_pending_login:{binding_id}:{phone}`) - Post-verification access gate inlined into `/login` so users learn their access status (shared / open_access / pending-approval) in the same message they verify on — matches Telegram UX - `access_requests.channel='whatsapp'` for restrictive-policy DMs - `proactive_message_service._deliver_whatsapp()` — explicit-only channel (not part of `auto` fallback), with chunking and partial-failure handling - Markdown → WhatsApp-native syntax conversion in `send_response` (`*bold*` not `**bold**`, `[text](url)` → `text (url)`) so agent markdown renders correctly on WhatsApp Tests: 28 new unit tests (67 total in test_whatsapp_adapter.py) + 10 new live-backend integration tests (test_whatsapp_integration.py). Integration tests POST real Twilio-signed webhook payloads and assert on DB state transitions for /login round-trip, pending-login Redis race, invalid-code rejection, restrictive/open-access gate outcomes, /logout, /whoami, bad-signature 403, unknown-secret 200. Related to #299 (Phase 1 merged as #463). Closes #467. Co-Authored-By: Claude Opus 4.7 (1M context) <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.
Summary
Wire WhatsApp into the unified cross-channel access control system (#311) so WhatsApp identities can be gated by the same
agent_sharing/access_requestsprimitives as Telegram/Slack/web.Pure application-only code — Phase 1 (#299, merged as #463) shipped the schema columns (
whatsapp_chat_links.verified_email,whatsapp_chat_links.verified_at) up-front specifically to keep this phase additive.What ships
Backend
adapters/whatsapp_adapter.py—/login,/logout,/whoamicommand handlers;_markdown_to_whatsappconverter (**bold**→*bold*,[text](url)→text (url));send_responseruns conversion on agent output; post-verification access gate inlined into/login.adapters/transports/twilio_webhook.py—_process_updatedetectsBody.startswith("/"), dispatches tohandle_command, short-circuiting the router pipeline (same pattern as Telegram so verification-state-changing commands aren't gated on verification).db/whatsapp_channels.py—update_chat_link_verified_email,clear_chat_link_verified_email,get_chat_links_by_verified_email(used by proactive delivery).services/proactive_message_service.py—_deliver_whatsapp()explicit-only channel (NOT part ofautofallback to avoid surprise Twilio charges), with 1600-char chunking and partial-failure handling.whatsapp_pending_login:{binding_id}:{phone}in Redis (TTL 600s).Docs
docs/memory/feature-flows/whatsapp-integration.md— new flows for/login, outbound markdown conversion, proactive_deliver_whatsapp.docs/memory/feature-flows/unified-channel-access-control.md— WhatsApp row added to channel table; new §WhatsApp section describing Redis-backed pending state and in-transport command dispatch rationale.docs/memory/feature-flows/proactive-messaging.md— WhatsApp as explicit-only delivery channel.docs/memory/architecture.md— adapter/DB descriptions updated;access_requests.channelcomment now includes'whatsapp'.docs/memory/feature-flows.md— index entry (2026-04-23)..claude/agents/test-runner.md— new test files registered.Key design decisions
/login)./loginitself isn't gated by the access gate it's establishing identity for. Matches Telegram exactly./login— users learn their access status (shared / open_access / pending approval) in the same message they verify on. Telegram UX precedent.*bold*not**bold**; plain URL after link text. Applied insend_responseso agent output renders correctly without agent-code awareness._deliver_whatsappis explicit-only — not inautofallback. Deliberately conservative: every WhatsApp message is billable via Twilio, and agents already have email + Telegram for auto-delivery.Test plan
pytest tests/test_whatsapp_adapter.py -v): 67 tests total (28 new). Covers markdown converter (7), command detection (2),/loginstate machine (7), access gate (3),/logout+/whoami(3),prompt_auth(1),_deliver_whatsappincl. chunking + partial failure (4),send_responsemarkdown integration (1).pytest tests/test_whatsapp_integration.py -v): 10 live-backend tests. Signs real Twilio-style webhook POSTs with HMAC-SHA1, asserts on DB state transitions inwhatsapp_chat_links,email_login_codes,access_requests. Requirestrinity-backendrunning athttp://localhost:8000. Handles the adapter's known race (sets Redis pending key after SMTP send) by polling Redis before submitting the code./login email→ receive code →/login 123456→ verify gate outcome matches agent policy.Dependencies
mainas671daac).twilio==9.10.5andredisfrom Phase 1.Out of scope (deferred to Phase 3)
Closes #467.
Related: #299, #311.
🤖 Generated with Claude Code