cpex_core/identity/hook.rs
1// Location: ./crates/cpex-core/src/identity/hook.rs
2// Copyright 2025
3// SPDX-License-Identifier: Apache-2.0
4// Authors: Teryl Taylor
5//
6// `IdentityHook` — the `HookTypeDef` marker for the IdentityResolve
7// hook family. Plugins implement `HookHandler<IdentityHook>`; the
8// framework dispatches into them at request entry to populate
9// `Extensions.security.subject` / `.client` / `.caller_workload` /
10// `Extensions.raw_credentials` before any tool / resource / prompt
11// hook runs.
12//
13// # Single hook name (for now)
14//
15// v0 registers under the single name `identity.resolve`. If a future
16// slice introduces an `identity.validate` phase that uses the same
17// payload + result shape (e.g. a post-resolve consistency check),
18// it can share `IdentityHook` and register under `identity.validate`
19// via the multi-name registration path — same pattern as CMF's
20// `cmf.tool_pre_invoke` / `cmf.llm_input` / etc. sharing `CmfHook`.
21// Phases with a different payload shape (e.g. TokenDelegate) get
22// their own hook type rather than reusing this one.
23//
24// # Lifecycle
25//
26// This file defines the *types*. Lifecycle wiring — when the
27// framework calls `invoke_named::<IdentityHook>(...)`, how results
28// merge back into `Extensions` — lands in sub-step B / C of slice 2.
29
30use crate::hooks::trait_def::PluginResult;
31
32use super::payload::IdentityPayload;
33
34/// Primary hook name for IdentityResolve handlers. Used as the
35/// registry key when a host registers the handler via the standard
36/// `register_handler` path.
37pub const HOOK_IDENTITY_RESOLVE: &str = "identity.resolve";
38
39crate::define_hook! {
40 /// Identity-resolve hook.
41 ///
42 /// **Payload** ([`IdentityPayload`]) — unified input + accumulator.
43 /// The host populates the input fields (`raw_token`, `source`,
44 /// `headers`, ...) once at request entry and never touches them
45 /// again; handlers populate the output fields (`subject`,
46 /// `client`, `caller_workload`, `delegation`, `raw_credentials`,
47 /// `rejected`, ...) on clones of the running payload. Input
48 /// fields are private and read through accessors — handlers
49 /// cannot mutate them even on a clone, so the wire-layer input
50 /// is canonical across the whole chain.
51 ///
52 /// **Result** ([`PluginResult<IdentityPayload>`][PluginResult]) —
53 /// the executor's standard envelope. `modified_payload` carries
54 /// the updated payload. `continue_processing = false` halts the
55 /// pipeline (set when the handler decides to reject).
56 ///
57 /// **Threading.** Sequential-phase semantics already thread
58 /// handler N's `modified_payload` into handler N+1's input, so
59 /// the chain's natural behavior is "each handler sees the prior
60 /// handler's contributions in the running payload." No bespoke
61 /// `resolve_identity` method on `PluginManager` — the standard
62 /// `invoke_named::<IdentityHook>(...)` does the right thing.
63 ///
64 /// **Handler signature:**
65 ///
66 /// ```rust,ignore
67 /// impl HookHandler<IdentityHook> for MyResolver {
68 /// async fn handle(
69 /// &self,
70 /// payload: &IdentityPayload,
71 /// _extensions: &Extensions,
72 /// _ctx: &mut PluginContext,
73 /// ) -> PluginResult<IdentityPayload> {
74 /// // Validate the raw token, build the SubjectExtension.
75 /// let claims = self.validate(payload.raw_token()).await?;
76 /// let mut updated = payload.clone();
77 /// updated.subject = Some(claims.into_subject());
78 /// PluginResult::modify_payload(updated)
79 /// }
80 /// }
81 /// ```
82 ///
83 /// Handlers that want to layer onto prior state without manually
84 /// preserving every untouched field reach for
85 /// [`IdentityPayload::merge`][merge].
86 ///
87 /// **Registration:** `manager.register_handler::<IdentityHook, _>(plugin, config)`
88 /// against the hook name `"identity.resolve"`. Multiple handlers
89 /// may register; the framework runs them in priority order and
90 /// the Sequential-phase chain accumulates their contributions
91 /// into the running payload.
92 ///
93 /// [merge]: super::payload::IdentityPayload::merge
94 /// [PluginResult]: crate::hooks::trait_def::PluginResult
95 IdentityHook, "identity.resolve" => {
96 payload: IdentityPayload,
97 result: PluginResult<IdentityPayload>,
98 }
99}