Repository navigation
fix(frontend): the workflow dashboard filtered on two events nobody publishes (#17643) - #17679
Conversation
…ublishes (#17643) `WORKFLOW_EVENT_TYPES` held `workflow_status_update` and a bare `step_completed`. No backend publishes either under any spelling, so a workflow that failed or was cancelled never left the active list without a manual click, and nothing moved mid-run. Only completion got through, and it had done since 2026-04-01. The names now come off the publish sites in `api/workflow.py`, split by what the event means for the list rather than handled alike: a terminal event changes WHICH workflows are active and refetches, a progress event moves one card and is applied in place. That is also the answer to #17477's per-step refetch cost -- the cost was real, and it goes away by routing progress events away from the refetch instead of by dropping them. Binding `@workflow-update` completes #17364's fourth criterion. The emit was written, declared, and never bound; `applyWorkflowProgress` patches the matching workflow by id using `step_index` / `total_steps` / `step_id`, which is exactly what the card's `stepOf` renders. No payload change and no invented event -- the data was already being published to nobody. The second consumer is on a different transport and was reconciled against its own producer, not this one. `useWorkflowBuilder.handleWebSocketMessage` reads `data.type` off `workflow_ws/{session_id}`, fed by `workflow_automation/*` `send_json`; three of its four cases named messages that producer never sends, including `approval_required`, which gates `pendingApproval` -- so plan approval never reached the UI on that path. It is now `workflow_plan_presented`, with the request under `plan`. `workflow_status_update` is removed rather than repointed: it carried an in-place update of a whole workflow object and nothing on either transport sends one. The capability is delivered by `applyWorkflowProgress`, on the transport that does carry the fields. The guard is per transport pair. #17643's original criterion asked that the frontend's names be a subset of "the backend's published set", which has no referent -- there are two producers, and a union of them would certify `workflow_paused` for the dashboard, a name that can never arrive there. The amendment is recorded on the issue. The overlap between the two vocabularies is pinned at exactly `workflow_completed` and `workflow_cancelled` (#17676), so a third shared name fails rather than widening quietly. One test changed because it was certifying the defect: `WorkflowLiveDashboard.subscriptions.test.ts` fired `workflow_status_update` and asserted a refresh. It passed, always had, and was a green assertion about an event that cannot occur. Closes #17643 Closes #17364 Refs #17477 Refs #17676 Refs #17677
|
Warning Review limit reachedNext included review available in 24 minutes. View limit detailsLimit details: You’ve used the included review currently available. You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Review configuration: ⚙️ Run configurationConfiguration used: Repository: mrveiss/AutoBot-AI/.coderabbit.yaml Review profile: ASSERTIVE Plan: Advanced Run ID: 📒 Files selected for processing (5)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
✅ SSOT Configuration Compliance: Passing🎉 No new hardcoded values of either class — Known backlog in |
Thinking Path
I was sent here on the hypothesis that #17364 was a symptom of #17643 — the dashboard written against an aggregate status event that was never built, with #17364's "stopped auto-refreshing" as its user-visible face. I killed that hypothesis before writing any code.
#17364 was never about event names. It was a channel defect: #17358 moved seven
api/workflow.pypublishes fromglobaltoworkflow:{workflow_id}and nothing subscribed to the new channel. It was fixed by #17478 /424a65f08c. I read the channel argument at each of the seven publish sites rather than the names — all seven go tof"workflow:{workflow_id}", which the dashboard now subscribes to per workflow.workflow_completedis in the filter, is published, and is on the subscribed channel, so completion refresh already works. A name defect cannot cause a symptom the channel fix resolved.What that left is #17643's real defect and #17364's one unmet criterion, which turn out to be the same change.
The finding that mattered more than the hypothesis: there are two transports, not one. #17643 reads its two consumers as sharing a producer. They do not:
So the issue's fourth criterion — "the frontend's event-name set is a subset of the backend's published set" — has no referent. A union guard certifies
workflow_pausedfor the dashboard, a name that can never arrive there; anapi/workflow.py-only guard fails every legitimate builder case. I amended the criterion on the issue before building the guard, so the record shows an amended criterion rather than a moved goalpost.Two things I got wrong and corrected mid-work, both worth stating because the fix depends on them:
workflow_completedandworkflow_cancelledare published on both transports. The guard's disjointness test failed on first run, which is how I found out. It now pins the overlap at exactly those two (arch(events): workflow_completed and workflow_cancelled are published on two transports under one name #17676) instead of denying it.#17679into the guard's docstring as the tracking issue before filing it — a reconstructed identifier, right shape and wrong content. Filed for real as arch(events): workflow_completed and workflow_cancelled are published on two transports under one name #17676 and bug(frontend): four workflow socket messages have no consumer, including a confirmation the executor blocks on #17677, references corrected.What Changed
WorkflowLiveDashboard.vue—WORKFLOW_EVENT_TYPESheldworkflow_status_updateand a barestep_completed. Neither is published by any backend under any spelling, so a workflow that failed or was cancelled never left the active list without a manual click, and nothing moved mid-run. Only completion got through, since 2026-04-01.Replaced with the names read off the publish sites, split by what the event means for the list:
A terminal event changes which workflows are active, so a refetch is correct. A progress event leaves membership alone and moves one card's counter. This is also the honest answer to #17477's per-step refetch objection — that cost was real, and it goes away by routing progress events away from the refetch rather than by dropping the events to avoid it.
WorkflowBuilderView.vue+useWorkflowBuilder.ts— bound@workflow-update, which completes #17364's fourth criterion. The emit was written and declared and never bound.applyWorkflowProgresspatches the matching workflow by id fromstep_index/total_steps/step_id— exactly the fields the card'sstepOfrenders. No payload change and no invented backend event: the data was already being published to nobody.useWorkflowBuilder.handleWebSocketMessage— reconciled against its own producer. Three of its four cases named messagesworkflow_automation/*never sends, not two of three as the issue states. The missed one isapproval_required, which gatespendingApproval— so plan approval never reached the UI on this path. It isworkflow_plan_presented, with the request underplanrather thanapproval.workflow_status_updateis removed rather than repointed. It carried an in-place update of a whole workflow object, and nothing on either transport sends one — there is no data source to wire it to. The capability itself is delivered byapplyWorkflowProgress, on the transport that does carry the fields.repo_tests/workflow_event_contract_17643_test.py(new, 312 lines) — the guard, per transport pair. Detectors work by property, never by a list of expected names: AST call-position forpublish_event(channel, name, …), AST dict-value for{"type": …}, brace-matchedcaseextraction so a neighbouring function's cases cannot be attributed to this one. Each detector has a contrast pair — a synthetic source it must recognise and one it must reject, including one that mentions a name without publishing it, which is the exact mistake that made three earlier readings of this area wrong. Each has a vacuity floor on names reached, so "no violations" cannot come from a detector that read nothing.One test changed because it was certifying the defect.
WorkflowLiveDashboard.subscriptions.test.ts:109-116firedworkflow_status_updateand asserted a refresh. It passed, and had always passed — a green assertion about an event that cannot occur. Flagging it explicitly so the rewrite does not read as removing an inconvenient test: it was pinning the bug. Replaced with terminal-vs-progress routing coverage plus a contrast case that fails if either dead name is ever reinstated.Verification
Mutation-tested, not just run green. Reinstating
workflow_status_updatein the dashboard's set:Pre-push ran vue-tsc, the relevant vitest file, and seven pytest guards — all green.
#17364's four criteria, since this PR closes it and only delivered the last one:
workflow_completedterminal →emit('refresh')→@refresh="refreshAll"; channel matches all seven publishes. Delivered by #17478, preserved and re-tested heresyncSubscriptions+onUnmounted; two tests. Delivered by #17478emits refresh when a workflow completesassertsemitted('refresh'). Delivered by #17478@workflow-updateunboundWorkflowBuilderView.vue:584Not claimed: no host verification. The panels behind this are frontend-only and I have no authenticated browser observation, so the user-visible half rests on the unit tests and the producer/consumer match, not on a live click.
Two findings filed rather than folded in: #17676 (the two-transport name overlap) and #17677 (four socket messages with no consumer — including
step_confirmation_required, which the executor blocks waiting for).Closes #17643
Closes #17364
Refs #17477
Refs #17676
Refs #17677
Model Used
Claude Opus 5 (1M context)