mirror of
https://github.com/morluto/rea.git
synced 2026-11-03 13:26:10 +00: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 parent
131fd5ddcd
commit
3b698645a0
39 files changed
+1897
-418
No files matched your search
@@ -0,0 +1,150 @@
|
||||
import { expect, it } from "vitest";
|
||||
|
||||
import { classifyBrowserCompleteness } from "../../domain/browserCompleteness.js";
|
||||
import {
|
||||
AnalysisCancelledError,
|
||||
AnalysisOutputError,
|
||||
} from "../../domain/analysisErrorCore.js";
|
||||
import { ProviderCleanupError } from "../../domain/providerCleanupError.js";
|
||||
import { parseEvidence } from "../../domain/evidence.js";
|
||||
import {
|
||||
electronPageInspectionSchema,
|
||||
electronTargetListSchema,
|
||||
inspectElectronPageInputSchema,
|
||||
} from "../../domain/javascript/electronObservation.js";
|
||||
import { err } from "../../domain/result.js";
|
||||
import type { ElectronObservationPort } from "./ElectronObservationPort.js";
|
||||
import {
|
||||
inspectElectronPage,
|
||||
listElectronTargets,
|
||||
} from "./ElectronObservationService.js";
|
||||
|
||||
const identity = { id: "electron-fixture", name: "Fixture", version: "1" };
|
||||
const input = inspectElectronPageInputSchema.parse({
|
||||
cdp_endpoint: "http://127.0.0.1:9222",
|
||||
target_id: "electron-page",
|
||||
observation_ms: 0,
|
||||
});
|
||||
const inspection = electronPageInspectionSchema.parse({
|
||||
browser: {
|
||||
product: "Electron",
|
||||
protocol_version: "1.3",
|
||||
revision: "fixture",
|
||||
user_agent: "Electron fixture",
|
||||
js_version: "V8 fixture",
|
||||
},
|
||||
target: {
|
||||
target_id: "electron-page",
|
||||
type: "page",
|
||||
title: "Fixture",
|
||||
file_path: "/app/index.html",
|
||||
attached: false,
|
||||
},
|
||||
capture_window: {
|
||||
started_at: "2026-01-01T00:00:00.000Z",
|
||||
ended_at: "2026-01-01T00:00:00.000Z",
|
||||
observation_ms: 0,
|
||||
},
|
||||
completeness: classifyBrowserCompleteness({
|
||||
policyFilteredSections: new Set(),
|
||||
attachLimitedSections: new Set(),
|
||||
truncatedSections: new Set(),
|
||||
unavailableSections: new Set(),
|
||||
excluded: [],
|
||||
droppedEvents: {
|
||||
scripts: 0,
|
||||
network_requests: 0,
|
||||
console_events: 0,
|
||||
websocket_connections: 0,
|
||||
websocket_frames: 0,
|
||||
webmcp_tools: 0,
|
||||
timeline_events: 0,
|
||||
},
|
||||
}),
|
||||
frames: [],
|
||||
dom: { total_nodes: 0, nodes: [] },
|
||||
scripts: { total: 0, items: [] },
|
||||
resources: [],
|
||||
workers: [],
|
||||
limitations: [],
|
||||
});
|
||||
|
||||
it("converts a retained raw Electron inspection into partial Evidence", async () => {
|
||||
const failure = new AnalysisCancelledError("inspect_electron_page", {
|
||||
partialObservation: inspection,
|
||||
});
|
||||
const provider: ElectronObservationPort = {
|
||||
identity: () => identity,
|
||||
listTargets: async () =>
|
||||
err(new AnalysisCancelledError("list_electron_targets")),
|
||||
inspectPage: async () => err(failure),
|
||||
};
|
||||
const result = await inspectElectronPage(provider, input);
|
||||
expect(result.ok).toBe(false);
|
||||
if (!result.ok) {
|
||||
expect(result.error).toBe(failure);
|
||||
expect(parseEvidence(result.error.partialObservation)).toMatchObject({
|
||||
operation: "inspect_electron_page",
|
||||
normalized_result: inspection,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it("converts a raw inspection retained by provider cleanup failure", async () => {
|
||||
const failure = new ProviderCleanupError(
|
||||
"rea-cdp-electron",
|
||||
["electron-page"],
|
||||
{ reason: "target detach failed" },
|
||||
{ operation: "inspect_electron_page", partialObservation: inspection },
|
||||
);
|
||||
const provider: ElectronObservationPort = {
|
||||
identity: () => identity,
|
||||
listTargets: async () =>
|
||||
err(new AnalysisCancelledError("list_electron_targets")),
|
||||
inspectPage: async () => err(failure),
|
||||
};
|
||||
const result = await inspectElectronPage(provider, input);
|
||||
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: "inspect_electron_page",
|
||||
normalized_result: inspection,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it("returns a typed output error for malformed Electron target output after abort", async () => {
|
||||
const controller = new AbortController();
|
||||
const malformed = {
|
||||
browser: {
|
||||
product: "Electron",
|
||||
protocol_version: "1.3",
|
||||
revision: "fixture",
|
||||
user_agent: "Electron fixture",
|
||||
js_version: "V8 fixture",
|
||||
},
|
||||
targets: [],
|
||||
excluded: { unsupported_url: 0, non_page: 0 },
|
||||
limitations: [() => undefined],
|
||||
} as unknown as ReturnType<typeof electronTargetListSchema.parse>;
|
||||
const provider: ElectronObservationPort = {
|
||||
identity: () => identity,
|
||||
listTargets: async () => {
|
||||
controller.abort();
|
||||
return { ok: true, value: malformed };
|
||||
},
|
||||
inspectPage: async () =>
|
||||
err(new AnalysisCancelledError("inspect_electron_page")),
|
||||
};
|
||||
const result = await listElectronTargets(
|
||||
provider,
|
||||
{
|
||||
cdp_endpoint: "http://127.0.0.1:9222",
|
||||
},
|
||||
{ signal: controller.signal },
|
||||
);
|
||||
expect(result.ok).toBe(false);
|
||||
if (!result.ok) expect(result.error).toBeInstanceOf(AnalysisOutputError);
|
||||
});
|
||||
Reference in new issue
Block a user