Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
bf17a8a
docs(plan): add PPE core adoption requirements and plan
araujof Oct 7, 2026
c981609
test(parity): capture 0.2.3 decision snapshots
araujof Oct 7, 2026
ee7c018
refactor!: replace engine crates with PPE 0.4.1
araujof Oct 7, 2026
62b4900
refactor(biscuit): port delegator to PPE
araujof Oct 7, 2026
2031a18
refactor(tutorial): port to PPE policy format
araujof Oct 7, 2026
57b9cb3
refactor(ffi)!: port to PPE with ABI v3
araujof Oct 7, 2026
bde1f89
refactor(hosts-python): port to PPE hook names
araujof Oct 7, 2026
6056f1c
fix(ffi): log only names and kind on secret refresh failure
araujof Oct 8, 2026
b4df7be
refactor(go)!: port to PolicyEngine and ABI v3
araujof Oct 8, 2026
07b735b
refactor(python)!: port bindings to PPE
araujof Oct 8, 2026
ac1071e
test(parity): replay 0.2.3 snapshots against PPE
araujof Oct 8, 2026
0997042
feat(hosts-python): translate cmf payloads for legacy workers
araujof Oct 8, 2026
892725f
ci: update build and release for the PPE layout
araujof Oct 8, 2026
a5cb811
fix(ffi): pass caller extensions to identity resolution
araujof Oct 8, 2026
3c98683
feat(python): send typed payload for identity.resolve
araujof Oct 8, 2026
ebb446f
test: add Python and Go acceptance suites
araujof Oct 8, 2026
8f72bd2
fix(hosts-python): fail closed on fields legacy workers cannot see
araujof Oct 8, 2026
5073b96
docs: update tutorial and add pages adapted from PPE
araujof Oct 8, 2026
edb38d2
build!: move to Rust edition 2024
araujof Oct 8, 2026
c75f563
docs: add 0.3 migration guide and update for PPE
araujof Oct 8, 2026
b9e6b7e
docs: fix stale notes in host and FFI
araujof Oct 8, 2026
81f48ec
chore(release): prepare 0.3.0
araujof Oct 8, 2026
f9d2a95
fix: apply code review fixes
araujof Oct 8, 2026
d6f8a37
fix(go): align extension types with PPE wire format
araujof Oct 8, 2026
7b05a8d
fix: reject removed hooks and forged identity at bindings
araujof Oct 8, 2026
32b57c3
fix(hosts-python): forward PPE identity and delegation payloads
araujof Oct 8, 2026
dcf1e64
fix: pass pre-hook siblings and drop identity header echo
araujof Oct 8, 2026
c6c6403
docs: cover review fixes in changelog and guides
araujof Oct 8, 2026
f07c6c2
docs(plan): mark PPE adoption plan completed
araujof Oct 8, 2026
7ef96fd
fix: address PPE adoption review findings
araujof Oct 9, 2026
8b3cbb9
fix(ci): repair PR 190 validation jobs
araujof Oct 9, 2026
ef2416f
test(parity): retain verifiable baseline
araujof Oct 13, 2026
b494c61
fix(host): warn on private destinations
araujof Oct 13, 2026
a149a2a
docs: reconcile PPE adoption review
araujof Oct 13, 2026
797b1ac
build: upgrade to PPE 0.5.0
araujof Oct 13, 2026
73ec880
build: replace deprecated YAML dependency
araujof Oct 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
151 changes: 134 additions & 17 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ jobs:
run: cargo fmt --all -- --check
- name: clippy
run: cargo clippy --workspace --all-targets -- -D warnings
# The facade's optional `python-host` feature is off in the workspace
# run, so lint it on its own.
- name: clippy (cpex, all features)
run: cargo clippy -p cpex --all-targets --all-features -- -D warnings

unused-deps:
name: Unused deps (advisory)
Expand Down Expand Up @@ -81,6 +85,10 @@ jobs:
# e2e tests are also `#[ignore]`d here — the `python-e2e` job below runs
# them with a real interpreter and framework checkout.
run: cargo test --workspace
- name: cargo test (cpex facade, python-host)
# Compiles the facade's python-host tests. They stop at plugin
# instantiation and never spawn an interpreter.
run: cargo test -p cpex --features python-host

