Skip to content

Releases: getaxonflow/axonflow

v7.6.0 — Policy-engine response cleanup + per-category enforcement controls

02 May 20:30
4f62d2c

Choose a tag to compare

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 like booking ABC123 continue to match. Action remains log (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

29 Apr 23:19
f2a23a2

Choose a tag to compare

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_id could 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 the warn default since v6.2.0; SQLi-shaped requests passed through to the LLM. Default flipped back to block; 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

  • /health now reports the deployed platform semver (v7.5.0). Was returning 1.0.0 on every deployed stack because the image tag failed the version-resolver regex. New PlatformVersion CFN parameter + repo-root VERSION file 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_id on every governance decision path (v7.5.0). Allow paths on /api/v1/mcp/check-input and /api/v1/mcp/check-output no longer silently drop the audit correlator; every allow / deny / redact decision now surfaces decision_id for 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

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.