Skip to content

Commit 03829b0

Browse files
V48 Gate 3 (implementation-only): #25 route dispatches deposit synthesis to the sandbox host in-box
Closes #25. The deposit route now branches on the configured HostKind: InlineHost runs the synthesis in this box (provision + pipeline in-process, unchanged); SandboxHost runs it IN the box via runDepositInBoxHarness — builds an asset-pack harness in deposit mode (git source + steering), dispatches it on VercelSandboxPipelineHost.runHarness (the pipeline executes in the box over its local checkout), and reads the synthesized options from evidence.depositOptions. Both branches then run the SAME pure projection (validateDepositSynthesisOptions + buildRealDepositAssetPackOptionSynthesis) and persist — identical result. - runDepositInBoxHarness (deposit-source-provisioning.ts): builds the deposit harness + dispatches (injectable host for tests) + returns evidence.depositOptions. - route: hostKind branch; the sandbox path rebuilds a minimal inventory from the returned options' covered paths (exclusions already enforced in-box) for the projection/persistence. Tests: runDepositInBoxHarness (deposit-mode plan + steering + git source; empty evidence -> []); the route sandbox-branch test (dispatches in-box, pipeline+provision NOT called, persists the decision payload). Fixed a mock-isolation leak (reset queued .once() impls in beforeEach). uapi deposit suites 3/14 green; tsc clean. Real-sandbox execution is verified against deployed sandbox infra; the dispatch + projection are unit-tested with a mocked host. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 893f017 commit 03829b0

4 files changed

Lines changed: 239 additions & 53 deletions

File tree

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

