# Channel Mapping Contract: Slack↔Matrix Portal Configuration # This contract documents how Slack channels map to Matrix rooms in the # mautrix-slack bridge. Unlike traditional bridges with static channel mappings, # mautrix-slack uses **automatic portal creation** based on activity. # ============================================================================= # Automatic Portal Creation Model # ============================================================================= # mautrix-slack does NOT use static configuration files for channel mappings. # Instead, portals (Slack channel ↔ Matrix room pairs) are created automatically # based on: # # 1. Initial Login: Bridge syncs recent conversations (controlled by conversation_count) # 2. Message Receipt: Portal auto-created when message arrives in new Slack channel # 3. Bot Membership: Channels where Slack bot is invited are auto-bridged # # This contract documents the EXPECTED structure for understanding operational # behavior, not a configuration file to be edited. # ============================================================================= # Portal Lifecycle # ============================================================================= portal_lifecycle: creation_triggers: - event: "Initial authentication (`login app` command)" action: "Bridge syncs N recent conversations (conversation_count)" portals_created: "Up to conversation_count channels/DMs" user_experience: "User receives Matrix room invitations" - event: "Message received in unbridged Slack channel" action: "Bridge auto-creates portal" portals_created: "1 new portal for that channel" user_experience: "Invitation to new Matrix room, message appears" - event: "Slack bot invited to channel (App Login mode)" action: "Bridge auto-creates portal" portals_created: "1 new portal" user_experience: "Matrix users can now interact with channel" destruction_triggers: - event: "Admin runs `delete-portal` command (if available)" action: "Bridge kicks users from Matrix room, deletes room, removes DB entry" result: "Portal destroyed, can be recreated if needed" - event: "Slack channel deleted" expected: "Portal becomes inactive (not documented behavior)" recommendation: "Test in pilot deployment to confirm" state_changes: - from: "Nonexistent" to: "Active" trigger: "Any creation trigger above" - from: "Active" to: "Archived" trigger: "Slack channel archived" behavior: "Read-only, no new messages flow" - from: "Archived" to: "Active" trigger: "Slack channel unarchived" - from: "Active" to: "Deleted" trigger: "delete-portal command or Slack channel deleted" # ============================================================================= # Portal Entity Structure # ============================================================================= # Stored in bridge PostgreSQL database (table: portal) portal_entity: slack_channel_id: type: "string" format: "C[A-Z0-9]{8,10}" # Public channel example: "C0123ABCDEF" primary_key: true description: "Slack channel identifier" slack_channel_type: type: "enum" values: - "public_channel" # Standard public channel - "private_channel" # Private channel (groups) - "dm" # 1:1 direct message - "mpim" # Multi-party direct message (group DM) - "connect" # Slack Connect shared channel description: "Type of Slack conversation" matrix_room_id: type: "string" format: "!([a-zA-Z0-9]+):clarun.xyz" example: "!AbCdEfGhIjKlMnOp:clarun.xyz" description: "Matrix room identifier (opaque ID)" matrix_room_alias: type: "string" format: "#slack_([a-z0-9_-]+):clarun.xyz" example: "#slack_dev-platform:clarun.xyz" description: "Human-readable Matrix room alias" notes: - "Based on Slack channel name" - "Lowercase, hyphens preserved, special chars removed" - "May not exist for DMs" channel_name: type: "string" example: "dev-platform" description: "Slack channel name (without #)" notes: "Synced from Slack, updated on channel rename" topic: type: "string" example: "Platform development discussion" description: "Channel topic/description" synced: true notes: "Updated when Slack topic changes" avatar_url: type: "mxc:// URI" example: "mxc://clarun.xyz/AbCdEfGhIjKlMnOpQrStUvWxYz" description: "Matrix Content URI for channel avatar" notes: "Synced from Slack workspace icon" encrypted: type: "boolean" default: false description: "Whether Matrix room has encryption enabled" notes: "Controlled by bridge.encryption.default config" in_space: type: "boolean" default: false description: "Whether portal is in a Matrix Space" notes: "Spaces not commonly used for bridge portals" members: type: "list[string]" example: ["U0123ABC", "U0456DEF", "U0789GHI"] description: "Slack user IDs of channel members" synced: true notes: "Updated when users join/leave channel" created_at: type: "timestamp" example: "2025-10-22T14:30:00Z" description: "When portal was created in bridge" last_activity: type: "timestamp" example: "2025-10-22T15:45:30Z" description: "Timestamp of last message in portal" notes: "Used for health monitoring" # ============================================================================= # Example Portal Mappings # ============================================================================= # Example 1: Public Channel # -------------------------- example_public_channel: slack: channel_id: "C05N2EXAMPLE" channel_name: "dev-platform" workspace: "chochacho" url: "https://chochacho.slack.com/archives/C05N2EXAMPLE" type: "public_channel" members: 12 topic: "Platform infrastructure development" matrix: room_id: "!xYzAbCdEfGhIjKlM:clarun.xyz" room_alias: "#slack_dev-platform:clarun.xyz" display_name: "dev-platform (Slack)" topic: "Platform infrastructure development" encrypted: false members: - "@alice:clarun.xyz" # Real Matrix user - "@slack_U0123ABC:clarun.xyz" # Ghost user (John from Slack) - "@slack_U0456DEF:clarun.xyz" # Ghost user (Jane from Slack) - "@slackbot:clarun.xyz" # Bridge bot portal_metadata: created_at: "2025-10-22T14:00:00Z" last_activity: "2025-10-22T16:30:00Z" message_count: 427 state: "active" # Example 2: Private Channel # --------------------------- example_private_channel: slack: channel_id: "G05N2EXAMPLE" # Private channels start with G channel_name: "leadership-team" workspace: "chochacho" type: "private_channel" members: 5 topic: "Leadership discussions" matrix: room_id: "!aBcDeFgHiJkLmNoP:clarun.xyz" room_alias: "#slack_leadership-team:clarun.xyz" display_name: "leadership-team (Slack)" topic: "Leadership discussions" encrypted: true # High-security channel members: - "@alice:clarun.xyz" - "@bob:clarun.xyz" - "@slack_U0789GHI:clarun.xyz" - "@slackbot:clarun.xyz" portal_metadata: created_at: "2025-10-22T14:05:00Z" last_activity: "2025-10-22T15:00:00Z" message_count: 89 state: "active" # Example 3: Direct Message # -------------------------- example_direct_message: slack: channel_id: "D05N2EXAMPLE" # DMs start with D workspace: "chochacho" type: "dm" members: 2 # Alice (Matrix) and John (Slack) matrix: room_id: "!qRsTuVwXyZaBcDeF:clarun.xyz" room_alias: null # DMs don't have aliases display_name: "John Doe (Slack DM)" encrypted: false members: - "@alice:clarun.xyz" - "@slack_U0123ABC:clarun.xyz" # John - "@slackbot:clarun.xyz" portal_metadata: created_at: "2025-10-22T14:10:00Z" last_activity: "2025-10-22T16:45:00Z" message_count: 52 state: "active" # Example 4: Archived Channel # ---------------------------- example_archived_channel: slack: channel_id: "C05N2OLDCHAN" channel_name: "old-project" workspace: "chochacho" type: "public_channel" archived: true members: 0 # All members removed when archived matrix: room_id: "!gHiJkLmNoPqRsTuV:clarun.xyz" room_alias: "#slack_old-project:clarun.xyz" display_name: "old-project (Slack) [ARCHIVED]" topic: "[Archived] Old project discussions" encrypted: false members: - "@slackbot:clarun.xyz" # Only bot remains portal_metadata: created_at: "2025-08-15T10:00:00Z" last_activity: "2025-10-01T12:00:00Z" # Before archival message_count: 1543 state: "archived" # ============================================================================= # Configuration Parameters (config.yaml) # ============================================================================= # The only configuration parameter affecting portal creation: conversation_count: type: "integer" default: 10 description: "Number of recent Slack conversations to sync on initial login" location: "config.yaml → slack.conversation_count" behavior: - "Set to 10: Sync 10 most recent channels/DMs" - "Set to 0: No initial sync (portals created on-demand only)" - "Set to 100: Sync 100 recent conversations (may be slow)" recommendation: "Start with 10 for testing, adjust based on team size" # No other static channel mapping configuration exists. # ============================================================================= # Operational Commands (via Matrix DM with @slackbot:clarun.xyz) # ============================================================================= # Note: These commands are inferred from other mautrix bridges and may not # all be available in mautrix-slack. Verify with `help` command. commands: - command: "help" description: "Display available bridge commands" usage: "help" - command: "login app" description: "Authenticate bridge with Slack app credentials" usage: "login app" prompts: - "Please provide bot token (xoxb-...)" - "Please provide app token (xapp-...)" - command: "logout" description: "Disconnect bridge from Slack" usage: "logout" effect: "All portals become inactive until re-login" - command: "delete-portal" description: "Remove portal for current room (if available)" usage: "delete-portal" context: "Send from within a bridged portal room" effect: "Kicks users, deletes Matrix room, removes from database" - command: "sync" description: "Re-sync portals from Slack (if available)" usage: "sync" effect: "Creates portals for newly joined Slack channels" - command: "status" description: "Display bridge connection status (if available)" usage: "status" expected_output: - "Connection: Connected" - "Workspace: chochacho" - "Portals: 12 active" - "Last message: 30 seconds ago" # ============================================================================= # Gradual Rollout Strategy # ============================================================================= # Phase 1: Single Test Channel (Week 1-2) # ---------------------------------------- phase_1: goal: "Validate bridge functionality with minimal scope" conversation_count: 5 # Limit initial sync channels: - slack_channel: "#dev-platform" matrix_room: "#slack_dev-platform:clarun.xyz" members: ["@alice:clarun.xyz", "@bob:clarun.xyz"] purpose: "Testing all bridge features" success_criteria: - "Messages flow bidirectionally within 5 seconds" - "Reactions, edits, deletes sync correctly" - "File attachments work" - "No service crashes or errors" # Phase 2: Small User Group (Week 3-4) # ------------------------------------- phase_2: goal: "Test multi-user shared portals" conversation_count: 10 channels: - "#dev-platform" - "#general" - "#random" users: 5 success_criteria: - "Multiple Matrix users can interact in same portal" - "Ghost users appear correctly" - "Performance acceptable with 5 users" - "No conflicts or race conditions" # Phase 3: Organic Expansion (Week 5+) # ------------------------------------- phase_3: goal: "Full team adoption" conversation_count: 20 # Increase as confidence grows channels: "Auto-created based on activity" users: "All team members (2-5)" approach: - "Don't pre-configure channel lists" - "Let users authenticate individually" - "Portals created organically as users interact" - "Monitor health metrics" success_criteria: - "99% uptime over 7 days" - "All messages delivered within 5 seconds" - "User feedback positive" - "No operational issues" # ============================================================================= # Monitoring Portal Health # ============================================================================= # Health indicators per portal (from data-model.md): health_indicators: connection_status: values: ["connected", "disconnected", "refreshing"] source: "Service logs" alert: "If disconnected > 5 minutes" last_successful_message: type: "timestamp" source: "portal.last_activity in database" alert: "If > 1 hour old in active channel" error_count: type: "integer" source: "Service logs (ERROR level)" alert: "If > 10 errors in 10 minutes" portal_count: type: "integer" source: "SELECT COUNT(*) FROM portal" expected: "Grows organically, typically 5-20 for small team" ghost_user_count: type: "integer" source: "SELECT COUNT(*) FROM puppet" expected: "One per Slack user in bridged channels" # Monitoring queries: monitoring_queries: - name: "List active portals" query: | SELECT slack_channel_id, mxid, name, topic FROM portal WHERE state = 'active' ORDER BY last_activity DESC; - name: "Find stale portals" query: | SELECT slack_channel_id, name, last_activity FROM portal WHERE last_activity < NOW() - INTERVAL '7 days' ORDER BY last_activity ASC; - name: "Count messages today" query: | SELECT COUNT(*) FROM message WHERE timestamp >= CURRENT_DATE; # ============================================================================= # Troubleshooting Portal Issues # ============================================================================= troubleshooting: - issue: "Portal not created for Slack channel" checks: - "Verify bridge is authenticated (status command)" - "Check if message was received (look for Slack event in logs)" - "Verify bot has access to channel (App Login mode)" - "Check bridge logs for errors" resolution: "Send message in Slack channel to trigger portal creation" - issue: "Messages not appearing in Matrix" checks: - "Verify Socket Mode connection active" - "Check last_successful_message timestamp" - "Look for relay errors in logs" - "Verify homeserver is reachable" resolution: "Check journalctl -u mautrix-slack for specific errors" - issue: "Messages not appearing in Slack" checks: - "Verify bot token valid (test with Slack API)" - "Check bot is member of channel" - "Look for Slack API errors in logs" - "Verify rate limits not exceeded" resolution: "Re-invite bot to channel if needed" - issue: "Portal shows as archived when channel is active" checks: - "Verify channel not actually archived in Slack" - "Check portal state in database" - "Look for Slack channel status sync errors" resolution: "Unarchive in Slack, may need to re-sync portal" # ============================================================================= # Related Documentation # ============================================================================= # mautrix-slack Docs: https://docs.mau.fi/bridges/go/slack/ # Matrix Spaces: https://matrix.org/docs/guides/spaces # Portal Pattern: https://matrix.org/docs/older/types-of-bridging/#portal-bridging # ============================================================================= # Version Information # ============================================================================= # Contract Version: 1.0 # Created: 2025-10-22 # Last Updated: 2025-10-22 # Related Spec: 002-slack-bridge-integration/spec.md # Portal Model: Automatic creation (no static mapping)