Skip to content

Commit 5a9b758

Browse files
wip v28
1 parent a2c3f6c commit 5a9b758

11 files changed

Lines changed: 202 additions & 14 deletions

File tree

BITCODE_SPEC_V28.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,8 @@ The V28 pipeline harness must write a manifest before execution containing:
410410

411411
- Read id and Read text;
412412
- Deposit id and optional deposited AssetPack id;
413+
- optional Deposit proof root, measurement root, and reconciliation readback
414+
root when the deposited evidence already has them;
413415
- repository full name, branch, and commit;
414416
- host capability summary;
415417
- expected stages: deposit search, candidate ranking, Read comprehension,
@@ -461,7 +463,11 @@ Read/Fit result review remains fail-closed:
461463
unless the global real-inference flag is present.
462464
- manifest-only Deposit supply can satisfy candidate recall but cannot produce
463465
`worthy_fit` unless proof and measurement posture are explicitly visible to
464-
the pipeline input and the downstream readback queries confirm them.
466+
the pipeline input and the downstream readback queries confirm them. Boolean
467+
wallet/measurement posture is not enough when the Read asks for proof-root,
468+
finality, or reconciliation readback; those roots must be supplied by the
469+
deposited evidence, synthesized and read back during Finish, or returned as
470+
explicit `blocked_readiness`.
465471
- every SDIVF phase, PTRR agent step, ThriceifiedGeneration, and tool execution
466472
must be inspectable as prompt/context input, raw model/tool output,
467473
parsed/typed output, usage/timing metadata, and phase/agent/step/failsafe

