mirror of
https://github.com/morluto/rea
synced 2026-10-26 14:25:49 -04:00
Merge remote-tracking branch 'origin/main' into codex/audit-continuation-20261020-refresh20
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
import { expect, it } from "vitest";
|
||||
|
||||
import { classifyBrowserCompleteness } from "../../domain/browserCompleteness.js";
|
||||
import {
|
||||
AnalysisCancelledError,
|
||||
AnalysisOutputError,
|
||||
@@ -12,14 +11,27 @@ import {
|
||||
electronTargetListSchema,
|
||||
inspectElectronPageInputSchema,
|
||||
} from "../../domain/javascript/electronObservation.js";
|
||||
import { emptyElectronPageCoverage } from "../../domain/javascript/electronPageInspection.fixture.js";
|
||||
import type { AnalysisError } from "../../domain/analysisErrorBase.js";
|
||||
import { err, ok } from "../../domain/result.js";
|
||||
import type { ElectronObservationPort } from "./ElectronObservationPort.js";
|
||||
import {
|
||||
inspectElectronPage,
|
||||
listElectronTargets,
|
||||
} from "./ElectronObservationService.js";
|
||||
import {
|
||||
expectDroppedPartial,
|
||||
expectKeptPartial,
|
||||
expectOwnerError,
|
||||
} from "../ownerObservationExpectation.fixture.js";
|
||||
|
||||
const identity = { id: "electron-fixture", name: "Fixture", version: "1" };
|
||||
const pageProvider = (failure: AnalysisError): ElectronObservationPort => ({
|
||||
identity: () => identity,
|
||||
listTargets: async () =>
|
||||
err(new AnalysisCancelledError("list_electron_targets")),
|
||||
inspectPage: async () => err(failure),
|
||||
});
|
||||
const input = inspectElectronPageInputSchema.parse({
|
||||
cdp_endpoint: "http://127.0.0.1:9222",
|
||||
target_id: "electron-page",
|
||||
@@ -40,33 +52,7 @@ const inspection = electronPageInspectionSchema.parse({
|
||||
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: [],
|
||||
...emptyElectronPageCoverage(),
|
||||
});
|
||||
|
||||
it("converts a retained raw Electron inspection into partial Evidence", async () => {
|
||||
@@ -77,13 +63,7 @@ it("converts a retained raw Electron inspection into partial Evidence", async ()
|
||||
cleanup: "transport-closed",
|
||||
},
|
||||
});
|
||||
const provider: ElectronObservationPort = {
|
||||
identity: () => identity,
|
||||
listTargets: async () =>
|
||||
err(new AnalysisCancelledError("list_electron_targets")),
|
||||
inspectPage: async () => err(failure),
|
||||
};
|
||||
const result = await inspectElectronPage(provider, input);
|
||||
const result = await inspectElectronPage(pageProvider(failure), input);
|
||||
expect(result.ok).toBe(false);
|
||||
if (!result.ok) {
|
||||
expect(result.error).toBe(failure);
|
||||
@@ -116,13 +96,7 @@ it("converts a raw inspection retained by provider cleanup failure", async () =>
|
||||
},
|
||||
},
|
||||
);
|
||||
const provider: ElectronObservationPort = {
|
||||
identity: () => identity,
|
||||
listTargets: async () =>
|
||||
err(new AnalysisCancelledError("list_electron_targets")),
|
||||
inspectPage: async () => err(failure),
|
||||
};
|
||||
const result = await inspectElectronPage(provider, input);
|
||||
const result = await inspectElectronPage(pageProvider(failure), input);
|
||||
expect(result.ok).toBe(false);
|
||||
if (!result.ok) {
|
||||
expect(result.error).toBe(failure);
|
||||
@@ -244,3 +218,43 @@ it.each(["list", "inspect"] as const)(
|
||||
expect(providerCalls).toBe(0);
|
||||
},
|
||||
);
|
||||
|
||||
it("keeps cancellation when the retained Electron inspection is incomplete", async () => {
|
||||
const reason = { requested: "stop before page inspection completed" };
|
||||
const partial = {
|
||||
kind: "electron-page-observation",
|
||||
inspection: { target_id: "electron-page" },
|
||||
cleanup: "incomplete",
|
||||
};
|
||||
const failure = new AnalysisCancelledError("inspect_electron_page", {
|
||||
cause: reason,
|
||||
partialObservation: partial as never,
|
||||
});
|
||||
const result = await inspectElectronPage(pageProvider(failure), input);
|
||||
expectKeptPartial(expectOwnerError(result, failure, reason), partial);
|
||||
});
|
||||
|
||||
it.each([
|
||||
{ inspection: { requests: Number.NaN }, cleanup: "incomplete" as const },
|
||||
{
|
||||
inspection: { scripts: [undefined] },
|
||||
cleanup: "transport-closed" as const,
|
||||
},
|
||||
])(
|
||||
"drops a non-JSON Electron inspection and keeps cancellation primary",
|
||||
async (unsafe) => {
|
||||
const reason = { requested: "stop before page inspection completed" };
|
||||
const failure = new AnalysisCancelledError("inspect_electron_page", {
|
||||
cause: reason,
|
||||
partialObservation: {
|
||||
kind: "electron-page-observation",
|
||||
...unsafe,
|
||||
} as never,
|
||||
});
|
||||
const result = await inspectElectronPage(pageProvider(failure), input);
|
||||
expectDroppedPartial(
|
||||
expectOwnerError(result, failure, reason),
|
||||
"Electron provider retained a non-JSON incomplete observation.",
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -18,7 +18,10 @@ import {
|
||||
electronPageInspectionSchema,
|
||||
electronTargetListSchema,
|
||||
} from "../../domain/javascript/electronObservation.js";
|
||||
import { completeProviderEvidence } from "../ProviderOperationExecution.js";
|
||||
import {
|
||||
completeProviderEvidence,
|
||||
keepIncompleteObservation,
|
||||
} from "../ProviderOperationExecution.js";
|
||||
|
||||
/** List local file pages exposed by the explicitly selected endpoint. */
|
||||
export const listElectronTargets = async (
|
||||
@@ -79,34 +82,32 @@ export const inspectElectronPage = async (
|
||||
partial.kind === "electron-page-observation"
|
||||
) {
|
||||
const report = electronPageInspectionSchema.safeParse(partial.inspection);
|
||||
if (!report.success) {
|
||||
if (result.error instanceof AnalysisCancelledError)
|
||||
return err(
|
||||
new AnalysisOutputError(
|
||||
"inspect_electron_page",
|
||||
"Electron provider retained a malformed completed page inspection.",
|
||||
{ cause: report.error },
|
||||
),
|
||||
);
|
||||
if (report.success) {
|
||||
const inspection = {
|
||||
...report.data,
|
||||
limitations: [
|
||||
...report.data.limitations,
|
||||
partial.cleanup === "transport-closed"
|
||||
? "REA closed its CDP transport; this does not imply target shutdown."
|
||||
: "CDP transport cleanup remains incomplete.",
|
||||
],
|
||||
} satisfies ElectronPageInspection;
|
||||
result.error.partialObservation = createElectronEvidence(
|
||||
"inspect_electron_page",
|
||||
input,
|
||||
inspection,
|
||||
ready.value.identity(),
|
||||
);
|
||||
return result;
|
||||
}
|
||||
const inspection = {
|
||||
...report.data,
|
||||
limitations: [
|
||||
...report.data.limitations,
|
||||
partial.cleanup === "transport-closed"
|
||||
? "REA closed its CDP transport; this does not imply target shutdown."
|
||||
: "CDP transport cleanup remains incomplete.",
|
||||
],
|
||||
} satisfies ElectronPageInspection;
|
||||
result.error.partialObservation = createElectronEvidence(
|
||||
"inspect_electron_page",
|
||||
input,
|
||||
inspection,
|
||||
ready.value.identity(),
|
||||
);
|
||||
}
|
||||
return result;
|
||||
return err(
|
||||
keepIncompleteObservation(
|
||||
result.error,
|
||||
"inspect_electron_page",
|
||||
"Electron provider retained a non-JSON incomplete observation.",
|
||||
),
|
||||
);
|
||||
}
|
||||
const report = electronPageInspectionSchema.safeParse(result.value);
|
||||
if (!report.success)
|
||||
|
||||
@@ -14,6 +14,35 @@ import {
|
||||
listJavaScriptRuntimeTargets,
|
||||
observeJavaScriptRuntime,
|
||||
} from "./JavaScriptRuntimeObservationService.js";
|
||||
import {
|
||||
expectDroppedPartial,
|
||||
expectKeptPartial,
|
||||
expectOutputError,
|
||||
expectOwnerError,
|
||||
expectPreemptedCancellation,
|
||||
} from "../ownerObservationExpectation.fixture.js";
|
||||
|
||||
const observeCancelled = async (partial: unknown) => {
|
||||
const reason = { requested: "stop before runtime observation completed" };
|
||||
const failure = new AnalysisCancelledError("observe_javascript_runtime", {
|
||||
cause: reason,
|
||||
partialObservation: partial as never,
|
||||
});
|
||||
const result = await observeJavaScriptRuntime(
|
||||
{
|
||||
identity: () => ({ id: "test", name: "Test", version: "1" }),
|
||||
listTargets: async () =>
|
||||
err(new AnalysisCancelledError("list_javascript_runtime_targets")),
|
||||
observe: async () => err(failure),
|
||||
},
|
||||
{
|
||||
inspector_endpoint: "http://127.0.0.1:9229",
|
||||
target_id: "target",
|
||||
observation_ms: 0,
|
||||
},
|
||||
);
|
||||
return { result, failure, reason };
|
||||
};
|
||||
|
||||
it("preserves failed Inspector cleanup when cancellation occurs during observation", async () => {
|
||||
const controller = new AbortController();
|
||||
@@ -126,11 +155,7 @@ it("does not let late cancellation hide an invalid completed Inspector report",
|
||||
},
|
||||
{ inspector_endpoint: "http://127.0.0.1:9229" },
|
||||
);
|
||||
expect(output).toMatchObject({
|
||||
ok: false,
|
||||
error: { _tag: "AnalysisOutputError" },
|
||||
});
|
||||
if (!output.ok) expect(output.error).toBeInstanceOf(AnalysisOutputError);
|
||||
expectOutputError(output);
|
||||
|
||||
const controller = new AbortController();
|
||||
const late = await listJavaScriptRuntimeTargets(
|
||||
@@ -147,11 +172,7 @@ it("does not let late cancellation hide an invalid completed Inspector report",
|
||||
{ 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);
|
||||
expectOutputError(late);
|
||||
});
|
||||
|
||||
it("converts a raw runtime observation retained by cleanup failure", async () => {
|
||||
@@ -309,11 +330,23 @@ it.each(["list", "observe"] as const)(
|
||||
{ signal: controller.signal },
|
||||
);
|
||||
|
||||
expect(result.ok).toBe(false);
|
||||
if (!result.ok) {
|
||||
expect(result.error).toBeInstanceOf(AnalysisCancelledError);
|
||||
expect(result.error.cause).toBe(reason);
|
||||
}
|
||||
expect(providerCalls).toBe(0);
|
||||
expectPreemptedCancellation(result, reason, providerCalls);
|
||||
},
|
||||
);
|
||||
|
||||
it("keeps cancellation when the retained runtime observation is incomplete", async () => {
|
||||
const partial = { runtime: "partial", scripts: [] };
|
||||
const { result, failure, reason } = await observeCancelled(partial);
|
||||
expectKeptPartial(expectOwnerError(result, failure, reason), partial);
|
||||
});
|
||||
|
||||
it.each([{ scripts: [undefined] }, { elapsed_ms: Number.NaN }])(
|
||||
"drops a non-JSON runtime partial and keeps cancellation primary",
|
||||
async (partial) => {
|
||||
const { result, failure, reason } = await observeCancelled(partial);
|
||||
expectDroppedPartial(
|
||||
expectOwnerError(result, failure, reason),
|
||||
"Inspector retained a non-JSON incomplete observation.",
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -13,7 +13,10 @@ import type {
|
||||
ObserveJavaScriptRuntimeInput,
|
||||
} from "../../domain/javascript/javascriptRuntimeObservation.js";
|
||||
import { err, ok, type Result } from "../../domain/result.js";
|
||||
import { completeProviderEvidence } from "../ProviderOperationExecution.js";
|
||||
import {
|
||||
completeProviderEvidence,
|
||||
keepIncompleteObservation,
|
||||
} from "../ProviderOperationExecution.js";
|
||||
import {
|
||||
javascriptRuntimeObservationSchema,
|
||||
javascriptRuntimeTargetListSchema,
|
||||
@@ -76,15 +79,7 @@ export const observeJavaScriptRuntime = async (
|
||||
!("kind" in partial)
|
||||
) {
|
||||
const report = javascriptRuntimeObservationSchema.safeParse(partial);
|
||||
if (!report.success && result.error instanceof AnalysisCancelledError)
|
||||
return err(
|
||||
new AnalysisOutputError(
|
||||
"observe_javascript_runtime",
|
||||
"Inspector retained a malformed completed runtime observation.",
|
||||
{ cause: report.error },
|
||||
),
|
||||
);
|
||||
if (report.success)
|
||||
if (report.success) {
|
||||
result.error.partialObservation =
|
||||
createJavaScriptRuntimeObservationEvidence(
|
||||
"observe_javascript_runtime",
|
||||
@@ -92,8 +87,16 @@ export const observeJavaScriptRuntime = async (
|
||||
report.data,
|
||||
ready.value.identity(),
|
||||
);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
return err(
|
||||
keepIncompleteObservation(
|
||||
result.error,
|
||||
"observe_javascript_runtime",
|
||||
"Inspector retained a non-JSON incomplete observation.",
|
||||
),
|
||||
);
|
||||
}
|
||||
const report = javascriptRuntimeObservationSchema.safeParse(result.value);
|
||||
if (!report.success)
|
||||
|
||||
Reference in New Issue
Block a user