Deliver realm grants to running sessions; boot permissioned accounts with a complete workspace list - #6088
Deliver realm grants to running sessions; boot permissioned accounts with a complete workspace list#6088FadhlanR wants to merge 10 commits into
Conversation
…complete The workspace list is assembled once at boot from Matrix account data, so a permission grant never reaches a signed-in session (reload required) and an account with no account-data key boots to an empty chooser despite holding permissions. - Seed app.boxel.realm-servers with the host's own realm server on first login when an account has no realm list, so boot assembles from realm permissions via _realm-auth instead of an empty list. Fall back to the own realm server in the event-driven refresh when no trusted-servers entry is persisted. - Emit realms-list-updated to each affected user from the permission write paths (patchRealmPermissions and the grafana upsert handler) so a running session re-derives its list live. - Provision a personal workspace lazily on login when the user has none. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Preview deploymentsHost Test Results 1 files 1 suites 2h 30m 49s ⏱️ Results for commit 2a4e8fb. Realm Server Test Results 1 files ±0 212 suites ±0 1h 20m 45s ⏱️ + 8m 21s Results for commit 2a4e8fb. ± Comparison against earlier commit 86651fd. |
- boot-assembly: a keyless account (neither account-data key) with permissions assembles its realm from _realm-auth and persists the own realm server into app.boxel.realm-servers; a legacy-list account is left on its existing path. - personal-realm: login provisions a personal workspace when the user has none and does not when one already exists. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Grants a user with an established session room via PATCH /_permissions and asserts a realms-list-updated event lands in that room; also asserts the grant succeeds when the grantee has no session room (best-effort push is a no-op). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Provisioning on every boot fired createRealm across the whole test suite — colliding with the workspace-chooser tests that stub createRealm and polluting unrelated acceptance tests with "Failed to fetch" console errors, and it would create stray realms in the matrix e2e app. Needs a narrower trigger and a harness create-realm mock; deferring to a follow-up. The grant-reaches-session and boot-from-permissions fixes stand on their own. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
28d649c to
7c96e5c
Compare
…rkspace Batch-created accounts (registered straight on Synapse) never go through the host sign-up flow that creates a personal workspace, so they boot without one. Add POST /_grafana-ensure-personal-realm?user=<mxid> (grafana-secret auth) that creates the user's personal realm if absent and pushes realms-list-updated so a running session shows it without a reload. Idempotent: createRealm rejects a pre-existing realm, which the endpoint reports as alreadyExisted. A host boot-time trigger was rejected: after the boot seed a granted Synapse account and an ordinary test account are indistinguishable at boot (both keyless, both assemble realms via _realm-auth, both lack a personal realm), so any boot trigger fires across the whole test suite. The ops-run endpoint is test-isolated and matches how these accounts are provisioned. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The boot seed flipped every keyless account onto the authoritative trusted-servers path, which disables the app.boxel.realms AccountData listener — so an account could no longer add realms (including foreign realm URLs that _realm-auth cannot serve) via account data. Only commit to the trusted path and persist the seed once _realm-auth confirms the account holds permissioned realms; otherwise revert to the legacy assembly so account-data updates keep driving the list. Fixes the realm-urls matrix e2e regression. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the ops-run grafana endpoint with automatic host-side provisioning: on login, when the user has no personal workspace, create one. This is what an account provisioned outside host sign-up (e.g. registered straight on Synapse) was missing, and unlike the endpoint it needs no ops action. The trigger is gated on ENV.hostedEnvironment being staging/production. That value is the realm-server's serve-time environment and is `local` in both the QUnit suite and the matrix e2e host, so auto-provisioning never fires in tests (which is what made a plain boot trigger pollute the suite before). The provisioning logic is exposed as a public method so a test drives it directly. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Expose the auto-provision trigger as `autoProvisionPersonalRealm` (a settable field) instead of an environment-only getter. Its default still comes from ENV.hostedEnvironment — the one in-app signal that is `local` in both the QUnit suite and the matrix e2e host and `staging`/`production` only in a real deployment — but a test can now flip it on and exercise the real start()->provision path. Provisioning runs through a dropTask so settled() waits for it in tests. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The boot seed for a keyless account decided whether to keep the authoritative trusted path by checking whether `_realm-auth` returned any realms. But `_realm-auth` returns the public base/catalog realms for every authenticated user, so the result was never empty — even for an account with no workspace of its own. That left every keyless account flipped to `trustedRealmServersAuthoritative`, which makes the `app.boxel.realms` AccountData listener ignore updates, so a realm added to that key (e.g. a foreign realm URL) never appeared without a reload. (Caught by matrix/tests/realm-urls.spec.ts.) Base the decision on user-visible realms instead: after the list is assembled, the public realms have deduped into their base/catalog entries, so `userRealmIdentifiers` is empty exactly when the account has no workspace of its own. Revert to the legacy path (and don't persist the seed) in that case; keep and persist the seed only when a real user realm is present. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Review follow-ups on the live-grant / boot-assembly work: - Consolidate session-room event delivery into a single runtime-common `createSendEvent` helper and give it stale-room self-heal: on a Matrix "not in room" rejection it clears the stored session_room_id so the user's next auth re-mints a room the current account is in. The realm grant notification and the grafana upsert handler now both route through it (deleting a fourth hand-rolled copy), and node-realm's broadcast reuses the moved error helpers. Covered by a new self-heal unit test. - Scope personal-realm auto-provisioning to the user's OWN workspace URL (derived the same way the server does) instead of matching any `/personal/` realm, so a foreign personal workspace shared with the user no longer suppresses provisioning of their own. New integration test. - Auto-provision on any real deployment (hostedEnvironment !== 'local'), not just staging/production, so self-hosted deployments get it too. - Comment accuracy: the permissions-test grantee choice now documents why it faithfully models the production single-account topology; the boot auto-provision comment credits the account-data channel that actually surfaces the realm for a legacy-path account; the already-exists branch is documented as a race backstop behind the own-URL check. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2a4e8fbd85
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| // does on staging, production, and any self-hosted deployment. A settable | ||
| // field (not a getter) so a test can flip it on and exercise the real | ||
| // start()->provision path. | ||
| autoProvisionPersonalRealm = ENV.hostedEnvironment !== 'local'; |
There was a problem hiding this comment.
Enable provisioning for self-hosted local deployments
When a self-hosted realm server does not set REALM_SENTRY_ENVIRONMENT, serve-index.ts retains the build default hostedEnvironment: 'local', so this condition disables provisioning there as well as in tests. Consequently, directly provisioned Matrix accounts in ordinary local/self-hosted installations still boot without the personal workspace this change is intended to create. Use an explicit test-mode signal or feature flag rather than treating every local deployment as a test.
Useful? React with 👍 / 👎.
| private ensurePersonalRealmTask = dropTask(async () => { | ||
| await this.ensurePersonalRealmForUserIfMissing(); | ||
| }); |
There was a problem hiding this comment.
Cancel provisioning when the session ends
If the user logs out or switches accounts while the profile lookup or realm creation is pending, this service-level task survives logout()/resetState(). It can then resume against the replacement Matrix client: the initial username check came from account A, while createPersonalRealmForUser() authenticates and appends account data using account B, potentially creating B's workspace with A's display name or mutating B's realm list. Cancel the task at the session boundary or verify that the initiating user is still current before creating and appending.
Useful? React with 👍 / 👎.
Problem
The host builds a user's workspace list once at boot from Matrix account data (
app.boxel.realm-servers→_realm-auth, or legacyapp.boxel.realms). Realm permissions live in a separate store (realm_user_permissions), which the running client only reads through that boot-time assembly. Two consequences:realm_user_permissionsbut pushes no live signal, so the realm doesn't appear until the user reloads (opening the realm URL directly works, proving the grant is live).Changes
Boot assembles from permissions (host —
matrix-service.ts)app.boxel.realm-serverswith the host's own realm server on first login and persist it, so boot runs the permissions-driven assembly (_realm-auth) instead of falling through to an empty list. Scoped to accounts with no legacy realm list, so existing accounts keep their current assembly + lazy-migration path.Live grant signal (realm-server / runtime-common)
patchRealmPermissionsnow notifies each affected user via the existingrealms-list-updatedsession-room event; the host already re-runs_realm-authon it. Nothing else told a grantee — therealm_index_updatedbroadcast is server-to-server only.Personal workspace on login (host)
_create-realmemitsrealms-list-updatedto the user, so it surfaces live. Idempotent against a concurrent tab / prior login.Verification
ember-tsc --noEmitclean for all three changed files (remaining errors in a fresh checkout are the known@cardstack/boxel-icons/boxel-uibaseline, none in the edited files).Testing status
Draft — automated tests are the next step. Planned coverage: boot with permissions but no account-data key shows the realm; a live
realms-list-updatedevent adds a newly-granted realm without reload; legacy-list boot yields an equal-or-pruned set; grant write paths emit the event (and no-op cleanly without a session room); personal realm created only when absent.🤖 Generated with Claude Code