Skip to content

Commit 82e55e9

Browse files
committed
fix(agent): lock external saves and distinguish workflow targets
1 parent 68451c9 commit 82e55e9

4 files changed

Lines changed: 50 additions & 6 deletions

File tree

apps/realtime/src/database/agent-tool-permissions.test.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ const tool = {
88
usageControlExpression: 'force',
99
}
1010
function block(
11-
tools = [tool],
11+
tools: Record<string, unknown>[] = [tool],
1212
modes: Record<string, string> = { '0:agentToolUsageControl': 'advanced' }
1313
) {
1414
return {
@@ -89,4 +89,25 @@ describe('variable permission changes', () => {
8989
)
9090
).toBe(true)
9191
})
92+
93+
it('rejects transferring a variable permission to a different workflow target', () => {
94+
const workflowTool = {
95+
...tool,
96+
type: 'workflow_input',
97+
toolId: 'workflow_executor',
98+
params: { ...tool.params, workflowId: 'workflow-a' },
99+
}
100+
const before = block([workflowTool])
101+
expect(hasAgentToolPermissionChanges([structuredClone(before)], [before])).toBe(false)
102+
expect(
103+
hasAgentToolPermissionChanges(
104+
[
105+
block([
106+
{ ...workflowTool, params: { ...workflowTool.params, workflowId: 'workflow-b' } },
107+
]),
108+
],
109+
[before]
110+
)
111+
).toBe(true)
112+
})
92113
})

apps/sim/lib/workflows/persistence/persist-agent-tool-permission-mode.test.ts

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
*/
44
import {
55
createAgentBlock,
6-
databaseMock,
76
dbChainMock,
7+
dbChainMockFns,
88
queueTableRows,
99
resetDbChainMock,
1010
schemaMock,
@@ -17,8 +17,6 @@ const mocks = vi.hoisted(() => ({
1717
saveRaw: vi.fn(),
1818
}))
1919

20-
vi.mock('@sim/db', () => ({ ...databaseMock, workflow: schemaMock.workflow }))
21-
2220
vi.mock('@/lib/core/config/feature-flags', () => ({
2321
isFeatureEnabled: mocks.isFeatureEnabled,
2422
}))
@@ -181,4 +179,28 @@ describe('agent tool Permission Mode write gate', () => {
181179
).rejects.toMatchObject({ code: 'validation' })
182180
expect(mocks.saveRaw).not.toHaveBeenCalled()
183181
})
182+
183+
it('waits for the caller transaction lock before reading retained permissions', async () => {
184+
const state = stateWithTool(
185+
{ type: 'function', usageControlExpression: 'force' },
186+
{ '0:agentToolUsageControl': 'advanced' }
187+
)
188+
let releaseLock!: (rows: { id: string }[]) => void
189+
const lock = new Promise<{ id: string }[]>((resolve) => {
190+
releaseLock = resolve
191+
})
192+
dbChainMockFns.for.mockReturnValueOnce(lock)
193+
194+
const save = saveWorkflowToNormalizedTables('workflow-1', state, GOVERNANCE, dbChainMock.db)
195+
await vi.waitFor(() => expect(dbChainMockFns.for).toHaveBeenCalledWith('update'), {
196+
interval: 1,
197+
})
198+
expect(mocks.isFeatureEnabled).not.toHaveBeenCalled()
199+
expect(mocks.saveRaw).not.toHaveBeenCalled()
200+
201+
queueTableRows(schemaMock.workflowBlocks, [])
202+
releaseLock([{ id: 'workflow-1' }])
203+
await expect(save).rejects.toMatchObject({ code: 'validation' })
204+
expect(mocks.saveRaw).not.toHaveBeenCalled()
205+
})
184206
})

apps/sim/lib/workflows/persistence/utils.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
import {
22
db,
33
runOutsideTransactionContext,
4-
workflow,
54
workflowDeploymentOperation,
65
workflowDeploymentVersion,
76
} from '@sim/db'
8-
import { credential } from '@sim/db/schema'
7+
import { credential, workflow } from '@sim/db/schema'
98
import { createLogger } from '@sim/logger'
109
import { getActiveWorkflowContext } from '@sim/platform-authz/workflow'
1110
import { getErrorMessage } from '@sim/utils/errors'
@@ -673,6 +672,7 @@ export async function saveWorkflowToNormalizedTables(
673672
externalTx?: DbOrTx
674673
): Promise<{ success: boolean; error?: string }> {
675674
await assertNoWithheldBlockType(governance, Object.values(state.blocks))
675+
if (externalTx) await lockWorkflowForUpdate(externalTx, workflowId)
676676
await assertAgentToolPermissionModeEnabled(Object.values(state.blocks), {
677677
workflowId,
678678
tx: externalTx,

packages/workflow-types/src/agent-tool-permissions.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ function toolPermissions(block: AgentToolPermissionBlock): ToolPermission[] {
6262
fn.name,
6363
params.serverId,
6464
params.toolName,
65+
params.workflowId,
6566
]),
6667
expression: tool.usageControlExpression,
6768
advanced,

0 commit comments

Comments
 (0)