Skip to content

Workflow History propogation & signing#102

Open
JoshVanL wants to merge 3 commits into
dapr:mainfrom
JoshVanL:workflow-context-propogation-signing
Open

Workflow History propogation & signing#102
JoshVanL wants to merge 3 commits into
dapr:mainfrom
JoshVanL:workflow-context-propogation-signing

Conversation

@JoshVanL
Copy link
Copy Markdown
Contributor

@JoshVanL JoshVanL commented Mar 5, 2026

(this proposal has not been one-shot).

(this proposal has not been one-shot).

Signed-off-by: joshvanl <me@joshvanl.dev>
Copilot AI review requested due to automatic review settings March 5, 2026 13:29
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a new design proposal describing opt-in workflow history propagation and chain-of-custody signing for Dapr Workflows (Durable Task Framework), aimed at enabling downstream authorization/audit scenarios (including MCP-style workflows).

Changes:

  • Introduces a detailed spec for propagating workflow execution history to child workflows/activities with configurable scopes.
  • Specifies a history signing model (chunked signatures chained via digests) and a deterministic canonical digest algorithm for cross-language verification.
  • Outlines intended protobuf, runtime, and SDK surface changes plus lifecycle/acceptance criteria.
Comments suppressed due to low confidence (4)

20260304-RBS-workflow-history-propagation-signing.md:612

  • In the sequence diagram, the propagated history signatures list shows signatures=[sig1,sig2], but earlier in the same diagram the persisted signatures are sig0 and sig1. This looks like an off-by-one/naming typo that could confuse implementers; update the diagram to reference the correct signature identifiers/order (e.g., [sig0,sig1]).
       |                           |-- TaskScheduled -------------------------------->|                      |
       |                           |   (PropagatedHistory:                            |                      |
       |                           |    events=[e0..e4]                               |                      |
       |                           |    signatures=[sig1,sig2])                       |                      |

20260304-RBS-workflow-history-propagation-signing.md:566

  • The numbered list under In dapr/dapr: has two items labeled 3. (the orchestrator actor section and the history verification utility). Renumber the second one (likely 4.) to avoid ambiguity when referencing these steps later in reviews/implementation tracking.
3. **`pkg/actors/targets/workflow/orchestrator/`**:
   - **State persistence**: After each orchestrator execution completes, in `saveInternalState()`, the new `HistorySignature` entries are included in the transactional state operation alongside history and inbox entries. Each signature is persisted as an individual actor state key (`signature-000000`, etc.).
   - **Propagation**: When building child workflow start events and activity task events, read signatures from the loaded state and copy the relevant events to assemble the `PropagatedHistory`.

3. **History Verification Utility** (`pkg/runtime/wfengine/historyverify/`):
   - `VerifySignatures(signatures []*HistorySignature, events []*HistoryEvent) error` - walks the signature chain, recomputes the canonical digest of each referenced event range, verifies it against the stored `events_digest`, then checks the cryptographic signature against the certificate and verifies certificate validity against the timestamp of the last event in each signed range.

20260304-RBS-workflow-history-propagation-signing.md:66

  • ## Related Items is currently empty. Since this repo’s proposal template expects links to related proposals/issues (or the section should be removed if none), please either add relevant references (issues/PRs/specs) or drop the heading to avoid an unfinished-looking section.
## Related Items

## Expectations and Alternatives

20260304-RBS-workflow-history-propagation-signing.md:406

  • In this section, WorkflowStateMetadata is described as adding signatureLength “matching inboxLength and historyLength”, but the protobuf example uses signature_length / history_signing_enabled (snake_case). To avoid implementer confusion, consider using the protobuf field names consistently here (or explicitly call out when you’re referring to generated-language property names).
The `WorkflowStateMetadata` protobuf is extended with a `signatureLength` field to track the number of stored signatures, matching the existing `inboxLength` and `historyLength` pattern.

