Skip to content

Commit 8d9c2ba

Browse files
committed
fix(files): narrow an initialization to a status the column accepts
The union carries three states; the sidecar's CHECK constraint accepts two. Only the replace path discriminated — initialize forwarded the status verbatim, so an 'unrecorded' would have reached the database as a value it rejects, aborting the enclosing transaction rather than writing a bad row. Nothing passes one today, which is why it belongs here and not in a caller. Also names the storage mismatch on the type itself: a stored 'unknown' reads back as 'unrecorded', not as the union's 'unknown'. Same word, two layers, different meanings.
1 parent dff6bc7 commit 8d9c2ba

2 files changed

Lines changed: 41 additions & 4 deletions

File tree

apps/sim/lib/uploads/contexts/workspace/workspace-file-secret-provenance.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,24 @@ describe('workspace file secret provenance', () => {
152152
expect(dbChainMockFns.set).toHaveBeenCalledWith({ secretProvenanceVersion: 1 })
153153
})
154154

155+
/**
156+
* The union carries three states; the column's CHECK constraint accepts two. Forwarding the
157+
* status verbatim would send `'unrecorded'` to the database as a value it rejects, aborting the
158+
* enclosing transaction rather than writing a bad row.
159+
*/
160+
it('narrows an unrecorded initialization to a status the column accepts', async () => {
161+
await initializeWorkspaceFileSecretProvenanceInTx(
162+
dbChainMock.db as unknown as DbTransaction,
163+
'file-1',
164+
CONTENT_UPDATED_AT,
165+
{ status: 'unrecorded' }
166+
)
167+
168+
expect(dbChainMockFns.values).toHaveBeenCalledWith(
169+
expect.objectContaining({ fileId: 'file-1', status: 'unknown', entries: [] })
170+
)
171+
})
172+
155173
it('rejects a marker write that cannot bind the exact tracked content version', async () => {
156174
dbChainMockFns.returning.mockResolvedValueOnce([])
157175

apps/sim/lib/uploads/contexts/workspace/workspace-file-secret-provenance.ts

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,17 @@ const LEGACY_ANONYMOUS_WORKSPACE_FILE_SECRET_STORAGE_NAME =
3333
export const MODEL_UNSAFE_WORKSPACE_FILE_ERROR_MESSAGE =
3434
'File cannot be sent to a model because its secret provenance is unavailable'
3535

36+
/**
37+
* What can be said about the secrets a file's bytes carry.
38+
*
39+
* Note the deliberate mismatch with storage: the sidecar's `status` column holds only
40+
* `'exact' | 'unknown'`, and a stored `'unknown'` maps to `'unrecorded'` here, not to the
41+
* `'unknown'` below. Storage is recording what the writer could vouch for; this union is recording
42+
* what a reader can conclude, and "the writer said it could not vouch" and "there is nothing usable
43+
* to read" are different conclusions that must not share a branch — only the first is an absence a
44+
* policy may relax. `'unrecorded'` is the name the shared vocabulary already uses for it
45+
* (`reportUnrecordedDurableProvenance`, the `secret_provenance.unrecorded` audit action).
46+
*/
3647
export type WorkspaceFileSecretProvenance =
3748
| { status: 'exact'; entries: readonly WorkspaceFileSecretProvenanceEntry[] }
3849
/** Nothing can be said: the row is gone, the version moved, or the sidecar is stale or malformed. */
@@ -421,21 +432,29 @@ export async function replaceWorkspaceFileSecretProvenanceInTx(
421432
await markWorkspaceFileSecretProvenanceTrackedInTx(tx, fileId, contentUpdatedAt)
422433
}
423434

424-
/** Initializes provenance for an exact file version without replacing an existing classification. */
435+
/**
436+
* Initializes provenance for an exact file version without replacing an existing classification.
437+
*
438+
* Narrows to the two states the column accepts, as {@link replaceWorkspaceFileSecretProvenanceInTx}
439+
* does. The union has three; the CHECK constraint permits `('exact', 'unknown')`, so forwarding the
440+
* status verbatim would let an `'unrecorded'` reach the database as a value it rejects — a
441+
* constraint violation aborting the enclosing transaction, not a bad row. Nothing passes one today,
442+
* which is exactly why it needs saying here rather than in a caller.
443+
*/
425444
export async function initializeWorkspaceFileSecretProvenanceInTx(
426445
tx: DbTransaction,
427446
fileId: string,
428447
contentUpdatedAt: Date,
429448
provenance: WorkspaceFileSecretProvenance
430449
): Promise<void> {
431-
const entries =
432-
provenance.status === 'exact' ? serializeExactEntriesForStorage(provenance.entries) : []
450+
const isExact = provenance.status === 'exact'
451+
const entries = isExact ? serializeExactEntriesForStorage(provenance.entries) : []
433452
await tx
434453
.insert(workspaceFileSecretProvenance)
435454
.values({
436455
fileId,
437456
contentUpdatedAt,
438-
status: provenance.status,
457+
status: isExact ? 'exact' : 'unknown',
439458
entries,
440459
updatedAt: new Date(),
441460
})

0 commit comments

Comments
 (0)