Skip to content

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

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

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

Conversation

@saurabhjain1592

@saurabhjain1592 saurabhjain1592 commented Jun 9, 2026

Copy link
Copy Markdown
Member

Ports the Decision Mode PEP (decide → fulfill → forward) contract into the Rust SDK — the SDK analog of platform/shared/pep (ADR-056, epic getaxonflow/axonflow-enterprise#2563, tracked by #2571). Cross-SDK parity with the Go (#181), TypeScript (#241), Python (#211), and Java SDK PRs; JSON wire field names are byte-identical.

What's in it

New PEP surface on AxonFlowClient:

  • decide(DecideRequest) -> DecideResponsePOST /api/v1/decide over the client's existing HTTP Basic (org:license) auth. 401 → AxonFlowError::ApiError { status: 401 }; deny is returned in the body (200), not an error.
  • fulfill_request(&DecideResponse, &str) -> (String, bool) — discharges each request-phase redact_pii obligation by POSTing to the obligation's check-input endpoint and returning engine-redacted content. Fails closed (AxonFlowError::ObligationNotFulfillable, never the original) on: no request-phase fulfillment, unadvertised content-type, foreign endpoint, engine error/non-200, or redaction_evaluated false/absent.
  • decide_and_fulfill(DecideRequest) -> (verdict, content, DecideResponse) — one-call path; surfaces the fail-closed error so a caller cannot forward the unredacted query.
  • has_request_redaction(&[Obligation]) -> bool.

Types (types::pep, re-exported from crate root): DecideRequest, DecideResponse, Obligation, ObligationFulfillment, DecisionCallerIdentity, DecisionTarget, plus MCPCheckInput/Output{Request,Response}. Added content_type to check-input request; redacted/redacted_statement/redaction_evaluated to check-input response; redaction_evaluated to check-output response — all #[serde(default)] so older platforms deserialize cleanly (fail-closed default false).

AxonFlowError::ObligationNotFulfillable(String) — the fail-closed signal; non-retryable, not fail-open-eligible.

No local redaction anywhere — fulfillment is always the engine round-trip.

Verification

  • cargo build / cargo clippy --all-targets -- -D warnings / cargo fmt --check — all green; zero new warnings vs clean tree.
  • Full suite: 103 tests pass (73 lib incl. 22 new PEP unit tests, + integration/doc). New-code coverage: pep.rs 98.8% lines, types/pep.rs 100% (llvm-cov).
  • Unit tests cover decide parse (allow+obligation / deny-in-body / 401), every fail-closed branch, passthrough cases, and decide_and_fulfill allow/deny/unfulfillable.
  • runtime-e2e (runtime-e2e/decide_fulfill_obligation/) against a live enterprise agent, no mocks: decide → allow + obligation; fulfill → engine-masked (jo****************om / 4**************1, neither john.doe@example.com nor 4111111111111111 survives); decide_and_fulfill parity; demo creds refused 401. Evidence committed under EVIDENCE/.

Versioning

Minor bump 0.6.0 → 0.7.0 (additive; SDK semver decoupled from platform). CHANGELOG updated. (NB: the brief said current was 0.6.0 → bump to 0.7.0, but the repo's latest released tag is v0.5.0 and Cargo.toml was 0.5.0; bumped from actual current.)

No wire-shape baseline gate exists in this SDK, so none to regenerate.

Refs getaxonflow/axonflow-enterprise#2571, getaxonflow/axonflow-enterprise#2563

@saurabhjain1592 saurabhjain1592 force-pushed the feat/2571-pep-decide-fulfill-obligation branch from d684e9a to d494ef4 Compare June 9, 2026 06:16
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() / has_request_redaction()
- DecideRequest/DecideResponse/Obligation/ObligationFulfillment +
  DecisionCallerIdentity/DecisionTarget types
- redacted/redacted_statement/redaction_evaluated on MCPCheckInputResponse;
  redaction_evaluated on MCPCheckOutputResponse; content_type on check-input
- AxonFlowError::ObligationNotFulfillable fail-closed signal; PEP constants
- 22 unit tests (98.8% line cov on new code) + runtime-e2e against a real
  enterprise agent (decide->fulfill->masked, demo creds refused 401)

Minor bump 0.5.0 -> 0.6.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 d494ef4 to 99bd849 Compare June 9, 2026 06:30
…dence (#2571)

R3 follow-up:
- LOW: fulfill_request fails closed (ObligationNotFulfillable) on a
  self-contradictory engine response (redacted=true, empty/absent
  redacted_statement) instead of forwarding the unredacted original; adds a
  unit test.
- MEDIUM: replace the stale runtime-e2e evidence log (captured at sdk_version
  0.5.0, predating the shipped path) with a fresh capture against the 0.7.0
  build + the real enterprise agent.

Signed-off-by: Saurabh Jain <saurabh.jain@getaxonflow.com>
@saurabhjain1592 saurabhjain1592 merged commit ba4e30f into main Jun 9, 2026
9 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.

1 participant