Repository navigation
refactor!: adopt PPE as the CPEX core for 0.3.0 - #190
Conversation
Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com>
Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com>
Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com>
Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com>
Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com>
Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com>
Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com>
Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com>
Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com>
Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com>
Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com>
Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com>
Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com>
Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com>
Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com>
Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com>
Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com>
Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com>
Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com>
Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com>
Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com>
Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com>
Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com>
Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com>
Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com>
Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com>
Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com>
Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com>
Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com>
Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com>
Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com>
terylt
left a comment
There was a problem hiding this comment.
Overall, I think things look good. I'd recommend the following.
Findings
1. The APL double-visitor defect is mitigated once per surface instead of upstream
The plan's Context correctly reports that install_builtins calls register_apl each time and that visitor registration appends, so a second install adds a second APL visitor. Both halves check out: install_builtins discards the returned visitor handle (crates/ppe/src/lib.rs:283) and register_visitor is an unconditional push with no dedup (crates/ppe-core/src/engine.rs:1403).
The mitigation is a per-handle installed flag in the FFI (U5) and a separate idempotency guard in the Python bindings (U8). That is the same upstream defect papered over once per binding, and it leaves A1, the Rust embedder, with no protection at all: a Rust host calling install_builtins twice still doubles the visitor, and nothing CPEX ships can prevent that. Because install_builtins throws the handle away, a host cannot even detect the condition.
The plan already has the channel for this. U2 establishes an upstreaming disposition process, and Key Technical Decisions commits to shipping any needed PPE fix as a 0.4.x patch from a release branch. This is exactly that kind of fix (dedup in register_visitor, or an idempotent install_builtins), and it is not routed there.
What I would do: file it as a PPE issue, keep the binding guards as defence in depth, and note in the migration guide that double install is a host error until the upstream fix lands.
2. Deleting the parity harness makes the plan's main control unfalsifiable
U1 says the capture harness location is "chosen during implementation; it is deleted or retargeted in U11". Parity is the one control standing between a 466-file engine swap and a silent behaviour change, and U11's verification is a diff against snapshots that can no longer be regenerated. When a difference appears, there is no way to distinguish a port bug from a bad snapshot without rebuilding the harness from the 0.2.3 tag.
What I would do: keep the harness in tests/parity/, pinned so it runs against the 0.2.3 tag, rather than deleting it. The cost is a directory that does not compile against 0.3.0 and has to be run out of tree; the benefit is that the control can be re-run.
3. Nothing asserts the hook rename table is total
U11 replays U1's request sets through "the reviewed legacy-to-PPE hook rename table". A missing or wrong row in that table converts a real behaviour change into a passing parity result, which is the precise failure mode parity exists to catch. The plan treats the table as reviewed, not as checked.
What I would do: assert in the parity test that every hook name appearing in any captured request has exactly one row, and that every PPE dispatched name reachable by the fixture set is either a target or explicitly listed as unused. Two assertions, and the table stops being load-bearing on review attention alone.
4. U3 does not apply the plan's own test-reconciliation learning
Institutional Learnings records "reconcile tests by executed count and list ignored tests explicitly". U3 deletes seven engine crates and nine builtins, and with them every test those crates carried, yet its verification is only that the workspace resolves and cargo build -p cpex / cargo test -p cpex pass. R19's "existing suites pass" is ambiguous across a deletion that large: a suite that no longer exists trivially passes.
What I would do: record the before and after executed-test counts across the cut-over, name the deleted suites as now covered upstream in PPE, and list the ignored tests on both sides. This is the step most likely to lose coverage without anyone noticing, and the plan already knows the technique.
5. The private-destination opt-in is a single global boolean with documentation as its only control
U12 states the consequence plainly, which is good: the switch permits loopback, RFC 1918, link-local including cloud metadata, and CGNAT, for every outbound builtin at once. But the surface is one constructor keyword argument, one Go option and one FFI flag, and the tutorial and demos turn it on. Those are the configurations integrators copy.
I checked whether a narrower knob exists upstream: it does not. HyperTransport's only egress control is with_allow_private_destinations (crates/ppe/src/http_hyper.rs:216); there is no per-destination allow-list. So a tighter switch is a PPE feature request, not something CPEX could have built, and it is correctly outside the plan's "no PPE feature work beyond upstreaming fixes CPEX needs" boundary.
What I would do: add it to the deferred list as a PPE issue (an allow-list of private hosts, so a JWKS on localhost does not also open cloud metadata), and in the meantime emit a startup warning when the flag is on, so an operator who inherited a demo config sees it in logs rather than only in docs.
6. The out-of-repo Python worker SDK is a release gate with no risk row
"Whether the out-of-process Python worker SDK (outside this repo, pointed to by CPEX_PYTHON_SOURCE) registers handlers by legacy hook names" sits in Deferred to Implementation, resolved in U4. If the answer is yes, every deployed Python plugin breaks on upgrade and the fix lands in a repository this plan does not cover, with its own release to coordinate against the 0.3.0 tag.
The risk table has a row for Python plugins silently losing credentials, which is the in-repo half of the same hazard. The out-of-repo half is larger and absent.
What I would do: give it a risk row with the coordinated-release gate named, next to the credentials row.
7. No abort criterion
One integration branch, one cut-over commit, sixteen crates deleted. The first risk row's mitigation reads "Research found no gaps; any found becomes a PPE 0.4.x patch from a release branch, gating the 0.3.0 tag". The first clause is confidence rather than mitigation, and the second covers small gaps only. The plan never states what happens if U11 surfaces a difference that can be neither fixed nor accepted as intended.
What I would do: one sentence naming the stop condition and who decides, since the branch is the rollback unit and that only works while it is unmerged.
Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com>
Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com>
Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com>
Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com>
|
Thanks, @terylt. I addressed the CPEX-side changes. The 0.2.3 capture harness is retained and pinned, the hook mapping is checked, and the test counts, ignored tests and upstream coverage are recorded. I also added the startup warning, SDK release gate and abort condition. CPEX now uses PPE 0.5.0, and the tests pass. I opened #208 for duplicate APL installation and #209 for a private-host allow-list. We can handle those in a separate PPE PR. Until then, the binding guards stay in place, and the migration guide calls out the once-per-engine requirement for Rust hosts. |
Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com>
Summary
CPEX 0.3.0 runs on the praxis-policy engine (PPE) 0.4.1 from crates.io instead of its own engine crates. The engine core and builtins are removed from this repo. CPEX keeps what PPE does not have: the Python plugin host, the C FFI, the Python and Go bindings, the biscuit delegator, the tutorial, and standalone docs. All of them move to PPE's API and policy format.
docs/brainstorms/ppe-core-adoption-requirements.mddocs/plans/2026-10-07-001-refactor-adopt-ppe-core-plan.mddocs/content/docs/migration-0.3.mdCHANGELOG.md[0.3.0]Key decisions
=0.4.1) from crates.io. The removed crates stay at 0.2.3 on crates.io.PluginManagerbecomesPolicyEngineon every surface. Python keepsPluginManageras an alias.cpex_apl_install(handle, flags),cpex_refresh_secretsandcpex_secrets_last_success.cmf.*payloads for 0.1.x workers.Verification
Parity:
tests/parityreplays 68 recorded 0.2.3 decisions. Every allow/deny decision matches, and the violation codes match apart from the rule-path change below. The 14 requests with field differences are listed with their cause intests/parity/intended-differences.yaml:.apl.authentication:Valkey: labels stored by 0.2.3 read back unchanged.
Acceptance suites: Python, Go and the Python host each have one, covering identity, delegation, Cedar, OPA, session labels and Valkey shared across engines.
Run locally on macOS:
cargo test --workspace, clippy-D warningsand fmtmake publish-dryfor all four publishable crates-raceNot run locally:
CI covers the first two. The tutorial workflow covers the Keycloak and SPIRE modules.
Known follow-ups
auth.jwks_unavailable. A distinct egress code would need an upstream change.LoadConfig: errors return codes only, and the message goes to the tracing log. A "last error" FFI call would be additive.kind:from YAML (same as 0.2.3).Post-Deploy Monitoring & Validation
This is a library release, with no service to deploy. After tagging 0.3.0:
Release workflow: all four crates publish in order (
cpex-hosts-python,cpex-plugin-delegator-biscuit,cpex,cpex-python). The FFI artifact workflow builds all five targets and reportsFFI_ABI=3.Healthy signals:
go/cpex/v0.3.0link against the matching FFI artifactFailure signals and rollback:
0.2.3 stays available for anyone who needs to roll back.
Owner: the release maintainer, for the first week after the tag.