Skip to main content

cpex_hosts_python/
lib.rs

1// Location: ./crates/cpex-hosts-python/src/lib.rs
2// Copyright 2026
3// SPDX-License-Identifier: Apache-2.0
4// Authors: Ted Habeck
5//
6// cpex-hosts-python — out-of-process host for existing Python CPEX plugins.
7//
8// The Rust `PolicyEngine` cannot run a Python plugin on its own. This crate
9// is the Rust counterpart to the Python CLI's `client.py` + `venv_comm.py`:
10// it builds a per-plugin virtualenv, launches the framework's `worker.py` in
11// that venv as a long-lived subprocess, and speaks the same
12// newline-delimited-JSON stdio protocol the Python CLI already uses. Existing
13// PII filters, identity resolvers, and token delegators keep working while
14// the gateway migrates to the Rust engine.
15//
16// # Topology
17//
18// The factory produces one plugin object per config entry. That object owns
19// the venv manager (build + cache) and the worker client (subprocess +
20// protocol), and supplies one hook adapter per declared hook:
21//
22// ```text
23//   PolicyEngine
24//     └── isolated_venv factory  ──> IsolatedPythonPlugin ──┬── venv manager
25//                                     (initialize/shutdown) └── worker client
26//           └── hook adapter (serialize, send, deserialize)      │
27//                                                               ▼
28//                                          worker.py subprocess in the venv
29// ```
30//
31// The three concerns are split deliberately rather than ported as one
32// one-to-one translation of `client.py`: each tests in isolation, and
33// shared-package venv handling falls naturally to the venv manager.
34//
35// # Lifecycle
36//
37// Venv construction and worker launch happen in `initialize()`, which the
38// engine awaits once per plugin (with rollback on failure), and teardown in
39// `shutdown()`, which it calls in reverse order. Neither belongs on the
40// invoke path — a cold pip install is measured in minutes.
41//
42// # Credentials
43//
44// The token fields on `RawInboundToken` / `RawDelegatedToken` are
45// `#[serde(skip)]`, so no generic serialization of an `Extensions` carries
46// token bytes across a process boundary. Identity and delegation plugins
47// genuinely need them, so this host adds a capability-gated wire DTO on a
48// separate channel — raw material *does* reach the worker process here, by
49// design and under the conditions below. A plugin that
50// declared `read_inbound_credentials` or `read_delegated_tokens` gets a
51// dedicated `credential` object on the task JSON, built by reading the
52// in-memory token directly. Production credential types keep their serde
53// guard and are never serialized. See the `credentials` module for the
54// fail-closed rules and the residual exposure this does not close.
55//
56// # Extensions
57//
58// The capability-filtered `Extensions` the executor produced for a plugin is
59// serialized onto the task, so a 3-arg `(payload, context, extensions)` hook
60// sees out-of-process what it would see in-process. The plugin's returned
61// extensions come back through the executor's existing copy-on-write merge,
62// which enforces the mutability tiers — this host adds no tier logic. Sensitive
63// headers are stripped in both directions and `raw_credentials` never rides
64// this channel. See the `extensions` module.
65
66pub mod conversion;
67pub mod credential_payloads;
68pub mod credentials;
69pub mod error;
70pub mod extensions;
71pub mod factory;
72pub mod legacy;
73pub mod plugin;
74pub mod venv;
75pub mod worker;
76
77// Test-only helpers. Also exposed behind the `testing` feature so the
78// integration tests under `tests/` can use them — a `cfg(test)` module is
79// invisible to a separate test binary.
80#[cfg(any(test, feature = "testing"))]
81pub mod testing;
82
83pub use conversion::{GenericPayload, PayloadKind};
84pub use credentials::CredentialDto;
85pub use error::HostError;
86pub use factory::{IsolatedVenvFactory, KIND};
87pub use plugin::{DEFAULT_PLUGIN_DIR, IsolatedPythonPlugin, VenvConfig};
88pub use venv::{CacheVerdict, EnsureOutcome, VenvManager};
89pub use worker::WorkerClient;