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.
129 lines
6.1 KiB
Markdown
129 lines
6.1 KiB
Markdown
# 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
|
|
|
|
1. Read `plans/identity-on-sx.md` — Phase queue + Progress log + Blockers.
|
|
2. `ls lib/identity/` — pick up from the most advanced file.
|
|
3. If `lib/identity/tests/*.sx` exist, run them via
|
|
`bash lib/identity/conformance.sh`. Green before new work.
|
|
4. Read `lib/erlang/runtime.sx` public API once — that's your process
|
|
substrate.
|
|
5. Check substrate readiness:
|
|
- `bash lib/erlang/conformance.sh` — must be green
|
|
- `lib/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/**` and `plans/identity-on-sx.md`. Do NOT
|
|
edit `spec/`, `hosts/`, `shared/`, `lib/erlang/`, `lib/persist/`,
|
|
`lib/acl/`, `lib/stdlib.sx`, or `lib/` root. May **import** from
|
|
`lib/erlang/`, and once they exist `lib/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-tree` MCP tools ONLY. `sx_validate` after edits.
|
|
- **Worktree:** commit, push to `origin/loops/identity`. Never touch `main`
|
|
or `architecture`.
|
|
- **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, return `login_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. Use `begin` for multi-expr sequences.
|
|
- `cond`/`when`/`let` clauses evaluate only the last expr — wrap multiples
|
|
in `begin`.
|
|
- `env-bind!` creates a binding; `env-set!` mutates an existing one (walks
|
|
scope chain).
|
|
- `sx_validate` after every structural edit.
|
|
- `list?` returns false on raw JS Arrays — host data must be SX-converted.
|
|
|
|
## Style
|
|
|
|
- No comments in `.sx` unless non-obvious.
|
|
- No new planning docs — update `plans/identity-on-sx.md` inline.
|
|
- Short, factual commit messages.
|
|
- One feature per iteration. Commit. Log. Push. Next.
|
|
|
|
Go. Start by reading the plan; find the first unchecked `[ ]`; implement it.
|