Repository navigation
Releases: contextforge-org/cpex
Release list
v0.2.3
Added
- Multi-principal delegation. A
delegate(...)step can now name whose identity the minted token speaks for (subject: user | client | caller_workload | this_workload) and who is acting (actor: user | client | caller_workload, an RFC 8693actor_tokenrecordingactalongsidesub). The mode is derived from the subject, never declared, so a route can't claim on-behalf-of-user while handing over a workload SVID. Adds SPIFFE JWT-SVID workload ingress (role: caller_workload, validated intocaller_workload.*and stashed asTokenKind::SpiffeJwt) and, forsubject: caller_workload, a two-leg OAuth delegator (SVID as an RFC 7523client_assertion→ base token → RFC 8693 exchange). (#131) - Top-level
groups:config section. Reusable policy bundles (authentication + authorization + plugins) now live at a canonical top-levelgroups:, and a route joins one with a first-classgroups:field (string-or-list).groups:is sugar over tags — it folds into the route's tag set at resolution, so host-injected runtime tags still join groups the same way. A route naming an undefined group is rejected at load. (#131) restricteffect and staticdata.*attributes. Policies can accumulate backend constraints for the host router to enforce, including allowed models, regions, sites, and cost tiers. File-backed static attributes and request-based interpolation let one policy apply per tenant or caller. Missing references and contradictory constraints fail closed. (#114)- Embedded OPA/Rego PDP. The
cpex-pdp-opabuiltin evaluates Rego in process, with boolean, decision-object, and deny-set results. Undefined decisions deny, and inline modules cannot override an operator's global package. (#142) - Out-of-process host for existing Python CPEX plugins. A new
cpex-hosts-pythoncrate registerskind: isolated_venv, running an unmodified Python CPEX plugin in its own cached virtualenv as a subprocess instead of in-process through the PyO3 bindings. Each plugin gets a venv keyed by a SHA-256 fingerprint of its requirements + manifest (rebuilt when either changes,rmtreed rather than upgraded in place so a removed dependency actually disappears), and the host drives the Python framework'sworker.pyover a newline-delimited JSON stdio protocol. Hook payloads,context, and the capability-filteredExtensionsview cross as JSON; returns come back as a serializedPluginResult, withmodified_extensionsmerged through the executor's existing copy-on-write tier validation — the host implements no tier logic of its own. Failure modes the executor cannot otherwise distinguish (venv build failure, worker death mid-flight, a task overmax_content_size, per-invocation timeout) map to distinctPluginErrors carrying a stablecodeand structureddetails, so the executor's configuredon_errorpolicy applies unchanged. Pure Rust plus a subprocess — no libpython link, so the crate is indefault-membersand a plaincargo builddoes not require a Python dev install. The wire contract is pinned indocs/specs/extensions-wire-contract.md; CMF §3 remains normative for the extension slots themselves. (#149)
Changed
-
BREAKING:
TokenRole::Workloadrenamed toTokenRole::CallerWorkload. A serdealias = "workload"keeps existing serialized config loading, but the Rust symbol is renamed — downstream Rust code must update. (#131) -
BREAKING:
DelegationMode::AsGatewayrenamed toAsThisWorkload. A serdealias = "as_gateway"keeps persisted values deserializing. (#131) -
BREAKING:
DelegationKeyis now#[non_exhaustive]and gained aclient_idfield (partitioning the delegated-token cache per calling OAuth client, mirroringworkload_id). Construct it viaDelegationKey::new(mode, audience, scopes)+ thewith_subject_id/with_workload_id/with_client_idsetters rather than a struct literal. (#131) -
BREAKING:
PipelineResultis now#[non_exhaustive]and gained apayload_modifiedfield.modified_payloadisSomeon every allowed pipeline, carrying the final payload whether or not a plugin touched it, so it never answered "did anything change?" — read the new flag for that. Construct viaallowed_with/deniedplus thewith_errors/with_payload_modifiedbuilders rather than a struct literal; exhaustive destructuring must gain a..arm. (#157) -
payload_modifiedis carried across the FFI to the Python and Go bindings.FfiPipelineResult,PyPipelineResult(as apayload_modifiedproperty), and Go'sPipelineResult/TypedPipelineResultall expose it, so non-Rust hosts can distinguish an accepted mutation from a payload the pipeline merely carried. Additive on the MessagePack wire, so the FFI ABI version is unchanged. The GoInvokedoc example no longer presentsModifiedPayload != nilas a mutation test — it is true on every allowed pipeline. (#157) -
BREAKING: a pipeline field name reported to a plugin is now root-relative everywhere. A
do:-block field op passedargs.citywhere theargs:/result:sections passedcity; both now passcity, with the phase selecting the root. The type ofPluginInvocation::Field.nameis unchanged, so this is a silent semantic change: any out-of-treePluginInvokerthat stripped anargs./result.prefix must drop that handling or it will mis-resolve the field. (#157) -
A pipeline stage plugin that rewrites a field to the value it already held is treated as no change. Previously any returned payload marked the field replaced. (#157)
-
payload_modifiederrs toward reporting a change. It records that the executor accepted an edit, not that the bytes differ, so it trips on a plugin returning an untouched clone and on a field pipeline writing a field the value it already held. Both previously reported no change. Operators sizing this: inside the engine it costs aValueclone and aMessageclone with no serialization, but a host that keys its wire re-encode off "did the payload change?" will now re-encode on somewhat more routes, and for FFI hosts the MessagePack step rides along. The direction is deliberate. A false positive costs one redundant re-encode; the false negative was the vulnerability fixed in this release, so a modest throughput shift on mutating routes is expected rather than a regression. (#157) -
CEL policies now use
cel0.14. List membership usesin; the upstream interpreter no longer provides container.contains()or the defaultmin/maxfunctions. Host-registered functions should use names distinct from CEL built-ins such asdouble, because built-in overloads can take precedence. (#185; supersedes #102) -
The Python plugin host is opt-in through the facade's
python-hostfeature. It is excluded from the defaultcpexdependency and thebuiltinsandfullfeature sets. Hosts that usecpex::IsolatedVenvFactorymust enablepython-host; its kind constant is exported asISOLATED_VENV_KIND. (#164) -
Documentation and examples expanded. Added a runnable policy tutorial, identity and static-attribute lessons, and a quickstart; improved identity and delegation guidance and repaired demo links. (#122, #124, #156, #165, #167, #169)
-
Design documentation added. The OpenShell integration plan and Vault SecretProvider requirements are recorded for future work. (#123, #168)
-
Dependencies and CI refreshed. Updated 14 routine Rust dependencies, moved direct SHA-256 users to
sha20.11, and refreshed GitHub Actions while keeping the Rust toolchain pinned to 1.96.0. (#177, #185) -
Internal cleanup and CI maintenance. Simplified code across the workspace and corrected the 0.1.x CI branch target. (#127, #162)
Deprecated
- The reserved
allgroup and theglobal.policies:bundle location, in favor of the top-levelgroups:section. Both still load. (#131)
Fixed
- Plugin payload mutations are no longer silently discarded. A plugin that rewrote anything other than a message's text — a tool result, a tool call's arguments, a thinking block, an attachment — had its mutation dropped by the APL route handler, which decided "was this modified?" by comparing concatenated text content. Redaction and sanitisation plugins are precisely the ones that rewrite tool results, so the failure was fail-open on the path that matters most: the plugin reported a successful redaction and the host forwarded the original secret. Mutation is now reported by the executor at the point it accepts a plugin's payload (
PipelineResult.payload_modified) and read from there, so it no longer depends on which part of the message changed. Plugins that appended a throwaway text part to force the old check to fire can drop that workaround. (#157) - A field pipeline no longer clobbers a plugin's edit to the same content part. Folding an
args:orresult:pipeline's rewrite back into the message replaced the whole argument map / result content, discarding edits a plugin had made to other fields of it. Only the paths the pipeline actually changed are applied now, so a pipeline redacting one argument and a plugin scrubbing another both survive. (#157) - A plugin invoked as a pipeline stage now reports a value for the field it was pointed at. It previously reported the message's concatenated text as the field's new value, which for a structured tool call meant an unrelated argument was overwritten with chat text. A plugin that rewrote some other part of the payload now reports no field change, and its mutation travels with the payload instead. The reported value is compared against the field as the payload held it before the plugin ran, so a
plugin(...)stage that leaves the field alone can no longer undo an earliermask/redact/hashstage in the same chain — those interim edits live only in the pipeline, never in the payload, and comparing against them handed ...
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)