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
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ The first Python SDK release carrying these entries sends the PEP capability han
- **`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 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`, 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.
- **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. A v11.0.0 platform answers a document store it cannot read with 503 `storage_unavailable`, which raises it too (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
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,7 @@ async with AxonFlow(endpoint="...", client_id="...", client_secret="...") as cli
- **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. 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).
- **`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`. A v11.0.0 platform answers a document store it cannot read with 503 `storage_unavailable`, which raises `TypedPolicyRefusal` too (getaxonflow/axonflow-enterprise#4255).
- `validate()` answers identically on every edition; the edition's boundary is applied when you publish.

## Configuration
Expand Down
6 changes: 3 additions & 3 deletions axonflow/typed_policies.py
Original file line number Diff line number Diff line change
Expand Up @@ -358,9 +358,9 @@ async def active(self) -> ActiveTypedPolicy | None:
``None`` only when the platform answers that nothing is active: a ``404``
whose reason is ``nothing_active``. Any other ``404``, from a platform
before v11.0.0 or an endpoint that is not an agent, raises
:class:`~axonflow.exceptions.TypedPolicyRefusal` with status 404. The
platform currently also answers ``nothing_active`` when its document
store cannot be read (getaxonflow/axonflow-enterprise#4255).
:class:`~axonflow.exceptions.TypedPolicyRefusal` with status 404. A
v11.0.0 platform answers a document store it cannot read with 503
``storage_unavailable``, raised the same way (getaxonflow/axonflow-enterprise#4255).
"""
response = await self._send("GET", f"{TYPED_POLICIES_PATH}/active")
if response.status_code == 404 and _reason(response) == "nothing_active": # noqa: PLR2004
Expand Down
2 changes: 1 addition & 1 deletion runtime-e2e/v11_examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ A stack built before getaxonflow/axonflow-enterprise#4247 shows only the bare `[
## What it does not prove

- **That the handshake reaches the wire.** A fresh Community stack allows the first decide with or without a declaration, and the `/pep_id` refusal happens in the client. The wire proof is `runtime-e2e/pep_handshake_planes`.
- **That the organization carries no other state.** The precondition proves no typed document is active. A recorded detection override, or a legacy per-policy override on a platform that still accepts them, would still apply. And while getaxonflow/axonflow-enterprise#4255 is open, an unreadable document store also answers `nothing_active`.
- **That the organization carries no other state.** The precondition proves no typed document is active. A recorded detection override, or a legacy per-policy override on a platform that still accepts them, would still apply. And on a stack built before getaxonflow/axonflow-enterprise#4255, an unreadable document store also answers `nothing_active`.

## Run

Expand Down
9 changes: 9 additions & 0 deletions tests/test_typed_policies.py
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,15 @@ def _refusal(status, reason, **extra):
pytest.param("activate", 409, _refusal(409, "activation_refused"), {}, id="activate-409"),
pytest.param("validate", 503, _refusal(503, "catalog_not_configured"), {}, id="validate-503"),
pytest.param("edition", 404, _refusal(404, "no_such_endpoint"), {}, id="edition-404"),
# A v11.0.0 platform answers a document store it cannot read with 503 storage_unavailable,
# not nothing_active (getaxonflow/axonflow-enterprise#4255).
pytest.param(
"active",
503,
_refusal(503, "storage_unavailable"),
{},
id="active-503-storage-unavailable",
),
]


Expand Down
Loading