Phase 3: link projects/chats/reviews/workflows to workspaces

↗ view on GitHub · Claude · 2026-05-15 · 171d88d4

Stamps every workspace-scoped resource with its workspace_id and
teaches the create paths to populate it, the list paths (projects
only for now) to filter by it.

Schema (migration 0014_workspace_links.sql):
- ALTER ... ADD COLUMN workspace_id uuid REFERENCES workspaces(id)
  ON DELETE CASCADE on projects, chats, tabular_reviews, workflows.
- Backfill UPDATE per table: projects use the owner's Personal
  workspace; chats and tabular_reviews inherit the parent project's
  workspace when set, else Personal; workflows go to the author's
  Personal (or NULL when user_id IS NULL for system workflows).
- ALTER COLUMN ... SET NOT NULL on projects, chats, tabular_reviews.
  workflows stays nullable because system workflows have no owner.
- Helper function personal_workspace_id(uuid) for the backfill UPDATE
  and any future helpers. Marked STABLE.
- Indexes on workspace_id for the typical list filter; workflows
  uses a partial index keyed on the not-null subset.

Helpers (lib/workspaces.ts):
- getPersonalWorkspaceId, resolveWorkspaceForUser (verifies caller
  is a member; falls back to Personal when null), and
  listAccessibleWorkspaceIds for future scope-by-membership queries.

Routes:
- POST /projects reads optional workspace_id and uses
  resolveWorkspaceForUser. 404 on non-member.
- POST /workflows same shape.
- POST /tabular-review prefers the parent project's workspace when
  project_id is set; otherwise resolveWorkspaceForUser.
- POST /chat/create and POST /chat (streaming) likewise inherit when
  the chat is project-scoped.
- POST /projects/:id/chat (projectChat.ts) always inherits the
  project's workspace; refuses if the project somehow has no
  workspace.
- GET /projects accepts ?workspace_id= and scopes both branches
  (own + shared-by-email). Unknown ids return [] without error to
  avoid leaking existence.

End-to-end verified against a fresh Postgres 16:
- Project without workspace_id stamps Personal.
- Project with explicit workspace_id stamps that workspace.
- Project with non-member workspace_id returns 404.
- GET /projects with no filter returns both; filtered returns the
  right subset; unknown returns [].
- Chat under project inherits the project's workspace; standalone
  chat goes to Personal.
- Workflow respects workspace_id parameter.
- Tabular review standalone goes to Personal; under a project
  inherits the project's workspace.
- DB shows every row in projects, chats, tabular_reviews, and
  workflows has workspace_id populated.

Next sub-phase: replace projects.shared_with JSONB with a proper
project_members table joined on user_id, and add a frontend
workspace switcher.
Repository cpatpa/PIP
Author Claude <noreply@anthropic.com>
Authored
Parents 61dda046
Stats 8 files changed , +400 , -42
Part of Phase 3 - workspaces (team-scope layer)

Capture this commit into my fork

Download a Markdown prompt that tells Claude how to port this exact commit into your working tree. Run it via claude -p < capture-commit-171d88d4.md from inside the repo you want the change in.

⬇ Download capture-commit-171d88d4.md