ops-jrz1/specs/002-slack-bridge-integration/contracts/channel-mapping.yaml
Dan ca379311b8 Add Slack bridge integration feature specification
Includes spec, plan, research, data model, contracts, and quickstart guide
for mautrix-slack Socket Mode bridge deployment.
2025-10-26 14:36:44 -07:00

498 lines
16 KiB
YAML

# 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)