Skip to content

Releases: contextforge-org/cpex

v0.2.2

Choose a tag to compare

@github-actions github-actions released this 15 Jul 12:49
3307bd7

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 one Step::Elicit, resolved by name to an ElicitationHandler plugin exactly like delegate(...). While the human hasn't answered, the phase suspends (Decision stays Allow with a pending bundle) and the host emits JSON-RPC -32120 so the agent retries by echoing the elicitation id; expiry, channel error, denial, or a failed validation fail closed (default on_error: deny). The approval is bound to the live request args via an APL scope: expression (args.amount <= 25000) the runtime checks at resolution — never an LLM summary. Ships a working Keycloak CIBA channel plugin (kind: elicitation/ciba, in cpex-builtins default features). See [Elicitation]({{< relref "/docs/apl/elicitation" >}}). (#115)

Changed

  • read_headers granted to every synthetic policy handler. The entity-less HTTP catch-all, per-entity routes (tool/prompt/resource), and defaults are now all granted the read_headers capability at install time, so http.* request attributes (http.method / http.path / http.host / http.scheme / http.request_headers.*) are available to policy evaluation wherever the host attaches an HttpExtension. Previously only the global HTTP catch-all had it, so a per-entity rule could not read the HTTP request line. This lets one policy combine http.* with entity/args.* predicates in a single evaluation (e.g. an MCP tool route that also gates on http.method). It is a no-op for hosts that never populate the HTTP extension — there is nothing to read — and http.host continues to be sourced from a validated request authority, never a raw client Host header. (#120)
  • APL order comparisons now coerce numeric-looking strings. numeric_compare parses string operands as f64 for order operators (>, >=, <, <=), so args.amount > 10000 fires when the arg arrives as the string "25000" — as LLM tool arguments routinely do. This affects all order comparisons in the engine, not just elicitation scope: bindings: a comparison that previously returned false because one side was a numeric string may now evaluate numerically. Equality (==) is unchanged, and genuinely non-numeric strings still don't order-compare (they yield false, per spec §2.3). (#115)

v0.2.1

Choose a tag to compare

@github-actions github-actions released this 14 Jul 12:30
9eba371

Added

  • HTTP request-line attributes. HttpExtension now carries optional method / path / host / scheme, surfaced in the APL attribute bag as http.method / http.path / http.host / http.scheme so CEL/APL predicates can reason over the HTTP request line. They ride the existing read_headers capability (the http extension slot is gated as a whole). http.host must be populated from a validated request authority (e.g. HTTP/2 :authority), never a raw client Host header, so host-based policy cannot be spoofed.
  • Custom denial response (response: block). A route — or global — may declare a custom HTTP status / body / headers for its denials via a response: block (a sibling of authorization:). On a deny, these are carried on PluginViolation.details (http.status / http.body / http.headers) for the host to render; absent, the host default is unchanged. No new APL grammar and no new PluginViolation fields. It is scope-local: a global response is not inherited by entity routes, and the block warns (inert) at defaults / policy-bundle scope.
  • Entity-less HTTP authorization. The catch-all global policy now authorizes generic (non-MCP/A2A) HTTP requests that carry no entity, via new reserved coordinates (http / *) and the cmf.http_request hook. A host fires cmf.http_request with those coordinates; the global authorization (or args) block is evaluated with read_headers granted, and a global response: decorates the denial. Fail-closed session-store denials carry the response too.
  • Python bindings (PyO3). Native cpex Python package wrapping the cpex-core PluginManager, 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: (at global, per-route, and policy-group scope)
    • policy:authorization.pre_invocation: (or flat pre_invocation:)
    • post_policy:authorization.post_invocation: (or flat post_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 keys args: / result: are unchanged (they stay aligned with the args.* / 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, with authentication: and authorization: as sibling blocks (pre_invocation: / post_invocation: nested under authorization:; args: / result: / pdp: / session_store: / response: as siblings). Both the apl: wrapper and the wrapper-free form remain accepted by the parser; this only standardizes the examples authors copy from.

v0.2.0

Choose a tag to compare

@github-actions github-actions released this 27 Jun 04:04
bc494a6

Added

  • CPEX redesign as a Rust framework with Go bindings
  • APL (Attribute Policy Language) governance is now bundled into libcpex_ffi.a. New cpex_apl_install extern 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 after cpex_manager_new_default and before cpex_load_config. Go hosts use PluginManager.EnableAPL(). (#60)
  • Publish libcpex_ffi.a as 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; see crates/cpex-ffi/RELEASE.md for the schema and the verify-and-consume recipe. (#60)
  • FFI ABI versioning: cpex_ffi_abi_version() extern C accessor exposes FFI_ABI_VERSION. The Go binding checks this in init() and panics on mismatch. Other language bindings must replicate the check. (#60)
  • CEL (Common Expression Language) policy decision backend. A new apl-pdp-cel crate registers kind: cel, letting authors write inline boolean predicates (cel: { expr: ... }) over the common attribute vocabulary (subject.id, delegation.depth, session.labels, ...), evaluated through the existing PdpResolver seam 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 with on_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 explicit apl: form still taking precedence. run(name) is accepted as an alias for plugin(name) in both policy steps and field pipelines. Unconditional deny('reason') / deny('reason', 'code') now parses as a bare action (e.g. in on_deny: lists), so a reason/code can be attached without a conditional. (#71)
  • Valkey-backed SessionStore for cross-node and cross-restart session label propagation. Selectable via a kind: valkey block under global.apl.session_store (factory pattern mirroring pdp), shipped in the apl-session-valkey crate and wired into cpex-ffi behind the optional valkey cargo feature (the default build and .a artifact 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 at docs/operations/valkey-session-store.md. (#74)
  • cpex host 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 on cpex and enable the plugins they want instead of pinning apl-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, and builtin_session_store_factories expose the pieces for hosts that assemble AplOptions themselves. (#77)
  • cpex-builtins aggregator crate: the bundled extension set (plugins, PDPs, session stores) behind a 1:1 cargo-feature map, with a declarative register_builtins! macro that expands to explicit, #[cfg]-gated register_factory calls (kept explicit rather than inventory/linkme so factory symbols survive the linker GC inside libcpex_ffi.a). register_builtins, builtin_pdps, builtin_session_store_factories, and install_builtins are the single source of truth that both the cpex facade and cpex-ffi now delegate to. (#72)

Changed

  • The cpex facade is now engine-only by default: cpex = "0.2" compiles no builtin plugins. The bundled set is opt-in via the new builtins feature (the common in-process set) or full (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 from cpex-builtins and appear only when a builtins feature is enabled. cpex-ffi keeps its prior bundled set (four hook plugins + cedar-direct) by selecting that exact cpex-builtins feature subset. No FFI ABI change. (#72)
  • PluginFactoryRegistry::register now logs a tracing::warn! when a registration overwrites an existing kind (last-writer-wins is unchanged, but silent override was a footgun). (#72)
  • Builtin extension crates moved out of the flat crates/ directory into a builtins/ tree (builtins/plugins/, builtins/pdps/, builtins/session/, builtins/cedarling/) and renamed off the apl- prefix, since they are CPEX plugins that use APL hooks rather than APL itself: apl-pii-scannercpex-plugin-pii-scanner, apl-audit-loggercpex-plugin-audit-logger, apl-identity-jwtcpex-plugin-identity-jwt, apl-delegator-oauthcpex-plugin-delegator-oauth, apl-delegator-biscuitcpex-plugin-delegator-biscuit, apl-pdp-cedar-directcpex-pdp-cedar-direct, apl-pdp-celcpex-pdp-cel, apl-session-valkeycpex-session-valkey, apl-cedarlingcpex-cedarling. The policy crates (apl-core, apl-cmf, apl-cpex) keep their names. Config-facing kind: strings and the FFI C ABI are unchanged. (#72)
  • FFI FFI_ABI_VERSION bumped 1 → 2: added the cpex_apl_install extern C function and changed cpex_load_config to run registered config visitors (it now calls load_config_yaml internally so apl: blocks are walked). The Go binding's expectedFFIABIVersion is bumped in lockstep. (#60)
  • Size-first [profile.release]: opt-level = "z", lto = true, codegen-units = 1, strip = true. libcpex_ffi.a is 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 on catch_unwind at its #[no_mangle] boundary). No API or ABI change. (#69)
  • Trimmed the workspace tokio feature floor from ["full"] to ["rt", "rt-multi-thread", "sync", "time", "macros"] — the union of what the crates actually use; reqwest/hyper still pull net/io where they need them via feature unification. Drops the unused fs/process/signal surface (and the signal-hook-registry dependency). (#69)
  • SessionStore trait methods (load_labels / append_labels) now return Result so backend failures propagate to callers — the error channel fail-closed requires. MemorySessionStore is 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 shared SessionStore contract that future bridges inherit. (#74)

Removed

  • Removed the cpex-cedarling crate (a Sub-step A stub with no real Cedarling calls), its cpex-ffi optional dependency + cedarling cargo feature, and the cedarling PDP dialect from the APL grammar (PdpDialect::Cedarling and cedarling: step recognition). This drops the only git dependency in the workspace (the Janssen cedarling crate, ~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 PDP kind: strings (cedar-direct, cel) and the FFI C ABI are unchanged. A cedarling-backed PDP can still be supplied out-of-tree (it degrades to PdpDialect::Custom, alongside the resolver-less opa / authzen / nemo dialects).

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-policy aborts when stacker::remaining_stack() is below its 100 KiB floor; the cedar dispatch in apl-pdp-cedar-direct is now wrapped in stacker::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

v0.2.0-alpha.4 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 19 Jun 04:28
702ab16

Added

  • cpex host 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 on cpex and enable the plugins they want instead of pinning apl-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, and builtin_session_store_factories expose the pieces for hosts that assemble AplOptions themselves. (#77)

v0.2.0-alpha.3

v0.2.0-alpha.3 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 19 Jun 02:46
b3faea1

Added

  • CEL (Common Expression Language) policy decision backend. A new apl-pdp-cel crate registers kind: cel, letting authors write inline boolean predicates (cel: { expr: ... }) over the common attribute vocabulary (subject.id, delegation.depth, session.labels, ...), evaluated through the existing PdpResolver seam 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 with on_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 explicit apl: form still taking precedence. run(name) is accepted as an alias for plugin(name) in both policy steps and field pipelines. Unconditional deny('reason') / deny('reason', 'code') now parses as a bare action (e.g. in on_deny: lists), so a reason/code can be attached without a conditional. (#71)
  • Valkey-backed SessionStore for cross-node and cross-restart session label propagation. Selectable via a kind: valkey block under global.apl.session_store (factory pattern mirroring pdp), shipped in the apl-session-valkey crate and wired into cpex-ffi behind the optional valkey cargo feature (the default build and .a artifact 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 at docs/operations/valkey-session-store.md. (#74)

Changed

  • SessionStore trait methods (load_labels / append_labels) now return Result so backend failures propagate to callers — the error channel fail-closed requires. MemorySessionStore is 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 shared SessionStore contract that future bridges inherit. (#74)

v0.2.0-alpha.2

v0.2.0-alpha.2 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 15 Jun 01:00
7bebb20

Changed

  • Size-first [profile.release]: opt-level = "z", lto = true, codegen-units = 1, strip = true. libcpex_ffi.a is 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 on catch_unwind at its #[no_mangle] boundary). No API or ABI change. (#69)
  • Trimmed the workspace tokio feature floor from ["full"] to ["rt", "rt-multi-thread", "sync", "time", "macros"] — the union of what the crates actually use; reqwest/hyper still pull net/io where they need them via feature unification. Drops the unused fs/process/signal surface (and the signal-hook-registry dependency). (#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-policy aborts when stacker::remaining_stack() is below its 100 KiB floor; the cedar dispatch in apl-pdp-cedar-direct is now wrapped in stacker::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

v0.2.0-alpha.1 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 10 Jun 21:14
de3b98f

Added

  • Initial Rust version of CPEX
  • Go support via cgo bindings
  • APL (Attribute Policy Language) governance is now bundled into libcpex_ffi.a. New cpex_apl_install extern 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 after cpex_manager_new_default and before cpex_load_config. Go hosts use PluginManager.EnableAPL(). The optional cedarling cargo feature adds the Cedarling-backed identity + PDP seams (off by default; the released .a stays lean). (#60)
  • Publish libcpex_ffi.a as 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; see crates/cpex-ffi/RELEASE.md for the schema and the verify-and-consume recipe. (#60)
  • FFI ABI versioning: cpex_ffi_abi_version() extern C accessor exposes FFI_ABI_VERSION. The Go binding checks this in init() and panics on mismatch. Other language bindings must replicate the check. (#60)

Changed

  • FFI FFI_ABI_VERSION bumped 1 → 2: added the cpex_apl_install extern C function and changed cpex_load_config to run registered config visitors (it now calls load_config_yaml internally so apl: blocks are walked). The Go binding's expectedFFIABIVersion is bumped in lockstep. (#60)

0.1.1

Choose a tag to compare

@araujof araujof released this 04 Jun 19:01
09b234b

Added

  • Plugin bundling, catalog, installation and versioning (#31)

Fixed

  • Implement __eq__ and __ne__ for CopyOnWriteDict (#55)
  • Respect PLUGINS_LOG_LEVEL environment variable in all runtime.py files (#48)

0.1.0

Choose a tag to compare

@araujof araujof released this 05 May 19:16
c215d1f

Added

  • Initial release