Skip to content

Commit e516e5d

Browse files
V48 Gate 3 (specification-implementation): deposit Validation agent (quality/coverage/smoke → iterate gate)
New DepositValidationAgent (factoryAgentWithPTRR + deterministic smoke/sanity checks) validates the synthesized deposit AssetPacks — measurement honesty (0..1), distinctness, source-safety, obfuscation/exclusion compliance, patch-descriptor coherence, coverage — emitting {issues, qualityScore, coverageGaps, recommendation:'complete'|'iterate'}. Issues stored at validation/implementation:issues (the key the canonical ReadyToFinish gate reads); any issue forces iterate, driving the DIV loop. registerValidationAgentsForType + validationPhase branch on mode (deposit → deposit-quality → ReadyToFinish; read → the 3 parallel validators → ReadyToFinish, untouched). Spec note records the checks + the gate integration. asset-pack 42/208 + typecheck green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 233afd1 commit e516e5d

4 files changed

Lines changed: 329 additions & 9 deletions

File tree

BITCODE_SPEC_V48_NOTES.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,12 @@ The five phases (same agents in both modes, lens-varied — decided 2026-06-25):
272272
cloned workspace is the delivery/settlement context.
273273
- **Validation**: quality thresholds, coverage/corrections/fixes, and AssetPack
274274
smoke/sanity checks; the Discovery/Implementation/Validation loop iterates until
275-
complete or maxIterations.
275+
complete or maxIterations. The deposit validator (`validation:deposit-quality`)
276+
validates the synthesized AssetPacks — measurement honesty (0..1), distinctness,
277+
source-safety, obfuscation/exclusion compliance, patch-descriptor coherence, and
278+
coverage — emitting `{issues, qualityScore, coverageGaps, recommendation}`. Its
279+
issues are stored at `validation/implementation:issues` (the key the canonical
280+
ReadyToFinish gate reads); any issue forces `iterate`.
276281
- **Finish**: upload the synthesized AssetPack(s) to Bitcode for review.
277282

