Skip to content

Commit b561779

Browse files
V48 Gate 3 (implementation-only): reliable SDIVF token rollup and zero-option fail-closed
Export sumLlmTokensFromExecutionTree that walks Map and array children so deposit synthesize-options captures nested PTRR usage. Fail closed when Validation leaves zero admissible candidates (missing absolutes or source-safety drops).
1 parent 4ebaaef commit b561779

3 files changed

Lines changed: 41 additions & 37 deletions

File tree

packages/pipelines/asset-pack/src/asset-packs-synthesis-pipeline.ts

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -183,12 +183,21 @@ export interface FormalSynthesisOutcome {
183183
totalTokens: number | null;
184184
}
185185

186-
function sumLlmTokens(execution: Execution): number | null {
186+
/**
187+
* Sum LLM usage across an Execution tree (Map children + array fallbacks).
188+
* Used by formal synthesis and by the deposit synthesize-options route so
189+
* nested PTRR/Failsafe generation usage is not missed.
190+
*/
191+
export function sumLlmTokensFromExecutionTree(execution: {
192+
get?: (namespace: string, key: string) => unknown;
193+
children?: Map<string, unknown> | unknown[] | { values?: () => IterableIterator<unknown> };
194+
}): number | null {
187195
let total = 0;
188196
let seen = false;
189-
const walk = (node: Execution) => {
197+
const walk = (node: any) => {
198+
if (!node) return;
190199
try {
191-
const usage = node.get<Record<string, number>>('llm', 'usage');
200+
const usage = node.get?.('llm', 'usage') as Record<string, number> | undefined;
192201
if (usage && typeof usage === 'object') {
193202
const prompt =
194203
usage.promptTokens ?? usage.prompt_tokens ?? usage.inputTokens ?? usage.input_tokens ?? 0;
@@ -210,12 +219,25 @@ function sumLlmTokens(execution: Execution): number | null {
210219
}
211220
}
212221
} catch {}
213-
for (const child of node.children.values()) walk(child);
222+
const children = node.children;
223+
if (!children) return;
224+
if (typeof children.values === 'function') {
225+
for (const child of children.values()) walk(child);
226+
return;
227+
}
228+
if (Array.isArray(children)) {
229+
for (const child of children) walk(child);
230+
}
214231
};
215232
walk(execution);
216233
return seen ? total : null;
217234
}
218235

236+
/** @deprecated alias — use sumLlmTokensFromExecutionTree */
237+
function sumLlmTokens(execution: Execution): number | null {
238+
return sumLlmTokensFromExecutionTree(execution);
239+
}
240+
219241
export async function synthesizeAssetPackCandidatesFormal(
220242
request: FormalSynthesisRequest,
221243
candidateSetSchema: z.ZodType<{ options: FormalSynthesisRawOption[] }>,

packages/pipelines/asset-pack/src/asset-packs-synthesis.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,13 @@ import type { MeasurementSpec } from '@bitcode/agent-generics';
55

66
import {
77
synthesizeAssetPackCandidatesFormal,
8+
sumLlmTokensFromExecutionTree,
89
type FormalSynthesisRawOption,
910
} from './asset-packs-synthesis-pipeline';
1011
import { isAssetPackRealInferenceEnabled } from './runtime-inference-policy';
1112

13+
export { sumLlmTokensFromExecutionTree };
14+
1215
/**
1316
* AssetPacksSynthesis — the single Bitcode synthesis/measurement pipeline
1417
* (V48 architecture law, QA ledger F12).

uapi/app/api/deposit/synthesize-options/route.ts

Lines changed: 12 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { createStreamingExecution, emitPhaseTransition } from '@bitcode/pipeline
1111
import {
1212
applyExclusionsToInventory,
1313
normalizeProtectedIpExclusions,
14+
sumLlmTokensFromExecutionTree,
1415
validateDepositSynthesisOptions,
1516
type AssetPacksSynthesisResult,
1617
type AssetPacksSynthesisSourceInventory,
@@ -384,39 +385,8 @@ export async function POST(request: Request) {
384385
protectedIpExclusions,
385386
candidateKinds: DEPOSIT_OPTION_KINDS,
386387
});
387-
// Roll up usage from the SDIVF execution tree when available.
388-
let rolledTokens: number | null = null;
389-
try {
390-
let total = 0;
391-
let seen = false;
392-
const walk = (node: any) => {
393-
if (!node) return;
394-
try {
395-
const usage = node.get?.('llm', 'usage');
396-
if (usage && typeof usage === 'object') {
397-
const prompt =
398-
usage.promptTokens ?? usage.prompt_tokens ?? usage.inputTokens ?? usage.input_tokens ?? 0;
399-
const completion =
400-
usage.completionTokens ??
401-
usage.completion_tokens ??
402-
usage.outputTokens ??
403-
usage.output_tokens ??
404-
0;
405-
const n = Number(prompt) + Number(completion);
406-
if (Number.isFinite(n) && n > 0) {
407-
total += n;
408-
seen = true;
409-
}
410-
}
411-
} catch {}
412-
const children = node.children ?? node._children ?? [];
413-
if (Array.isArray(children)) for (const child of children) walk(child);
414-
};
415-
walk(execution);
416-
rolledTokens = seen ? total : null;
417-
} catch {
418-
rolledTokens = null;
419-
}
388+
// Roll up usage from the full SDIVF execution tree (Map children + nested PTRR).
389+
const rolledTokens = sumLlmTokensFromExecutionTree(execution as never);
420390
const result: AssetPacksSynthesisResult = {
421391
lens: 'deposit',
422392
candidates: validated.candidates,
@@ -432,6 +402,15 @@ export async function POST(request: Request) {
432402
await emitStatus(
433403
`Validated candidates fail-closed: ${result.candidates.length} admissible, ${result.droppedCandidateCount} dropped.`,
434404
);
405+
if (result.candidates.length === 0) {
406+
const detail =
407+
result.exclusionViolations.length > 0
408+
? result.exclusionViolations.slice(0, 5).join('; ')
409+
: 'no admissible measured candidates survived Validation absolutes / source-safety checks';
410+
throw new Error(
411+
`AssetPacksSynthesis produced zero admissible options (fail-closed): ${detail}`,
412+
);
413+
}
435414

436415
const { synthesis, reviewProjections } = buildRealDepositAssetPackOptionSynthesis(
437416
{

0 commit comments

Comments
 (0)