Skip to content

fix(frontend): the workflow dashboard filtered on two events nobody publishes (#17643) - #17679

Merged
mrveiss merged 2 commits into
mainfrom
issue-17643-workflow-event-contract
Sep 28, 2026
Merged

mrveiss merged 2 commits into
mainfrom
issue-17643-workflow-event-contract

Conversation

@mrveiss

@mrveiss mrveiss commented Sep 28, 2026

Copy link
Copy Markdown
Owner

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.py publishes from global to workflow:{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 to f"workflow:{workflow_id}", which the dashboard now subscribes to per workflow. workflow_completed is 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:

WorkflowLiveDashboard.vue   useEventBus subscribe `workflow:{id}`   event.event_type
                            <- api/workflow.py publish_event                      7 names

useWorkflowBuilder.ts       raw WS  workflow_ws/{session_id}        data.type
                            <- services/workflow_automation/* send_json          10 names

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_paused for the dashboard, a name that can never arrive there; an api/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:

What Changed

WorkflowLiveDashboard.vue — WORKFLOW_EVENT_TYPES held workflow_status_update and a bare step_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:

terminal -> emit('refresh')          workflow_completed  workflow_failed  workflow_cancelled
progress -> emit('workflow-update')  workflow_step_started  workflow_step_completed
                                     workflow_approval  workflow_approval_required

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. applyWorkflowProgress patches the matching workflow by id from step_index / total_steps / step_id — exactly the fields the card's stepOf renders. 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 messages workflow_automation/* never sends, not two of three as the issue states. The missed one is approval_required, which gates pendingApproval — so plan approval never reached the UI on this path. It is workflow_plan_presented, with the request under plan rather than approval.

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 — there is no data source to wire it to. The capability itself is delivered by applyWorkflowProgress, 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 for publish_event(channel, name, …), AST dict-value for {"type": …}, brace-matched case extraction 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-116 fired workflow_status_update and 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

$ python3 -m pytest repo_tests/workflow_event_contract_17643_test.py -q
11 passed in 0.57s

$ npx vitest run src/components/workflow/__tests__/WorkflowLiveDashboard.subscriptions.test.ts
Test Files  1 passed (1)      Tests  10 passed (10)

$ npm run type-check          # vue-tsc --noEmit -p tsconfig.app.json
exit 0, 0 errors

$ npx eslint <the 4 changed frontend files>
exit 0

Mutation-tested, not just run green. Reinstating workflow_status_update in the dashboard's set:

MUTATION -> CAUGHT
AssertionError: WorkflowLiveDashboard.vue filters on ['workflow_status_update'], which
api/workflow.py publishes under no spelling. A filter entry with no producer is silent:
the event never arrives and the handler never runs. Published: ['workflow_approval',
'workflow_approval_required', 'workflow_cancelled', 'workflow_completed',
'workflow_failed', 'workflow_step_completed', 'workflow_step_started']

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:

AC State Evidence
Completion refreshes without a click met workflow_completed terminal → emit('refresh') → @refresh="refreshAll"; channel matches all seven publishes. Delivered by #17478, preserved and re-tested here
Subscribes per workflow and unsubscribes met syncSubscriptions + onUnmounted; two tests. Delivered by #17478
A test asserts the refresh fires, not just receipt met emits refresh when a workflow completes asserts emitted('refresh'). Delivered by #17478
Check the other emit — @workflow-update unbound met here It was meant to be bound, not dead. WorkflowBuilderView.vue:584

Not 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)

…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
@coderabbitai

coderabbitai Bot commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

Next included review available in 24 minutes.

Check out review usage here.

View limit details

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

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Repository: mrveiss/AutoBot-AI/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Advanced

Run ID: 1c35375a-6b39-4529-9c8a-1c263ef2565d

📥 Commits

Reviewing files that changed from the base of the PR and between 1b1777f and 311fc0a.

📒 Files selected for processing (5)
  • autobot-frontend/src/components/workflow/WorkflowLiveDashboard.vue
  • autobot-frontend/src/components/workflow/__tests__/WorkflowLiveDashboard.subscriptions.test.ts
  • autobot-frontend/src/composables/useWorkflowBuilder.ts
  • autobot-frontend/src/views/WorkflowBuilderView.vue
  • repo_tests/workflow_event_contract_17643_test.py

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown
Contributor

✅ SSOT Configuration Compliance: Passing

🎉 No new hardcoded values of either class — ssot and other both block.

Known backlog in pipeline-scripts/hardcoded_values_baseline.txt is suppressed and tracked in #14371.

@mrveiss
mrveiss merged commit a5123c0 into main Sep 28, 2026
70 checks passed
@mrveiss
mrveiss deleted the issue-17643-workflow-event-contract branch September 28, 2026 07:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant