Skip to content

feat(pep): decide → fulfill → forward Decision Mode PEP (#2571)#211

Merged
saurabhjain1592 merged 3 commits into
mainfrom
feat/2571-pep-decide-fulfill-obligation
Jun 9, 2026
Merged

feat(pep): decide → fulfill → forward Decision Mode PEP (#2571)#211
saurabhjain1592 merged 3 commits into
mainfrom
feat/2571-pep-decide-fulfill-obligation

Conversation

@saurabhjain1592

Copy link
Copy Markdown
Member

Summary

Adds the Python SDK analog of platform/shared/pep (ADR-056, epic getaxonflow/axonflow-enterprise#2563; tracked by getaxonflow/axonflow-enterprise#2571). A Policy Enforcement Point now follows one path — decide → fulfill → forward — and the SDK makes the engine-fulfillable obligation contract impossible to misuse: there is no local redaction path, so a redact_pii obligation can only be discharged by round-tripping content through the engine endpoint the obligation names.

Python is the partner-critical, canonical SDK (BukuWarung's reference MCP adapter is Python); this PR sets cross-SDK parity for the Go/TS/Java/Rust ports.

What's added

  • decide(DecideRequest) / decide_and_fulfill(...) / fulfill_request(decision, statement) on the async and sync clients.
  • Types: DecideRequest, DecideResponse, Obligation, ObligationFulfillment, DecisionCallerIdentity, DecisionTarget.
  • redacted / redacted_statement / redaction_evaluated on MCPCheckInputResponse; redaction_evaluated on MCPCheckOutputResponse; content_type on MCPCheckInputRequest + mcp_check_input(...).
  • ObligationNotFulfillableError (a fail-closed signal) + PEP constants + has_request_redaction(...).

Fail-closed guarantees (no local redaction)

fulfill_request raises ObligationNotFulfillableError — caller must block, never forward — when an obligation:

  • names no request-phase fulfillment,
  • advertises a content-type the PEP is not holding,
  • names an endpoint other than the request-redaction endpoint (a malformed verdict can't steer the PEP into calling an arbitrary URL),
  • the engine call fails, or
  • the engine reports redaction_evaluated=false (redactor disabled — can't distinguish "looked, found nothing" from "wasn't looking").

Verification

  • 27 unit tests (tests/test_pep.py) covering decide parsing, every fail-closed branch, passthrough, and decide_and_fulfill.
  • runtime-e2e (runtime-e2e/decide_fulfill_obligation/test.py) against a real enterprise agent (HTTP Basic org:license, real Ed25519 license): proves decide → fulfill → masked (email + credit card masked by the engine, raw PII gone) and that demo credentials are refused (401 → AuthenticationError). No mocks (passes the no-mocks lint).
  • mypy axonflow clean (no new errors), ruff check/ruff format clean, full suite 1038 passed, coverage 82% (pep.py 97%).
  • Wire-shape: new platform fields recorded in the baseline as an acknowledged SDK superset; openapi_specs_sha unchanged (no SHA-bump-guard trip).

Compatibility

Purely additive, optional fields backward-compatible with older platforms. SDK semver is decoupled from the platform: minor bump 8.4.0 → 8.5.0.

Comment thread runtime-e2e/decide_fulfill_obligation/test.py Fixed
@saurabhjain1592 saurabhjain1592 force-pushed the feat/2571-pep-decide-fulfill-obligation branch 2 times, most recently from 3da5d03 to 69bd668 Compare June 9, 2026 06:24
Add the SDK analog of platform/shared/pep (ADR-056, epic #2563): a decide
client that surfaces engine-fulfillable redact_pii obligations, plus a
fulfill helper that discharges them by round-tripping content through the
named engine endpoint (check-input) -- never by redacting locally.

- decide() / fulfill_request() / decide_and_fulfill() on async + sync clients
- DecideRequest/DecideResponse/Obligation/ObligationFulfillment types
- redacted/redacted_statement/redaction_evaluated on MCPCheckInputResponse;
  redaction_evaluated on MCPCheckOutputResponse; content_type on check-input
- ObligationNotFulfillableError fail-closed signal; PEP constants + helper
- 27 unit tests + runtime-e2e (real enterprise agent: decide->fulfill->masked,
  demo creds refused); wire-shape baseline annotated (SHA unchanged)

Minor bump 8.4.0 -> 8.5.0 (additive, SDK semver decoupled from platform).

Signed-off-by: Saurabh Jain <saurabh.jain@getaxonflow.com>
@saurabhjain1592 saurabhjain1592 force-pushed the feat/2571-pep-decide-fulfill-obligation branch from 69bd668 to 3b2f41f Compare June 9, 2026 06:35
…571)

R3 LOW follow-up: a self-contradictory engine response (redacted=true but
no redacted_statement) now raises ObligationNotFulfillableError instead of
forwarding the unredacted original. Adds a unit test.

Signed-off-by: Saurabh Jain <saurabh.jain@getaxonflow.com>
The LOW-1 fix insertion shifted one client.py baselined finding's line number;
re-keyed via --write-baseline (line-shift only, no new findings).

Signed-off-by: Saurabh Jain <saurabh.jain@getaxonflow.com>
@saurabhjain1592 saurabhjain1592 merged commit 008ebe0 into main Jun 9, 2026
18 checks passed
@saurabhjain1592 saurabhjain1592 deleted the feat/2571-pep-decide-fulfill-obligation branch June 9, 2026 09:57
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.

2 participants