What can you actually do — and where are the gaps?
CAPABILITY MAP: IDENTITY & ACCESS
════════════════════════════════════════════════════════════
CAPABILITY MATURITY GAP?
────────────────────────────── ──────── ────
Authentication (Clerk) ████░ (4)
Identity Resolution (Adapter) ████░ (4)
Multi-Tenancy Schema ████░ (4)
Auto-Provisioning ███░░ (3)
Org ID Propagation ███░░ (3)
Audit Trail Schema ██░░░ (2)
Billing Schema █░░░░ (1)
Authorization (PolicyEngine) █░░░░ (1) CRITICAL
Redirect Error Handling ░░░░░ (0) CRITICAL
Role Seeding ░░░░░ (0) CRITICAL
Admin Bootstrap ░░░░░ (0) CRITICAL
Invite Flow ░░░░░ (0) GAP
Org Switching ░░░░░ (0) GAP
Security Testing ░░░░░ (0) GAP
════════════════════════════════════════════════════════════
Maturity Assessment
| Capability | Maturity | Evidence | Category |
|---|---|---|---|
| Authentication (Clerk) | 4 — Managed | Clerk middleware validated in production, session handling working, 3-layer validation | Generic (buy) |
| Identity Resolution | 4 — Managed | ClerkAuthAdapter resolves ExternalAuthId → SystemUserId → OrganisationId, branded ID types | Supporting |
| Multi-Tenancy Schema | 4 — Managed | All business tables FK to org_organisation, composite unique constraints, typed IDs | Core |
| Auto-Provisioning | 3 — Defined | Creates user + org on first login, documented pattern, but no role assignment | Supporting |
| Org ID Propagation | 3 — Defined | ApplicationContext carries org ID, buildService injects context, but not enforced | Core |
| Audit Trail Schema | 2 — Repeatable | governance_access_audit table exists in schema, not wired to any decision | Supporting |
| Billing Schema | 1 — Ad-hoc | org_subscription + stripeCustomerId fields exist, nothing wired | Supporting |
| Authorization | 1 — Ad-hoc | PolicyEngine exists with canI() method, but defaultAllow: true bypasses everything | Core |
| Redirect Handling | 0 — Not built | No error page — access denied causes infinite redirect loop | Supporting |
| Role Seeding | 0 — Not built | governance_user_roles table empty, no seed script | Core |
| Admin Bootstrap | 0 — Not built | First user gets no role, ADMIN_EMAILS not configured | Core |
| Invite Flow | 0 — Not built | No invitation model, no org joining, no membership management | Core |
| Org Switching | 0 — Not built | No UI, no resolution logic for multi-org users | Supporting |
| Security Testing | 0 — Not built | No negative tests for cross-tenant data leakage | Core |
Gap Analysis
| Priority | Capability | Current | Target | Impact | Fix |
|---|---|---|---|---|---|
| P1 | Authorization | 1 | 3 | Cannot enforce any permissions | Seed roles, switch defaultAllow to false |
| P1 | Role Seeding | 0 | 3 | No roles exist to assign | Migration or seed script |
| P1 | Admin Bootstrap | 0 | 3 | Owner locked out on every deploy | Auto-assign Admin during provisioning |
| P1 | Redirect Handling | 0 | 3 | 789 errors in 30min, unusable UX | Error page instead of redirect loop |
| P2 | Invite Flow | 0 | 3 | Can't onboard second user without developer | Invite-by-email, membership model |
| P2 | Security Testing | 0 | 3 | No proof of data isolation | Negative tests per endpoint |
| P3 | Org Switching | 0 | 3 | Multi-org users stuck in one org | Org switcher in nav |
| P3 | Billing Schema | 1 | 3 | Can't charge per org | Wire Stripe customer to org |
Category Summary
| Category | Count | Avg Maturity | Health |
|---|---|---|---|
| Core | 7 | 1.6 | 4 of 7 at zero or one — critical |
| Supporting | 6 | 1.8 | Authentication strong, rest weak |
| Generic | 1 | 4.0 | Clerk handles this well |
Overall: 22% of multi-tenant checklist passing. Schema depth is real — most fails are wiring, not architecture.
Investment Strategy
| Tier | Action | Capability | Effort |
|---|---|---|---|
| Buy | Authentication | Clerk — already purchased, working well | $0 (free tier) |
| Build (P1) | Role Seeding + Admin Bootstrap + Redirect Fix + Auth enforcement | Core security infrastructure | 2 days |
| Build (P2) | Invite Flow + Security Testing | Core user management | 3 days |
| Build (P3) | Org Switching + Billing + Settings | Supporting operations | 5 days |
| Defer | RLS, SSO/SAML, Parent/Child orgs | Enterprise features | Future |
Current State
What's built vs what's wired vs what's working.
| Layer | Component | Built | Wired | Working |
|---|---|---|---|---|
| Authentication | Clerk middleware | Yes | Yes | Yes |
| Authentication | ClerkAuthAdapter (3-layer validation) | Yes | Yes | Yes |
| Identity | Auto-provision user + "Personal Workspace" org | Yes | Yes | Untested |
| Identity | org_system_user table (Clerk ID → internal UUID → org) | Yes | Yes | Yes |
| Identity | Branded ID types (ExternalAuthId, SystemUserId, OrganisationId) | Yes | Yes | Yes |
| Authorization | isOrgAdmin() guard | Yes | Yes | Blocks owner |
| Authorization | ADMIN_EMAILS env var check | Yes | Not configured | No |
| Authorization | governance_user_roles table | Yes | No seed data | No |
| Authorization | PolicyEngine (canI()) | Yes | defaultAllow: true | Bypassed |
| Multi-tenancy | org_organisation table | Yes | Yes | Yes |
| Multi-tenancy | Org-scoped data (all business tables FK to org) | Yes | Yes | Yes |
| Billing | org_subscription table | Yes | Schema only | No |
Authentication is solid. Multi-tenancy schema is solid. Authorization is a landmine — schema exists, guards deployed, but no data and no admin bootstrapping.
Architecture Flows
Authentication (WHO are you?)
Browser → Clerk Middleware → Session Valid?
├── NO → /sign-in (public route)
└── YES → ClerkAuthAdapter.validateContext()
├── Clerk userId present?
│ ├── NO → INVALID_SESSION error
│ └── YES → Lookup in org_system_user
│ ├── FOUND → Return ApplicationContext
│ └── NOT FOUND → Auto-provision
│ ├── Create org_system_user
│ ├── Create "Personal Workspace" org
│ ├── Assign Admin role (bootstrap) ← MISSING
│ └── Return ApplicationContext
└── Error → LOOKUP_FAILED / PROVISION_FAILED
Authorization (WHAT can you do?)
Server Action called
→ buildService() → authAdapter.validateContext()
→ Returns ApplicationContext { userId, systemUserId, organizationId }
→ governance.policyEngine.canI(context, action, resourceType)
├── defaultAllow: true → ALLOW (current state — bypass)
└── defaultAllow: false → Check governance_role_permissions
├── Permission found → ALLOW
└── Permission not found → DENY → 403
Multi-Tenancy Model
Clerk User (external)
└── org_system_user (internal, branded SystemUserId)
└── org_organisation (tenant boundary)
├── All business data scoped here
├── governance_user_roles (what can this user do in this org?)
└── governance_permissions (what actions exist?)
One user can belong to multiple orgs (composite unique: external_auth_id + organisation_id)
Data Flow: Org ID Propagation
Request arrives
→ Clerk Middleware (validates session cookie)
→ ClerkAuthAdapter.validateContext()
→ Resolves: externalAuthId → systemUserId → organisationId
→ Returns ApplicationContext { systemUserId, organisationId, ... }
→ buildService(context)
→ Every repository method receives organisationId
→ Every SQL query includes WHERE organisation_id = $1
→ Response scoped to tenant
The enforcement gap: Nothing prevents a repository method from omitting the WHERE clause. Fix is either base repository class (Tier 2) or Postgres RLS (Tier 3).
Multi-Tenant Checklist
From the Multi-Tenant SaaS Checklist, 54 items audited:
| Section | Items | Pass | Partial | Fail |
|---|---|---|---|---|
| Foundational Model | 5 | 3 | 2 | 0 |
| Data Isolation | 5 | 1 | 1 | 3 |
| User-Org Association | 4 | 0 | 1 | 3 |
| Authentication & Sessions | 5 | 1 | 1 | 3 |
| RBAC | 7 | 3 | 0 | 4 |
| Billing & Subscriptions | 4 | 1 | 2 | 1 |
| Security & Isolation | 5 | 2 | 2 | 1 |
| Total | 54 | 12 (22%) | 10 (19%) | 25 (46%) |
Schema depth is real — most "Fail" items have tables built but no wiring. The gap is operational, not architectural.
Gate
Before moving to Agent & Instrument Diagram:
- All capabilities assessed with evidence-based maturity — YES (14 capabilities)
- Capabilities categorised (Core / Supporting / Generic) — YES
- Gaps identified and prioritised (P1 / P2 / P3) — YES (4 P1, 2 P2, 2 P3)
- Investment strategy matches category — YES (buy generic, build core)
- Critical gaps have escalation paths — YES (P1 gaps are Tier 0 tasks)
Context
- Dependency Map — Previous: what must happen first
- Agent & Instrument Diagram — Next: how agents orchestrate
- Capability Map Template — The empty pattern