162 lines
6.1 KiB
TypeScript
162 lines
6.1 KiB
TypeScript
import { createHash } from "node:crypto";
|
|
|
|
type NativeStatus = "queued" | "running" | "completed" | "failed" | "blocked" | "canceled";
|
|
|
|
interface NativeStep {
|
|
status: NativeStatus;
|
|
stage: string;
|
|
terminal: boolean;
|
|
blocker?: { code: string; summary: string; layer: string };
|
|
}
|
|
|
|
interface NativeRunState {
|
|
runId: string;
|
|
caseId: string;
|
|
cursor: number;
|
|
steps: NativeStep[];
|
|
}
|
|
|
|
const MODE = "native-test";
|
|
const CONTRACT_VERSION = "caserun.v1";
|
|
const BASE_TIME = Date.parse("2026-07-17T00:00:00.000Z");
|
|
|
|
const scenarios: Record<string, { title: string; steps: NativeStep[] }> = {
|
|
"native-queued": { title: "Native queued", steps: [{ status: "queued", stage: "queued", terminal: false }] },
|
|
"native-running": { title: "Native running", steps: [{ status: "queued", stage: "queued", terminal: false }, { status: "running", stage: "agent-running", terminal: false }] },
|
|
"native-completed": { title: "Native completed", steps: [{ status: "queued", stage: "queued", terminal: false }, { status: "running", stage: "agent-running", terminal: false }, { status: "completed", stage: "completed", terminal: true }] },
|
|
"native-failed": { title: "Native failed", steps: [{ status: "queued", stage: "queued", terminal: false }, { status: "running", stage: "building", terminal: false }, { status: "failed", stage: "failed", terminal: true, blocker: { code: "native-build-failed", summary: "Deterministic native build failure", layer: "build" } }] },
|
|
"native-blocked": { title: "Native blocked", steps: [{ status: "queued", stage: "queued", terminal: false }, { status: "blocked", stage: "preparing", terminal: true, blocker: { code: "native-capability-blocked", summary: "Deterministic native capability blocker", layer: "hwpod" } }] },
|
|
"native-canceled": { title: "Native canceled", steps: [{ status: "queued", stage: "queued", terminal: false }, { status: "running", stage: "agent-running", terminal: false }, { status: "canceled", stage: "canceled", terminal: true }] }
|
|
};
|
|
|
|
let runSequence = 0;
|
|
const runs = new Map<string, NativeRunState>();
|
|
|
|
function timestamp(index: number): string {
|
|
return new Date(BASE_TIME + index * 1000).toISOString();
|
|
}
|
|
|
|
function aggregateResponse(run: NativeRunState) {
|
|
const step = run.steps[run.cursor] ?? run.steps.at(-1)!;
|
|
const summary = {
|
|
status: step.status,
|
|
jobId: `job-${run.runId}`,
|
|
artifactCount: step.status === "completed" ? 2 : 0
|
|
};
|
|
const evidence = {
|
|
status: step.status,
|
|
blocker: step.blocker ?? null,
|
|
artifacts: step.status === "completed" ? [{ name: "firmware.bin" }, { name: "caserun.log" }] : []
|
|
};
|
|
const archivedRun = {
|
|
runId: run.runId,
|
|
caseId: run.caseId,
|
|
status: step.status,
|
|
stage: step.stage,
|
|
terminal: step.terminal,
|
|
mode: MODE
|
|
};
|
|
const source = { runId: run.runId, caseId: run.caseId, status: step.status, summary, evidence, run: archivedRun };
|
|
return {
|
|
ok: true,
|
|
mode: MODE,
|
|
contractVersion: "caserun.aggregate.v1",
|
|
runId: run.runId,
|
|
caseId: run.caseId,
|
|
status: step.status,
|
|
stage: step.stage,
|
|
terminal: step.terminal,
|
|
sourceAuthority: { mode: MODE, sequence: "request-driven" },
|
|
sha256: createHash("sha256").update(JSON.stringify(source)).digest("hex"),
|
|
summary,
|
|
evidence,
|
|
run: archivedRun
|
|
};
|
|
}
|
|
|
|
function runResponse(run: NativeRunState) {
|
|
const step = run.steps[run.cursor] ?? run.steps.at(-1)!;
|
|
return {
|
|
ok: true,
|
|
mode: MODE,
|
|
contractVersion: CONTRACT_VERSION,
|
|
runId: run.runId,
|
|
caseId: run.caseId,
|
|
status: step.status,
|
|
stage: step.stage,
|
|
terminal: step.terminal,
|
|
blocker: step.blocker ?? null,
|
|
createdAt: timestamp(0),
|
|
updatedAt: timestamp(run.cursor),
|
|
facts: {
|
|
jobId: `job-${run.runId}`,
|
|
artifactCount: step.status === "completed" ? 2 : 0,
|
|
evidenceStatus: step.status
|
|
},
|
|
references: {
|
|
status: { href: `/v1/caserun/runs/${run.runId}` },
|
|
events: { href: `/v1/caserun/runs/${run.runId}/events`, count: run.cursor + 1 },
|
|
aggregate: { href: `/v1/caserun/runs/${run.runId}/aggregate`, sha256: aggregateResponse(run).sha256 },
|
|
manifest: step.status === "completed" ? { href: `/v1/caserun/runs/${run.runId}/manifest`, count: 2 } : null,
|
|
replay: null,
|
|
trace: { traceId: `trace-${run.runId}` },
|
|
hwpod: { hwpodId: "native-hwpod", nodeId: "native-node" }
|
|
},
|
|
sourceAuthority: { mode: MODE, sequence: "request-driven" },
|
|
error: null,
|
|
statusUrl: `/v1/caserun/runs/${run.runId}`
|
|
};
|
|
}
|
|
|
|
export function listNativeCases() {
|
|
const cases = Object.entries(scenarios).map(([caseId, scenario]) => ({
|
|
caseId,
|
|
title: scenario.title,
|
|
mode: MODE,
|
|
available: true,
|
|
hwpodSpec: "native-test",
|
|
runtime: { sequence: scenario.steps.map((step) => step.status) }
|
|
}));
|
|
return { ok: true, mode: MODE, contractVersion: CONTRACT_VERSION, count: cases.length, cases, caseAuthority: { mode: MODE } };
|
|
}
|
|
|
|
export function startNativeRun(caseId: string) {
|
|
const scenario = scenarios[caseId];
|
|
if (!scenario) return null;
|
|
runSequence += 1;
|
|
const runId = `${caseId}-${runSequence}`;
|
|
const run: NativeRunState = { runId, caseId, cursor: 0, steps: scenario.steps };
|
|
runs.set(runId, run);
|
|
return runResponse(run);
|
|
}
|
|
|
|
export function readNativeRun(runId: string, advance = true) {
|
|
const run = runs.get(runId);
|
|
if (!run) return null;
|
|
if (advance && run.cursor < run.steps.length - 1) run.cursor += 1;
|
|
return runResponse(run);
|
|
}
|
|
|
|
export function readNativeEvents(runId: string) {
|
|
const run = runs.get(runId);
|
|
if (!run) return null;
|
|
const current = runResponse(run);
|
|
const events = run.steps.slice(0, run.cursor + 1).map((step, index) => ({
|
|
at: timestamp(index),
|
|
status: step.status,
|
|
stage: step.stage,
|
|
payload: { mode: MODE, terminal: step.terminal, blocker: step.blocker ?? null }
|
|
}));
|
|
return { ok: true, mode: MODE, contractVersion: CONTRACT_VERSION, runId, caseId: run.caseId, status: current.status, stage: current.stage, terminal: current.terminal, sourceAuthority: current.sourceAuthority, events };
|
|
}
|
|
|
|
export function readNativeAggregate(runId: string) {
|
|
const run = runs.get(runId);
|
|
return run ? aggregateResponse(run) : null;
|
|
}
|
|
|
|
export function resetNativeFixtures(): void {
|
|
runSequence = 0;
|
|
runs.clear();
|
|
}
|