Includes spec, plan, research, data model, contracts, and quickstart guide for mautrix-slack Socket Mode bridge deployment.
23 KiB
Data Model: Slack↔Matrix Bridge
Feature: 002-slack-bridge-integration Created: 2025-10-22 Status: Design Complete
Overview
This document defines the conceptual data model for the mautrix-slack bridge. Since this is infrastructure configuration (not application code), the model focuses on configuration entities, runtime state, and operational data flows.
Key Insight: Most data is managed internally by mautrix-slack (PostgreSQL database). Our model focuses on configuration inputs and observable runtime state relevant to NixOS deployment.
1. Configuration Entities
1.1 Bridge Service
Description: The mautrix-slack service instance
Properties:
| Property | Type | Source | Description |
|---|---|---|---|
workspace |
string | NixOS config | Slack workspace name ("chochacho") |
homeserverUrl |
URL | NixOS config | Matrix homeserver address (http://127.0.0.1:8008) |
serverName |
domain | NixOS config | Matrix server domain (clarun.xyz) |
databaseUri |
URI | NixOS config | PostgreSQL connection string |
port |
integer | NixOS config | Appservice listen port (29319) |
commandPrefix |
string | NixOS config | Bridge command prefix ("!slack") |
permissions |
map | NixOS config | Domain → permission level mappings |
loggingLevel |
enum | NixOS config | Log verbosity (debug/info/warn/error) |
conversationCount |
integer | config.yaml | Number of recent chats to sync on login |
Lifecycle:
- Created: NixOS configuration deployment
- Modified: Configuration updates → rebuild
- Destroyed: Service disabled in config
State Transitions: See section 3.1 (Bridge Service State Machine)
1.2 Slack Credentials
Description: Authentication tokens for Slack API
Properties:
| Property | Type | Source | Description |
|---|---|---|---|
botToken |
secret (xoxb-) | sops-nix → bridge DB | Slack bot OAuth token |
appToken |
secret (xapp-) | sops-nix → bridge DB | Slack app-level token (Socket Mode) |
workspace |
string | Interactive login | Slack workspace identifier (T...) |
Lifecycle:
- Created: Slack app configuration → manual token generation
- Stored: Provided via
login appcommand → bridge database - Rotated: Manual token regeneration → re-authentication
- Revoked: Slack app settings or user removes app
Security Requirements:
- Tokens never in Nix store (evaluation-time exposure risk)
- Tokens never in config.yaml (file permission risk)
- Tokens stored in bridge PostgreSQL database (encrypted at rest via LUKS)
- Optional: Encrypt in sops-nix for disaster recovery (not used by bridge directly)
1.3 Matrix Appservice Registration
Description: Matrix homeserver configuration for bridge integration
Properties:
| Property | Type | Source | Description |
|---|---|---|---|
id |
string | Generated | Appservice identifier ("slack") |
url |
URL | Generated | Bridge endpoint (http://127.0.0.1:29319) |
asToken |
secret | Generated | Appservice → homeserver auth |
hsToken |
secret | Generated | Homeserver → appservice auth |
senderLocalpart |
string | Generated | Bot user localpart ("slackbot") |
usernameTemplate |
string | Generated | Ghost user format ("slack_{{.}}") |
namespaces.users |
list | Generated | Reserved user namespaces |
Lifecycle:
- Created: First service start (
mautrix-slack -g -r registration.yaml) - Modified: Rarely (only on namespace changes)
- Consumed: Loaded by Matrix homeserver (conduwuit)
File Location: /var/lib/matrix-appservices/mautrix_slack_registration.yaml
1.4 Channel Portal
Description: A bridged Slack channel ↔ Matrix room pair
Properties (stored in mautrix-slack database):
| Property | Type | Description |
|---|---|---|
slackChannelId |
string | Slack channel ID (C...) |
matrixRoomId |
string | Matrix room ID (!...clarun.xyz) |
channelName |
string | Slack channel name (#dev-platform) |
roomAlias |
string | Matrix room alias (#slack_dev-platform:clarun.xyz) |
topic |
string | Channel topic/description |
members |
list | Slack users in channel |
encrypted |
boolean | Whether Matrix room is encrypted |
createdAt |
timestamp | Portal creation time |
lastActivity |
timestamp | Last message timestamp |
Lifecycle: See section 3.3 (Channel Portal State Machine)
Observable via:
- Matrix room list (user perspective)
- Bridge database queries (admin perspective)
- Bot command:
!slack status(if implemented)
2. Runtime State Entities
2.1 Socket Mode Connection
Description: WebSocket connection to Slack's real-time messaging service
Properties:
| Property | Type | Description |
|---|---|---|
websocketUrl |
URL | Dynamic WebSocket URL (wss://wss.slack.com/link/...) |
connectionState |
enum | disconnected / connecting / connected / refreshing |
connectionId |
string | Unique connection identifier |
connectedAt |
timestamp | When connection established |
refreshAt |
timestamp | Estimated refresh time (~2-4 hours) |
lastHeartbeat |
timestamp | Last ping/pong from Slack |
reconnectAttempts |
integer | Consecutive failed reconnection count |
rateLimit |
timestamp | Earliest next connection attempt (1/minute limit) |
State Transitions: See section 3.2 (Socket Mode Connection State Machine)
Observable via:
- Service logs:
journalctl -u mautrix-slack -f - Health indicators: Connection status, last successful message timestamp
2.2 Ghost User
Description: Matrix representation of a Slack user
Properties:
| Property | Type | Description |
|---|---|---|
matrixUserId |
string | Ghost user MXID (@slack_U123ABC:clarun.xyz) |
slackUserId |
string | Slack user ID (U...) |
displayName |
string | Synced from Slack profile |
avatarUrl |
mxc:// | Synced from Slack avatar |
isBot |
boolean | Whether user is a bot account |
email |
string | Slack user email (if available) |
slackTeam |
string | Workspace identifier |
Lifecycle:
- Created: First message from Slack user in bridged channel
- Updated: Slack profile changes → synced to Matrix
- Deactivated: User leaves workspace (profile retained but inactive)
Namespace: @slack_*:clarun.xyz (reserved via appservice registration)
2.3 Message Event
Description: A bridged message in transit
Properties:
| Property | Type | Description |
|---|---|---|
sourceService |
enum | slack / matrix |
sourceEventId |
string | Slack ts or Matrix event ID |
targetEventId |
string | Event ID in destination service |
messageType |
enum | text / image / file / reaction / edit / delete |
content |
object | Message payload (text, attachments, etc.) |
sender |
string | User ID in source service |
channel |
string | Portal ID |
timestamp |
timestamp | Message send time |
deliveredAt |
timestamp | When relayed to destination |
latency |
duration | deliveredAt - timestamp (should be <5s) |
Lifecycle (ephemeral):
- Received: Slack WebSocket event or Matrix /transactions POST
- Transformed: Format conversion (Slack JSON ↔ Matrix JSON)
- Sent: Posted to destination API
- Acknowledged: Event ID stored for deduplication
Observable via:
- Bridge logs (debug level)
- Health metrics: Message count, delivery latency
- Spec requirement: FR-001/FR-002 (5 second latency SLA)
3. State Machines
3.1 Bridge Service State Machine
┌─────────────┐
│ Disabled │ (services.mautrix-slack.enable = false)
└──────┬──────┘
│ nixos-rebuild switch (enable = true)
↓
┌─────────────┐
│ Starting │ ExecStartPre: Generate config, create registration
└──────┬──────┘
│ Config valid, database reachable
↓
┌─────────────┐
│Unauthenticated (service running, waiting for `login app`)
└──────┬──────┘
│ User sends `login app` command, provides tokens
↓
┌─────────────┐
│ Connecting │ Establishing Socket Mode WebSocket
└──────┬──────┘
│ WebSocket handshake successful
↓
┌─────────────┐
│ Active │ Normal operation (relaying messages)
└──┬─────┬────┘
│ │ Connection refresh (every ~2-4 hours)
│ └──→ Connecting (automatic reconnection)
│
│ Configuration error, auth revoked, database failure
↓
┌─────────────┐
│ Failed │ Service exits (systemd restarts after 10s)
└──────┬──────┘
│ systemd RestartSec expires
└──→ Starting
Key Observations:
- Unauthenticated state is valid: Service can run without Slack credentials
- Automatic restart: systemd handles crash recovery
- Connection refresh is normal: Not a failure state, automatic transition
3.2 Socket Mode Connection State Machine
┌─────────────┐
│Disconnected │ Initial state or after connection loss
└──────┬──────┘
│ Bridge has valid credentials
↓
┌─────────────┐
│Requesting URL Call apps.connections.open API
└──────┬──────┘
│ API returns wss:// URL (rate limit: 1/minute)
↓
┌─────────────┐
│ Connecting │ WebSocket handshake in progress
└──────┬──────┘
│ Receives "hello" message from Slack
↓
┌─────────────┐
│ Connected │ Receiving events, acknowledging with envelope_id
└──┬───┬───┬──┘
│ │ │ Slack sends "warning" disconnect (10s notice)
│ │ └──→ Refreshing
│ │
│ │ Network error, timeout, Slack backend restart
│ └──→ Disconnected (immediate reconnection attempt)
│
│ Normal operation continues
↓
┌─────────────┐
│ Refreshing │ Graceful connection renewal
└──────┬──────┘
│ Fetch new WebSocket URL
└──→ Requesting URL
Error Paths:
- Rate limited: Stay in Disconnected, retry after 1 minute
- Auth invalid: Transition to Failed (requires re-authentication)
- Network partition: Exponential backoff reconnection attempts
Health Indicators:
connection_status: current state namelast_successful_message: timestamp of last eventreconnection_attempts: incremented on failed connections, reset on success
3.3 Channel Portal State Machine
┌─────────────┐
│ Pending │ User receives message in unbridged Slack channel
└──────┬──────┘
│ Bridge auto-creates portal
↓
┌─────────────┐
│ Creating │ Allocating Matrix room, sending invites
└──────┬──────┘
│ Room created, Matrix users invited
↓
┌─────────────┐
│ Active │ Relaying messages bidirectionally
└──┬───┬───┬──┘
│ │ │ Slack channel archived
│ │ └──→ Archived
│ │
│ │ Admin runs delete-portal command (if available)
│ └──→ Deleting
│
│ Normal message relay continues
↓
(Active - steady state)
┌─────────────┐
│ Archived │ Slack channel is read-only
└──────┬──────┘
│ Slack channel unarchived
└──→ Active
┌─────────────┐
│ Deleting │ Cleanup: kick users, delete room, remove from DB
└──────┬──────┘
│ Cleanup complete
↓
┌─────────────┐
│ Deleted │ Portal removed (can be recreated if needed)
└─────────────┘
Key Properties by State:
- Pending: Not yet in bridge database
- Creating: Room exists but membership incomplete
- Active:
lastActivityupdates on each message - Archived: Read-only, no new messages flow
- Deleted: Database record removed, room unlinked
4. Relationships
4.1 Entity Relationship Diagram
┌──────────────────┐
│ Bridge Service │
└────────┬─────────┘
│ 1
│
│ manages
│
↓ N
┌──────────────────┐ ┌──────────────────┐
│ Socket Connection│←──────│ Slack Credentials│
└────────┬─────────┘ 1 └──────────────────┘
│ uses
│
│ receives events via
│
↓ N
┌──────────────────┐
│ Channel Portal │
└────────┬─────────┘
│ bridges
│
↓ N
┌──────────────────┐ ┌──────────────────┐
│ Message Event │───────│ Ghost User │
└──────────────────┘ from └──────────────────┘
│ N │ N
│ │
│ relays to │ represents
↓ 1 ↓ 1
┌──────────────────┐ ┌──────────────────┐
│ Matrix Room │ │ Slack User │
└──────────────────┘ └──────────────────┘
4.2 Cardinality Table
| Entity A | Relationship | Entity B | Cardinality | Notes |
|---|---|---|---|---|
| Bridge Service | manages | Socket Connection | 1:1 | One WebSocket per bridge instance |
| Bridge Service | creates | Channel Portal | 1:N | Multiple channels bridged |
| Socket Connection | uses | Slack Credentials | 1:1 | Credentials shared across portals |
| Channel Portal | contains | Message Event | 1:N | Many messages per channel |
| Channel Portal | links | Matrix Room | 1:1 | Bidirectional mapping |
| Ghost User | sends | Message Event | 1:N | User can send many messages |
| Ghost User | represents | Slack User | 1:1 | One MXID per Slack user per workspace |
| Appservice Registration | reserves | Ghost User namespace | 1:N | All @slack_*:clarun.xyz |
5. Data Flow Diagrams
5.1 Message Flow: Slack → Matrix
Slack User
│ Posts message in #dev-platform
↓
Slack API (WebSocket event)
│ message.channels event
↓
mautrix-slack (Socket Mode listener)
│ 1. Acknowledge event (envelope_id)
│ 2. Check portal exists for channel
│ 3. Transform message format
│ 4. Lookup/create ghost user
↓
Matrix Homeserver (/_matrix/app/v1/transactions)
│ PUT transaction with event
↓
Matrix Room (#slack_dev-platform:clarun.xyz)
│ Event appears in room timeline
↓
Matrix Users
│ See message from @slack_john:clarun.xyz
Latency Budget: <5 seconds (FR-001)
Failure Modes:
- Portal doesn't exist → Auto-create, then deliver
- Ghost user doesn't exist → Create, set profile, then deliver
- Matrix homeserver unreachable → Retry with exponential backoff
- Event deduplication → Check Slack
tsagainst database, skip if duplicate
5.2 Message Flow: Matrix → Slack
Matrix User (@alice:clarun.xyz)
│ Sends message in #slack_dev-platform:clarun.xyz
↓
Matrix Homeserver
│ Appservice transaction to bridge
↓
mautrix-slack (/_matrix/app/v1/transactions)
│ 1. Verify hs_token
│ 2. Lookup portal by room ID
│ 3. Transform message format
│ 4. Determine sender identity
↓
Slack API (chat.postMessage or chat.postEphemeral)
│ POST message to channel via bot token
↓
Slack Channel (#dev-platform)
│ Message appears from bridge bot
│ (with Matrix user's display name if using customization)
↓
Slack Users
│ See message: "Alice (Matrix): Hello from Matrix!"
Latency Budget: <5 seconds (FR-002)
Failure Modes:
- Portal not found → Log error, return 200 OK (avoid retry loop)
- Slack API rate limited → Queue message, retry with backoff
- Bot not in channel → Attempt to join, or return error to Matrix user
- Invalid message format → Send error reply to Matrix user
5.3 Authentication Flow
Admin (NixOS deployment)
│ 1. Deploy configuration (services.mautrix-slack.enable = true)
↓
NixOS Activation
│ 2. Start systemd service
↓
mautrix-slack service
│ 3. Generate config, start service
│ 4. Listen on port 29319
│ 5. State: Unauthenticated
↓
Admin (Matrix client)
│ 6. Open DM with @slackbot:clarun.xyz
│ 7. Send: "login app"
↓
mautrix-slack
│ 8. Prompt: "Please provide bot token"
↓
Admin
│ 9. Send: "xoxb-..."
↓
mautrix-slack
│ 10. Prompt: "Please provide app token"
↓
Admin
│ 11. Send: "xapp-..."
↓
mautrix-slack
│ 12. Store tokens in database
│ 13. Call apps.connections.open
│ 14. Establish WebSocket connection
│ 15. Sync recent conversations (conversation_count)
│ 16. State: Active
↓
Admin
│ 17. Receive success message
│ 18. Invited to bridged channel portals
Security Notes:
- Tokens transmitted over encrypted Matrix federation (TLS)
- Tokens stored in PostgreSQL database (LUKS-encrypted filesystem)
- Tokens never logged (mautrix bridges sanitize logs)
- Admin can revoke via Slack app settings
5.4 Portal Creation Flow
Slack User
│ Sends message in #general (not yet bridged)
↓
Slack API (WebSocket event)
│ message.channels event
↓
mautrix-slack
│ 1. Check database: portal exists for channel_id?
│ 2. Not found → Initiate auto-create
↓
Portal Creation Logic
│ 3. Create Matrix room via homeserver API
│ 4. Set room name, topic, avatar
│ 5. Insert portal record in database
│ 6. Map Slack channel ↔ Matrix room
↓
Membership Sync
│ 7. For each Slack member in channel:
│ - Create/update ghost user
│ - Invite ghost user to Matrix room
↓
Relay Message
│ 8. Transform and send original message
↓
Matrix Users
│ 9. Receive room invitation
│ 10. Join room, see first message
Timing: Portal creation adds ~2-5 seconds latency to first message
Failure Recovery:
- Room creation fails → Retry up to 3 times
- Ghost user creation fails → Skip that user, continue
- Database insert fails → Rollback, log error, retry
6. Database Schema (Conceptual)
Note: Actual schema managed by mautrix-slack. This is conceptual understanding for operational purposes.
Key Tables
portal
CREATE TABLE portal (
slack_channel_id TEXT PRIMARY KEY, -- C0123ABC
mxid TEXT NOT NULL, -- !xyz:clarun.xyz
name TEXT, -- dev-platform
topic TEXT,
encrypted BOOLEAN DEFAULT FALSE,
in_space BOOLEAN DEFAULT FALSE,
avatar_url TEXT,
name_set BOOLEAN,
topic_set BOOLEAN,
avatar_set BOOLEAN
);
puppet (Ghost Users)
CREATE TABLE puppet (
slack_user_id TEXT PRIMARY KEY, -- U0123DEF
team_id TEXT, -- T0456GHI
mxid TEXT NOT NULL, -- @slack_john:clarun.xyz
display_name TEXT,
avatar_url TEXT,
name_set BOOLEAN,
avatar_set BOOLEAN,
contact_info_set BOOLEAN,
is_bot BOOLEAN,
custom_mxid TEXT -- For double-puppeting
);
user (Logged-in Matrix users)
CREATE TABLE "user" (
mxid TEXT PRIMARY KEY, -- @alice:clarun.xyz
slack_user_id TEXT, -- U789JKL (after login)
team_id TEXT, -- T0456GHI
access_token TEXT, -- Encrypted Slack token
management_room TEXT -- DM with bridge bot
);
message (Event mapping for edits/deletes)
CREATE TABLE message (
slack_ts TEXT, -- 1234567890.123456
slack_channel_id TEXT, -- C0123ABC
mxid TEXT, -- $event_id:clarun.xyz
UNIQUE(slack_ts, slack_channel_id)
);
Queries Used:
- Message relay:
SELECT mxid FROM portal WHERE slack_channel_id = ? - Ghost user lookup:
SELECT mxid FROM puppet WHERE slack_user_id = ? - Edit/delete:
SELECT mxid FROM message WHERE slack_ts = ? AND slack_channel_id = ?
7. Configuration Data Flow
Git Repository (specs/002-slack-bridge-integration/)
│ Contains: spec.md, plan.md, data-model.md
↓
NixOS Configuration (hosts/ops-jrz1.nix)
│ services.mautrix-slack = { ... }
↓
NixOS Evaluation
│ Merges: modules/mautrix-slack.nix options
↓
ExecStartPre (Python script)
│ 1. Generate example config: mautrix-slack -e
│ 2. Merge configOverrides
│ 3. Write: /var/lib/mautrix_slack/config/config.yaml
↓
mautrix-slack service
│ Reads config.yaml on startup
↓
Runtime Behavior
│ Connects to Matrix, Slack, PostgreSQL
Configuration Layers (in order of precedence):
- Hardcoded defaults (in mautrix-slack binary)
- Example config (generated with
-eflag) - NixOS module overrides (
configOverridesoption) - User extraConfig (
extraConfigoption) - Runtime authentication (tokens from
login appcommand)
8. Observability Data
Health Indicators (SC-003a)
| Metric | Source | Purpose |
|---|---|---|
connection_status |
Service logs | Socket Mode connection state |
last_successful_message |
Service logs | Timestamp of last relayed message |
error_count |
Service logs | Count of errors since last restart |
portal_count |
Database query | Number of active channel portals |
ghost_user_count |
Database query | Number of Slack users bridged |
message_latency |
Bridge metrics | Time between source→destination (should be <5s) |
Log Events
Key log patterns (from mautrix bridge codebase):
INFO [WebSocket] Connected to Slack via Socket Mode
INFO [Portal] Creating portal for channel #dev-platform (C0123ABC)
INFO [Message] Relaying message from Slack to Matrix: {...}
WARN [Connection] WebSocket disconnected, reconnecting in 5s
ERROR [Auth] Invalid bot token, authentication failed
Monitoring Strategy:
- Use
journalctl -u mautrix-slack -ffor real-time monitoring - Export logs to persistent storage for analysis
- Alert on
ERRORlevel logs - Track
last_successful_messagemetric (alert if >1 hour stale)
9. Document History
- 2025-10-22: Initial data model design
- Phase 1 Status: ✅ Complete
- Next: Create contracts/ directory with schemas