feat(settings): workflows admin — dedicated Running section above paginated History [shot:workflow-running-section] (bookshelf-33oo) #1205
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "bd-bookshelf-33oo"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Reworks the existing Workflows admin tab to replace the page-local RUNNING-first sort with a dedicated two-section layout:
The page-local sort only reordered within the current 50-row page, leaving running workflows hidden past page 1.
Changes
Test plan
Closes bead bookshelf-33oo on merge.
Security Review — PR #1205 (RUNNING-first page-local sort)
Scope: verify no new data exposure, no DB/query change, admin gating intact, no secrets/PII/engine coupling.
Security surface: clean.
SortWorkflowInstancesreorders the already-fetched, already-truncated page slice in-memory. Page size is unchanged (shellWorkflowsPageSize), no new fields are read (WorkflowInstanceRow= InstanceID/ExecutionID/ParentID/CreatedAt/CompletedAt/State/Queue — all identifiers/timestamps, no secrets/PII), and the workflows view is a global admin-only surface (no per-user scoping to leak).adminOnlyTabs["workflows"] = true(shell_handler.go:148) andresolveShellTabreturns forbidden for non-admins (shell_handler.go:311). This diff does not touch routing or gating.sort.SliceStable; no new query, no injection surface. Comparator is a total order (status rank → CreatedAt desc → InstanceID tiebreaker) — no flaky non-total sort.sort.internal/settingsis a domain-boundary-safe location.Findings
[MAJOR] internal/settings/shell_handler.go:613 — next-page cursor derived AFTER the page is re-sorted
SortWorkflowInstances(instances)runs beforenextID/nextExecare read frominstances[len-1](lines 614-619). The keyset pagination cursor from the go-workflows backend (ListInstances/GetWorkflowInstances) assumes the page is still in the backend's native newest-first(created_at, id)order — the correct "next" cursor is the element that was last in DB order. After the RUNNING-first reorder,instances[len-1]is generally a different row (e.g. the oldest completed instance, which can be NEWER than a long-running old instance pulled to the top). The next page then queriesaftera cursor that isn't the true page tail, causing instances to be skipped or duplicated across page boundaries whenever a RUNNING instance isn't already at the DB-order tail (only observable with >shellWorkflowsPageSizeinstances). This is a correctness regression, not a security issue, but it is introduced by this diff. Fix: derivenextID/nextExecfrom the DB-ordered slice before callingSortWorkflowInstances, or capture the cursor row prior to the sort and sort a copy for display.REVIEW VERDICT: 0 blocker, 1 major, 0 minor
Code Review — PR #1205 (backfilled by orchestrator; the review agent hit an auth error posting)
[MAJOR] internal/settings/shell_handler.go — next-page cursor derived from the SORTED page, not DB order. The keyset cursor (
nextID/nextExec) was read frominstances[len-1]afterSortWorkflowInstancesreordered the page (RUNNING-first), so the cursor no longer pointed at the backend's native(created_at,id)tail → instances could be skipped/duplicated across page boundaries (>1 page). RESOLVED in commit922b8770: the cursor is now captured from the DB-ordered tail before the display sort, with a RED-before/GREEN-after regression test.Positives verified: total-order comparator (status rank → CreatedAt DESC → InstanceID tiebreaker) — deterministic, no flaky ties; page-local sort only (no DB/query change); black-box tests; 100% coverage; no new exclusions.
REVIEW VERDICT: 0 blocker, 1 major (fixed in
922b8770), 0 minorfeat(settings): workflows admin list — RUNNING-first page-local sort (bookshelf-33oo)to feat(settings): workflows admin — dedicated Running section above paginated History [shot:workflow-running-section] (bookshelf-33oo)Workflow Running section screenshot (workflows-running-section)
Workflow Running section screenshot (workflows-running-section)
Workflow Running section screenshot (workflows-running-section)
Security Review — PR #1205 (
bd-bookshelf-33oo)Independent security review of the reworked PR adding
ListRunningInstances(window-function query over the go-workflowsinstancestable) surfaced in the admin Settings → Workflows tab.1. SQL injection — CLEAN.
runningInstancesSQL(internal/wfengine/diag_accessor.go:662) is a compile-timeconststring. The only bound value is thecaplimit, passed as a positional?parameter (queryContext(ctx, runningInstancesSQL, cap), line 688). No identifiers or values are string-concatenated, andcapis the compile-time constantshellWorkflowsRunningCap+1(=501,shell_handler.go:162), never request-derived. No user input reaches the SQL.2. Determinism — CLEAN. Outer
ORDER BY created_at DESC, instance_id ASC: after thern = 1dedup,instance_idis unique per row → total order. InnerROW_NUMBER() OVER (PARTITION BY instance_id ORDER BY created_at DESC, id DESC)carries the uniqueid DESCtiebreaker. No observable ties → no flake.3. Bounded read — CLEAN. Query ends in
LIMIT ?; cap fixed at 501. Handler trims to 500 and setsWorkflowRunningCapped(shell_handler.go:166-169). Cannot be coerced into an unbounded scan.4. AuthZ — CLEAN.
"workflows"is inadminOnlyTabs(shell_handler.go:148). The gate atshell_handler.go:311returnsErrForbiddenfor non-admins BEFOREloadShellTabData→loadWorkflowsTabData→loadRunningWorkflowsis ever reached. The new Running section rides the same gate; no bypass.5. Data exposure — CLEAN. Selected columns are
instance_id, execution_id, parent_instance_id, created_at, completed_at, queue(state hard-set to"running"). No workflow payload/args/metadata, no secrets, no PII. No new logging of sensitive fields. Workflow instances are system-level background jobs, not per-user data, so admin-gating (not per-user scoping) is the correct control.6. Architecture boundary — CLEAN. The query lives in
internal/wfengine, which legitimately owns theinstancestable.internal/settingsconsumes results via the cycle-safeappwire.WorkflowRowtype and does not importwfengine. No domain package newly couples to go-workflows.Findings
[MINOR] internal/settings/workflow_instances_sort.go:513 — Exported
SortWorkflowInstances(and helperworkflowStatusRank) is dead production code.It is referenced only from
*_test.go; no production caller exists (loadWorkflowHistoryexplicitly "no longer sorts the page"). Not a security issue, but an unused exported surface adds maintenance/confusion. Either wire it into the History render or delete it (and its test) in the same diff.REVIEW VERDICT: 0 blocker, 0 major, 1 minor
UI Review — PR #1205 (bd-bookshelf-33oo)
Screenshot read: PNG 1280x800 confirmed. Both sections rendered and visible.
[MAJOR] internal/settings/shell_handler.go:640 — History section includes RUNNING instances, causing duplicates
Confirmed in screenshot: instance df27bdbd-790a-4675-a671-66bc88e6d441 with state RUNNING appears verbatim in both the Running section (top table) and the History section (bottom table). The comment at line 641-643 explicitly acknowledges "Running instances may appear here too" — but that contradicts the feature spec, where History EXCLUDES running instances. Root cause: loadWorkflowHistory calls ListWorkflowInstances with no filter on completed_at; every active instance lands in both result sets. Fix: add WHERE completed_at IS NOT NULL to the ListWorkflowInstances query (or its underlying SQL), or post-filter in loadWorkflowHistory by excluding any instance ID already present in data.WorkflowRunning. The stale comment at lines 641-643 must also be removed.
[MINOR] templates/pages/settings_shell.html:1753,1799 — heading uses undefined .section-title instead of canonical .tasks-section-title
The h3 "Running" and "History" headings use class="section-title" which has no rule in static/css/main.css. The canonical heading class for .tasks-section containers is .tasks-section-title (main.css:4955). Browser default h3 styling makes this look passable visually, but it diverges from the design system. Fix: replace section-title with tasks-section-title on both headings.
Positive observations:
REVIEW VERDICT: 0 blocker, 1 major, 1 minor
UPDATED screenshot — running instance NO LONGER duplicated in History (fix commit)
Running section shows
shot-running-001(state=RUNNING).History section shows only
shot-completed-001(state=COMPLETED).The
shot-running-001running instance is correctly excluded from History, both in the initial server-rendered HTML (handler dedup viarunningSet) and in the auto-refresh feed (RecentInstancesHandlernow filtersstate=="running"before returning JSON to theworkflow-listStimulus controller)..tasks-section-title has text-transform:uppercase in CSS, so Chromium's innerText returns "RUNNING". The previous assertion used ContainSubstring("Running") which fails a case-sensitive match against "RUNNING". Apply strings.ToLower before comparing, matching the same pattern used in journey_workflow_detail_test.go. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>Workflow Running section screenshot (workflows-running-section)
Workflow Running section screenshot (workflows-running-section)
Workflow Running section screenshot (workflows-running-section)
Security re-review — dedup-fix delta (head
782b0f61, sincecae63df9)Scope: the fix delta added to #1205 after the original clean security review — the History-excludes-running filter (
internal/settings/shell_handler.go) and theRecentInstancesHandlerrunning-filter + overfetch (internal/wfengine/recent_instances_handler.go).1. Bounded queries — OK.
RecentInstancesHandler:limitstarts atrecentInstancesDefault(20) and is only overwritten whenn > 0 && n <= recentInstancesMax(50). Negative, zero, non-numeric, and overflow (strconv.Atoireturns an out-of-range error) all fall through to the default. Solimitis in [1,50] andfetch = limit*2 <= 100. No request param can make the fetch unbounded. Post-filter truncationhistory[:limit]is guarded bylen(history) > limit. No mis-behaving path.loadWorkflowHistory): fetch staysshellWorkflowsPageSize+1(constant) with keyset cursors (after_id/after_exec). The running-filter is in-memory and does not turn it into a scan. Cursor is derived from the DB-ordered tail BEFORE filtering, so keyset pagination stays correct.2. Authz — unchanged.
GET /admin/wf-instancesremainsadminRequired(...)-gated (engine.go:1622); the History section is served by the admin settings shell. The delta touches only in-memory row filtering — no new surface, no additional instances exposed to non-admins. The filter strictly reduces what is shown.3. No data exposure / injection — OK. Filtering is in-memory over already-fetched rows (
inst.State != "running"/runningSetmembership byExecutionID). No new SQL, no query built from user input, no new logs, no secrets/PII emitted.4. Determinism (flake) — OK. Ordering is the pre-existing keyset query (
created_at DESC, id DESCtotal order;id DESCtiebreaker documented as load-bearing, r6lx.2). The delta does not change ordering; the in-memory filter preserves DB order (append in iteration order).Note (non-blocking, not a security finding):
data.WorkflowRunningis capped atshellWorkflowsRunningCap; if running instances exceed that cap, an over-cap running execution would not be inrunningSetand could still appear in History — a display-dedup edge, no security impact.REVIEW VERDICT: 0 blocker, 0 major, 0 minor
782b0f613543efd38febWorkflow Running section screenshot (workflows-running-section)