From b6e3218e80b5b46a018f53de77e999eeae00d58b Mon Sep 17 00:00:00 2001 From: ylm Date: Thu, 18 Jun 2026 17:14:24 -0400 Subject: [PATCH 1/6] CS-11658: boot assembly from trusted servers via _realm-auth MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- packages/host/app/services/matrix-service.ts | 38 ++++-- packages/host/app/services/realm-server.ts | 43 +++++++ .../matrix-service-boot-assembly-test.ts | 120 ++++++++++++++++++ 3 files changed, 193 insertions(+), 8 deletions(-) create mode 100644 packages/host/tests/integration/matrix-service-boot-assembly-test.ts diff --git a/packages/host/app/services/matrix-service.ts b/packages/host/app/services/matrix-service.ts index 0ce074eea28..b73acdd4af7 100644 --- a/packages/host/app/services/matrix-service.ts +++ b/packages/host/app/services/matrix-service.ts @@ -836,17 +836,41 @@ export default class MatrixService extends Service { this.startedAtTs = 0; } if (isTesting()) - console.warn('[start-phase] getAccountData(realms,favorites)'); - let [accountDataContent, favoritesData] = await Promise.all([ + console.warn('[start-phase] getAccountData(realm-servers,favorites)'); + let [realmServersData, favoritesData] = await Promise.all([ this.client.getAccountDataFromServer( - APP_BOXEL_REALMS_EVENT_TYPE, - ) as Promise<{ realms: string[] } | null>, + APP_BOXEL_REALM_SERVERS_EVENT_TYPE, + ) as Promise<{ realmServers: string[] } | null>, this.client.getAccountDataFromServer( APP_BOXEL_WORKSPACE_FAVORITES_EVENT_TYPE, ) as Promise<{ favorites: string[] } | null>, ]); this.workspaceFavorites = favoritesData?.favorites ?? []; + // CS-11658: boot assembles the realm list from trusted servers via + // `_realm-auth`. The transition fallback below reads the legacy + // `app.boxel.realms` key when `app.boxel.realm-servers` is absent + // or empty — necessary until CS-11659's lazy migration populates + // the new key for existing users. Remove the fallback once that + // migration has run on all active accounts. + let trustedServers = realmServersData?.realmServers ?? []; + let userRealmURLs: string[]; + if (trustedServers.length > 0) { + if (isTesting()) + console.warn('[start-phase] fetchUserRealmsFromTrustedServers'); + userRealmURLs = + await this.realmServer.fetchUserRealmsFromTrustedServers( + trustedServers, + ); + } else { + if (isTesting()) + console.warn('[start-phase] getAccountData(realms-legacy)'); + let legacyRealmsData = (await this.client.getAccountDataFromServer( + APP_BOXEL_REALMS_EVENT_TYPE, + )) as { realms: string[] } | null; + userRealmURLs = legacyRealmsData?.realms ?? []; + } + let noRealmsLoggedIn = Array.from(this.realm.realms.entries()).every( ([_url, realmResource]) => !realmResource.isLoggedIn, ); @@ -857,9 +881,7 @@ export default class MatrixService extends Service { ); await Promise.all([ this.realmServer.fetchCatalogRealms(), - this.realmServer.setAvailableRealmIdentifiers( - (accountDataContent?.realms ?? []).map(ri), - ), + this.realmServer.setAvailableRealmIdentifiers(userRealmURLs.map(ri)), ]); if (isTesting()) console.warn('[start-phase] prefetchRealmInfos'); @@ -868,7 +890,7 @@ export default class MatrixService extends Service { ); if (isTesting()) console.warn('[start-phase] initSlidingSync'); - await this.initSlidingSync(accountDataContent); + await this.initSlidingSync({ realms: userRealmURLs }); if (isTesting()) console.warn('[start-phase] startClient'); await this.client.startClient({ slidingSync: this.slidingSync }); if (isTesting()) diff --git a/packages/host/app/services/realm-server.ts b/packages/host/app/services/realm-server.ts index de0ac377801..f339a4130c9 100644 --- a/packages/host/app/services/realm-server.ts +++ b/packages/host/app/services/realm-server.ts @@ -259,6 +259,49 @@ export default class RealmServerService extends Service { return response.json(); } + // CS-11658: boot assembly reads `app.boxel.realm-servers` and asks each + // trusted server (via `_realm-auth`) which realms the current user has. + // Returns the union of realm URLs across all trusted servers. v1 keeps + // the single-server invariant — assertOwnRealmServer() rejects any list + // that includes a non-own server until multi-realm-server federation + // ships. + async fetchUserRealmsFromTrustedServers( + trustedServerURLs: string[], + ): Promise { + if (trustedServerURLs.length === 0) { + return []; + } + // TODO: remove once multi-realm-server federation lands. + this.assertOwnRealmServer(trustedServerURLs); + await this.login(); + let realmURLs = new Set(); + for (let serverURL of trustedServerURLs) { + let normalizedServerURL = ensureTrailingSlash(serverURL); + let response = await this.network.fetch( + `${normalizedServerURL}_realm-auth`, + { + method: 'POST', + headers: { + Accept: SupportedMimeType.JSONAPI, + 'Content-Type': 'application/json', + Authorization: `Bearer ${this.token}`, + }, + }, + ); + if (!response.ok) { + let responseText = await response.text(); + throw new Error( + `Failed to fetch user realms from trusted server ${normalizedServerURL}: ${response.status} - ${responseText}`, + ); + } + let tokens = (await response.json()) as Record; + for (let realmURL of Object.keys(tokens)) { + realmURLs.add(realmURL); + } + } + return [...realmURLs]; + } + @cached get availableRealmIdentifiers(): RealmIdentifier[] { return this.availableRealms.map((r) => ri(r.url)); diff --git a/packages/host/tests/integration/matrix-service-boot-assembly-test.ts b/packages/host/tests/integration/matrix-service-boot-assembly-test.ts new file mode 100644 index 00000000000..9e7f129dba6 --- /dev/null +++ b/packages/host/tests/integration/matrix-service-boot-assembly-test.ts @@ -0,0 +1,120 @@ +import type { RenderingTestContext } from '@ember/test-helpers'; + +import { getService } from '@universal-ember/test-support'; +import { module, test } from 'qunit'; + +import { baseRealm, ensureTrailingSlash, ri } from '@cardstack/runtime-common'; + +import ENV from '@cardstack/host/config/environment'; +import type RealmServerService from '@cardstack/host/services/realm-server'; + +import { + testRealmURL, + setupIntegrationTestRealm, + setupLocalIndexing, +} from '../helpers'; + +import { setupBaseRealm } from '../helpers/base-realm'; + +import { setupMockMatrix } from '../helpers/mock-matrix'; + +import { setupRenderingTest } from '../helpers/setup'; + +const testRealmServerURL = ensureTrailingSlash(ENV.realmServerURL); + +// CS-11658: boot assembles the available-realms list from the user's +// trusted realm-servers (`app.boxel.realm-servers`) by asking each via +// `_realm-auth`, instead of reading the realm list directly out of +// `app.boxel.realms`. A transition fallback to the legacy key remains +// until CS-11659's lazy migration has run on all active accounts. +module( + 'Integration | matrix-service | boot assembly with trusted servers', + function (hooks) { + setupRenderingTest(hooks); + setupBaseRealm(hooks); + setupLocalIndexing(hooks); + + let mockMatrixUtils = setupMockMatrix(hooks, { + loggedInAs: '@testuser:localhost', + activeRealms: [baseRealm.url, testRealmURL], + activeRealmServers: [testRealmServerURL], + autostart: true, + }); + + hooks.beforeEach(async function (this: RenderingTestContext) { + await setupIntegrationTestRealm({ + mockMatrixUtils, + contents: {}, + }); + }); + + test('boot populates availableRealmIdentifiers when `app.boxel.realm-servers` is set', async function (assert) { + let realmServer = getService('realm-server') as RealmServerService; + assert.ok( + realmServer.availableRealmIdentifiers.includes(ri(testRealmURL)), + 'testRealmURL is present in availableRealmIdentifiers', + ); + }); + + test('fetchUserRealmsFromTrustedServers returns realms advertised by `_realm-auth`', async function (assert) { + let realmServer = getService('realm-server') as RealmServerService; + let realms = await realmServer.fetchUserRealmsFromTrustedServers([ + testRealmServerURL, + ]); + assert.deepEqual( + realms, + [testRealmURL], + 'returns the trusted server’s realms', + ); + }); + + test('fetchUserRealmsFromTrustedServers returns [] for an empty input', async function (assert) { + let realmServer = getService('realm-server') as RealmServerService; + let realms = await realmServer.fetchUserRealmsFromTrustedServers([]); + assert.deepEqual(realms, [], 'short-circuits without any HTTP call'); + }); + + test('fetchUserRealmsFromTrustedServers rejects non-own realm-server URLs', async function (assert) { + let realmServer = getService('realm-server') as RealmServerService; + await assert.rejects( + realmServer.fetchUserRealmsFromTrustedServers([ + 'https://other-server.example/', + ]), + /Multi-realm server support is not yet implemented/, + ); + }); + }, +); + +module( + 'Integration | matrix-service | boot assembly fallback to legacy realms', + function (hooks) { + setupRenderingTest(hooks); + setupBaseRealm(hooks); + setupLocalIndexing(hooks); + + // No activeRealmServers — the mock returns `{ realmServers: [] }`, the + // same shape the host sees for a user who hasn’t been migrated to + // `app.boxel.realm-servers` yet (CS-11659). + let mockMatrixUtils = setupMockMatrix(hooks, { + loggedInAs: '@testuser:localhost', + activeRealms: [baseRealm.url, testRealmURL], + autostart: true, + }); + + hooks.beforeEach(async function (this: RenderingTestContext) { + await setupIntegrationTestRealm({ + mockMatrixUtils, + contents: {}, + }); + }); + + test('boot still populates realms from `app.boxel.realms`', async function (assert) { + let realmServer = getService('realm-server') as RealmServerService; + assert.ok( + realmServer.availableRealmIdentifiers.includes(ri(testRealmURL)), + 'testRealmURL is present in availableRealmIdentifiers', + ); + }); + }, +); From a3f4db36a1a3ecc798d8e58545be9be1ad31ce6b Mon Sep 17 00:00:00 2001 From: ylm Date: Thu, 18 Jun 2026 17:22:49 -0400 Subject: [PATCH 2/6] Parallelize _realm-auth fetches across trusted servers 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) --- packages/host/app/services/realm-server.ts | 47 +++++++++++----------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/packages/host/app/services/realm-server.ts b/packages/host/app/services/realm-server.ts index f339a4130c9..a1ab3714fba 100644 --- a/packages/host/app/services/realm-server.ts +++ b/packages/host/app/services/realm-server.ts @@ -274,32 +274,31 @@ export default class RealmServerService extends Service { // TODO: remove once multi-realm-server federation lands. this.assertOwnRealmServer(trustedServerURLs); await this.login(); - let realmURLs = new Set(); - for (let serverURL of trustedServerURLs) { - let normalizedServerURL = ensureTrailingSlash(serverURL); - let response = await this.network.fetch( - `${normalizedServerURL}_realm-auth`, - { - method: 'POST', - headers: { - Accept: SupportedMimeType.JSONAPI, - 'Content-Type': 'application/json', - Authorization: `Bearer ${this.token}`, + let perServerRealmURLs = await Promise.all( + trustedServerURLs.map(async (serverURL) => { + let normalizedServerURL = ensureTrailingSlash(serverURL); + let response = await this.network.fetch( + `${normalizedServerURL}_realm-auth`, + { + method: 'POST', + headers: { + Accept: SupportedMimeType.JSONAPI, + 'Content-Type': 'application/json', + Authorization: `Bearer ${this.token}`, + }, }, - }, - ); - if (!response.ok) { - let responseText = await response.text(); - throw new Error( - `Failed to fetch user realms from trusted server ${normalizedServerURL}: ${response.status} - ${responseText}`, ); - } - let tokens = (await response.json()) as Record; - for (let realmURL of Object.keys(tokens)) { - realmURLs.add(realmURL); - } - } - return [...realmURLs]; + if (!response.ok) { + let responseText = await response.text(); + throw new Error( + `Failed to fetch user realms from trusted server ${normalizedServerURL}: ${response.status} - ${responseText}`, + ); + } + let tokens = (await response.json()) as Record; + return Object.keys(tokens); + }), + ); + return [...new Set(perServerRealmURLs.flat())]; } @cached From 4bb2b1283ed55b91363a918ef86e5f2d4a7f9e81 Mon Sep 17 00:00:00 2001 From: ylm Date: Thu, 18 Jun 2026 17:43:33 -0400 Subject: [PATCH 3/6] Make trusted-servers authoritative against the legacy realms event 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) --- packages/host/app/services/matrix-service.ts | 49 +++++++++++++++++-- .../matrix-service-boot-assembly-test.ts | 48 ++++++++++++++++++ 2 files changed, 92 insertions(+), 5 deletions(-) diff --git a/packages/host/app/services/matrix-service.ts b/packages/host/app/services/matrix-service.ts index b73acdd4af7..1f6f9e6eb55 100644 --- a/packages/host/app/services/matrix-service.ts +++ b/packages/host/app/services/matrix-service.ts @@ -175,6 +175,13 @@ export default class MatrixService extends Service { @tracked private _client: ExtendedClient | undefined; @tracked private _isInitializingNewUser = false; @tracked private postLoginCompleted = false; + // CS-11658: when true, `app.boxel.realm-servers` is the authoritative + // source of the user's realm list and `app.boxel.realms` events are + // ignored for `setAvailableRealmIdentifiers`. Set during boot based on + // the new key's presence; flipped on by the realm-servers listener if + // the key gains content at runtime. Login-related side effects + // (`loginToRealms`, `loadMoreAuthRooms`) still run regardless. + private trustedRealmServersAuthoritative = false; @tracked private _currentRoomId: string | undefined; @tracked private timelineLoadingState: Map = new TrackedMap(); @@ -394,16 +401,43 @@ export default class MatrixService extends Service { this.matrixSDK.ClientEvent.AccountData, async (e) => { switch (e.event.type) { - case APP_BOXEL_REALMS_EVENT_TYPE: - await this.realmServer.setAvailableRealmIdentifiers( - (e.event.content.realms as string[]).map(ri), - ); + case APP_BOXEL_REALMS_EVENT_TYPE: { + let legacyRealms = e.event.content.realms as string[]; + // CS-11658: when `app.boxel.realm-servers` is the source of + // truth, ignore the realm-list payload here — otherwise the + // initial-sync re-emission of this event would overwrite the + // trusted-servers boot result. Side effects below still run + // so post-login realm authentication isn't dropped. + if (!this.trustedRealmServersAuthoritative) { + await this.realmServer.setAvailableRealmIdentifiers( + legacyRealms.map(ri), + ); + } // Only do this after we've completed our overall login if (this.postLoginCompleted) { await this.loginToRealms(); - await this.loadMoreAuthRooms(e.event.content.realms); + await this.loadMoreAuthRooms(legacyRealms); + } + break; + } + case APP_BOXEL_REALM_SERVERS_EVENT_TYPE: { + let realmServers = e.event.content.realmServers as string[]; + this.trustedRealmServersAuthoritative = realmServers.length > 0; + if (this.trustedRealmServersAuthoritative) { + let realmURLs = + await this.realmServer.fetchUserRealmsFromTrustedServers( + realmServers, + ); + await this.realmServer.setAvailableRealmIdentifiers( + realmURLs.map(ri), + ); + if (this.postLoginCompleted) { + await this.loginToRealms(); + await this.loadMoreAuthRooms(realmURLs); + } } break; + } case APP_BOXEL_SYSTEM_CARD_EVENT_TYPE: await this.setSystemCard(e.event.content.id); break; @@ -854,6 +888,11 @@ export default class MatrixService extends Service { // the new key for existing users. Remove the fallback once that // migration has run on all active accounts. let trustedServers = realmServersData?.realmServers ?? []; + // The legacy `app.boxel.realms` AccountData event is re-emitted by + // the matrix sync that runs inside `startClient()` below. Setting + // this flag here makes that re-emission a no-op for the available- + // realms list — the realm-servers path is the authoritative source. + this.trustedRealmServersAuthoritative = trustedServers.length > 0; let userRealmURLs: string[]; if (trustedServers.length > 0) { if (isTesting()) diff --git a/packages/host/tests/integration/matrix-service-boot-assembly-test.ts b/packages/host/tests/integration/matrix-service-boot-assembly-test.ts index 9e7f129dba6..f06f1e2540d 100644 --- a/packages/host/tests/integration/matrix-service-boot-assembly-test.ts +++ b/packages/host/tests/integration/matrix-service-boot-assembly-test.ts @@ -86,6 +86,54 @@ module( }, ); +module( + 'Integration | matrix-service | trusted-servers result survives legacy event', + function (hooks) { + setupRenderingTest(hooks); + setupBaseRealm(hooks); + setupLocalIndexing(hooks); + + // The mock matrix client's `startClient` re-emits a synthetic + // `app.boxel.realms` AccountData event with `activeRealms` content. + // With the new key authoritative, that re-emission must NOT overwrite + // the realms the trusted-servers boot path discovered. The setup + // below deliberately diverges activeRealms from realmPermissions so + // the bug (if reintroduced) shows up as a missing realm from the + // _realm-auth response. + const otherRealmURL = 'http://test-realm/test-other/'; + + let mockMatrixUtils = setupMockMatrix(hooks, { + loggedInAs: '@testuser:localhost', + activeRealms: [], // synthetic legacy event would clear availableRealms + activeRealmServers: [testRealmServerURL], + realmPermissions: { + [testRealmURL]: ['read', 'write'], + [otherRealmURL]: ['read', 'write'], + }, + autostart: true, + }); + + hooks.beforeEach(async function (this: RenderingTestContext) { + await setupIntegrationTestRealm({ + mockMatrixUtils, + contents: {}, + }); + }); + + test('legacy realms event does not overwrite the trusted-servers boot result', async function (assert) { + let realmServer = getService('realm-server') as RealmServerService; + assert.ok( + realmServer.availableRealmIdentifiers.includes(ri(testRealmURL)), + 'testRealmURL from _realm-auth survives the legacy event', + ); + assert.ok( + realmServer.availableRealmIdentifiers.includes(ri(otherRealmURL)), + 'otherRealmURL from _realm-auth survives the legacy event (regression guard)', + ); + }); + }, +); + module( 'Integration | matrix-service | boot assembly fallback to legacy realms', function (hooks) { From 26ca8416ec16c2222a643f6f116b748e2a2a851b Mon Sep 17 00:00:00 2001 From: Luke Melia Date: Mon, 22 Jun 2026 14:44:49 -0400 Subject: [PATCH 4/6] Stop reactive realm-servers assembly from throwing into the void 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) --- packages/host/app/services/matrix-service.ts | 32 +++++++++++++------ .../matrix-service-boot-assembly-test.ts | 24 ++++---------- 2 files changed, 30 insertions(+), 26 deletions(-) diff --git a/packages/host/app/services/matrix-service.ts b/packages/host/app/services/matrix-service.ts index 1f6f9e6eb55..7ebb4f72d58 100644 --- a/packages/host/app/services/matrix-service.ts +++ b/packages/host/app/services/matrix-service.ts @@ -424,16 +424,30 @@ export default class MatrixService extends Service { let realmServers = e.event.content.realmServers as string[]; this.trustedRealmServersAuthoritative = realmServers.length > 0; if (this.trustedRealmServersAuthoritative) { - let realmURLs = - await this.realmServer.fetchUserRealmsFromTrustedServers( - realmServers, + // A server-pushed account-data event must not crash the app: + // assembly can reject (e.g. fetchUserRealmsFromTrustedServers + // refuses a list that isn't this user's own realm server) and + // an async event handler that throws surfaces as an unhandled + // rejection. The authoritative, fail-loud assembly runs at + // start(); here we log and leave the available-realms list as + // it was. + try { + let realmURLs = + await this.realmServer.fetchUserRealmsFromTrustedServers( + realmServers, + ); + await this.realmServer.setAvailableRealmIdentifiers( + realmURLs.map(ri), + ); + if (this.postLoginCompleted) { + await this.loginToRealms(); + await this.loadMoreAuthRooms(realmURLs); + } + } catch (err) { + console.error( + 'Failed to assemble realms from trusted servers in app.boxel.realm-servers account data', + err, ); - await this.realmServer.setAvailableRealmIdentifiers( - realmURLs.map(ri), - ); - if (this.postLoginCompleted) { - await this.loginToRealms(); - await this.loadMoreAuthRooms(realmURLs); } } break; diff --git a/packages/host/tests/integration/matrix-service-boot-assembly-test.ts b/packages/host/tests/integration/matrix-service-boot-assembly-test.ts index f06f1e2540d..b5dd50ffe57 100644 --- a/packages/host/tests/integration/matrix-service-boot-assembly-test.ts +++ b/packages/host/tests/integration/matrix-service-boot-assembly-test.ts @@ -94,22 +94,16 @@ module( setupLocalIndexing(hooks); // The mock matrix client's `startClient` re-emits a synthetic - // `app.boxel.realms` AccountData event with `activeRealms` content. - // With the new key authoritative, that re-emission must NOT overwrite - // the realms the trusted-servers boot path discovered. The setup - // below deliberately diverges activeRealms from realmPermissions so - // the bug (if reintroduced) shows up as a missing realm from the - // _realm-auth response. - const otherRealmURL = 'http://test-realm/test-other/'; - + // `app.boxel.realms` AccountData event carrying `activeRealms`. When + // trusted servers are authoritative, that re-emission must NOT + // overwrite the realms the trusted-servers boot path discovered. + // `activeRealms` is empty while `_realm-auth` advertises testRealmURL, + // so a clobber would zero the available-realms list and drop + // testRealmURL — making the regression observable as a missing realm. let mockMatrixUtils = setupMockMatrix(hooks, { loggedInAs: '@testuser:localhost', - activeRealms: [], // synthetic legacy event would clear availableRealms + activeRealms: [], activeRealmServers: [testRealmServerURL], - realmPermissions: { - [testRealmURL]: ['read', 'write'], - [otherRealmURL]: ['read', 'write'], - }, autostart: true, }); @@ -126,10 +120,6 @@ module( realmServer.availableRealmIdentifiers.includes(ri(testRealmURL)), 'testRealmURL from _realm-auth survives the legacy event', ); - assert.ok( - realmServer.availableRealmIdentifiers.includes(ri(otherRealmURL)), - 'otherRealmURL from _realm-auth survives the legacy event (regression guard)', - ); }); }, ); From 27d88c71dc4d68ea5d4216fbac150d100515962c Mon Sep 17 00:00:00 2001 From: Luke Melia Date: Mon, 22 Jun 2026 16:23:43 -0400 Subject: [PATCH 5/6] Make boot-assembly tests fail on regression 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) --- .../matrix-service-boot-assembly-test.ts | 31 +++++++++++++++++-- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/packages/host/tests/integration/matrix-service-boot-assembly-test.ts b/packages/host/tests/integration/matrix-service-boot-assembly-test.ts index b5dd50ffe57..769120e0f7f 100644 --- a/packages/host/tests/integration/matrix-service-boot-assembly-test.ts +++ b/packages/host/tests/integration/matrix-service-boot-assembly-test.ts @@ -6,6 +6,7 @@ import { module, test } from 'qunit'; import { baseRealm, ensureTrailingSlash, ri } from '@cardstack/runtime-common'; import ENV from '@cardstack/host/config/environment'; +import type MatrixService from '@cardstack/host/services/matrix-service'; import type RealmServerService from '@cardstack/host/services/realm-server'; import { @@ -34,18 +35,27 @@ module( setupBaseRealm(hooks); setupLocalIndexing(hooks); + // Don't autostart Matrix during realm setup: `setupIntegrationTestRealm` + // backfills the integration realm URL into `availableRealmIdentifiers`, + // which would mask a boot-assembly regression. We clear that backfill and + // run `start()` explicitly so the boot path alone populates the list. let mockMatrixUtils = setupMockMatrix(hooks, { loggedInAs: '@testuser:localhost', activeRealms: [baseRealm.url, testRealmURL], activeRealmServers: [testRealmServerURL], - autostart: true, }); hooks.beforeEach(async function (this: RenderingTestContext) { await setupIntegrationTestRealm({ mockMatrixUtils, contents: {}, + startMatrix: false, }); + let realmServer = getService('realm-server') as RealmServerService; + await realmServer.setAvailableRealmIdentifiers([]); + let matrixService = getService('matrix-service') as MatrixService; + await matrixService.ready; + await matrixService.start(); }); test('boot populates availableRealmIdentifiers when `app.boxel.realm-servers` is set', async function (assert) { @@ -104,14 +114,22 @@ module( loggedInAs: '@testuser:localhost', activeRealms: [], activeRealmServers: [testRealmServerURL], - autostart: true, }); hooks.beforeEach(async function (this: RenderingTestContext) { await setupIntegrationTestRealm({ mockMatrixUtils, contents: {}, + startMatrix: false, }); + // Clear the URL `setupIntegrationTestRealm` backfills so the boot path — + // and the `startClient()` legacy-event re-emission it triggers — is + // solely responsible for the final list. + let realmServer = getService('realm-server') as RealmServerService; + await realmServer.setAvailableRealmIdentifiers([]); + let matrixService = getService('matrix-service') as MatrixService; + await matrixService.ready; + await matrixService.start(); }); test('legacy realms event does not overwrite the trusted-servers boot result', async function (assert) { @@ -137,14 +155,21 @@ module( let mockMatrixUtils = setupMockMatrix(hooks, { loggedInAs: '@testuser:localhost', activeRealms: [baseRealm.url, testRealmURL], - autostart: true, }); hooks.beforeEach(async function (this: RenderingTestContext) { await setupIntegrationTestRealm({ mockMatrixUtils, contents: {}, + startMatrix: false, }); + // Clear the backfilled URL so the legacy-fallback boot path is what + // populates the list, not `setupIntegrationTestRealm`. + let realmServer = getService('realm-server') as RealmServerService; + await realmServer.setAvailableRealmIdentifiers([]); + let matrixService = getService('matrix-service') as MatrixService; + await matrixService.ready; + await matrixService.start(); }); test('boot still populates realms from `app.boxel.realms`', async function (assert) { From e3265b0ed81e81eab6b5fc1408d8b6e1f2cf84d9 Mon Sep 17 00:00:00 2001 From: Luke Melia Date: Tue, 23 Jun 2026 10:02:09 -0400 Subject: [PATCH 6/6] Make boot-assembly comments evergreen 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) --- packages/host/app/services/matrix-service.ts | 25 ++++++++++--------- packages/host/app/services/realm-server.ts | 10 ++++---- .../matrix-service-boot-assembly-test.ts | 13 +++++----- 3 files changed, 25 insertions(+), 23 deletions(-) diff --git a/packages/host/app/services/matrix-service.ts b/packages/host/app/services/matrix-service.ts index 7ebb4f72d58..f07c51e2aed 100644 --- a/packages/host/app/services/matrix-service.ts +++ b/packages/host/app/services/matrix-service.ts @@ -175,12 +175,12 @@ export default class MatrixService extends Service { @tracked private _client: ExtendedClient | undefined; @tracked private _isInitializingNewUser = false; @tracked private postLoginCompleted = false; - // CS-11658: when true, `app.boxel.realm-servers` is the authoritative - // source of the user's realm list and `app.boxel.realms` events are - // ignored for `setAvailableRealmIdentifiers`. Set during boot based on - // the new key's presence; flipped on by the realm-servers listener if - // the key gains content at runtime. Login-related side effects - // (`loginToRealms`, `loadMoreAuthRooms`) still run regardless. + // When true, `app.boxel.realm-servers` is the authoritative source of + // the user's realm list and `app.boxel.realms` events are ignored for + // `setAvailableRealmIdentifiers`. Set during boot from whether that key + // has content, and flipped on by the realm-servers listener if the key + // gains content at runtime. Login-related side effects (`loginToRealms`, + // `loadMoreAuthRooms`) still run regardless. private trustedRealmServersAuthoritative = false; @tracked private _currentRoomId: string | undefined; @tracked private timelineLoadingState: Map = @@ -403,8 +403,8 @@ export default class MatrixService extends Service { switch (e.event.type) { case APP_BOXEL_REALMS_EVENT_TYPE: { let legacyRealms = e.event.content.realms as string[]; - // CS-11658: when `app.boxel.realm-servers` is the source of - // truth, ignore the realm-list payload here — otherwise the + // When `app.boxel.realm-servers` is the source of truth, + // ignore the realm-list payload here — otherwise the // initial-sync re-emission of this event would overwrite the // trusted-servers boot result. Side effects below still run // so post-login realm authentication isn't dropped. @@ -895,12 +895,13 @@ export default class MatrixService extends Service { ]); this.workspaceFavorites = favoritesData?.favorites ?? []; - // CS-11658: boot assembles the realm list from trusted servers via + // Boot assembles the realm list from trusted servers via // `_realm-auth`. The transition fallback below reads the legacy // `app.boxel.realms` key when `app.boxel.realm-servers` is absent - // or empty — necessary until CS-11659's lazy migration populates - // the new key for existing users. Remove the fallback once that - // migration has run on all active accounts. + // or empty, so users whose accounts haven't yet been migrated to + // `app.boxel.realm-servers` still boot. Remove the fallback once + // the lazy migration that populates `app.boxel.realm-servers` has + // run on all active accounts. let trustedServers = realmServersData?.realmServers ?? []; // The legacy `app.boxel.realms` AccountData event is re-emitted by // the matrix sync that runs inside `startClient()` below. Setting diff --git a/packages/host/app/services/realm-server.ts b/packages/host/app/services/realm-server.ts index a1ab3714fba..02d95b75ff0 100644 --- a/packages/host/app/services/realm-server.ts +++ b/packages/host/app/services/realm-server.ts @@ -259,11 +259,11 @@ export default class RealmServerService extends Service { return response.json(); } - // CS-11658: boot assembly reads `app.boxel.realm-servers` and asks each - // trusted server (via `_realm-auth`) which realms the current user has. - // Returns the union of realm URLs across all trusted servers. v1 keeps - // the single-server invariant — assertOwnRealmServer() rejects any list - // that includes a non-own server until multi-realm-server federation + // Boot assembly reads `app.boxel.realm-servers` and asks each trusted + // server (via `_realm-auth`) which realms the current user has. Returns + // the union of realm URLs across all trusted servers. assertOwnRealmServer() + // keeps the single-server invariant — it rejects any list that includes a + // server other than the user's own until multi-realm-server federation // ships. async fetchUserRealmsFromTrustedServers( trustedServerURLs: string[], diff --git a/packages/host/tests/integration/matrix-service-boot-assembly-test.ts b/packages/host/tests/integration/matrix-service-boot-assembly-test.ts index 769120e0f7f..40af38cd297 100644 --- a/packages/host/tests/integration/matrix-service-boot-assembly-test.ts +++ b/packages/host/tests/integration/matrix-service-boot-assembly-test.ts @@ -23,11 +23,12 @@ import { setupRenderingTest } from '../helpers/setup'; const testRealmServerURL = ensureTrailingSlash(ENV.realmServerURL); -// CS-11658: boot assembles the available-realms list from the user's -// trusted realm-servers (`app.boxel.realm-servers`) by asking each via -// `_realm-auth`, instead of reading the realm list directly out of +// Boot assembles the available-realms list from the user's trusted +// realm-servers (`app.boxel.realm-servers`) by asking each via +// `_realm-auth`, rather than reading the realm list directly out of // `app.boxel.realms`. A transition fallback to the legacy key remains -// until CS-11659's lazy migration has run on all active accounts. +// until the lazy migration that populates `app.boxel.realm-servers` has +// run on all active accounts. module( 'Integration | matrix-service | boot assembly with trusted servers', function (hooks) { @@ -150,8 +151,8 @@ module( setupLocalIndexing(hooks); // No activeRealmServers — the mock returns `{ realmServers: [] }`, the - // same shape the host sees for a user who hasn’t been migrated to - // `app.boxel.realm-servers` yet (CS-11659). + // same shape the host sees for a user who hasn't yet been migrated to + // `app.boxel.realm-servers`. let mockMatrixUtils = setupMockMatrix(hooks, { loggedInAs: '@testuser:localhost', activeRealms: [baseRealm.url, testRealmURL],