Releases: contextforge-org/cpex
Release list
v0.2.2
Added
- Human-in-the-loop (HIL) elicitation for 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. Sugar verbs (
require_approval/confirm/require_step_up/require_attestation/request_info/require_review) desugar to oneStep::Elicit, resolved by name to anElicitationHandlerplugin exactly likedelegate(...). While the human hasn't answered, the phase suspends (DecisionstaysAllowwith a pending bundle) and the host emits JSON-RPC-32120so the agent retries by echoing the elicitation id; expiry, channel error, denial, or a failed validation fail closed (defaulton_error: deny). The approval is bound to the live request args via an APLscope:expression (args.amount <= 25000) the runtime checks at resolution — never an LLM summary. Ships a working Keycloak CIBA channel plugin (kind: elicitation/ciba, incpex-builtinsdefault features). See [Elicitation]({{< relref "/docs/apl/elicitation" >}}). (#115)
Changed
read_headersgranted to every synthetic policy handler. The entity-less HTTP catch-all, per-entity routes (tool/prompt/resource), and defaults are now all granted theread_headerscapability at install time, sohttp.*request attributes (http.method/http.path/http.host/http.scheme/http.request_headers.*) are available to policy evaluation wherever the host attaches anHttpExtension. Previously only theglobalHTTP catch-all had it, so a per-entity rule could not read the HTTP request line. This lets one policy combinehttp.*with entity/args.*predicates in a single evaluation (e.g. an MCP tool route that also gates onhttp.method). It is a no-op for hosts that never populate the HTTP extension — there is nothing to read — andhttp.hostcontinues to be sourced from a validated request authority, never a raw clientHostheader. (#120)- APL order comparisons now coerce numeric-looking strings.
numeric_compareparses string operands asf64for order operators (>,>=,<,<=), soargs.amount > 10000fires when the arg arrives as the string"25000"— as LLM tool arguments routinely do. This affects all order comparisons in the engine, not just elicitationscope:bindings: a comparison that previously returnedfalsebecause one side was a numeric string may now evaluate numerically. Equality (==) is unchanged, and genuinely non-numeric strings still don't order-compare (they yieldfalse, per spec §2.3). (#115)
v0.2.1
Added
- HTTP request-line attributes.
HttpExtensionnow carries optionalmethod/path/host/scheme, surfaced in the APL attribute bag ashttp.method/http.path/http.host/http.schemeso CEL/APL predicates can reason over the HTTP request line. They ride the existingread_headerscapability (thehttpextension slot is gated as a whole).http.hostmust be populated from a validated request authority (e.g. HTTP/2:authority), never a raw clientHostheader, so host-based policy cannot be spoofed. - Custom denial response (
response:block). A route — orglobal— may declare a custom HTTPstatus/body/headersfor its denials via aresponse:block (a sibling ofauthorization:). On a deny, these are carried onPluginViolation.details(http.status/http.body/http.headers) for the host to render; absent, the host default is unchanged. No new APL grammar and no newPluginViolationfields. It is scope-local: aglobalresponse is not inherited by entity routes, and the block warns (inert) atdefaults/ policy-bundle scope. - Entity-less HTTP authorization. The catch-all
globalpolicy now authorizes generic (non-MCP/A2A) HTTP requests that carry no entity, via new reserved coordinates (http/*) and thecmf.http_requesthook. A host firescmf.http_requestwith those coordinates; the globalauthorization(orargs) block is evaluated withread_headersgranted, and a globalresponse:decorates the denial. Fail-closed session-store denials carry the response too. - Python bindings (PyO3). Native
cpexPython package wrapping the cpex-corePluginManager, built with maturin/PyO3. (#70)
Changed
-
BREAKING — APL authz/authn config keys renamed for clarity. The old key names no longer parse; a config using them fails to load with an error naming the replacement (a dropped authorization or authentication block would otherwise fail open, so the rejection is deliberate). Migration:
identity:→authentication:(atglobal, per-route, and policy-group scope)policy:→authorization.pre_invocation:(or flatpre_invocation:)post_policy:→authorization.post_invocation:(or flatpost_invocation:)
The two authorization phases may be written either nested under an
authorization:block or flat directly on the section; the forms are equivalent. The field-pipeline keysargs:/result:are unchanged (they stay aligned with theargs.*/result.*attribute namespaces that predicates and interpolation read). Internal APL IR is unchanged. (#105) -
Canonical APL config shape in docs. All documentation, the README, and the bundled examples now use one canonical shape — no
apl:wrapper, withauthentication:andauthorization:as sibling blocks (pre_invocation:/post_invocation:nested underauthorization:;args:/result:/pdp:/session_store:/response:as siblings). Both theapl:wrapper and the wrapper-free form remain accepted by the parser; this only standardizes the examples authors copy from.
v0.2.0
Added
- CPEX redesign as a Rust framework with Go bindings
- APL (Attribute Policy Language) governance is now bundled into
libcpex_ffi.a. Newcpex_apl_installextern C entry point registers the standard APL plugin/PDP factories (validator/pii-scan,audit/logger,identity/jwt,delegator/oauth,cedar-direct) and installs the APL config visitor on a manager. Call it aftercpex_manager_new_defaultand beforecpex_load_config. Go hosts usePluginManager.EnableAPL(). (#60) - Publish
libcpex_ffi.aas signed GitHub Release artifacts on every semver tag push (linux-amd64-gnu,linux-arm64-gnu,linux-amd64-musl,linux-arm64-musl,darwin-arm64). Cosign keyless signatures + SHA256 checksums; seecrates/cpex-ffi/RELEASE.mdfor the schema and the verify-and-consume recipe. (#60) - FFI ABI versioning:
cpex_ffi_abi_version()extern C accessor exposesFFI_ABI_VERSION. The Go binding checks this ininit()and panics on mismatch. Other language bindings must replicate the check. (#60) - CEL (Common Expression Language) policy decision backend. A new
apl-pdp-celcrate registerskind: cel, letting authors write inline boolean predicates (cel: { expr: ... }) over the common attribute vocabulary (subject.id,delegation.depth,session.labels, ...), evaluated through the existingPdpResolverseam alongside Cedar, OPA, and AuthZen. Expressions compile once and cache by source; compile errors, undeclared-variable references, and non-boolean results fail closed (deny), overridable withon_error: allow. No change to APL evaluation semantics. (#68) - APL authoring ergonomics (backwards-compatible). The
apl:wrapper is now optional — recognized APL terms (policy,post_policy,args,result,pdp,session_store) written directly on a section are honored, with the explicitapl:form still taking precedence.run(name)is accepted as an alias forplugin(name)in both policy steps and field pipelines. Unconditionaldeny('reason')/deny('reason', 'code')now parses as a bare action (e.g. inon_deny:lists), so a reason/code can be attached without a conditional. (#71) - Valkey-backed
SessionStorefor cross-node and cross-restart session label propagation. Selectable via akind: valkeyblock underglobal.apl.session_store(factory pattern mirroringpdp), shipped in theapl-session-valkeycrate and wired intocpex-ffibehind the optionalvalkeycargo feature (the default build and.aartifact are unaffected). Labels live in a Redis SET so appends are an atomic server-side union (SADD); the store is fail-closed (a load/append error denies the request rather than under-labeling), serves primary-only reads, supports an optional sliding TTL, requires TLS for non-localhost endpoints, and SHA-256s session ids out of the keyspace. When no block is configured the default remains the in-process memory store. See the operator runbook atdocs/operations/valkey-session-store.md. (#74) cpexhost facade crate: a single dependency that re-exports the host runtime (PluginManager,AplOptions,register_apl) and the bundled plugin factories, each behind a cargo feature (jwt,oauth,pii,audit,cedar,cel,valkey). Hosts depend oncpexand enable the plugins they want instead of pinningapl-cmf/apl-cpex/apl-pdp-*/apl-session-*individually.install_builtins(&mgr)registers every enabled factory and installs the APL config visitor in one call;register_builtin_plugins,builtin_pdp_factories, andbuiltin_session_store_factoriesexpose the pieces for hosts that assembleAplOptionsthemselves. (#77)cpex-builtinsaggregator crate: the bundled extension set (plugins, PDPs, session stores) behind a 1:1 cargo-feature map, with a declarativeregister_builtins!macro that expands to explicit,#[cfg]-gatedregister_factorycalls (kept explicit rather thaninventory/linkmeso factory symbols survive the linker GC insidelibcpex_ffi.a).register_builtins,builtin_pdps,builtin_session_store_factories, andinstall_builtinsare the single source of truth that both thecpexfacade andcpex-ffinow delegate to. (#72)
Changed
- The
cpexfacade is now engine-only by default:cpex = "0.2"compiles no builtin plugins. The bundled set is opt-in via the newbuiltinsfeature (the common in-process set) orfull(everything, incl. Valkey), with the granular plugin features (jwt,oauth,pii,audit,cedar,cel,valkey) preserved as passthroughs. The registration helpers and concrete factory types are re-exported fromcpex-builtinsand appear only when a builtins feature is enabled.cpex-ffikeeps its prior bundled set (four hook plugins +cedar-direct) by selecting that exactcpex-builtinsfeature subset. No FFI ABI change. (#72) PluginFactoryRegistry::registernow logs atracing::warn!when a registration overwrites an existingkind(last-writer-wins is unchanged, but silent override was a footgun). (#72)- Builtin extension crates moved out of the flat
crates/directory into abuiltins/tree (builtins/plugins/,builtins/pdps/,builtins/session/,builtins/cedarling/) and renamed off theapl-prefix, since they are CPEX plugins that use APL hooks rather than APL itself:apl-pii-scanner→cpex-plugin-pii-scanner,apl-audit-logger→cpex-plugin-audit-logger,apl-identity-jwt→cpex-plugin-identity-jwt,apl-delegator-oauth→cpex-plugin-delegator-oauth,apl-delegator-biscuit→cpex-plugin-delegator-biscuit,apl-pdp-cedar-direct→cpex-pdp-cedar-direct,apl-pdp-cel→cpex-pdp-cel,apl-session-valkey→cpex-session-valkey,apl-cedarling→cpex-cedarling. The policy crates (apl-core,apl-cmf,apl-cpex) keep their names. Config-facingkind:strings and the FFI C ABI are unchanged. (#72) - FFI
FFI_ABI_VERSIONbumped1 → 2: added thecpex_apl_installextern C function and changedcpex_load_configto run registered config visitors (it now callsload_config_yamlinternally soapl:blocks are walked). The Go binding'sexpectedFFIABIVersionis bumped in lockstep. (#60) - Size-first
[profile.release]:opt-level = "z",lto = true,codegen-units = 1,strip = true.libcpex_ffi.ais linked statically into host binaries, so this flows straight into their image size — a representative statically-linked consumer shrank ~21%.panic = "abort"is intentionally not set (the FFI relies oncatch_unwindat its#[no_mangle]boundary). No API or ABI change. (#69) - Trimmed the workspace
tokiofeature floor from["full"]to["rt", "rt-multi-thread", "sync", "time", "macros"]— the union of what the crates actually use;reqwest/hyperstill pullnet/iowhere they need them via feature unification. Drops the unusedfs/process/signalsurface (and thesignal-hook-registrydependency). (#69) SessionStoretrait methods (load_labels/append_labels) now returnResultso backend failures propagate to callers — the error channel fail-closed requires.MemorySessionStoreis infallible and adapts trivially; the CMF invoker (for_request/persist_session) and the route handler propagate the error and fail the request closed on a load/append failure. This is part of the sharedSessionStorecontract that future bridges inherit. (#74)
Removed
- Removed the
cpex-cedarlingcrate (a Sub-step A stub with no real Cedarling calls), itscpex-ffioptional dependency +cedarlingcargo feature, and thecedarlingPDP dialect from the APL grammar (PdpDialect::Cedarlingandcedarling:step recognition). This drops the onlygitdependency in the workspace (the Janssencedarlingcrate, ~200 transitive deps), making every crate publishable to crates.io. Cedarling was wired nowhere — no config, host, or Go binding referenced it — so there is no functional change; the remaining PDPkind:strings (cedar-direct,cel) and the FFI C ABI are unchanged. Acedarling-backed PDP can still be supplied out-of-tree (it degrades toPdpDialect::Custom, alongside the resolver-lessopa/authzen/nemodialects).
Fixed
- Cedar evaluation no longer fails with "recursion limit reached" on hosts that give the FFI a small thread stack (notably musl, whose default is 128 KiB).
cedar-policyaborts whenstacker::remaining_stack()is below its 100 KiB floor; the cedar dispatch inapl-pdp-cedar-directis now wrapped instacker::maybe_grow, so it runs on an adequately sized stack regardless of the host (a no-op when there is already headroom, e.g. glibc's 8 MiB threads). Regression test exercises a real evaluation on a 128 KiB stack. (#69)
v0.2.0-alpha.4
Added
cpexhost facade crate: a single dependency that re-exports the host runtime (PluginManager,AplOptions,register_apl) and the bundled plugin factories, each behind a cargo feature (jwt,oauth,pii,audit,cedar,cel,valkey). Hosts depend oncpexand enable the plugins they want instead of pinningapl-cmf/apl-cpex/apl-pdp-*/apl-session-*individually.install_builtins(&mgr)registers every enabled factory and installs the APL config visitor in one call;register_builtin_plugins,builtin_pdp_factories, andbuiltin_session_store_factoriesexpose the pieces for hosts that assembleAplOptionsthemselves. (#77)
v0.2.0-alpha.3
Added
- CEL (Common Expression Language) policy decision backend. A new
apl-pdp-celcrate registerskind: cel, letting authors write inline boolean predicates (cel: { expr: ... }) over the common attribute vocabulary (subject.id,delegation.depth,session.labels, ...), evaluated through the existingPdpResolverseam alongside Cedar, OPA, and AuthZen. Expressions compile once and cache by source; compile errors, undeclared-variable references, and non-boolean results fail closed (deny), overridable withon_error: allow. No change to APL evaluation semantics. (#68) - APL authoring ergonomics (backwards-compatible). The
apl:wrapper is now optional — recognized APL terms (policy,post_policy,args,result,pdp,session_store) written directly on a section are honored, with the explicitapl:form still taking precedence.run(name)is accepted as an alias forplugin(name)in both policy steps and field pipelines. Unconditionaldeny('reason')/deny('reason', 'code')now parses as a bare action (e.g. inon_deny:lists), so a reason/code can be attached without a conditional. (#71) - Valkey-backed
SessionStorefor cross-node and cross-restart session label propagation. Selectable via akind: valkeyblock underglobal.apl.session_store(factory pattern mirroringpdp), shipped in theapl-session-valkeycrate and wired intocpex-ffibehind the optionalvalkeycargo feature (the default build and.aartifact are unaffected). Labels live in a Redis SET so appends are an atomic server-side union (SADD); the store is fail-closed (a load/append error denies the request rather than under-labeling), serves primary-only reads, supports an optional sliding TTL, requires TLS for non-localhost endpoints, and SHA-256s session ids out of the keyspace. When no block is configured the default remains the in-process memory store. See the operator runbook atdocs/operations/valkey-session-store.md. (#74)
Changed
SessionStoretrait methods (load_labels/append_labels) now returnResultso backend failures propagate to callers — the error channel fail-closed requires.MemorySessionStoreis infallible and adapts trivially; the CMF invoker (for_request/persist_session) and the route handler propagate the error and fail the request closed on a load/append failure. This is part of the sharedSessionStorecontract that future bridges inherit. (#74)
v0.2.0-alpha.2
Changed
- Size-first
[profile.release]:opt-level = "z",lto = true,codegen-units = 1,strip = true.libcpex_ffi.ais linked statically into host binaries, so this flows straight into their image size — a representative statically-linked consumer shrank ~21%.panic = "abort"is intentionally not set (the FFI relies oncatch_unwindat its#[no_mangle]boundary). No API or ABI change. (#69) - Trimmed the workspace
tokiofeature floor from["full"]to["rt", "rt-multi-thread", "sync", "time", "macros"]— the union of what the crates actually use;reqwest/hyperstill pullnet/iowhere they need them via feature unification. Drops the unusedfs/process/signalsurface (and thesignal-hook-registrydependency). (#69)
Fixed
- Cedar evaluation no longer fails with "recursion limit reached" on hosts that give the FFI a small thread stack (notably musl, whose default is 128 KiB).
cedar-policyaborts whenstacker::remaining_stack()is below its 100 KiB floor; the cedar dispatch inapl-pdp-cedar-directis now wrapped instacker::maybe_grow, so it runs on an adequately sized stack regardless of the host (a no-op when there is already headroom, e.g. glibc's 8 MiB threads). Regression test exercises a real evaluation on a 128 KiB stack. (#69)
v0.2.0-alpha.1
Added
- Initial Rust version of CPEX
- Go support via cgo bindings
- APL (Attribute Policy Language) governance is now bundled into
libcpex_ffi.a. Newcpex_apl_installextern C entry point registers the standard APL plugin/PDP factories (validator/pii-scan,audit/logger,identity/jwt,delegator/oauth,cedar-direct) and installs the APL config visitor on a manager. Call it aftercpex_manager_new_defaultand beforecpex_load_config. Go hosts usePluginManager.EnableAPL(). The optionalcedarlingcargo feature adds the Cedarling-backed identity + PDP seams (off by default; the released.astays lean). (#60) - Publish
libcpex_ffi.aas signed GitHub Release artifacts on every semver tag push (linux-amd64-gnu,linux-arm64-gnu,linux-amd64-musl,linux-arm64-musl,darwin-arm64). Cosign keyless signatures + SHA256 checksums; seecrates/cpex-ffi/RELEASE.mdfor the schema and the verify-and-consume recipe. (#60) - FFI ABI versioning:
cpex_ffi_abi_version()extern C accessor exposesFFI_ABI_VERSION. The Go binding checks this ininit()and panics on mismatch. Other language bindings must replicate the check. (#60)
Changed
- FFI
FFI_ABI_VERSIONbumped1 → 2: added thecpex_apl_installextern C function and changedcpex_load_configto run registered config visitors (it now callsload_config_yamlinternally soapl:blocks are walked). The Go binding'sexpectedFFIABIVersionis bumped in lockstep. (#60)