Skip to content

feat(api): run a pinned deployment version and expose paused run points in v2 - #9854

Merged
waleedlatif1 merged 24 commits into
stagingfrom
feat/v2-execute-version-pin-paused-detail
Nov 10, 2026
Merged

waleedlatif1 merged 24 commits into
stagingfrom
feat/v2-execute-version-pin-paused-detail

Conversation

@waleedlatif1

Copy link
Copy Markdown
Collaborator

Summary

  • Fixes pinned deployment versions being ignored by the executor: executeWorkflowService loaded the pinned version only for its variables while the core ran the active graph. The pin now reaches loadWorkflowState on the sync, stream, and queued paths (this also makes MCP serve run the version it advertised)
  • Adds run: { source: 'deployment', version } to POST /api/v2/workflows/{id}/execute to run an earlier deployment version without activating it. It requires write access (operation workflows.execute_version); an unknown version is a 404 and anonymous public-API callers can't pin
  • GET /api/v2/workflows/{id}/runs/{runId} now reports the version that ran and paused.points: every pause point with block, kind, status, parallel/loop position, queue position, input fields, and display content
  • Pause display content is redacted against the run's resolved secrets at capture time and withheld on the same permission-group terms as the run's outputs; points captured before this change expose no content
  • Fixes queued API, scheduled, and webhook runs using the editor's draft variables instead of the deployed version's (versions deployed before variables were snapshotted keep the previous fallback), and queued runs now take blocks and variables from the same version
  • Documents running an earlier version and paused run points in the deployment API docs

Deploy note: a background worker on the previous release ignores pinnedDeploymentVersionId and runs the active version, so deploy workers with or before the web tier.

Type of Change

  • Bug fix
  • New feature

Testing

  • Integration against PostgreSQL: execute-workflow-version.integration.ts (pinned sync/async runs execute v1 while v2 is active; red with the executor fix reverted), deployed-run-variables.integration.ts (queued/schedule/webhook variables, legacy fallback), read-workflow-run-pause.integration.ts (display withheld for restricted viewers), pause-persistence.integration.ts (multi-branch points, redaction through the real engine with an encrypted secret, oversized display keeps input fields, legacy points)
  • Unit: human-in-the-loop manager, execution status, execution core, engine, v2 runs/execute routes, schedule and webhook execution
  • tsc on apps/sim; check:openapi, cli-api, mcp-operations, v2-route-table, api-validation:strict, permission-group-enforcement, actorless-executor-operations, capability-subject, application-graph, boundaries; root scripts and sim-cli suites; biome
  • apps/sim/scripts/test-workflow-pinned-execute-e2e.ts covers the HTTP-only paths against a dev stack

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing (new tests pass the test-audit authoring gate)
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

executeWorkflowService loaded the pinned deployment version only to read
its variables and blocks; the execution metadata carried no pin, so the
core loaded and ran the active deployment. Carry the pin as
pinnedDeploymentVersionId on the sync, stream, and async paths and load
that version in the core. MCP serve, which already passed a pin, now runs
the version it selected.
run: { source: 'deployment', version } runs that deployment version
instead of the active one. The pin is its own write-role operation,
workflows.execute_version, selected after parsing; the use case resolves
the number to the version row and answers an unknown version with 404.
Anonymous public-API callers cannot pin. The run resource reports the
deployment version each run executed.
GET /api/v2/workflows/{id}/runs/{runId} lists every point a paused run
waits at under paused.points: block, kind, resume state, parallel and
loop scope, queue position, the fields a human pause asks for, and its
reviewer content. A pause's stored response can carry resolved secrets,
so the content and fields are scrubbed through the run's resolved-secret
registry when the pause is captured and marked displayScrubbed; points
stored before this expose neither. Resume links, API-mode status and
headers, and the execution snapshot are never read into the response.
…s field optional

The shared executeDeployedWorkflow helper took a broad Principal, which no
longer satisfies executeWorkflowService's WorkflowExecutionPrincipal once the
use case's narrowed principal is widened by the extraction. The internal run
status response's new deploymentVersion field is optional so a browser bundle
deployed ahead of its server still parses the response.
Runs a workflow deployed twice through the execute-version use case: a sync
and an async run pinned to version 1 execute version 1 while version 2 is
active and record it on the log, the queued payload carries the pin, and an
unknown version is not_found before any run starts. Both pinned cases fail
with the executor change reverted.
An unpinned queued run seeded its execution snapshot with the workflow row's
variables, which are the editor's draft, while the sync and stream paths use
the variables of the deployed state they run. Load the active deployment's
variables in the worker as the sync path does, so a queued run of the
deployment no longer sees unsaved or undeployed variable edits.
…lay gate

The core loads a resumed run's admitted version and a pinned run's version
through one branch. The scrubbed-display rule that persisting and mapping a
pause point both apply is one helper, and mapping is the single gate the run
detail relies on. The pin rides the queue payload as a plain optional field,
and a whitespace-only input field name is no longer a field.
Adds the test:workflow-pinned-execute:e2e package script and names the
environment the script needs against a dev stack.
Scheduled and webhook runs executed their deployed version's blocks with
the workflow row's saved variables, the editor's draft. They now use the
variables the version was deployed with, as queued API runs do.

