Skip to main content

ElicitationInvoker

Trait ElicitationInvoker 

Source
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::scope against the live request args — is the runtime’s job, not the plugin’s, because scope is 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§

Source

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.

Source

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.

Source

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".

Implementors§