Skip to content

Releases: getaxonflow/axonflow

v7.4.5 — MAP org propagation fix and example bug fixes

28 Apr 18:39
0bd9256

Choose a tag to compare

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/executions returned zero rows for newly-completed MAP plans. Plans executed via POST /api/request with request_type=execute-plan were recorded in the execution-tracking store with an empty org_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, and mvn exec:java work against the published SDKs on a fresh checkout.
  • examples/cost-estimation/http/execution-cost-validation.sh now 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/go now compiles from a clean checkout. The directory was missing go.mod and go.sum, so go run main.go failed with no 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/typescript Test 4b no longer fails with tenant_id=undefined. The TypeScript SDK exposes MediaGovernanceConfig fields 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 so auditToolCall succeeds without Missing authentication errors.
  • examples/llm-routing/go updated to the current routing API shape so the demo works against a stock stack.
  • examples/mcp-connectors/cloud-storage rewritten to exercise a working S3-compatible flow against the MinIO instance bundled in the docker-compose stack.
  • examples/.gitignore no longer excludes go.sum. Every Go example now ships with its lockfile committed so a clean clone runs without needing go mod tidy. Two stale replace directives in examples/wcp-retry-idempotency/{community,evaluation}/go/go.mod (pointing at sibling-checkout SDK paths) and a 0-byte orphan examples/policies/go/go.mod were also cleaned up.
  • examples/policies/http/policies.sh Create Custom Policy now sends a valid request body — a single pattern regex 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/python no longer crashes with TypeError: get_env() missing 1 required positional argument.
  • examples/workflow-control/go now compiles. ApproveStep and RejectStep were called with two arguments after the SDK signatures had added a third (approver_id).
  • examples/hello-world/typescript is now a policy-only demo matching the other three SDKs. The Gateway Mode TypeScript example moved to examples/integrations/gateway-mode/typescript/.

Documentation

  • Python version prerequisite for examples. examples/README.md and a new examples/retry-semantics/python/README.md now state that several Python examples require Python 3.10+ and the current axonflow PyPI release. The older pinned axonflow==4.1.0 from 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 where python3 defaults 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

25 Apr 20:22
4cc8a0c

Choose a tag to compare

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_type
  • expires_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_cap or below_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 undefined for 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

25 Apr 17:40
8c8d6c9

Choose a tag to compare

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 correlator
  • risk_levellow | medium | high | critical
  • policy_matches — array of ExplainPolicy
  • override_available — whether session override is permitted
  • override_existing_id — already-active override, if any

MCPCheckOutputResponse gains 3 fields

  • redacted_message — text-redaction counterpart to redacted_data
  • decision_id
  • policy_matches

New explainability schemas

  • ExplainPolicy — per-policy explainability record
  • ExplainRule — per-rule explainability record
  • DecisionExplanation — full payload returned by the explain_decision MCP 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

25 Apr 14:33
10d44eb

Choose a tag to compare

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.materialitymateriality_classification in docs/api/masfeat-api.yaml. The server has emitted materiality_classification since the 3-dimensional risk-rating refactor for MAS AI Risk Management Guidelines 2025.
  • DynamicPolicyInfo in docs/api/agent-api.yaml rewritten 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 (WebhookSubscription identity-based equality on id)
  • 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

23 Apr 21:24
487b07d

Choose a tag to compare

