From adfda93f002fff6dc872cf8f2cc1cc0819e5c71d Mon Sep 17 00:00:00 2001 From: TassioSales Date: Wed, 28 Oct 2026 12:06:44 -0300 Subject: [PATCH] test(worker): run every Temporal workflow in a real time-skipping sandbox and gate replay determinism (#60) - Add @temporalio/testing devDependency pinned to match @temporalio/* packages. - Implement comprehensive sandbox test suites running all 8 Temporal workflows in TestWorkflowEnvironment.createTimeSkipping() with stubbed activities. - Cover wake signal vs timeout and continue-as-new loops in dotGoalWorkflow (asserting timer was bypassed early). - Cover enqueue/stop signals, 1s condition polling, mission interruption, and continue-as-new bounds (50 items / 250 polls) in roomAgentSupervisorWorkflow and legacy pre-patch branch, validating runId divergence. - Stub runCodexAppServerTurn child activity to allow codexAppServerTurnWorkflow to complete cleanly. - Implement workflow history recording script producing closed JSON histories in tests/fixtures/workflow-histories/. - Implement replay determinism gate in workflow-replay.test.ts using Worker.runReplayHistories against real workflow modules. - Include negative replay test against replay-violation-workflows.ts to prove DeterminismViolationError detection. - Document history re-recording policy and patched() constraints in workflow-histories/README.md. Signed-off-by: TassioSales --- apps/worker/package.json | 4 +- apps/worker/src/workflows.ts | 7 +- .../fixtures/replay-violation-workflows.ts | 23 + .../fixtures/workflow-histories/README.md | 50 ++ .../codexAppServerTurnWorkflow.json | 253 ++++++ .../workflow-histories/dotGoalWorkflow.json | 251 ++++++ .../workflow-histories/dummyTurnWorkflow.json | 383 ++++++++ .../legacyRoomAgentSupervisorWorkflow.json | 374 ++++++++ .../memoryConsolidationWorkflow.json | 251 ++++++ .../memoryMaintenanceWorkflow.json | 816 ++++++++++++++++++ .../memoryMutationWorkflow.json | 251 ++++++ .../roomAgentActionWorkflow.json | 253 ++++++ .../roomAgentSupervisorWorkflow.json | 476 ++++++++++ .../tests/room-supervisor-sandbox.test.ts | 306 +++++++ apps/worker/tests/workflow-histories.mts | 345 ++++++++ apps/worker/tests/workflow-replay.test.ts | 93 ++ .../tests/workflow-sandbox-extra.test.ts | 280 ++++++ apps/worker/tests/workflow-sandbox.test.ts | 325 +++++++ package-lock.json | 20 + 19 files changed, 4759 insertions(+), 2 deletions(-) create mode 100644 apps/worker/tests/fixtures/replay-violation-workflows.ts create mode 100644 apps/worker/tests/fixtures/workflow-histories/README.md create mode 100644 apps/worker/tests/fixtures/workflow-histories/codexAppServerTurnWorkflow.json create mode 100644 apps/worker/tests/fixtures/workflow-histories/dotGoalWorkflow.json create mode 100644 apps/worker/tests/fixtures/workflow-histories/dummyTurnWorkflow.json create mode 100644 apps/worker/tests/fixtures/workflow-histories/legacyRoomAgentSupervisorWorkflow.json create mode 100644 apps/worker/tests/fixtures/workflow-histories/memoryConsolidationWorkflow.json create mode 100644 apps/worker/tests/fixtures/workflow-histories/memoryMaintenanceWorkflow.json create mode 100644 apps/worker/tests/fixtures/workflow-histories/memoryMutationWorkflow.json create mode 100644 apps/worker/tests/fixtures/workflow-histories/roomAgentActionWorkflow.json create mode 100644 apps/worker/tests/fixtures/workflow-histories/roomAgentSupervisorWorkflow.json create mode 100644 apps/worker/tests/room-supervisor-sandbox.test.ts create mode 100644 apps/worker/tests/workflow-histories.mts create mode 100644 apps/worker/tests/workflow-replay.test.ts create mode 100644 apps/worker/tests/workflow-sandbox-extra.test.ts create mode 100644 apps/worker/tests/workflow-sandbox.test.ts diff --git a/apps/worker/package.json b/apps/worker/package.json index d7a484ac8..36b52b4ba 100644 --- a/apps/worker/package.json +++ b/apps/worker/package.json @@ -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 --experimental-strip-types tests/workflow-histories.mts --record" }, "dependencies": { "@space/codex-app-server": "0.2.5", @@ -26,6 +27,7 @@ "undici": "8.10.2" }, "devDependencies": { + "@temporalio/testing": "1.24.0", "typescript": "^5.9.3", "vitest": "^4.1.12" }, diff --git a/apps/worker/src/workflows.ts b/apps/worker/src/workflows.ts index 5f3a7d4cd..50401975a 100644 --- a/apps/worker/src/workflows.ts +++ b/apps/worker/src/workflows.ts @@ -140,7 +140,12 @@ export async function roomAgentSupervisorWorkflow(rawInput: RoomAgentSupervisorI return continuingRoomAgentSupervisorWorkflow(rawInput); } -async function legacyRoomAgentSupervisorWorkflow(rawInput: RoomAgentSupervisorInput): Promise { +/** + * The pre-`room-background-continuation-v1` branch. Exported so the real + * workflow sandbox can exercise it directly: a fresh execution always takes the + * patched branch, so only a historical replay used to reach this code. + */ +export async function legacyRoomAgentSupervisorWorkflow(rawInput: RoomAgentSupervisorInput): Promise { const input = roomAgentSupervisorInputSchema.parse(rawInput); let pending = [...input.pending]; let interrupted: Array<{ item: RoomAgentSupervisorQueueItem; reason: string }> = []; diff --git a/apps/worker/tests/fixtures/replay-violation-workflows.ts b/apps/worker/tests/fixtures/replay-violation-workflows.ts new file mode 100644 index 000000000..3bfc039d3 --- /dev/null +++ b/apps/worker/tests/fixtures/replay-violation-workflows.ts @@ -0,0 +1,23 @@ +/** + * Test-only workflow module for the replay gate's negative case. + * + * Same exported name as the recorded history, but the workflow now schedules an + * extra activity before the recorded one. Replaying a history with no such + * command must fail with a determinism violation, which proves the gate is + * wired to real replay. + */ +import { proxyActivities } from "@temporalio/workflow"; + +const { recordDummyTurnStarted, recordDummyTurnCompleted, extraUnrecordedActivity } = + proxyActivities<{ + recordDummyTurnStarted(input: unknown): Promise<{ workflowId: string }>; + recordDummyTurnCompleted(input: unknown): Promise; + extraUnrecordedActivity(input: unknown): Promise; + }>({ startToCloseTimeout: "1 minute", retry: { maximumAttempts: 3 } }); + +export async function dummyTurnWorkflow(input: unknown): Promise { + // The command the recorded history does not contain. + await extraUnrecordedActivity(input); + await recordDummyTurnStarted(input); + return recordDummyTurnCompleted(input); +} diff --git a/apps/worker/tests/fixtures/workflow-histories/README.md b/apps/worker/tests/fixtures/workflow-histories/README.md new file mode 100644 index 000000000..7d6963186 --- /dev/null +++ b/apps/worker/tests/fixtures/workflow-histories/README.md @@ -0,0 +1,50 @@ +# How to re-record workflow histories + +The replay gate in `workflow-replay.test.ts` compares the workflow code in +`apps/worker/src` against the histories in `fixtures/workflow-histories/`. +Every workflow that ships must have at least one recorded history, and every +recorded history must replay without a determinism violation. + +## Record + +```sh +npm run temporal:record-histories -w @space/worker +``` + +This starts a time-skipping Temporal test environment, runs one instance of +each workflow with stub activities, and writes the closed run's events to +`tests/fixtures/workflow-histories/.json`. The history is read +only after `result()` resolves: an earlier read returns the first events of a +still-open run, which replays trivially and would hide a determinism break. + +## When re-recording is allowed + +Only together with a `patched()` change, in the same commit. Never re-record to +make a failing replay pass: a replay failure means a running execution would +break on the new code, and replacing the history erases exactly the evidence +that says so. + +## Adding a workflow + +Add it to `recordHistories()` in `workflow-histories.mts` with stub activities, +record, and confirm `workflow-replay.test.ts` replays the new file. If the +workflow lives in its own module (as `memoryMaintenanceWorkflow` and +`memoryMutationWorkflow` do), add the module to `bundleFor()` in +`workflow-replay.test.ts` so the history is replayed against the bundle that +worker actually ships. + +A workflow that never returns (the room supervisor parks in `condition()`) is +closed by cancelling it before recording. `record()` then tolerates the +cancellation rejection: the run is closed, which is all replay needs. + +Both supervisor branches are recorded. `legacyRoomAgentSupervisorWorkflow` is the +pre-`room-background-continuation-v1` branch: a fresh execution always takes the +patched branch, so only a history can pin it. + +## Proving the gate still bites + +`fixtures/replay-violation-workflows.ts` is a test-only variant that schedules an +extra activity before the recorded one. `workflow-replay.test.ts` replays the +recorded `dummyTurnWorkflow` history against it and expects a +`DeterminismViolationError`. If that test ever passes without an error, the gate +is no longer wired to real replay. diff --git a/apps/worker/tests/fixtures/workflow-histories/codexAppServerTurnWorkflow.json b/apps/worker/tests/fixtures/workflow-histories/codexAppServerTurnWorkflow.json new file mode 100644 index 000000000..1069bc4f9 --- /dev/null +++ b/apps/worker/tests/fixtures/workflow-histories/codexAppServerTurnWorkflow.json @@ -0,0 +1,253 @@ +[ + { + "eventId": "1", + "eventTime": { + "seconds": "3053954524", + "nanos": 360000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_EXECUTION_STARTED", + "workflowExecutionStartedEventAttributes": { + "workflowType": { + "name": "codexAppServerTurnWorkflow" + }, + "taskQueue": { + "name": "space-turns", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJyb29tSWQiOiJyb29tOnJlY29yZCIsInBhbmVJZCI6InBhbmU6cmVjb3JkIiwicHJvbXB0IjoicmVjb3JkIGEgaGlzdG9yeSIsInJvb21BZ2VudE9wZXJhdG9yUmVxdWVzdCI6InJlY29yZCBhIGhpc3RvcnkiLCJtb2RlbElkIjoiZ3B0LTUuNi1zb2wiLCJyZWFzb25pbmdFZmZvcnQiOiJoaWdoIiwiYWdlbnRTZXNzaW9uSWQiOiJzZXNzaW9uOnJlY29yZCIsImFnZW50UnVuSWQiOiJhZ2VudF9ydW46cmVjb3JkIiwiYWdlbnRVc2VyTWVzc2FnZUlkIjoiYWdlbnRfbXNnOnVzZXItcmVjb3JkIiwiYWdlbnRBc3Npc3RhbnRNZXNzYWdlSWQiOiJhZ2VudF9tc2c6YXNzaXN0YW50LXJlY29yZCIsInRyYWNlSWQiOiJ0cmFjZTpyZWNvcmQifQ==" + } + ] + }, + "workflowExecutionTimeout": { + "seconds": "315360000" + }, + "workflowRunTimeout": { + "seconds": "315360000" + }, + "workflowTaskTimeout": { + "seconds": "10" + }, + "originalExecutionRunId": "aa0f9e59-308e-4608-adab-bbea24598787", + "identity": "32544@DESKTOP-QG1LIN3", + "firstExecutionRunId": "aa0f9e59-308e-4608-adab-bbea24598787", + "attempt": 1, + "firstWorkflowTaskBackoff": {}, + "header": {} + } + }, + { + "eventId": "2", + "eventTime": { + "seconds": "3053954524", + "nanos": 360000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_SCHEDULED", + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "space-turns", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "startToCloseTimeout": { + "seconds": "10" + }, + "attempt": 1 + } + }, + { + "eventId": "3", + "eventTime": { + "seconds": "3053954524", + "nanos": 360000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "2", + "identity": "32544@DESKTOP-QG1LIN3", + "historySizeBytes": "634" + } + }, + { + "eventId": "4", + "eventTime": { + "seconds": "3053954525", + "nanos": 305000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "2", + "identity": "32544@DESKTOP-QG1LIN3", + "binaryChecksum": "@temporalio/worker@1.24.0+86d854fc950701a33c6f41ef73ce75a518c298fa916171764a1b9e18166f7477", + "sdkMetadata": { + "coreUsedFlags": [ + 1, + 2, + 3 + ], + "sdkName": "temporal-typescript", + "sdkVersion": "1.24.0" + }, + "meteringMetadata": {} + } + }, + { + "eventId": "5", + "eventTime": { + "seconds": "3053954525", + "nanos": 305000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_SCHEDULED", + "activityTaskScheduledEventAttributes": { + "activityId": "1", + "activityType": { + "name": "runCodexAppServerTurn" + }, + "taskQueue": { + "name": "space-turns", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "header": {}, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJyb29tSWQiOiJyb29tOnJlY29yZCIsInBhbmVJZCI6InBhbmU6cmVjb3JkIiwicHJvbXB0IjoicmVjb3JkIGEgaGlzdG9yeSIsInJvb21BZ2VudE9wZXJhdG9yUmVxdWVzdCI6InJlY29yZCBhIGhpc3RvcnkiLCJtb2RlbElkIjoiZ3B0LTUuNi1zb2wiLCJyZWFzb25pbmdFZmZvcnQiOiJoaWdoIiwiYWdlbnRTZXNzaW9uSWQiOiJzZXNzaW9uOnJlY29yZCIsImFnZW50UnVuSWQiOiJhZ2VudF9ydW46cmVjb3JkIiwiYWdlbnRVc2VyTWVzc2FnZUlkIjoiYWdlbnRfbXNnOnVzZXItcmVjb3JkIiwiYWdlbnRBc3Npc3RhbnRNZXNzYWdlSWQiOiJhZ2VudF9tc2c6YXNzaXN0YW50LXJlY29yZCIsInRyYWNlSWQiOiJ0cmFjZTpyZWNvcmQifQ==" + } + ] + }, + "scheduleToCloseTimeout": { + "seconds": "315360000" + }, + "scheduleToStartTimeout": { + "seconds": "315360000" + }, + "startToCloseTimeout": { + "seconds": "315360000" + }, + "heartbeatTimeout": { + "seconds": "15" + }, + "workflowTaskCompletedEventId": "3", + "retryPolicy": { + "initialInterval": { + "seconds": "1" + }, + "backoffCoefficient": 2, + "maximumInterval": { + "seconds": "100" + }, + "maximumAttempts": 5 + } + } + }, + { + "eventId": "6", + "eventTime": { + "seconds": "3053954525", + "nanos": 306000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_STARTED", + "activityTaskStartedEventAttributes": { + "scheduledEventId": "5", + "identity": "32544@DESKTOP-QG1LIN3", + "attempt": 1 + } + }, + { + "eventId": "7", + "eventTime": { + "seconds": "3053954525", + "nanos": 310000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_COMPLETED", + "activityTaskCompletedEventAttributes": { + "result": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJ3b3JrZmxvd0lkIjoid2Y6cmVjb3JkOmNvZGV4LXR1cm4iLCJyb29tSWQiOiJyb29tOnJlY29yZCIsInBhbmVJZCI6InBhbmU6cmVjb3JkIiwidHJhY2VJZCI6InRyYWNlOnJlY29yZCJ9" + } + ] + }, + "scheduledEventId": "5", + "startedEventId": "6", + "identity": "32544@DESKTOP-QG1LIN3" + } + }, + { + "eventId": "8", + "eventTime": { + "seconds": "3053954525", + "nanos": 310000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_SCHEDULED", + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "space-turns", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "startToCloseTimeout": { + "seconds": "10" + }, + "attempt": 1 + } + }, + { + "eventId": "9", + "eventTime": { + "seconds": "3053954525", + "nanos": 310000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "8", + "identity": "32544@DESKTOP-QG1LIN3", + "historySizeBytes": "1651" + } + }, + { + "eventId": "10", + "eventTime": { + "seconds": "3053954525", + "nanos": 317000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "8", + "identity": "32544@DESKTOP-QG1LIN3", + "binaryChecksum": "@temporalio/worker@1.24.0+86d854fc950701a33c6f41ef73ce75a518c298fa916171764a1b9e18166f7477", + "sdkMetadata": {}, + "meteringMetadata": {} + } + }, + { + "eventId": "11", + "eventTime": { + "seconds": "3053954525", + "nanos": 317000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_EXECUTION_COMPLETED", + "workflowExecutionCompletedEventAttributes": { + "result": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJ3b3JrZmxvd0lkIjoid2Y6cmVjb3JkOmNvZGV4LXR1cm4iLCJyb29tSWQiOiJyb29tOnJlY29yZCIsInBhbmVJZCI6InBhbmU6cmVjb3JkIiwidHJhY2VJZCI6InRyYWNlOnJlY29yZCJ9" + } + ] + }, + "workflowTaskCompletedEventId": "9" + } + } +] \ No newline at end of file diff --git a/apps/worker/tests/fixtures/workflow-histories/dotGoalWorkflow.json b/apps/worker/tests/fixtures/workflow-histories/dotGoalWorkflow.json new file mode 100644 index 000000000..f66b14315 --- /dev/null +++ b/apps/worker/tests/fixtures/workflow-histories/dotGoalWorkflow.json @@ -0,0 +1,251 @@ +[ + { + "eventId": "1", + "eventTime": { + "seconds": "1792514514", + "nanos": 291000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_EXECUTION_STARTED", + "workflowExecutionStartedEventAttributes": { + "workflowType": { + "name": "dotGoalWorkflow" + }, + "taskQueue": { + "name": "space-dot-goals", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJnb2FsSWQiOiJnb2FsOnJlY29yZCJ9" + } + ] + }, + "workflowExecutionTimeout": { + "seconds": "315360000" + }, + "workflowRunTimeout": { + "seconds": "315360000" + }, + "workflowTaskTimeout": { + "seconds": "10" + }, + "originalExecutionRunId": "ae550edd-1597-4596-a748-3875213600f8", + "identity": "32544@DESKTOP-QG1LIN3", + "firstExecutionRunId": "ae550edd-1597-4596-a748-3875213600f8", + "attempt": 1, + "firstWorkflowTaskBackoff": {}, + "header": {} + } + }, + { + "eventId": "2", + "eventTime": { + "seconds": "1792514514", + "nanos": 291000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_SCHEDULED", + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "space-dot-goals", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "startToCloseTimeout": { + "seconds": "10" + }, + "attempt": 1 + } + }, + { + "eventId": "3", + "eventTime": { + "seconds": "1792514514", + "nanos": 291000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "2", + "identity": "32544@DESKTOP-QG1LIN3", + "historySizeBytes": "291" + } + }, + { + "eventId": "4", + "eventTime": { + "seconds": "1792514515", + "nanos": 299000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "2", + "identity": "32544@DESKTOP-QG1LIN3", + "binaryChecksum": "@temporalio/worker@1.24.0+86d854fc950701a33c6f41ef73ce75a518c298fa916171764a1b9e18166f7477", + "sdkMetadata": { + "coreUsedFlags": [ + 2, + 3, + 1 + ], + "sdkName": "temporal-typescript", + "sdkVersion": "1.24.0" + }, + "meteringMetadata": {} + } + }, + { + "eventId": "5", + "eventTime": { + "seconds": "1792514515", + "nanos": 299000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_SCHEDULED", + "activityTaskScheduledEventAttributes": { + "activityId": "1", + "activityType": { + "name": "dotGoalStep" + }, + "taskQueue": { + "name": "space-dot-goals", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "header": {}, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "ImdvYWw6cmVjb3JkIg==" + } + ] + }, + "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": "1792514515", + "nanos": 299000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_STARTED", + "activityTaskStartedEventAttributes": { + "scheduledEventId": "5", + "identity": "32544@DESKTOP-QG1LIN3", + "attempt": 1 + } + }, + { + "eventId": "7", + "eventTime": { + "seconds": "1792514515", + "nanos": 318000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_COMPLETED", + "activityTaskCompletedEventAttributes": { + "result": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJzdGF0ZSI6IkRPTkUiLCJydW5JZCI6bnVsbCwiZ29hbFN0YXR1cyI6IkNPTVBMRVRFRCJ9" + } + ] + }, + "scheduledEventId": "5", + "startedEventId": "6", + "identity": "32544@DESKTOP-QG1LIN3" + } + }, + { + "eventId": "8", + "eventTime": { + "seconds": "1792514515", + "nanos": 318000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_SCHEDULED", + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "space-dot-goals", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "startToCloseTimeout": { + "seconds": "10" + }, + "attempt": 1 + } + }, + { + "eventId": "9", + "eventTime": { + "seconds": "1792514515", + "nanos": 318000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "8", + "identity": "32544@DESKTOP-QG1LIN3", + "historySizeBytes": "892" + } + }, + { + "eventId": "10", + "eventTime": { + "seconds": "1792514515", + "nanos": 327000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "8", + "identity": "32544@DESKTOP-QG1LIN3", + "binaryChecksum": "@temporalio/worker@1.24.0+86d854fc950701a33c6f41ef73ce75a518c298fa916171764a1b9e18166f7477", + "sdkMetadata": {}, + "meteringMetadata": {} + } + }, + { + "eventId": "11", + "eventTime": { + "seconds": "1792514515", + "nanos": 327000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_EXECUTION_COMPLETED", + "workflowExecutionCompletedEventAttributes": { + "result": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJnb2FsU3RhdHVzIjoiQ09NUExFVEVEIn0=" + } + ] + }, + "workflowTaskCompletedEventId": "9" + } + } +] \ No newline at end of file diff --git a/apps/worker/tests/fixtures/workflow-histories/dummyTurnWorkflow.json b/apps/worker/tests/fixtures/workflow-histories/dummyTurnWorkflow.json new file mode 100644 index 000000000..48cbe9376 --- /dev/null +++ b/apps/worker/tests/fixtures/workflow-histories/dummyTurnWorkflow.json @@ -0,0 +1,383 @@ +[ + { + "eventId": "1", + "eventTime": { + "seconds": "2107874517", + "nanos": 166000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_EXECUTION_STARTED", + "workflowExecutionStartedEventAttributes": { + "workflowType": { + "name": "dummyTurnWorkflow" + }, + "taskQueue": { + "name": "space-turns", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJyb29tSWQiOiJyb29tOnJlY29yZCIsInBhbmVJZCI6InBhbmU6cmVjb3JkIiwicHJvbXB0IjoicmVjb3JkIGEgaGlzdG9yeSIsInJvb21BZ2VudE9wZXJhdG9yUmVxdWVzdCI6InJlY29yZCBhIGhpc3RvcnkiLCJtb2RlbElkIjoiZ3B0LTUuNi1zb2wiLCJyZWFzb25pbmdFZmZvcnQiOiJoaWdoIiwiYWdlbnRTZXNzaW9uSWQiOiJzZXNzaW9uOnJlY29yZCIsImFnZW50UnVuSWQiOiJhZ2VudF9ydW46cmVjb3JkIiwiYWdlbnRVc2VyTWVzc2FnZUlkIjoiYWdlbnRfbXNnOnVzZXItcmVjb3JkIiwiYWdlbnRBc3Npc3RhbnRNZXNzYWdlSWQiOiJhZ2VudF9tc2c6YXNzaXN0YW50LXJlY29yZCIsInRyYWNlSWQiOiJ0cmFjZTpyZWNvcmQifQ==" + } + ] + }, + "workflowExecutionTimeout": { + "seconds": "315360000" + }, + "workflowRunTimeout": { + "seconds": "315360000" + }, + "workflowTaskTimeout": { + "seconds": "10" + }, + "originalExecutionRunId": "dbcd4451-b5dd-477f-a0b0-3e4f391a6a17", + "identity": "32544@DESKTOP-QG1LIN3", + "firstExecutionRunId": "dbcd4451-b5dd-477f-a0b0-3e4f391a6a17", + "attempt": 1, + "firstWorkflowTaskBackoff": {}, + "header": {} + } + }, + { + "eventId": "2", + "eventTime": { + "seconds": "2107874517", + "nanos": 166000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_SCHEDULED", + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "space-turns", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "startToCloseTimeout": { + "seconds": "10" + }, + "attempt": 1 + } + }, + { + "eventId": "3", + "eventTime": { + "seconds": "2107874517", + "nanos": 166000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "2", + "identity": "32544@DESKTOP-QG1LIN3", + "historySizeBytes": "623" + } + }, + { + "eventId": "4", + "eventTime": { + "seconds": "2107874518", + "nanos": 153000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "2", + "identity": "32544@DESKTOP-QG1LIN3", + "binaryChecksum": "@temporalio/worker@1.24.0+86d854fc950701a33c6f41ef73ce75a518c298fa916171764a1b9e18166f7477", + "sdkMetadata": { + "coreUsedFlags": [ + 1, + 2, + 3 + ], + "sdkName": "temporal-typescript", + "sdkVersion": "1.24.0" + }, + "meteringMetadata": {} + } + }, + { + "eventId": "5", + "eventTime": { + "seconds": "2107874518", + "nanos": 153000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_SCHEDULED", + "activityTaskScheduledEventAttributes": { + "activityId": "1", + "activityType": { + "name": "recordDummyTurnStarted" + }, + "taskQueue": { + "name": "space-turns", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "header": {}, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJyb29tSWQiOiJyb29tOnJlY29yZCIsInBhbmVJZCI6InBhbmU6cmVjb3JkIiwicHJvbXB0IjoicmVjb3JkIGEgaGlzdG9yeSIsInJvb21BZ2VudE9wZXJhdG9yUmVxdWVzdCI6InJlY29yZCBhIGhpc3RvcnkiLCJtb2RlbElkIjoiZ3B0LTUuNi1zb2wiLCJyZWFzb25pbmdFZmZvcnQiOiJoaWdoIiwiYWdlbnRTZXNzaW9uSWQiOiJzZXNzaW9uOnJlY29yZCIsImFnZW50UnVuSWQiOiJhZ2VudF9ydW46cmVjb3JkIiwiYWdlbnRVc2VyTWVzc2FnZUlkIjoiYWdlbnRfbXNnOnVzZXItcmVjb3JkIiwiYWdlbnRBc3Npc3RhbnRNZXNzYWdlSWQiOiJhZ2VudF9tc2c6YXNzaXN0YW50LXJlY29yZCIsInRyYWNlSWQiOiJ0cmFjZTpyZWNvcmQifQ==" + } + ] + }, + "scheduleToCloseTimeout": { + "seconds": "315360000" + }, + "scheduleToStartTimeout": { + "seconds": "315360000" + }, + "startToCloseTimeout": { + "seconds": "60" + }, + "heartbeatTimeout": {}, + "workflowTaskCompletedEventId": "3", + "retryPolicy": { + "initialInterval": { + "seconds": "1" + }, + "backoffCoefficient": 2, + "maximumInterval": { + "seconds": "100" + }, + "maximumAttempts": 3 + } + } + }, + { + "eventId": "6", + "eventTime": { + "seconds": "2107874518", + "nanos": 154000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_STARTED", + "activityTaskStartedEventAttributes": { + "scheduledEventId": "5", + "identity": "32544@DESKTOP-QG1LIN3", + "attempt": 1 + } + }, + { + "eventId": "7", + "eventTime": { + "seconds": "2107874518", + "nanos": 157000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_COMPLETED", + "activityTaskCompletedEventAttributes": { + "result": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJ3b3JrZmxvd0lkIjoid2Y6cmVjb3JkOnR1cm4ifQ==" + } + ] + }, + "scheduledEventId": "5", + "startedEventId": "6", + "identity": "32544@DESKTOP-QG1LIN3" + } + }, + { + "eventId": "8", + "eventTime": { + "seconds": "2107874518", + "nanos": 157000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_SCHEDULED", + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "space-turns", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "startToCloseTimeout": { + "seconds": "10" + }, + "attempt": 1 + } + }, + { + "eventId": "9", + "eventTime": { + "seconds": "2107874518", + "nanos": 157000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "8", + "identity": "32544@DESKTOP-QG1LIN3", + "historySizeBytes": "1549" + } + }, + { + "eventId": "10", + "eventTime": { + "seconds": "2107874518", + "nanos": 165000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "8", + "identity": "32544@DESKTOP-QG1LIN3", + "binaryChecksum": "@temporalio/worker@1.24.0+86d854fc950701a33c6f41ef73ce75a518c298fa916171764a1b9e18166f7477", + "sdkMetadata": {}, + "meteringMetadata": {} + } + }, + { + "eventId": "11", + "eventTime": { + "seconds": "2107874518", + "nanos": 165000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_SCHEDULED", + "activityTaskScheduledEventAttributes": { + "activityId": "2", + "activityType": { + "name": "recordDummyTurnCompleted" + }, + "taskQueue": { + "name": "space-turns", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "header": {}, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJyb29tSWQiOiJyb29tOnJlY29yZCIsInBhbmVJZCI6InBhbmU6cmVjb3JkIiwicHJvbXB0IjoicmVjb3JkIGEgaGlzdG9yeSIsInJvb21BZ2VudE9wZXJhdG9yUmVxdWVzdCI6InJlY29yZCBhIGhpc3RvcnkiLCJtb2RlbElkIjoiZ3B0LTUuNi1zb2wiLCJyZWFzb25pbmdFZmZvcnQiOiJoaWdoIiwiYWdlbnRTZXNzaW9uSWQiOiJzZXNzaW9uOnJlY29yZCIsImFnZW50UnVuSWQiOiJhZ2VudF9ydW46cmVjb3JkIiwiYWdlbnRVc2VyTWVzc2FnZUlkIjoiYWdlbnRfbXNnOnVzZXItcmVjb3JkIiwiYWdlbnRBc3Npc3RhbnRNZXNzYWdlSWQiOiJhZ2VudF9tc2c6YXNzaXN0YW50LXJlY29yZCIsInRyYWNlSWQiOiJ0cmFjZTpyZWNvcmQifQ==" + } + ] + }, + "scheduleToCloseTimeout": { + "seconds": "315360000" + }, + "scheduleToStartTimeout": { + "seconds": "315360000" + }, + "startToCloseTimeout": { + "seconds": "60" + }, + "heartbeatTimeout": {}, + "workflowTaskCompletedEventId": "9", + "retryPolicy": { + "initialInterval": { + "seconds": "1" + }, + "backoffCoefficient": 2, + "maximumInterval": { + "seconds": "100" + }, + "maximumAttempts": 3 + } + } + }, + { + "eventId": "12", + "eventTime": { + "seconds": "2107874518", + "nanos": 165000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_STARTED", + "activityTaskStartedEventAttributes": { + "scheduledEventId": "11", + "identity": "32544@DESKTOP-QG1LIN3", + "attempt": 1 + } + }, + { + "eventId": "13", + "eventTime": { + "seconds": "2107874518", + "nanos": 167000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_COMPLETED", + "activityTaskCompletedEventAttributes": { + "result": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJ3b3JrZmxvd0lkIjoid2Y6cmVjb3JkOnR1cm4iLCJyb29tSWQiOiJyb29tOnJlY29yZCIsInBhbmVJZCI6InBhbmU6cmVjb3JkIiwidHJhY2VJZCI6InRyYWNlOnJlY29yZCJ9" + } + ] + }, + "scheduledEventId": "11", + "startedEventId": "12", + "identity": "32544@DESKTOP-QG1LIN3" + } + }, + { + "eventId": "14", + "eventTime": { + "seconds": "2107874518", + "nanos": 167000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_SCHEDULED", + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "space-turns", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "startToCloseTimeout": { + "seconds": "10" + }, + "attempt": 1 + } + }, + { + "eventId": "15", + "eventTime": { + "seconds": "2107874518", + "nanos": 167000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "14", + "identity": "32544@DESKTOP-QG1LIN3", + "historySizeBytes": "2516" + } + }, + { + "eventId": "16", + "eventTime": { + "seconds": "2107874518", + "nanos": 171000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "14", + "identity": "32544@DESKTOP-QG1LIN3", + "binaryChecksum": "@temporalio/worker@1.24.0+86d854fc950701a33c6f41ef73ce75a518c298fa916171764a1b9e18166f7477", + "sdkMetadata": {}, + "meteringMetadata": {} + } + }, + { + "eventId": "17", + "eventTime": { + "seconds": "2107874518", + "nanos": 171000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_EXECUTION_COMPLETED", + "workflowExecutionCompletedEventAttributes": { + "result": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJ3b3JrZmxvd0lkIjoid2Y6cmVjb3JkOnR1cm4iLCJyb29tSWQiOiJyb29tOnJlY29yZCIsInBhbmVJZCI6InBhbmU6cmVjb3JkIiwidHJhY2VJZCI6InRyYWNlOnJlY29yZCJ9" + } + ] + }, + "workflowTaskCompletedEventId": "15" + } + } +] \ No newline at end of file diff --git a/apps/worker/tests/fixtures/workflow-histories/legacyRoomAgentSupervisorWorkflow.json b/apps/worker/tests/fixtures/workflow-histories/legacyRoomAgentSupervisorWorkflow.json new file mode 100644 index 000000000..0e72fec11 --- /dev/null +++ b/apps/worker/tests/fixtures/workflow-histories/legacyRoomAgentSupervisorWorkflow.json @@ -0,0 +1,374 @@ +[ + { + "eventId": "1", + "eventTime": { + "seconds": "4315394534", + "nanos": 977000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_EXECUTION_STARTED", + "workflowExecutionStartedEventAttributes": { + "workflowType": { + "name": "legacyRoomAgentSupervisorWorkflow" + }, + "taskQueue": { + "name": "space-room-supervisor", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJyb29tSWQiOiJyb29tOnJlY29yZCIsInBlbmRpbmciOltdLCJtb25pdG9yaW5nTWlzc2lvbklkcyI6WyJtaXNzaW9uOnJlY29yZCJdLCJpbnRlcnJ1cHRlZCI6W10sInByb2Nlc3NlZENvdW50IjowfQ==" + } + ] + }, + "workflowExecutionTimeout": { + "seconds": "315360000" + }, + "workflowRunTimeout": { + "seconds": "315360000" + }, + "workflowTaskTimeout": { + "seconds": "10" + }, + "originalExecutionRunId": "0c0aedd1-6968-4e7d-a6de-0d64baf867db", + "identity": "32544@DESKTOP-QG1LIN3", + "firstExecutionRunId": "0c0aedd1-6968-4e7d-a6de-0d64baf867db", + "attempt": 1, + "firstWorkflowTaskBackoff": {}, + "header": {} + } + }, + { + "eventId": "2", + "eventTime": { + "seconds": "4315394534", + "nanos": 977000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_SCHEDULED", + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "space-room-supervisor", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "startToCloseTimeout": { + "seconds": "10" + }, + "attempt": 1 + } + }, + { + "eventId": "3", + "eventTime": { + "seconds": "4315394534", + "nanos": 977000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "2", + "identity": "32544@DESKTOP-QG1LIN3", + "historySizeBytes": "414" + } + }, + { + "eventId": "4", + "eventTime": { + "seconds": "4315394535", + "nanos": 906000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "2", + "identity": "32544@DESKTOP-QG1LIN3", + "binaryChecksum": "@temporalio/worker@1.24.0+86d854fc950701a33c6f41ef73ce75a518c298fa916171764a1b9e18166f7477", + "sdkMetadata": { + "coreUsedFlags": [ + 2, + 1, + 3 + ], + "sdkName": "temporal-typescript", + "sdkVersion": "1.24.0" + }, + "meteringMetadata": {} + } + }, + { + "eventId": "5", + "eventTime": { + "seconds": "4315394535", + "nanos": 906000000 + }, + "eventType": "EVENT_TYPE_TIMER_STARTED", + "timerStartedEventAttributes": { + "timerId": "1", + "startToFireTimeout": { + "seconds": "1" + }, + "workflowTaskCompletedEventId": "3" + } + }, + { + "eventId": "6", + "eventTime": { + "seconds": "4315394536", + "nanos": 906000000 + }, + "eventType": "EVENT_TYPE_TIMER_FIRED", + "timerFiredEventAttributes": { + "timerId": "1", + "startedEventId": "5" + } + }, + { + "eventId": "7", + "eventTime": { + "seconds": "4315394536", + "nanos": 906000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_SCHEDULED", + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "space-room-supervisor", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "startToCloseTimeout": { + "seconds": "10" + }, + "attempt": 1 + } + }, + { + "eventId": "8", + "eventTime": { + "seconds": "4315394536", + "nanos": 906000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "7", + "identity": "32544@DESKTOP-QG1LIN3", + "historySizeBytes": "747" + } + }, + { + "eventId": "9", + "eventTime": { + "seconds": "4315394536", + "nanos": 914000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "7", + "identity": "32544@DESKTOP-QG1LIN3", + "binaryChecksum": "@temporalio/worker@1.24.0+86d854fc950701a33c6f41ef73ce75a518c298fa916171764a1b9e18166f7477", + "sdkMetadata": {}, + "meteringMetadata": {} + } + }, + { + "eventId": "10", + "eventTime": { + "seconds": "4315394536", + "nanos": 914000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_SCHEDULED", + "activityTaskScheduledEventAttributes": { + "activityId": "1", + "activityType": { + "name": "settleRoomAgentBackgroundMission" + }, + "taskQueue": { + "name": "space-room-supervisor", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "header": {}, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJyb29tSWQiOiJyb29tOnJlY29yZCIsIm1pc3Npb25JZCI6Im1pc3Npb246cmVjb3JkIn0=" + } + ] + }, + "scheduleToCloseTimeout": { + "seconds": "315360000" + }, + "scheduleToStartTimeout": { + "seconds": "315360000" + }, + "startToCloseTimeout": { + "seconds": "86400" + }, + "heartbeatTimeout": { + "seconds": "15" + }, + "workflowTaskCompletedEventId": "8", + "retryPolicy": { + "initialInterval": { + "seconds": "1" + }, + "backoffCoefficient": 2, + "maximumInterval": { + "seconds": "100" + }, + "maximumAttempts": 5 + } + } + }, + { + "eventId": "11", + "eventTime": { + "seconds": "4315394536", + "nanos": 914000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_STARTED", + "activityTaskStartedEventAttributes": { + "scheduledEventId": "10", + "identity": "32544@DESKTOP-QG1LIN3", + "attempt": 1 + } + }, + { + "eventId": "12", + "eventTime": { + "seconds": "4315394536", + "nanos": 916000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_COMPLETED", + "activityTaskCompletedEventAttributes": { + "result": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "dHJ1ZQ==" + } + ] + }, + "scheduledEventId": "10", + "startedEventId": "11", + "identity": "32544@DESKTOP-QG1LIN3" + } + }, + { + "eventId": "13", + "eventTime": { + "seconds": "4315394536", + "nanos": 916000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_SCHEDULED", + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "space-room-supervisor", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "startToCloseTimeout": { + "seconds": "10" + }, + "attempt": 1 + } + }, + { + "eventId": "14", + "eventTime": { + "seconds": "4315394536", + "nanos": 916000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "13", + "identity": "32544@DESKTOP-QG1LIN3", + "historySizeBytes": "1340" + } + }, + { + "eventId": "15", + "eventTime": { + "seconds": "4315394536", + "nanos": 922000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "13", + "identity": "32544@DESKTOP-QG1LIN3", + "binaryChecksum": "@temporalio/worker@1.24.0+86d854fc950701a33c6f41ef73ce75a518c298fa916171764a1b9e18166f7477", + "sdkMetadata": {}, + "meteringMetadata": {} + } + }, + { + "eventId": "16", + "eventTime": { + "seconds": "4315394539", + "nanos": 980000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_EXECUTION_CANCEL_REQUESTED", + "workflowExecutionCancelRequestedEventAttributes": { + "identity": "32544@DESKTOP-QG1LIN3" + } + }, + { + "eventId": "17", + "eventTime": { + "seconds": "4315394539", + "nanos": 980000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_SCHEDULED", + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "space-room-supervisor", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "startToCloseTimeout": { + "seconds": "10" + }, + "attempt": 1 + } + }, + { + "eventId": "18", + "eventTime": { + "seconds": "4315394539", + "nanos": 980000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "17", + "identity": "32544@DESKTOP-QG1LIN3", + "historySizeBytes": "1626" + } + }, + { + "eventId": "19", + "eventTime": { + "seconds": "4315394539", + "nanos": 985000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "17", + "identity": "32544@DESKTOP-QG1LIN3", + "binaryChecksum": "@temporalio/worker@1.24.0+86d854fc950701a33c6f41ef73ce75a518c298fa916171764a1b9e18166f7477", + "sdkMetadata": {}, + "meteringMetadata": {} + } + }, + { + "eventId": "20", + "eventTime": { + "seconds": "4315394539", + "nanos": 985000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_EXECUTION_CANCELED", + "workflowExecutionCanceledEventAttributes": { + "workflowTaskCompletedEventId": "18", + "details": {} + } + } +] \ No newline at end of file diff --git a/apps/worker/tests/fixtures/workflow-histories/memoryConsolidationWorkflow.json b/apps/worker/tests/fixtures/workflow-histories/memoryConsolidationWorkflow.json new file mode 100644 index 000000000..d6fbcfe7a --- /dev/null +++ b/apps/worker/tests/fixtures/workflow-histories/memoryConsolidationWorkflow.json @@ -0,0 +1,251 @@ +[ + { + "eventId": "1", + "eventTime": { + "seconds": "3684674529", + "nanos": 592000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_EXECUTION_STARTED", + "workflowExecutionStartedEventAttributes": { + "workflowType": { + "name": "memoryConsolidationWorkflow" + }, + "taskQueue": { + "name": "space-memory-maintenance", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJydW5JZCI6InJ1bjpyZWNvcmQiLCJ0cmFjZUlkIjoidHJhY2U6cmVjb3JkIn0=" + } + ] + }, + "workflowExecutionTimeout": { + "seconds": "315360000" + }, + "workflowRunTimeout": { + "seconds": "315360000" + }, + "workflowTaskTimeout": { + "seconds": "10" + }, + "originalExecutionRunId": "5c553535-7ef5-4c19-b962-6e31a23f0d6e", + "identity": "32544@DESKTOP-QG1LIN3", + "firstExecutionRunId": "5c553535-7ef5-4c19-b962-6e31a23f0d6e", + "attempt": 1, + "firstWorkflowTaskBackoff": {}, + "header": {} + } + }, + { + "eventId": "2", + "eventTime": { + "seconds": "3684674529", + "nanos": 592000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_SCHEDULED", + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "space-memory-maintenance", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "startToCloseTimeout": { + "seconds": "10" + }, + "attempt": 1 + } + }, + { + "eventId": "3", + "eventTime": { + "seconds": "3684674529", + "nanos": 592000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "2", + "identity": "32544@DESKTOP-QG1LIN3", + "historySizeBytes": "344" + } + }, + { + "eventId": "4", + "eventTime": { + "seconds": "3684674530", + "nanos": 531000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "2", + "identity": "32544@DESKTOP-QG1LIN3", + "binaryChecksum": "@temporalio/worker@1.24.0+89821a4cb92c4bd76e380f93891e7ea328a70df58f29452928f443c887d3eb4b", + "sdkMetadata": { + "coreUsedFlags": [ + 2, + 3, + 1 + ], + "sdkName": "temporal-typescript", + "sdkVersion": "1.24.0" + }, + "meteringMetadata": {} + } + }, + { + "eventId": "5", + "eventTime": { + "seconds": "3684674530", + "nanos": 531000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_SCHEDULED", + "activityTaskScheduledEventAttributes": { + "activityId": "1", + "activityType": { + "name": "prepareMemoryConsolidation" + }, + "taskQueue": { + "name": "space-memory-maintenance", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "header": {}, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJydW5JZCI6InJ1bjpyZWNvcmQiLCJ0cmFjZUlkIjoidHJhY2U6cmVjb3JkIn0=" + } + ] + }, + "scheduleToCloseTimeout": { + "seconds": "315360000" + }, + "scheduleToStartTimeout": { + "seconds": "315360000" + }, + "startToCloseTimeout": { + "seconds": "600" + }, + "heartbeatTimeout": {}, + "workflowTaskCompletedEventId": "3", + "retryPolicy": { + "initialInterval": { + "seconds": "1" + }, + "backoffCoefficient": 2, + "maximumInterval": { + "seconds": "100" + }, + "maximumAttempts": 3 + } + } + }, + { + "eventId": "6", + "eventTime": { + "seconds": "3684674530", + "nanos": 531000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_STARTED", + "activityTaskStartedEventAttributes": { + "scheduledEventId": "5", + "identity": "32544@DESKTOP-QG1LIN3", + "attempt": 1 + } + }, + { + "eventId": "7", + "eventTime": { + "seconds": "3684674530", + "nanos": 534000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_COMPLETED", + "activityTaskCompletedEventAttributes": { + "result": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJydW4iOnsicnVuSWQiOiJydW46cmVjb3JkIiwic3RhdHVzIjoiQ09NUExFVEVEIn0sImNoYW5nZVNldElkIjpudWxsfQ==" + } + ] + }, + "scheduledEventId": "5", + "startedEventId": "6", + "identity": "32544@DESKTOP-QG1LIN3" + } + }, + { + "eventId": "8", + "eventTime": { + "seconds": "3684674530", + "nanos": 534000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_SCHEDULED", + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "space-memory-maintenance", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "startToCloseTimeout": { + "seconds": "10" + }, + "attempt": 1 + } + }, + { + "eventId": "9", + "eventTime": { + "seconds": "3684674530", + "nanos": 534000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "8", + "identity": "32544@DESKTOP-QG1LIN3", + "historySizeBytes": "1029" + } + }, + { + "eventId": "10", + "eventTime": { + "seconds": "3684674530", + "nanos": 541000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "8", + "identity": "32544@DESKTOP-QG1LIN3", + "binaryChecksum": "@temporalio/worker@1.24.0+89821a4cb92c4bd76e380f93891e7ea328a70df58f29452928f443c887d3eb4b", + "sdkMetadata": {}, + "meteringMetadata": {} + } + }, + { + "eventId": "11", + "eventTime": { + "seconds": "3684674530", + "nanos": 541000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_EXECUTION_COMPLETED", + "workflowExecutionCompletedEventAttributes": { + "result": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJydW5JZCI6InJ1bjpyZWNvcmQiLCJzdGF0dXMiOiJDT01QTEVURUQifQ==" + } + ] + }, + "workflowTaskCompletedEventId": "9" + } + } +] \ No newline at end of file diff --git a/apps/worker/tests/fixtures/workflow-histories/memoryMaintenanceWorkflow.json b/apps/worker/tests/fixtures/workflow-histories/memoryMaintenanceWorkflow.json new file mode 100644 index 000000000..35c41135d --- /dev/null +++ b/apps/worker/tests/fixtures/workflow-histories/memoryMaintenanceWorkflow.json @@ -0,0 +1,816 @@ +[ + { + "eventId": "1", + "eventTime": { + "seconds": "2738594520", + "nanos": 928000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_EXECUTION_STARTED", + "workflowExecutionStartedEventAttributes": { + "workflowType": { + "name": "memoryMaintenanceWorkflow" + }, + "taskQueue": { + "name": "space-memory-maintenance", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJ0cmFjZUlkIjoidHJhY2U6cmVjb3JkIn0=" + } + ] + }, + "workflowExecutionTimeout": { + "seconds": "315360000" + }, + "workflowRunTimeout": { + "seconds": "315360000" + }, + "workflowTaskTimeout": { + "seconds": "10" + }, + "originalExecutionRunId": "28a257a7-d12e-4d4d-ba84-b6405c681542", + "identity": "32544@DESKTOP-QG1LIN3", + "firstExecutionRunId": "28a257a7-d12e-4d4d-ba84-b6405c681542", + "attempt": 1, + "firstWorkflowTaskBackoff": {}, + "header": {} + } + }, + { + "eventId": "2", + "eventTime": { + "seconds": "2738594520", + "nanos": 928000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_SCHEDULED", + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "space-memory-maintenance", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "startToCloseTimeout": { + "seconds": "10" + }, + "attempt": 1 + } + }, + { + "eventId": "3", + "eventTime": { + "seconds": "2738594520", + "nanos": 929000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "2", + "identity": "32544@DESKTOP-QG1LIN3", + "historySizeBytes": "321" + } + }, + { + "eventId": "4", + "eventTime": { + "seconds": "2738594521", + "nanos": 922000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "2", + "identity": "32544@DESKTOP-QG1LIN3", + "binaryChecksum": "@temporalio/worker@1.24.0+89821a4cb92c4bd76e380f93891e7ea328a70df58f29452928f443c887d3eb4b", + "sdkMetadata": { + "coreUsedFlags": [ + 3, + 1, + 2 + ], + "sdkName": "temporal-typescript", + "sdkVersion": "1.24.0" + }, + "meteringMetadata": {} + } + }, + { + "eventId": "5", + "eventTime": { + "seconds": "2738594521", + "nanos": 922000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_SCHEDULED", + "activityTaskScheduledEventAttributes": { + "activityId": "1", + "activityType": { + "name": "refreshMemoryGraphSnapshot" + }, + "taskQueue": { + "name": "space-memory-maintenance", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "header": {}, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJ0cmFjZUlkIjoidHJhY2U6cmVjb3JkIn0=" + } + ] + }, + "scheduleToCloseTimeout": { + "seconds": "315360000" + }, + "scheduleToStartTimeout": { + "seconds": "315360000" + }, + "startToCloseTimeout": { + "seconds": "600" + }, + "heartbeatTimeout": {}, + "workflowTaskCompletedEventId": "3", + "retryPolicy": { + "initialInterval": { + "seconds": "1" + }, + "backoffCoefficient": 2, + "maximumInterval": { + "seconds": "100" + }, + "maximumAttempts": 3 + } + } + }, + { + "eventId": "6", + "eventTime": { + "seconds": "2738594521", + "nanos": 922000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_STARTED", + "activityTaskStartedEventAttributes": { + "scheduledEventId": "5", + "identity": "32544@DESKTOP-QG1LIN3", + "attempt": 1 + } + }, + { + "eventId": "7", + "eventTime": { + "seconds": "2738594521", + "nanos": 926000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_COMPLETED", + "activityTaskCompletedEventAttributes": { + "result": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJzb3VyY2VIYXNoIjoiaGFzaDpyZWNvcmQifQ==" + } + ] + }, + "scheduledEventId": "5", + "startedEventId": "6", + "identity": "32544@DESKTOP-QG1LIN3" + } + }, + { + "eventId": "8", + "eventTime": { + "seconds": "2738594521", + "nanos": 926000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_SCHEDULED", + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "space-memory-maintenance", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "startToCloseTimeout": { + "seconds": "10" + }, + "attempt": 1 + } + }, + { + "eventId": "9", + "eventTime": { + "seconds": "2738594521", + "nanos": 926000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "8", + "identity": "32544@DESKTOP-QG1LIN3", + "historySizeBytes": "943" + } + }, + { + "eventId": "10", + "eventTime": { + "seconds": "2738594521", + "nanos": 933000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "8", + "identity": "32544@DESKTOP-QG1LIN3", + "binaryChecksum": "@temporalio/worker@1.24.0+89821a4cb92c4bd76e380f93891e7ea328a70df58f29452928f443c887d3eb4b", + "sdkMetadata": {}, + "meteringMetadata": {} + } + }, + { + "eventId": "11", + "eventTime": { + "seconds": "2738594521", + "nanos": 933000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_SCHEDULED", + "activityTaskScheduledEventAttributes": { + "activityId": "2", + "activityType": { + "name": "refreshAllMonthsMemoryGraphSnapshot" + }, + "taskQueue": { + "name": "space-memory-maintenance", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "header": {}, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJ0cmFjZUlkIjoidHJhY2U6cmVjb3JkIn0=" + } + ] + }, + "scheduleToCloseTimeout": { + "seconds": "315360000" + }, + "scheduleToStartTimeout": { + "seconds": "315360000" + }, + "startToCloseTimeout": { + "seconds": "600" + }, + "heartbeatTimeout": {}, + "workflowTaskCompletedEventId": "9", + "retryPolicy": { + "initialInterval": { + "seconds": "1" + }, + "backoffCoefficient": 2, + "maximumInterval": { + "seconds": "100" + }, + "maximumAttempts": 3 + } + } + }, + { + "eventId": "12", + "eventTime": { + "seconds": "2738594521", + "nanos": 933000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_STARTED", + "activityTaskStartedEventAttributes": { + "scheduledEventId": "11", + "identity": "32544@DESKTOP-QG1LIN3", + "attempt": 1 + } + }, + { + "eventId": "13", + "eventTime": { + "seconds": "2738594521", + "nanos": 935000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_COMPLETED", + "activityTaskCompletedEventAttributes": { + "result": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJzb3VyY2VIYXNoIjoiaGFzaDpyZWNvcmQifQ==" + } + ] + }, + "scheduledEventId": "11", + "startedEventId": "12", + "identity": "32544@DESKTOP-QG1LIN3" + } + }, + { + "eventId": "14", + "eventTime": { + "seconds": "2738594521", + "nanos": 935000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_SCHEDULED", + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "space-memory-maintenance", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "startToCloseTimeout": { + "seconds": "10" + }, + "attempt": 1 + } + }, + { + "eventId": "15", + "eventTime": { + "seconds": "2738594521", + "nanos": 935000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "14", + "identity": "32544@DESKTOP-QG1LIN3", + "historySizeBytes": "1539" + } + }, + { + "eventId": "16", + "eventTime": { + "seconds": "2738594521", + "nanos": 939000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "14", + "identity": "32544@DESKTOP-QG1LIN3", + "binaryChecksum": "@temporalio/worker@1.24.0+89821a4cb92c4bd76e380f93891e7ea328a70df58f29452928f443c887d3eb4b", + "sdkMetadata": {}, + "meteringMetadata": {} + } + }, + { + "eventId": "17", + "eventTime": { + "seconds": "2738594521", + "nanos": 939000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_SCHEDULED", + "activityTaskScheduledEventAttributes": { + "activityId": "3", + "activityType": { + "name": "reconcileStaleSpaceAgentSessions" + }, + "taskQueue": { + "name": "space-memory-maintenance", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "header": {}, + "input": {}, + "scheduleToCloseTimeout": { + "seconds": "315360000" + }, + "scheduleToStartTimeout": { + "seconds": "315360000" + }, + "startToCloseTimeout": { + "seconds": "600" + }, + "heartbeatTimeout": {}, + "workflowTaskCompletedEventId": "15", + "retryPolicy": { + "initialInterval": { + "seconds": "1" + }, + "backoffCoefficient": 2, + "maximumInterval": { + "seconds": "100" + }, + "maximumAttempts": 3 + } + } + }, + { + "eventId": "18", + "eventTime": { + "seconds": "2738594521", + "nanos": 939000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_STARTED", + "activityTaskStartedEventAttributes": { + "scheduledEventId": "17", + "identity": "32544@DESKTOP-QG1LIN3", + "attempt": 1 + } + }, + { + "eventId": "19", + "eventTime": { + "seconds": "2738594521", + "nanos": 941000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_COMPLETED", + "activityTaskCompletedEventAttributes": { + "result": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJyZWNvbmNpbGVkIjowfQ==" + } + ] + }, + "scheduledEventId": "17", + "startedEventId": "18", + "identity": "32544@DESKTOP-QG1LIN3" + } + }, + { + "eventId": "20", + "eventTime": { + "seconds": "2738594521", + "nanos": 941000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_SCHEDULED", + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "space-memory-maintenance", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "startToCloseTimeout": { + "seconds": "10" + }, + "attempt": 1 + } + }, + { + "eventId": "21", + "eventTime": { + "seconds": "2738594521", + "nanos": 941000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "20", + "identity": "32544@DESKTOP-QG1LIN3", + "historySizeBytes": "2065" + } + }, + { + "eventId": "22", + "eventTime": { + "seconds": "2738594521", + "nanos": 945000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "20", + "identity": "32544@DESKTOP-QG1LIN3", + "binaryChecksum": "@temporalio/worker@1.24.0+89821a4cb92c4bd76e380f93891e7ea328a70df58f29452928f443c887d3eb4b", + "sdkMetadata": {}, + "meteringMetadata": {} + } + }, + { + "eventId": "23", + "eventTime": { + "seconds": "2738594521", + "nanos": 945000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_SCHEDULED", + "activityTaskScheduledEventAttributes": { + "activityId": "4", + "activityType": { + "name": "createScheduledMemoryAudit" + }, + "taskQueue": { + "name": "space-memory-maintenance", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "header": {}, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJ0cmFjZUlkIjoidHJhY2U6cmVjb3JkIiwic291cmNlSGFzaCI6Imhhc2g6cmVjb3JkIn0=" + } + ] + }, + "scheduleToCloseTimeout": { + "seconds": "315360000" + }, + "scheduleToStartTimeout": { + "seconds": "315360000" + }, + "startToCloseTimeout": { + "seconds": "600" + }, + "heartbeatTimeout": {}, + "workflowTaskCompletedEventId": "21", + "retryPolicy": { + "initialInterval": { + "seconds": "1" + }, + "backoffCoefficient": 2, + "maximumInterval": { + "seconds": "100" + }, + "maximumAttempts": 3 + } + } + }, + { + "eventId": "24", + "eventTime": { + "seconds": "2738594521", + "nanos": 945000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_STARTED", + "activityTaskStartedEventAttributes": { + "scheduledEventId": "23", + "identity": "32544@DESKTOP-QG1LIN3", + "attempt": 1 + } + }, + { + "eventId": "25", + "eventTime": { + "seconds": "2738594521", + "nanos": 947000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_COMPLETED", + "activityTaskCompletedEventAttributes": { + "result": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJpZCI6InJ1bjpyZWNvcmQiLCJ3b3JrZmxvd0lkIjoid2Y6cmVjb3JkOmNvbnNvbGlkYXRpb24ifQ==" + } + ] + }, + "scheduledEventId": "23", + "startedEventId": "24", + "identity": "32544@DESKTOP-QG1LIN3" + } + }, + { + "eventId": "26", + "eventTime": { + "seconds": "2738594521", + "nanos": 947000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_SCHEDULED", + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "space-memory-maintenance", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "startToCloseTimeout": { + "seconds": "10" + }, + "attempt": 1 + } + }, + { + "eventId": "27", + "eventTime": { + "seconds": "2738594521", + "nanos": 947000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "26", + "identity": "32544@DESKTOP-QG1LIN3", + "historySizeBytes": "2709" + } + }, + { + "eventId": "28", + "eventTime": { + "seconds": "2738594521", + "nanos": 954000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "26", + "identity": "32544@DESKTOP-QG1LIN3", + "binaryChecksum": "@temporalio/worker@1.24.0+89821a4cb92c4bd76e380f93891e7ea328a70df58f29452928f443c887d3eb4b", + "sdkMetadata": {}, + "meteringMetadata": {} + } + }, + { + "eventId": "29", + "eventTime": { + "seconds": "2738594521", + "nanos": 954000000 + }, + "eventType": "EVENT_TYPE_START_CHILD_WORKFLOW_EXECUTION_INITIATED", + "startChildWorkflowExecutionInitiatedEventAttributes": { + "namespace": "default", + "workflowId": "wf:record:consolidation", + "workflowType": { + "name": "memoryConsolidationWorkflow" + }, + "taskQueue": { + "name": "space-memory-maintenance", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJydW5JZCI6InJ1bjpyZWNvcmQiLCJ0cmFjZUlkIjoidHJhY2U6cmVjb3JkIn0=" + } + ] + }, + "workflowExecutionTimeout": {}, + "workflowRunTimeout": {}, + "workflowTaskTimeout": { + "seconds": "10" + }, + "workflowTaskCompletedEventId": "27", + "header": {}, + "memo": {} + } + }, + { + "eventId": "30", + "eventTime": { + "seconds": "2738594521", + "nanos": 954000000 + }, + "eventType": "EVENT_TYPE_CHILD_WORKFLOW_EXECUTION_STARTED", + "childWorkflowExecutionStartedEventAttributes": { + "namespace": "default", + "initiatedEventId": "29", + "workflowExecution": { + "workflowId": "wf:record:consolidation", + "runId": "b8871ee3-e67e-4d49-9fb4-455360cc2f34" + }, + "workflowType": { + "name": "memoryConsolidationWorkflow" + } + } + }, + { + "eventId": "31", + "eventTime": { + "seconds": "2738594521", + "nanos": 954000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_SCHEDULED", + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "space-memory-maintenance", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "startToCloseTimeout": { + "seconds": "10" + }, + "attempt": 1 + } + }, + { + "eventId": "32", + "eventTime": { + "seconds": "2738594521", + "nanos": 954000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "31", + "identity": "32544@DESKTOP-QG1LIN3", + "historySizeBytes": "3291" + } + }, + { + "eventId": "33", + "eventTime": { + "seconds": "2738594522", + "nanos": 645000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "31", + "identity": "32544@DESKTOP-QG1LIN3", + "binaryChecksum": "@temporalio/worker@1.24.0+89821a4cb92c4bd76e380f93891e7ea328a70df58f29452928f443c887d3eb4b", + "sdkMetadata": {}, + "meteringMetadata": {} + } + }, + { + "eventId": "34", + "eventTime": { + "seconds": "2738594522", + "nanos": 653000000 + }, + "eventType": "EVENT_TYPE_CHILD_WORKFLOW_EXECUTION_COMPLETED", + "childWorkflowExecutionCompletedEventAttributes": { + "result": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJydW5JZCI6InJ1bjpyZWNvcmQifQ==" + } + ] + }, + "namespace": "default", + "workflowExecution": { + "workflowId": "wf:record:consolidation", + "runId": "b8871ee3-e67e-4d49-9fb4-455360cc2f34" + }, + "workflowType": { + "name": "memoryConsolidationWorkflow" + }, + "initiatedEventId": "29", + "startedEventId": "30" + } + }, + { + "eventId": "35", + "eventTime": { + "seconds": "2738594522", + "nanos": 653000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_SCHEDULED", + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "space-memory-maintenance", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "startToCloseTimeout": { + "seconds": "10" + }, + "attempt": 1 + } + }, + { + "eventId": "36", + "eventTime": { + "seconds": "2738594522", + "nanos": 653000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "35", + "identity": "32544@DESKTOP-QG1LIN3", + "historySizeBytes": "3719" + } + }, + { + "eventId": "37", + "eventTime": { + "seconds": "2738594522", + "nanos": 661000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "35", + "identity": "32544@DESKTOP-QG1LIN3", + "binaryChecksum": "@temporalio/worker@1.24.0+89821a4cb92c4bd76e380f93891e7ea328a70df58f29452928f443c887d3eb4b", + "sdkMetadata": {}, + "meteringMetadata": {} + } + }, + { + "eventId": "38", + "eventTime": { + "seconds": "2738594522", + "nanos": 661000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_EXECUTION_COMPLETED", + "workflowExecutionCompletedEventAttributes": { + "result": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJzb3VyY2VIYXNoIjoiaGFzaDpyZWNvcmQifQ==" + } + ] + }, + "workflowTaskCompletedEventId": "36" + } + } +] \ No newline at end of file diff --git a/apps/worker/tests/fixtures/workflow-histories/memoryMutationWorkflow.json b/apps/worker/tests/fixtures/workflow-histories/memoryMutationWorkflow.json new file mode 100644 index 000000000..cf2807f52 --- /dev/null +++ b/apps/worker/tests/fixtures/workflow-histories/memoryMutationWorkflow.json @@ -0,0 +1,251 @@ +[ + { + "eventId": "1", + "eventTime": { + "seconds": "2423234519", + "nanos": 13000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_EXECUTION_STARTED", + "workflowExecutionStartedEventAttributes": { + "workflowType": { + "name": "memoryMutationWorkflow" + }, + "taskQueue": { + "name": "space-memory-mutations", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJjaGFuZ2VTZXRJZCI6ImNzOnJlY29yZCIsInRyYWNlSWQiOiJ0cmFjZTpyZWNvcmQifQ==" + } + ] + }, + "workflowExecutionTimeout": { + "seconds": "315360000" + }, + "workflowRunTimeout": { + "seconds": "315360000" + }, + "workflowTaskTimeout": { + "seconds": "10" + }, + "originalExecutionRunId": "b22cf921-77d8-49cd-837d-27fde1d34aa2", + "identity": "32544@DESKTOP-QG1LIN3", + "firstExecutionRunId": "b22cf921-77d8-49cd-837d-27fde1d34aa2", + "attempt": 1, + "firstWorkflowTaskBackoff": {}, + "header": {} + } + }, + { + "eventId": "2", + "eventTime": { + "seconds": "2423234519", + "nanos": 13000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_SCHEDULED", + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "space-memory-mutations", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "startToCloseTimeout": { + "seconds": "10" + }, + "attempt": 1 + } + }, + { + "eventId": "3", + "eventTime": { + "seconds": "2423234519", + "nanos": 14000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "2", + "identity": "32544@DESKTOP-QG1LIN3", + "historySizeBytes": "338" + } + }, + { + "eventId": "4", + "eventTime": { + "seconds": "2423234519", + "nanos": 78000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "2", + "identity": "32544@DESKTOP-QG1LIN3", + "binaryChecksum": "@temporalio/worker@1.24.0+934f9e8ee5a6527226f2daf40d34b4c92630958672e3e631fdbf595ad5fcbd32", + "sdkMetadata": { + "coreUsedFlags": [ + 2, + 3, + 1 + ], + "sdkName": "temporal-typescript", + "sdkVersion": "1.24.0" + }, + "meteringMetadata": {} + } + }, + { + "eventId": "5", + "eventTime": { + "seconds": "2423234519", + "nanos": 78000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_SCHEDULED", + "activityTaskScheduledEventAttributes": { + "activityId": "1", + "activityType": { + "name": "executePersistedMemoryChangeSet" + }, + "taskQueue": { + "name": "space-memory-mutations", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "header": {}, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJjaGFuZ2VTZXRJZCI6ImNzOnJlY29yZCIsInRyYWNlSWQiOiJ0cmFjZTpyZWNvcmQifQ==" + } + ] + }, + "scheduleToCloseTimeout": { + "seconds": "315360000" + }, + "scheduleToStartTimeout": { + "seconds": "315360000" + }, + "startToCloseTimeout": { + "seconds": "900" + }, + "heartbeatTimeout": {}, + "workflowTaskCompletedEventId": "3", + "retryPolicy": { + "initialInterval": { + "seconds": "1" + }, + "backoffCoefficient": 2, + "maximumInterval": { + "seconds": "100" + }, + "maximumAttempts": 3 + } + } + }, + { + "eventId": "6", + "eventTime": { + "seconds": "2423234519", + "nanos": 78000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_STARTED", + "activityTaskStartedEventAttributes": { + "scheduledEventId": "5", + "identity": "32544@DESKTOP-QG1LIN3", + "attempt": 1 + } + }, + { + "eventId": "7", + "eventTime": { + "seconds": "2423234519", + "nanos": 83000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_COMPLETED", + "activityTaskCompletedEventAttributes": { + "result": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJjaGFuZ2VTZXRJZCI6ImNzOnJlY29yZCIsImFwcGxpZWQiOnRydWV9" + } + ] + }, + "scheduledEventId": "5", + "startedEventId": "6", + "identity": "32544@DESKTOP-QG1LIN3" + } + }, + { + "eventId": "8", + "eventTime": { + "seconds": "2423234519", + "nanos": 83000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_SCHEDULED", + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "space-memory-mutations", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "startToCloseTimeout": { + "seconds": "10" + }, + "attempt": 1 + } + }, + { + "eventId": "9", + "eventTime": { + "seconds": "2423234519", + "nanos": 83000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "8", + "identity": "32544@DESKTOP-QG1LIN3", + "historySizeBytes": "995" + } + }, + { + "eventId": "10", + "eventTime": { + "seconds": "2423234519", + "nanos": 89000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "8", + "identity": "32544@DESKTOP-QG1LIN3", + "binaryChecksum": "@temporalio/worker@1.24.0+934f9e8ee5a6527226f2daf40d34b4c92630958672e3e631fdbf595ad5fcbd32", + "sdkMetadata": {}, + "meteringMetadata": {} + } + }, + { + "eventId": "11", + "eventTime": { + "seconds": "2423234519", + "nanos": 89000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_EXECUTION_COMPLETED", + "workflowExecutionCompletedEventAttributes": { + "result": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJjaGFuZ2VTZXRJZCI6ImNzOnJlY29yZCIsImFwcGxpZWQiOnRydWV9" + } + ] + }, + "workflowTaskCompletedEventId": "9" + } + } +] \ No newline at end of file diff --git a/apps/worker/tests/fixtures/workflow-histories/roomAgentActionWorkflow.json b/apps/worker/tests/fixtures/workflow-histories/roomAgentActionWorkflow.json new file mode 100644 index 000000000..80c7808b7 --- /dev/null +++ b/apps/worker/tests/fixtures/workflow-histories/roomAgentActionWorkflow.json @@ -0,0 +1,253 @@ +[ + { + "eventId": "1", + "eventTime": { + "seconds": "3369314527", + "nanos": 7000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_EXECUTION_STARTED", + "workflowExecutionStartedEventAttributes": { + "workflowType": { + "name": "roomAgentActionWorkflow" + }, + "taskQueue": { + "name": "space-turns", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJicmlkZ2UiOnsicm9vbUlkIjoicm9vbTpyZWNvcmQiLCJtaXNzaW9uSWQiOiJtaXNzaW9uOnJlY29yZCJ9LCJhY3Rpb25JZCI6ImFjdGlvbjpyZWNvcmQiLCJ0cmFjZUlkIjoidHJhY2U6cmVjb3JkIn0=" + } + ] + }, + "workflowExecutionTimeout": { + "seconds": "315360000" + }, + "workflowRunTimeout": { + "seconds": "315360000" + }, + "workflowTaskTimeout": { + "seconds": "10" + }, + "originalExecutionRunId": "9837fc97-d283-4f57-af59-069c3db4b91b", + "identity": "32544@DESKTOP-QG1LIN3", + "firstExecutionRunId": "9837fc97-d283-4f57-af59-069c3db4b91b", + "attempt": 1, + "firstWorkflowTaskBackoff": {}, + "header": {} + } + }, + { + "eventId": "2", + "eventTime": { + "seconds": "3369314527", + "nanos": 7000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_SCHEDULED", + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "space-turns", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "startToCloseTimeout": { + "seconds": "10" + }, + "attempt": 1 + } + }, + { + "eventId": "3", + "eventTime": { + "seconds": "3369314527", + "nanos": 7000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "2", + "identity": "32544@DESKTOP-QG1LIN3", + "historySizeBytes": "383" + } + }, + { + "eventId": "4", + "eventTime": { + "seconds": "3369314527", + "nanos": 934000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "2", + "identity": "32544@DESKTOP-QG1LIN3", + "binaryChecksum": "@temporalio/worker@1.24.0+86d854fc950701a33c6f41ef73ce75a518c298fa916171764a1b9e18166f7477", + "sdkMetadata": { + "coreUsedFlags": [ + 2, + 1, + 3 + ], + "sdkName": "temporal-typescript", + "sdkVersion": "1.24.0" + }, + "meteringMetadata": {} + } + }, + { + "eventId": "5", + "eventTime": { + "seconds": "3369314527", + "nanos": 934000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_SCHEDULED", + "activityTaskScheduledEventAttributes": { + "activityId": "1", + "activityType": { + "name": "runRoomAgentBackgroundAction" + }, + "taskQueue": { + "name": "space-turns", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "header": {}, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJicmlkZ2UiOnsicm9vbUlkIjoicm9vbTpyZWNvcmQiLCJtaXNzaW9uSWQiOiJtaXNzaW9uOnJlY29yZCJ9LCJhY3Rpb25JZCI6ImFjdGlvbjpyZWNvcmQiLCJ0cmFjZUlkIjoidHJhY2U6cmVjb3JkIn0=" + } + ] + }, + "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": "3369314527", + "nanos": 935000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_STARTED", + "activityTaskStartedEventAttributes": { + "scheduledEventId": "5", + "identity": "32544@DESKTOP-QG1LIN3", + "attempt": 1 + } + }, + { + "eventId": "7", + "eventTime": { + "seconds": "3369314527", + "nanos": 939000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_COMPLETED", + "activityTaskCompletedEventAttributes": { + "result": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJhY3Rpb25JZCI6ImFjdGlvbjpyZWNvcmQiLCJyYW4iOnRydWV9" + } + ] + }, + "scheduledEventId": "5", + "startedEventId": "6", + "identity": "32544@DESKTOP-QG1LIN3" + } + }, + { + "eventId": "8", + "eventTime": { + "seconds": "3369314527", + "nanos": 939000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_SCHEDULED", + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "space-turns", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "startToCloseTimeout": { + "seconds": "10" + }, + "attempt": 1 + } + }, + { + "eventId": "9", + "eventTime": { + "seconds": "3369314527", + "nanos": 939000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "8", + "identity": "32544@DESKTOP-QG1LIN3", + "historySizeBytes": "1086" + } + }, + { + "eventId": "10", + "eventTime": { + "seconds": "3369314527", + "nanos": 945000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "8", + "identity": "32544@DESKTOP-QG1LIN3", + "binaryChecksum": "@temporalio/worker@1.24.0+86d854fc950701a33c6f41ef73ce75a518c298fa916171764a1b9e18166f7477", + "sdkMetadata": {}, + "meteringMetadata": {} + } + }, + { + "eventId": "11", + "eventTime": { + "seconds": "3369314527", + "nanos": 945000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_EXECUTION_COMPLETED", + "workflowExecutionCompletedEventAttributes": { + "result": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJhY3Rpb25JZCI6ImFjdGlvbjpyZWNvcmQiLCJyYW4iOnRydWV9" + } + ] + }, + "workflowTaskCompletedEventId": "9" + } + } +] \ No newline at end of file diff --git a/apps/worker/tests/fixtures/workflow-histories/roomAgentSupervisorWorkflow.json b/apps/worker/tests/fixtures/workflow-histories/roomAgentSupervisorWorkflow.json new file mode 100644 index 000000000..54c2e2279 --- /dev/null +++ b/apps/worker/tests/fixtures/workflow-histories/roomAgentSupervisorWorkflow.json @@ -0,0 +1,476 @@ +[ + { + "eventId": "1", + "eventTime": { + "seconds": "4000034532", + "nanos": 283000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_EXECUTION_STARTED", + "workflowExecutionStartedEventAttributes": { + "workflowType": { + "name": "roomAgentSupervisorWorkflow" + }, + "taskQueue": { + "name": "space-room-supervisor", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJyb29tSWQiOiJyb29tOnJlY29yZCIsInBlbmRpbmciOlt7Im1pc3Npb25JZCI6Im1pc3Npb246cmVjb3JkIiwidHVybiI6eyJyb29tSWQiOiJyb29tOnJlY29yZCIsInBhbmVJZCI6InBhbmU6cmVjb3JkIiwicHJvbXB0IjoicmVjb3JkIGEgaGlzdG9yeSIsInJvb21BZ2VudE9wZXJhdG9yUmVxdWVzdCI6InJlY29yZCBhIGhpc3RvcnkiLCJtb2RlbElkIjoiZ3B0LTUuNi1zb2wiLCJyZWFzb25pbmdFZmZvcnQiOiJoaWdoIiwiYWdlbnRTZXNzaW9uSWQiOiJzZXNzaW9uOnJlY29yZCIsImFnZW50UnVuSWQiOiJhZ2VudF9ydW46cmVjb3JkIiwiYWdlbnRVc2VyTWVzc2FnZUlkIjoiYWdlbnRfbXNnOnVzZXItcmVjb3JkIiwiYWdlbnRBc3Npc3RhbnRNZXNzYWdlSWQiOiJhZ2VudF9tc2c6YXNzaXN0YW50LXJlY29yZCIsInRyYWNlSWQiOiJ0cmFjZTpyZWNvcmQifX1dLCJtb25pdG9yaW5nTWlzc2lvbklkcyI6W10sImludGVycnVwdGVkIjpbXSwicHJvY2Vzc2VkQ291bnQiOjB9" + } + ] + }, + "workflowExecutionTimeout": { + "seconds": "315360000" + }, + "workflowRunTimeout": { + "seconds": "315360000" + }, + "workflowTaskTimeout": { + "seconds": "10" + }, + "originalExecutionRunId": "9dd75ecb-db4b-41f6-80e2-40e083d6c197", + "identity": "32544@DESKTOP-QG1LIN3", + "firstExecutionRunId": "9dd75ecb-db4b-41f6-80e2-40e083d6c197", + "attempt": 1, + "firstWorkflowTaskBackoff": {}, + "header": {} + } + }, + { + "eventId": "2", + "eventTime": { + "seconds": "4000034532", + "nanos": 283000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_SCHEDULED", + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "space-room-supervisor", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "startToCloseTimeout": { + "seconds": "10" + }, + "attempt": 1 + } + }, + { + "eventId": "3", + "eventTime": { + "seconds": "4000034532", + "nanos": 283000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "2", + "identity": "32544@DESKTOP-QG1LIN3", + "historySizeBytes": "792" + } + }, + { + "eventId": "4", + "eventTime": { + "seconds": "4000034533", + "nanos": 236000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "2", + "identity": "32544@DESKTOP-QG1LIN3", + "binaryChecksum": "@temporalio/worker@1.24.0+86d854fc950701a33c6f41ef73ce75a518c298fa916171764a1b9e18166f7477", + "sdkMetadata": { + "coreUsedFlags": [ + 3, + 2, + 1 + ], + "sdkName": "temporal-typescript", + "sdkVersion": "1.24.0" + }, + "meteringMetadata": {} + } + }, + { + "eventId": "5", + "eventTime": { + "seconds": "4000034533", + "nanos": 236000000 + }, + "eventType": "EVENT_TYPE_MARKER_RECORDED", + "markerRecordedEventAttributes": { + "markerName": "core_patch", + "details": { + "patch-data": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJpZCI6InJvb20tYmFja2dyb3VuZC1jb250aW51YXRpb24tdjEiLCJkZXByZWNhdGVkIjpmYWxzZX0=" + } + ] + } + }, + "workflowTaskCompletedEventId": "3" + } + }, + { + "eventId": "6", + "eventTime": { + "seconds": "4000034533", + "nanos": 236000000 + }, + "eventType": "EVENT_TYPE_UPSERT_WORKFLOW_SEARCH_ATTRIBUTES", + "upsertWorkflowSearchAttributesEventAttributes": { + "workflowTaskCompletedEventId": "3", + "searchAttributes": { + "indexedFields": { + "TemporalChangeVersion": { + "metadata": { + "encoding": "anNvbi9wbGFpbg==", + "type": "S2V5d29yZExpc3Q=" + }, + "data": "WyJyb29tLWJhY2tncm91bmQtY29udGludWF0aW9uLXYxIl0=" + } + } + } + } + }, + { + "eventId": "7", + "eventTime": { + "seconds": "4000034533", + "nanos": 236000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_SCHEDULED", + "activityTaskScheduledEventAttributes": { + "activityId": "1", + "activityType": { + "name": "checkRoomAgentSupervisorTurn" + }, + "taskQueue": { + "name": "space-room-supervisor", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "header": {}, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJtaXNzaW9uSWQiOiJtaXNzaW9uOnJlY29yZCIsInR1cm4iOnsicm9vbUlkIjoicm9vbTpyZWNvcmQiLCJwYW5lSWQiOiJwYW5lOnJlY29yZCIsInByb21wdCI6InJlY29yZCBhIGhpc3RvcnkiLCJhcnRpZmFjdElkcyI6W10sIm1vZGVsSWQiOiJncHQtNS42LXNvbCIsInJlYXNvbmluZ0VmZm9ydCI6ImhpZ2giLCJhZ2VudFNlc3Npb25JZCI6InNlc3Npb246cmVjb3JkIiwiYWdlbnRSdW5JZCI6ImFnZW50X3J1bjpyZWNvcmQiLCJyb29tQWdlbnRPcGVyYXRvclJlcXVlc3QiOiJyZWNvcmQgYSBoaXN0b3J5IiwiYWdlbnRVc2VyTWVzc2FnZUlkIjoiYWdlbnRfbXNnOnVzZXItcmVjb3JkIiwiYWdlbnRBc3Npc3RhbnRNZXNzYWdlSWQiOiJhZ2VudF9tc2c6YXNzaXN0YW50LXJlY29yZCIsInBlcm1pc3Npb25Nb2RlIjpudWxsLCJjb2xsYWJvcmF0aW9uTW9kZSI6ImRlZmF1bHQiLCJ0cmFjZUlkIjoidHJhY2U6cmVjb3JkIn19" + } + ] + }, + "scheduleToCloseTimeout": { + "seconds": "315360000" + }, + "scheduleToStartTimeout": { + "seconds": "315360000" + }, + "startToCloseTimeout": { + "seconds": "60" + }, + "heartbeatTimeout": {}, + "workflowTaskCompletedEventId": "3", + "retryPolicy": { + "initialInterval": { + "seconds": "1" + }, + "backoffCoefficient": 2, + "maximumInterval": { + "seconds": "100" + }, + "maximumAttempts": 5 + } + } + }, + { + "eventId": "8", + "eventTime": { + "seconds": "4000034533", + "nanos": 236000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_STARTED", + "activityTaskStartedEventAttributes": { + "scheduledEventId": "7", + "identity": "32544@DESKTOP-QG1LIN3", + "attempt": 1 + } + }, + { + "eventId": "9", + "eventTime": { + "seconds": "4000034533", + "nanos": 239000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_COMPLETED", + "activityTaskCompletedEventAttributes": { + "result": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "IlNLSVAi" + } + ] + }, + "scheduledEventId": "7", + "startedEventId": "8", + "identity": "32544@DESKTOP-QG1LIN3" + } + }, + { + "eventId": "10", + "eventTime": { + "seconds": "4000034533", + "nanos": 239000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_SCHEDULED", + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "space-room-supervisor", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "startToCloseTimeout": { + "seconds": "10" + }, + "attempt": 1 + } + }, + { + "eventId": "11", + "eventTime": { + "seconds": "4000034533", + "nanos": 239000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "10", + "identity": "32544@DESKTOP-QG1LIN3", + "historySizeBytes": "2097" + } + }, + { + "eventId": "12", + "eventTime": { + "seconds": "4000034533", + "nanos": 247000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "10", + "identity": "32544@DESKTOP-QG1LIN3", + "binaryChecksum": "@temporalio/worker@1.24.0+86d854fc950701a33c6f41ef73ce75a518c298fa916171764a1b9e18166f7477", + "sdkMetadata": {}, + "meteringMetadata": {} + } + }, + { + "eventId": "13", + "eventTime": { + "seconds": "4000034533", + "nanos": 247000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_SCHEDULED", + "activityTaskScheduledEventAttributes": { + "activityId": "2", + "activityType": { + "name": "discardRoomAgentSupervisorTurn" + }, + "taskQueue": { + "name": "space-room-supervisor", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "header": {}, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJpdGVtIjp7Im1pc3Npb25JZCI6Im1pc3Npb246cmVjb3JkIiwidHVybiI6eyJyb29tSWQiOiJyb29tOnJlY29yZCIsInBhbmVJZCI6InBhbmU6cmVjb3JkIiwicHJvbXB0IjoicmVjb3JkIGEgaGlzdG9yeSIsImFydGlmYWN0SWRzIjpbXSwibW9kZWxJZCI6ImdwdC01LjYtc29sIiwicmVhc29uaW5nRWZmb3J0IjoiaGlnaCIsImFnZW50U2Vzc2lvbklkIjoic2Vzc2lvbjpyZWNvcmQiLCJhZ2VudFJ1bklkIjoiYWdlbnRfcnVuOnJlY29yZCIsInJvb21BZ2VudE9wZXJhdG9yUmVxdWVzdCI6InJlY29yZCBhIGhpc3RvcnkiLCJhZ2VudFVzZXJNZXNzYWdlSWQiOiJhZ2VudF9tc2c6dXNlci1yZWNvcmQiLCJhZ2VudEFzc2lzdGFudE1lc3NhZ2VJZCI6ImFnZW50X21zZzphc3Npc3RhbnQtcmVjb3JkIiwicGVybWlzc2lvbk1vZGUiOm51bGwsImNvbGxhYm9yYXRpb25Nb2RlIjoiZGVmYXVsdCIsInRyYWNlSWQiOiJ0cmFjZTpyZWNvcmQifX0sInJlYXNvbiI6IlR1cm4gaXMgbm8gbG9uZ2VyIGFjdGl2ZSBvciB3YXMgc3VwZXJzZWRlZC4ifQ==" + } + ] + }, + "scheduleToCloseTimeout": { + "seconds": "315360000" + }, + "scheduleToStartTimeout": { + "seconds": "315360000" + }, + "startToCloseTimeout": { + "seconds": "60" + }, + "heartbeatTimeout": {}, + "workflowTaskCompletedEventId": "11", + "retryPolicy": { + "initialInterval": { + "seconds": "1" + }, + "backoffCoefficient": 2, + "maximumInterval": { + "seconds": "100" + }, + "maximumAttempts": 5 + } + } + }, + { + "eventId": "14", + "eventTime": { + "seconds": "4000034533", + "nanos": 247000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_STARTED", + "activityTaskStartedEventAttributes": { + "scheduledEventId": "13", + "identity": "32544@DESKTOP-QG1LIN3", + "attempt": 1 + } + }, + { + "eventId": "15", + "eventTime": { + "seconds": "4000034533", + "nanos": 249000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_COMPLETED", + "activityTaskCompletedEventAttributes": { + "result": { + "payloads": [ + { + "metadata": { + "encoding": "YmluYXJ5L251bGw=" + } + } + ] + }, + "scheduledEventId": "13", + "startedEventId": "14", + "identity": "32544@DESKTOP-QG1LIN3" + } + }, + { + "eventId": "16", + "eventTime": { + "seconds": "4000034533", + "nanos": 249000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_SCHEDULED", + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "space-room-supervisor", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "startToCloseTimeout": { + "seconds": "10" + }, + "attempt": 1 + } + }, + { + "eventId": "17", + "eventTime": { + "seconds": "4000034533", + "nanos": 249000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "16", + "identity": "32544@DESKTOP-QG1LIN3", + "historySizeBytes": "3155" + } + }, + { + "eventId": "18", + "eventTime": { + "seconds": "4000034533", + "nanos": 254000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "16", + "identity": "32544@DESKTOP-QG1LIN3", + "binaryChecksum": "@temporalio/worker@1.24.0+86d854fc950701a33c6f41ef73ce75a518c298fa916171764a1b9e18166f7477", + "sdkMetadata": {}, + "meteringMetadata": {} + } + }, + { + "eventId": "19", + "eventTime": { + "seconds": "4000034537", + "nanos": 287000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_EXECUTION_CANCEL_REQUESTED", + "workflowExecutionCancelRequestedEventAttributes": { + "identity": "32544@DESKTOP-QG1LIN3" + } + }, + { + "eventId": "20", + "eventTime": { + "seconds": "4000034537", + "nanos": 287000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_SCHEDULED", + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "space-room-supervisor", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "startToCloseTimeout": { + "seconds": "10" + }, + "attempt": 1 + } + }, + { + "eventId": "21", + "eventTime": { + "seconds": "4000034537", + "nanos": 287000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "20", + "identity": "32544@DESKTOP-QG1LIN3", + "historySizeBytes": "3439" + } + }, + { + "eventId": "22", + "eventTime": { + "seconds": "4000034537", + "nanos": 295000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "20", + "identity": "32544@DESKTOP-QG1LIN3", + "binaryChecksum": "@temporalio/worker@1.24.0+86d854fc950701a33c6f41ef73ce75a518c298fa916171764a1b9e18166f7477", + "sdkMetadata": {}, + "meteringMetadata": {} + } + }, + { + "eventId": "23", + "eventTime": { + "seconds": "4000034537", + "nanos": 295000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_EXECUTION_CANCELED", + "workflowExecutionCanceledEventAttributes": { + "workflowTaskCompletedEventId": "21", + "details": {} + } + } +] \ No newline at end of file diff --git a/apps/worker/tests/room-supervisor-sandbox.test.ts b/apps/worker/tests/room-supervisor-sandbox.test.ts new file mode 100644 index 000000000..6c3cdb983 --- /dev/null +++ b/apps/worker/tests/room-supervisor-sandbox.test.ts @@ -0,0 +1,306 @@ +import { TestWorkflowEnvironment } from "@temporalio/testing"; +import { Worker, type WorkerOptions } from "@temporalio/worker"; +import { afterAll, beforeAll, describe, expect, it } from "vitest"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const SRC = path.resolve(__dirname, "../src"); +const WORKFLOWS = path.join(SRC, "workflows.ts"); +const SUPERVISOR_QUEUE = "space-room-supervisor"; + +let env: TestWorkflowEnvironment; + +beforeAll(async () => { + env = await TestWorkflowEnvironment.createTimeSkipping(); +}, 300_000); + +afterAll(async () => { + await env?.teardown(); +}); + +async function withWorker( + activities: NonNullable, + run: () => Promise +): Promise { + const worker = await Worker.create({ + connection: env.nativeConnection, + taskQueue: SUPERVISOR_QUEUE, + workflowsPath: WORKFLOWS, + activities + }); + await worker.runUntil(run()); +} + +function turn(suffix: string, overrides: Record = {}) { + return { + roomId: "room:supervisor", + paneId: "pane:supervisor", + prompt: `Request ${suffix}`, + roomAgentOperatorRequest: `Request ${suffix}`, + modelId: "gpt-5.6-sol", + reasoningEffort: "high", + agentSessionId: "session:supervisor", + agentRunId: `agent_run:${suffix}`, + agentUserMessageId: `agent_msg:user-${suffix}`, + agentAssistantMessageId: `agent_msg:assistant-${suffix}`, + roomAgentMissionId: "mission:supervisor", + traceId: `trace:${suffix}`, + ...overrides + }; +} + +function queueItem(suffix: string, overrides: Record = {}) { + return { missionId: "mission:supervisor", turn: turn(suffix, overrides) }; +} + +/** + * The supervisor never returns: it parks in condition() and runs until + * continueAsNew or cancellation. These tests drive it with stub activities and + * end each run by cancelling the workflow, which is what an operator stop does. + */ +describe("roomAgentSupervisorWorkflow (real sandbox)", () => { + it("runs a queued mission to completion, then parks", async () => { + const calls: string[] = []; + await withWorker( + { + checkRoomAgentSupervisorTurn: async () => { + calls.push("check"); + return "RUN"; + }, + runCodexAppServerTurn: async () => { + calls.push("turn"); + return { + status: "COMPLETED", + message: "done", + roomAgentOutcome: { + status: "VERIFIED", + statusReason: "done" + } + }; + }, + finishRoomAgentSupervisorTurn: async (input: { status?: string }) => { + calls.push(`finish:${input.status ?? "none"}`); + }, + discardRoomAgentSupervisorTurn: async () => { + calls.push("discard"); + } + }, + async () => { + const handle = await env.client.workflow.start("roomAgentSupervisorWorkflow", { + args: [ + { + roomId: "room:supervisor", + pending: [queueItem("one")], + monitoringMissionIds: [], + interrupted: [], + processedCount: 0 + } + ], + taskQueue: SUPERVISOR_QUEUE, + workflowId: "wf:supervisor:complete" + }); + // The child codexAppServerTurnWorkflow runs for real; its activity is stubbed. + await env.sleep("30 seconds"); + await handle.cancel(); + await expect(handle.result()).rejects.toThrow(); + } + ); + expect(calls).toContain("check"); + expect(calls).toContain("turn"); + expect(calls).toContain("finish:COMPLETED"); + }, 300_000); + + it("records an operator stop on a queued mission as INTERRUPTED", async () => { + const finishes: Array<{ status?: string; statusReason?: string }> = []; + await withWorker( + { + checkRoomAgentSupervisorTurn: async () => { + // Never returns RUN, so the stop signal lands on a queued item. + return "PAUSED"; + }, + finishRoomAgentSupervisorTurn: async (input: { status?: string; statusReason?: string }) => { + finishes.push(input); + }, + discardRoomAgentSupervisorTurn: async () => undefined + }, + async () => { + const handle = await env.client.workflow.start("roomAgentSupervisorWorkflow", { + args: [ + { + roomId: "room:supervisor", + pending: [queueItem("stop")], + monitoringMissionIds: [], + interrupted: [], + processedCount: 0 + } + ], + taskQueue: SUPERVISOR_QUEUE, + workflowId: "wf:supervisor:stop" + }); + await env.sleep("5 seconds"); + await handle.signal("stopRoomAgentMission", { + missionId: "mission:supervisor", + reason: "Operator stopped the room agent." + }); + await env.sleep("5 seconds"); + await handle.cancel(); + await expect(handle.result()).rejects.toThrow(); + } + ); + expect(finishes.some((entry) => entry.status === "INTERRUPTED")).toBe(true); + }, 300_000); + + it("continues as new once 50 missions have been processed", async () => { + let processed = 0; + await withWorker( + { + checkRoomAgentSupervisorTurn: async () => { + processed += 1; + return "SKIP"; + }, + finishRoomAgentSupervisorTurn: async () => undefined, + discardRoomAgentSupervisorTurn: async () => undefined + }, + async () => { + const pending = Array.from({ length: 50 }, (_, index) => + queueItem(`bulk-${String(index).padStart(3, "0")}`) + ); + const handle = await env.client.workflow.start("roomAgentSupervisorWorkflow", { + args: [ + { + roomId: "room:supervisor", + pending, + monitoringMissionIds: [], + interrupted: [], + processedCount: 0 + } + ], + taskQueue: SUPERVISOR_QUEUE, + workflowId: "wf:supervisor:continue" + }); + // continueAsNew starts a fresh run under the same workflow id, so the + // handle keeps following the chain and the run id changes. + await env.sleep("120 seconds"); + const description = await handle.describe(); + expect(description.workflowId).toBe("wf:supervisor:continue"); + expect(description.runId).not.toBe(handle.firstExecutionRunId); + await handle.cancel(); + await expect(handle.result()).rejects.toThrow(); + } + ); + expect(processed).toBeGreaterThanOrEqual(50); + }, 300_000); + + it("continues as new after 250 monitoring polls with the carried state intact", async () => { + let polls = 0; + await withWorker( + { + checkRoomAgentSupervisorTurn: async () => "SKIP", + finishRoomAgentSupervisorTurn: async () => undefined, + discardRoomAgentSupervisorTurn: async () => undefined, + // "WORKING" keeps the mission in the monitoring set, so the poll loop + // keeps running until the 250-poll bound trips continueAsNew. + prepareRoomAgentBackgroundContinuation: async () => { + polls += 1; + return { state: "WORKING" }; + } + }, + async () => { + const handle = await env.client.workflow.start("roomAgentSupervisorWorkflow", { + args: [ + { + roomId: "room:supervisor", + pending: [], + monitoringMissionIds: ["mission:supervisor"], + interrupted: [], + processedCount: 0 + } + ], + taskQueue: SUPERVISOR_QUEUE, + workflowId: "wf:supervisor:polls" + }); + await env.sleep("600 seconds"); + const description = await handle.describe(); + expect(description.workflowId).toBe("wf:supervisor:polls"); + expect(description.runId).not.toBe(handle.firstExecutionRunId); + await handle.cancel(); + await expect(handle.result()).rejects.toThrow(); + } + ); + expect(polls).toBeGreaterThanOrEqual(250); + }, 300_000); + + it("keeps the legacy branch reachable for histories that predate the patch", async () => { + const calls: string[] = []; + await withWorker( + { + settleRoomAgentBackgroundMission: async () => { + calls.push("settle"); + return true; + }, + markRoomAgentMissionStarted: async () => undefined, + markRoomAgentMissionFinished: async () => undefined, + markRoomAgentMissionContinued: async () => undefined + }, + async () => { + const handle = await env.client.workflow.start("legacyRoomAgentSupervisorWorkflow", { + args: [ + { + roomId: "room:supervisor", + pending: [], + monitoringMissionIds: ["mission:supervisor"], + interrupted: [], + processedCount: 0 + } + ], + taskQueue: SUPERVISOR_QUEUE, + workflowId: "wf:supervisor:legacy" + }); + await env.sleep("30 seconds"); + await handle.cancel(); + await expect(handle.result()).rejects.toThrow(); + } + ); + // The legacy branch polls the boolean settle activity while monitoring. + expect(calls).toContain("settle"); + }, 300_000); + + it("continues as new from the pre-patch branch after 250 monitoring polls", async () => { + let polls = 0; + await withWorker( + { + // false keeps the mission in the monitoring set so the legacy poll loop + // reaches its 250-poll bound, which is that branch's continueAsNew path. + settleRoomAgentBackgroundMission: async () => { + polls += 1; + return false; + }, + markRoomAgentMissionStarted: async () => undefined, + markRoomAgentMissionFinished: async () => undefined, + markRoomAgentMissionContinued: async () => undefined + }, + async () => { + const handle = await env.client.workflow.start("legacyRoomAgentSupervisorWorkflow", { + args: [ + { + roomId: "room:supervisor", + pending: [], + monitoringMissionIds: ["mission:supervisor"], + interrupted: [], + processedCount: 0 + } + ], + taskQueue: SUPERVISOR_QUEUE, + workflowId: "wf:supervisor:legacy-continue" + }); + await env.sleep("600 seconds"); + const description = await handle.describe(); + expect(description.runId).not.toBe(handle.firstExecutionRunId); + await handle.cancel(); + await expect(handle.result()).rejects.toThrow(); + } + ); + expect(polls).toBeGreaterThanOrEqual(250); + }, 300_000); +}); diff --git a/apps/worker/tests/workflow-histories.mts b/apps/worker/tests/workflow-histories.mts new file mode 100644 index 000000000..24d617f71 --- /dev/null +++ b/apps/worker/tests/workflow-histories.mts @@ -0,0 +1,345 @@ +import { TestWorkflowEnvironment } from "@temporalio/testing"; +import { Worker, type WorkerOptions } from "@temporalio/worker"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { mkdir, readdir, readFile, writeFile } from "node:fs/promises"; +import { historyFromJSON } from "@temporalio/common/lib/proto-utils.js"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const SRC = path.resolve(__dirname, "../src"); +const HISTORIES = path.resolve(__dirname, "fixtures/workflow-histories"); + +const DOT_QUEUE = "space-dot-goals"; +const TURN_QUEUE = "space-turns"; +const MAINTENANCE_QUEUE = "space-memory-maintenance"; +const MUTATION_QUEUE = "space-memory-mutations"; +const SUPERVISOR_QUEUE = "space-room-supervisor"; + +const WORKFLOWS = path.join(SRC, "workflows.ts"); +const MAINTENANCE = path.join(SRC, "memory-maintenance-workflows.ts"); +const MUTATION = path.join(SRC, "memory-mutation-workflows.ts"); + +function turn() { + return { + roomId: "room:record", + paneId: "pane:record", + prompt: "record a history", + roomAgentOperatorRequest: "record a history", + modelId: "gpt-5.6-sol", + reasoningEffort: "high", + agentSessionId: "session:record", + agentRunId: "agent_run:record", + agentUserMessageId: "agent_msg:user-record", + agentAssistantMessageId: "agent_msg:assistant-record", + traceId: "trace:record" + }; +} + +async function withWorker( + env: TestWorkflowEnvironment, + options: Pick, + run: () => Promise +) { + const worker = await Worker.create({ connection: env.nativeConnection, ...options }); + await worker.runUntil(run()); +} + +async function record( + env: TestWorkflowEnvironment, + file: string, + handle: { workflowId: string; result(): Promise }, + options: { cancelled?: boolean } = {} +) { + // Read the history only after the run closed: reading it earlier returns the + // first events of a still-open run, which replays trivially and would hide a + // determinism break. A workflow that parks forever (the supervisor) is closed + // by cancelling it, and that rejection is the expected close, not a failure. + const result = options.cancelled + ? await handle.result().catch(() => undefined) + : await handle.result(); + const history = await env.client.workflow.getHandle(handle.workflowId).fetchHistory(); + if (!history.events?.length) throw new Error(`empty history for ${file}`); + await mkdir(HISTORIES, { recursive: true }); + await writeFile( + path.join(HISTORIES, `${file}.json`), + JSON.stringify(history.events, null, 1), + "utf8" + ); + return result; +} + +/** Run each workflow once in the real sandbox and store its history. */ +export async function recordHistories(): Promise { + const env = await TestWorkflowEnvironment.createTimeSkipping(); + const written: string[] = []; + try { + await withWorker( + env, + { + taskQueue: DOT_QUEUE, + workflowsPath: WORKFLOWS, + activities: { + dotGoalStep: async () => ({ state: "DONE", runId: null, goalStatus: "COMPLETED" }), + dotGoalObserve: async () => ({ run: "COMPLETED", goalStatus: "COMPLETED" }) + } + }, + async () => { + const handle = await env.client.workflow.start("dotGoalWorkflow", { + args: [{ goalId: "goal:record" }], + taskQueue: DOT_QUEUE, + workflowId: "wf:record:dot-done" + }); + await record(env, "dotGoalWorkflow", handle); + written.push("dotGoalWorkflow"); + } + ); + + await withWorker( + env, + { + taskQueue: TURN_QUEUE, + workflowsPath: WORKFLOWS, + activities: { + recordDummyTurnStarted: async () => ({ workflowId: "wf:record:turn" }), + recordDummyTurnCompleted: async () => ({ + workflowId: "wf:record:turn", + roomId: "room:record", + paneId: "pane:record", + traceId: "trace:record" + }) + } + }, + async () => { + const handle = await env.client.workflow.start("dummyTurnWorkflow", { + args: [turn()], + taskQueue: TURN_QUEUE, + workflowId: "wf:record:turn" + }); + await record(env, "dummyTurnWorkflow", handle); + written.push("dummyTurnWorkflow"); + } + ); + + await withWorker( + env, + { + taskQueue: MUTATION_QUEUE, + workflowsPath: MUTATION, + activities: { + executePersistedMemoryChangeSet: async (input: { changeSetId: string }) => ({ + changeSetId: input.changeSetId, + applied: true + }) + } + }, + async () => { + const handle = await env.client.workflow.start("memoryMutationWorkflow", { + args: [{ changeSetId: "cs:record", traceId: "trace:record" }], + taskQueue: MUTATION_QUEUE, + workflowId: "wf:record:mutation" + }); + await record(env, "memoryMutationWorkflow", handle); + written.push("memoryMutationWorkflow"); + } + ); + + await withWorker( + env, + { + taskQueue: MAINTENANCE_QUEUE, + workflowsPath: MAINTENANCE, + activities: { + refreshMemoryGraphSnapshot: async () => ({ sourceHash: "hash:record" }), + refreshAllMonthsMemoryGraphSnapshot: async () => ({ sourceHash: "hash:record" }), + reconcileStaleSpaceAgentSessions: async () => ({ reconciled: 0 }), + createScheduledMemoryAudit: async () => ({ id: "run:record", workflowId: "wf:record:consolidation" }), + prepareMemoryConsolidation: async () => ({ run: { runId: "run:record" }, changeSetId: null }) + } + }, + async () => { + const handle = await env.client.workflow.start("memoryMaintenanceWorkflow", { + args: [{ traceId: "trace:record" }], + taskQueue: MAINTENANCE_QUEUE, + workflowId: "wf:record:maintenance" + }); + await record(env, "memoryMaintenanceWorkflow", handle); + written.push("memoryMaintenanceWorkflow"); + } + ); + + await withWorker( + env, + { + taskQueue: TURN_QUEUE, + workflowsPath: WORKFLOWS, + activities: { + runCodexAppServerTurn: async (input: { roomId: string; paneId: string }) => ({ + workflowId: "wf:record:codex-turn", + roomId: input.roomId, + paneId: input.paneId, + traceId: "trace:record" + }) + } + }, + async () => { + const handle = await env.client.workflow.start("codexAppServerTurnWorkflow", { + args: [turn()], + taskQueue: TURN_QUEUE, + workflowId: "wf:record:codex-turn" + }); + await record(env, "codexAppServerTurnWorkflow", handle); + written.push("codexAppServerTurnWorkflow"); + } + ); + + await withWorker( + env, + { + taskQueue: TURN_QUEUE, + workflowsPath: WORKFLOWS, + activities: { + runRoomAgentBackgroundAction: async () => ({ actionId: "action:record", ran: true }), + failRoomAgentBackgroundAction: async () => undefined + } + }, + async () => { + const handle = await env.client.workflow.start("roomAgentActionWorkflow", { + args: [{ bridge: { roomId: "room:record", missionId: "mission:record" }, actionId: "action:record", traceId: "trace:record" }], + taskQueue: TURN_QUEUE, + workflowId: "wf:record:room-action" + }); + await record(env, "roomAgentActionWorkflow", handle); + written.push("roomAgentActionWorkflow"); + } + ); + + await withWorker( + env, + { + taskQueue: MAINTENANCE_QUEUE, + workflowsPath: MAINTENANCE, + activities: { + prepareMemoryConsolidation: async () => ({ run: { runId: "run:record", status: "COMPLETED" }, changeSetId: null }) + } + }, + async () => { + const handle = await env.client.workflow.start("memoryConsolidationWorkflow", { + args: [{ runId: "run:record", traceId: "trace:record" }], + taskQueue: MAINTENANCE_QUEUE, + workflowId: "wf:record:consolidation" + }); + await record(env, "memoryConsolidationWorkflow", handle); + written.push("memoryConsolidationWorkflow"); + } + ); + + // The supervisor never returns; cancel it so the run closes and the history + // (which is what replay reads) is complete. + await withWorker( + env, + { + taskQueue: SUPERVISOR_QUEUE, + workflowsPath: WORKFLOWS, + activities: { + checkRoomAgentSupervisorTurn: async () => "SKIP", + discardRoomAgentSupervisorTurn: async () => undefined, + finishRoomAgentSupervisorTurn: async () => undefined, + prepareRoomAgentBackgroundContinuation: async () => ({ state: "DONE" }) + } + }, + async () => { + const pending = [{ missionId: "mission:record", turn: turn() }]; + const handle = await env.client.workflow.start("roomAgentSupervisorWorkflow", { + args: [{ roomId: "room:record", pending, monitoringMissionIds: [], interrupted: [], processedCount: 0 }], + taskQueue: SUPERVISOR_QUEUE, + workflowId: "wf:record:supervisor" + }); + await env.sleep("5 seconds"); + await handle.cancel(); + await record(env, "roomAgentSupervisorWorkflow", handle, { cancelled: true }); + written.push("roomAgentSupervisorWorkflow"); + } + ); + + await withWorker( + env, + { + taskQueue: SUPERVISOR_QUEUE, + workflowsPath: WORKFLOWS, + activities: { + settleRoomAgentBackgroundMission: async () => true, + markRoomAgentMissionStarted: async () => undefined, + markRoomAgentMissionFinished: async () => undefined, + markRoomAgentMissionContinued: async () => undefined, + prepareRoomAgentBackgroundContinuation: async () => ({ state: "DONE" }) + } + }, + async () => { + // Monitoring-only entry: the legacy branch then polls its boolean settle + // activity, which is the behaviour only this branch has. + const handle = await env.client.workflow.start("legacyRoomAgentSupervisorWorkflow", { + args: [{ roomId: "room:record", pending: [], monitoringMissionIds: ["mission:record"], interrupted: [], processedCount: 0 }], + taskQueue: SUPERVISOR_QUEUE, + workflowId: "wf:record:legacy-supervisor" + }); + await env.sleep("5 seconds"); + await handle.cancel(); + await record(env, "legacyRoomAgentSupervisorWorkflow", handle, { cancelled: true }); + written.push("legacyRoomAgentSupervisorWorkflow"); + } + ); + } finally { + await env.teardown(); + } + return written; +} + +/** Replay every stored history against the current workflow code. */ +export async function replayHistories(): Promise< + Array<{ file: string; workflowId: string; error?: string }> +> { + let files: string[]; + try { + files = (await readdir(HISTORIES)).filter((name) => name.endsWith(".json")).sort(); + } catch { + return []; + } + const results: Array<{ file: string; workflowId: string; error?: string }> = []; + const bundleFor = (file: string) => + file.startsWith("memoryMaintenance") || file.startsWith("memoryConsolidation") ? MAINTENANCE + : file.startsWith("memoryMutation") ? MUTATION + : WORKFLOWS; + + for (const file of files) { + const events = JSON.parse(await readFile(path.join(HISTORIES, file), "utf8")); + const history = historyFromJSON({ events }); + const workflowId = `replay:${file}`; + const errors: string[] = []; + const iterable = Worker.runReplayHistories( + { + workflowsPath: bundleFor(file), + taskQueue: "space-replay", + activities: {} + }, + [{ workflowId, history }] + ); + for await (const result of iterable) { + if (result.error) errors.push(`${result.error.name}: ${result.error.message}`); + } + results.push({ file, workflowId, ...(errors.length ? { error: errors.join("; ") } : {}) }); + } + return results; +} + +if (process.argv[2] === "--record") { + recordHistories().then( + (written) => { + console.log("RECORDED", JSON.stringify(written)); + process.exit(0); + }, + (error) => { + console.error("RECORD FAILED", error); + process.exit(1); + } + ); +} diff --git a/apps/worker/tests/workflow-replay.test.ts b/apps/worker/tests/workflow-replay.test.ts new file mode 100644 index 000000000..2446d4fd6 --- /dev/null +++ b/apps/worker/tests/workflow-replay.test.ts @@ -0,0 +1,93 @@ +import { Worker, type ReplayWorkerOptions } from "@temporalio/worker"; +import { describe, expect, it } from "vitest"; +import { readdir, readFile } from "node:fs/promises"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const SRC = path.resolve(__dirname, "../src"); +const HISTORIES = path.resolve(__dirname, "fixtures/workflow-histories"); + +const WORKFLOWS = path.join(SRC, "workflows.ts"); +const MAINTENANCE = path.join(SRC, "memory-maintenance-workflows.ts"); +const MUTATION = path.join(SRC, "memory-mutation-workflows.ts"); + +/** Each recorded file replays against the module its production worker ships. */ +function bundleFor(file: string): string { + if (file.startsWith("memoryMaintenance") || file.startsWith("memoryConsolidation")) return MAINTENANCE; + if (file.startsWith("memoryMutation")) return MUTATION; + return WORKFLOWS; +} + +async function historyFor(file: string) { + // @temporalio/proto builds the protobuf message the replay worker expects; + // historyFromJSON cannot be used because the workspace pins protobufjs 7 for + // @grpc/proto-loader while @temporalio/common loads protobufjs 8. + const proto: any = await import("@temporalio/proto"); + const History = proto.default.temporal.api.history.v1.History; + const events = JSON.parse(await readFile(path.join(HISTORIES, file), "utf8")); + return History.fromObject({ events }); +} + +async function replayOne( + file: string, + workflowsPath: string +): Promise> { + const options: ReplayWorkerOptions = { + workflowsPath, + taskQueue: "space-replay", + activities: {} + }; + const results: Array<{ workflowId: string; error?: string }> = []; + const iterable = Worker.runReplayHistories(options, [ + { workflowId: `replay:${file}`, history: await historyFor(file) } + ]); + for await (const result of iterable) { + results.push({ + workflowId: result.workflowId, + ...(result.error ? { error: `${result.error.name}: ${result.error.message}` } : {}) + }); + } + return results; +} + +describe("replay gate", () => { + it("has a recorded history for every workflow in scope", async () => { + const files = (await readdir(HISTORIES)).filter((name) => name.endsWith(".json")); + // One per workflow in the issue's scope list, plus the pre-patch supervisor + // branch, which only a history can reach. + expect(files).toEqual(expect.arrayContaining([ + "dotGoalWorkflow.json", + "dummyTurnWorkflow.json", + "codexAppServerTurnWorkflow.json", + "roomAgentActionWorkflow.json", + "roomAgentSupervisorWorkflow.json", + "legacyRoomAgentSupervisorWorkflow.json", + "memoryMaintenanceWorkflow.json", + "memoryConsolidationWorkflow.json", + "memoryMutationWorkflow.json" + ])); + }, 30_000); + + it("replays every recorded history without a determinism violation", async () => { + const files = (await readdir(HISTORIES)).filter((name) => name.endsWith(".json")).sort(); + const failures: string[] = []; + for (const file of files) { + const results = await replayOne(file, bundleFor(file)); + expect(results.length).toBe(1); + for (const result of results) { + if (result.error) failures.push(`${file}: ${result.error}`); + } + } + expect(failures).toEqual([]); + }, 600_000); + + it("fails replay when a workflow schedules an activity the history does not contain", async () => { + // The test-only variant schedules an extra activity before the recorded one. + const variant = path.resolve(__dirname, "fixtures/replay-violation-workflows.ts"); + const results = await replayOne("dummyTurnWorkflow.json", variant); + expect(results.length).toBe(1); + expect(results[0].error).toBeTruthy(); + expect(results[0].error).toMatch(/determinis|nondeterminis/i); + }, 600_000); +}); diff --git a/apps/worker/tests/workflow-sandbox-extra.test.ts b/apps/worker/tests/workflow-sandbox-extra.test.ts new file mode 100644 index 000000000..288f25663 --- /dev/null +++ b/apps/worker/tests/workflow-sandbox-extra.test.ts @@ -0,0 +1,280 @@ +import { TestWorkflowEnvironment } from "@temporalio/testing"; +import { Worker, type WorkerOptions } from "@temporalio/worker"; +import { afterAll, beforeAll, describe, expect, it } from "vitest"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const SRC = path.resolve(__dirname, "../src"); +const WORKFLOWS = path.join(SRC, "workflows.ts"); +const MAINTENANCE = path.join(SRC, "memory-maintenance-workflows.ts"); + +const TURN_QUEUE = "space-turns"; +const MAINTENANCE_QUEUE = "space-memory-maintenance"; + +let env: TestWorkflowEnvironment; + +beforeAll(async () => { + env = await TestWorkflowEnvironment.createTimeSkipping(); +}, 300_000); + +afterAll(async () => { + await env?.teardown(); +}); + +async function withWorker( + options: Pick, + run: () => Promise +): Promise { + const worker = await Worker.create({ connection: env.nativeConnection, ...options }); + await worker.runUntil(run()); +} + +function turn(overrides: Record = {}) { + return { + roomId: "room:rest", + paneId: "pane:rest", + prompt: "do the thing", + roomAgentOperatorRequest: "do the thing", + modelId: "gpt-5.6-sol", + reasoningEffort: "high", + agentSessionId: "session:rest", + agentRunId: "agent_run:rest", + agentUserMessageId: "agent_msg:user-rest", + agentAssistantMessageId: "agent_msg:assistant-rest", + traceId: "trace:rest", + ...overrides + }; +} + +describe("memoryConsolidationWorkflow", () => { + it("stops after prepare when the activity reports no change set", async () => { + const calls: string[] = []; + await withWorker( + { + taskQueue: MAINTENANCE_QUEUE, + workflowsPath: MAINTENANCE, + activities: { + prepareMemoryConsolidation: async () => { + calls.push("prepare"); + return { run: { runId: "run:noop" }, changeSetId: null }; + }, + executePersistedMemoryChangeSet: async () => { + calls.push("mutate"); + return { changeSetId: "unexpected" }; + }, + finalizeMemoryRepair: async () => { + calls.push("finalize"); + return { runId: "run:noop" }; + } + } + }, + async () => { + const result = await env.client.workflow.execute("memoryConsolidationWorkflow", { + args: [{ runId: "run:noop", traceId: "trace:noop" }], + taskQueue: MAINTENANCE_QUEUE, + workflowId: "wf:consolidation:noop" + }); + expect(result).toEqual({ runId: "run:noop" }); + } + ); + expect(calls).toEqual(["prepare"]); + }, 240_000); + + it("hands the change set to the mutation task queue and finalizes the repair", async () => { + // memoryConsolidationWorkflow proxies executePersistedMemoryChangeSet onto + // MEMORY_MUTATION_TASK_QUEUE. Two workers poll while the run is in flight; + // the mutation worker is stopped as soon as the run settles. + const calls: string[] = []; + const maintenance = await Worker.create({ + connection: env.nativeConnection, + taskQueue: MAINTENANCE_QUEUE, + workflowsPath: MAINTENANCE, + activities: { + prepareMemoryConsolidation: async () => { + calls.push("prepare"); + return { run: { runId: "run:work" }, changeSetId: "cs:work" }; + }, + finalizeMemoryRepair: async (input: { runId: string }) => { + calls.push(`finalize:${input.runId}`); + return { runId: input.runId, repaired: true }; + } + } + }); + const mutation = await Worker.create({ + connection: env.nativeConnection, + taskQueue: "space-memory-mutations", + activities: { + executePersistedMemoryChangeSet: async (input: { changeSetId: string }) => { + calls.push(`mutate:${input.changeSetId}`); + return { changeSetId: input.changeSetId, applied: true }; + } + } + }); + + let releaseMutation: () => void = () => undefined; + const mutationStop = new Promise((resolve) => { + releaseMutation = resolve; + }); + const mutationRun = mutation.runUntil(mutationStop); + + try { + const result = await maintenance.runUntil(() => + env.client.workflow.execute("memoryConsolidationWorkflow", { + args: [{ runId: "run:work", traceId: "trace:work" }], + taskQueue: MAINTENANCE_QUEUE, + workflowId: "wf:consolidation:work" + }) + ); + expect(result).toEqual({ runId: "run:work", repaired: true }); + } finally { + releaseMutation(); + await mutationRun; + } + expect(calls).toEqual(["prepare", "mutate:cs:work", "finalize:run:work"]); + }, 180_000); +}); + +describe("roomAgentActionWorkflow", () => { + it("returns the activity result on success and records no failure", async () => { + const calls: string[] = []; + await withWorker( + { + taskQueue: TURN_QUEUE, + workflowsPath: WORKFLOWS, + activities: { + runRoomAgentBackgroundAction: async () => { + calls.push("run"); + return { actionId: "action:1", status: "COMPLETED" }; + }, + failRoomAgentBackgroundAction: async () => { + calls.push("fail"); + } + } + }, + async () => { + const result = await env.client.workflow.execute("roomAgentActionWorkflow", { + args: [ + { + actionId: "action:1", + traceId: "trace:action", + bridge: { roomId: "room:rest", missionId: "mission:rest" } + } + ], + taskQueue: TURN_QUEUE, + workflowId: "wf:room-action:ok" + }); + expect(result).toEqual({ actionId: "action:1", status: "COMPLETED" }); + } + ); + expect(calls).toEqual(["run"]); + }, 240_000); + + it("records the background failure on the patched branch before rethrowing", async () => { + const failures: Array> = []; + await withWorker( + { + taskQueue: TURN_QUEUE, + workflowsPath: WORKFLOWS, + activities: { + runRoomAgentBackgroundAction: async () => { + throw new Error("transport failed"); + }, + failRoomAgentBackgroundAction: async (input: Record) => { + failures.push(input); + } + } + }, + async () => { + const failure = await env.client.workflow + .execute("roomAgentActionWorkflow", { + args: [ + { + actionId: "action:2", + traceId: "trace:action-fail", + bridge: { roomId: "room:rest", missionId: "mission:rest" } + } + ], + taskQueue: TURN_QUEUE, + workflowId: "wf:room-action:fail" + }) + .then( + () => null, + (error: unknown) => error + ); + expect(failure).not.toBeNull(); + } + ); + expect(failures).toHaveLength(1); + expect(failures[0]).toMatchObject({ + roomId: "room:rest", + missionId: "mission:rest", + actionId: "action:2", + cancelled: false + }); + }, 240_000); +}); + +describe("codexAppServerTurnWorkflow", () => { + it("routes an ordinary turn to the room agent activity", async () => { + const calls: string[] = []; + await withWorker( + { + taskQueue: TURN_QUEUE, + workflowsPath: WORKFLOWS, + activities: { + runCodexAppServerTurn: async () => { + calls.push("room-agent"); + return { status: "COMPLETED", message: "done" }; + }, + runOpenCodeAgentTurn: async () => { + calls.push("opencode"); + return { status: "COMPLETED", message: "opencode" }; + }, + runCliAgentTurn: async () => { + calls.push("cli"); + return { status: "COMPLETED", message: "cli" }; + } + } + }, + async () => { + const result = await env.client.workflow.execute("codexAppServerTurnWorkflow", { + args: [turn()], + taskQueue: TURN_QUEUE, + workflowId: "wf:turn:room-agent" + }); + expect(result).toEqual({ status: "COMPLETED", message: "done" }); + } + ); + expect(calls).toEqual(["room-agent"]); + }, 240_000); + + it("routes an opencode provider to the opencode activity", async () => { + const calls: string[] = []; + await withWorker( + { + taskQueue: TURN_QUEUE, + workflowsPath: WORKFLOWS, + activities: { + runCodexAppServerTurn: async () => { + calls.push("room-agent"); + return { status: "COMPLETED", message: "room" }; + }, + runOpenCodeAgentTurn: async () => { + calls.push("opencode"); + return { status: "COMPLETED", message: "opencode" }; + } + } + }, + async () => { + const result = await env.client.workflow.execute("codexAppServerTurnWorkflow", { + args: [turn({ providerId: "opencode" })], + taskQueue: TURN_QUEUE, + workflowId: "wf:turn:opencode" + }); + expect(result).toEqual({ status: "COMPLETED", message: "opencode" }); + } + ); + expect(calls).toEqual(["opencode"]); + }, 240_000); +}); diff --git a/apps/worker/tests/workflow-sandbox.test.ts b/apps/worker/tests/workflow-sandbox.test.ts new file mode 100644 index 000000000..1d864a101 --- /dev/null +++ b/apps/worker/tests/workflow-sandbox.test.ts @@ -0,0 +1,325 @@ +import { TestWorkflowEnvironment } from "@temporalio/testing"; +import { Worker, type WorkerOptions } from "@temporalio/worker"; +import { afterAll, beforeAll, describe, expect, it } from "vitest"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const SRC = path.resolve(__dirname, "../src"); + +/** + * Real Temporal workflow sandbox, time-skipping, stub activities. + * + * Production points each worker at a single workflow module + * (`worker.ts` -> workflows.js, `memory-mutation-worker.ts` -> + * memory-mutation-workflows.js, `memory-maintenance-worker.ts` -> + * memory-maintenance-workflows.js). These tests use the same modules, so the + * bundle under test is the bundle that ships. + */ +const TURN_QUEUE = "space-turns"; +const DOT_QUEUE = "space-dot-goals"; +const MAINTENANCE_QUEUE = "space-memory-maintenance"; +const MUTATION_QUEUE = "space-memory-mutations"; + +const WORKFLOWS = path.join(SRC, "workflows.ts"); +const MAINTENANCE = path.join(SRC, "memory-maintenance-workflows.ts"); +const MUTATION = path.join(SRC, "memory-mutation-workflows.ts"); + +let env: TestWorkflowEnvironment; + +beforeAll(async () => { + env = await TestWorkflowEnvironment.createTimeSkipping(); +}, 300_000); + +afterAll(async () => { + await env?.teardown(); +}); + +async function withWorker( + options: Pick, + run: () => Promise +): Promise { + const worker = await Worker.create({ connection: env.nativeConnection, ...options }); + await worker.runUntil(run()); +} + +function turn(overrides: Record = {}) { + return { + roomId: "room:test", + paneId: "pane:test", + prompt: "hello", + roomAgentOperatorRequest: "hello", + modelId: "gpt-5.6-sol", + reasoningEffort: "high", + agentSessionId: "session:test", + agentRunId: "agent_run:test", + agentUserMessageId: "agent_msg:user-test", + agentAssistantMessageId: "agent_msg:assistant-test", + traceId: "trace:test", + ...overrides + }; +} + +describe("memoryMutationWorkflow", () => { + it("returns the activity result unchanged", async () => { + await withWorker( + { + taskQueue: MUTATION_QUEUE, + workflowsPath: MUTATION, + activities: { + executePersistedMemoryChangeSet: async (input: { changeSetId: string }) => ({ + changeSetId: input.changeSetId, + applied: true + }) + } + }, + async () => { + const result = await env.client.workflow.execute("memoryMutationWorkflow", { + args: [{ changeSetId: "cs:1", traceId: "trace:1" }], + taskQueue: MUTATION_QUEUE, + workflowId: "wf:mutation:1" + }); + expect(result).toEqual({ changeSetId: "cs:1", applied: true }); + } + ); + }, 180_000); + + it("retries the activity three times before the workflow fails", async () => { + let attempts = 0; + await withWorker( + { + taskQueue: MUTATION_QUEUE, + workflowsPath: MUTATION, + activities: { + executePersistedMemoryChangeSet: async () => { + attempts += 1; + throw new Error("activity exploded"); + } + } + }, + async () => { + const failure = await env.client.workflow + .execute("memoryMutationWorkflow", { + args: [{ changeSetId: "cs:retry", traceId: "trace:retry" }], + taskQueue: MUTATION_QUEUE, + workflowId: "wf:mutation:retry" + }) + .then( + () => null, + (error: unknown) => error as { cause?: { cause?: { message?: string } } } + ); + expect(failure).not.toBeNull(); + // The activity message survives in the failure cause chain. + expect(JSON.stringify(failure?.cause ?? {})).toContain("activity exploded"); + } + ); + expect(attempts).toBe(3); + }, 240_000); +}); + +describe("memoryMaintenanceWorkflow", () => { + it("survives the two optional refreshes and runs consolidation as a child", async () => { + const calls: string[] = []; + await withWorker( + { + taskQueue: MAINTENANCE_QUEUE, + workflowsPath: MAINTENANCE, + activities: { + refreshMemoryGraphSnapshot: async () => { + calls.push("refresh"); + return { sourceHash: "hash:1" }; + }, + refreshAllMonthsMemoryGraphSnapshot: async () => { + calls.push("all-months"); + throw new Error("all months unavailable"); + }, + reconcileStaleSpaceAgentSessions: async () => { + calls.push("reconcile"); + throw new Error("reconcile unavailable"); + }, + createScheduledMemoryAudit: async () => { + calls.push("audit"); + return { id: "run:1", workflowId: "wf:consolidation:1" }; + }, + prepareMemoryConsolidation: async () => { + calls.push("prepare"); + return { run: { runId: "run:1" }, changeSetId: null }; + } + } + }, + async () => { + const result = await env.client.workflow.execute("memoryMaintenanceWorkflow", { + args: [{ traceId: "trace:maintenance" }], + taskQueue: MAINTENANCE_QUEUE, + workflowId: "wf:maintenance:1" + }); + expect(result).toEqual({ sourceHash: "hash:1" }); + } + ); + // Both optional failures are retried three times, then swallowed; the run + // still reaches the audit and starts the consolidation child. + expect(calls[0]).toBe("refresh"); + expect(calls.slice(1, 4)).toEqual(["all-months", "all-months", "all-months"]); + expect(calls.slice(4, 7)).toEqual(["reconcile", "reconcile", "reconcile"]); + expect(calls.slice(7)).toEqual(["audit", "prepare"]); + }, 240_000); +}); + +describe("dotGoalWorkflow", () => { + it("returns the goal status as soon as a step reports DONE", async () => { + await withWorker( + { + taskQueue: DOT_QUEUE, + workflowsPath: WORKFLOWS, + activities: { + dotGoalStep: async () => ({ state: "DONE", runId: null, goalStatus: "COMPLETED" }), + dotGoalObserve: async () => ({ run: "COMPLETED", goalStatus: "COMPLETED" }) + } + }, + async () => { + const result = await env.client.workflow.execute("dotGoalWorkflow", { + args: [{ goalId: "goal:1" }], + taskQueue: DOT_QUEUE, + workflowId: "wf:dot:done" + }); + expect(result).toEqual({ goalStatus: "COMPLETED" }); + } + ); + }, 180_000); + + it("ends a 30 second pause early when the wake signal arrives", async () => { + let steps = 0; + await withWorker( + { + taskQueue: DOT_QUEUE, + workflowsPath: WORKFLOWS, + activities: { + dotGoalStep: async () => { + steps += 1; + if (steps === 1) return { state: "WAITING", runId: null, goalStatus: "ACTIVE" }; + return { state: "DONE", runId: null, goalStatus: "COMPLETED" }; + }, + dotGoalObserve: async () => ({ run: "COMPLETED", goalStatus: "COMPLETED" }) + } + }, + async () => { + const handle = await env.client.workflow.start("dotGoalWorkflow", { + args: [{ goalId: "goal:wait" }], + taskQueue: DOT_QUEUE, + workflowId: "wf:dot:wait" + }); + await env.sleep("1 second"); + await handle.signal("dotGoalWakeSignal"); + expect(await handle.result()).toEqual({ goalStatus: "COMPLETED" }); + const { events } = await handle.fetchHistory(); + expect(events?.some((event) => event.timerFiredEventAttributes)).toBe(false); + } + ); + expect(steps).toBeGreaterThanOrEqual(2); + }, 180_000); + + it("polls a running step until the run settles, then finishes", async () => { + let observed = 0; + let stepCalls = 0; + await withWorker( + { + taskQueue: DOT_QUEUE, + workflowsPath: WORKFLOWS, + activities: { + dotGoalStep: async () => { + stepCalls += 1; + // First step hands out a run to poll; the second one closes the goal. + if (stepCalls === 1) return { state: "RUNNING", runId: "run:1", goalStatus: "ACTIVE" }; + return { state: "DONE", runId: null, goalStatus: "COMPLETED" }; + }, + dotGoalObserve: async () => { + observed += 1; + return observed < 3 + ? { run: "RUNNING", goalStatus: "ACTIVE" } + : { run: "COMPLETED", goalStatus: "COMPLETED" }; + } + } + }, + async () => { + const result = await env.client.workflow.execute("dotGoalWorkflow", { + args: [{ goalId: "goal:poll" }], + taskQueue: DOT_QUEUE, + workflowId: "wf:dot:poll" + }); + expect(result).toEqual({ goalStatus: "COMPLETED" }); + } + ); + expect(observed).toBe(3); + }, 180_000); + + it("continues as new while the goal is still unfinished, keeping the goal id", async () => { + // runDotGoalLoop gives up after its 300-iteration bound and returns a null + // status, which is the workflow's cue to continue as new rather than close. + let stepCalls = 0; + await withWorker( + { + taskQueue: DOT_QUEUE, + workflowsPath: WORKFLOWS, + activities: { + dotGoalStep: async () => { + stepCalls += 1; + // First run never settles; the carried run finishes, proving the + // goal id survived the continue-as-new boundary. + return stepCalls <= 300 + ? { state: "WAITING", runId: null, goalStatus: "ACTIVE" } + : { state: "DONE", runId: null, goalStatus: "COMPLETED" }; + }, + dotGoalObserve: async () => ({ run: "COMPLETED", goalStatus: "COMPLETED" }) + } + }, + async () => { + const handle = await env.client.workflow.start("dotGoalWorkflow", { + args: [{ goalId: "goal:continue" }], + taskQueue: DOT_QUEUE, + workflowId: "wf:dot:continue" + }); + const result = await handle.result(); + expect(result).toEqual({ goalStatus: "COMPLETED" }); + } + ); + // Each run is bounded at 300 loop iterations. More than that means the + // workflow closed its first run with continueAsNew and the carried goal id + // resumed in a second run, which then finished. + expect(stepCalls).toBeGreaterThan(300); + }, 300_000); +}); + +describe("dummyTurnWorkflow", () => { + it("records the start and the completion around one turn", async () => { + const calls: string[] = []; + await withWorker( + { + taskQueue: TURN_QUEUE, + workflowsPath: WORKFLOWS, + activities: { + recordDummyTurnStarted: async () => { + calls.push("started"); + return { workflowId: "wf:turn" }; + }, + recordDummyTurnCompleted: async () => { + calls.push("completed"); + return { + workflowId: "wf:turn", + roomId: "room:test", + paneId: "pane:test", + traceId: "trace:test" + }; + } + } + }, + async () => { + await env.client.workflow.execute("dummyTurnWorkflow", { + args: [turn()], + taskQueue: TURN_QUEUE, + workflowId: "wf:dummy-turn:1" + }); + } + ); + expect(calls).toEqual(["started", "completed"]); + }, 180_000); +}); diff --git a/package-lock.json b/package-lock.json index 1e2aa2a88..1dfde2758 100644 --- a/package-lock.json +++ b/package-lock.json @@ -192,6 +192,7 @@ "undici": "8.10.2" }, "devDependencies": { + "@temporalio/testing": "1.24.0", "typescript": "^5.9.3", "vitest": "^4.1.12" } @@ -3674,6 +3675,25 @@ "node": ">=12.0.0" } }, + "node_modules/@temporalio/testing": { + "version": "1.24.0", + "resolved": "https://registry.npmjs.org/@temporalio/testing/-/testing-1.24.0.tgz", + "integrity": "sha512-XKCuvgb7smKR3oXy5nDgwYSFX/hmg9zTJ2rZQQsVwJzFkga1TbnWDiuT5YMU0INKjoLhnM6c5YA/uLKlp8sdfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@temporalio/activity": "1.24.0", + "@temporalio/client": "1.24.0", + "@temporalio/common": "1.24.0", + "@temporalio/core-bridge": "1.24.0", + "@temporalio/proto": "1.24.0", + "@temporalio/worker": "1.24.0", + "@temporalio/workflow": "1.24.0" + }, + "engines": { + "node": ">= 20.3.0" + } + }, "node_modules/@temporalio/worker": { "version": "1.24.0", "resolved": "https://registry.npmjs.org/@temporalio/worker/-/worker-1.24.0.tgz",