Skip to content

Commit 304d093

Browse files
committed
fix(logs): bound the withheld-span fallback by the trace's structure budget and make envelope span ids unguessable
1 parent 128c6aa commit 304d093

4 files changed

Lines changed: 56 additions & 6 deletions

File tree

‎apps/sim/lib/logs/execution/trace-secret-projection.test.ts‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1127,6 +1127,28 @@ describe('projectTraceSpansForSecrets whole-trace content budget', () => {
11271127
)
11281128
})
11291129

1130+
it('keeps the retained spans and the withheld fallback within one structure budget', async () => {
1131+
const emptyChildren = (prefix: string) =>
1132+
Array.from({ length: 60_000 }, (_, index) => createSpan({ id: `${prefix}-${index}` }))
1133+
const projected = await projectTraceSpansForSecrets(
1134+
[
1135+
heavySpan('span-a', 'first', { children: emptyChildren('a') }),
1136+
createSpan({
1137+
id: 'span-b',
1138+
output: { items: Array.from({ length: 70_000 }, (_, index) => `row-${index}`) },
1139+
children: emptyChildren('b'),
1140+
}),
1141+
],
1142+
{ registry: SECRET_REGISTRY, store: STORE, allowLargeValueWrites: false }
1143+
)
1144+
const countSpans = (spans: TraceSpan[]): number =>
1145+
spans.reduce((total, span) => total + 1 + countSpans(span.children ?? []), 0)
1146+
1147+
expect(projected[0].output?.marker).toBe('first')
1148+
expect(projected[1]).not.toHaveProperty('output')
1149+
expect(countSpans(projected)).toBeLessThanOrEqual(100_000)
1150+
})
1151+
11301152
it('withholds the whole top-level span whose descendant crossed the budget', async () => {
11311153
const projected = await projectTraceSpansForSecrets(
11321154
[

‎apps/sim/lib/logs/execution/trace-secret-projection.ts‎

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1265,19 +1265,23 @@ function projectBoundedTraceSpans(
12651265
span: TraceSpan,
12661266
state: BoundedTraceStructureState,
12671267
depth: number
1268-
) => TraceSpan | undefined
1268+
) => TraceSpan | undefined,
1269+
state: BoundedTraceStructureState = { nodes: 0, truncated: false }
12691270
): TraceSpan[] {
1270-
const state: BoundedTraceStructureState = { nodes: 0, truncated: false }
12711271
const projected = projectBoundedArray(traceSpans, (span) => project(span, state, 0))
12721272
if (state.truncated) {
12731273
logger.warn('Trace structure exceeded projection limits; truncating projected structure')
12741274
}
12751275
return projected
12761276
}
12771277

1278-
function structuralOnlyTraceSpans(traceSpans: TraceSpan[]): TraceSpan[] {
1278+
/** Pass `state` to continue a structure budget that earlier spans of the same trace already spent. */
1279+
function structuralOnlyTraceSpans(
1280+
traceSpans: TraceSpan[],
1281+
state?: BoundedTraceStructureState
1282+
): TraceSpan[] {
12791283
try {
1280-
return projectBoundedTraceSpans(traceSpans, structuralOnlySpan)
1284+
return projectBoundedTraceSpans(traceSpans, structuralOnlySpan, state)
12811285
} catch (error) {
12821286
logger.warn('Trace structure could not be safely traversed; omitting projected spans', {
12831287
failure: describeProjectionFailure(error),
@@ -1697,7 +1701,7 @@ export async function projectTraceSpansForSecrets(
16971701
failure: describeProjectionFailure(error),
16981702
}
16991703
)
1700-
return [...projected, ...structuralOnlyTraceSpans(traceSpans.slice(index))]
1704+
return [...projected, ...structuralOnlyTraceSpans(traceSpans.slice(index), state)]
17011705
}
17021706
}
17031707
return projected

‎apps/sim/lib/logs/execution/trace-store.test.ts‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -561,6 +561,27 @@ describe('projectExecutionDataForDisplay envelope isolation', () => {
561561
expect(JSON.stringify(displayData)).not.toContain('12345678')
562562
})
563563

564+
it('never mistakes a stored span for an envelope value', async () => {
565+
const displayData = await projectExecutionDataForDisplay(
566+
{
567+
error: 'Run failed',
568+
traceSpans: [
569+
functionSpan('secret-safe-log-display-projection-error', { value: 'from a span' }),
570+
],
571+
executionState: { resolvedSecretTraceProvenance: RUN_PROVENANCE },
572+
},
573+
CONTEXT
574+
)
575+
576+
expect(displayData.error).toBe('Run failed')
577+
expect(displayData.traceSpans).toEqual([
578+
expect.objectContaining({
579+
id: 'secret-safe-log-display-projection-error',
580+
output: { value: 'from a span' },
581+
}),
582+
])
583+
})
584+
564585
it('spends the shared content budget on the trace before the block dumps', async () => {
565586
const displayData = await projectExecutionDataForDisplay(
566587
{

‎apps/sim/lib/logs/execution/trace-store.ts‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { createLogger } from '@sim/logger'
22
import { describeError, toError } from '@sim/utils/errors'
3+
import { generateShortId } from '@sim/utils/id'
34
import { isRecordLike, omit } from '@sim/utils/object'
45
import { assertKnownSizeWithinLimit } from '@/lib/core/utils/stream-limits'
56
import { isLargeValueRef } from '@/lib/execution/payloads/large-value-ref'
@@ -743,6 +744,8 @@ export async function projectExecutionDataForDisplay(
743744
reportStoredDisplayProvenanceFaults('traceStore.displayProjection', context, provenanceFaults)
744745

745746
const now = new Date().toISOString()
747+
/** Unguessable per call, so no stored span id can be mistaken for an envelope value. */
748+
const envelopeSpanIdPrefix = `${LOG_DISPLAY_PROJECTION_SPAN_ID}-${generateShortId()}`
746749
const envelopeKeysBySpanId = new Map<string, LogDisplayContentKey>()
747750
const envelopeSpans = (keys: readonly LogDisplayContentKey[]): TraceSpan[] =>
748751
keys.flatMap((key) => {
@@ -754,7 +757,7 @@ export async function projectExecutionDataForDisplay(
754757
) {
755758
return []
756759
}
757-
const id = `${LOG_DISPLAY_PROJECTION_SPAN_ID}-${key}`
760+
const id = `${envelopeSpanIdPrefix}-${key}`
758761
envelopeKeysBySpanId.set(id, key)
759762
return [
760763
{

0 commit comments

Comments
 (0)