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 projectedworkflow_steps.approved_byverbatim regardless of terminal state. The step now has bothapproved_by/approved_atandrejected_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/summarynow returns six new card-view aggregates (total_requests,allowed_requests,blocked_requests,modified_requests,block_rate_percent,avg_latency_ms) in addition to the existingtotal_events/by_severity/by_actionfields. The math always closes:total_requests == allowed + blocked + modified, so no orphanpending_approvalorerrorrows hide behind the total. - HITL approve + reject now emit
audit_logsrows. Before the patch, the approval history lived only inhitl_approval_history. Any audit pipeline that readsaudit_logs— including the portal audit page and the compliance summary — was blind to queue-driven approvals and rejections. Both paths now writeaudit_logsrows 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}/rejectcalls 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 toexecution_historyviaOnWorkflowAborted. - 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_stepsrow 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:
| Path | request_type | policy_decision | user_email |
|---|---|---|---|
| approve | workflow_step_approved | allowed | from X-User-Email header |
| reject | workflow_step_rejected | blocked | from 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 whenrequire_approvalfired)policy_decision:allowedon approve,blockedon rejectuser_email/user_role: from the reviewer recordpolicy_details: carriesworkflow_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.
Sidebar approvals badge refreshes on approve or reject
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:
| Surface | Version |
|---|---|
| Go SDK | v5.6.0 |
| TypeScript SDK | v5.6.0 |
| Python SDK | v6.6.0 |
| Java SDK | v5.7.0 |
| OpenClaw plugin | v1.3.2 |
| Claude Code / Cursor plugins | v0.5.2 |
| Codex plugin | v0.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_logsthat 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.