278283
Implementation status vs this model (Gate-3 build-down list): the deposit
Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,292 @@
1+
/**
2+
* Deposit-mode Validation agent — Validation phase (V48 Gate 2/3).
3+
*
4+
* The deposit lens of the SynthesizeAssetPacks Validation phase: validate the
5+
* synthesized, measured-patch AssetPacks (implementation:options /
6+
* implementation:assetPacks) before Finish uploads them for depositor review.
7+
* The read lens validates fits artifacts; the deposit lens validates the
8+
* QUALITY of the synthesized supply — measurement honesty, distinctness,
9+
* source-safety, obfuscation/exclusion compliance, patch coherence, and
10+
* repository coverage — and drives the iterate-vs-complete decision.
11+
*
12+
* Source-safety (hard constraint): validation describes quality, never raw
13+
* source. It reasons only over the source-safe AssetPack descriptors (title,
14+
* summary, coveredSourcePaths, measurements, confidence, and the source-safe
15+
* patch descriptor of fileChanges path+op + patchSummary), the inventory paths,
16+
* the obfuscation guidance, and the protected-IP exclusions — never raw code.
17+
*
18+
* Runs on the formal PTRR machinery (factoryAgentWithPTRR → Plan/Try/Refine/
19+
* Retry) so every call renders in the SDIVF telemetry. The agent's qualitative
20+
* findings are merged with deterministic AssetPack smoke/sanity checks, and the
21+
* resulting issues are stored under validation/implementation:issues — the exact
22+
* store the AssetPack ReadyToFinish gate reads to decide finish vs. review.
23+
*/
24+
25+
import { factoryAgentWithPTRR } from '@bitcode/agent-generics';
26+
import { Prompt } from '@bitcode/prompts/prompt';
27+
import type { PromptPart } from '@bitcode/prompts/parts/PromptPart';
28+
import { z } from 'zod';
29+
import { DEPOSIT_MEASUREMENT_CATALOG } from '../../asset-packs-synthesis';
30+
31+
const part = (content: string): PromptPart => content as PromptPart;
32+
33+
const DepositValidationInputSchema = z.object({
34+
assetPacks: z.any().optional(),
35+
inventory: z.any().optional(),
36+
obfuscationGuidance: z.any().optional(),
37+
protectedIpExclusions: z.any().optional(),
38+
});
39+
40+
const DepositValidationOutputSchema = z.object({
41+
issues: z.array(z.string()),
42+
qualityScore: z.number().min(0).max(1),
43+
coverageGaps: z.array(z.string()),
44+
recommendation: z.enum(['complete', 'iterate']),
45+
});
46+
47+
export type DepositValidationResult = z.infer<typeof DepositValidationOutputSchema>;
48+
49+
const IDENTITY = part(
50+
'You are the SynthesizeAssetPacks Validation agent in DEPOSIT mode. You validate ' +
51+
'the QUALITY of the synthesized, measured-patch AssetPacks the depositor will ' +
52+
'review — never the raw source. You reason only over the source-safe AssetPack ' +
53+
'descriptors, the repository inventory paths, the obfuscation guidance, and the ' +
54+
'protected-IP exclusions. You describe quality and never quote, reconstruct, or ' +
55+
'expose raw source, code, secrets, or file contents. Your verdict drives the ' +
56+
'iterate-vs-complete decision for the deposit supply.',
57+
);
58+
59+
const REQUIREMENTS = part(
60+
[
61+
'Validate the synthesized deposit AssetPacks against these checks and report ' +
62+
'every concrete problem as a short, source-safe issue string:',
63+
'- Quality threshold: each pack carries honest 0..1 measurements (an object whose ' +
64+
'values are in [0,1]) over the expected measurement kinds (' +
65+
DEPOSIT_MEASUREMENT_CATALOG.map((spec) => spec.measurementKind).join(', ') +
66+
') with a confidence in [0,1] that is reasonable for the evidence; flag inflated, ' +
67+
'out-of-range, or unjustified measurements/confidence.',
68+
'- Distinctness: the packs are genuinely distinct, complementary knowledge slices, ' +
69+
'not duplicative or near-identical; flag overlap or repetition.',
70+
'- Source-safety: NO raw source, code, diffs, secrets, or file contents appear in any ' +
71+
'title, summary, measurementRationale, or patchSummary; flag any leakage.',
72+
'- Obfuscation/exclusion compliance: no coveredSourcePaths and no patch fileChanges ' +
73+
'path touches an obfuscated path/concept (from the obfuscation guidance) or a ' +
74+
'protected-IP exclusion; flag any violation by path.',
75+
'- Patch coherence: each pack has a source-safe patch descriptor with a non-empty ' +
76+
'fileChanges list (path + op = create | modify | delete) and a patchSummary; flag ' +
77+
'missing or incoherent patch descriptors.',
78+
'- Coverage: the packs adequately cover the repository\'s distinct, buyer-legible ' +
79+
'knowledge as represented by the inventory; list the notable uncovered areas in ' +
80+
'coverageGaps.',
81+
'Set qualityScore in [0,1] as your overall honest quality of the synthesized supply. ' +
82+
'Set recommendation to "iterate" when issues or material coverage gaps remain, else ' +
83+
'"complete". Return ONLY {"issues":[...],"qualityScore":n,"coverageGaps":[...],' +
84+
'"recommendation":"complete"|"iterate"}.',
85+
].join('\n'),
86+
);
87+
88+
const PLAN = part('Plan: enumerate the synthesized AssetPacks and the quality dimensions to check.');
89+
const TRY = part('Try: run each quality, distinctness, source-safety, compliance, patch, and coverage check.');
90+
const REFINE = part('Refine: keep only concrete, source-safe issues and an honest qualityScore and recommendation.');
91+
const RETRY = part('Retry: when evidence is thin, validate the available AssetPack state and name what is missing.');
92+
93+
function createPrompt(): Prompt {
94+
const prompt = new Prompt();
95+
prompt.set('agent:identity', IDENTITY);
96+
prompt.set('agent:requirements', REQUIREMENTS);
97+
prompt.set('ptrr:plan', PLAN);
98+
prompt.set('ptrr:try', TRY);
99+
prompt.set('ptrr:refine', REFINE);
100+
prompt.set('ptrr:retry', RETRY);
101+
prompt.require('agent:identity');
102+
prompt.require('agent:requirements');
103+
prompt.requirePattern('ptrr:*');
104+
return prompt;
105+
}
106+
107+
const prompt = createPrompt();
108+
109+
export const DepositValidationAgent = factoryAgentWithPTRR<
110+
z.infer<typeof DepositValidationInputSchema>,
111+
DepositValidationResult
112+
>({
113+
name: 'DepositValidationAgent',
114+
description:
115+
'Validates the synthesized deposit AssetPacks for quality, distinctness, source-safety, obfuscation/exclusion compliance, patch coherence, and coverage (deposit lens).',
116+
outputSchema: DepositValidationOutputSchema,
117+
tools: [],
118+
prompt,
119+
stepPrompts: {
120+
plan: () => prompt,
121+
try: () => prompt,
122+
refine: () => prompt,
123+
retry: () => prompt,
124+
},
125+
plan: { chunkThreshold: 2000 },
126+
try: { chunkThreshold: 4000 },
127+
refine: { maxAttempts: 2 },
128+
retry: { maxAttempts: 1 },
129+
});
130+
131+
function findValue(execution: any, namespace: string, key: string): any {
132+
const local = execution?.get?.(namespace, key);
133+
if (local !== undefined) return local;
134+
return execution?.findUp?.(namespace, key);
135+
}
136+
137+
function isNum01(value: unknown): boolean {
138+
return typeof value === 'number' && Number.isFinite(value) && value >= 0 && value <= 1;
139+
}
140+
141+
function asPathList(value: unknown): string[] {
142+
if (!Array.isArray(value)) return [];
143+
return value.filter((p): p is string => typeof p === 'string' && p.length > 0);
144+
}
145+
146+
// A path violates an exclusion/obfuscation entry when it equals it or sits beneath
147+
// it as a directory prefix. Source-safe: operates on paths only, never contents.
148+
function pathViolates(path: string, entry: string): boolean {
149+
if (!path || !entry) return false;
150+
if (path === entry) return true;
151+
const dir = entry.endsWith('/') ? entry : `${entry}/`;
152+
return path.startsWith(dir);
153+
}
154+
155+
// Deterministic AssetPack smoke/sanity checks over the source-safe descriptors.
156+
// These ground the gate so structurally broken or non-compliant synthesis output
157+
// always surfaces an issue, independent of the model's qualitative pass.
158+
function smokeCheckAssetPacks(
159+
assetPacks: any[],
160+
protectedIpExclusions: string[],
161+
obfuscatedPaths: string[],
162+
): string[] {
163+
const issues: string[] = [];
164+
if (!Array.isArray(assetPacks) || assetPacks.length === 0) {
165+
issues.push('No AssetPacks were synthesized to validate.');
166+
return issues;
167+
}
168+
const forbidden = [...protectedIpExclusions, ...obfuscatedPaths];
169+
const seenTitles = new Map<string, number>();
170+
171+
assetPacks.forEach((pack: any, index: number) => {
172+
const label = pack?.title ? `"${pack.title}"` : `#${index + 1}`;
173+
174+
if (!isNum01(pack?.confidence)) {
175+
issues.push(`AssetPack ${label} has a missing or out-of-range confidence (expected 0..1).`);
176+
}
177+
178+
const measurements = pack?.measurements;
179+
if (!measurements || typeof measurements !== 'object' || Array.isArray(measurements)) {
180+
issues.push(`AssetPack ${label} is missing its 0..1 measurements object.`);
181+
} else {
182+
for (const [key, value] of Object.entries(measurements)) {
183+
if (!isNum01(value)) {
184+
issues.push(`AssetPack ${label} measurement "${key}" is not an honest 0..1 volume.`);
185+
}
186+
}
187+
}
188+
189+
const coveredPaths = asPathList(pack?.coveredSourcePaths);
190+
if (coveredPaths.length === 0) {
191+
issues.push(`AssetPack ${label} declares no coveredSourcePaths.`);
192+
}
193+
194+
const fileChanges = pack?.patch?.fileChanges;
195+
if (!Array.isArray(fileChanges) || fileChanges.length === 0) {
196+
issues.push(`AssetPack ${label} has no patch descriptor with fileChanges (patch coherence).`);
197+
} else if (typeof pack?.patch?.patchSummary !== 'string' || pack.patch.patchSummary.length === 0) {
198+
issues.push(`AssetPack ${label} patch descriptor is missing a source-safe patchSummary.`);
199+
}
200+
201+
// Obfuscation/exclusion compliance over covered paths and patch fileChange paths.
202+
const patchPaths = Array.isArray(fileChanges)
203+
? fileChanges.map((fc: any) => fc?.path).filter((p: any): p is string => typeof p === 'string')
204+
: [];
205+
for (const path of [...coveredPaths, ...patchPaths]) {
206+
const hit = forbidden.find((entry) => pathViolates(path, entry));
207+
if (hit) {
208+
issues.push(`AssetPack ${label} touches withheld path "${path}" (violates exclusion/obfuscation "${hit}").`);
209+
}
210+
}
211+
212+
if (typeof pack?.title === 'string' && pack.title.length > 0) {
213+
const norm = pack.title.trim().toLowerCase();
214+
seenTitles.set(norm, (seenTitles.get(norm) ?? 0) + 1);
215+
}
216+
});
217+
218+
for (const [title, count] of seenTitles) {
219+
if (count > 1) {
220+
issues.push(`AssetPacks are not distinct: ${count} packs share the title "${title}".`);
221+
}
222+
}
223+
224+
return issues;
225+
}
226+
227+
function dedupe(values: string[]): string[] {
228+
return Array.from(new Set(values.filter((v) => typeof v === 'string' && v.length > 0)));
229+
}
230+
231+
export default async function runDepositValidationAgent(input: any, execution: any) {
232+
const assetPacks =
233+
input?.assetPacks ??
234+
findValue(execution, 'implementation', 'options') ??
235+
findValue(execution, 'implementation', 'assetPacks') ??
236+
[];
237+
const inventory = input?.inventory ?? findValue(execution, 'deposit', 'inventory');
238+
const obfuscationGuidance =
239+
input?.obfuscationGuidance ?? findValue(execution, 'setup', 'inputComprehension');
240+
const protectedIpExclusions = asPathList(
241+
input?.protectedIpExclusions ?? findValue(execution, 'deposit', 'protectedIpExclusions') ?? [],
242+
);
243+
const obfuscatedPaths = asPathList((obfuscationGuidance as any)?.obfuscatedPaths);
244+
const packs = Array.isArray(assetPacks) ? assetPacks : [];
245+
246+
const agentOutput = await DepositValidationAgent(
247+
{
248+
...input,
249+
assetPacks: packs,
250+
inventory,
251+
inventoryPaths: inventory?.paths,
252+
obfuscationGuidance,
253+
protectedIpExclusions,
254+
},
255+
execution,
256+
);
257+
258+
// Deterministic smoke/sanity checks ground the model's qualitative pass.
259+
const smokeIssues = smokeCheckAssetPacks(packs, protectedIpExclusions, obfuscatedPaths);
260+
261+
// Default-fallback to a clean "complete" verdict when the agent returns nothing;
262+
// the deterministic smoke issues are always merged so the gate stays grounded.
263+
const structured =
264+
agentOutput && typeof agentOutput === 'object' && Array.isArray((agentOutput as any).issues);
265+
const base: DepositValidationResult = structured
266+
? {
267+
issues: Array.isArray((agentOutput as any).issues) ? (agentOutput as any).issues : [],
268+
qualityScore: isNum01((agentOutput as any).qualityScore) ? (agentOutput as any).qualityScore : 1,
269+
coverageGaps: Array.isArray((agentOutput as any).coverageGaps) ? (agentOutput as any).coverageGaps : [],
270+
recommendation: (agentOutput as any).recommendation === 'iterate' ? 'iterate' : 'complete',
271+
}
272+
: { issues: [], qualityScore: 1, coverageGaps: [], recommendation: 'complete' };
273+
274+
const issues = dedupe([...base.issues, ...smokeIssues]);
275+
const result: DepositValidationResult = {
276+
issues,
277+
qualityScore: base.qualityScore,
278+
coverageGaps: base.coverageGaps,
279+
// Any concrete issue (qualitative or smoke) forces an iterate verdict.
280+
recommendation: issues.length > 0 ? 'iterate' : base.recommendation,
281+
};
282+
283+
try {
284+
// Feed the AssetPack ReadyToFinish gate: it reads validation/implementation:issues
285+
// (a bare string[]) to decide finish vs. review. Match that store exactly.
286+
execution.store('validation/implementation', 'issues', result.issues);
287+
// Record the full deposit-quality verdict for telemetry / the /deposit surface.
288+
execution.store('validation', 'depositQuality', result);
289+
} catch {}
290+
291+
return { ...(input || {}), ...result };
292+
}

