fix: project analyzer generated time

This commit is contained in:
AgentRun Codex
2026-07-12 22:24:00 +00:00
parent 5fd3a1f07d
commit 771a3596f1
8 changed files with 61 additions and 14 deletions
@@ -166,6 +166,7 @@ console.log(JSON.stringify({
ok: true,
command: "web-probe-observe analyze",
stateDir,
generatedAt: report.generatedAt,
jsonlScope: report.jsonlScope,
reportJsonPath,
reportJsonSha256: jsonMeta.sha256,
@@ -1,11 +1,12 @@
import assert from "node:assert/strict";
import { mkdtempSync, rmSync } from "node:fs";
import { spawnSync } from "node:child_process";
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { test } from "bun:test";
import { hwlabRuntimeLaneSpecForNode } from "./hwlab-node-lanes";
import { classifyQuickVerifyCleanupStep, quickVerifyCleanupFindings, sentinelServiceProxyInvocation } from "./hwlab-node-web-sentinel-p5-observe";
import { classifyQuickVerifyCleanupStep, quickVerifyCleanupFindings, readAnalysisSummaryFromWorkspace, sentinelServiceProxyInvocation } from "./hwlab-node-web-sentinel-p5-observe";
import { createWebProbeSentinelService } from "./hwlab-node-web-sentinel-service";
test("sentinel service proxy executes its script through trans standard input", () => {
@@ -15,6 +16,31 @@ test("sentinel service proxy executes its script through trans standard input",
});
});
test("workspace analysis fallback maps report generatedAt to reportUpdatedAt", () => {
const directory = mkdtempSync(join(tmpdir(), "unidesk-workspace-analysis-"));
try {
const stateDir = ".state/web-observe/webobs-fixture-20260712T000000Z";
mkdirSync(join(directory, stateDir, "analysis"), { recursive: true });
writeFileSync(join(directory, stateDir, "analysis", "report.json"), JSON.stringify({ generatedAt: "2026-07-12T00:00:00.000Z", findings: [] }));
const summary = readAnalysisSummaryFromWorkspace({} as never, stateDir, 5, (script) => {
const result = spawnSync("sh", { cwd: directory, input: script, encoding: "utf8" });
return {
command: ["sh"],
cwd: directory,
exitCode: result.status,
stdout: result.stdout ?? "",
stderr: result.stderr ?? "",
signal: result.signal,
timedOut: false,
};
});
assert.equal(summary.ok, true, JSON.stringify(summary));
assert.equal(summary.reportUpdatedAt, "2026-07-12T00:00:00.000Z", JSON.stringify(summary));
} finally {
rmSync(directory, { recursive: true, force: true });
}
});
test("recorded sentinel run is available through the latest report index", () => {
const stateRoot = mkdtempSync(join(tmpdir(), "unidesk-sentinel-index-"));
const service = createWebProbeSentinelService({
@@ -1488,7 +1488,7 @@ function compactAnalyzeBrowserProcess(value: unknown): Record<string, unknown> |
};
}
function readAnalysisSummaryFromWorkspace(state: SentinelCicdState, stateDir: string, timeoutSeconds: number): Record<string, unknown> {
export function readAnalysisSummaryFromWorkspace(state: SentinelCicdState, stateDir: string, timeoutSeconds: number, execute: (script: string) => CommandResult = (script) => runCommand(["trans", `${state.spec.nodeId}:${state.spec.workspace}`, "sh"], repoRoot, { input: script, timeoutMs: Math.min(timeoutSeconds, 60) * 1000 })): Record<string, unknown> {
if (!isSafeRelativeStateDir(stateDir)) return { ok: false, reason: "unsafe-state-dir", stateDir, valuesRedacted: true };
const waitMs = Math.max(0, Math.min(50_000, (Math.max(5, timeoutSeconds) * 1000) - 5000));
const script = [
@@ -1517,10 +1517,10 @@ function readAnalysisSummaryFromWorkspace(state: SentinelCicdState, stateDir: st
"function pageMemory(page){const eff=rec(page.effectiveMemory); const heap=rec(page.heapUsage); const perf=rec(page.performance); const metrics=rec(perf.metrics); const heapUsed=num(eff.heapUsedMb)??mb(heap.usedSize); const jsHeap=num(eff.jsHeapUsedMb)??mb(metrics.JSHeapUsedSize); const memory=heapUsed??jsHeap; if(memory==null)return null; return {memoryMb:memory,heapUsedMb:heapUsed,jsHeapUsedMb:jsHeap,effectiveHeapUsedMb:num(eff.effectiveHeapUsedMb),effectiveJsHeapUsedMb:num(eff.effectiveJsHeapUsedMb),domNodes:num(eff.domNodes)??num(rec(page.domCounters).nodes)??num(metrics.Nodes)}; }",
"function browserProcessPageSeries(){const p=path.join(stateDir,'browser-process.jsonl'); let lines=[]; try{lines=String(read(p)||'').split(/\\r?\\n/).filter(Boolean)}catch{} const map=new Map(); let firstTs=null; for(const line of lines){let row=null; try{row=JSON.parse(line)}catch{} if(!row||row.type!=='browser-process-sample')continue; const ts=row.ts||null; const tsMs=Date.parse(String(ts||'')); if(!ts||!Number.isFinite(tsMs))continue; for(const page of arr(row.pages)){const memory=pageMemory(rec(page)); if(!memory)continue; if(firstTs==null)firstTs=tsMs; const role=clip(page.pageRole||'page',32); const id=clip(page.pageId||`${role}-${page.pageEpoch??map.size}`,80); const key=`${role}:${id}`; const current=map.get(key)||{key,pageRole:role,pageId:id,label:`${role} ${String(id).slice(0,10)}`,url:clip(page.url,180),points:[],valuesRedacted:true}; current.points.push({ts,elapsedSeconds:Math.max(0,Math.round((tsMs-firstTs)/1000)),elapsedMinutes:Math.round(Math.max(0,(tsMs-firstTs)/60000)*100)/100,...memory,valuesRedacted:true}); map.set(key,current);} } return Array.from(map.values()).filter((item)=>item.points.length>0); }",
"const browserPageSeries=browserProcessPageSeries();",
"console.log(JSON.stringify({ok:!!report,reason:report?null:(jsonBuf?'report-json-parse-failed':'report-json-missing'),reportReadWaitMs:waitMs,reportParseError:clip(reportParseError,220),reportOk:!!report&&report.ok!==false,stateDir,reportJsonPath:reportPath,reportJsonSha256:sha(jsonBuf),reportJsonBytes:jsonBuf.length,reportUpdatedAt:typeof report?.updatedAt==='string'?report.updatedAt:(typeof report?.analyzedAt==='string'?report.analyzedAt:null),reportMdPath,reportMdSha256:sha(read(reportMdPath)),findingCount:Number(report?.findingCount??findings.length),artifactCount,screenshot,findings,counts:rec(report?.counts),analysisWindow:rec(report?.analysisWindow??report?.windows?.recent?.summary),pagePerformanceSlowApi:slow,browserProcess:{source:'analysis-browser-process-jsonl',unit:'MB',metric:'page-heap-used',pageCount:browserPageSeries.length,sampleCount:browserPageSeries.reduce((sum,item)=>sum+item.points.length,0),pageSeries:browserPageSeries,valuesRedacted:true},requestRate:compactRequestRate(report?.requestRate),valuesRedacted:true}));",
"console.log(JSON.stringify({ok:!!report,reason:report?null:(jsonBuf?'report-json-parse-failed':'report-json-missing'),reportReadWaitMs:waitMs,reportParseError:clip(reportParseError,220),reportOk:!!report&&report.ok!==false,stateDir,reportJsonPath:reportPath,reportJsonSha256:sha(jsonBuf),reportJsonBytes:jsonBuf.length,reportUpdatedAt:typeof report?.updatedAt==='string'?report.updatedAt:(typeof report?.analyzedAt==='string'?report.analyzedAt:(typeof report?.generatedAt==='string'?report.generatedAt:null)),reportMdPath,reportMdSha256:sha(read(reportMdPath)),findingCount:Number(report?.findingCount??findings.length),artifactCount,screenshot,findings,counts:rec(report?.counts),analysisWindow:rec(report?.analysisWindow??report?.windows?.recent?.summary),pagePerformanceSlowApi:slow,browserProcess:{source:'analysis-browser-process-jsonl',unit:'MB',metric:'page-heap-used',pageCount:browserPageSeries.length,sampleCount:browserPageSeries.reduce((sum,item)=>sum+item.points.length,0),pageSeries:browserPageSeries,valuesRedacted:true},requestRate:compactRequestRate(report?.requestRate),valuesRedacted:true}));",
"NODE",
].join("\n");
const result = runCommand(["trans", `${state.spec.nodeId}:${state.spec.workspace}`, "sh"], repoRoot, { input: script, timeoutMs: Math.min(timeoutSeconds, 60) * 1000 });
const result = execute(script);
const parsedResolution = resolveCliChildJsonCommandResult({
result,
requestedStdoutType: "web-probe sentinel quick-verify analysis summary JSON",
@@ -6,6 +6,8 @@ import { spawnSync } from "node:child_process";
import { test } from "bun:test";
import { nodeWebObserveAnalyzerSource } from "../hwlab-node-web-observe-analyzer-source";
import { compactWebObserveAnalyzeAnalysisForRaw } from "./web-probe-observe";
import { buildMonitorTerminalIngest } from "../monitor-terminal-ingest";
const alertThresholds = {
sameOriginApiSlowMs: 60000,
@@ -65,7 +67,7 @@ const browserFreezePolicy = {
},
};
async function runObserveAnalyzer(files: Record<string, string>): Promise<Record<string, any>> {
async function runObserveAnalyzer(files: Record<string, string>): Promise<{ report: Record<string, any>; stdout: Record<string, unknown> }> {
const stateDir = await mkdtemp(join(tmpdir(), "unidesk-web-observe-analyzer-"));
const analyzerPath = join(stateDir, "analyze.mjs");
await writeFile(analyzerPath, nodeWebObserveAnalyzerSource(), { mode: 0o700 });
@@ -83,9 +85,20 @@ async function runObserveAnalyzer(files: Record<string, string>): Promise<Record
encoding: "utf8",
});
assert.equal(result.status, 0, result.stderr || result.stdout);
return JSON.parse(await readFile(join(stateDir, "analysis", "report.json"), "utf8"));
return {
report: JSON.parse(await readFile(join(stateDir, "analysis", "report.json"), "utf8")),
stdout: JSON.parse(result.stdout),
};
}
test("observe analyzer stdout carries generatedAt into terminal ingest", async () => {
const output = await runObserveAnalyzer({ "samples.jsonl": "" });
assert.equal(output.stdout.generatedAt, output.report.generatedAt);
const compact = compactWebObserveAnalyzeAnalysisForRaw(output.stdout);
const ingest = buildMonitorTerminalIngest({ sentinelId: "fixture", node: "NC01", lane: "v03", runId: "analyzer-stdout" }, compact, [{ relativePath: "analysis/report.json", kind: "report-json", sha256: output.stdout.reportJsonSha256 as string, sizeBytes: output.report ? 1 : 0 }], { ownerNode: "NC01", ownerLane: "v03", ownerPvc: "fixture-pvc", stateDir: output.stdout.stateDir as string });
assert.equal(ingest.updatedAt, output.report.generatedAt);
});
test("observe analyzer classifies stale completed session rail when a newer turn is running", async () => {
const stateDir = await mkdtemp(join(tmpdir(), "unidesk-web-observe-analyzer-"));
const analyzerPath = join(stateDir, "analyze.mjs");
@@ -226,7 +239,7 @@ test("observe analyzer flags automatic recovery legacy fanout authority", async
JSON.stringify({ ts: "2026-07-01T15:21:30.000Z", type: "response", method: "GET", status: 200, url: "https://hwlab.example.test/v1/workbench/sync?sessionId=ses_auto&traceId=trc_auto&since=7", bodySummary: { pathKind: "workbench-sync-replay", eventCount: 1, cursorOutboxSeq: 12, realtimeAuthority: "workbench-realtime-authority-v2", terminalSeal: true } })
].join("\n") + "\n";
const report = await runObserveAnalyzer({ "samples.jsonl": samples, "network.jsonl": network });
const { report } = await runObserveAnalyzer({ "samples.jsonl": samples, "network.jsonl": network });
const finding = report.findings.find((item: Record<string, unknown>) => item.id === "workbench-automatic-recovery-fanout-authority");
assert.equal(finding?.rootCause, "workbench_automatic_recovery_legacy_fanout");
@@ -280,7 +293,7 @@ test("observe analyzer allows explicit detail trace reads when sync authority ev
})
].join("\n") + "\n";
const report = await runObserveAnalyzer({ "samples.jsonl": samples, "network.jsonl": network });
const { report } = await runObserveAnalyzer({ "samples.jsonl": samples, "network.jsonl": network });
const finding = report.findings.find((item: Record<string, unknown>) => item.id === "workbench-automatic-recovery-fanout-authority");
assert.equal(finding, undefined);
@@ -30,7 +30,7 @@ test("artifact recovery helper projects report time without filesystem mtime", (
assert.equal(recovered?.reportUpdatedAt, "2026-07-12T00:00:00.000Z");
assert.match(recoveryScript, /const reportJson = objectOrNull\(readJson\(reportJsonPath\)\) \|\| \{\};/u);
assert.equal((recoveryScript.match(/const reportJson =/gu) ?? []).length, 1);
assert.match(recoveryScript, /source\.reportUpdatedAt \?\? source\.updatedAt \?\? source\.analyzedAt \?\? reportJson\.reportUpdatedAt \?\? reportJson\.updatedAt \?\? reportJson\.analyzedAt/u);
assert.match(recoveryScript, /source\.reportUpdatedAt \?\? source\.updatedAt \?\? source\.analyzedAt \?\? reportJson\.reportUpdatedAt \?\? reportJson\.updatedAt \?\? reportJson\.analyzedAt \?\? source\.generatedAt \?\? reportJson\.generatedAt/u);
assert.doesNotMatch(recoveryScript, /mtime/u);
const marker = "UNIDESK_WEB_OBSERVE_RECOVER_ANALYZE_ARTIFACT";
const start = recoveryScript.indexOf(`<<'${marker}'\n`) + marker.length + 5;
@@ -41,6 +41,11 @@ test("artifact recovery helper projects report time without filesystem mtime", (
writeFileSync(programPath, recoveryScript.slice(start, end));
const syntax = spawnSync(process.execPath, ["--check", programPath], { encoding: "utf8" });
assert.equal(syntax.status, 0, syntax.stderr);
const reportJsonPath = join(directory, "report.json");
writeFileSync(reportJsonPath, JSON.stringify({ generatedAt: "2026-07-12T00:00:00.000Z" }));
const executed = spawnSync(process.execPath, [programPath, directory, join(directory, "stdout.json"), join(directory, "stderr.log"), reportJsonPath, join(directory, "report.md"), "124", "true"], { encoding: "utf8" });
assert.equal(executed.status, 0, executed.stderr);
assert.equal(JSON.parse(executed.stdout).reportUpdatedAt, "2026-07-12T00:00:00.000Z");
} finally {
rmSync(directory, { recursive: true, force: true });
}
+2 -2
View File
@@ -2220,7 +2220,7 @@ export function compactWebObserveAnalyzeAnalysisForRaw(analysis: Record<string,
reportJsonPath: stringOrNullValue(analysis.reportJsonPath) ?? stringOrNullValue(recordValue(analysis.analyzer).reportJsonPath),
reportJsonSha256: stringOrNullValue(analysis.reportJsonSha256) ?? stringOrNullValue(recordValue(analysis.analyzer).reportJsonSha256),
reportJsonBytes: numberOrNullValue(analysis.reportJsonBytes) ?? numberOrNullValue(recordValue(analysis.analyzer).reportJsonBytes),
reportUpdatedAt: stringOrNullValue(analysis.reportUpdatedAt) ?? stringOrNullValue(analysis.updatedAt) ?? stringOrNullValue(analysis.analyzedAt) ?? stringOrNullValue(recordValue(analysis.analyzer).reportUpdatedAt) ?? stringOrNullValue(recordValue(analysis.analyzer).updatedAt) ?? stringOrNullValue(recordValue(analysis.analyzer).analyzedAt),
reportUpdatedAt: stringOrNullValue(analysis.reportUpdatedAt) ?? stringOrNullValue(analysis.updatedAt) ?? stringOrNullValue(analysis.analyzedAt) ?? stringOrNullValue(analysis.generatedAt) ?? stringOrNullValue(recordValue(analysis.analyzer).reportUpdatedAt) ?? stringOrNullValue(recordValue(analysis.analyzer).updatedAt) ?? stringOrNullValue(recordValue(analysis.analyzer).analyzedAt),
reportMdPath: stringOrNullValue(analysis.reportMdPath),
reportMdSha256: stringOrNullValue(analysis.reportMdSha256),
analyzer: compactWebObserveAnalyzeAnalyzerForRaw(analysis.analyzer),
@@ -2489,7 +2489,7 @@ export function recoverWebObserveAnalyzeFromArtifacts(options: NodeWebProbeObser
"const archiveSampleMetrics = objectOrNull(archiveSummary.sampleMetrics) || objectOrNull(source.sampleMetrics?.summary) || objectOrNull(srcMetrics.summary) || {};",
"const slowApis = arr(source.pagePerformanceSlowApi).length > 0 ? arr(source.pagePerformanceSlowApi) : arr(pagePerformance.sameOriginApiByPath).filter((item) => Number(item?.overBudgetCount ?? item?.overFiveSecondCount ?? 0) > 0);",
"const compact = { ok: source.ok === true, command: source.command ?? 'web-probe-observe analyze', stateDir: source.stateDir ?? stateDir, jsonlScope: source.jsonlScope ?? null, alertThresholds: source.alertThresholds ?? null, counts: source.counts ?? null, archiveSummary: { ...archiveSummary, sampleMetrics: archiveSampleMetrics, pagePerformance: objectOrNull(archiveSummary.pagePerformance) || objectOrNull(pagePerformance.summary) || {}, runtimeAlerts: objectOrNull(archiveSummary.runtimeAlerts) || objectOrNull(runtimeAlerts.summary) || {}, browserProcess: objectOrNull(archiveSummary.browserProcess) || objectOrNull(browserProcess.summary) || {}, requestRate: objectOrNull(archiveSummary.requestRate) || requestRateSummary || {}, frontendPerformance: objectOrNull(archiveSummary.frontendPerformance) || frontendPerformanceSummary || {}, redFindings: arr(archiveSummary.redFindings).slice(0, 12).map(slimFinding) }, analysisWindow: source.analysisWindow ?? objectOrNull(recent.summary), sampleMetrics: compactMetrics(srcMetrics), pageProvenance: objectOrNull(source.pageProvenance?.summary) || source.pageProvenance || null, pagePerformance: objectOrNull(pagePerformance.summary) || pagePerformance, projectManagement: objectOrNull(source.projectManagement) || null, promptNetwork: objectOrNull(promptNetwork.summary) || promptNetwork, requestRate: requestRateSummary, requestRateCurve, requestRatePeaks, frontendPerformance: frontendPerformanceSummary, frontendPerformanceHotspots, runtimeAlerts: objectOrNull(runtimeAlerts.summary) || runtimeAlerts, browserProcess: objectOrNull(browserProcess.summary) || browserProcess, runnerErrors: arr(source.runnerErrors).slice(-8), commandFailures: arr(source.commandFailures).slice(-8), commandState: objectOrNull(source.commandState) || null, toolFindings: arr(source.toolFindings).slice(0, 8).map(slimFinding), httpErrorGroups: arr(source.httpErrorGroups ?? runtimeAlerts.networkHttpErrorsByPath).slice(0, 8).map(slimGroup), requestFailedGroups: arr(source.requestFailedGroups ?? runtimeAlerts.networkRequestFailedByPath).slice(0, 8).map(slimGroup), domDiagnosticGroups: arr(source.domDiagnosticGroups ?? runtimeAlerts.domDiagnosticsByText).slice(0, 5).map(slimGroup), domDiagnosticSamples: arr(source.domDiagnosticSamples ?? runtimeAlerts.domDiagnostics).slice(0, 8).map(slimGroup), consoleAlertGroups: arr(source.consoleAlertGroups ?? runtimeAlerts.consoleAlertsByPath).slice(0, 8).map(slimGroup), consoleAlertSamples: arr(source.consoleAlertSamples ?? runtimeAlerts.consoleAlerts).slice(0, 8).map(slimGroup), turnTimingRecentUpdateJumps: arr(source.turnTimingRecentUpdateJumps ?? srcMetrics.turnTimingRecentUpdateSawtoothJumps).slice(0, 8), turnTimingElapsedZeroResets: arr(source.turnTimingElapsedZeroResets ?? srcMetrics.turnTimingElapsedZeroResets).slice(0, 8), turnTimingTotalElapsedForwardJumps: arr(source.turnTimingTotalElapsedForwardJumps ?? srcMetrics.turnTimingTotalElapsedForwardJumps).slice(0, 8), pagePerformanceSlowApi: slowApis.slice(0, 8).map(slimSlowApi), archivePagePerformanceSlowApi: arr(source.archivePagePerformanceSlowApi).slice(0, 8).map(slimSlowApi), pagePerformancePartialApi: arr(source.pagePerformancePartialApi).slice(0, 8), pagePerformanceSseStreams: arr(source.pagePerformanceSseStreams).slice(0, 8), findings: arr(source.findings).slice(0, 12).map(slimFinding), archiveRedFindings: arr(source.archiveRedFindings ?? archiveSummary.redFindings).slice(0, 12).map(slimFinding), reportJsonPath: source.reportJsonPath ?? reportJsonPath, reportJsonSha256: source.reportJsonSha256 ?? sha256(reportJsonPath), reportMdPath: source.reportMdPath ?? reportMdPath, reportMdSha256: source.reportMdSha256 ?? sha256(reportMdPath), analyzer: { ...(objectOrNull(source.analyzer) || {}), recoveredFrom: 'analysis-artifact-after-transport-timeout', stdoutPath, stderrPath, stdoutBytes: statSize(stdoutPath), stderrBytes: statSize(stderrPath), reportJsonBytes: statSize(reportJsonPath), reportMdBytes: statSize(reportMdPath), transportExitCode: Number(transportExitRaw), transportTimedOut: transportTimedOutRaw === 'true', valuesRedacted: true }, valuesRedacted: true };",
"compact.reportUpdatedAt = source.reportUpdatedAt ?? source.updatedAt ?? source.analyzedAt ?? reportJson.reportUpdatedAt ?? reportJson.updatedAt ?? reportJson.analyzedAt ?? null;",
"compact.reportUpdatedAt = source.reportUpdatedAt ?? source.updatedAt ?? source.analyzedAt ?? reportJson.reportUpdatedAt ?? reportJson.updatedAt ?? reportJson.analyzedAt ?? source.generatedAt ?? reportJson.generatedAt ?? null;",
"console.log(JSON.stringify(compact));",
"UNIDESK_WEB_OBSERVE_RECOVER_ANALYZE_ARTIFACT",
].join("\n");
@@ -37,8 +37,10 @@ test("terminal writer requires explicit mutually exclusive authority", () => {
});
test("real compact analyze projects stable report time and artifact facts", () => {
const compact = compactWebObserveAnalyzeAnalysisForRaw({ updatedAt: "2026-07-12T00:00:00.000Z", analyzer: { reportJsonPath: "/state/run-1/analysis/report.json", reportJsonSha256: `sha256:${"c".repeat(64)}`, reportJsonBytes: 12 } });
const compact = compactWebObserveAnalyzeAnalysisForRaw({ generatedAt: "2026-07-12T00:00:00.000Z", analyzer: { reportJsonPath: "/state/run-1/analysis/report.json", reportJsonSha256: `sha256:${"c".repeat(64)}`, reportJsonBytes: 12 } });
assert.deepEqual({ reportUpdatedAt: compact.reportUpdatedAt, reportJsonSha256: compact.reportJsonSha256, reportJsonBytes: compact.reportJsonBytes }, { reportUpdatedAt: "2026-07-12T00:00:00.000Z", reportJsonSha256: `sha256:${"c".repeat(64)}`, reportJsonBytes: 12 });
const ingest = buildMonitorTerminalIngest({ sentinelId: "fixture", node: "NC01", lane: "v03", runId: "generated-at" }, compact, [{ relativePath: "analysis/report.json", kind: "report-json", sha256: `sha256:${"c".repeat(64)}`, sizeBytes: 12 }], { ownerNode: "NC01", ownerLane: "v03", ownerPvc: "fixture-pvc", stateDir: "/state/run-1" });
assert.equal(ingest.updatedAt, "2026-07-12T00:00:00.000Z");
});
test("SQLite snapshot reconciles the 228/242/234 fixture shape without production assumptions", async () => {
+1 -1
View File
@@ -16,7 +16,7 @@ export class MonitorIngestFailedError extends Error {
export function buildMonitorTerminalIngest(identity: MonitorTerminalIdentity, report: Record<string, unknown>, artifacts: readonly MonitorTerminalArtifact[], locatorOwner: Pick<MonitorArtifactLocator, "ownerNode" | "ownerLane" | "ownerPvc" | "stateDir">): MonitorCentralIngest {
const immutableReport = JSON.parse(monitorCentralStableJson(report)) as Record<string, unknown>;
if (!identity.runId) throw new MonitorIngestFailedError({ reason: "run_id_missing", valuesRedacted: true });
const updatedAt = stringValue(immutableReport.updatedAt) ?? stringValue(immutableReport.analyzedAt);
const updatedAt = stringValue(immutableReport.updatedAt) ?? stringValue(immutableReport.analyzedAt) ?? stringValue(immutableReport.reportUpdatedAt);
if (!updatedAt) throw new MonitorIngestFailedError({ reason: "terminal_timestamp_missing", runId: identity.runId, valuesRedacted: true });
for (const artifact of artifacts) if (!/^sha256:[a-f0-9]{64}$/u.test(artifact.sha256) || !Number.isSafeInteger(artifact.sizeBytes) || artifact.sizeBytes < 1) throw new MonitorIngestFailedError({ reason: "terminal_artifact_unverified", runId: identity.runId, relativePath: artifact.relativePath, valuesRedacted: true });
return normalizeMonitorCentralIngest({