Skip to content

Commit cbf9e99

Browse files
fix(provenance): bind durable reads to their source snapshots (#7814)
* fix(provenance): bind durable reads to their source snapshots * test(provenance): align Copilot query mock with snapshot reader
1 parent 0def46d commit cbf9e99

32 files changed

Lines changed: 1827 additions & 781 deletions

‎apps/sim/app/api/table/row-secret-provenance.test.ts‎

Lines changed: 0 additions & 229 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
*/
44
import { createMockRequest } from '@sim/testing'
55
import { describe, expect, it } from 'vitest'
6-
import { AuthType } from '@/lib/auth/hybrid'
76
import {
87
PRIVATE_SECRET_PROVENANCE_BUNDLE_V1,
98
PRIVATE_SECRET_PROVENANCE_FIELD,
@@ -13,29 +12,16 @@ import {
1312
RESOLVED_SECRET_PROVENANCE_METADATA_V1,
1413
} from '@/lib/execution/private-tool-metadata'
1514
import { TableRowProvenanceError } from '@/lib/table/application/row-secret-provenance'
16-
import { rowDataNameToId } from '@/lib/table/column-keys'
1715
import { tableRowSecretProvenanceSelectionKey } from '@/lib/table/secret-provenance-selection'
18-
import type { RowData } from '@/lib/table/types'
1916
import {
20-
createTableWriteProvenanceTargets,
2117
finalizeTableRowsProvenance,
2218
negotiateTableRowsProvenance,
2319
readTableRowProvenanceEnvelope,
24-
resolveTableWriteSecretProvenance,
2520
} from '@/app/api/table/row-secret-provenance'
2621

2722
const USER_ID = 'user-1'
2823
const WORKSPACE_ID = 'ws-1'
2924

30-
/** Mirrors the internal-JWT wire translator: names → ids, unknown names dropped. */
31-
const ID_BY_NAME = new Map([
32-
['email', 'col_email'],
33-
['company', 'col_company'],
34-
])
35-
36-
const translateNames = (data: RowData): RowData => rowDataNameToId(data, ID_BY_NAME)
37-
const translateIdentity = (data: RowData): RowData => data
38-
3925
function traceProvenance() {
4026
return {
4127
version: 1,
@@ -59,221 +45,6 @@ function bundleRequest(selectionKeys: string[]) {
5945
return { request, payload }
6046
}
6147

62-
describe('createTableWriteProvenanceTargets', () => {
63-
it('maps column names to their storage ids', () => {
64-
const targets = createTableWriteProvenanceTargets([{ email: 'a@b.c' }], translateNames)
65-
66-
expect(targets).toEqual([
67-
{
68-
selectionKey: tableRowSecretProvenanceSelectionKey(0, 'email'),
69-
rowKey: '0',
70-
columnId: 'col_email',
71-
},
72-
])
73-
})
74-
75-
it('returns a null column id for a column the wire translator drops', () => {
76-
const targets = createTableWriteProvenanceTargets(
77-
[{ email: 'a@b.c', notAColumn: 'x' }],
78-
translateNames
79-
)
80-
81-
expect(targets).toHaveLength(2)
82-
expect(targets[0].columnId).toBe('col_email')
83-
expect(targets[1]).toEqual({
84-
selectionKey: tableRowSecretProvenanceSelectionKey(0, 'notAColumn'),
85-
rowKey: '0',
86-
columnId: null,
87-
})
88-
})
89-
90-
it('keeps one target per submitted column so bundle selections stay paired', () => {
91-
const targets = createTableWriteProvenanceTargets(
92-
[{ notAColumn: 'x', alsoNotAColumn: 'y' }],
93-
translateNames
94-
)
95-
96-
expect(targets.map((target) => target.columnId)).toEqual([null, null])
97-
})
98-
99-
it('passes column ids through for identity (session) translation', () => {
100-
const targets = createTableWriteProvenanceTargets([{ col_email: 'a@b.c' }], translateIdentity)
101-
102-
expect(targets[0].columnId).toBe('col_email')
103-
})
104-
105-
it('keys targets by row index across multiple rows', () => {
106-
const targets = createTableWriteProvenanceTargets(
107-
[{ email: 'a@b.c' }, { company: 'Acme' }],
108-
translateNames
109-
)
110-
111-
expect(targets.map((target) => target.rowKey)).toEqual(['0', '1'])
112-
expect(targets[1].selectionKey).toBe(tableRowSecretProvenanceSelectionKey(1, 'company'))
113-
})
114-
})
115-
116-
describe('resolveTableWriteSecretProvenance', () => {
117-
it('records no provenance for a dropped column on an unsupported session write', () => {
118-
const rows = [{ email: 'a@b.c', notAColumn: 'x' }]
119-
const result = resolveTableWriteSecretProvenance({
120-
request: createMockRequest('POST', { rows }),
121-
payload: { rows },
122-
authType: AuthType.SESSION,
123-
userId: USER_ID,
124-
workspaceId: WORKSPACE_ID,
125-
targets: createTableWriteProvenanceTargets(rows, translateNames),
126-
rowKeys: ['0'],
127-
})
128-
129-
expect(result.success).toBe(true)
130-
if (!result.success) return
131-
expect(Object.keys(result.provenanceByRowKey?.['0'].columns ?? {})).toEqual(['col_email'])
132-
})
133-
134-
it('accepts a complete bundle that covers a dropped column', () => {
135-
const rows = [{ email: 'a@b.c', notAColumn: 'x' }]
136-
const { request, payload } = bundleRequest([
137-
tableRowSecretProvenanceSelectionKey(0, 'email'),
138-
tableRowSecretProvenanceSelectionKey(0, 'notAColumn'),
139-
])
140-
141-
const result = resolveTableWriteSecretProvenance({
142-
request,
143-
payload,
144-
authType: AuthType.INTERNAL_JWT,
145-
userId: USER_ID,
146-
workspaceId: WORKSPACE_ID,
147-
targets: createTableWriteProvenanceTargets(rows, translateNames),
148-
rowKeys: ['0'],
149-
})
150-
151-
expect(result.success).toBe(true)
152-
if (!result.success) return
153-
expect(Object.keys(result.provenanceByRowKey?.['0'].columns ?? {})).toEqual(['col_email'])
154-
})
155-
156-
it('stores provenance for a fully translatable bundle', () => {
157-
const rows = [{ email: 'a@b.c', company: 'Acme' }]
158-
const { request, payload } = bundleRequest([
159-
tableRowSecretProvenanceSelectionKey(0, 'email'),
160-
tableRowSecretProvenanceSelectionKey(0, 'company'),
161-
])
162-
163-
const result = resolveTableWriteSecretProvenance({
164-
request,
165-
payload,
166-
authType: AuthType.INTERNAL_JWT,
167-
userId: USER_ID,
168-
workspaceId: WORKSPACE_ID,
169-
targets: createTableWriteProvenanceTargets(rows, translateNames),
170-
rowKeys: ['0'],
171-
})
172-
173-
expect(result.success).toBe(true)
174-
if (!result.success) return
175-
expect(Object.keys(result.provenanceByRowKey?.['0'].columns ?? {}).sort()).toEqual([
176-
'col_company',
177-
'col_email',
178-
])
179-
})
180-
181-
it('rejects a bundle whose selection matches no submitted column', () => {
182-
const rows = [{ email: 'a@b.c' }]
183-
const { request, payload } = bundleRequest([tableRowSecretProvenanceSelectionKey(0, 'company')])
184-
185-
const result = resolveTableWriteSecretProvenance({
186-
request,
187-
payload,
188-
authType: AuthType.INTERNAL_JWT,
189-
userId: USER_ID,
190-
workspaceId: WORKSPACE_ID,
191-
targets: createTableWriteProvenanceTargets(rows, translateNames),
192-
rowKeys: ['0'],
193-
})
194-
195-
expect(result.success).toBe(false)
196-
})
197-
198-
it('accepts a different source user in the authorized destination workspace', () => {
199-
const rows = [{ email: 'a@b.c' }]
200-
const payload = {
201-
[PRIVATE_SECRET_PROVENANCE_FIELD]: {
202-
version: 1,
203-
complete: true,
204-
selections: [
205-
{
206-
key: tableRowSecretProvenanceSelectionKey(0, 'email'),
207-
provenance: {
208-
...traceProvenance(),
209-
scope: { userId: 'someone-else', workspaceId: WORKSPACE_ID },
210-
},
211-
},
212-
],
213-
},
214-
}
215-
216-
const result = resolveTableWriteSecretProvenance({
217-
request: createMockRequest('POST', payload, {
218-
[PRIVATE_SECRET_PROVENANCE_HEADER]: PRIVATE_SECRET_PROVENANCE_BUNDLE_V1,
219-
}),
220-
payload,
221-
authType: AuthType.INTERNAL_JWT,
222-
userId: USER_ID,
223-
workspaceId: WORKSPACE_ID,
224-
targets: createTableWriteProvenanceTargets(rows, translateNames),
225-
rowKeys: ['0'],
226-
})
227-
228-
expect(result.success).toBe(true)
229-
if (!result.success) return
230-
expect(result.provenanceByRowKey?.['0'].columns.col_email).toMatchObject({
231-
scope: { userId: 'someone-else', workspaceId: WORKSPACE_ID },
232-
})
233-
})
234-
235-
it('rejects a bundle whose selection comes from another workspace', () => {
236-
const rows = [{ email: 'a@b.c' }]
237-
const payload = {
238-
[PRIVATE_SECRET_PROVENANCE_FIELD]: {
239-
version: 1,
240-
complete: true,
241-
selections: [
242-
{
243-
key: tableRowSecretProvenanceSelectionKey(0, 'email'),
244-
provenance: {
245-
...traceProvenance(),
246-
scope: { userId: USER_ID, workspaceId: 'another-workspace' },
247-
},
248-
},
249-
],
250-
},
251-
}
252-
253-
const result = resolveTableWriteSecretProvenance({
254-
request: createMockRequest('POST', payload, {
255-
[PRIVATE_SECRET_PROVENANCE_HEADER]: PRIVATE_SECRET_PROVENANCE_BUNDLE_V1,
256-
}),
257-
payload,
258-
authType: AuthType.INTERNAL_JWT,
259-
userId: USER_ID,
260-
workspaceId: WORKSPACE_ID,
261-
targets: createTableWriteProvenanceTargets(rows, translateNames),
262-
rowKeys: ['0'],
263-
})
264-
265-
expect(result.success).toBe(false)
266-
})
267-
})
268-
269-
/**
270-
* The transport half of the envelope, used by the migrated single-row routes.
271-
*
272-
* These are the only cover these helpers have: the route tests assert `mapInput`
273-
* and `present`, so each helper could be replaced by a constant without a route
274-
* test noticing — and a constant `readTableRowProvenanceEnvelope` would silently
275-
* downgrade every executor write from a stamped bundle to untracked.
276-
*/
27748
describe('readTableRowProvenanceEnvelope', () => {
27849
it('reports no envelope when the caller sent none', () => {
27950
const request = createMockRequest('PATCH', { data: {} })

0 commit comments

Comments
 (0)