Skip to content

Commit 4f6c956

Browse files
V48 Gate 3 (implementation-only): wire agent-measure-absolutes into the Validation phase (formal absolutes flow to the /deposit options)
Completes the absolutes formalization: the Validation phase now MEASURES each synthesized patch's absolutes via the formal measure-agent, and the formal absolutes flow through to the /deposit option tiles — replacing the synthesis agent's inline self-scored placeholder catalog as the effective measurement. - deposit-validation-agent.ts: after the quality verdict, run measureAssetPackAbsolutes per synthesized pack (parallel; each LLM call F25-timeout-bounded; deterministic descriptor-derived fallback when real inference is off), attach the absolutes to each pack in place, and re-store implementation:options/assetPacks (the exact keys the route + Finish read). The measure-agent's PTRR substeps render in the SDIVF telemetry, content withheld. - asset-packs-synthesis.ts: validateDepositSynthesisOptions now PREFERS option.absolutes (the formal measurements) as the candidate's measurements; the legacy inline-record catalog mapping is the back-compat fallback (bounded path / pre-Gate-4 read lens). - deposit-asset-pack-options.ts: widened DepositAssetPackOptionMeasurement.measurementKind to string + carries category/magnitude/unit. deposit-option-real-synthesis.ts projects those through. - DepositPageClient.tsx: the measurement tile surfaces the size magnitude (e.g. "12 functions · 60% / weight 0.18"). Verified: asset-pack 44 suites/216 tests, agent-generics 8/16, uapi depositPageClient 2/2, all green; tsc clean for asset-pack + uapi. The deterministic fallback + absolutes-preferred-when-present keep the bounded-synthesis path working. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 27fb2a8 commit 4f6c956

6 files changed

Lines changed: 143 additions & 10 deletions

File tree

packages/pipelines/asset-pack/src/__tests__/agent-measure-absolutes.test.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
import {
99
ASSET_PACK_ABSOLUTES_CATALOG,
1010
ASSET_PACK_ABSOLUTE_KINDS,
11+
validateDepositSynthesisOptions,
1112
} from '../asset-packs-synthesis';
1213

1314
const PATCH: MeasurableAssetPackPatch = {
@@ -70,3 +71,45 @@ describe('agent-measure-absolutes', () => {
7071
expect(deposit.measurementSpecs).toHaveLength(ASSET_PACK_ABSOLUTES_CATALOG.length);
7172
});
7273
});
74+
75+
describe('validateDepositSynthesisOptions absolutes wiring', () => {
76+
const baseOption = {
77+
kind: 'capability-slice',
78+
title: 'Auth capability slice',
79+
summary: 'A reusable authentication capability extracted from the source.',
80+
coveredSourcePaths: ['src/auth.ts'],
81+
measurements: { 'source-coverage': 0.7, 'demand-alignment': 0.6, 'reuse-likelihood': 0.5 },
82+
measurementRationale: 'Covers the auth module.',
83+
confidence: 0.8,
84+
};
85+
const context = {
86+
lens: 'deposit' as const,
87+
inventoryPaths: ['src/auth.ts'],
88+
protectedIpExclusions: [],
89+
candidateKinds: ['capability-slice', 'implementation-pattern', 'proof-operations-slice'],
90+
};
91+
92+
it('prefers the formal absolutes over the placeholder catalog', () => {
93+
const absolutes = [
94+
{ measurementKind: 'function-count', label: 'Functions', weight: 0.18, volume: 0.5, category: 'absolute', magnitude: 12, unit: 'functions' },
95+
{ measurementKind: 'semantic-volume', label: 'Semantic volume', weight: 0.28, volume: 0.66, category: 'absolute', unit: 'normalized' },
96+
];
97+
const out = validateDepositSynthesisOptions([{ ...baseOption, absolutes }], context);
98+
const measurements = out.candidates[0].measurements;
99+
expect(measurements.map((m) => m.measurementKind)).toEqual(['function-count', 'semantic-volume']);
100+
const fn = measurements.find((m) => m.measurementKind === 'function-count');
101+
expect(fn?.magnitude).toBe(12);
102+
expect(fn?.category).toBe('absolute');
103+
expect(fn?.unit).toBe('functions');
104+
// placeholder kinds are NOT present once formal absolutes are supplied
105+
expect(measurements.map((m) => m.measurementKind)).not.toContain('source-coverage');
106+
});
107+
108+
it('falls back to the placeholder catalog when no absolutes are present', () => {
109+
const out = validateDepositSynthesisOptions([baseOption], context);
110+
const kinds = out.candidates[0].measurements.map((m) => m.measurementKind);
111+
expect(kinds).toContain('source-coverage');
112+
expect(kinds).toContain('demand-alignment');
113+
expect(kinds).toContain('reuse-likelihood');
114+
});
115+
});

