refactor: consolidate three drifted copies, close a gate blind spot, fix stale docs

Merged
WA
2mo ago
+155-185

Six independent changes, one commit each. Everything below was verified by hand — the audits that surfaced these also produced several claims that did not survive checking, noted at the end.

1. One owner for new-workflow sort order

The same ~35-line query existed three times. Two are character-identical modulo the table alias; the third, in persistence/duplicate.ts, had drifted — it omits isNull(workflow.archivedAt), so a folder whose lowest-sortOrder workflow is soft-deleted positioned a duplicate differently from a create. The folder-side query agrees in all three, marking it a copy-paste slip.

Promoted to lib/workflows/sort-order.ts with an optional DbOrTx. Its own module rather than utils.ts because duplicate.test.ts and workflow-lifecycle.test.ts both mock @/lib/workflows/utils wholesale — from a separate module the real query still runs under those suites, so their existing sort-order assertions kept their meaning and needed no edits. All 2,130 lib/workflows tests pass unchanged.

The archived-row behavior is not unit-testable here: the shared dbChainMock does not evaluate WHERE predicates. The guarantee is structural — one query builder instead of three.

2. ee integration allowlist lost its case-folding

ee/access-control re-implemented the intersection instead of calling intersectIntegrationAllowlists, normalizing only on the envAllowlist !== null branch. With ALLOWED_INTEGRATIONS unset a stored config passed through untouched, and callers compare against blockType.toLowerCase() — so a stored 'Slack' failed to match 'slack'.

Scoped honestly: the UI writes block.type and block types are lowercase, so this is unreachable from the UI. It is reachable through the API, where the contract is a bare z.array(z.string()). Two tests added; the first fails against the old code.

3. getFullOrganization dropped its AbortSignal

useOrganization destructured signal and passed it to fetchOrganization, which named the parameter _signal and never used it — satisfying the linter, which only checks that the queryFn destructures signal. Org detail fetches could not be cancelled; switching orgs rapidly left every prior request in flight to resolve out of order. Only _signal under apps/sim/hooks. Test added, fails without the fix.

4. check-zustand-v5-selectors could not see the canvas store

The gate matched /use[A-Z]\w*Store\(/. Exactly one Zustand store is not named with a Store suffix — useWorkflowRegistry — so the store behind the canvas went unchecked, and two bare whole-store subscriptions had accumulated in the action bar, re-rendering it on every registry mutation. Every other call site already uses a selector.

Widened to (?:Store|Registry). Verified the widened gate fails on the two call sites and reports nothing else — no cleanup tail.

5. Comments naming symbols that do not exist

acquireTablePositionLock and nextAutoPosition (removed with the service.ts split), performRestoreFolder, and a waitForHead method the queue class does not have. Plus a docs route deleted with Ask AI, and a "Level 1.5 / 2 / 3" view scheme that appears nowhere else.

The restore-resource.ts one is the reason this is worth a commit: it justified a total Record by describing a 'workflow' default that restoreFolder does not have (resourceType is required, the config lookup is a bare index). A comment that invents a failure mode is how someone later talks themselves into relaxing the Record to a Partial.

6. Three derived values

import-modal corrected an invalid selection in two effects, committing and painting one frame where the profile belongs to the previously selected browser — with Import enabled, submitting via a profiles.find across every browser. Now derived during render; covered by the existing "never leaves a profile selected that belongs to another browser" test. Also isWorkflowEmpty (a second memo over the same dep computing !hasBlocks) and a thinking-loader effect writing state shown = variant ?? cycleVariant can never read.

Verification

bun run type-check clean (the 11 remaining errors are pre-existing: unresolved @c15t/scripts, window.gtag/twq). Biome clean. Tests: 2,130 in lib/workflows, 63 in ee/access-control, plus hooks/queries, lib/logs, lib/table, lib/resources, lib/knowledge/search, import-modal, thinking-loader — all green. Each of the three new tests was verified to fail against the unfixed code.

Reported but rejected

An audit claimed the ee allowlist bug denies blocks in normal deployments — it cannot, because the UI only ever writes lowercase. Another claimed check-react-query-patterns is blind to useQuery<T>( and useQueries; that was fixed in #7020 and re-verified across all 34 call sites — that vein is mined out.