Repository navigation
Add ADR 004: federate realm servers, each paired with its own Matrix homeserver - #6795
Conversation
7a253a6 to
ec0372a
Compare
| ### 1.6. Each realm server serves its own infrastructure realms | ||
|
|
||
| Each realm server MUST serve its own copy of the infrastructure realms (base, | ||
| catalog, and skills), addressed by their `@cardstack/<realm>/` prefixes | ||
| (ADR 002 §1.4). |
There was a problem hiding this comment.
Hmm, this seems dubious
There was a problem hiding this comment.
[Claude Code 🤖] Agreed. §1.6 doesn't hold up once one client reads realms on more than one server, and the ADR doesn't address that case.
The host registers each @cardstack/<realm>/ prefix against the realm its own config names (packages/host/app/services/network.ts). It serves @cardstack/base/* from its own bundle (shimBundledBase). So when a host paired with server A loads a card from server B:
- A link in that card to
@cardstack/catalog/...resolves to A's catalog, not the catalog on B that the card was written and indexed against. The host keys cards by RRI, so it can't hold both. - B's card definitions run against A's bundled base, but B indexed and prerendered them against its own base. A base version difference between the two servers shows up only at render time.
The Rationale's "that is how prefixes already resolve (ADR 002 §1.4)" doesn't support §1.6 either. ADR 002 §1.4 says only that each VirtualNetwork registers its own prefixes. ADR 002's portability argument covers moving between dev, staging and production, not one client reading two servers at once.
There are two ways out:
- (a) Move §1.6 to Open questions. The questions are what a prefix-form reference means in another server's content, and what base compatibility servers owe each other. Keep "each server serves its own infrastructure realms" as a SHOULD.
- (b) Keep the MUST and add a rule that makes it coherent. For example, a prefix names whichever copy the reader resolves, and servers in a federation MUST serve compatible copies.
I'd take (a), because no source supports either half of (b). This ADR introduces the problem, and it's blocking: §1.6 is a MUST that reviewers would enforce.
There was a problem hiding this comment.
[Claude Code 🤖] Done in 719b3eb289 with option (a). §1.6 is now a SHOULD: each realm server serves its own infrastructure realms. The Rationale no longer says ADR 002 implies it. A new Open questions section (§5) asks what a prefix means in content read from another realm server. It names the hybrid as the leading candidate: base is the reader's copy, governed by a compatibility rule, and catalog and skills resolve against the server the content came from.
lukemelia
left a comment
There was a problem hiding this comment.
[Claude Code 🤖] This review checks the ADR against ADR 001 and against the code it cites: the quoted sources, the Consequences claims, and whether adr-for finds the ADR from the code its rules govern. It doesn't weigh whether federation is the right direction.
One blocking issue: §1.6 lets a prefix-form reference name a different realm depending on whose client resolves it. The details are in the reply on the §1.6 thread. Everything else is non-blocking.
- Move §1.6 to Open questions. Blocking. See the §1.6 thread.
- Add four files to
scope. See the thread onscope. - List the other two copies of the hostname heuristic in §4. See the thread on
userIdFromUsername. - Name the rules decided at recording. See the thread on
date.
The red checks come from a duplicate CI run that was cancelled. The other run on the same commit passed, so you can ignore them.
| scope: | ||
| - packages/realm-server/** | ||
| - packages/runtime-common/realm-auth-client.ts | ||
| - packages/runtime-common/matrix-backend-authentication.ts | ||
| - packages/runtime-common/matrix-client.ts | ||
| - packages/runtime-common/realm-permission-checker.ts | ||
| - packages/host/app/services/realm-server.ts | ||
| - packages/host/app/services/matrix-service.ts | ||
| - packages/host/app/services/realm.ts | ||
| - packages/boxel-cli/src/lib/profile-manager.ts | ||
| - packages/matrix/support/synapse/** |
There was a problem hiding this comment.
[Claude Code 🤖] adr-for doesn't list this ADR for several files its rules govern, so a change in one of them gets reviewed without it. I ran adr-for on each of these files, and none of them matched:
packages/runtime-common/realm.tsissues and verifies the session JWTs (§1.3) and callsuserIdFromUsername(§1.2).packages/host/app/services/store.tsandpackages/host/app/services/host-mode-service.tshold two of theassertOwnRealmServercall sites that §1.9 is about.packages/boxel-cli/src/lib/seed-auth.tscontainsderiveHostFromRealmUrl(see the comment on §4).
Add these four files to scope. Non-blocking.
There was a problem hiding this comment.
[Claude Code 🤖] Done in 5faf7ebd40. scope now includes all four files, plus packages/runtime-common/db-queries/realm-permission-queries.ts, which reduces realm owners to localparts (§1.2). adr-for lists ADR 004 for each of them.
| - `userIdFromUsername` derives a server name from the homeserver hostname | ||
| (§1.2). |
There was a problem hiding this comment.
[Claude Code 🤖] This bullet lists only userIdFromUsername, but the code has two more copies of the same heuristic. Both derive the server name from the realm server's hostname, not the homeserver's:
deriveHostFromRealmUrlinpackages/boxel-cli/src/lib/seed-auth.ts- the server-name helper in
packages/realm-server/scripts/claude-prerender-token.ts
List them here, so that whoever fixes §1.2 fixes all three. Non-blocking.
| - `userIdFromUsername` derives a server name from the homeserver hostname | |
| (§1.2). | |
| - `userIdFromUsername` in `matrix-client.ts`, `deriveHostFromRealmUrl` in | |
| boxel-cli's `seed-auth.ts`, and `claude-prerender-token.ts` derive a server | |
| name from a hostname (§1.2). |
There was a problem hiding this comment.
[Claude Code 🤖] Done in 5faf7ebd40. All three copies are listed, but in the Remediation section (§7) rather than §4, because each one breaks a MUST in §1.2. Each entry names a file and a symbol: userIdFromUsername and ensureFullMatrixUserId in matrix-client.ts, deriveHostFromRealmUrl in seed-auth.ts, and deriveMatrixDomain in claude-prerender-token.ts. Consequences now lists only federation work that no MUST requires.
| @@ -0,0 +1,197 @@ | |||
| --- | |||
| status: proposed | |||
| date: 2026-01-08 | |||
There was a problem hiding this comment.
[Claude Code 🤖] ADR 001 §1.7 makes date the date of the original decision. Several requirements here cite only [10], the answers given when this ADR was recorded:
- the
users-row rule in §1.4 - §1.5
- §1.6
- §1.9
As written, the ADR says these rules were decided on 2026-01-08. You could fix this in either of two ways:
- Add a summary sentence that names the rules decided at recording. I'd do this.
- Move those rules into a second ADR that isn't retroactive.
Non-blocking.
There was a problem hiding this comment.
[Claude Code 🤖] Done in 01ebda4523, with the summary sentence. It says the users-row rule in §1.4, and §1.5, §1.6 and §1.9, were decided when the ADR was recorded, not on its date.
Records, as a retroactive proposed ADR, that Boxel is a federation of independent realm servers, each paired with exactly one Matrix homeserver, and the rules that keep new code compatible with that design before cross-server identity is built. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CSuMQ23CEVDjQ9xMzoFRgU
§1.6 required each realm server to serve its own copy of base, catalog and skills, addressed by prefix. Prefixes resolve per VirtualNetwork and the host serves base from its own bundle, so within a federation the same prefix names a different realm depending on whose client resolves it. Make §1.6 a SHOULD, drop the claim that ADR 002 implies it, and record the cross-server meaning of a prefix as an open question with the hybrid (base as the reader's copy, catalog and skills from the origin server) as the leading candidate. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CSuMQ23CEVDjQ9xMzoFRgU
List each known gap between the code and ADR 004's MUST requirements as one file, symbol, and broken section, following the Remediation rules for proposed ADRs. Move the gaps out of Consequences, which keeps only the federation work that no MUST requires, and widen the scope so every listed file resolves to this ADR. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CSuMQ23CEVDjQ9xMzoFRgU
13e06ab to
5faf7eb
Compare
The ADR's date is when the pairing was stated as an established rule. The users-row rule in §1.4, and §1.5, §1.6 and §1.9, cite only the answers given when the ADR was recorded, so say so in the summary. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CSuMQ23CEVDjQ9xMzoFRgU
richardhjtan
left a comment
There was a problem hiding this comment.
[Claude Code 🤖] One blocking issue: §7 lists a gap in packages/realm-server/scripts/claude-prerender-token.ts, which no longer exists on this branch. See the inline comment.
The seed-minting prerender token script is gone from main, replaced by the diagnostic render token endpoint, which takes the realm owner's full user id from the database. The gap it listed is closed, so its entry goes (ADR 001 §1.6). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CSuMQ23CEVDjQ9xMzoFRgU
This PR adds one ADR and its index row. ADR 001 defines the format. It cites ADR 002 (#6758) and takes the number after ADR 003 (#6794).
Summary
ADR 004 records, as a retroactive
proposedADR, that Boxel is a federation of independent realm servers. Each realm server is paired with exactly one Matrix homeserver, which provides identity for that server. Cross-server identity is not built yet. The ADR sets out the target design and the rules that keep new code compatible with it. Its scope coverspackages/realm-server, the realm-auth, Matrix, permission and realm files inpackages/runtime-common, the host'srealm-server,matrix-service,realm,storeandhost-mode-serviceservices, the boxel-cli profile manager and seed auth, and the Synapse config, soscripts/adr-for.mtslists it for changes there.matrix_server_name), through the federation userinfo endpoint. Each server issues and verifies its own session tokens and never accepts another server's.usersrow grants only users of the paired homeserver.@cardstack/<realm>/prefix means in content read from another realm server is left as an open question.assertOwnRealmServercall sites or other single-server assumptions without a TODO citing ADR 004.Key decisions
Review focus
userssubject as any signed-in user. §1.4 here narrows it to users of the paired homeserver. Should ADR 006 say so too?runtime-commonand host files rather than whole packages, to avoid listing ADR 004 for unrelated changes.Test plan
node scripts/adr-for.mts packages/runtime-common/matrix-client.ts packages/host/app/services/realm-server.ts packages/realm-server/handlers/create-realm.tslists ADR 004.🤖 Generated with Claude Code
https://claude.ai/code/session_01CSuMQ23CEVDjQ9xMzoFRgU