docs(architecture): resolve OQ-11 and OQ-19 — all open questions resolved
OQ-11 (handler-level auth observability): Option B — handlers store resolved identity on Connection via set_identity. Two identity scopes: connection-level (observability, write-once-read-many) and per-request (ACL, on OperationContext). Per-request takes precedence for ACL; connection-level is for logging/audit only. OQ-19 (session-scoped registries): Protocol doesn't need changes. OperationEnv must remain a trait (not concrete) to enable session-overlay pattern. Three-tier registry: core (static, External+Internal), session (dynamic, Internal-only), promotion (curated review). Documented as implementation guard in operation-registry.md. All 19 open questions are now resolved. No open one-way or two-way doors remain. The architecture is ready for review and implementation.
This commit is contained in:
@@ -41,7 +41,7 @@ Structured RPC over QUIC: operations, request/response, streaming subscriptions,
|
||||
| OQ-13 | Operation path format and routing scope | resolved | `/{service}/{op}` is the correct design; remote dispatch is a separate layer |
|
||||
| OQ-14 | Batch operation semantics | resolved | Correlated `call.requested` events is the correct protocol design |
|
||||
| OQ-16 | Safe vault operations for call protocol exposure | resolved (ADR-014) | None exposed for now |
|
||||
| OQ-19 | Session-scoped operation registries | open | Agent-written operations overlaid on global registry via `OperationEnv` trait layering. Protocol doesn't need changes; one-way door is not closing the trait-based composition point |
|
||||
| OQ-19 | Session-scoped operation registries | resolved | Agent-written operations overlaid on global registry via `OperationEnv` trait layering. Protocol doesn't need changes; `OperationEnv` must remain a trait |
|
||||
|
||||
## Key Design Principles
|
||||
|
||||
|
||||
@@ -312,7 +312,7 @@ See [open-questions.md](../../open-questions.md) for full details.
|
||||
- **OQ-13** (resolved): Operation path format is `/{service}/{op}`. Remote dispatch is a separate mechanism, not a path prefix.
|
||||
- **OQ-14** (resolved): Batch is a client-side pattern of correlated `call.requested` events, not a protocol primitive.
|
||||
- **OQ-16** (resolved by ADR-014): No vault operations are exposed over the call protocol for now.
|
||||
- **OQ-19** (open): Session-scoped operation registries — agent-written operations overlaid on global registry via `OperationEnv` trait layering. Protocol doesn't need changes.
|
||||
- **OQ-19** (resolved): Session-scoped operation registries — agent-written operations overlaid on global registry via `OperationEnv` trait layering. Protocol doesn't need changes; `OperationEnv` must remain a trait.
|
||||
|
||||
## References
|
||||
|
||||
|
||||
@@ -188,6 +188,8 @@ impl OperationEnv for LocalOperationEnv {
|
||||
|
||||
Future work may add irpc service dispatch and remote call protocol dispatch as additional backends. The handler-facing API stays the same.
|
||||
|
||||
**`OperationEnv` must remain a trait.** This is a constraint, not a suggestion. The trait-based design enables session-scoped registries (OQ-19) — a session env wraps the global env (check session registry first, fall through to global). Making `OperationEnv` concrete or hardcoding the global registry into the dispatch path would close the session-overlay pattern. See OQ-19.
|
||||
|
||||
### Service Discovery
|
||||
|
||||
Two built-in operations expose what the node offers:
|
||||
@@ -324,7 +326,7 @@ See [open-questions.md](../../open-questions.md) for full details.
|
||||
- **OQ-13** (resolved): Operation path format is `/{service}/{op}`. Remote dispatch is a separate mechanism, not a path prefix.
|
||||
- **OQ-14** (resolved): Batch is a client-side pattern of correlated `call.requested` events, not a protocol primitive.
|
||||
- **OQ-16** (resolved by ADR-014): No vault operations are exposed over the call protocol for now.
|
||||
- **OQ-19** (open): Session-scoped operation registries — agent-written operations overlaid on the global registry via `OperationEnv` trait layering. Protocol doesn't need changes; one-way door is not closing the trait-based composition point.
|
||||
- **OQ-19** (resolved): Session-scoped operation registries — agent-written operations overlaid on the global registry via `OperationEnv` trait layering. Protocol doesn't need changes; `OperationEnv` must remain a trait.
|
||||
|
||||
## References
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
status: draft
|
||||
last_updated: 2026-06-16
|
||||
last_updated: 2026-06-21
|
||||
---
|
||||
|
||||
# alknet-core
|
||||
@@ -36,7 +36,7 @@ Core library for ALPN-based protocol dispatch. Every handler crate depends on al
|
||||
|----|-------|--------|-----------|
|
||||
| OQ-04 | Dynamic handler registration | resolved (start static) | HandlerRegistry is immutable at startup |
|
||||
| OQ-05 | Multi-connectivity endpoint | resolved (quinn + iroh) | AlknetEndpoint supports both, both feature-gated |
|
||||
| OQ-11 | AuthContext resolution completeness | open | How handlers signal auth completion |
|
||||
| OQ-11 | Handler-level auth resolution observability | resolved | Handlers store resolved identity on Connection; two identity scopes (connection-level for observability, per-request for ACL) |
|
||||
|
||||
## Key Design Principles
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
status: draft
|
||||
last_updated: 2026-06-16
|
||||
last_updated: 2026-06-21
|
||||
---
|
||||
|
||||
# Authentication
|
||||
@@ -44,7 +44,7 @@ The endpoint constructs `AuthContext` from the QUIC connection:
|
||||
|
||||
### Handler-level resolution
|
||||
|
||||
Handlers that require authentication extract protocol-specific credentials and call `IdentityProvider` inside `handle()`:
|
||||
Handlers that require authentication extract protocol-specific credentials and call `IdentityProvider` inside `handle()`. When identity is resolved, the handler stores it on the `Connection` for observability:
|
||||
|
||||
```rust
|
||||
// Example: CallAdapter extracting an AuthToken from the first frame
|
||||
@@ -59,11 +59,25 @@ async fn handle(&self, connection: Connection, auth: &AuthContext) -> Result<(),
|
||||
.ok_or(HandlerError::AuthRequired)?
|
||||
}
|
||||
};
|
||||
connection.set_identity(identity); // Store for observability (OQ-11)
|
||||
// ... proceed with authenticated identity
|
||||
}
|
||||
```
|
||||
|
||||
Handlers that don't require authentication (e.g., DNS resolver, health check) can ignore `auth.identity` entirely.
|
||||
Handlers that don't require authentication (e.g., DNS resolver, health check) can ignore `auth.identity` entirely and don't call `set_identity`.
|
||||
|
||||
### Two Identity Scopes
|
||||
|
||||
There are two distinct identity scopes that must not be conflated:
|
||||
|
||||
| Scope | Where it's set | Where it's stored | What it represents | Used for |
|
||||
|-------|---------------|-------------------|-------------------|----------|
|
||||
| Connection-level | Handler in `handle()` | `Connection` (via `set_identity`) | Who opened this QUIC connection | Observability, logging, audit |
|
||||
| Per-request | `CallAdapter` per `call.requested` | `OperationContext.identity` | Who is making this specific call | ACL (ADR-015) |
|
||||
|
||||
The connection-level identity is stable — set once when the handler resolves it. The per-request identity is dynamic — resolved per `call.requested`, potentially different across requests on the same connection (if different auth tokens are used). The per-request identity takes precedence for ACL on `OperationContext`; the connection-level identity is for observability only, not for ACL.
|
||||
|
||||
`Connection` exposes `set_identity` via interior mutability — the handler sets it once when resolved, the endpoint and observability layers read it. The identity is write-once-read-many.
|
||||
|
||||
### AuthContext is Clone and immutable
|
||||
|
||||
@@ -231,7 +245,8 @@ The endpoint's `AlknetEndpoint` also holds `Arc<dyn IdentityProvider>` for endpo
|
||||
| AuthContext with optional Identity | [ADR-011](../../decisions/011-authcontext-structure.md) | Explicit None, not "partially authenticated" |
|
||||
| AuthContext is immutable in handle() | [ADR-011](../../decisions/011-authcontext-structure.md) | Handlers create local variables for resolved identity |
|
||||
| Two resolution paths | [ADR-004](../../decisions/004-auth-as-shared-core.md) | Fingerprint and token, not phased auth |
|
||||
| Handler stores resolved identity on Connection | OQ-11 (resolved) | `connection.set_identity()` — write-once-read-many for observability |
|
||||
|
||||
## Open Questions
|
||||
|
||||
- **OQ-11**: See [open-questions.md](../../open-questions.md) — handler-level auth resolution observability.
|
||||
None. All auth-related open questions are resolved.
|
||||
Reference in New Issue
Block a user