Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 15 additions & 15 deletions .lint_baselines/falsey_clobber.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,24 +22,24 @@
"axonflow/adapters/tool_wrapper.py:190:20",
"axonflow/adapters/tool_wrapper.py:208:20",
"axonflow/adapters/tool_wrapper.py:220:20",
"axonflow/client.py:1124:20",
"axonflow/client.py:1225:20",
"axonflow/client.py:1405:16",
"axonflow/client.py:1482:16",
"axonflow/client.py:2027:37",
"axonflow/client.py:2068:18",
"axonflow/client.py:2126:37",
"axonflow/client.py:2677:24",
"axonflow/client.py:2698:33",
"axonflow/client.py:2699:31",
"axonflow/client.py:2711:25",
"axonflow/client.py:2772:28",
"axonflow/client.py:2813:69",
"axonflow/client.py:1149:20",
"axonflow/client.py:1250:20",
"axonflow/client.py:1430:16",
"axonflow/client.py:1507:16",
"axonflow/client.py:2052:37",
"axonflow/client.py:2093:18",
"axonflow/client.py:2151:37",
"axonflow/client.py:2702:24",
"axonflow/client.py:2723:33",
"axonflow/client.py:2724:31",
"axonflow/client.py:2736:25",
"axonflow/client.py:2797:28",
"axonflow/client.py:2838:69",
"axonflow/client.py:316:14",
"axonflow/client.py:321:24",
"axonflow/client.py:322:20",
"axonflow/client.py:649:44",
"axonflow/client.py:7247:25",
"axonflow/client.py:665:44",
"axonflow/client.py:7300:25",
"axonflow/execution.py:205:19",
"axonflow/masfeat.py:345:23",
"axonflow/masfeat.py:346:24",
Expand Down
8 changes: 4 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@ The first Python SDK release carrying these entries sends the PEP capability han

