Skip to content
Closed
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
6 changes: 5 additions & 1 deletion apps/worker/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
"start": "node dist/worker.js",
"build": "tsc -p tsconfig.json",
"check": "tsc -p tsconfig.json --noEmit",
"test": "vitest run"
"test": "vitest run",
"temporal:record-histories": "node scripts/record-workflow-histories.mjs"
},
"dependencies": {
"@space/codex-app-server": "0.2.5",
Expand All @@ -26,6 +27,9 @@
"undici": "8.12.3"
},
"devDependencies": {
"@temporalio/common": "1.24.0",
"@temporalio/proto": "1.24.0",
"@temporalio/testing": "1.24.0",
"typescript": "^5.9.3",
"vitest": "^4.1.12"
},
Expand Down
39 changes: 39 additions & 0 deletions apps/worker/scripts/record-workflow-histories.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import assert from "node:assert/strict";
import { constants } from "node:fs";
import { mkdtemp, readdir, mkdir, copyFile, rm, lstat } from "node:fs/promises";
import { spawnSync } from "node:child_process";
import { tmpdir } from "node:os";
import { join, resolve } from "node:path";
import { fileURLToPath } from "node:url";

const root = fileURLToPath(new URL("../", import.meta.url));
const args = process.argv.slice(2);
assert.ok(args.length === 0 || args.length === 2 && args[0] === "--output", "Usage: temporal:record-histories [--output new-directory]");
const destination = args.length ? resolve(args[1]) : join(root, "tests/fixtures/workflow-histories");
const expected = ["dotGoalWorkflow", "dummyTurnWorkflow", "codexAppServerTurnWorkflow", "roomAgentSupervisorWorkflow",
"roomAgentSupervisorWorkflow-legacy", "roomAgentActionWorkflow", "memoryMaintenanceWorkflow", "memoryConsolidationWorkflow", "memoryMutationWorkflow"].map(name => `${name}.json`).sort();
// Refuse before starting the server, including existing symlinks or directories.
for (const file of expected) {
const existing = await lstat(join(destination, file)).catch(error => {
if (error.code !== "ENOENT") throw error;
return null;
});
if (existing) throw new Error(`Refusing to overwrite workflow history: ${join(destination, file)}. Use --output with a new directory and preserve earlier baselines.`);
}
const output = await mkdtemp(join(tmpdir(), "spaceapp-workflow-histories-"));
try {
const cli = fileURLToPath(new URL("./vitest.mjs", import.meta.resolve("vitest/package.json")));
const result = spawnSync(process.execPath, [cli, "run", "tests/workflow-sandbox.test.ts"], {
cwd: root, stdio: "inherit", env: { ...process.env, SPACE_RECORD_WORKFLOW_HISTORIES: output }
});
if (result.error) throw result.error;
if (result.status !== 0) process.exitCode = result.status ?? 1;
else {
const files = (await readdir(output)).sort();
assert.deepEqual(files, expected);
await mkdir(destination, { recursive: true });
// Exclusive creation also protects against a file appearing after the preflight.
for (const file of files) await copyFile(join(output, file), join(destination, file), constants.COPYFILE_EXCL);
console.log(`Recorded ${files.length} synthetic histories after all sandbox assertions passed.`);
}
} finally { await rm(output, { recursive: true, force: true }); }
47 changes: 47 additions & 0 deletions apps/worker/tests/fixtures/workflow-histories/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Workflow history compatibility

These histories contain synthetic IDs, activity results and signals from the
real Temporal time-skipping server. No database, provider or Docker is used.
The SDK downloads its test server on first use; subsequent runs can reuse it.

After `npm run check` builds the internal package exports, `npm test -w
@space/worker` runs the sandbox cases and replays every required JSON against the
current workflow bundle without rebuilding those packages. The existing root
public test command also builds the exports before running these tests in CI.
A separate test adds one timer to `dotGoalWorkflow` in a test-only bundle and
requires `DeterminismViolationError`, proving the replay gate rejects drift.

## Recording

```sh
ELECTRON_SKIP_BINARY_DOWNLOAD=1 npm ci
npm run check
npm run temporal:record-histories -w @space/worker -- --output /tmp/new-workflow-histories
npm test -w @space/worker
```