# The Python host's end-to-end tests, actually run.
#
Expand All @@ -98,16 +106,10 @@ jobs:
- uses: actions/checkout@v7
with:
fetch-depth: 1
# The Python framework lives on its own branch, and PyPI's `cpex` is behind
# it (its worker.py predates the credential and extensions fields). The
# *stable* 0.1.x branch is not enough either: it carries no
# cpex/framework/isolated/worker.py at all. The companion branch below is
# the one whose worker.py both consumes `credential` and delivers
# `extensions=` to execute_plugin, which is what these tests gate on.
#
# This is a cross-branch dependency: when the Python side merges, retarget
# this ref. Until then a wrong ref fails loudly (skips are errors here)
# rather than silently skipping — which is the whole point of this job.
# The 0.1.x branch carries the isolated worker with credential and
# extensions support. PyPI's `cpex` may lag that worker, so these tests
# install directly from the checked-out branch. A missing worker fails
# this job rather than silently skipping the integration tests.
- name: Checkout the cpex Python framework
uses: actions/checkout@v7
with:
Expand All @@ -128,6 +130,117 @@ jobs:
CPEX_PYTHON_SOURCE: ${{ github.workspace }}/cpex-python
run: make test-python-e2e

# The PyO3 bindings: in-tree pytest suite (built with `maturin develop`),
# then a release wheel installed into a fresh venv and exercised from
# outside the source tree. The second step is what catches a wheel that
# builds but is missing its native module, package or stubs.
python-bindings:
name: Python bindings (pytest + wheel smoke)
if: github.event_name != 'pull_request' || !github.event.pull_request.draft
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 1
- name: Install Python
uses: actions/setup-python@v7
with:
python-version: "3.12"
- name: Install Rust 1.96.0
uses: dtolnay/rust-toolchain@stable
with:
toolchain: "1.96.0"
- uses: Swatinem/rust-cache@v2
- name: make bindings-python-test
run: make bindings-python-test
- name: make bindings-python-smoke
run: make bindings-python-smoke

# The Go package against the cpex-ffi release library, on both platforms
# the FFI ships for. GO_STATIC=1 removes the shared library so Go links
# libcpex_ffi.a the way release consumers do, which also checks the
# native link flags in go/cpex/ffi.go (macOS has needed extra frameworks
# before). The race run covers the shared tokio runtime behind the FFI.
go:
name: Go (${{ matrix.os }})
if: github.event_name != 'pull_request' || !github.event.pull_request.draft
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
runs-on: ${{ matrix.os }}
timeout-minutes: 45
# Linux only: macOS runners have no Docker, so service containers are not
# available there. A blank image skips the service on that leg, and the
# Valkey acceptance test skips when CPEX_TEST_VALKEY_URL is empty.
services:
valkey:
image: ${{ matrix.os == 'ubuntu-latest' && 'valkey/valkey:8-alpine' || '' }}
ports:
- 6379:6379
options: >-
--health-cmd "valkey-cli ping"
--health-interval 5s
--health-timeout 3s
--health-retries 10
env:
CPEX_TEST_VALKEY_URL: ${{ matrix.os == 'ubuntu-latest' && 'redis://127.0.0.1:6379' || '' }}
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 1
- name: Install Rust 1.96.0
uses: dtolnay/rust-toolchain@stable
with:
toolchain: "1.96.0"
- name: Install Go
uses: actions/setup-go@v7
with:
go-version-file: go/cpex/go.mod
cache-dependency-path: go/cpex/go.sum
- uses: Swatinem/rust-cache@v2
# One make invocation, so the release library is built (and its shared
# copy removed) once for all three targets.
- name: make go-vet go-test go-test-race
run: make go-vet go-test go-test-race GO_STATIC=1

