6bf8bebf51
CI / Test and Build (push) Failing after 1s
CI / Migrate Dev DB (push) Has been skipped
CI / Migrate DB (push) Has been skipped
CodeQL / Analyze actions (push) Has been cancelled
CodeQL / Analyze javascript-typescript (push) Has been cancelled
CI / Detect Version (push) Has been cancelled
CI / Detect Desktop Changes (push) Has been cancelled
CI / Build AMD64 (blacksmith-2vcpu-ubuntu-2404, ./docker/cron.Dockerfile, ubuntu-latest, ghcr.io/simstudioai/cron) (push) Has been cancelled
CI / Build AMD64 (blacksmith-2vcpu-ubuntu-2404, ./docker/db.Dockerfile, ECR_MIGRATIONS, ubuntu-latest, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (blacksmith-4vcpu-ubuntu-2404, ./docker/pii.Dockerfile, ECR_PII, ubuntu-latest, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (blacksmith-4vcpu-ubuntu-2404, ./docker/realtime.Dockerfile, ECR_REALTIME, ubuntu-latest, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build AMD64 (blacksmith-8vcpu-ubuntu-2404, ./docker/app.Dockerfile, ECR_APP, linux-x64-8-core, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (blacksmith-4vcpu-ubuntu-2404-arm, ./docker/cron.Dockerfile, ubuntu-24.04-arm, ghcr.io/simstudioai/cron) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (blacksmith-4vcpu-ubuntu-2404-arm, ./docker/db.Dockerfile, ubuntu-24.04-arm, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (blacksmith-4vcpu-ubuntu-2404-arm, ./docker/pii.Dockerfile, ubuntu-24.04-arm, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (blacksmith-4vcpu-ubuntu-2404-arm, ./docker/realtime.Dockerfile, ubuntu-24.04-arm, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (blacksmith-8vcpu-ubuntu-2404-arm, ./docker/app.Dockerfile, linux-arm64-8-core, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
Helm Chart / Lint, test, and validate chart (push) Has been cancelled
Helm Chart / Chart version bumped (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
CI / Build Dev ECR (blacksmith-8vcpu-ubuntu-2404, ./docker/app.Dockerfile, ECR_APP, linux-x64-8-core) (push) Has been cancelled
CI / Promote Images (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/cron) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build Dev ECR (blacksmith-2vcpu-ubuntu-2404, ./docker/db.Dockerfile, ECR_MIGRATIONS, ubuntu-latest) (push) Has been cancelled
CI / Build Dev ECR (blacksmith-4vcpu-ubuntu-2404, ./docker/pii.Dockerfile, ECR_PII, ubuntu-latest) (push) Has been cancelled
CI / Build Dev ECR (blacksmith-4vcpu-ubuntu-2404, ./docker/realtime.Dockerfile, ECR_REALTIME, ubuntu-latest) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Check Desktop Signing Secrets (push) Has been cancelled
CI / Desktop Release (push) Has been cancelled
CI / Create Desktop Prerelease (push) Has been cancelled
CI / Desktop Prerelease Build (push) Has been cancelled
CI / Publish Desktop Prerelease (push) Has been cancelled
CI / Prune Desktop Prereleases (push) Has been cancelled
Helm Chart / Install on kind and run helm test (push) Has been cancelled
400 lines
12 KiB
TypeScript
400 lines
12 KiB
TypeScript
/**
|
|
* @vitest-environment node
|
|
*/
|
|
import { dbChainMock, queueTableRows, resetDbChainMock, schemaMock } from '@sim/testing'
|
|
import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
|
|
const { mockMaterializeExecutionData } = vi.hoisted(() => ({
|
|
mockMaterializeExecutionData: vi.fn(),
|
|
}))
|
|
|
|
vi.mock('@sim/db', () => ({ ...dbChainMock, ...schemaMock }))
|
|
|
|
vi.mock('@/lib/logs/execution/trace-store', () => ({
|
|
materializeExecutionData: mockMaterializeExecutionData,
|
|
TRACE_STORE_REF_KEY: 'traceStoreRef',
|
|
}))
|
|
|
|
import {
|
|
getExecutionInputForWorkflow,
|
|
getExecutionStateForWorkflow,
|
|
getLatestExecutionStateWithExecutionId,
|
|
getTrustedWorkflowToolExecution,
|
|
} from '@/lib/workflows/executor/execution-state'
|
|
|
|
const EXECUTION_STATE = {
|
|
blockStates: {},
|
|
executedBlocks: ['block-1'],
|
|
blockLogs: [],
|
|
decisions: {},
|
|
completedLoops: [],
|
|
activeExecutionPath: [],
|
|
}
|
|
|
|
describe('execution state lookup', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks()
|
|
resetDbChainMock()
|
|
mockMaterializeExecutionData.mockReset()
|
|
})
|
|
|
|
afterAll(() => {
|
|
resetDbChainMock()
|
|
})
|
|
|
|
it('materializes externalized execution data for a specific execution', async () => {
|
|
const slimExecutionData = {
|
|
traceStoreRef: {
|
|
__simLargeValueRef: true,
|
|
id: 'value-1',
|
|
key: 'execution/workspace-1/workflow-1/execution-1/value.json',
|
|
kind: 'object',
|
|
size: 100,
|
|
version: 1,
|
|
executionId: 'execution-1',
|
|
},
|
|
}
|
|
queueTableRows(schemaMock.workflowExecutionLogs, [
|
|
{
|
|
executionId: 'execution-1',
|
|
workflowId: 'workflow-1',
|
|
workspaceId: 'workspace-1',
|
|
executionData: slimExecutionData,
|
|
},
|
|
])
|
|
mockMaterializeExecutionData.mockResolvedValueOnce({
|
|
executionState: EXECUTION_STATE,
|
|
})
|
|
|
|
const result = await getExecutionStateForWorkflow('execution-1', 'workflow-1')
|
|
|
|
expect(mockMaterializeExecutionData).toHaveBeenCalledWith(slimExecutionData, {
|
|
workspaceId: 'workspace-1',
|
|
workflowId: 'workflow-1',
|
|
executionId: 'execution-1',
|
|
})
|
|
expect(result).toEqual(EXECUTION_STATE)
|
|
})
|
|
|
|
it('loads a terminal workflow result with an exact persisted Copilot binding', async () => {
|
|
const provenance = {
|
|
version: 1 as const,
|
|
complete: true,
|
|
entries: [{ name: 'API_KEY', encryptedValue: 'encrypted-secret' }],
|
|
scope: { userId: 'user-1', workspaceId: 'workspace-1' },
|
|
}
|
|
queueTableRows(schemaMock.workflowExecutionLogs, [
|
|
{
|
|
executionId: 'execution-1',
|
|
workflowId: 'workflow-1',
|
|
workspaceId: 'workspace-1',
|
|
status: 'completed',
|
|
executionData: {},
|
|
},
|
|
])
|
|
mockMaterializeExecutionData.mockResolvedValueOnce({
|
|
correlation: { copilotToolCallId: 'tool-call-1' },
|
|
finalOutput: { token: 'raw-secret' },
|
|
executionState: { ...EXECUTION_STATE, resolvedSecretTraceProvenance: provenance },
|
|
})
|
|
|
|
await expect(
|
|
getTrustedWorkflowToolExecution('execution-1', 'workflow-1', 'tool-call-1')
|
|
).resolves.toEqual({
|
|
executionId: 'execution-1',
|
|
workflowId: 'workflow-1',
|
|
status: 'completed',
|
|
contentAvailable: true,
|
|
finalOutput: { token: 'raw-secret' },
|
|
blockLogs: [],
|
|
provenance,
|
|
})
|
|
})
|
|
|
|
it('accepts a bound complete execution with no activated secrets', async () => {
|
|
const provenance = { version: 1 as const, complete: true, entries: [] }
|
|
queueTableRows(schemaMock.workflowExecutionLogs, [
|
|
{
|
|
executionId: 'execution-1',
|
|
workflowId: 'workflow-1',
|
|
workspaceId: 'workspace-1',
|
|
status: 'completed',
|
|
executionData: {},
|
|
},
|
|
])
|
|
mockMaterializeExecutionData.mockResolvedValueOnce({
|
|
trigger: { data: { correlation: { copilotToolCallId: 'tool-call-1' } } },
|
|
executionState: { ...EXECUTION_STATE, resolvedSecretTraceProvenance: provenance },
|
|
})
|
|
|
|
await expect(
|
|
getTrustedWorkflowToolExecution('execution-1', 'workflow-1', 'tool-call-1')
|
|
).resolves.toMatchObject({ provenance })
|
|
})
|
|
|
|
it('returns validated incomplete provenance so the terminal projector can fail closed', async () => {
|
|
queueTableRows(schemaMock.workflowExecutionLogs, [
|
|
{
|
|
executionId: 'execution-1',
|
|
workflowId: 'workflow-1',
|
|
workspaceId: 'workspace-1',
|
|
status: 'failed',
|
|
executionData: {},
|
|
},
|
|
])
|
|
mockMaterializeExecutionData.mockResolvedValueOnce({
|
|
correlation: { copilotToolCallId: 'tool-call-1' },
|
|
executionState: {
|
|
...EXECUTION_STATE,
|
|
resolvedSecretTraceProvenance: { version: 1, complete: false, entries: [] },
|
|
},
|
|
})
|
|
|
|
await expect(
|
|
getTrustedWorkflowToolExecution('execution-1', 'workflow-1', 'tool-call-1')
|
|
).resolves.toMatchObject({
|
|
status: 'failed',
|
|
provenance: { version: 1, complete: false, entries: [] },
|
|
})
|
|
})
|
|
|
|
it('trusts compacted terminal status while withholding unavailable execution content', async () => {
|
|
queueTableRows(schemaMock.workflowExecutionLogs, [
|
|
{
|
|
executionId: 'execution-1',
|
|
workflowId: 'workflow-1',
|
|
workspaceId: 'workspace-1',
|
|
status: 'completed',
|
|
executionData: {},
|
|
},
|
|
])
|
|
mockMaterializeExecutionData.mockResolvedValueOnce({
|
|
correlation: { copilotToolCallId: 'tool-call-1' },
|
|
executionStateSummary: {
|
|
executedBlockCount: 1,
|
|
blockLogCount: 1,
|
|
completedLoopCount: 0,
|
|
activeExecutionPathLength: 0,
|
|
pendingQueueLength: 0,
|
|
},
|
|
finalOutput: { token: 'must-not-cross' },
|
|
})
|
|
|
|
await expect(
|
|
getTrustedWorkflowToolExecution('execution-1', 'workflow-1', 'tool-call-1')
|
|
).resolves.toEqual({
|
|
executionId: 'execution-1',
|
|
workflowId: 'workflow-1',
|
|
status: 'completed',
|
|
contentAvailable: false,
|
|
})
|
|
})
|
|
|
|
it('withholds execution content when persisted provenance is malformed', async () => {
|
|
queueTableRows(schemaMock.workflowExecutionLogs, [
|
|
{
|
|
executionId: 'execution-1',
|
|
workflowId: 'workflow-1',
|
|
workspaceId: 'workspace-1',
|
|
status: 'completed',
|
|
executionData: {},
|
|
},
|
|
])
|
|
mockMaterializeExecutionData.mockResolvedValueOnce({
|
|
correlation: { copilotToolCallId: 'tool-call-1' },
|
|
finalOutput: { token: 'must-not-cross' },
|
|
executionState: {
|
|
...EXECUTION_STATE,
|
|
resolvedSecretTraceProvenance: { version: 2, complete: true, entries: [] },
|
|
},
|
|
})
|
|
|
|
await expect(
|
|
getTrustedWorkflowToolExecution('execution-1', 'workflow-1', 'tool-call-1')
|
|
).resolves.toEqual({
|
|
executionId: 'execution-1',
|
|
workflowId: 'workflow-1',
|
|
status: 'completed',
|
|
contentAvailable: false,
|
|
})
|
|
})
|
|
|
|
it('rejects mismatched bindings and nonterminal rows', async () => {
|
|
queueTableRows(schemaMock.workflowExecutionLogs, [
|
|
{
|
|
executionId: 'execution-1',
|
|
workflowId: 'workflow-1',
|
|
workspaceId: 'workspace-1',
|
|
status: 'completed',
|
|
executionData: {},
|
|
},
|
|
])
|
|
mockMaterializeExecutionData.mockResolvedValueOnce({
|
|
correlation: { copilotToolCallId: 'another-tool-call' },
|
|
executionState: {
|
|
...EXECUTION_STATE,
|
|
resolvedSecretTraceProvenance: { version: 1, complete: true, entries: [] },
|
|
},
|
|
})
|
|
|
|
await expect(
|
|
getTrustedWorkflowToolExecution('execution-1', 'workflow-1', 'tool-call-1')
|
|
).resolves.toBeNull()
|
|
|
|
queueTableRows(schemaMock.workflowExecutionLogs, [
|
|
{
|
|
executionId: 'execution-2',
|
|
workflowId: 'workflow-1',
|
|
workspaceId: 'workspace-1',
|
|
status: 'running',
|
|
executionData: {},
|
|
},
|
|
])
|
|
|
|
await expect(
|
|
getTrustedWorkflowToolExecution('execution-2', 'workflow-1', 'tool-call-1')
|
|
).resolves.toBeNull()
|
|
})
|
|
|
|
it('materializes externalized execution data when reusing workflow input', async () => {
|
|
const slimExecutionData = {
|
|
traceStoreRef: {
|
|
__simLargeValueRef: true,
|
|
id: 'value-1',
|
|
key: 'execution/workspace-1/workflow-1/execution-1/value.json',
|
|
kind: 'object',
|
|
size: 100,
|
|
version: 1,
|
|
executionId: 'execution-1',
|
|
},
|
|
}
|
|
queueTableRows(schemaMock.workflowExecutionLogs, [
|
|
{
|
|
executionId: 'execution-1',
|
|
workflowId: 'workflow-1',
|
|
workspaceId: 'workspace-1',
|
|
executionData: slimExecutionData,
|
|
},
|
|
])
|
|
mockMaterializeExecutionData.mockResolvedValueOnce({
|
|
workflowInput: { leadId: 'lead-1' },
|
|
})
|
|
|
|
const result = await getExecutionInputForWorkflow('execution-1', 'workflow-1')
|
|
|
|
expect(result).toEqual({
|
|
found: true,
|
|
input: { leadId: 'lead-1' },
|
|
})
|
|
expect(mockMaterializeExecutionData).toHaveBeenCalledWith(slimExecutionData, {
|
|
workspaceId: 'workspace-1',
|
|
workflowId: 'workflow-1',
|
|
executionId: 'execution-1',
|
|
})
|
|
})
|
|
|
|
it('recovers legacy workflow input from the pre-populated starter block state', async () => {
|
|
const legacyInput = { leadId: 'legacy-lead' }
|
|
queueTableRows(schemaMock.workflowExecutionLogs, [
|
|
{
|
|
executionId: 'execution-1',
|
|
workflowId: 'workflow-1',
|
|
workspaceId: 'workspace-1',
|
|
executionData: {},
|
|
},
|
|
])
|
|
mockMaterializeExecutionData.mockResolvedValueOnce({
|
|
executionState: {
|
|
blockStates: {
|
|
'executed-block': {
|
|
output: { leadId: 'wrong-lead' },
|
|
executed: true,
|
|
executionTime: 10,
|
|
},
|
|
start: {
|
|
output: legacyInput,
|
|
executed: false,
|
|
executionTime: 0,
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
const result = await getExecutionInputForWorkflow('execution-1', 'workflow-1')
|
|
|
|
expect(result).toEqual({ found: true, input: legacyInput })
|
|
})
|
|
|
|
it('prefers persisted workflow input over the legacy starter block state', async () => {
|
|
const workflowInput = { leadId: 'current-lead' }
|
|
queueTableRows(schemaMock.workflowExecutionLogs, [
|
|
{
|
|
executionId: 'execution-1',
|
|
workflowId: 'workflow-1',
|
|
workspaceId: 'workspace-1',
|
|
executionData: {},
|
|
},
|
|
])
|
|
mockMaterializeExecutionData.mockResolvedValueOnce({
|
|
workflowInput,
|
|
executionState: {
|
|
blockStates: {
|
|
start: {
|
|
output: { leadId: 'legacy-lead' },
|
|
executed: false,
|
|
executionTime: 0,
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
const result = await getExecutionInputForWorkflow('execution-1', 'workflow-1')
|
|
|
|
expect(result).toEqual({ found: true, input: workflowInput })
|
|
})
|
|
|
|
it('checks older pointer-backed candidates when the latest has no execution state', async () => {
|
|
queueTableRows(schemaMock.workflowExecutionLogs, [
|
|
{
|
|
executionId: 'execution-2',
|
|
workflowId: 'workflow-1',
|
|
workspaceId: 'workspace-1',
|
|
executionState: null,
|
|
traceStoreRef: { id: 'value-2' },
|
|
},
|
|
{
|
|
executionId: 'execution-1',
|
|
workflowId: 'workflow-1',
|
|
workspaceId: 'workspace-1',
|
|
executionState: null,
|
|
traceStoreRef: { id: 'value-1' },
|
|
},
|
|
])
|
|
mockMaterializeExecutionData
|
|
.mockResolvedValueOnce({})
|
|
.mockImplementationOnce(async (executionData: Record<string, unknown>) => {
|
|
const { traceStoreRef: _traceStoreRef, ...inlineValues } = executionData
|
|
return { executionState: EXECUTION_STATE, ...inlineValues }
|
|
})
|
|
|
|
const result = await getLatestExecutionStateWithExecutionId('workflow-1')
|
|
|
|
expect(result).toEqual({
|
|
executionId: 'execution-1',
|
|
state: EXECUTION_STATE,
|
|
})
|
|
expect(mockMaterializeExecutionData).toHaveBeenCalledTimes(2)
|
|
expect(mockMaterializeExecutionData).toHaveBeenNthCalledWith(
|
|
1,
|
|
{
|
|
traceStoreRef: { id: 'value-2' },
|
|
},
|
|
{
|
|
workspaceId: 'workspace-1',
|
|
workflowId: 'workflow-1',
|
|
executionId: 'execution-2',
|
|
}
|
|
)
|
|
})
|
|
})
|