Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/validate_examples.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ jobs:
- name: Set Dapr Runtime version
# TODO: revert to release-detection (or shared action from PR #815) after dapr v1.18 GA
run: |
RUNTIME_VERSION=1.18.0-rc.3
RUNTIME_VERSION=1.18.0-rc.4
echo "DAPR_RUNTIME_VER=$RUNTIME_VERSION" >> $GITHUB_ENV
echo "Using $RUNTIME_VERSION"

Expand Down Expand Up @@ -176,6 +176,7 @@ jobs:
"service",
"socket",
"workflow",
"workflow-history-propagation",
"workflow-parallel",
"workflow-taskexecutionid"
]
Expand Down
File renamed without changes.
6 changes: 3 additions & 3 deletions examples/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ replace github.com/dapr/go-sdk => ../

require (
github.com/alecthomas/kingpin/v2 v2.4.0
github.com/dapr/durabletask-go v0.11.4-0.20260515205209-1a486ff44e82
github.com/dapr/durabletask-go v0.12.0
github.com/dapr/go-sdk v0.0.0-00010101000000-000000000000
github.com/dapr/kit v0.17.1-0.20260505124817-5579fd105e21
github.com/dapr/kit v0.18.0
github.com/go-redis/redis/v8 v8.11.5
github.com/google/uuid v1.6.0
google.golang.org/grpc v1.79.3
Expand All @@ -20,7 +20,7 @@ require (
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/dapr/dapr v1.18.0-rc.3 // indirect
github.com/dapr/dapr v1.18.0-rc.4 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/go-chi/chi/v5 v5.2.2 // indirect
github.com/go-jose/go-jose/v4 v4.1.4 // indirect
Expand Down
12 changes: 6 additions & 6 deletions examples/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK3
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/dapr/dapr v1.18.0-rc.3 h1:fsd1yDu9lk+hdwoWU1QkKpOb4ZyXKnkkkOlL+X0C1o8=
github.com/dapr/dapr v1.18.0-rc.3/go.mod h1:2fHQM8C7tGbJ5ARwwwKVjDWt0cFb6ysKA9eV1AISCAI=
github.com/dapr/durabletask-go v0.11.4-0.20260515205209-1a486ff44e82 h1:JZOOveoVmZypxW8v/dFk+3+k1ZAJbqyJ0rK30pxzifE=
github.com/dapr/durabletask-go v0.11.4-0.20260515205209-1a486ff44e82/go.mod h1:/qqf208i7YnZgQ6Yxuee42yk+DLnZYe7horTwo0fVYI=
github.com/dapr/kit v0.17.1-0.20260505124817-5579fd105e21 h1:eQaTMLBT+Z5X4imJ5C9iE83n7LOW/L1KHXcttHD9y/Q=
github.com/dapr/kit v0.17.1-0.20260505124817-5579fd105e21/go.mod h1:2v02LZdXzPmOadxoT6EMEt0bsEYe6h1fn2ndYWmylCg=
github.com/dapr/dapr v1.18.0-rc.4 h1:h5WG5uH8jduRAIt2Rn8vyi/eUUJjmzDzyAx3u/aAxDk=
github.com/dapr/dapr v1.18.0-rc.4/go.mod h1:etCWO1UxMp8IvPJcZ+EyroJ8R3q2MSaW9m61i2qbx5w=
github.com/dapr/durabletask-go v0.12.0 h1:R44GG/DYmonLMh6RVDkG7QlbTGkiGi8sCbuB/4WhWwE=
github.com/dapr/durabletask-go v0.12.0/go.mod h1:jeY+1a3CdvSjVebLijYuXl2vbI+bpdssIeu5Crw0OoY=
github.com/dapr/kit v0.18.0 h1:AF1eI7C8sLx9n0Lg3c+etPkHvUCVM8k769aABKSlyF4=
github.com/dapr/kit v0.18.0/go.mod h1:2v02LZdXzPmOadxoT6EMEt0bsEYe6h1fn2ndYWmylCg=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
Expand Down
9 changes: 9 additions & 0 deletions examples/workflow-history-propagation/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# The binary is expected to be built on the host and copied in.
# Build this from examples/workflow-history-propagation, where examples/go.mod
# provides the local durabletask-go / go-sdk paths via replace directives.
# Build with:
# CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -o payment-app .
# (use GOARCH=amd64 on Intel machines)
FROM gcr.io/distroless/base-debian12
COPY payment-app /payment-app
ENTRYPOINT ["/payment-app"]
237 changes: 237 additions & 0 deletions examples/workflow-history-propagation/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
# Dapr Workflow History Propagation Example

This example demonstrates how workflows can propagate their execution history
to child workflows and activities, enabling downstream consumers to inspect
the full (or partial) execution context of their caller.

## Workflow Architecture

```
MerchantCheckout (workflow)
├── ValidateMerchant (activity, no propagation)
└── ProcessPayment (child workflow, PropagateLineage)
├── ValidateCard (activity, no propagation)
├── CheckSpendingLimits (activity, no propagation)
├── FraudDetection (child workflow, PropagateLineage)
│ → sees 15 events: MerchantCheckout + ProcessPayment
└── SettlePayment (activity, PropagateOwnHistory)
→ sees 12 events: ProcessPayment only
```

### Propagation Scope

| Mode | What it sends | Use case |
|------|--------------|----------|
| `PropagateLineage()` | Caller's own events + any ancestor events it received | Full chain-of-custody verification |
| `PropagateOwnHistory()` | Caller's own events only (no ancestor chain) | Trust boundary — downstream only sees the immediate caller |

### Key Demonstration

- **FraudDetection** receives 15 events via `PropagateLineage()` — it can verify
that `ValidateMerchant` ran in the top-level/grandparent workflow (MerchantCheckout),
plus `ValidateCard` and `CheckSpendingLimits` ran in ProcessPayment.

- **SettlePayment** receives 12 events via `PropagateOwnHistory()` — it only sees
ProcessPayment's events. The MerchantCheckout ancestral history is excluded.

## Running the Example

Propagation works in either of two modes:

- **Standalone / `dapr run`** — quickest path. Propagation runs end-to-end with
no mTLS or signing setup. Each propagating dispatch logs a warning so
operators are aware the chunks are unsigned and can't be cryptographically
verified by receivers.
- **Kubernetes with mTLS + WorkflowHistorySigning** — production-grade path.
Chunks travel between sidecars over mTLS and the workflow's own history is
signed. No warning logs are emitted.

### Option A: Standalone (`dapr run`)

First-time setup: run `dapr init` if you haven't already. The commands below assume
you're in `examples/workflow-history-propagation`.

Build the app binary:

<!-- STEP
name: Build payment-app
timeout_seconds: 300
-->

```bash
go build -o payment-app .
```

<!-- END_STEP -->

Run it under Dapr — propagation runs end-to-end:

<!-- STEP
name: Run history propagation demo
output_match_mode: substring
expected_stdout_lines:
- 'WORKFLOW HISTORY PROPAGATION DEMO'
- '[MerchantCheckout] Starting checkout'
- '[ValidateMerchant] Validating merchant'
- '[ProcessPayment] Starting payment'
- 'events (scope: LINEAGE)'
- '[FraudDetection] APPROVED'
- 'scope=OWN_HISTORY'
- '[SettlePayment] SETTLED'
- '= COMPLETE ='
background: true
sleep: 30
timeout_seconds: 90
-->

```bash
dapr run --app-id payment-app --resources-path config -- ./payment-app
```

<!-- END_STEP -->

Note: build the binary and run it directly rather than `go run .` to ensure Ctrl+C
properly allows `dapr run` to exit.

You'll see lines like:

```
[FraudDetection] Received propagated history: 15 events (scope: LINEAGE)
... level=warning msg="propagating unsigned workflow history to ..."
```

The warnings are expected — they're telling you that without
`WorkflowHistorySigning` enabled, the chunks aren't signed.

### Option B: Kubernetes with mTLS + signing

This path adds Sentry-issued mTLS for sidecar-to-sidecar traffic and turns on
`WorkflowHistorySigning` so propagated chunks travel within a signed
trust boundary.

#### Prerequisites

- A running Kubernetes cluster with `kubectl` context set (eg `kind`).
- `docker` to build the example app image.
- `dapr` CLI on your PATH.

#### 1. Install Dapr into the cluster with mTLS on

Use the latest RC that includes workflow history propagation. mTLS is on by
default for Helm/`dapr init -k` installs.

```bash
dapr init -k --runtime-version=1.18.0 # 1.18.0+
```

Wait for the dapr-system pods to be Running:

```bash
kubectl get pods -n dapr-system
```

Expected: `dapr-sentry`, `dapr-placement-server-0`, `dapr-scheduler-server-0`,
`dapr-operator`, `dapr-sidecar-injector`.

#### 2. Build and load the example app image

From this example's directory:

```bash
cd examples/workflow-history-propagation

CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -o payment-app .

docker build -t payment-app:dev .
kind load docker-image payment-app:dev # kind clusters only; otherwise push to your registry
```

#### 3. Deploy the example

```bash
kubectl apply -f k8s/
```

This applies four manifests:

- `redis-deploy.yaml` — Redis Deployment + Service (`payment-app-redis`)
- `wf-store.yaml` — Dapr `state.redis` Component pointing at `payment-app-redis:6379`
- `signing-config.yaml` — Configuration CRD enabling `WorkflowHistorySigning`
- `app-deploy.yaml` — `payment-app` Deployment with Dapr sidecar annotations

#### 4. Watch the output

```bash
kubectl logs -l app=payment-app -c payment-app -f
```

Expected substrings (all should appear):

```
WORKFLOW HISTORY PROPAGATION DEMO
[MerchantCheckout] Starting checkout for merchant
[ValidateMerchant] Validating merchant
[ProcessPayment] Starting payment
[FraudDetection] Received propagated history: 15 events (scope: LINEAGE)
[FraudDetection] Verification: ValidateMerchant=true, ValidateCard=true, CheckSpendingLimits=true
[FraudDetection] APPROVED
[SettlePayment] Settling
propagated history: 12 events, scope=OWN_HISTORY
[SettlePayment] SETTLED
COMPLETE
```

In this mode the daprd sidecar should NOT log
`propagating unsigned workflow history` — the chunks are signed because
`WorkflowHistorySigning` is on.

### Troubleshooting

- **FraudDetection reports 0 events** — the workflow code didn't request
propagation. Confirm the parent calls `CallChildWorkflow` /
`CallActivity` with `workflow.WithHistoryPropagation(...)`.
- **`propagating unsigned workflow history` warnings in standalone** —
expected; switch to the Kubernetes path above (or enable Sentry +
`WorkflowHistorySigning`) if you want signed chunks.
- **K8s: Sentry connection errors** — check `kubectl get pods -n dapr-system`
and re-deploy the control plane.

### Cleanup

Tear down the example resources (`payment-app` Deployment, `payment-app-redis`
Deployment + Service, `wf-store` Component, `signing` Configuration):

```bash
kubectl delete -f k8s/
```

Confirm nothing is left over:

```bash
kubectl get deploy,svc,po,component,configuration -l app=payment-app
kubectl get deploy,svc,po -l app=payment-app-redis
```
Comment thread
cicoyle marked this conversation as resolved.

Both should return `No resources found`.

To uninstall the Dapr control plane (matching `dapr init -k`):

```bash
dapr uninstall -k --all # removes dapr-system namespace + CRDs
```

## Files

```
workflow-history-propagation/
├── README.md # this file
├── main.go # workflow + activity definitions
├── config/
│ └── redis.yaml # local self-hosted Dapr Component (dapr run)
├── Dockerfile # packages the pre-built payment-app binary
└── k8s/
├── signing-config.yaml # Configuration CRD enabling WorkflowHistorySigning
├── wf-store.yaml # Dapr Component (state.redis, actorStateStore)
├── redis-deploy.yaml # Redis Deployment + Service (payment-app-redis)
└── app-deploy.yaml # payment-app Deployment with dapr sidecar annotations
```
14 changes: 14 additions & 0 deletions examples/workflow-history-propagation/config/redis.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: wf-store
spec:
type: state.redis
version: v1
metadata:
- name: redisHost
value: localhost:6379
- name: redisPassword
value: ""
- name: actorStateStore
value: "true"
26 changes: 26 additions & 0 deletions examples/workflow-history-propagation/k8s/app-deploy.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: payment-app
namespace: default
Comment thread
cicoyle marked this conversation as resolved.
labels:
app: payment-app
spec:
replicas: 1
selector:
matchLabels:
app: payment-app
template:
metadata:
labels:
app: payment-app
annotations:
dapr.io/enabled: "true"
dapr.io/app-id: "payment-app"
dapr.io/config: "signing"
dapr.io/log-level: "info"
spec:
containers:
- name: payment-app
image: payment-app:dev
imagePullPolicy: IfNotPresent
36 changes: 36 additions & 0 deletions examples/workflow-history-propagation/k8s/redis-deploy.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: payment-app-redis
namespace: default
Comment thread
cicoyle marked this conversation as resolved.
labels:
app: payment-app-redis
spec:
replicas: 1
selector:
matchLabels:
app: payment-app-redis
template:
metadata:
labels:
app: payment-app-redis
spec:
containers:
- name: redis
image: redis:7-alpine
ports:
- containerPort: 6379
---
apiVersion: v1
kind: Service
metadata:
name: payment-app-redis
namespace: default
Comment thread
cicoyle marked this conversation as resolved.
labels:
app: payment-app-redis
spec:
selector:
app: payment-app-redis
ports:
- port: 6379
targetPort: 6379
11 changes: 11 additions & 0 deletions examples/workflow-history-propagation/k8s/signing-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
apiVersion: dapr.io/v1alpha1
kind: Configuration
metadata:
name: signing
namespace: default
Comment thread
cicoyle marked this conversation as resolved.
labels:
app: payment-app
spec:
features:
- name: WorkflowHistorySigning
enabled: true
Loading
Loading