Lines changed: 90 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,16 @@ import {
1212
normalizeProtectedIpExclusions,
1313
validateDepositSynthesisOptions,
1414
type AssetPacksSynthesisResult,
15+
type AssetPacksSynthesisSourceInventory,
1516
} from '@bitcode/pipeline-asset-pack/asset-packs-synthesis';
1617
import { synthesizeAssetPacksPipeline } from '@bitcode/pipeline-asset-pack';
1718
import { buildRealDepositAssetPackOptionSynthesis } from '@bitcode/pipeline-asset-pack/deposit-option-real-synthesis';
1819
import { isAssetPackRealInferenceEnabled } from '@bitcode/pipeline-asset-pack/runtime-inference-policy';
1920
import {
2021
provisionDepositSourceInventory,
2122
resolveDepositPipelineHost,
23+
runDepositInBoxHarness,
24+
selectDepositHostKind,
2225
} from '@/lib/deposit-source-provisioning';
2326
import {
2427
bitcodeServerTelemetry,
@@ -203,66 +206,101 @@ export async function POST(request: Request) {
203206
supabaseAdmin,
204207
);
205208
const reference = sourceCommit || sourceBranch || 'HEAD';
206-
// Provision the FULL repository checkout on the primitive Host (InlineHost
207-
// in-process; the Vercel Sandbox host in prod). The static-analysis measurement
208-
// reads the real full source (inventory.sources); bounded samples feed the
209-
// prompts. This is the host's job — the dispatching request does not clone.
210-
const host = await resolveDepositPipelineHost();
211-
await emitStatus(
212-
`Provisioning ${repositoryFullName}@${reference} on the ${host.capabilities.hostKind} host…`,
213-
);
214-
const provisioned = await provisionDepositSourceInventory({
215-
host,
216-
repositoryFullName,
217-
url: `https://github.com/${repositoryFullName}.git`,
218-
revision: reference,
219-
token: auth.accessToken,
220-
});
221-
const inventory = applyExclusionsToInventory(provisioned, protectedIpExclusions);
222-
await emitStatus(
223-
`Checkout ready: ${inventory.paths.length} files (${inventory.excludedPathCount} withheld by ${protectedIpExclusions.length} protected-IP exclusions; full source measured, ${inventory.samples.length} prompt excerpts).`,
224-
);
225-
226209
const DEPOSIT_OPTION_KINDS = ['capability-slice', 'implementation-pattern', 'proof-operations-slice'];
210+
// The synthesis runs WITHIN the configured Host. InlineHost runs it in this box
211+
// (provision the full checkout + run the pipeline in-process). SandboxHost runs it
212+
// IN the box (#25): the harness dispatches the deposit pipeline into the sandbox,
213+
// which clones + runs over its local checkout; the options come back in evidence.
214+
const hostKind = selectDepositHostKind();
215+
let rawOptions: Parameters<typeof validateDepositSynthesisOptions>[0];
216+
let inventoryPaths: string[];
217+
// The inventory the option projection + persistence reference. Real for inline;
218+
// for the sandbox path the box held the inventory, so a minimal shape is rebuilt
219+
// from the returned options (exclusions were already enforced in-box).
220+
let inventory: AssetPacksSynthesisSourceInventory;
227221

228-
// The full SynthesizeAssetPacks SDIVF pipeline (deposit mode) is the ONLY deposit
229-
// synthesis path: Setup → Discovery → Implementation → Validation → Finish,
230-
// streaming the rich phase→agent→step→failsafe→generation telemetry. Validation
231-
// runs the formal absolutes measurement — it is never skipped.
232-
await emitStatus(
233-
'Running SynthesizeAssetPacks (deposit mode): Setup → Discovery → Implementation → Validation → Finish…',
234-
);
235-
const [owner, name] = repositoryFullName.split('/');
236-
await synthesizeAssetPacksPipeline(
237-
{
238-
mode: 'deposit',
239-
synthesizeMode: 'deposit',
222+
if (hostKind === 'sandbox') {
223+
await emitStatus(
224+
`Dispatching deposit synthesis to the sandbox host (in-box) for ${repositoryFullName}@${reference}…`,
225+
);
226+
rawOptions = (await runDepositInBoxHarness({
240227
repositoryFullName,
241-
sourceBranch,
242-
sourceCommit,
243-
repository: {
244-
owner,
245-
name,
246-
repo: name,
247-
branch: sourceBranch,
248-
commit: sourceCommit,
249-
fullName: repositoryFullName,
250-
url: `https://github.com/${repositoryFullName}`,
251-
},
228+
revision: reference,
229+
branch: sourceBranch,
230+
commit: sourceCommit,
231+
token: auth.accessToken,
252232
obfuscations,
253233
protectedIpExclusions,
254234
demandContext,
255-
inventory,
256-
candidateKinds: DEPOSIT_OPTION_KINDS,
257-
} as never,
258-
execution as never,
259-
);
260-
const rawOptions = ((execution as any).get?.('implementation', 'options') ??
261-
(execution as any).findUp?.('implementation', 'options') ??
262-
[]) as Parameters<typeof validateDepositSynthesisOptions>[0];
235+
onEvent: (event) => {
236+
void emitStatus(`sandbox: ${event.type}`);
237+
},
238+
})) as Parameters<typeof validateDepositSynthesisOptions>[0];
239+
// The in-box run validated covered paths against the real inventory; the route's
240+
// re-validation enforces exclusions over the options' own covered paths.
241+
inventoryPaths = [
242+
...new Set((rawOptions || []).flatMap((option: any) => option?.coveredSourcePaths || [])),
243+
] as string[];
244+
inventory = {
245+
paths: inventoryPaths,
246+
samples: [],
247+
sources: [],
248+
totalPathCount: inventoryPaths.length,
249+
excludedPathCount: 0,
250+
};
251+
} else {
252+
const host = await resolveDepositPipelineHost();
253+
await emitStatus(
254+
`Provisioning ${repositoryFullName}@${reference} on the ${host.capabilities.hostKind} host…`,
255+
);
256+
const provisioned = await provisionDepositSourceInventory({
257+
host,
258+
repositoryFullName,
259+
url: `https://github.com/${repositoryFullName}.git`,
260+
revision: reference,
261+
token: auth.accessToken,
262+
});
263+
inventory = applyExclusionsToInventory(provisioned, protectedIpExclusions);
264+
await emitStatus(
265+
`Checkout ready: ${inventory.paths.length} files (${inventory.excludedPathCount} withheld by ${protectedIpExclusions.length} protected-IP exclusions; full source measured, ${inventory.samples.length} prompt excerpts).`,
266+
);
267+
await emitStatus(
268+
'Running SynthesizeAssetPacks (deposit mode): Setup → Discovery → Implementation → Validation → Finish…',
269+
);
270+
const [owner, name] = repositoryFullName.split('/');
271+
await synthesizeAssetPacksPipeline(
272+
{
273+
mode: 'deposit',
274+
synthesizeMode: 'deposit',
275+
repositoryFullName,
276+
sourceBranch,
277+
sourceCommit,
278+
repository: {
279+
owner,
280+
name,
281+
repo: name,
282+
branch: sourceBranch,
283+
commit: sourceCommit,
284+
fullName: repositoryFullName,
285+
url: `https://github.com/${repositoryFullName}`,
286+
},
287+
obfuscations,
288+
protectedIpExclusions,
289+
demandContext,
290+
inventory,
291+
candidateKinds: DEPOSIT_OPTION_KINDS,
292+
} as never,
293+
execution as never,
294+
);
295+
rawOptions = ((execution as any).get?.('implementation', 'options') ??
296+
(execution as any).findUp?.('implementation', 'options') ??
297+
[]) as Parameters<typeof validateDepositSynthesisOptions>[0];
298+
inventoryPaths = inventory.paths;
299+
}
300+
263301
const validated = validateDepositSynthesisOptions(rawOptions, {
264302
lens: 'deposit',
265-
inventoryPaths: inventory.paths,
303+
inventoryPaths,
266304
protectedIpExclusions,
267305
candidateKinds: DEPOSIT_OPTION_KINDS,
268306
});

uapi/lib/deposit-source-provisioning.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,15 @@
1616

1717
import {
1818
InlineHost,
19+
VercelSandboxPipelineHost,
20+
buildAssetPackSandboxHarness,
21+
loadVercelSandboxFactory,
1922
readWorkspaceSources,
2023
type BitcodeHostKind,
2124
type BitcodePipelineHost,
2225
type HostSourceFile,
26+
type PipelineHarnessHostEvent,
27+
type PipelineHarnessRunResult,
2328
} from '@bitcode/pipeline-hosts';
2429

2530
export interface ProvisionedDepositInventory {
@@ -87,6 +92,65 @@ export async function resolveDepositPipelineHost(): Promise<BitcodePipelineHost>
8792
return new InlineHost();
8893
}
8994

95+
/** A host that can run a harness plan (the VercelSandboxPipelineHost shape). */
96+
export interface DepositInBoxHarnessHost {
97+
runHarness(plan: unknown): Promise<PipelineHarnessRunResult>;
98+
}
99+
100+
/**
101+
* Run the deposit synthesis IN the sandbox box (#25). Builds an asset-pack harness in
102+
* DEPOSIT mode (git source for the revision + steering), dispatches it on the sandbox
103+
* host (the pipeline runs in the box, reading its local checkout), and returns the
104+
* synthesized options surfaced in the evidence (`depositOptions`). The host is
105+
* injectable so the dispatch is unit-tested without a real sandbox.
106+
*/
107+
export async function runDepositInBoxHarness(input: {
108+
repositoryFullName: string;
109+
revision: string;
110+
branch: string | null;
111+
commit: string | null;
112+
token?: string;
113+
obfuscations: string | null;
114+
protectedIpExclusions: string[];
115+
demandContext: string[];
116+
onEvent?: (event: PipelineHarnessHostEvent) => void;
117+
hostFactory?: () => Promise<DepositInBoxHarnessHost>;
118+
}): Promise<unknown[]> {
119+
const plan = buildAssetPackSandboxHarness({
120+
mode: 'asset_pack_pipeline',
121+
synthesizeMode: 'deposit',
122+
read: { id: `deposit-read-${input.repositoryFullName}`, prompt: 'Deposit synthesis (no read need).' },
123+
deposit: { id: `deposit-${input.repositoryFullName}` },
124+
sourceRevision: {
125+
repositoryFullName: input.repositoryFullName,
126+
branch: input.branch || 'main',
127+
commit: input.commit || input.revision,
128+
},
129+
source: {
130+
type: 'git',
131+
url: `https://github.com/${input.repositoryFullName}.git`,
132+
revision: input.revision,
133+
username: input.token ? 'x-access-token' : undefined,
134+
password: input.token,
135+
depth: 1,
136+
},
137+
depositSteering: {
138+
obfuscations: input.obfuscations,
139+
protectedIpExclusions: input.protectedIpExclusions,
140+
demandContext: input.demandContext,
141+
},
142+
});
143+
const host = input.hostFactory
144+
? await input.hostFactory()
145+
: new VercelSandboxPipelineHost({
146+
sandboxFactory: await loadVercelSandboxFactory(),
147+
onEvent: input.onEvent,
148+
});
149+
const result = await host.runHarness(plan);
150+
const evidence = result?.artifacts?.evidence as { depositOptions?: unknown } | null;
151+
return evidence && Array.isArray(evidence.depositOptions) ? evidence.depositOptions : [];
152+
}
153+
90154
/**
91155
* Provision the full checkout on the Host and build the deposit inventory from it.
92156
* Reads every tracked file's verbatim content (`sources`, for measurement), derives

uapi/tests/api/depositSynthesizeOptionsRoute.test.ts

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,20 +46,28 @@ jest.mock('@bitcode/pipeline-asset-pack', () => ({
4646
jest.mock('@/lib/deposit-source-provisioning', () => ({
4747
resolveDepositPipelineHost: jest.fn(() => ({ capabilities: { hostKind: 'inline' } })),
4848
provisionDepositSourceInventory: jest.fn(),
49+
selectDepositHostKind: jest.fn(() => 'inline'),
50+
runDepositInBoxHarness: jest.fn(),
4951
}));
5052

5153
import { createClient } from '@bitcode/supabase/ssr/server';
5254
import { supabaseAdmin } from '@bitcode/supabase';
5355
import { createStreamingExecution } from '@bitcode/pipelines-generics';
5456
import { synthesizeAssetPacksPipeline } from '@bitcode/pipeline-asset-pack';
5557
import { isAssetPackRealInferenceEnabled } from '@bitcode/pipeline-asset-pack/runtime-inference-policy';
56-
import { provisionDepositSourceInventory } from '@/lib/deposit-source-provisioning';
58+
import {
59+
provisionDepositSourceInventory,
60+
runDepositInBoxHarness,
61+
selectDepositHostKind,
62+
} from '@/lib/deposit-source-provisioning';
5763
import { POST } from '@/app/api/deposit/synthesize-options/route';
5864

5965
const mockRealInference = isAssetPackRealInferenceEnabled as jest.Mock;
6066
const mockPipeline = synthesizeAssetPacksPipeline as jest.Mock;
6167
const mockCreateExecution = createStreamingExecution as jest.Mock;
6268
const mockProvision = provisionDepositSourceInventory as jest.Mock;
69+
const mockSelectKind = selectDepositHostKind as jest.Mock;
70+
const mockRunHarness = runDepositInBoxHarness as jest.Mock;
6371

6472
// The synthesized options the pipeline leaves at implementation:options. The route's
6573
// validateDepositSynthesisOptions (real) turns these into measured deposit options.
@@ -196,8 +204,13 @@ async function flushBackground(predicate: () => boolean, max = 50) {
196204
describe('POST /api/deposit/synthesize-options', () => {
197205
beforeEach(() => {
198206
jest.clearAllMocks();
207+
// Reset queued .once() implementations (clearAllMocks does not) so a pipeline/
208+
// harness mock queued by one test never leaks into the next.
209+
mockPipeline.mockReset();
210+
mockRunHarness.mockReset();
199211
mockRealInference.mockReturnValue(true);
200212
mockProvision.mockResolvedValue(PROVISIONED);
213+
mockSelectKind.mockReturnValue('inline');
201214
});
202215

203216
it('requires a session', async () => {
@@ -268,6 +281,30 @@ describe('POST /api/deposit/synthesize-options', () => {
268281
expect(completed.output.reviewProjections[0].coveredSourcePaths).toEqual(['README.md', 'src/app.py']);
269282
});
270283

284+
it('runs the synthesis in-box on the sandbox host when configured (#25)', async () => {
285+
const { executionRow } = installSupabaseMocks({});
286+
installExecutionMock();
287+
mockSelectKind.mockReturnValue('sandbox');
288+
mockRunHarness.mockResolvedValue(RAW_OPTIONS);
289+
290+
const response = await POST(createRequest());
291+
expect(response.status).toBe(200);
292+
293+
await flushBackground(() =>
294+
executionRow.upsert.mock.calls.some((call) => call[0]?.status === 'completed'),
295+
);
296+
// Dispatched to the in-box harness; the in-process pipeline + provisioning were NOT run.
297+
expect(mockRunHarness).toHaveBeenCalledTimes(1);
298+
expect(mockPipeline).not.toHaveBeenCalled();
299+
expect(mockProvision).not.toHaveBeenCalled();
300+
const completed = executionRow.upsert.mock.calls.find((call) => call[0]?.status === 'completed')![0];
301+
expect(completed.output.depositOptionSynthesis.optionCount).toBe(1);
302+
expect(completed.output.depositOptionSynthesis.options[0].contents.provenantSourcePaths).toEqual([
303+
'README.md',
304+
'src/app.py',
305+
]);
306+
});
307+
271308
it('persists a failed row when the background synthesis throws', async () => {
272309
const { executionRow } = installSupabaseMocks({});
273310
installExecutionMock({ failPipeline: true });

uapi/tests/lib/depositSourceProvisioning.test.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import {
55
provisionDepositSourceInventory,
66
resolveDepositPipelineHost,
7+
runDepositInBoxHarness,
78
selectDepositHostKind,
89
} from '@/lib/deposit-source-provisioning';
910
import type { BitcodeHostWorkspace, BitcodePipelineHost } from '@bitcode/pipeline-hosts';
@@ -108,3 +109,49 @@ describe('resolveDepositPipelineHost', () => {
108109
await expect(resolveDepositPipelineHost()).rejects.toThrow(/not yet wired/i);
109110
});
110111
});
112+
113+
describe('runDepositInBoxHarness (#25)', () => {
114+
it('dispatches a deposit-mode harness and returns evidence.depositOptions', async () => {
115+
let receivedPlan: any;
116+
const fakeHost = {
117+
runHarness: async (plan: any) => {
118+
receivedPlan = plan;
119+
return {
120+
artifacts: { evidence: { depositOptions: [{ title: 'Auth slice', coveredSourcePaths: ['src/auth.ts'] }] }, telemetry: null },
121+
outcome: 'completed',
122+
stopped: true,
123+
manifest: plan.manifest,
124+
commands: [],
125+
};
126+
},
127+
};
128+
const options = await runDepositInBoxHarness({
129+
repositoryFullName: 'engineeredsoftware/demo',
130+
revision: 'abc123',
131+
branch: 'main',
132+
commit: 'abc123',
133+
token: 'ghs_tok',
134+
obfuscations: 'hide internal names',
135+
protectedIpExclusions: ['secret/'],
136+
demandContext: ['auth'],
137+
hostFactory: async () => fakeHost,
138+
});
139+
140+
expect(options).toEqual([{ title: 'Auth slice', coveredSourcePaths: ['src/auth.ts'] }]);
141+
// The dispatched plan ran the deposit lens in-box, with a git source + steering.
142+
expect(receivedPlan.manifest.synthesizeMode).toBe('deposit');
143+
expect(receivedPlan.manifest.depositSteering).toMatchObject({ protectedIpExclusions: ['secret/'] });
144+
expect(receivedPlan.createOptions.source).toMatchObject({ type: 'git', revision: 'abc123' });
145+
});
146+
147+
it('returns [] when the evidence has no depositOptions', async () => {
148+
const fakeHost = {
149+
runHarness: async () => ({ artifacts: { evidence: {}, telemetry: null }, outcome: 'completed', stopped: true, manifest: {}, commands: [] }),
150+
};
151+
const options = await runDepositInBoxHarness({
152+
repositoryFullName: 'o/r', revision: 'main', branch: 'main', commit: null,
153+
obfuscations: null, protectedIpExclusions: [], demandContext: [], hostFactory: async () => fakeHost,
154+
});
155+
expect(options).toEqual([]);
156+
});
157+
});

0 commit comments

Comments
 (0)