The monolithic open-questions.md (1310 lines, 47 OQs) was large enough to be unmanageable, with high size variance (OQ-42 at 220 lines next to OQ-06 at 8). Decomposed into one file per OQ under docs/architecture/questions/ (NNN-slug.md, mirroring the ADR convention), with open-questions.md retained as the index: theme-grouped tables plus a cross-theme Deferred/Blocked section that surfaces the 6 deferred OQs with their Blocked-on conditions inline (the safe-exit visibility surface). Per-OQ content moved verbatim; all 62 inbound links stay valid (none used anchors). README's curated OQ summary dropped (now redundant with the index tables). Also seeds tasks/architecture/ with this task plus two follow-ups found during the decompose: OQ-09/10 missing structured Blocked-on fields, and the tasks/architecture/ blocker-task half of the Safe Exit protocol being unenforced.
3.3 KiB
OQ-11: Handler-Level Auth Resolution Observability
-
Origin: auth.md
-
Status: resolved
-
Door type: Two-way
-
Priority: medium
-
Resolution: Option B — handlers store resolved identity on the Connection. When a handler resolves identity inside
handle()(the handler-level auth phase), it callsconnection.set_identity(identity)to store the resolvedIdentityon the connection object. The endpoint and observability layers can read it later for connection logging, audit trails, and metrics.Why not Option A (return identity from
handle()): it changes theProtocolHandlertrait signature for all handlers, even those that don't do auth resolution (DNS, health check). It also assumes one identity per connection — but the call protocol can have different identities per request on the same connection (one connection, multiplecall.requestedevents with different auth tokens). Returning a single identity fromhandle()would be misleading for the call protocol.Why not Option C (identity stays local): the resolved identity is useful beyond the handler. The endpoint may want to log "connection from X authenticated as Y." A connection-level observability layer needs the identity. If it stays local, every handler that resolves identity would need to duplicate logging logic, and the endpoint can't correlate connections to identities.
Two identity scopes exist and must not be conflated:
- Connection-level identity (this decision): set once by the handler in
handle(), stored onConnection, read by the endpoint for logging/observability. This is the "connection owner" — who opened this QUIC connection. - Per-request identity (already in the call protocol spec): set per
call.requestedby theCallAdapter, stored onOperationContext.identity. This is the "call caller" — who is making this specific call, which may upgrade mid-session (different auth tokens on the same connection).
Both exist. The connection-level identity is the stable "who is this connection from"; the per-request identity is the dynamic "who is this specific call from." The call protocol's per-request resolution (which may produce a different identity than the connection-level resolution) takes precedence for ACL on
OperationContext— the connection-level identity is for observability only, not for ACL.C13 resolution (review #002): the endpoint does not read
identity()afterhandle()returns. TheConnectionis moved into the spawned handler task (endpoint.md), so the endpoint no longer has a reference to it. Connection-level observability (remote addr, ALPN, connection ID) is logged by the endpoint before the move. Identity-level observability is logged by the handler (the handler knows which identity it resolved and can log it). There is noArc<Connection>sharing or channel-based identity-reporting mechanism — the simplest honest answer that avoids over-engineering the observability path before there's a demonstrated need. If a future use case requires the endpoint to correlate connections to identities, anArc<Connection>or a side-channel can be added then. - Connection-level identity (this decision): set once by the handler in
-
Cross-references: ADR-004, ADR-011, ADR-015 (per-request identity on OperationContext), auth.md