mirror of
https://github.com/morluto/rea
synced 2026-10-26 22:28:27 -04:00
fix(evidence): retain completed observations on late cancellation
Validate completed reports before cancellation projection, retain valid Evidence and captured command diagnostics, and convert raw cleanup partials at their application owner. Record partial Evidence consistently across MCP handlers. Keep page inspection separate from unfinished bundle analysis. Refs #4261
This commit is contained in:
@@ -1,8 +1,14 @@
|
||||
import { expect, it } from "vitest";
|
||||
|
||||
import { BrowserObservationError } from "../../domain/browserObservationError.js";
|
||||
import { AnalysisCancelledError } from "../../domain/analysisErrorCore.js";
|
||||
import {
|
||||
AnalysisCancelledError,
|
||||
AnalysisOutputError,
|
||||
} from "../../domain/analysisErrorCore.js";
|
||||
import { err, ok } from "../../domain/result.js";
|
||||
import { parseEvidence } from "../../domain/evidence.js";
|
||||
import type { JavaScriptRuntimeTargetList } from "../../domain/javascript/javascriptRuntimeObservation.js";
|
||||
import { javascriptRuntimeObservationSchema } from "../../domain/javascript/javascriptRuntimeObservation.js";
|
||||
import type { JavaScriptRuntimeObservationPort } from "./JavaScriptRuntimeObservationPort.js";
|
||||
import {
|
||||
listJavaScriptRuntimeTargets,
|
||||
@@ -85,4 +91,138 @@ it("does not publish targets when cancellation arrives during provider cleanup",
|
||||
operation: "list_javascript_runtime_targets",
|
||||
},
|
||||
});
|
||||
if (!result.ok) {
|
||||
expect(parseEvidence(result.error.partialObservation)).toMatchObject({
|
||||
operation: "list_javascript_runtime_targets",
|
||||
normalized_result: { targets: [] },
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it("does not let late cancellation hide an invalid completed Inspector report", async () => {
|
||||
const malformed = {
|
||||
runtime: {
|
||||
product: "Node.js",
|
||||
protocol_version: "1.3",
|
||||
v8_version: "12.0",
|
||||
},
|
||||
targets: [],
|
||||
excluded: { unsupported_location: -1, unconnectable: 0 },
|
||||
limitations: [],
|
||||
} as unknown as JavaScriptRuntimeTargetList;
|
||||
const output = await listJavaScriptRuntimeTargets(
|
||||
{
|
||||
identity: () => ({ id: "test", name: "Test", version: "1" }),
|
||||
listTargets: async () => ok(malformed),
|
||||
observe: async () => {
|
||||
throw new Error("not used");
|
||||
},
|
||||
},
|
||||
{ inspector_endpoint: "http://127.0.0.1:9229" },
|
||||
);
|
||||
expect(output).toMatchObject({
|
||||
ok: false,
|
||||
error: { _tag: "AnalysisOutputError" },
|
||||
});
|
||||
if (!output.ok) expect(output.error).toBeInstanceOf(AnalysisOutputError);
|
||||
|
||||
const controller = new AbortController();
|
||||
const late = await listJavaScriptRuntimeTargets(
|
||||
{
|
||||
identity: () => ({ id: "test", name: "Test", version: "1" }),
|
||||
listTargets: async () => {
|
||||
controller.abort();
|
||||
return ok(malformed);
|
||||
},
|
||||
observe: async () => {
|
||||
throw new Error("not used");
|
||||
},
|
||||
},
|
||||
{ inspector_endpoint: "http://127.0.0.1:9229" },
|
||||
{ signal: controller.signal },
|
||||
);
|
||||
expect(late).toMatchObject({
|
||||
ok: false,
|
||||
error: { _tag: "AnalysisOutputError" },
|
||||
});
|
||||
if (!late.ok) expect(late.error).toBeInstanceOf(AnalysisOutputError);
|
||||
});
|
||||
|
||||
it("converts a raw runtime observation retained by cleanup failure", async () => {
|
||||
const observation = javascriptRuntimeObservationSchema.parse({
|
||||
runtime: {
|
||||
product: "node.js/v24",
|
||||
protocol_version: "1.3",
|
||||
v8_version: "13",
|
||||
},
|
||||
target: {
|
||||
target_id: "target",
|
||||
protocol_type: "node",
|
||||
attached: false,
|
||||
location: { kind: "file", file_path: "/fixture.js" },
|
||||
runtime_kind: "node",
|
||||
runtime_kind_authority: "caller-declared-unverified",
|
||||
},
|
||||
capture: {
|
||||
observation_ms: 0,
|
||||
events_observed: 0,
|
||||
events_retained: 0,
|
||||
events_dropped: 0,
|
||||
metadata_bytes_retained: 0,
|
||||
truncated: false,
|
||||
truncation_reasons: [],
|
||||
},
|
||||
scripts: {
|
||||
items: [],
|
||||
observed_total: 0,
|
||||
excluded: { unsupported_location: 0, invalid_protocol_value: 0 },
|
||||
},
|
||||
execution_contexts: [],
|
||||
directly_observed: ["Inspector target attached and queried."],
|
||||
unavailable_without_instrumentation: [],
|
||||
unknowns: [],
|
||||
limitations: [],
|
||||
});
|
||||
const failure = new BrowserObservationError(
|
||||
"observe_javascript_runtime",
|
||||
"cleanup_failed",
|
||||
{
|
||||
cleanup: {
|
||||
reason: "Inspector close failed",
|
||||
resources: ["browser_transport"],
|
||||
},
|
||||
partialObservation: observation,
|
||||
},
|
||||
);
|
||||
const result = await observeJavaScriptRuntime(
|
||||
{
|
||||
identity: () => ({ id: "test", name: "Test", version: "1" }),
|
||||
listTargets: async () =>
|
||||
ok({
|
||||
runtime: {
|
||||
product: "node.js/v24",
|
||||
protocol_version: "1.3",
|
||||
v8_version: "13",
|
||||
},
|
||||
targets: [],
|
||||
excluded: { unsupported_location: 0, unconnectable: 0 },
|
||||
limitations: [],
|
||||
}),
|
||||
observe: async () => err(failure),
|
||||
},
|
||||
{
|
||||
inspector_endpoint: "http://127.0.0.1:9229",
|
||||
target_id: "target",
|
||||
observation_ms: 0,
|
||||
},
|
||||
);
|
||||
expect(result.ok).toBe(false);
|
||||
if (!result.ok) {
|
||||
expect(result.error).toBe(failure);
|
||||
expect(result.error.cleanupIncomplete).toBe(true);
|
||||
expect(parseEvidence(result.error.partialObservation)).toMatchObject({
|
||||
operation: "observe_javascript_runtime",
|
||||
normalized_result: observation,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user