Skip to content

Commit ee14816

Browse files
wip v28
1 parent 8ef218b commit ee14816

15 files changed

Lines changed: 348 additions & 19 deletions

BITCODE_SPEC_V28.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,11 @@ Read/Fit result review remains fail-closed:
470470
- artifact telemetry must subscribe to stream events even when database
471471
streaming is disabled; database persistence is an additional acceptance gate,
472472
not the only way to inspect a live failed run.
473+
- the deployed Terminal harness stream must expose sandbox id, run id, and
474+
incremental telemetry artifact lines while the detached sandbox command is
475+
still running. Browser-only Network inspection is not sufficient; the
476+
operator-visible stream must show phase, agent, generation/tool, parsed
477+
output, and failsafe context as first-class Read/Fit run evidence.
473478
- the harness must enforce an internal runtime budget before the caller's host
474479
timeout, producing a `blocked_readiness` artifact rather than allowing the
475480
host to terminate without evidence.

BITCODE_V28_QA.md

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -926,6 +926,10 @@ First-run execution boundary:
926926
and the last visible stream event summaries. A failing command with no
927927
prompt/context, raw output, parsed/cast output, or phase/agent correlation is
928928
not enough visibility for live staging-testnet debugging.
929+
- The Terminal live-run panel must show the active Read id, Deposit id,
930+
source commit, sandbox id when available, pipeline run id when available, and
931+
incremental `telemetry.jsonl` line summaries while the sandbox command is
932+
still running. A state visible only in browser Network logs fails V28 QA.
929933
- Depository vector recall uses `text-embedding-3-small` by default with
930934
`encoding_format='float'`, `dimensions=1536`, Supabase
931935
`deliverable_vectors.embedding vector(1536)`, `ivfflat`,
@@ -1233,11 +1237,14 @@ After either harness run:
12331237
messages when available, raw response content, parsed/cast output when the
12341238
generation was parsed, provider/model identity, usage tokens when supplied,
12351239
and phase/agent/step/failsafe/generation correlation.
1236-
4. Run saved query
1240+
4. Inspect the Terminal live-run panel for the same run id and sandbox id. The
1241+
panel must include host lifecycle events and incremental telemetry artifact
1242+
event summaries, not only the final harness completion payload.
1243+
5. Run saved query
12371244
`supabase/queries/v28_qa_terminal_07_pipeline_harness_after_fit.sql`.
1238-
5. Rerun `v28_qa_terminal_06_read_fit_quality_after_read` and
1245+
6. Rerun `v28_qa_terminal_06_read_fit_quality_after_read` and
12391246
`v28_qa_terminal_03_btd_ledger_after_terminal`.
1240-
6. Capture Vercel Sandbox dashboard/log evidence for the same timestamps.
1247+
7. Capture Vercel Sandbox dashboard/log evidence for the same timestamps.
12411248
12421249
Pass criteria:
12431250

packages/pipeline-hosts/README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,18 @@ agent, generation, or tool last produced input/output evidence.
5858
The runner subscribes to stream events for artifact telemetry even when database
5959
streaming is disabled; database streaming adds persistence, not basic event
6060
visibility.
61+
While the pipeline command runs detached, the host polls `telemetry.jsonl` and
62+
emits each new line as a `telemetry-artifact-event` so Terminal can show
63+
sandbox id, run id, phase, agent, generation/tool, parsed-output, and failsafe
64+
context before final artifact readback. Browser Network logs are not the
65+
operator interface for live Read/Fit debugging.
6166
The live runner also enforces `BITCODE_PIPELINE_HARNESS_MAX_RUNTIME_MS`
6267
(default `240000`) inside the sandbox so a heavyweight pipeline run records a
6368
blocked-readiness artifact before the calling Vercel Function or local harness
6469
process reaches its own timeout.
70+
`BITCODE_PIPELINE_HARNESS_CHECKPOINT_INTERVAL_MS` defaults to `2000` so the
71+
exported telemetry artifact stays close enough to the live runner for SSE
72+
tailing without writing on every internal event.
6573

6674
For live model execution, the sandbox runner also needs `OPENAI_API_KEY` in the
6775
trusted command environment. For local Vercel Sandbox creation, either pull

