Skip to content

feat(policy-engine): update the demo for PPE 0.2.0 - #17

Merged
araujof merged 13 commits into
praxis-proxy:mainfrom
araujof:feat/ppe_0.2.0
Sep 5, 2026
Merged

feat(policy-engine): update the demo for PPE 0.2.0#17
araujof merged 13 commits into
praxis-proxy:mainfrom
araujof:feat/ppe_0.2.0

Conversation

@araujof

@araujof araujof commented Sep 4, 2026

Copy link
Copy Markdown
Member

Updates the policy-engine demo to PPE 0.2.0.

The engine now comes from crates.io. The gateway still builds Praxis with the policy-engine filter and the two unpublished reference plugins from source.

Adds scenario 12 for the assertions: block. The gateway derives four identity headers from the verified token, and hr-mcp logs what it receives. When Bob repeats the call with a spoofed x-auth-user-id: root header, the upstream still receives his real subject ID.

Other changes:

  • Migrates the demo policies to the 0.2.0 configuration format.
  • Fixes the Keycloak health check, which failed because its request lacked a Host header.
  • Ensures scenario 11 approves the intended request instead of one left by an abandoned run.
  • Adapts to the Praxis Pingora 0.8.4 fork and the move to the crates/ directory.

Builds from praxis main at a pinned commit.

Verified from a fresh clone of both upstreams: 12/12 scenarios on Cedar, CEL and OPA, no alarms.

0.1.1 is unreleased, and cargo honours [patch] only in the workspace root
it builds, so praxis patching the engine to a sibling path does nothing
from here and the gateway has to name those crates itself.

Not a second thing to point at: praxis reads the engine as
../praxis-policy, so .policy follows the same relative path from whatever
.praxis resolves to. PPE_DIR overrides and a clone is the fallback.

Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com>
Every engine crate, not only the facade: praxis depends on the facade and
the facade on the rest, so leaving one unpatched resolves it from crates.io
at 0.1.0 and the graph gets two copies. For praxis-policy-core that means
two PluginFactory traits and the host registration in main.rs stops
typechecking. cargo tree --duplicates reports none.

The two reference plugins move to the checkout for the same reason; they
are workspace members there and resolve the core by path.

Two fixes the port surfaced, both unrelated to the engine:

praxis-ai at 333f93d predates praxis a6ea13f4, which converted &Option<T>
to Option<&T> across crates, so its MCP and A2A filters no longer compile.
Bumped to ai main.

That revision then needs praxis's admin-api feature: it calls the
admin-endpoint helpers unconditionally while praxis gates them and
praxis-ai declares its praxis dependencies with no features. Turned on the
way policy-engine already is.

Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com>
engine_settings replaces plugin_settings, dispatch: policy replaces
routing_enabled, and each route's flat pre_invocation nests under
authorization. A declared priority is a load error under policy dispatch,
and the audit logger's ordering comment went with the key: order is now the
order the document writes the steps.

The five plugins that reach Keycloak declare perform_http. The engine
performs no outbound HTTP of its own since 0.1.1, so without it a jwks_url
issuer, a token exchange and a CIBA dispatch fail at initialize().

audit-log declares one hook rather than three: this demo has only tool
routes, so the other two were uncovered and the load reported it.

Two comments in the CEL and OPA variants described a wrapped APL form that
no document can write now.

The praxis*.yaml listeners opt into allow_private_endpoints. Every backend
here is on localhost and praxis refuses those unless a config says so.

All three load clean and raise no alarms.

Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com>
The engine is unreleased too, so the gateway patches its crates to
gateway/.policy alongside praxis at gateway/.praxis. Records why the entries
cannot be inherited from praxis, why every engine crate is listed, and that
.policy is derived from .praxis rather than asked for separately.

Also drops an apl: wrapper mention from the file table; no document can
write one now.

Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com>
Every other scenario shows the gateway deciding. This is the only channel
that carries a decision onward: global.assertions.request: renders
engine-derived identity into headers hr-mcp reads.

Four entries, one per shape the feature has: a slot, a claim, a collection
with the encode: it must declare, and a members object. strip: withholds
Bob's raw JWT, which delegate() already replaced on authorization, so the
upstream ends up holding no credential he issued.

Scenario 12 shows both halves, the second being the point: Bob sends
x-auth-user-id: root and the upstream still sees his real subject id,
because an entry removes its target before injecting. hr-mcp logs the
asserted names so the effect is visible.

The headers are unsigned and the README says so first: hr-mcp believes them
because it believes the network path, not because it can verify anything.

Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com>
The engine refuses loopback egress by default as of the security fixes on
main, so every scenario failed at the identity gate with
auth.jwks_unavailable. Keycloak runs on localhost:8081 here.

Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com>
The engine's egress resolver added it. Comes through the local path dep.

Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com>
Only praxis-policy-core and praxis-policy-orchestration stay on the local
checkout. The reference plugins are unpublished and reach those two by
path, so patching them keeps the graph to one copy of each. The other
eleven engine crates come from the registry.

Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com>
The probe sent HTTP/1.0 with no Host, and Keycloak answers 500 there:
it throws building the realm's base URL from a null authority. So the
container never reported healthy and logged an NPE every 5s.

Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com>
The auto-approve step took the first pending entry from the auth-channel,
which is the oldest. A run abandoning one, such as a manual run that timed
out, left it there for the next run to approve instead of its own, which
then polled a still-pending request until it gave up. Record the pending
ids before the call and take one that was not there.

Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com>
praxis main requires pingora-core 0.8.4, and the gateway lock pinned the
fork at 0.8.2, so the graph no longer resolved. Updating it left the
fork's lru crate wired to arrayvec 0.5, which does not compile against
0.8.4, so that package is re-resolved too.

Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com>
The gateway pins praxis by path, and praxis moved core, filter, protocol
and tls under `crates/`, so all eight `[patch]` entries stopped
resolving.

`DEFAULT_PRAXIS_REF` stays where it is: it clones from praxis upstream,
and no upstream commit yet carries both that move and the PPE 0.2.0 port.
Recorded on the ref instead, since the demo needs `.praxis` pointing at a
checkout that has the port either way.

Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com>
The PPE 0.2.0 port is on praxis main, so the pinned ref moves to that
commit and the demo no longer needs a local praxis checkout. Verified
from a fresh clone of both upstreams: 12/12 scenarios on Cedar, CEL and
OPA.

Two claims that stopped being true are corrected here too. The engine
release is published; what is unpublished is its two reference plugins,
which is why `.policy` is still needed. And praxis reads the engine from
crates.io rather than `../praxis-policy`, so the sibling lookup is a
convenience for working on both at once, not a constraint.

Signed-off-by: Frederico Araujo <frederico.araujo@ibm.com>
@araujof
araujof marked this pull request as ready for review September 5, 2026 15:56
@araujof
araujof merged commit fbcfef3 into praxis-proxy:main Sep 5, 2026
1 check passed
@araujof
araujof deleted the feat/ppe_0.2.0 branch September 5, 2026 15:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant