# identity-on-sx: OAuth2, sessions & membership on Erlang

> **DRAFT outline.** The identity core `acl-on-sx` assumes already exists. `acl`
> answers "may X do Y"; identity answers "who is X, and how did they prove it."
> Depends on `persist-on-sx` (grant/audit ledger). Pairs with `acl-on-sx`.

rose-ash's `account` domain is the OAuth2 authorization server every other app is
a client of: silent SSO, per-app first-party cookies, grant verification,
membership. Sessions and grants are **long-lived, concurrent, individually
addressable, and expire on their own** — that is the actor model. Erlang's
processes + mailboxes map cleanly: a session is a process, token issue/refresh/
revoke are messages, expiry is a process timeout, and SSO is one process answering
many apps.

End-state: an Erlang-on-SX layer with the OAuth2 authorization-code + silent
(`prompt=none`) flows as message protocols, a session/grant registry, token
lifecycle (issue/refresh/revoke/introspect), and membership state — all auditable
through the event log, all authorization questions delegated to `acl-on-sx`.

## Status (rolling)

`bash lib/identity/conformance.sh` → **0/0** (not yet started)

## Ground rules

- **Scope:** only `lib/identity/**` and `plans/identity-on-sx.md`. May **import**
  from `lib/erlang/`, and (once they exist) `lib/persist/` + `lib/acl/`. Do not edit
  substrates.
- **Architecture:** a session/grant is a process holding its own state; the
  registry routes messages by subject/client id. Tokens are opaque + introspected,
  not self-validating (revocation must be real). Authorization decisions are NOT
  made here — `identity` proves identity, `acl` decides permission.
- **Security:** revocation is immediate (kill the process / tombstone the grant);
  no decision relies on a token that outlived its grant. Negative answers are
  explicit, never "absence of a yes."
- **Commits:** one feature per commit. Progress log + tick boxes.

## Architecture sketch

```
Auth request                            Token / session
  (authorize client scope subject)        {:access :refresh :expires :grant}
        │                                       ▲
        ▼                                       │
lib/identity/oauth.sx                   lib/identity/token.sx
  — authz-code + prompt=none flows        — issue / refresh / revoke / introspect
  — as Erlang message protocols           — opaque tokens, grant-backed
        │                                       ▲
        ▼                                       │
lib/identity/session.sx                 lib/identity/registry.sx
  — session = process, expiry=timeout     — route by subject/client; SSO fan-out
        │                                       │
        ▼                                       ▼
lib/identity/api.sx ── (identity/login) (identity/grant?) (identity/revoke) ──┐
        │                                                                      │
        └──────── grant + audit events → persist ;  permission? → acl ──────────┘
```

## Phase 1 — Sessions + tokens
- [ ] `session.sx` — session process, create/lookup/expire
- [ ] `token.sx` — issue/introspect/revoke (opaque, grant-backed)
- [ ] `registry.sx` — route by subject/client
- [ ] `api.sx` + tests + scoreboard + conformance.sh

## Phase 2 — OAuth2 flows
- [ ] authorization-code flow as a message protocol
- [ ] refresh + rotation; revocation cascades to issued tokens
- [ ] tests: full code exchange, refresh, revoke-then-use (must fail)

## Phase 3 — Silent SSO + membership
- [ ] `prompt=none` cross-app login (one session, many clients)
- [ ] membership state + per-app grant projection
- [ ] grant verification delegated cache (mirror Redis-cache pattern)

## Phase 4 — Audit + federation
- [ ] every issue/refresh/revoke is a `persist` event; `(identity/audit subject)`
- [ ] federated identity (peer-asserted subject) — advisory, trust-gated stub
- [ ] tests: audit completeness, cross-instance subject mapping

## Progress log
(loop fills this in)

## Blockers
(loop fills this in)
