Releases: getaxonflow/axonflow
v7.4.5 — MAP org propagation fix and example bug fixes
PATCH: bug fixes only. The headline platform fix is a pair of org-identity propagation bugs in the MAP execution path that made GET /api/v1/executions return zero rows for newly-completed plans and let body-supplied identity override the authenticated org/tenant on policy evaluation. The rest is the close-out of the Phase 1 quality-freeze sweep against the bundled examples — every example now compiles, runs, and exits with a clear PASS/FAIL summary against a stock community-mode docker-compose stack.
No breaking changes. No new endpoints, SDK methods, or features.
Community
Fixed
GET /api/v1/executionsreturned zero rows for newly-completed MAP plans. Plans executed viaPOST /api/requestwithrequest_type=execute-planwere recorded in the execution-tracking store with an emptyorg_id, while the read-side filter (driven by the authenticated org from Basic auth) required a non-empty match — so every MAP plan execution produced a row that was invisible to subsequent list calls. The execution recorder now persists the same authenticated org used for filtering on read, and policy evaluation, plan storage, and replay tracking all read org and tenant from the same authoritative source on every request. A request can no longer be recorded under one org and policy-evaluated under another, even if a caller bypasses the agent and supplies mismatched values directly.- MAP examples updated to current SDK releases — Go 5.8.0, TypeScript 6.1.0, Python 6.8.0, Java 6.1.0 — so
go run,npm start,python main.py, andmvn exec:javawork against the published SDKs on a fresh checkout. examples/cost-estimation/http/execution-cost-validation.shnow exits with a clear PASS/FAIL summary instead of aborting mid-run with exit code 5 when a response is malformed (e.g. on auth failure). The script was rewritten to use the generate-plan → execute-plan → fetch-cost flow that actually surfaces a non-zero plan cost in Community.examples/risk-tiered-approvals/gonow compiles from a clean checkout. The directory was missinggo.modandgo.sum, sogo run main.gofailed withno required module provides package. The example also referenced an SDK type that was renamed before release; corrected to the published name. Both Go and Python variants now pass end-to-end. Test 3 (HITL queue listing) skips on Community and Evaluation with an accurate message — the queue endpoint is Enterprise-only and was previously mis-labelled.examples/media-governance-policies/typescriptTest 4b no longer fails withtenant_id=undefined. The TypeScript SDK exposesMediaGovernanceConfigfields in camelCase (tenantId); the example was asserting on the wire-shape snake_case field. Aligned the assertion and the log line.examples/audit-logging(TypeScript and Java) now match the Python variant's authentication setup soauditToolCallsucceeds withoutMissing authenticationerrors.examples/llm-routing/goupdated to the current routing API shape so the demo works against a stock stack.examples/mcp-connectors/cloud-storagerewritten to exercise a working S3-compatible flow against the MinIO instance bundled in the docker-compose stack.examples/.gitignoreno longer excludesgo.sum. Every Go example now ships with its lockfile committed so a clean clone runs without needinggo mod tidy. Two stalereplacedirectives inexamples/wcp-retry-idempotency/{community,evaluation}/go/go.mod(pointing at sibling-checkout SDK paths) and a 0-byte orphanexamples/policies/go/go.modwere also cleaned up.examples/policies/http/policies.shCreate Custom Policy now sends a valid request body — a singlepatternregex with a recognized category — instead of an invalid array shape that the platform was rejecting with HTTP 400. The script previously printed "Status: Created" without checking the response, masking the failure.examples/gateway-policy-config/pythonno longer crashes withTypeError: get_env() missing 1 required positional argument.examples/workflow-control/gonow compiles.ApproveStepandRejectStepwere called with two arguments after the SDK signatures had added a third (approver_id).examples/hello-world/typescriptis now a policy-only demo matching the other three SDKs. The Gateway Mode TypeScript example moved toexamples/integrations/gateway-mode/typescript/.
Documentation
- Python version prerequisite for examples.
examples/README.mdand a newexamples/retry-semantics/python/README.mdnow state that several Python examples require Python 3.10+ and the currentaxonflowPyPI release. The older pinnedaxonflow==4.1.0from earlier examples does not expose retry-policy or lifecycle fields used here and will fail on import or with a missing-attribute error. Users on systems wherepython3defaults to 3.9 (e.g. older macOS) should create a venv on a newer interpreter before running these examples.
v7.4.4 — CreateOverrideResponse schema split
PATCH — documentation-grade OpenAPI correction, no platform behaviour change.
Splits the POST /api/v1/policies/{id}/overrides create-response shape from the at-rest PolicyOverride entity, matching what the platform server has been emitting all along. Mirrors the CreateWorkflowResponse precedent (orchestrator-api.yaml) — create-time concerns split from at-rest concerns rather than overloaded onto one schema.
Fixed
CreateOverrideResponse schema added
New schema in docs/api/agent-api.yaml carrying the create-time fields:
id,policy_id,policy_typeexpires_at(server-clamped)ttl_seconds(effective TTL after clamping)requested_ttl(the originally requested TTL — present only when clamping occurred)clamped(boolean — true if TTL was server-adjusted)clamped_reason(exceeds_hard_caporbelow_minimum)created_at
createStaticPolicyOverride: 201 retargeted to reference CreateOverrideResponse. The at-rest PolicyOverride schema retains its role on GET /api/v1/policy-overrides and GET /api/v1/policy-overrides/{id}.
Why this matters
Code-generated clients written against the prior spec would have:
- Read
undefinedfor the create-time TTL clamping fields (ttl_seconds,requested_ttl,clamped,clamped_reason) - Expected at-rest fields (
action_override,enabled_override,tool_signature) that the create response doesn't carry
Hand-written clients (the OpenClaw plugin) already match the actual server shape — this fix aligns the spec with reality.
v7.4.3 — Plugin Batch 1 / ADR-043 spec corrections
PATCH — documentation-grade corrections, no platform behaviour change.
Two MCP-response schemas have been stale relative to what the agent has emitted since Plugin Batch 1 shipped. AxonFlow's hand-written plugins (OpenClaw, Claude Code, Cursor, Codex) all read these fields correctly because they were authored against actual server responses; the OpenAPI spec just hadn't been updated. Code-generated clients written against the prior spec would have read undefined for these fields.
Fixed
MCPCheckInputResponse gains 5 fields
The agent has emitted these since v7.1.0; the spec just hadn't documented them.
decision_id— audit correlatorrisk_level—low|medium|high|criticalpolicy_matches— array ofExplainPolicyoverride_available— whether session override is permittedoverride_existing_id— already-active override, if any
MCPCheckOutputResponse gains 3 fields
redacted_message— text-redaction counterpart toredacted_datadecision_idpolicy_matches
New explainability schemas
ExplainPolicy— per-policy explainability recordExplainRule— per-rule explainability recordDecisionExplanation— full payload returned by theexplain_decisionMCP tool
Companion plugin gate work
Surfaced via the wire-shape contract gates landing on the four AxonFlow plugins (parity with the four SDK gates). Each gate's initial baseline grandfathers these fields as known spec-missing drift; the next baseline regen against this v7.4.3 spec auto-resolves the bulk:
- OpenClaw plugin: 7 SDK-only fields cleared from MCPCheck* responses + 3 unmapped types (
ExplainPolicy/ExplainRule/DecisionExplanation) now mapped - Claude Code / Cursor / Codex plugins: 5 plugin-only field entries clear per plugin (4 from MCPCheckInputResponse + 1 from MCPCheckOutputResponse)
v7.4.2 — OpenAPI spec corrections
PATCH — documentation-grade corrections, no platform behavior change.
Two OpenAPI schemas were stale relative to what the server has been emitting. Code-generated clients written against the prior spec would have read undefined for the affected fields. AxonFlow's hand-written SDKs (TypeScript, Python, Go, Java) are already correct against the server and gain no functional change from this release; the fix lives entirely in the spec artefacts.
Fixed
AISystemRegistry.materiality→materiality_classificationindocs/api/masfeat-api.yaml. The server has emittedmateriality_classificationsince the 3-dimensional risk-rating refactor for MAS AI Risk Management Guidelines 2025.DynamicPolicyInfoindocs/api/agent-api.yamlrewritten from 8 aspirational fields to the 4 actual server fields:policies_evaluated,matched_policies,orchestrator_reachable,processing_time_ms.
SDK companion releases (same day)
These corrections auto-resolve a baseline drift entry in each SDK's wire-shape contract gate (DynamicPolicyInfo across all four; AISystemRegistry and PolicyInfo additionally on TS).
- TypeScript SDK v6.0.0 — major (PolicyInfo / MCPPolicyInfo rename) bundled with wire-shape canonicalization sweep
- Java SDK v6.0.0 — major (
WebhookSubscriptionidentity-based equality onid) - Python SDK v6.7.0 — minor, additive wire-shape canonicalization sweep
- Go SDK v5.7.0 — minor, additive wire-shape canonicalization sweep
Contributor docs
New: docs/contributing/sdk-audit-methodology.md — the four-pass methodology AxonFlow runs on SDK PRs that touch wire-bound types or transformers (diff-based field enumeration, transformer reachability walk, sync-wrapper signature parity, falsey-clobber audit).
v7.4.1 — Portal HITL + audit trail fixes
[7.4.1] - 2026-04-23 — Portal HITL + audit trail fixes
PATCH: portal-visible bugs fixed around human approval visibility —
approver identity on the execution timeline, Compliance Summary card
aggregates, HITL audit trail row emission, workflow-level aborted
status propagation, stale-snapshot reconciliation for pre-patch
workflows, and a sidebar badge refresh on approve/reject. Platform-only
release — no SDK or plugin changes. All fixes hit the same HITL audit
and approval visibility story so operators can answer "who approved
what, when, and did the compliance summary count it?" without joining
three tables.
Community
Fixed
- Unified execution step now distinguishes approver from rejector.
The unified execution API (/api/v1/unified/executions/{id})
populatedapproved_bywith the rejector's email on rejected steps
because the serializer projectedworkflow_steps.approved_by
verbatim regardless of terminal state. The step serializer now
splits intoapproved_by/approved_aton the approval path and
rejected_by/rejected_aton the rejection path, mirroring the
split already done byworkflow_control.ProjectStepGateToHTTPon
the WCP HTTP response.execution.StepStatusgains two new fields
(RejectedBy,RejectedAt). /api/v1/audit/summaryreturns six card-view aggregates. The
response previously emittedtotal_events/by_severity/
by_action/top_policies/compliance_score— the handler
now additionally computes and returnstotal_requests,
allowed_requests,blocked_requests,modified_requests,
block_rate_percent,avg_latency_ms. Legacy fields retained for
back-compat. Block rate is derived from the allowed/blocked/modified
counts; average latency is a separate query overresponse_time_ms
excluding rows where latency wasn't measured (HITL decisions,
workflow-lifecycle events).- Compliance Summary arithmetic always closes. The summary
handler previously only countedallowed/blocked/redacted
decisions explicitly;pending_approval(fromworkflow_step_gate
rows where HITL fires require_approval) anderrordecisions were
dropped between the buckets, sototal_requestscould exceed
allowed_requests + blocked_requests + modified_requestsby the
number of orphan rows. Now every non-blocked, non-redacted decision
rolls into Allowed — Total = Allowed + Blocked + Modified is
always true.pending_approvalcounts as allowed because the
policy didn't block; the subsequent human decision writes its own
workflow_step_approved/workflow_step_rejectedrow. - Historical workflows decided before v7.4.1 deployed now render
their terminal approval state. The unified-execution cache was
written at/gatetime (approval_status=pending) and pre-v7.4.1
approve/reject paths did not re-sync it — so any workflow decided
before the fix deployed would forever show "Approval: pending" on
the execution API.GetWorkflowStatusnow reconciles cached step
snapshots against currentworkflow_stepsstate on every read via
a newreconcileStepApprovalshelper. Steps present in the cache
but absent from the fresh rows are left untouched so partial WCP
state can't clobber the cache; WCP fetch failure falls back to the
cached snapshot.
Evaluation
Fixed
- WCP step approve + reject now emit rows in
audit_logs. The
WCP step-approve/reject endpoints
(/api/v1/workflows/{id}/steps/{step_id}/approve|reject, Evaluation+)
previously updatedworkflow_steps.approval_statusand fired a
webhook but never wrote toaudit_logs, so any audit pipeline that
readsaudit_logshad no trace of approvals or rejections — rejected
steps never appeared as "Blocked" rows, compliance summaries ignored
the events, and operator dashboards showed "N/A" under user. Both
paths now write anaudit_logsrow via the existing
WorkflowAuditEntrypipeline withrequest_type="workflow_step_approved"
/"workflow_step_rejected",policy_decision="allowed"on approve
/"blocked"on reject, and the reviewer'sX-User-Emailpopulated
onuser_email.WorkflowAuditEntrygainsUserEmail/UserRole
so reviewer identity carries through the audit adapter end-to-end. - Reject propagates the aborted status to the unified execution
tracker.RejectStepflippedworkflow_steps.approval_status
and calledrepo.Abort(...)on the workflow, but never notified
executionTracker.OnWorkflowAborted(...).GetWorkflowStatus
prefers the cached unified execution when one exists, so
/api/v1/unified/executions/{id}kept reporting the overall
execution as running/pending even though the rejection had already
aborted the workflow. Now callsOnWorkflowAbortedafter the abort
succeeds — only when the abort actually landed, so we don't lie
about workflow state on an abort failure.
Enterprise
Fixed
- HITL queue approve + reject now emit rows in
audit_logs. The
Enterprise HITL queue endpoints
(/api/v1/hitl/queue/{id}/approve|reject) previously wrote only to
hitl_approval_history(the immutable compliance audit trail), so
the audit-logs-based portal audit page had no trace of queue-driven
approvals/rejections — the USER / TENANT column showed "N/A" and
rejections never appeared as "Blocked" rows. Both paths now write
anaudit_logsrow via a newRepository.WriteHITLAuditEvent
helper withrequest_type="workflow_step_gate",
policy_decision="allowed"on approve /"blocked"on reject,
the reviewer's email and role populated, andworkflow_id/
step_id/request_id/policy_nameinpolicy_details. Write
is best-effort — a DB failure does not fail the mutation because
hitl_approval_historyremains the authoritative record. - Portal execution timeline renders rejector identity correctly.
The portal execution page already readapproved_byand
rejected_byas separate fields, but the Community-side serializer
only populatedapproved_by— so a rejected step appeared as
"approved by <rejector>". Paired with the Community-side split
above, the timeline now renders "Approval: rejected by <email>
on <date>" whenapproval_status=rejectedand the approved
variant when approved, suppressing the other field in each case.
ExecutionStepon the portal API client gainsrejected_by/
rejected_at. - Sidebar approvals badge refreshes immediately after approve or
reject in the same tab. The Navigation component polls
getPendingApprovalsevery 30 s. When a reviewer approved or
rejected from the side panel, the approvals list removed the row
optimistically but the1badge next to "Approvals" in the sidebar
lingered until the next poll — visually making the queue look
unreclaimed. The approvals page now dispatches an
approvals:updatedCustomEvent on success; Navigation listens and
re-fetches immediately. Event listener cleaned up on unmount
alongside the polling interval. Cross-tab approvals (second browser
window, SDK, or CLI) still fall back to the 30 s poll — same-tab
only is the scope of this fix.
v7.4.0 — HITL Response Parity + Evaluation-tier MAP plan-scoped endpoints
MINOR: both HITL planes now return the same rich response shape, MAP's
plan-scoped approve/reject endpoints are now available at Evaluation tier
(previously Enterprise-only), and MAP gains a plane-scoped pending-approvals
listing symmetric with the existing WCP endpoint. decision resolves to
"allow" / "block" once the mutation lands, retry_context mirrors the
gate response retry state, approver metadata comes from the same persisted
row, approval_id surfaces the HITL queue entry UUID, and policies_matched
reconstructs the governance trail. Contract tests in CI lock the two planes'
response shapes together so future additions surface on both endpoints by
default — both for approve/reject and for the plane-scoped pending listings.
No breaking changes. Purely additive — the legacy workflow_id / step_id /
status / approval_status / approved_by / message fields existing
callers rely on are unchanged.
Community
Added
- Shared HITL response projection helper in the community codebase —
workflow_control.ProjectStepGateToHTTPandDeriveHITLApprovalID. Both
planes' handlers use it, so the wire shape stays consistent and the
deterministic HITL queue UUID reappears on every response where the
backing workflow_steps row exists. - Plan-to-workflow lookup —
GetWorkflowByPlanIDservice method +
PostgreSQL repository implementation (index onmetadata->>'plan_id').
Enables plan-scoped HITL endpoints to project from the same
workflow_stepsrow that /gate and /complete use.
Deprecated
DO_NOT_TRACK=1as an AxonFlow SDK telemetry opt-out — scheduled for
removal after 2026-05-05 in the next major release. Use
AXONFLOW_TELEMETRY=offinstead. All 4 SDKs emit a one-line migration
warning whenDO_NOT_TRACK=1is the active control and
AXONFLOW_TELEMETRY=offis not also set. See the SDK CHANGELOGs for
per-language notes.
Evaluation
Added
- Rich WCP approve/reject responses.
POST /api/v1/workflows/{id}/steps/{step_id}/approve
and.../rejectnow returndecision,reason,retry_context,
approval_id,approved_by/approved_at(orrejected_by/
rejected_at),policies_matched,status, andmessage. Documented
in OpenAPI asApprovalResponse; mirrors the step-gate response field set. - Rich MAP approve/reject responses at the
/api/v1/plans/{id}/steps/{step_id}/approve|reject
endpoints. Same shape as WCP plus aplan_idfield. Two underlying flows —
confirm/step mode (WCP-backed) and legacy policy-driven pause/resume —
now surface a uniform shape so clients don't branch on which mode the
plan ran in. - Plane-scoped pending-approvals listing — new
GET /api/v1/plans/approvals/pendingendpoint (Evaluation+), the MAP
counterpart of the existingGET /api/v1/workflows/approvals/pending.
Returns{pending_approvals, count}with every entry carryingplan_id
(populated fromworkflows.metadata->>'plan_id'). Optional?plan_id=
query param scopes the listing to a single plan so reviewer tools can
render per-plan context without filtering client-side. Tier-gated on
IsHITLApprovalEnabled()— same gate as the plane-scoped approve/reject.
Changed
- MAP plan-scoped HITL tier gate lowered to Evaluation+ (was Enterprise-only
pre-v7.4.0). Tier check now matches WCP: community + Evaluation license →
accepted; community + no license → 403; enterprise mode → accepted. Error
message updated from "requires Enterprise license" to "requires Evaluation
or Enterprise license." - Cross-plane contract test in CI asserts the WCP and MAP response field
sets stay aligned modulo the intentionalplan_idasymmetry. Guards
against silent future drift when either plane grows a new field. A sibling
TestPendingApprovalsPlaneParitydoes the same for the plane-scoped
pending-approvals listings — the intentionalplan_idasymmetry is
enforced: populated on every MAP entry, never on WCP entries.
SDKs
- Go SDK v5.6.0 —
ApproveStepResponse/RejectStepResponsegain
Decision,Reason,ApprovalStatus,ApprovalID,ApprovedBy/
ApprovedAt/RejectedBy/RejectedAt,PoliciesMatched,
RetryContext,Message,PlanID. NewGetPendingPlanApprovalsmethod
covers the MAP-plane listing.PendingApprovalextended withPlanID,
StepIndex,Decision,DecisionReason,PoliciesMatched,StepInput,
ApprovalStatus. Also fixes three pre-existing URL bugs on
ApproveStep/RejectStep/GetPendingApprovals(they were hitting
non-existent/api/v1/workflow-control/paths) and renames the response
wire shape to match the server (PendingApprovals/Count). - TypeScript SDK v5.6.0 — same rich fields on
ApproveStepResponse/
RejectStepResponseinterfaces, newgetPendingPlanApprovals, extended
PendingApprovalinterface, and the same WCP URL / response-shape fixes. - Python SDK v6.6.0 — rich optional fields on the pydantic
ApproveStepResponse/RejectStepResponsemodels, new
get_pending_plan_approvalsmethod (sync wrapper included), extended
PendingApprovalmodel, and the same WCP URL / response-shape fixes. - Java SDK v5.7.0 — rich fields on
WorkflowTypes.ApproveStepResponse
and.RejectStepResponse, plus back-compat 3-arg constructors so existing
test fixtures keep compiling. NewgetPendingPlanApprovals+ async
variant. ExtendedPendingApprovalclass with back-compat 6-arg
constructor. Same WCP URL / response-getter fixes.
v7.3.0 — Retry Semantics & Idempotency on Workflow Control Plane
[7.3.0] - 2026-04-21 — Retry Semantics & Idempotency
MINOR: first-class retry and idempotency surfaces on the Workflow Control
Plane. The cached: bool signal every gate response has been returning is
now a deprecated alias — responses carry a retry_context block that
answers "how many gate calls?", "did any prior attempt complete?", and
"what was the prior decision?" unambiguously. A new caller-supplied
idempotency_key on gate + complete anchors a workflow step to a
business-level identity (payment intent, invoice, claim reference), with
strict match validation between the two endpoints.
No breaking changes. Purely additive.
Community
Added
retry_contexton everyStepGateResponse— always present, including
on the first gate call (where counters are 1/0 and
prior_completion_statusis"none"). Fields:gate_count,
completion_count,prior_completion_status(enumnone/completed/
gated_not_completed),prior_output_available,prior_output,
prior_completion_at,first_attempt_at,last_attempt_at,
last_decision,idempotency_key. Counter bookkeeping is atomic inside
the repository UPSERT; a separate cached-hit update keeps counters
accurate across idempotent retries without re-evaluating policy.?include_prior_output=truequery param on/gate— opt-in inclusion
of the prior/completepayload inretry_context.prior_output. Default
isfalse(null) because output may be large and/or contain sensitive
data. When the opt-in is set AND a prior completion exists, the full
output is returned so agents can safely short-circuit re-execution.- Caller-supplied
idempotency_keyon/gateand/complete—
optional opaque string up to 255 chars. Recorded on the first gate call
that sets it; immutable for the step's lifetime. Surfaced on every
subsequentretry_context.idempotency_key. Audit log records the key
on everystep_gateandstep_completedevent. HTTP 409 IDEMPOTENCY_KEY_MISMATCHreturned when/complete(or a
subsequent/gate) passes a different key than the one recorded on the
first gate, or when one side supplies a key and the other omits it.
Response envelope includesexpected_idempotency_keyand
received_idempotency_keyso SDKs can build typed errors.cachedanddecision_sourcefields remain on every response so
existing SDK versions continue working unchanged. Both are marked
deprecated in the wire docs;retry_context.gate_count > 1replaces
cached: trueandretry_context.prior_completion_statusreplaces the
string-typeddecision_source.
Changed
MarkStepCompletedHTTP handler now reads tenant identity from
X-Tenant-IDconsistently withStepGaterather than from
X-Client-ID. A real multi-tenant caller setting the tenant header now
works on both endpoints; previously the complete path rejected the
request as "workflow not found" because the isolation check compared
against the wrong attribute. No behavior change for callers using
empty headers.
Evaluation
Added
- Retry-aware dynamic policy conditions — the policy engine now
resolves seven newstep.*fields:step.gate_count,
step.completion_count,step.prior_completion_status(enumnone/
completed/gated_not_completed),step.prior_output_available,
step.last_decision,step.first_attempt_age_seconds, and
step.idempotency_key. Policy authors can write rules like
"retry on un-completed payment requires approval", "more than three
attempts = block", or "rapid retry within 30 seconds escalates severity"
without custom code. These fields are added toValidPolicyFieldsso
the create/update policy APIs accept them. - Tier-gated create: attempting to author a dynamic policy with any
step.*condition on a Community license is rejected at create time
withFEATURE_REQUIRES_EVALUATION_LICENSE. Evaluation and Enterprise
tiers accept. Enforcement sits inPolicyService.validateTierForCreate
before the tenant policy-count check, so the rejection fires cleanly
without a DB roundtrip. - UX note for policy authors: retry-aware policies only fire when
callers passretry_policy: "reevaluate"on subsequent/gatecalls.
Default-idempotent retries hit the cache and bypass the policy engine,
consistent with the existing cache semantics. Documented in the
bundled Evaluation-tier example.
Enterprise
No Enterprise-exclusive additions in this release. Cross-workflow
idempotency lookup, windowed operators like idempotency_key_seen_within,
retry-pattern correlation across workflows, and compliance-grade
audit/reporting for duplicate prevention are on the roadmap for a
later release.
v7.2.1
[7.2.1] - 2026-04-21
PATCH: surface the HITL approval metadata that was already being captured
internally but dropped on the way out of the API. No schema changes, no
breaking changes — callers that previously handled null simply start
seeing real values.
Community
Fixed
/api/v1/workflows/{id}now surfacesapproved_byandapproved_aton
each step. TheStepInfoDTO used by the workflow-detail response was
missing both fields, so callers polling for approval completion saw
approval_status: "approved"but no approver identity or timestamp.
Both fields were already captured byApproveStepand persisted on the
WorkflowSteprow — the DTO just wasn't copying them over. Portal and
SDK consumers now get the full provenance without a second round-trip
to the audit log.StepGateResponse.approval_idpopulated onrequire_approval
decisions. The HITL adapter was creating the approval queue entry and
settingStepGateEvaluation.ApprovalID, but the API response struct
didn't carry the field. SDK clients that want to correlate a paused
step with its HITL queue row (for Slack/PagerDuty routing, direct
portal deep-links, or programmatic approval) now getapproval_idon
the same response that reports therequire_approvaldecision.
Enterprise
Fixed
- Customer Portal
/approvalspage no longer crashes on expand. The
PendingApproval.policies_matchedTypeScript type declared the field
asstring[], but the/api/v1/workflows/approvals/pendingendpoint
returns an array ofPolicyMatchobjects ({policy_id, policy_name, action, risk_level, allow_override, policy_description}). React
tried to render the object directly and threw error #31 ("Objects
are not valid as a React child"), dumping the approver into the
ErrorBoundary fallback the moment they clicked a row to expand the
detail panel. The approvals page now accepts either shape, extracts
policy_namewhen given an object, and surfacespolicy_description
as a tooltip on the matched-policy chip.
v7.2.0 — The Bug Bash Bonanza
A focused hardening release: a full sweep across the Customer Portal
HTTP surface, tenant-scope fail-closed enforcement on every
read-and-action endpoint, three new public platform knobs for the
MAP plan-execution budget, dedicated HTTP examples for every route
the Portal calls, a login-endpoint fix that closes an
org-enumeration leak via both response body and timing, and fixes
to make MAP plans run the full 5 minutes the server is happy to
give them. MINOR per semver — additive surface only; every 7.1.x
caller keeps working without changes.
Added
Community
AXONFLOW_MAP_MAX_TIMEOUT_SECONDSorchestrator env. Caps the
MAP plan-execution budget without a binary rebuild. Clamped to
60..1800 seconds; default 300. The effective value is logged at
startup when non-default. If you front the orchestrator with a
reverse proxy or load balancer, set its idle / read timeout to
at least the orchestrator cap — otherwise long plans will be
cut off at the proxy before the orchestrator finishes.
Enterprise
AlbIdleTimeoutSecondsCloudFormation parameter on the
AWS Marketplace template. Mirror of the Community knob.middleware.MaxBodyBytesMiddlewareexported on the Customer
Portal. Caps every POST/PUT/PATCH request body at 1 MiB by
default;MaxBodyBytesMiddlewareWithLimit(n int64)returns a
variant for routes that legitimately need a larger ceiling (SAML
metadata, future file uploads). GET/HEAD are not wrapped.- Per-feature HTTP examples for every route the Portal UI
calls. Each example is runnable against a local Docker Compose
stack and covers one topic end-to-end:- Full RBI AI-system lifecycle: register → validate → incident
→ kill-switch → board report → audit export. - SEBI dashboard, retention, readiness, and audit export.
- EU AI Act conformity, accuracy/bias, and async export jobs.
- Compliance evidence bundle (summary + stream-download).
- Decision explainability with the cross-tenant 404 guard
asserted. - License, deployment, nodes, and session metadata.
- Policy bundle export/import with
overwrite_modesemantics. - Full
/api/v1/auth/*walkthrough: login, session, logout,
SSO availability, forgot-password, reset-password,
change-password — including the auth-enum identical-body
assertions.
A curl-based smoke runner covers every Portal API route (44
total) without needing a browser, pairing with the Playwright
health spec so CI and demo-freeze runs have a Playwright-free
verification path. Every script passesshellcheckand runs
green against local compose.
- Full RBI AI-system lifecycle: register → validate → incident
- Compliance → Evidence Export Download button. The Compliance
page showed per-type record counts (audit logs, workflow steps,
HITL approvals) but had no way to actually download the bundle.
Added a Download Evidence button that streams the JSON bundle as
a blob with a 30-day default window (the backend caps by tier)
and saves asaxonflow-evidence-<start>-to-<end>.json. Disabled
with a tooltip explanation when counts are zero. Surfaces any
backend error (tier / license / quota) as an inline alert
instead of silently doing nothing.
Fixed
Community
- Agent now proxies
/api/v1/euaiact/*to the orchestrator.
The single-entry-point mux listed/rbi,/sebi, and
/masfeatalongside the rest of the compliance family but
omitted/euaiact, so every EU AI Act call that landed on the
front-door ALB returned404 page not foundand the Portal's
Compliance page reported the module as "not enabled for this
tenant" even though peer modules rendered fine. Added the prefix
to both the router and the proxy-allow-list. - Canonical
/api/v1/policy-overridesalias on the agent. The
Portal's overrides handler proxies to this path, matching the
policy-categories/static-policies/dynamic-policies
naming pattern; the agent previously only exposed the tenant
override list under/api/v1/static-policies/overrides.
Callers using the canonical path hit 404 and the Portal's
Policies → Overrides tab rendered empty. Same handler, new
path, auth unchanged. - Agent
/healthincludestier. The validated license tier
(Community/Evaluation/Professional/Enterprise/
starting) is now surfaced on the health response. Operators
queryingcurl /health | jq .tierused to get"unknown"
because the field was not present. - Orchestrator MAP plan-execution budget now caps at 300s by
default. MAP plans chain multiple LLM calls (~15s each); a
typical 5-step plan routinely ran past the old 60s server-side
default and truncated mid-stream even though the orchestrator
was still working. The cap is now 300s out of the box and
tunable viaAXONFLOW_MAP_MAX_TIMEOUT_SECONDS. SDK note: the
TypeScript SDK's defaultmapTimeoutis 120s; clients relying
on the default will cut off at 120s before the server cap
takes effect. PassmapTimeout: 300000on the SDK client
config to match. - Orchestrator policy type allowlist accepts
context_aware.
Three seeded system policies (Tenant Isolation, Debug Mode
Restriction, Sensitive Data Control) ship with
policy_type=context_aware; any update through
PUT /api/v1/policies/{id}returned 400 because that type was
missing from the allowlist. POST /api/v1/policies/{id}/overridesaccepts
require_approval(HITL). The override validator's allow-list
was hand-written as{block, redact, warn, log}, silently
dropping the HITL action even though the rest of the stack
accepted it end-to-end. Standardised on a single canonical list
of valid actions:- Policy authoring —
alert,block,log,modify_risk,
redact,require_approval,route,warn. - Override endpoint (terminal-action subset) —
block,
require_approval,redact,warn,log. Authoring-only
actions are deliberately excluded — they have no
terminal-action meaning and the agent's override repository
would reject them anyway.
- Policy authoring —
- Test / Edit / Delete / Versions of legacy-named policies no
longer 400. The policy-ID validator only accepted UUIDs and
thesys_*prefix, so seeded policies like
sensitive_data_controlandtenant_isolationfailed every
per-policy action with "Invalid policy ID format". Allowlist now
also accepts the legacy snake-case form. GET /api/v1/policieshonours thetierandcategoryquery
params. The orchestrator's list handler dropped both at the
handler boundary even though the repo supported them. Without
this every Tier / Category dropdown in the Portal's
/policiespage returned the full unfiltered list.- Legacy V1 HMAC license format purged from active code. The
V2 Ed25519 format has been the only accepted key for months;
stale HMAC references would have produced confusing failures
in a clean shell. The rejection-path code that returns"V1 license format no longer supported"is kept so an old key
ever surfacing gets a clear error, not silent acceptance.
Enterprise
- RBI FREE-AI: registering an AI system no longer 500s. The
repository's INSERT listedboard_approval_required, but that
column is a stored-generated column computed from
risk_category. PostgreSQL rejected every write with
cannot insert a non-DEFAULT value into column board_approval_required, so the Portal's RBI compliance page
could never progress past step 1. Removed the column from the
INSERT and UPDATE statements; the Go struct field is still
populated at read time. - RBI FREE-AI: generating a board report no longer 500s. The
service layer setgeneration_method = "automated"but the
database check constraint only accepts'automatic'or
'manual'. Fixed the literal. - Marketplace CloudFormation: Agent security group can reach the
Customer Portal. Per the single-entry-point architecture,
every public request funnels through the Agent, which then
proxies/api/v1/auth/*,/api/v1/portal/*,
/api/v1/code-governance/*, and/api/v1/git-providers/*to
the Customer Portal over Cloud Map. The security group allowed
ALB → Portal and Portal → Agent but nothing allowed Agent →
Portal, so every auth call hitting the raw stack domain timed
out after 30 seconds and fell back to503 Backend service unavailable. The vanity-domain host-header rule routed
directly to the UI and masked the issue during demo prep; only
requests on the raw stack domain surfaced it. Applies on
update-stackwithout recycling ECS tasks. - Usage & Billing no longer returns zeros for every tenant.
The daily rollup was defined but never invoked — no scheduler,
no goroutine, no on-demand call — so the rollup table stayed
empty forever and the Portal's summary and time-series queries
returned zeros even when the underlying events had rows. The
aggregator is now idempotent (re-running an overlapping window
recomputes the bucket rather than adding to it) and is called
on-demand from the Usage handlers before they query the
rollup — self-healing, no scheduler required. A pre-existing
latent bug that surfaced once rollups populated
(COALESCE(AVG())returns numeric, the scan target was int)
is fixed in the same change. GET /api/v1/export/usageno longer 500s. The handler
queried columns that didn't exist (policy_id,latency_ms,
success— the real columns arepolicy_decision,
response_time_ms, and a derived success flag), constructed
INTERVAL '$2 days'which is not valid PostgreSQL
parameterization (the$2inside a string literal is treated
as literal characters, not a placeholder), and ignored the
start/endquery params the UI sends (only honouring a
legacydays=param). Rewritten against the per-request
metering table with correct columns, proper date-range handling
for RFC3339 timestamps or `...
v7.1.1
Closes ten gaps in v7.1.0 surfaced across two rounds of post-release install-and-use E2E testing. No new features — every change makes an already-shipped v7.1.0 feature actually work end-to-end for plugin consumers, across both the HTTP check-input surface (OpenClaw) and the MCP tools/call surface (Claude Code, Cursor, Codex).
Changed
-
MCP
tools/callpath now emits richer block context.check_policyandcheck_outputresponses includedecision_id,risk_level,policy_matches,override_available, andoverride_existing_idon blocks — same shape the HTTP/api/v1/mcp/check-inputendpoint already returned. Claude Code, Cursor, and Codex plugins now render the same rich deny message OpenClaw has been rendering since v7.1.0. -
MCP check paths now consult active session overrides. A user with an active override on a matched policy now flips deny → allow and emits an
override_usedaudit event — previously only the WCP step-gate path applied overrides. MCP check decisions are also dual-written toaudit_logssoexplainDecision(id)resolves MCP-path decisions. -
POST /api/v1/overrides+GET /api/v1/overridesaccept UUID or slug/name. Plugin callers readpolicy_idfrom a block response'spolicy_matches[](which carries the human-readable slug) and can now pass it straight to the override endpoints. Canonical UUID is stored inpolicy_overrides.policy_idregardless of input form. -
Cache invalidation on override create resolves policy synonyms. Override-created events now flush WCP step-gate deny cache entries matching the policy's UUID, slug, or name — the WCP adapter writes the policy name in
policies_matched[].policy_id, which previously bypassed UUID-only invalidation. -
DatabaseDynamicPolicyEngineschema covers migration 070's new columns (id UUID,risk_level,allow_override) so integration tests and fresh clusters match the migration's shape. -
Workflow handler
getUserIDfalls back toX-User-Emailfor per-user scoping on the WCP path, matching Plugin Batch 1's per-user identity convention. -
Test visibility policy formalized. Top-level
tests/now syncs to this community mirror by default. Sensitive tests belong underee/**/tests/(excluded from sync). Seedocs/test-visibility-policy.mdfor the full policy including the hybrid plugin-repo / enterprise-repo split for install-and-use scenarios and release-blocking gates.
Added
-
tests/e2e/plugin-batch-1/{openclaw,claude,cursor,codex}-install/— install-and-use E2E scenarios per plugin covering block + richer context, explain endpoint, override lifecycle, audit filter parity, and cache invalidation against a live stack. -
.github/workflows/tests-hygiene.yml— CI scan on every PR touchingtests/for private sibling-repo references, internal hostnames, AWS account IDs, and real-credential patterns. Failures block merge. -
docs/test-visibility-policy.md— canonical test visibility policy with "Where tests live" (plugin-repo owns / enterprise owns + negative lists) and "Release-blocking gates" table covering plugin-tag vs platform-tag independence.
Fixed
-
audit_logsrow scan tolerates NULL columns.SearchAuditLogspreviously aborted the scan on a NULLprovider/model/error_message/cost/response_time_ms/tokens_usedand dropped the entire result set, hiding every Plugin Batch 1 audit row. Now uses nullable types and maps NULL → zero values. -
MCP per-user identity.
authenticateMCPServerRequestextractsX-User-Email/X-User-IDfrom request headers instead of returning syntheticuserID="0". Previously collapsed all plugin users onto one identity, breaking explain access control, historical-hit-count scoping, and override ownership. -
Platform
agent/mcp_richer_context.gohelper module consolidatesbuildRicherCheckInputBlock,lookupPolicyRiskOverride,lookupActiveOverride,applyOverrideToCheckInputBlock,writeExplainableAuditLog,writeOverrideUsedEvent. Unit-tested via sqlmock.
Plugin compatibility
Plugin patch releases ship alongside v7.1.1:
- OpenClaw v1.3.1 — required for
createOverride/revokeOverride/listOverrides. v1.3.0 clients get HTTP 401 on those endpoints because the client didn't forwardX-User-Email. - Claude Code v0.5.1, Cursor v0.5.1, Codex v0.4.1 — code-identical to v0.5.0 / v0.5.0 / v0.4.0. Patch bump adds an install-and-use smoke E2E. Users on v0.5.0 / v0.4.0 against platform v7.1.1 still get the full richer-context block shape automatically.
Full Changelog: v7.1.0...v7.1.1