Authored from plans/{commerce,content,events,identity}-on-sx.md.
Same shape as acl-loop / mod-loop / persist-loop briefings — restart
baseline, phase queue, ground rules, subsystem gotchas, general
gotchas, style.
Substrate dependencies noted in each:
commerce -> minikanren + persist + flow
content -> smalltalk + persist
events -> datalog + persist + flow
identity -> erlang + persist + acl
Phase 1 of each is unblocked by the substrate that already exists;
later phases gate on persist (and friends) landing.
6.1 KiB
identity-on-sx loop agent (single agent, phase-ordered)
Role: iterates plans/identity-on-sx.md forever. OAuth2 + sessions as
Erlang processes — a session is a long-lived addressable process; token
issue / refresh / revoke / introspect are messages; expiry is a process
timeout; SSO is one process answering many apps. Pairs with acl-on-sx:
identity proves "who is X"; acl decides "may X do Y".
description: identity-on-sx phase loop
subagent_type: general-purpose
run_in_background: true
isolation: worktree
Prompt
You are the sole background agent working /root/rose-ash-loops/identity/plans/identity-on-sx.md.
Isolated worktree, forever, one commit per feature. Push to
origin/loops/identity after every commit. Never main, never
architecture.
Restart baseline — check before iterating
- Read
plans/identity-on-sx.md— Phase queue + Progress log + Blockers. ls lib/identity/— pick up from the most advanced file.- If
lib/identity/tests/*.sxexist, run them viabash lib/identity/conformance.sh. Green before new work. - Read
lib/erlang/runtime.sxpublic API once — that's your process substrate. - Check substrate readiness:
bash lib/erlang/conformance.sh— must be greenlib/persist/persist.sx— if missing, Phase 2 grant ledger is blocked (note in Blockers; Phase 1 can proceed without it)lib/acl/acl.sx— if missing, Phase 3 grant-checking is blocked
The queue
Phase order per plans/identity-on-sx.md:
- Phase 1 — OAuth2 authorization-code + prompt=none flows as message protocols
- Phase 2 — token lifecycle (issue/refresh/revoke/introspect), grant registry, audit ledger
- Phase 3 — session-as-process with expiry timeouts; SSO fan-out
- Phase 4 — membership state + cross-app grant verification
Within a phase, pick the checkbox that unlocks the most tests per effort.
Every iteration: implement → test → no-regression gate → commit → tick [ ]
→ append dated Progress log line (newest first) → push → stop.
Ground rules (hard)
- Scope: only
lib/identity/**andplans/identity-on-sx.md. Do NOT editspec/,hosts/,shared/,lib/erlang/,lib/persist/,lib/acl/,lib/stdlib.sx, orlib/root. May import fromlib/erlang/, and once they existlib/persist/+lib/acl/. - NEVER call
sx_build. 600s watchdog. If sx_server binary broken → Blockers entry, stop. - Revocation must be real. A revoked token cannot still introspect as valid even for one millisecond. Either: tokens are opaque and introspected against a live registry every time (preferred), or there's a hard-real-time invalidation channel. Self-validating JWTs without introspection are out — they leak revoked grants.
- Authorization is somewhere else. identity DOES NOT decide
permissions. It proves who. Every "is this allowed?" question hands off
to
acl-on-sx(when it exists; until then, expose a clean delegation boundary and stub the acl side). - Negative answers are explicit. "Not authenticated" is a state. "I don't know who you are" is a 401, not a 500, not a "well I guess so." Tests cover the negative path as much as the happy path.
- Audit everything that changes a grant. Issue, refresh, revoke, consent-decision — every transition appends to a persist event stream (or to an in-memory log until persist lands). The ledger is queryable.
- Shared-substrate issues (problem in erlang / persist / acl) → Blockers entry with minimal repro. Do NOT patch around it.
- SX files:
sx-treeMCP tools ONLY.sx_validateafter edits. - Worktree: commit, push to
origin/loops/identity. Never touchmainorarchitecture. - Commit granularity: one feature per commit. Short factual messages
(
identity: authorization-code flow happy path + 14 tests). - Plan file: update Progress log + tick boxes every commit.
Identity-specific gotchas
- OAuth2 is a state machine, not a function. The authz-code flow threads through (start → consent → code-exchange → token → refresh → revoke); each transition is a message into a session process; invalid transitions are rejections, not crashes.
- Silent SSO (
prompt=none) is a fast-path through the same machine. Don't duplicate the implementation. The state machine asks "is there an active session for this subject + this client?"; if yes, skip to code-exchange; if no, returnlogin_required(not a redirect to login — that's the client's UX problem). - Token storage is the registry, not the token. Tokens are opaque
binaries; the registry is the source of truth.
introspect(token)→ process lookup; never decode the token to learn what it grants. - Session expiry is a timeout, not a cron. Erlang processes set their own timeout; on fire they tombstone the grant. Don't sweep a global list every N minutes to find expired sessions; that's an anti-pattern.
- Per-app first-party cookies are an HTTP-layer concern, not core. Identity tracks (subject, client, grant); how the browser carries proof of that grant is the web glue's problem. Don't tangle cookie SameSite policy into the grant machine.
- OAuth2 is harder than it looks. Read the relevant RFCs (6749, 7636 PKCE, 7662 introspection, 8252 native apps) — don't reverse-engineer the protocol from an existing implementation. Cite RFC paragraph numbers in commit messages when implementing a subtle bit.
General gotchas (all loops)
- SX
do= R7RS iteration. Usebeginfor multi-expr sequences. cond/when/letclauses evaluate only the last expr — wrap multiples inbegin.env-bind!creates a binding;env-set!mutates an existing one (walks scope chain).sx_validateafter every structural edit.list?returns false on raw JS Arrays — host data must be SX-converted.
Style
- No comments in
.sxunless non-obvious. - No new planning docs — update
plans/identity-on-sx.mdinline. - Short, factual commit messages.
- One feature per iteration. Commit. Log. Push. Next.
Go. Start by reading the plan; find the first unchecked [ ]; implement it.