Feat: Human-in-the-loop effect for APL and elicitation plugin#115
Draft
terylt wants to merge 3 commits into
Draft
Feat: Human-in-the-loop effect for APL and elicitation plugin#115terylt wants to merge 3 commits into
terylt wants to merge 3 commits into
Conversation
Signed-off-by: Teryl Taylor <terylt@ibm.com>
Signed-off-by: Teryl Taylor <terylt@ibm.com>
Signed-off-by: Teryl Taylor <terylt@ibm.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds human-in-the-loop (HIL) elicitation to APL: a policy can pause an operation to ask a human — manager approval, a confirm, a step-up re-auth, an attestation — and resume once the human responds, without blocking the request path. The human's decision is bound to the actual request args and recorded from CPEX-owned state, so it is the source of truth for "what was approved," never an LLM summary. Includes a working Keycloak CIBA channel plugin.
A policy expresses it with a sugar verb that parses to a single
Step::Elicit:fromis who to ask (approver, resolved from the bag — may differ from the subject);scopeis the args-binding the runtime checks against the live request;purposeis the audited, human-readable description.The model: async, retry-based resume
An elicitation has three short, synchronous touch-points — the hours-long human gap lives in the channel (Keycloak CIBA), never in a blocking call:
Pending/Resolved{approved|denied}/Expired) without blocking.scope-over-args sufficiency check before honoring the approval.While pending, the phase suspends rather than denies.
Decisionstays binary — a suspended phase reportsAllow+Some(PendingElicitation), and the host maps that to JSON-RPC-32120("not complete, retry echoing this id"). The forwarding rule stays one clause: forward iffAllowANDpending.is_none(). Expiry / channel error fails closed (on_error: denydefault).What's included
apl-core)require_approval/confirm/step_up/attestation/info/review— sugar over oneStep::Elicit(ElicitStep)with anElicitKind. Fields:from,purpose,scope,timeout,channel,config_override,on_error. Parser + evaluator (dispatch/check/validate flow, pending short-circuit,scope-over-args) +route.rsresume wiring.cpex-core)ElicitationHook(single hook name"elicit") +ElicitationPayloadwith aDispatch/Check/Validateoperation discriminator — mirrors the delegation hook layout; dispatch is free viainvoke_entries::<ElicitationHook>. cpex-core keeps its ownElicitationOp/ElicitationStatusKind/ElicitationOutcomeKind(no apl-core dep); the bridge maps.apl-cpex)ElicitationInvoker(dispatch/check/validate) +elicitation_invoker.rs; dispatch-plan resolves anelicitentry by name;route_handlerdrives the pending/resume flow and surfaceselicitation.*bag attrs (id,approver,intent_id,channel) for audit.builtins/plugins/elicitation-ciba, new crate)kind: elicitation/ciba— aHookHandler<ElicitationHook>implementing the three operations against Keycloak CIBA:from→login_hint,purpose→binding_message,timeout→requested_expiry. Intent store, config, factory. Registered viacpex-builtins.scopeargs.amount <= 25000) the runtime evaluates at validate — kept in APL because Keycloak has no RFC 9396 RAR.Notable design decisions
fromresolves the party to ask from the request bag (e.g.claim.manager); the resolved identity is cross-checked against the actual responder at validate.scopeover live args) stays in the runtime because it is an APL expression the plugin can't evaluate.elicithook + payload discriminator (not three hook names) keeps registration and the dispatch plan trivial — one plugin, one entry, resolved by name liketoken.delegate.Decision.PendingElicitationrides alongside anAllowso the deny path stays untouched; the host's one-clause forward rule and-32120retry contract carry the suspend/resume.on_error).ElicitKind/ElicitationStatus/ElicitationOutcome.Public API surface
cpex-core::elicitation:ElicitationHook,HOOK_ELICIT("elicit"),ElicitationPayload,ElicitationOp,ElicitationStatusKind,ElicitationOutcomeKind.apl-core::step:Step::Elicit,ElicitStep,ElicitKind,ElicitationInvoker(dispatch/check/validate),ElicitationDispatch,ElicitationStatus,ElicitationOutcome,ElicitationValidation,PendingElicitation,ElicitationError.apl-cpex:ElicitationInvokerbridge, dispatch-planelicitation_entries, route-handler pending/resume wiring.builtins/plugins/elicitation-ciba:kind = "elicitation/ciba"(CibaApproverFactory), registered throughcpex-builtins.Testing
builtins/plugins/elicitation-ciba/tests/ciba_e2e.rs(7 tests) +live_keycloak.rs(1 live integration test, ignored by default).tests/elicit_step_e2e.rs: end-to-end through a realPluginManager(dispatch → pending → resolve → validate; approve / deny / expire; args-binding). Existing suites (delegate_step_e2e,end_to_end_route,cmf_invoker_dispatch) updated for the new step/plan shape.step.rs/route.rsunit tests;tests/yaml_end_to_end.rsupdated for the elicit verbs;apl-cmf/tests/end_to_end.rstouched.Follow-ups
docs/content/docs/apl/(none in this PR).elicithook; richer per-kind validation contracts.