Story 1680: Clerk/Postgres Membership Reconciliation
Historical note. Firm membership in Postgres is a mirror of Clerk organization membership, and the mirror is fed by async webhooks. Story 1680 added a scheduled out-of-VPC Lambda backstop for detecting drift, but pramaan-spec #1924 retired that live worker because Lambda must not call Clerk under pramaan-docs ADR-022 and pramaan-docs ADR-068.
What remains after pramaan-spec #1924
- The scheduled Lambda handler at
src/workers/clerk_membership_reconciliation/handler.pywas removed. - The non-VPC deployment workflow that injected
CLERK_SECRET_KEYand ran on EventBridge was removed. - The detect-only DB reconciliation service under
src/domains/clerk_membership_reconciliation/remains for possible future reuse; it does not call Clerk. - A staff-only RLS table,
ops.clerk_membership_drift, added by migration0128. - The worker-only manual span and drift metric were removed with the worker.
Retired live boundary. There is no live scheduled reconciler after pramaan-spec #1924. Any future replacement must keep Clerk API calls in an approved non-Lambda boundary and may reuse only the DB-side service/table if still appropriate.
Flow
Retired EventBridge schedule
|
v
retired non-VPC Lambda (Clerk egress)
|
+-- list firms from business.firms under staff RLS
|
+-- per firm:
|
+-- Clerk GET memberships, paginated by limit/offset
|
+-- one bounded PG snapshot:
active/inactive firm memberships
users matching Clerk user ids
active platform_staff join
expected_absent SQL predicate
|
+-- diff:
Clerk current but no active PG -> sync_gap
active PG but absent from Clerk -> ghost
role mismatch -> role_drift
staff/internal client hats -> expected_absent, not drift
|
+-- upsert ops.clerk_membership_drift
|
+-- mark previously-open missing drift rows resolved
Drift record behavior
| Case | Record | Why |
|---|---|---|
| Clerk current, PG not active | sync_gap | Likely missed organizationMembership.created or inactive mirror row. |
| PG active, Clerk absent | ghost | Likely missed organizationMembership.deleted. |
| Same membership id, different role | role_drift | Likely missed organizationMembership.updated. |
| Active staff/internal client role absent from PG | No drift | Expected-absent under pramaan-docs ADR-123 identity-hat rules. |
Rows are keyed by (firm_id, drift_type, clerk_membership_id). The retained DB-side service refreshes last_seen_at and clears resolved_at for still-current drift; drift that disappears is marked resolved instead of duplicated or deleted.
Proof
- The service test seeds a real firm, users, active PG memberships, a platform-staff identity, and Clerk snapshots, then asserts all three drift directions and the expected-absent exclusions.
- The worker-only metric tests were removed with the retired Lambda metric.
- The migration creates RLS, grants only
SELECT/INSERT/UPDATE, and revokes delete for the app role.
Recall: the scheduled egress-capable reconciler is no longer live; only the DB-side service and
ops.clerk_membership_drift table remain for possible future reuse.