packages/pipeline-hosts/src/__tests__/vercel-sandbox-host.test.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,10 +154,16 @@ describe('VercelSandboxPipelineHost', () => {
154154

155155
it('polls detached command artifacts instead of relying on the command stream', async () => {
156156
const fakeSandbox = new DetachedFakeSandbox();
157+
const events: PipelineHarnessHostEvent[] = [];
157158
const factory: SandboxFactory = {
158159
create: async () => fakeSandbox,
159160
};
160-
const host = new VercelSandboxPipelineHost({ sandboxFactory: factory });
161+
const host = new VercelSandboxPipelineHost({
162+
sandboxFactory: factory,
163+
onEvent: (event) => {
164+
events.push(event);
165+
},
166+
});
161167
const plan = buildAssetPackSandboxHarness({
162168
read: {
163169
id: 'read-1',
@@ -194,6 +200,14 @@ describe('VercelSandboxPipelineHost', () => {
194200
stdout: 'detached stdout',
195201
stderr: '',
196202
});
203+
expect(events).toContainEqual(
204+
expect.objectContaining({
205+
type: 'telemetry-artifact-event',
206+
label: 'detached-run',
207+
lineNumber: 1,
208+
telemetryEvent: { type: 'harness-complete' },
209+
}),
210+
);
197211
});
198212

199213
it('passes access-token auth fields to Sandbox.create when OIDC is unavailable', async () => {

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ function buildCommands(
210210
stdoutPath: PIPELINE_STDOUT_PATH,
211211
stderrPath: PIPELINE_STDERR_PATH,
212212
maxWaitMs,
213-
pollIntervalMs: 5000,
213+
pollIntervalMs: 2000,
214214
required: true,
215215
});
216216

@@ -323,6 +323,7 @@ const DEFAULT_USER_ID = '00000000-0000-4000-8000-000000000000';
323323
const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
324324
const startedAt = new Date().toISOString();
325325
const harnessMaxRuntimeMs = Number(process.env.BITCODE_PIPELINE_HARNESS_MAX_RUNTIME_MS || 240000);
326+
const checkpointIntervalMs = Number(process.env.BITCODE_PIPELINE_HARNESS_CHECKPOINT_INTERVAL_MS || 2000);
326327
let manifest = null;
327328
let manifestRoot = null;
328329
let userId = process.env.BITCODE_PIPELINE_USER_ID || DEFAULT_USER_ID;
@@ -584,7 +585,7 @@ async function writeCheckpoint(reason) {
584585
585586
function scheduleCheckpoint(reason) {
586587
const now = Date.now();
587-
if (now - lastCheckpointAt < 15000) return;
588+
if (now - lastCheckpointAt < checkpointIntervalMs) return;
588589
lastCheckpointAt = now;
589590
checkpointInFlight = checkpointInFlight
590591
.catch(() => {})

packages/pipeline-hosts/src/types.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,14 @@ export type PipelineHarnessHostEvent =
268268
startedAt: string;
269269
completedAt: string;
270270
}
271+
| {
272+
type: 'telemetry-artifact-event';
273+
timestamp: string;
274+
label: string;
275+
telemetryPath: string;
276+
lineNumber: number;
277+
telemetryEvent: unknown;
278+
}
271279
| {
272280
type: 'artifacts-read';
273281
timestamp: string;

packages/pipeline-hosts/src/vercel-sandbox-host.ts

Lines changed: 61 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ export class VercelSandboxPipelineHost {
6262
});
6363

6464
for (const command of plan.commands) {
65-
const commandResult = await this.runCommand(sandbox, command);
65+
const commandResult = await this.runCommand(sandbox, command, plan.artifactPaths.telemetry);
6666
commands.push(commandResult);
6767

6868
if (command.required !== false && commandResult.exitCode !== 0) {
@@ -107,7 +107,8 @@ export class VercelSandboxPipelineHost {
107107

108108
private async runCommand(
109109
sandbox: SandboxSession,
110-
command: PipelineHarnessCommand
110+
command: PipelineHarnessCommand,
111+
telemetryPath?: string
111112
): Promise<PipelineHarnessCommandResult> {
112113
await this.emit({
113114
type: 'command-started',
@@ -132,7 +133,7 @@ export class VercelSandboxPipelineHost {
132133
detached: command.detached,
133134
});
134135
if (command.detached) {
135-
const detachedResult = await this.waitForDetachedCommand(sandbox, command);
136+
const detachedResult = await this.waitForDetachedCommand(sandbox, command, telemetryPath);
136137
exitCode = detachedResult.exitCode;
137138
stdout = detachedResult.stdout;
138139
stderr = detachedResult.stderr;
@@ -174,11 +175,13 @@ export class VercelSandboxPipelineHost {
174175

175176
private async waitForDetachedCommand(
176177
sandbox: SandboxSession,
177-
command: PipelineHarnessCommand
178+
command: PipelineHarnessCommand,
179+
telemetryPath?: string
178180
): Promise<{ exitCode: number | null; stdout: string; stderr: string }> {
179181
const startedAt = Date.now();
180182
const maxWaitMs = command.maxWaitMs ?? 45 * 60 * 1000;
181183
const pollIntervalMs = command.pollIntervalMs ?? 5000;
184+
let emittedTelemetryLineCount = 0;
182185

183186
if (!command.exitCodePath) {
184187
return {
@@ -189,8 +192,20 @@ export class VercelSandboxPipelineHost {
189192
}
190193

191194
while (Date.now() - startedAt <= maxWaitMs) {
195+
emittedTelemetryLineCount = await this.emitNewTelemetryArtifactEvents(
196+
sandbox,
197+
command,
198+
telemetryPath,
199+
emittedTelemetryLineCount
200+
);
192201
const exitCodeText = await this.readTextArtifact(sandbox, command.exitCodePath);
193202
if (exitCodeText !== null) {
203+
emittedTelemetryLineCount = await this.emitNewTelemetryArtifactEvents(
204+
sandbox,
205+
command,
206+
telemetryPath,
207+
emittedTelemetryLineCount
208+
);
194209
const parsedExitCode = Number.parseInt(exitCodeText.trim(), 10);
195210
const [stdout, stderr] = await Promise.all([
196211
command.stdoutPath ? this.readTextArtifact(sandbox, command.stdoutPath) : Promise.resolve(null),
@@ -205,6 +220,12 @@ export class VercelSandboxPipelineHost {
205220
await sleep(pollIntervalMs);
206221
}
207222

223+
emittedTelemetryLineCount = await this.emitNewTelemetryArtifactEvents(
224+
sandbox,
225+
command,
226+
telemetryPath,
227+
emittedTelemetryLineCount
228+
);
208229
const [stdout, stderr] = await Promise.all([
209230
command.stdoutPath ? this.readTextArtifact(sandbox, command.stdoutPath) : Promise.resolve(null),
210231
command.stderrPath ? this.readTextArtifact(sandbox, command.stderrPath) : Promise.resolve(null),
@@ -219,6 +240,31 @@ export class VercelSandboxPipelineHost {
219240
};
220241
}
221242

243+
private async emitNewTelemetryArtifactEvents(
244+
sandbox: SandboxSession,
245+
command: PipelineHarnessCommand,
246+
telemetryPath: string | undefined,
247+
emittedLineCount: number
248+
): Promise<number> {
249+
if (!telemetryPath) return emittedLineCount;
250+
const telemetry = await this.readTextArtifact(sandbox, telemetryPath);
251+
if (!telemetry) return emittedLineCount;
252+
253+
const lines = telemetry.split(/\r?\n/).filter((line) => line.trim().length > 0);
254+
const nextStartIndex = emittedLineCount > lines.length ? 0 : emittedLineCount;
255+
for (let index = nextStartIndex; index < lines.length; index += 1) {
256+
await this.emit({
257+
type: 'telemetry-artifact-event',
258+
timestamp: new Date().toISOString(),
259+
label: command.label,
260+
telemetryPath,
261+
lineNumber: index + 1,
262+
telemetryEvent: parseTelemetryLine(lines[index]),
263+
});
264+
}
265+
return lines.length;
266+
}
267+
222268
private async readJsonArtifact(sandbox: SandboxSession, path: string): Promise<unknown | null> {
223269
const text = await this.readTextArtifact(sandbox, path);
224270
if (!text) return null;
@@ -247,6 +293,17 @@ function sleep(ms: number): Promise<void> {
247293
return new Promise((resolve) => setTimeout(resolve, ms));
248294
}
249295

296+
function parseTelemetryLine(line: string): unknown {
297+
try {
298+
return JSON.parse(line);
299+
} catch {
300+
return {
301+
parseError: true,
302+
raw: line,
303+
};
304+
}
305+
}
306+
250307
function withTimeout<T>(
251308
promise: Promise<T>,
252309
timeoutMs: number,

supabase/queries/v28_qa_terminal_06_read_fit_quality_after_read.sql

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
-- Saved query name: v28_qa_terminal_06_read_fit_quality_after_read
22
-- Purpose: run after accepting a Read and recording Fit posture. It verifies
3-
-- the commercially critical Read/Fit sequence is bound to the latest deposited
3+
-- the critical Read/Fit sequence is bound to the latest deposited
44
-- repository revision and flags mock/frontier leakage or missing proof posture.
55

66
CREATE OR REPLACE FUNCTION pg_temp.v28_qa_terminal_read_fit_quality_after_read()
@@ -346,7 +346,7 @@ BEGIN
346346
'read_fit_quality_gate'::text,
347347
jsonb_build_array(
348348
jsonb_build_object(
349-
'commercial_expectation', 'A Read against the current deposited Bitcode data-space must return a proof-bearing AssetPack fit, explicit no-worthy-fit evidence, or blocked-readiness until real pipeline execution evidence exists.',
349+
'review_expectation', 'A Read against the current deposited Bitcode data-space must return a proof-bearing AssetPack fit, explicit no-worthy-fit evidence, or blocked-readiness until real pipeline execution evidence exists.',
350350
'required_positive_controls', jsonb_build_array(
351351
'repository_full_name matches the latest deposited repository',
352352
'source_branch present',

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

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,28 @@ function hasModelProviderCredential(provider: string, env: Record<string, string
212212
}
213213
}
214214

215+
function summarizeHarnessPreflight(body: AssetPackHarnessRequest): Record<string, unknown> {
216+
const supabaseUrl = process.env.SUPABASE_URL || process.env.NEXT_PUBLIC_SUPABASE_URL;
217+
const serviceRole =
218+
process.env.SUPABASE_SERVICE_ROLE_KEY ||
219+
process.env.SUPABASE_SECRET_KEY ||
220+
process.env.SUPABASE_ADMIN_KEY;
221+
const budgetMs = Number(process.env.BITCODE_PIPELINE_HARNESS_MAX_RUNTIME_MS || 240000);
222+
return {
223+
repositoryFullName: body.repositoryFullName || null,
224+
sourceBranch: body.sourceBranch || null,
225+
sourceCommit: body.sourceCommit || null,
226+
readId: body.readId || null,
227+
depositId: body.depositId || null,
228+
realInferenceEnabled: isEnabled(process.env.BITCODE_ASSET_PACK_REAL_INFERENCE),
229+
openaiCredentialProvided: isUsableSecretValue(process.env.OPENAI_API_KEY),
230+
supabaseUrlProvided: isUsableSupabaseUrl(supabaseUrl),
231+
supabaseServiceRoleProvided: isUsableSecretValue(serviceRole),
232+
runtimeBudgetMs: Number.isFinite(budgetMs) ? budgetMs : null,
233+
deployedRuntime: isDeployedRuntime(),
234+
};
235+
}
236+
215237
function sourceCredentialsFromEnv(): { username?: string; password?: string } {
216238
const password =
217239
process.env.BITCODE_SANDBOX_SOURCE_GIT_PASSWORD ||
@@ -494,6 +516,7 @@ export async function POST(request: NextRequest): Promise<Response> {
494516
readId: body.readId,
495517
depositId: body.depositId,
496518
});
519+
emitSse(controller, 'harness-preflight', summarizeHarnessPreflight(body));
497520

498521
const plan = buildAssetPackSandboxHarness({
499522
mode,
@@ -549,6 +572,11 @@ export async function POST(request: NextRequest): Promise<Response> {
549572
} catch (runError) {
550573
emitSse(controller, 'harness-failed', {
551574
error: runError instanceof Error ? runError.message : String(runError),
575+
preflight: summarizeHarnessPreflight(body),
576+
});
577+
console.error('[bitcode-pipeline-harness-route-failed]', {
578+
error: runError instanceof Error ? runError.message : String(runError),
579+
preflight: summarizeHarnessPreflight(body),
552580
});
553581
} finally {
554582
controller.close();

0 commit comments

Comments
 (0)