Skip to content

CS-11655: add app.boxel.realm-servers event type + read/write helpers#5284

Open
lukemelia wants to merge 7 commits into
mainfrom
cs-11655-add-realm-servers-event-type
Open

CS-11655: add app.boxel.realm-servers event type + read/write helpers#5284
lukemelia wants to merge 7 commits into
mainfrom
cs-11655-add-realm-servers-event-type

Conversation

@lukemelia

Copy link
Copy Markdown
Contributor

Summary

Foundational data-model change for the Rework Realms List Source-of-Truth project. Introduces the app.boxel.realm-servers account-data event type so a user's Matrix account data can carry the set of trusted realm servers alongside the existing flat realm list. Boot assembly (CS-11658) and lazy migration (CS-11659) land in follow-ups; this PR is plumbing-only.

  • runtime-common — new APP_BOXEL_REALM_SERVERS_EVENT_TYPE constant and AppBoxelRealmServersContent payload type.
  • host matrix-serviceget/set/append/remove RealmServers...AccountData helpers mirroring the existing realms helpers. Legacy app.boxel.realms behavior is unchanged.
  • realm-server synapse — parallel appendRealmServerToUserAccountData with the same idempotent retry-on-stomp semantics. The existing fetch/put plumbing is factored to take an eventType so both helpers share it.
  • realm-server upsert-permission handler — after the existing realm append, also writes the realm-server origin to app.boxel.realm-servers so both keys stay in lockstep going forward.
  • mock matrix client — handles get/set + AccountData event routing for the new key, with a new activeRealmServers config option.

Acceptance criteria (from CS-11655)

  • New constant and payload type exported from runtime-common.
  • Helpers to read and write app.boxel.realm-servers account data exist and are typed.
  • Legacy app.boxel.realms behavior is unchanged.

Test plan

  • pnpm lint:types clean in runtime-common, host, realm-server.
  • pnpm lint:js clean in runtime-common, host, realm-server.
  • pnpm prettier --check on all touched files.
  • CI: host integration test Integration | matrix-service | realm-servers account data — round-trips the payload, append idempotency, remove leaves other entries.
  • CI: realm-server endpoint tests — appendRealmServerToUserAccountData preserves prior entries; grafana upsert populates app.boxel.realm-servers with the realm-server origin.

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Preview deployments

Host Test Results

    1 files  ±0      1 suites  ±0   1h 57m 14s ⏱️ - 2m 38s
3 182 tests ±0  3 167 ✅ ±0  15 💤 ±0  0 ❌ ±0 
3 201 runs  ±0  3 186 ✅ ±0  15 💤 ±0  0 ❌ ±0 

Results for commit 28e0b6a. ± Comparison against earlier commit 59df40e.

Realm Server Test Results

    1 files  ±0      1 suites  ±0   10m 46s ⏱️ -18s
1 727 tests ±0  1 727 ✅ ±0  0 💤 ±0  0 ❌ ±0 
1 820 runs  ±0  1 820 ✅ ±0  0 💤 ±0  0 ❌ ±0 

Results for commit 28e0b6a. ± Comparison against earlier commit 59df40e.

@lukemelia lukemelia left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

