Skip to content

fix(provenance): let a run that never started report why it failed - #7281

Merged
icecrasher321 merged 4 commits into
stagingfrom
provenance-finaloutput-producer
Aug 29, 2026
Merged

fix(provenance): let a run that never started report why it failed#7281
icecrasher321 merged 4 commits into
stagingfrom
provenance-finaloutput-producer

Conversation

@icecrasher321

Copy link
Copy Markdown
Collaborator

Summary

  • Closes the last actively-generating provenance signal: value-provenance-absent on copilotWorkflowMutation.failedRunCrossing, unchanged since 08-21 across five deploys.
  • What was wrong: a copilot-run workflow that fails before reaching the engine — undeployed workflow, invalid input, admission refusal — crossed back with no provenance. That latched the tool's registry, and inspectToolResultForCopilot reduced the result to {success: false, error: "result unavailable"}. The caller was told its run failed but not why, for a message this layer wrote that named no secret because none had been resolved yet.
  • The fix: the executor attaches its execution result to every throw, so the absence of one proves no block ran — output, logs and error are all undefined and the only content is thrownMessage. That's an absence, not an inability to vouch, so the crossing now carries an exact-empty envelope. The message still passes the tool boundary's egress projection against the same registry, so anything that registry knows is still redacted. A run that did execute and could not vouch hands back its incomplete envelope exactly as before, and that still latches — pinned by its own test.
  • Makes the executor's attach total rather than conditional on error instanceof Error, so that inference is a guarantee rather than an accident. A block failure is already normalized on the way in via toError, so the old guard held in practice; it just didn't cover a non-Error raised by the engine's own synchronous work. toError is identity-preserving.
  • Moves the exact-empty envelope into the registry module, which owns this vocabulary, replacing a private copy in the logging session — one definition of "vouched for, naming nothing".

Verified against production

Read the externalized execution payloads from S3 for this workflow's runs (DB gives the storage pointer; the envelope isn't visible to SQL). Sampling before and after the v0.8.13 deploy that carried #7173: pre-fix 2/10 had finalOutput complete=false; post-fix 0/10, across completed, failed and cancelled. So the TraceStore — displayProjection stream is confirmed decaying stock of pre-08-27 executions, not a live producer — its emission logic is fixed, and this PR is about the separate copilot path.

Also worth recording: inherited-incomplete-source (2026-08-09) and mounted-file-provenance-unavailable (2026-08-10) are long-standing reasons that were newly observed, not newly added — the former is just a per-tool fork inheriting a latched parent, which is the isolation working.

Type of Change

  • Bug fix

Testing

Two new tests on the crossing: a failure that never reached the engine now imports a complete envelope; one that did execute still passes through its incomplete envelope. Verified the first fails without the fix. Plus an executor test pinning that every throw out of a failed run carries its execution result. 6,828 tests green across executor/, lib/logs/, lib/workflows/, lib/copilot/; bun run type-check clean; all 39 audits pass. Rebased onto latest staging, conflicts resolved keeping both sides' tests.

Checklist

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

A copilot-run workflow that fails before reaching the engine crossed
back with no provenance, which latched the tool's registry and reduced
the result to "result unavailable". The caller was told its run failed
but not that the workflow was undeployed, or the input invalid, or the
slot unavailable — the reasons this layer produces before any block
runs, naming no secret because none had been resolved yet.

The executor attaches its execution result to every throw, so the
absence of one is proof that no block ran: output, logs and error are
all undefined and the only content is a message this layer wrote. That
is an absence, not an inability to vouch, so the crossing now carries
an exact-empty envelope. The message still passes the tool boundary's
egress projection against the same registry, so anything that registry
knows is still redacted. A run that did execute and could not vouch
hands back its incomplete envelope exactly as before, and that still
latches.

Make the attach total rather than conditional to keep that inference
sound. A block failure is already normalized on the way in, so the old
`instanceof Error` guard held in practice; what it did not give was a
guarantee covering a non-Error raised by the engine's own synchronous
work. toError is identity-preserving, so ordinary failures keep their
type.

The empty envelope moves to the registry module, which owns the
vocabulary, replacing a private copy in the logging session so one
definition states what "vouched for, naming nothing" is.
@vercel

vercel Bot commented Aug 29, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Aug 29, 2026 8:16pm

Request Review

@greptile-apps

greptile-apps Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR lets Copilot surface the original reason when a workflow fails before entering the execution engine while preserving untrusted provenance for failures after execution.

  • Normalizes every engine throw and attaches its execution result.
  • Propagates completed execution results through post-execution failures.
  • Imports complete-empty provenance only when neither the executor nor the post-run crossing produced a result.
  • Centralizes construction of the complete-empty provenance envelope.
  • Adds regression coverage for never-started, executed, post-execution, and crossing-import failures.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
