Skip to content

Commit 5a253d4

Browse files
committed
docs: updated CHANGELOG.md to reflect the new feature, and the change to RC_PANIC. Added debug_assert that a violation is set when continue_processing is false.
Signed-off-by: Teryl Taylor <terylt@ibm.com>
1 parent 386710a commit 5a253d4

2 files changed

Lines changed: 26 additions & 3 deletions

File tree

CHANGELOG.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,13 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
1818
### Added
1919

2020
- **Out-of-process host for existing Python CPEX plugins.** A new `cpex-hosts-python` crate registers `kind: 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, `rmtree`d rather than upgraded in place so a removed dependency actually disappears), and the host drives the Python framework's `worker.py` over a newline-delimited JSON stdio protocol. Hook payloads, `context`, and the capability-filtered `Extensions` view cross as JSON; returns come back as a serialized `PluginResult`, with `modified_extensions` merged 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 over `max_content_size`, per-invocation timeout) map to distinct `PluginError`s carrying a stable `code` and structured `details`, so the executor's configured `on_error` policy applies unchanged. Pure Rust plus a subprocess — no libpython link, so the crate is in `default-members` and a plain `cargo build` does not require a Python dev install. The wire contract is pinned in `docs/specs/extensions-wire-contract.md`; CMF §3 remains normative for the extension slots themselves. (#149)
21-
- **First-class decision and effect auditing.** CPEX can now audit its own enforcement — every allow, deny, and modify — instead of only the allowed post-hook traffic an observation plugin happened to see. A new `AuditHook` family, auto-attached by the `PluginManager`, fires at the executor's verdict return points (not a pipeline phase), so a blocked call, a scope narrowing, and a clean allow all produce a record. Each carries a `DecisionLog` — executor-owned and handed only to audit sinks, never placed on `PluginContext` — with the ordered plugin steps, the terminal verdict, the invocation's W3C trace span (`trace_id` / `span_id` / `parent_span_id`, child-span model: a fresh span whose parent is the request's span, for causal-DAG reconstruction), the taint labels the request arrived with, and, opt-in, a content hash of the payload at entry. Irreversible external effects (a token mint, an approval grant) are audited as their own events through a capability-gated, write-ahead protocol: a plugin holding `emit_effect` calls `ext.begin_effect` to durably record intent *before* the act (fail-closed — no durable record, no act) and `ext.complete_effect` to record the outcome (`confirmed` / `rejected` / `unknown`). A durable `FileEffectLog` write-ahead log (append + `fsync`, serialized against concurrent writers, self-compacting at a configurable threshold) makes the intent crash-safe; startup recovery (`PluginManager::recover_effects`) compacts completed effects and reconciles crash-orphaned ones against the issuing participant through an `EffectReconciler` seam (the default logs and leaves them `unknown`). `Extensions::perform_effect` brackets the two-phase protocol so a caller cannot skip, reorder, or forget it. Opt-in throughout: no effect WAL and no content hashing unless `plugin_settings.effect_log_path` / `plugin_settings.capture_content_provenance` are set. (#XXX)
22-
- **The OAuth delegator emits write-ahead audit for the tokens it mints.** `cpex-plugin-delegator-oauth` now brackets both mint legs — the workload `client_assertion` base-token mint and the RFC 8693 exchange — with `begin_effect` / `complete_effect`, mapping a successful exchange to `confirmed`, a definitive IdP rejection to `rejected`, and a timeout or unreachable IdP to `unknown` (reconciled later, never assumed minted). Effects are emitted only when the operator grants the plugin `emit_effect` and configures an effect WAL; otherwise the mint path is unchanged. There is deliberately no OAuth-specific reconciler — an IdP exposes no lookup by mint key, so the core default (log and leave `unknown`) is the honest behavior. (#XXX)
23-
- **The reference `audit-logger` renders the new provenance.** Decision records now include the invocation `span`, a `taint` object (the labels the request arrived with vs. the labels after the pipeline — their difference is the taint this node added), and, when content provenance is enabled, a `content` object with the input and output payload hashes (`sha256:…`, digests only, never the content itself). (#XXX)
21+
- **First-class decision and effect auditing.** CPEX can now audit its own enforcement — every allow, deny, and modify. A new `AuditHook` family, auto-attached by the `PluginManager`, fires at the executor's verdict return points. Each carries a `DecisionLog` — executor-owned and handed only to audit sinks with the ordered plugin steps, the terminal verdict, the invocation's W3C trace span (`trace_id` / `span_id` / `parent_span_id`, child-span model: a fresh span whose parent is the request's span, for causal-DAG reconstruction), the taint labels the request arrived with, and, opt-in, a content hash of the payload at entry. Irreversible external effects (a token mint, an approval grant) are audited as their own events through a capability-gated, write-ahead protocol: a plugin holding `emit_effect` calls `ext.begin_effect` to durably record intent *before* the act (fail-closed — no durable record, no act) and `ext.complete_effect` to record the outcome (`confirmed` / `rejected` / `unknown`). A durable `FileEffectLog` write-ahead log (append + `fsync`, serialized against concurrent writers, self-compacting at a configurable threshold) makes the intent crash-safe; startup recovery (`PluginManager::recover_effects`) compacts completed effects and reconciles crash-orphaned ones against the issuing participant through an `EffectReconciler` seam (the default logs and leaves them `unknown`). `Extensions::perform_effect` brackets the two-phase protocol so a caller cannot skip, reorder, or forget it. Opt-in throughout: no effect WAL and no content hashing unless `plugin_settings.effect_log_path` / `plugin_settings.capture_content_provenance` are set. (#166)
22+
- **The OAuth delegator emits write-ahead audit for the tokens it mints.** `cpex-plugin-delegator-oauth` now brackets both mint legs — the workload `client_assertion` base-token mint and the RFC 8693 exchange — with `begin_effect` / `complete_effect`, mapping a successful exchange to `confirmed`, a definitive IdP rejection to `rejected`, and a timeout or unreachable IdP to `unknown`. Effects are emitted only when the operator grants the plugin `emit_effect` and configures an effect WAL; otherwise the mint path is unchanged. (#166)
23+
- **The reference `audit-logger` renders the new provenance.** Decision records now include the invocation `span`, a `taint` object (the labels the request arrived with vs. the labels after the pipeline — their difference is the taint this node added), and, when content provenance is enabled, a `content` object with the input and output payload hashes (`sha256:…`, digests only, never the content itself). (#166)
24+
25+
### Changed
26+
27+
- **Serial-phase plugin panics are now contained, not fatal to the call.** A panic in a plugin's hook body is caught by the executor's serial phase (matching the concurrent phase, which already contained panics), converted to a plugin error, and run through the plugin's `on_error` policy — so with the default `on_error: fail` it becomes a fail-closed **deny**, recorded in the decision stream with the violation code `plugin_panic`, rather than aborting the invocation with no record. This extends "completeness by construction" to crashes. **Breaking (FFI / language bindings):** a host that keyed on `RC_PANIC` to detect a *plugin-body* panic now sees a normal deny instead — the panic detail is carried on the deny violation (code `plugin_panic`, distinguishable from an ordinary `plugin_error`). `RC_PANIC` still fires for panics *outside* a plugin body (config load, plugin registration, executor internals). (#166)
2428

2529
### Fixed
2630

crates/cpex-core/src/executor.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -805,6 +805,25 @@ impl Executor {
805805
// Enforcement is unchanged (the pipeline proceeds);
806806
// this plugin's modifications are skipped, since it
807807
// asked to stop rather than shape.
808+
//
809+
// Keys on `violation.is_some()`, mirroring the blocking
810+
// branch above: a stop signal is only recorded as a
811+
// deny / DenyIgnored when it carries a violation. The
812+
// `PluginResult` contract documents that a violation is
813+
// present whenever `continue_processing` is false, and
814+
// `PluginResult::deny()` always sets one — so this holds
815+
// for any plugin built through the constructors. A
816+
// hand-built stop with no violation would fall through
817+
// to allow/modify in either phase; the assert pins that
818+
// contract so such a result surfaces in tests rather
819+
// than silently reading as an allow.
820+
debug_assert!(
821+
erased.continue_processing || erased.violation.is_some(),
822+
"{} plugin '{}' set continue_processing=false without a violation; \
823+
use PluginResult::deny() so the stop is recorded, not read as allow",
824+
phase_label,
825+
plugin_name,
826+
);
808827
let deny_ignored =
809828
!erased.continue_processing && !can_block && erased.violation.is_some();
810829
if deny_ignored {

0 commit comments

Comments
 (0)