Files
alknet/tasks/call/registry/handler-registration.md
glm-5.2 098fd8b9b9 tasks: decompose vault, core, call crates into 28 atomic implementation tasks
Break down the three initial crates (alknet-vault, alknet-core, alknet-call)
into dependency-ordered task files for implementation agents.

Structure:
- tasks/vault/ (10 tasks) — drift fixes from ADR-025/026 refactor, review,
  spec sync. Vault is independent and can run fully in parallel with core/call.
- tasks/core/ (6 tasks) — crate init, core types, config, auth, endpoint,
  review. Core is foundational; call depends on it.
- tasks/call/ (12 tasks) — split into registry/ and protocol/ topic subdirs
  reflecting the two subsystems. CallAdapter is the merge point.

Key decisions:
- Drifts 3+9+10 grouped as one task (key-versioning-rotation) — the complete
  ADR-021 rotation feature that doesn't compile in pieces
- Reviews injected at end of each crate phase (vault, core, call)
- Vault spec-sync task removes the drift table and bumps doc status to stable
- ACME deferred in core/endpoint (noted as TODO; X509 manual certs for now)
- OperationEnv kept as a trait (load-bearing for ADR-024 layering)

Validated: 28 tasks, no cycles, 11 generations of parallel work.
Critical path runs through call (11 tasks). Vault completes by generation 4.
6 high-risk tasks identified (21%): irpc-removal, endpoint, operation-context,
operation-env, call-adapter, abort-cascade.
2026-06-23 12:41:47 +00:00

7.7 KiB

id, name, status, depends_on, scope, risk, impact, level
id name status depends_on scope risk impact level
call/registry/handler-registration Implement Handler, HandlerRegistration, OperationProvenance, OperationRegistry, and OperationRegistryBuilder pending
call/registry/operation-context
broad medium component implementation

Description

Implement the handler registration types and the operation registry in src/registry/registration.rs. The registry maps operation names to registration bundles and provides the dispatch entry point.

Handler

pub type Handler = Arc<
    dyn Fn(Value, OperationContext) -> Pin<Box<dyn Future<Output = ResponseEnvelope> + Send>>
        + Send + Sync
>;

Handlers are async. They receive:

  • input: Value — deserialized payload from call.requested (always serde_json::Value)
  • context: OperationContext — request ID, identity, metadata, env

And return ResponseEnvelope (defined in protocol/wire task — use a forward reference or define a minimal version here, full impl in the wire task).

HandlerRegistration

pub struct HandlerRegistration {
    pub spec: OperationSpec,
    pub handler: Handler,
    pub provenance: OperationProvenance,
    pub composition_authority: Option<CompositionAuthority>, // None for leaves
    pub scoped_env: Option<ScopedOperationEnv>,               // None for leaves
    pub capabilities: Capabilities,
}

The registration bundle carries everything the dispatch path needs to construct an OperationContext. See ADR-022.

OperationProvenance

pub enum OperationProvenance {
    Local,           // Assembly-written, trusted, can compose
    FromOpenAPI,     // HTTP forwarding stub, leaf
    FromMCP,         // MCP forwarding stub, leaf
    FromCall,        // QUIC forwarding stub, leaf locally
    FromJsonSchema,  // JSON Schema definition, no handler — schema only
    Session,         // Agent-written, sandboxed, can compose within sandbox
}
Provenance Can compose? Has composition authority? Default visibility
Local Yes Yes External or Internal (assembly declares)
FromOpenAPI No (leaf) No Internal
FromMCP No (leaf) No Internal
FromCall No (leaf in local registry) No Internal
FromJsonSchema N/A (no handler) No N/A
Session Yes (within sandbox) Yes Internal always

OperationRegistry

pub struct OperationRegistry {
    operations: HashMap<String, HandlerRegistration>,
}

The curated layer (Layer 0) is a HashMap<String, HandlerRegistration>. Session and connection overlays (Layers 1 and 2) are separate maps composed into the per-call OperationContext.env by the CallAdapter (ADR-024).

Methods:

  • register(registration): add to curated layer at startup
  • registration(name): find by operation name (checks active overlays first, then curated base — ADR-024). Returns spec, handler, provenance, composition authority, scoped env, capabilities.
  • invoke(name, input, context): look up, check ACL, invoke handler, return result
  • list_operations(): return all registered specs (for /services/list — returns curated + active overlay ops, External only)

