Chat Architecture
System Context
The chat system operates within the broader Phenom ecosystem, connecting mobile users, AI agents, and support staff through real-time messaging.
(React Native)"] AI["AI Agents
(MCP clients)"] SS["Support Staff
(Synapse Admin UI)"] end subgraph "Phenom Chat System" CHAT["Chat Platform
Dual-implementation A/B
Matrix Synapse + Hasura Lite"] end subgraph "External Systems" COG["AWS Cognito
Identity Provider"] PHENOM["Phenom Backend
Hasura GraphQL Engine"] CW["CloudWatch
Logging & Monitoring"] end MU -->|"Send/receive messages"| CHAT AI -->|"9 MCP tools"| CHAT SS -->|"Admin UI"| CHAT CHAT -->|"OIDC SSO"| COG CHAT -->|"Link preview data"| PHENOM CHAT -->|"Logs & metrics"| CW style CHAT fill:#121010,color:#a5e3e8,rx:30 style COG fill:#151515,color:#e0e0e0,rx:30
Component Architecture
Component Details
| Component | Image / Runtime | Port | CPU | Memory | Purpose |
|---|---|---|---|---|---|
| Synapse Homeserver | applepublicdotcom/phenom-synapse:testing | 8008 | 512 | 1024 MiB | Matrix homeserver (Implementation A) |
| Synapse Admin UI | awesometechnologies/synapse-admin:latest | 80 | 256 | 512 MiB | Web-based admin dashboard for Synapse |
| Chat MCP Server | applepublicdotcom/phenom-chat-mcp:testing | 3001 | 256 | 512 MiB | MCP tool server with 9 chat operations |
| Link Preview Lambda | Node.js 18.x | – | – | 256 MiB | Resolves Phenom URLs to preview cards |
| User Provisioner Lambda | Node.js 18.x | – | – | 128 MiB | Cognito post-auth trigger, provisions chat membership |
| RDS PostgreSQL 17.4 | db.m5.large | 5432 | – | – | Synapse database + Hasura Lite chat tables |
ALB Routing Rules
All traffic enters through the Application Load Balancer on chat-testing.thephenom.app. Path-based rules route requests to the correct backend:
| Priority | Path Pattern | Target | Port |
|---|---|---|---|
| 200 | /_matrix/*, /_synapse/* | Synapse target group | 8008 |
| 201 | /chat-admin/* | Synapse Admin UI target group | 80 |
| 300 | /mcp/* | MCP Server target group | 3001 |
Security Groups
ALB Security Group
├── Inbound: 443 (HTTPS) from 0.0.0.0/0
└── Outbound: All traffic
Synapse Security Group
├── Inbound: 8008 from ALB SG
├── Outbound: 5432 to RDS SG (PostgreSQL)
└── Outbound: 443 to 0.0.0.0/0 (Cognito OIDC, federation)
ECS Tasks Security Group (MCP, Admin UI)
├── Inbound: 3001, 80 from ALB SG
└── Outbound: All traffic
RDS Security Group
├── Inbound: 5432 from Synapse SG
├── Inbound: 5432 from ECS Tasks SG
└── Outbound: None
Database Schema
Hasura Lite Tables (Implementation B)
All tables exist in the public schema of the shared RDS PostgreSQL instance.
chat_rooms
| Column | Type | Constraints | Description |
|---|---|---|---|
id | UUID | PRIMARY KEY, DEFAULT gen_random_uuid() | Room identifier |
name | TEXT | NOT NULL | Display name (“Phenom Internal”, “Phenom Partners”, “Phenom Community”) |
description | TEXT | – | Room description |
created_at | TIMESTAMPTZ | DEFAULT now() | Creation timestamp |
is_active | BOOLEAN | DEFAULT true | Whether the room accepts new messages |
Seeded with three rooms: Phenom Internal, Phenom Partners, Phenom Community.
chat_messages
| Column | Type | Constraints | Description |
|---|---|---|---|
id | UUID | PRIMARY KEY, DEFAULT gen_random_uuid() | Message identifier |
room_id | UUID | NOT NULL, FK → chat_rooms(id) | Room this message belongs to |
user_id | TEXT | NOT NULL, FK → users(id) | Sender’s user ID |
content | TEXT | NOT NULL, CHECK (char_length <= 4000) | Message body (max 4000 chars) |
message_type | TEXT | DEFAULT 'text', CHECK IN ('text', 'phenom_link') | Message type |
phenom_id | TEXT | – | Phenom content ID (for phenom_link messages) |
is_deleted | BOOLEAN | DEFAULT false | Soft-delete flag |
deleted_by | TEXT | FK → users(id) | Who deleted the message |
created_at | TIMESTAMPTZ | DEFAULT now() | Creation timestamp |
updated_at | TIMESTAMPTZ | DEFAULT now() | Auto-updated via trigger |
Indexes:
idx_chat_messages_room_createdon(room_id, created_at DESC)– primary query pathidx_chat_messages_useron(user_id)– user message lookups
chat_members
| Column | Type | Constraints | Description |
|---|---|---|---|
id | UUID | PRIMARY KEY, DEFAULT gen_random_uuid() | Membership record ID |
room_id | UUID | NOT NULL, FK → chat_rooms(id) | Room |
user_id | TEXT | NOT NULL, FK → users(id) | User |
role | TEXT | DEFAULT 'user', CHECK IN ('user', 'support', 'admin') | Membership role |
is_muted | BOOLEAN | DEFAULT false | Whether user is muted |
muted_until | TIMESTAMPTZ | – | Mute expiration (null = indefinite if muted) |
joined_at | TIMESTAMPTZ | DEFAULT now() | When user joined |
Constraints: UNIQUE(room_id, user_id)
Index: idx_chat_members_room on (room_id)
chat_bans
| Column | Type | Constraints | Description |
|---|---|---|---|
id | UUID | PRIMARY KEY, DEFAULT gen_random_uuid() | Ban record ID |
room_id | UUID | NOT NULL, FK → chat_rooms(id) | Room |
user_id | TEXT | NOT NULL, FK → users(id) | Banned user |
banned_by | TEXT | NOT NULL, FK → users(id) | Admin who issued the ban |
reason | TEXT | – | Ban reason |
banned_at | TIMESTAMPTZ | DEFAULT now() | When the ban was issued |
expires_at | TIMESTAMPTZ | – | Ban expiration (null = permanent) |
Index: idx_chat_bans_room_user on (room_id, user_id)
link_previews
| Column | Type | Constraints | Description |
|---|---|---|---|
id | UUID | PRIMARY KEY, DEFAULT gen_random_uuid() | Preview record ID |
url | TEXT | NOT NULL | Full URL |
phenom_id | TEXT | UNIQUE | Phenom content ID |
title | TEXT | – | Preview title |
description | TEXT | – | Preview description |
thumbnail_url | TEXT | – | Preview image URL |
media_type | TEXT | – | Content type (video, image, etc.) |
resolved_at | TIMESTAMPTZ | DEFAULT now() | When the preview was resolved |
expires_at | TIMESTAMPTZ | – | Cache expiration |
Index: idx_link_previews_phenom on (phenom_id)
Entity Relationship Diagram
Sequence Diagrams
Authentication Flow – Implementation A (Matrix/Synapse SSO)
Authentication Flow – Implementation B (Hasura Lite)
Send Message Flow
(Synapse or Hasura) participant LP as Link Preview Lambda participant RDS as PostgreSQL App->>Backend: Send message (content, room_id) alt Implementation A (Matrix) Backend->>RDS: Store event in Synapse DB Backend-->>App: Event ID else Implementation B (Hasura) Backend->>RDS: INSERT into chat_messages Backend-->>App: Message object end alt Message contains Phenom URL RDS->>LP: Hasura event trigger (INSERT on chat_messages with phenom_link type) LP->>LP: Fetch Phenom content metadata via GraphQL LP->>RDS: INSERT/UPDATE link_previews LP-->>RDS: Preview resolved end Backend-->>App: Real-time notification to other clients
Link Preview Resolution
Moderation Flow
Security Architecture
Transport Security
- All external traffic uses HTTPS (TLS 1.2+) terminated at the ALB.
- Internal traffic between ECS tasks and RDS uses private VPC networking (no public exposure).
- WebSocket connections for GraphQL subscriptions use WSS (TLS-encrypted WebSockets).
Authentication & Authorization
| Layer | Mechanism | Details |
|---|---|---|
| User identity | AWS Cognito | Email + password, optional MFA |
| Synapse auth | OIDC SSO | Cognito as identity provider via authorization code flow |
| Hasura auth | JWT validation | Cognito ID token with x-hasura-* claims |
| MCP auth | Cognito agent credentials | USER_PASSWORD_AUTH flow with machine account |
| Admin API | Bearer token | Synapse admin access token |
Role-Based Access Control (Hasura Lite)
Hasura permissions are configured per-role for each table:
| Role | chat_rooms | chat_messages | chat_members | chat_bans | link_previews |
|---|---|---|---|---|---|
user | SELECT | SELECT (non-deleted), INSERT (own) | SELECT (own room membership) | – | SELECT |
support | SELECT | SELECT, UPDATE (soft-delete) | SELECT | SELECT, INSERT | SELECT |
admin | SELECT, UPDATE | SELECT, UPDATE, DELETE | SELECT, UPDATE, INSERT | SELECT, INSERT, DELETE | SELECT, INSERT |
Rate Limiting
| Backend | Mechanism | Limits |
|---|---|---|
| Synapse | Built-in rc_message config | 10 messages/second, burst of 30 |
| Hasura Lite | Application-level + Cognito token expiry | JWT-gated; 1 message per request |
| MCP Server | Cognito token validation | Agent credentials with token caching (5-min refresh margin) |
Infrastructure
Terraform Module Structure
modules/
├── chat-shared/ # Cognito clients (OIDC, agent), Secrets Manager
├── chat-link-preview/ # Link preview Lambda function
├── chat-hasura-lite/ # SQL migration, Hasura metadata setup
├── chat-synapse/ # Synapse ECS service, Admin UI, security groups, DB setup
└── chat-mcp-server/ # MCP server ECS service, ALB rules
ECS Task Sizing
| Service | CPU (units) | Memory (MiB) | Desired Count | Launch Type |
|---|---|---|---|---|
| Synapse | 512 | 1024 | 1 | Fargate |
| Synapse Admin UI | 256 | 512 | 1 | Fargate |
| Chat MCP Server | 256 | 512 | 1 | Fargate |
Health Checks
| Service | Path | Interval | Timeout | Retries | Start Period |
|---|---|---|---|---|---|
| Synapse | /health | 30s | 5s | 3 | 60s |
| MCP Server | /health | 30s | 5s | 3 | 60s |
| Admin UI | / | 30s | 5s | 3 | 30s |
Related Documentation
- API Reference – complete API specifications
- Admin & Operations – monitoring and troubleshooting
- Deployment Guide – Terraform deployment procedures
Feedback
Was this page helpful?
Glad to hear it! Please tell us how we can improve.
Sorry to hear that. Please tell us how we can improve.