Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions apps/sim/executor/utils/resolved-secret-trace-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -880,9 +880,10 @@ export class ResolvedSecretTraceRegistry {
private readonly scope?: ResolvedSecretTraceScopeV1
private readonly completeProvenanceEnvelopeBytes: number
/**
* A staged registry filters one value and is then discarded. Its caller re-reports whatever
* fault it hits against the real input path, so its own summary lines would restate that with
* strictly less context. Entry-level detail still logs — the caller cannot reconstruct it.
* A staged registry filters values for one operation and is then discarded. Its caller owns the
* reporting and says it with strictly more context — the real input path for a value filter, the
* execution for a display read — so the registry's own summary lines would only restate it.
* Entry-level detail still logs — the caller cannot reconstruct it.
*/
private readonly staged: boolean

Expand Down
244 changes: 240 additions & 4 deletions apps/sim/lib/logs/execution/trace-store.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,16 @@
*/
import { beforeEach, describe, expect, it, vi } from 'vitest'

const { decryptSecretMock, materializeLargeValueRefMock, storeLargeValueMock } = vi.hoisted(() => ({
decryptSecretMock: vi.fn(),
materializeLargeValueRefMock: vi.fn(),
storeLargeValueMock: vi.fn(),
const { decryptSecretMock, materializeLargeValueRefMock, storeLargeValueMock, mockLogger } =
vi.hoisted(() => ({
decryptSecretMock: vi.fn(),
materializeLargeValueRefMock: vi.fn(),
storeLargeValueMock: vi.fn(),
mockLogger: { info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() },
}))

vi.mock('@sim/logger', () => ({
createLogger: () => mockLogger,
}))

vi.mock('@/lib/core/security/encryption', () => ({
Expand Down Expand Up @@ -536,3 +542,233 @@ describe('projectExecutionDataForDisplay provenance handling', () => {
expect(displayData.traceSpans).toEqual([])
})
})

describe('stored provenance display reporting', () => {
const REGISTRY_SUMMARY_MESSAGES = [
'Resolved secret registry marked incomplete',
'Resolved secret input path marked incomplete',
]

function registrySummaryLines(): unknown[] {
return [...mockLogger.warn.mock.calls, ...mockLogger.error.mock.calls].filter(([message]) =>
REGISTRY_SUMMARY_MESSAGES.includes(message as string)
)
}

/**
* The stored state was recorded when the run wrote it; a view re-deriving it must say which
* execution it served, once — not restate the latch through registry summaries that name none.
*/
it('reports an incomplete stored envelope once, naming the execution and the parts', async () => {
const displayData = await projectExecutionDataForDisplay(
{
finalOutput: { result: 'value' },
executionState: {
resolvedSecretTraceProvenance: { version: 1, complete: false, entries: [] },
finalOutputResolvedSecretTraceProvenance: { version: 1, complete: false, entries: [] },
},
},
CONTEXT
)

expect(displayData).not.toHaveProperty('finalOutput')
expect(registrySummaryLines()).toHaveLength(0)
expect(mockLogger.warn).toHaveBeenCalledWith(
'Stored execution provenance cannot vouch for display content',
expect.objectContaining({
site: 'traceStore.displayProjection',
executionId: 'execution-1',
workflowId: 'workflow-1',
workspaceId: 'workspace-1',
parts: ['traceSpans', 'finalOutput'],
partCount: 2,
})
)
expect(mockLogger.error).not.toHaveBeenCalled()
})

it('reports a malformed stored envelope at error, keeping the value withheld', async () => {
const displayData = await projectExecutionDataForDisplay(
{
finalOutput: { result: 'value' },
executionState: {
resolvedSecretTraceProvenance: {
version: 1,
complete: true,
entries: [],
scope: { userId: 'user-1', workspaceId: 'workspace-1' },
},
finalOutputResolvedSecretTraceProvenance: 'garbage',
},
},
CONTEXT
)

expect(displayData).not.toHaveProperty('finalOutput')
expect(registrySummaryLines()).toHaveLength(0)
expect(mockLogger.error).toHaveBeenCalledWith(
'Stored execution provenance is malformed',
expect.objectContaining({
site: 'traceStore.displayProjection',
executionId: 'execution-1',
parts: ['finalOutput'],
})
)
})

/** A complete envelope whose entries cannot be decrypted withholds content like any fault. */
it('attributes an undecryptable stored envelope to its execution at error', async () => {
decryptSecretMock.mockRejectedValue(new Error('key rotated'))

const displayData = await projectExecutionDataForDisplay(
{
finalOutput: { result: 'value' },
executionState: {
resolvedSecretTraceProvenance: {
version: 1,
complete: true,
entries: [],
scope: { userId: 'user-1', workspaceId: 'workspace-1' },
},
finalOutputResolvedSecretTraceProvenance: {
version: 1,
complete: true,
entries: [{ name: 'SECRET', encryptedValue: 'ciphertext' }],
scope: { userId: 'user-1', workspaceId: 'workspace-1' },
},
},
},
CONTEXT
)

expect(displayData).not.toHaveProperty('finalOutput')
expect(registrySummaryLines()).toHaveLength(0)
expect(mockLogger.error).toHaveBeenCalledWith(
'Stored execution provenance could not be decrypted',
expect.objectContaining({
site: 'traceStore.displayProjection',
executionId: 'execution-1',
parts: ['finalOutput'],
})
)
})

it('reports a malformed block-output envelope at error, withholding the output', async () => {
const result = await materializeExecutionDataForDisplayWithBlockOutputs(
{
executionState: {
resolvedSecretTraceProvenance: {
version: 1,
complete: true,
entries: [],
scope: { userId: 'user-1', workspaceId: 'workspace-1' },
},
blockStates: {
'block-1': { output: { value: 1 }, resolvedSecretTraceProvenance: 'garbage' },
},
},
},
CONTEXT,
['block-1']
)

expect(result.blockOutputs.has('block-1')).toBe(false)
expect(registrySummaryLines()).toHaveLength(0)
expect(mockLogger.error).toHaveBeenCalledWith(
'Stored execution provenance is malformed',
expect.objectContaining({
site: 'traceStore.blockOutputs',
executionId: 'execution-1',
parts: ['blockOutput:block-1'],
})
)
})

it('stays silent when every stored envelope is complete', async () => {
const displayData = await projectExecutionDataForDisplay(
{
finalOutput: { result: 'direct-literal' },
executionState: {
resolvedSecretTraceProvenance: {
version: 1,
complete: true,
entries: [],
scope: { userId: 'user-1', workspaceId: 'workspace-1' },
},
finalOutputResolvedSecretTraceProvenance: {
version: 1,
complete: true,
entries: [],
scope: { userId: 'user-1', workspaceId: 'workspace-1' },
},
},
},
CONTEXT
)

expect(displayData.finalOutput).toEqual({ result: 'direct-literal' })
expect(mockLogger.warn).not.toHaveBeenCalled()
expect(mockLogger.error).not.toHaveBeenCalled()
})

/** The block entry point runs both display functions; each names its own site for the envelope. */
it('attributes an incomplete run envelope under both sites on a block-outputs read', async () => {
await materializeExecutionDataForDisplayWithBlockOutputs(
{
finalOutput: { result: 'value' },
executionState: {
resolvedSecretTraceProvenance: { version: 1, complete: false, entries: [] },
blockStates: {
'block-1': { output: { value: 1 } },
},
},
},
CONTEXT,
['block-1']
)

expect(registrySummaryLines()).toHaveLength(0)
expect(mockLogger.warn).toHaveBeenCalledWith(
'Stored execution provenance cannot vouch for display content',
expect.objectContaining({ site: 'traceStore.displayProjection', parts: ['traceSpans'] })
)
expect(mockLogger.warn).toHaveBeenCalledWith(
'Stored execution provenance cannot vouch for display content',
expect.objectContaining({ site: 'traceStore.blockOutputs', parts: ['run'] })
)
})

it('reports incomplete block-output envelopes once for the whole block read', async () => {
const result = await materializeExecutionDataForDisplayWithBlockOutputs(
{
executionState: {
resolvedSecretTraceProvenance: {
version: 1,
complete: true,
entries: [],
scope: { userId: 'user-1', workspaceId: 'workspace-1' },
},
blockStates: {
'block-1': {
output: { value: 1 },
resolvedSecretTraceProvenance: { version: 1, complete: false, entries: [] },
},
},
},
},
CONTEXT,
['block-1']
)

expect(result.blockOutputs.has('block-1')).toBe(false)
expect(registrySummaryLines()).toHaveLength(0)
expect(mockLogger.warn).toHaveBeenCalledWith(
'Stored execution provenance cannot vouch for display content',
expect.objectContaining({
site: 'traceStore.blockOutputs',
executionId: 'execution-1',
parts: ['blockOutput:block-1'],
})
)
})
})
Loading
Loading