Skip to content

Support multiple Matrix homeservers - #33

Closed
fmartingr wants to merge 8 commits into
masterfrom
feat/multiple-server-support-phase-3
Closed

Support multiple Matrix homeservers#33
fmartingr wants to merge 8 commits into
masterfrom
feat/multiple-server-support-phase-3

Conversation

@fmartingr

Copy link
Copy Markdown
Contributor

Summary

Route Mattermost ↔ Matrix traffic through any number of Matrix homeservers instead of a single global one (MM-64622). Server configuration is now a registry (keyed by a deterministic serverID) managed with new /matrix server admin slash commands, since the System Console can only edit one server. Existing single-homeserver installs are migrated automatically and behave unchanged.

What's included

  • Registry-backed config: servers_config KV entry replaces the flat matrix_server_* plugin.json settings; a v3 migration seeds it from the legacy config on upgrade.
  • Inbound routing: incoming Application Service requests are matched to a server by hs_token; all reads/writes for that request are namespaced by serverID.
  • Outbound routing: Mattermost events route through the Matrix client for the channel's mapped server; ghost users, aliases, and username generation are per-server.
  • Per-server loop prevention: each server gets its own shared-channels remoteID; ping health-checks target the specific pinged remote's server.
  • Admin commands: /matrix server add|remove|list|status|map|unmap|registration to manage homeservers and channel mappings.
  • Local dev: second Synapse/Element stack in docker-compose + docs/local-development.md.

…v3 migration)

Restructure the plugin internals so a Matrix homeserver is modeled as an entry
in a managed registry keyed by a stable serverID, laying the groundwork for
supporting multiple homeservers. No cross-server routing yet: the bridge still
resolves a single server. Zero behavior change for a single-server operator.

- Registry: servers_config KV entry (ServerConfig) reconciled from the flat
  plugin.json config; serverID derived deterministically from the homeserver
  hostname so records are re-adopted if a server is re-created with the same URL.
- KV namespacing: per-server keys become <prefix><serverID>_<id>; channel_mapping_
  value becomes a []ChannelServerMapping (server association in the value).
- Client registry: p.matrixClient -> map[serverID]*matrix.Client with accessors.
- v3 KV migration: namespaces existing per-server keys and converts channel_mapping
  values; deterministic, idempotent, and aborts (without bumping the version) on
  partial failure so it safely retries. Legacy v1/v2 steps stay un-namespaced.
- Per-server username prefix resolved from the registry (source of truth).
- Tests: registry reconcile/derivation, migration (fresh/upgrade/idempotent/
  reset/failure-abort), per-server isolation, corrupt-value handling, command
  handlers, and a two-container live-Synapse integration suite. Docs under spec/.
Route inbound Application Service traffic to the correct homeserver by
matching the presented hs_token against each server's HSToken, resolving
a serverID, and scoping all lookups to that server's KV namespace.

- api.go: MatrixAuthorizationRequired matches the bearer token against
  every server's hs_token (constant-time), injects the resolved serverID
  into the request context, and enforces the per-server Enabled flag on
  top of the global enable_sync switch.
- matrix_webhook.go: dedup transactions per (serverID, txnID); thread
  serverID through processMatrixEvent, room-mapping lookup, ghost-user
  detection, and Matrix-initiated DM handling.
- plugin.go/bridge_utils.go/sync_to_mattermost.go: construct inbound
  bridges scoped to the originating server; resolve the per-server domain
  for the matrix_event_id_<domain> post property.
- kvstore: add SetAtomicWithRetries for concurrency-safe upserts of the
  shared channel_mapping value across servers.

Also adds an admin-only /matrix server command group and backing registry
mutation API for local multi-server testing until the System Console UI
lands.
Route Mattermost->Matrix traffic to the homeserver(s) each channel is mapped
to, and register the plugin once per Matrix server so loop prevention and sync
cursors are per-remote. Completes the multi-server core (phases 1-3).

Implementation:
- Per-server shared-channels registration with a distinct SiteURL per server
  (primary keeps the legacy empty SiteURL for upgrade compatibility); store
  each returned remoteID on its registry entry; add remoteToServerID /
  ownRemoteIDs maps and remoteIDForServer / serverIDForRemoteID / isOwnRemoteID.
- Outbound fan-out: resolveOutboundServers() + per-server
  newMattermostToMatrixBridge(serverID); posts/edits/deletes/reactions/files/
  profile-images/DMs target the mapped server(s) only.
- Per-server pending-file and post trackers (keyed by (serverID, postID)).
- Set-based loop prevention (isOwnRemoteID) across N servers.
- Server-aware username round-trip (reconstruct from the registry per server).
- /matrix map refuses when >1 server is registered (use /matrix server map);
  /matrix server add registers the added server immediately.
- Bump min_server_version to 11.8.0 (SiteURL per-remote registration).

Bugs fixed (each a single-server assumption leaking into a per-server path,
surfaced by the multi-server integration tests):
- matrix_event_id_<domain> post-property key resolved per server from the
  registry (ServerName), not the flat-config URL host - fixes collisions
  between homeservers sharing a host but differing by port.
- serverDomainForID prefers the homeserver ServerName for ghost recognition,
  so inbound loop prevention recognizes our own ghosts under delegation.
- Inbound bridge attributes posts/users to the originating server's remote
  (remoteIDForServer), not the primary.
- SiteURL keyed by homeserver hostname so servers sharing a ServerName do not
  collapse onto one remote.