packages/pipelines/asset-pack/src/agents/validation/deposit-validation-agent.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import { Prompt } from '@bitcode/prompts/prompt';
2727
import type { PromptPart } from '@bitcode/prompts/parts/PromptPart';
2828
import { z } from 'zod';
2929
import { DEPOSIT_MEASUREMENT_CATALOG } from '../../asset-packs-synthesis';
30+
import { measureAssetPackAbsolutes } from './agent-measure-absolutes';
3031

3132
const part = (content: string): PromptPart => content as PromptPart;
3233

@@ -291,5 +292,43 @@ export default async function runDepositValidationAgent(input: any, execution: a
291292
execution.store('validation', 'depositQuality', result);
292293
} catch {}
293294

295+
// V48 Gate 3 — formal ABSOLUTES measurement. Each AssetPack is a measured patch:
296+
// the patch was synthesized in Implementation; here the formal measure-agent
297+
// (agent-measure-absolutes) MEASURES its absolutes (sizes / correctness /
298+
// semantic-volume) and attaches them to the pack in place. Real inference runs
299+
// the measure-agent (its PTRR substeps render in the SDIVF telemetry, content
300+
// withheld); otherwise the deterministic descriptor-derived fallback applies.
301+
// Per pack in parallel so wall-clock ≈ one measurement; each LLM call is bounded
302+
// by the F25 per-call timeout.
303+
if (packs.length > 0) {
304+
await Promise.all(
305+
packs.map(async (pack: any) => {
306+
try {
307+
const absolutes = await measureAssetPackAbsolutes(
308+
{
309+
title: String(pack?.title ?? ''),
310+
summary: String(pack?.summary ?? ''),
311+
coveredSourcePaths: asPathList(pack?.coveredSourcePaths),
312+
fileChanges: Array.isArray(pack?.patch?.fileChanges) ? pack.patch.fileChanges : undefined,
313+
confidence: typeof pack?.confidence === 'number' ? pack.confidence : undefined,
314+
patchSummary:
315+
typeof pack?.patch?.patchSummary === 'string' ? pack.patch.patchSummary : undefined,
316+
},
317+
{ lens: 'deposit', execution },
318+
);
319+
// Attach the formal absolutes to the measured patch in place; the route's
320+
// validateDepositSynthesisOptions prefers these over the legacy inline record.
321+
pack.absolutes = absolutes;
322+
} catch {}
323+
}),
324+
);
325+
try {
326+
// Re-store the measured packs (in-place mutation + explicit re-store) under the
327+
// exact keys the route + Finish read.
328+
execution.store('implementation', 'options', packs);
329+
execution.store('implementation', 'assetPacks', packs);
330+
} catch {}
331+
}
332+
294333
return { ...(input || {}), ...result };
295334
}

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

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -502,6 +502,10 @@ export function validateDepositSynthesisOptions(
502502
measurements: Record<string, number>;
503503
measurementRationale: string;
504504
confidence: number;
505+
// V48 Gate 3: the FORMAL absolutes measured by agent-measure-absolutes in the
506+
// Validation phase. When present these are the candidate's measurements (the
507+
// legacy `measurements` record is the back-compat fallback only).
508+
absolutes?: AssetPackCandidateMeasurement[] | null;
505509
// Deposit lens only: the read-demand preview signal (v0). neediness is
506510
// COMPUTED from this; absent/invalid signals yield no neediness preview.
507511
needinessSignal?: { demand?: number; saturation?: number; rationale?: string } | null;
@@ -534,12 +538,28 @@ export function validateDepositSynthesisOptions(
534538
);
535539
continue;
536540
}
537-
const measurements = catalog.map((spec) => ({
538-
measurementKind: spec.measurementKind,
539-
label: spec.label,
540-
weight: spec.weight,
541-
volume: clampVolume(option.measurements?.[spec.measurementKind] ?? 0),
542-
}));
541+
// Prefer the FORMAL absolutes (agent-measure-absolutes, Validation phase) when
542+
// present; otherwise map the legacy inline record through the lens catalog.
543+
const formalAbsolutes = Array.isArray(option.absolutes) ? option.absolutes : null;
544+
const measurements: AssetPackCandidateMeasurement[] =
545+
formalAbsolutes && formalAbsolutes.length > 0
546+
? formalAbsolutes.map((m) => ({
547+
measurementKind: String(m.measurementKind),
548+
label: String(m.label ?? m.measurementKind),
549+
weight: Number.isFinite(m.weight) ? Number(m.weight) : 0,
550+
volume: clampVolume(Number(m.volume) || 0),
551+
category: m.category === 'neediness' ? 'neediness' : 'absolute',
552+
...(Number.isFinite(m.magnitude as number)
553+
? { magnitude: Math.max(0, Math.round(Number(m.magnitude))) }
554+
: {}),
555+
...(m.unit ? { unit: String(m.unit) } : {}),
556+
}))
557+
: catalog.map((spec) => ({
558+
measurementKind: spec.measurementKind,
559+
label: spec.label,
560+
weight: spec.weight,
561+
volume: clampVolume(option.measurements?.[spec.measurementKind] ?? 0),
562+
}));
543563
candidates.push({
544564
kind: allowedKinds.has(option.kind) ? option.kind : context.candidateKinds[0],
545565
title: String(option.title).trim(),

packages/pipelines/asset-pack/src/deposit-asset-pack-options.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,18 @@ export interface DepositOptionSynthesisRequest {
3030
export interface DepositAssetPackOptionMeasurement {
3131
id: string;
3232
label: string;
33-
measurementKind: 'source-coverage' | 'demand-alignment' | 'reuse-likelihood';
33+
// V48 Gate 3: widened to the absolutes catalog kinds (function-count, type-count,
34+
// file-span, correctness-estimate, semantic-volume) — the formal measurements —
35+
// as well as the legacy placeholder kinds the deterministic blueprint emits.
36+
measurementKind: string;
3437
weight: number;
3538
volume: number;
39+
/** Which measurement category — absolutes form the weighted composite. */
40+
category?: 'absolute' | 'neediness';
41+
/** Raw count for size measurements (functions / types / files). */
42+
magnitude?: number;
43+
/** functions | types | files | estimate | normalized. */
44+
unit?: string;
3645
evidenceRoot: string;
3746
}
3847

packages/pipelines/asset-pack/src/deposit-option-real-synthesis.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,9 +180,13 @@ export function buildRealDepositAssetPackOptionSynthesis(
180180
const measurements: DepositAssetPackOptionMeasurement[] = candidate.measurements.map((measurement) => ({
181181
id: `${optionId}:${measurement.measurementKind}`,
182182
label: measurement.label,
183-
measurementKind: measurement.measurementKind as DepositAssetPackOptionMeasurement['measurementKind'],
183+
measurementKind: measurement.measurementKind,
184184
weight: measurement.weight,
185185
volume: measurement.volume,
186+
// V48 Gate 3: carry the absolutes provenance (category + size magnitude/unit).
187+
...(measurement.category ? { category: measurement.category } : {}),
188+
...(typeof measurement.magnitude === 'number' ? { magnitude: measurement.magnitude } : {}),
189+
...(measurement.unit ? { unit: measurement.unit } : {}),
186190
evidenceRoot: root('deposit-option-measurement', {
187191
measurementKind: measurement.measurementKind,
188192
weight: measurement.weight,

uapi/app/deposit/DepositPageClient.tsx

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1687,8 +1687,26 @@ export default function DepositPageClient() {
16871687
{measurement.label}
16881688
</dt>
16891689
<dd className="mt-1 text-sm text-neutral-200">
1690-
{(measurement.volume * 100).toFixed(0)}% / weight{" "}
1691-
{measurement.weight.toFixed(2)}
1690+
{typeof measurement.magnitude === "number" ? (
1691+
<>
1692+
{measurement.magnitude}
1693+
{measurement.unit &&
1694+
measurement.unit !== "normalized" &&
1695+
measurement.unit !== "estimate"
1696+
? ` ${measurement.unit}`
1697+
: ""}
1698+
<span className="text-neutral-500">
1699+
{" "}
1700+
· {(measurement.volume * 100).toFixed(0)}% / weight{" "}
1701+
{measurement.weight.toFixed(2)}
1702+
</span>
1703+
</>
1704+
) : (
1705+
<>
1706+
{(measurement.volume * 100).toFixed(0)}% / weight{" "}
1707+
{measurement.weight.toFixed(2)}
1708+
</>
1709+
)}
16921710
</dd>
16931711
</div>
16941712
))}

0 commit comments

Comments
 (0)