- **v11 decision provenance on every governed response.** From a v11.0.0 platform, `DecideResponse`, the gateway pre-check result (`PolicyApprovalResult`), `ClientResponse` (`/api/request`), `MCPCheckOutputResponse` and `ConnectorResponse` carry `engine`, `subject_type`, `policy_bundle` and `legacy_validators` (a list of `LegacyValidatorAction`), through a shared `DecisionProvenance` base. `DecideResponse` also carries `policy_identities` (each matched policy named, as `PolicyIdentity`), `policy_packs` and `document_version`, and the pre-check result carries `decision_id` and `verdict`. Every new field is `None` on an older platform.
- **`LegacyPolicyWriteFrozenError`.** A v11.0.0 platform refuses writes to the static- and dynamic-policy routes with `409 LEGACY_POLICY_WRITE_FROZEN`. The SDK raises this typed error, a subclass of `AxonFlowError`, carrying the platform's message, which names the typed policy route. Every other 409 is handled as before.
- **`PlatformRouteDeprecationWarning`.** When the platform stamps the route a call used as deprecated (`X-AxonFlow-Removed-In`, or the RFC 9745 `Deprecation` header), the SDK issues this `DeprecationWarning`, naming the successor route from `Link: rel="successor-version"` and the removal release. A v11.0.0 platform stamps the legacy static-policy, dynamic-policy and policy-simulation routes. Each route is reported once per client, keyed by method and path without the query string, and a client derived through `as_user` shares that memory, as the TypeScript and Go SDKs do.
- **`PlatformRouteDeprecationWarning`.** When the platform stamps the route a call used as deprecated (`X-AxonFlow-Removed-In`, or the RFC 9745 `Deprecation` header), the SDK issues this `DeprecationWarning`, naming the successor route from `Link: rel="successor-version"` and the removal release. A v11.0.0 platform stamps the legacy static-policy, dynamic-policy and policy-simulation routes. Each route is reported once per client, keyed by method and route: a route that carries an id is reported once, by its template (for example `GET /api/v1/static-policies/{id}`), and any other by its path without the query string. A client derived through `as_user` shares that memory.
- **The PEP capability handshake.** A v11 platform lets an enforcement point declare, on each call, the exact obligation types and versions it can discharge, in the `X-Axonflow-PEP-Handshake` header. `PEPHandshake` builds the declaration (`pep_id`, `audience` and the exact `PEPCapability` set, which may be empty), validates it by the platform's own rules, raising `PEPHandshakeError` with the JSON Pointer of the member at fault, and encodes it once. Pass it as `AxonFlow(pep_handshake=...)` to present it on every call to a plane that reads it: `decide`, `evaluate`, `evaluate_all`, `mcp_check_input`, `mcp_check_output`, `check_tool_input`, `check_tool_output`, `get_policy_approved_context`, `pre_check`, and the engine round-trip of `fulfill_request` and `decide_and_fulfill`. Pass it to one of those methods as `pep_handshake=` to declare it for that call only, in place of the client's. It is never sent to `proxy_llm_call` (`/api/request`), the OpenAI-compatible route or any other route, none of which reads it, and a client given no declaration sends none. On an Enterprise deployment, an allow carrying a mandatory obligation the declared set cannot discharge becomes a deny. From v11.0.0, in every edition, `decide` under an organization's redact override refuses a client that does not declare redaction, including one that sends no handshake. A call that passes `pep_handshake=` and also sets the header in `extra_headers` is refused.
- **Typed policy authoring (`client.typed_policies`).** The v11 successor to the legacy policy routes: `edition()`, `validate()`, `publish()`, `activate()`, `active()` and `system()` over the six routes the agent proxies under `/api/v1/typed-policies`, with typed results and `TypedPolicyRefusal` carrying the status, the platform's `reason`, any findings and `retry_after`. `activate()` promotes a published digest; rollback and withdraw are customer portal operations that the agent does not proxy, so the SDK has no method for them. The synchronous client has the same namespace.
- **Examples for the v11.0.0 platform.** `examples/typed_policies.py` authors policy as a typed document (it publishes and activates only when asked), and `examples/pep_handshake.py` declares an enforcement point's capabilities for the client and for one call. Both exit non-zero when a step fails, and CI compiles and imports them. The README gains a "v11.0.0 platform" section naming what each part needs from the platform, and its feature overview no longer shows `list_policies()`, which this SDK does not have, or a dynamic-policy write, which a v11.0.0 platform refuses.
- **Typed policy authoring (`client.typed_policies`).** The v11 successor to the legacy policy routes: `edition()`, `validate()`, `publish()`, `activate()`, `active()` and `system()` over the six routes the agent proxies under `/api/v1/typed-policies`, with typed results and `TypedPolicyRefusal` carrying the status, the platform's `reason`, the `policy` a tier refusal names, any findings and `retry_after`. The typed answers carry every member the platform sends: `TypedAuthoringEdition`'s `catalog_digest`, `registry_version` and `catalog_fixture`; a `TemplateOmissionReport` (`template_omissions` and `template_omissions_unavailable` on the publication and the activation), naming the organization template's controls a document omits, since activating it removes them; and `TypedPolicySystemControl.name` and `mandatory`, a plain `bool` that is `False` when the platform omits it. `active()` returns `None` only for the platform's `nothing_active`; any other 404, from a platform before v11.0.0 or an endpoint that is not an agent, raises `TypedPolicyRefusal` with status 404. The platform currently also answers `nothing_active` for a store read failure (getaxonflow/axonflow-enterprise#4255). `activate()` promotes a published digest; rollback and withdraw are customer portal operations that the agent does not proxy, so the SDK has no method for them. The synchronous client has the same namespace.
- **Examples for the v11.0.0 platform.** `examples/pep_handshake.py` declares an enforcement point's capabilities for the client and for one call, and `examples/typed_policies.py` authors policy as a typed document (it publishes and activates only when asked, prints the publication's template-omission report before it activates, and finds its default document from its own location, so it runs from any directory). Both exit non-zero when a step fails, including a publication or activation the example asked for and the platform refused; the README runs the handshake example first, and CI compiles and imports them. The README gains a "v11.0.0 platform" section naming what each part needs from the platform, and its feature overview no longer shows `list_policies()`, which this SDK does not have, or a dynamic-policy write, which a v11.0.0 platform refuses.

### Deprecated

- **`simulate_policies`, `get_policy_impact_report` and `detect_policy_conflicts`.** A v11.0.0 platform deprecates the policy simulation routes and removes them in v11.1. Each method's documentation says so, and the client reports each route once through `PlatformRouteDeprecationWarning` when the platform stamps it; the successor is `/api/v1/typed-policies`. `create_policy_override` and `delete_policy_override` document that a v11.0.0 platform refuses them with `409 LEGACY_POLICY_WRITE_FROZEN`, raised as `LegacyPolicyWriteFrozenError`.
- **`simulate_policies`, `get_policy_impact_report` and `detect_policy_conflicts`.** A v11.0.0 platform deprecates the policy simulation routes and removes them in v11.1. Each keeps answering until v11.1; on a v11.0.0 platform its result comes from the legacy engine, which no longer decides, so it does not predict what the platform enforces. Each method's documentation says so, and the client reports each route once through `PlatformRouteDeprecationWarning` when the platform stamps it; the successor is `/api/v1/typed-policies`. `create_policy_override` and `delete_policy_override` document that a v11.0.0 platform refuses them with `409 LEGACY_POLICY_WRITE_FROZEN`, raised as `LegacyPolicyWriteFrozenError`.