Tests:
- Multi-server integration suites for outbound and inbound, per event type
  (message / edit / delete / reaction add+remove / file / profile / loop / DM),
  each asserting delivery and attribution on both servers.
- Shared-channel suites (one channel bridged to a room on each server), both
  directions, asserting per-server remote / user / ghost attribution.
- Unit tests for outbound routing resolution, remote-ID maps, ghost-domain
  recognition, and the /matrix map guard.

Docs: local-development multi-server section; server autocomplete lists "map".
The System Console can only configure a single homeserver, which blocks
running multiple Matrix servers. Move server configuration (add, remove,
list, status, map, unmap, registration) to admin-only /matrix server slash
commands backed by the server registry, drop the now-redundant flat
matrix_server_* settings from plugin.json, make shared-channels ping
health-check the specific remote's owning server, and simplify the v3
migration to seed the registry directly from legacy config.
@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 46939ca6-a94c-4bdb-8626-b9565efa507f

📥 Commits

Reviewing files that changed from the base of the PR and between 4219427 and 112d2e0.

📒 Files selected for processing (6)
  • server/command/command.go
  • server/command/server_command_test.go
  • server/command/slash_command_test.go
  • server/plugin.go
  • server/servers_test.go
  • server/testhelpers_test.go
🚧 Files skipped from review as they are similar to previous changes (5)
  • server/command/slash_command_test.go
  • server/servers_test.go
  • server/command/server_command_test.go
  • server/command/command.go
  • server/plugin.go

📝 Walkthrough

Walkthrough

This PR changes the bridge from a single Matrix homeserver configuration to managed multi-homeserver support. It adds server-scoped KV data, routing, authentication, commands, migrations, synchronized state, Docker services, documentation, and extensive unit and integration tests.

Changes

Multi-Matrix server support

