Skip to content

feat: request-scoped Workspaces — each person works in their own - #356

Merged
cxxxxxn (cxxxxxn) merged 15 commits into
microsoft:mainfrom
ultmaster:feat/228-request-scoped-workspaces
Nov 3, 2026
Merged

cxxxxxn (cxxxxxn) merged 15 commits into
microsoft:mainfrom
ultmaster:feat/228-request-scoped-workspaces

Conversation

@ultmaster

@ultmaster Yuge Zhang (ultmaster) commented Oct 28, 2026 •

Copy link
Copy Markdown
Collaborator

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.

Public contract Change Rationale and compatibility
WorkspaceCapabilities / workspaceCapabilitiesSchema Add namedWorkspaces?: boolean. Bubble responses advertise true. 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.
Agent reachback Add /api/rfs/w/<workspaceId>/<canvasId>/…; retain /api/rfs/<canvasId>/…. 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.
AccessLevel, ResourceAccess.workspaces(): Promise<readonly string[] | 'all'> 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.
IdentityService.selectWorkspace? / chooseWorkspace? 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.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
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>
Copilot AI balanced review requested due to automatic review settings October 28, 2026 09:48

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Unresolved authorization flaws allow revoked access and privilege escalation, while migration can permanently delete ownership grants when Workspaces are temporarily unreadable.

6 open issues
What changed in this PR

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.
apps/​web/​src/​pages/​WorkspaceSetupPage.tsx Adds named Workspace selection and creation UI.
apps/​web/​src/​pages/​CanvasListPage.tsx Displays named Workspace labels.
apps/​web/​src/​i18n/​resources/​zh-CN/​common.json Adds Chinese Workspace UI strings.
apps/​web/​src/​i18n/​resources/​en/​common.json Adds English Workspace UI strings.
apps/​web/​src/​api/​workspace.ts Adds the named Workspace creation client.
apps/​server/​src/​modules/​workspaces.route.ts Adds caller-specific Workspace management.
apps/​server/​src/​modules/​workspace/​scoped-routes.ts Binds routes to an explicit Workspace.
apps/​server/​src/​modules/​workspace/​scoped-routes.test.ts Tests scoped routes and reachback URLs.
apps/​server/​src/​modules/​workspace/​context.ts Introduces asynchronous Workspace context.
apps/​server/​src/​modules/​workspace.ts Makes getters scope-aware and leases per Workspace.
apps/​server/​src/​modules/​workspace.route.ts Reports personal capabilities and hides member paths.
apps/​server/​src/​modules/​workspace.route.test.ts Updates the identity test fixture.
apps/​server/​src/​modules/​workspace-activation.ts Creates named Workspaces across backends.
apps/​server/​src/​modules/​storage/​storage.ts Opens scoped Workspaces and caches their World IDs.
apps/​server/​src/​modules/​storage/​index.ts Exports scoped Workspace opening.
apps/​server/​src/​modules/​storage/​backends/​sqlite/​database.ts Resolves SQLite namespaces from request scope.
apps/​server/​src/​modules/​storage/​backends/​postgres/​database.ts Resolves PostgreSQL namespaces from request scope.
apps/​server/​src/​modules/​storage/​backends/​disk/​canvas-dirs.ts Keeps directory indexes per Workspace.
apps/​server/​src/​modules/​security/​space-access.ts Supports manage-level Space checks.
apps/​server/​src/​modules/​security/​space-access.test.ts Tests manage versus write permissions.
apps/​server/​src/​modules/​remote_fs/​public-skill.ts Recognizes scoped public bootstrap URLs.
apps/​server/​src/​modules/​identity/​workspace-selection.ts Selects and provisions personal Workspaces.
apps/​server/​src/​modules/​identity/​service.ts Extends Workspace and permission interfaces.
apps/​server/​src/​modules/​identity/​resources.ts Resolves resources across Workspaces.
apps/​server/​src/​modules/​identity/​request.ts Stores request-level Workspace selection callbacks.
apps/​server/​src/​modules/​identity/​preferences.ts Persists each principal’s current Workspace.
apps/​server/​src/​modules/​identity/​multi-user.test.ts Runs multi-user scenarios on Disk.
apps/​server/​src/​modules/​identity/​multi-user.scenarios.ts Tests isolation, switching, and viewer restrictions.
apps/​server/​src/​modules/​identity/​multi-user.remote.test.ts Runs multi-user scenarios on PostgreSQL.
apps/​server/​src/​modules/​identity/​index.ts Wires preferences and Workspace selection.
apps/​server/​src/​modules/​identity/​http.ts Opens and fills request Workspace scopes.
apps/​server/​src/​modules/​identity/​bubble.ts Defines roles, qualified grants, and migration.
apps/​server/​src/​modules/​identity/​bubble.test.ts Tests provisioning, roles, sharing, and migration.
apps/​server/​src/​modules/​canvas/​space-hierarchy.test.ts Updates the ownership test fixture.
apps/​server/​src/​modules/​canvas/​space-hierarchy.route.ts Requires manage for subtree deletion.
apps/​server/​src/​modules/​canvas/​space-card.route.ts Requires manage for the title endpoint.
apps/​server/​src/​modules/​canvas/​preprocess-access.route.test.ts Updates the preprocessing identity fixture.
apps/​server/​src/​modules/​canvas/​member-spaces.route.test.ts Updates member access fixtures.
apps/​server/​src/​modules/​canvas/​canvas.route.ts Requires manage for Space deletion.
apps/​server/​src/​modules/​artifact/​send-blob.test.ts Updates artifact access fixtures.
apps/​server/​src/​modules/​agent/​memory/​op-counter-hook.test.ts Updates the memory-hook identity fixture.
apps/​server/​src/​modules/​agent/​acp/​reachback-env.ts Includes Workspace identity in reachback URLs.
apps/​server/​src/​app.ts Mounts Workspace-qualified RFS routes.

