docs(architecture): add ADR-024 — operation registry layering, resolve C6
Diagnoses a conflation in the pre-ADR-024 spec: the OperationRegistry inherited immutability by analogy from ADR-010's HandlerRegistry (ALPN-level), but the TLS-config argument that justifies HandlerRegistry immutability does not apply to the operation registry, which lives behind a single ALPN (alknet/call). This made from_call (which discovers ops over a live connection at runtime) structurally incompatible with the blanket immutability claim. ADR-024 layers the operation registry by trust boundary: curated (Local) ops are static and immutable — the startup trust boundary is where their composition authority is granted; session (Session) and imported (FromCall etc.) ops are dynamic at their respective scopes (per-session, per-connection) — their trust boundaries are per-scope, not per-startup. The principle: immutability follows the trust boundary. Immutability is the security control for composing ops (can escalate privilege); provenance + composition authority are the controls for non-composing ops (can't escalate). The OperationEnv trait becomes the integration point (Arc<dyn OperationEnv>), following the IdentityProvider precedent (ADR-004): the CallAdapter composes the root OperationContext.env per incoming call from the active layers (curated base + connection overlay + session overlay). Children inherit the parent's composite env by Arc::clone — overlay composition happens once at the root and propagates through the composition tree. Resolves review #002 C6 (OperationContext.env type identity crisis): the field is split into scoped_env: ScopedOperationEnv (reachability data, from the registration bundle) and env: Arc<dyn OperationEnv + Send + Sync> (dispatch trait object). One field was being used as two different types (reachability set with .allows() and dispatch trait with .invoke()); Localizes W4 (hot-swap ↔ registry mutability coupling) to the connection scope: no global mutable registry to hot-swap; overlays replace naturally with connect/disconnect and session start/end. Schema-drift on reconnect is a per-connection overlay-rebuild concern, not a global hot-swap protocol. Partially addresses W3 (CallClient registry security): the registry-shape sub-question is resolved by the overlay model; the capability-exposure sub-question (what capabilities a remote peer can trigger) remains for ADR-017 — ADR-024 does not overclaim resolution there. Amends OQ-04 to scope its immutability claim to the HandlerRegistry and cross-reference ADR-024 for the operation registry. Generalizes OQ-19's session-overlay mechanism to also cover connection-scoped remote imports — both are per-scope dynamic overlays on the static curated base, using the same trait-layering mechanism.
This commit is contained in:
@@ -319,8 +319,12 @@ fn build_root_context(
|
||||
handler_identity: registration.composition_authority, // C1: from bundle, None for leaves
|
||||
capabilities: registration.capabilities.clone(), // C3: from bundle
|
||||
metadata: HashMap::new(),
|
||||
env: registration.scoped_env.clone()
|
||||
// env/scoped_env split by ADR-024: scoped_env is the reachability
|
||||
// data (from the bundle), env is the dispatch trait object (composed
|
||||
// per-call by the CallAdapter from active overlays).
|
||||
scoped_env: registration.scoped_env.clone()
|
||||
.unwrap_or_else(ScopedOperationEnv::empty), // C2: from bundle, empty for leaves
|
||||
env: /* CallAdapter.compose_root_env(...) — see ADR-024 */,
|
||||
internal: false, // wire call — ACL against caller identity
|
||||
}
|
||||
}
|
||||
@@ -339,7 +343,9 @@ async fn invoke(&self, namespace: &str, operation: &str, input: Value,
|
||||
|
||||
// Reachability check (C2): is this op in the parent's scoped env?
|
||||
// If not, return NOT_FOUND. This is the reachability control.
|
||||
if !parent.env.allows(&name) {
|
||||
// (ADR-024: the reachability check consults parent.scoped_env, not
|
||||
// parent.env — env is now the dispatch trait, scoped_env is the data.)
|
||||
if !parent.scoped_env.allows(&name) {
|
||||
return ResponseEnvelope::not_found(name);
|
||||
}
|
||||
|
||||
@@ -351,8 +357,10 @@ async fn invoke(&self, namespace: &str, operation: &str, input: Value,
|
||||
handler_identity: registration.composition_authority.clone(), // C1: child's own authority
|
||||
capabilities: parent.capabilities.clone(), // C3: propagate through composition
|
||||
metadata: HashMap::new(), // fresh — does NOT propagate (ADR-014)
|
||||
env: registration.scoped_env.clone()
|
||||
// env/scoped_env split by ADR-024:
|
||||
scoped_env: registration.scoped_env.clone()
|
||||
.unwrap_or_else(ScopedOperationEnv::empty), // C2: child's own scoped env
|
||||
env: parent.env.clone(), // child inherits parent's composite env (Arc::clone)
|
||||
internal: true, // composition — ACL against handler_identity
|
||||
};
|
||||
self.registry.invoke(&name, input, context).await
|
||||
@@ -580,16 +588,27 @@ the fuzzer validates the implementation against the spec.
|
||||
cascade tree; `parent_request_id` indexes it)
|
||||
- ADR-017: Call protocol client and adapter contract (adapter-registered
|
||||
ops are `Internal` by default; this ADR's provenance makes that explicit)
|
||||
- ADR-024: Operation registry layering (amends this ADR's Decision 5: the
|
||||
`env` field shown in `build_root_context` and `invoke()` is split into
|
||||
`scoped_env: ScopedOperationEnv` (reachability data, populated from the
|
||||
bundle's `scoped_env`) and `env: Arc<dyn OperationEnv + Send + Sync>`
|
||||
(dispatch trait object). The split is required by ADR-024's overlay model
|
||||
— the trait-object design is what enables connection and session overlays
|
||||
to compose. The `HandlerRegistration` bundle shape, provenance model,
|
||||
composition authority, and capability injection specified by this ADR
|
||||
are unchanged.)
|
||||
- ADR-008: Vault integration point (assembly layer is the trust boundary)
|
||||
- OQ-19: Session-scoped operation registries (session ops are `Session`
|
||||
provenance, always `Internal`, compose under restricted authority)
|
||||
- docs/reviews/001-pre-implementation-architecture-sanity-check.md (findings
|
||||
C1–C4, which this ADR resolves)
|
||||
- docs/reviews/002-pre-implementation-architecture-sanity-check.md (finding
|
||||
C6, resolved by ADR-024's `env`/`scoped_env` split)
|
||||
- `/workspace/@alkdev/flowgraph/README.md` — operation graph, call graph, and
|
||||
scoped subgraph concepts (the graph model this ADR uses as framing)
|
||||
- `/workspace/@alkdev/alknet-main/docs/architecture/flowgraph.md` — prior
|
||||
Rust speccing of flowgraph (incomplete; this ADR uses the model, not the
|
||||
crate)
|
||||
- Kernel/user mode analogy: `getaddrinfo` runs under kernel authority, not
|
||||
the caller's `CAP_NET_RAW`; the curated entry point exists to do things the
|
||||
user can't, on the user's behalf
|
||||
the caller's `CAP_NET_RAW`; the curated entry point exists to do things
|
||||
the user can't, on the user's behalf
|
||||
Reference in New Issue
Block a user