Layer / File(s) Summary
Registry, schemas, and migrations
server/store/kvstore/*, server/servers.go, server/migrations.go
Managed server records, server-scoped mappings, atomic KV updates, deterministic IDs, legacy materialization, and KV version 3 migrations are added.
Plugin and bridge routing
server/plugin.go, server/bridge_utils.go, server/configuration.go, server/matrix_util.go
Matrix clients and bridges are created per server, remote ownership is tracked, and identity, room, DM, and channel mapping logic uses server-scoped registry data.
Commands and webhook handling
server/command/command.go, server/api.go, server/matrix_webhook.go, server/hooks.go
Server administration commands, server-aware mapping flows, token-based server resolution, scoped transaction deduplication, inbound routing, outbound fan-out, attachment handling, profile updates, and health checks are implemented.
State isolation
server/sync_to_matrix.go, server/sync_to_mattermost.go, server/post_tracker.go
User, room, event, reaction, ghost, pending-file, and post-tracking state is namespaced by server ID.
Validation and integration coverage
server/*_test.go, server/*integration_test.go
Tests cover registry operations, migrations, commands, authentication, routing, server isolation, inbound events, outbound fan-out, shared channels, DMs, reactions, attachments, and loop prevention.
Local development stack
docker-compose.yml, docker/*, docs/local-development.md, README.md, plugin.json
The local stack adds Element and a second Synapse/Postgres deployment, while documentation and plugin settings direct configuration through /matrix server commands.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 58.99% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: support for multiple Matrix homeservers.
Description check ✅ Passed The description is clearly related to the changeset and matches the multi-homeserver routing work.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/multiple-server-support-phase-3

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 16

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
server/command/command.go (2)

926-948: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

golangci-lint (staticcheck QF1012) fails on these three lines.

Use fmt.Fprintf(&responseText, ...) instead of responseText.WriteString(fmt.Sprintf(...)).

🔧 Proposed fix
-			responseText.WriteString(fmt.Sprintf("**Current Channel:** %s → %s\n\n", channelName, renderRooms(current)))
+			fmt.Fprintf(&responseText, "**Current Channel:** %s → %s\n\n", channelName, renderRooms(current))
@@
-		responseText.WriteString(fmt.Sprintf("**All Mappings (%d channels):**\n", len(mappings)))
+		fmt.Fprintf(&responseText, "**All Mappings (%d channels):**\n", len(mappings))
@@
-			responseText.WriteString(fmt.Sprintf("• %s → %s%s\n", channelName, renderRooms(entries), currentMarker))
+			fmt.Fprintf(&responseText, "• %s → %s%s\n", channelName, renderRooms(entries), currentMarker)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/command/command.go` around lines 926 - 948, Replace the three
responseText.WriteString(fmt.Sprintf(...)) calls in the channel-mapping
rendering flow with fmt.Fprintf(&responseText, ...) calls, preserving each
existing format string and arguments, including the current-channel and
all-mappings output.

Source: Linters/SAST tools


485-513: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Read-modify-write on the shared channel-mapping key isn't atomic.

GetUpsertSet can lose an entry if two /matrix server map invocations for different servers target the same channel concurrently. KVStore exposes SetAtomicWithRetries precisely for this shared-key pattern (see server/store/kvstore/kvstore.go); the same applies to the unmap path at Lines 688-691.

♻️ Sketch
-	channelMappings, err := kvstore.ParseChannelServerMappings(existingMapping)
-	...
-	channelMappings = kvstore.UpsertChannelServerMapping(channelMappings, serverID, roomIdentifier)
-	mappingValue, err := kvstore.MarshalChannelServerMappings(channelMappings)
-	if err == nil {
-		err = c.kvstore.Set(mappingKey, mappingValue)
-	}
+	err := c.kvstore.SetAtomicWithRetries(mappingKey, func(old []byte) ([]byte, error) {
+		existing, perr := kvstore.ParseChannelServerMappings(old)
+		if perr != nil {
+			return nil, perr
+		}
+		return kvstore.MarshalChannelServerMappings(
+			kvstore.UpsertChannelServerMapping(existing, serverID, roomIdentifier))
+	})
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/command/command.go` around lines 485 - 513, Replace the non-atomic
Get/Upsert/Set sequence in the channel-mapping flow with
KVStore.SetAtomicWithRetries, preserving UpsertChannelServerMapping and the
existing error handling. Apply the same atomic read-modify-write approach to the
corresponding unmap path around its channel-mapping update, so concurrent
mappings cannot overwrite one another.
🧹 Nitpick comments (9)
server/command/command.go (1)

546-582: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Same alias is resolved up to three times.

ResolveRoomAlias(roomIdentifier) is called at Lines 521, 556 and 578 for the same identifier. Resolve once up front and reuse the result for the reverse mapping, the bridge alias and the member sync.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/command/command.go` around lines 546 - 582, Resolve roomIdentifier
once before the reverse-mapping, bridge-alias, and member-sync logic, then reuse
the resolved room ID across all three paths. Update the calls in the surrounding
command flow, including syncChannelMembersToMatrixRoom setup, to avoid repeated
ResolveRoomAlias invocations while preserving fallback behavior when resolution
fails.
server/dm_room_creation_test.go (1)

108-131: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the repeated per-server test setup into a shared helper.

The setTestMatrixClient + SetServerDomain + plugin.configuration = &configuration{} + seedServerEntry(...) + reverse-mapping kvstore.Set(kvstore.BuildMattermostUserKey(...)) block is duplicated verbatim across all three test cases (lines 108-131, 252-275, 380-403). Any future change to the seeding contract (e.g., a new required ServerConfig field) needs to be updated in three places.

♻️ Suggested helper extraction
func seedDMTestPlugin(t *testing.T, plugin *Plugin, container *matrixtest.Container, matrixUserID, matrixID string) {
	setTestMatrixClient(plugin, createMatrixClientWithTestLogger(t, container.ServerURL, container.ASToken, testRemoteID))
	plugin.GetMatrixClient().SetServerDomain(container.ServerDomain)

	plugin.configuration = &configuration{}
	seedServerEntry(plugin, kvstore.ServerConfig{
		ServerID:   testServerID,
		ServerURL:  container.ServerURL,
		ServerName: container.ServerDomain,
		ASToken:    container.ASToken,
		HSToken:    container.HSToken,
		Enabled:    true,
		RemoteID:   testRemoteID,
	})

	err := plugin.kvstore.Set(kvstore.BuildMattermostUserKey(testServerID, matrixUserID), []byte(matrixID))
	require.NoError(t, err)
}

Also applies to: 252-275, 380-403

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/dm_room_creation_test.go` around lines 108 - 131, The per-server setup
is duplicated across the DM room creation tests. Add a shared seedDMTestPlugin
helper that performs the Matrix client setup, server-domain configuration,
plugin configuration initialization, server entry seeding, and reverse user
mapping with require.NoError; replace the duplicated setup blocks in all three
test cases with calls to this helper.
server/plugin.go (2)

300-310: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

remoteIDForServer performs a KV read on every bridge construction.

Both bridge constructors call this, and bridges are built per inbound event and per outbound fan-out target, so each event now incurs a registry read plus JSON decode. The remote IDs are already held in memory (remoteToServerID); consider maintaining the inverse serverID → remoteID map in initMatrixClients and reading it under matrixClientsLock, falling back to the registry only on a miss. Note the current form also silently swallows read errors.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/plugin.go` around lines 300 - 310, Update remoteIDForServer to consult
an in-memory serverID-to-remoteID map maintained by initMatrixClients under
matrixClientsLock, avoiding KV reads for known servers; retain a registry lookup
only when the map misses, and propagate or otherwise handle registry read errors
instead of silently ignoring them.

242-246: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Stale comment: p.remoteID no longer exists.

The struct no longer carries a single remoteID field, so the "Falls back to p.remoteID in the single-server case" note is misleading — remoteIDForServer returns "" when the server is unknown. Same for the "Rebuilt from the registry in initMatrixClient" comments on lines 46 and 51 and line 168 (the function is initMatrixClients).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/plugin.go` around lines 242 - 246, Update the comments around RemoteID
and the initMatrixClients setup to remove references to the deleted p.remoteID
field and the singular initMatrixClient function. Document that
remoteIDForServer returns an empty string for unknown servers, and use the
correct initMatrixClients symbol wherever referenced.
server/post_tracker_test.go (1)

9-31: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a test that proves the new per-server namespacing.

All existing cases use a single testTrackerServer, so they would still pass if trackerKey ignored serverID. A two-server case pins the actual contract this change introduces.

🧪 Suggested additional test
func TestPostTracker_ServerIsolation(t *testing.T) {
	tracker := NewPostTracker(DefaultPostTrackerMaxEntries)
	const postID = "shared_post"

	require.NoError(t, tracker.Put("srv1", postID, 100))
	require.NoError(t, tracker.Put("srv2", postID, 200))

	got1, ok1 := tracker.Get("srv1", postID)
	got2, ok2 := tracker.Get("srv2", postID)
	require.True(t, ok1)
	require.True(t, ok2)
	require.Equal(t, int64(100), got1)
	require.Equal(t, int64(200), got2)

	tracker.Delete("srv1", postID)
	_, ok1 = tracker.Get("srv1", postID)
	_, ok2 = tracker.Get("srv2", postID)
	require.False(t, ok1)
	require.True(t, ok2, "deleting one server's entry must not affect another's")
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/post_tracker_test.go` around lines 9 - 31, Add a
TestPostTracker_ServerIsolation test covering identical post IDs on two distinct
servers. Verify Put/Get retain independent timestamps for each server, then
confirm deleting the entry for one server removes only that server’s entry while
the other remains available.
server/sync_to_matrix.go (1)

309-313: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Cache serverDomain() for these event paths. It reads and parses the server registry on every call, so the sync/delete/reaction handlers are paying that cost repeatedly. If the domain is stable per bridge, keep it on BridgeUtils and reuse it.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/sync_to_matrix.go` around lines 309 - 313, Cache the stable server
domain on BridgeUtils instead of invoking serverDomain() repeatedly in the sync,
delete, and reaction event paths. Initialize the cached value when the bridge is
created, then update the relevant handlers to reuse that field when constructing
matrix_event_id_ property keys.
server/bridge_utils.go (2)

158-222: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicated registry-lookup logic across matrixUsernamePrefix, serverDomain, and reconstructMatrixUserIDFromUsername.

matrixUsernamePrefix (167-179) and serverDomain (201-210) each independently do kvstore.Get(KeyServersConfig)ParseServersConfigServerConfigForID, and reconstructMatrixUserIDFromUsername (line 558-565 below) repeats the same three-step lookup a third time with different error handling. Extracting a single helper would remove the duplication and, as a side effect, unify error visibility (see the note on line 555-570) and give a single place to add caching later if this read-per-call pattern (documented as intentional for freshness) ever needs to trade some freshness for reduced KV I/O on the per-post serverDomain() path.

♻️ Suggested consolidation
+// getServerConfigEntry centralizes the KV read + parse + lookup shared by
+// matrixUsernamePrefix, serverDomain, and reconstructMatrixUserIDFromUsername.
+func (s *BridgeUtils) getServerConfigEntry() (kvstore.ServerConfig, bool) {
+	data, err := s.kvstore.Get(kvstore.KeyServersConfig)
+	if err != nil {
+		s.logger.LogError("Failed to read server registry", "server_id", s.serverID, "error", err)
+		return kvstore.ServerConfig{}, false
+	}
+	servers, err := kvstore.ParseServersConfig(data)
+	if err != nil {
+		s.logger.LogError("Corrupt server registry", "server_id", s.serverID, "error", err)
+		return kvstore.ServerConfig{}, false
+	}
+	return kvstore.ServerConfigForID(servers, s.serverID)
+}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/bridge_utils.go` around lines 158 - 222, Extract the shared
KeyServersConfig read, ParseServersConfig call, and ServerConfigForID lookup
into a BridgeUtils helper that resolves the current server configuration for
s.serverID and centralizes error logging. Update matrixUsernamePrefix,
serverDomain, and reconstructMatrixUserIDFromUsername to use this helper while
preserving each method’s existing fallback behavior and live-read semantics.

555-570: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Registry lookup errors are silently swallowed here, unlike the sibling helpers.

matrixUsernamePrefix and serverDomain both explicitly LogError on a KV read failure vs. a parse failure vs. a missing entry. Here, regErr/parseErr are dropped on the floor (the if ... err == nil guards just skip the block), so a transient KV failure and a genuinely-missing registry entry both collapse into the same generic "No registry entry for server" warning below (line 567-568). This makes it harder to distinguish a transient backend issue from a real configuration gap when debugging. Rolling this into the shared helper suggested at lines 158-222 fixes it for free.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/bridge_utils.go` around lines 555 - 570, Update the registry lookup in
the reconstruction flow to reuse the shared helper used by matrixUsernamePrefix
and serverDomain, such as the helper covering lines 158-222, so KV read
failures, parse failures, and missing entries are logged distinctly. Preserve
the existing serverURL/configuredServerName assignment and generic warning
behavior only for a genuinely unavailable registry entry.
server/servers_test.go (1)

142-145: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Doc comment describes the wrong function.

This block documents TestDeriveServerID but sits on TestManagedServers; the actual TestDeriveServerID at Line 335 is undocumented.

♻️ Move the comment
-// TestDeriveServerID verifies the deterministic serverID derivation: same
-// hostname (regardless of scheme/port/path/case) yields the same 26-char base32
-// ID, distinct hostnames yield distinct IDs, and an unusable URL errors.
+// TestManagedServers covers the registry lifecycle: add/upsert, per-server
+// SiteURL and remote assignment, and permanent removal.
 func TestManagedServers(t *testing.T) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/servers_test.go` around lines 142 - 145, Move the existing
deterministic server ID documentation from above TestManagedServers to
immediately above TestDeriveServerID. Update the TestManagedServers comment as
needed so it documents that test, while keeping the TestDeriveServerID behavior
description with its actual function.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docker/mattermost-bridge-registration2.yaml`:
- Around line 2-6: Update the url setting in the Mattermost bridge registration
to use a portable address reachable by the local Docker Compose network, such as
the synapse2 service/container hostname, instead of the personal Tailscale
hostname. Keep the existing plugin path and token values unchanged.

In `@docs/local-development.md`:
- Around line 94-96: Update the three fenced command examples in the local
development documentation to specify an explicit language identifier, such as
text, on each opening fence, including the examples around the visible /matrix
server command and the referenced sections.
- Around line 17-20: Update the local-development setup steps to remove the
obsolete Matrix Server URL and registration-file generation instructions.
Document the supported /matrix server add and /matrix server registration
commands using the plugin’s server-management flow for both the primary server
and the second server, including copying each generated registration file to the
expected Docker configuration location.

In `@server/command/command.go`:
- Around line 701-706: The unmap flow around the roomMappingKey deletion must
remove both reverse mappings created by mapChannelToRoom for alias-based
mappings. Resolve the stored alias to its room ID, then delete the alias-keyed
and resolved-room-ID-keyed BuildRoomMappingKey entries before returning success,
while preserving the existing warning behavior for deletion failures.
- Around line 1454-1462: Remove the "matrix.org" fallback in
buildRegistrationYAML and propagate the failure when both ServerName and
ExtractServerDomain(server.ServerURL) are unusable. Update
executeServerRegistrationCommand to detect this condition and return an
ephemeral error instead of emitting a misleading registration YAML file.

In `@server/command/server_command_test.go`:
- Around line 273-281: Add a targeted gosec suppression for the fixture token
values in TestBuildRegistrationYAML, applying //nolint:gosec with the
test-fixture explanation to the kvstore.ServerConfig literal. Keep the test
behavior and token values unchanged.

In `@server/hooks.go`:
- Around line 326-330: Update the GetMatrixRoomID error branch in the remote
user invite flow to log the returned error at an error-appropriate level and
preserve it as a failure instead of treating it as an unmapped channel. Keep the
existing matrixRoomID == "" handling for unmapped channels, since
GetMatrixRoomID returns no error in that case.
- Around line 190-215: Update the per-server upload loop in
OnSharedChannelsAttachmentSyncMsg to track UploadMedia failures instead of
silently continuing: retain the existing per-server logging and processing for
successful uploads, but return a non-nil error after processing targets when at
least one server failed so shared-channel synchronization retries and does not
post without the attachment.

In `@server/matrix_mentions_integration_test.go`:
- Around line 97-98: Handle errors in both channel-mapping setup blocks at
server/matrix_mentions_integration_test.go lines 97-98 and 298-299: capture the
error from BuildSingleChannelMapping and assert it with require.NoError(t, err),
then assert the kvstore.Set result with require.NoError. You may extract a
shared mapChannelToRoom helper, but both sites must use equivalent error-checked
behavior.

In `@server/multi_server_integration_test.go`:
- Around line 52-61: Suppress gosec G101 for the test-only ASToken and HSToken
literals in the MatrixTestConfig initializations within the multi-server
integration test, using a targeted nolint directive with the stated test-only
rationale; do not broaden the global lint policy.

In `@server/servers.go`:
- Around line 180-189: Update Plugin.persistServers to persist servers_config
through kvstore.KVStore.SetAtomicWithRetries instead of kvstore.KVStore.Set,
preserving the existing JSON marshaling and error-wrapping behavior. Ensure the
atomic operation writes the marshaled data and retains the failed-persistence
context.

In `@spec/2026-07-16-multi-matrix-server-support-phase-2.md`:
- Around line 48-51: Update the test requirement wording in the multi-matrix
server support specification by replacing “Unknown//invalid token” with
“Unknown/invalid token,” without changing the surrounding behavior or
assertions.
- Around line 17-20: Update the server registry write paths for `/matrix server
add`, updates, and migrations to validate that `hs_token` is non-empty and
unique across all registered servers before persistence. Reject duplicate or
empty tokens consistently, while preserving existing valid writes and
token-based `serverID` resolution.

In `@spec/2026-07-16-multi-matrix-server-support-phase-3.md`:
- Around line 45-49: Update the min_server_version section to reflect
plugin.json’s current minimum of 11.8.0, and document the confirmed Mattermost
version/reason that supports SiteURL on RegisterPluginOpts for per-remote
registration. Remove the stale 10.7.1 value so the specification and manifest
remain consistent.

In `@spec/2026-07-16-multi-matrix-server-support-phase-4.md`:
- Around line 42-45: Resolve the server-removal policy in the phase-4
specification before exposing DELETE: choose either cleanup or orphan-marking,
define behavior for retries and idempotent repeated deletion, and specify the
exact namespaced KV keys and shared-channel state affected. Add tests covering
the selected cleanup set and re-adding a server with mapped channels.

In `@spec/2026-07-16-multi-matrix-server-support-phase-6.md`:
- Around line 22-24: Define one canonical `/matrix server` command contract and
apply it consistently across the specification, local-development guide, and
plugin administrator help. Update the specification at
spec/2026-07-16-multi-matrix-server-support-phase-6.md lines 22-24,
docs/local-development.md lines 98-101, and plugin.json line 22 to use the exact
syntax and list all supported subcommands, including `unmap` and `status`;
ensure autocomplete and tests follow the same contract.

---

Outside diff comments:
In `@server/command/command.go`:
- Around line 926-948: Replace the three
responseText.WriteString(fmt.Sprintf(...)) calls in the channel-mapping
rendering flow with fmt.Fprintf(&responseText, ...) calls, preserving each
existing format string and arguments, including the current-channel and
all-mappings output.
- Around line 485-513: Replace the non-atomic Get/Upsert/Set sequence in the
channel-mapping flow with KVStore.SetAtomicWithRetries, preserving
UpsertChannelServerMapping and the existing error handling. Apply the same
atomic read-modify-write approach to the corresponding unmap path around its
channel-mapping update, so concurrent mappings cannot overwrite one another.

---

Nitpick comments:
In `@server/bridge_utils.go`:
- Around line 158-222: Extract the shared KeyServersConfig read,
ParseServersConfig call, and ServerConfigForID lookup into a BridgeUtils helper
that resolves the current server configuration for s.serverID and centralizes
error logging. Update matrixUsernamePrefix, serverDomain, and
reconstructMatrixUserIDFromUsername to use this helper while preserving each
method’s existing fallback behavior and live-read semantics.
- Around line 555-570: Update the registry lookup in the reconstruction flow to
reuse the shared helper used by matrixUsernamePrefix and serverDomain, such as
the helper covering lines 158-222, so KV read failures, parse failures, and
missing entries are logged distinctly. Preserve the existing
serverURL/configuredServerName assignment and generic warning behavior only for
a genuinely unavailable registry entry.

In `@server/command/command.go`:
- Around line 546-582: Resolve roomIdentifier once before the reverse-mapping,
bridge-alias, and member-sync logic, then reuse the resolved room ID across all
three paths. Update the calls in the surrounding command flow, including
syncChannelMembersToMatrixRoom setup, to avoid repeated ResolveRoomAlias
invocations while preserving fallback behavior when resolution fails.

In `@server/dm_room_creation_test.go`:
- Around line 108-131: The per-server setup is duplicated across the DM room
creation tests. Add a shared seedDMTestPlugin helper that performs the Matrix
client setup, server-domain configuration, plugin configuration initialization,
server entry seeding, and reverse user mapping with require.NoError; replace the
duplicated setup blocks in all three test cases with calls to this helper.

In `@server/plugin.go`:
- Around line 300-310: Update remoteIDForServer to consult an in-memory
serverID-to-remoteID map maintained by initMatrixClients under
matrixClientsLock, avoiding KV reads for known servers; retain a registry lookup
only when the map misses, and propagate or otherwise handle registry read errors
instead of silently ignoring them.
- Around line 242-246: Update the comments around RemoteID and the
initMatrixClients setup to remove references to the deleted p.remoteID field and
the singular initMatrixClient function. Document that remoteIDForServer returns
an empty string for unknown servers, and use the correct initMatrixClients
symbol wherever referenced.

In `@server/post_tracker_test.go`:
- Around line 9-31: Add a TestPostTracker_ServerIsolation test covering
identical post IDs on two distinct servers. Verify Put/Get retain independent
timestamps for each server, then confirm deleting the entry for one server
removes only that server’s entry while the other remains available.

In `@server/servers_test.go`:
- Around line 142-145: Move the existing deterministic server ID documentation
from above TestManagedServers to immediately above TestDeriveServerID. Update
the TestManagedServers comment as needed so it documents that test, while
keeping the TestDeriveServerID behavior description with its actual function.

In `@server/sync_to_matrix.go`:
- Around line 309-313: Cache the stable server domain on BridgeUtils instead of
invoking serverDomain() repeatedly in the sync, delete, and reaction event
paths. Initialize the cached value when the bridge is created, then update the
relevant handlers to reuse that field when constructing matrix_event_id_
property keys.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2dc698f9-8987-404e-90b5-3e7780bce541

📥 Commits

Reviewing files that changed from the base of the PR and between dfc0fd0 and 7b8996c.

⛔ Files ignored due to path filters (1)
  • webapp/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (58)
  • README.md
  • docker-compose.yml
  • docker/element-config.json
  • docker/element2-config.json
  • docker/mattermost-bridge-registration2.yaml
  • docker/synapse2_config.yaml
  • docs/local-development.md
  • plugin.json
  • server/api.go
  • server/api_test.go
  • server/bridge_utils.go
  • server/command/command.go
  • server/command/command_test.go
  • server/command/server_command_test.go
  • server/command/slash_command_test.go
  • server/configuration.go
  • server/dm_room_creation_test.go
  • server/dm_support_test.go
  • server/hooks.go
  • server/hooks_ping_test.go
  • server/matrix_mentions_integration_test.go
  • server/matrix_util.go
  • server/matrix_webhook.go
  • server/matrix_webhook_test.go
  • server/migrations.go
  • server/migrations_test.go
  • server/mocks/mock_kvstore.go
  • server/multi_server_inbound_integration_test.go
  • server/multi_server_inbound_same_channel_integration_test.go
  • server/multi_server_integration_test.go
  • server/multi_server_outbound_integration_test.go
  • server/multi_server_outbound_same_channel_integration_test.go
  • server/outbound_routing_test.go
  • server/plugin.go
  • server/plugin_integration_test.go
  • server/post_tracker.go
  • server/post_tracker_test.go
  • server/servers.go
  • server/servers_test.go
  • server/store/kvstore/constants.go
  • server/store/kvstore/kvstore.go
  • server/store/kvstore/schema.go
  • server/store/kvstore/schema_test.go
  • server/store/kvstore/startertemplate.go
  • server/sync_to_matrix.go
  • server/sync_to_matrix_integration_test.go
  • server/sync_to_matrix_test.go
  • server/sync_to_mattermost.go
  • server/sync_to_mattermost_test.go
  • server/testhelpers_test.go
  • server/thread_mapping_test.go
  • server/user_remote_detection_test.go
  • spec/2026-07-16-multi-matrix-server-support-phase-1.md
  • spec/2026-07-16-multi-matrix-server-support-phase-2.md
  • spec/2026-07-16-multi-matrix-server-support-phase-3.md
  • spec/2026-07-16-multi-matrix-server-support-phase-4.md
  • spec/2026-07-16-multi-matrix-server-support-phase-5.md
  • spec/2026-07-16-multi-matrix-server-support-phase-6.md

Comment thread docker/mattermost-bridge-registration2.yaml Outdated
Comment thread docs/local-development.md Outdated
Comment thread docs/local-development.md Outdated
Comment thread server/command/command.go
Comment thread server/command/command.go Outdated
Comment on lines +17 to +20
An AS appends the fixed suffix `/_matrix/app/v1/transactions/{txnId}` to the registration
`url`, so a discriminator cannot sit mid-path. **Give each server a unique `hs_token`** and
match the presented bearer token against every server's token to resolve `serverID` — zero
URL restructuring. (Fallback if needed: put `serverID` as the first path segment via the

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Enforce unique, non-empty hs_token values at registry write time.

Token-only routing is ambiguous if /matrix server add, update, or migration accepts duplicates or empty values. Reject those inputs before persistence; otherwise events can be routed into the wrong server namespace.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@spec/2026-07-16-multi-matrix-server-support-phase-2.md` around lines 17 - 20,
Update the server registry write paths for `/matrix server add`, updates, and
migrations to validate that `hs_token` is non-empty and unique across all
registered servers before persistence. Reject duplicate or empty tokens
consistently, while preserving existing valid writes and token-based `serverID`
resolution.

Comment on lines +48 to +51
- Two-server unit tests: transactions with distinct `hs_token`s route to distinct
namespaces; colliding `txnID`s across servers are NOT deduped against each other.
- Single-server behavior unchanged.
- Unknown//invalid token → 401.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Fix the invalid-token test wording.

Change Unknown//invalid token to Unknown/invalid token.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@spec/2026-07-16-multi-matrix-server-support-phase-2.md` around lines 48 - 51,
Update the test requirement wording in the multi-matrix server support
specification by replacing “Unknown//invalid token” with “Unknown/invalid
token,” without changing the surrounding behavior or assertions.

Comment on lines +45 to +49
## `min_server_version`

Confirm the earliest Mattermost server version that ships the `SiteURL` field on
`RegisterPluginOpts` (per-remote registration) and bump `plugin.json` `min_server_version`
accordingly (currently `10.7.1`).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Synchronize the version decision with plugin.json.

This specification still says the current minimum is 10.7.1, while the manifest now requires 11.8.0. Record the confirmed reason and final value here so the compatibility contract is not contradictory.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@spec/2026-07-16-multi-matrix-server-support-phase-3.md` around lines 45 - 49,
Update the min_server_version section to reflect plugin.json’s current minimum
of 11.8.0, and document the confirmed Mattermost version/reason that supports
SiteURL on RegisterPluginOpts for per-remote registration. Remove the stale
10.7.1 value so the specification and manifest remain consistent.

Comment on lines +42 to +45
Mirror Mattermost Connected Workspaces: removing a server **stops syncing** and tears down
its shared-channels remote; it does **not** delete channel content and does not hard-block.
Provide a guard/warning when channels are still mapped, and clean up (or orphan-mark) that
server's namespaced KV keys. Finalize exact cleanup batch here.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Resolve deletion behaviour before exposing DELETE.

“Clean up (or orphan-mark)” leaves two incompatible outcomes, especially for re-adding a server and preserving mapped channels. Choose one policy, define retry/idempotency semantics, and test the exact cleanup set.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@spec/2026-07-16-multi-matrix-server-support-phase-4.md` around lines 42 - 45,
Resolve the server-removal policy in the phase-4 specification before exposing
DELETE: choose either cleanup or orphan-marking, define behavior for retries and
idempotent repeated deletion, and specify the exact namespaced KV keys and
shared-channel state affected. Add tests covering the selected cleanup set and
re-adding a server with mapped channels.

Comment on lines +22 to +24
3. **`/matrix servers`**: new subcommand listing configured servers (id, domain, enabled,
mapped-channel count). Add to dispatch (`command.go:762-853`) and autocomplete
(`command.go:266-293`).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use one canonical /matrix server command contract.

The supplied implementation context and local guide use /matrix server with subcommands, while this specification introduces a top-level /matrix servers. The manifest help also omits supported subcommands such as unmap and status. Align the specification, help text, local guide, autocomplete, and tests with one exact syntax.

  • spec/2026-07-16-multi-matrix-server-support-phase-6.md#L22-L24: define the canonical command form.
  • docs/local-development.md#L98-L101: document the same form and complete subcommand set.
  • plugin.json#L22-L22: update the administrator help text to match the supported commands.
🧰 Tools
🪛 LanguageTool

[uncategorized] ~22-~22: Loose punctuation mark.
Context: ... is configured. 3. /matrix servers: new subcommand listing configured serve...

(UNLIKELY_OPENING_PUNCTUATION)

📍 Affects 3 files
  • spec/2026-07-16-multi-matrix-server-support-phase-6.md#L22-L24 (this comment)
  • docs/local-development.md#L98-L101
  • plugin.json#L22-L22
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@spec/2026-07-16-multi-matrix-server-support-phase-6.md` around lines 22 - 24,
Define one canonical `/matrix server` command contract and apply it consistently
across the specification, local-development guide, and plugin administrator
help. Update the specification at
spec/2026-07-16-multi-matrix-server-support-phase-6.md lines 22-24,
docs/local-development.md lines 98-101, and plugin.json line 22 to use the exact
syntax and list all supported subcommands, including `unmap` and `status`;
ensure autocomplete and tests follow the same contract.

Addresses 17 confirmed findings from the PR #33 review: orphaned
reverse room mapping on unmap, silent matrix.org fallback in
registration YAML generation, non-atomic reads-modify-writes on the
shared channel-mapping and server-registry KV keys, swallowed errors
in attachment sync and remote-user-invite, staticcheck/gosec lint
violations, discarded test-setup errors, and a non-portable Tailscale
URL plus stale setup instructions in the local dev docs.
The multi-server support work they described (phases 1-6) has been
implemented; the standalone planning docs are no longer needed.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
server/command/command.go (1)

703-713: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Alias-resolution failure during unmap cleanup is silently swallowed.

If matrixClient.ResolveRoomAlias(matrixRoomIdentifier) errors here, the resolved-room-ID reverse mapping is left behind with no log entry — every other failure path in this function (Lines 657, 689, 699, 731) logs a Warn/Error. This silently defeats the purpose of the fix (avoiding orphaned reverse mappings) with no operator visibility into the failure.

Suggested fix
 	if strings.HasPrefix(matrixRoomIdentifier, "#") {
-		if resolvedRoomID, err := matrixClient.ResolveRoomAlias(matrixRoomIdentifier); err == nil && resolvedRoomID != "" {
-			if err := c.kvstore.Delete(kvstore.BuildRoomMappingKey(serverID, resolvedRoomID)); err != nil {
-				c.client.Log.Warn("Failed to remove resolved room-ID mapping", "error", err, "room_id", resolvedRoomID, "channel_id", args.ChannelId)
-			}
-		}
+		resolvedRoomID, resolveErr := matrixClient.ResolveRoomAlias(matrixRoomIdentifier)
+		if resolveErr != nil {
+			c.client.Log.Warn("Failed to resolve alias for reverse-mapping cleanup; resolved-room-ID mapping may remain orphaned", "error", resolveErr, "alias", matrixRoomIdentifier, "channel_id", args.ChannelId)
+		} else if resolvedRoomID != "" {
+			if err := c.kvstore.Delete(kvstore.BuildRoomMappingKey(serverID, resolvedRoomID)); err != nil {
+				c.client.Log.Warn("Failed to remove resolved room-ID mapping", "error", err, "room_id", resolvedRoomID, "channel_id", args.ChannelId)
+			}
+		}
 	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/command/command.go` around lines 703 - 713, Update the alias cleanup
block in the unmap flow around ResolveRoomAlias to log a warning when alias
resolution fails, including the error and relevant matrix room/channel
identifiers. Preserve the existing resolved-room-ID deletion and warning
behavior when resolution succeeds.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@server/command/command.go`:
- Around line 486-498: Update the SetAtomicWithRetries closure in the /matrix
map flow to recover from ParseChannelServerMappings errors by treating corrupt
existing data as an empty mapping and continuing with
UpsertChannelServerMapping. Preserve normal parsed mappings when parsing
succeeds, then marshal and save the updated mapping instead of returning the
parse error.

---

Nitpick comments:
In `@server/command/command.go`:
- Around line 703-713: Update the alias cleanup block in the unmap flow around
ResolveRoomAlias to log a warning when alias resolution fails, including the
error and relevant matrix room/channel identifiers. Preserve the existing
resolved-room-ID deletion and warning behavior when resolution succeeds.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 64530bd6-2245-4801-adc9-3ff55174399e

📥 Commits

Reviewing files that changed from the base of the PR and between 7b8996c and 4219427.

📒 Files selected for processing (11)
  • docker-compose.yml
  • docker/mattermost-bridge-registration2.yaml
  • docs/local-development.md
  • server/command/command.go
  • server/command/server_command_test.go
  • server/hooks.go
  • server/matrix_mentions_integration_test.go
  • server/multi_server_integration_test.go
  • server/plugin.go
  • server/servers.go
  • server/servers_test.go
🚧 Files skipped from review as they are similar to previous changes (9)
  • docker/mattermost-bridge-registration2.yaml
  • docs/local-development.md
  • docker-compose.yml
  • server/servers_test.go
  • server/command/server_command_test.go
  • server/servers.go
  • server/multi_server_integration_test.go
  • server/plugin.go
  • server/hooks.go

Comment thread server/command/command.go
/matrix status probed each enabled homeserver sequentially via
TestConnection, whose HTTP client allows 30s per request. With more than
one server registered, an unreachable homeserver could push the command
past Mattermost's slash-command timeout, so the multi-server status
output added in 7b8996c was unusable in exactly the setup it was written
for. Probes now run concurrently under a shared 8s deadline and a server
that misses it renders as timed out, rather than being reported as
healthy or as a failure that was never observed.

Counting mapped channels scanned the whole channel_mapping_ keyspace
once per server, in both /matrix status and /matrix server list, giving
O(servers x channels) KV reads for the same data. Replace it with a
single scan returning per-server counts, and log rather than silently
swallow a ListKeysWithPrefix failure that would otherwise pass a partial
count off as complete.

Also recover from a corrupt channel-mapping value in /matrix server map
instead of aborting with no recovery path, matching /matrix unmap, and
fix three issues found while reviewing the branch: resolveOutboundServers
treated an isDirectChannel lookup error as "not a DM" and silently
dropped the event, a doc comment sat on the wrong test function, and a
test fixture discarded a mapping-build error.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant