Skip to content

feat(cloud): workspace model + role-based access (C1)#365

Merged
OBenner merged 5 commits into
developfrom
claude/cloud-workspaces-c1
Jun 25, 2026
Merged

feat(cloud): workspace model + role-based access (C1)#365
OBenner merged 5 commits into
developfrom
claude/cloud-workspaces-c1

Conversation

@OBenner

@OBenner OBenner commented Jun 24, 2026

Copy link
Copy Markdown
Owner

First slice of Track C (cloud multitenancy — see docs/strategy/roadmap.md).

  • Models (api/models/workspace.py): Workspace (tenant boundary, owner) + WorkspaceUser (membership with owner/editor/viewer role, unique per (workspace, user)).
  • Permissions (core/permissions.py): hierarchical WorkspaceRole, check_workspace_access (pure, unit-tested), and a require_workspace_access(required_role) FastAPI dependency factory for workspace-scoped routes.
  • Migration 003_create_workspaces.py creates both tables (FKs + unique constraint + indexes); conftest registers the models for create_all.
  • Tests (tests/test_workspaces.py): relationships, unique constraint, role hierarchy, access checks — 4/4 pass; ruff clean.

Single-user mode reuses this with one auto-created "Personal" workspace; the bootstrap + route wiring (and per-workspace isolation) are the next slices (C2+).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added multi-workspace support with workspace membership and role-based permissions (owner/editor/viewer).
    • Introduced workspace-scoped access checks and a FastAPI authorization dependency that blocks requests with insufficient permissions.
  • Bug Fixes
    • Enforced unique membership per user within a workspace.
    • Added explicit behavior for workspace owners when no membership row exists, while denying access to non-members.
  • Tests
    • Added database-backed tests covering relationships, uniqueness, role hierarchy, access control, and rejection of invalid roles.

First slice of Track C (cloud multitenancy). Adds Workspace + WorkspaceUser models (owner/editor/viewer membership, unique per user/workspace), core/permissions.py with a role hierarchy, check_workspace_access (pure, unit-tested), and a require_workspace_access FastAPI dependency factory for workspace-scoped routes. Migration 003 creates both tables; conftest registers the models. Tests cover relationships, the unique constraint, the role hierarchy, and access checks. (Single-user auto 'Personal' workspace bootstrap + route wiring are C2.)

Verified: web-backend pytest 4/4 pass; ruff clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds workspace multitenancy support with new ORM models, a migration for the underlying tables, hierarchical workspace access helpers, and tests covering relationships, constraints, and authorization behavior.

Changes

Workspace Multitenancy

Layer / File(s) Summary
ORM models
apps/web-backend/api/models/workspace.py
Workspace and WorkspaceUser define ownership, membership links, cascading relationships, timestamps, uniqueness, and allowed membership roles.
Role checks and dependency
apps/web-backend/core/permissions.py
WorkspaceRole, role comparison, effective role lookup, access checks, and the FastAPI dependency for workspace-scoped authorization are added.
Database migration
apps/web-backend/migrations/versions/003_create_workspaces.py
Creates the workspaces and workspace_users tables with keys, indexes, defaults, constraints, and downgrade cleanup.
Metadata registration and behavior tests
apps/web-backend/tests/conftest.py, apps/web-backend/tests/test_workspaces.py
Test metadata registration is updated and new tests cover memberships, uniqueness, role hierarchy, access checks, owner access, and invalid-role rejection.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

Poem

🐇 I hopped through workspaces, bright and new,
With roles to rank and members too.
Owners, editors, viewers play,
And 403 hops in when access says nay.
The burrow’s tables now stand tall and neat,
With tests to keep the carrots sweet. 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.86% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: workspace models plus role-based access for cloud multitenancy.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/cloud-workspaces-c1

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Comment thread apps/web-backend/tests/conftest.py Fixed
…068)

Sonar flagged the literal placeholder assigned to hashed_password as a hard-coded credential (python:S2068), tripping the security-rating gate. Generate the throwaway hash with secrets.token_hex instead — same intent (no real auth in these tests), no literal.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/web-backend/core/permissions.py`:
- Around line 42-70: The workspace authorization helpers currently ignore
Workspace.owner_id and only derive access from WorkspaceUser membership, so
ownership can drift from the actual permission check. Update
user_role_in_workspace and check_workspace_access to treat the workspace owner
as having owner-level access when Workspace.owner_id matches the user, likely by
querying Workspace alongside WorkspaceUser and mapping owners to the highest
WorkspaceRole before applying role_satisfies. Ensure the logic stays consistent
with existing role handling and continues to fall back to membership-based roles
for non-owners.

In `@apps/web-backend/migrations/versions/003_create_workspaces.py`:
- Around line 37-51: The workspace owner relationship in the migration is using
a cascading foreign key that can delete the whole workspace when a user is
removed. Update the foreign key defined in the workspace table migration so the
owner reference in the create_workspaces migration no longer uses
ondelete="CASCADE"; keep the constraint but make deletions non-cascading and
handle owner transfer or explicit workspace deletion in application logic
instead. Use the owner_id foreign key and the workspace table definition to
locate the change.
- Around line 62-76: The workspace membership table currently accepts any string
in the role column, which allows invalid values to persist even though
WorkspaceRole in core.permissions is a closed set. Update the migration that
defines the workspace membership table to enforce the allowed role values at the
database level, using the existing role column in the membership table
definition and a schema constraint or equivalent DB enum check so only valid
WorkspaceRole entries can be stored.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: c3fb9e4a-c62d-4eb6-9a0a-13f09252b1df

📥 Commits

Reviewing files that changed from the base of the PR and between 59bc436 and b0b02f0.

📒 Files selected for processing (5)
  • apps/web-backend/api/models/workspace.py
  • apps/web-backend/core/permissions.py
  • apps/web-backend/migrations/versions/003_create_workspaces.py
  • apps/web-backend/tests/conftest.py
  • apps/web-backend/tests/test_workspaces.py

Comment thread apps/web-backend/core/permissions.py
Comment thread apps/web-backend/migrations/versions/003_create_workspaces.py Outdated
Comment thread apps/web-backend/migrations/versions/003_create_workspaces.py
OBenner and others added 2 commits June 24, 2026 22:40
…1 review)

Address review on the workspace foundation:
- permissions: user_role_in_workspace now treats Workspace.owner_id as owner-level access, even without a WorkspaceUser row — so the single-user 'Personal' workspace owner actually satisfies every check (was previously membership-only, contradicting the docstring).
- schema (model + migration 003): owner_id FK is RESTRICT, not CASCADE, so deleting a user no longer silently destroys their whole workspace; ownership transfer/deletion is explicit app logic (Track C).
- schema (model + migration 003): workspace_users.role gets a CHECK constraint mirroring the WorkspaceRole closed set, so invalid roles cannot persist.
Added tests: owner-without-membership access, and DB rejection of an invalid role (IntegrityError). 6/6 pass; ruff clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…d-import)

conftest imports Workspace/WorkspaceUser purely to register them with Base.metadata. CodeQL's py/unused-import ignores ruff's # noqa, so it flagged them as unused — unlike User/GitRepository, which were already in __all__. Add the two models to __all__ (re-export = used), matching the existing pattern.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/web-backend/tests/test_workspaces.py`:
- Around line 109-120: Tighten
test_workspace_owner_has_owner_access_without_membership so it explicitly
verifies the workspace owner does not have an owner membership row before
asserting access. Use the existing test_db setup plus the Workspace and
user_role_in_workspace/check_workspace_access flow, and add an assertion against
the membership source used by workspace role resolution to confirm the
owner-level access is coming from Workspace.owner_id rather than an auto-created
membership.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: db3deddd-49fb-4cfb-bda2-5f12fcc4ed45

📥 Commits

Reviewing files that changed from the base of the PR and between b0b02f0 and 322cfff.

📒 Files selected for processing (5)
  • apps/web-backend/api/models/workspace.py
  • apps/web-backend/core/permissions.py
  • apps/web-backend/migrations/versions/003_create_workspaces.py
  • apps/web-backend/tests/conftest.py
  • apps/web-backend/tests/test_workspaces.py

Comment thread apps/web-backend/tests/test_workspaces.py
@OBenner OBenner merged commit 28bac44 into develop Jun 25, 2026
19 checks passed
@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants