7.6 KiB
phase, plan, subsystem, tags, dependency_graph, tech_stack, key_files, decisions, metrics
| phase | plan | subsystem | tags | dependency_graph | tech_stack | key_files | decisions | metrics | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 06-web-chat | 01 | backend |
|
|
|
|
|
|
Phase 6 Plan 01: Web Chat Backend Infrastructure Summary
One-liner: WebSocket endpoint + Redis pub-sub response bridge + RBAC REST API providing complete web chat plumbing from portal UI to the agent pipeline.
What Was Built
This plan establishes the complete backend for web chat — the "web" channel that lets portal users talk to AI employees directly from the Konstruct portal UI without setting up Slack or WhatsApp.
ChannelType.WEB and Redis key
ChannelType.WEB = "web" added to the shared message model. webchat_response_key(tenant_id, conversation_id) added to redis_keys.py following the established namespace pattern ({tenant_id}:webchat:response:{conversation_id}).
DB Schema (migration 008)
Two new tables with FORCE ROW LEVEL SECURITY:
web_conversations— one per (tenant_id, agent_id, user_id) triple with UniqueConstraint for get-or-create semanticsweb_conversation_messages— individual messages with TEXT+CHECK role column ('user'/'assistant') and CASCADE deletechannel_connections.channel_typeCHECK constraint replaced to include 'web'
WebSocket Endpoint (/chat/ws/{conversation_id})
Full message lifecycle in gateway/channels/web.py:
- Accept connection
- Auth handshake via first JSON message (browser limitation)
- For each message: typing indicator → save to DB → Celery dispatch → Redis subscribe → save response → send to client
- try/finally cleanup on all Redis connections
REST API (/api/portal/chat/*)
Four endpoints in shared/api/chat.py:
GET /conversations— list with RBAC (platform_admin sees all, others see own)POST /conversations— get-or-create with IntegrityError race condition handlingGET /conversations/{id}/messages— paginated history with cursor supportDELETE /conversations/{id}— message reset keeping conversation row
Orchestrator Integration
tasks.py updated:
handle_messagepopsconversation_idandportal_user_idbeforemodel_validate_build_response_extrashandles "web" case returning{conversation_id, tenant_id}_send_responsehandles "web" case with Redis pub-sub publish and try/finally cleanupwebchat_response_keyimported at module level
Test Coverage
19 unit tests written (TDD, all passing):
| Test | Covers |
|---|---|
test_webchat_response_key_format |
Key format correct |
test_webchat_response_key_isolation |
Tenant isolation |
test_channel_type_web_exists |
ChannelType.WEB |
test_normalize_web_event_* (5 tests) |
Message normalization CHAT-01 |
test_send_response_web_publishes_to_redis |
Redis pub-sub publish CHAT-02 |
test_send_response_web_connection_cleanup |
try/finally Redis cleanup |
test_send_response_web_missing_conversation_id_logs_warning |
Error handling |
test_typing_indicator_sent_before_dispatch |
Typing indicator CHAT-05 |
test_chat_rbac_enforcement |
403 for non-member CHAT-04 |
test_platform_admin_cross_tenant |
Admin bypass CHAT-04 |
test_list_conversation_history |
Paginated messages CHAT-03 |
test_create_conversation |
Get-or-create CHAT-03 |
test_create_conversation_rbac_forbidden |
403 for non-member |
test_delete_conversation_resets_messages |
Message reset |
Full 313-test suite passes.
Deviations from Plan
Auto-fixed Issues
1. [Rule 1 - Bug] Test dependency injection: patch vs dependency_overrides
- Found during: Task 1 test implementation
- Issue:
patch("shared.db.get_session")doesn't work for FastAPI endpoint testing because FastAPI's dependency injection resolvesDepends(get_session)at function definition time, not via module attribute lookup - Fix: Used
app.dependency_overrides[get_session] = _override_get_sessionpattern in test helper_make_app_with_session_override()— consistent with other test files in the project - Files modified:
tests/unit/test_chat_api.py
2. [Rule 2 - Missing functionality] session.refresh mock populating server defaults
- Found during: Task 1 create_conversation test
- Issue: Mocked
session.refresh()was a no-op, leavingcreated_at/updated_atasNoneon new ORM objects (server_default not applied without real DB) - Fix: Test uses an async side_effect function that populates datetime fields on the object passed to
refresh() - Files modified:
tests/unit/test_chat_api.py
Self-Check: PASSED
All key artifacts verified:
ChannelType.WEB = "web"— present in message.pywebchat_response_key()— present in redis_keys.pyWebConversationORM class — present in models/chat.pychat_websocketWebSocket endpoint — present in gateway/channels/web.pychat_router— exported from shared/api/init.pyweb_conversationstable — created in migration 008- Commits
c72beb9and56c11a0— verified in git log - 313/313 unit tests pass