v9.13 → v9.14 Upgrade Guide
v9.14.0 is mostly a compliance-surface release: one report facade across all five regulators (EU AI Act, SEBI, RBI, MAS FEAT, OJK), real renderers behind every format, and honest three-state answers where an empty 200 used to stand in for everything. Almost all of it lands on restart and asks nothing of you.
Three changes do need a decision before you pull the image, and this guide
is about those. The largest by far is a refusal change: the whole-tenant
compliance and audit export endpoints now require administrative authority,
not merely tenant-wide read scope. Export automation that ran on a tenant-scoped
license or internal-service credential worked on v9.12 and v9.13 and returns
403 on v9.14 until you give it an admin per-user token. The other two are OJK
specific: identity resolution is now org-first (a blank org header is refused
400), and OJK readiness is now measured rather than asserted (so a score that
looked high before can drop, correctly).
There is no new preflight for v9.14. The four migrations are additive and mutate no rows, so the v9.13 preflight's backup check is the only preflight step that still applies. If you are jumping from v9.12 you must work the v9.13 guide first - read Start here before anything below.
Start here: you also need the v9.13 guide
This guide covers only the delta from v9.13 to v9.14. A jump from v9.12.x
straight to v9.14.0 crosses both release boundaries, and the v9.13 boundary
carries the changes with irreversible data effects - core/155 disables policy
rows whose identifiers are unrecoverable afterwards, and core/156 stamps a
sentinel its own down migration deliberately leaves behind.
If you are on v9.12.x:
- Work through the v9.12 → v9.13 Upgrade Guide in full first - run its preflight against your still-running v9.12 stack, resolve every FAIL, read every WARNING, and take the snapshot it asks for. Nothing on v9.14 supersedes that procedure.
- Then return here for the v9.14 delta.
The two boundaries can be crossed in a single image pull (migrations from both apply in order at boot), but the decisions in the v9.13 guide have to be made before that pull, not after. The v9.13 preflight reads your old stack; once you are on v9.14 the rows it would have named no longer exist to be named.
If you are already on v9.13, skip straight to What changed.
Before you upgrade: take a snapshot
All four v9.14 migrations are additive and rewrite no existing rows, so v9.14
adds no new irreversible data effect of its own. Take the usual pre-upgrade
database snapshot anyway - if you are crossing the v9.13 boundary in the same
pull, its core/155 and core/156 data changes are the reason the snapshot
matters, and a snapshot is their only rollback. Re-pinning the previous image
digests rolls the images back exactly and undoes neither migration's data
change.
What changed - the things that need a decision
1. Whole-tenant compliance and audit exports now require ADMIN authority
This is the change most likely to break a working self-hosted integration, and it is silent in the sense that nothing in your database records who is affected - it is a property of the credential your export automation carries, not of your deployment.
What is gated. On v9.14 these endpoints require the caller to hold
administrative authority over the tenant, and answer 403 otherwise:
POST /api/v1/ojk/audit/exportPOST /api/v1/sebi/audit/exportPOST /api/v1/euaiact/export- the evidence export and media-governance export
- compliance-report generate (
POST /api/v1/compliance/reports) and download (GET /api/v1/compliance/reports/{id}/download)
The compliance-report status poll (GET /api/v1/compliance/reports/{id})
is deliberately left readable by any session in the tenancy - only generate and
download are gated.
Separately, the audit REST read endpoints (/api/v1/audit/report,
/search, /session-summary, /export) have been RBAC read-scoped since v9.13
(#2922): a non-admin per-user token, or a license/internal-service credential,
is scoped to its own rows and a full audit trail returns empty or near-empty
rather than 403. That is the same axis. If any of those reads is driven by a
license or internal-service credential and expects the whole tenant's trail,
it needs the same admin token described below.
Why it changed. Administrative authority and tenant-wide read scope used to be one field. A portal session with only viewer-level read permission carries tenant-wide read scope, and that was enough to POST a whole-tenant export. The two axes are now separate: read scope answers "how wide may this caller see within its own tenant," and admin authority answers "is this caller an administrator." Exports gate on the second. (#3248)
The symptom. Export automation that returned 200 on v9.12/v9.13 now
returns 403 with a body like tenant-wide audit export requires an admin/owner role. A tenant-scoped license key or internal-service credential carries
tenant-wide read but not admin authority, so this hits export automation
that authenticates with the license credential alone.
Portal sessions in admin, owner or policy_admin roles are unaffected - the
portal stamps a trusted admin-authority assertion for exactly those roles.
Portal sessions in viewer or developer roles now receive 403 on export POSTs.
The admin-authority assertion the orchestrator honors is stamped by the agent
from a validated per-user token and is stripped from inbound client requests
at the agent boundary - you cannot set it directly. Restore export automation by
minting an admin (or owner) role per-user token and sending it as the
X-User-Token header to the agent, alongside your existing HTTP Basic
license credential. A token sent straight to the orchestrator does not elevate.
Mint the token. The install bundle ships mint-admin-token.sh, which signs
a short-lived HS256 user_token with role=admin locally over your
AXONFLOW_JWT_SECRET - it does not call the platform and does not need the stack
up. It fails closed if the secret is unset (an empty key signs forgeable
tokens). Read its header comment for the full claim shape; the common calls:
# From the axonflow-install bundle directory. Org and JWT secret are read
# from ./.env; default TTL is 24h; default role is admin.
./mint-admin-token.sh
# Explicit org and a 1-hour owner token:
./mint-admin-token.sh --org acme --role owner --ttl-hours 1
# Capture the token for scripting (human output and the sample curl go to
# stderr, so this captures only the token):
TOKEN="$(./mint-admin-token.sh)"
Only --role admin and --role owner are accepted; the script refuses any
other role because only those restore tenant-wide authority.
Wire it into your export automation. Add the minted token as X-User-Token
on the request to the agent, keeping your existing license credential:
curl -sS -u "$AXONFLOW_ORG_ID:$AXONFLOW_LICENSE_KEY" \
-H "X-User-Token: $TOKEN" \
-X POST "$AXONFLOW_AGENT_URL/api/v1/ojk/audit/export" \
-H 'Content-Type: application/json' \
-d '{ ... }'
Mint on a schedule that stays inside the token TTL (default 24h; set
--ttl-hours to match your automation cadence, minimum 1). Sign with the same
AXONFLOW_JWT_SECRET the agent uses.
If your export automation instead drives the portal (a browser or portal-session
flow rather than a direct API call), move it to an admin, owner or policy_admin
portal role - a viewer or developer session now receives 403 on export
actions. Portal org-login sessions in an admin role need no token change.
2. Four additive migrations run at boot
Parallel to how the v9.13 guide names core/155 and core/156: here is what
runs on the v9.14 pull. All four are additive and mutate no existing rows, so
there is no maintenance-window concern and no data to capture beforehand.
| Migration | Applies to | What it does |
|---|---|---|
core/157 | every deployment mode | Adds nullable static_policies.segment_id. NULL on every existing row; no behavior change until a segment-scoped policy is authored. |
enterprise/136 | enterprise migration sets | New compliance_report_jobs table (org- and tenant-keyed, RLS enabled and forced, CHECK-constrained completion states). |
enterprise/137 | enterprise migration sets | New indonesia_pii_detection_events table (RLS on org_id, masked values only). |
enterprise/138 | enterprise migration sets | Adds storage columns to euaiact_exports, fixing a GET /api/v1/euaiact/export 500 that hit every non-travel deployment. Its down migration deliberately retains the columns. |
in-vpc-enterprise runs all four. There is no ACCESS EXCLUSIVE scan window
here as there was for core/156 on v9.13 - these add a nullable column and
three new tables, which do not scan existing data.
3. OJK identity resolution is org-first; a blank org header is refused
OJK routes now resolve their scoping organization from X-Org-ID first, falling
back to X-Tenant-ID only when the org header is absent, and the value is
trimmed. A blank or whitespace-only org identity is now refused with 400 missing_org instead of reaching the repositories as an empty scope. The previous
resolver read the tenant header first into org-labelled columns. (#3250)
What to do. Ensure every OJK caller sends a valid, non-blank org or tenant
header. Callers going through the AxonFlow Agent or the Customer Portal already
do - both stamp those headers from the validated credential - so this bites only
a caller that reaches OJK routes with an empty or whitespace org value. On v9
licenses where the license organization and the tenant string legitimately
differ, sending the correct X-Org-ID also matters for correctness: the tenancy
predicate on audit_logs-backed sections now distinguishes org from tenant
rather than treating either as interchangeable.
If your deployment holds ojk_breach_notifications rows and sends distinct
org and tenant values, read docs/compliance/ojk-org-scope-upgrade.md in the
platform tree before upgrading - the tenancy predicate on those rows changed
shape.
4. OJK readiness is now MEASURED, not asserted
Set expectations here so a lower number after the upgrade is not read as a
regression. Before v9.14, four of the five OJK readiness checks returned
unconditional pass literals and the dashboard carried hardcoded counts - a
deployment with no reachable database could still score 80 or better. Every check
now queries the state it names, or reports unknown, which scores zero and stays
in the denominator. (#3250)
The consequence: OJK readiness scores on real deployments will move,
typically down, on upgrade. That is the score becoming honest, not the platform
regressing. The OJK module also now appears in the /health components map, so
/health gains an OJK entry it did not carry before.
What you need to do - by consumer type
Compliance export automation (the one that matters here)
If any automation calls a whole-tenant compliance or audit export endpoint
(OJK, SEBI, EU AI Act, evidence, media-governance, or compliance-report
generate/download) using a license key or internal-service credential, it
will 403 on v9.14. Mint an admin per-user token with mint-admin-token.sh and
send it as X-User-Token to the agent alongside the license credential - see
section 1. This is the single most likely break on an
existing self-hosted integration.
OJK consumers
Confirm every OJK caller sends a non-blank X-Org-ID (or X-Tenant-ID) - a
blank org identity is now 400 (section 3) - and expect the
OJK readiness score to drop to its real value (section 4).
Agent- and portal-proxied callers already send the header.
SDK / plugin / decision / MCP / policy enforcement consumers
Nothing, with one edge condition below. Enforcement callers on
/api/v1/decide, /api/policy/pre-check, the MCP evaluate plane, and the
v9.13-era tenancy-header requirements are unchanged by v9.14. The compliance and
export gates above do not touch the enforcement path.
/api/request planeOn enterprise mode, the segment gate (#3057, ADR-060) now participates in policy
selection on /api/request. A genuine segment-resolution error fails the
request closed, and a governed /api/request call whose token carries no
email claim currently fail-closes to 403. Most integrations use
/api/v1/decide, /api/policy/pre-check or the MCP evaluate plane - not
/api/request - and are entirely unaffected. If any caller does use
/api/request, mint its token with an email claim (for example
--kind user --email you@your-org --org-id your-org) so it resolves an identity.
The empty-email case is loosened to proceed org-only in v9.14.2; until then,
carry the email claim.
SEBI export consumers only
POST /api/v1/sebi/audit/export?format=xml now returns 501 XML_NOT_IMPLEMENTED (it previously returned a JSON body mislabeled under an XML
content type, so nothing could have consumed it as real XML), and format=csv
now returns genuine CSV rather than JSON under a text/csv header. Audit any
SEBI export automation for a hardcoded format=xml or a JSON parse of the csv
response before upgrading. OJK-only deployments are unaffected. (#3248)
Self-hosted operators (summary)
- If on v9.12.x, complete the v9.13 upgrade guide in full first.
- Take a database snapshot.
- Give export automation an admin per-user token via
X-User-Token(section 1). - Confirm OJK callers send a non-blank org header, and expect the OJK readiness score to drop to its measured value.
- Pull the image. The four migrations apply additively at boot; no window sizing is needed.
Getting help
If something behaves differently after upgrading, the most useful information for support:
- The exact endpoint and HTTP status code. A
403on an export endpoint is almost always the admin-authority gate - say whether the caller was a license credential or a portal session, and which role. - Your
DEPLOYMENT_MODE, quoted, on both the agent and the orchestrator. - For an OJK
400, the org and tenant header values the caller sent (redact as needed).
[email protected] - please mention "v9.14 upgrade" in the subject so it routes correctly.
See also
- v9.12 → v9.13 Upgrade Guide - the previous boundary and its preflight; required first if you are on v9.12.x.
- v9.14.0 Release Notes - the full list of what changed and why.
- v8 → v9 Migration Guide - the previous major upgrade (audit decision vocabulary).
- Deployment Mode Matrix - which
DEPLOYMENT_MODEa deployment should carry. - Failure Modes And Recovery - degraded-provider, connector, approval and runtime behaviour.
