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

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

CaseRecordWhy
Clerk current, PG not activesync_gapLikely missed organizationMembership.created or inactive mirror row.
PG active, Clerk absentghostLikely missed organizationMembership.deleted.
Same membership id, different rolerole_driftLikely missed organizationMembership.updated.
Active staff/internal client role absent from PGNo driftExpected-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

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.