Skip to main content

AxonFlow v7.4.1 Release Notes

AxonFlow v7.4.1 is a portal-visibility patch. Nine fixes across the HITL approval surface, the audit trail, and the portal UI — all pointed at the same question an operator asks on a Tuesday morning: who approved what, when, and did the compliance summary count it? Before v7.4.1 that question needed a three-table join. After v7.4.1 the portal answers it on one page.

Platform-only release. No SDK or plugin changes — your existing v5.6.0 / v5.7.0 / v6.6.0 SDKs work against this patch without modification.

What changed in one read​

  • The execution timeline now distinguishes the approver from the rejector. Before v7.4.1 a rejected step displayed the rejector's email in approved_by, because the unified execution serializer projected workflow_steps.approved_by verbatim regardless of terminal state. The step now has both approved_by / approved_at and rejected_by / rejected_at — populated only on the path that applies.
  • The Compliance Summary card on the audit page stops showing zeros. POST /api/v1/audit/summary now returns six new card-view aggregates (total_requests, allowed_requests, blocked_requests, modified_requests, block_rate_percent, avg_latency_ms) in addition to the existing total_events / by_severity / by_action fields. The math always closes: total_requests == allowed + blocked + modified, so no orphan pending_approval or error rows hide behind the total.
  • HITL approve + reject now emit audit_logs rows. Before the patch, the approval history lived only in hitl_approval_history. Any audit pipeline that reads audit_logs — including the portal audit page and the compliance summary — was blind to queue-driven approvals and rejections. Both paths now write audit_logs rows carrying the reviewer's identity and workflow/step context.
  • A rejection now aborts the overall execution status. POST /api/v1/workflows/{id}/steps/{step_id}/reject calls the workflow abort path, but pre-v7.4.1 it never notified the unified execution tracker. The cached unified execution kept reporting the workflow as running until something else refreshed it. Now a successful abort immediately propagates to execution_history via OnWorkflowAborted.
  • Historical workflows decided before v7.4.1 deployed now display their terminal state. The read path reconciles the cached step snapshot against the current workflow_steps row on every /api/v1/unified/executions/{id} call — so workflows approved or rejected before the fix deployed stop showing "Approval: pending" forever on a timeline read.

Community — core code path and backend aggregates​

Community builds pick up every fix whose code lives under platform/ and whose endpoint is available at the Community tier (unified executions read, audit summary). The four Community fixes are the ones a self-hosted Community operator will observe directly against the API.

/api/v1/audit/summary returns six card-view aggregates​

Before v7.4.1 the handler emitted total_events / by_severity / by_action / top_policies / compliance_score. The response now additionally includes per-decision counts and a latency aggregate:

{
"total_requests": 385,
"allowed_requests": 369,
"blocked_requests": 10,
"modified_requests": 6,
"block_rate_percent": 2.6,
"avg_latency_ms": 142.3,

"total_events": 385,
"by_severity": { "critical": 10, "warning": 6, "info": 369 },
"by_action": { "llm_call": 369, "workflow_step_gate": 10 },
"top_policies": [],
"compliance_score": 97.4
}

Legacy fields stay on the response for back-compat. block_rate_percent is derived from the three counts. avg_latency_ms 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 + blocked + modified by the number of orphan rows. Now every non-blocked, non-redacted decision rolls into Allowed — the sum of the three always equals total. 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 so the approval outcome doesn't double-count.

Unified execution step distinguishes approver from rejector​

execution.StepStatus — the serialization struct behind /api/v1/unified/executions/{id} — gains two new fields:

type StepStatus struct {
...
ApprovalStatus *ApprovalStatus
ApprovedBy string
ApprovedAt *time.Time
RejectedBy string // new in v7.4.1
RejectedAt *time.Time // new in v7.4.1
}

The step serializer splits the shared workflow_steps.approved_by column into approved_by / approved_at on the approval path and rejected_by / rejected_at on the rejection path. Mirrors the split already done by workflow_control.ProjectStepGateToHTTP on the WCP HTTP response, so the unified execution view and the direct WCP response agree on which field carries the reviewer identity.

Historical workflows reconcile on read​