apps/sim/lib/workflows/application/run-workflow-from-copilot.ts Distinguishes genuinely never-started failures from engine, post-execution, and post-crossing failures using attached or retained execution results.
apps/sim/lib/workflows/executor/execute-workflow.ts Retains the core execution result and attaches it to normalized failures raised by subsequent processing.
apps/sim/executor/execution/engine.ts Normalizes arbitrary thrown values so every propagated engine failure can carry its execution result.
apps/sim/executor/utils/resolved-secret-trace-registry.ts Exposes a shared factory for fresh complete-empty provenance envelopes.
apps/sim/lib/workflows/application/run-workflow-from-copilot.test.ts Covers never-started, post-crossing, post-execution, and incomplete-provenance failure paths.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[Copilot starts workflow] --> B[executeWorkflow]
  B -->|Fails before engine result| C[No execution result]
  B -->|Engine or post-execution failure| D[Attach execution result]
  B -->|Returns successfully| E[Retain run result]
  C --> F[Import complete-empty provenance]
  D --> G[Import attached provenance]
  E --> H[Import returned provenance]
  H -->|Crossing import fails| I[Retry failed crossing with retained result provenance]
Loading

Reviews (4): Last reviewed commit: "fix(provenance): normalize a post-execut..." | Re-trigger Greptile

Comment thread apps/sim/lib/workflows/application/run-workflow-from-copilot.ts

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

Copy link
Copy Markdown

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 6 files

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

Fix all with cubic | Re-trigger cubic

Comment thread apps/sim/lib/workflows/application/run-workflow-from-copilot.ts
Comment thread apps/sim/executor/execution/engine.test.ts Outdated
…tarted claim

Review round 1, both findings accepted.

The post-run crossing runs inside the same try as the executor call, so
when that import is what throws, the catch sees an error carrying no
execution result — the same evidence a run that never started leaves.
The previous condition read that as "nothing crossed" and vouched for
it, when in fact an execution exists and its provenance was never
imported, which is exactly the content that cannot be vouched for.

Record whether the executor returned and require both facts before
claiming the absence: not past the executor, and no result attached.
Everything else hands back whatever envelope it has, and an incomplete
one still latches.

The executor test also could not fail against the old gated attach: a
block failure is normalized on the way in, so its rejection already
arrived as an Error. Drive it through the cancellation subscribe run()
awaits before the queue instead, which is its own synchronous work and
reaches the catch untouched — the case the total attach exists for.
@icecrasher321

Copy link
Copy Markdown
Collaborator Author

@greptile

@icecrasher321

Copy link
Copy Markdown
Collaborator Author

@cubic-dev-ai review

@cubic-dev-ai

cubic-dev-ai Bot commented Aug 29, 2026

Copy link
Copy Markdown

@cubic-dev-ai review

@icecrasher321 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

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 6 files

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

Fix all with cubic | Re-trigger cubic

Comment thread apps/sim/lib/workflows/application/run-workflow-from-copilot.ts Outdated
Round 2, cubic's finding accepted — and it was a distinct window, not a
restatement of round 1. The executor's post-execution work runs after
the run has produced a result but before `executeWorkflow` returns, so
a failure there reached callers with no result attached: the run threw
nothing itself, and the flag added last round could not be set yet.
Every consumer that reads a missing result as "no block ran" was wrong
in that window, this crossing included.

Fix it where the result lives rather than at each reader. The executor
attaches its own on the throws it raises; `executeWorkflow` now does the
same for failures raised after it holds one, skipping the case the
executor already recorded. Logging and trace spans get the same benefit
for free — they read the identical signal.

That makes an absent result total again, so the boolean flag goes and
the crossing reads one thing: the result from the error, or the one
already returned when the failure came later still, from the crossing
itself. Only a failure with neither can claim nothing ran. The
post-return case now describes content with the run's real envelope
rather than latching blind, which is strictly more accurate than either
prior behaviour.
@icecrasher321

Copy link
Copy Markdown
Collaborator Author

@greptile

@icecrasher321

Copy link
Copy Markdown
Collaborator Author

@cubic-dev-ai review

@cubic-dev-ai

cubic-dev-ai Bot commented Aug 29, 2026

Copy link
Copy Markdown

@cubic-dev-ai review

@icecrasher321 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

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 7 files

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

Fix all with cubic | Re-trigger cubic

Comment thread apps/sim/lib/workflows/executor/execute-workflow.ts Outdated
…he result

Round 3, cubic's finding accepted. The guard added last round required
the caught value to already be an `Error`, so a non-Error raised by
post-execution work skipped the attach and was rethrown bare — the same
hole this branch closed in the executor, left open one layer up by my
own change. A Copilot run would have reported an executed workflow as
never started and vouched for content it cannot describe.

Normalize once at the top of the catch and use that value throughout,
including the rethrow, matching what the executor does. `toError`
returns an `Error` unchanged, so a custom error class keeps its
identity and every ordinary failure is untouched — the existing
identity assertion on the rejection path still holds.

Two tests: the result reaches an ordinary post-execution failure, and a
non-Error one is normalized so it can carry the result too. The second
fails against the previous guard.
@icecrasher321

Copy link
Copy Markdown
Collaborator Author

@greptile

@icecrasher321

Copy link
Copy Markdown
Collaborator Author

@cubic-dev-ai review

@cubic-dev-ai

cubic-dev-ai Bot commented Aug 29, 2026

Copy link
Copy Markdown

@cubic-dev-ai review

@icecrasher321 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

Choose a reason for hiding this comment

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

No issues found across 8 files

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

Re-trigger cubic

@icecrasher321
icecrasher321 merged commit 95d1969 into staging Aug 29, 2026
27 checks passed
@icecrasher321
icecrasher321 deleted the provenance-finaloutput-producer branch August 29, 2026 21: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

Development

Successfully merging this pull request may close these issues.

1 participant