[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})
    populated approved_by with the rejector's email on rejected steps
    because the serializer projected workflow_steps.approved_by
    verbatim regardless of terminal state. The step serializer now
    splits into approved_by / approved_at on the approval path and
    rejected_by / rejected_at on the rejection path, mirroring the
    split already done by workflow_control.ProjectStepGateToHTTP on
    the WCP HTTP response. execution.StepStatus gains two new fields
    (RejectedBy, RejectedAt).
  • /api/v1/audit/summary returns six card-view aggregates. The
    response previously emitted total_events / by_severity /
    by_action / top_policies / compliance_score — the handler
    now additionally computes and returns total_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 over response_time_ms
    excluding rows where latency wasn't measured (HITL decisions,
    workflow-lifecycle events).
  • Compliance Summary arithmetic always closes. The summary
    handler previously only counted allowed / blocked / redacted
    decisions explicitly; pending_approval (from workflow_step_gate
    rows where HITL fires require_approval) and error decisions were
    dropped between the buckets, so total_requests could exceed
    allowed_requests + blocked_requests + modified_requests by the
    number of orphan rows. Now every non-blocked, non-redacted decision
    rolls into Allowed — Total = Allowed + Blocked + Modified is
    always true. pending_approval counts as allowed because the
    policy didn't block; the subsequent human decision writes its own
    workflow_step_approved / workflow_step_rejected row.
  • Historical workflows decided before v7.4.1 deployed now render
    their terminal approval state.
    The unified-execution cache was
    written at /gate time (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. GetWorkflowStatus now reconciles cached step
    snapshots against current workflow_steps state on every read via
    a new reconcileStepApprovals helper. 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 updated workflow_steps.approval_status and fired a
    webhook but never wrote to audit_logs, so any audit pipeline that
    reads audit_logs had 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 an audit_logs row via the existing
    WorkflowAuditEntry pipeline with request_type="workflow_step_approved"
    / "workflow_step_rejected", policy_decision="allowed" on approve
    / "blocked" on reject, and the reviewer's X-User-Email populated
    on user_email. WorkflowAuditEntry gains UserEmail / UserRole
    so reviewer identity carries through the audit adapter end-to-end.
  • Reject propagates the aborted status to the unified execution
    tracker.
    RejectStep flipped workflow_steps.approval_status
    and called repo.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 calls OnWorkflowAborted after 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
    an audit_logs row via a new Repository.WriteHITLAuditEvent
    helper with request_type="workflow_step_gate",
    policy_decision="allowed" on approve / "blocked" on reject,
    the reviewer's email and role populated, and workflow_id /
    step_id / request_id / policy_name in policy_details. Write
    is best-effort — a DB failure does not fail the mutation because
    hitl_approval_history remains the authoritative record.
  • Portal execution timeline renders rejector identity correctly.
    The portal execution page already read approved_by and
    rejected_by as separate fields, but the Community-side serializer
    only populated approved_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>" when approval_status=rejected and the approved
    variant when approved, suppressing the other field in each case.
    ExecutionStep on the portal API client gains rejected_by /
    rejected_at.
  • Sidebar approvals badge refreshes immediately after approve or
    reject in the same tab.
    The Navigation component polls
    getPendingApprovals every 30 s. When a reviewer approved or
    rejected from the side panel, the approvals list removed the row
    optimistically but the 1 badge next to "Approvals" in the sidebar
    lingered until the next poll — visually making the queue look
    unreclaimed. The approvals page now dispatches an
    approvals:updated CustomEvent 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

22 Apr 13:18
6ee724e

Choose a tag to compare

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.ProjectStepGateToHTTP and DeriveHITLApprovalID. 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 lookupGetWorkflowByPlanID service method +
    PostgreSQL repository implementation (index on metadata->>'plan_id').
    Enables plan-scoped HITL endpoints to project from the same
    workflow_steps row that /gate and /complete use.

Deprecated

  • DO_NOT_TRACK=1 as an AxonFlow SDK telemetry opt-out — scheduled for
    removal after 2026-05-05 in the next major release. Use
    AXONFLOW_TELEMETRY=off instead. All 4 SDKs emit a one-line migration
    warning when DO_NOT_TRACK=1 is the active control and
    AXONFLOW_TELEMETRY=off is 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 .../reject now return decision, reason, retry_context,
    approval_id, approved_by / approved_at (or rejected_by /
    rejected_at), policies_matched, status, and message. Documented
    in OpenAPI as ApprovalResponse; 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 a plan_id field. 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/pending endpoint (Evaluation+), the MAP
    counterpart of the existing GET /api/v1/workflows/approvals/pending.
    Returns {pending_approvals, count} with every entry carrying plan_id
    (populated from workflows.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 intentional plan_id asymmetry. Guards
    against silent future drift when either plane grows a new field. A sibling
    TestPendingApprovalsPlaneParity does the same for the plane-scoped
    pending-approvals listings — the intentional plan_id asymmetry is
    enforced: populated on every MAP entry, never on WCP entries.

SDKs

  • Go SDK v5.6.0ApproveStepResponse / RejectStepResponse gain
    Decision, Reason, ApprovalStatus, ApprovalID, ApprovedBy /
    ApprovedAt / RejectedBy / RejectedAt, PoliciesMatched,
    RetryContext, Message, PlanID. New GetPendingPlanApprovals method
    covers the MAP-plane listing. PendingApproval extended with PlanID,
    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 /
    RejectStepResponse interfaces, new getPendingPlanApprovals, extended
    PendingApproval interface, and the same WCP URL / response-shape fixes.
  • Python SDK v6.6.0 — rich optional fields on the pydantic
    ApproveStepResponse / RejectStepResponse models, new
    get_pending_plan_approvals method (sync wrapper included), extended
    PendingApproval model, 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. New getPendingPlanApprovals + async
    variant. Extended PendingApproval class with back-compat 6-arg
    constructor. Same WCP URL / response-getter fixes.

v7.3.0 — Retry Semantics & Idempotency on Workflow Control Plane

21 Apr 22:03
67d5234

Choose a tag to compare

[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_context on every StepGateResponse — always present, including
    on the first gate call (where counters are 1/0 and
    prior_completion_status is "none"). Fields: gate_count,
    completion_count, prior_completion_status (enum none / 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=true query param on /gate — opt-in inclusion
    of the prior /complete payload in retry_context.prior_output. Default
    is false (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_key on /gate and /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
    subsequent retry_context.idempotency_key. Audit log records the key
    on every step_gate and step_completed event.
  • HTTP 409 IDEMPOTENCY_KEY_MISMATCH returned 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 includes expected_idempotency_key and
    received_idempotency_key so SDKs can build typed errors.
  • cached and decision_source fields remain on every response so
    existing SDK versions continue working unchanged. Both are marked
    deprecated in the wire docs; retry_context.gate_count > 1 replaces
    cached: true and retry_context.prior_completion_status replaces the
    string-typed decision_source.

Changed

  • MarkStepCompleted HTTP handler now reads tenant identity from
    X-Tenant-ID consistently with StepGate rather 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 new step.* fields: step.gate_count,
    step.completion_count, step.prior_completion_status (enum none /
    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 to ValidPolicyFields so
    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
    with FEATURE_REQUIRES_EVALUATION_LICENSE. Evaluation and Enterprise
    tiers accept. Enforcement sits in PolicyService.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 pass retry_policy: "reevaluate" on subsequent /gate calls.
    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

21 Apr 18:42
444f1cd

Choose a tag to compare

[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 surfaces approved_by and approved_at on
    each step.
    The StepInfo DTO 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 by ApproveStep and persisted on the
    WorkflowStep row — 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_id populated on require_approval
    decisions.
    The HITL adapter was creating the approval queue entry and
    setting StepGateEvaluation.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 get approval_id on
    the same response that reports the require_approval decision.

Enterprise

Fixed

  • Customer Portal /approvals page no longer crashes on expand. The
    PendingApproval.policies_matched TypeScript type declared the field
    as string[], but the /api/v1/workflows/approvals/pending endpoint
    returns an array of PolicyMatch objects ({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_name when given an object, and surfaces policy_description
    as a tooltip on the matched-policy chip.

v7.2.0 — The Bug Bash Bonanza

20 Apr 22:10
e2c3987

Choose a tag to compare

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_SECONDS orchestrator 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

  • AlbIdleTimeoutSeconds CloudFormation parameter on the
    AWS Marketplace template.
    Mirror of the Community knob.
  • middleware.MaxBodyBytesMiddleware exported 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_mode semantics.
    • 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 passes shellcheck and runs
      green against local compose.
  • 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 as axonflow-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
    /masfeat alongside the rest of the compliance family but
    omitted /euaiact, so every EU AI Act call that landed on the
    front-door ALB returned 404 page not found and 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-overrides alias 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 /health includes tier. The validated license tier
    (Community / Evaluation / Professional / Enterprise /
    starting) is now surfaced on the health response. Operators
    querying curl /health | jq .tier used 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 via AXONFLOW_MAP_MAX_TIMEOUT_SECONDS. SDK note: the
    TypeScript SDK's default mapTimeout is 120s; clients relying
    on the default will cut off at 120s before the server cap
    takes effect. Pass mapTimeout: 300000 on 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}/overrides accepts
    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.
  • Test / Edit / Delete / Versions of legacy-named policies no
    longer 400.
    The policy-ID validator only accepted UUIDs and
    the sys_* prefix, so seeded policies like
    sensitive_data_control and tenant_isolation failed every
    per-policy action with "Invalid policy ID format". Allowlist now
    also accepts the legacy snake-case form.
  • GET /api/v1/policies honours the tier and category query
    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
    /policies page 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 listed board_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 set generation_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 to 503 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-stack without 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/usage no longer 500s. The handler
    queried columns that didn't exist (policy_id, latency_ms,
    success — the real columns are policy_decision,
    response_time_ms, and a derived success flag), constructed
    INTERVAL '$2 days' which is not valid PostgreSQL
    parameterization (the $2 inside a string literal is treated
    as literal characters, not a placeholder), and ignored the
    start / end query params the UI sends (only honouring a
    legacy days= param). Rewritten against the per-request
    metering table with correct columns, proper date-range handling
    for RFC3339 timestamps or `...
Read more

v7.1.1

19 Apr 11:09
1f69e5c

Choose a tag to compare

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/call path now emits richer block context. check_policy and check_output responses include decision_id, risk_level, policy_matches, override_available, and override_existing_id on blocks — same shape the HTTP /api/v1/mcp/check-input endpoint 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_used audit event — previously only the WCP step-gate path applied overrides. MCP check decisions are also dual-written to audit_logs so explainDecision(id) resolves MCP-path decisions.

  • POST /api/v1/overrides + GET /api/v1/overrides accept UUID or slug/name. Plugin callers read policy_id from a block response's policy_matches[] (which carries the human-readable slug) and can now pass it straight to the override endpoints. Canonical UUID is stored in policy_overrides.policy_id regardless 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.

  • DatabaseDynamicPolicyEngine schema 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 getUserID falls back to X-User-Email for 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 under ee/**/tests/ (excluded from sync). See docs/test-visibility-policy.md for 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 touching tests/ 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_logs row scan tolerates NULL columns. SearchAuditLogs previously aborted the scan on a NULL provider / model / error_message / cost / response_time_ms / tokens_used and 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. authenticateMCPServerRequest extracts X-User-Email / X-User-ID from request headers instead of returning synthetic userID="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.go helper module consolidates buildRicherCheckInputBlock, 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 forward X-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