Skip to content

Commit 40f8761

Browse files
committed
fix(provenance): report where the guard tripped on a refusal too
The registry retained the first guard's location, but the refusal reporter copies named fields out of the diagnostics and never picked it up — so the line an operator actually reads when a projection is refused still named only the reason. Nested rather than spread flat: the guard's `inputPath` names where it tripped and the refusal's names where the refusal happened, which differ whenever a latch travels, so flattening would overwrite one with the other.
1 parent f83c491 commit 40f8761

2 files changed

Lines changed: 38 additions & 0 deletions

File tree

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

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,34 @@ describe('refuseResolvedSecretProjection', () => {
7979
)
8080
})
8181

82+
/**
83+
* The reason says what tripped; without the location a reader still has to go hunting for which
84+
* block. Nested rather than flattened because the guard's own `inputPath` and the refusal's name
85+
* different places once a latch has travelled.
86+
*/
87+
it('reports where the first guard tripped, not only what it was', () => {
88+
const registry = new ResolvedSecretTraceRegistry([], scope)
89+
registry.markIncomplete('structural-input-root-unprojected', {
90+
detail: { blockType: 'table', tool: 'table_insert_row', inputPath: 'data' },
91+
})
92+
93+
expect(() =>
94+
refuseResolvedSecretProjection({
95+
site: 'agent.toolCallCrossing',
96+
message: 'Tool call could not be safely projected',
97+
registry,
98+
inputPath: 'messages',
99+
})
100+
).toThrow()
101+
102+
expect(refusalRecords()[0][1]).toEqual(
103+
expect.objectContaining({
104+
inputPath: 'messages',
105+
detail: { blockType: 'table', tool: 'table_insert_row', inputPath: 'data' },
106+
})
107+
)
108+
})
109+
82110
it('names a by-design origin that was silenced when it was marked', () => {
83111
const registry = createIncompleteResolvedSecretTraceRegistry(scope)
84112
expect(mockLogger.error).not.toHaveBeenCalled()

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,16 @@ function reportRefusal({ site, registry, inputPath }: ResolvedSecretProjectionRe
7171
...(diagnostics.scopeWorkspaceId
7272
? { scopeWorkspaceId: diagnostics.scopeWorkspaceId }
7373
: {}),
74+
/**
75+
* Where the first guard tripped — the block, tool and input path that cost the run its
76+
* completeness, which is what a reader needs to go and fix.
77+
*
78+
* Nested rather than spread flat: its `inputPath` names where the guard tripped, while
79+
* this line's own `inputPath` names where the refusal happened. Those are different
80+
* places whenever a latch travels, and flattening would silently overwrite one with the
81+
* other.
82+
*/
83+
...(diagnostics.detail ? { detail: diagnostics.detail } : {}),
7484
}
7585
: {}),
7686
})

0 commit comments

Comments
 (0)