Learning Platformindex / ADRs / ADR-003E3

ADR-003 — Identity, SSO & Security (E3)

Status: Proposed (design-panel synthesis) · Date: 2026-07-22 · Track: E3 (identity, SSO, security) Method: adversarial design panel — estate-scout + four thinker-canon lenses (auth/threat · specification · simplicity · data). Dissent preserved below.

Follows the ADR-001 template. Brand-free: providers/mechanisms named by capability only. This ADR governs Pillar 1 (identity spine) and the invariant that the whole tier moat hangs off.


1. Context

The learning core (the forked LMS) already ships real local auth: username/email + argon2 password hashing, email-verify gating, self-serve reset, four roles (admin/instructor/moderator/student). That is a working single-app login — and only a single-app login. The foundation spec (Pillar 1) commits to something larger: one member identity across every surface (learning core, community, realtime, AI, admin), with tier as one attribute on the member, read everywhere, re-implemented nowhere — and the strategic reason to own identity at all is to own the tier (I1/I2). "One login" and "own the tier" are asserted in prose; nowhere are they specified.

The estate-scout confirmed the hard truth: the identity core is greenfield. The two candidate estate repos (wild-capability-gate, wild-session-telemetry) are Ruby, agent-facing, and sit downstream of identity — one is a fail-closed authorization primitive keyed on a service-account string (not a human IdP), the other is agent-session telemetry with an allowlist PII filter (not human security-event logging). Neither authenticates a human. Roughly 80–90% of E3 is net-new; the estate contributes a security stance (fail-closed authorization + audit, allowlist-based PII redaction) to carry forward, not a head start on the IdP itself.

E3's first real work — per Pillar 1 and the build order — is the security/login audit (session, secrets, exposure, MFA, rate-limiting), then the one-identity design on top.

2. Decision drivers

3. Options considered

4. The panel (rankings diverged; read the core argument, not the rank)

Lens Rank #1 Core argument
Auth / threat (security) A A single hardened choke point is the only place MFA, login rate-limiting, refresh-token rotation, session revocation, and tier-claim integrity can be enforced uniformly. But "own the IdP" means own and operate a proven one — never hand-write an authorization server (that is B's sin in disguise). Resolve revocation with short token TTL + an introspection/revocation path so a downgrade propagates in seconds. Audit first — but scope it to the one-identity target threat model, not today's single-app posture.
Specification (Lamport) A "One identity" is an unstated bijection: exactly one immutable, internally-minted sub per human; every legacy account and every social login maps to it, never is it. Two live front doors (native login + IdP running in parallel) makes "one identity" false — federation must retire the native path. Own the authority, rent the protocol (mature OIDC, don't hand-roll). Specify the invariants now; they are the expensive-to-retrofit part regardless of mechanism.
Simplicity (Torvalds) D Tier is a column; sub is a foreign key. You don't stand up a stateful, patch-it-forever identity service to own a column and an FK — native auth already owns the member table and sub. Putting a broker in front of live native auth doubles identity (two account systems → drift → tier leaks — the exact failure ADR-001 warned about). "One login" is a session/token problem, not an IdP problem. Do the audit first; let its findings — not the slogan — decide whether SSO needs anything heavier than a shared signed session. Defer the IdP, don't kill it.
Data (Kleppmann) A sub is an immutable forever join key — treat it like a primary key, not a login detail. The catastrophic failure isn't slow login, it's silent referential corruption: a re-entering member minted a second sub is orphaned from every prior enrollment and certificate, no error fires. Tier in the token is a cache, and cache invalidation is the hard problem — a tier-bearing JWT contradicts I9 by construction. Native→sub migration is a one-time, exactly-once event with no undo.
Estate-scout Identity is greenfield (~80–90% net-new). The estate gives a stance, not a component: fail-closed authorization + structured audit (wild-capability-gate) and allowlist-based PII redaction (wild-session-telemetry). Adopt a proven auth provider/framework; don't build the IdP from estate parts.

5. The convergence (what all five agree on, under the different ranks)

Despite ranking A over D (or D over A), and unanimously rejecting B and C, the lenses converge on one architecture:

  1. The one irreversible decision is sub and where tier authority lives — not the IdP mechanism. Every lens names the same single-most-costly item: which identifier becomes the immutable join key, and whether sub can ever be duplicated or reassigned. The IdP (self-run, hosted, or "just native auth") is a repoint. sub written into every FK and stamped immutably into the append-only conversation log (ADR-001 I9) is a rewrite. This is the seam the foundation flags irreversible.
  2. Mint sub internally, immutable, opaque — never an email, never a vendor's subject id, never the learning core's local user-id. Own the sub namespace in our own member store from day one; every credential source (native, social, future) links to an existing sub, never mints a new one for a known human. This is what "own the identity so we own the tier" actually reduces to. It is why C is rejected on invariant grounds (a vendor owns your primary key) and why B is dangerous (upstream LMS churn lands on your most irreplaceable data).
  3. Don't hand-write an authorization server. "Own the IdP" (security, Lamport) and "don't build an IdP" (Torvalds, Kleppmann) are the same instruction from two doors: own the authority (the sub↔︎tier binding, in our store); rent the protocol machinery (OIDC token minting, social brokering, MFA, session crypto) from a proven implementation — self-hosted. Hand-rolled OIDC/revocation/MFA is where subtle auth CVEs live. B is last or near-last on every lens for exactly this.
  4. Tier is never an authoritative token claim on a destructive surface. A tier-bearing token with any TTL is a stale cache; a downgrade/ban leaks until expiry — a timed violation of ADR-001 I9, originating in the session layer where the P0 tier-leak eval never looks. Tier is resolved fresh at the retrieval/decision boundary against the reader's current tier — the same evaluation path the AI index already uses (one tier path, not two).
  5. One authentication authority after federation. Two live front doors (native login and an IdP, both able to mint a session) makes "one identity" only as strong as the weaker door. The federation step retires the standalone native login path; native argon2 stays as a credential backend behind the authority, not a parallel authority.
  6. The audit runs first — scoped to the one-identity threat model. The trap all lenses name: audit the LMS login in isolation, find it "fine," and drift into B. The audit's job is also to measure whether an SSO problem heavy enough to need a broker even exists yet.

6. Decision — "Own the join key · rent the protocol · defer the daemon"

A hybrid that takes the winning move from each lens. It is D-shaped now, A-frontable later, and never B or C — and it is identical whether the v1 mechanism is a shared signed session or a self-hosted OIDC broker, because the expensive, locked parts live below that choice.

6.1 Own (the moat — cheap now, catastrophic to retrofit)

6.2 Rent (the commodity — don't hand-roll, don't let a vendor own the key)

6.3 Defer (Torvalds' cut — scoped, not killed)

7. Tradeoff matrix

Dimension A broker IdP B extend native C hosted IdP D token/federation Decision (hybrid)
Own sub namespace + tier authority (moat) ✅ if minted into our namespace ❌ vendor owns the key by construction
One authentication authority ⚠️ two front doors risk ✅ (retire native front door)
No hand-rolled auth server ✅ (rent protocol) ❌ worst attack surface ✅ (thin, or rent later)
Tier-at-read survives to session layer ⚠️ if sub-only tokens ⚠️ ⚠️ vendor claim ⚠️ if sub-only tokens ✅ (tier never a destructive token claim)
Revocation / bounded downgrade ✅ introspection path build it vendor-dependent ❌ stateless leaks to TTL ✅ (short TTL + live-resolve on destructive)
Ops-service count / small team ❌ 2nd always-on target ✅ (rented) ✅ lowest ✅ (defer daemon, one box)
Reversibility of the IdP mechanism ✅ repoint ⚠️ merge tax ❌ vendor sub ✅ repoint ✅ IdP-frontable by design
Merge tax on upstream syncs ✅ none ❌ forever, on auth code ✅ (adapters upstreamed)

8. Consequences

New locked invariants (promote into the foundation spec §7):

New required evals (the moat is only as trustworthy as these):

E3's first task is the audit, then the invariant lock — not a build:

  1. Run the security/login audit (session, secrets, exposure, MFA, rate-limiting) — scoped to the one-identity target threat model, not the current single-app posture. The audit also measures whether an SSO problem heavy enough to justify a broker exists yet.
  2. Lock I11–I17, single-sub-minting, and the immutable native→sub migration ledger before any surface writes a member FK. This is priority-0 and precedes all rendering, community, and AI build.

Estate seams (what we actually reuse — patterns, not components):

9. Preserved dissent (do not resolve away)

10. Follow-up (beads to file in Phase B under the E3 epic)

  1. Security/login audit (session, secrets, exposure, MFA, rate-limiting), scoped to the one-identity threat model — before any identity build. (priority 0)
  2. Lock the single-sub-minting rule + the immutable native-account→sub migration ledger, before any surface writes a member FK. (priority 0)
  3. Lock invariants I11–I17 into the foundation spec §7.
  4. P0 identity single-minting / linking-correctness eval (fail-closed) — before the first non-founder logs in.
  5. Extend the ADR-001 P0 tier-leak eval into the session layer; assert per-surface tier-freshness (live-resolve on destructive surfaces, short-TTL elsewhere; tier never an authoritative token claim). (priority 0)
  6. P1 auth-hardening regression eval (MFA, rate-limiting, refresh rotation, revocation latency).
  7. Decide (resolves foundation open-question §9): MFA + login rate-limiting go upstream to the learning core vs. the adopted protocol layer — audit-driven.
  8. Specify the account-link flow (verified-email binding, explicit + audited + reversible) and the revocation channel + staleness bound.
  9. Annotate the E3 estate-integration map with per-seam reversibility: sub + tier authority = irreversible; IdP mechanism, social provider, session transport = repoint.

Panel transcript basis: estate-scout + auth/threat, Lamport, Torvalds, Kleppmann lenses, 2026-07-22. Follows the ADR-001 template; the identity/tier seam is the one this platform cannot walk back.