diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 078772388..c301ca5b3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -106,6 +106,37 @@ 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. +## Workflow sandbox and replay histories + +`apps/worker/tests/workflow-sandbox.test.ts` runs every workflow in `apps/worker/src` +inside a real `TestWorkflowEnvironment.createTimeSkipping()` worker with stubbed +activities: no database, no Docker, no provider. The mocked suites next to it keep +their unit-level coverage; the sandbox suite is what exercises real timers, signal +ordering and `continueAsNew`. + +`apps/worker/tests/workflow-replay.test.ts` replays the JSON histories in +`apps/worker/tests/fixtures/workflow-histories/` against the current workflow code, +so a change that is not replay-compatible fails in CI instead of in a user's +installation. `apps/worker/tests/workflow-replay-gate.test.ts` proves the gate itself +still fires: it replays one history against unchanged code and against a copy that +adds a timer, and requires the second to fail with a determinism violation. + +### Re-recording a history + +```sh +npm run temporal:record-histories -w @space/worker +``` + +The script runs one canonical execution per workflow in the time-skipping sandbox and +overwrites the fixtures. Treat the output as test data, not as a snapshot to refresh +when a test is red. + +**Re-record only together with a `patched()` change that keeps running executions +valid.** Never re-record to make a failing replay pass: a failing replay means a +deployed execution would break when the worker picks it up. If a workflow change is +intentionally replay-breaking, wrap it in `patched()`, add the fixture that covers the +pre-patch branch, and say so in the pull request. + ## License of contributions SpaceApp is licensed under the [MIT License](LICENSE). Contributions are diff --git a/apps/worker/package.json b/apps/worker/package.json index d7a484ac8..d1f7503e2 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": "npm run build -w @space/contracts && vitest run", + "temporal:record-histories": "node scripts/record-workflow-histories.mjs" }, "dependencies": { "@space/codex-app-server": "0.2.5", @@ -26,6 +27,7 @@ "undici": "8.10.2" }, "devDependencies": { + "@temporalio/testing": "1.24.0", "typescript": "^5.9.3", "vitest": "^4.1.12" }, diff --git a/apps/worker/scripts/record-workflow-histories.mjs b/apps/worker/scripts/record-workflow-histories.mjs new file mode 100644 index 000000000..7c191fcb3 --- /dev/null +++ b/apps/worker/scripts/record-workflow-histories.mjs @@ -0,0 +1,165 @@ +#!/usr/bin/env node +/** + * Records real Temporal histories for every workflow in apps/worker into + * tests/fixtures/workflow-histories/ so `workflow-replay.test.ts` can replay them + * against the current workflow code in CI. + * + * Run with `npm run temporal:record-histories -w @space/worker`. + * + * Re-record only together with a `patched()` change. Never re-record to make a + * failing replay pass: a failing replay means a running execution would break. + * + * The scenarios here are intentionally minimal — one canonical run per workflow, + * plus the supervisor's continue-as-new handover. They exist to produce a replayable + * history, not to assert behaviour; behaviour lives in workflow-sandbox.test.ts. + */ +import { mkdir, writeFile } from "node:fs/promises"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; +import { TestWorkflowEnvironment } from "@temporalio/testing"; +import { Worker } from "@temporalio/worker"; + +const here = dirname(fileURLToPath(import.meta.url)); +const fixtures = join(here, "..", "tests", "fixtures", "workflow-histories"); + +const MAIN = join(here, "..", "src", "workflows.ts"); +const MAINTENANCE = join(here, "..", "src", "memory-maintenance-workflows.ts"); +const MUTATION = join(here, "..", "src", "memory-mutation-workflows.ts"); + +const DEFAULT_TASK_QUEUE = "space-agent-turns"; +const MEMORY_MAINTENANCE_TASK_QUEUE = "space-memory-maintenance"; +const MEMORY_MUTATION_TASK_QUEUE = "space-memory-mutations"; + +const verifiedTurn = () => ({ + status: "COMPLETED", + message: "ok", + roomAgentOutcome: { status: "VERIFIED", executedActionCount: 0, statusReason: "Verified." } +}); + +const activities = { + recordDummyTurnStarted: async () => undefined, + recordDummyTurnCompleted: async () => ({ status: "COMPLETED", message: "ok" }), + runCodexAppServerTurn: async () => verifiedTurn(), + runOpenCodeAgentTurn: async () => verifiedTurn(), + runCliAgentTurn: async () => verifiedTurn(), + runRoomAgentBackgroundAction: async () => ({ status: "COMPLETED" }), + markRoomAgentMissionStarted: async () => undefined, + markRoomAgentMissionFinished: async () => undefined, + markRoomAgentMissionContinued: async () => undefined, + settleRoomAgentBackgroundMission: async () => true, + checkRoomAgentSupervisorTurn: async () => "PROCEED", + discardRoomAgentSupervisorTurn: async () => undefined, + finishRoomAgentSupervisorTurn: async () => undefined, + prepareRoomAgentBackgroundContinuation: async () => ({ state: "DONE" }), + failRoomAgentBackgroundAction: async () => undefined, + dotGoalStep: async () => ({ state: "DONE", runId: null, goalStatus: "COMPLETE" }), + dotGoalObserve: async () => ({ run: "SUCCEEDED", goalStatus: "COMPLETE" }), + refreshMemoryGraphSnapshot: async (input) => ({ + status: "REFRESHED", generatedAt: input.scheduledAt, sourceHash: "hash-1", + previousSourceHash: null, summary: {}, durationMs: 1 + }), + refreshAllMonthsMemoryGraphSnapshot: async () => undefined, + reconcileStaleSpaceAgentSessions: async () => undefined, + createScheduledMemoryAudit: async () => ({ id: "run-recorded", workflowId: "space-memory-maintenance:recorded" }), + prepareMemoryConsolidation: async () => ({ changeSetId: "cs-recorded", run: { id: "run-recorded", status: "PENDING" } }), + finalizeMemoryRepair: async (input) => ({ id: input.runId, status: "REPAIRED" }), + executePersistedMemoryChangeSet: async () => ({ status: "APPLIED" }) +}; + +const turn = { roomId: "room-recorder", paneId: "pane-recorder", prompt: "Record a history", artifactIds: [] }; + +const scenario = [ + { name: "main-dummy-turn", bundle: MAIN, queue: DEFAULT_TASK_QUEUE, type: "dummyTurnWorkflow", args: [turn] }, + { name: "main-codex-turn", bundle: MAIN, queue: DEFAULT_TASK_QUEUE, type: "codexAppServerTurnWorkflow", args: [turn] }, + { + name: "main-room-action", bundle: MAIN, queue: DEFAULT_TASK_QUEUE, type: "roomAgentActionWorkflow", + workflowId: "space-room-action:recorded", args: [{ bridge: { roomId: "room-recorder", missionId: "mission-recorder" } }] + }, + { name: "main-dot-goal", bundle: MAIN, queue: DEFAULT_TASK_QUEUE, type: "dotGoalWorkflow", args: [{ goalId: "goal-recorder" }] }, + { + name: "main-supervisor-first-run", bundle: MAIN, queue: DEFAULT_TASK_QUEUE, type: "roomAgentSupervisorWorkflow", + args: [{ + roomId: "room-recorder", monitoringMissionIds: [], interrupted: [], processedCount: 49, + pending: [{ missionId: "mission-recorder", turn: { ...turn, agentRunId: "agent-run-recorder", traceId: "trace-recorder-1" } }] + }], + recordFirstRun: true + }, + { + name: "maintenance-run", bundle: MAINTENANCE, queue: MEMORY_MAINTENANCE_TASK_QUEUE, type: "memoryMaintenanceWorkflow", + args: [{ scheduledAt: "2026-10-25T00:00:00.000Z", traceId: "trace-recorder" }] + }, + { + name: "maintenance-consolidation", bundle: MAINTENANCE, queue: MEMORY_MAINTENANCE_TASK_QUEUE, type: "memoryConsolidationWorkflow", + args: [{ runId: "run-recorder", traceId: "trace-recorder" }] + }, + { + name: "mutation-run", bundle: MUTATION, queue: MEMORY_MUTATION_TASK_QUEUE, type: "memoryMutationWorkflow", + args: [{ changeSetId: "cs-recorder", traceId: "trace-recorder" }] + } +]; + +const namespace = "default"; + +/** + * Waits until the workflow has handed over to a new run, then returns the id of the + * run that was superseded. Waiting on the run id instead of sleeping a fixed interval + * keeps the recorded history shape stable across machines. + */ +async function waitForContinuation(env, workflowId, firstRunId, name, timeoutMs = 120_000) { + const latest = env.client.workflow.getHandle(workflowId); + const deadline = Date.now() + timeoutMs; + for (;;) { + const description = await latest.describe(); + if (description.runId !== firstRunId) return; + if (Date.now() > deadline) throw new Error(`${name}: the run never continued as new`); + await new Promise((resolve) => setTimeout(resolve, 50)); + } +} + +async function main() { + const env = await TestWorkflowEnvironment.createTimeSkipping(); + const workers = await Promise.all( + [...new Set(scenario.map((entry) => `${entry.bundle}|${entry.queue}`))].map((key) => { + const [bundle, queue] = key.split("|"); + return Worker.create({ connection: env.nativeConnection, taskQueue: queue, workflowsPath: bundle, activities }); + }) + ); + const runs = workers.map((worker) => worker.run()); + + await mkdir(fixtures, { recursive: true }); + + try { + for (const entry of scenario) { + const workflowId = entry.workflowId ?? `${entry.name}-recorder`; + const handle = await env.client.workflow.start(entry.type, { + taskQueue: entry.queue, workflowId, args: entry.args + }); + + // A workflow that never returns is recorded from its first run: that is the run + // holding the activities, the child workflow and the handover, not the idle run + // that continueAsNew leaves behind. + const recordedRunId = handle.firstExecutionRunId; + if (entry.recordFirstRun) await waitForContinuation(env, workflowId, recordedRunId, entry.name); + else await handle.result(); + + const response = await env.client.workflowService.getWorkflowExecutionHistory({ + namespace, + execution: { workflowId, runId: recordedRunId } + }); + const events = response.history?.events?.length ?? 0; + await writeFile(join(fixtures, `${entry.name}.json`), `${JSON.stringify(response.history, null, 2)}\n`, "utf8"); + console.log(`recorded ${entry.name}.json (${events} events, run ${recordedRunId.slice(0, 8)})`); + + if (entry.recordFirstRun) await handle.terminate("recorded for the replay gate"); + } + } finally { + for (const worker of workers) worker.shutdown(); + await Promise.allSettled(runs); + await env.teardown(); + } +} + +main().catch((error) => { + console.error(error); + process.exitCode = 1; +}); diff --git a/apps/worker/tests/fixtures/replay/clean-workflows.ts b/apps/worker/tests/fixtures/replay/clean-workflows.ts new file mode 100644 index 000000000..0d098f243 --- /dev/null +++ b/apps/worker/tests/fixtures/replay/clean-workflows.ts @@ -0,0 +1,21 @@ +import { proxyActivities, sleep } from "@temporalio/workflow"; + +/** + * Reference workflow for the replay gate: a fixed number of activities separated by + * timers. Its recorded history must replay against this file unchanged. + * + * The sibling `drifted-workflows.ts` deliberately differs by one extra timer; the + * replay test asserts that the drift is caught as a determinism violation. + */ +const { recordReplayProbeStep } = proxyActivities<{ + recordReplayProbeStep(input: { step: number }): Promise; +}>({ startToCloseTimeout: "1 minute" }); + +export async function replayProbeWorkflow(): Promise { + await recordReplayProbeStep({ step: 1 }); + await sleep("1 minute"); + await recordReplayProbeStep({ step: 2 }); + await sleep("1 minute"); + await recordReplayProbeStep({ step: 3 }); + return "done"; +} diff --git a/apps/worker/tests/fixtures/replay/drifted-workflows.ts b/apps/worker/tests/fixtures/replay/drifted-workflows.ts new file mode 100644 index 000000000..3ef52334b --- /dev/null +++ b/apps/worker/tests/fixtures/replay/drifted-workflows.ts @@ -0,0 +1,22 @@ +import { proxyActivities, sleep } from "@temporalio/workflow"; + +/** + * The same workflow as `clean-workflows.ts` with one extra timer at the start. + * + * It exists only so the replay gate can prove it still catches drift: replaying a + * history recorded by the clean file against this file must fail with + * `DeterminismViolationError`. Nothing in production references this file. + */ +const { recordReplayProbeStep } = proxyActivities<{ + recordReplayProbeStep(input: { step: number }): Promise; +}>({ startToCloseTimeout: "1 minute" }); + +export async function replayProbeWorkflow(): Promise { + await sleep("1 minute"); // <- the drift: a timer the recorded history never had + await recordReplayProbeStep({ step: 1 }); + await sleep("1 minute"); + await recordReplayProbeStep({ step: 2 }); + await sleep("1 minute"); + await recordReplayProbeStep({ step: 3 }); + return "done"; +} diff --git a/apps/worker/tests/fixtures/workflow-histories/main-codex-turn.json b/apps/worker/tests/fixtures/workflow-histories/main-codex-turn.json new file mode 100644 index 000000000..12a078038 --- /dev/null +++ b/apps/worker/tests/fixtures/workflow-histories/main-codex-turn.json @@ -0,0 +1,255 @@ +{ + "events": [ + { + "eventId": "1", + "eventTime": { + "seconds": "2108277222", + "nanos": 132000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_EXECUTION_STARTED", + "workflowExecutionStartedEventAttributes": { + "workflowType": { + "name": "codexAppServerTurnWorkflow" + }, + "taskQueue": { + "name": "space-agent-turns", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJyb29tSWQiOiJyb29tLXJlY29yZGVyIiwicGFuZUlkIjoicGFuZS1yZWNvcmRlciIsInByb21wdCI6IlJlY29yZCBhIGhpc3RvcnkiLCJhcnRpZmFjdElkcyI6W119" + } + ] + }, + "workflowExecutionTimeout": { + "seconds": "315360000" + }, + "workflowRunTimeout": { + "seconds": "315360000" + }, + "workflowTaskTimeout": { + "seconds": "10" + }, + "originalExecutionRunId": "ac6aced3-d376-4fe6-b8b8-d2aef2b004d4", + "identity": "16412@DESKTOP-6JCB1MB", + "firstExecutionRunId": "ac6aced3-d376-4fe6-b8b8-d2aef2b004d4", + "attempt": 1, + "firstWorkflowTaskBackoff": {}, + "header": {} + } + }, + { + "eventId": "2", + "eventTime": { + "seconds": "2108277222", + "nanos": 132000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_SCHEDULED", + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "space-agent-turns", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "startToCloseTimeout": { + "seconds": "10" + }, + "attempt": 1 + } + }, + { + "eventId": "3", + "eventTime": { + "seconds": "2108277222", + "nanos": 132000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "2", + "identity": "16412@DESKTOP-6JCB1MB", + "historySizeBytes": "376" + } + }, + { + "eventId": "4", + "eventTime": { + "seconds": "2108277222", + "nanos": 510000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "2", + "identity": "16412@DESKTOP-6JCB1MB", + "binaryChecksum": "@temporalio/worker@1.24.0+fb587b11958185d14c39963c565443c18de6f11cf53c1bf08ae3a0d70f2405ae", + "sdkMetadata": { + "coreUsedFlags": [ + 1, + 2, + 3 + ], + "sdkName": "temporal-typescript", + "sdkVersion": "1.24.0" + }, + "meteringMetadata": {} + } + }, + { + "eventId": "5", + "eventTime": { + "seconds": "2108277222", + "nanos": 510000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_SCHEDULED", + "activityTaskScheduledEventAttributes": { + "activityId": "1", + "activityType": { + "name": "runCodexAppServerTurn" + }, + "taskQueue": { + "name": "space-agent-turns", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "header": {}, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJyb29tSWQiOiJyb29tLXJlY29yZGVyIiwicGFuZUlkIjoicGFuZS1yZWNvcmRlciIsInByb21wdCI6IlJlY29yZCBhIGhpc3RvcnkiLCJhcnRpZmFjdElkcyI6W119" + } + ] + }, + "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": "2108277222", + "nanos": 510000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_STARTED", + "activityTaskStartedEventAttributes": { + "scheduledEventId": "5", + "identity": "16412@DESKTOP-6JCB1MB", + "attempt": 1 + } + }, + { + "eventId": "7", + "eventTime": { + "seconds": "2108277222", + "nanos": 511000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_COMPLETED", + "activityTaskCompletedEventAttributes": { + "result": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJzdGF0dXMiOiJDT01QTEVURUQiLCJtZXNzYWdlIjoib2siLCJyb29tQWdlbnRPdXRjb21lIjp7InN0YXR1cyI6IlZFUklGSUVEIiwiZXhlY3V0ZWRBY3Rpb25Db3VudCI6MCwic3RhdHVzUmVhc29uIjoiVmVyaWZpZWQuIn19" + } + ] + }, + "scheduledEventId": "5", + "startedEventId": "6", + "identity": "16412@DESKTOP-6JCB1MB" + } + }, + { + "eventId": "8", + "eventTime": { + "seconds": "2108277222", + "nanos": 511000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_SCHEDULED", + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "space-agent-turns", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "startToCloseTimeout": { + "seconds": "10" + }, + "attempt": 1 + } + }, + { + "eventId": "9", + "eventTime": { + "seconds": "2108277222", + "nanos": 512000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "8", + "identity": "16412@DESKTOP-6JCB1MB", + "historySizeBytes": "1156" + } + }, + { + "eventId": "10", + "eventTime": { + "seconds": "2108277222", + "nanos": 517000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "8", + "identity": "16412@DESKTOP-6JCB1MB", + "binaryChecksum": "@temporalio/worker@1.24.0+fb587b11958185d14c39963c565443c18de6f11cf53c1bf08ae3a0d70f2405ae", + "sdkMetadata": {}, + "meteringMetadata": {} + } + }, + { + "eventId": "11", + "eventTime": { + "seconds": "2108277222", + "nanos": 517000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_EXECUTION_COMPLETED", + "workflowExecutionCompletedEventAttributes": { + "result": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJzdGF0dXMiOiJDT01QTEVURUQiLCJtZXNzYWdlIjoib2siLCJyb29tQWdlbnRPdXRjb21lIjp7InN0YXR1cyI6IlZFUklGSUVEIiwiZXhlY3V0ZWRBY3Rpb25Db3VudCI6MCwic3RhdHVzUmVhc29uIjoiVmVyaWZpZWQuIn19" + } + ] + }, + "workflowTaskCompletedEventId": "9" + } + } + ] +} diff --git a/apps/worker/tests/fixtures/workflow-histories/main-dot-goal.json b/apps/worker/tests/fixtures/workflow-histories/main-dot-goal.json new file mode 100644 index 000000000..9079edf84 --- /dev/null +++ b/apps/worker/tests/fixtures/workflow-histories/main-dot-goal.json @@ -0,0 +1,253 @@ +{ + "events": [ + { + "eventId": "1", + "eventTime": { + "seconds": "2738997222", + "nanos": 861000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_EXECUTION_STARTED", + "workflowExecutionStartedEventAttributes": { + "workflowType": { + "name": "dotGoalWorkflow" + }, + "taskQueue": { + "name": "space-agent-turns", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJnb2FsSWQiOiJnb2FsLXJlY29yZGVyIn0=" + } + ] + }, + "workflowExecutionTimeout": { + "seconds": "315360000" + }, + "workflowRunTimeout": { + "seconds": "315360000" + }, + "workflowTaskTimeout": { + "seconds": "10" + }, + "originalExecutionRunId": "3c11a610-95fe-499f-bc9f-32b128de8517", + "identity": "16412@DESKTOP-6JCB1MB", + "firstExecutionRunId": "3c11a610-95fe-499f-bc9f-32b128de8517", + "attempt": 1, + "firstWorkflowTaskBackoff": {}, + "header": {} + } + }, + { + "eventId": "2", + "eventTime": { + "seconds": "2738997222", + "nanos": 861000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_SCHEDULED", + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "space-agent-turns", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "startToCloseTimeout": { + "seconds": "10" + }, + "attempt": 1 + } + }, + { + "eventId": "3", + "eventTime": { + "seconds": "2738997222", + "nanos": 861000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "2", + "identity": "16412@DESKTOP-6JCB1MB", + "historySizeBytes": "297" + } + }, + { + "eventId": "4", + "eventTime": { + "seconds": "2738997223", + "nanos": 221000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "2", + "identity": "16412@DESKTOP-6JCB1MB", + "binaryChecksum": "@temporalio/worker@1.24.0+fb587b11958185d14c39963c565443c18de6f11cf53c1bf08ae3a0d70f2405ae", + "sdkMetadata": { + "coreUsedFlags": [ + 1, + 2, + 3 + ], + "sdkName": "temporal-typescript", + "sdkVersion": "1.24.0" + }, + "meteringMetadata": {} + } + }, + { + "eventId": "5", + "eventTime": { + "seconds": "2738997223", + "nanos": 221000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_SCHEDULED", + "activityTaskScheduledEventAttributes": { + "activityId": "1", + "activityType": { + "name": "dotGoalStep" + }, + "taskQueue": { + "name": "space-agent-turns", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "header": {}, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "ImdvYWwtcmVjb3JkZXIi" + } + ] + }, + "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": "2738997223", + "nanos": 221000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_STARTED", + "activityTaskStartedEventAttributes": { + "scheduledEventId": "5", + "identity": "16412@DESKTOP-6JCB1MB", + "attempt": 1 + } + }, + { + "eventId": "7", + "eventTime": { + "seconds": "2738997223", + "nanos": 223000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_COMPLETED", + "activityTaskCompletedEventAttributes": { + "result": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJzdGF0ZSI6IkRPTkUiLCJydW5JZCI6bnVsbCwiZ29hbFN0YXR1cyI6IkNPTVBMRVRFIn0=" + } + ] + }, + "scheduledEventId": "5", + "startedEventId": "6", + "identity": "16412@DESKTOP-6JCB1MB" + } + }, + { + "eventId": "8", + "eventTime": { + "seconds": "2738997223", + "nanos": 223000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_SCHEDULED", + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "space-agent-turns", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "startToCloseTimeout": { + "seconds": "10" + }, + "attempt": 1 + } + }, + { + "eventId": "9", + "eventTime": { + "seconds": "2738997223", + "nanos": 223000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "8", + "identity": "16412@DESKTOP-6JCB1MB", + "historySizeBytes": "898" + } + }, + { + "eventId": "10", + "eventTime": { + "seconds": "2738997223", + "nanos": 227000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "8", + "identity": "16412@DESKTOP-6JCB1MB", + "binaryChecksum": "@temporalio/worker@1.24.0+fb587b11958185d14c39963c565443c18de6f11cf53c1bf08ae3a0d70f2405ae", + "sdkMetadata": {}, + "meteringMetadata": {} + } + }, + { + "eventId": "11", + "eventTime": { + "seconds": "2738997223", + "nanos": 227000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_EXECUTION_COMPLETED", + "workflowExecutionCompletedEventAttributes": { + "result": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJnb2FsU3RhdHVzIjoiQ09NUExFVEUifQ==" + } + ] + }, + "workflowTaskCompletedEventId": "9" + } + } + ] +} diff --git a/apps/worker/tests/fixtures/workflow-histories/main-dummy-turn.json b/apps/worker/tests/fixtures/workflow-histories/main-dummy-turn.json new file mode 100644 index 000000000..6a713a617 --- /dev/null +++ b/apps/worker/tests/fixtures/workflow-histories/main-dummy-turn.json @@ -0,0 +1,384 @@ +{ + "events": [ + { + "eventId": "1", + "eventTime": { + "seconds": "1792917221", + "nanos": 602000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_EXECUTION_STARTED", + "workflowExecutionStartedEventAttributes": { + "workflowType": { + "name": "dummyTurnWorkflow" + }, + "taskQueue": { + "name": "space-agent-turns", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJyb29tSWQiOiJyb29tLXJlY29yZGVyIiwicGFuZUlkIjoicGFuZS1yZWNvcmRlciIsInByb21wdCI6IlJlY29yZCBhIGhpc3RvcnkiLCJhcnRpZmFjdElkcyI6W119" + } + ] + }, + "workflowExecutionTimeout": { + "seconds": "315360000" + }, + "workflowRunTimeout": { + "seconds": "315360000" + }, + "workflowTaskTimeout": { + "seconds": "10" + }, + "originalExecutionRunId": "81c907f6-11cf-4f63-8c54-6a28cf20d7b1", + "identity": "16412@DESKTOP-6JCB1MB", + "firstExecutionRunId": "81c907f6-11cf-4f63-8c54-6a28cf20d7b1", + "attempt": 1, + "firstWorkflowTaskBackoff": {}, + "header": {} + } + }, + { + "eventId": "2", + "eventTime": { + "seconds": "1792917221", + "nanos": 602000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_SCHEDULED", + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "space-agent-turns", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "startToCloseTimeout": { + "seconds": "10" + }, + "attempt": 1 + } + }, + { + "eventId": "3", + "eventTime": { + "seconds": "1792917221", + "nanos": 603000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "2", + "identity": "16412@DESKTOP-6JCB1MB", + "historySizeBytes": "369" + } + }, + { + "eventId": "4", + "eventTime": { + "seconds": "1792917222", + "nanos": 93000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "2", + "identity": "16412@DESKTOP-6JCB1MB", + "binaryChecksum": "@temporalio/worker@1.24.0+fb587b11958185d14c39963c565443c18de6f11cf53c1bf08ae3a0d70f2405ae", + "sdkMetadata": { + "coreUsedFlags": [ + 3, + 1, + 2 + ], + "sdkName": "temporal-typescript", + "sdkVersion": "1.24.0" + }, + "meteringMetadata": {} + } + }, + { + "eventId": "5", + "eventTime": { + "seconds": "1792917222", + "nanos": 93000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_SCHEDULED", + "activityTaskScheduledEventAttributes": { + "activityId": "1", + "activityType": { + "name": "recordDummyTurnStarted" + }, + "taskQueue": { + "name": "space-agent-turns", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "header": {}, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJyb29tSWQiOiJyb29tLXJlY29yZGVyIiwicGFuZUlkIjoicGFuZS1yZWNvcmRlciIsInByb21wdCI6IlJlY29yZCBhIGhpc3RvcnkiLCJhcnRpZmFjdElkcyI6W119" + } + ] + }, + "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": "1792917222", + "nanos": 93000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_STARTED", + "activityTaskStartedEventAttributes": { + "scheduledEventId": "5", + "identity": "16412@DESKTOP-6JCB1MB", + "attempt": 1 + } + }, + { + "eventId": "7", + "eventTime": { + "seconds": "1792917222", + "nanos": 100000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_COMPLETED", + "activityTaskCompletedEventAttributes": { + "result": { + "payloads": [ + { + "metadata": { + "encoding": "YmluYXJ5L251bGw=" + } + } + ] + }, + "scheduledEventId": "5", + "startedEventId": "6", + "identity": "16412@DESKTOP-6JCB1MB" + } + }, + { + "eventId": "8", + "eventTime": { + "seconds": "1792917222", + "nanos": 100000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_SCHEDULED", + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "space-agent-turns", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "startToCloseTimeout": { + "seconds": "10" + }, + "attempt": 1 + } + }, + { + "eventId": "9", + "eventTime": { + "seconds": "1792917222", + "nanos": 100000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "8", + "identity": "16412@DESKTOP-6JCB1MB", + "historySizeBytes": "1008" + } + }, + { + "eventId": "10", + "eventTime": { + "seconds": "1792917222", + "nanos": 109000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "8", + "identity": "16412@DESKTOP-6JCB1MB", + "binaryChecksum": "@temporalio/worker@1.24.0+fb587b11958185d14c39963c565443c18de6f11cf53c1bf08ae3a0d70f2405ae", + "sdkMetadata": {}, + "meteringMetadata": {} + } + }, + { + "eventId": "11", + "eventTime": { + "seconds": "1792917222", + "nanos": 109000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_SCHEDULED", + "activityTaskScheduledEventAttributes": { + "activityId": "2", + "activityType": { + "name": "recordDummyTurnCompleted" + }, + "taskQueue": { + "name": "space-agent-turns", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "header": {}, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJyb29tSWQiOiJyb29tLXJlY29yZGVyIiwicGFuZUlkIjoicGFuZS1yZWNvcmRlciIsInByb21wdCI6IlJlY29yZCBhIGhpc3RvcnkiLCJhcnRpZmFjdElkcyI6W119" + } + ] + }, + "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": "1792917222", + "nanos": 109000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_STARTED", + "activityTaskStartedEventAttributes": { + "scheduledEventId": "11", + "identity": "16412@DESKTOP-6JCB1MB", + "attempt": 1 + } + }, + { + "eventId": "13", + "eventTime": { + "seconds": "1792917222", + "nanos": 111000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_COMPLETED", + "activityTaskCompletedEventAttributes": { + "result": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJzdGF0dXMiOiJDT01QTEVURUQiLCJtZXNzYWdlIjoib2sifQ==" + } + ] + }, + "scheduledEventId": "11", + "startedEventId": "12", + "identity": "16412@DESKTOP-6JCB1MB" + } + }, + { + "eventId": "14", + "eventTime": { + "seconds": "1792917222", + "nanos": 111000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_SCHEDULED", + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "space-agent-turns", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "startToCloseTimeout": { + "seconds": "10" + }, + "attempt": 1 + } + }, + { + "eventId": "15", + "eventTime": { + "seconds": "1792917222", + "nanos": 111000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "14", + "identity": "16412@DESKTOP-6JCB1MB", + "historySizeBytes": "1651" + } + }, + { + "eventId": "16", + "eventTime": { + "seconds": "1792917222", + "nanos": 115000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "14", + "identity": "16412@DESKTOP-6JCB1MB", + "binaryChecksum": "@temporalio/worker@1.24.0+fb587b11958185d14c39963c565443c18de6f11cf53c1bf08ae3a0d70f2405ae", + "sdkMetadata": {}, + "meteringMetadata": {} + } + }, + { + "eventId": "17", + "eventTime": { + "seconds": "1792917222", + "nanos": 115000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_EXECUTION_COMPLETED", + "workflowExecutionCompletedEventAttributes": { + "result": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJzdGF0dXMiOiJDT01QTEVURUQiLCJtZXNzYWdlIjoib2sifQ==" + } + ] + }, + "workflowTaskCompletedEventId": "15" + } + } + ] +} diff --git a/apps/worker/tests/fixtures/workflow-histories/main-room-action.json b/apps/worker/tests/fixtures/workflow-histories/main-room-action.json new file mode 100644 index 000000000..ded549fd3 --- /dev/null +++ b/apps/worker/tests/fixtures/workflow-histories/main-room-action.json @@ -0,0 +1,255 @@ +{ + "events": [ + { + "eventId": "1", + "eventTime": { + "seconds": "2423637222", + "nanos": 519000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_EXECUTION_STARTED", + "workflowExecutionStartedEventAttributes": { + "workflowType": { + "name": "roomAgentActionWorkflow" + }, + "taskQueue": { + "name": "space-agent-turns", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJicmlkZ2UiOnsicm9vbUlkIjoicm9vbS1yZWNvcmRlciIsIm1pc3Npb25JZCI6Im1pc3Npb24tcmVjb3JkZXIifX0=" + } + ] + }, + "workflowExecutionTimeout": { + "seconds": "315360000" + }, + "workflowRunTimeout": { + "seconds": "315360000" + }, + "workflowTaskTimeout": { + "seconds": "10" + }, + "originalExecutionRunId": "b653a842-73f5-4a0e-a94f-4d665d8a1958", + "identity": "16412@DESKTOP-6JCB1MB", + "firstExecutionRunId": "b653a842-73f5-4a0e-a94f-4d665d8a1958", + "attempt": 1, + "firstWorkflowTaskBackoff": {}, + "header": {} + } + }, + { + "eventId": "2", + "eventTime": { + "seconds": "2423637222", + "nanos": 519000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_SCHEDULED", + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "space-agent-turns", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "startToCloseTimeout": { + "seconds": "10" + }, + "attempt": 1 + } + }, + { + "eventId": "3", + "eventTime": { + "seconds": "2423637222", + "nanos": 519000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "2", + "identity": "16412@DESKTOP-6JCB1MB", + "historySizeBytes": "347" + } + }, + { + "eventId": "4", + "eventTime": { + "seconds": "2423637222", + "nanos": 851000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "2", + "identity": "16412@DESKTOP-6JCB1MB", + "binaryChecksum": "@temporalio/worker@1.24.0+fb587b11958185d14c39963c565443c18de6f11cf53c1bf08ae3a0d70f2405ae", + "sdkMetadata": { + "coreUsedFlags": [ + 3, + 2, + 1 + ], + "sdkName": "temporal-typescript", + "sdkVersion": "1.24.0" + }, + "meteringMetadata": {} + } + }, + { + "eventId": "5", + "eventTime": { + "seconds": "2423637222", + "nanos": 851000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_SCHEDULED", + "activityTaskScheduledEventAttributes": { + "activityId": "1", + "activityType": { + "name": "runRoomAgentBackgroundAction" + }, + "taskQueue": { + "name": "space-agent-turns", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "header": {}, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJicmlkZ2UiOnsicm9vbUlkIjoicm9vbS1yZWNvcmRlciIsIm1pc3Npb25JZCI6Im1pc3Npb24tcmVjb3JkZXIifX0=" + } + ] + }, + "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": "2423637222", + "nanos": 851000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_STARTED", + "activityTaskStartedEventAttributes": { + "scheduledEventId": "5", + "identity": "16412@DESKTOP-6JCB1MB", + "attempt": 1 + } + }, + { + "eventId": "7", + "eventTime": { + "seconds": "2423637222", + "nanos": 852000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_COMPLETED", + "activityTaskCompletedEventAttributes": { + "result": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJzdGF0dXMiOiJDT01QTEVURUQifQ==" + } + ] + }, + "scheduledEventId": "5", + "startedEventId": "6", + "identity": "16412@DESKTOP-6JCB1MB" + } + }, + { + "eventId": "8", + "eventTime": { + "seconds": "2423637222", + "nanos": 852000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_SCHEDULED", + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "space-agent-turns", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "startToCloseTimeout": { + "seconds": "10" + }, + "attempt": 1 + } + }, + { + "eventId": "9", + "eventTime": { + "seconds": "2423637222", + "nanos": 852000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "8", + "identity": "16412@DESKTOP-6JCB1MB", + "historySizeBytes": "996" + } + }, + { + "eventId": "10", + "eventTime": { + "seconds": "2423637222", + "nanos": 854000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "8", + "identity": "16412@DESKTOP-6JCB1MB", + "binaryChecksum": "@temporalio/worker@1.24.0+fb587b11958185d14c39963c565443c18de6f11cf53c1bf08ae3a0d70f2405ae", + "sdkMetadata": {}, + "meteringMetadata": {} + } + }, + { + "eventId": "11", + "eventTime": { + "seconds": "2423637222", + "nanos": 854000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_EXECUTION_COMPLETED", + "workflowExecutionCompletedEventAttributes": { + "result": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJzdGF0dXMiOiJDT01QTEVURUQifQ==" + } + ] + }, + "workflowTaskCompletedEventId": "9" + } + } + ] +} diff --git a/apps/worker/tests/fixtures/workflow-histories/main-supervisor-first-run.json b/apps/worker/tests/fixtures/workflow-histories/main-supervisor-first-run.json new file mode 100644 index 000000000..5321cc606 --- /dev/null +++ b/apps/worker/tests/fixtures/workflow-histories/main-supervisor-first-run.json @@ -0,0 +1,620 @@ +{ + "events": [ + { + "eventId": "1", + "eventTime": { + "seconds": "3054357223", + "nanos": 230000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_EXECUTION_STARTED", + "workflowExecutionStartedEventAttributes": { + "workflowType": { + "name": "roomAgentSupervisorWorkflow" + }, + "taskQueue": { + "name": "space-agent-turns", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJyb29tSWQiOiJyb29tLXJlY29yZGVyIiwibW9uaXRvcmluZ01pc3Npb25JZHMiOltdLCJpbnRlcnJ1cHRlZCI6W10sInByb2Nlc3NlZENvdW50Ijo0OSwicGVuZGluZyI6W3sibWlzc2lvbklkIjoibWlzc2lvbi1yZWNvcmRlciIsInR1cm4iOnsicm9vbUlkIjoicm9vbS1yZWNvcmRlciIsInBhbmVJZCI6InBhbmUtcmVjb3JkZXIiLCJwcm9tcHQiOiJSZWNvcmQgYSBoaXN0b3J5IiwiYXJ0aWZhY3RJZHMiOltdLCJhZ2VudFJ1bklkIjoiYWdlbnQtcnVuLXJlY29yZGVyIiwidHJhY2VJZCI6InRyYWNlLXJlY29yZGVyLTEifX1dfQ==" + } + ] + }, + "workflowExecutionTimeout": { + "seconds": "315360000" + }, + "workflowRunTimeout": { + "seconds": "315360000" + }, + "workflowTaskTimeout": { + "seconds": "10" + }, + "originalExecutionRunId": "dc181ba1-cfbe-449b-be29-2036fcd1a10e", + "identity": "16412@DESKTOP-6JCB1MB", + "firstExecutionRunId": "dc181ba1-cfbe-449b-be29-2036fcd1a10e", + "attempt": 1, + "firstWorkflowTaskBackoff": {}, + "header": {} + } + }, + { + "eventId": "2", + "eventTime": { + "seconds": "3054357223", + "nanos": 230000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_SCHEDULED", + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "space-agent-turns", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "startToCloseTimeout": { + "seconds": "10" + }, + "attempt": 1 + } + }, + { + "eventId": "3", + "eventTime": { + "seconds": "3054357223", + "nanos": 231000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "2", + "identity": "16412@DESKTOP-6JCB1MB", + "historySizeBytes": "585" + } + }, + { + "eventId": "4", + "eventTime": { + "seconds": "3054357223", + "nanos": 593000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "2", + "identity": "16412@DESKTOP-6JCB1MB", + "binaryChecksum": "@temporalio/worker@1.24.0+fb587b11958185d14c39963c565443c18de6f11cf53c1bf08ae3a0d70f2405ae", + "sdkMetadata": { + "coreUsedFlags": [ + 2, + 1, + 3 + ], + "sdkName": "temporal-typescript", + "sdkVersion": "1.24.0" + }, + "meteringMetadata": {} + } + }, + { + "eventId": "5", + "eventTime": { + "seconds": "3054357223", + "nanos": 593000000 + }, + "eventType": "EVENT_TYPE_MARKER_RECORDED", + "markerRecordedEventAttributes": { + "markerName": "core_patch", + "details": { + "patch-data": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJpZCI6InJvb20tYmFja2dyb3VuZC1jb250aW51YXRpb24tdjEiLCJkZXByZWNhdGVkIjpmYWxzZX0=" + } + ] + } + }, + "workflowTaskCompletedEventId": "3" + } + }, + { + "eventId": "6", + "eventTime": { + "seconds": "3054357223", + "nanos": 593000000 + }, + "eventType": "EVENT_TYPE_UPSERT_WORKFLOW_SEARCH_ATTRIBUTES", + "upsertWorkflowSearchAttributesEventAttributes": { + "workflowTaskCompletedEventId": "3", + "searchAttributes": { + "indexedFields": { + "TemporalChangeVersion": { + "metadata": { + "encoding": "anNvbi9wbGFpbg==", + "type": "S2V5d29yZExpc3Q=" + }, + "data": "WyJyb29tLWJhY2tncm91bmQtY29udGludWF0aW9uLXYxIl0=" + } + } + } + } + }, + { + "eventId": "7", + "eventTime": { + "seconds": "3054357223", + "nanos": 593000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_SCHEDULED", + "activityTaskScheduledEventAttributes": { + "activityId": "1", + "activityType": { + "name": "checkRoomAgentSupervisorTurn" + }, + "taskQueue": { + "name": "space-agent-turns", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "header": {}, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJtaXNzaW9uSWQiOiJtaXNzaW9uLXJlY29yZGVyIiwidHVybiI6eyJyb29tSWQiOiJyb29tLXJlY29yZGVyIiwicGFuZUlkIjoicGFuZS1yZWNvcmRlciIsInByb21wdCI6IlJlY29yZCBhIGhpc3RvcnkiLCJhcnRpZmFjdElkcyI6W10sImFnZW50UnVuSWQiOiJhZ2VudC1ydW4tcmVjb3JkZXIiLCJwZXJtaXNzaW9uTW9kZSI6bnVsbCwiY29sbGFib3JhdGlvbk1vZGUiOiJkZWZhdWx0IiwidHJhY2VJZCI6InRyYWNlLXJlY29yZGVyLTEifX0=" + } + ] + }, + "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": "3054357223", + "nanos": 593000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_STARTED", + "activityTaskStartedEventAttributes": { + "scheduledEventId": "7", + "identity": "16412@DESKTOP-6JCB1MB", + "attempt": 1 + } + }, + { + "eventId": "9", + "eventTime": { + "seconds": "3054357223", + "nanos": 595000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_COMPLETED", + "activityTaskCompletedEventAttributes": { + "result": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "IlBST0NFRUQi" + } + ] + }, + "scheduledEventId": "7", + "startedEventId": "8", + "identity": "16412@DESKTOP-6JCB1MB" + } + }, + { + "eventId": "10", + "eventTime": { + "seconds": "3054357223", + "nanos": 595000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_SCHEDULED", + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "space-agent-turns", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "startToCloseTimeout": { + "seconds": "10" + }, + "attempt": 1 + } + }, + { + "eventId": "11", + "eventTime": { + "seconds": "3054357223", + "nanos": 595000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "10", + "identity": "16412@DESKTOP-6JCB1MB", + "historySizeBytes": "1674" + } + }, + { + "eventId": "12", + "eventTime": { + "seconds": "3054357223", + "nanos": 603000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "10", + "identity": "16412@DESKTOP-6JCB1MB", + "binaryChecksum": "@temporalio/worker@1.24.0+fb587b11958185d14c39963c565443c18de6f11cf53c1bf08ae3a0d70f2405ae", + "sdkMetadata": {}, + "meteringMetadata": {} + } + }, + { + "eventId": "13", + "eventTime": { + "seconds": "3054357223", + "nanos": 603000000 + }, + "eventType": "EVENT_TYPE_START_CHILD_WORKFLOW_EXECUTION_INITIATED", + "startChildWorkflowExecutionInitiatedEventAttributes": { + "namespace": "default", + "workflowId": "workflow:codex-app-server:room-recorder:pane-recorder:trace-recorder-1", + "workflowType": { + "name": "codexAppServerTurnWorkflow" + }, + "taskQueue": { + "name": "space-agent-turns", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJyb29tSWQiOiJyb29tLXJlY29yZGVyIiwicGFuZUlkIjoicGFuZS1yZWNvcmRlciIsInByb21wdCI6IlJlY29yZCBhIGhpc3RvcnkiLCJhcnRpZmFjdElkcyI6W10sImFnZW50UnVuSWQiOiJhZ2VudC1ydW4tcmVjb3JkZXIiLCJwZXJtaXNzaW9uTW9kZSI6bnVsbCwiY29sbGFib3JhdGlvbk1vZGUiOiJkZWZhdWx0IiwidHJhY2VJZCI6InRyYWNlLXJlY29yZGVyLTEifQ==" + } + ] + }, + "workflowExecutionTimeout": {}, + "workflowRunTimeout": {}, + "workflowTaskTimeout": { + "seconds": "10" + }, + "workflowTaskCompletedEventId": "11", + "header": {}, + "memo": {} + } + }, + { + "eventId": "14", + "eventTime": { + "seconds": "3054357223", + "nanos": 603000000 + }, + "eventType": "EVENT_TYPE_CHILD_WORKFLOW_EXECUTION_STARTED", + "childWorkflowExecutionStartedEventAttributes": { + "namespace": "default", + "initiatedEventId": "13", + "workflowExecution": { + "workflowId": "workflow:codex-app-server:room-recorder:pane-recorder:trace-recorder-1", + "runId": "c9261062-4f7e-4c0e-9837-c0c05350cc33" + }, + "workflowType": { + "name": "codexAppServerTurnWorkflow" + } + } + }, + { + "eventId": "15", + "eventTime": { + "seconds": "3054357223", + "nanos": 603000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_SCHEDULED", + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "space-agent-turns", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "startToCloseTimeout": { + "seconds": "10" + }, + "attempt": 1 + } + }, + { + "eventId": "16", + "eventTime": { + "seconds": "3054357223", + "nanos": 603000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "15", + "identity": "16412@DESKTOP-6JCB1MB", + "historySizeBytes": "2502" + } + }, + { + "eventId": "17", + "eventTime": { + "seconds": "3054357223", + "nanos": 989000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "15", + "identity": "16412@DESKTOP-6JCB1MB", + "binaryChecksum": "@temporalio/worker@1.24.0+fb587b11958185d14c39963c565443c18de6f11cf53c1bf08ae3a0d70f2405ae", + "sdkMetadata": {}, + "meteringMetadata": {} + } + }, + { + "eventId": "18", + "eventTime": { + "seconds": "3054357223", + "nanos": 993000000 + }, + "eventType": "EVENT_TYPE_CHILD_WORKFLOW_EXECUTION_COMPLETED", + "childWorkflowExecutionCompletedEventAttributes": { + "result": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJzdGF0dXMiOiJDT01QTEVURUQiLCJtZXNzYWdlIjoib2siLCJyb29tQWdlbnRPdXRjb21lIjp7InN0YXR1cyI6IlZFUklGSUVEIiwiZXhlY3V0ZWRBY3Rpb25Db3VudCI6MCwic3RhdHVzUmVhc29uIjoiVmVyaWZpZWQuIn19" + } + ] + }, + "namespace": "default", + "workflowExecution": { + "workflowId": "workflow:codex-app-server:room-recorder:pane-recorder:trace-recorder-1", + "runId": "c9261062-4f7e-4c0e-9837-c0c05350cc33" + }, + "workflowType": { + "name": "codexAppServerTurnWorkflow" + }, + "initiatedEventId": "13", + "startedEventId": "14" + } + }, + { + "eventId": "19", + "eventTime": { + "seconds": "3054357223", + "nanos": 993000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_SCHEDULED", + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "space-agent-turns", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "startToCloseTimeout": { + "seconds": "10" + }, + "attempt": 1 + } + }, + { + "eventId": "20", + "eventTime": { + "seconds": "3054357223", + "nanos": 993000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "19", + "identity": "16412@DESKTOP-6JCB1MB", + "historySizeBytes": "3079" + } + }, + { + "eventId": "21", + "eventTime": { + "seconds": "3054357224" + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "19", + "identity": "16412@DESKTOP-6JCB1MB", + "binaryChecksum": "@temporalio/worker@1.24.0+fb587b11958185d14c39963c565443c18de6f11cf53c1bf08ae3a0d70f2405ae", + "sdkMetadata": {}, + "meteringMetadata": {} + } + }, + { + "eventId": "22", + "eventTime": { + "seconds": "3054357224" + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_SCHEDULED", + "activityTaskScheduledEventAttributes": { + "activityId": "2", + "activityType": { + "name": "finishRoomAgentSupervisorTurn" + }, + "taskQueue": { + "name": "space-agent-turns", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "header": {}, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJpdGVtIjp7Im1pc3Npb25JZCI6Im1pc3Npb24tcmVjb3JkZXIiLCJ0dXJuIjp7InJvb21JZCI6InJvb20tcmVjb3JkZXIiLCJwYW5lSWQiOiJwYW5lLXJlY29yZGVyIiwicHJvbXB0IjoiUmVjb3JkIGEgaGlzdG9yeSIsImFydGlmYWN0SWRzIjpbXSwiYWdlbnRSdW5JZCI6ImFnZW50LXJ1bi1yZWNvcmRlciIsInBlcm1pc3Npb25Nb2RlIjpudWxsLCJjb2xsYWJvcmF0aW9uTW9kZSI6ImRlZmF1bHQiLCJ0cmFjZUlkIjoidHJhY2UtcmVjb3JkZXItMSJ9fSwic3RhdHVzIjoiQ09NUExFVEVEIiwic3RhdHVzUmVhc29uIjoiVmVyaWZpZWQuIn0=" + } + ] + }, + "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": "3054357224" + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_STARTED", + "activityTaskStartedEventAttributes": { + "scheduledEventId": "22", + "identity": "16412@DESKTOP-6JCB1MB", + "attempt": 1 + } + }, + { + "eventId": "24", + "eventTime": { + "seconds": "3054357224", + "nanos": 1000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_COMPLETED", + "activityTaskCompletedEventAttributes": { + "result": { + "payloads": [ + { + "metadata": { + "encoding": "YmluYXJ5L251bGw=" + } + } + ] + }, + "scheduledEventId": "22", + "startedEventId": "23", + "identity": "16412@DESKTOP-6JCB1MB" + } + }, + { + "eventId": "25", + "eventTime": { + "seconds": "3054357224", + "nanos": 1000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_SCHEDULED", + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "space-agent-turns", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "startToCloseTimeout": { + "seconds": "10" + }, + "attempt": 1 + } + }, + { + "eventId": "26", + "eventTime": { + "seconds": "3054357224", + "nanos": 1000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "25", + "identity": "16412@DESKTOP-6JCB1MB", + "historySizeBytes": "3888" + } + }, + { + "eventId": "27", + "eventTime": { + "seconds": "3054357224", + "nanos": 6000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "25", + "identity": "16412@DESKTOP-6JCB1MB", + "binaryChecksum": "@temporalio/worker@1.24.0+fb587b11958185d14c39963c565443c18de6f11cf53c1bf08ae3a0d70f2405ae", + "sdkMetadata": {}, + "meteringMetadata": {} + } + }, + { + "eventId": "28", + "eventTime": { + "seconds": "3054357224", + "nanos": 6000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_EXECUTION_CONTINUED_AS_NEW", + "workflowExecutionContinuedAsNewEventAttributes": { + "newExecutionRunId": "e5e30cc2-1038-44e5-b711-07e8b4b9e429", + "workflowType": { + "name": "roomAgentSupervisorWorkflow" + }, + "taskQueue": { + "name": "space-agent-turns", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJyb29tSWQiOiJyb29tLXJlY29yZGVyIiwicGVuZGluZyI6W10sImludGVycnVwdGVkIjpbXSwibW9uaXRvcmluZ01pc3Npb25JZHMiOltdLCJwcm9jZXNzZWRDb3VudCI6MH0=" + } + ] + }, + "workflowRunTimeout": { + "seconds": "315360000" + }, + "workflowTaskTimeout": { + "seconds": "10" + }, + "workflowTaskCompletedEventId": "26", + "backoffStartInterval": {} + } + } + ] +} diff --git a/apps/worker/tests/fixtures/workflow-histories/maintenance-consolidation.json b/apps/worker/tests/fixtures/workflow-histories/maintenance-consolidation.json new file mode 100644 index 000000000..747eb9963 --- /dev/null +++ b/apps/worker/tests/fixtures/workflow-histories/maintenance-consolidation.json @@ -0,0 +1,517 @@ +{ + "events": [ + { + "eventId": "1", + "eventTime": { + "seconds": "3054357225", + "nanos": 5000000 + }, + "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": "eyJydW5JZCI6InJ1bi1yZWNvcmRlciIsInRyYWNlSWQiOiJ0cmFjZS1yZWNvcmRlciJ9" + } + ] + }, + "workflowExecutionTimeout": { + "seconds": "315360000" + }, + "workflowRunTimeout": { + "seconds": "315360000" + }, + "workflowTaskTimeout": { + "seconds": "10" + }, + "originalExecutionRunId": "d5a699d6-f15d-42d3-b894-36f76d73abd3", + "identity": "16412@DESKTOP-6JCB1MB", + "firstExecutionRunId": "d5a699d6-f15d-42d3-b894-36f76d73abd3", + "attempt": 1, + "firstWorkflowTaskBackoff": {}, + "header": {} + } + }, + { + "eventId": "2", + "eventTime": { + "seconds": "3054357225", + "nanos": 5000000 + }, + "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": "3054357225", + "nanos": 6000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "2", + "identity": "16412@DESKTOP-6JCB1MB", + "historySizeBytes": "346" + } + }, + { + "eventId": "4", + "eventTime": { + "seconds": "3054357225", + "nanos": 356000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "2", + "identity": "16412@DESKTOP-6JCB1MB", + "binaryChecksum": "@temporalio/worker@1.24.0+a38c63bdcd9c21b629389c984a449c37c0e971b2093a9f232d6f2e64448f4134", + "sdkMetadata": { + "coreUsedFlags": [ + 1, + 3, + 2 + ], + "sdkName": "temporal-typescript", + "sdkVersion": "1.24.0" + }, + "meteringMetadata": {} + } + }, + { + "eventId": "5", + "eventTime": { + "seconds": "3054357225", + "nanos": 356000000 + }, + "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": "eyJydW5JZCI6InJ1bi1yZWNvcmRlciIsInRyYWNlSWQiOiJ0cmFjZS1yZWNvcmRlciJ9" + } + ] + }, + "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": "3054357225", + "nanos": 356000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_STARTED", + "activityTaskStartedEventAttributes": { + "scheduledEventId": "5", + "identity": "16412@DESKTOP-6JCB1MB", + "attempt": 1 + } + }, + { + "eventId": "7", + "eventTime": { + "seconds": "3054357225", + "nanos": 357000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_COMPLETED", + "activityTaskCompletedEventAttributes": { + "result": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJjaGFuZ2VTZXRJZCI6ImNzLXJlY29yZGVkIiwicnVuIjp7ImlkIjoicnVuLXJlY29yZGVkIiwic3RhdHVzIjoiUEVORElORyJ9fQ==" + } + ] + }, + "scheduledEventId": "5", + "startedEventId": "6", + "identity": "16412@DESKTOP-6JCB1MB" + } + }, + { + "eventId": "8", + "eventTime": { + "seconds": "3054357225", + "nanos": 357000000 + }, + "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": "3054357225", + "nanos": 357000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "8", + "identity": "16412@DESKTOP-6JCB1MB", + "historySizeBytes": "1041" + } + }, + { + "eventId": "10", + "eventTime": { + "seconds": "3054357225", + "nanos": 360000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "8", + "identity": "16412@DESKTOP-6JCB1MB", + "binaryChecksum": "@temporalio/worker@1.24.0+a38c63bdcd9c21b629389c984a449c37c0e971b2093a9f232d6f2e64448f4134", + "sdkMetadata": {}, + "meteringMetadata": {} + } + }, + { + "eventId": "11", + "eventTime": { + "seconds": "3054357225", + "nanos": 360000000 + }, + "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": "eyJjaGFuZ2VTZXRJZCI6ImNzLXJlY29yZGVkIiwidHJhY2VJZCI6InRyYWNlLXJlY29yZGVyIn0=" + } + ] + }, + "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": "3054357225", + "nanos": 360000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_STARTED", + "activityTaskStartedEventAttributes": { + "scheduledEventId": "11", + "identity": "16412@DESKTOP-6JCB1MB", + "attempt": 1 + } + }, + { + "eventId": "13", + "eventTime": { + "seconds": "3054357225", + "nanos": 361000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_COMPLETED", + "activityTaskCompletedEventAttributes": { + "result": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJzdGF0dXMiOiJBUFBMSUVEIn0=" + } + ] + }, + "scheduledEventId": "11", + "startedEventId": "12", + "identity": "16412@DESKTOP-6JCB1MB" + } + }, + { + "eventId": "14", + "eventTime": { + "seconds": "3054357225", + "nanos": 361000000 + }, + "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": "3054357225", + "nanos": 361000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "14", + "identity": "16412@DESKTOP-6JCB1MB", + "historySizeBytes": "1653" + } + }, + { + "eventId": "16", + "eventTime": { + "seconds": "3054357225", + "nanos": 363000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "14", + "identity": "16412@DESKTOP-6JCB1MB", + "binaryChecksum": "@temporalio/worker@1.24.0+a38c63bdcd9c21b629389c984a449c37c0e971b2093a9f232d6f2e64448f4134", + "sdkMetadata": {}, + "meteringMetadata": {} + } + }, + { + "eventId": "17", + "eventTime": { + "seconds": "3054357225", + "nanos": 363000000 + }, + "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": "eyJydW5JZCI6InJ1bi1yZWNvcmRlciIsImNoYW5nZVNldElkIjoiY3MtcmVjb3JkZWQiLCJtdXRhdGlvblJlc3VsdCI6eyJzdGF0dXMiOiJBUFBMSUVEIn19" + } + ] + }, + "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": "3054357225", + "nanos": 363000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_STARTED", + "activityTaskStartedEventAttributes": { + "scheduledEventId": "17", + "identity": "16412@DESKTOP-6JCB1MB", + "attempt": 1 + } + }, + { + "eventId": "19", + "eventTime": { + "seconds": "3054357225", + "nanos": 365000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_COMPLETED", + "activityTaskCompletedEventAttributes": { + "result": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJpZCI6InJ1bi1yZWNvcmRlciIsInN0YXR1cyI6IlJFUEFJUkVEIn0=" + } + ] + }, + "scheduledEventId": "17", + "startedEventId": "18", + "identity": "16412@DESKTOP-6JCB1MB" + } + }, + { + "eventId": "20", + "eventTime": { + "seconds": "3054357225", + "nanos": 365000000 + }, + "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": "3054357225", + "nanos": 365000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "20", + "identity": "16412@DESKTOP-6JCB1MB", + "historySizeBytes": "2311" + } + }, + { + "eventId": "22", + "eventTime": { + "seconds": "3054357225", + "nanos": 368000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "20", + "identity": "16412@DESKTOP-6JCB1MB", + "binaryChecksum": "@temporalio/worker@1.24.0+a38c63bdcd9c21b629389c984a449c37c0e971b2093a9f232d6f2e64448f4134", + "sdkMetadata": {}, + "meteringMetadata": {} + } + }, + { + "eventId": "23", + "eventTime": { + "seconds": "3054357225", + "nanos": 368000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_EXECUTION_COMPLETED", + "workflowExecutionCompletedEventAttributes": { + "result": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJpZCI6InJ1bi1yZWNvcmRlciIsInN0YXR1cyI6IlJFUEFJUkVEIn0=" + } + ] + }, + "workflowTaskCompletedEventId": "21" + } + } + ] +} diff --git a/apps/worker/tests/fixtures/workflow-histories/maintenance-run.json b/apps/worker/tests/fixtures/workflow-histories/maintenance-run.json new file mode 100644 index 000000000..285e64843 --- /dev/null +++ b/apps/worker/tests/fixtures/workflow-histories/maintenance-run.json @@ -0,0 +1,816 @@ +{ + "events": [ + { + "eventId": "1", + "eventTime": { + "seconds": "3054357224", + "nanos": 48000000 + }, + "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": "eyJzY2hlZHVsZWRBdCI6IjIwMjYtMTAtMjVUMDA6MDA6MDAuMDAwWiIsInRyYWNlSWQiOiJ0cmFjZS1yZWNvcmRlciJ9" + } + ] + }, + "workflowExecutionTimeout": { + "seconds": "315360000" + }, + "workflowRunTimeout": { + "seconds": "315360000" + }, + "workflowTaskTimeout": { + "seconds": "10" + }, + "originalExecutionRunId": "90b791bb-537b-4b30-aad2-eb04e78266e7", + "identity": "16412@DESKTOP-6JCB1MB", + "firstExecutionRunId": "90b791bb-537b-4b30-aad2-eb04e78266e7", + "attempt": 1, + "firstWorkflowTaskBackoff": {}, + "header": {} + } + }, + { + "eventId": "2", + "eventTime": { + "seconds": "3054357224", + "nanos": 48000000 + }, + "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": "3054357224", + "nanos": 49000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "2", + "identity": "16412@DESKTOP-6JCB1MB", + "historySizeBytes": "362" + } + }, + { + "eventId": "4", + "eventTime": { + "seconds": "3054357224", + "nanos": 528000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "2", + "identity": "16412@DESKTOP-6JCB1MB", + "binaryChecksum": "@temporalio/worker@1.24.0+a38c63bdcd9c21b629389c984a449c37c0e971b2093a9f232d6f2e64448f4134", + "sdkMetadata": { + "coreUsedFlags": [ + 1, + 2, + 3 + ], + "sdkName": "temporal-typescript", + "sdkVersion": "1.24.0" + }, + "meteringMetadata": {} + } + }, + { + "eventId": "5", + "eventTime": { + "seconds": "3054357224", + "nanos": 528000000 + }, + "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": "eyJzY2hlZHVsZWRBdCI6IjIwMjYtMTAtMjVUMDA6MDA6MDAuMDAwWiIsInRyYWNlSWQiOiJ0cmFjZS1yZWNvcmRlciJ9" + } + ] + }, + "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": "3054357224", + "nanos": 529000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_STARTED", + "activityTaskStartedEventAttributes": { + "scheduledEventId": "5", + "identity": "16412@DESKTOP-6JCB1MB", + "attempt": 1 + } + }, + { + "eventId": "7", + "eventTime": { + "seconds": "3054357224", + "nanos": 533000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_COMPLETED", + "activityTaskCompletedEventAttributes": { + "result": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJzdGF0dXMiOiJSRUZSRVNIRUQiLCJnZW5lcmF0ZWRBdCI6IjIwMjYtMTAtMjVUMDA6MDA6MDAuMDAwWiIsInNvdXJjZUhhc2giOiJoYXNoLTEiLCJwcmV2aW91c1NvdXJjZUhhc2giOm51bGwsInN1bW1hcnkiOnt9LCJkdXJhdGlvbk1zIjoxfQ==" + } + ] + }, + "scheduledEventId": "5", + "startedEventId": "6", + "identity": "16412@DESKTOP-6JCB1MB" + } + }, + { + "eventId": "8", + "eventTime": { + "seconds": "3054357224", + "nanos": 533000000 + }, + "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": "3054357224", + "nanos": 533000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "8", + "identity": "16412@DESKTOP-6JCB1MB", + "historySizeBytes": "1141" + } + }, + { + "eventId": "10", + "eventTime": { + "seconds": "3054357224", + "nanos": 538000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "8", + "identity": "16412@DESKTOP-6JCB1MB", + "binaryChecksum": "@temporalio/worker@1.24.0+a38c63bdcd9c21b629389c984a449c37c0e971b2093a9f232d6f2e64448f4134", + "sdkMetadata": {}, + "meteringMetadata": {} + } + }, + { + "eventId": "11", + "eventTime": { + "seconds": "3054357224", + "nanos": 538000000 + }, + "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": "eyJzY2hlZHVsZWRBdCI6IjIwMjYtMTAtMjVUMDA6MDA6MDAuMDAwWiIsInRyYWNlSWQiOiJ0cmFjZS1yZWNvcmRlciJ9" + } + ] + }, + "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": "3054357224", + "nanos": 538000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_STARTED", + "activityTaskStartedEventAttributes": { + "scheduledEventId": "11", + "identity": "16412@DESKTOP-6JCB1MB", + "attempt": 1 + } + }, + { + "eventId": "13", + "eventTime": { + "seconds": "3054357224", + "nanos": 539000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_COMPLETED", + "activityTaskCompletedEventAttributes": { + "result": { + "payloads": [ + { + "metadata": { + "encoding": "YmluYXJ5L251bGw=" + } + } + ] + }, + "scheduledEventId": "11", + "startedEventId": "12", + "identity": "16412@DESKTOP-6JCB1MB" + } + }, + { + "eventId": "14", + "eventTime": { + "seconds": "3054357224", + "nanos": 539000000 + }, + "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": "3054357224", + "nanos": 539000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "14", + "identity": "16412@DESKTOP-6JCB1MB", + "historySizeBytes": "1751" + } + }, + { + "eventId": "16", + "eventTime": { + "seconds": "3054357224", + "nanos": 542000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "14", + "identity": "16412@DESKTOP-6JCB1MB", + "binaryChecksum": "@temporalio/worker@1.24.0+a38c63bdcd9c21b629389c984a449c37c0e971b2093a9f232d6f2e64448f4134", + "sdkMetadata": {}, + "meteringMetadata": {} + } + }, + { + "eventId": "17", + "eventTime": { + "seconds": "3054357224", + "nanos": 542000000 + }, + "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": "3054357224", + "nanos": 542000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_STARTED", + "activityTaskStartedEventAttributes": { + "scheduledEventId": "17", + "identity": "16412@DESKTOP-6JCB1MB", + "attempt": 1 + } + }, + { + "eventId": "19", + "eventTime": { + "seconds": "3054357224", + "nanos": 543000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_COMPLETED", + "activityTaskCompletedEventAttributes": { + "result": { + "payloads": [ + { + "metadata": { + "encoding": "YmluYXJ5L251bGw=" + } + } + ] + }, + "scheduledEventId": "17", + "startedEventId": "18", + "identity": "16412@DESKTOP-6JCB1MB" + } + }, + { + "eventId": "20", + "eventTime": { + "seconds": "3054357224", + "nanos": 543000000 + }, + "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": "3054357224", + "nanos": 543000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "20", + "identity": "16412@DESKTOP-6JCB1MB", + "historySizeBytes": "2260" + } + }, + { + "eventId": "22", + "eventTime": { + "seconds": "3054357224", + "nanos": 545000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "20", + "identity": "16412@DESKTOP-6JCB1MB", + "binaryChecksum": "@temporalio/worker@1.24.0+a38c63bdcd9c21b629389c984a449c37c0e971b2093a9f232d6f2e64448f4134", + "sdkMetadata": {}, + "meteringMetadata": {} + } + }, + { + "eventId": "23", + "eventTime": { + "seconds": "3054357224", + "nanos": 545000000 + }, + "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": "eyJzY2hlZHVsZWRBdCI6IjIwMjYtMTAtMjVUMDA6MDA6MDAuMDAwWiIsInRyYWNlSWQiOiJ0cmFjZS1yZWNvcmRlciIsInNvdXJjZUhhc2giOiJoYXNoLTEifQ==" + } + ] + }, + "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": "3054357224", + "nanos": 545000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_STARTED", + "activityTaskStartedEventAttributes": { + "scheduledEventId": "23", + "identity": "16412@DESKTOP-6JCB1MB", + "attempt": 1 + } + }, + { + "eventId": "25", + "eventTime": { + "seconds": "3054357224", + "nanos": 546000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_COMPLETED", + "activityTaskCompletedEventAttributes": { + "result": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJpZCI6InJ1bi1yZWNvcmRlZCIsIndvcmtmbG93SWQiOiJzcGFjZS1tZW1vcnktbWFpbnRlbmFuY2U6cmVjb3JkZWQifQ==" + } + ] + }, + "scheduledEventId": "23", + "startedEventId": "24", + "identity": "16412@DESKTOP-6JCB1MB" + } + }, + { + "eventId": "26", + "eventTime": { + "seconds": "3054357224", + "nanos": 546000000 + }, + "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": "3054357224", + "nanos": 546000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "26", + "identity": "16412@DESKTOP-6JCB1MB", + "historySizeBytes": "2954" + } + }, + { + "eventId": "28", + "eventTime": { + "seconds": "3054357224", + "nanos": 551000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "26", + "identity": "16412@DESKTOP-6JCB1MB", + "binaryChecksum": "@temporalio/worker@1.24.0+a38c63bdcd9c21b629389c984a449c37c0e971b2093a9f232d6f2e64448f4134", + "sdkMetadata": {}, + "meteringMetadata": {} + } + }, + { + "eventId": "29", + "eventTime": { + "seconds": "3054357224", + "nanos": 551000000 + }, + "eventType": "EVENT_TYPE_START_CHILD_WORKFLOW_EXECUTION_INITIATED", + "startChildWorkflowExecutionInitiatedEventAttributes": { + "namespace": "default", + "workflowId": "space-memory-maintenance:recorded", + "workflowType": { + "name": "memoryConsolidationWorkflow" + }, + "taskQueue": { + "name": "space-memory-maintenance", + "kind": "TASK_QUEUE_KIND_NORMAL" + }, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJydW5JZCI6InJ1bi1yZWNvcmRlZCIsInRyYWNlSWQiOiJ0cmFjZS1yZWNvcmRlciJ9" + } + ] + }, + "workflowExecutionTimeout": {}, + "workflowRunTimeout": {}, + "workflowTaskTimeout": { + "seconds": "10" + }, + "workflowTaskCompletedEventId": "27", + "header": {}, + "memo": {} + } + }, + { + "eventId": "30", + "eventTime": { + "seconds": "3054357224", + "nanos": 551000000 + }, + "eventType": "EVENT_TYPE_CHILD_WORKFLOW_EXECUTION_STARTED", + "childWorkflowExecutionStartedEventAttributes": { + "namespace": "default", + "initiatedEventId": "29", + "workflowExecution": { + "workflowId": "space-memory-maintenance:recorded", + "runId": "93ab811a-2d7e-4369-9334-e2494c9c03be" + }, + "workflowType": { + "name": "memoryConsolidationWorkflow" + } + } + }, + { + "eventId": "31", + "eventTime": { + "seconds": "3054357224", + "nanos": 551000000 + }, + "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": "3054357224", + "nanos": 552000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "31", + "identity": "16412@DESKTOP-6JCB1MB", + "historySizeBytes": "3560" + } + }, + { + "eventId": "33", + "eventTime": { + "seconds": "3054357224", + "nanos": 976000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "31", + "identity": "16412@DESKTOP-6JCB1MB", + "binaryChecksum": "@temporalio/worker@1.24.0+a38c63bdcd9c21b629389c984a449c37c0e971b2093a9f232d6f2e64448f4134", + "sdkMetadata": {}, + "meteringMetadata": {} + } + }, + { + "eventId": "34", + "eventTime": { + "seconds": "3054357224", + "nanos": 991000000 + }, + "eventType": "EVENT_TYPE_CHILD_WORKFLOW_EXECUTION_COMPLETED", + "childWorkflowExecutionCompletedEventAttributes": { + "result": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJpZCI6InJ1bi1yZWNvcmRlZCIsInN0YXR1cyI6IlJFUEFJUkVEIn0=" + } + ] + }, + "namespace": "default", + "workflowExecution": { + "workflowId": "space-memory-maintenance:recorded", + "runId": "93ab811a-2d7e-4369-9334-e2494c9c03be" + }, + "workflowType": { + "name": "memoryConsolidationWorkflow" + }, + "initiatedEventId": "29", + "startedEventId": "30" + } + }, + { + "eventId": "35", + "eventTime": { + "seconds": "3054357224", + "nanos": 991000000 + }, + "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": "3054357224", + "nanos": 991000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "35", + "identity": "16412@DESKTOP-6JCB1MB", + "historySizeBytes": "4017" + } + }, + { + "eventId": "37", + "eventTime": { + "seconds": "3054357224", + "nanos": 994000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "35", + "identity": "16412@DESKTOP-6JCB1MB", + "binaryChecksum": "@temporalio/worker@1.24.0+a38c63bdcd9c21b629389c984a449c37c0e971b2093a9f232d6f2e64448f4134", + "sdkMetadata": {}, + "meteringMetadata": {} + } + }, + { + "eventId": "38", + "eventTime": { + "seconds": "3054357224", + "nanos": 994000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_EXECUTION_COMPLETED", + "workflowExecutionCompletedEventAttributes": { + "result": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJzdGF0dXMiOiJSRUZSRVNIRUQiLCJnZW5lcmF0ZWRBdCI6IjIwMjYtMTAtMjVUMDA6MDA6MDAuMDAwWiIsInNvdXJjZUhhc2giOiJoYXNoLTEiLCJwcmV2aW91c1NvdXJjZUhhc2giOm51bGwsInN1bW1hcnkiOnt9LCJkdXJhdGlvbk1zIjoxfQ==" + } + ] + }, + "workflowTaskCompletedEventId": "36" + } + } + ] +} diff --git a/apps/worker/tests/fixtures/workflow-histories/mutation-run.json b/apps/worker/tests/fixtures/workflow-histories/mutation-run.json new file mode 100644 index 000000000..5195e4b84 --- /dev/null +++ b/apps/worker/tests/fixtures/workflow-histories/mutation-run.json @@ -0,0 +1,253 @@ +{ + "events": [ + { + "eventId": "1", + "eventTime": { + "seconds": "3054357225", + "nanos": 378000000 + }, + "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": "eyJjaGFuZ2VTZXRJZCI6ImNzLXJlY29yZGVyIiwidHJhY2VJZCI6InRyYWNlLXJlY29yZGVyIn0=" + } + ] + }, + "workflowExecutionTimeout": { + "seconds": "315360000" + }, + "workflowRunTimeout": { + "seconds": "315360000" + }, + "workflowTaskTimeout": { + "seconds": "10" + }, + "originalExecutionRunId": "90dcc30f-47f2-4d7f-916f-735e7580f0ff", + "identity": "16412@DESKTOP-6JCB1MB", + "firstExecutionRunId": "90dcc30f-47f2-4d7f-916f-735e7580f0ff", + "attempt": 1, + "firstWorkflowTaskBackoff": {}, + "header": {} + } + }, + { + "eventId": "2", + "eventTime": { + "seconds": "3054357225", + "nanos": 378000000 + }, + "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": "3054357225", + "nanos": 378000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "2", + "identity": "16412@DESKTOP-6JCB1MB", + "historySizeBytes": "344" + } + }, + { + "eventId": "4", + "eventTime": { + "seconds": "3054357225", + "nanos": 411000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "2", + "identity": "16412@DESKTOP-6JCB1MB", + "binaryChecksum": "@temporalio/worker@1.24.0+2ecb8619e5867fd0813a3bd3cd9b4a58543f5f71a0e018f80caa739140f9ab9b", + "sdkMetadata": { + "coreUsedFlags": [ + 1, + 2, + 3 + ], + "sdkName": "temporal-typescript", + "sdkVersion": "1.24.0" + }, + "meteringMetadata": {} + } + }, + { + "eventId": "5", + "eventTime": { + "seconds": "3054357225", + "nanos": 411000000 + }, + "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": "eyJjaGFuZ2VTZXRJZCI6ImNzLXJlY29yZGVyIiwidHJhY2VJZCI6InRyYWNlLXJlY29yZGVyIn0=" + } + ] + }, + "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": "3054357225", + "nanos": 411000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_STARTED", + "activityTaskStartedEventAttributes": { + "scheduledEventId": "5", + "identity": "16412@DESKTOP-6JCB1MB", + "attempt": 1 + } + }, + { + "eventId": "7", + "eventTime": { + "seconds": "3054357225", + "nanos": 412000000 + }, + "eventType": "EVENT_TYPE_ACTIVITY_TASK_COMPLETED", + "activityTaskCompletedEventAttributes": { + "result": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJzdGF0dXMiOiJBUFBMSUVEIn0=" + } + ] + }, + "scheduledEventId": "5", + "startedEventId": "6", + "identity": "16412@DESKTOP-6JCB1MB" + } + }, + { + "eventId": "8", + "eventTime": { + "seconds": "3054357225", + "nanos": 412000000 + }, + "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": "3054357225", + "nanos": 413000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_STARTED", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "8", + "identity": "16412@DESKTOP-6JCB1MB", + "historySizeBytes": "989" + } + }, + { + "eventId": "10", + "eventTime": { + "seconds": "3054357225", + "nanos": 419000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_TASK_COMPLETED", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "8", + "identity": "16412@DESKTOP-6JCB1MB", + "binaryChecksum": "@temporalio/worker@1.24.0+2ecb8619e5867fd0813a3bd3cd9b4a58543f5f71a0e018f80caa739140f9ab9b", + "sdkMetadata": {}, + "meteringMetadata": {} + } + }, + { + "eventId": "11", + "eventTime": { + "seconds": "3054357225", + "nanos": 419000000 + }, + "eventType": "EVENT_TYPE_WORKFLOW_EXECUTION_COMPLETED", + "workflowExecutionCompletedEventAttributes": { + "result": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "eyJzdGF0dXMiOiJBUFBMSUVEIn0=" + } + ] + }, + "workflowTaskCompletedEventId": "9" + } + } + ] +} diff --git a/apps/worker/tests/workflow-replay-gate.test.ts b/apps/worker/tests/workflow-replay-gate.test.ts new file mode 100644 index 000000000..bcdf23449 --- /dev/null +++ b/apps/worker/tests/workflow-replay-gate.test.ts @@ -0,0 +1,72 @@ +import { fileURLToPath } from "node:url"; +import { afterAll, beforeAll, expect, it } from "vitest"; +import { TestWorkflowEnvironment } from "@temporalio/testing"; +import { Worker } from "@temporalio/worker"; +/** + * Proves the replay gate itself works. + * + * A history recorded by `fixtures/replay/clean-workflows.ts` must replay without an + * error against that same file, and must fail with `DeterminismViolationError` + * against `fixtures/replay/drifted-workflows.ts`, which adds one timer. Without this + * negative case a green replay run would prove nothing. + */ + +const CLEAN_BUNDLE = fileURLToPath(new URL("./fixtures/replay/clean-workflows.ts", import.meta.url)); +const DRIFTED_BUNDLE = fileURLToPath(new URL("./fixtures/replay/drifted-workflows.ts", import.meta.url)); +const PROBE_TASK_QUEUE = "workflow-replay-probe"; +const PROBE_WORKFLOW_ID = "workflow-replay-probe-clean"; + +type RecordedHistory = { workflowId: string; history: { events: unknown[] } }; + +let env: TestWorkflowEnvironment; +let recorded: RecordedHistory[] = []; + +beforeAll(async () => { + env = await TestWorkflowEnvironment.createTimeSkipping(); + const worker = await Worker.create({ + connection: env.nativeConnection, + taskQueue: PROBE_TASK_QUEUE, + workflowsPath: CLEAN_BUNDLE, + activities: { recordReplayProbeStep: async () => undefined } + }); + await worker.runUntil(async () => { + const handle = await env.client.workflow.start("replayProbeWorkflow", { + taskQueue: PROBE_TASK_QUEUE, workflowId: PROBE_WORKFLOW_ID, args: [] + }); + await expect(handle.result()).resolves.toBe("done"); + const description = await handle.describe(); + const response = await env.client.workflowService.getWorkflowExecutionHistory({ + namespace: "default", + execution: { workflowId: PROBE_WORKFLOW_ID, runId: description.runId } + }); + expect(response.history?.events?.length).toBeGreaterThan(0); + recorded = [{ workflowId: PROBE_WORKFLOW_ID, history: response.history as { events: unknown[] } }]; + }); +}, 300_000); + +afterAll(async () => { + await env?.teardown(); +}); + +async function replay(workflowsPath: string) { + const results = []; + for await (const result of Worker.runReplayHistories({ workflowsPath }, recorded)) results.push(result); + return results; +} + +it("replays a recorded history against unchanged workflow code", async () => { + const results = await replay(CLEAN_BUNDLE); + expect(results).toHaveLength(1); + expect(results[0]!.error).toBeUndefined(); +}, 180_000); + +it("fails replay with a determinism violation when a timer was added", async () => { + const results = await replay(DRIFTED_BUNDLE); + expect(results).toHaveLength(1); + const error = results[0]!.error; + // Compared by name: the class is defined in @temporalio/workflow, and importing it + // here would load a second copy that `instanceof` cannot match. + expect(error).toBeDefined(); + expect(error!.name).toBe("DeterminismViolationError"); + expect(String(error)).toContain("Replay"); +}, 180_000); diff --git a/apps/worker/tests/workflow-replay.test.ts b/apps/worker/tests/workflow-replay.test.ts new file mode 100644 index 000000000..bc950ac12 --- /dev/null +++ b/apps/worker/tests/workflow-replay.test.ts @@ -0,0 +1,92 @@ +import { readdir, readFile } from "node:fs/promises"; +import { join } from "node:path"; +import { fileURLToPath } from "node:url"; +import { describe, expect, it } from "vitest"; +import { Worker } from "@temporalio/worker"; +import { temporal } from "@temporalio/proto"; + +const History = temporal.api.history.v1.History as any; + +/** + * Replay gate (CORE-006): + * + * Every history in `fixtures/workflow-histories/` was recorded by + * `scripts/record-workflow-histories.mjs` against the workflow code at the time of + * recording. Replaying them here fails whenever a workflow change is not + * replay-compatible with running executions, so a determinism break shows up in CI + * instead of in a user's installation. + * + * When a replay fails: either the change broke replay (fix the code, or wrap the + * change in `patched()`), or the history is stale and must be re-recorded together + * with a `patched()` change. Never re-record just to make this test pass. + */ + +const FIXTURES = fileURLToPath(new URL("./fixtures/workflow-histories/", import.meta.url)); + +const BUNDLES: Record = { + main: fileURLToPath(new URL("../src/workflows.ts", import.meta.url)), + maintenance: fileURLToPath(new URL("../src/memory-maintenance-workflows.ts", import.meta.url)), + mutation: fileURLToPath(new URL("../src/memory-mutation-workflows.ts", import.meta.url)) +}; + +type Recorded = { workflowId: string; history: { events: Array> } }; + +/** + * One fixture per workflow, written by `scripts/record-workflow-histories.mjs`. + * Listing them explicitly is what makes a deleted or renamed fixture fail: the + * per-bundle tests below only require each bundle to be non-empty, so a single + * surviving file would otherwise keep them green while coverage quietly shrank. + */ +const EXPECTED_FIXTURES = [ + "main-codex-turn.json", + "main-dot-goal.json", + "main-dummy-turn.json", + "main-room-action.json", + "main-supervisor-first-run.json", + "maintenance-consolidation.json", + "maintenance-run.json", + "mutation-run.json" +]; + +it("keeps exactly one recorded history per workflow", async () => { + const files = (await readdir(FIXTURES)).filter((file) => file.endsWith(".json")); + expect([...files].sort()).toEqual([...EXPECTED_FIXTURES].sort()); +}, 30_000); + +async function historiesFor(prefix: string): Promise { + const files = (await readdir(FIXTURES)).filter((file) => file.endsWith(".json") && file.startsWith(`${prefix}-`)).sort(); + return Promise.all(files.map(async (file) => { + const json = JSON.parse(await readFile(join(FIXTURES, file), "utf8")); + // `Worker.runReplayHistories` converts a history through `historyFromJSON` whenever + // events[0].eventId is a string. That helper resolves its protobufjs instance from + // @temporalio/common while the type it looks up comes from @temporalio/proto, and + // this repository's lockfile pins one protobufjs install per package, so the lookup + // fails with "type must be a Type". `History.fromObject` from @temporalio/proto builds + // the message with the same instance the worker encodes it with, so the ids arrive as + // Longs and the worker hands the object straight to the replay bridge. + const history = History.fromObject(json) as unknown as Recorded["history"]; + const started = (json.events as Array>).find((event) => event.workflowExecutionStartedEventAttributes); + return { + workflowId: started?.workflowExecutionStartedEventAttributes?.workflowId ?? file.replace(/\.json$/, ""), + history + }; + })); +} + +for (const [prefix, workflowsPath] of Object.entries(BUNDLES)) { + describe(`${prefix} workflow histories`, () => { + it("replays every recorded history without a determinism violation", async () => { + const recorded = await historiesFor(prefix); + expect(recorded.length).toBeGreaterThan(0); + + const failures: Array<{ workflowId: string; error: string }> = []; + for await (const result of Worker.runReplayHistories({ workflowsPath }, recorded)) { + if (result.error) { + failures.push({ workflowId: result.workflowId, error: `${result.error.name}: ${result.error.message}` }); + } + } + + expect(failures).toEqual([]); + }, 180_000); + }); +} diff --git a/apps/worker/tests/workflow-sandbox.test.ts b/apps/worker/tests/workflow-sandbox.test.ts new file mode 100644 index 000000000..0a9f8c30b --- /dev/null +++ b/apps/worker/tests/workflow-sandbox.test.ts @@ -0,0 +1,488 @@ +import { fileURLToPath } from "node:url"; +import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { TestWorkflowEnvironment } from "@temporalio/testing"; +import { Worker } from "@temporalio/worker"; +import { DEFAULT_TASK_QUEUE } from "../src/constants.js"; +import { MEMORY_MAINTENANCE_TASK_QUEUE } from "../src/memory-maintenance-config.js"; +import { MEMORY_MUTATION_TASK_QUEUE } from "../src/memory-mutation-config.js"; +import { DOT_GOAL_SIGNAL, ROOM_AGENT_ENQUEUE_SIGNAL } from "@space/contracts"; +import { runDotGoalLoop } from "../src/dot-goal-loop.js"; + +/** + * Real Temporal workflow sandbox (CORE-006). + * + * Every workflow in `apps/worker/src` runs here inside + * `TestWorkflowEnvironment.createTimeSkipping()` with the production workflow + * code and stubbed activities: no database, no Docker, no AI provider. The + * mocked suites next to this file keep their own unit-level coverage; this file + * is the only place the real workflow sandbox, timers, signal ordering and + * continue-as-new behaviour are exercised. + */ + +const MAIN_WORKFLOWS = fileURLToPath(new URL("../src/workflows.ts", import.meta.url)); +const MAINTENANCE_WORKFLOWS = fileURLToPath(new URL("../src/memory-maintenance-workflows.ts", import.meta.url)); +const MUTATION_WORKFLOWS = fileURLToPath(new URL("../src/memory-mutation-workflows.ts", import.meta.url)); + +type Call = { name: string; args: unknown[] }; +type Handler = (...args: any[]) => unknown; + +const calls: Call[] = []; +let handlers: Record = {}; + +function stub(name: string) { + return async (...args: unknown[]) => { + calls.push({ name, args }); + const handler = handlers[name]; + if (!handler) throw new Error(`workflow called unstubbed activity: ${name}`); + return handler(...args); + }; +} + +const ACTIVITY_NAMES = [ + // workflows.ts + "recordDummyTurnStarted", "recordDummyTurnCompleted", + "runCodexAppServerTurn", "runOpenCodeAgentTurn", "runCliAgentTurn", "runRoomAgentBackgroundAction", + "markRoomAgentMissionStarted", "markRoomAgentMissionFinished", "markRoomAgentMissionContinued", + "settleRoomAgentBackgroundMission", "checkRoomAgentSupervisorTurn", "discardRoomAgentSupervisorTurn", + "finishRoomAgentSupervisorTurn", "prepareRoomAgentBackgroundContinuation", "failRoomAgentBackgroundAction", + "dotGoalStep", "dotGoalObserve", + // memory-maintenance-workflows.ts + "refreshMemoryGraphSnapshot", "refreshAllMonthsMemoryGraphSnapshot", "reconcileStaleSpaceAgentSessions", + "createScheduledMemoryAudit", "prepareMemoryConsolidation", "finalizeMemoryRepair", + // memory-mutation-workflows.ts + "executePersistedMemoryChangeSet" +] as const; + +function activities() { + return Object.fromEntries(ACTIVITY_NAMES.map((name) => [name, stub(name)])); +} + +function called(name: string): Call[] { + return calls.filter((call) => call.name === name); +} + +let env: TestWorkflowEnvironment; +let workers: Worker[] = []; +let runs: Promise[] = []; + +beforeAll(async () => { + env = await TestWorkflowEnvironment.createTimeSkipping(); + workers = await Promise.all([ + Worker.create({ connection: env.nativeConnection, taskQueue: DEFAULT_TASK_QUEUE, workflowsPath: MAIN_WORKFLOWS, activities: activities() }), + Worker.create({ connection: env.nativeConnection, taskQueue: MEMORY_MAINTENANCE_TASK_QUEUE, workflowsPath: MAINTENANCE_WORKFLOWS, activities: activities() }), + Worker.create({ connection: env.nativeConnection, taskQueue: MEMORY_MUTATION_TASK_QUEUE, workflowsPath: MUTATION_WORKFLOWS, activities: activities() }) + ]); + runs = workers.map((worker) => worker.run()); +}, 300_000); + +beforeEach(() => { + calls.length = 0; + handlers = {}; +}); + +afterAll(async () => { + for (const worker of workers) worker.shutdown(); + await Promise.allSettled(runs); + await env?.teardown(); +}); + +let sequence = 0; +function workflowId(prefix: string) { + sequence += 1; + return `${prefix}-sandbox-${sequence}`; +} + +async function waitFor(predicate: () => boolean, label: string, timeoutMs = 90_000) { + const startedAt = Date.now(); + while (!predicate()) { + if (Date.now() - startedAt > timeoutMs) throw new Error(`timed out waiting for ${label}`); + await new Promise((resolve) => setTimeout(resolve, 20)); + } +} + +/** + * continueAsNew closes the current run and opens a new one under the same workflow + * id, so a changed run id is the observable proof that the handover happened. This + * avoids describing a closed run, which the time-skipping server does not answer. + */ +async function waitForContinuation( + handle: { describe(): Promise<{ runId: string }> }, + firstRunId: string, + label: string, + timeoutMs = 120_000 +) { + const startedAt = Date.now(); + for (;;) { + const description = await handle.describe(); + if (description.runId !== firstRunId) return description; + if (Date.now() - startedAt > timeoutMs) throw new Error(`timed out waiting for ${label}; the run id never changed`); + await new Promise((resolve) => setTimeout(resolve, 50)); + } +} + +const TURN: any = { + roomId: "room-sandbox", + paneId: "pane-sandbox", + prompt: "Run the sandbox turn", + artifactIds: [] +}; + +describe("dummyTurnWorkflow", () => { + it("records start then completion through real activities", async () => { + handlers.recordDummyTurnStarted = () => undefined; + handlers.recordDummyTurnCompleted = (input) => ({ status: "COMPLETED", message: `done:${input.prompt}` }); + + const handle = await env.client.workflow.start("dummyTurnWorkflow", { + taskQueue: DEFAULT_TASK_QUEUE, workflowId: workflowId("dummy"), args: [TURN] + }); + await expect(handle.result()).resolves.toEqual({ status: "COMPLETED", message: `done:${TURN.prompt}` }); + expect(called("recordDummyTurnStarted")).toHaveLength(1); + expect(called("recordDummyTurnCompleted")).toHaveLength(1); + }, 120_000); +}); + +describe("codexAppServerTurnWorkflow", () => { + it("routes opencode providers to the OpenCode turn activity", async () => { + handlers.runOpenCodeAgentTurn = () => ({ status: "COMPLETED", message: "opencode" }); + const handle = await env.client.workflow.start("codexAppServerTurnWorkflow", { + taskQueue: DEFAULT_TASK_QUEUE, workflowId: workflowId("turn-opencode"), + args: [{ ...TURN, providerId: "opencode" }] + }); + await expect(handle.result()).resolves.toEqual({ status: "COMPLETED", message: "opencode" }); + expect(called("runOpenCodeAgentTurn")).toHaveLength(1); + expect(called("runCodexAppServerTurn")).toHaveLength(0); + }, 120_000); + + it("routes CLI chat providers to the CLI turn activity", async () => { + handlers.runCliAgentTurn = () => ({ status: "COMPLETED", message: "cli" }); + const handle = await env.client.workflow.start("codexAppServerTurnWorkflow", { + taskQueue: DEFAULT_TASK_QUEUE, workflowId: workflowId("turn-cli"), + args: [{ ...TURN, providerId: "cli:claude" }] + }); + await expect(handle.result()).resolves.toEqual({ status: "COMPLETED", message: "cli" }); + expect(called("runCliAgentTurn")).toHaveLength(1); + expect(called("runCodexAppServerTurn")).toHaveLength(0); + }, 120_000); + + it("retries the turn activity and surfaces the failure to the caller", async () => { + let attempts = 0; + handlers.runCodexAppServerTurn = () => { + attempts += 1; + throw new Error("provider exploded"); + }; + const handle = await env.client.workflow.start("codexAppServerTurnWorkflow", { + taskQueue: DEFAULT_TASK_QUEUE, workflowId: workflowId("turn-retry"), args: [TURN] + }); + await expect(handle.result()).rejects.toThrow(); + expect(attempts).toBeGreaterThan(1); + }, 120_000); +}); + +describe("roomAgentActionWorkflow", () => { + const actionInput = { bridge: { roomId: "room-sandbox", missionId: "mission-sandbox" }, traceId: "trace-sandbox" }; + + it("returns the background action result", async () => { + handlers.runRoomAgentBackgroundAction = () => ({ status: "COMPLETED" }); + const handle = await env.client.workflow.start("roomAgentActionWorkflow", { + taskQueue: DEFAULT_TASK_QUEUE, workflowId: "space-room-action:action-sandbox-ok", args: [actionInput] + }); + await expect(handle.result()).resolves.toEqual({ status: "COMPLETED" }); + expect(called("failRoomAgentBackgroundAction")).toHaveLength(0); + }, 120_000); + + it("persists the failure through the patched failure activity and rethrows", async () => { + handlers.runRoomAgentBackgroundAction = () => { throw new Error("transport failed"); }; + handlers.failRoomAgentBackgroundAction = () => undefined; + const handle = await env.client.workflow.start("roomAgentActionWorkflow", { + taskQueue: DEFAULT_TASK_QUEUE, workflowId: "space-room-action:action-sandbox-fail", args: [actionInput] + }); + await expect(handle.result()).rejects.toThrow(); + const failures = called("failRoomAgentBackgroundAction"); + expect(failures).toHaveLength(1); + expect(failures[0]!.args[0]).toMatchObject({ + roomId: "room-sandbox", missionId: "mission-sandbox", actionId: "action-sandbox-fail", cancelled: false + }); + }, 120_000); +}); + +describe("memoryMutationWorkflow", () => { + it("executes the persisted change set", async () => { + handlers.executePersistedMemoryChangeSet = () => ({ status: "APPLIED" }); + const handle = await env.client.workflow.start("memoryMutationWorkflow", { + taskQueue: MEMORY_MUTATION_TASK_QUEUE, workflowId: workflowId("mutation"), + args: [{ changeSetId: "change-set-sandbox", traceId: "trace-sandbox" }] + }); + await expect(handle.result()).resolves.toEqual({ status: "APPLIED" }); + expect(called("executePersistedMemoryChangeSet")).toHaveLength(1); + }, 120_000); +}); + +describe("memoryConsolidationWorkflow", () => { + it("returns the prepared run without mutating when there is no change set", async () => { + handlers.prepareMemoryConsolidation = () => ({ changeSetId: null, run: { id: "run-sandbox", status: "NO_CHANGES" } }); + const handle = await env.client.workflow.start("memoryConsolidationWorkflow", { + taskQueue: MEMORY_MAINTENANCE_TASK_QUEUE, workflowId: workflowId("consolidation-empty"), + args: [{ runId: "run-sandbox", traceId: "trace-sandbox" }] + }); + await expect(handle.result()).resolves.toEqual({ id: "run-sandbox", status: "NO_CHANGES" }); + expect(called("executePersistedMemoryChangeSet")).toHaveLength(0); + expect(called("finalizeMemoryRepair")).toHaveLength(0); + }, 120_000); + + it("executes and finalizes the change set when the audit produced one", async () => { + handlers.prepareMemoryConsolidation = () => ({ changeSetId: "cs-1", run: { id: "run-sandbox", status: "PENDING" } }); + handlers.executePersistedMemoryChangeSet = () => ({ status: "APPLIED" }); + handlers.finalizeMemoryRepair = (input) => ({ id: input.runId, status: "REPAIRED", changeSetId: input.changeSetId }); + const handle = await env.client.workflow.start("memoryConsolidationWorkflow", { + taskQueue: MEMORY_MAINTENANCE_TASK_QUEUE, workflowId: workflowId("consolidation-apply"), + args: [{ runId: "run-sandbox", traceId: "trace-sandbox" }] + }); + await expect(handle.result()).resolves.toEqual({ id: "run-sandbox", status: "REPAIRED", changeSetId: "cs-1" }); + expect(called("executePersistedMemoryChangeSet")[0]!.args[0]).toEqual({ changeSetId: "cs-1", traceId: "trace-sandbox" }); + expect(called("finalizeMemoryRepair")).toHaveLength(1); + }, 120_000); +}); + +describe("memoryMaintenanceWorkflow", () => { + const input = { scheduledAt: "2026-10-25T00:00:00.000Z", traceId: "trace-sandbox" }; + const refreshed = { status: "REFRESHED", generatedAt: input.scheduledAt, sourceHash: "hash-1", previousSourceHash: null, summary: {}, durationMs: 12 }; + + it("runs consolidation as a child workflow when every step succeeds", async () => { + handlers.refreshMemoryGraphSnapshot = () => refreshed; + handlers.refreshAllMonthsMemoryGraphSnapshot = () => undefined; + handlers.reconcileStaleSpaceAgentSessions = () => undefined; + handlers.createScheduledMemoryAudit = () => ({ id: "run-ok", workflowId: workflowId("audit") }); + handlers.prepareMemoryConsolidation = () => ({ changeSetId: null, run: { id: "run-ok", status: "NO_CHANGES" } }); + + const handle = await env.client.workflow.start("memoryMaintenanceWorkflow", { + taskQueue: MEMORY_MAINTENANCE_TASK_QUEUE, workflowId: workflowId("maintenance-ok"), args: [input] + }); + await expect(handle.result()).resolves.toEqual(refreshed); + expect(called("refreshMemoryGraphSnapshot")).toHaveLength(1); + expect(called("refreshAllMonthsMemoryGraphSnapshot")).toHaveLength(1); + expect(called("reconcileStaleSpaceAgentSessions")).toHaveLength(1); + expect(called("createScheduledMemoryAudit")).toHaveLength(1); + // The child workflow really executed in the sandbox: it calls its own activity. + expect(called("prepareMemoryConsolidation")).toHaveLength(1); + }, 180_000); + + it("continues when the all-months refresh and the session reconciliation both fail", async () => { + handlers.refreshMemoryGraphSnapshot = () => refreshed; + handlers.refreshAllMonthsMemoryGraphSnapshot = () => { throw new Error("all-months refresh failed"); }; + handlers.reconcileStaleSpaceAgentSessions = () => { throw new Error("reconciliation failed"); }; + handlers.createScheduledMemoryAudit = () => ({ id: "run-degraded", workflowId: workflowId("audit-degraded") }); + handlers.prepareMemoryConsolidation = () => ({ changeSetId: null, run: { id: "run-degraded", status: "NO_CHANGES" } }); + + const handle = await env.client.workflow.start("memoryMaintenanceWorkflow", { + taskQueue: MEMORY_MAINTENANCE_TASK_QUEUE, workflowId: workflowId("maintenance-degraded"), args: [input] + }); + await expect(handle.result()).resolves.toEqual(refreshed); + // Both failure paths were retried up to their maximum attempts and then swallowed. + expect(called("refreshAllMonthsMemoryGraphSnapshot").length).toBeGreaterThan(1); + expect(called("reconcileStaleSpaceAgentSessions").length).toBeGreaterThan(1); + expect(called("createScheduledMemoryAudit")).toHaveLength(1); + expect(called("prepareMemoryConsolidation")).toHaveLength(1); + }, 180_000); + + it("fails the workflow when the live snapshot refresh itself fails", async () => { + handlers.refreshMemoryGraphSnapshot = () => { throw new Error("snapshot refresh failed"); }; + + const handle = await env.client.workflow.start("memoryMaintenanceWorkflow", { + taskQueue: MEMORY_MAINTENANCE_TASK_QUEUE, workflowId: workflowId("maintenance-fail"), args: [input] + }); + await expect(handle.result()).rejects.toThrow(); + expect(called("refreshMemoryGraphSnapshot").length).toBeGreaterThan(1); + expect(called("createScheduledMemoryAudit")).toHaveLength(0); + }, 180_000); +}); + +describe("dotGoalWorkflow", () => { + it("returns the goal status as soon as the API reports DONE", async () => { + handlers.dotGoalStep = () => ({ state: "DONE", runId: null, goalStatus: "COMPLETE" }); + + const handle = await env.client.workflow.start("dotGoalWorkflow", { + taskQueue: DEFAULT_TASK_QUEUE, workflowId: workflowId("dot-done"), args: [{ goalId: "goal-sandbox" }] + }); + await expect(handle.result()).resolves.toEqual({ goalStatus: "COMPLETE" }); + expect(called("dotGoalStep")).toHaveLength(1); + expect(called("dotGoalObserve")).toHaveLength(0); + }, 120_000); + + it("ends the wait early when the wake signal arrives instead of the timeout", async () => { + let steps = 0; + handlers.dotGoalStep = () => { + steps += 1; + return steps === 1 + ? { state: "WAITING", runId: null, goalStatus: "RUNNING" } + : { state: "DONE", runId: null, goalStatus: "COMPLETE" }; + }; + + const id = workflowId("dot-wake"); + const handle = await env.client.workflow.start("dotGoalWorkflow", { + taskQueue: DEFAULT_TASK_QUEUE, workflowId: id, args: [{ goalId: "goal-sandbox" }] + }); + // The workflow is idle in its 30 second wait; a wake-up must end it at once. + await waitFor(() => called("dotGoalStep").length === 1, "the first goal step"); + await handle.signal(DOT_GOAL_SIGNAL); + await expect(handle.result()).resolves.toEqual({ goalStatus: "COMPLETE" }); + expect(called("dotGoalStep")).toHaveLength(2); + }, 120_000); + + it("polls the observed run until it stops being QUEUED or RUNNING", async () => { + let observations = 0; + handlers.dotGoalStep = () => ({ state: "STARTED", runId: "run-sandbox-step", goalStatus: "RUNNING" }); + handlers.dotGoalObserve = () => { + observations += 1; + return observations < 3 + ? { run: "RUNNING", goalStatus: "RUNNING" } + : { run: "SUCCEEDED", goalStatus: "RUNNING" }; + }; + // The final step reports DONE so the loop can return. + const originalStep = handlers.dotGoalStep; + handlers.dotGoalStep = (goalId: string) => { + if (observations >= 3) return { state: "DONE", runId: null, goalStatus: "COMPLETE" }; + return originalStep(goalId); + }; + + const handle = await env.client.workflow.start("dotGoalWorkflow", { + taskQueue: DEFAULT_TASK_QUEUE, workflowId: workflowId("dot-observe"), args: [{ goalId: "goal-sandbox" }] + }); + await expect(handle.result()).resolves.toEqual({ goalStatus: "COMPLETE" }); + expect(called("dotGoalObserve")).toHaveLength(3); + }, 120_000); + + it("reports an unfinished goal when the loop exhausts its iteration budget", async () => { + // This drives `runDotGoalLoop` directly; it does not start dotGoalWorkflow, so it + // does not exercise continueAsNew itself. What it pins down is the value the + // workflow branches on: `goalStatus === null` is the only input that reaches + // `continueAsNew`. The end-to-end handover needs the workflow to burn its full + // 300-iteration budget, and every iteration is a real activity round trip, which + // takes minutes and would break the three-minute CI budget this issue sets. The + // branch itself is one line and stays uncovered here on purpose. + let steps = 0; + const result = await runDotGoalLoop("goal-budget-sandbox", { + step: async () => { steps += 1; return { state: "WAITING", runId: null, goalStatus: "RUNNING" }; }, + observe: async () => ({ run: "SUCCEEDED", goalStatus: "RUNNING" }), + wait: async () => undefined + }, 3); + expect(result).toEqual({ goalStatus: null }); + expect(steps).toBe(3); + }, 30_000); + + it("stops the loop as soon as the history says to continue as new", async () => { + let steps = 0; + const result = await runDotGoalLoop("goal-history-sandbox", { + step: async () => { steps += 1; return { state: "WAITING", runId: null, goalStatus: "RUNNING" }; }, + observe: async () => ({ run: "SUCCEEDED", goalStatus: "RUNNING" }), + wait: async () => undefined, + shouldContinueAsNew: () => true + }, 300); + expect(result).toEqual({ goalStatus: null }); + expect(steps).toBe(0); + }, 30_000); +}); + +describe("roomAgentSupervisorWorkflow", () => { + const ROOM = "room-sandbox"; + const queueItem = (missionId: string, agentRunId: string) => ({ + missionId, + turn: { + roomId: ROOM, paneId: "pane-sandbox", prompt: "Run the sandbox mission", + artifactIds: [], agentRunId, traceId: `trace-${agentRunId}-sandbox` + } + }); + const supervisorInput = (overrides: Record = {}) => ({ + roomId: ROOM, pending: [], monitoringMissionIds: [], interrupted: [], processedCount: 0, ...overrides + }); + const verifiedTurn = () => ({ + status: "COMPLETED", message: "ok", + roomAgentOutcome: { status: "VERIFIED", executedActionCount: 0, statusReason: "Verified." } + }); + + beforeEach(() => { + handlers.checkRoomAgentSupervisorTurn = () => "PROCEED"; + handlers.discardRoomAgentSupervisorTurn = () => undefined; + handlers.finishRoomAgentSupervisorTurn = () => undefined; + handlers.runCodexAppServerTurn = verifiedTurn; + }); + + it("runs a queued turn through the child workflow and finishes the mission", async () => { + const handle = await env.client.workflow.start("roomAgentSupervisorWorkflow", { + taskQueue: DEFAULT_TASK_QUEUE, workflowId: workflowId("supervisor-run"), + args: [supervisorInput({ pending: [queueItem("mission-run", "agent-run")] })] + }); + await waitFor(() => called("finishRoomAgentSupervisorTurn").length === 1, "the mission to finish"); + expect(called("checkRoomAgentSupervisorTurn")).toHaveLength(1); + expect(called("runCodexAppServerTurn")).toHaveLength(1); + expect(called("finishRoomAgentSupervisorTurn")[0]!.args[0]).toMatchObject({ status: "COMPLETED" }); + await handle.terminate("sandbox test finished"); + }, 180_000); + + it("accepts a new mission through the enqueue signal once the queue is idle", async () => { + const handle = await env.client.workflow.start("roomAgentSupervisorWorkflow", { + taskQueue: DEFAULT_TASK_QUEUE, workflowId: workflowId("supervisor-signal"), + args: [supervisorInput({ pending: [queueItem("mission-first", "agent-run-first")] })] + }); + await waitFor(() => called("finishRoomAgentSupervisorTurn").length === 1, "the first mission to finish"); + // The supervisor is now parked on a signal condition with its handlers registered. + await handle.signal(ROOM_AGENT_ENQUEUE_SIGNAL, queueItem("mission-second", "agent-run-second")); + await waitFor(() => called("finishRoomAgentSupervisorTurn").length === 2, "the signalled mission to finish"); + expect(called("checkRoomAgentSupervisorTurn")).toHaveLength(2); + expect(called("runCodexAppServerTurn")).toHaveLength(2); + await handle.terminate("sandbox test finished"); + }, 180_000); + + it("discards and interrupts a turn carried in the interrupted list", async () => { + const handle = await env.client.workflow.start("roomAgentSupervisorWorkflow", { + taskQueue: DEFAULT_TASK_QUEUE, workflowId: workflowId("supervisor-interrupted"), + args: [supervisorInput({ interrupted: [{ item: queueItem("mission-stop", "agent-run-stop"), reason: "Stopped by operator." }] })] + }); + await waitFor(() => called("finishRoomAgentSupervisorTurn").length === 1, "the interrupted turn to be retired"); + expect(called("discardRoomAgentSupervisorTurn")).toHaveLength(1); + expect(called("finishRoomAgentSupervisorTurn")[0]!.args[0]).toMatchObject({ status: "INTERRUPTED", statusReason: "Stopped by operator." }); + expect(called("runCodexAppServerTurn")).toHaveLength(0); + await handle.terminate("sandbox test finished"); + }, 180_000); + + it("skips a turn the API reports as no longer active", async () => { + handlers.checkRoomAgentSupervisorTurn = () => "SKIP"; + const handle = await env.client.workflow.start("roomAgentSupervisorWorkflow", { + taskQueue: DEFAULT_TASK_QUEUE, workflowId: workflowId("supervisor-skip"), + args: [supervisorInput({ pending: [queueItem("mission-skip", "agent-run-skip")] })] + }); + await waitFor(() => called("discardRoomAgentSupervisorTurn").length === 1, "the skipped turn to be discarded"); + expect(called("runCodexAppServerTurn")).toHaveLength(0); + expect(called("finishRoomAgentSupervisorTurn")).toHaveLength(0); + await handle.terminate("sandbox test finished"); + }, 180_000); + + it("records a failed turn with the supervisor failure code", async () => { + handlers.runCodexAppServerTurn = () => { throw new Error("child failed"); }; + const handle = await env.client.workflow.start("roomAgentSupervisorWorkflow", { + taskQueue: DEFAULT_TASK_QUEUE, workflowId: workflowId("supervisor-fail"), + args: [supervisorInput({ pending: [queueItem("mission-fail", "agent-run-fail")] })] + }); + await waitFor(() => called("finishRoomAgentSupervisorTurn").length === 1, "the failed turn to be recorded"); + expect(called("discardRoomAgentSupervisorTurn")).toHaveLength(1); + expect(called("finishRoomAgentSupervisorTurn")[0]!.args[0]).toMatchObject({ + status: "FAILED", errorCode: "ROOM_AGENT_SUPERVISOR_FAILED" + }); + await handle.terminate("sandbox test finished"); + }, 180_000); + + it("continues as new once the processed count reaches the carry bound", async () => { + handlers.checkRoomAgentSupervisorTurn = () => "SKIP"; + const id = workflowId("supervisor-can"); + const handle = await env.client.workflow.start("roomAgentSupervisorWorkflow", { + taskQueue: DEFAULT_TASK_QUEUE, workflowId: id, + args: [supervisorInput({ pending: [queueItem("mission-carry", "agent-run-carry")], processedCount: 49 })] + }); + const firstRunId = handle.firstExecutionRunId; + const latest = env.client.workflow.getHandle(id); + const continued = await waitForContinuation(latest, firstRunId, "the supervisor run to continue as new"); + expect(continued.runId).not.toBe(firstRunId); + // The carried queue and counter are handed to the next run, which idles on its signals. + await handle.terminate("sandbox test finished"); + }, 180_000); + +}); diff --git a/package-lock.json b/package-lock.json index 1e2aa2a88..5fe71e89d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -192,6 +192,7 @@ "undici": "8.10.2" }, "devDependencies": { + "@temporalio/testing": "1.24.0", "typescript": "^5.9.3", "vitest": "^4.1.12" } @@ -3674,6 +3675,25 @@ "node": ">=12.0.0" } }, + "node_modules/@temporalio/testing": { + "version": "1.24.0", + "resolved": "https://registry.npmjs.org/@temporalio/testing/-/testing-1.24.0.tgz", + "integrity": "sha512-XKCuvgb7smKR3oXy5nDgwYSFX/hmg9zTJ2rZQQsVwJzFkga1TbnWDiuT5YMU0INKjoLhnM6c5YA/uLKlp8sdfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@temporalio/activity": "1.24.0", + "@temporalio/client": "1.24.0", + "@temporalio/common": "1.24.0", + "@temporalio/core-bridge": "1.24.0", + "@temporalio/proto": "1.24.0", + "@temporalio/worker": "1.24.0", + "@temporalio/workflow": "1.24.0" + }, + "engines": { + "node": ">= 20.3.0" + } + }, "node_modules/@temporalio/worker": { "version": "1.24.0", "resolved": "https://registry.npmjs.org/@temporalio/worker/-/worker-1.24.0.tgz", @@ -11641,12 +11661,12 @@ } }, "packages/run-spaceapp": { - "version": "1.0.35", + "version": "1.1.7", "cpu": [ "x64", "arm64" ], - "license": "Apache-2.0", + "license": "MIT", "os": [ "darwin", "linux",