BITCODE_V28_QA.md

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1209,7 +1209,10 @@ curl -N "$BITCODE_UAPI_URL/api/pipeline-harness/asset-pack" \
12091209
"depositId": "3f68d845-d910-41ef-835a-89cf0103ac0a",
12101210
"depositAssetId": "asset_repository-revision-deposit-engineeredsoftware-engi_aece31322e",
12111211
"depositHasWalletOrAttestationProof": true,
1212-
"depositHasAssetMeasurementEvidence": true
1212+
"depositHasAssetMeasurementEvidence": true,
1213+
"depositProofRoot": "sha256:<proof-root-when-known>",
1214+
"depositMeasurementRoot": "sha256:<measurement-root-when-known>",
1215+
"depositReconciliationReadbackRoot": "sha256:<readback-root-when-known>"
12131216
}'
12141217
```
12151218
@@ -1353,6 +1356,24 @@ Later staging-testnet evidence on 2026-05-17 after the no-overlay deployment:
13531356
future failures show the same phase/agent/step/generation metadata operators
13541357
already use for persisted Bitcode activity.
13551358
1359+
Follow-up local Vercel Sandbox overlay evidence on 2026-05-17:
1360+
1361+
- Vercel Sandbox run `sbx_GLXzpbkAjP6sIceauReWGdp0GWTE` exported artifacts to
1362+
`.bitcode/pipeline-harness-runs/2026-05-17T20-08-16-548Z-sbx_GLXzpbkAjP6sIceauReWGdp0GWTE/`.
1363+
- The run progressed past setup risk admission and into real model-backed
1364+
discovery, exporting 3237 telemetry lines with prompt/context, response,
1365+
usage, phase, agent, step, failsafe, and generation correlation.
1366+
- The run failed closed with `resultState='blocked_readiness'` because
1367+
`applyResearchApproachSemanticMirrors` assumed `approach.phases` existed on
1368+
the top-level return value, while full PTRR returns envelope-shaped typed
1369+
output. The V28 fix is to normalize PTRR envelopes for discovery mirrors
1370+
before reading semantic aliases.
1371+
- The run also exposed an overclaim risk: manifest-only Deposit evidence with
1372+
wallet/measurement booleans was being treated as enough for a Read requiring
1373+
proof/finality and reconciliation readback. V28 now requires proof-root and
1374+
reconciliation readback roots, or explicit blocked-readiness, before such a
1375+
candidate can become `worthy_fit`.
1376+
13561377
## 2026-05-13 Staging Deployment Readiness Gate
13571378
13581379
Purpose:

packages/pipeline-hosts/src/__tests__/asset-pack-harness.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,5 +145,6 @@ describe('asset-pack sandbox harness plan', () => {
145145
expect(source).toContain('settlementOwnershipBoundary');
146146
expect(source).toContain('depositor owns minted BTD range');
147147
expect(source).toContain('reader pays BTC fee');
148+
expect(source).toContain('verificationEvidence');
148149
});
149150
});

packages/pipeline-hosts/src/asset-pack-harness.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -471,6 +471,11 @@ function buildManifestDepositoryAsset(manifest) {
471471
const assetId = manifest.deposit?.assetId || manifest.deposit?.id || 'manifest-deposit-reference';
472472
const source = manifest.sourceRevision || {};
473473
const repositoryFullName = source.repositoryFullName || '';
474+
const verificationEvidence = {
475+
proofRoot: manifest.deposit?.proofRoot || null,
476+
measurementRoot: manifest.deposit?.measurementRoot || null,
477+
reconciliationReadbackRoot: manifest.deposit?.reconciliationReadbackRoot || null,
478+
};
474479
const text = [
475480
'Deposited repository revision',
476481
repositoryFullName,
@@ -504,6 +509,9 @@ function buildManifestDepositoryAsset(manifest) {
504509
},
505510
hasWalletOrAttestationProof: manifest.deposit?.hasWalletOrAttestationProof === true,
506511
hasAssetMeasurementEvidence: manifest.deposit?.hasAssetMeasurementEvidence === true,
512+
verificationEvidence: Object.values(verificationEvidence).some(Boolean)
513+
? verificationEvidence
514+
: null,
507515
};
508516
}
509517

packages/pipeline-hosts/src/dev/run-asset-pack-sandbox-harness.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,10 @@ async function main(): Promise<void> {
380380
assetId: process.env.BITCODE_SANDBOX_DEPOSIT_ASSET_ID || null,
381381
hasWalletOrAttestationProof: process.env.BITCODE_SANDBOX_DEPOSIT_HAS_PROOF === '1',
382382
hasAssetMeasurementEvidence: process.env.BITCODE_SANDBOX_DEPOSIT_HAS_MEASUREMENT === '1',
383+
proofRoot: process.env.BITCODE_SANDBOX_DEPOSIT_PROOF_ROOT || null,
384+
measurementRoot: process.env.BITCODE_SANDBOX_DEPOSIT_MEASUREMENT_ROOT || null,
385+
reconciliationReadbackRoot:
386+
process.env.BITCODE_SANDBOX_DEPOSIT_RECONCILIATION_READBACK_ROOT || null,
383387
},
384388
sourceRevision: {
385389
repositoryFullName,

packages/pipeline-hosts/src/types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,9 @@ export interface PipelineDepositReference {
8282
assetId?: string | null;
8383
hasWalletOrAttestationProof?: boolean;
8484
hasAssetMeasurementEvidence?: boolean;
85+
proofRoot?: string | null;
86+
measurementRoot?: string | null;
87+
reconciliationReadbackRoot?: string | null;
8588
}
8689

8790
export interface PipelineHarnessManifest {

packages/pipelines/asset-pack/src/__tests__/depository-search.test.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,10 @@ describe('AssetPack depository search', () => {
116116
signingSurface: null,
117117
githubBoundary: null,
118118
attestations: [],
119+
verificationEvidence: {
120+
measurementRoot: 'sha256:test-measurement-root',
121+
reconciliationReadbackRoot: 'sha256:test-reconciliation-readback-root',
122+
},
119123
hasWalletOrAttestationProof: false,
120124
}),
121125
],
@@ -134,6 +138,10 @@ describe('AssetPack depository search', () => {
134138
asset({
135139
assetMeasurement: null,
136140
measurementProvenance: [],
141+
verificationEvidence: {
142+
proofRoot: 'sha256:test-proof-root',
143+
reconciliationReadbackRoot: 'sha256:test-reconciliation-readback-root',
144+
},
137145
hasAssetMeasurementEvidence: false,
138146
}),
139147
],

packages/pipelines/asset-pack/src/__tests__/discovery-semantic-mirrors.test.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,37 @@ describe('AssetPack discovery semantic mirrors', () => {
5151
expect(output.approach.phases[0].writtenAssets).toEqual(['draft pull request']);
5252
});
5353

54+
it('normalizes PTRR envelope research outputs before mirroring', () => {
55+
const output = applyResearchApproachSemanticMirrors({
56+
output: {
57+
approach: {
58+
methodology: 'parallel analysis',
59+
phases: [
60+
{
61+
name: 'finish',
62+
description: 'materialize the AssetPack evidence',
63+
assetPackSynthesisArtifacts: ['source patch evidence'],
64+
},
65+
],
66+
tools: ['git'],
67+
estimatedEffort: 'medium',
68+
},
69+
alternatives: [],
70+
risks: [],
71+
recommendation: 'finish with an AssetPack',
72+
},
73+
} as any);
74+
75+
expect(output.approach.phases[0].writtenAssets).toEqual(['source patch evidence']);
76+
});
77+
78+
it('fails softly when PTRR research output omits approach phases', () => {
79+
const output = applyResearchApproachSemanticMirrors({ output: { recommendation: 'continue' } } as any);
80+
81+
expect(output.approach.phases).toEqual([]);
82+
expect(output.recommendation).toBe('continue');
83+
});
84+
5485
it('mirrors definition of read into read satisfaction criteria', () => {
5586
const output = applyPlanImplementationSemanticMirrors({
5687
implementationPlan: {
@@ -89,4 +120,25 @@ describe('AssetPack discovery semantic mirrors', () => {
89120

90121
expect(output.readSatisfactionCriteria).toEqual(['asset pack accepted']);
91122
});
123+
124+
it('normalizes PTRR envelope plan outputs before mirroring', () => {
125+
const output = applyPlanImplementationSemanticMirrors({
126+
finalOutput: {
127+
implementationPlan: {
128+
overview: 'deliver the requested change',
129+
milestones: [],
130+
dependencies: [],
131+
},
132+
testingStrategy: {
133+
approach: 'targeted regression',
134+
testTypes: ['unit'],
135+
coverage: 'focused',
136+
},
137+
validationCriteria: ['tests pass'],
138+
definitionOfRead: ['asset pack accepted'],
139+
},
140+
} as any);
141+
142+
expect(output.readSatisfactionCriteria).toEqual(['asset pack accepted']);
143+
});
92144
});

packages/pipelines/asset-pack/src/agents/discovery-agents.ts

Lines changed: 88 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -195,23 +195,67 @@ const ResearchApproachOutputSchema = z.object({
195195
recommendation: z.string()
196196
});
197197

198+
function pickStructuredOutput<T>(rawOutput: T, expectedKeys: string[]): any {
199+
const envelopeCandidates = [
200+
(rawOutput as any)?.output,
201+
(rawOutput as any)?.finalOutput,
202+
(rawOutput as any)?.processedResult,
203+
(rawOutput as any)?.result,
204+
].filter((candidate) => candidate && typeof candidate === 'object');
205+
const candidates = [
206+
rawOutput,
207+
...envelopeCandidates,
208+
];
209+
const match = candidates.find((candidate) =>
210+
candidate &&
211+
typeof candidate === 'object' &&
212+
expectedKeys.some((key) => Object.prototype.hasOwnProperty.call(candidate, key))
213+
);
214+
return match || envelopeCandidates[0] || rawOutput;
215+
}
216+
217+
function normalizeResearchApproachOutput(
218+
rawOutput: z.infer<typeof ResearchApproachOutputSchema>
219+
): z.infer<typeof ResearchApproachOutputSchema> {
220+
const output = pickStructuredOutput(rawOutput, ['approach']);
221+
const approach = output?.approach && typeof output.approach === 'object' ? output.approach : {};
222+
return {
223+
approach: {
224+
methodology: typeof approach.methodology === 'string' ? approach.methodology : '',
225+
phases: Array.isArray(approach.phases) ? approach.phases : [],
226+
tools: Array.isArray(approach.tools) ? approach.tools : [],
227+
estimatedEffort: typeof approach.estimatedEffort === 'string' ? approach.estimatedEffort : '',
228+
},
229+
alternatives: Array.isArray(output?.alternatives) ? output.alternatives : [],
230+
risks: Array.isArray(output?.risks) ? output.risks : [],
231+
recommendation: typeof output?.recommendation === 'string' ? output.recommendation : '',
232+
};
233+
}
234+
198235
export function applyResearchApproachSemanticMirrors(
199-
output: z.infer<typeof ResearchApproachOutputSchema>
236+
rawOutput: z.infer<typeof ResearchApproachOutputSchema>
200237
): z.infer<typeof ResearchApproachOutputSchema> {
238+
const output = normalizeResearchApproachOutput(rawOutput);
201239
return {
202240
...output,
203241
approach: {
204242
...output.approach,
205-
phases: output.approach.phases.map((phase) => ({
206-
...phase,
207-
writtenAssets:
208-
phase.writtenAssets ??
209-
phase.assetPackSynthesisArtifacts ??
210-
phase.shippables,
211-
assetPackSynthesisArtifacts:
212-
phase.assetPackSynthesisArtifacts ??
213-
phase.writtenAssets,
214-
})),
243+
phases: output.approach.phases.map((phase) => {
244+
const normalizedPhase: any =
245+
phase && typeof phase === 'object'
246+
? phase
247+
: { name: String(phase || ''), description: '' };
248+
return {
249+
...normalizedPhase,
250+
writtenAssets:
251+
normalizedPhase.writtenAssets ??
252+
normalizedPhase.assetPackSynthesisArtifacts ??
253+
normalizedPhase.shippables,
254+
assetPackSynthesisArtifacts:
255+
normalizedPhase.assetPackSynthesisArtifacts ??
256+
normalizedPhase.writtenAssets,
257+
};
258+
}),
215259
},
216260
};
217261
}
@@ -327,9 +371,41 @@ const PlanImplementationOutputSchema = z.object({
327371
readSatisfactionCriteria: z.array(z.string()).optional()
328372
});
329373

374+
function normalizePlanImplementationOutput(
375+
rawOutput: z.infer<typeof PlanImplementationOutputSchema>
376+
): z.infer<typeof PlanImplementationOutputSchema> {
377+
const output = pickStructuredOutput(rawOutput, ['implementationPlan']);
378+
const implementationPlan =
379+
output?.implementationPlan && typeof output.implementationPlan === 'object'
380+
? output.implementationPlan
381+
: {};
382+
const testingStrategy =
383+
output?.testingStrategy && typeof output.testingStrategy === 'object'
384+
? output.testingStrategy
385+
: {};
386+
return {
387+
implementationPlan: {
388+
overview: typeof implementationPlan.overview === 'string' ? implementationPlan.overview : '',
389+
milestones: Array.isArray(implementationPlan.milestones) ? implementationPlan.milestones : [],
390+
dependencies: Array.isArray(implementationPlan.dependencies) ? implementationPlan.dependencies : [],
391+
},
392+
testingStrategy: {
393+
approach: typeof testingStrategy.approach === 'string' ? testingStrategy.approach : '',
394+
testTypes: Array.isArray(testingStrategy.testTypes) ? testingStrategy.testTypes : [],
395+
coverage: typeof testingStrategy.coverage === 'string' ? testingStrategy.coverage : '',
396+
},
397+
validationCriteria: Array.isArray(output?.validationCriteria) ? output.validationCriteria : [],
398+
definitionOfRead: Array.isArray(output?.definitionOfRead) ? output.definitionOfRead : undefined,
399+
readSatisfactionCriteria: Array.isArray(output?.readSatisfactionCriteria)
400+
? output.readSatisfactionCriteria
401+
: undefined,
402+
};
403+
}
404+
330405
export function applyPlanImplementationSemanticMirrors(
331-
output: z.infer<typeof PlanImplementationOutputSchema>
406+
rawOutput: z.infer<typeof PlanImplementationOutputSchema>
332407
): z.infer<typeof PlanImplementationOutputSchema> {
408+
const output = normalizePlanImplementationOutput(rawOutput);
333409
return {
334410
...output,
335411
readSatisfactionCriteria: output.readSatisfactionCriteria ?? output.definitionOfRead,

uapi/app/api/pipeline-harness/asset-pack/route.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ type AssetPackHarnessRequest = {
2323
depositAssetId?: string | null;
2424
depositHasWalletOrAttestationProof?: boolean;
2525
depositHasAssetMeasurementEvidence?: boolean;
26+
depositProofRoot?: string | null;
27+
depositMeasurementRoot?: string | null;
28+
depositReconciliationReadbackRoot?: string | null;
2629
repositoryFullName?: string;
2730
sourceBranch?: string;
2831
sourceCommit?: string;
@@ -529,6 +532,9 @@ export async function POST(request: NextRequest): Promise<Response> {
529532
assetId: body.depositAssetId || null,
530533
hasWalletOrAttestationProof: body.depositHasWalletOrAttestationProof,
531534
hasAssetMeasurementEvidence: body.depositHasAssetMeasurementEvidence,
535+
proofRoot: body.depositProofRoot || null,
536+
measurementRoot: body.depositMeasurementRoot || null,
537+
reconciliationReadbackRoot: body.depositReconciliationReadbackRoot || null,
532538
},
533539
sourceRevision: {
534540
repositoryFullName,

0 commit comments

Comments
 (0)