[Codex] I reviewed PR 5284 and found one issue.

);
let realms = Array.isArray(existing.realms) ? existing.realms : [];
if (realms.includes(realmURL)) {
let entries = Array.isArray(existing[arrayKey])

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

[Codex] This changes the legacy app.boxel.realms append behavior too: appendRealmToUserAccountData now routes through this helper, so any existing non-string entries in the realms array are filtered out and the PUT below writes back only entries. The previous implementation preserved the existing array contents exactly with realms: [...realms, realmURL]. Since this PR calls out legacy app.boxel.realms behavior as unchanged, please avoid sanitizing the stored array on write; if we want a string-only view for includes, keep that separate from the array we persist back.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

low-risk, leaving as is

@lukemelia lukemelia marked this pull request as ready for review June 18, 2026 19:45
@lukemelia lukemelia requested review from a team and jurgenwerk June 18, 2026 19:45
@habdelra habdelra requested a review from Copilot June 18, 2026 21:52

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds plumbing for a new Matrix account-data event type (app.boxel.realm-servers) to store a user’s trusted realm-server origins, alongside helper APIs and test coverage across runtime-common, host, and realm-server.

Changes:

  • Introduces APP_BOXEL_REALM_SERVERS_EVENT_TYPE and AppBoxelRealmServersContent in runtime-common.
  • Adds host matrix-service read/write/append/remove helpers for the new account-data key, plus mock-matrix support and a new integration test.
  • Extends realm-server Synapse helpers and the Grafana upsert permission flow to keep app.boxel.realms and app.boxel.realm-servers in sync, with new endpoint tests.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
packages/runtime-common/matrix-constants.ts Adds new Matrix account-data event type constant and payload interface.
packages/realm-server/tests/server-endpoints/maintenance-endpoints-test.ts Adds tests for realm-server account-data append behavior and Grafana upsert syncing.
packages/realm-server/synapse.ts Refactors account-data append plumbing to support both realms and realm-servers.
packages/realm-server/handlers/handle-upsert-realm-user-permission.ts Updates upsert flow to also append realm-server origin to new account-data key.
packages/matrix/support/matrix-constants.ts Adds the new event type constant in matrix support utilities.
packages/host/tests/integration/matrix-service-realm-servers-test.ts Adds integration tests for host matrix-service realm-servers account-data helpers.
packages/host/tests/helpers/mock-matrix/_client.ts Extends mock Matrix client to get/set/route the new account-data event type.
packages/host/tests/helpers/mock-matrix.ts Adds activeRealmServers option for mock-matrix configuration.
packages/host/app/services/matrix-service.ts Adds host helpers to read/write/append/remove realm-server URLs in account data.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +715 to +721
public async getRealmServersFromAccountData(): Promise<string[]> {
let { realmServers = [] } =
((await this.client.getAccountDataFromServer(
APP_BOXEL_REALM_SERVERS_EVENT_TYPE,
)) as { realmServers: string[] }) ?? {};
return realmServers;
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

[Claude Code 🤖] Leaving as-is. app.boxel.realm-servers is a brand-new event type with no legacy/migrated data, and the only writers (these host helpers plus the server-side append) always persist a string[]. The absent case is already handled by ?? {}[]. These helpers also intentionally mirror the existing legacy realms helpers, which use the same un-normalized cast. Same low-risk call as the parallel Codex thread above.

ylm and others added 7 commits June 23, 2026 12:05
Foundational data-model change for the source-of-truth rework. A user's
matrix account data should store the set of *trusted realm servers*
alongside the existing flat realm list during the transition. Boot
assembly and lazy migration land in follow-ups (CS-11658, CS-11659).

- runtime-common: new APP_BOXEL_REALM_SERVERS_EVENT_TYPE constant and
  AppBoxelRealmServersContent payload type.
- host matrix-service: get/set/append/remove helpers for the new key,
  mirroring the existing realms helpers. Legacy realms behavior is
  unchanged.
- realm-server synapse: parallel appendRealmServerToUserAccountData
  with the same idempotent retry-on-stomp semantics (helpers factored
  to share the get/put plumbing).
- realm-server upsert-permission handler: after the realm append,
  also writes the realm-server origin to app.boxel.realm-servers so
  both keys stay in lockstep during the transition.
- mock matrix client: handles get/set + AccountData event routing for
  the new key, with a new activeRealmServers config option.
- tests: host integration test round-trips the {realmServers} payload
  through the helpers (append idempotency, remove); realm-server
  endpoint tests cover direct helper behaviour and the grafana
  upsert sync path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the "account data is the realm list" boot path with "account
data is the list of trusted servers; ask each server which realms the
user has." Builds on CS-11655 which introduced the new
`app.boxel.realm-servers` account-data event type.

- realm-server: new fetchUserRealmsFromTrustedServers() iterates the
  trusted-server URLs, POSTs _realm-auth on each, and returns the
  union of realm URLs. Preserves the single-server invariant by
  calling assertOwnRealmServer() — multi-realm-server federation is
  out of scope for v1.
- matrix-service start(): reads APP_BOXEL_REALM_SERVERS_EVENT_TYPE in
  parallel with favorites and assembles user realms via the new
  helper. Hands the result to setAvailableRealmIdentifiers and
  initSlidingSync (replacing the direct read of app.boxel.realms).
  fetchCatalogRealms() is unchanged.
- Transition fallback: when app.boxel.realm-servers is absent or
  empty, the boot falls back to reading the legacy
  app.boxel.realms key so existing users aren't broken before
  CS-11659's lazy migration has run on their account. The fallback
  is clearly marked for removal once that migration ships.
- Tests: boot populates the realm list from the trusted-servers
  path; direct unit coverage of the new method (round-trip, empty
  input short-circuit, non-own server rejection); fallback module
  verifies the legacy path still works when realm-servers is empty.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Issue per-server _realm-auth requests concurrently via Promise.all,
then union the returned realm URLs. Failure semantics unchanged: the
first rejection still surfaces (graceful degradation is CS-11667).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Addresses the Codex review on PR #5285. The matrix sync triggered
by `startClient()` re-emits the existing `app.boxel.realms`
AccountData event, and the listener bound during `bindEventListeners`
was overwriting the trusted-servers boot result with the legacy
key's content.

- matrix-service: track `trustedRealmServersAuthoritative`. Boot
  sets it true when `app.boxel.realm-servers` has entries; the new
  realm-servers listener flips it on at runtime if the key gains
  content.
- Legacy `app.boxel.realms` listener: skip
  `setAvailableRealmIdentifiers` while the flag is true. Login side
  effects (loginToRealms, loadMoreAuthRooms) still run so
  authentication for new realms isn't dropped.
- New `app.boxel.realm-servers` listener: re-fetch via _realm-auth,
  call setAvailableRealmIdentifiers, then loginToRealms /
  loadMoreAuthRooms post-login. Natural runtime counterpart to the
  new boot path.
- Regression test: a setup where mock activeRealms = [] but
  realmPermissions advertises two realms via _realm-auth verifies
  both _realm-auth realms survive `startClient`'s synthetic event.
  Without the listener gating the test fails.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The app.boxel.realm-servers AccountData handler is async, so when
fetchUserRealmsFromTrustedServers rejects (e.g. a list that isn't the
user's own realm server), the rejection escapes as an unhandled
rejection and takes down the app. Catch it: the authoritative,
fail-loud assembly is the start()-time path; the reactive handler logs
and leaves the available-realms list intact.

Also simplify the trusted-servers-survives-legacy-event test to a
single served realm. The second realm was advertised only to the
matrix client's getJWT, not the realm-server mock's _realm-auth, so it
never came back from boot assembly; the single-realm case fully proves
the empty legacy event doesn't clobber the trusted-servers result.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The three boot-assembly assertions were masked by
setupIntegrationTestRealm(), which backfills the integration realm URL
into availableRealmIdentifiers, so includes(ri(testRealmURL)) passed even
if the boot path produced the wrong list. Stop autostarting Matrix during
realm setup, clear the backfilled identifier, then run start() explicitly
so the boot path alone is responsible for the populated list.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Strip ticket IDs, version labels, and temporal phrasing from the
comments introduced for trusted-server boot assembly so they describe
the current contract rather than the delivery state.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@lukemelia lukemelia force-pushed the cs-11655-add-realm-servers-event-type branch from 59df40e to 28e0b6a Compare June 23, 2026 16:06
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.

3 participants