Skip to content

Commit 25e1a8d

Browse files
V48 Gate 3: Fix Setup iter badge; serial Discovery
DIV iter must not paint on Setup formal rows (late dual-write after currentIteration=1). Default Discovery wave-1 to serial and pin BITCODE_DEBUG_DISCOVERY_SERIAL + Node heap for sandbox product hosts to avoid exit 137 after Setup on large monorepos.
1 parent 568b8dc commit 25e1a8d

5 files changed

Lines changed: 172 additions & 19 deletions

File tree

apps/uapi/components/bitcode/pipeline/models/pipeline-run-activity.ts

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -219,16 +219,40 @@ function updateRollingContext(ctx: ExecContext, payload: any): void {
219219
if (payload?.type === 'phase' && payload?.phase) ctx.phase = String(payload.phase);
220220
if (payload?.type === 'agent' && payload?.agent) ctx.agent = String(payload.agent);
221221

222-
// DIV-loop iteration (1-based): the SDIVF executor variant stores
223-
// phase/iteration at each observed D/I/V phase start; the classic variant
224-
// stores pipeline/currentIteration at the top of each pass. Setup precedes
225-
// the first store and Finish runs outside the loop, so clear the latch once
226-
// the finish phase starts.
222+
// DIV-loop iteration (1-based): the SDIVF executor stores
223+
// pipeline/currentIteration when the DIV loop starts (just before Discovery),
224+
// and phase/iteration on D/I/V phase starts. Setup/Finish rows must not show
225+
// "iter N" — that is enforced in iterationForPhase when stamping formal rows
226+
// (not by clearing the latch here: currentIteration is written while phase
227+
// is still "setup" for a beat, and clearing would drop iter before Discovery).
227228
if ((ns === 'phase' && key === 'iteration') || (ns === 'pipeline' && key === 'currentIteration')) {
228229
const iteration = Number(value);
229230
if (Number.isFinite(iteration) && iteration > 0) ctx.iteration = iteration;
230231
}
231-
if (String(ctx.phase || '').toLowerCase().includes('finish')) ctx.iteration = null;
232+
if (String(ctx.phase || '').toLowerCase().includes('finish')) {
233+
ctx.iteration = null;
234+
}
235+
}
236+
237+
/** DIV-loop phases only — Setup/Finish must never carry an iteration badge. */
238+
function iterationForPhase(
239+
phase: string | null | undefined,
240+
rollingIteration: number | null | undefined,
241+
): number | null {
242+
const p = String(phase || '').toLowerCase();
243+
if (!p || p.includes('setup') || p.includes('finish')) return null;
244+
const inDiv =
245+
p.includes('discovery') ||
246+
p.includes('implementation') ||
247+
p.includes('validation') ||
248+
// short product labels sometimes omit the full phase name
249+
p === 'd' ||
250+
p === 'i' ||
251+
p === 'v';
252+
if (!inDiv) return null;
253+
return typeof rollingIteration === 'number' && rollingIteration > 0
254+
? rollingIteration
255+
: null;
232256
}
233257