packages/pipelines/asset-pack/src/phases/index.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -99,10 +99,18 @@ export const validationPhase: PhaseDelegator<ImplementationOutput, ValidationOut
9999
// Sequential: validators -> ReadyToFinish
100100
const readyToFinish = createAgentExecutor('validation:asset-pack-ready-to-finish-agent');
101101

102-
const exec: Executor<any, any> = sequential(
103-
parallelValidators as any,
104-
readyToFinish // Final go/no-go before Finish
105-
);
102+
// Deposit lens: validate the synthesized AssetPacks for quality (the deposit
103+
// validator writes validation/implementation:issues), then the same ReadyToFinish
104+
// gate. Read lens keeps the canonical parallel validators -> ReadyToFinish.
105+
const exec: Executor<any, any> = mode === 'deposit'
106+
? sequential(
107+
createAgentExecutor('validation:deposit-quality'),
108+
readyToFinish // Final go/no-go before Finish
109+
)
110+
: sequential(
111+
parallelValidators as any,
112+
readyToFinish // Final go/no-go before Finish
113+
);
106114
return await exec(input, execution);
107115
}) as unknown as PhaseDelegator<ImplementationOutput, ValidationOutput>;
108116

packages/pipelines/asset-pack/src/phases/validation.ts

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import { createPhaseRunner, PhaseConfig } from '@bitcode/pipelines-generics';
2-
import { registerValidationAgentsForType as registerAgents } from '../agents/validation-agents';
2+
import {
3+
registerValidationAgentsForType as registerAgents,
4+
AssetPackValidationReadyToFinishAgent,
5+
} from '../agents/validation-agents';
36
import { AssetPackWrittenAssetType } from '../types/AssetPackWrittenAssetType';
47
import type { SynthesizeAssetPacksMode } from '../synthesize-asset-packs';
58

