You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
feat: request-scoped Workspaces — each person works in their own - #356
First of two stacked PRs toward #228, following #237. The shared plan is in docs/proposals/multi-user-service.md; #357 builds on this PR to enable member agents and personal settings.
After #237, several people could sign in but shared the Server's one active Workspace. This PR gives each admitted person a current Workspace of their own. First sign-in provisions a personal Workspace when needed; members can create and switch Workspaces without moving another person. Local mode keeps its process-active Workspace.
API contracts and types
The public schema change is one optional capability field. The larger changes are to authorization and the meaning of existing endpoints; they do not require new request/response variants.
The UI needs to discover name-based creation and switching. Optional preserves compatibility with older servers and existing local responses; no provider-specific public type hierarchy is needed.
WorkspaceInfo, WorkspaceDescriptor
Shapes unchanged. In Bubble mode, Workspace identity and active describe the caller's selection. Members receive path: null; administrators may receive a Disk path.
Reuses the existing nullable path and stable UUID. A member must never choose or learn arbitrary server filesystem paths. Type comments now describe this distinction.
WorkspaceCreateRequest
Shape unchanged: { path?: string, name?: string }. Bubble members create with { name }; local Disk adoption and administrator folder operations retain { path, name? }. Local SQL creation already accepts { name }.
Validation depends on the caller and backend, so duplicating the schema into provider-specific request types would add little. Creation returns an inactive descriptor; activation is a separate request.
/api/workspaces and /:workspaceId/activate
Bubble lists the caller's enterable Workspaces and activation changes their stored preference. Rename/unregister require Workspace manage; unauthorized targets return 404.
Reuses the existing routes. Other people and already admitted requests keep their Workspace. Folder operations remain administrator-only.
Bubble roles and grant targets
Add Workspace owner/editor/viewer; separate resource read, write, manage from system huabu.admin. Space grant ids become <workspaceId>:<canvasId>.
Resource management must not imply deployment administration. Workspace-qualified grants prevent identical Space ids in different Workspaces from sharing authorization. This is an authorization contract change even though IdentityResponse has no new fields.
New runs name the Workspace they started in. Legacy machine requests use the process-active Workspace; the explicit mount overrides that default.
read, write, and manage flow down Workspace → root Space → child Space. Editors can edit content and create Spaces; managers can rename, delete, share, and change a Space's parent. Creating a child authorizes its parent; creating/importing a root requires Workspace write. Creating a Space claims ownership of that new Space.
Server interfaces and design assessment
Internal type or boundary
Purpose
Assessment
WorkspaceScope { handle, path } and AsyncLocalStorage
Carry one admitted request's Workspace through asynchronous work. Existing Workspace getters read the scope before falling back to process state. An empty scope means no Workspace.
Justified: keeps existing storage callers intact and avoids a second mutable global selection. path is materialization metadata, not another Workspace identity.
Share read/write/manage across guards and stream revalidation; discover enterable Workspaces without exposing Bubble grants to routes.
Justified extension of the existing authorization owner. 'all' represents an administrator without eagerly enumerating every Workspace.
ResourceDirectory
Make spaceParent explicitly Workspace-qualified; add legacy lookup and open/default/create operations returning Workspace scopes.
Reuses the existing storage seam. Storage retains backend/path ownership; the identity provider determines selection and grants. No parallel resource repository is introduced.
Resolve a member's current Workspace and authorize changes to it. Local identity leaves these absent.
Two concrete provider behaviors justify the optional methods; a general provider framework would be unnecessary.
SpaceMoveAccess.canManage
Authorize each canonical child whose entry is moved, under the hierarchy lock and before destination creation or content writes.
Extends the existing move authorization port. The move plan already determines affected children; no second hierarchy or ACL model is needed.
Agent state-report adapter
Restore the creating request's Workspace around out-of-request driver state callbacks.
A small Huabu driver adapter keeps Workspace knowledge out of generic Agenetes. Local mode returns the original handle.
Request Workspace chooser
Bind switching to the authenticated caller through the existing request-state pattern.
Small transport adapter; avoids importing a process-wide identity singleton into Workspace routes.
WorkspacePreferences
Store the principal's current Workspace in huabu_identity.current_workspaces, beside Bubble's existing Postgres deployment.
A preference is Huabu state, so it belongs outside Bubble's account directory. The memory implementation supports deterministic tests.
Provisioning and storage coordination
Coalesce personal provisioning per principal; read SQL namespace per call; keep Disk indexes and World ids per Workspace; count operation leases per Workspace.
Required for concurrent Workspaces. Existing database-wide write coordination stays because it also protects registry and name allocation; finer locks are outside this PR.
The web change uses the capability to select the named setup flow and keeps the Workspace already chosen by the Server during boot. It reuses the existing Workspace-change lifecycle and request/response types.
Copilot follow-up fixes
The original six findings are fixed in 760efd6b3:
A content PUT checks manage when its title would change, before renaming or saving. An editor's ordinary content save still succeeds.
Parent changes require manage on the moved Space. A detachment-only check was insufficient: an editor could also attach someone else's Space beneath a Space they own and inherit management authority. Both paths are covered.
Migration propagates Space read failures and retains unresolved legacy grants for retry after an offline registered Disk folder returns. Unqualified targets grant no access under the new policy. The affected grant is removed only after its qualified replacement is saved.
Personal provisioning reloads enterable Workspaces before reusing a saved preference, preserving first-request coalescing without restoring revoked access.
Root creation and import require Workspace write. Child creation still checks the parent Space.
Accepted Bubble machine requests fill their scope from the process-active Workspace; the explicit RFS mount can replace that default.
Three additional findings are fixed in 5da9f2402:
Moving a selection into a new root requires Workspace write, including when asChild is omitted. Creating a child destination remains authorized through its source parent.
Moving canonical child entries requires management of every affected child under the hierarchy lock, before any destination is created/claimed or content is written. Ordinary note moves and permitted owner moves still work. Existing move error contracts are reused.
ACP state-report callbacks restore the Workspace captured when the driver handle was created. Their queued conversation-store writes and subsequent turn draining stay in that Workspace even though Gateway events arrive outside the request chain.
The migration behavior above supersedes the original description's claim that unresolved grants are dropped. Corresponding architecture/proposal text and API type comments are updated in the commit. The addressed Copilot threads are resolved.
Validation
Automated checks before the first follow-up commit (760efd6b3), 2026-10-30:
pnpm -r --if-present --no-bail run test: passed across the workspace, including 4,756 web tests. Final full Server rerun: 244 files passed, 1 skipped; 3,059 tests passed, 28 skipped, 2 todo.
pnpm run test:storage-backends: 17 files, 510 tests passed against real PostgreSQL and Azurite. Final PostgreSQL multi-user rerun after the hierarchy fix: 4 passed.
Shared multi-user route scenarios now run on Disk, SQLite, and PostgreSQL. They cover interleaved Workspace isolation, independent switching, forbidden targets, viewer creation/import denial, editor content saves, rename/reparent denial, and permitted owner operations. Disk additionally covers offline-folder migration and real read errors.
pnpm run typecheck, pnpm run lint (0 errors, 359 existing warnings), pnpm run format:check, and pnpm run build: passed.
i18n parity, agent-team skill tests/checks, and license-header check: passed.
New regression expectations were also run against the original PR implementation to confirm the failures they detect.
For the three additional fixes in 5da9f2402, only the new focused cases were run: 6 passed across Disk/SQLite move admission and real Disk conversation stores. They verify denial leaves source, destination, parents, and grants unchanged; permitted owner/child moves succeed; out-of-request state reports persist and a mounted turn completes in both scoped and local modes. Server typecheck, changed-file ESLint (0 errors, 7 existing warnings), formatting, and diff checks passed. The broad automated suites and PostgreSQL cases were not repeated after that commit.
Hands-on regressions with Bubble disabled
Used real running servers, real browser interactions, isolated on-disk folders/configuration, and the actual Disk/SQLite backends. HUABU_IDENTITY_PROVIDER was unset and /api/identity confirmed the local owner. No personal data, saved credentials, mocked UI, or store injection was used. Setup/edit/switch/restart checks ran on 760efd6b3; final move and RFS checks ran on 5da9f2402.
Scenario
Observed result
Fresh local Disk start
Folder setup appears; selecting a folder opens Spaces with the path switch link and ordinary local controls. No named/Bubble setup appears.
Everyday editing
Create a Space, rename its title, add/edit a note, reload, and restart the Server: title and note survive.
Folder switching
A → B shows a separate empty list; create a B Space; return to A restores only A's Spaces/content. Restarting and booting / restores A.
Startup-locked Disk (HUABU_WORKSPACE)
Header has a plain folder label, no switch link; /setup redirects to Spaces. Create/delete and import work.
Local SQLite with no folder
Boots directly into Spaces with a pathless Workspace; create/rename/edit/reload/restart preserves the title and note.
Basic Auth + local identity
Unauthenticated identity request returns 401; valid configured credentials return the local owner and open Spaces normally.
Hierarchy and moves
Create/open a child, change its parent to top level, move a note through “Move to another Space”, then “Collect into child Space”: all succeed and preserve content. An API request moving it to a new root also returns 200; opening the result in the UI shows the complete note.
Export/import
Export returns a valid ZIP; uploading it through Import Space in another folder restores the title and note. Existing export archives one Space folder, so a link to an unexported child reports a missing target in the other folder.
RFS compatibility
With the disposable Server's machine credential, both legacy and Workspace-qualified RFS download routes return 200 and the saved note. Without the credential RFS returns 401.
All 20 screenshots were visually inspected. Matched captures of the main frontend and PR frontend used the same fixture data/API at roughly 1440×1000 CSS pixels and 209%/291% canvas zoom. No PR-specific visual regression was confirmed. A contextual-toolbar container can clip at high zoom in both main and this PR; controls remained visible in the matched captures. The baseline comparison used main's frontend against the same current disposable backend, so it establishes rendering parity rather than a full main-backend comparison.
The original PR's live Bubble/Postgres browser checks on 0116f51f exercised member sign-in, the named Workspace list, and creating/switching a Workspace. This follow-up did not repeat live Bubble sign-in, Playwright acceptance, live model/provider agent turns, Desktop packaging, or narrow/mobile viewports. The ACP callback check uses a reporting driver at the external boundary with real mounted Agenetes and conversation storage. All disposable servers and browser tabs launched for this pass were stopped/closed.
Limits
Members still cannot run agents; feat: private agents and settings for every user #357 handles that. Settings and service credentials remain deployment-wide in this PR. Memory and Skills are shared within a Workspace.
One person's tabs share one current Workspace. Putting the Workspace in every browser URL is deferred.
A copied Disk folder can repeat Space ids. Grants are Workspace-qualified, but some existing in-memory maps still key by Space id. Members cannot adopt arbitrary folders; administrators must avoid registering copies with duplicate ids.
Unregistering leaves content on disk/in the database. Azure blob prefix behavior is unchanged.
Personal provisioning is coalesced within this Server process, not across multiple Server replicas.
A request or background job can now carry the Workspace it runs in
through AsyncLocalStorage. The Workspace getters, the SQL stores, the
Disk folder index and the SQL World id read that scope before the
process-active Workspace, and operation leases count per Workspace.
Local mode never opens a scope, so it behaves as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Bubble mode selects each admitted request's Workspace from the
caller's stored current Workspace, creating a personal one for a new
member. Roles become Workspace owner/editor/viewer plus Space owner,
with read/write/manage flowing down Workspace -> Space -> child Space
and huabu.admin on the system. Space grants name their Workspace
(<workspaceId>:<canvasId>); microsoft#237's grants are rewritten at startup.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Routes can declare spaceAccess: 'manage'; the Space delete, delete-tree
and rename routes now do, so a Workspace editor can change Spaces but
not remove them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…orkspaces
In Bubble mode the Workspace routes act for the caller alone: they list
the Workspaces the caller may enter, create one by name (on Disk, a
folder under the data directory) owned by its creator, switch only the
caller's current Workspace, and rename or unregister by huabu.manage.
Folder picking and paths stay with administrators. GET /api/workspace
reports namedWorkspaces so the web app can offer a name-based switcher.
A Disk and a Postgres suite run two members and an administrator
through the real routes at once.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An agent's reachback URL now names its Workspace
(/api/rfs/w/<workspaceId>/<canvasId>), and the RFS routes are also
served under that prefix in that Workspace. A run no longer follows its
starter's later switches or the process-active Workspace. The old
unscoped URL keeps working for runs started before the upgrade.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When the Server reports namedWorkspaces, boot keeps the Workspace the
Server chose for the caller instead of activating the first registered
one, the setup page lists the caller's Workspaces and creates one by
name instead of picking a folder, and the header calls it a Workspace.
Local mode renders as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fold PR A into the architecture docs: the Workspace/Space role model,
Workspace-qualified Space grants and their migration, per-person
current Workspaces, the Workspace routes for members, the per-call
storage scope and the Workspace-scoped RFS URL. The proposal records
what PR A built and what it deliberately left.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The reason will be displayed to describe this comment to others. Learn more.
🟡 Changes recommended
Unresolved authorization flaws allow revoked access and privilege escalation, while migration can permanently delete ownership grants when Workspaces are temporarily unreadable.
This PR extends Bubble identity toward #228 by giving each person an independent current Workspace on one server. It introduces request-scoped storage and authorization, while retaining the process-active Workspace model for local mode.
Changes:
Adds per-person Workspace selection, provisioning, management, and Workspace-qualified Space grants.
Makes storage namespaces and agent reachback Workspace-aware.
Adds named Workspace selection in the web app, integration tests, and architecture documentation.
File
Description
packages/shared/src/types/api/workspace.ts
Adds the named-Workspaces capability.
docs/README.md
Indexes the multi-user proposal.
docs/proposals/multi-user-service.md
Records the two-stage multi-user design.
docs/architecture/deployment-security.md
Documents Workspace selection and authorization.
docs/architecture/canvas-storage.md
Documents request-scoped storage behavior.
docs/architecture/agent-reachback.md
Documents Workspace-qualified RFS URLs.
apps/web/src/store/workspaceStore.ts
Preserves server selection and creates named Workspaces.
apps/web/src/store/workspaceStore.test.ts
Tests named Workspace initialization and creation.
The reason will be displayed to describe this comment to others. Learn more.
🟡 Changes recommended
Authorization bypasses allow unauthorized root creation and child-Space privilege escalation, while lost Workspace context can fail agent turns and prematurely close authorized streams.
[P2] Deleting the last Space shared with a member leaves its Bubble grant behind, so this branch still makes the containing Workspace enterable. Admission keeps selecting it and skips personal provisioning even though the member can no longer read any resource there. Check read access to the qualified Space target with a fresh discovery policy before adding its Workspace.
Bind stream revalidation to the final Workspace scope
[P2] A Bubble administrator currently in Workspace A can have an authorized streaming request under /api/rfs/w/B/<canvasId>/... closed at the first revalidation tick, about 30 seconds later. The timer was created in A before this override, so it checks B's recorded Space id against A and denies it when that Space is absent there. Bind revalidation to the final route scope, or record and restore the Workspace with each authorization.
Allow admins to unregister unavailable Disk Workspaces
[P2] Even an administrator receives 404 when unregistering an offline Disk Workspace because canManage() requires the target to resolve, while the Disk repository's get() returns null for an unavailable folder. This prevents the supported cleanup of deleted or unmounted registrations. Let administrators bypass this existence-dependent permission check while retaining the lease and active-Workspace checks.
Space deletion leaves its Bubble grant behind, so a member whose only resource was that Space still gets its Workspace in the enterable list. selectRequestWorkspace() then keeps selecting a Workspace where the member cannot read any Spaces or create a root, rather than provisioning a personal one. Verify that the qualified Space still exists before adding its Workspace.
If the ownership-grant write fails after creation succeeds, the personal Workspace remains registered but inaccessible to the member, and retries create more abandoned folders or database rows. Make creation and claiming recoverable through backend-aware compensation or a provisioning record that retries reuse. The same issue affects named creation in apps/server/src/modules/workspaces.route.ts:285–289; use the same recovery mechanism there.
Check managed mode before changing workspace preference
[P2] Preserve the managed-mode activation restriction. This Bubble branch returns before rejectReadOnlyMutation(), so a deployment locked with HUABU_WORKSPACE still accepts activation of another registered Workspace and persists that selection for subsequent requests, even though /api/workspace advertises canChangeWorkspace: false. Check managed mode before changing the caller's preference, as the other Workspace mutations do.
Workspace leases cover individual agent storage/tool operations, not the whole invocation, so this guard can pass while an external agent is waiting. An administrator can switch away from a non-process Workspace and unregister it during that wait, causing the agent's next Workspace-qualified RFS request to return 404. Hold a Workspace lease until each invocation settles, including background Task Runs.
Integrate main through d426983 while retaining the manage requirement
for Space reparenting and adopting the updated parent-route signature.
Restore each conversation observer's admitted Workspace for snapshot
refreshes triggered outside its request context, with a real HTTP/Disk
regression test. Record the device/filesystem authorization implications
for the stacked multi-user design in its existing proposal.
Validation: full workspace tests, PostgreSQL/Azurite storage contracts,
four focused Playwright tests, typecheck, lint, formatting, build, i18n,
Agent Team checks, license headers, and diff checks passed.
- Require Workspace write to detach a Space into a new root.
- Keep every Bubble member in the managed-mode Workspace: no personal
Workspace, no switching, and only that Workspace listed.
- Do not fail startup on an unmigratable legacy Space grant; migrate the
rest and retry at the next start.
- Recheck a long-lived stream's Space authorizations in the Workspace they
were granted in, not the caller's current one.
- Make admin folder routes act on the process-active Workspace, and move
the admin with a folder they pick.
- Discard a newly created Workspace if recording its owner fails.
- Keep Disk dir indexes and the CanvasStore cache per Workspace instead of
clearing all of them when requests alternate Workspaces.
- Reuse the call's own Workspace when resolving Space parents.
- Close the Bubble store and preferences pool on every open failure.
- Run canvas update and agent state listeners in their subscriber's
context, replacing the per-call-site patch.
- Skip the Workspace lookup for credential-free Skill bootstrap requests.
- Share the lease key format and the "may enter" check; drop dead code.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The reason will be displayed to describe this comment to others. Learn more.
🟡 Changes recommended
A Bubble administrator’s folder change destroys watchers in other users’ Workspaces, leaving their open streams without filesystem updates until they reconnect.
Keep request-scoped external note sessions alive when the process Workspace
changes, while resetting process-bound sessions and closing all on shutdown.
Read resource permissions after provisioning and refresh them after a claim,
including concurrent first-request writes.
Prepare inactive Disk Workspaces on their first scoped open with the isolated
worker, coalescing preparation and allowing retry after failure. Give source
workers their TypeScript loader. Filter Workspace listings by allowed IDs
before reading manifests, with consistent SQL ordering when timestamps tie.
Add regression coverage over real files, child processes, Bubble admission,
and the shared Disk/SQLite/PostgreSQL Workspace contract.
Validation: 3153 Server tests and 516 remote storage tests passed. Other
package tests, lint, formatting, repository typecheck/build, i18n parity,
Agent Team skill checks, and license headers passed.
The reason will be displayed to describe this comment to others. Learn more.
🔵 Needs a closer look
Human assessment is needed for interacting Workspace scope and lifecycle risks, including activation targeting the wrong Workspace and removal disrupting an active agent invocation.
…idy edges
- An administrator with no current Workspace lands only in the Server's
own one. On a fresh Disk install that left them in whichever Workspace
was registered first, often a member's personal one, where whatever
they made was that member's to see. The web client no longer
auto-opens the first registered Workspace when Workspaces are personal;
the person chooses instead.
- An /api path no route serves is a JSON 404 for every method, instead
of a 503 or the application's index.html. The SPA shell moves to
modules/web-shell.ts, and the Workspace-required guard skips unmatched
routes and static shell files.
- GET /api/workspace reports capabilities.canCreateSpaces for callers
whose access is per Workspace. The Space list and App menu hide New
Space and Import Space when the server would refuse them. Local mode
omits the field, and the client treats absence as allowed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Note: a member who gets a shared Space before their first sign-in never gets a personal Workspace
I found this during end-to-end testing with real OIDC logins (Dex behind the auth0 driver, Disk backend). It is current behavior, not a regression, and f0c47db does not change it.
Repro
Bob, a member, shares one of his Spaces with Carol (huabu.space.owner on <bobWorkspace>:<canvasId>).
Carol is admitted as a member and signs in for the first time.
Carol lands in Bob's Workspace, where she sees only the shared Space. No personal Workspace is created for her.
Why:selectRequestWorkspace (modules/identity/workspace-selection.ts) puts a caller in the first Workspace they may enter. A Space grant makes its Workspace enterable, so provisionPersonalWorkspace never runs for her.
Effect: Carol can't create top-level Spaces where she lands. As of f0c47db, New Space and Import Space are hidden there (canCreateSpaces: false). She can still make her own Workspace by name from the Workspace switcher, but nothing in the app suggests it.
Possible directions, if we want to change it:
Always provision a personal Workspace on a member's first admission, whatever they have been shared.
Or prefer a Workspace the caller owns or edits over one where they hold only Space grants.
The reason will be displayed to describe this comment to others. Learn more.
🔵 Needs a closer look
Human assessment is needed because unauthorized Workspace requests can trigger preparation or creation before denial, alongside recovery failures and stale-grant access risks.
Rework microsoft#357 on main after microsoft#356 merged. Preserve Workspace permissions and the current Settings navigation, isolate member OCR, OAuth and model discovery state, prevent external thread control, and repair refresh, logout and save recovery. Add server and browser regressions for the resulting behavior.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
First of two stacked PRs toward #228, following #237. The shared plan is in
docs/proposals/multi-user-service.md; #357 builds on this PR to enable member agents and personal settings.After #237, several people could sign in but shared the Server's one active Workspace. This PR gives each admitted person a current Workspace of their own. First sign-in provisions a personal Workspace when needed; members can create and switch Workspaces without moving another person. Local mode keeps its process-active Workspace.
API contracts and types
The public schema change is one optional capability field. The larger changes are to authorization and the meaning of existing endpoints; they do not require new request/response variants.
WorkspaceCapabilities/workspaceCapabilitiesSchemanamedWorkspaces?: boolean. Bubble responses advertisetrue.WorkspaceInfo,WorkspaceDescriptoractivedescribe the caller's selection. Members receivepath: null; administrators may receive a Disk path.WorkspaceCreateRequest{ path?: string, name?: string }. Bubble members create with{ name }; local Disk adoption and administrator folder operations retain{ path, name? }. Local SQL creation already accepts{ name }./api/workspacesand/:workspaceId/activatemanage; unauthorized targets return 404.read,write,managefrom systemhuabu.admin. Space grant ids become<workspaceId>:<canvasId>.IdentityResponsehas no new fields./api/rfs/w/<workspaceId>/<canvasId>/…; retain/api/rfs/<canvasId>/….read,write, andmanageflow down Workspace → root Space → child Space. Editors can edit content and create Spaces; managers can rename, delete, share, and change a Space's parent. Creating a child authorizes its parent; creating/importing a root requires Workspace write. Creating a Space claims ownership of that new Space.Server interfaces and design assessment
WorkspaceScope { handle, path }andAsyncLocalStoragepathis materialization metadata, not another Workspace identity.AccessLevel,ResourceAccess.workspaces(): Promise<readonly string[] | 'all'>read/write/manageacross guards and stream revalidation; discover enterable Workspaces without exposing Bubble grants to routes.'all'represents an administrator without eagerly enumerating every Workspace.ResourceDirectoryspaceParentexplicitly Workspace-qualified; add legacy lookup and open/default/create operations returning Workspace scopes.IdentityService.selectWorkspace?/chooseWorkspace?SpaceMoveAccess.canManageWorkspacePreferenceshuabu_identity.current_workspaces, beside Bubble's existing Postgres deployment.The web change uses the capability to select the named setup flow and keeps the Workspace already chosen by the Server during boot. It reuses the existing Workspace-change lifecycle and request/response types.
Copilot follow-up fixes
The original six findings are fixed in
760efd6b3:PUTchecksmanagewhen its title would change, before renaming or saving. An editor's ordinary content save still succeeds.manageon the moved Space. A detachment-only check was insufficient: an editor could also attach someone else's Space beneath a Space they own and inherit management authority. Both paths are covered.Three additional findings are fixed in
5da9f2402:asChildis omitted. Creating a child destination remains authorized through its source parent.The migration behavior above supersedes the original description's claim that unresolved grants are dropped. Corresponding architecture/proposal text and API type comments are updated in the commit. The addressed Copilot threads are resolved.
Validation
Automated checks before the first follow-up commit (
760efd6b3), 2026-10-30:pnpm -r --if-present --no-bail run test: passed across the workspace, including 4,756 web tests. Final full Server rerun: 244 files passed, 1 skipped; 3,059 tests passed, 28 skipped, 2 todo.pnpm run test:storage-backends: 17 files, 510 tests passed against real PostgreSQL and Azurite. Final PostgreSQL multi-user rerun after the hierarchy fix: 4 passed.pnpm run typecheck,pnpm run lint(0 errors, 359 existing warnings),pnpm run format:check, andpnpm run build: passed.For the three additional fixes in
5da9f2402, only the new focused cases were run: 6 passed across Disk/SQLite move admission and real Disk conversation stores. They verify denial leaves source, destination, parents, and grants unchanged; permitted owner/child moves succeed; out-of-request state reports persist and a mounted turn completes in both scoped and local modes. Server typecheck, changed-file ESLint (0 errors, 7 existing warnings), formatting, and diff checks passed. The broad automated suites and PostgreSQL cases were not repeated after that commit.Hands-on regressions with Bubble disabled
Used real running servers, real browser interactions, isolated on-disk folders/configuration, and the actual Disk/SQLite backends.
HUABU_IDENTITY_PROVIDERwas unset and/api/identityconfirmed the local owner. No personal data, saved credentials, mocked UI, or store injection was used. Setup/edit/switch/restart checks ran on760efd6b3; final move and RFS checks ran on5da9f2402./restores A.HUABU_WORKSPACE)/setupredirects to Spaces. Create/delete and import work.All 20 screenshots were visually inspected. Matched captures of the main frontend and PR frontend used the same fixture data/API at roughly 1440×1000 CSS pixels and 209%/291% canvas zoom. No PR-specific visual regression was confirmed. A contextual-toolbar container can clip at high zoom in both main and this PR; controls remained visible in the matched captures. The baseline comparison used main's frontend against the same current disposable backend, so it establishes rendering parity rather than a full main-backend comparison.
The original PR's live Bubble/Postgres browser checks on
0116f51fexercised member sign-in, the named Workspace list, and creating/switching a Workspace. This follow-up did not repeat live Bubble sign-in, Playwright acceptance, live model/provider agent turns, Desktop packaging, or narrow/mobile viewports. The ACP callback check uses a reporting driver at the external boundary with real mounted Agenetes and conversation storage. All disposable servers and browser tabs launched for this pass were stopped/closed.Limits