Skip to content

feat(workspaces): every row belongs to a workspace, every account has one (D5) - #492

Merged
huyplb merged 1 commit into
mainfrom
feat/workspaces-foundation
Oct 10, 2026
Merged

huyplb merged 1 commit into
mainfrom
feat/workspaces-foundation

Conversation

@huyplb

@huyplb huyplb commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

D5 of the workspaces plan. Stacked on #491 (D1); retarget to main once that merges.

No visible change: every account gets a personal workspace and its role there mirrors the account role (admin = owner), so effective permissions are exactly what they were. Deviation from the plan: the plan made new accounts owners of their own workspace; mirroring the account role keeps this PR permission-neutral, and D3 decides how inviting into one's own workspace works.

What changes

  • Migrations 28–30: workspaces, workspace_members, workspace_invites; workspace_id on the six workspace-owned tables; unique keys re-keyed from author to workspace (secret names, cloud credential names, Lokee database identity) and one personal workspace per account.
  • Scope instead of user id: the seven services that own those rows take a WorkspaceScope (platform/http/scope.ts). authGuard resolves X-Fox-Workspace → last used → personal; a workspace the account is not in answers 404. /api/auth/me returns the workspace and its permissions.
  • Permission split: INSTALL_PERMISSIONS (code cells, workflows, Git, user management, workspace.create) come only from the account role; the rest only from the workspace role. New keys workspace.members / workspace.settings (owner, plus a one-time grant to roles that deploy migrations) and workspace.create (admin).
  • Upgrade: backfillWorkspaces runs at every start: personal workspace for each account without one, and rows without a workspace move to their author's. Verified on a dev DB with 949 connections: every account kept exactly its own.
  • Route deps that were typed any are now typed. Several were, which would have let a missed userId → scope change compile and fail at runtime as "not found".

Tests

  • workspace.service.test.ts (17): personal workspaces + role sync, request resolution (404 not 403, last-used, archived), permissions per role, backfill, isolation for every scoped service (same author, two workspaces), unique keys.
  • effectivePermissions tests; two live HTTP checks in the contract test (unknown workspace → 404; /me fallback).
  • A/B-checked: scoping by author, no backfill, accepting any workspace id, a plain permission union, no role sync and no migration 30 each fail their test.
  • Full suite 5948 passed; e2e smoke, SQLite SQL editor and admin-policy pass against the dev server.

Review

Fixed: per-user unique indexes (migration 30), possible duplicate personal workspaces, the CLI following the browser's last workspace. Left: run-history pruning is now per workspace; a few extra metadata queries per request.

🤖 Generated with Claude Code


Note

High Risk
Large metadata migration and auth/scoping changes affect how all workspace-owned data is read and written; incorrect backfill or scope resolution could leak or hide data across workspaces.

Overview
Introduces workspaces as the tenancy boundary for connections, secrets, migration history, Lokee schema history, and related metadata. Every account gets a personal workspace (with role mirrored from the account role so effective permissions stay the same for this release), and existing rows are backfilled onto the author’s workspace at startup.

Server: DB migrations add workspaces / members / invites, workspace_id on scoped tables, and workspace-scoped unique keys (secret names, cloud cred names, Lokee fingerprints). Protected APIs resolve the active workspace from X-Fox-Workspace, last-used preference, or personal default; workspaces the user cannot access return 404. /api/auth/me returns the active workspace and workspace-filtered permissions (install-level vs workspace-level split). Connection, secrets, history, Lokee, data-migrate, file import, git, and access routes now pass WorkspaceScope instead of userId into stores.

CLI/TUI: getContext() always uses the install owner’s personal workspace so the CLI does not follow the browser’s last-selected workspace; commands and tests updated accordingly.

Reviewed by Cursor Bugbot for commit fd40ee9. Bugbot is set up for automated code reviews on this repo. Configure here.

@cursor

cursor Bot commented Oct 9, 2026

Copy link
Copy Markdown
Contributor

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: ce2b8a9b-f689-49ac-be3f-18f39577e540)

… one

D5 of docs/plans/2026-10-09-workspaces-and-admin-policy.md, with no
visible change: each account gets a personal workspace, and its role
there mirrors the account role, so nobody can do anything new.

- Migrations 28 (workspaces, members, invites), 29 (workspace_id on
  connections, secrets, cloud credentials, Lokee databases, migration
  and data-migrate runs) and 30 (unique keys per workspace, one
  personal workspace per account).
- Services take a WorkspaceScope instead of a user id; authGuard
  resolves X-Fox-Workspace (unknown or not a member: 404).
- Permissions = install keys from the account role + workspace keys
  from the workspace role; code cells, workflows, Git and user
  management stay install keys.
- backfillWorkspaces runs at every start and moves rows without a
  workspace into their author's.
- Route deps that were typed any are typed, so the compiler finds every
  call site.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@huyplb
huyplb force-pushed the feat/workspaces-foundation branch from 94b7577 to fd40ee9 Compare October 10, 2026 04:23
@cursor

cursor Bot commented Oct 10, 2026

Copy link
Copy Markdown
Contributor

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: e5e904ef-1e4d-4535-abb1-2487216e79d4)

async (req: AppRequest, res: FastifyReply) => {
try {
const userId = (req as AuthedRequest).userId!;
const scope = scopeOf(req as AuthedRequest)!;
async (req: AppRequest, res: FastifyReply) => {
try {
const userId = (req as AuthedRequest).userId!;
const scope = scopeOf(req as AuthedRequest)!;
requirePermissions('editor.access'),
async (req: AppRequest, res: FastifyReply) => {
const userId = (req as AuthedRequest).userId!;
const scope = scopeOf(req as AuthedRequest)!;
@huyplb
huyplb merged commit 034e4b0 into main Oct 10, 2026
12 checks passed
@huyplb
huyplb deleted the feat/workspaces-foundation branch October 10, 2026 04:26
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.

1 participant