@@ -25,9 +28,21 @@ export const runValidationPhase = createPhaseRunner(validationPhaseConfig);
2528
export function registerValidationAgentsForType(
2629
writtenAssetType: string,
2730
agentRegistry: any,
28-
// mode drives conditional registration: deposit validates synthesized AP
29-
// patches for deposit review; read validates fits artifacts. Shared for now.
30-
_mode?: SynthesizeAssetPacksMode,
31+
// mode drives conditional registration: deposit validates the synthesized AP
32+
// patches for deposit review; read validates fits artifacts.
33+
mode?: SynthesizeAssetPacksMode,
3134
): void {
35+
if (mode === 'deposit') {
36+
// Deposit lens: a single quality validator over the synthesized AssetPacks,
37+
// then the canonical ReadyToFinish gate (it reads validation/implementation:issues).
38+
agentRegistry.registerAgent('validation:deposit-quality', () =>
39+
import('../agents/validation/deposit-validation-agent').then(m => m.default),
40+
);
41+
agentRegistry.registerAgent(
42+
'validation:asset-pack-ready-to-finish-agent',
43+
AssetPackValidationReadyToFinishAgent,
44+
);
45+
return;
46+
}
3247
registerAgents(writtenAssetType || AssetPackWrittenAssetType.ReadSatisfactionAssetPack, agentRegistry);
3348
}

0 commit comments

Comments
 (0)