234258
type FormalLogLineKind = 'llm' | 'tool';
@@ -432,13 +456,17 @@ export function buildPipelineRunActivityFromEvents(
432456
// The LLM call carries the full hierarchy itself; fall back to the rolling
433457
// context only for any field the event omits.
434458
const own = readEventExecutionState(payload);
459+
const phase = own.phase ?? rollingContext.phase ?? null;
435460
const merged: ExecContext = {
436-
phase: own.phase ?? rollingContext.phase ?? null,
461+
phase,
437462
agent: own.agent ?? rollingContext.agent ?? null,
438463
step: own.step ?? rollingContext.step ?? null,
439464
failsafe: own.failsafe ?? null,
440465
generation: own.generation ?? null,
441-
iteration: rollingContext.iteration ?? null,
466+
// Never stamp DIV iter onto Setup rows (late dual-write after
467+
// currentIteration=1 at Discovery start used to paint "iter 1" on the
468+
// last Setup refine STRUCTURE line).
469+
iteration: iterationForPhase(phase, rollingContext.iteration),
442470
};
443471
const text = String(payload?.message || payload?.status?.message || '[content withheld — source-safe]');
444472
pushRow(text, stampExecutionState(merged, { ...payload, type: 'generation' }, deriveFailsafeRepairMarkers(payload)));
@@ -463,11 +491,12 @@ export function buildPipelineRunActivityFromEvents(
463491
payload?.metadata?.toolName ||
464492
(key === 'error' ? 'tool (failed)' : 'tool');
465493
// Tool uses have Phase/Agent/Step but no Failsafe/Thinkings.
494+
const phase = rollingContext.phase ?? null;
466495
const merged: ExecContext = {
467-
phase: rollingContext.phase ?? null,
496+
phase,
468497
agent: rollingContext.agent ?? null,
469498
step: rollingContext.step ?? null,
470-
iteration: rollingContext.iteration ?? null,
499+
iteration: iterationForPhase(phase, rollingContext.iteration),
471500
};
472501
const enriched = stampExecutionState(merged, { ...payload, type: 'tool-use' }, { tool: toolName });
473502
enriched.metadata = {

apps/uapi/lib/pipeline-host-command-env.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,26 @@ export function selectedPipelineHostCommandEnvironment(
9090
// left execution_events as status-only spam and the UI showed "No logs
9191
// available" despite healthy Setup/Discovery (run 793f8be1).
9292
env.BITCODE_PIPELINE_LEGACY_EVENTS_DB = '1';
93+
// Cap the in-box Node heap so Discovery on large monorepos fails more
94+
// gracefully (and sooner) than an unbounded V8 growth + host SIGKILL.
95+
// Child language servers use BITCODE_LSP_MAX_OLD_SPACE_MB separately.
96+
const pipelineHeapMb = (() => {
97+
const raw = Number(process.env.BITCODE_PIPELINE_MAX_OLD_SPACE_MB);
98+
if (Number.isFinite(raw) && raw >= 512 && raw <= 4096) return Math.floor(raw);
99+
return 1536;
100+
})();
101+
const parentNodeOptions = String(process.env.NODE_OPTIONS || '');
102+
env.NODE_OPTIONS = [
103+
parentNodeOptions.replace(/--max-old-space-size=\d+/g, '').trim(),
104+
`--max-old-space-size=${pipelineHeapMb}`,
105+
]
106+
.filter(Boolean)
107+
.join(' ');
108+
// Prefer serial Discovery wave-1 inside the box even on older images that
109+
// still default parallel (set before image rebuild lands).
110+
if (!env.BITCODE_DEBUG_DISCOVERY_SERIAL && !process.env.BITCODE_DEBUG_DISCOVERY_PARALLEL) {
111+
env.BITCODE_DEBUG_DISCOVERY_SERIAL = '1';
112+
}
93113
// Inference is owned by the host/Pipeliner process. Product default is on
94114
// when unset; honor explicit opt-out (0/false/off) for unit tests.
95115
const realInferenceRaw = process.env.BITCODE_ASSET_PACK_REAL_INFERENCE;

apps/uapi/tests/lib/depositSourceProvisioning.test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,9 +319,13 @@ describe('runDepositInBoxHost (#25)', () => {
319319
BITCODE_PIPELINE_STREAM_TO_DATABASE: '1',
320320
BITCODE_PIPELINE_STRUCTURED_DB: '1',
321321
BITCODE_PIPELINE_LEGACY_EVENTS_DB: '1',
322+
BITCODE_DEBUG_DISCOVERY_SERIAL: '1',
322323
BITCODE_PIPELINE_USER_ID: 'user-deposit-test',
323324
BITCODE_PIPELINE_RUN_ID: 'run-deposit-test',
324325
});
326+
expect(receivedPlan.createOptions.env.NODE_OPTIONS || '').toMatch(
327+
/max-old-space-size=/,
328+
);
325329
expect(receivedPlan.manifest.sourceRevision).toMatchObject({
326330
branch: 'main',
327331
commit: 'abc123def456',

apps/uapi/tests/pipelineExecutionLogTelemetryUx.test.tsx

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,88 @@ describe('buildPipelineRunActivityFromEvents — mode latch + latest context', (
286286
const snapshot = buildPipelineRunActivityFromEvents(duped, null, [], null);
287287
expect(Object.keys(snapshot.outputDetails)).toHaveLength(1);
288288
});
289+
290+
it('never stamps DIV iter on Setup formal rows even after currentIteration=1', () => {
291+
// Late dual-write: Setup refine STRUCTURE after Discovery latched iter 1
292+
// used to paint "iter 1" on the last Setup log line (run 9d8bcf0f UX).
293+
const mixed = [
294+
{
295+
id: 's1',
296+
event: {
297+
type: 'generation',
298+
namespace: 'llm',
299+
key: 'output',
300+
message: '[content withheld — source-safe]',
301+
executionState: {
302+
phase: 'setup',
303+
agent: 'DepositInputComprehensionAgent',
304+
step: 'refine',
305+
failsafe: 'prepare_concise_context',
306+
generation: 'reason',
307+
},
308+
},
309+
created_at: '2026-07-01T00:00:01.000Z',
310+
},
311+
{
312+
id: 'iter',
313+
event: {
314+
type: 'status',
315+
namespace: 'pipeline',
316+
key: 'currentIteration',
317+
data: 1,
318+
},
319+
created_at: '2026-07-01T00:00:02.000Z',
320+
},
321+
{
322+
id: 's2-late',
323+
event: {
324+
type: 'generation',
325+
namespace: 'llm',
326+
key: 'output',
327+
message: '[content withheld — source-safe]',
328+
executionState: {
329+
phase: 'setup',
330+
agent: 'DepositInputComprehensionAgent',
331+
step: 'refine',
332+
failsafe: 'handle_prompts',
333+
generation: 'structured_output',
334+
},
335+
},
336+
created_at: '2026-07-01T00:00:03.000Z',
337+
},
338+
{
339+
id: 'd1',
340+
event: {
341+
type: 'generation',
342+
namespace: 'llm',
343+
key: 'output',
344+
message: '[content withheld — source-safe]',
345+
executionState: {
346+
phase: 'discovery',
347+
agent: 'DepositCodebaseComprehensionAgent',
348+
step: 'plan',
349+
failsafe: 'prepare_concise_context',
350+
generation: 'reason',
351+
},
352+
},
353+
created_at: '2026-07-01T00:00:04.000Z',
354+
},
355+
];
356+
const snapshot = buildPipelineRunActivityFromEvents(mixed, null, [], null);
357+
const rows = Object.values(snapshot.outputDetails) as any[];
358+
const setupRows = rows.filter((r) =>
359+
String(r?.executionState?.phase || '').toLowerCase().includes('setup'),
360+
);
361+
const discoveryRows = rows.filter((r) =>
362+
String(r?.executionState?.phase || '').toLowerCase().includes('discovery'),
363+
);
364+
expect(setupRows.length).toBe(2);
365+
for (const row of setupRows) {
366+
expect(row.executionState.iteration ?? null).toBeNull();
367+
}
368+
expect(discoveryRows.length).toBe(1);
369+
expect(discoveryRows[0].executionState.iteration).toBe(1);
370+
});
289371
});
290372

291373
// ---------------------------------------------------------------------------

packages/asset-packs-pipelines/syntheses/deposit/src/phases/execution-pipeline-sdivf-execution-phase-discovery-synthesis-deposit-asset-packs.ts

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -145,15 +145,33 @@ export const executionPipelineSDIVFExecutionPhaseDiscoverySynthesisDepositAssetP
145145
);
146146
}
147147

148-
const serialDiscovery =
149-
String(process.env.BITCODE_DEBUG_DISCOVERY_SERIAL || '').toLowerCase() ===
150-
'1' ||
151-
String(process.env.BITCODE_DEBUG_DISCOVERY_SERIAL || '').toLowerCase() ===
152-
'true' ||
153-
String(process.env.BITCODE_DEBUG_SETUP_SERIAL || '').toLowerCase() ===
154-
'1' ||
155-
String(process.env.BITCODE_DEBUG_SETUP_SERIAL || '').toLowerCase() ===
156-
'true';
148+
// Product default: serial wave-1 Discovery. Parallel comprehend-codebase ∥
149+
// inherent-regurgitation OOM-killed the sandbox (exit 137) on Bitcode monorepo
150+
// right after Setup closed (run 9d8bcf0f). Opt into parallel only with
151+
// BITCODE_DEBUG_DISCOVERY_PARALLEL=1 on small checkouts.
152+
const forceParallel =
153+
['1', 'true', 'yes', 'on'].includes(
154+
String(process.env.BITCODE_DEBUG_DISCOVERY_PARALLEL || '')
155+
.trim()
156+
.toLowerCase(),
157+
);
158+
const forceSerial =
159+
['1', 'true', 'yes', 'on'].includes(
160+
String(process.env.BITCODE_DEBUG_DISCOVERY_SERIAL || '')
161+
.trim()
162+
.toLowerCase(),
163+
) ||
164+
['1', 'true', 'yes', 'on'].includes(
165+
String(process.env.BITCODE_DEBUG_SETUP_SERIAL || '')
166+
.trim()
167+
.toLowerCase(),
168+
);
169+
const serialDiscovery = forceSerial || !forceParallel;
170+
try {
171+
(execution as any).store?.('discovery', 'wave1Serial', serialDiscovery);
172+
} catch {
173+
/* ignore */
174+
}
157175
const wave1 = serialDiscovery
158176
? sequential(
159177
createAgentExecutor(DISCOVERY_COMPREHEND_CODEBASE),

0 commit comments

Comments
 (0)