pub trait ElicitationInvoker: Send + Sync {
// Required methods
fn dispatch<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
step: &'life1 ElicitStep,
resolved_from: &'life2 str,
) -> Pin<Box<dyn Future<Output = Result<ElicitationDispatch, ElicitationError>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait;
fn check<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
step: &'life1 ElicitStep,
id: &'life2 str,
) -> Pin<Box<dyn Future<Output = Result<ElicitationStatus, ElicitationError>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait;
fn validate<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
step: &'life1 ElicitStep,
id: &'life2 str,
) -> Pin<Box<dyn Future<Output = Result<ElicitationValidation, ElicitationError>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait;
}Expand description
Elicitation dispatch — drives a human-in-the-loop step (approval,
confirmation, step-up, …) through a channel plugin. apl-cpex
implements this against the named ElicitationHandler plugin
(step.plugin_name, resolved name → entry like delegation); tests
and un-wired hosts pass NoopElicitationInvoker.
Three short, synchronous touchpoints span the human’s (possibly hours-long) decision. The wait itself lives in the channel (e.g. Keycloak CIBA), never inside a trait call:
dispatch— once, on the first request that reaches the step: register the intent, open the backchannel, and return the id the agent echoes on retry.check— on every retry: read the current status (pending / resolved / expired) without blocking.validate— once status is resolved: confirm the response is genuine (signature, intent binding, responder identity). The sufficiency check —ElicitStep::scopeagainst the live request args — is the runtime’s job, not the plugin’s, becausescopeis an APL expression the plugin cannot evaluate.
Like DelegationInvoker, the invoker holds the request-scoped
Extensions internally, so the trait methods take only the step / id
and never the request context.
Required Methods§
Sourcefn dispatch<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
step: &'life1 ElicitStep,
resolved_from: &'life2 str,
) -> Pin<Box<dyn Future<Output = Result<ElicitationDispatch, ElicitationError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
fn dispatch<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
step: &'life1 ElicitStep,
resolved_from: &'life2 str,
) -> Pin<Box<dyn Future<Output = Result<ElicitationDispatch, ElicitationError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
First arrival. Register the intent and open the channel
backchannel for step, returning the correlation id plus the
pending metadata the evaluator writes into the bag
(elicitation.id / .approver / .intent_id). Short and
synchronous — the human’s decision happens after this returns,
inside the channel.
resolved_from is step.from already resolved against the request
bag by the runtime (e.g. claim.manager → the manager’s actual
identity), or the literal step.from when it isn’t a bag key. The
attribute vocabulary lives in the runtime, so the invoker receives
the resolved identity rather than re-resolving it — for CIBA this
becomes the login_hint.
Sourcefn check<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
step: &'life1 ElicitStep,
id: &'life2 str,
) -> Pin<Box<dyn Future<Output = Result<ElicitationStatus, ElicitationError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
fn check<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
step: &'life1 ElicitStep,
id: &'life2 str,
) -> Pin<Box<dyn Future<Output = Result<ElicitationStatus, ElicitationError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Retry. Read the current status of a dispatched elicitation by
id without blocking — Pending until the human acts, then
Resolved (carrying approved/denied) or Expired. step is
passed (the same step that dispatched) so the invoker can resolve
which handler plugin owns this elicitation — on a retry only the
id is in the bag, but the step is still in scope.
Sourcefn validate<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
step: &'life1 ElicitStep,
id: &'life2 str,
) -> Pin<Box<dyn Future<Output = Result<ElicitationValidation, ElicitationError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
fn validate<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
step: &'life1 ElicitStep,
id: &'life2 str,
) -> Pin<Box<dyn Future<Output = Result<ElicitationValidation, ElicitationError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Resolution. Verify that the resolved response is genuine — the
signed token validates, its intent binding matches this id, and
the responder is the resolved approver. Returns the verdict plus
the facts the evaluator records for audit. The runtime applies the
scope-over-args check separately before honoring an approval.
step resolves the owning handler plugin (see check).
Dyn Compatibility§
This trait is dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety".