Uh oh!
There was an error while loading. Please reload this page.
fix(v2): derive the log and run status enums from the persisted status list - #6612
Conversation
…s list
`GET /api/v2/logs` and `GET /api/v2/logs/{runId}` parse the raw
`workflow_execution_logs.status` column against a six-value enum that omits
`paused`, so a run holding that value returns 500. The list response is
validated whole-page, so one such row 500s every page it lands on, and the
row is durable until the run is resumed, cancelled, or failed.
`paused` is not written by an ordinary human-in-the-loop pause — that path
persists `pending` (logging-session.ts:1180). It is written by
`PauseResumeManager.markResumeAttemptFailed`, which fires on any
`ResumeAdmissionError`: a workspace over its usage limit, an archived or
undeployed workflow, or a concurrent resume losing the claim race. That is a
routine business path.
The enum was supposed to be protected by an `AssertNever` exhaustiveness gate,
but the gate was vacuous: it compared against `PersistedWorkflowExecutionStatus`,
a hand-written union that was itself missing `paused`, because the write goes
through a raw `sql` CASE fragment Drizzle cannot type-check. Adding `paused` to
both lists would leave the same vacuous gate in place for the next status.
Instead, `PERSISTED_WORKFLOW_EXECUTION_STATUSES` becomes the single runtime
source of truth, `PersistedWorkflowExecutionStatus` is derived from it, and both
v2 contracts derive their enums from the const rather than re-declaring them.
Both surfaces pass the column through verbatim, so their reported set is the
persisted set by definition — there is no editorial choice for a gate to force,
only the question of whether a newly persisted status should be public, which
the option-list tests now pin. The `[...V2_PERSISTED_RUN_STATUSES, 'paused']`
append on the runs contract is deleted rather than adjusted; it would otherwise
be a duplicate.
Alternatives rejected:
- A `.catch()` or `safeParse` in the presenters is dead code:
`v2-json-route.ts:271` re-parses the whole body with the same schema.
- Normalizing `markResumeAttemptFailed` to write `pending` would remove the
distinction the resume claim query at human-in-the-loop-manager.ts:973 relies
on, and leaves the contract wrong for any other future status.
- Typing the Drizzle column does not help: the offending write is a raw `sql`
fragment, and `packages/db` cannot import the app's status list.
The v2 workflows spec changes are reordering and description only — the value
set there already contained `paused`. The v2 logs spec gains `paused`, which is
additive and safe while the whole `/api/v2` surface is behind the off-by-default
`v2-api` flag; it must land before v2 GA, after which it would be breaking.The latest updates on your projects. Learn more about Vercel for GitHub.
|
PR SummaryMedium Risk Overview Introduces Reviewed by Cursor Bugbot for commit f6e0952. Configure here. |
Greptile SummaryThe PR establishes the persisted workflow-execution status tuple as the runtime source of truth for v2 log and run status schemas, allowing persisted
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains.
|
| Filename | Overview |
|---|---|
| apps/sim/lib/logs/types.ts | Introduces the canonical persisted-status tuple and derives the corresponding TypeScript union from it. |
| apps/sim/lib/api/contracts/v2/logs.ts | Derives the v2 log status schema from the persisted tuple so paused rows validate successfully. |
| apps/sim/lib/api/contracts/v2/workflows.ts | Derives run status schemas from the persisted tuple and accurately documents the limits of paused-state discrimination. |
| packages/db/schema.ts | Replaces stale inline status guidance with compliant TSDoc pointing to the canonical runtime status list. |
| apps/sim/lib/api/contracts/v2/log-status.test.ts | Pins the public log-status wire contract and verifies that it remains derived from the persisted tuple. |
| apps/sim/lib/api/contracts/v2/workflow-run-status.test.ts | Verifies persisted-status derivation while preserving the detail-only queued overlay and narrower filter contract. |
| apps/docs/openapi-v2-logs.json | Regenerates the log API specification to include and explain the persisted paused status. |
| apps/docs/openapi-v2-workflows.json | Regenerates workflow run schemas with the derived status ordering and corrected paused-state semantics. |
Reviews (3): Last reviewed commit: "fix(v2): stop promising a paused discrim..." | Re-trigger Greptile
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 6931ea6. Configure here.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 16ba04d. Configure here.
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit f6e0952. Configure here.
Summary
workflow_execution_logs.statuscan holdpaused, but the v2 log presenters parsed it against an enum that omitted the value, so one such row 500s the response.pending.pausedis written only byPauseResumeManager.markResumeAttemptFailed, when a resume attempt does not run to completion (failed admission on billing/usage limits, an archived or undeployed workflow, a lost claim race; also an unavailable run buffer, a resume job that could not be enqueued, or a cancelled attempt). All routine business paths, not outages.items.map, so a single row turns the whole page into a 500 for that workspace every time it is fetched — until the row ages out.V2_LOG_STATUSESwas declaredas const satisfies readonly PersistedWorkflowExecutionStatus[]with anAssertNeverexhaustiveness gate, butPersistedWorkflowExecutionStatusitself omittedpaused— andsatisfieschecks membership, not completeness. The guard could never have caught this.lib/logs/types.tsnow exportsPERSISTED_WORKFLOW_EXECUTION_STATUSESas the single runtime source of truth, withPersistedWorkflowExecutionStatusderived from it; both v2 enums derive from that const, and the duplicatepausedappend incontracts/v2/workflows.tsis deleted. Adding a string to one list and forgetting the other is no longer expressible./api/v2sits behind the off-by-defaultv2-apifeature flag, so no default configuration reaches this code. This is a v2-GA blocker, not a release blocker.Verification
bun run check:openapifailing as stale.apps/docs/openapi-v2-logs.jsonandopenapi-v2-workflows.jsonwere regenerated withbun run generate:openapi, not hand-edited;bun run check:openapipasses (7 specs, 128 operations, 130 contracts cross-checked).bun run type-checkclean, biome clean.Type of Change
Testing
14 tests across 4 suites:
lib/api/contracts/v2/log-status.test.ts,lib/api/contracts/v2/workflow-run-status.test.ts,app/api/v2/logs/route.test.ts,app/api/v2/logs/[runId]/route.test.ts.Checklist