# Replays the 0.2.3 decision snapshots against the current engine, with a
# Valkey so the session-store scenarios and the stored-label read-back run
# too. `make parity` fails when PARITY_VALKEY_URL is set and anything is
# still skipped. The workspace `test` job runs the same suite without
# Valkey.
parity:
name: Parity (0.2.3 snapshots, with Valkey)
if: github.event_name != 'pull_request' || !github.event.pull_request.draft
runs-on: ubuntu-latest
timeout-minutes: 30
services:
valkey:
image: valkey/valkey:8-alpine
ports:
- 6379:6379
options: >-
--health-cmd "valkey-cli ping"
--health-interval 5s
--health-timeout 3s
--health-retries 10
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 1
- name: Install Rust 1.96.0
uses: dtolnay/rust-toolchain@stable
with:
toolchain: "1.96.0"
- uses: Swatinem/rust-cache@v2
- name: make parity
env:
# The harness runs FLUSHDB on this database; the service is
# throwaway.
PARITY_VALKEY_URL: redis://127.0.0.1:6379/0
run: make parity

examples:
name: Examples build (Rust + Go FFI)
if: github.event_name != 'pull_request' || !github.event.pull_request.draft
Expand All @@ -144,10 +257,12 @@ jobs:
- name: Install Go
uses: actions/setup-go@v7
with:
go-version: "1.25.4"
go-version-file: examples/go-demo/go.mod
cache-dependency-path: examples/go-demo/go.sum
- uses: Swatinem/rust-cache@v2
# Builds every Rust example plus the Go demo (which links the release
# cpex-ffi cdylib) — the cheapest guard against stale public-API usage.
# Builds every Rust example (the tutorial modules) plus the Go demo
# (which links the release cpex-demo-ffi library), the cheapest guard
# against stale public-API usage.
- name: make examples-build
run: make examples-build

Expand Down Expand Up @@ -178,15 +293,17 @@ jobs:
# every publishable member without uploading. Catches publishability
# regressions (missing version reqs, packaging-excluded files, broken
# inter-crate deps) on every push/PR, long before a release tag. Mirrors
# `make publish-dry` and the release workflow's dry-run. The
# `publish = false` crates are excluded (not part of the registry set):
# the two FFI crates and the tutorial companion crate.
# `make publish-dry` and the release workflow's dry-run. That covers
# cpex, cpex-hosts-python, cpex-plugin-delegator-biscuit and
# cpex-python. The `publish = false` crates are excluded (not part of
# the registry set): the two FFI crates, the tutorial companion crate,
# and the parity harness.
- name: cargo package (no upload)
run: |
# Drop any pre-seeded registry index/cache so cargo fetches fresh and
# --locked resolves against a clean index (see note above).
rm -rf ~/.cargo/registry/index ~/.cargo/registry/cache
cargo package --workspace --locked --exclude cpex-ffi --exclude cpex-demo-ffi --exclude cpex-tutorial
cargo package --workspace --locked --exclude cpex-ffi --exclude cpex-demo-ffi --exclude cpex-tutorial --exclude cpex-parity-harness

docs:
name: Docs Build
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/release-ffi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,9 @@ jobs:
key: ${{ matrix.target }}

- name: Install musl toolchain
# ring (via jsonwebtoken/rustls/quinn) compiles C + asm through
# cc-rs, which for a *-unknown-linux-musl target shells out to
# <arch>-linux-musl-gcc. The runners ship only the glibc gcc, so
# ring (via rustls) and aws-lc-sys (via jsonwebtoken in the PPE
# builtins) compile C + asm through cc-rs, which for a
# *-unknown-linux-musl target shells out to <arch>-linux-musl-gcc. The runners ship only the glibc gcc, so
# we install musl-gcc here. The matrix runs each musl target on
# its native-arch runner, so musl-gcc targets the host arch and
# the CC_/LINKER env vars below redirect cc-rs and the linker to
Expand Down
69 changes: 37 additions & 32 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
#
# Triggered by a semver tag (vX.Y.Z). Validates the tag matches the
# workspace version, runs the test suite, then publishes every
# publishable crate to crates.io in dependency (leaf-first) order.
# publishable crate to crates.io in dependency (leaf-first) order:
# cpex-hosts-python, cpex-plugin-delegator-biscuit, cpex, cpex-python.
#
# `cpex-ffi` is `publish = false` (distributed as signed prebuilt
# artifacts by release-ffi.yaml), so it is intentionally absent below.
# artifacts by release-ffi.yaml), so it is intentionally absent below,
# as are the example, tutorial and parity harness crates.
#
# Requires repo secret: CARGO_REGISTRY_TOKEN.

