diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 290739cb7..30afd5276 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -102,6 +102,38 @@ results and known failures are indexed there in `docs/quality/README.md`, and its `AGENTS.md` describes the guidance to read before changing session attachment, room retention, control, readiness, polling or deployment. +## Temporal workflow tests and the replay determinism gate + +`apps/worker` runs every Temporal workflow on the real time-skipping test +server (`@temporalio/testing`, `TestWorkflowEnvironment.createTimeSkipping`) +with fully stubbed activities; see `apps/worker/tests/temporal-sandbox/`. +These tests need no network, database, Docker, Temporal cluster or AI +provider: + +```sh +npm test -w @space/worker +``` + +The same directory contains the replay determinism gate +(`apps/worker/tests/temporal-sandbox/replay-gate.test.ts`). It replays the +synthetic workflow histories recorded under +`apps/worker/tests/fixtures/workflow-histories/` through +`Worker.runReplayHistories`, so a workflow change that is not replay +compatible fails CI with `DeterminismViolationError`. A negative test also +replays one history against a deliberately incompatible workflow variant +to prove the gate detects it. + +Re-record histories only when a workflow legitimately changes its command +sequence, for example after shipping a new `patched()` change: + +```sh +npm run temporal:record-histories -w @space/worker +``` + +Never re-record just to make a failing replay pass — that is exactly the +class of non-deterministic workflow change the gate exists to catch. The +fixtures README documents the rules. + Keep changes reviewable and include the exact commands and limitations of your verification. Never include credentials, private transcripts or local runtime state in a contribution. diff --git a/apps/worker/package.json b/apps/worker/package.json index ec58125fa..8b6cd914a 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": "RECORD_HISTORIES=1 vitest run tests/temporal-sandbox/record-histories.test.ts" }, "dependencies": { "@space/codex-app-server": "0.2.5", @@ -26,6 +27,8 @@ "undici": "8.12.3" }, "devDependencies": { + "@temporalio/proto": "1.24.0", + "@temporalio/testing": "1.24.0", "typescript": "^5.9.3", "vitest": "^4.1.12" }, 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..ddab92328 --- /dev/null +++ b/apps/worker/tests/fixtures/workflow-histories/README.md @@ -0,0 +1,34 @@ +# Workflow history fixtures (replay determinism gate) + +These are synthetic Temporal workflow histories recorded from the real +workflow code on the time-skipping test server with fully stubbed +activities. They contain no real rooms, missions or memory state. The +replay gate (`tests/temporal-sandbox/replay-gate.test.ts`) replays every +history through `Worker.runReplayHistories`, so any change to a workflow +that is not replay compatible fails CI with `DeterminismViolationError`. + +There must be at least one history per workflow plus one recorded from +the pre-patch supervisor branch (no `room-background-continuation-v1` +marker), which replays the legacy code path kept for old histories. The +pre-patch fixture is produced by running the production +`roomAgentSupervisorWorkflow` with Temporal's `patchActivationCallback` +forcing that patch off — not by copying workflow code. + +## When to re-record + +Re-record only when a workflow legitimately changes its command sequence, +for example after shipping a new `patched()` change. Run: + +```sh +npm run temporal:record-histories -w @space/worker +``` + +Never re-record just to make a failing replay pass — that is exactly the +class of non-deterministic workflow change this gate exists to catch. If +a replay fails, fix the workflow code (usually by guarding the change +with `patched()`) or record the pre-change history under a new fixture name +instead of overwriting the old one. The recording command writes only its +fixed fixture names. Before re-recording, copy the affected pre-change JSON +to the new name, then add that name to `EXPECTED_FIXTURES` in +`tests/temporal-sandbox/replay-gate.test.ts` and to the expected list in +`tests/temporal-sandbox/record-histories.test.ts`. 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..c6c0b7ced --- /dev/null +++ b/apps/worker/tests/fixtures/workflow-histories/codexAppServerTurnWorkflow.json @@ -0,0 +1,258 @@ +{ + "workflowId": "fixture-codex-turn", + "history": { + "events": [ + { + "eventId": "1", + "eventTime": { + "seconds": "2424028730", + "nanos": 620000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_EXECUTION_STARTED", + "workflowExecutionStartedEventAttributes": { + "workflowType": { + "name": "codexAppServerTurnWorkflow" + }, + "taskQueue": { + "name": "temporal-sandbox-tests", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJyb29tSWQiOiJyb29tLWZpeHR1cmUtMDEiLCJwYW5lSWQiOiJwYW5lOnJvb20tZml4dHVyZS0wMS1maXh0dXJlIiwicHJvbXB0IjoiU3ludGhldGljIHR1cm4gcHJvbXB0LiIsImFydGlmYWN0SWRzIjpbXSwicHJvdmlkZXJJZCI6bnVsbCwibW9kZWxJZCI6ImdwdC10ZXN0IiwicmVhc29uaW5nRWZmb3J0IjpudWxsLCJhZ2VudFNlc3Npb25JZCI6InNlc3Npb246cm9vbS1maXh0dXJlLTAxLWZpeHR1cmUiLCJhZ2VudFJ1bklkIjoiYWdlbnRfcnVuOnJvb20tZml4dHVyZS0wMS1maXh0dXJlIiwiYWdlbnRBc3Npc3RhbnRNZXNzYWdlSWQiOiJhZ2VudF9tc2c6YXNzaXN0YW50LXJvb20tZml4dHVyZS0wMS1maXh0dXJlIiwiY29sbGFib3JhdGlvbk1vZGUiOiJkZWZhdWx0IiwidHJhY2VJZCI6InRyYWNlOnJvb20tZml4dHVyZS0wMS1maXh0dXJlIiwicm9vbUFnZW50TWlzc2lvbklkIjoibWlzc2lvbi1maXh0dXJlLTAxIiwicm9vbUFnZW50T3BlcmF0b3JSZXF1ZXN0IjoiU3ludGhldGljIG9wZXJhdG9yIHJlcXVlc3QuIiwic2VsZWN0ZWRUb29sSWRzIjpbInJvb206aW5zcGVjdCJdfQ==" + } + ] + }, + "workflowExecutionTimeout": { + "seconds": "315360000" + }, + "workflowRunTimeout": { + "seconds": "315360000" + }, + "workflowTaskTimeout": { + "seconds": "10" + }, + "originalExecutionRunId": "2bd10397-54a6-4def-9422-6849ad6af5b4", + "identity": "25805@cursor", + "firstExecutionRunId": "2bd10397-54a6-4def-9422-6849ad6af5b4", + "attempt": 1, + "firstWorkflowTaskBackoff": {}, + "header": {} + } + }, + { + "eventId": "2", + "eventTime": { + "seconds": "2424028730", + "nanos": 620000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_SCHEDULED", + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "temporal-sandbox-tests", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "startToCloseTimeout": { + "seconds": "10" + }, + "attempt": 1 + } + }, + { + "eventId": "3", + "eventTime": { + "seconds": "2424028730", + "nanos": 620000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "2", + "identity": "25805@cursor", + "historySizeBytes": "845" + } + }, + { + "eventId": "4", + "eventTime": { + "seconds": "2424028730", + "nanos": 845000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "2", + "identity": "25805@cursor", + "binaryChecksum": "@temporalio/worker@1.24.0+9a365569848f3b7720675da78257957763d6c9652a5349034772dfe58fee989c", + "sdkMetadata": { + "coreUsedFlags": [ + 1, + 2, + 3 + ], + "sdkName": "temporal-typescript", + "sdkVersion": "1.24.0" + }, + "meteringMetadata": {} + } + }, + { + "eventId": "5", + "eventTime": { + "seconds": "2424028730", + "nanos": 845000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_SCHEDULED", + "activityTaskScheduledEventAttributes": { + "activityId": "1", + "activityType": { + "name": "runCodexAppServerTurn" + }, + "taskQueue": { + "name": "temporal-sandbox-tests", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "header": {}, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJyb29tSWQiOiJyb29tLWZpeHR1cmUtMDEiLCJwYW5lSWQiOiJwYW5lOnJvb20tZml4dHVyZS0wMS1maXh0dXJlIiwicHJvbXB0IjoiU3ludGhldGljIHR1cm4gcHJvbXB0LiIsImFydGlmYWN0SWRzIjpbXSwicHJvdmlkZXJJZCI6bnVsbCwibW9kZWxJZCI6ImdwdC10ZXN0IiwicmVhc29uaW5nRWZmb3J0IjpudWxsLCJhZ2VudFNlc3Npb25JZCI6InNlc3Npb246cm9vbS1maXh0dXJlLTAxLWZpeHR1cmUiLCJhZ2VudFJ1bklkIjoiYWdlbnRfcnVuOnJvb20tZml4dHVyZS0wMS1maXh0dXJlIiwiYWdlbnRBc3Npc3RhbnRNZXNzYWdlSWQiOiJhZ2VudF9tc2c6YXNzaXN0YW50LXJvb20tZml4dHVyZS0wMS1maXh0dXJlIiwiY29sbGFib3JhdGlvbk1vZGUiOiJkZWZhdWx0IiwidHJhY2VJZCI6InRyYWNlOnJvb20tZml4dHVyZS0wMS1maXh0dXJlIiwicm9vbUFnZW50TWlzc2lvbklkIjoibWlzc2lvbi1maXh0dXJlLTAxIiwicm9vbUFnZW50T3BlcmF0b3JSZXF1ZXN0IjoiU3ludGhldGljIG9wZXJhdG9yIHJlcXVlc3QuIiwic2VsZWN0ZWRUb29sSWRzIjpbInJvb206aW5zcGVjdCJdfQ==" + } + ] + }, + "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": "2424028730", + "nanos": 845000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_STARTED", + "activityTaskStartedEventAttributes": { + "scheduledEventId": "5", + "identity": "25805@cursor", + "attempt": 1 + } + }, + { + "eventId": "7", + "eventTime": { + "seconds": "2424028730", + "nanos": 847000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_COMPLETED", + "activityTaskCompletedEventAttributes": { + "result": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJ3b3JrZmxvd0lkIjoic3BhY2UtdHVybjp0cmFjZTpyb29tLWZpeHR1cmUtMDEtZml4dHVyZSIsInJvb21JZCI6InJvb20tZml4dHVyZS0wMSIsInBhbmVJZCI6InBhbmU6cm9vbS1maXh0dXJlLTAxLWZpeHR1cmUiLCJ0cmFjZUlkIjoidHJhY2U6cm9vbS1maXh0dXJlLTAxLWZpeHR1cmUiLCJzdGF0dXMiOiJDT01QTEVURUQiLCJtZXNzYWdlIjoiU3ludGhldGljIHR1cm4gY29tcGxldGVkLiIsInJvb21BZ2VudE91dGNvbWUiOnsic3RhdHVzIjoiVkVSSUZJRUQiLCJleGVjdXRlZEFjdGlvbkNvdW50IjoxLCJzdGF0dXNSZWFzb24iOiJTeW50aGV0aWMgcm9vbS1hZ2VudCB0dXJuIHZlcmlmaWVkLiJ9fQ==" + } + ] + }, + "scheduledEventId": "5", + "startedEventId": "6", + "identity": "25805@cursor" + } + }, + { + "eventId": "8", + "eventTime": { + "seconds": "2424028730", + "nanos": 847000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_SCHEDULED", + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "temporal-sandbox-tests", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "startToCloseTimeout": { + "seconds": "10" + }, + "attempt": 1 + } + }, + { + "eventId": "9", + "eventTime": { + "seconds": "2424028730", + "nanos": 847000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "8", + "identity": "25805@cursor", + "historySizeBytes": "2280" + } + }, + { + "eventId": "10", + "eventTime": { + "seconds": "2424028730", + "nanos": 852000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "8", + "identity": "25805@cursor", + "binaryChecksum": "@temporalio/worker@1.24.0+9a365569848f3b7720675da78257957763d6c9652a5349034772dfe58fee989c", + "sdkMetadata": {}, + "meteringMetadata": {} + } + }, + { + "eventId": "11", + "eventTime": { + "seconds": "2424028730", + "nanos": 852000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_EXECUTION_COMPLETED", + "workflowExecutionCompletedEventAttributes": { + "result": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJ3b3JrZmxvd0lkIjoic3BhY2UtdHVybjp0cmFjZTpyb29tLWZpeHR1cmUtMDEtZml4dHVyZSIsInJvb21JZCI6InJvb20tZml4dHVyZS0wMSIsInBhbmVJZCI6InBhbmU6cm9vbS1maXh0dXJlLTAxLWZpeHR1cmUiLCJ0cmFjZUlkIjoidHJhY2U6cm9vbS1maXh0dXJlLTAxLWZpeHR1cmUiLCJzdGF0dXMiOiJDT01QTEVURUQiLCJtZXNzYWdlIjoiU3ludGhldGljIHR1cm4gY29tcGxldGVkLiIsInJvb21BZ2VudE91dGNvbWUiOnsic3RhdHVzIjoiVkVSSUZJRUQiLCJleGVjdXRlZEFjdGlvbkNvdW50IjoxLCJzdGF0dXNSZWFzb24iOiJTeW50aGV0aWMgcm9vbS1hZ2VudCB0dXJuIHZlcmlmaWVkLiJ9fQ==" + } + ] + }, + "workflowTaskCompletedEventId": "9" + } + } + ] + } +} 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..6d55ecb92 --- /dev/null +++ b/apps/worker/tests/fixtures/workflow-histories/dotGoalWorkflow.json @@ -0,0 +1,480 @@ +{ + "workflowId": "fixture-dot-goal", + "history": { + "events": [ + { + "eventId": "1", + "eventTime": { + "seconds": "1793308729", + "nanos": 907000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_EXECUTION_STARTED", + "workflowExecutionStartedEventAttributes": { + "workflowType": { + "name": "dotGoalWorkflow" + }, + "taskQueue": { + "name": "temporal-sandbox-tests", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJnb2FsSWQiOiJnb2FsLWZpeHR1cmUtMDEifQ==" + } + ] + }, + "workflowExecutionTimeout": { + "seconds": "315360000" + }, + "workflowRunTimeout": { + "seconds": "315360000" + }, + "workflowTaskTimeout": { + "seconds": "10" + }, + "originalExecutionRunId": "9a95612e-ea77-4982-b54f-c075b00dd9ed", + "identity": "25805@cursor", + "firstExecutionRunId": "9a95612e-ea77-4982-b54f-c075b00dd9ed", + "attempt": 1, + "firstWorkflowTaskBackoff": {}, + "header": {} + } + }, + { + "eventId": "2", + "eventTime": { + "seconds": "1793308729", + "nanos": 907000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_SCHEDULED", + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "temporal-sandbox-tests", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "startToCloseTimeout": { + "seconds": "10" + }, + "attempt": 1 + } + }, + { + "eventId": "3", + "eventTime": { + "seconds": "1793308729", + "nanos": 908000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "2", + "identity": "25805@cursor", + "historySizeBytes": "300" + } + }, + { + "eventId": "4", + "eventTime": { + "seconds": "1793308730", + "nanos": 288000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "2", + "identity": "25805@cursor", + "binaryChecksum": "@temporalio/worker@1.24.0+9a365569848f3b7720675da78257957763d6c9652a5349034772dfe58fee989c", + "sdkMetadata": { + "coreUsedFlags": [ + 1, + 3, + 2 + ], + "sdkName": "temporal-typescript", + "sdkVersion": "1.24.0" + }, + "meteringMetadata": {} + } + }, + { + "eventId": "5", + "eventTime": { + "seconds": "1793308730", + "nanos": 288000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_SCHEDULED", + "activityTaskScheduledEventAttributes": { + "activityId": "1", + "activityType": { + "name": "dotGoalStep" + }, + "taskQueue": { + "name": "temporal-sandbox-tests", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "header": {}, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "ImdvYWwtZml4dHVyZS0wMSI=" + } + ] + }, + "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": "1793308730", + "nanos": 288000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_STARTED", + "activityTaskStartedEventAttributes": { + "scheduledEventId": "5", + "identity": "25805@cursor", + "attempt": 1 + } + }, + { + "eventId": "7", + "eventTime": { + "seconds": "1793308730", + "nanos": 297000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_COMPLETED", + "activityTaskCompletedEventAttributes": { + "result": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJzdGF0ZSI6IldBSVRJTkciLCJydW5JZCI6bnVsbCwiZ29hbFN0YXR1cyI6IlVOS05PV04ifQ==" + } + ] + }, + "scheduledEventId": "5", + "startedEventId": "6", + "identity": "25805@cursor" + } + }, + { + "eventId": "8", + "eventTime": { + "seconds": "1793308730", + "nanos": 297000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_SCHEDULED", + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "temporal-sandbox-tests", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "startToCloseTimeout": { + "seconds": "10" + }, + "attempt": 1 + } + }, + { + "eventId": "9", + "eventTime": { + "seconds": "1793308730", + "nanos": 297000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "8", + "identity": "25805@cursor", + "historySizeBytes": "884" + } + }, + { + "eventId": "10", + "eventTime": { + "seconds": "1793308730", + "nanos": 306000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "8", + "identity": "25805@cursor", + "binaryChecksum": "@temporalio/worker@1.24.0+9a365569848f3b7720675da78257957763d6c9652a5349034772dfe58fee989c", + "sdkMetadata": {}, + "meteringMetadata": {} + } + }, + { + "eventId": "11", + "eventTime": { + "seconds": "1793308730", + "nanos": 306000000 + }, + "eventType": "EVENT_TYPE_TIMER_STARTED", + "timerStartedEventAttributes": { + "timerId": "1", + "startToFireTimeout": { + "seconds": "30" + }, + "workflowTaskCompletedEventId": "9" + } + }, + { + "eventId": "12", + "eventTime": { + "seconds": "1793308730", + "nanos": 306000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_EXECUTION_SIGNALED", + "workflowExecutionSignaledEventAttributes": { + "signalName": "dotGoalWake", + "input": {}, + "identity": "25805@cursor", + "header": {} + } + }, + { + "eventId": "13", + "eventTime": { + "seconds": "1793308730", + "nanos": 306000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_SCHEDULED", + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "temporal-sandbox-tests", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "startToCloseTimeout": { + "seconds": "10" + }, + "attempt": 2 + } + }, + { + "eventId": "14", + "eventTime": { + "seconds": "1793308730", + "nanos": 306000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "13", + "identity": "25805@cursor", + "historySizeBytes": "1191" + } + }, + { + "eventId": "15", + "eventTime": { + "seconds": "1793308730", + "nanos": 313000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "13", + "identity": "25805@cursor", + "binaryChecksum": "@temporalio/worker@1.24.0+9a365569848f3b7720675da78257957763d6c9652a5349034772dfe58fee989c", + "sdkMetadata": { + "langUsedFlags": [ + 2 + ] + }, + "meteringMetadata": {} + } + }, + { + "eventId": "16", + "eventTime": { + "seconds": "1793308730", + "nanos": 313000000 + }, + "eventType": "EVENT_TYPE_TIMER_CANCELED", + "timerCanceledEventAttributes": { + "timerId": "1", + "startedEventId": "11", + "workflowTaskCompletedEventId": "14" + } + }, + { + "eventId": "17", + "eventTime": { + "seconds": "1793308730", + "nanos": 313000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_SCHEDULED", + "activityTaskScheduledEventAttributes": { + "activityId": "2", + "activityType": { + "name": "dotGoalStep" + }, + "taskQueue": { + "name": "temporal-sandbox-tests", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "header": {}, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "ImdvYWwtZml4dHVyZS0wMSI=" + } + ] + }, + "scheduleToCloseTimeout": { + "seconds": "315360000" + }, + "scheduleToStartTimeout": { + "seconds": "315360000" + }, + "startToCloseTimeout": { + "seconds": "120" + }, + "heartbeatTimeout": {}, + "workflowTaskCompletedEventId": "14", + "retryPolicy": { + "initialInterval": { + "seconds": "5" + }, + "backoffCoefficient": 2, + "maximumInterval": { + "seconds": "120" + }, + "maximumAttempts": 20 + } + } + }, + { + "eventId": "18", + "eventTime": { + "seconds": "1793308730", + "nanos": 313000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_STARTED", + "activityTaskStartedEventAttributes": { + "scheduledEventId": "17", + "identity": "25805@cursor", + "attempt": 1 + } + }, + { + "eventId": "19", + "eventTime": { + "seconds": "1793308730", + "nanos": 315000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_COMPLETED", + "activityTaskCompletedEventAttributes": { + "result": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJzdGF0ZSI6IkRPTkUiLCJydW5JZCI6bnVsbCwiZ29hbFN0YXR1cyI6IkNPTVBMRVRFRCJ9" + } + ] + }, + "scheduledEventId": "17", + "startedEventId": "18", + "identity": "25805@cursor" + } + }, + { + "eventId": "20", + "eventTime": { + "seconds": "1793308730", + "nanos": 315000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_SCHEDULED", + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "temporal-sandbox-tests", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "startToCloseTimeout": { + "seconds": "10" + }, + "attempt": 1 + } + }, + { + "eventId": "21", + "eventTime": { + "seconds": "1793308730", + "nanos": 315000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "20", + "identity": "25805@cursor", + "historySizeBytes": "1770" + } + }, + { + "eventId": "22", + "eventTime": { + "seconds": "1793308730", + "nanos": 319000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "20", + "identity": "25805@cursor", + "binaryChecksum": "@temporalio/worker@1.24.0+9a365569848f3b7720675da78257957763d6c9652a5349034772dfe58fee989c", + "sdkMetadata": {}, + "meteringMetadata": {} + } + }, + { + "eventId": "23", + "eventTime": { + "seconds": "1793308730", + "nanos": 319000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_EXECUTION_COMPLETED", + "workflowExecutionCompletedEventAttributes": { + "result": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJnb2FsU3RhdHVzIjoiQ09NUExFVEVEIn0=" + } + ] + }, + "workflowTaskCompletedEventId": "21" + } + } + ] + } +} 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..07f31d819 --- /dev/null +++ b/apps/worker/tests/fixtures/workflow-histories/dummyTurnWorkflow.json @@ -0,0 +1,388 @@ +{ + "workflowId": "fixture-dummy-turn", + "history": { + "events": [ + { + "eventId": "1", + "eventTime": { + "seconds": "2108668730", + "nanos": 335000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_EXECUTION_STARTED", + "workflowExecutionStartedEventAttributes": { + "workflowType": { + "name": "dummyTurnWorkflow" + }, + "taskQueue": { + "name": "temporal-sandbox-tests", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJyb29tSWQiOiJyb29tLWZpeHR1cmUtMDEiLCJwYW5lSWQiOiJwYW5lOnJvb20tZml4dHVyZS0wMS1kdW1teSIsInByb21wdCI6IlN5bnRoZXRpYyB0dXJuIHByb21wdC4iLCJhcnRpZmFjdElkcyI6W10sInByb3ZpZGVySWQiOm51bGwsIm1vZGVsSWQiOiJncHQtdGVzdCIsInJlYXNvbmluZ0VmZm9ydCI6bnVsbCwiYWdlbnRTZXNzaW9uSWQiOiJzZXNzaW9uOnJvb20tZml4dHVyZS0wMS1kdW1teSIsImFnZW50UnVuSWQiOiJhZ2VudF9ydW46cm9vbS1maXh0dXJlLTAxLWR1bW15IiwiYWdlbnRBc3Npc3RhbnRNZXNzYWdlSWQiOiJhZ2VudF9tc2c6YXNzaXN0YW50LXJvb20tZml4dHVyZS0wMS1kdW1teSIsImNvbGxhYm9yYXRpb25Nb2RlIjoiZGVmYXVsdCIsInRyYWNlSWQiOiJ0cmFjZTpyb29tLWZpeHR1cmUtMDEtZHVtbXkifQ==" + } + ] + }, + "workflowExecutionTimeout": { + "seconds": "315360000" + }, + "workflowRunTimeout": { + "seconds": "315360000" + }, + "workflowTaskTimeout": { + "seconds": "10" + }, + "originalExecutionRunId": "513b068c-60c1-46e2-8b87-11ddf957fd89", + "identity": "25805@cursor", + "firstExecutionRunId": "513b068c-60c1-46e2-8b87-11ddf957fd89", + "attempt": 1, + "firstWorkflowTaskBackoff": {}, + "header": {} + } + }, + { + "eventId": "2", + "eventTime": { + "seconds": "2108668730", + "nanos": 335000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_SCHEDULED", + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "temporal-sandbox-tests", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "startToCloseTimeout": { + "seconds": "10" + }, + "attempt": 1 + } + }, + { + "eventId": "3", + "eventTime": { + "seconds": "2108668730", + "nanos": 335000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "2", + "identity": "25805@cursor", + "historySizeBytes": "692" + } + }, + { + "eventId": "4", + "eventTime": { + "seconds": "2108668730", + "nanos": 586000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "2", + "identity": "25805@cursor", + "binaryChecksum": "@temporalio/worker@1.24.0+9a365569848f3b7720675da78257957763d6c9652a5349034772dfe58fee989c", + "sdkMetadata": { + "coreUsedFlags": [ + 1, + 2, + 3 + ], + "sdkName": "temporal-typescript", + "sdkVersion": "1.24.0" + }, + "meteringMetadata": {} + } + }, + { + "eventId": "5", + "eventTime": { + "seconds": "2108668730", + "nanos": 586000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_SCHEDULED", + "activityTaskScheduledEventAttributes": { + "activityId": "1", + "activityType": { + "name": "recordDummyTurnStarted" + }, + "taskQueue": { + "name": "temporal-sandbox-tests", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "header": {}, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJyb29tSWQiOiJyb29tLWZpeHR1cmUtMDEiLCJwYW5lSWQiOiJwYW5lOnJvb20tZml4dHVyZS0wMS1kdW1teSIsInByb21wdCI6IlN5bnRoZXRpYyB0dXJuIHByb21wdC4iLCJhcnRpZmFjdElkcyI6W10sInByb3ZpZGVySWQiOm51bGwsIm1vZGVsSWQiOiJncHQtdGVzdCIsInJlYXNvbmluZ0VmZm9ydCI6bnVsbCwiYWdlbnRTZXNzaW9uSWQiOiJzZXNzaW9uOnJvb20tZml4dHVyZS0wMS1kdW1teSIsImFnZW50UnVuSWQiOiJhZ2VudF9ydW46cm9vbS1maXh0dXJlLTAxLWR1bW15IiwiYWdlbnRBc3Npc3RhbnRNZXNzYWdlSWQiOiJhZ2VudF9tc2c6YXNzaXN0YW50LXJvb20tZml4dHVyZS0wMS1kdW1teSIsImNvbGxhYm9yYXRpb25Nb2RlIjoiZGVmYXVsdCIsInRyYWNlSWQiOiJ0cmFjZTpyb29tLWZpeHR1cmUtMDEtZHVtbXkifQ==" + } + ] + }, + "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": "2108668730", + "nanos": 587000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_STARTED", + "activityTaskStartedEventAttributes": { + "scheduledEventId": "5", + "identity": "25805@cursor", + "attempt": 1 + } + }, + { + "eventId": "7", + "eventTime": { + "seconds": "2108668730", + "nanos": 589000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_COMPLETED", + "activityTaskCompletedEventAttributes": { + "result": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJ3b3JrZmxvd0lkIjoid29ya2Zsb3c6ZHVtbXk6cm9vbS1maXh0dXJlLTAxOnBhbmU6cm9vbS1maXh0dXJlLTAxLWR1bW15OnRyYWNlOnJvb20tZml4dHVyZS0wMS1kdW1teSIsInJvb21JZCI6InJvb20tZml4dHVyZS0wMSIsInBhbmVJZCI6InBhbmU6cm9vbS1maXh0dXJlLTAxLWR1bW15IiwidHJhY2VJZCI6InRyYWNlOnJvb20tZml4dHVyZS0wMS1kdW1teSIsInN0YXR1cyI6IkNPTVBMRVRFRCIsIm1lc3NhZ2UiOiJTeW50aGV0aWMgZHVtbXkgdHVybiBjb21wbGV0ZWQuIn0=" + } + ] + }, + "scheduledEventId": "5", + "startedEventId": "6", + "identity": "25805@cursor" + } + }, + { + "eventId": "8", + "eventTime": { + "seconds": "2108668730", + "nanos": 589000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_SCHEDULED", + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "temporal-sandbox-tests", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "startToCloseTimeout": { + "seconds": "10" + }, + "attempt": 1 + } + }, + { + "eventId": "9", + "eventTime": { + "seconds": "2108668730", + "nanos": 589000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "8", + "identity": "25805@cursor", + "historySizeBytes": "1909" + } + }, + { + "eventId": "10", + "eventTime": { + "seconds": "2108668730", + "nanos": 615000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "8", + "identity": "25805@cursor", + "binaryChecksum": "@temporalio/worker@1.24.0+9a365569848f3b7720675da78257957763d6c9652a5349034772dfe58fee989c", + "sdkMetadata": {}, + "meteringMetadata": {} + } + }, + { + "eventId": "11", + "eventTime": { + "seconds": "2108668730", + "nanos": 615000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_SCHEDULED", + "activityTaskScheduledEventAttributes": { + "activityId": "2", + "activityType": { + "name": "recordDummyTurnCompleted" + }, + "taskQueue": { + "name": "temporal-sandbox-tests", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "header": {}, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJyb29tSWQiOiJyb29tLWZpeHR1cmUtMDEiLCJwYW5lSWQiOiJwYW5lOnJvb20tZml4dHVyZS0wMS1kdW1teSIsInByb21wdCI6IlN5bnRoZXRpYyB0dXJuIHByb21wdC4iLCJhcnRpZmFjdElkcyI6W10sInByb3ZpZGVySWQiOm51bGwsIm1vZGVsSWQiOiJncHQtdGVzdCIsInJlYXNvbmluZ0VmZm9ydCI6bnVsbCwiYWdlbnRTZXNzaW9uSWQiOiJzZXNzaW9uOnJvb20tZml4dHVyZS0wMS1kdW1teSIsImFnZW50UnVuSWQiOiJhZ2VudF9ydW46cm9vbS1maXh0dXJlLTAxLWR1bW15IiwiYWdlbnRBc3Npc3RhbnRNZXNzYWdlSWQiOiJhZ2VudF9tc2c6YXNzaXN0YW50LXJvb20tZml4dHVyZS0wMS1kdW1teSIsImNvbGxhYm9yYXRpb25Nb2RlIjoiZGVmYXVsdCIsInRyYWNlSWQiOiJ0cmFjZTpyb29tLWZpeHR1cmUtMDEtZHVtbXkifQ==" + } + ] + }, + "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": "2108668730", + "nanos": 615000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_STARTED", + "activityTaskStartedEventAttributes": { + "scheduledEventId": "11", + "identity": "25805@cursor", + "attempt": 1 + } + }, + { + "eventId": "13", + "eventTime": { + "seconds": "2108668730", + "nanos": 617000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_COMPLETED", + "activityTaskCompletedEventAttributes": { + "result": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJ3b3JrZmxvd0lkIjoid29ya2Zsb3c6ZHVtbXk6cm9vbS1maXh0dXJlLTAxOnBhbmU6cm9vbS1maXh0dXJlLTAxLWR1bW15OnRyYWNlOnJvb20tZml4dHVyZS0wMS1kdW1teSIsInJvb21JZCI6InJvb20tZml4dHVyZS0wMSIsInBhbmVJZCI6InBhbmU6cm9vbS1maXh0dXJlLTAxLWR1bW15IiwidHJhY2VJZCI6InRyYWNlOnJvb20tZml4dHVyZS0wMS1kdW1teSIsInN0YXR1cyI6IkNPTVBMRVRFRCIsIm1lc3NhZ2UiOiJTeW50aGV0aWMgZHVtbXkgdHVybiBjb21wbGV0ZWQuIn0=" + } + ] + }, + "scheduledEventId": "11", + "startedEventId": "12", + "identity": "25805@cursor" + } + }, + { + "eventId": "14", + "eventTime": { + "seconds": "2108668730", + "nanos": 617000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_SCHEDULED", + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "temporal-sandbox-tests", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "startToCloseTimeout": { + "seconds": "10" + }, + "attempt": 1 + } + }, + { + "eventId": "15", + "eventTime": { + "seconds": "2108668730", + "nanos": 617000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "14", + "identity": "25805@cursor", + "historySizeBytes": "3093" + } + }, + { + "eventId": "16", + "eventTime": { + "seconds": "2108668730", + "nanos": 620000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "14", + "identity": "25805@cursor", + "binaryChecksum": "@temporalio/worker@1.24.0+9a365569848f3b7720675da78257957763d6c9652a5349034772dfe58fee989c", + "sdkMetadata": {}, + "meteringMetadata": {} + } + }, + { + "eventId": "17", + "eventTime": { + "seconds": "2108668730", + "nanos": 620000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_EXECUTION_COMPLETED", + "workflowExecutionCompletedEventAttributes": { + "result": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJ3b3JrZmxvd0lkIjoid29ya2Zsb3c6ZHVtbXk6cm9vbS1maXh0dXJlLTAxOnBhbmU6cm9vbS1maXh0dXJlLTAxLWR1bW15OnRyYWNlOnJvb20tZml4dHVyZS0wMS1kdW1teSIsInJvb21JZCI6InJvb20tZml4dHVyZS0wMSIsInBhbmVJZCI6InBhbmU6cm9vbS1maXh0dXJlLTAxLWR1bW15IiwidHJhY2VJZCI6InRyYWNlOnJvb20tZml4dHVyZS0wMS1kdW1teSIsInN0YXR1cyI6IkNPTVBMRVRFRCIsIm1lc3NhZ2UiOiJTeW50aGV0aWMgZHVtbXkgdHVybiBjb21wbGV0ZWQuIn0=" + } + ] + }, + "workflowTaskCompletedEventId": "15" + } + } + ] + } +} 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..6b178afe0 --- /dev/null +++ b/apps/worker/tests/fixtures/workflow-histories/memoryConsolidationWorkflow.json @@ -0,0 +1,520 @@ +{ + "workflowId": "fixture-memory-consolidation", + "history": { + "events": [ + { + "eventId": "1", + "eventTime": { + "seconds": "3685468762", + "nanos": 912000000 + }, + "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": "eyJydW5JZCI6InJ1bi10cmFjZTpmaXh0dXJlLW1lbW9yeSIsInRyYWNlSWQiOiJ0cmFjZTpmaXh0dXJlLW1lbW9yeSJ9" + } + ] + }, + "workflowExecutionTimeout": { + "seconds": "315360000" + }, + "workflowRunTimeout": { + "seconds": "315360000" + }, + "workflowTaskTimeout": { + "seconds": "10" + }, + "originalExecutionRunId": "09662c08-b542-4a2f-b284-e595825ce31a", + "identity": "25805@cursor", + "firstExecutionRunId": "09662c08-b542-4a2f-b284-e595825ce31a", + "attempt": 1, + "firstWorkflowTaskBackoff": {}, + "header": {} + } + }, + { + "eventId": "2", + "eventTime": { + "seconds": "3685468762", + "nanos": 912000000 + }, + "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": "3685468762", + "nanos": 913000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "2", + "identity": "25805@cursor", + "historySizeBytes": "357" + } + }, + { + "eventId": "4", + "eventTime": { + "seconds": "3685468763", + "nanos": 130000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "2", + "identity": "25805@cursor", + "binaryChecksum": "@temporalio/worker@1.24.0+9a365569848f3b7720675da78257957763d6c9652a5349034772dfe58fee989c", + "sdkMetadata": { + "coreUsedFlags": [ + 1, + 2, + 3 + ], + "sdkName": "temporal-typescript", + "sdkVersion": "1.24.0" + }, + "meteringMetadata": {} + } + }, + { + "eventId": "5", + "eventTime": { + "seconds": "3685468763", + "nanos": 130000000 + }, + "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": "eyJydW5JZCI6InJ1bi10cmFjZTpmaXh0dXJlLW1lbW9yeSIsInRyYWNlSWQiOiJ0cmFjZTpmaXh0dXJlLW1lbW9yeSJ9" + } + ] + }, + "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": "3685468763", + "nanos": 130000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_STARTED", + "activityTaskStartedEventAttributes": { + "scheduledEventId": "5", + "identity": "25805@cursor", + "attempt": 1 + } + }, + { + "eventId": "7", + "eventTime": { + "seconds": "3685468763", + "nanos": 132000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_COMPLETED", + "activityTaskCompletedEventAttributes": { + "result": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJydW4iOnsiaWQiOiJydW4tdHJhY2U6Zml4dHVyZS1tZW1vcnkiLCJtb2RlIjoiQVVESVQiLCJ0cmlnZ2VyS2luZCI6IlNDSEVEVUxFRCIsInN0YXR1cyI6IlNVQ0NFRURFRCIsIndvcmtmbG93SWQiOiJzcGFjZS1tZW1vcnktY29uc29saWRhdGlvbjpydW4tdHJhY2U6Zml4dHVyZS1tZW1vcnkiLCJkZWR1cGVLZXkiOiJzY2hlZHVsZWQ6cnVuLXRyYWNlOmZpeHR1cmUtbWVtb3J5Iiwic291cmNlSGFzaCI6bnVsbCwiYWN0b3JVc2VySWQiOm51bGwsInByb2dyZXNzQ29tcGxldGVkIjoxLCJwcm9ncmVzc1RvdGFsIjoxLCJmaW5kaW5nQ291bnQiOjAsImFwcGxpZWRPcGVyYXRpb25Db3VudCI6MCwic2tpcHBlZE9wZXJhdGlvbkNvdW50IjowLCJmYWlsZWRPcGVyYXRpb25Db3VudCI6MCwibWV0cmljcyI6e30sIm1vZGVsSWQiOm51bGwsImFpVmVyaWZpZWQiOmZhbHNlLCJhaUV2aWRlbmNlIjp7fSwic3RhdHVzUmVhc29uIjpudWxsLCJjcmVhdGVkQXQiOiIyMDI2LTAxLTAxVDAwOjAwOjAwLjAwMFoiLCJzdGFydGVkQXQiOiIyMDI2LTAxLTAxVDAwOjAwOjAwLjAwMFoiLCJjb21wbGV0ZWRBdCI6IjIwMjYtMDEtMDFUMDA6MDA6MDAuMDAwWiIsInVwZGF0ZWRBdCI6IjIwMjYtMDEtMDFUMDA6MDA6MDAuMDAwWiJ9LCJjaGFuZ2VTZXRJZCI6ImNoYW5nZXNldC0wMDAxIn0=" + } + ] + }, + "scheduledEventId": "5", + "startedEventId": "6", + "identity": "25805@cursor" + } + }, + { + "eventId": "8", + "eventTime": { + "seconds": "3685468763", + "nanos": 132000000 + }, + "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": "3685468763", + "nanos": 132000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "8", + "identity": "25805@cursor", + "historySizeBytes": "1619" + } + }, + { + "eventId": "10", + "eventTime": { + "seconds": "3685468763", + "nanos": 134000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "8", + "identity": "25805@cursor", + "binaryChecksum": "@temporalio/worker@1.24.0+9a365569848f3b7720675da78257957763d6c9652a5349034772dfe58fee989c", + "sdkMetadata": {}, + "meteringMetadata": {} + } + }, + { + "eventId": "11", + "eventTime": { + "seconds": "3685468763", + "nanos": 134000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_SCHEDULED", + "activityTaskScheduledEventAttributes": { + "activityId": "2", + "activityType": { + "name": "executePersistedMemoryChangeSet" + }, + "taskQueue": { + "name": "space-memory-mutations", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "header": {}, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJjaGFuZ2VTZXRJZCI6ImNoYW5nZXNldC0wMDAxIiwidHJhY2VJZCI6InRyYWNlOmZpeHR1cmUtbWVtb3J5In0=" + } + ] + }, + "scheduleToCloseTimeout": { + "seconds": "315360000" + }, + "scheduleToStartTimeout": { + "seconds": "315360000" + }, + "startToCloseTimeout": { + "seconds": "900" + }, + "heartbeatTimeout": {}, + "workflowTaskCompletedEventId": "9", + "retryPolicy": { + "initialInterval": { + "seconds": "1" + }, + "backoffCoefficient": 2, + "maximumInterval": { + "seconds": "100" + }, + "maximumAttempts": 3 + } + } + }, + { + "eventId": "12", + "eventTime": { + "seconds": "3685468763", + "nanos": 135000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_STARTED", + "activityTaskStartedEventAttributes": { + "scheduledEventId": "11", + "identity": "25805@cursor", + "attempt": 1 + } + }, + { + "eventId": "13", + "eventTime": { + "seconds": "3685468763", + "nanos": 136000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_COMPLETED", + "activityTaskCompletedEventAttributes": { + "result": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJzdGF0dXMiOiJBUFBMSUVEIiwiY2hhbmdlU2V0SWQiOiJjaGFuZ2VzZXQtMDAwMSIsInJlc3VsdGluZ1NvdXJjZUhhc2giOiIwMTIzNDU2Nzg5YWJjZGVmMDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWYwMTIzNDU2Nzg5YWJjZGVmIiwicmVhc29uIjpudWxsfQ==" + } + ] + }, + "scheduledEventId": "11", + "startedEventId": "12", + "identity": "25805@cursor" + } + }, + { + "eventId": "14", + "eventTime": { + "seconds": "3685468763", + "nanos": 136000000 + }, + "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": "3685468763", + "nanos": 136000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "14", + "identity": "25805@cursor", + "historySizeBytes": "2336" + } + }, + { + "eventId": "16", + "eventTime": { + "seconds": "3685468763", + "nanos": 138000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "14", + "identity": "25805@cursor", + "binaryChecksum": "@temporalio/worker@1.24.0+9a365569848f3b7720675da78257957763d6c9652a5349034772dfe58fee989c", + "sdkMetadata": {}, + "meteringMetadata": {} + } + }, + { + "eventId": "17", + "eventTime": { + "seconds": "3685468763", + "nanos": 138000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_SCHEDULED", + "activityTaskScheduledEventAttributes": { + "activityId": "3", + "activityType": { + "name": "finalizeMemoryRepair" + }, + "taskQueue": { + "name": "space-memory-maintenance", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "header": {}, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJydW5JZCI6InJ1bi10cmFjZTpmaXh0dXJlLW1lbW9yeSIsImNoYW5nZVNldElkIjoiY2hhbmdlc2V0LTAwMDEiLCJtdXRhdGlvblJlc3VsdCI6eyJzdGF0dXMiOiJBUFBMSUVEIiwiY2hhbmdlU2V0SWQiOiJjaGFuZ2VzZXQtMDAwMSIsInJlc3VsdGluZ1NvdXJjZUhhc2giOiIwMTIzNDU2Nzg5YWJjZGVmMDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWYwMTIzNDU2Nzg5YWJjZGVmIiwicmVhc29uIjpudWxsfX0=" + } + ] + }, + "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": "3685468763", + "nanos": 138000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_STARTED", + "activityTaskStartedEventAttributes": { + "scheduledEventId": "17", + "identity": "25805@cursor", + "attempt": 1 + } + }, + { + "eventId": "19", + "eventTime": { + "seconds": "3685468763", + "nanos": 139000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_COMPLETED", + "activityTaskCompletedEventAttributes": { + "result": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJpZCI6InJ1bi10cmFjZTpmaXh0dXJlLW1lbW9yeSIsIm1vZGUiOiJBVURJVCIsInRyaWdnZXJLaW5kIjoiU0NIRURVTEVEIiwic3RhdHVzIjoiU1VDQ0VFREVEIiwid29ya2Zsb3dJZCI6InNwYWNlLW1lbW9yeS1jb25zb2xpZGF0aW9uOnJ1bi10cmFjZTpmaXh0dXJlLW1lbW9yeSIsImRlZHVwZUtleSI6InNjaGVkdWxlZDpydW4tdHJhY2U6Zml4dHVyZS1tZW1vcnkiLCJzb3VyY2VIYXNoIjpudWxsLCJhY3RvclVzZXJJZCI6bnVsbCwicHJvZ3Jlc3NDb21wbGV0ZWQiOjEsInByb2dyZXNzVG90YWwiOjEsImZpbmRpbmdDb3VudCI6MCwiYXBwbGllZE9wZXJhdGlvbkNvdW50IjowLCJza2lwcGVkT3BlcmF0aW9uQ291bnQiOjAsImZhaWxlZE9wZXJhdGlvbkNvdW50IjowLCJtZXRyaWNzIjp7fSwibW9kZWxJZCI6bnVsbCwiYWlWZXJpZmllZCI6ZmFsc2UsImFpRXZpZGVuY2UiOnt9LCJzdGF0dXNSZWFzb24iOm51bGwsImNyZWF0ZWRBdCI6IjIwMjYtMDEtMDFUMDA6MDA6MDAuMDAwWiIsInN0YXJ0ZWRBdCI6IjIwMjYtMDEtMDFUMDA6MDA6MDAuMDAwWiIsImNvbXBsZXRlZEF0IjoiMjAyNi0wMS0wMVQwMDowMDowMC4wMDBaIiwidXBkYXRlZEF0IjoiMjAyNi0wMS0wMVQwMDowMDowMC4wMDBaIn0=" + } + ] + }, + "scheduledEventId": "17", + "startedEventId": "18", + "identity": "25805@cursor" + } + }, + { + "eventId": "20", + "eventTime": { + "seconds": "3685468763", + "nanos": 139000000 + }, + "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": "3685468763", + "nanos": 139000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "20", + "identity": "25805@cursor", + "historySizeBytes": "3690" + } + }, + { + "eventId": "22", + "eventTime": { + "seconds": "3685468763", + "nanos": 142000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "20", + "identity": "25805@cursor", + "binaryChecksum": "@temporalio/worker@1.24.0+9a365569848f3b7720675da78257957763d6c9652a5349034772dfe58fee989c", + "sdkMetadata": {}, + "meteringMetadata": {} + } + }, + { + "eventId": "23", + "eventTime": { + "seconds": "3685468763", + "nanos": 142000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_EXECUTION_COMPLETED", + "workflowExecutionCompletedEventAttributes": { + "result": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJpZCI6InJ1bi10cmFjZTpmaXh0dXJlLW1lbW9yeSIsIm1vZGUiOiJBVURJVCIsInRyaWdnZXJLaW5kIjoiU0NIRURVTEVEIiwic3RhdHVzIjoiU1VDQ0VFREVEIiwid29ya2Zsb3dJZCI6InNwYWNlLW1lbW9yeS1jb25zb2xpZGF0aW9uOnJ1bi10cmFjZTpmaXh0dXJlLW1lbW9yeSIsImRlZHVwZUtleSI6InNjaGVkdWxlZDpydW4tdHJhY2U6Zml4dHVyZS1tZW1vcnkiLCJzb3VyY2VIYXNoIjpudWxsLCJhY3RvclVzZXJJZCI6bnVsbCwicHJvZ3Jlc3NDb21wbGV0ZWQiOjEsInByb2dyZXNzVG90YWwiOjEsImZpbmRpbmdDb3VudCI6MCwiYXBwbGllZE9wZXJhdGlvbkNvdW50IjowLCJza2lwcGVkT3BlcmF0aW9uQ291bnQiOjAsImZhaWxlZE9wZXJhdGlvbkNvdW50IjowLCJtZXRyaWNzIjp7fSwibW9kZWxJZCI6bnVsbCwiYWlWZXJpZmllZCI6ZmFsc2UsImFpRXZpZGVuY2UiOnt9LCJzdGF0dXNSZWFzb24iOm51bGwsImNyZWF0ZWRBdCI6IjIwMjYtMDEtMDFUMDA6MDA6MDAuMDAwWiIsInN0YXJ0ZWRBdCI6IjIwMjYtMDEtMDFUMDA6MDA6MDAuMDAwWiIsImNvbXBsZXRlZEF0IjoiMjAyNi0wMS0wMVQwMDowMDowMC4wMDBaIiwidXBkYXRlZEF0IjoiMjAyNi0wMS0wMVQwMDowMDowMC4wMDBaIn0=" + } + ] + }, + "workflowTaskCompletedEventId": "21" + } + } + ] + } +} 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..9e6aaafd4 --- /dev/null +++ b/apps/worker/tests/fixtures/workflow-histories/memoryMaintenanceWorkflow.json @@ -0,0 +1,821 @@ +{ + "workflowId": "fixture-memory-maintenance", + "history": { + "events": [ + { + "eventId": "1", + "eventTime": { + "seconds": "3370108762", + "nanos": 201000000 + }, + "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": "eyJzY2hlZHVsZWRBdCI6IjIwMjYtMDEtMDFUMDA6MDA6MDAuMDAwWiIsInRyYWNlSWQiOiJ0cmFjZTpmaXh0dXJlLW1lbW9yeSJ9" + } + ] + }, + "workflowExecutionTimeout": { + "seconds": "315360000" + }, + "workflowRunTimeout": { + "seconds": "315360000" + }, + "workflowTaskTimeout": { + "seconds": "10" + }, + "originalExecutionRunId": "d070ede4-a9a3-407d-ac2f-f2f78be9ea49", + "identity": "25805@cursor", + "firstExecutionRunId": "d070ede4-a9a3-407d-ac2f-f2f78be9ea49", + "attempt": 1, + "firstWorkflowTaskBackoff": {}, + "header": {} + } + }, + { + "eventId": "2", + "eventTime": { + "seconds": "3370108762", + "nanos": 201000000 + }, + "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": "3370108762", + "nanos": 201000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "2", + "identity": "25805@cursor", + "historySizeBytes": "359" + } + }, + { + "eventId": "4", + "eventTime": { + "seconds": "3370108762", + "nanos": 585000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "2", + "identity": "25805@cursor", + "binaryChecksum": "@temporalio/worker@1.24.0+9a365569848f3b7720675da78257957763d6c9652a5349034772dfe58fee989c", + "sdkMetadata": { + "coreUsedFlags": [ + 1, + 2, + 3 + ], + "sdkName": "temporal-typescript", + "sdkVersion": "1.24.0" + }, + "meteringMetadata": {} + } + }, + { + "eventId": "5", + "eventTime": { + "seconds": "3370108762", + "nanos": 585000000 + }, + "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": "eyJzY2hlZHVsZWRBdCI6IjIwMjYtMDEtMDFUMDA6MDA6MDAuMDAwWiIsInRyYWNlSWQiOiJ0cmFjZTpmaXh0dXJlLW1lbW9yeSJ9" + } + ] + }, + "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": "3370108762", + "nanos": 587000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_STARTED", + "activityTaskStartedEventAttributes": { + "scheduledEventId": "5", + "identity": "25805@cursor", + "attempt": 1 + } + }, + { + "eventId": "7", + "eventTime": { + "seconds": "3370108762", + "nanos": 589000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_COMPLETED", + "activityTaskCompletedEventAttributes": { + "result": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJzdGF0dXMiOiJSRUZSRVNIRUQiLCJnZW5lcmF0ZWRBdCI6IjIwMjYtMDEtMDFUMDA6MDA6MDAuMDAwWiIsInNvdXJjZUhhc2giOiIwMTIzNDU2Nzg5YWJjZGVmMDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWYwMTIzNDU2Nzg5YWJjZGVmIiwicHJldmlvdXNTb3VyY2VIYXNoIjpudWxsLCJzdW1tYXJ5Ijp7InNvdXJjZUNvdW50IjoxLCJyZWNvcmRDb3VudCI6Miwibm9kZUNvdW50IjozLCJlZGdlQ291bnQiOjQsImlzc3VlQ291bnQiOjB9LCJkdXJhdGlvbk1zIjoxMn0=" + } + ] + }, + "scheduledEventId": "5", + "startedEventId": "6", + "identity": "25805@cursor" + } + }, + { + "eventId": "8", + "eventTime": { + "seconds": "3370108762", + "nanos": 589000000 + }, + "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": "3370108762", + "nanos": 589000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "8", + "identity": "25805@cursor", + "historySizeBytes": "1241" + } + }, + { + "eventId": "10", + "eventTime": { + "seconds": "3370108762", + "nanos": 595000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "8", + "identity": "25805@cursor", + "binaryChecksum": "@temporalio/worker@1.24.0+9a365569848f3b7720675da78257957763d6c9652a5349034772dfe58fee989c", + "sdkMetadata": {}, + "meteringMetadata": {} + } + }, + { + "eventId": "11", + "eventTime": { + "seconds": "3370108762", + "nanos": 595000000 + }, + "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": "eyJzY2hlZHVsZWRBdCI6IjIwMjYtMDEtMDFUMDA6MDA6MDAuMDAwWiIsInRyYWNlSWQiOiJ0cmFjZTpmaXh0dXJlLW1lbW9yeSJ9" + } + ] + }, + "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": "3370108762", + "nanos": 595000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_STARTED", + "activityTaskStartedEventAttributes": { + "scheduledEventId": "11", + "identity": "25805@cursor", + "attempt": 1 + } + }, + { + "eventId": "13", + "eventTime": { + "seconds": "3370108762", + "nanos": 596000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_COMPLETED", + "activityTaskCompletedEventAttributes": { + "result": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJzdGF0dXMiOiJSRUZSRVNIRUQiLCJnZW5lcmF0ZWRBdCI6IjIwMjYtMDEtMDFUMDE6MDA6MDAuMDAwWiIsInNvdXJjZUhhc2giOiIwMTIzNDU2Nzg5YWJjZGVmMDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWYwMTIzNDU2Nzg5YWJjZGVmIiwicHJldmlvdXNTb3VyY2VIYXNoIjpudWxsLCJzdW1tYXJ5Ijp7InNvdXJjZUNvdW50IjoxLCJyZWNvcmRDb3VudCI6Miwibm9kZUNvdW50IjozLCJlZGdlQ291bnQiOjQsImlzc3VlQ291bnQiOjB9LCJkdXJhdGlvbk1zIjoxMn0=" + } + ] + }, + "scheduledEventId": "11", + "startedEventId": "12", + "identity": "25805@cursor" + } + }, + { + "eventId": "14", + "eventTime": { + "seconds": "3370108762", + "nanos": 596000000 + }, + "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": "3370108762", + "nanos": 596000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "14", + "identity": "25805@cursor", + "historySizeBytes": "2098" + } + }, + { + "eventId": "16", + "eventTime": { + "seconds": "3370108762", + "nanos": 605000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "14", + "identity": "25805@cursor", + "binaryChecksum": "@temporalio/worker@1.24.0+9a365569848f3b7720675da78257957763d6c9652a5349034772dfe58fee989c", + "sdkMetadata": {}, + "meteringMetadata": {} + } + }, + { + "eventId": "17", + "eventTime": { + "seconds": "3370108762", + "nanos": 605000000 + }, + "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": "3370108762", + "nanos": 605000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_STARTED", + "activityTaskStartedEventAttributes": { + "scheduledEventId": "17", + "identity": "25805@cursor", + "attempt": 1 + } + }, + { + "eventId": "19", + "eventTime": { + "seconds": "3370108762", + "nanos": 607000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_COMPLETED", + "activityTaskCompletedEventAttributes": { + "result": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "MA==" + } + ] + }, + "scheduledEventId": "17", + "startedEventId": "18", + "identity": "25805@cursor" + } + }, + { + "eventId": "20", + "eventTime": { + "seconds": "3370108762", + "nanos": 607000000 + }, + "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": "3370108762", + "nanos": 607000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "20", + "identity": "25805@cursor", + "historySizeBytes": "2573" + } + }, + { + "eventId": "22", + "eventTime": { + "seconds": "3370108762", + "nanos": 616000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "20", + "identity": "25805@cursor", + "binaryChecksum": "@temporalio/worker@1.24.0+9a365569848f3b7720675da78257957763d6c9652a5349034772dfe58fee989c", + "sdkMetadata": {}, + "meteringMetadata": {} + } + }, + { + "eventId": "23", + "eventTime": { + "seconds": "3370108762", + "nanos": 616000000 + }, + "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": "eyJzY2hlZHVsZWRBdCI6IjIwMjYtMDEtMDFUMDA6MDA6MDAuMDAwWiIsInRyYWNlSWQiOiJ0cmFjZTpmaXh0dXJlLW1lbW9yeSIsInNvdXJjZUhhc2giOiIwMTIzNDU2Nzg5YWJjZGVmMDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWYwMTIzNDU2Nzg5YWJjZGVmIn0=" + } + ] + }, + "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": "3370108762", + "nanos": 616000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_STARTED", + "activityTaskStartedEventAttributes": { + "scheduledEventId": "23", + "identity": "25805@cursor", + "attempt": 1 + } + }, + { + "eventId": "25", + "eventTime": { + "seconds": "3370108762", + "nanos": 618000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_COMPLETED", + "activityTaskCompletedEventAttributes": { + "result": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJpZCI6InJ1bi10cmFjZTpmaXh0dXJlLW1lbW9yeSIsIm1vZGUiOiJBVURJVCIsInRyaWdnZXJLaW5kIjoiU0NIRURVTEVEIiwic3RhdHVzIjoiU1VDQ0VFREVEIiwid29ya2Zsb3dJZCI6InNwYWNlLW1lbW9yeS1jb25zb2xpZGF0aW9uOnJ1bi10cmFjZTpmaXh0dXJlLW1lbW9yeSIsImRlZHVwZUtleSI6InNjaGVkdWxlZDpydW4tdHJhY2U6Zml4dHVyZS1tZW1vcnkiLCJzb3VyY2VIYXNoIjpudWxsLCJhY3RvclVzZXJJZCI6bnVsbCwicHJvZ3Jlc3NDb21wbGV0ZWQiOjEsInByb2dyZXNzVG90YWwiOjEsImZpbmRpbmdDb3VudCI6MCwiYXBwbGllZE9wZXJhdGlvbkNvdW50IjowLCJza2lwcGVkT3BlcmF0aW9uQ291bnQiOjAsImZhaWxlZE9wZXJhdGlvbkNvdW50IjowLCJtZXRyaWNzIjp7fSwibW9kZWxJZCI6bnVsbCwiYWlWZXJpZmllZCI6ZmFsc2UsImFpRXZpZGVuY2UiOnt9LCJzdGF0dXNSZWFzb24iOm51bGwsImNyZWF0ZWRBdCI6IjIwMjYtMDEtMDFUMDA6MDA6MDAuMDAwWiIsInN0YXJ0ZWRBdCI6IjIwMjYtMDEtMDFUMDA6MDA6MDAuMDAwWiIsImNvbXBsZXRlZEF0IjoiMjAyNi0wMS0wMVQwMDowMDowMC4wMDBaIiwidXBkYXRlZEF0IjoiMjAyNi0wMS0wMVQwMDowMDowMC4wMDBaIn0=" + } + ] + }, + "scheduledEventId": "23", + "startedEventId": "24", + "identity": "25805@cursor" + } + }, + { + "eventId": "26", + "eventTime": { + "seconds": "3370108762", + "nanos": 618000000 + }, + "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": "3370108762", + "nanos": 618000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "26", + "identity": "25805@cursor", + "historySizeBytes": "3855" + } + }, + { + "eventId": "28", + "eventTime": { + "seconds": "3370108762", + "nanos": 622000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "26", + "identity": "25805@cursor", + "binaryChecksum": "@temporalio/worker@1.24.0+9a365569848f3b7720675da78257957763d6c9652a5349034772dfe58fee989c", + "sdkMetadata": {}, + "meteringMetadata": {} + } + }, + { + "eventId": "29", + "eventTime": { + "seconds": "3370108762", + "nanos": 622000000 + }, + "eventType": "EVENT_TYPE_START_CHILD_WORKFLOW_EXECUTION_INITIATED", + "startChildWorkflowExecutionInitiatedEventAttributes": { + "namespace": "default", + "workflowId": "space-memory-consolidation:run-trace:fixture-memory", + "workflowType": { + "name": "memoryConsolidationWorkflow" + }, + "taskQueue": { + "name": "space-memory-maintenance", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJydW5JZCI6InJ1bi10cmFjZTpmaXh0dXJlLW1lbW9yeSIsInRyYWNlSWQiOiJ0cmFjZTpmaXh0dXJlLW1lbW9yeSJ9" + } + ] + }, + "workflowExecutionTimeout": {}, + "workflowRunTimeout": {}, + "workflowTaskTimeout": { + "seconds": "10" + }, + "workflowTaskCompletedEventId": "27", + "header": {}, + "memo": {} + } + }, + { + "eventId": "30", + "eventTime": { + "seconds": "3370108762", + "nanos": 623000000 + }, + "eventType": "EVENT_TYPE_CHILD_WORKFLOW_EXECUTION_STARTED", + "childWorkflowExecutionStartedEventAttributes": { + "namespace": "default", + "initiatedEventId": "29", + "workflowExecution": { + "workflowId": "space-memory-consolidation:run-trace:fixture-memory", + "runId": "061f69ab-e1df-4288-b273-ec1815449187" + }, + "workflowType": { + "name": "memoryConsolidationWorkflow" + } + } + }, + { + "eventId": "31", + "eventTime": { + "seconds": "3370108762", + "nanos": 623000000 + }, + "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": "3370108762", + "nanos": 623000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "31", + "identity": "25805@cursor", + "historySizeBytes": "4498" + } + }, + { + "eventId": "33", + "eventTime": { + "seconds": "3370108762", + "nanos": 898000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "31", + "identity": "25805@cursor", + "binaryChecksum": "@temporalio/worker@1.24.0+9a365569848f3b7720675da78257957763d6c9652a5349034772dfe58fee989c", + "sdkMetadata": {}, + "meteringMetadata": {} + } + }, + { + "eventId": "34", + "eventTime": { + "seconds": "3370108762", + "nanos": 913000000 + }, + "eventType": "EVENT_TYPE_CHILD_WORKFLOW_EXECUTION_COMPLETED", + "childWorkflowExecutionCompletedEventAttributes": { + "result": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJpZCI6InJ1bi10cmFjZTpmaXh0dXJlLW1lbW9yeSIsIm1vZGUiOiJBVURJVCIsInRyaWdnZXJLaW5kIjoiU0NIRURVTEVEIiwic3RhdHVzIjoiU1VDQ0VFREVEIiwid29ya2Zsb3dJZCI6InNwYWNlLW1lbW9yeS1jb25zb2xpZGF0aW9uOnJ1bi10cmFjZTpmaXh0dXJlLW1lbW9yeSIsImRlZHVwZUtleSI6InNjaGVkdWxlZDpydW4tdHJhY2U6Zml4dHVyZS1tZW1vcnkiLCJzb3VyY2VIYXNoIjpudWxsLCJhY3RvclVzZXJJZCI6bnVsbCwicHJvZ3Jlc3NDb21wbGV0ZWQiOjEsInByb2dyZXNzVG90YWwiOjEsImZpbmRpbmdDb3VudCI6MCwiYXBwbGllZE9wZXJhdGlvbkNvdW50IjowLCJza2lwcGVkT3BlcmF0aW9uQ291bnQiOjAsImZhaWxlZE9wZXJhdGlvbkNvdW50IjowLCJtZXRyaWNzIjp7fSwibW9kZWxJZCI6bnVsbCwiYWlWZXJpZmllZCI6ZmFsc2UsImFpRXZpZGVuY2UiOnt9LCJzdGF0dXNSZWFzb24iOm51bGwsImNyZWF0ZWRBdCI6IjIwMjYtMDEtMDFUMDA6MDA6MDAuMDAwWiIsInN0YXJ0ZWRBdCI6IjIwMjYtMDEtMDFUMDA6MDA6MDAuMDAwWiIsImNvbXBsZXRlZEF0IjoiMjAyNi0wMS0wMVQwMDowMDowMC4wMDBaIiwidXBkYXRlZEF0IjoiMjAyNi0wMS0wMVQwMDowMDowMC4wMDBaIn0=" + } + ] + }, + "namespace": "default", + "workflowExecution": { + "workflowId": "space-memory-consolidation:run-trace:fixture-memory", + "runId": "061f69ab-e1df-4288-b273-ec1815449187" + }, + "workflowType": { + "name": "memoryConsolidationWorkflow" + }, + "initiatedEventId": "29", + "startedEventId": "30" + } + }, + { + "eventId": "35", + "eventTime": { + "seconds": "3370108762", + "nanos": 913000000 + }, + "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": "3370108762", + "nanos": 913000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "35", + "identity": "25805@cursor", + "historySizeBytes": "5540" + } + }, + { + "eventId": "37", + "eventTime": { + "seconds": "3370108762", + "nanos": 917000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "35", + "identity": "25805@cursor", + "binaryChecksum": "@temporalio/worker@1.24.0+9a365569848f3b7720675da78257957763d6c9652a5349034772dfe58fee989c", + "sdkMetadata": {}, + "meteringMetadata": {} + } + }, + { + "eventId": "38", + "eventTime": { + "seconds": "3370108762", + "nanos": 917000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_EXECUTION_COMPLETED", + "workflowExecutionCompletedEventAttributes": { + "result": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJzdGF0dXMiOiJSRUZSRVNIRUQiLCJnZW5lcmF0ZWRBdCI6IjIwMjYtMDEtMDFUMDA6MDA6MDAuMDAwWiIsInNvdXJjZUhhc2giOiIwMTIzNDU2Nzg5YWJjZGVmMDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWYwMTIzNDU2Nzg5YWJjZGVmIiwicHJldmlvdXNTb3VyY2VIYXNoIjpudWxsLCJzdW1tYXJ5Ijp7InNvdXJjZUNvdW50IjoxLCJyZWNvcmRDb3VudCI6Miwibm9kZUNvdW50IjozLCJlZGdlQ291bnQiOjQsImlzc3VlQ291bnQiOjB9LCJkdXJhdGlvbk1zIjoxMn0=" + } + ] + }, + "workflowTaskCompletedEventId": "36" + } + } + ] + } +} 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..bfcdac98b --- /dev/null +++ b/apps/worker/tests/fixtures/workflow-histories/memoryMutationWorkflow.json @@ -0,0 +1,256 @@ +{ + "workflowId": "fixture-memory-mutation", + "history": { + "events": [ + { + "eventId": "1", + "eventTime": { + "seconds": "4000828763", + "nanos": 142000000 + }, + "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": "eyJjaGFuZ2VTZXRJZCI6ImNoYW5nZXNldC1maXh0dXJlLTAxIiwidHJhY2VJZCI6InRyYWNlOmZpeHR1cmUtbWVtb3J5In0=" + } + ] + }, + "workflowExecutionTimeout": { + "seconds": "315360000" + }, + "workflowRunTimeout": { + "seconds": "315360000" + }, + "workflowTaskTimeout": { + "seconds": "10" + }, + "originalExecutionRunId": "59db3de6-59a0-4e4e-9592-20e6a7ebe983", + "identity": "25805@cursor", + "firstExecutionRunId": "59db3de6-59a0-4e4e-9592-20e6a7ebe983", + "attempt": 1, + "firstWorkflowTaskBackoff": {}, + "header": {} + } + }, + { + "eventId": "2", + "eventTime": { + "seconds": "4000828763", + "nanos": 142000000 + }, + "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": "4000828763", + "nanos": 142000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "2", + "identity": "25805@cursor", + "historySizeBytes": "348" + } + }, + { + "eventId": "4", + "eventTime": { + "seconds": "4000828763", + "nanos": 515000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "2", + "identity": "25805@cursor", + "binaryChecksum": "@temporalio/worker@1.24.0+9a365569848f3b7720675da78257957763d6c9652a5349034772dfe58fee989c", + "sdkMetadata": { + "coreUsedFlags": [ + 2, + 1, + 3 + ], + "sdkName": "temporal-typescript", + "sdkVersion": "1.24.0" + }, + "meteringMetadata": {} + } + }, + { + "eventId": "5", + "eventTime": { + "seconds": "4000828763", + "nanos": 515000000 + }, + "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": "eyJjaGFuZ2VTZXRJZCI6ImNoYW5nZXNldC1maXh0dXJlLTAxIiwidHJhY2VJZCI6InRyYWNlOmZpeHR1cmUtbWVtb3J5In0=" + } + ] + }, + "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": "4000828763", + "nanos": 516000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_STARTED", + "activityTaskStartedEventAttributes": { + "scheduledEventId": "5", + "identity": "25805@cursor", + "attempt": 1 + } + }, + { + "eventId": "7", + "eventTime": { + "seconds": "4000828763", + "nanos": 517000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_COMPLETED", + "activityTaskCompletedEventAttributes": { + "result": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJzdGF0dXMiOiJBUFBMSUVEIiwiY2hhbmdlU2V0SWQiOiJjaGFuZ2VzZXQtZml4dHVyZS0wMSIsInJlc3VsdGluZ1NvdXJjZUhhc2giOiIwMTIzNDU2Nzg5YWJjZGVmMDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWYwMTIzNDU2Nzg5YWJjZGVmIiwicmVhc29uIjpudWxsfQ==" + } + ] + }, + "scheduledEventId": "5", + "startedEventId": "6", + "identity": "25805@cursor" + } + }, + { + "eventId": "8", + "eventTime": { + "seconds": "4000828763", + "nanos": 517000000 + }, + "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": "4000828763", + "nanos": 517000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "8", + "identity": "25805@cursor", + "historySizeBytes": "1115" + } + }, + { + "eventId": "10", + "eventTime": { + "seconds": "4000828763", + "nanos": 521000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "8", + "identity": "25805@cursor", + "binaryChecksum": "@temporalio/worker@1.24.0+9a365569848f3b7720675da78257957763d6c9652a5349034772dfe58fee989c", + "sdkMetadata": {}, + "meteringMetadata": {} + } + }, + { + "eventId": "11", + "eventTime": { + "seconds": "4000828763", + "nanos": 521000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_EXECUTION_COMPLETED", + "workflowExecutionCompletedEventAttributes": { + "result": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJzdGF0dXMiOiJBUFBMSUVEIiwiY2hhbmdlU2V0SWQiOiJjaGFuZ2VzZXQtZml4dHVyZS0wMSIsInJlc3VsdGluZ1NvdXJjZUhhc2giOiIwMTIzNDU2Nzg5YWJjZGVmMDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWYwMTIzNDU2Nzg5YWJjZGVmIiwicmVhc29uIjpudWxsfQ==" + } + ] + }, + "workflowTaskCompletedEventId": "9" + } + } + ] + } +} 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..d9c7f2b90 --- /dev/null +++ b/apps/worker/tests/fixtures/workflow-histories/roomAgentActionWorkflow.json @@ -0,0 +1,446 @@ +{ + "workflowId": "fixture-room-action", + "history": { + "events": [ + { + "eventId": "1", + "eventTime": { + "seconds": "3054748746", + "nanos": 973000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_EXECUTION_STARTED", + "workflowExecutionStartedEventAttributes": { + "workflowType": { + "name": "roomAgentActionWorkflow" + }, + "taskQueue": { + "name": "temporal-sandbox-tests", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJhY3Rpb25JZCI6ImFjdC1maXh0dXJlLTAxIiwiYnJpZGdlIjp7InJvb21JZCI6InJvb20tZml4dHVyZS0wMSIsIm1pc3Npb25JZCI6Im1pc3Npb24tZml4dHVyZS0wNSIsImFnZW50UGFuZUlkIjoicGFuZTpyb29tLWZpeHR1cmUtMDEtYWN0aW9uIiwiYWdlbnRTZXNzaW9uSWQiOiJzZXNzaW9uOnJvb20tZml4dHVyZS0wMS1hY3Rpb24iLCJzZWxlY3RlZFRvb2xJZHMiOlsicm9vbTppbnNwZWN0Il0sImFjdGlvbnMiOlt7InRvb2xJZCI6InJvb206aW5zcGVjdCIsImFjdGlvbiI6eyJ0eXBlIjoiaW5zcGVjdCIsImFyZ3VtZW50cyI6e319fV19LCJ0cmFjZUlkIjoidHJhY2U6Zml4dHVyZS1hY3Rpb24ifQ==" + } + ] + }, + "workflowExecutionTimeout": { + "seconds": "315360000" + }, + "workflowRunTimeout": { + "seconds": "315360000" + }, + "workflowTaskTimeout": { + "seconds": "10" + }, + "originalExecutionRunId": "577b2329-4dd7-4313-9260-19d2469b5770", + "identity": "25805@cursor", + "firstExecutionRunId": "577b2329-4dd7-4313-9260-19d2469b5770", + "attempt": 1, + "firstWorkflowTaskBackoff": {}, + "header": {} + } + }, + { + "eventId": "2", + "eventTime": { + "seconds": "3054748746", + "nanos": 973000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_SCHEDULED", + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "temporal-sandbox-tests", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "startToCloseTimeout": { + "seconds": "10" + }, + "attempt": 1 + } + }, + { + "eventId": "3", + "eventTime": { + "seconds": "3054748746", + "nanos": 974000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "2", + "identity": "25805@cursor", + "historySizeBytes": "626" + } + }, + { + "eventId": "4", + "eventTime": { + "seconds": "3054748747", + "nanos": 172000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "2", + "identity": "25805@cursor", + "binaryChecksum": "@temporalio/worker@1.24.0+9a365569848f3b7720675da78257957763d6c9652a5349034772dfe58fee989c", + "sdkMetadata": { + "coreUsedFlags": [ + 3, + 2, + 1 + ], + "sdkName": "temporal-typescript", + "sdkVersion": "1.24.0" + }, + "meteringMetadata": {} + } + }, + { + "eventId": "5", + "eventTime": { + "seconds": "3054748747", + "nanos": 172000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_SCHEDULED", + "activityTaskScheduledEventAttributes": { + "activityId": "1", + "activityType": { + "name": "runRoomAgentBackgroundAction" + }, + "taskQueue": { + "name": "temporal-sandbox-tests", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "header": {}, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJhY3Rpb25JZCI6ImFjdC1maXh0dXJlLTAxIiwiYnJpZGdlIjp7InJvb21JZCI6InJvb20tZml4dHVyZS0wMSIsIm1pc3Npb25JZCI6Im1pc3Npb24tZml4dHVyZS0wNSIsImFnZW50UGFuZUlkIjoicGFuZTpyb29tLWZpeHR1cmUtMDEtYWN0aW9uIiwiYWdlbnRTZXNzaW9uSWQiOiJzZXNzaW9uOnJvb20tZml4dHVyZS0wMS1hY3Rpb24iLCJzZWxlY3RlZFRvb2xJZHMiOlsicm9vbTppbnNwZWN0Il0sImFjdGlvbnMiOlt7InRvb2xJZCI6InJvb206aW5zcGVjdCIsImFjdGlvbiI6eyJ0eXBlIjoiaW5zcGVjdCIsImFyZ3VtZW50cyI6e319fV19LCJ0cmFjZUlkIjoidHJhY2U6Zml4dHVyZS1hY3Rpb24ifQ==" + } + ] + }, + "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": "3054748762", + "nanos": 186000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_STARTED", + "activityTaskStartedEventAttributes": { + "scheduledEventId": "5", + "identity": "25805@cursor", + "attempt": 5 + } + }, + { + "eventId": "7", + "eventTime": { + "seconds": "3054748762", + "nanos": 187000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_FAILED", + "activityTaskFailedEventAttributes": { + "failure": { + "message": "synthetic background action failure", + "source": "TypeScriptSDK", + "stackTrace": "Error: synthetic background action failure\n at /workspace/apps/worker/tests/temporal-sandbox/record-histories.test.ts:146:13\n at /workspace/apps/worker/tests/temporal-sandbox/activity-stubs.ts:41:20", + "applicationFailureInfo": { + "type": "Error" + } + }, + "scheduledEventId": "5", + "startedEventId": "6", + "identity": "25805@cursor", + "retryState": "RETRY_STATE_MAXIMUM_ATTEMPTS_REACHED" + } + }, + { + "eventId": "8", + "eventTime": { + "seconds": "3054748762", + "nanos": 187000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_SCHEDULED", + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "temporal-sandbox-tests", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "startToCloseTimeout": { + "seconds": "10" + }, + "attempt": 1 + } + }, + { + "eventId": "9", + "eventTime": { + "seconds": "3054748762", + "nanos": 187000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "8", + "identity": "25805@cursor", + "historySizeBytes": "1744" + } + }, + { + "eventId": "10", + "eventTime": { + "seconds": "3054748762", + "nanos": 195000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "8", + "identity": "25805@cursor", + "binaryChecksum": "@temporalio/worker@1.24.0+9a365569848f3b7720675da78257957763d6c9652a5349034772dfe58fee989c", + "sdkMetadata": {}, + "meteringMetadata": {} + } + }, + { + "eventId": "11", + "eventTime": { + "seconds": "3054748762", + "nanos": 195000000 + }, + "eventType": "EVENT_TYPE_MARKER_RECORDED", + "markerRecordedEventAttributes": { + "markerName": "core_patch", + "details": { + "patch-data": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJpZCI6InJvb20tYmFja2dyb3VuZC1hY3Rpb24tZmFpbHVyZS12MSIsImRlcHJlY2F0ZWQiOmZhbHNlfQ==" + } + ] + } + }, + "workflowTaskCompletedEventId": "9" + } + }, + { + "eventId": "12", + "eventTime": { + "seconds": "3054748762", + "nanos": 195000000 + }, + "eventType": "EVENT_TYPE_UPSERT_WORKFLOW_SEARCH_ATTRIBUTES", + "upsertWorkflowSearchAttributesEventAttributes": { + "workflowTaskCompletedEventId": "9", + "searchAttributes": { + "indexedFields": { + "TemporalChangeVersion": { + "metadata": { + "type": "S2V5d29yZExpc3Q=", + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "WyJyb29tLWJhY2tncm91bmQtYWN0aW9uLWZhaWx1cmUtdjEiXQ==" + } + } + } + } + }, + { + "eventId": "13", + "eventTime": { + "seconds": "3054748762", + "nanos": 195000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_SCHEDULED", + "activityTaskScheduledEventAttributes": { + "activityId": "2", + "activityType": { + "name": "failRoomAgentBackgroundAction" + }, + "taskQueue": { + "name": "temporal-sandbox-tests", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "header": {}, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJyb29tSWQiOiJyb29tLWZpeHR1cmUtMDEiLCJtaXNzaW9uSWQiOiJtaXNzaW9uLWZpeHR1cmUtMDUiLCJhY3Rpb25JZCI6ImFjdC1maXh0dXJlLTAxIiwiY2FuY2VsbGVkIjpmYWxzZX0=" + } + ] + }, + "scheduleToCloseTimeout": { + "seconds": "315360000" + }, + "scheduleToStartTimeout": { + "seconds": "315360000" + }, + "startToCloseTimeout": { + "seconds": "60" + }, + "heartbeatTimeout": {}, + "workflowTaskCompletedEventId": "9", + "retryPolicy": { + "initialInterval": { + "seconds": "1" + }, + "backoffCoefficient": 2, + "maximumInterval": { + "seconds": "100" + }, + "maximumAttempts": 5 + } + } + }, + { + "eventId": "14", + "eventTime": { + "seconds": "3054748762", + "nanos": 195000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_STARTED", + "activityTaskStartedEventAttributes": { + "scheduledEventId": "13", + "identity": "25805@cursor", + "attempt": 1 + } + }, + { + "eventId": "15", + "eventTime": { + "seconds": "3054748762", + "nanos": 197000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_COMPLETED", + "activityTaskCompletedEventAttributes": { + "result": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "bnVsbA==" + } + ] + }, + "scheduledEventId": "13", + "startedEventId": "14", + "identity": "25805@cursor" + } + }, + { + "eventId": "16", + "eventTime": { + "seconds": "3054748762", + "nanos": 197000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_SCHEDULED", + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "temporal-sandbox-tests", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "startToCloseTimeout": { + "seconds": "10" + }, + "attempt": 1 + } + }, + { + "eventId": "17", + "eventTime": { + "seconds": "3054748762", + "nanos": 197000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "16", + "identity": "25805@cursor", + "historySizeBytes": "2620" + } + }, + { + "eventId": "18", + "eventTime": { + "seconds": "3054748762", + "nanos": 203000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "16", + "identity": "25805@cursor", + "binaryChecksum": "@temporalio/worker@1.24.0+9a365569848f3b7720675da78257957763d6c9652a5349034772dfe58fee989c", + "sdkMetadata": {}, + "meteringMetadata": {} + } + }, + { + "eventId": "19", + "eventTime": { + "seconds": "3054748762", + "nanos": 203000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_EXECUTION_FAILED", + "workflowExecutionFailedEventAttributes": { + "failure": { + "message": "Activity task failed", + "cause": { + "message": "synthetic background action failure", + "source": "TypeScriptSDK", + "stackTrace": "Error: synthetic background action failure\n at /workspace/apps/worker/tests/temporal-sandbox/record-histories.test.ts:146:13\n at /workspace/apps/worker/tests/temporal-sandbox/activity-stubs.ts:41:20", + "applicationFailureInfo": { + "type": "Error" + } + }, + "activityFailureInfo": { + "scheduledEventId": "5", + "startedEventId": "6", + "identity": "25805@cursor", + "activityType": { + "name": "runRoomAgentBackgroundAction" + }, + "activityId": "1", + "retryState": "RETRY_STATE_MAXIMUM_ATTEMPTS_REACHED" + } + }, + "workflowTaskCompletedEventId": "17" + } + } + ] + } +} 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..8346c0b9a --- /dev/null +++ b/apps/worker/tests/fixtures/workflow-histories/roomAgentSupervisorWorkflow.json @@ -0,0 +1,627 @@ +{ + "workflowId": "fixture-supervisor", + "history": { + "events": [ + { + "eventId": "1", + "eventTime": { + "seconds": "2739388730", + "nanos": 851000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_EXECUTION_STARTED", + "workflowExecutionStartedEventAttributes": { + "workflowType": { + "name": "roomAgentSupervisorWorkflow" + }, + "taskQueue": { + "name": "temporal-sandbox-tests", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJyb29tSWQiOiJyb29tLWZpeHR1cmUtc3VwIiwicGVuZGluZyI6W3sibWlzc2lvbklkIjoibWlzc2lvbi1maXh0dXJlLTAyIiwidHVybiI6eyJyb29tSWQiOiJyb29tLWZpeHR1cmUtc3VwIiwicGFuZUlkIjoicGFuZTpyb29tLWZpeHR1cmUtc3VwLXN1cCIsInByb21wdCI6IlN5bnRoZXRpYyB0dXJuIHByb21wdC4iLCJhcnRpZmFjdElkcyI6W10sInByb3ZpZGVySWQiOm51bGwsIm1vZGVsSWQiOiJncHQtdGVzdCIsInJlYXNvbmluZ0VmZm9ydCI6bnVsbCwiYWdlbnRTZXNzaW9uSWQiOiJzZXNzaW9uOnJvb20tZml4dHVyZS1zdXAtc3VwIiwiYWdlbnRSdW5JZCI6ImFnZW50X3J1bjpyb29tLWZpeHR1cmUtc3VwLXN1cCIsImFnZW50QXNzaXN0YW50TWVzc2FnZUlkIjoiYWdlbnRfbXNnOmFzc2lzdGFudC1yb29tLWZpeHR1cmUtc3VwLXN1cCIsImNvbGxhYm9yYXRpb25Nb2RlIjoiZGVmYXVsdCIsInRyYWNlSWQiOiJ0cmFjZTpyb29tLWZpeHR1cmUtc3VwLXN1cCIsInJvb21BZ2VudE1pc3Npb25JZCI6Im1pc3Npb24tZml4dHVyZS0wMiIsInJvb21BZ2VudE9wZXJhdG9yUmVxdWVzdCI6IlN5bnRoZXRpYyBvcGVyYXRvciByZXF1ZXN0LiIsInNlbGVjdGVkVG9vbElkcyI6WyJyb29tOmluc3BlY3QiXSwiYWdlbnRVc2VyTWVzc2FnZUlkIjoiYWdlbnRfbXNnOnVzZXItcm9vbS1maXh0dXJlLXN1cC1zdXAifX1dLCJtb25pdG9yaW5nTWlzc2lvbklkcyI6W10sImludGVycnVwdGVkIjpbXSwicHJvY2Vzc2VkQ291bnQiOjQ5fQ==" + } + ] + }, + "workflowExecutionTimeout": { + "seconds": "315360000" + }, + "workflowRunTimeout": { + "seconds": "315360000" + }, + "workflowTaskTimeout": { + "seconds": "10" + }, + "originalExecutionRunId": "decd363e-f64a-4686-8fdb-37d074843a39", + "identity": "25805@cursor", + "firstExecutionRunId": "decd363e-f64a-4686-8fdb-37d074843a39", + "attempt": 1, + "firstWorkflowTaskBackoff": {}, + "header": {} + } + }, + { + "eventId": "2", + "eventTime": { + "seconds": "2739388730", + "nanos": 851000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_SCHEDULED", + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "temporal-sandbox-tests", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "startToCloseTimeout": { + "seconds": "10" + }, + "attempt": 1 + } + }, + { + "eventId": "3", + "eventTime": { + "seconds": "2739388730", + "nanos": 851000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "2", + "identity": "25805@cursor", + "historySizeBytes": "1038" + } + }, + { + "eventId": "4", + "eventTime": { + "seconds": "2739388731", + "nanos": 74000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "2", + "identity": "25805@cursor", + "binaryChecksum": "@temporalio/worker@1.24.0+9a365569848f3b7720675da78257957763d6c9652a5349034772dfe58fee989c", + "sdkMetadata": { + "coreUsedFlags": [ + 1, + 2, + 3 + ], + "sdkName": "temporal-typescript", + "sdkVersion": "1.24.0" + }, + "meteringMetadata": {} + } + }, + { + "eventId": "5", + "eventTime": { + "seconds": "2739388731", + "nanos": 74000000 + }, + "eventType": "EVENT_TYPE_MARKER_RECORDED", + "markerRecordedEventAttributes": { + "markerName": "core_patch", + "details": { + "patch-data": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJpZCI6InJvb20tYmFja2dyb3VuZC1jb250aW51YXRpb24tdjEiLCJkZXByZWNhdGVkIjpmYWxzZX0=" + } + ] + } + }, + "workflowTaskCompletedEventId": "3" + } + }, + { + "eventId": "6", + "eventTime": { + "seconds": "2739388731", + "nanos": 74000000 + }, + "eventType": "EVENT_TYPE_UPSERT_WORKFLOW_SEARCH_ATTRIBUTES", + "upsertWorkflowSearchAttributesEventAttributes": { + "workflowTaskCompletedEventId": "3", + "searchAttributes": { + "indexedFields": { + "TemporalChangeVersion": { + "metadata": { + "encoding": "anNvbi9wbGFpbg==", + "type": "S2V5d29yZExpc3Q=" + }, + "data": "WyJyb29tLWJhY2tncm91bmQtY29udGludWF0aW9uLXYxIl0=" + } + } + } + } + }, + { + "eventId": "7", + "eventTime": { + "seconds": "2739388731", + "nanos": 74000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_SCHEDULED", + "activityTaskScheduledEventAttributes": { + "activityId": "1", + "activityType": { + "name": "checkRoomAgentSupervisorTurn" + }, + "taskQueue": { + "name": "temporal-sandbox-tests", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "header": {}, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJtaXNzaW9uSWQiOiJtaXNzaW9uLWZpeHR1cmUtMDIiLCJ0dXJuIjp7InJvb21JZCI6InJvb20tZml4dHVyZS1zdXAiLCJwYW5lSWQiOiJwYW5lOnJvb20tZml4dHVyZS1zdXAtc3VwIiwicHJvbXB0IjoiU3ludGhldGljIHR1cm4gcHJvbXB0LiIsImFydGlmYWN0SWRzIjpbXSwicHJvdmlkZXJJZCI6bnVsbCwibW9kZWxJZCI6ImdwdC10ZXN0IiwicmVhc29uaW5nRWZmb3J0IjpudWxsLCJhZ2VudFNlc3Npb25JZCI6InNlc3Npb246cm9vbS1maXh0dXJlLXN1cC1zdXAiLCJhZ2VudFJ1bklkIjoiYWdlbnRfcnVuOnJvb20tZml4dHVyZS1zdXAtc3VwIiwicm9vbUFnZW50TWlzc2lvbklkIjoibWlzc2lvbi1maXh0dXJlLTAyIiwicm9vbUFnZW50T3BlcmF0b3JSZXF1ZXN0IjoiU3ludGhldGljIG9wZXJhdG9yIHJlcXVlc3QuIiwiYWdlbnRVc2VyTWVzc2FnZUlkIjoiYWdlbnRfbXNnOnVzZXItcm9vbS1maXh0dXJlLXN1cC1zdXAiLCJhZ2VudEFzc2lzdGFudE1lc3NhZ2VJZCI6ImFnZW50X21zZzphc3Npc3RhbnQtcm9vbS1maXh0dXJlLXN1cC1zdXAiLCJzZWxlY3RlZFRvb2xJZHMiOlsicm9vbTppbnNwZWN0Il0sInBlcm1pc3Npb25Nb2RlIjpudWxsLCJjb2xsYWJvcmF0aW9uTW9kZSI6ImRlZmF1bHQiLCJ0cmFjZUlkIjoidHJhY2U6cm9vbS1maXh0dXJlLXN1cC1zdXAifX0=" + } + ] + }, + "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": "2739388731", + "nanos": 74000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_STARTED", + "activityTaskStartedEventAttributes": { + "scheduledEventId": "7", + "identity": "25805@cursor", + "attempt": 1 + } + }, + { + "eventId": "9", + "eventTime": { + "seconds": "2739388731", + "nanos": 77000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_COMPLETED", + "activityTaskCompletedEventAttributes": { + "result": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "IlJFQURZIg==" + } + ] + }, + "scheduledEventId": "7", + "startedEventId": "8", + "identity": "25805@cursor" + } + }, + { + "eventId": "10", + "eventTime": { + "seconds": "2739388731", + "nanos": 77000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_SCHEDULED", + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "temporal-sandbox-tests", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "startToCloseTimeout": { + "seconds": "10" + }, + "attempt": 1 + } + }, + { + "eventId": "11", + "eventTime": { + "seconds": "2739388731", + "nanos": 77000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "10", + "identity": "25805@cursor", + "historySizeBytes": "2510" + } + }, + { + "eventId": "12", + "eventTime": { + "seconds": "2739388731", + "nanos": 84000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "10", + "identity": "25805@cursor", + "binaryChecksum": "@temporalio/worker@1.24.0+9a365569848f3b7720675da78257957763d6c9652a5349034772dfe58fee989c", + "sdkMetadata": {}, + "meteringMetadata": {} + } + }, + { + "eventId": "13", + "eventTime": { + "seconds": "2739388731", + "nanos": 84000000 + }, + "eventType": "EVENT_TYPE_START_CHILD_WORKFLOW_EXECUTION_INITIATED", + "startChildWorkflowExecutionInitiatedEventAttributes": { + "namespace": "default", + "workflowId": "workflow:codex-app-server:room-fixture-sup:pane:room-fixture-sup-sup:trace:room-fixture-sup-sup", + "workflowType": { + "name": "codexAppServerTurnWorkflow" + }, + "taskQueue": { + "name": "temporal-sandbox-tests", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJyb29tSWQiOiJyb29tLWZpeHR1cmUtc3VwIiwicGFuZUlkIjoicGFuZTpyb29tLWZpeHR1cmUtc3VwLXN1cCIsInByb21wdCI6IlN5bnRoZXRpYyB0dXJuIHByb21wdC4iLCJhcnRpZmFjdElkcyI6W10sInByb3ZpZGVySWQiOm51bGwsIm1vZGVsSWQiOiJncHQtdGVzdCIsInJlYXNvbmluZ0VmZm9ydCI6bnVsbCwiYWdlbnRTZXNzaW9uSWQiOiJzZXNzaW9uOnJvb20tZml4dHVyZS1zdXAtc3VwIiwiYWdlbnRSdW5JZCI6ImFnZW50X3J1bjpyb29tLWZpeHR1cmUtc3VwLXN1cCIsInJvb21BZ2VudE1pc3Npb25JZCI6Im1pc3Npb24tZml4dHVyZS0wMiIsInJvb21BZ2VudE9wZXJhdG9yUmVxdWVzdCI6IlN5bnRoZXRpYyBvcGVyYXRvciByZXF1ZXN0LiIsImFnZW50VXNlck1lc3NhZ2VJZCI6ImFnZW50X21zZzp1c2VyLXJvb20tZml4dHVyZS1zdXAtc3VwIiwiYWdlbnRBc3Npc3RhbnRNZXNzYWdlSWQiOiJhZ2VudF9tc2c6YXNzaXN0YW50LXJvb20tZml4dHVyZS1zdXAtc3VwIiwic2VsZWN0ZWRUb29sSWRzIjpbInJvb206aW5zcGVjdCJdLCJwZXJtaXNzaW9uTW9kZSI6bnVsbCwiY29sbGFib3JhdGlvbk1vZGUiOiJkZWZhdWx0IiwidHJhY2VJZCI6InRyYWNlOnJvb20tZml4dHVyZS1zdXAtc3VwIn0=" + } + ] + }, + "workflowExecutionTimeout": {}, + "workflowRunTimeout": {}, + "workflowTaskTimeout": { + "seconds": "10" + }, + "workflowTaskCompletedEventId": "11", + "header": {}, + "memo": {} + } + }, + { + "eventId": "14", + "eventTime": { + "seconds": "2739388731", + "nanos": 84000000 + }, + "eventType": "EVENT_TYPE_CHILD_WORKFLOW_EXECUTION_STARTED", + "childWorkflowExecutionStartedEventAttributes": { + "namespace": "default", + "initiatedEventId": "13", + "workflowExecution": { + "workflowId": "workflow:codex-app-server:room-fixture-sup:pane:room-fixture-sup-sup:trace:room-fixture-sup-sup", + "runId": "ca210a8a-2537-4a5e-8a3c-62a4d6f807d9" + }, + "workflowType": { + "name": "codexAppServerTurnWorkflow" + } + } + }, + { + "eventId": "15", + "eventTime": { + "seconds": "2739388731", + "nanos": 84000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_SCHEDULED", + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "temporal-sandbox-tests", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "startToCloseTimeout": { + "seconds": "10" + }, + "attempt": 1 + } + }, + { + "eventId": "16", + "eventTime": { + "seconds": "2739388731", + "nanos": 85000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "15", + "identity": "25805@cursor", + "historySizeBytes": "3791" + } + }, + { + "eventId": "17", + "eventTime": { + "seconds": "2739388731", + "nanos": 291000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "15", + "identity": "25805@cursor", + "binaryChecksum": "@temporalio/worker@1.24.0+9a365569848f3b7720675da78257957763d6c9652a5349034772dfe58fee989c", + "sdkMetadata": {}, + "meteringMetadata": {} + } + }, + { + "eventId": "18", + "eventTime": { + "seconds": "2739388731", + "nanos": 296000000 + }, + "eventType": "EVENT_TYPE_CHILD_WORKFLOW_EXECUTION_COMPLETED", + "childWorkflowExecutionCompletedEventAttributes": { + "result": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJ3b3JrZmxvd0lkIjoic3BhY2UtdHVybjp0cmFjZTpyb29tLWZpeHR1cmUtc3VwLXN1cCIsInJvb21JZCI6InJvb20tZml4dHVyZS1zdXAiLCJwYW5lSWQiOiJwYW5lOnJvb20tZml4dHVyZS1zdXAtc3VwIiwidHJhY2VJZCI6InRyYWNlOnJvb20tZml4dHVyZS1zdXAtc3VwIiwic3RhdHVzIjoiQ09NUExFVEVEIiwibWVzc2FnZSI6IlN5bnRoZXRpYyB0dXJuIGNvbXBsZXRlZC4iLCJyb29tQWdlbnRPdXRjb21lIjp7InN0YXR1cyI6IlZFUklGSUVEIiwiZXhlY3V0ZWRBY3Rpb25Db3VudCI6MSwic3RhdHVzUmVhc29uIjoiU3ludGhldGljIHJvb20tYWdlbnQgdHVybiB2ZXJpZmllZC4ifX0=" + } + ] + }, + "namespace": "default", + "workflowExecution": { + "workflowId": "workflow:codex-app-server:room-fixture-sup:pane:room-fixture-sup-sup:trace:room-fixture-sup-sup", + "runId": "ca210a8a-2537-4a5e-8a3c-62a4d6f807d9" + }, + "workflowType": { + "name": "codexAppServerTurnWorkflow" + }, + "initiatedEventId": "13", + "startedEventId": "14" + } + }, + { + "eventId": "19", + "eventTime": { + "seconds": "2739388731", + "nanos": 296000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_SCHEDULED", + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "temporal-sandbox-tests", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "startToCloseTimeout": { + "seconds": "10" + }, + "attempt": 1 + } + }, + { + "eventId": "20", + "eventTime": { + "seconds": "2739388731", + "nanos": 296000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "19", + "identity": "25805@cursor", + "historySizeBytes": "4586" + } + }, + { + "eventId": "21", + "eventTime": { + "seconds": "2739388731", + "nanos": 302000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "19", + "identity": "25805@cursor", + "binaryChecksum": "@temporalio/worker@1.24.0+9a365569848f3b7720675da78257957763d6c9652a5349034772dfe58fee989c", + "sdkMetadata": {}, + "meteringMetadata": {} + } + }, + { + "eventId": "22", + "eventTime": { + "seconds": "2739388731", + "nanos": 302000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_SCHEDULED", + "activityTaskScheduledEventAttributes": { + "activityId": "2", + "activityType": { + "name": "finishRoomAgentSupervisorTurn" + }, + "taskQueue": { + "name": "temporal-sandbox-tests", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "header": {}, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJpdGVtIjp7Im1pc3Npb25JZCI6Im1pc3Npb24tZml4dHVyZS0wMiIsInR1cm4iOnsicm9vbUlkIjoicm9vbS1maXh0dXJlLXN1cCIsInBhbmVJZCI6InBhbmU6cm9vbS1maXh0dXJlLXN1cC1zdXAiLCJwcm9tcHQiOiJTeW50aGV0aWMgdHVybiBwcm9tcHQuIiwiYXJ0aWZhY3RJZHMiOltdLCJwcm92aWRlcklkIjpudWxsLCJtb2RlbElkIjoiZ3B0LXRlc3QiLCJyZWFzb25pbmdFZmZvcnQiOm51bGwsImFnZW50U2Vzc2lvbklkIjoic2Vzc2lvbjpyb29tLWZpeHR1cmUtc3VwLXN1cCIsImFnZW50UnVuSWQiOiJhZ2VudF9ydW46cm9vbS1maXh0dXJlLXN1cC1zdXAiLCJyb29tQWdlbnRNaXNzaW9uSWQiOiJtaXNzaW9uLWZpeHR1cmUtMDIiLCJyb29tQWdlbnRPcGVyYXRvclJlcXVlc3QiOiJTeW50aGV0aWMgb3BlcmF0b3IgcmVxdWVzdC4iLCJhZ2VudFVzZXJNZXNzYWdlSWQiOiJhZ2VudF9tc2c6dXNlci1yb29tLWZpeHR1cmUtc3VwLXN1cCIsImFnZW50QXNzaXN0YW50TWVzc2FnZUlkIjoiYWdlbnRfbXNnOmFzc2lzdGFudC1yb29tLWZpeHR1cmUtc3VwLXN1cCIsInNlbGVjdGVkVG9vbElkcyI6WyJyb29tOmluc3BlY3QiXSwicGVybWlzc2lvbk1vZGUiOm51bGwsImNvbGxhYm9yYXRpb25Nb2RlIjoiZGVmYXVsdCIsInRyYWNlSWQiOiJ0cmFjZTpyb29tLWZpeHR1cmUtc3VwLXN1cCJ9fSwic3RhdHVzIjoiQ09NUExFVEVEIiwic3RhdHVzUmVhc29uIjoiU3ludGhldGljIHJvb20tYWdlbnQgdHVybiB2ZXJpZmllZC4ifQ==" + } + ] + }, + "scheduleToCloseTimeout": { + "seconds": "315360000" + }, + "scheduleToStartTimeout": { + "seconds": "315360000" + }, + "startToCloseTimeout": { + "seconds": "60" + }, + "heartbeatTimeout": {}, + "workflowTaskCompletedEventId": "20", + "retryPolicy": { + "initialInterval": { + "seconds": "1" + }, + "backoffCoefficient": 2, + "maximumInterval": { + "seconds": "100" + }, + "maximumAttempts": 5 + } + } + }, + { + "eventId": "23", + "eventTime": { + "seconds": "2739388731", + "nanos": 302000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_STARTED", + "activityTaskStartedEventAttributes": { + "scheduledEventId": "22", + "identity": "25805@cursor", + "attempt": 1 + } + }, + { + "eventId": "24", + "eventTime": { + "seconds": "2739388731", + "nanos": 303000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_COMPLETED", + "activityTaskCompletedEventAttributes": { + "result": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "bnVsbA==" + } + ] + }, + "scheduledEventId": "22", + "startedEventId": "23", + "identity": "25805@cursor" + } + }, + { + "eventId": "25", + "eventTime": { + "seconds": "2739388731", + "nanos": 303000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_SCHEDULED", + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "temporal-sandbox-tests", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "startToCloseTimeout": { + "seconds": "10" + }, + "attempt": 1 + } + }, + { + "eventId": "26", + "eventTime": { + "seconds": "2739388731", + "nanos": 303000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "25", + "identity": "25805@cursor", + "historySizeBytes": "5839" + } + }, + { + "eventId": "27", + "eventTime": { + "seconds": "2739388731", + "nanos": 308000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "25", + "identity": "25805@cursor", + "binaryChecksum": "@temporalio/worker@1.24.0+9a365569848f3b7720675da78257957763d6c9652a5349034772dfe58fee989c", + "sdkMetadata": {}, + "meteringMetadata": {} + } + }, + { + "eventId": "28", + "eventTime": { + "seconds": "2739388731", + "nanos": 308000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_EXECUTION_CONTINUED_AS_NEW", + "workflowExecutionContinuedAsNewEventAttributes": { + "newExecutionRunId": "6935fbb1-bb7a-459f-9f1d-3547f9a3984b", + "workflowType": { + "name": "roomAgentSupervisorWorkflow" + }, + "taskQueue": { + "name": "temporal-sandbox-tests", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJyb29tSWQiOiJyb29tLWZpeHR1cmUtc3VwIiwicGVuZGluZyI6W10sImludGVycnVwdGVkIjpbXSwibW9uaXRvcmluZ01pc3Npb25JZHMiOltdLCJwcm9jZXNzZWRDb3VudCI6MH0=" + } + ] + }, + "workflowRunTimeout": { + "seconds": "315360000" + }, + "workflowTaskTimeout": { + "seconds": "10" + }, + "workflowTaskCompletedEventId": "26", + "backoffStartInterval": {} + } + } + ] + } +} diff --git a/apps/worker/tests/fixtures/workflow-histories/roomAgentSupervisorWorkflow.pre-patch.json b/apps/worker/tests/fixtures/workflow-histories/roomAgentSupervisorWorkflow.pre-patch.json new file mode 100644 index 000000000..a5e66d5d0 --- /dev/null +++ b/apps/worker/tests/fixtures/workflow-histories/roomAgentSupervisorWorkflow.pre-patch.json @@ -0,0 +1,819 @@ +{ + "workflowId": "fixture-supervisor-pre-patch", + "history": { + "events": [ + { + "eventId": "1", + "eventTime": { + "seconds": "3054748731", + "nanos": 311000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_EXECUTION_STARTED", + "workflowExecutionStartedEventAttributes": { + "workflowType": { + "name": "roomAgentSupervisorWorkflow" + }, + "taskQueue": { + "name": "temporal-sandbox-tests-pre-patch", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJyb29tSWQiOiJyb29tLWZpeHR1cmUtbGVnYWN5IiwicGVuZGluZyI6W3sibWlzc2lvbklkIjoibWlzc2lvbi1maXh0dXJlLTAzIiwidHVybiI6eyJyb29tSWQiOiJyb29tLWZpeHR1cmUtbGVnYWN5IiwicGFuZUlkIjoicGFuZTpyb29tLWZpeHR1cmUtbGVnYWN5LWxlZ2FjeS1hIiwicHJvbXB0IjoiU3ludGhldGljIHR1cm4gcHJvbXB0LiIsImFydGlmYWN0SWRzIjpbXSwicHJvdmlkZXJJZCI6bnVsbCwibW9kZWxJZCI6ImdwdC10ZXN0IiwicmVhc29uaW5nRWZmb3J0IjpudWxsLCJhZ2VudFNlc3Npb25JZCI6InNlc3Npb246cm9vbS1maXh0dXJlLWxlZ2FjeS1sZWdhY3ktYSIsImFnZW50UnVuSWQiOiJhZ2VudF9ydW46cm9vbS1maXh0dXJlLWxlZ2FjeS1sZWdhY3ktYSIsImFnZW50QXNzaXN0YW50TWVzc2FnZUlkIjoiYWdlbnRfbXNnOmFzc2lzdGFudC1yb29tLWZpeHR1cmUtbGVnYWN5LWxlZ2FjeS1hIiwiY29sbGFib3JhdGlvbk1vZGUiOiJkZWZhdWx0IiwidHJhY2VJZCI6InRyYWNlOnJvb20tZml4dHVyZS1sZWdhY3ktbGVnYWN5LWEiLCJyb29tQWdlbnRNaXNzaW9uSWQiOiJtaXNzaW9uLWZpeHR1cmUtMDMiLCJyb29tQWdlbnRPcGVyYXRvclJlcXVlc3QiOiJTeW50aGV0aWMgb3BlcmF0b3IgcmVxdWVzdC4iLCJzZWxlY3RlZFRvb2xJZHMiOlsicm9vbTppbnNwZWN0Il0sImFnZW50VXNlck1lc3NhZ2VJZCI6ImFnZW50X21zZzp1c2VyLXJvb20tZml4dHVyZS1sZWdhY3ktbGVnYWN5LWEifX0seyJtaXNzaW9uSWQiOiJtaXNzaW9uLWZpeHR1cmUtMDQiLCJ0dXJuIjp7InJvb21JZCI6InJvb20tZml4dHVyZS1sZWdhY3kiLCJwYW5lSWQiOiJwYW5lOnJvb20tZml4dHVyZS1sZWdhY3ktbGVnYWN5LWIiLCJwcm9tcHQiOiJTeW50aGV0aWMgdHVybiBwcm9tcHQuIiwiYXJ0aWZhY3RJZHMiOltdLCJwcm92aWRlcklkIjpudWxsLCJtb2RlbElkIjoiZ3B0LXRlc3QiLCJyZWFzb25pbmdFZmZvcnQiOm51bGwsImFnZW50U2Vzc2lvbklkIjoic2Vzc2lvbjpyb29tLWZpeHR1cmUtbGVnYWN5LWxlZ2FjeS1iIiwiYWdlbnRSdW5JZCI6ImFnZW50X3J1bjpyb29tLWZpeHR1cmUtbGVnYWN5LWxlZ2FjeS1iIiwiYWdlbnRBc3Npc3RhbnRNZXNzYWdlSWQiOiJhZ2VudF9tc2c6YXNzaXN0YW50LXJvb20tZml4dHVyZS1sZWdhY3ktbGVnYWN5LWIiLCJjb2xsYWJvcmF0aW9uTW9kZSI6ImRlZmF1bHQiLCJ0cmFjZUlkIjoidHJhY2U6cm9vbS1maXh0dXJlLWxlZ2FjeS1sZWdhY3ktYiIsInJvb21BZ2VudE1pc3Npb25JZCI6Im1pc3Npb24tZml4dHVyZS0wNCIsInJvb21BZ2VudE9wZXJhdG9yUmVxdWVzdCI6IlN5bnRoZXRpYyBvcGVyYXRvciByZXF1ZXN0LiIsInNlbGVjdGVkVG9vbElkcyI6WyJyb29tOmluc3BlY3QiXSwiYWdlbnRVc2VyTWVzc2FnZUlkIjoiYWdlbnRfbXNnOnVzZXItcm9vbS1maXh0dXJlLWxlZ2FjeS1sZWdhY3ktYiJ9fV0sIm1vbml0b3JpbmdNaXNzaW9uSWRzIjpbXSwiaW50ZXJydXB0ZWQiOltdLCJwcm9jZXNzZWRDb3VudCI6MH0=" + } + ] + }, + "workflowExecutionTimeout": { + "seconds": "315360000" + }, + "workflowRunTimeout": { + "seconds": "315360000" + }, + "workflowTaskTimeout": { + "seconds": "10" + }, + "originalExecutionRunId": "4fa5c898-e71e-425c-89f9-179cc653e361", + "identity": "25805@cursor", + "firstExecutionRunId": "4fa5c898-e71e-425c-89f9-179cc653e361", + "attempt": 1, + "firstWorkflowTaskBackoff": {}, + "header": {} + } + }, + { + "eventId": "2", + "eventTime": { + "seconds": "3054748731", + "nanos": 311000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_SCHEDULED", + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "temporal-sandbox-tests-pre-patch", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "startToCloseTimeout": { + "seconds": "10" + }, + "attempt": 1 + } + }, + { + "eventId": "3", + "eventTime": { + "seconds": "3054748731", + "nanos": 311000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "2", + "identity": "25805@cursor", + "historySizeBytes": "1809" + } + }, + { + "eventId": "4", + "eventTime": { + "seconds": "3054748731", + "nanos": 685000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "2", + "identity": "25805@cursor", + "binaryChecksum": "@temporalio/worker@1.24.0+9a365569848f3b7720675da78257957763d6c9652a5349034772dfe58fee989c", + "sdkMetadata": { + "coreUsedFlags": [ + 2, + 3, + 1 + ], + "sdkName": "temporal-typescript", + "sdkVersion": "1.24.0" + }, + "meteringMetadata": {} + } + }, + { + "eventId": "5", + "eventTime": { + "seconds": "3054748731", + "nanos": 685000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_SCHEDULED", + "activityTaskScheduledEventAttributes": { + "activityId": "1", + "activityType": { + "name": "markRoomAgentMissionStarted" + }, + "taskQueue": { + "name": "temporal-sandbox-tests-pre-patch", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "header": {}, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJtaXNzaW9uSWQiOiJtaXNzaW9uLWZpeHR1cmUtMDMiLCJyb29tSWQiOiJyb29tLWZpeHR1cmUtbGVnYWN5IiwicGFuZUlkIjoicGFuZTpyb29tLWZpeHR1cmUtbGVnYWN5LWxlZ2FjeS1hIn0=" + } + ] + }, + "scheduleToCloseTimeout": { + "seconds": "315360000" + }, + "scheduleToStartTimeout": { + "seconds": "315360000" + }, + "startToCloseTimeout": { + "seconds": "60" + }, + "heartbeatTimeout": {}, + "workflowTaskCompletedEventId": "3", + "retryPolicy": { + "initialInterval": { + "seconds": "1" + }, + "backoffCoefficient": 2, + "maximumInterval": { + "seconds": "100" + }, + "maximumAttempts": 5 + } + } + }, + { + "eventId": "6", + "eventTime": { + "seconds": "3054748731", + "nanos": 697000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_EXECUTION_SIGNALED", + "workflowExecutionSignaledEventAttributes": { + "signalName": "stopRoomAgentMission", + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJtaXNzaW9uSWQiOiJtaXNzaW9uLWZpeHR1cmUtMDQiLCJyZWFzb24iOiJTeW50aGV0aWMgcHJlLXBhdGNoIHN0b3AuIn0=" + } + ] + }, + "identity": "25805@cursor", + "header": {} + } + }, + { + "eventId": "7", + "eventTime": { + "seconds": "3054748731", + "nanos": 697000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_SCHEDULED", + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "temporal-sandbox-tests-pre-patch", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "startToCloseTimeout": { + "seconds": "10" + }, + "attempt": 1 + } + }, + { + "eventId": "8", + "eventTime": { + "seconds": "3054748731", + "nanos": 697000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "7", + "identity": "25805@cursor", + "historySizeBytes": "2522" + } + }, + { + "eventId": "9", + "eventTime": { + "seconds": "3054748731", + "nanos": 707000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "7", + "identity": "25805@cursor", + "binaryChecksum": "@temporalio/worker@1.24.0+9a365569848f3b7720675da78257957763d6c9652a5349034772dfe58fee989c", + "sdkMetadata": { + "langUsedFlags": [ + 2 + ] + }, + "meteringMetadata": {} + } + }, + { + "eventId": "10", + "eventTime": { + "seconds": "3054748731", + "nanos": 685000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_STARTED", + "activityTaskStartedEventAttributes": { + "scheduledEventId": "5", + "identity": "25805@cursor", + "attempt": 1 + } + }, + { + "eventId": "11", + "eventTime": { + "seconds": "3054748731", + "nanos": 707000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_COMPLETED", + "activityTaskCompletedEventAttributes": { + "result": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "bnVsbA==" + } + ] + }, + "scheduledEventId": "5", + "startedEventId": "9", + "identity": "25805@cursor" + } + }, + { + "eventId": "12", + "eventTime": { + "seconds": "3054748731", + "nanos": 707000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_SCHEDULED", + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "temporal-sandbox-tests-pre-patch", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "startToCloseTimeout": { + "seconds": "10" + }, + "attempt": 2 + } + }, + { + "eventId": "13", + "eventTime": { + "seconds": "3054748731", + "nanos": 708000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "12", + "identity": "25805@cursor", + "historySizeBytes": "2872" + } + }, + { + "eventId": "14", + "eventTime": { + "seconds": "3054748731", + "nanos": 720000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "12", + "identity": "25805@cursor", + "binaryChecksum": "@temporalio/worker@1.24.0+9a365569848f3b7720675da78257957763d6c9652a5349034772dfe58fee989c", + "sdkMetadata": {}, + "meteringMetadata": {} + } + }, + { + "eventId": "15", + "eventTime": { + "seconds": "3054748731", + "nanos": 720000000 + }, + "eventType": "EVENT_TYPE_START_CHILD_WORKFLOW_EXECUTION_INITIATED", + "startChildWorkflowExecutionInitiatedEventAttributes": { + "namespace": "default", + "workflowId": "workflow:codex-app-server:room-fixture-legacy:pane:room-fixture-legacy-legacy-a:trace:room-fixture-legacy-legacy-a", + "workflowType": { + "name": "codexAppServerTurnWorkflow" + }, + "taskQueue": { + "name": "temporal-sandbox-tests-pre-patch", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJyb29tSWQiOiJyb29tLWZpeHR1cmUtbGVnYWN5IiwicGFuZUlkIjoicGFuZTpyb29tLWZpeHR1cmUtbGVnYWN5LWxlZ2FjeS1hIiwicHJvbXB0IjoiU3ludGhldGljIHR1cm4gcHJvbXB0LiIsImFydGlmYWN0SWRzIjpbXSwicHJvdmlkZXJJZCI6bnVsbCwibW9kZWxJZCI6ImdwdC10ZXN0IiwicmVhc29uaW5nRWZmb3J0IjpudWxsLCJhZ2VudFNlc3Npb25JZCI6InNlc3Npb246cm9vbS1maXh0dXJlLWxlZ2FjeS1sZWdhY3ktYSIsImFnZW50UnVuSWQiOiJhZ2VudF9ydW46cm9vbS1maXh0dXJlLWxlZ2FjeS1sZWdhY3ktYSIsInJvb21BZ2VudE1pc3Npb25JZCI6Im1pc3Npb24tZml4dHVyZS0wMyIsInJvb21BZ2VudE9wZXJhdG9yUmVxdWVzdCI6IlN5bnRoZXRpYyBvcGVyYXRvciByZXF1ZXN0LiIsImFnZW50VXNlck1lc3NhZ2VJZCI6ImFnZW50X21zZzp1c2VyLXJvb20tZml4dHVyZS1sZWdhY3ktbGVnYWN5LWEiLCJhZ2VudEFzc2lzdGFudE1lc3NhZ2VJZCI6ImFnZW50X21zZzphc3Npc3RhbnQtcm9vbS1maXh0dXJlLWxlZ2FjeS1sZWdhY3ktYSIsInNlbGVjdGVkVG9vbElkcyI6WyJyb29tOmluc3BlY3QiXSwicGVybWlzc2lvbk1vZGUiOm51bGwsImNvbGxhYm9yYXRpb25Nb2RlIjoiZGVmYXVsdCIsInRyYWNlSWQiOiJ0cmFjZTpyb29tLWZpeHR1cmUtbGVnYWN5LWxlZ2FjeS1hIn0=" + } + ] + }, + "workflowExecutionTimeout": {}, + "workflowRunTimeout": {}, + "workflowTaskTimeout": { + "seconds": "10" + }, + "workflowTaskCompletedEventId": "13", + "header": {}, + "memo": {} + } + }, + { + "eventId": "16", + "eventTime": { + "seconds": "3054748731", + "nanos": 721000000 + }, + "eventType": "EVENT_TYPE_CHILD_WORKFLOW_EXECUTION_STARTED", + "childWorkflowExecutionStartedEventAttributes": { + "namespace": "default", + "initiatedEventId": "15", + "workflowExecution": { + "workflowId": "workflow:codex-app-server:room-fixture-legacy:pane:room-fixture-legacy-legacy-a:trace:room-fixture-legacy-legacy-a", + "runId": "7c3d6a12-a738-4962-82a0-9fb1d2176a01" + }, + "workflowType": { + "name": "codexAppServerTurnWorkflow" + } + } + }, + { + "eventId": "17", + "eventTime": { + "seconds": "3054748731", + "nanos": 721000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_SCHEDULED", + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "temporal-sandbox-tests-pre-patch", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "startToCloseTimeout": { + "seconds": "10" + }, + "attempt": 1 + } + }, + { + "eventId": "18", + "eventTime": { + "seconds": "3054748731", + "nanos": 721000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "17", + "identity": "25805@cursor", + "historySizeBytes": "4267" + } + }, + { + "eventId": "19", + "eventTime": { + "seconds": "3054748731", + "nanos": 968000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "17", + "identity": "25805@cursor", + "binaryChecksum": "@temporalio/worker@1.24.0+9a365569848f3b7720675da78257957763d6c9652a5349034772dfe58fee989c", + "sdkMetadata": {}, + "meteringMetadata": {} + } + }, + { + "eventId": "20", + "eventTime": { + "seconds": "3054748731", + "nanos": 972000000 + }, + "eventType": "EVENT_TYPE_CHILD_WORKFLOW_EXECUTION_COMPLETED", + "childWorkflowExecutionCompletedEventAttributes": { + "result": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJ3b3JrZmxvd0lkIjoic3BhY2UtdHVybjp0cmFjZTpyb29tLWZpeHR1cmUtbGVnYWN5LWxlZ2FjeS1hIiwicm9vbUlkIjoicm9vbS1maXh0dXJlLWxlZ2FjeSIsInBhbmVJZCI6InBhbmU6cm9vbS1maXh0dXJlLWxlZ2FjeS1sZWdhY3ktYSIsInRyYWNlSWQiOiJ0cmFjZTpyb29tLWZpeHR1cmUtbGVnYWN5LWxlZ2FjeS1hIiwic3RhdHVzIjoiQ09NUExFVEVEIiwibWVzc2FnZSI6IlN5bnRoZXRpYyB0dXJuIGNvbXBsZXRlZC4iLCJyb29tQWdlbnRPdXRjb21lIjp7InN0YXR1cyI6IlZFUklGSUVEIiwiZXhlY3V0ZWRBY3Rpb25Db3VudCI6MSwic3RhdHVzUmVhc29uIjoiU3ludGhldGljIHJvb20tYWdlbnQgdHVybiB2ZXJpZmllZC4ifX0=" + } + ] + }, + "namespace": "default", + "workflowExecution": { + "workflowId": "workflow:codex-app-server:room-fixture-legacy:pane:room-fixture-legacy-legacy-a:trace:room-fixture-legacy-legacy-a", + "runId": "7c3d6a12-a738-4962-82a0-9fb1d2176a01" + }, + "workflowType": { + "name": "codexAppServerTurnWorkflow" + }, + "initiatedEventId": "15", + "startedEventId": "16" + } + }, + { + "eventId": "21", + "eventTime": { + "seconds": "3054748731", + "nanos": 972000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_SCHEDULED", + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "temporal-sandbox-tests-pre-patch", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "startToCloseTimeout": { + "seconds": "10" + }, + "attempt": 1 + } + }, + { + "eventId": "22", + "eventTime": { + "seconds": "3054748731", + "nanos": 973000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "21", + "identity": "25805@cursor", + "historySizeBytes": "5119" + } + }, + { + "eventId": "23", + "eventTime": { + "seconds": "3054748731", + "nanos": 976000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "21", + "identity": "25805@cursor", + "binaryChecksum": "@temporalio/worker@1.24.0+9a365569848f3b7720675da78257957763d6c9652a5349034772dfe58fee989c", + "sdkMetadata": {}, + "meteringMetadata": {} + } + }, + { + "eventId": "24", + "eventTime": { + "seconds": "3054748731", + "nanos": 976000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_SCHEDULED", + "activityTaskScheduledEventAttributes": { + "activityId": "2", + "activityType": { + "name": "markRoomAgentMissionFinished" + }, + "taskQueue": { + "name": "temporal-sandbox-tests-pre-patch", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "header": {}, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJtaXNzaW9uSWQiOiJtaXNzaW9uLWZpeHR1cmUtMDMiLCJyb29tSWQiOiJyb29tLWZpeHR1cmUtbGVnYWN5Iiwic3RhdHVzIjoiQ09NUExFVEVEIiwic3RhdHVzUmVhc29uIjoiU3ludGhldGljIHJvb20tYWdlbnQgdHVybiB2ZXJpZmllZC4ifQ==" + } + ] + }, + "scheduleToCloseTimeout": { + "seconds": "315360000" + }, + "scheduleToStartTimeout": { + "seconds": "315360000" + }, + "startToCloseTimeout": { + "seconds": "60" + }, + "heartbeatTimeout": {}, + "workflowTaskCompletedEventId": "22", + "retryPolicy": { + "initialInterval": { + "seconds": "1" + }, + "backoffCoefficient": 2, + "maximumInterval": { + "seconds": "100" + }, + "maximumAttempts": 5 + } + } + }, + { + "eventId": "25", + "eventTime": { + "seconds": "3054748731", + "nanos": 976000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_STARTED", + "activityTaskStartedEventAttributes": { + "scheduledEventId": "24", + "identity": "25805@cursor", + "attempt": 1 + } + }, + { + "eventId": "26", + "eventTime": { + "seconds": "3054748731", + "nanos": 978000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_COMPLETED", + "activityTaskCompletedEventAttributes": { + "result": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "bnVsbA==" + } + ] + }, + "scheduledEventId": "24", + "startedEventId": "25", + "identity": "25805@cursor" + } + }, + { + "eventId": "27", + "eventTime": { + "seconds": "3054748731", + "nanos": 978000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_SCHEDULED", + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "temporal-sandbox-tests-pre-patch", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "startToCloseTimeout": { + "seconds": "10" + }, + "attempt": 1 + } + }, + { + "eventId": "28", + "eventTime": { + "seconds": "3054748731", + "nanos": 978000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "27", + "identity": "25805@cursor", + "historySizeBytes": "5779" + } + }, + { + "eventId": "29", + "eventTime": { + "seconds": "3054748731", + "nanos": 981000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "27", + "identity": "25805@cursor", + "binaryChecksum": "@temporalio/worker@1.24.0+9a365569848f3b7720675da78257957763d6c9652a5349034772dfe58fee989c", + "sdkMetadata": {}, + "meteringMetadata": {} + } + }, + { + "eventId": "30", + "eventTime": { + "seconds": "3054748731", + "nanos": 981000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_SCHEDULED", + "activityTaskScheduledEventAttributes": { + "activityId": "3", + "activityType": { + "name": "markRoomAgentMissionFinished" + }, + "taskQueue": { + "name": "temporal-sandbox-tests-pre-patch", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "header": {}, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJtaXNzaW9uSWQiOiJtaXNzaW9uLWZpeHR1cmUtMDQiLCJyb29tSWQiOiJyb29tLWZpeHR1cmUtbGVnYWN5Iiwic3RhdHVzIjoiSU5URVJSVVBURUQiLCJzdGF0dXNSZWFzb24iOiJTeW50aGV0aWMgcHJlLXBhdGNoIHN0b3AuIn0=" + } + ] + }, + "scheduleToCloseTimeout": { + "seconds": "315360000" + }, + "scheduleToStartTimeout": { + "seconds": "315360000" + }, + "startToCloseTimeout": { + "seconds": "60" + }, + "heartbeatTimeout": {}, + "workflowTaskCompletedEventId": "28", + "retryPolicy": { + "initialInterval": { + "seconds": "1" + }, + "backoffCoefficient": 2, + "maximumInterval": { + "seconds": "100" + }, + "maximumAttempts": 5 + } + } + }, + { + "eventId": "31", + "eventTime": { + "seconds": "3054748731", + "nanos": 981000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_STARTED", + "activityTaskStartedEventAttributes": { + "scheduledEventId": "30", + "identity": "25805@cursor", + "attempt": 1 + } + }, + { + "eventId": "32", + "eventTime": { + "seconds": "3054748731", + "nanos": 982000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_COMPLETED", + "activityTaskCompletedEventAttributes": { + "result": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "bnVsbA==" + } + ] + }, + "scheduledEventId": "30", + "startedEventId": "31", + "identity": "25805@cursor" + } + }, + { + "eventId": "33", + "eventTime": { + "seconds": "3054748731", + "nanos": 982000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_SCHEDULED", + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "temporal-sandbox-tests-pre-patch", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "startToCloseTimeout": { + "seconds": "10" + }, + "attempt": 1 + } + }, + { + "eventId": "34", + "eventTime": { + "seconds": "3054748731", + "nanos": 982000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "33", + "identity": "25805@cursor", + "historySizeBytes": "6431" + } + }, + { + "eventId": "35", + "eventTime": { + "seconds": "3054748731", + "nanos": 985000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "33", + "identity": "25805@cursor", + "binaryChecksum": "@temporalio/worker@1.24.0+9a365569848f3b7720675da78257957763d6c9652a5349034772dfe58fee989c", + "sdkMetadata": {}, + "meteringMetadata": {} + } + }, + { + "eventId": "36", + "eventTime": { + "seconds": "3054748731", + "nanos": 985000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_EXECUTION_CANCEL_REQUESTED", + "workflowExecutionCancelRequestedEventAttributes": { + "identity": "25805@cursor" + } + }, + { + "eventId": "37", + "eventTime": { + "seconds": "3054748731", + "nanos": 985000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_SCHEDULED", + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "temporal-sandbox-tests-pre-patch", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "startToCloseTimeout": { + "seconds": "10" + }, + "attempt": 2 + } + }, + { + "eventId": "38", + "eventTime": { + "seconds": "3054748731", + "nanos": 985000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "37", + "identity": "25805@cursor", + "historySizeBytes": "6701" + } + }, + { + "eventId": "39", + "eventTime": { + "seconds": "3054748731", + "nanos": 988000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "37", + "identity": "25805@cursor", + "binaryChecksum": "@temporalio/worker@1.24.0+9a365569848f3b7720675da78257957763d6c9652a5349034772dfe58fee989c", + "sdkMetadata": {}, + "meteringMetadata": {} + } + }, + { + "eventId": "40", + "eventTime": { + "seconds": "3054748731", + "nanos": 988000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_EXECUTION_CANCELED", + "workflowExecutionCanceledEventAttributes": { + "workflowTaskCompletedEventId": "38", + "details": {} + } + } + ] + } +} diff --git a/apps/worker/tests/support/all-workflows.ts b/apps/worker/tests/support/all-workflows.ts new file mode 100644 index 000000000..5c760d970 --- /dev/null +++ b/apps/worker/tests/support/all-workflows.ts @@ -0,0 +1,7 @@ +/** + * Test-only bundle entry: re-exports every production workflow so the + * time-skipping worker registers all 8 workflows from issue #60. + */ +export * from "../../src/workflows.js"; +export * from "../../src/memory-maintenance-workflows.js"; +export * from "../../src/memory-mutation-workflows.js"; diff --git a/apps/worker/tests/support/determinism-violation-workflow.ts b/apps/worker/tests/support/determinism-violation-workflow.ts new file mode 100644 index 000000000..48f09298b --- /dev/null +++ b/apps/worker/tests/support/determinism-violation-workflow.ts @@ -0,0 +1,34 @@ +/** + * TEST-ONLY workflow variant used by the replay gate's negative test + * (issue #60): identical to dotGoalWorkflow from src/workflows.ts, but it + * schedules one extra timer at the start. Replaying a history recorded + * from the real workflow against this variant must fail with + * DeterminismViolationError - proving the gate actually detects + * non-replay-compatible changes instead of silently passing. + */ +import { condition, continueAsNew, defineSignal, proxyActivities, setHandler, sleep, workflowInfo } from "@temporalio/workflow"; +import { DOT_GOAL_SIGNAL } from "@space/contracts"; +import type * as activities from "../../src/activities.js"; +import { runDotGoalLoop } from "../../src/dot-goal-loop.js"; + +const dotGoalActivities = proxyActivities({ + startToCloseTimeout: "2 minutes", + retry: { maximumAttempts: 20, initialInterval: "5 seconds", backoffCoefficient: 2, maximumInterval: "2 minutes" } +}); +export const dotGoalWakeSignal = defineSignal<[]>(DOT_GOAL_SIGNAL); + +export async function dotGoalWorkflow(input: { goalId: string }): Promise<{ goalStatus: string }> { + // The deliberate replay violation: an extra timer the recorded history + // does not contain. + await sleep("1 second"); + let woken = false; + setHandler(dotGoalWakeSignal, () => { woken = true; }); + const result = await runDotGoalLoop(input.goalId, { + step: (goalId) => dotGoalActivities.dotGoalStep(goalId), + observe: (goalId, runId) => dotGoalActivities.dotGoalObserve(goalId, runId), + wait: async (timeout) => { await condition(() => woken, timeout); woken = false; }, + shouldContinueAsNew: () => workflowInfo().continueAsNewSuggested || workflowInfo().historyLength > 10_000 + }, 300); + if (result.goalStatus === null) await continueAsNew({ goalId: input.goalId }); + return { goalStatus: result.goalStatus ?? "UNKNOWN" }; +} diff --git a/apps/worker/tests/temporal-sandbox/activity-stubs.ts b/apps/worker/tests/temporal-sandbox/activity-stubs.ts new file mode 100644 index 000000000..44ee6b8b5 --- /dev/null +++ b/apps/worker/tests/temporal-sandbox/activity-stubs.ts @@ -0,0 +1,140 @@ +/** + * Configurable, recording activity stubs for the real time-skipping + * tests. Activities run in-process with the worker, so the same + * ActivityStubs instance is shared between the test process and the + * activity implementations. + */ +import { Context } from "@temporalio/activity"; +import { consolidationRun, dummyTurnResult, memoryMaintenanceResult, turnWorkflowResult } from "./stubs.js"; + +type Behavior = (input: any, callIndex: number) => unknown; + +export class ActivityStubs { + private readonly callsByKey = new Map(); + private readonly overrides = new Map(); + + reset(): void { + this.callsByKey.clear(); + this.overrides.clear(); + } + + override(name: string, behavior: Behavior): void { + this.overrides.set(name, behavior); + } + + callsOf(name: string): any[] { + return (this.callsByKey.get(name) ?? []) as any[]; + } + + count(name: string): number { + return this.callsOf(name).length; + } + + /** Wraps a default behavior into an activity implementation bound to this instance. */ + bind(name: string, defaults: Behavior): (input: any) => unknown { + return async (input: any) => { + const callIndex = this.count(name); + const list = this.callsByKey.get(name) ?? []; + list.push(input); + this.callsByKey.set(name, list); + const behavior = this.overrides.get(name) ?? defaults; + return await behavior(input, callIndex); + }; + } + + /** Waits (real time) until at least minCount calls of the activity were made. */ + async waitForCall(name: string, minCount = 1, timeoutMs = 20000): Promise { + const deadline = Date.now() + timeoutMs; + for (;;) { + const calls = this.callsOf(name); + if (calls.length >= minCount) return calls; + if (Date.now() > deadline) { + throw new Error("Timed out waiting for " + minCount + " call(s) of activity " + name + " (seen " + calls.length + ")"); + } + await new Promise((resolve) => setTimeout(resolve, 10)); + } + } +} + +/** Never resolves; rejects when the enclosing activity is cancelled. */ +export function blockUntilCancelled(): Promise { + // Cancellation is delivered through heartbeats on SDK 1.24, so keep + // heartbeating while the activity waits for the workflow cancel request. + const ctx = Context.current(); + const timer = setInterval(() => ctx.heartbeat(), 100); + return ctx.cancelled.finally(() => clearInterval(timer)); +} + +/** A promise that stays pending until open() is called from the test. */ +export function makeGate(): { promise: Promise; open(): void } { + let open!: () => void; + const promise = new Promise((resolve) => { + open = resolve; + }); + return { promise, open }; +} + +/** Turn result with a verified room-agent outcome (used by supervisor children). */ +function verifiedTurnResult(input: any) { + return turnWorkflowResult(input, { + roomAgentOutcome: { + status: "VERIFIED", + executedActionCount: 1, + statusReason: "Synthetic room-agent turn verified." + } + }); +} + +/** + * The full set of synthetic activity implementations for all workflows from + * issue #60. Register the result on every task queue worker. + */ +export function buildActivities(stubs: ActivityStubs): Record unknown> { + return { + // dotGoalWorkflow + dotGoalStep: stubs.bind("dotGoalStep", () => ({ state: "DONE", runId: null, goalStatus: "COMPLETED" })), + dotGoalObserve: stubs.bind("dotGoalObserve", () => ({ run: "IDLE", goalStatus: "COMPLETED" })), + // dummyTurnWorkflow / codexAppServerTurnWorkflow + recordDummyTurnStarted: stubs.bind("recordDummyTurnStarted", (input) => dummyTurnResult(input)), + recordDummyTurnCompleted: stubs.bind("recordDummyTurnCompleted", (input) => dummyTurnResult(input)), + runCodexAppServerTurn: stubs.bind("runCodexAppServerTurn", (input) => verifiedTurnResult(input)), + runOpenCodeAgentTurn: stubs.bind("runOpenCodeAgentTurn", (input) => turnWorkflowResult(input)), + runCliAgentTurn: stubs.bind("runCliAgentTurn", (input) => turnWorkflowResult(input)), + // roomAgentSupervisorWorkflow (legacy + continuing branches) + markRoomAgentMissionStarted: stubs.bind("markRoomAgentMissionStarted", () => null), + markRoomAgentMissionFinished: stubs.bind("markRoomAgentMissionFinished", () => null), + markRoomAgentMissionContinued: stubs.bind("markRoomAgentMissionContinued", () => null), + settleRoomAgentBackgroundMission: stubs.bind("settleRoomAgentBackgroundMission", () => true), + discardRoomAgentSupervisorTurn: stubs.bind("discardRoomAgentSupervisorTurn", () => null), + finishRoomAgentSupervisorTurn: stubs.bind("finishRoomAgentSupervisorTurn", () => null), + checkRoomAgentSupervisorTurn: stubs.bind("checkRoomAgentSupervisorTurn", () => "READY"), + prepareRoomAgentBackgroundContinuation: stubs.bind("prepareRoomAgentBackgroundContinuation", () => ({ state: "WAITING" })), + // roomAgentActionWorkflow + runRoomAgentBackgroundAction: stubs.bind("runRoomAgentBackgroundAction", () => ({ + status: "EXECUTED", + statusReason: "Synthetic background action executed.", + paneId: null, + missionId: null, + evidence: {} + })), + failRoomAgentBackgroundAction: stubs.bind("failRoomAgentBackgroundAction", () => null), + // memoryMaintenanceWorkflow / memoryConsolidationWorkflow / memoryMutationWorkflow + createScheduledMemoryAudit: stubs.bind("createScheduledMemoryAudit", (input) => consolidationRun("run-" + input.traceId)), + finalizeMemoryRepair: stubs.bind("finalizeMemoryRepair", (input) => consolidationRun(input.runId)), + prepareMemoryConsolidation: stubs.bind("prepareMemoryConsolidation", (input) => ({ + run: consolidationRun(input.runId), + changeSetId: "changeset-0001" + })), + reconcileStaleSpaceAgentSessions: stubs.bind("reconcileStaleSpaceAgentSessions", () => 0), + refreshMemoryGraphSnapshot: stubs.bind("refreshMemoryGraphSnapshot", () => memoryMaintenanceResult()), + refreshAllMonthsMemoryGraphSnapshot: stubs.bind("refreshAllMonthsMemoryGraphSnapshot", () => + memoryMaintenanceResult({ generatedAt: "2026-01-01T01:00:00.000Z" }) + ), + executePersistedMemoryChangeSet: stubs.bind("executePersistedMemoryChangeSet", (input) => ({ + status: "APPLIED", + changeSetId: input.changeSetId, + resultingSourceHash: "0123456789abcdef".repeat(4), + reason: null + })) + }; +} diff --git a/apps/worker/tests/temporal-sandbox/dot-goal.test.ts b/apps/worker/tests/temporal-sandbox/dot-goal.test.ts new file mode 100644 index 000000000..e6cc51bc4 --- /dev/null +++ b/apps/worker/tests/temporal-sandbox/dot-goal.test.ts @@ -0,0 +1,94 @@ +/** + * dotGoalWorkflow on the real time-skipping test server (issue #60). + */ +import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { DOT_GOAL_SIGNAL } from "@space/contracts"; +import { ActivityStubs, buildActivities } from "./activity-stubs.js"; +import { createSandboxEnv, MAIN_TASK_QUEUE, type SandboxEnv } from "./sandbox.js"; + +const stubs = new ActivityStubs(); +let env: SandboxEnv; + +beforeAll(async () => { + env = await createSandboxEnv(); + await env.startWorker({ taskQueue: MAIN_TASK_QUEUE, activities: buildActivities(stubs) }); +}, 120000); + +beforeEach(() => stubs.reset()); + +afterAll(async () => { + await env.teardown(); +}, 60000); + +function startDotGoal(workflowId: string, goalId: string) { + return env.testEnv.client.workflow.start("dotGoalWorkflow", { + taskQueue: MAIN_TASK_QUEUE, + workflowId, + args: [{ goalId }] + }); +} + +describe("dotGoalWorkflow on the time-skipping test server", () => { + it("returns the goal status when the first step reports DONE", async () => { + stubs.override("dotGoalStep", () => ({ state: "DONE", runId: null, goalStatus: "COMPLETED" })); + + const handle = await startDotGoal("dot-goal-test-done", "goal-0001"); + + const result = await handle.result(); + expect(result).toEqual({ goalStatus: "COMPLETED" }); + expect(stubs.count("dotGoalStep")).toBe(1); + expect(stubs.count("dotGoalObserve")).toBe(0); + }, 60000); + + it("keeps a wake-up that arrives during a step and ends the next wait at once", async () => { + const steps = [ + { state: "WAITING", runId: null, goalStatus: "UNKNOWN" }, + { state: "DONE", runId: null, goalStatus: "COMPLETED" } + ]; + stubs.override("dotGoalStep", (_input, index) => steps[index]); + + const handle = await startDotGoal("dot-goal-test-wake", "goal-0002"); + // The signal handler is registered before the first step; wait for the + // first step so the wake-up is guaranteed to land inside the wait. + await stubs.waitForCall("dotGoalStep", 1); + await handle.signal(DOT_GOAL_SIGNAL); + + const result = await handle.result(); + expect(result).toEqual({ goalStatus: "COMPLETED" }); + expect(stubs.count("dotGoalStep")).toBe(2); + }, 60000); + + it("waits for the full timeout when no wake-up arrives and steps again", async () => { + const steps = [ + { state: "WAITING", runId: null, goalStatus: "UNKNOWN" }, + { state: "DONE", runId: null, goalStatus: "COMPLETED" } + ]; + stubs.override("dotGoalStep", (_input, index) => steps[index]); + + const handle = await startDotGoal("dot-goal-test-timeout", "goal-0003"); + + const result = await handle.result(); + expect(result).toEqual({ goalStatus: "COMPLETED" }); + expect(stubs.count("dotGoalStep")).toBe(2); + }, 60000); + + it("continues as a new run when the goal is still unfinished after the iteration budget", async () => { + stubs.override("dotGoalStep", () => ({ state: "WAITING", runId: null, goalStatus: "UNKNOWN" })); + + const handle = await startDotGoal("dot-goal-test-continue-as-new", "goal-0004"); + const firstRunId = (await handle.describe()).runId; + + // Every WAITING step waits 30 seconds of server time and the iteration + // budget is 300, so drive the clock forward until the workflow continues + // as a new run. Waiting in real time would take hours; time is only + // skipped automatically while a result is awaited. + for (let i = 0; i < 320; i++) { + await env.testEnv.sleep("30 seconds"); + if ((await handle.describe()).runId !== firstRunId) break; + } + expect(stubs.count("dotGoalStep")).toBeGreaterThanOrEqual(300); + + await handle.cancel(); + await expect(handle.result()).rejects.toThrow(); + }, 180000); +}); diff --git a/apps/worker/tests/temporal-sandbox/memory.test.ts b/apps/worker/tests/temporal-sandbox/memory.test.ts new file mode 100644 index 000000000..7712edfce --- /dev/null +++ b/apps/worker/tests/temporal-sandbox/memory.test.ts @@ -0,0 +1,195 @@ +/** + * memoryMaintenanceWorkflow, memoryConsolidationWorkflow and + * memoryMutationWorkflow on the real time-skipping test server (issue #60). + */ +import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { ActivityStubs, buildActivities } from "./activity-stubs.js"; +import { consolidationRun, HEX64, memoryMaintenanceResult } from "./stubs.js"; +import { + createSandboxEnv, + MAIN_TASK_QUEUE, + MEMORY_MAINTENANCE_TASK_QUEUE, + MEMORY_MUTATION_TASK_QUEUE, + type SandboxEnv +} from "./sandbox.js"; + +const stubs = new ActivityStubs(); +let env: SandboxEnv; + +beforeAll(async () => { + env = await createSandboxEnv(); + // All three task queues host workers with the real workflows and stubbed + // activities: the maintenance workflow proxies the mutation activity to + // the space-memory-mutations queue. + await env.startWorker({ taskQueue: MAIN_TASK_QUEUE, activities: buildActivities(stubs) }); + await env.startWorker({ taskQueue: MEMORY_MAINTENANCE_TASK_QUEUE, activities: buildActivities(stubs) }); + await env.startWorker({ taskQueue: MEMORY_MUTATION_TASK_QUEUE, activities: buildActivities(stubs) }); +}, 120000); + +beforeEach(() => stubs.reset()); + +afterAll(async () => { + await env.teardown(); +}, 60000); + +const maintenanceInput = (suffix: string) => ({ + scheduledAt: "2026-01-01T00:00:00.000Z", + traceId: "trace:memory-" + suffix +}); + +describe("memoryMaintenanceWorkflow", () => { + it("refreshes the snapshot, audits, and runs the consolidation child with a persisted change set", async () => { + const handle = await env.testEnv.client.workflow.start("memoryMaintenanceWorkflow", { + taskQueue: MEMORY_MAINTENANCE_TASK_QUEUE, + workflowId: "memory-maint-test-01", + args: [maintenanceInput("01")] + }); + + const result = await handle.result(); + + expect(result).toEqual(memoryMaintenanceResult()); + expect(stubs.count("refreshMemoryGraphSnapshot")).toBe(1); + expect(stubs.count("refreshAllMonthsMemoryGraphSnapshot")).toBe(1); + expect(stubs.count("reconcileStaleSpaceAgentSessions")).toBe(1); + expect(stubs.callsOf("createScheduledMemoryAudit")[0]).toMatchObject({ + traceId: "trace:memory-01", + sourceHash: HEX64 + }); + // The child memoryConsolidationWorkflow ran to completion through the + // mutation task queue. + expect(stubs.callsOf("prepareMemoryConsolidation")[0]).toMatchObject({ runId: "run-trace:memory-01", traceId: "trace:memory-01" }); + expect(stubs.callsOf("executePersistedMemoryChangeSet")[0]).toMatchObject({ + changeSetId: "changeset-0001", + traceId: "trace:memory-01" + }); + expect(stubs.count("finalizeMemoryRepair")).toBe(1); + }, 60000); + + it("keeps maintaining memory when the all-months refresh and session reconciliation fail", async () => { + stubs.override("refreshAllMonthsMemoryGraphSnapshot", () => { + throw new Error("synthetic all-months failure"); + }); + stubs.override("reconcileStaleSpaceAgentSessions", () => { + throw new Error("synthetic reconciliation failure"); + }); + + const handle = await env.testEnv.client.workflow.start("memoryMaintenanceWorkflow", { + taskQueue: MEMORY_MAINTENANCE_TASK_QUEUE, + workflowId: "memory-maint-test-02", + args: [maintenanceInput("02")] + }); + + const result = await handle.result(); + + expect(result.status).toBe("REFRESHED"); + expect(stubs.count("refreshAllMonthsMemoryGraphSnapshot")).toBe(3); + expect(stubs.count("reconcileStaleSpaceAgentSessions")).toBe(3); + expect(stubs.count("prepareMemoryConsolidation")).toBe(1); + expect(stubs.count("finalizeMemoryRepair")).toBe(1); + }, 60000); + + it("retries a failing snapshot refresh and completes once the activity recovers", async () => { + stubs.override("refreshMemoryGraphSnapshot", (_input, index) => { + if (index < 2) throw new Error("synthetic refresh failure"); + return memoryMaintenanceResult(); + }); + + const handle = await env.testEnv.client.workflow.start("memoryMaintenanceWorkflow", { + taskQueue: MEMORY_MAINTENANCE_TASK_QUEUE, + workflowId: "memory-maint-test-03", + args: [maintenanceInput("03")] + }); + + const result = await handle.result(); + expect(result.status).toBe("REFRESHED"); + expect(stubs.count("refreshMemoryGraphSnapshot")).toBe(3); + }, 60000); +}); + +describe("memoryConsolidationWorkflow", () => { + it("applies a prepared change set and finalizes the repair", async () => { + const handle = await env.testEnv.client.workflow.start("memoryConsolidationWorkflow", { + taskQueue: MEMORY_MAINTENANCE_TASK_QUEUE, + workflowId: "memory-cons-test-01", + args: [{ runId: "run-trace:memory-04", traceId: "trace:memory-04" }] + }); + + const result = await handle.result(); + + expect(result).toEqual(consolidationRun("run-trace:memory-04")); + expect(stubs.callsOf("prepareMemoryConsolidation")[0]).toMatchObject({ runId: "run-trace:memory-04" }); + expect(stubs.count("executePersistedMemoryChangeSet")).toBe(1); + expect(stubs.count("finalizeMemoryRepair")).toBe(1); + }, 60000); + + it("returns the prepared run unchanged when there is no change set to apply", async () => { + stubs.override("prepareMemoryConsolidation", (input) => ({ + run: consolidationRun(input.runId), + changeSetId: null + })); + + const handle = await env.testEnv.client.workflow.start("memoryConsolidationWorkflow", { + taskQueue: MEMORY_MAINTENANCE_TASK_QUEUE, + workflowId: "memory-cons-test-02", + args: [{ runId: "run-trace:memory-05", traceId: "trace:memory-05" }] + }); + + const result = await handle.result(); + + expect(result).toEqual(consolidationRun("run-trace:memory-05")); + expect(stubs.count("executePersistedMemoryChangeSet")).toBe(0); + expect(stubs.count("finalizeMemoryRepair")).toBe(0); + }, 60000); + + it("fails the consolidation when the mutation activity keeps failing", async () => { + stubs.override("executePersistedMemoryChangeSet", () => { + throw new Error("synthetic mutation failure"); + }); + + const handle = await env.testEnv.client.workflow.start("memoryConsolidationWorkflow", { + taskQueue: MEMORY_MAINTENANCE_TASK_QUEUE, + workflowId: "memory-cons-test-03", + args: [{ runId: "run-trace:memory-06", traceId: "trace:memory-06" }] + }); + + await expect(handle.result()).rejects.toThrow(); + expect(stubs.count("executePersistedMemoryChangeSet")).toBe(3); + expect(stubs.count("finalizeMemoryRepair")).toBe(0); + }, 60000); +}); + +describe("memoryMutationWorkflow", () => { + it("applies a persisted change set on the mutation task queue", async () => { + const handle = await env.testEnv.client.workflow.start("memoryMutationWorkflow", { + taskQueue: MEMORY_MUTATION_TASK_QUEUE, + workflowId: "memory-mut-test-01", + args: [{ changeSetId: "changeset-0002", traceId: "trace:memory-07" }] + }); + + const result = await handle.result(); + + expect(result).toMatchObject({ status: "APPLIED", changeSetId: "changeset-0002" }); + expect(stubs.callsOf("executePersistedMemoryChangeSet")[0]).toMatchObject({ + changeSetId: "changeset-0002", + traceId: "trace:memory-07" + }); + }, 60000); + + it("retries a failing mutation and completes once the activity recovers", async () => { + stubs.override("executePersistedMemoryChangeSet", (input, index) => { + if (index < 2) throw new Error("synthetic mutation failure"); + return { status: "APPLIED", changeSetId: input.changeSetId, resultingSourceHash: HEX64, reason: null }; + }); + + const handle = await env.testEnv.client.workflow.start("memoryMutationWorkflow", { + taskQueue: MEMORY_MUTATION_TASK_QUEUE, + workflowId: "memory-mut-test-02", + args: [{ changeSetId: "changeset-0003", traceId: "trace:memory-08" }] + }); + + const result = await handle.result(); + + expect(result.status).toBe("APPLIED"); + expect(stubs.count("executePersistedMemoryChangeSet")).toBe(3); + }, 60000); +}); diff --git a/apps/worker/tests/temporal-sandbox/record-histories.test.ts b/apps/worker/tests/temporal-sandbox/record-histories.test.ts new file mode 100644 index 000000000..e4e52878c --- /dev/null +++ b/apps/worker/tests/temporal-sandbox/record-histories.test.ts @@ -0,0 +1,209 @@ +/** + * Records synthetic workflow histories for the replay gate (issue #60). + * + * Skipped unless RECORD_HISTORIES=1; run with: + * npm run temporal:record-histories -w @space/worker + * + * Histories are recorded from the REAL workflow code on the time-skipping + * test server with stubbed activities, and contain synthetic data only. + * The supervisor pre-patch history is recorded from the production + * workflowsPath with patchActivationCallback forcing the legacy branch. + */ +import { mkdir, writeFile } from "node:fs/promises"; +import { resolve } from "node:path"; +import { fileURLToPath } from "node:url"; +import { afterAll, beforeAll, describe, expect, it } from "vitest"; +import * as proto from "@temporalio/proto"; +import { DOT_GOAL_SIGNAL, ROOM_AGENT_STOP_SIGNAL } from "@space/contracts"; +import { ActivityStubs, buildActivities, makeGate } from "./activity-stubs.js"; +import { nativeTurnInput, roomAgentTurnInput, roomActionBridgeRequest, supervisorInput, supervisorQueueItem } from "./stubs.js"; +import { + createSandboxEnv, + MAIN_TASK_QUEUE, + MEMORY_MAINTENANCE_TASK_QUEUE, + MEMORY_MUTATION_TASK_QUEUE, + PRE_PATCH_TASK_QUEUE, + ROOM_BACKGROUND_CONTINUATION_PATCH, + waitForRunIdChange, + type SandboxEnv +} from "./sandbox.js"; + +const recordMode = process.env.RECORD_HISTORIES === "1"; +const suite = recordMode ? describe : describe.skip; + +const __dirname = fileURLToPath(new URL(".", import.meta.url)); +const FIXTURES_DIR = resolve(__dirname, "../fixtures/workflow-histories"); + +const stubs = new ActivityStubs(); +let env: SandboxEnv; + +async function recordHistory(workflowId: string, runId: string | undefined, fileName: string): Promise { + const handle = env.testEnv.client.workflow.getHandle(workflowId, runId); + // Normalize enum names to their numeric proto values so the replay worker + // can encode the history back to protobuf without any conversion layer. + const history = proto.temporal.api.history.v1.History.fromObject(await handle.fetchHistory()); + await mkdir(FIXTURES_DIR, { recursive: true }); + await writeFile(resolve(FIXTURES_DIR, fileName), JSON.stringify({ workflowId, history }, null, 2) + "\n"); +} + +suite("record workflow histories for the replay gate", () => { + beforeAll(async () => { + env = await createSandboxEnv(); + await env.startWorker({ taskQueue: MAIN_TASK_QUEUE, activities: buildActivities(stubs) }); + await env.startWorker({ taskQueue: MEMORY_MAINTENANCE_TASK_QUEUE, activities: buildActivities(stubs) }); + await env.startWorker({ taskQueue: MEMORY_MUTATION_TASK_QUEUE, activities: buildActivities(stubs) }); + await env.startWorker({ + taskQueue: PRE_PATCH_TASK_QUEUE, + activities: buildActivities(stubs), + disablePatches: [ROOM_BACKGROUND_CONTINUATION_PATCH] + }); + }, 180000); + + afterAll(async () => { + await env.teardown(); + }, 60000); + + it("records one history per workflow plus the pre-patch supervisor", async () => { + stubs.reset(); + + // dotGoalWorkflow: one WAITING step, a wake-up signal, then DONE. + const steps = [ + { state: "WAITING", runId: null, goalStatus: "UNKNOWN" }, + { state: "DONE", runId: null, goalStatus: "COMPLETED" } + ]; + stubs.override("dotGoalStep", (_input, index) => steps[index]); + let handle = await env.testEnv.client.workflow.start("dotGoalWorkflow", { + taskQueue: MAIN_TASK_QUEUE, workflowId: "fixture-dot-goal", args: [{ goalId: "goal-fixture-01" }] + }); + await stubs.waitForCall("dotGoalStep", 1); + await handle.signal(DOT_GOAL_SIGNAL); + await handle.result(); + await recordHistory("fixture-dot-goal", undefined, "dotGoalWorkflow.json"); + + stubs.reset(); + // dummyTurnWorkflow. + handle = await env.testEnv.client.workflow.start("dummyTurnWorkflow", { + taskQueue: MAIN_TASK_QUEUE, workflowId: "fixture-dummy-turn", args: [nativeTurnInput("room-fixture-01", "dummy")] + }); + await handle.result(); + await recordHistory("fixture-dummy-turn", undefined, "dummyTurnWorkflow.json"); + + stubs.reset(); + // codexAppServerTurnWorkflow (room-agent turn). + handle = await env.testEnv.client.workflow.start("codexAppServerTurnWorkflow", { + taskQueue: MAIN_TASK_QUEUE, + workflowId: "fixture-codex-turn", + args: [roomAgentTurnInput("room-fixture-01", "mission-fixture-01", "fixture")] + }); + await handle.result(); + await recordHistory("fixture-codex-turn", undefined, "codexAppServerTurnWorkflow.json"); + + stubs.reset(); + // roomAgentSupervisorWorkflow (current patched branch), closed by + // continueAsNew after the 50th processed mission. + const supRoom = "room-fixture-sup"; + const supItem = supervisorQueueItem(supRoom, "mission-fixture-02", "sup"); + handle = await env.testEnv.client.workflow.start("roomAgentSupervisorWorkflow", { + taskQueue: MAIN_TASK_QUEUE, + workflowId: "fixture-supervisor", + args: [supervisorInput(supRoom, { pending: [supItem], processedCount: 49 })] + }); + const supervisorRunId = (await handle.describe()).runId; + await stubs.waitForCall("finishRoomAgentSupervisorTurn"); + await waitForRunIdChange(handle, supervisorRunId, 30000); + await recordHistory("fixture-supervisor", supervisorRunId, "roomAgentSupervisorWorkflow.json"); + await handle.cancel().catch(() => undefined); + await handle.result().catch(() => undefined); + + stubs.reset(); + // roomAgentSupervisorWorkflow pre-patch branch: one mission completes, + // one pending mission is interrupted by a stop signal, then the run is + // cancelled while idle (marker-less history from production code). + const legacyRoom = "room-fixture-legacy"; + const legacyItem1 = supervisorQueueItem(legacyRoom, "mission-fixture-03", "legacy-a"); + const legacyItem2 = supervisorQueueItem(legacyRoom, "mission-fixture-04", "legacy-b"); + const gate = makeGate(); + stubs.override("markRoomAgentMissionStarted", async (input) => { + if (input.missionId === "mission-fixture-03") await gate.promise; + return null; + }); + handle = await env.testEnv.client.workflow.start("roomAgentSupervisorWorkflow", { + taskQueue: PRE_PATCH_TASK_QUEUE, + workflowId: "fixture-supervisor-pre-patch", + args: [supervisorInput(legacyRoom, { pending: [legacyItem1, legacyItem2] })] + }); + await stubs.waitForCall("markRoomAgentMissionStarted"); + await handle.signal(ROOM_AGENT_STOP_SIGNAL, { missionId: "mission-fixture-04", reason: "Synthetic pre-patch stop." }); + gate.open(); + await stubs.waitForCall("markRoomAgentMissionFinished", 2); + await handle.cancel(); + await handle.result().catch(() => undefined); + await recordHistory("fixture-supervisor-pre-patch", undefined, "roomAgentSupervisorWorkflow.pre-patch.json"); + + stubs.reset(); + // roomAgentActionWorkflow failure path. + stubs.override("runRoomAgentBackgroundAction", () => { + throw new Error("synthetic background action failure"); + }); + handle = await env.testEnv.client.workflow.start("roomAgentActionWorkflow", { + taskQueue: MAIN_TASK_QUEUE, + workflowId: "fixture-room-action", + args: [{ + actionId: "act-fixture-01", + bridge: roomActionBridgeRequest("room-fixture-01", "mission-fixture-05"), + traceId: "trace:fixture-action" + }] + }); + await handle.result().catch(() => undefined); + await recordHistory("fixture-room-action", undefined, "roomAgentActionWorkflow.json"); + + stubs.reset(); + // memoryMaintenanceWorkflow with its consolidation child. + handle = await env.testEnv.client.workflow.start("memoryMaintenanceWorkflow", { + taskQueue: MEMORY_MAINTENANCE_TASK_QUEUE, + workflowId: "fixture-memory-maintenance", + args: [{ scheduledAt: "2026-01-01T00:00:00.000Z", traceId: "trace:fixture-memory" }] + }); + await handle.result(); + await recordHistory("fixture-memory-maintenance", undefined, "memoryMaintenanceWorkflow.json"); + + stubs.reset(); + // memoryConsolidationWorkflow. + handle = await env.testEnv.client.workflow.start("memoryConsolidationWorkflow", { + taskQueue: MEMORY_MAINTENANCE_TASK_QUEUE, + workflowId: "fixture-memory-consolidation", + args: [{ runId: "run-trace:fixture-memory", traceId: "trace:fixture-memory" }] + }); + await handle.result(); + await recordHistory("fixture-memory-consolidation", undefined, "memoryConsolidationWorkflow.json"); + + stubs.reset(); + // memoryMutationWorkflow. + handle = await env.testEnv.client.workflow.start("memoryMutationWorkflow", { + taskQueue: MEMORY_MUTATION_TASK_QUEUE, + workflowId: "fixture-memory-mutation", + args: [{ changeSetId: "changeset-fixture-01", traceId: "trace:fixture-memory" }] + }); + await handle.result(); + await recordHistory("fixture-memory-mutation", undefined, "memoryMutationWorkflow.json"); + + const expected = [ + "codexAppServerTurnWorkflow.json", + "dotGoalWorkflow.json", + "dummyTurnWorkflow.json", + "memoryConsolidationWorkflow.json", + "memoryMaintenanceWorkflow.json", + "memoryMutationWorkflow.json", + "roomAgentActionWorkflow.json", + "roomAgentSupervisorWorkflow.json", + "roomAgentSupervisorWorkflow.pre-patch.json" + ]; + const { readdir, stat } = await import("node:fs/promises"); + const files = (await readdir(FIXTURES_DIR)).filter((file) => file.endsWith(".json")).sort(); + expect(files).toEqual(expected); + for (const file of files) { + const info = await stat(resolve(FIXTURES_DIR, file)); + console.log("recorded fixture", file, info.size, "bytes"); + } + }, 300000); +}); diff --git a/apps/worker/tests/temporal-sandbox/replay-gate.test.ts b/apps/worker/tests/temporal-sandbox/replay-gate.test.ts new file mode 100644 index 000000000..1c5f76ea9 --- /dev/null +++ b/apps/worker/tests/temporal-sandbox/replay-gate.test.ts @@ -0,0 +1,98 @@ +/** + * Replay determinism gate (issue #60): every recorded workflow history must + * replay against the current workflow code via Worker.runReplayHistories, + * and a deliberately replay-incompatible change must fail with + * DeterminismViolationError naming the workflow and history file. + */ +import { readdir, readFile } from "node:fs/promises"; +import { resolve } from "node:path"; +import { fileURLToPath } from "node:url"; +import { describe, expect, it } from "vitest"; +import { DeterminismViolationError } from "@temporalio/workflow"; +import { Worker } from "@temporalio/worker"; +import * as proto from "@temporalio/proto"; +import { allWorkflowsPath, buildWorkflowBundle } from "./sandbox.js"; + +const __dirname = fileURLToPath(new URL(".", import.meta.url)); +const FIXTURES_DIR = resolve(__dirname, "../fixtures/workflow-histories"); + +const EXPECTED_FIXTURES = [ + "codexAppServerTurnWorkflow.json", + "dotGoalWorkflow.json", + "dummyTurnWorkflow.json", + "memoryConsolidationWorkflow.json", + "memoryMaintenanceWorkflow.json", + "memoryMutationWorkflow.json", + "roomAgentActionWorkflow.json", + "roomAgentSupervisorWorkflow.json", + "roomAgentSupervisorWorkflow.pre-patch.json" +]; + +/** + * Loads the recorded fixtures. The histories are stored as the JSON form + * emitted by protobufjs (enum names, int64s as strings), so they are decoded + * back into History messages with History.fromObject() here. That converts + * enum names to their numeric proto values, which lets + * Worker.validateHistory hand the history straight to the replay worker + * without any JSON-conversion layer. + */ +async function readHistories() { + const files = (await readdir(FIXTURES_DIR)).filter((file) => file.endsWith(".json")).sort(); + const histories = []; + for (const file of files) { + const recorded = JSON.parse(await readFile(resolve(FIXTURES_DIR, file), "utf8")); + const history = proto.temporal.api.history.v1.History.fromObject(recorded.history); + histories.push({ workflowId: recorded.workflowId, history, fileName: file }); + } + return { files, histories }; +} + +describe("replay determinism gate", () => { + it("has one recorded history per workflow plus the pre-patch supervisor", async () => { + const { files } = await readHistories(); + expect(files).toEqual(EXPECTED_FIXTURES); + }, 30000); + + it("replays every recorded history against the real workflow code", async () => { + const { histories } = await readHistories(); + expect(histories.length).toBeGreaterThanOrEqual(9); + const bundle = await buildWorkflowBundle(allWorkflowsPath()); + + const results = []; + for await (const result of Worker.runReplayHistories({ workflowBundle: { code: bundle.code } }, histories)) { + results.push(result); + if (result.error) { + const match = histories.find((item) => item.workflowId === result.workflowId); + console.error("replay failed for", result.workflowId, match?.fileName, result.error); + } + } + + expect(results).toHaveLength(histories.length); + const failures = results.filter((result) => result.error); + expect(failures).toEqual([]); + }, 120000); + + it("rejects a replay-incompatible workflow change with DeterminismViolationError", async () => { + const { histories } = await readHistories(); + const dotGoalHistory = histories.find((item) => item.workflowId === "fixture-dot-goal"); + expect(dotGoalHistory).toBeDefined(); + // tests/support/determinism-violation-workflow.ts: dotGoalWorkflow with + // one extra timer at the start. + const bundle = await buildWorkflowBundle( + resolve(__dirname, "../support/determinism-violation-workflow.ts") + ); + + const results = []; + for await (const result of Worker.runReplayHistories( + { workflowBundle: { code: bundle.code } }, + [dotGoalHistory!] + )) { + results.push(result); + } + + expect(results).toHaveLength(1); + expect(results[0]!.error).toBeInstanceOf(DeterminismViolationError); + const message = String(results[0]!.error); + expect(message).toMatch(/dotGoalWorkflow|fixture-dot-goal|DeterminismViolation/i); + }, 60000); +}); diff --git a/apps/worker/tests/temporal-sandbox/room-action.test.ts b/apps/worker/tests/temporal-sandbox/room-action.test.ts new file mode 100644 index 000000000..e71ab273e --- /dev/null +++ b/apps/worker/tests/temporal-sandbox/room-action.test.ts @@ -0,0 +1,79 @@ +/** + * roomAgentActionWorkflow on the real time-skipping test server + * (issue #60), including the room-background-action-failure-v1 failure + * path. + */ +import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { ActivityStubs, blockUntilCancelled, buildActivities } from "./activity-stubs.js"; +import { roomActionBridgeRequest } from "./stubs.js"; +import { createSandboxEnv, MAIN_TASK_QUEUE, type SandboxEnv } from "./sandbox.js"; + +const stubs = new ActivityStubs(); +let env: SandboxEnv; + +beforeAll(async () => { + env = await createSandboxEnv(); + await env.startWorker({ taskQueue: MAIN_TASK_QUEUE, activities: buildActivities(stubs) }); +}, 120000); + +beforeEach(() => stubs.reset()); + +afterAll(async () => { + await env.teardown(); +}, 60000); + +function startAction(workflowId: string, roomId: string, missionId: string, actionId?: string) { + return env.testEnv.client.workflow.start("roomAgentActionWorkflow", { + taskQueue: MAIN_TASK_QUEUE, + workflowId, + args: [{ actionId, bridge: roomActionBridgeRequest(roomId, missionId), traceId: "trace:" + workflowId }] + }); +} + +describe("roomAgentActionWorkflow", () => { + it("returns the background action result on success", async () => { + const handle = await startAction("room-action-test-01", "room-action-01", "mission-0100", "act-0001"); + + const result = await handle.result(); + expect(result).toMatchObject({ status: "EXECUTED" }); + expect(stubs.callsOf("runRoomAgentBackgroundAction")[0]).toMatchObject({ + actionId: "act-0001", + bridge: { roomId: "room-action-01", missionId: "mission-0100" } + }); + expect(stubs.count("failRoomAgentBackgroundAction")).toBe(0); + }, 60000); + + it("marks the background action failed and rethrows after exhausting activity retries", async () => { + stubs.override("runRoomAgentBackgroundAction", () => { + throw new Error("synthetic background action failure"); + }); + + const handle = await startAction("room-action-test-02", "room-action-01", "mission-0101", "act-0002"); + + await expect(handle.result()).rejects.toThrow(); + expect(stubs.count("runRoomAgentBackgroundAction")).toBe(5); + expect(stubs.callsOf("failRoomAgentBackgroundAction")[0]).toMatchObject({ + roomId: "room-action-01", + missionId: "mission-0101", + actionId: "act-0002", + cancelled: false + }); + }, 60000); + + it("reports a cancelled background action when the workflow is cancelled", async () => { + stubs.override("runRoomAgentBackgroundAction", () => blockUntilCancelled()); + + const handle = await startAction("room-action-test-03", "room-action-01", "mission-0102", "act-0003"); + await stubs.waitForCall("runRoomAgentBackgroundAction"); + await handle.cancel(); + + await stubs.waitForCall("failRoomAgentBackgroundAction"); + expect(stubs.callsOf("failRoomAgentBackgroundAction")[0]).toMatchObject({ + roomId: "room-action-01", + missionId: "mission-0102", + actionId: "act-0003", + cancelled: true + }); + await expect(handle.result()).rejects.toThrow(); + }, 60000); +}); diff --git a/apps/worker/tests/temporal-sandbox/room-supervisor-carry-bound.test.ts b/apps/worker/tests/temporal-sandbox/room-supervisor-carry-bound.test.ts new file mode 100644 index 000000000..730eba7fc --- /dev/null +++ b/apps/worker/tests/temporal-sandbox/room-supervisor-carry-bound.test.ts @@ -0,0 +1,85 @@ +/** + * Carry-bound continueAsNew coverage in an isolated time-skipping environment + * so the large pending drain cannot degrade later sleep()-based tests. + */ +import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { ROOM_AGENT_ENQUEUE_SIGNAL } from "@space/contracts"; +import { ActivityStubs, buildActivities, makeGate } from "./activity-stubs.js"; +import { supervisorInput, supervisorQueueItem } from "./stubs.js"; +import { + createSandboxEnv, + MAIN_TASK_QUEUE, + waitForRunIdChange, + type SandboxEnv +} from "./sandbox.js"; + +const stubs = new ActivityStubs(); +let env: SandboxEnv; +const openHandles: Array<{ cancel(): Promise; result(): Promise }> = []; + +beforeAll(async () => { + env = await createSandboxEnv(); + await env.startWorker({ taskQueue: MAIN_TASK_QUEUE, activities: buildActivities(stubs) }); +}, 120000); + +beforeEach(() => stubs.reset()); + +afterEach(async () => { + while (openHandles.length) { + const handle = openHandles.pop()!; + await handle.cancel().catch(() => undefined); + await Promise.race([ + handle.result().catch(() => undefined), + new Promise((resolve) => setTimeout(resolve, 3000)) + ]); + } +}, 30000); + +afterAll(async () => { + await env.teardown(); +}, 60000); + +async function startSupervisor(workflowId: string, input: unknown) { + const handle = await env.testEnv.client.workflow.start("roomAgentSupervisorWorkflow", { + taskQueue: MAIN_TASK_QUEUE, + workflowId, + args: [input] + }); + openHandles.push(handle); + return handle; +} + +describe("roomAgentSupervisorWorkflow carry bounds", () => { + it("defers continueAsNew until the carried queue fits the next run's input bounds", async () => { + const roomId = "room-sup-bound"; + const item = (index: number) => + supervisorQueueItem(roomId, "mission-bound", `bound-${String(index).padStart(3, "0")}`); + const gate = makeGate(); + let gated = false; + stubs.override("checkRoomAgentSupervisorTurn", async () => { + if (!gated) { + gated = true; + await gate.promise; + } + return "SKIP"; + }); + + const handle = await startSupervisor( + "sup-test-bound", + supervisorInput(roomId, { pending: [item(0)], processedCount: 49 }) + ); + await stubs.waitForCall("checkRoomAgentSupervisorTurn"); + for (let index = 1; index <= 105; index += 1) { + await handle.signal(ROOM_AGENT_ENQUEUE_SIGNAL, item(index)); + } + const firstRunId = (await handle.describe()).runId; + gate.open(); + await waitForRunIdChange(handle, firstRunId, 120000); + // Initial item plus five extras drained so the carried pending list fits + // the schema max of 100 (105 flooded + 1 initial - 6 drained = 100). + expect(stubs.count("checkRoomAgentSupervisorTurn")).toBeGreaterThanOrEqual(6); + expect(stubs.count("discardRoomAgentSupervisorTurn")).toBeGreaterThanOrEqual(6); + + await handle.cancel(); + }, 180000); +}); diff --git a/apps/worker/tests/temporal-sandbox/room-supervisor-polls.test.ts b/apps/worker/tests/temporal-sandbox/room-supervisor-polls.test.ts new file mode 100644 index 000000000..0e9852f90 --- /dev/null +++ b/apps/worker/tests/temporal-sandbox/room-supervisor-polls.test.ts @@ -0,0 +1,97 @@ +/** + * 250-poll continueAsNew coverage in an isolated time-skipping environment. + * These tests need a clean test server: an orphaned pending activity from an + * earlier suite silently disables time skipping and turns sleep() into wall clock. + */ +import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { ActivityStubs, buildActivities } from "./activity-stubs.js"; +import { supervisorInput } from "./stubs.js"; +import { + createSandboxEnv, + MAIN_TASK_QUEUE, + PRE_PATCH_TASK_QUEUE, + ROOM_BACKGROUND_CONTINUATION_PATCH, + waitForRunIdChange, + type SandboxEnv +} from "./sandbox.js"; + +const stubs = new ActivityStubs(); +let env: SandboxEnv; +const openHandles: Array<{ cancel(): Promise; result(): Promise }> = []; + +beforeAll(async () => { + env = await createSandboxEnv(); + await env.startWorker({ taskQueue: MAIN_TASK_QUEUE, activities: buildActivities(stubs) }); + await env.startWorker({ + taskQueue: PRE_PATCH_TASK_QUEUE, + activities: buildActivities(stubs), + disablePatches: [ROOM_BACKGROUND_CONTINUATION_PATCH] + }); +}, 120000); + +beforeEach(() => stubs.reset()); + +afterEach(async () => { + while (openHandles.length) { + const handle = openHandles.pop()!; + await handle.cancel().catch(() => undefined); + await Promise.race([ + handle.result().catch(() => undefined), + new Promise((resolve) => setTimeout(resolve, 3000)) + ]); + } +}, 30000); + +afterAll(async () => { + await env.teardown(); +}, 60000); + +async function startSupervisor(workflowId: string, input: unknown, taskQueue: string) { + const handle = await env.testEnv.client.workflow.start("roomAgentSupervisorWorkflow", { + taskQueue, + workflowId, + args: [input] + }); + openHandles.push(handle); + return handle; +} + +describe("roomAgentSupervisorWorkflow 250-poll continueAsNew", () => { + it("continues as a new run after 250 monitoring polls on the current branch", async () => { + stubs.override("prepareRoomAgentBackgroundContinuation", () => ({ state: "WAITING" })); + + const handle = await startSupervisor( + "sup-test-07", + supervisorInput("room-sup-07", { monitoringMissionIds: ["mission-0009"] }), + MAIN_TASK_QUEUE + ); + const firstRunId = (await handle.describe()).runId; + + while (stubs.count("prepareRoomAgentBackgroundContinuation") < 250) { + await env.testEnv.sleep("10 seconds"); + } + await waitForRunIdChange(handle, firstRunId, 30000); + expect(stubs.count("prepareRoomAgentBackgroundContinuation")).toBeGreaterThanOrEqual(250); + + await handle.cancel(); + }, 120000); + + it("continues as a new run after 250 monitoring polls on the pre-patch branch", async () => { + stubs.override("settleRoomAgentBackgroundMission", () => false); + + const handle = await startSupervisor( + "sup-test-11-legacy-polls", + supervisorInput("room-sup-11", { monitoringMissionIds: ["mission-0015"] }), + PRE_PATCH_TASK_QUEUE + ); + const firstRunId = (await handle.describe()).runId; + + while (stubs.count("settleRoomAgentBackgroundMission") < 250) { + await env.testEnv.sleep("10 seconds"); + } + await waitForRunIdChange(handle, firstRunId, 30000); + expect(stubs.count("settleRoomAgentBackgroundMission")).toBeGreaterThanOrEqual(250); + + await handle.cancel(); + }, 120000); +}); diff --git a/apps/worker/tests/temporal-sandbox/room-supervisor.test.ts b/apps/worker/tests/temporal-sandbox/room-supervisor.test.ts new file mode 100644 index 000000000..0e526cfac --- /dev/null +++ b/apps/worker/tests/temporal-sandbox/room-supervisor.test.ts @@ -0,0 +1,352 @@ +/** + * roomAgentSupervisorWorkflow on the real time-skipping test server + * (issue #60): enqueue/stop signals, one-second polling loops, interrupting + * a running mission, continueAsNew with carried state, and both the current + * (patched) and the pre-patch branches. Also covers every behavioural + * assertion from the mocked room-supervisor-continuation.test.ts. + */ +import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { ROOM_AGENT_ENQUEUE_SIGNAL, ROOM_AGENT_STOP_SIGNAL } from "@space/contracts"; +import { ActivityStubs, blockUntilCancelled, buildActivities, makeGate } from "./activity-stubs.js"; +import { supervisorInput, supervisorQueueItem } from "./stubs.js"; +import { + createSandboxEnv, + MAIN_TASK_QUEUE, + PRE_PATCH_TASK_QUEUE, + ROOM_BACKGROUND_CONTINUATION_PATCH, + waitForRunIdChange, + type SandboxEnv +} from "./sandbox.js"; + +const stubs = new ActivityStubs(); +let env: SandboxEnv; +const openHandles: Array<{ cancel(): Promise; result(): Promise }> = []; + +beforeAll(async () => { + env = await createSandboxEnv(); + await env.startWorker({ taskQueue: MAIN_TASK_QUEUE, activities: buildActivities(stubs) }); + // Production workflowsPath with the continuation patch forced off so the + // live execution takes legacyRoomAgentSupervisorWorkflow without copying it. + await env.startWorker({ + taskQueue: PRE_PATCH_TASK_QUEUE, + activities: buildActivities(stubs), + disablePatches: [ROOM_BACKGROUND_CONTINUATION_PATCH] + }); +}, 120000); + +beforeEach(() => stubs.reset()); + +afterEach(async () => { + // Cancel any still-open supervisors so a later testEnv.sleep keeps skipping + // time (an orphaned pending activity silently falls back to wall clock). + while (openHandles.length) { + const handle = openHandles.pop()!; + await handle.cancel().catch(() => undefined); + await Promise.race([ + handle.result().catch(() => undefined), + new Promise((resolve) => setTimeout(resolve, 3000)) + ]); + } +}, 30000); + +afterAll(async () => { + await env.teardown(); +}, 60000); + +async function startSupervisor(workflowId: string, input: unknown, taskQueue = MAIN_TASK_QUEUE) { + const handle = await env.testEnv.client.workflow.start("roomAgentSupervisorWorkflow", { + taskQueue, + workflowId, + args: [input] + }); + openHandles.push(handle); + return handle; +} + +describe("roomAgentSupervisorWorkflow (current patched branch)", () => { + it("runs an enqueued mission end to end (allocation → turn → finish)", async () => { + const roomId = "room-sup-01"; + const item = supervisorQueueItem(roomId, "mission-0001", "a"); + + const handle = await startSupervisor("sup-test-01", supervisorInput(roomId)); + await handle.signal(ROOM_AGENT_ENQUEUE_SIGNAL, item); + + await stubs.waitForCall("finishRoomAgentSupervisorTurn"); + expect(stubs.count("checkRoomAgentSupervisorTurn")).toBe(1); + expect(stubs.callsOf("checkRoomAgentSupervisorTurn")[0]).toMatchObject({ missionId: "mission-0001" }); + expect(stubs.callsOf("finishRoomAgentSupervisorTurn")[0]).toMatchObject({ + item: { missionId: "mission-0001" }, + status: "COMPLETED" + }); + expect(stubs.count("discardRoomAgentSupervisorTurn")).toBe(0); + + await handle.cancel(); + }, 60000); + + it("moves a pending mission to the interrupted queue on a stop signal instead of running it", async () => { + const roomId = "room-sup-02"; + const item1 = supervisorQueueItem(roomId, "mission-0002", "b"); + const item2 = supervisorQueueItem(roomId, "mission-0003", "c"); + const gate = makeGate(); + stubs.override("checkRoomAgentSupervisorTurn", async (input) => { + if (input.missionId === "mission-0002") await gate.promise; + return "READY"; + }); + + const handle = await startSupervisor("sup-test-02", supervisorInput(roomId, { pending: [item1, item2] })); + await stubs.waitForCall("checkRoomAgentSupervisorTurn"); + await handle.signal(ROOM_AGENT_STOP_SIGNAL, { missionId: "mission-0003", reason: "Synthetic operator stop." }); + gate.open(); + + await stubs.waitForCall("finishRoomAgentSupervisorTurn", 2); + const finishCalls = stubs.callsOf("finishRoomAgentSupervisorTurn"); + expect(finishCalls[0]).toMatchObject({ item: { missionId: "mission-0002" }, status: "COMPLETED" }); + expect(finishCalls[1]).toMatchObject({ + item: { missionId: "mission-0003" }, + status: "INTERRUPTED", + statusReason: "Synthetic operator stop." + }); + expect(stubs.callsOf("discardRoomAgentSupervisorTurn")[0]).toMatchObject({ + item: { missionId: "mission-0003" }, + reason: "Synthetic operator stop." + }); + + await handle.cancel(); + }, 60000); + + it("interrupts a running mission on a stop signal and marks it INTERRUPTED", async () => { + const roomId = "room-sup-03"; + const item = supervisorQueueItem(roomId, "mission-0004", "d"); + // Heartbeat until cancelled so the stop signal settles the activity cleanly + // instead of leaving an orphaned pending record that disables time skipping. + stubs.override("runCodexAppServerTurn", () => blockUntilCancelled()); + + const handle = await startSupervisor("sup-test-03", supervisorInput(roomId, { pending: [item] })); + await stubs.waitForCall("runCodexAppServerTurn"); + await handle.signal(ROOM_AGENT_STOP_SIGNAL, { missionId: "mission-0004", reason: "Synthetic operator interruption." }); + + await stubs.waitForCall("discardRoomAgentSupervisorTurn"); + await stubs.waitForCall("finishRoomAgentSupervisorTurn"); + expect(stubs.callsOf("finishRoomAgentSupervisorTurn")[0]).toMatchObject({ + item: { missionId: "mission-0004" }, + status: "INTERRUPTED", + statusReason: "Synthetic operator interruption." + }); + expect(stubs.callsOf("discardRoomAgentSupervisorTurn")[0]).toMatchObject({ + item: { missionId: "mission-0004" }, + reason: "Synthetic operator interruption." + }); + + await handle.cancel(); + }, 60000); + + it("records a failed child turn as FAILED with its cause code, never as superseded", async () => { + const roomId = "room-sup-fail"; + const item = supervisorQueueItem(roomId, "mission-fail1", "fail"); + stubs.override("runCodexAppServerTurn", () => { + throw Object.assign(new Error("child failed"), { + name: "ApplicationFailure", + type: "CODEX_APP_SERVER_TURN_TIMEOUT" + }); + }); + + const handle = await startSupervisor("sup-test-fail", supervisorInput(roomId, { pending: [item] })); + await stubs.waitForCall("finishRoomAgentSupervisorTurn"); + expect(stubs.callsOf("finishRoomAgentSupervisorTurn")[0]).toMatchObject({ + item: { missionId: "mission-fail1" }, + status: "FAILED" + }); + expect(stubs.callsOf("discardRoomAgentSupervisorTurn")[0]).toMatchObject({ + item: { missionId: "mission-fail1" } + }); + await handle.cancel(); + }, 60000); + + it("re-queues a paused turn and re-checks it after the one-second wait", async () => { + const roomId = "room-sup-04"; + const item = supervisorQueueItem(roomId, "mission-0005", "e"); + const states = ["PAUSED", "READY"]; + stubs.override("checkRoomAgentSupervisorTurn", (_input, index) => states[index]); + + const handle = await startSupervisor("sup-test-04", supervisorInput(roomId, { pending: [item] })); + + await stubs.waitForCall("checkRoomAgentSupervisorTurn", 2); + await stubs.waitForCall("finishRoomAgentSupervisorTurn"); + expect(stubs.callsOf("finishRoomAgentSupervisorTurn")[0]).toMatchObject({ status: "COMPLETED" }); + + await handle.cancel(); + }, 60000); + + it("prepares a monitored background continuation on the one-second poll loop and runs it", async () => { + const roomId = "room-sup-05"; + const continuation = supervisorQueueItem(roomId, "mission-0006", "f"); + continuation.turn.roomAgentContinuation = { sourceRunId: "agent_run:room-sup-05-src", generation: 1 }; + stubs.override("prepareRoomAgentBackgroundContinuation", () => ({ state: "READY", item: continuation })); + + const handle = await startSupervisor("sup-test-05", supervisorInput(roomId, { monitoringMissionIds: ["mission-0006"] })); + + await stubs.waitForCall("prepareRoomAgentBackgroundContinuation"); + await stubs.waitForCall("checkRoomAgentSupervisorTurn"); + expect(stubs.callsOf("checkRoomAgentSupervisorTurn")[0]).toMatchObject({ missionId: "mission-0006" }); + await stubs.waitForCall("finishRoomAgentSupervisorTurn"); + expect(stubs.callsOf("finishRoomAgentSupervisorTurn")[0]).toMatchObject({ + item: { missionId: "mission-0006" }, + status: "COMPLETED" + }); + + await handle.cancel(); + }, 60000); + + it("discards a prepared continuation when a stop arrives before the activity reply", async () => { + const roomId = "room-sup-race"; + const continuation = supervisorQueueItem(roomId, "mission-race1", "race"); + continuation.turn.roomAgentContinuation = { sourceRunId: "agent_run:room-sup-race-src", generation: 1 }; + const gate = makeGate(); + stubs.override("prepareRoomAgentBackgroundContinuation", async () => { + await gate.promise; + return { state: "READY", item: continuation }; + }); + + const handle = await startSupervisor( + "sup-test-race-stop", + supervisorInput(roomId, { monitoringMissionIds: ["mission-race1"] }) + ); + await stubs.waitForCall("prepareRoomAgentBackgroundContinuation"); + await handle.signal(ROOM_AGENT_STOP_SIGNAL, { missionId: "mission-race1", reason: "Stop during prepare." }); + gate.open(); + + await stubs.waitForCall("discardRoomAgentSupervisorTurn"); + expect(stubs.callsOf("discardRoomAgentSupervisorTurn")[0]).toMatchObject({ + item: { missionId: "mission-race1" }, + reason: "Superseded while preparing the automatic continuation." + }); + expect(stubs.count("checkRoomAgentSupervisorTurn")).toBe(0); + + await handle.cancel(); + }, 60000); + + it("continues as a new run with carried state after 50 processed missions", async () => { + const roomId = "room-sup-06"; + const item1 = supervisorQueueItem(roomId, "mission-0007", "g"); + const item2 = supervisorQueueItem(roomId, "mission-0008", "h"); + + const handle = await startSupervisor("sup-test-06", supervisorInput(roomId, { pending: [item1], processedCount: 49 })); + // Capture the run id before the first turn: the continueAsNew after the + // 50th processed mission can happen before waitForCall resolves. + const firstRunId = (await handle.describe()).runId; + await stubs.waitForCall("finishRoomAgentSupervisorTurn"); + await waitForRunIdChange(handle, firstRunId, 30000); + + // The continued run keeps working: enqueue another mission and watch it run. + await handle.signal(ROOM_AGENT_ENQUEUE_SIGNAL, item2); + await stubs.waitForCall("checkRoomAgentSupervisorTurn", 2); + expect(stubs.callsOf("checkRoomAgentSupervisorTurn")[1]).toMatchObject({ missionId: "mission-0008" }); + + await handle.cancel(); + }, 90000); + + it("carries a prepared continuation through continueAsNew and starts it once", async () => { + const roomId = "room-sup-carry"; + const continuation = supervisorQueueItem(roomId, "mission-carry", "carry"); + continuation.turn.roomAgentContinuation = { sourceRunId: "agent_run:room-sup-carry-src", generation: 1 }; + stubs.override("prepareRoomAgentBackgroundContinuation", () => ({ state: "READY", item: continuation })); + + // Start already at the 50-processed threshold so the run continues as new + // after preparing the continuation, carrying it into the next run unstarted. + const handle = await startSupervisor( + "sup-test-carry", + supervisorInput(roomId, { monitoringMissionIds: ["mission-carry"], processedCount: 50 }) + ); + const firstRunId = (await handle.describe()).runId; + await stubs.waitForCall("prepareRoomAgentBackgroundContinuation"); + await waitForRunIdChange(handle, firstRunId, 30000); + // The continued run must start the carried item exactly once. + await stubs.waitForCall("checkRoomAgentSupervisorTurn"); + await stubs.waitForCall("finishRoomAgentSupervisorTurn"); + expect(stubs.count("checkRoomAgentSupervisorTurn")).toBe(1); + expect(stubs.count("finishRoomAgentSupervisorTurn")).toBe(1); + expect(stubs.count("prepareRoomAgentBackgroundContinuation")).toBe(1); + + await handle.cancel(); + }, 90000); + +}); + +describe("roomAgentSupervisorWorkflow (pre-patch branch)", () => { + it("finishes a completed mission and interrupts a stopped pending mission like a pre-patch execution", async () => { + const roomId = "room-sup-08"; + const item1 = supervisorQueueItem(roomId, "mission-0010", "i"); + const item2 = supervisorQueueItem(roomId, "mission-0011", "j"); + const gate = makeGate(); + stubs.override("markRoomAgentMissionStarted", async (input) => { + if (input.missionId === "mission-0010") await gate.promise; + return null; + }); + + const handle = await startSupervisor( + "sup-test-08-legacy", + supervisorInput(roomId, { pending: [item1, item2] }), + PRE_PATCH_TASK_QUEUE + ); + await stubs.waitForCall("markRoomAgentMissionStarted"); + await handle.signal(ROOM_AGENT_STOP_SIGNAL, { missionId: "mission-0011", reason: "Synthetic legacy stop." }); + gate.open(); + + await stubs.waitForCall("markRoomAgentMissionFinished", 2); + const finished = stubs.callsOf("markRoomAgentMissionFinished"); + expect(finished[0]).toMatchObject({ missionId: "mission-0010", status: "COMPLETED" }); + expect(finished[1]).toMatchObject({ + missionId: "mission-0011", + status: "INTERRUPTED", + statusReason: "Synthetic legacy stop." + }); + expect(stubs.count("discardRoomAgentSupervisorTurn")).toBe(0); + // Legacy path uses mark*/settle*, never the continuing-branch activities. + expect(stubs.count("checkRoomAgentSupervisorTurn")).toBe(0); + + await handle.cancel(); + }, 60000); + + it("polls settleRoomAgentBackgroundMission on the legacy one-second loop", async () => { + const roomId = "room-sup-09"; + stubs.override("settleRoomAgentBackgroundMission", () => true); + + const handle = await startSupervisor( + "sup-test-09-legacy-settle", + supervisorInput(roomId, { monitoringMissionIds: ["mission-0012"] }), + PRE_PATCH_TASK_QUEUE + ); + + await stubs.waitForCall("settleRoomAgentBackgroundMission"); + expect(stubs.callsOf("settleRoomAgentBackgroundMission")[0]).toMatchObject({ + roomId, + missionId: "mission-0012" + }); + expect(stubs.count("checkRoomAgentSupervisorTurn")).toBe(0); + expect(stubs.count("prepareRoomAgentBackgroundContinuation")).toBe(0); + + await handle.cancel(); + }, 60000); + + it("continues as a new run with carried state after 50 processed missions on the pre-patch branch", async () => { + const roomId = "room-sup-10"; + const item1 = supervisorQueueItem(roomId, "mission-0013", "k"); + const item2 = supervisorQueueItem(roomId, "mission-0014", "l"); + + const handle = await startSupervisor( + "sup-test-10-legacy-can", + supervisorInput(roomId, { pending: [item1], processedCount: 49 }), + PRE_PATCH_TASK_QUEUE + ); + const firstRunId = (await handle.describe()).runId; + await stubs.waitForCall("markRoomAgentMissionFinished"); + await waitForRunIdChange(handle, firstRunId, 30000); + + await handle.signal(ROOM_AGENT_ENQUEUE_SIGNAL, item2); + await stubs.waitForCall("markRoomAgentMissionStarted", 2); + expect(stubs.callsOf("markRoomAgentMissionStarted")[1]).toMatchObject({ missionId: "mission-0014" }); + + await handle.cancel(); + }, 90000); + +}); diff --git a/apps/worker/tests/temporal-sandbox/sandbox.ts b/apps/worker/tests/temporal-sandbox/sandbox.ts new file mode 100644 index 000000000..13c3b26fa --- /dev/null +++ b/apps/worker/tests/temporal-sandbox/sandbox.ts @@ -0,0 +1,153 @@ +/** + * Shared harness for the real Temporal time-skipping tests (issue #60). + * + * Every test runs the REAL workflow code inside the workflow sandbox via + * {@link TestWorkflowEnvironment.createTimeSkipping} with fully stubbed + * activities. No network, database, Docker or AI provider is required. + * + * Workers are created with `workflowsPath` pointing at the test-only + * all-workflows entry (which re-exports production sources). A + * `webpackConfigHook` resolves `@space/*` workspace packages to their + * TypeScript sources - the same aliases `vitest.config.ts` uses - so the + * tests exercise the TypeScript sources directly. + * + * Pre-patch supervisor coverage uses Temporal's `patchActivationCallback` + * so the production `roomAgentSupervisorWorkflow` takes the legacy branch + * without copying workflow code. + */ +import { resolve } from "node:path"; +import { fileURLToPath } from "node:url"; +import { TestWorkflowEnvironment, type TestWorkflowEnvironmentOptions } from "@temporalio/testing"; +import { Worker, type WorkerOptions } from "@temporalio/worker"; + +const __dirname = fileURLToPath(new URL(".", import.meta.url)); +const ROOT = resolve(__dirname, "../../../.."); +const PACKAGES = resolve(ROOT, "packages"); + +export const MAIN_TASK_QUEUE = "temporal-sandbox-tests"; +export const PRE_PATCH_TASK_QUEUE = "temporal-sandbox-tests-pre-patch"; +export const MEMORY_MAINTENANCE_TASK_QUEUE = "space-memory-maintenance"; +export const MEMORY_MUTATION_TASK_QUEUE = "space-memory-mutations"; + +/** Patch id that gates the continuing supervisor branch in production. */ +export const ROOM_BACKGROUND_CONTINUATION_PATCH = "room-background-continuation-v1"; + +/** Resolves `@space/*` workspace packages to their TypeScript sources. */ +const workflowBundleAliases: Record = { + "@space/contracts": resolve(PACKAGES, "contracts/src/index.ts"), + "@space/db": resolve(PACKAGES, "db/src/index.ts"), + "@space/runtime": resolve(PACKAGES, "runtime/src/index.ts"), + "@space/streaming": resolve(PACKAGES, "streaming/src/index.ts") +}; + +/** The default workflowsPath: every production workflow (all 8 from issue #60). */ +export function allWorkflowsPath(): string { + return resolve(__dirname, "../support/all-workflows.ts"); +} + +export interface StartWorkerOptions { + taskQueue: string; + activities: Record; + /** Force selected patches off so production code takes a pre-patch branch. */ + disablePatches?: readonly string[]; + workflowsPath?: string; +} + +export interface SandboxEnv { + testEnv: TestWorkflowEnvironment; + queues: Map; + startWorker(options: StartWorkerOptions): Promise; + teardown(): Promise; +} + +export async function createSandboxEnv( + options: TestWorkflowEnvironmentOptions = {} +): Promise { + const testEnv = await TestWorkflowEnvironment.createTimeSkipping(options); + const queues = new Map(); + const runs: Promise[] = []; + + const startWorker = async ({ taskQueue, activities, disablePatches, workflowsPath }: StartWorkerOptions) => { + const existing = queues.get(taskQueue); + if (existing) return existing; + const disabled = new Set(disablePatches ?? []); + const worker = await Worker.create({ + connection: testEnv.nativeConnection, + namespace: testEnv.namespace, + taskQueue, + workflowsPath: workflowsPath ?? allWorkflowsPath(), + activities: activities as WorkerOptions["activities"], + maxConcurrentWorkflowTaskExecutions: 10, + bundlerOptions: { + webpackConfigHook: (config) => { + config.resolve = config.resolve ?? {}; + config.resolve.alias = { + ...(config.resolve.alias as Record | undefined), + ...workflowBundleAliases + }; + return config; + } + }, + ...(disabled.size + ? { + // Temporal 1.24: PatchActivationCallback receives { workflowInfo, patchId }. + patchActivationCallback: ({ patchId }: { patchId: string }) => !disabled.has(patchId) + } + : {}) + } as WorkerOptions); + runs.push(worker.run()); + queues.set(taskQueue, worker); + return worker; + }; + + const teardown = async () => { + for (const worker of queues.values()) worker.shutdown(); + queues.clear(); + await Promise.allSettled(runs); + await testEnv.teardown(); + }; + + return { testEnv, queues, startWorker, teardown }; +} + +/** Bundles an entry for offline replay / negative determinism checks. */ +export async function buildWorkflowBundle(entry: string): Promise<{ code: string; entry: string }> { + const { bundleWorkflowCode } = await import("@temporalio/worker"); + const { code } = await bundleWorkflowCode({ + workflowsPath: entry, + webpackConfigHook: (config) => { + config.resolve = config.resolve ?? {}; + config.resolve.alias = { + ...(config.resolve.alias as Record | undefined), + ...workflowBundleAliases + }; + return config; + } + }); + return { code, entry }; +} + +/** Waits until predicate() is true, polling with a short interval. */ +export async function waitFor(predicate: () => boolean, timeoutMs = 15000, stepMs = 10): Promise { + const deadline = Date.now() + timeoutMs; + for (;;) { + if (predicate()) return; + if (Date.now() > deadline) throw new Error("Timed out waiting for test condition"); + await new Promise((resolve) => setTimeout(resolve, stepMs)); + } +} + +/** Waits until the workflow handle points at a new run (continueAsNew). */ +export async function waitForRunIdChange( + handle: { describe(): Promise<{ runId: string }> }, + previousRunId: string, + timeoutMs = 30000 +): Promise<{ runId: string }> { + const deadline = Date.now() + timeoutMs; + for (;;) { + const description = await handle.describe(); + if (description.runId !== previousRunId) return description; + if (Date.now() > deadline) throw new Error("Timed out waiting for continueAsNew"); + await new Promise((resolve) => setTimeout(resolve, 25)); + } +} diff --git a/apps/worker/tests/temporal-sandbox/stubs.ts b/apps/worker/tests/temporal-sandbox/stubs.ts new file mode 100644 index 000000000..bb0ad0766 --- /dev/null +++ b/apps/worker/tests/temporal-sandbox/stubs.ts @@ -0,0 +1,134 @@ +/** + * Synthetic activity results and workflow inputs for the time-skipping + * tests. Everything here is synthetic data only - no real rooms, missions + * or memory state. + */ +import type { MemoryConsolidationRun } from "@space/contracts"; + +export const HEX64 = "0123456789abcdef".repeat(4); +const NOW = "2026-01-01T00:00:00.000Z"; + +export function memoryMaintenanceResult(overrides: Record = {}) { + return { + status: "REFRESHED", + generatedAt: NOW, + sourceHash: HEX64, + previousSourceHash: null, + summary: { sourceCount: 1, recordCount: 2, nodeCount: 3, edgeCount: 4, issueCount: 0 }, + durationMs: 12, + ...overrides + }; +} + +export function consolidationRun(id: string, overrides: Partial = {}): MemoryConsolidationRun { + return { + id, + mode: "AUDIT", + triggerKind: "SCHEDULED", + status: "SUCCEEDED", + workflowId: "space-memory-consolidation:" + id, + dedupeKey: "scheduled:" + id, + sourceHash: null, + actorUserId: null, + progressCompleted: 1, + progressTotal: 1, + findingCount: 0, + appliedOperationCount: 0, + skippedOperationCount: 0, + failedOperationCount: 0, + metrics: {}, + modelId: null, + aiVerified: false, + aiEvidence: {}, + statusReason: null, + createdAt: NOW, + startedAt: NOW, + completedAt: NOW, + updatedAt: NOW, + ...overrides + } as MemoryConsolidationRun; +} + +export function turnWorkflowResult(input: { roomId: string; paneId: string; traceId: string }, overrides: Record = {}) { + return { + workflowId: "space-turn:" + input.traceId, + roomId: input.roomId, + paneId: input.paneId, + traceId: input.traceId, + status: "COMPLETED", + message: "Synthetic turn completed.", + ...overrides + }; +} + +export function dummyTurnResult(input: { roomId: string; paneId: string; traceId: string }) { + return { + workflowId: "workflow:dummy:" + input.roomId + ":" + input.paneId + ":" + input.traceId, + roomId: input.roomId, + paneId: input.paneId, + traceId: input.traceId, + status: "COMPLETED", + message: "Synthetic dummy turn completed." + }; +} + +/** A DummyTurnInput that routes to the native chat turn activity. */ +export function nativeTurnInput(roomId: string, suffix = "native") { + return { + roomId, + paneId: "pane:" + roomId + "-" + suffix, + prompt: "Synthetic turn prompt.", + artifactIds: [], + providerId: null, + modelId: "gpt-test", + reasoningEffort: null, + agentSessionId: "session:" + roomId + "-" + suffix, + agentRunId: "agent_run:" + roomId + "-" + suffix, + agentAssistantMessageId: "agent_msg:assistant-" + roomId + "-" + suffix, + collaborationMode: "default", + traceId: "trace:" + roomId + "-" + suffix + }; +} + +/** A DummyTurnInput that routes to the room-agent turn activity. */ +export function roomAgentTurnInput(roomId: string, missionId: string, suffix = "room-agent") { + return { + ...nativeTurnInput(roomId, suffix), + roomAgentMissionId: missionId, + roomAgentOperatorRequest: "Synthetic operator request.", + selectedToolIds: ["room:inspect"] + }; +} + +/** A valid supervisor queue item for roomId. */ +export function supervisorQueueItem(roomId: string, missionId: string, agentRunSuffix: string) { + const turn = roomAgentTurnInput(roomId, missionId, agentRunSuffix); + return { + missionId, + turn: { ...turn, agentUserMessageId: "agent_msg:user-" + roomId + "-" + agentRunSuffix } + }; +} + +/** A valid supervisor workflow input. */ +export function supervisorInput(roomId: string, overrides: Record = {}) { + return { + roomId, + pending: [], + monitoringMissionIds: [], + interrupted: [], + processedCount: 0, + ...overrides + }; +} + +/** A valid room action bridge request for the background action workflow. */ +export function roomActionBridgeRequest(roomId: string, missionId: string) { + return { + roomId, + missionId, + agentPaneId: "pane:" + roomId + "-action", + agentSessionId: "session:" + roomId + "-action", + selectedToolIds: ["room:inspect"], + actions: [{ toolId: "room:inspect", action: { type: "inspect", arguments: {} } }] + }; +} diff --git a/apps/worker/tests/temporal-sandbox/turn-workflows.test.ts b/apps/worker/tests/temporal-sandbox/turn-workflows.test.ts new file mode 100644 index 000000000..49de10de6 --- /dev/null +++ b/apps/worker/tests/temporal-sandbox/turn-workflows.test.ts @@ -0,0 +1,149 @@ +/** + * dummyTurnWorkflow and codexAppServerTurnWorkflow on the real + * time-skipping test server (issue #60), including activity retry + * behavior. + */ +import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { ActivityStubs, buildActivities } from "./activity-stubs.js"; +import { dummyTurnResult, nativeTurnInput, roomAgentTurnInput } from "./stubs.js"; +import { createSandboxEnv, MAIN_TASK_QUEUE, type SandboxEnv } from "./sandbox.js"; + +const stubs = new ActivityStubs(); +let env: SandboxEnv; + +beforeAll(async () => { + env = await createSandboxEnv(); + await env.startWorker({ taskQueue: MAIN_TASK_QUEUE, activities: buildActivities(stubs) }); +}, 120000); + +beforeEach(() => stubs.reset()); + +afterAll(async () => { + await env.teardown(); +}, 60000); + +function startWorkflow(name: string, workflowId: string, input: unknown) { + return env.testEnv.client.workflow.start(name, { + taskQueue: MAIN_TASK_QUEUE, + workflowId, + args: [input] + }); +} + +describe("dummyTurnWorkflow", () => { + it("records the start and the completion of a dummy turn", async () => { + const input = nativeTurnInput("room-turn-01", "dummy"); + + const handle = await startWorkflow("dummyTurnWorkflow", "dummy-turn-test-01", input); + const result = await handle.result(); + + expect(result).toEqual(dummyTurnResult(input)); + expect(stubs.callsOf("recordDummyTurnStarted")[0]).toMatchObject({ + roomId: input.roomId, + paneId: input.paneId, + traceId: input.traceId + }); + expect(stubs.count("recordDummyTurnCompleted")).toBe(1); + }, 60000); + + it("retries a failing completion activity and finishes on the third attempt", async () => { + const input = nativeTurnInput("room-turn-01", "retry"); + stubs.override("recordDummyTurnCompleted", (turnInput, index) => { + if (index < 2) throw new Error("synthetic completion failure"); + return dummyTurnResult(turnInput); + }); + + const handle = await startWorkflow("dummyTurnWorkflow", "dummy-turn-test-02", input); + const result = await handle.result(); + + expect(result.status).toBe("COMPLETED"); + expect(stubs.count("recordDummyTurnCompleted")).toBe(3); + }, 60000); + + it("fails the turn after exhausting the start-activity retries", async () => { + const input = nativeTurnInput("room-turn-01", "fail"); + stubs.override("recordDummyTurnStarted", () => { + throw new Error("synthetic start failure"); + }); + + const handle = await startWorkflow("dummyTurnWorkflow", "dummy-turn-test-03", input); + + await expect(handle.result()).rejects.toThrow(); + expect(stubs.count("recordDummyTurnStarted")).toBe(3); + expect(stubs.count("recordDummyTurnCompleted")).toBe(0); + }, 60000); +}); + +describe("codexAppServerTurnWorkflow", () => { + it("runs a native chat turn through the native activity group", async () => { + const input = nativeTurnInput("room-turn-02", "native"); + + const handle = await startWorkflow("codexAppServerTurnWorkflow", "turn-test-native-01", input); + const result = await handle.result(); + + expect(result.status).toBe("COMPLETED"); + expect(stubs.count("runCodexAppServerTurn")).toBe(1); + const turnCall = stubs.callsOf("runCodexAppServerTurn")[0]; + expect(turnCall).toMatchObject({ agentSessionId: input.agentSessionId }); + expect(turnCall).not.toHaveProperty("roomAgentMissionId"); + expect(stubs.count("runOpenCodeAgentTurn")).toBe(0); + expect(stubs.count("runCliAgentTurn")).toBe(0); + }, 60000); + + it("runs a room-agent mission turn through the room-agent activity group", async () => { + const input = roomAgentTurnInput("room-turn-02", "mission-0001", "agent"); + + const handle = await startWorkflow("codexAppServerTurnWorkflow", "turn-test-room-agent-01", input); + const result = await handle.result(); + + expect(result.status).toBe("COMPLETED"); + expect(result.roomAgentOutcome).toMatchObject({ status: "VERIFIED", executedActionCount: 1 }); + expect(stubs.callsOf("runCodexAppServerTurn")[0]).toMatchObject({ + roomAgentMissionId: "mission-0001" + }); + }, 60000); + + it("routes opencode provider turns to runOpenCodeAgentTurn", async () => { + const input = { ...nativeTurnInput("room-turn-02", "opencode"), providerId: "opencode" }; + + const handle = await startWorkflow("codexAppServerTurnWorkflow", "turn-test-opencode-01", input); + const result = await handle.result(); + + expect(result.status).toBe("COMPLETED"); + expect(stubs.count("runOpenCodeAgentTurn")).toBe(1); + expect(stubs.count("runCodexAppServerTurn")).toBe(0); + }, 60000); + + it("runs CLI provider turns without retries and fails the turn on the first error", async () => { + const input = { ...nativeTurnInput("room-turn-02", "cli"), providerId: "cli:claude" }; + stubs.override("runCliAgentTurn", () => { + throw new Error("synthetic CLI failure"); + }); + + const handle = await startWorkflow("codexAppServerTurnWorkflow", "turn-test-cli-01", input); + + await expect(handle.result()).rejects.toThrow(); + expect(stubs.count("runCliAgentTurn")).toBe(1); + }, 60000); + + it("retries a failing room-agent turn activity and completes once it recovers", async () => { + const input = roomAgentTurnInput("room-turn-02", "mission-0002", "retry"); + stubs.override("runCodexAppServerTurn", (turnInput, index) => { + if (index < 4) throw new Error("synthetic turn failure"); + return { + workflowId: "space-turn:" + turnInput.traceId, + roomId: turnInput.roomId, + paneId: turnInput.paneId, + traceId: turnInput.traceId, + status: "COMPLETED", + message: "Recovered on the fifth attempt." + }; + }); + + const handle = await startWorkflow("codexAppServerTurnWorkflow", "turn-test-retry-01", input); + const result = await handle.result(); + + expect(result.status).toBe("COMPLETED"); + expect(stubs.count("runCodexAppServerTurn")).toBe(5); + }, 60000); +}); diff --git a/package-lock.json b/package-lock.json index 96d7fd636..5b090c646 100644 --- a/package-lock.json +++ b/package-lock.json @@ -192,6 +192,8 @@ "undici": "8.12.3" }, "devDependencies": { + "@temporalio/proto": "1.24.0", + "@temporalio/testing": "1.24.0", "typescript": "^5.9.3", "vitest": "^4.1.12" } @@ -3674,6 +3676,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", @@ -11641,12 +11662,12 @@ } }, "packages/run-spaceapp": { - "version": "1.0.35", + "version": "1.1.9", "cpu": [ "x64", "arm64" ], - "license": "Apache-2.0", + "license": "MIT", "os": [ "darwin", "linux",