Review effort: Balanced

Copilot AI balanced review requested due to automatic review settings October 30, 2026 03:13

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 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.

3 open issues
3 previously missed issues

In code that hasn't changed since last review

Medium severity Recheck Space access before Workspace admission

apps/​server/​src/​modules/​identity/​bubble.ts:321

[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.

Medium severity Bind stream revalidation to the final Workspace scope

apps/​server/​src/​modules/​workspace/​scoped-routes.ts:25

[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.

Medium severity Allow admins to unregister unavailable Disk Workspaces

apps/​server/​src/​modules/​workspaces.route.ts:493

[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.

6 resolved since last review

Review effort: Balanced

Copilot AI balanced review requested due to automatic review settings October 30, 2026 03:54

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

Unresolved issues allow managed-mode workspace switching, keep workspaces selectable through deleted-Space grants, permit workspace removal during active agent invocations, and leave inaccessible workspaces behind when ownership-grant writes fail.

No open issues 🎉

4 previously missed issues

In code that hasn't changed since last review

Medium severity Ignore deleted Spaces when deriving enterable Workspaces

apps/​server/​src/​modules/​identity/​bubble.ts:321

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.

Medium severity Make Workspace creation and claiming retry-safe

apps/​server/​src/​modules/​identity/​workspace-selection.ts:106

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.

Medium severity Check managed mode before changing workspace preference

apps/​server/​src/​modules/​workspaces.route.ts:399

[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.

Medium severity Hold Workspace lease until invocation settles

apps/​server/​src/​modules/​workspaces.route.ts:500

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.

3 resolved since last review

Review effort: Balanced

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.
Copilot AI balanced review requested due to automatic review settings November 2, 2026 10:38

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Unresolved Workspace-scoping and provisioning defects can skip migrations, report the wrong activated Workspace, deny authorized writes, and leave inaccessible orphan Workspaces.

6 open issues

Review effort: Balanced

- 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>
Copilot AI balanced review requested due to automatic review settings November 3, 2026 01:32

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

A Bubble administrator’s folder change destroys watchers in other users’ Workspaces, leaving their open streams without filesystem updates until they reconnect.

6 open issues
1 resolved since last review

Review effort: Balanced

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.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 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.

No open issues 🎉

6 resolved since last review

Review effort: Balanced

…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>
Copilot AI balanced review requested due to automatic review settings November 3, 2026 05:33
@ultmaster

Copy link
Copy Markdown
Collaborator Author

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

  1. Bob, a member, shares one of his Spaces with Carol (huabu.space.owner on <bobWorkspace>:<canvasId>).
  2. Carol is admitted as a member and signs in for the first time.
  3. 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.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 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.

No open issues 🎉

Review effort: Balanced

@cxxxxxn
cxxxxxn (cxxxxxn) merged commit 14822a9 into microsoft:main Nov 3, 2026
4 checks passed
Yuge Zhang (ultmaster) added a commit to ultmaster/Huabu that referenced this pull request Nov 3, 2026
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants