Skip to content

fix: persist tenant identity and preserve Personal workspace access - #447

Merged
duyetbot merged 2 commits into
mainfrom
fix/org-identity-sync
Sep 17, 2026
Merged

duyetbot merged 2 commits into
mainfrom
fix/org-identity-sync

Conversation

@duyetbot

@duyetbot duyetbot commented Sep 17, 2026

Copy link
Copy Markdown
Collaborator

Summary

Closes #389.

  • Persist verified Clerk user/organization bindings to stable internal tenant IDs; resolve once before dashboard reads and writes.
  • Migrate recognized existing personal and organization identities in place. Never claim shared default or unknown legacy data automatically; document operator audit and verified recovery.
  • Normalize flat and nested Clerk organization claims, rejecting malformed/conflicting identity claims.
  • Keep Personal explicitly selectable without automatic membership activation; switching workspaces never transfers personal data.
  • Add migration, concurrency, tenant-isolation, compatibility-drift, same-slug, and rendered-switcher regressions.

Verification

  • API: 525 tests passed (33 files).
  • API TypeScript and changed-file Biome passed.
  • Dashboard: 12 tests passed; production build passed with placeholder Clerk key.
  • SDK examples passed; TS SDK typecheck/build and 17 tests passed (2 skipped); Python 55 passed (2 skipped).
  • Full CI pending; will wait for green. Do not merge or deploy.

Deployment note

Run the documented unbound-organization audit after migration. Ambiguous legacy rows require independently verified ownership; this PR does not access or modify production data. No release-please changes.

Co-Authored-By: Duyet Le me@duyet.net
Co-Authored-By: duyetbot bot@duyet.net

Summary by Sourcery

Persist verified workspace identities and use them consistently for tenant authorization while preserving explicit, isolated Personal workspace access.

New Features:

  • Add durable Clerk user and organization identity bindings that resolve dashboard sessions to stable internal tenant IDs.
  • Add an explicit Personal workspace alongside organization workspaces without automatically activating memberships.

Bug Fixes:

  • Prevent tenant data leakage and empty-workspace access caused by deriving authorization from mutable Clerk identity fields or sharing Personal data.
  • Reject malformed, conflicting, reserved, and ambiguous legacy identity claims instead of silently assigning ownership.

Enhancements:

  • Preserve recognized existing tenant and project data during migration while isolating unknown or shared legacy rows for verified operator recovery.
  • Scope project, analytics, domains, traces, OAuth, and organization operations through the resolved tenant context, including same-slug projects across workspaces.

Build:

  • Add the dashboard happy-dom test dependency and update lockfile metadata.

Deployment:

  • Document the post-migration audit and operator-only recovery process for unbound legacy organizations.

Documentation:

  • Document stable organization identity bindings, Personal workspace behavior, migration guarantees, and legacy recovery procedures.

Tests:

  • Add regressions covering concurrent provisioning, tenant isolation, identity drift, claim compatibility, migration behavior, same-slug workspaces, and rendered workspace switching.

duyetbot and others added 2 commits September 17, 2026 09:01
…izations

Resolve verified Clerk principals once before dashboard reads and writes. Preserve stable tenant IDs, reject ambiguous legacy matches, and support nested session organization claims. Add signed-session and migration regression coverage for #389.

Co-Authored-By: Duyet Le <me@duyet.net>
Co-Authored-By: duyetbot <bot@duyet.net>
Remove automatic membership selection, preserve separate personal and team scopes, and test switching to Personal through the rendered control.

Co-Authored-By: Duyet Le <me@duyet.net>
Co-Authored-By: duyetbot <bot@duyet.net>

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Sorry @duyetbot, you've used your own review budget of 250,000 diff characters for the last 7 days.

You can request another review in 5 days and 17 hours by commenting @sourcery-ai review. Upgrade to get a review now.

@coderabbitai

coderabbitai Bot commented Sep 17, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Advanced

Run ID: e7572989-ba76-4fff-901d-95259e507b4c


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.

@sourcery-ai

sourcery-ai Bot commented Sep 17, 2026

Copy link
Copy Markdown

Reviewer's Guide

The PR replaces string-derived dashboard tenancy with persisted, verified Clerk-principal bindings to stable internal tenant IDs, migrates only recognizable legacy identities in place, hardens claim parsing and conflict handling, and restores an explicit Personal/team workspace switcher with extensive isolation and compatibility regression coverage.

Sequence diagram for verified tenant resolution

