diff --git a/quartz/plans/2026-06-04-auth-scope-relay-components.svg b/quartz/plans/2026-06-04-auth-scope-relay-components.svg
index 801319fd75..31d9a1af73 100644
--- a/quartz/plans/2026-06-04-auth-scope-relay-components.svg
+++ b/quartz/plans/2026-06-04-auth-scope-relay-components.svg
@@ -10,7 +10,7 @@
Building a Relay with Quartz — components & data flow
- after the plan: auth identity lives in the connection scope, not in a policy
+ auth identity lives in the engine-owned connection scope, not in a policy
@@ -51,7 +51,7 @@
accept(EVENT/REQ/COUNT/AUTH)
→ allow · reject · rewrite filters
onConnect(scope, send) ← reads scope to gate
- onAuthenticated() : Boolean → "record this id?"
+ onAuthenticated(event) : Boolean → "record this id?"
FullAuthPolicy: per-connection — may hold scope ref
VerifyPolicy / EmptyPolicy: shared singletons —
@@ -94,14 +94,14 @@
creates & owns
- WRITE: add(pubkey)
+ WRITE: add(event.pubKey)
on verified AUTH
accept(cmd)
- onAuthenticated()→Bool
+ onAuthenticated(event)→Bool
@@ -126,23 +126,23 @@
- What the plan changes (vs. the shipped branch)
+ How authentication flows (current design)
- Ownership moves: auth identity is SCOPE, not POLICY
- • the connection scope (engine-owned) now holds authenticatedUsers — it was a field on FullAuthPolicy.
- • RequestContext.authenticatedUsers reads that backing set directly (no policy involved).
+ Ownership: auth identity is SCOPE, not POLICY
+ • the engine-owned connection scope holds authenticatedUsers; the policy runs the auth logic but stores nothing.
+ • RequestContext.authenticatedUsers reads that backing set directly; it is one set per RelaySession (per connection).
WRITE path (green): engine-side, single commit
- • RelaySession.handleAuth: policy.accept(AuthCmd) verifies → onAuthenticated() returns Boolean →
- only on true does the engine add(pubkey). PassThrough/Empty return false → unverified ids never recorded.
+ • RelaySession.handleAuth: policy.accept(AuthCmd) verifies → onAuthenticated(event): Boolean →
+ only on true does the engine add(event.pubKey). PassThrough/Empty return false → unverified ids never recorded.
READ path (teal): policy reads, source reads
• gating policies read scope.authenticatedUsers (scope injected via onConnect(scope, send)).
• EventSource reads ctx.authenticatedUsers for caller-relative results.
- Deleted
- • AuthScopedPolicy marker • PolicyStack.authenticatedUsers union • RequestContext `as? AuthScopedPolicy` downcast
+ One place, by design
+ • auth state lives only in the scope — no AuthScopedPolicy marker, no PolicyStack union, no RequestContext downcast.
- Signature changes (source-breaking, in-tree SPI)
- • IRelayPolicy.onConnect(send) → onConnect(scope, send) • IRelayPolicy.onAuthenticated(...) → returns Boolean
+ Policy contract
+ • IRelayPolicy.onConnect(scope, send) • IRelayPolicy.onAuthenticated(event): Boolean
diff --git a/quartz/plans/auth-scope-relay-components.png b/quartz/plans/auth-scope-relay-components.png
index bfae704c6e..0972cd0f34 100644
Binary files a/quartz/plans/auth-scope-relay-components.png and b/quartz/plans/auth-scope-relay-components.png differ