cpex-hosts-python 0.3.0

CPEX host — runs Python plugins out-of-process in isolated virtualenvs.
Documentation
# Location: ./crates/cpex-hosts-python/Cargo.toml
# Copyright 2026
# SPDX-License-Identifier: Apache-2.0
# Authors: Ted Habeck
#
# cpex-hosts-python — out-of-process host for existing Python CPEX
# plugins. Each plugin runs in its own cached virtualenv, driven by the
# Python framework's `worker.py` over a newline-delimited JSON stdio
# protocol. Registers under the `isolated_venv` plugin kind.
#
# Pure Rust plus a subprocess — no libpython link, so this crate is in
# `default-members` (unlike `bindings/python`, which links libpython).

[package]
name = "cpex-hosts-python"
version.workspace      = true
edition.workspace      = true
license.workspace      = true
authors.workspace      = true
description = "CPEX host — runs Python plugins out-of-process in isolated virtualenvs."
repository.workspace = true
homepage.workspace = true
keywords.workspace = true
categories.workspace = true
rust-version.workspace = true

[dependencies]
praxis-policy-core = { workspace = true }

async-trait = { workspace = true }
# Expiry on a token a Python delegation plugin mints (`credential_payloads`).
chrono      = { workspace = true }
serde       = { workspace = true }
serde_json  = { workspace = true }
tracing     = { workspace = true }
uuid        = { workspace = true }

# Version-pinned rather than workspace-inherited, matching how the rest of the
# tree declares it. Used by `venv`
# to fingerprint a requirement set into its cache directory name.
#
# No `zeroize` here, deliberately, even though this crate handles plaintext
# credentials. `praxis-policy-core` and the OAuth delegator hold a secret
# in memory for its lifetime, so `Zeroizing` there wipes the one authoritative
# copy. This host instead *forwards* the plaintext: `credentials` reads the
# token out of core's `Zeroizing` field and it is immediately copied into a
# `serde_json::Value`, then into the serialized request line, then into the
# pipe buffer, then into the worker's own address space. Zeroizing the DTO
# field would wipe one of those copies and leave the rest, which reads as a
# control that is not one. The real boundary is the worker process, and it is
# documented as such in `credentials`' "Residual exposure" note.
sha2 = "0.11"

# `process` and `io-util` are absent from the workspace tokio floor (the
# embedded library never spawns children). This host's whole job is a
# long-lived child spoken to over piped stdio, so it opts both in.
tokio = { workspace = true, features = ["process", "io-util"] }

[features]
# Exposes the `testing` module (temp dirs, python3 detection) to the
# integration tests under `tests/`, which cannot see a `cfg(test)` module.
# Not enabled by default — production builds carry none of it.
testing = []

[dev-dependencies]
serde_yaml = { workspace = true }
# The APL runtime, so `tests/acceptance_e2e.rs` can drive a route's
# `require(...)` and `delegate(...)` steps through the real engine. A dev-only
# edge: the facade depends on this crate, never the other way round.
praxis-policy = { workspace = true }
tokio      = { workspace = true, features = ["process", "io-util", "macros", "rt", "rt-multi-thread"] }

# Self-dependency with the test-only feature on, so `tests/*.rs` see
# `cpex_hosts_python::testing`. Standard trick for cfg(test)-adjacent helpers.
cpex-hosts-python = { path = ".", features = ["testing"] }

[lints]
workspace = true