sequenceDiagram
    participant Clerk
    participant Auth as clerkDashboardAuth
    participant Claims as normalizeSessionClaims
    participant DB as organization_identities
    participant Tenant as resolveTenant
    participant Route as Dashboard route

    Clerk->>Auth: verifyDashboardSession
    Auth->>Claims: normalizeSessionClaims
    Claims-->>Auth: DashboardPrincipal
    Auth->>Tenant: resolveTenant(principal)
    Tenant->>DB: Lookup principal binding
    alt binding exists
        DB-->>Tenant: organization_id
    else binding missing
        Tenant->>DB: Atomically create organization and binding
        DB-->>Tenant: organization_id
    end
    Tenant-->>Auth: tenantId
    Auth->>Route: Continue with tenantId
    Route->>Route: Scope project reads and writes by tenantId
Loading

Entity relationship diagram for tenant identity bindings

erDiagram
    ORGANIZATIONS {
        text id PK
        text clerk_org_id
    }
    ORGANIZATION_IDENTITIES {
        text principal_kind
        text clerk_subject
        text organization_id FK
    }
    ORGANIZATIONS ||--o| ORGANIZATION_IDENTITIES : has_stable_binding
Loading

File-Level Changes

Change Details Files
Introduces persisted, conflict-safe mappings from verified Clerk principals to stable internal tenant IDs.
  • Adds the organization_identities table with principal and tenant uniqueness constraints.
  • Adds a migration that backfills recognizable personal and organization identities in place while leaving ambiguous legacy rows unbound.
  • Resolves or provisions a tenant during dashboard authentication, returning an identity conflict instead of adopting an ambiguous legacy organization.
  • Rejects malformed, conflicting, reserved, and incompatible organization claims.
packages/api/drizzle/0012_mixed_monster_badoon.sql
packages/api/drizzle/meta/0012_snapshot.json
packages/api/drizzle/meta/_journal.json
packages/api/src/db/schema.ts
packages/api/src/lib/clerk-session.ts
packages/api/src/middleware/clerk-dashboard-auth.ts
packages/api/src/services/organizations.ts
packages/api/src/types.ts
packages/api/test/clerk-jwt.ts
packages/api/test/organization-identity.test.ts
packages/api/test/setup.ts
Moves dashboard authorization and project operations to the resolved internal tenant context.
  • Replaces per-route Clerk-identity lookups with middleware-provided tenantId checks.
  • Scopes project lookup by tenant to support identical slugs across workspaces.
  • Updates project creation, listing, organization sync, OAuth authorization, analytics, domains, and traces to preserve tenant isolation.
  • Keeps compatibility Clerk organization values for display while preventing identity drift from changing authorization.
packages/api/src/routes/analytics.ts
packages/api/src/routes/domains.ts
packages/api/src/routes/oauth/index.ts
packages/api/src/routes/organizations/index.ts
packages/api/src/routes/project-traces.ts
packages/api/src/routes/projects.ts
packages/api/src/services/projects.ts
packages/api/test/domains.test.ts
packages/api/test/organization-identity.test.ts
Restores explicit Personal workspace selection without automatically activating a team membership.
  • Replaces the previous auto-activation switcher with a reusable workspace selector.
  • Always renders Personal and clears the active Clerk organization when selected.
  • Preserves private personal projects when switching between Personal and team workspaces.
  • Adds rendered UI regressions for no-membership, multi-membership, and return-to-Personal behavior.
packages/dashboard/src/components/app-shell.tsx
packages/dashboard/src/components/workspace-switcher.tsx
packages/dashboard/src/lib/workspace-switcher.test.tsx
packages/dashboard/package.json
Documents migration auditing and operator-controlled recovery for unbound legacy tenant data.
  • Documents the identity contract, migration behavior, audit query, recovery safeguards, and rollback guidance.
  • Adds the organization identity documentation to the documentation index and durable maintenance notes.
  • Records verification and deployment constraints in the maintenance plan.
docs/knowledge/organization-identity.md
docs/INDEX.md
docs/knowledge/core-memory.md
PLAN.md
Updates dependency lock metadata associated with the implementation.
  • Refreshes the Bun lockfile for the added dashboard test dependency and related dependency resolution.
bun.lock

Assessment against linked issues

Issue Objective Addressed Explanation
#389 Replace session-derived and lazily created organization identity with a persisted, stable binding between verified Clerk principals and internal tenant IDs, and use that binding for dashboard reads, writes, and authorization.
#389 Migrate recognizable existing personal and Clerk organization identities to the existing internal organization rows without moving or orphaning tenant data, while safely quarantining shared, malformed, or ambiguous legacy identities.
#389 Preserve Personal workspace access independently from Clerk organization membership, including explicit Personal selection and isolation when users attach to or switch between organizations.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@socket-security

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addednpm/​happy-dom@​20.14.5661008896100

View full report

@duyetbot
duyetbot merged commit 8f76140 into main Sep 17, 2026
6 checks passed
@duyetbot
duyetbot deleted the fix/org-identity-sync branch September 17, 2026 02:06
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.

api: org rows are created lazily, so org-identity changes silently orphan tenant data

1 participant