Releases: getaxonflow/axonflow
v7.6.0 — Policy-engine response cleanup + per-category enforcement controls
MINOR release. Adds a new API field on the policy enforcement response and fixes two audit-trail bugs. No breaking changes; existing SDK / dashboard consumers continue to work.
Bug fixes
-
policy_info.matched_policies no longer duplicates when a policy matches both query and parameter contexts (e.g.
["sys_sqli_grant", "sys_sqli_grant"]). One entry per match, regardless of context count. Block-decision behaviour unchanged. -
sys_pii_booking_ref no longer fires on SQL keywords (SELECT / INSERT / DELETE / UPDATE / CREATE). The pattern was
\b[A-Z0-9]{6}\b— too broad. Now requires a booking-context label (booking, reservation, reference, ref, pnr, confirmation, conf) before the alphanumeric token. Real booking refs likebooking ABC123continue to match. Action remainslog(audit-only). Migration 074 patches already-deployed systems in place.
API additions
- policy_info.matched_policies field added alongside the existing
policies_evaluated. The new name is canonical; legacy field kept populated for back-compat, drops in next major.
v7.5.0 — Production, quality, and security hardening — upgrade encouraged
Upgrade strongly recommended. Over the past month we've shipped substantial production, quality, and security hardening across the AxonFlow platform — upgrade for a more secure, reliable, and bug-free experience.
Security highlights from this release cycle
- Multi-tenant isolation in MAP execution (v7.4.5). A body-supplied
org_idcould override the authenticated org for both recording AND policy evaluation. Identity is now sourced from authenticated headers consistently across recording, read filters, and policy evaluation. - SQL-injection enforcement restored on
try.getaxonflow.com(v7.5.0). The Community SaaS endpoint had inherited thewarndefault since v6.2.0; SQLi-shaped requests passed through to the LLM. Default flipped back toblock; configurable per deploy. - Cross-tenant audit-log isolation (v7.2.0). Evidence and explain handlers fail-closed when tenant context is missing instead of returning data scoped to a different tenant.
The full set of platform-side security fixes addressed in this cycle — including five additional access-control and DoS hardening items not listed above — is documented in the consolidated security advisory GHSA-9h64-2846-7x7f.
Reliability and bug-fix highlights
/healthnow reports the deployed platform semver (v7.5.0). Was returning1.0.0on every deployed stack because the image tag failed the version-resolver regex. NewPlatformVersionCFN parameter + repo-rootVERSIONfile as single source of truth.- ALB idle timeout 300s on Community SaaS (v7.5.0). Long MAP plan generation requests no longer 504 at the load balancer before the orchestrator finishes.
decision_idon every governance decision path (v7.5.0). Allow paths on/api/v1/mcp/check-inputand/api/v1/mcp/check-outputno longer silently drop the audit correlator; every allow / deny / redact decision now surfacesdecision_idfor forensic correlation.
No breaking platform changes. Existing SDK and plugin callers continue to work; they receive a one-time upgrade hint when they sit below the new floor.
SDKs and Plugins
Coordinated major-version bump across all eight artifacts. The four SDKs (Go, Python, TypeScript, Java) advance to v7.0.0. The OpenClaw plugin advances to v2.0.0; the Claude Code, Cursor, and Codex CLI plugins graduate from v0.x.x to v1.0.0. The breaking change driving all eight is the DO_NOT_TRACK telemetry opt-out removal — AXONFLOW_TELEMETRY=off is now the canonical and only opt-out across every SDK and every plugin.
Full CHANGELOG entry: ## [7.5.0] on main.
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.