### Fixed

Expand Down
14 changes: 8 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -343,11 +343,11 @@ Against a v11.0.0 platform this SDK reaches the new decision plane. Against an o

- **Decision provenance (v11.0.0).** `DecideResponse`, the gateway pre-check result (`PolicyApprovalResult`), `ClientResponse` (`proxy_llm_call`), `MCPCheckOutputResponse` and `ConnectorResponse` carry `engine`, `subject_type`, `policy_bundle` and `legacy_validators`. A decision adds `policy_identities`, `policy_packs` and `document_version`, and the pre-check result `decision_id` and `verdict`. `legacy_validators` is filled only where a checksum validator acted, so it is `None` on `proxy_llm_call` by design.
- **Frozen legacy policy writes (v11.0.0).** A write to the static- or dynamic-policy routes is refused with `409 LEGACY_POLICY_WRITE_FROZEN`, raised as `LegacyPolicyWriteFrozenError`, whose message names the typed policy route.
- **Route deprecation (v11.0.0).** The platform stamps its legacy policy routes, and the client reports each stamped route once through `PlatformRouteDeprecationWarning`. `simulate_policies`, `get_policy_impact_report` and `detect_policy_conflicts` are documented as deprecated: the platform removes their routes in v11.1.
- **Route deprecation (v11.0.0).** The platform stamps its legacy policy routes, and the client reports each stamped route once through `PlatformRouteDeprecationWarning`; a route that carries an id is reported once, by its template (`GET /api/v1/static-policies/{id}`), not once per id. `simulate_policies`, `get_policy_impact_report` and `detect_policy_conflicts` are deprecated: each keeps answering until v11.1, and on a v11.0.0 platform its result comes from the legacy engine, which no longer decides, so it does not predict what the platform enforces.
- **The PEP capability handshake (v10.4.0).** The platform reads the declaration from v10.4.0. From v11.0.0, an organization's redact override on `decide` refuses a caller that does not declare redaction (see [PEP capability handshake](#pep-capability-handshake)).
- **Typed policy authoring (v11.0.0).** An older platform does not serve these routes: `edition()`, `validate()`, `publish()` and `activate()` raise `TypedPolicyRefusal` with the platform's answer, and `active()` reads a 404 as `None` (see [Typed policy authoring](#typed-policy-authoring)).
- **Typed policy authoring (v11.0.0).** An older platform does not serve these routes: `edition()`, `validate()`, `publish()` and `activate()` raise `TypedPolicyRefusal` with the platform's answer, and `active()` does not read that as nothing active: it returns `None` only for the platform's `404 nothing_active` (see [Typed policy authoring](#typed-policy-authoring)).

Runnable programs: [`examples/typed_policies.py`](examples/typed_policies.py) and [`examples/pep_handshake.py`](examples/pep_handshake.py).
Runnable programs, in this order: [`examples/pep_handshake.py`](examples/pep_handshake.py), then [`examples/typed_policies.py`](examples/typed_policies.py). Run the handshake example first: after a document with an organization-scope constraint is activated, a decide that does not supply the attribute the constraint conditions on is denied fail-closed with reasons ["unknown_constraint"]; supply the attribute or run this example on a fresh stack. From v11.0.0 the deny's first reason is that code, followed by one naming each constraint it could not evaluate and the attribute it needed (getaxonflow/axonflow-enterprise#4247). The typed-policies example publishes and activates only when asked, and before it activates it prints which of the organization template's controls the document omits.

### PEP capability handshake

Expand All @@ -373,7 +373,7 @@ The client sends it on every call to a plane that reads it: `decide`, `evaluate`
One process can be two enforcement points: a request path and a response path that discharge different obligations. Pass `pep_handshake=` to one of those methods to declare it for that call only, in place of the client's.

- **There is no default.** A client given no declaration sends no header. From v11.0.0 that is not neutral: `decide` under an organization's redact override refuses a caller that does not declare redaction, in every edition. `capabilities=[]` declares that the enforcement point discharges nothing.
- **What a declaration changes.** On an Enterprise deployment, an allow verdict carrying a mandatory obligation the declared set cannot discharge becomes a deny, so declare every obligation your enforcement point carries out, and only those. A Community deployment does not turn such an allow into a deny, and drops any capability in a family it does not issue; the redact-override refusal above applies in every edition.
- **Which edition refuses what.** From v11.0.0, on every edition, the engine refuses with `unsupported_obligation` a mandatory obligation the caller's declaration cannot discharge, and a caller that presents no declaration can discharge none: `decide` under an organization's redact override refuses a caller that does not declare redaction (`field_redact` at version 1), so on Community too a caller that declares `field_mask` but not `field_redact` is refused there. What only Enterprise adds happens at the handler, for an enforcement point that presented a declaration: an allow carrying a mandatory obligation outside the declared set becomes a deny, a refusal names the capability the declaration lacks, and on the MCP check-input round-trip a redaction the declaration cannot discharge is refused rather than handed back masked. So declare every obligation your enforcement point carries out, and only those. A Community deployment drops a declared capability in a family its edition does not issue, counts it, and lets the request proceed.
- **Refused before it is sent.** `PEPHandshake` applies the platform's own rules at construction and raises `PEPHandshakeError` naming the member at fault (`.pointer` is `/pep_id`, `/audience` or `/capabilities`), instead of the first governed call coming back `400`.
- **A header set by hand** in `extra_headers` is sent as given, in place of the client's declaration for that call. Passing it together with `pep_handshake=` is refused.

Expand All @@ -391,9 +391,11 @@ async with AxonFlow(endpoint="...", client_id="...", client_secret="...") as cli
system = await client.typed_policies.system() # the platform's own controls, read-only
```

- **Activation replaces the organization template.** Activating a document that omits the organization template's controls removes those controls for the organization. The template's controls carry the destructive-command blocks, DROP and TRUNCATE prevention and the blocking SQL-injection rows. `publish()` and `activate()` report which ones a document omits as `template_omissions`, and the example prints that report. The publish fixture the example uses is a minimal example, not a starting point for production: it omits all of them.
- **Activation promotes.** A digest whose version does not advance past the active one is refused. Rolling back to an earlier document, and withdrawing the active one, are operations of the customer portal behind its session; the agent does not proxy them, so the SDK has no method for either.
- **The organization and the author are the ones your credentials resolve to.** The agent stamps both, and the platform overwrites any author named inside the document.
- **Refusals are typed.** Every refusal raises `TypedPolicyRefusal` with the HTTP `status`, the platform's `reason` (such as `publication_refused`, `activation_refused` or `tier_limit`), any `findings`, and `retry_after` when the refusal is retryable. On an edition with separation of duties, publishing refuses with the finding code `APPROVER_IS_AUTHOR`: the route names no approver, and such a deployment approves in the customer portal.
- **The organization and the author are the ones your credentials resolve to.** The agent stamps both, and the platform overwrites any author named inside the document. On Community the organization is the deployment's (`ORG_ID`).
- **Refusals are typed.** Every refusal raises `TypedPolicyRefusal` with the HTTP `status`, the platform's `reason` (such as `publication_refused`, `activation_refused` or `tier_limit`), any `findings`, the `policy` a tier refusal names, and `retry_after` when the refusal is retryable. On an edition with separation of duties, publishing refuses with the finding code `APPROVER_IS_AUTHOR`: the route names no approver, and such a deployment approves in the customer portal.
- **`active()` is `None` only for the platform's `nothing_active`.** Any other `404`, from a platform before v11.0.0 or an endpoint that is not an agent, raises `TypedPolicyRefusal` with status `404`. The platform currently also answers `nothing_active` when its document store cannot be read (getaxonflow/axonflow-enterprise#4255).
- `validate()` answers identically on every edition; the edition's boundary is applied when you publish.

## Configuration
Expand Down
2 changes: 2 additions & 0 deletions axonflow/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@
AuthoringFinding,
EditionConstructReport,
SyncTypedPoliciesNamespace,
TemplateOmissionReport,
TypedAuthoringDocumentRequest,
TypedAuthoringEdition,
TypedPoliciesNamespace,
Expand Down Expand Up @@ -430,6 +431,7 @@
"ActiveTypedPolicy",
"TypedPolicySystemControl",
"TypedPolicySystemCorpus",
"TemplateOmissionReport",
"REQUEST_REDACTION_PATH",
"RESPONSE_REDACTION_PATH",
"has_request_redaction",
Expand Down
Loading
Loading