Expand Down Expand Up @@ -89,43 +91,46 @@ jobs:
DRY_RUN: ${{ github.event_name == 'workflow_dispatch' && inputs.dry_run }}
run: |
set -euo pipefail
# Dependency order. Only these four members publish; the engine
# itself comes from the praxis-policy crates on crates.io, which
# are already there. `cpex` takes `cpex-hosts-python` as an
# optional dependency, and cargo still needs it in the index, so
# the host goes first. `cpex-plugin-delegator-biscuit` has no member
# dependency; `cpex-python` depends on `cpex`, so it goes last.
# Each `cargo publish`
# blocks until the crate is visible in the index before returning,
# so the next crate's dependency resolves; the short sleep is
# extra slack for propagation.
crates=(
cpex-hosts-python
cpex-plugin-delegator-biscuit
cpex
cpex-python
)
# The list must name exactly the publishable members: a missing one
# stops the release halfway, as it did for 0.2.2. Compare against
# cargo metadata first, in dry runs too. `publish = false` shows up
# there as an empty list.
expected="$(cargo metadata --no-deps --format-version 1 \
| jq -r '.packages[] | select(.publish != []) | .name' | sort)"
listed="$(printf '%s\n' "${crates[@]}" | sort)"
if [ "$expected" != "$listed" ]; then
echo "::error::publish list does not match the publishable workspace members"
diff <(echo "$expected") <(echo "$listed") || true
exit 1
fi
# Dry run: build + verify a .crate for every publishable member
# (cargo skips `publish = false` crates and resolves inter-member
# deps against each other's packaged versions) without uploading.
# This is how the workflow is exercised without a real release.
# (cargo resolves inter-member deps against each other's packaged
# versions) without uploading. This is how the workflow is
# exercised without a real release.
if [ "$DRY_RUN" = "true" ]; then
echo "::group::dry run — cargo package (no upload)"
# Verify the crates.io-published set only; these three
# Verify the crates.io-published set only; these four
# publish=false crates are not part of the registry release.
cargo package --workspace --locked --exclude cpex-ffi --exclude cpex-demo-ffi --exclude cpex-tutorial
cargo package --workspace --locked --exclude cpex-ffi --exclude cpex-demo-ffi --exclude cpex-tutorial --exclude cpex-parity-harness
echo "::endgroup::"
exit 0
fi
# Leaf-first topological order. Each `cargo publish` blocks until the
# crate is visible in the index before returning, so the next crate's
# dependency resolves; the short sleep is extra slack for propagation.
crates=(
cpex-orchestration
cpex-core
cpex-sdk
apl-core
apl-cmf
apl-cpex
cpex-plugin-pii-scanner
cpex-plugin-audit-logger
cpex-plugin-identity-jwt
cpex-plugin-delegator-oauth
cpex-plugin-delegator-biscuit
cpex-plugin-elicitation-ciba
cpex-pdp-cedar-direct
cpex-pdp-cel
cpex-pdp-opa
cpex-session-valkey
cpex-hosts-python
cpex-builtins
cpex
cpex-python
)
for c in "${crates[@]}"; do
echo "::group::publish $c"
# Idempotent: a prior run can publish a leading prefix of this
Expand Down
10 changes: 10 additions & 0 deletions .github/workflows/tutorial.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,27 @@
# `--check`, and tear it down.
name: Tutorial

# The modules run against the facade, the kept builtin and the PPE crates
# pinned in Cargo.toml/Cargo.lock, so a change to any of those triggers the
# run on pull requests as well as on main.
on:
push:
branches: [main]
paths:
- "examples/tutorial/**"
- "crates/**"
- "builtins/**"
- "Cargo.toml"
- "Cargo.lock"
- ".github/workflows/tutorial.yaml"
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
paths:
- "examples/tutorial/**"
- "crates/**"
- "builtins/**"
- "Cargo.toml"
- "Cargo.lock"
- ".github/workflows/tutorial.yaml"

jobs:
Expand Down
Loading
Loading