The recorder first runs every sandbox assertion into a temporary directory.
It publishes the complete nine-file inventory only after the suite passes.
An existing destination file is an error: the recorder never overwrites a
baseline, including if a file appears while the suite is running. The default
destination contains the committed histories and therefore refuses re-recording.
Use a new output directory, review the results, and add any new compatibility
fixtures under distinct names while preserving supported earlier histories.
Comment thread
soundcake marked this conversation as resolved.
Resolved
Register each new fixture name (without `.json`) in `historyNames` in
`apps/worker/tests/support/workflow-sandbox.ts`. The replay test requires the
JSON inventory to match that list and replays every listed history.
The supervisor histories capture the first run, including all 250 polling
iterations and its continue-as-new event, rather than an empty successor.

The legacy recording bundle selects the existing pre-patch supervisor function
from production source, without changing the function body or mocking Temporal.
It emits no `room-background-continuation-v1` marker. Replaying that history
against the **unchanged current bundle** exercises Temporal's real `patched()`
compatibility branch. The test-only selector fails if the source shape changes,
so updating the legacy recording requires an explicit compatibility review.

After this initial fixture introduction, re-record histories **only together
with an intentional replay-safe `patched()` change**. Never re-record merely to
make a failing replay pass. Preserve histories for supported earlier branches;
new behavior should normally add a fixture rather than replace its predecessor.
Review synthetic payloads and the diff before committing generated histories.
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"events": [
{"eventId":"1","eventTime":{"seconds":"1793071191","nanos":942000000},"eventType":"EVENT_TYPE_WORKFLOW_EXECUTION_STARTED","workflowExecutionStartedEventAttributes":{"workflowType":{"name":"codexAppServerTurnWorkflow"},"taskQueue":{"name":"sandbox-current","kind":"TASK_QUEUE_KIND_NORMAL"},"input":{"payloads":[{"metadata":{"encoding":"anNvbi9wbGFpbg=="},"data":"eyJyb29tSWQiOiJyb29tOnByb2JlIiwicGFuZUlkIjoicGFuZTpwcm9iZSIsInByb21wdCI6IlN5bnRoZXRpYyByZXF1ZXN0IDIiLCJhcnRpZmFjdElkcyI6W10sImFnZW50UnVuSWQiOiJhZ2VudF9ydW46cHJvYmUtMiIsInBlcm1pc3Npb25Nb2RlIjpudWxsLCJjb2xsYWJvcmF0aW9uTW9kZSI6ImRlZmF1bHQiLCJ0cmFjZUlkIjoidHJhY2U6cHJvYmUtMiIsInByb3ZpZGVySWQiOiJjb2RleCJ9"}]},"workflowExecutionTimeout":{"seconds":"315360000"},"workflowRunTimeout":{"seconds":"315360000"},"workflowTaskTimeout":{"seconds":"10"},"originalExecutionRunId":"a26a51b0-74ff-4a0f-b6d1-caea929a1159","identity":"spaceapp-sandbox-tests","firstExecutionRunId":"a26a51b0-74ff-4a0f-b6d1-caea929a1159","attempt":1,"firstWorkflowTaskBackoff":{},"header":{}}},
{"eventId":"2","eventTime":{"seconds":"1793071191","nanos":942000000},"eventType":"EVENT_TYPE_WORKFLOW_TASK_SCHEDULED","workflowTaskScheduledEventAttributes":{"taskQueue":{"name":"sandbox-current","kind":"TASK_QUEUE_KIND_NORMAL"},"startToCloseTimeout":{"seconds":"10"},"attempt":1}},
{"eventId":"3","eventTime":{"seconds":"1793071191","nanos":942000000},"eventType":"EVENT_TYPE_WORKFLOW_TASK_STARTED","workflowTaskStartedEventAttributes":{"scheduledEventId":"2","identity":"spaceapp-sandbox-tests","historySizeBytes":"507"}},
{"eventId":"4","eventTime":{"seconds":"1793071192","nanos":133000000},"eventType":"EVENT_TYPE_WORKFLOW_TASK_COMPLETED","workflowTaskCompletedEventAttributes":{"scheduledEventId":"2","identity":"spaceapp-sandbox-tests","binaryChecksum":"@temporalio/worker@1.24.0+47f3d4da032a16b5c6d8954709774f91b65741827eeafa861c687ec30f526bc0","sdkMetadata":{"coreUsedFlags":[2,3,1],"sdkName":"temporal-typescript","sdkVersion":"1.24.0"},"meteringMetadata":{}}},
{"eventId":"5","eventTime":{"seconds":"1793071192","nanos":133000000},"eventType":"EVENT_TYPE_ACTIVITY_TASK_SCHEDULED","activityTaskScheduledEventAttributes":{"activityId":"1","activityType":{"name":"runCodexAppServerTurn"},"taskQueue":{"name":"sandbox-current","kind":"TASK_QUEUE_KIND_NORMAL"},"header":{},"input":{"payloads":[{"metadata":{"encoding":"anNvbi9wbGFpbg=="},"data":"eyJyb29tSWQiOiJyb29tOnByb2JlIiwicGFuZUlkIjoicGFuZTpwcm9iZSIsInByb21wdCI6IlN5bnRoZXRpYyByZXF1ZXN0IDIiLCJhcnRpZmFjdElkcyI6W10sImFnZW50UnVuSWQiOiJhZ2VudF9ydW46cHJvYmUtMiIsInBlcm1pc3Npb25Nb2RlIjpudWxsLCJjb2xsYWJvcmF0aW9uTW9kZSI6ImRlZmF1bHQiLCJ0cmFjZUlkIjoidHJhY2U6cHJvYmUtMiIsInByb3ZpZGVySWQiOiJjb2RleCJ9"}]},"scheduleToCloseTimeout":{"seconds":"315360000"},"scheduleToStartTimeout":{"seconds":"315360000"},"startToCloseTimeout":{"seconds":"86400"},"heartbeatTimeout":{"seconds":"15"},"workflowTaskCompletedEventId":"3","retryPolicy":{"initialInterval":{"seconds":"1"},"backoffCoefficient":2,"maximumInterval":{"seconds":"100"},"maximumAttempts":5}}},
{"eventId":"6","eventTime":{"seconds":"1793071192","nanos":133000000},"eventType":"EVENT_TYPE_ACTIVITY_TASK_STARTED","activityTaskStartedEventAttributes":{"scheduledEventId":"5","identity":"spaceapp-sandbox-tests","attempt":1}},
{"eventId":"7","eventTime":{"seconds":"1793071192","nanos":134000000},"eventType":"EVENT_TYPE_ACTIVITY_TASK_COMPLETED","activityTaskCompletedEventAttributes":{"result":{"payloads":[{"metadata":{"encoding":"anNvbi9wbGFpbg=="},"data":"eyJzdGF0dXMiOiJDT01QTEVURUQiLCJtZXNzYWdlIjoiU3ludGhldGljIHZlcmlmaWVkIHJlc3VsdCIsInJvb21BZ2VudE91dGNvbWUiOnsic3RhdHVzIjoiVkVSSUZJRUQiLCJleGVjdXRlZEFjdGlvbkNvdW50IjoxLCJzdGF0dXNSZWFzb24iOiJTeW50aGV0aWMgdmVyaWZpY2F0aW9uIGNvbXBsZXRlLiJ9fQ=="}]},"scheduledEventId":"5","startedEventId":"6","identity":"spaceapp-sandbox-tests"}},
{"eventId":"8","eventTime":{"seconds":"1793071192","nanos":134000000},"eventType":"EVENT_TYPE_WORKFLOW_TASK_SCHEDULED","workflowTaskScheduledEventAttributes":{"taskQueue":{"name":"sandbox-current","kind":"TASK_QUEUE_KIND_NORMAL"},"startToCloseTimeout":{"seconds":"10"},"attempt":1}},
{"eventId":"9","eventTime":{"seconds":"1793071192","nanos":134000000},"eventType":"EVENT_TYPE_WORKFLOW_TASK_STARTED","workflowTaskStartedEventAttributes":{"scheduledEventId":"8","identity":"spaceapp-sandbox-tests","historySizeBytes":"1461"}},
{"eventId":"10","eventTime":{"seconds":"1793071192","nanos":136000000},"eventType":"EVENT_TYPE_WORKFLOW_TASK_COMPLETED","workflowTaskCompletedEventAttributes":{"scheduledEventId":"8","identity":"spaceapp-sandbox-tests","binaryChecksum":"@temporalio/worker@1.24.0+47f3d4da032a16b5c6d8954709774f91b65741827eeafa861c687ec30f526bc0","sdkMetadata":{},"meteringMetadata":{}}},
{"eventId":"11","eventTime":{"seconds":"1793071192","nanos":136000000},"eventType":"EVENT_TYPE_WORKFLOW_EXECUTION_COMPLETED","workflowExecutionCompletedEventAttributes":{"result":{"payloads":[{"metadata":{"encoding":"anNvbi9wbGFpbg=="},"data":"eyJzdGF0dXMiOiJDT01QTEVURUQiLCJtZXNzYWdlIjoiU3ludGhldGljIHZlcmlmaWVkIHJlc3VsdCIsInJvb21BZ2VudE91dGNvbWUiOnsic3RhdHVzIjoiVkVSSUZJRUQiLCJleGVjdXRlZEFjdGlvbkNvdW50IjoxLCJzdGF0dXNSZWFzb24iOiJTeW50aGV0aWMgdmVyaWZpY2F0aW9uIGNvbXBsZXRlLiJ9fQ=="}]},"workflowTaskCompletedEventId":"9"}}
]
}
26 changes: 26 additions & 0 deletions apps/worker/tests/fixtures/workflow-histories/dotGoalWorkflow.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"events": [
{"eventId":"1","eventTime":{"seconds":"1793071186","nanos":762000000},"eventType":"EVENT_TYPE_WORKFLOW_EXECUTION_STARTED","workflowExecutionStartedEventAttributes":{"workflowType":{"name":"dotGoalWorkflow"},"taskQueue":{"name":"sandbox-current","kind":"TASK_QUEUE_KIND_NORMAL"},"input":{"payloads":[{"metadata":{"encoding":"anNvbi9wbGFpbg=="},"data":"eyJnb2FsSWQiOiJnb2FsOnRpbWVvdXQifQ=="}]},"workflowExecutionTimeout":{"seconds":"315360000"},"workflowRunTimeout":{"seconds":"315360000"},"workflowTaskTimeout":{"seconds":"10"},"originalExecutionRunId":"f01dec60-7708-49a5-a3a3-a607559b06f1","identity":"spaceapp-sandbox-tests","firstExecutionRunId":"f01dec60-7708-49a5-a3a3-a607559b06f1","attempt":1,"firstWorkflowTaskBackoff":{},"header":{}}},
{"eventId":"2","eventTime":{"seconds":"1793071186","nanos":762000000},"eventType":"EVENT_TYPE_WORKFLOW_TASK_SCHEDULED","workflowTaskScheduledEventAttributes":{"taskQueue":{"name":"sandbox-current","kind":"TASK_QUEUE_KIND_NORMAL"},"startToCloseTimeout":{"seconds":"10"},"attempt":1}},
{"eventId":"3","eventTime":{"seconds":"1793071186","nanos":764000000},"eventType":"EVENT_TYPE_WORKFLOW_TASK_STARTED","workflowTaskStartedEventAttributes":{"scheduledEventId":"2","identity":"spaceapp-sandbox-tests","historySizeBytes":"293"}},
{"eventId":"4","eventTime":{"seconds":"1793071186","nanos":988000000},"eventType":"EVENT_TYPE_WORKFLOW_TASK_COMPLETED","workflowTaskCompletedEventAttributes":{"scheduledEventId":"2","identity":"spaceapp-sandbox-tests","binaryChecksum":"@temporalio/worker@1.24.0+47f3d4da032a16b5c6d8954709774f91b65741827eeafa861c687ec30f526bc0","sdkMetadata":{"coreUsedFlags":[3,2,1],"sdkName":"temporal-typescript","sdkVersion":"1.24.0"},"meteringMetadata":{}}},
{"eventId":"5","eventTime":{"seconds":"1793071186","nanos":988000000},"eventType":"EVENT_TYPE_ACTIVITY_TASK_SCHEDULED","activityTaskScheduledEventAttributes":{"activityId":"1","activityType":{"name":"dotGoalStep"},"taskQueue":{"name":"sandbox-current","kind":"TASK_QUEUE_KIND_NORMAL"},"header":{},"input":{"payloads":[{"metadata":{"encoding":"anNvbi9wbGFpbg=="},"data":"ImdvYWw6dGltZW91dCI="}]},"scheduleToCloseTimeout":{"seconds":"315360000"},"scheduleToStartTimeout":{"seconds":"315360000"},"startToCloseTimeout":{"seconds":"120"},"heartbeatTimeout":{},"workflowTaskCompletedEventId":"3","retryPolicy":{"initialInterval":{"seconds":"5"},"backoffCoefficient":2,"maximumInterval":{"seconds":"120"},"maximumAttempts":20}}},
{"eventId":"6","eventTime":{"seconds":"1793071186","nanos":988000000},"eventType":"EVENT_TYPE_ACTIVITY_TASK_STARTED","activityTaskStartedEventAttributes":{"scheduledEventId":"5","identity":"spaceapp-sandbox-tests","attempt":1}},
{"eventId":"7","eventTime":{"seconds":"1793071186","nanos":994000000},"eventType":"EVENT_TYPE_ACTIVITY_TASK_COMPLETED","activityTaskCompletedEventAttributes":{"result":{"payloads":[{"metadata":{"encoding":"anNvbi9wbGFpbg=="},"data":"eyJzdGF0ZSI6IldBSVRJTkciLCJydW5JZCI6bnVsbCwiZ29hbFN0YXR1cyI6IkFDVElWRSJ9"}]},"scheduledEventId":"5","startedEventId":"6","identity":"spaceapp-sandbox-tests"}},
{"eventId":"8","eventTime":{"seconds":"1793071186","nanos":994000000},"eventType":"EVENT_TYPE_WORKFLOW_TASK_SCHEDULED","workflowTaskScheduledEventAttributes":{"taskQueue":{"name":"sandbox-current","kind":"TASK_QUEUE_KIND_NORMAL"},"startToCloseTimeout":{"seconds":"10"},"attempt":1}},
{"eventId":"9","eventTime":{"seconds":"1793071186","nanos":994000000},"eventType":"EVENT_TYPE_WORKFLOW_TASK_STARTED","workflowTaskStartedEventAttributes":{"scheduledEventId":"8","identity":"spaceapp-sandbox-tests","historySizeBytes":"899"}},
{"eventId":"10","eventTime":{"seconds":"1793071186","nanos":999000000},"eventType":"EVENT_TYPE_WORKFLOW_TASK_COMPLETED","workflowTaskCompletedEventAttributes":{"scheduledEventId":"8","identity":"spaceapp-sandbox-tests","binaryChecksum":"@temporalio/worker@1.24.0+47f3d4da032a16b5c6d8954709774f91b65741827eeafa861c687ec30f526bc0","sdkMetadata":{},"meteringMetadata":{}}},
{"eventId":"11","eventTime":{"seconds":"1793071186","nanos":999000000},"eventType":"EVENT_TYPE_TIMER_STARTED","timerStartedEventAttributes":{"timerId":"1","startToFireTimeout":{"seconds":"30"},"workflowTaskCompletedEventId":"9"}},
{"eventId":"12","eventTime":{"seconds":"1793071216","nanos":999000000},"eventType":"EVENT_TYPE_TIMER_FIRED","timerFiredEventAttributes":{"timerId":"1","startedEventId":"11"}},
{"eventId":"13","eventTime":{"seconds":"1793071216","nanos":999000000},"eventType":"EVENT_TYPE_WORKFLOW_TASK_SCHEDULED","workflowTaskScheduledEventAttributes":{"taskQueue":{"name":"sandbox-current","kind":"TASK_QUEUE_KIND_NORMAL"},"startToCloseTimeout":{"seconds":"10"},"attempt":1}},
{"eventId":"14","eventTime":{"seconds":"1793071216","nanos":999000000},"eventType":"EVENT_TYPE_WORKFLOW_TASK_STARTED","workflowTaskStartedEventAttributes":{"scheduledEventId":"13","identity":"spaceapp-sandbox-tests","historySizeBytes":"1193"}},
{"eventId":"15","eventTime":{"seconds":"1793071217","nanos":1000000},"eventType":"EVENT_TYPE_WORKFLOW_TASK_COMPLETED","workflowTaskCompletedEventAttributes":{"scheduledEventId":"13","identity":"spaceapp-sandbox-tests","binaryChecksum":"@temporalio/worker@1.24.0+47f3d4da032a16b5c6d8954709774f91b65741827eeafa861c687ec30f526bc0","sdkMetadata":{},"meteringMetadata":{}}},
{"eventId":"16","eventTime":{"seconds":"1793071217","nanos":1000000},"eventType":"EVENT_TYPE_ACTIVITY_TASK_SCHEDULED","activityTaskScheduledEventAttributes":{"activityId":"2","activityType":{"name":"dotGoalStep"},"taskQueue":{"name":"sandbox-current","kind":"TASK_QUEUE_KIND_NORMAL"},"header":{},"input":{"payloads":[{"metadata":{"encoding":"anNvbi9wbGFpbg=="},"data":"ImdvYWw6dGltZW91dCI="}]},"scheduleToCloseTimeout":{"seconds":"315360000"},"scheduleToStartTimeout":{"seconds":"315360000"},"startToCloseTimeout":{"seconds":"120"},"heartbeatTimeout":{},"workflowTaskCompletedEventId":"14","retryPolicy":{"initialInterval":{"seconds":"5"},"backoffCoefficient":2,"maximumInterval":{"seconds":"120"},"maximumAttempts":20}}},
{"eventId":"17","eventTime":{"seconds":"1793071217","nanos":1000000},"eventType":"EVENT_TYPE_ACTIVITY_TASK_STARTED","activityTaskStartedEventAttributes":{"scheduledEventId":"16","identity":"spaceapp-sandbox-tests","attempt":1}},
{"eventId":"18","eventTime":{"seconds":"1793071217","nanos":1000000},"eventType":"EVENT_TYPE_ACTIVITY_TASK_COMPLETED","activityTaskCompletedEventAttributes":{"result":{"payloads":[{"metadata":{"encoding":"anNvbi9wbGFpbg=="},"data":"eyJzdGF0ZSI6IkRPTkUiLCJydW5JZCI6bnVsbCwiZ29hbFN0YXR1cyI6IkNPTVBMRVRFRCJ9"}]},"scheduledEventId":"16","startedEventId":"17","identity":"spaceapp-sandbox-tests"}},
{"eventId":"19","eventTime":{"seconds":"1793071217","nanos":1000000},"eventType":"EVENT_TYPE_WORKFLOW_TASK_SCHEDULED","workflowTaskScheduledEventAttributes":{"taskQueue":{"name":"sandbox-current","kind":"TASK_QUEUE_KIND_NORMAL"},"startToCloseTimeout":{"seconds":"10"},"attempt":1}},
{"eventId":"20","eventTime":{"seconds":"1793071217","nanos":2000000},"eventType":"EVENT_TYPE_WORKFLOW_TASK_STARTED","workflowTaskStartedEventAttributes":{"scheduledEventId":"19","identity":"spaceapp-sandbox-tests","historySizeBytes":"1754"}},
{"eventId":"21","eventTime":{"seconds":"1793071217","nanos":4000000},"eventType":"EVENT_TYPE_WORKFLOW_TASK_COMPLETED","workflowTaskCompletedEventAttributes":{"scheduledEventId":"19","identity":"spaceapp-sandbox-tests","binaryChecksum":"@temporalio/worker@1.24.0+47f3d4da032a16b5c6d8954709774f91b65741827eeafa861c687ec30f526bc0","sdkMetadata":{},"meteringMetadata":{}}},
{"eventId":"22","eventTime":{"seconds":"1793071217","nanos":4000000},"eventType":"EVENT_TYPE_WORKFLOW_EXECUTION_COMPLETED","workflowExecutionCompletedEventAttributes":{"result":{"payloads":[{"metadata":{"encoding":"anNvbi9wbGFpbg=="},"data":"eyJnb2FsU3RhdHVzIjoiQ09NUExFVEVEIn0="}]},"workflowTaskCompletedEventId":"20"}}
]
}
Loading