Skip to content

feat(whatsapp): access control (WHATSAPP-001 Phase 2, #467) - #469

Merged
vybe merged 1 commit into
mainfrom
feature/467-whatsapp-access-control
Apr 23, 2026
Merged

vybe merged 1 commit into
mainfrom
feature/467-whatsapp-access-control

Conversation

@vybe

@vybe vybe commented Apr 23, 2026

Copy link
Copy Markdown
Contributor

Summary

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, 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, /whoami command handlers; _markdown_to_whatsapp converter (**bold** → *bold*, [text](url) → text (url)); send_response runs conversion on agent output; post-verification access gate inlined into /login.
  • adapters/transports/twilio_webhook.py — _process_update detects Body.startswith("/"), dispatches to handle_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 of auto fallback to avoid surprise Twilio charges), with 1600-char chunking and partial-failure handling.
  • Pending-login state keyed 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.channel comment 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

  1. Pending state in Redis, not per-process dict — survives backend restarts (Redis stays up). Same recovery UX as Telegram if Redis is unavailable (re-issue /login).
  2. Command dispatch in the transport, not the router — so /login itself isn't gated by the access gate it's establishing identity for. Matches Telegram exactly.
  3. Access gate inlined into /login — users learn their access status (shared / open_access / pending approval) in the same message they verify on. Telegram UX precedent.
  4. WhatsApp-native markdown — *bold* not **bold**; plain URL after link text. Applied in send_response so agent output renders correctly without agent-code awareness.
  5. _deliver_whatsapp is explicit-only — not in auto fallback. Deliberately conservative: every WhatsApp message is billable via Twilio, and agents already have email + Telegram for auto-delivery.

Test plan

  • Unit tests pass (pytest tests/test_whatsapp_adapter.py -v): 67 tests total (28 new). Covers markdown converter (7), command detection (2), /login state machine (7), access gate (3), /logout+/whoami (3), prompt_auth (1), _deliver_whatsapp incl. chunking + partial failure (4), send_response markdown integration (1).
  • Integration tests pass (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 in whatsapp_chat_links, email_login_codes, access_requests. Requires trinity-backend running at http://localhost:8000. Handles the adapter's known race (sets Redis pending key after SMTP send) by polling Redis before submitting the code.
  • Manual E2E: Twilio Sandbox → /login email → receive code → /login 123456 → verify gate outcome matches agent policy.

Dependencies

Out of scope (deferred to Phase 3)

  • SMS on the same Twilio binding
  • WhatsApp Business templates (outbound-first outside 24h window)
  • Interactive buttons
  • Voice-note transcription

Closes #467.
Related: #299, #311.

🤖 Generated with Claude Code

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>
@vybe
vybe merged commit c5703a6 into main Apr 23, 2026
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.

feat(whatsapp): access control integration (WHATSAPP-001 Phase 2, #299 follow-up)

1 participant