Skip to main content

AxonFlow v9.11.0 Release Notes

v9.11.0 is a minor release built around one enabling feature and a governance-correctness sweep. The feature makes AxonFlow's inline prompt-DLP usable on streaming LLM chat: an agentgateway ext_proc leg can now govern and redact the prompt in full before the provider sees it while the SSE completion streams back untouched. Around it, this release closes three governance-signal gaps — a matched PII policy that could still return a bare allow, a headers-only enforcement seam that had no org-controllable posture when it structurally could not mask a body, and the Path B / SSO org-key and per-org role-seeding defects that blocked fleet OIDC and SCIM role mapping.

This release carries database migrations (core/144, core/145, core/146; a core/147 backfill lands with the SCIM role-assignment fix — see Migrations) and three deliberate behavior changes that need explicit attention on upgrade — see Behavior changes and Upgrading.

Behavior changes

Three changes in this release can alter what your deployment allows, denies, or redacts. Read these before upgrading.

⚠️ 1. pii-indonesia is now evaluated on the proxy and OpenAI-compatible planes

Before v9.11.0, the proxy and OpenAI-compatible request planes hand-listed the PII categories they evaluated and omitted pii-indonesia — so a matched Indonesian-PII policy (KTP / NIK) produced a bare allow on those planes: the policy matched, but no obligation and no reason came back. v9.11.0 converges every plane on a single canonical PII category set, so pii-indonesia is now evaluated everywhere the other PII categories are (fix(decide): matched policy must never yield bare allow#2970).

The consequence depends on the effective PII action for that org:

  • Under the default redact posture, a matched KTP/NIK now returns allow + redact_pii on these planes (previously allow with nothing) — content that flowed unredacted is now redacted.
  • Under a block posture, these planes now deny Indonesian-PII traffic they previously forwarded. This is the largest-impact change in the release: a deployment running PII_ACTION=block (or a per-org pii override of block) will start blocking KTP/NIK prompts on the proxy and OpenAI-compatible endpoints. Review your Indonesian-PII posture before upgrading.

This is not gated by deployment mode — the KTP/NIK static policy is global (migrations/core/116, sys_pii_indonesia_ktp) and now carries a governance signal end to end on every request plane.

A related flip: a warn / log PII posture no longer silently redacts. Previously any non-blocking PII match still set a redaction obligation; now warn / log emit only an advisory reason and leave content unmodified, which is what those actions are supposed to mean.

⚠️ 2. Headers-only seams default to log, not deny, on an unmaskable redaction

When /api/v1/decide returns an allow that carries a request-body redaction obligation and the calling enforcement point's seam cannot rewrite a body (for example an Envoy ext_authz leg, which is headers-only), the platform previously converted that outcome to a local allow → 403 deny inside the adapter. v9.11.0 replaces that adapter-local conversion with an engine-resolved, org-controllable posture: the new obligation_fallback detection-posture category (fix(decide): emit only obligations the seam can fulfill#2961).

Its default is log — the request is allowed and the suppressed redaction is recorded in the audit trail. An org that wants deny-on-unmaskable must set obligation_fallback = block (Settings → Governance → Detection Posture, or the detection-posture API). Until it does, a seam that structurally cannot mask a body now allows-and-logs where the older adapter would have returned a 403. See Detection Posture and Decision Mode → Seam capabilities.

⚠️ 3. ext_proc responseBodyMode: none now requires an explicit adapter opt-in

Earlier axonflow-gateway-adapters builds silently accepted an ext_proc leg advertising responseBodyMode: none — the response plane simply never ran, so a gateway-config edit alone could turn response governance off with no signal. v9.11.0 rejects a none response leg fail-closed unless the adapter is explicitly run with AXONFLOW_EXTPROC_RESPONSE_GOVERNANCE=off (feat(gateway-adapters): streaming-safe ext_proc redaction#2966).

A deployment already running responseBodyMode: none must set AXONFLOW_EXTPROC_RESPONSE_GOVERNANCE=off when it upgrades the adapter, or the adapter refuses those legs. The default (buffered) keeps today's fully-governed contract, where every response body is scanned. This is the opt-in behind the streaming feature below.

Added

Streaming-safe prompt-DLP for SSE completions (Enterprise)

Inline prompt governance on a streaming LLM chat previously forced a choice: buffer the whole SSE response to inspect it (defeating streaming) or disable body inspection entirely (losing prompt-DLP). v9.11.0 makes the axonflow-gateway-adapters ext_proc seam validate gateway body modes per direction instead of OR-ing them, so requestBodyMode: buffered + responseBodyMode: none is a supported configuration (#2966).

On such a leg the prompt is still decided and engine-redacted in full before the provider sees it — a deny blocks pre-stream, and AXONFLOW_FAIL_MODE and the body-size bound are unchanged — while the completion streams back as Server-Sent Events with no adapter in the response path. The response body is not scanned on an opted-in none leg; the posture is logged at adapter startup and once per stream. This is the seam for inline prompt-DLP on streaming chat, enabled by the AXONFLOW_EXTPROC_RESPONSE_GOVERNANCE=off opt-in described in behavior change 3. Partial modes (bufferedPartial, streamed, fullDuplexStreamed) remain rejected in both directions. See agentgateway integration → streaming.

Seam-capability–aware obligations (Enterprise)

/api/v1/decide now emits only the obligations the calling seam can actually fulfill (#2961). A PEP advertises what its seam can discharge via fulfillment_capabilities on the decide request (wire vocabulary request_body_redaction, request_header_mutation); the PDP drops any request-body redaction a non-body-capable seam cannot perform, then applies the org's server-side obligation_fallback posturelog allows and records the suppressed redaction, block denies. The posture is resolved from the org, never from the request. A caller that sends no capabilities (any SDK today, or a pre-9.11.0 PEP) is treated as legacy and its obligations are emitted unchanged. This is advertised as the seam_capability_decisioning capability (Since: 9.11.0) and replaces the older adapter-local allow → 403 conversion so every outcome is a single engine round-trip. See Decision Mode → Seam capabilities.

obligation_fallback detection-posture category (Enterprise)

A new per-org, per-category posture — obligation_fallback — controls what happens when a policy decided a request body must be redacted but the enforcement seam structurally cannot mask it. It accepts block or log only (redact is precisely what the seam cannot do; warn has no enforcement distinct from log on this axis), defaults to log, and is set from the same authenticated surface as every other posture: Settings → Governance → Detection Posture in the portal, or the detection-posture API, both gated on sso:configure. See Detection Posture. Backed by migrations/core/144, which widens the detection-posture category set.

caller_name on the tool-call audit surface (Community)

The audit_tool_call MCP tool and the orchestrator tool-call audit entry gain a caller_name field identifying which client made the call (claude_code, codex, cursor, openclaw). The misnamed tool_type — which every real caller used to identify itself, never to classify a tool — is soft-deprecated: still accepted as a legacy input fallback, no longer authoritative. Resolution is central: caller_name → legacy tool_type → a terminal "unknown" default, so an unidentified caller is no longer misattributed to a specific client. New audit rows write policy_details.caller_name and stop writing policy_details.tool_type — SIEM consumers keying on policy_details.tool_type for attribution should move to caller_name; unattributed calls now read "unknown". Historical rows are not backfilled; the WCP/HITL plane's genuine tool_type call-kind is unaffected. The check-output request also gains a tool field for two-field (server, tool) response-plane identity, matching the check-input split.

Client and SDK releases riding this train

  • Plugins (dual-send): Claude Code 1.11.0, Cursor 1.7.0, Codex 1.7.0, OpenClaw 2.8.0 send caller_name alongside the legacy tool_type for the deprecation window — attribution is exact on a 9.11.0+ platform and unchanged on older platforms, so plugins upgrade independently of the platform.
  • SDK 9.0.0 majors (go / python / typescript / java): the langgraph adapters stop concatenating server + tool into a single connector identifier and send them as distinct fields (major because the wire identity of langgraph tool calls changes); the Go module path moves to /v9. The Rust SDK is unaffected and stays 0.8.1.
  • /health recommendations advertise all of the above; minimum supported floors are unchanged.

Fixed

Path B fleet OIDC: SSO configuration no longer collapses the org key (Enterprise)

On in-VPC deployments, the portal collapsed an org's tenant_id to a platform sentinel and wrote that sentinel into org_id as well — but org_id is the row-level-security isolation key. Every reader scoped to the real org then missed the SSO configuration row: the fleet OIDC verifier resolved nothing and rejected every in-VPC Path B / OIDC per-user token fail-closed, the login page offered no SSO, and an admin org-list join broke (fix(sso): decouple org_id from tenant_id#2968). v9.11.0 decouples the two keys — org_id always carries the real org (isolation), tenant_id keeps the deployment-collapsed addressing value — resolves the org for the pre-auth SAML path through a new SECURITY DEFINER function, and migrations/core/145 repairs existing sentinel rows in place. In-VPC Path B tokens now validate. This is a fix, not a new behavior; apply core/145 as part of the upgrade (the row repair requires ORG_ID to be set on the platform — see Migrations).

Per-org fleet system roles are now seeded, and non-fleet SCIM role names are rejected (Enterprise)

An org created after the base role migration had no custom roles, so GET /api/v1/scim/roles returned an empty list and a SCIM group could not be mapped to any role — Path B group-to-role mapping was impossible on those orgs (feat(rbac): seed fleet system roles per org#2969). v9.11.0 seeds the six fleet system rolesadmin, owner, policy_admin, developer, member, viewer — for every organization, via an AFTER INSERT trigger on the organizations table plus a one-time backfill (migrations/core/146), so GET /api/v1/scim/roles now returns the full set and group mapping works. Relatedly, mapping a SCIM group to a role name that is not one of the recognized fleet roles is now rejected with 400 (naming the allowed set) instead of silently dropping the group's members to least privilege.

Migrations

MigrationWhat it doesPR
core/144Widens the detection-posture category set to include obligation_fallback (existence-guarded, with a down migration)#2961
core/145Decouples org_id from tenant_id on SSO configuration, adds the SAML org-resolution function, and repairs sentinel org_id rows (existence-guarded; the row repair applies only when ORG_ID is set on the platform)#2968
core/146Seeds the six fleet system roles per org via an AFTER INSERT trigger + backfill (existence-guarded, with a down migration that keeps the seeded rows)#2969
core/147Backfills / repairs the SCIM role_assignments org key so Path B role resolution reads the same org it was written under#2964

Neither the matched-PII fix (#2970) nor the streaming feature (#2966) adds a migration — the matched-PII fix is code-only (the core/116 change is a comment) and the streaming feature is adapter-side.

Upgrading

Apply the migrations above as part of a normal upgrade.

# Community
docker compose pull && docker compose up -d

# Enterprise
docker compose -f docker-compose.yml -f docker-compose.enterprise.yml pull
docker compose -f docker-compose.yml -f docker-compose.enterprise.yml up -d

Before or during the upgrade, act on the three behavior changes:

  1. Review your Indonesian-PII posture. If any org runs pii at block, KTP/NIK prompts on the proxy and OpenAI-compatible planes will now be denied (previously forwarded). Confirm this is what you want, or set that org's pii posture to redact.
  2. Set obligation_fallback = block if you rely on deny-on-unmaskable. A headers-only seam that cannot mask a body now defaults to allow-and-log; set the posture to block to restore a deny.
  3. Set AXONFLOW_EXTPROC_RESPONSE_GOVERNANCE=off if you already run ext_proc responseBodyMode: none. Otherwise the upgraded adapter rejects those legs fail-closed. Leave it unset (default buffered) to keep every response body scanned.

Single-admin and Community-mode deployments are unaffected by the RBAC / Path B fixes (no fleet of distinct users or IdP to configure).