Before touching unfamiliar code, ask Claude to surface what you don't know you don't know — and to tell you how to prompt around each gap.
I'm adding a new SSO auth provider to Acme but I've never touched the auth module. Do a blindspot pass: find my unknown unknowns in this part of the codebase, explain each one, and tell me how to prompt you better for the implementation.
Scanned services/auth/ (47 files), 214 commits touching it since Jan 2025, 3 migrations in flight, 2 reverted attempts. Seven things you'd have learned the hard way.
"Add a new SSO provider" — which sounds like: implement an interface, register it, write the redirect handlers, done. On a clean codebase, a two-day task.
A module mid-migration on session storage, with one provider that quietly bypasses the middleware everyone assumes runs, a reverted previous attempt at exactly this task, and an identity-linking model that isn't visible from the provider interface at all.
Acme is mid-migration from Postgres-backed sessions to Redis (MIG-118, stalled since March). Every session write currently goes through SessionBridge.write(), which fans out to both RedisSessionStore and the legacy pg_sessions table. The migration was never finished, so reads still come from Postgres.
RedisSessionStore first — it looks canonical, has the newest code, and the best docs. Write to it directly and your logins will appear to work in dev (Redis reads are enabled there) and silently fail in prod, where reads still hit Postgres.Every provider except SAML mounts under authPipeline, which runs rate limiting, audit logging, and the device-trust check. saml/handler.ts mounts its ACS endpoint directly on the router because SP-initiated POST callbacks broke the CSRF layer in 2024, and nobody circled back. There's a // TODO(priya): fold back into pipeline from 14 months ago.
PR #2841 ("Add Okta OIDC provider", Sept 2025) was merged and reverted nine days later. The revert commit explains why: Acme callback URLs must carry a tenant suffix (/auth/callback/:provider/:workspaceSlug) so workspace-scoped invite links survive the redirect round-trip. Okta's strict redirect-URI matching rejected the dynamic suffix, and invited users landed in the wrong workspace. The fix everyone agreed on — a state-parameter workspace claim signed with AUTH_STATE_SECRET — was designed in the revert thread but never built.
Acme splits users from identities: one account can have several linked identities (email+password, Google, SAML), joined in the identities table. The provider interface only returns a profile; the linking decision happens in identity/linker.ts, keyed on verified email. Providers that can't guarantee verified emails (many SSO IdPs don't) must set requiresManualLink: true or the linker will happily merge two different people who share a corporate alias.
Refresh-token rotation is gated by auth.refresh_rotation in flags.yaml. It defaults to true in dev and staging, false in prod (a paused rollout after a clock-skew incident in May). With the flag off, prod sessions live exactly ACCESS_TOKEN_TTL (12h) and then hard-expire — there is no refresh path.
A provider isn't live when it compiles. It must be (1) registered in providers/registry.ts, (2) inserted into the auth_providers table via a migration — the admin UI reads the DB, not the registry — and (3) added to the fixture list in __tests__/provider-contract.spec.ts, which runs the shared contract suite (state validation, replay protection, logout fan-out) against every registered provider. CI fails if the registry and fixture list disagree.
Session revocation publishes auth.session.revoked on the internal event bus. The realtime review player holds its own long-lived playback grants and only releases them when it consumes that event. logout.ts handles this for existing providers, but SSO single-logout (SLO) callbacks arrive on a separate path and must publish the event themselves.
Add a new SSO auth provider to Acme. Constraints from the blindspot pass: 1. Sessions: route all session writes through SessionBridge (MIG-118 is unfinished; prod still reads from pg_sessions). Never touch RedisSessionStore directly. 2. Template: base the structure on oauth/google.ts, NOT saml/handler.ts — SAML bypasses authPipeline. Mount all new routes inside authPipeline and show me the mount point. 3. History: read the revert of PR #2841 first. Implement the signed state-parameter workspace claim (AUTH_STATE_SECRET) instead of tenant-suffixed callback URLs, and test the workspace-invite flow. 4. Identity linking: walk me through identity/linker.ts before coding. If this IdP doesn't guarantee verified emails, set requiresManualLink and build the confirmation path — do not auto-link. 5. Flags: implement against auth.refresh_rotation=false (prod). Sessions hard-expire at 12h; no refresh path. Note what changes if rotation ships. 6. Registration: all three steps — providers/registry.ts, an auth_providers migration, and the provider-contract fixture list. Show the contract suite green. 7. Logout: every revocation path must publish auth.session.revoked, and add a test that the review player's playback grant is released on IdP-initiated logout. Work in this order: linker walkthrough → provider skeleton → callback + state claim → registration → SLO + events → contract tests. Stop and show me the plan after the walkthrough before writing code.
Seven sentences you couldn't have written this morning — each one bought with someone else's half-day. That's the point of the pass.