GetWorkflowStatus now reconciles cached step snapshots against the current workflow_steps state on every read via a new reconcileStepApprovals helper. 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 patch deployed would forever show "Approval: pending" on a timeline read. Steps present in the cache but absent from the fresh rows are left untouched so partial WCP state can't clobber the cache; a WCP fetch failure falls back to the cached snapshot.


Evaluation — WCP approve/reject audit trail​

Evaluation and Enterprise share the WCP approve/reject endpoints. Two fixes land under this tier because they surface via those endpoints.

WCP step approve + reject now emit audit_logs rows​

POST /api/v1/workflows/{id}/steps/{step_id}/approve and .../reject (Evaluation+ per v7.4.0) previously updated workflow_steps.approval_status and fired a webhook but wrote nothing to audit_logs. Any audit pipeline reading from audit_logs was blind to these decisions — rejections never appeared as "Blocked" rows, the compliance summary ignored the events, and operator dashboards showed "N/A" under the user column.

Both paths now write an audit_logs row via the existing WorkflowAuditEntry pipeline:

Pathrequest_typepolicy_decisionuser_email
approveworkflow_step_approvedallowedfrom X-User-Email header
rejectworkflow_step_rejectedblockedfrom X-User-Email header

WorkflowAuditEntry gains UserEmail / UserRole fields so reviewer identity carries through the audit adapter end-to-end.

Reject propagates 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 or pending even though the rejection had already aborted the workflow. The portal timeline would show a rejected workflow as if it were still in progress.

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 path.


Enterprise — HITL queue + portal UI​

Three fixes are Enterprise-only: the HITL queue endpoint's audit row emission (queue itself is EE), and the two portal UI changes (portal is EE).

HITL queue approve + reject now emit audit_logs rows​

Enterprise POST /api/v1/hitl/queue/{id}/approve|reject previously wrote only to hitl_approval_history — the immutable compliance audit trail. Audit pipelines reading audit_logs saw nothing, so the portal audit page had no record of queue-driven decisions.

Both paths now also write an audit_logs row via a new Repository.WriteHITLAuditEvent helper. Field shape:

  • request_type: workflow_step_gate (matches the gate row written when require_approval fired)
  • policy_decision: allowed on approve, blocked on reject
  • user_email / user_role: from the reviewer record
  • policy_details: carries workflow_id, step_id, request_id, policy_name, reviewer ID/role, action, and free-form comment

Writes are best-effort — a DB failure does not fail the mutation because hitl_approval_history remains the authoritative record.

Portal execution timeline renders rejector identity​

The portal execution page already read approved_by and rejected_by as separate fields, but the backend 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: approved by <email> on <date>" when approval_status=approved
  • "Approval: rejected by <email> on <date>" when approval_status=rejected

suppressing the other field in each case. ExecutionStep on the portal API client gains rejected_by / rejected_at to match.

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.

Scope is same-tab only. Cross-tab approvals (second browser window, SDK, or CLI) still fall back to the 30 s poll — a future version can lift that to a BroadcastChannel if cross-tab review becomes a real workflow.


SDK and plugin compatibility​

No SDK or plugin versions changed. The existing v7.4.0 train works unchanged:

SurfaceVersion
Go SDKv5.6.0
TypeScript SDKv5.6.0
Python SDKv6.6.0
Java SDKv5.7.0
OpenClaw pluginv1.3.2
Claude Code / Cursor pluginsv0.5.2
Codex pluginv0.4.2

Anything reading the HITL approve/reject JSON shape keeps working — the six new card-view fields on /api/v1/audit/summary are additive, the rejected_by / rejected_at fields on the execution step are additive, and the new workflow_step_approved / workflow_step_rejected audit rows are new rows, not modifications of existing ones.

Who benefits from this release​

  • Compliance teams reviewing the portal audit page now see every approval and rejection with the reviewer's identity — no more "N/A" for HITL events.
  • Operators spot-checking the Compliance Summary card get the six card-view aggregates the card was designed around, and the math always closes.
  • Reviewer tools integrating with the WCP or HITL queue approve/reject endpoints get a durable record in audit_logs that downstream pipelines (SIEM exports, compliance reports, Grafana dashboards) already index.
  • Anyone with a portal workflow approved or rejected pre-v7.4.1 sees the correct terminal state on their execution timeline as soon as the platform restarts — the read-path reconciliation backfills historical state on every render.