OperationRegistryBuilder

Fluent API with convenience methods:

pub struct OperationRegistryBuilder {
    operations: HashMap<String, HandlerRegistration>,
}

impl OperationRegistryBuilder {
    pub fn new() -> Self;

    // with_local: Local provenance, full bundle — all 5 args required
    pub fn with_local(
        mut self,
        spec: OperationSpec,
        handler: Handler,
        composition_authority: Option<CompositionAuthority>,
        scoped_env: Option<ScopedOperationEnv>,
        capabilities: Capabilities,
    ) -> Self;

    // with_leaf: leaf provenance (FromOpenAPI/FromMCP/FromCall), no authority, no scoped env
    pub fn with_leaf(
        mut self,
        spec: OperationSpec,
        handler: Handler,
        capabilities: Capabilities,
    ) -> Self;

    // with: full manual registration (any provenance)
    pub fn with(mut self, registration: HandlerRegistration) -> Self;

    pub fn build(self) -> OperationRegistry;
}

with_local sets provenance: Local. with_leaf sets provenance: FromOpenAPI (or a parameter), composition_authority: None, scoped_env: None. with takes the full bundle for any provenance.

Registry invoke flow

impl OperationRegistry {
    pub async fn invoke(&self, name: &str, input: Value, context: OperationContext) -> ResponseEnvelope {
        // 1. Look up registration by name
        // 2. Check visibility: if Internal and context is external (internal: false), return NOT_FOUND
        // 3. Check ACL: access_control.check(identity or handler_identity depending on internal flag)
        // 4. If denied: return FORBIDDEN
        // 5. Invoke handler: (handler)(input, context).await
        // 6. Return ResponseEnvelope
    }
}

The ACL authority depends on context.internal:

  • internal: false (wire call): check against context.identity (caller)
  • internal: true (composition): check against context.handler_identity.as_identity()

Layer 0 immutability

The curated layer (Layer 0 — Local provenance ops) is immutable after construction. Adding a Local op requires restarting the process. Session and imported overlays are dynamic at their respective scopes (ADR-024). The OperationRegistryBuilder is Layer-0-only; runtime overlay registration uses CallConnection::register_imported() (in the protocol/connection task).

Acceptance Criteria

  • Handler type alias (async closure returning ResponseEnvelope)
  • HandlerRegistration struct with all 6 fields
  • OperationProvenance enum with all 6 variants
  • OperationRegistry struct with operations HashMap
  • OperationRegistry::register() adds to curated layer
  • OperationRegistry::registration() looks up by name
  • OperationRegistry::invoke() checks visibility, ACL, invokes handler
  • OperationRegistry::list_operations() returns External specs only
  • OperationRegistryBuilder with new(), with_local(), with_leaf(), with(), build()
  • with_local sets provenance Local, requires all 5 args
  • with_leaf sets provenance leaf, composition_authority None, scoped_env None
  • invoke: Internal op called externally → NOT_FOUND (not FORBIDDEN)
  • invoke: ACL denied → FORBIDDEN
  • invoke: internal: true → ACL against handler_identity, not identity
  • invoke: internal: false → ACL against identity
  • Unit test: register and invoke a simple operation
  • Unit test: Internal op returns NOT_FOUND from external call
  • Unit test: ACL check with sufficient scopes → Allowed
  • Unit test: ACL check with insufficient scopes → Forbidden
  • Unit test: builder with_local and with_leaf produce correct provenance
  • cargo test -p alknet-call succeeds
  • cargo clippy -p alknet-call succeeds with no warnings

References

  • docs/architecture/crates/call/operation-registry.md — Handler, HandlerRegistration, OperationRegistry, builder
  • docs/architecture/decisions/022-handler-registration-provenance-and-composition-authority.md — ADR-022
  • docs/architecture/decisions/024-operation-registry-layering.md — ADR-024 (layering, immutability)

Notes

The registry is the dispatch core. The ACL authority switch (internal: true → handler_identity, internal: false → identity) is the ADR-015 privilege model — get this right. Internal ops return NOT_FOUND from the wire (don't leak existence), not FORBIDDEN. The builder is Layer-0-only; runtime overlay registration is via CallConnection (protocol task).

Summary

To be filled on completion