Skip to content

Commit 11f734c

Browse files
committed
fix(provenance): name the importer that condemned a run
A bundle that arrives already incomplete latches the whole registry one-way, so every later model projection in the run refuses. #6483 made the refusal say what went wrong; it could not say who. In production the answer was reason=source-provenance-incomplete with 23 candidate importers and no way to tell them apart, which is where the last investigation stopped. Carry a stable origin across the import boundary and retain it beside the reason. It inherits through forks and merges — the step that erased attribution before, since a tool crossing forks, imports, then merges back — so the refusal names the importer even though the latch happened frames earlier. Tool crossings take the tool id, so a tool-sourced bundle identifies itself rather than being inferred from timestamps. Origins are caller-supplied strings rather than a closed union, so unlike reasons they carry an explicit bound. No behaviour change: the field is optional, additive, and read only when building a log record.
1 parent 156ee3e commit 11f734c

25 files changed

Lines changed: 219 additions & 52 deletions

apps/sim/app/api/mcp/serve/[serverId]/route.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,7 @@ async function projectWorkflowMcpModelContent(
288288
): Promise<unknown> {
289289
const registry = new ResolvedSecretTraceRegistry([], scope)
290290
const imported = await registry.importCrossingProvenance(privateProvenance, value, {
291+
origin: 'mcpServe.workflowCrossing',
291292
trusted: true,
292293
})
293294
if (!imported || !registry.isComplete()) {

apps/sim/app/api/providers/route.test.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,10 @@ describe('POST /api/providers', () => {
208208
)
209209

210210
expect(res.status).toBe(200)
211-
expect(mockImportProvenance).toHaveBeenCalledWith(provenance, { trusted: true })
211+
expect(mockImportProvenance).toHaveBeenCalledWith(provenance, {
212+
trusted: true,
213+
origin: 'providersRoute.requestProvenance',
214+
})
212215
})
213216

214217
it('projects legacy private prompt provenance on the provider-facing copy', async () => {

apps/sim/app/api/providers/route.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
272272
const provenanceReady =
273273
await providerRuntimeContext.resolvedSecretTraceRegistry.importProvenance(
274274
provenanceInspection.value,
275-
{ trusted: true }
275+
{ trusted: true, origin: 'providersRoute.requestProvenance' }
276276
)
277277
if (!provenanceReady || !providerRuntimeContext.resolvedSecretTraceRegistry.isComplete()) {
278278
return NextResponse.json(

apps/sim/app/api/workflows/[id]/log/route.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,10 @@ export const POST = withRouteHandler(
144144
if (trustedProvenance === undefined) {
145145
resolvedSecretTraceRegistry.markIncomplete()
146146
} else {
147-
await resolvedSecretTraceRegistry.importProvenance(trustedProvenance, { trusted: true })
147+
await resolvedSecretTraceRegistry.importProvenance(trustedProvenance, {
148+
trusted: true,
149+
origin: 'workflowLogRoute.trustedProvenance',
150+
})
148151
}
149152
loggingSession.setResolvedSecretTraceRegistry(resolvedSecretTraceRegistry)
150153

apps/sim/executor/handlers/mothership/mothership-handler.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,7 @@ describe('MothershipBlockHandler', () => {
293293
content: 'raw secret remains functional',
294294
__resolvedSecretTraceProvenance: undefined,
295295
}),
296-
{ trusted: true }
296+
{ trusted: true, origin: 'mothership.payloadCrossing' }
297297
)
298298
expect(registry.markIncomplete).not.toHaveBeenCalled()
299299
expect(result).toMatchObject({ content: 'raw secret remains functional' })
@@ -519,7 +519,7 @@ describe('MothershipBlockHandler', () => {
519519
error: 'secret-backed failure',
520520
__resolvedSecretTraceProvenance: undefined,
521521
}),
522-
{ trusted: true }
522+
{ trusted: true, origin: 'mothership.payloadCrossing' }
523523
)
524524
expect(context.errorResolvedSecretTraceRegistry).toBeDefined()
525525
expect(context.errorResolvedSecretTraceRegistry).not.toBe(context.resolvedSecretTraceRegistry)
@@ -557,7 +557,7 @@ describe('MothershipBlockHandler', () => {
557557
error: 'secret-backed failure',
558558
__resolvedSecretTraceProvenance: undefined,
559559
}),
560-
{ trusted: true }
560+
{ trusted: true, origin: 'mothership.payloadCrossing' }
561561
)
562562
expect(registry.markIncomplete).not.toHaveBeenCalled()
563563
expect(context.errorResolvedSecretTraceRegistry).toBeDefined()
@@ -601,7 +601,7 @@ describe('MothershipBlockHandler', () => {
601601
content: 'unchanged',
602602
__resolvedSecretTraceProvenance: undefined,
603603
}),
604-
{ trusted: true }
604+
{ trusted: true, origin: 'mothership.payloadCrossing' }
605605
)
606606
expect(registry.markIncomplete).not.toHaveBeenCalled()
607607
expect(JSON.stringify(result.execution.output)).not.toContain('__resolvedSecretTraceProvenance')

apps/sim/executor/handlers/mothership/mothership-handler.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,10 @@ async function consumeMothershipProvenance(
378378

379379
if (!registry) return false
380380

381-
const imported = await registry.importProvenanceForValue(provenance, payload, { trusted: true })
381+
const imported = await registry.importProvenanceForValue(provenance, payload, {
382+
trusted: true,
383+
origin: 'mothership.payloadCrossing',
384+
})
382385
if (!imported) throw new Error('Mothership response provenance metadata is invalid')
383386
return true
384387
}

apps/sim/executor/handlers/workflow/workflow-handler.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,7 @@ export class WorkflowBlockHandler implements BlockHandler {
450450
await childResolvedSecretTraceRegistry.importProvenance(crossingProvenance, {
451451
trusted: true,
452452
anonymous: true,
453+
origin: 'workflowHandler.childCrossing',
453454
})
454455
}
455456
// Custom-block children authenticate internal tool calls as the source
@@ -711,6 +712,7 @@ export class WorkflowBlockHandler implements BlockHandler {
711712
await ctx.resolvedSecretTraceRegistry.importProvenance(crossingProvenance, {
712713
trusted: true,
713714
anonymous: true,
715+
origin: 'workflowHandler.parentCrossing',
714716
})
715717
}
716718
return exposedOutput

apps/sim/executor/utils/resolved-secret-projection-refusal.test.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,35 @@ describe('refuseResolvedSecretProjection', () => {
195195
expect(refusalRecords()).toHaveLength(3)
196196
})
197197

198+
it('names the importer that condemned the run, through the fork and merge that hid it', async () => {
199+
const parent = new ResolvedSecretTraceRegistry([], scope)
200+
const fork = parent.forkForToolCall()
201+
await fork.importCrossingProvenance(
202+
{ version: 1, complete: false, entries: [], scope },
203+
{ rows: [] },
204+
{ trusted: true, origin: 'tool.table_query_rows' }
205+
)
206+
parent.mergeToolCallRegistry(fork)
207+
mockLogger.error.mockClear()
208+
mockLogger.warn.mockClear()
209+
210+
expect(() =>
211+
refuseResolvedSecretProjection({
212+
site: 'router.contextModelInput',
213+
message: 'Router model input could not be safely projected',
214+
registry: parent,
215+
inputPath: 'context,routes',
216+
})
217+
).toThrow()
218+
219+
expect(refusalRecords()[0][1]).toEqual(
220+
expect.objectContaining({
221+
reason: 'source-provenance-incomplete',
222+
origins: ['tool.table_query_rows'],
223+
})
224+
)
225+
})
226+
198227
it('records no secret material', () => {
199228
const registry = new ResolvedSecretTraceRegistry(
200229
[{ name: 'API_KEY', plaintext: 'super-secret-value', encryptedValue: 'encrypted' }],

apps/sim/executor/utils/resolved-secret-projection-refusal.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ function reportRefusal({ site, registry, inputPath }: ResolvedSecretProjectionRe
6464
? {
6565
reason: diagnostics.reasons[0],
6666
reasons: diagnostics.reasons,
67+
/** The importers that cost the run its completeness — who to go and look at. */
68+
...(diagnostics.origins.length > 0 ? { origins: diagnostics.origins } : {}),
6769
incompleteInputPathCount: diagnostics.incompleteInputPathCount,
6870
activeEntryCount: diagnostics.activeEntryCount,
6971
...(diagnostics.scopeWorkspaceId

apps/sim/executor/utils/resolved-secret-trace-registry.test.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1562,6 +1562,40 @@ describe('incompleteness diagnostics', () => {
15621562
expect(registry.getIncompletenessDiagnostics()?.reasons[0]).toBe('constructed-incomplete')
15631563
})
15641564

1565+
it('attributes an untrustworthy bundle to the caller that imported it', async () => {
1566+
const registry = new ResolvedSecretTraceRegistry([], scope)
1567+
1568+
await registry.importProvenance(
1569+
{ version: 1, complete: false, entries: [], scope },
1570+
{ trusted: true, origin: 'workflowHandler.childCrossing' }
1571+
)
1572+
1573+
expect(registry.getIncompletenessDiagnostics()?.origins).toEqual([
1574+
'workflowHandler.childCrossing',
1575+
])
1576+
expect(mockLogger.warn).toHaveBeenCalledWith(
1577+
'Resolved secret registry marked incomplete',
1578+
expect.objectContaining({
1579+
reason: 'source-provenance-incomplete',
1580+
origin: 'workflowHandler.childCrossing',
1581+
})
1582+
)
1583+
})
1584+
1585+
it('bounds retained origins, which are caller-supplied rather than a closed union', async () => {
1586+
const registry = new ResolvedSecretTraceRegistry([], scope)
1587+
1588+
for (let index = 0; index < 20; index++) {
1589+
await registry.importProvenance(
1590+
{ version: 1, complete: false, entries: [], scope },
1591+
{ trusted: true, origin: `caller.${index}` }
1592+
)
1593+
}
1594+
1595+
expect(registry.getIncompletenessDiagnostics()?.origins).toHaveLength(8)
1596+
expect(registry.getIncompletenessDiagnostics()?.origins[0]).toBe('caller.0')
1597+
})
1598+
15651599
it('records no secret material alongside the reason', () => {
15661600
const registry = new ResolvedSecretTraceRegistry([], scope)
15671601

0 commit comments

Comments
 (0)