> ## Documentation Index
> Fetch the complete documentation index at: https://docs.reflections.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# ADR-0028: Clerk free-plan organization canonicalization

> Canonicalize reflection team-sharing onto Clerk Organizations using free-plan-compatible roles only while preserving creator-private access semantics.

<Info>**Status:** Accepted **Date:** 2026-03-04 **Deciders:** Reflections Maintainers</Info>

## Context

Reflection collaboration needs first-class team sharing with reliable invitation lifecycle, role management, and identity resolution. The prior model mixed Clerk-authenticated users with a mostly custom invite/membership stack, increasing operational and security surface area.

At the same time, production is on Clerk free plan, where custom org roles/permissions are not available for production instances. The architecture must therefore avoid paid-only role features while keeping access control strict.

Evidence in code/config:

* `apps/api/src/lib/clerk-organizations.ts`
* `apps/api/src/lib/authorization.ts`
* `apps/api/src/routes/team/invites-routes.ts`
* `apps/api/src/routes/team/members-routes.ts`
* `apps/api/src/routes/webhooks/handlers/membership.ts`
* `apps/api/src/routes/webhooks/handlers/invitation.ts`
* `packages/db/src/queries/reflections.ts`
* `packages/db/src/queries/memberships.ts`
* `supabase/migrations/20260304190000_clerk_free_plan_role_canonicalization.sql`
* `supabase/migrations/20260304193000_drop_team_ownership_transfers.sql`
* `docs/runbooks/clerk-org-team-cutover.md`

## Decision

Adopt a strict Clerk-native free-plan model:

1. **Tenancy model:** `1 reflection = 1 Clerk organization`.
2. **Canonical authority:** Clerk is the only write authority for organization invites, membership changes, and role changes.
3. **Role model:** Free-plan defaults only:
   * `org:admin`
   * `org:member`
4. **Local projection:** Postgres `reflection_memberships` and `team_invites` remain projection/cache for query performance, RLS, and auditability; they are synced from Clerk events and Clerk-backed command handlers.
5. **Creator-private behavior:** `owner_private` access is derived from `reflections.creator_user_id == current_user_id`, not from a Clerk custom role.
6. **No legacy token acceptance path:** Invite acceptance is Clerk-native only; legacy token endpoints are removed.
7. **Ownership transfer feature:** Removed from runtime/API surface under the free-plan model; creator authority is identity-derived.
8. **Dashboard runtime:** Team management runs in Clerk-backed dashboard surfaces only; readiness is derived from the live selected reflection plus the active Clerk org after hydration.
9. **Shipped team permissions:** Owners and admins can manage members and invites in the shipped Team workspace; destructive organization actions remain disabled or hidden until reflection/org lifecycle symmetry is explicitly implemented.
10. **Readiness vs sync health:** `sync_failed` is a warning-only metadata-sync state; it does not block Team access when reflection mapping and active-org alignment are healthy.
11. **Deterministic handoff:** Team links and native handoffs carry reflection identity (and canonical Clerk org identity when available) so the Team route can switch or hydrate the intended workspace deterministically.

## Alternatives Considered

### Alternative 1: Keep custom `owner/admin/operator/viewer` stack as canonical

Pros:

* Maximum in-app role flexibility.

Cons:

* Re-implements invite lifecycle and membership authority outside Clerk.
* Larger authz and sync surface; higher drift risk.
* Lower leverage of Clerk's native org identity/invite model.

### Alternative 2: Upgrade plan and use custom Clerk roles immediately

Pros:

* Rich permission model in Clerk.

Cons:

* Adds billing dependency now.
* Not required to satisfy current collaboration requirements.

### Alternative 3: Collapse creator-private semantics into admin role

Pros:

* Simpler conceptual model.

Cons:

* Breaks product requirement that creator-private content remains creator-only.

## Consequences

Benefits:

* Fully Clerk-native authority for invites/memberships/roles.
* Free-plan-compatible production posture with no paid-role dependency.
* Clear separation between identity authority (Clerk) and data scoping authority (DB + creator identity + RLS).
* Reduced dead-path complexity by removing ownership-transfer runtime.

Costs:

* Two-role model (`admin/member`) is less granular than custom-role systems.
* App must keep creator identity checks to preserve `owner_private` behavior.
* Legacy token acceptance internals are removed entirely; in-app team operations rely on Clerk UI/components.

## Implementation Notes

* Clerk role mapping is normalized in `apps/api/src/lib/clerk-organizations.ts`.
* Authorization remains server-side and server-derived in `apps/api/src/lib/authorization.ts`.
* Team membership/invite projection is webhook-driven from Clerk events.
* Team UX uses Clerk-hosted organization management components.
* Dashboard Team UX uses a product-owned shell around Clerk-backed team management, with owner/admin management capability, deterministic invalid-mapping recovery, and explicit non-destructive policy copy.
* Webhook handlers keep projection eventually consistent and replay-safe:
  * `apps/api/src/routes/webhooks/handlers/membership.ts`
  * `apps/api/src/routes/webhooks/handlers/invitation.ts`
  * `apps/api/src/routes/webhooks/handlers/organization.ts`
* Replay-safe org deletion cleanup derives durable `deleted_org` UI state from backend truth rather than transient frontend inference.
* Creator identity is stored in `reflections.creator_user_id` and used for virtual owner semantics:
  * `packages/db/src/queries/reflections.ts`
  * `packages/db/src/queries/memberships.ts`
* DB canonicalization and cleanup migrations:
  * `20260304190000_clerk_free_plan_role_canonicalization.sql`
  * `20260304193000_drop_team_ownership_transfers.sql`

## Related ADRs

* [ADR-0008: Authentication and RBAC Model](/decisions/adr-0008)
* [ADR-0009: API Architecture and Authorization Enforcement](/decisions/adr-0009)
* [ADR-0015: Tenant Isolation and Database RLS Boundary](/decisions/adr-0015)