```protobuf
// Extend existing WorkflowStateMetadata
message WorkflowStateMetadata {
  // ... existing fields (inboxLength, historyLength, generation) ...

  // Number of HistorySignature entries stored.
  uint64 signature_length = 4;

  // Whether history signing is enabled for this workflow instance.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

@javier-aliaga
Copy link
Copy Markdown

javier-aliaga commented Mar 5, 2026

I have concerns about this proposal, I think making history propagation available on any CallActivity/CallSubOrchestrator opens the door to developers using propagated history as a substitute for explicit inputs.

Using the same scenario in the proposal:

#### 5. Conditional Authorization Based on History

A payment processing activity only executes if the propagated history proves:
- The request originated from an authorized frontend application.
- A fraud detection workflow step completed successfully.
- The amount was approved by an activity running in the authorization service.

Lets imagine now we remove the fraud step, the child workflow will break an none of the payments will move forward.

This is a big risk, it opens the door to this checks without explicit contracts

Alternative

Maybe an alternative is having the MCP as a First-Class Primitive, so in this case it makes sense to propagate the history as the MCP expects it.

So I would prefer:

  • Normal activities/subOrchestrators should be pure: input -> output.
  • MCP tool calls are not pure — they are trust boundary crossings where the callee needs to verify how you got here, not just what you want.

@olitomlinson
Copy link
Copy Markdown

I very much approve of this proposal. Even outside of MCP, having some provenance that this activity/child-workflow was called under the right circumstances (that can't be forged) I think is very valuable.


I would be in favour of having the concept of simple first-class markers in the workflow history.

Something Like this.

... in the middle of workflow code

 
 workflowContext.AddMarker("some-arbitrary-marker");
 
 workflowContext.CallActivity("my-important-activity", input);
 

... somewhere in my-important-activity Activity code

 
 // marker is present, continue
 activityContext.ValidateMarker("some-arbitrary-marker");
 
 // marker is not present, fail activity without retry
 activityContext.ValidateMarker("some-missing-marker");
 

 

Comment thread 20260304-RBS-workflow-history-propagation-signing.md
Signed-off-by: joshvanl <me@joshvanl.dev>
Copy link
Copy Markdown
Contributor

@cicoyle cicoyle left a comment

Choose a reason for hiding this comment

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

few things from me

Comment thread 20260304-RBS-workflow-history-propagation-signing.md Outdated
Comment thread 20260304-RBS-workflow-history-propagation-signing.md
Comment thread 20260304-RBS-workflow-history-propagation-signing.md
Signed-off-by: joshvanl <me@joshvanl.dev>
JoshVanL added a commit to JoshVanL/durabletask-protobuf that referenced this pull request Mar 19, 2026
Extends the backend service proto with messages needed for chain-of-custody
signing of workflow history events. Each orchestrator execution signs the new
event range and chains to the previous signature. Certificates are
deduplicated in a separate table and referenced by index.

- SigningCertificate: stores DER-encoded X.509 certificate per identity
- HistorySignature: signing metadata covering a contiguous event range,
  linked via previousSignatureDigest to form a verifiable chain
- WorkflowStateMetadata: extended with signatureLength and
  signingCertificateLength fields

Proposal: dapr/proposals#102

Signed-off-by: joshvanl <me@joshvanl.dev>
JoshVanL added a commit to JoshVanL/durabletask-go that referenced this pull request Mar 19, 2026
Introduce chain-of-custody signing for workflow history events, allowing
each orchestrator execution to produce a cryptographic signature over
newly appended events. Signatures are chained via previousSignatureDigest
to form a tamper-evident log.

- Add historysigning package with deterministic event marshaling,
  raw-bytes digest computation, and sign/verify logic supporting
  Ed25519, ECDSA P-256, and RSA PKCS#1 v1.5
- Verify certificate validity (NotBefore/NotAfter) against the timestamp
  of the last event in each signed range
- Export SigningCertificate and HistorySignature type aliases from
  backend package
- Add comprehensive tests covering all key types, chain verification,
  certificate rotation, tamper detection, and certificate validity

Proposal: dapr/proposals#102

Signed-off-by: joshvanl <me@joshvanl.dev>
JoshVanL added a commit to JoshVanL/durabletask-go that referenced this pull request Apr 6, 2026
Introduce chain-of-custody signing for workflow history events, allowing
each orchestrator execution to produce a cryptographic signature over
newly appended events. Signatures are chained via previousSignatureDigest
to form a tamper-evident log.

- Add historysigning package with deterministic event marshaling,
  raw-bytes digest computation, and sign/verify logic supporting
  Ed25519, ECDSA P-256, and RSA PKCS#1 v1.5
- Verify certificate validity (NotBefore/NotAfter) against the timestamp
  of the last event in each signed range
- Export SigningCertificate and HistorySignature type aliases from
  backend package
- Add comprehensive tests covering all key types, chain verification,
  certificate rotation, tamper detection, and certificate validity

Proposal: dapr/proposals#102

Signed-off-by: joshvanl <me@joshvanl.dev>
JoshVanL added a commit to JoshVanL/durabletask-go that referenced this pull request Apr 6, 2026
Introduce chain-of-custody signing for workflow history events, allowing
each orchestrator execution to produce a cryptographic signature over
newly appended events. Signatures are chained via previousSignatureDigest
to form a tamper-evident log.

- Add historysigning package with deterministic event marshaling,
  raw-bytes digest computation, and sign/verify logic supporting
  Ed25519, ECDSA P-256, and RSA PKCS#1 v1.5
- Verify certificate validity (NotBefore/NotAfter) against the timestamp
  of the last event in each signed range
- Export SigningCertificate and HistorySignature type aliases from
  backend package
- Regenerate protobuf outputs for renamed proto types
- Add comprehensive tests covering all key types, chain verification,
  certificate rotation, tamper detection, and certificate validity

Proposal: dapr/proposals#102'

Signed-off-by: joshvanl <me@joshvanl.dev>
Copy link
Copy Markdown
Contributor

@cicoyle cicoyle left a comment

Choose a reason for hiding this comment

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

+1 binding - I am good with the proposal as-is. There is alignment that the sdk perspective will be:

workflow.WithHistoryPropagation(workflow.PropagateLineage()),
OR
workflow.WithHistoryPropagation(workflow.PropagateOwnHistory()),

@WhitWaldo
Copy link
Copy Markdown
Contributor

I think I'd want to spend more time thinking about what the SDK shape looks like myself as it seems like a better fit to drop into a workflow option instead of adopting a fluent interface just for this.

Is there any expectation that that SDK will be able to read any of the signed history out or is it only accessible through tooling that can access the events?

@cicoyle
Copy link
Copy Markdown
Contributor

cicoyle commented May 12, 2026

I think I'd want to spend more time thinking about what the SDK shape looks like myself as it seems like a better fit to drop into a workflow option instead of adopting a fluent interface just for this.

Is there any expectation that that SDK will be able to read any of the signed history out or is it only accessible through tooling that can access the events?

All sdks should have nice helpers for end users to consume. This is what I was thinking for the go-sdk.. We should try to align for consistency across asks, and account for future expansion with the options - like the redaction and depth in the description from the PR :)

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.

7 participants