A version deployed before deployments snapshotted variables has no
variables in its snapshot, and the loader defaults them to {}. Those runs
have always used the saved variables, so the loader now reports whether the
snapshot recorded variables and resolveDeployedRunVariables keeps the saved
ones only for such a version, on the queued API, schedule, and webhook
paths. The sync path is unchanged. Proven against real Postgres by running a
schedule and a generic webhook delivery in-process; both read the saved
variables before this change, and the pre-snapshot case reads {} without
the fallback.
The worker loaded the active deployment for its variables and the core then
loaded the active deployment again for its blocks, so a deploy landing
between the two could pair one version's variables with another's graph.
The worker now pins the core to the version it loaded, and retries that
read on a transient database error as the core's own load does.

Schedules and webhooks already hand the core the blocks of the version
they loaded, so they need no change.
A paused run's pause points carried each point's reviewer display whatever
the caller asked for or the viewer's permission group withheld, though the
display is rendered from upstream block output. The run detail now returns
display only with includeOutput and when logs.trace_spans is not denied, as
it does finalOutput, blockOutputs, and files; input fields are block
configuration and stay.

The display and the input fields are captured as separate projections, so
content too large to prove free of secrets withholds only itself. A point
recorded without a registration time lists with registeredAt null instead
of failing the response.

A real human-in-the-loop pause run in-process through the engine, with a
workspace secret in its content, proves the stored point and the run detail
carry the redacted reference, and that both display gates hold.
Covers run.version on v2 execute (write access, keyed callers, 404 for an
unknown version, version on the run status) and paused.points on the run
detail, including when display is withheld.
@waleedlatif1
waleedlatif1 requested a review from a team as a code owner November 10, 2026 21:25
@vercel

vercel Bot commented Nov 10, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
docs Ready Ready Preview Nov 10, 2026 10:17pm UTC

Request Review

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 36 files

Turn on auto-fix | Re-trigger cubic

@greptile-apps

greptile-apps Bot commented Nov 10, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 5/5

[High impact] The PR appears safe to merge; no new actionable issues remain.

Summary

Adds pinned deployment runs and pause details to v2.

  • Workflow runs can use a chosen deployment version without activating it.
  • Queued and triggered runs use variables from their deployed version.
  • Paused runs show the points people can resume and safe details about each one.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[Poll run ID] --> B{Settled resume attempt?}
  B -->|No| C[Read requested run]
  B -->|Yes| D[Read parent run]
  C --> E[Return status and stateExecutionId]
  D --> E
  E --> F[Read pause points using stateExecutionId]
  F --> G[Apply viewer restrictions]
  G --> H[Return v2 run details]
Loading

Reviews (4) · Last reviewed commit: "fix(api): list a paused run's points whe..." · Reviewed by Greptile

…y once

A resume still starting or queued has no log row of its own, so its status
reported no deployment version until it settled and then reported the
parent's. It now reports the version its parent run recorded throughout.

The pause context detail strips each listed point's response data because
the selected point already carries it; it now strips the listed points'
display for the same reason instead of returning every point's content.
…hten waits

The webhook delivery case now runs a version that a later deployment
superseded, so it shows the run uses its admitted version's variables
rather than the active one's. Run waits end below the integration test
timeout so a failed run reports its status instead of a bare timeout. The
pinned execute e2e script logs static messages with metadata, and its
approval declares the field whose capture it asserts.
Adds the API deployment page's new code example to the compatibility
baseline without renumbering the page's existing examples.
The canonical-terminal-status test parses a v2 run body against the route
contract, which now requires version on the run resource.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cubic-dev-ai review this PR

@cubic-dev-ai

cubic-dev-ai Bot commented Nov 10, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai review this PR

@waleedlatif1 I have started the AI code review. It will take a few minutes to complete.

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 39 files

Reply with feedback, questions, or to request a fix.

Turn on auto-fix | Re-trigger cubic

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cubic-dev-ai review this PR

@cubic-dev-ai

cubic-dev-ai Bot commented Nov 10, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai review this PR

@waleedlatif1 I have started the AI code review. It will take a few minutes to complete.

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 39 files

Reply with feedback, questions, or to request a fix.

Turn on auto-fix | Re-trigger cubic

A settled resume attempt has no log row of its own, so the status read
answers from the parent run it continued and can report that run paused.
The run detail then read pause points by the attempt id, which has no
paused record, and returned none. The status read now reports which
execution's state answered it, and the run detail reads the points from
that execution.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cubic-dev-ai review this PR

@cubic-dev-ai

cubic-dev-ai Bot commented Nov 10, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai review this PR

@waleedlatif1 I have started the AI code review. It will take a few minutes to complete.

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 39 files

Reply with feedback, questions, or to request a fix.

Turn on auto-fix | Re-trigger cubic

This branch was successfully deployed

1 active deployment
Preview — 36c7cc93 Deployed Nov 10, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant