mirror of
https://github.com/morluto/rea.git
synced 2026-11-12 01:41:21 +00:00
fix(electron): admit provider reports and bind selected inputs (#10041)
* fix(electron): admit runtime observations before publishing evidence * fix(electron): retain passive observation source JSON
This commit is contained in:
1 parent
550b7df262
commit
ecf6ac9f8d
8 files changed
+489
-97
No files matched your search
@@ -265,3 +265,144 @@ it.skipIf(process.platform === "win32")(
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
it("does not enter the active provider after caller cancellation", async () => {
|
||||
const { readable } = await fixture();
|
||||
const { provider, capture } = recordingProvider();
|
||||
const controller = new AbortController();
|
||||
const reason = { requested: "stop before capture" };
|
||||
controller.abort(reason);
|
||||
const result = await captureElectronScenario(
|
||||
provider,
|
||||
electronActiveObservationInputSchema.parse({
|
||||
executable_path: process.execPath,
|
||||
application_path: join(readable, "main.js"),
|
||||
}),
|
||||
{ signal: controller.signal },
|
||||
);
|
||||
expect(result).toMatchObject({
|
||||
ok: false,
|
||||
error: { _tag: "AnalysisCancelledError", cause: reason },
|
||||
});
|
||||
expect(capture).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it.each([false, true])(
|
||||
"validates completed active output before late cancellation (malformed: %s)",
|
||||
async (malformed) => {
|
||||
const { readable } = await fixture();
|
||||
const controller = new AbortController();
|
||||
const reason = { requested: "stop during capture finalization" };
|
||||
const application = join(readable, "main.js");
|
||||
const report = createElectronActiveObservationFixtureResult(application);
|
||||
const result = await captureElectronScenario(
|
||||
{
|
||||
identity: () => ({ id: "test", name: "test", version: "1" }),
|
||||
capture: async () => {
|
||||
controller.abort(reason);
|
||||
return ok(
|
||||
malformed
|
||||
? { ...report, limitations: [undefined] as never }
|
||||
: report,
|
||||
);
|
||||
},
|
||||
},
|
||||
electronActiveObservationInputSchema.parse({
|
||||
executable_path: process.execPath,
|
||||
application_path: application,
|
||||
}),
|
||||
{ signal: controller.signal },
|
||||
);
|
||||
expect(result).toMatchObject({
|
||||
ok: false,
|
||||
error: malformed
|
||||
? { _tag: "AnalysisOutputError", partialObservation: undefined }
|
||||
: {
|
||||
_tag: "AnalysisCancelledError",
|
||||
cause: reason,
|
||||
partialObservation: {
|
||||
operation: "capture_electron_scenario",
|
||||
normalized_result: report,
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
it.each(["executable_path", "application_path"] as const)(
|
||||
"does not attest active output for another %s",
|
||||
async (field) => {
|
||||
const { readable } = await fixture();
|
||||
const application = join(readable, "main.js");
|
||||
const report = createElectronActiveObservationFixtureResult(application);
|
||||
const changed = {
|
||||
...report,
|
||||
application: { ...report.application, [field]: join(readable, "other") },
|
||||
};
|
||||
const result = await captureElectronScenario(
|
||||
{
|
||||
identity: () => ({ id: "test", name: "test", version: "1" }),
|
||||
capture: async () => ok(changed),
|
||||
},
|
||||
electronActiveObservationInputSchema.parse({
|
||||
executable_path: process.execPath,
|
||||
application_path: application,
|
||||
}),
|
||||
);
|
||||
expect(result).toMatchObject({
|
||||
ok: false,
|
||||
error: { _tag: "AnalysisOutputError", partialObservation: changed },
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
it("does not invoke hidden provider getters while validating an active capture", async () => {
|
||||
const { readable } = await fixture();
|
||||
const application = join(readable, "main.js");
|
||||
const report = createElectronActiveObservationFixtureResult(application);
|
||||
let getterCalls = 0;
|
||||
Object.defineProperty(report, "windows", {
|
||||
enumerable: false,
|
||||
get() {
|
||||
getterCalls += 1;
|
||||
throw new Error("Hidden windows getter must not run");
|
||||
},
|
||||
});
|
||||
await expect(
|
||||
captureElectronScenario(
|
||||
{
|
||||
identity: () => ({ id: "test", name: "test", version: "1" }),
|
||||
capture: async () => ok(report),
|
||||
},
|
||||
electronActiveObservationInputSchema.parse({
|
||||
executable_path: process.execPath,
|
||||
application_path: application,
|
||||
}),
|
||||
),
|
||||
).resolves.toMatchObject({
|
||||
ok: false,
|
||||
error: { _tag: "AnalysisOutputError" },
|
||||
});
|
||||
expect(getterCalls).toBe(0);
|
||||
});
|
||||
|
||||
it("refuses active result extensions under its strict completed contract", async () => {
|
||||
const { readable } = await fixture();
|
||||
const application = join(readable, "main.js");
|
||||
const report = createElectronActiveObservationFixtureResult(application);
|
||||
const result = await captureElectronScenario(
|
||||
{
|
||||
identity: () => ({ id: "test", name: "test", version: "1" }),
|
||||
capture: async () =>
|
||||
ok({ ...report, provider_metadata: { source_id: "unsupported" } }),
|
||||
},
|
||||
electronActiveObservationInputSchema.parse({
|
||||
executable_path: process.execPath,
|
||||
application_path: application,
|
||||
}),
|
||||
);
|
||||
expect(result).toMatchObject({
|
||||
ok: false,
|
||||
error: { _tag: "AnalysisOutputError" },
|
||||
});
|
||||
});
|
||||
@@ -9,15 +9,22 @@ import type {
|
||||
} from "./ElectronActiveObservationPort.js";
|
||||
import {
|
||||
AnalysisAccessDeniedError,
|
||||
AnalysisCancelledError,
|
||||
AnalysisCapabilityUnavailableError,
|
||||
AnalysisInputError,
|
||||
AnalysisOutputError,
|
||||
type AnalysisInputIssue,
|
||||
} from "../../domain/analysisErrorCore.js";
|
||||
import type { AnalysisError } from "../../domain/analysisErrorBase.js";
|
||||
import type { Evidence } from "../../domain/evidence.js";
|
||||
import type { ElectronActiveObservationInput } from "../../domain/javascript/electronActiveObservation.js";
|
||||
import {
|
||||
electronActiveObservationResultSchema,
|
||||
type ElectronActiveObservationInput,
|
||||
} from "../../domain/javascript/electronActiveObservation.js";
|
||||
import { err, ok, type Result } from "../../domain/result.js";
|
||||
import { createElectronActiveEvidence } from "./ElectronActiveEvidence.js";
|
||||
import { completeProviderEvidence } from "../ProviderOperationExecution.js";
|
||||
import { jsonValueSchema } from "../../domain/jsonValue.js";
|
||||
|
||||
const OPERATION = "capture_electron_scenario" as const;
|
||||
const PROVIDER_ID = "rea-playwright-electron-active";
|
||||
@@ -186,8 +193,16 @@ export const captureElectronScenario = async (
|
||||
input: ElectronActiveObservationInput,
|
||||
options: ExecutionOptions = {},
|
||||
): Promise<Result<Evidence, AnalysisError>> => {
|
||||
if (options.signal?.aborted)
|
||||
return err(
|
||||
new AnalysisCancelledError(OPERATION, { cause: options.signal.reason }),
|
||||
);
|
||||
const canonical = await canonicalizeInput(input);
|
||||
if (!canonical.ok) return canonical;
|
||||
if (options.signal?.aborted)
|
||||
return err(
|
||||
new AnalysisCancelledError(OPERATION, { cause: options.signal.reason }),
|
||||
);
|
||||
if (provider === undefined)
|
||||
return err(
|
||||
new AnalysisCapabilityUnavailableError(
|
||||
@@ -197,15 +212,45 @@ export const captureElectronScenario = async (
|
||||
),
|
||||
);
|
||||
const captured = await provider.capture(canonical.value, options);
|
||||
return captured.ok
|
||||
? {
|
||||
ok: true,
|
||||
value: createElectronActiveEvidence(
|
||||
input,
|
||||
captured.value,
|
||||
provider.identity(),
|
||||
canonical.value,
|
||||
),
|
||||
}
|
||||
: captured;
|
||||
if (!captured.ok) return captured;
|
||||
const json = jsonValueSchema.safeParse(captured.value);
|
||||
if (!json.success)
|
||||
return err(
|
||||
new AnalysisOutputError(
|
||||
OPERATION,
|
||||
"Electron provider returned non-JSON active capture data.",
|
||||
{ cause: json.error },
|
||||
),
|
||||
);
|
||||
const report = electronActiveObservationResultSchema.safeParse(json.data);
|
||||
if (!report.success)
|
||||
return err(
|
||||
new AnalysisOutputError(
|
||||
OPERATION,
|
||||
"Electron provider returned a malformed active capture.",
|
||||
{ cause: report.error },
|
||||
),
|
||||
);
|
||||
if (
|
||||
report.data.application.executable_path !==
|
||||
canonical.value.executable_path ||
|
||||
report.data.application.application_path !==
|
||||
canonical.value.application_path
|
||||
)
|
||||
return err(
|
||||
new AnalysisOutputError(
|
||||
OPERATION,
|
||||
"Electron provider changed the selected executable or application.",
|
||||
{ partialObservation: report.data },
|
||||
),
|
||||
);
|
||||
return completeProviderEvidence(
|
||||
createElectronActiveEvidence(
|
||||
input,
|
||||
report.data,
|
||||
provider.identity(),
|
||||
canonical.value,
|
||||
),
|
||||
options.signal,
|
||||
);
|
||||
};
|
||||
@@ -1,3 +1,5 @@
|
||||
import { isDeepStrictEqual } from "node:util";
|
||||
import type { JsonValue } from "../../domain/jsonValue.js";
|
||||
import type { ProviderIdentity } from "../AnalysisProvider.js";
|
||||
import {
|
||||
createEvidence,
|
||||
@@ -11,16 +13,23 @@ import type {
|
||||
ListElectronTargetsInput,
|
||||
} from "../../domain/javascript/electronObservation.js";
|
||||
|
||||
/** Own JSON source and the facts admitted by the named Electron output contract. */
|
||||
export interface ElectronEvidenceReport {
|
||||
readonly source: JsonValue;
|
||||
readonly facts: ElectronTargetList | ElectronPageInspection;
|
||||
}
|
||||
|
||||
type ElectronOperation = "list_electron_targets" | "inspect_electron_page";
|
||||
|
||||
/** Create Evidence for one Electron observation. */
|
||||
export const createElectronEvidence = (
|
||||
operation: ElectronOperation,
|
||||
input: ListElectronTargetsInput | InspectElectronPageInput,
|
||||
result: ElectronTargetList | ElectronPageInspection,
|
||||
report: ElectronEvidenceReport,
|
||||
provider: ProviderIdentity,
|
||||
): Evidence =>
|
||||
createEvidence(undefined, provider, {
|
||||
): Evidence => {
|
||||
const result = report.facts;
|
||||
return createEvidence(undefined, provider, {
|
||||
predicateType:
|
||||
operation === "list_electron_targets"
|
||||
? "rea.electron-target-list"
|
||||
@@ -28,6 +37,7 @@ export const createElectronEvidence = (
|
||||
operation,
|
||||
parameters: parameters(input),
|
||||
result,
|
||||
rawResult: isDeepStrictEqual(report.source, result) ? null : report.source,
|
||||
confidence: "observed",
|
||||
authority: "external-service",
|
||||
environment: {
|
||||
@@ -41,6 +51,7 @@ export const createElectronEvidence = (
|
||||
"Browser host platform and architecture were not observed.",
|
||||
],
|
||||
});
|
||||
};
|
||||
|
||||
const parameters = (
|
||||
input: ListElectronTargetsInput | InspectElectronPageInput,
|
||||
|
||||
@@ -274,3 +274,132 @@ it.each([
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
it.each([
|
||||
{ ...inspection, target: { ...inspection.target, target_id: "other-page" } },
|
||||
{
|
||||
...inspection,
|
||||
capture_window: { ...inspection.capture_window, observation_ms: 1 },
|
||||
},
|
||||
])(
|
||||
"refuses to attest a different selected page or capture window",
|
||||
async (report) => {
|
||||
const provider = pageProvider(
|
||||
new AnalysisCancelledError("inspect_electron_page"),
|
||||
);
|
||||
const result = await inspectElectronPage(
|
||||
{ ...provider, inspectPage: async () => ok(report) },
|
||||
input,
|
||||
);
|
||||
expect(result).toMatchObject({
|
||||
ok: false,
|
||||
error: { _tag: "AnalysisOutputError", partialObservation: report },
|
||||
});
|
||||
const failure = new AnalysisCancelledError("inspect_electron_page", {
|
||||
partialObservation: {
|
||||
kind: "electron-page-observation",
|
||||
inspection: report,
|
||||
cleanup: "transport-closed",
|
||||
},
|
||||
});
|
||||
const partial = failure.partialObservation;
|
||||
const cancelled = await inspectElectronPage(pageProvider(failure), input);
|
||||
expect(cancelled).toMatchObject({ ok: false, error: failure });
|
||||
expect(failure.partialObservation).toEqual(partial);
|
||||
expect(failure.secondaryFailures).toMatchObject([
|
||||
{ _tag: "AnalysisOutputError" },
|
||||
]);
|
||||
},
|
||||
);
|
||||
|
||||
it.each(["completed", "partial"] as const)(
|
||||
"does not invoke hidden provider getters in a %s page report",
|
||||
async (phase) => {
|
||||
const report = { ...inspection };
|
||||
let getterCalls = 0;
|
||||
Object.defineProperty(report, "target", {
|
||||
enumerable: false,
|
||||
get() {
|
||||
getterCalls += 1;
|
||||
throw new Error("Hidden target getter must not run");
|
||||
},
|
||||
});
|
||||
const failure = new AnalysisCancelledError("inspect_electron_page", {
|
||||
partialObservation: {
|
||||
kind: "electron-page-observation",
|
||||
inspection: report,
|
||||
cleanup: "transport-closed",
|
||||
},
|
||||
});
|
||||
const provider = pageProvider(failure);
|
||||
const result = inspectElectronPage(
|
||||
phase === "partial"
|
||||
? provider
|
||||
: { ...provider, inspectPage: async () => ok(report) },
|
||||
input,
|
||||
);
|
||||
await expect(result).resolves.toMatchObject({
|
||||
ok: false,
|
||||
error:
|
||||
phase === "partial"
|
||||
? { _tag: "AnalysisCancelledError" }
|
||||
: { _tag: "AnalysisOutputError" },
|
||||
});
|
||||
expect(getterCalls).toBe(0);
|
||||
},
|
||||
);
|
||||
|
||||
it.each(["extension", "omission"])(
|
||||
"retains passive Electron source %s before named contract normalization",
|
||||
async (kind) => {
|
||||
const { workers: _workers, ...withoutWorkers } = inspection;
|
||||
const source =
|
||||
kind === "extension"
|
||||
? { ...inspection, provider_metadata: { source_id: "own-source" } }
|
||||
: withoutWorkers;
|
||||
const provider: ElectronObservationPort = {
|
||||
identity: () => identity,
|
||||
listTargets: async () =>
|
||||
err(new AnalysisCancelledError("list_electron_targets")),
|
||||
inspectPage: async () => ok(source as typeof inspection),
|
||||
};
|
||||
const result = await inspectElectronPage(provider, input);
|
||||
if (!result.ok) throw result.error;
|
||||
expect(result.value.raw_result).toEqual(source);
|
||||
expect(result.value.normalized_result).toHaveProperty("workers", []);
|
||||
expect(result.value.normalized_result).not.toHaveProperty(
|
||||
"provider_metadata",
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
it("retains passive source metadata when promoting a completed failure inspection", async () => {
|
||||
const source = {
|
||||
...inspection,
|
||||
provider_metadata: { source_id: "partial-source" },
|
||||
};
|
||||
const failure = new AnalysisCancelledError("inspect_electron_page", {
|
||||
partialObservation: {
|
||||
kind: "electron-page-observation",
|
||||
inspection: source,
|
||||
cleanup: "transport-closed",
|
||||
},
|
||||
});
|
||||
const result = await inspectElectronPage(pageProvider(failure), input);
|
||||
if (result.ok) throw new Error("expected retained failure");
|
||||
expect(parseEvidence(result.error.partialObservation).raw_result).toEqual(
|
||||
source,
|
||||
);
|
||||
});
|
||||
|
||||
it("does not duplicate unchanged passive Electron source JSON", async () => {
|
||||
const result = await inspectElectronPage(
|
||||
{
|
||||
...pageProvider(new AnalysisCancelledError("inspect_electron_page")),
|
||||
inspectPage: async () => ok(inspection),
|
||||
},
|
||||
input,
|
||||
);
|
||||
if (!result.ok) throw result.error;
|
||||
expect(result.value.raw_result).toBeNull();
|
||||
});
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
import type { AnalysisError } from "../../domain/analysisErrorBase.js";
|
||||
import { err, ok, type Result } from "../../domain/result.js";
|
||||
import { createElectronEvidence } from "./ElectronEvidence.js";
|
||||
import { jsonObjectSchema, jsonValueSchema } from "../../domain/jsonValue.js";
|
||||
import {
|
||||
electronPageInspectionSchema,
|
||||
electronTargetListSchema,
|
||||
@@ -39,7 +40,16 @@ export const listElectronTargets = async (
|
||||
);
|
||||
const result = await ready.value.listTargets(input, options);
|
||||
if (!result.ok) return result;
|
||||
const report = electronTargetListSchema.safeParse(result.value);
|
||||
const json = jsonValueSchema.safeParse(result.value);
|
||||
if (!json.success)
|
||||
return err(
|
||||
new AnalysisOutputError(
|
||||
"list_electron_targets",
|
||||
"Electron provider returned non-JSON target data.",
|
||||
{ cause: json.error },
|
||||
),
|
||||
);
|
||||
const report = electronTargetListSchema.safeParse(json.data);
|
||||
if (!report.success)
|
||||
return err(
|
||||
new AnalysisOutputError(
|
||||
@@ -52,7 +62,7 @@ export const listElectronTargets = async (
|
||||
createElectronEvidence(
|
||||
"list_electron_targets",
|
||||
input,
|
||||
report.data,
|
||||
{ source: json.data, facts: report.data },
|
||||
ready.value.identity(),
|
||||
),
|
||||
options.signal,
|
||||
@@ -75,19 +85,28 @@ export const inspectElectronPage = async (
|
||||
);
|
||||
const result = await ready.value.inspectPage(input, options);
|
||||
if (!result.ok) {
|
||||
const partial = result.error.partialObservation;
|
||||
if (
|
||||
partial !== undefined &&
|
||||
"kind" in partial &&
|
||||
partial.kind === "electron-page-observation"
|
||||
) {
|
||||
const report = electronPageInspectionSchema.safeParse(partial.inspection);
|
||||
const partial = jsonObjectSchema.safeParse(result.error.partialObservation);
|
||||
if (partial.success && partial.data.kind === "electron-page-observation") {
|
||||
const report = electronPageInspectionSchema.safeParse(
|
||||
partial.data.inspection,
|
||||
);
|
||||
if (report.success) {
|
||||
const mismatch = pageSelectionFailure(input, report.data);
|
||||
if (mismatch !== undefined) {
|
||||
result.error.retainSecondaryFailure(mismatch);
|
||||
return err(
|
||||
keepIncompleteObservation(
|
||||
result.error,
|
||||
"inspect_electron_page",
|
||||
"Electron provider retained a non-JSON incomplete observation.",
|
||||
),
|
||||
);
|
||||
}
|
||||
const inspection = {
|
||||
...report.data,
|
||||
limitations: [
|
||||
...report.data.limitations,
|
||||
partial.cleanup === "transport-closed"
|
||||
partial.data.cleanup === "transport-closed"
|
||||
? "REA closed its CDP transport; this does not imply target shutdown."
|
||||
: "CDP transport cleanup remains incomplete.",
|
||||
],
|
||||
@@ -95,7 +114,10 @@ export const inspectElectronPage = async (
|
||||
result.error.partialObservation = createElectronEvidence(
|
||||
"inspect_electron_page",
|
||||
input,
|
||||
inspection,
|
||||
{
|
||||
source: jsonValueSchema.parse(partial.data.inspection),
|
||||
facts: inspection,
|
||||
},
|
||||
ready.value.identity(),
|
||||
);
|
||||
return result;
|
||||
@@ -109,7 +131,16 @@ export const inspectElectronPage = async (
|
||||
),
|
||||
);
|
||||
}
|
||||
const report = electronPageInspectionSchema.safeParse(result.value);
|
||||
const json = jsonValueSchema.safeParse(result.value);
|
||||
if (!json.success)
|
||||
return err(
|
||||
new AnalysisOutputError(
|
||||
"inspect_electron_page",
|
||||
"Electron provider returned non-JSON page data.",
|
||||
{ cause: json.error },
|
||||
),
|
||||
);
|
||||
const report = electronPageInspectionSchema.safeParse(json.data);
|
||||
if (!report.success)
|
||||
return err(
|
||||
new AnalysisOutputError(
|
||||
@@ -118,17 +149,33 @@ export const inspectElectronPage = async (
|
||||
{ cause: report.error },
|
||||
),
|
||||
);
|
||||
const mismatch = pageSelectionFailure(input, report.data);
|
||||
if (mismatch !== undefined)
|
||||
return err(mismatch.retainPartialObservation(report.data));
|
||||
return completeProviderEvidence(
|
||||
createElectronEvidence(
|
||||
"inspect_electron_page",
|
||||
input,
|
||||
report.data,
|
||||
{ source: json.data, facts: report.data },
|
||||
ready.value.identity(),
|
||||
),
|
||||
options.signal,
|
||||
);
|
||||
};
|
||||
|
||||
/** Retained facts must describe the page and window the caller selected. */
|
||||
const pageSelectionFailure = (
|
||||
input: InspectElectronPageInput,
|
||||
report: ElectronPageInspection,
|
||||
): AnalysisOutputError | undefined =>
|
||||
report.target.target_id !== input.target_id ||
|
||||
report.capture_window.observation_ms !== input.observation_ms
|
||||
? new AnalysisOutputError(
|
||||
"inspect_electron_page",
|
||||
"Electron provider changed the selected page or observation window.",
|
||||
)
|
||||
: undefined;
|
||||
|
||||
const requireProvider = (
|
||||
provider: ElectronObservationPort | undefined,
|
||||
operation: "list_electron_targets" | "inspect_electron_page",
|
||||
|
||||
@@ -436,7 +436,10 @@ import type {
|
||||
import type { FileOffsetPartialObservation } from "./native/fileOffsetPartialObservation.js";
|
||||
import type { NativeCallPartialObservation } from "./native/nativeCallPartialObservation.js";
|
||||
import type { HopperCallerReconciliationPartial } from "./native/hopperCallerReconciliation.js";
|
||||
import type { ElectronActivePartialObservation } from "./javascript/electronActiveObservation.js";
|
||||
import type {
|
||||
ElectronActiveObservationResult,
|
||||
ElectronActivePartialObservation,
|
||||
} from "./javascript/electronActiveObservation.js";
|
||||
import type { JavaScriptRuntimeObservation } from "./javascript/javascriptRuntimeObservation.js";
|
||||
import type {
|
||||
ArtifactInventoryPartialObservation,
|
||||
@@ -446,7 +449,10 @@ import type { WebScriptExportPartialObservation } from "./webScriptExport.js";
|
||||
import type { JavaScriptRecoveryPartialObservation } from "./javascript/javascriptRecovery.js";
|
||||
import type { BrowserScenarioPartialObservation } from "./browserScenarioCapture.js";
|
||||
import type { Evidence } from "./evidence.js";
|
||||
import type { ElectronPagePartialObservation } from "./javascript/electronObservation.js";
|
||||
import type {
|
||||
ElectronPageInspection,
|
||||
ElectronPagePartialObservation,
|
||||
} from "./javascript/electronObservation.js";
|
||||
import type { AndroidPartialObservation } from "./android/androidPartialObservation.js";
|
||||
import type { FirmwarePartialObservation } from "./firmware/firmwareAnalysis.js";
|
||||
import type {
|
||||
@@ -516,6 +522,7 @@ export type AnalysisPartialObservation =
|
||||
| HopperCallerReconciliationPartial
|
||||
| FileOffsetPartialObservation
|
||||
| ElectronActivePartialObservation
|
||||
| ElectronActiveObservationResult
|
||||
| BrowserScenarioPartialObservation
|
||||
| WebPageInspection
|
||||
| WebBundleAnalysis
|
||||
@@ -541,6 +548,7 @@ export type AnalysisPartialObservation =
|
||||
| InspectSignature
|
||||
| InspectMacho
|
||||
| ElectronPagePartialObservation
|
||||
| ElectronPageInspection
|
||||
| AdbActivityStartPartialObservation
|
||||
| AdbAcquiredFilePartialObservation
|
||||
| AdbPackagePullPartialObservation
|
||||
|
||||
@@ -118,7 +118,7 @@ it("records completed Electron facts after final detach as server Evidence", asy
|
||||
file_path: "/tmp/index.html",
|
||||
attached: true,
|
||||
},
|
||||
...emptyElectronPageCoverage(1),
|
||||
...emptyElectronPageCoverage(0),
|
||||
};
|
||||
const provider: ElectronObservationPort = {
|
||||
identity: () => ({
|
||||
@@ -849,10 +849,18 @@ it.skipIf(process.platform === "win32")(
|
||||
);
|
||||
await symlink("application/child", join(root, "link"));
|
||||
const capture = vi.fn<ElectronActiveObservationPort["capture"]>(
|
||||
async (input) =>
|
||||
resultOk(
|
||||
createElectronActiveObservationFixtureResult(input.application_path),
|
||||
),
|
||||
async (input) => {
|
||||
const report = createElectronActiveObservationFixtureResult(
|
||||
input.application_path,
|
||||
);
|
||||
return resultOk({
|
||||
...report,
|
||||
application: {
|
||||
...report.application,
|
||||
executable_path: input.executable_path,
|
||||
},
|
||||
});
|
||||
},
|
||||
);
|
||||
const client = await activePathAdmissionClient(capture);
|
||||
const selected = {
|
||||
|
||||
+65
-62
@@ -1,3 +1,5 @@
|
||||
import { jsonValueSchema } from "../../src/domain/jsonValue.js";
|
||||
import type { ElectronPageInspection } from "../../src/domain/javascript/electronObservation.js";
|
||||
import { writeFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
|
||||
@@ -57,71 +59,72 @@ export const electronRuntimeEvidence = (
|
||||
observation_ms: 100,
|
||||
include_script_sources: sourceIncluded,
|
||||
});
|
||||
const report = {
|
||||
browser: {
|
||||
product: "Electron/fixture",
|
||||
protocol_version: "1.3",
|
||||
revision: "fixture",
|
||||
user_agent: "Electron fixture",
|
||||
js_version: "13",
|
||||
},
|
||||
target: {
|
||||
target_id: targetId,
|
||||
type: "page",
|
||||
title: "Fixture",
|
||||
file_path: join(root, "index.html"),
|
||||
attached: false,
|
||||
},
|
||||
capture_window: {
|
||||
started_at: "2026-07-15T00:00:00.000Z",
|
||||
ended_at: "2026-07-15T00:00:00.100Z",
|
||||
observation_ms: 100,
|
||||
},
|
||||
completeness: captureCompleteness(options),
|
||||
frames: [
|
||||
{
|
||||
frame_id: "frame-main",
|
||||
parent_frame_id: null,
|
||||
file_path: join(root, "index.html"),
|
||||
},
|
||||
],
|
||||
dom: { total_nodes: 0, nodes: [] },
|
||||
scripts: {
|
||||
total: 1,
|
||||
items: [
|
||||
{
|
||||
script_key: `electron_script_${"1".repeat(64)}`,
|
||||
frame_id: "frame-main",
|
||||
file_path: join(root, scriptFile),
|
||||
cdp_hash: "fixture",
|
||||
length: Buffer.byteLength(source),
|
||||
is_module: true,
|
||||
language: "JavaScript",
|
||||
source: {
|
||||
included: true as const,
|
||||
artifact: createWebTextArtifact(source, "text/javascript"),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
resources: [],
|
||||
workers: includeWorker
|
||||
? [
|
||||
{
|
||||
target_id: "worker-main",
|
||||
type: "worker",
|
||||
file_path: join(root, "worker.js"),
|
||||
attached: false,
|
||||
opener_target_id: targetId,
|
||||
parent_frame_id: "frame-main",
|
||||
},
|
||||
]
|
||||
: [],
|
||||
limitations: ["Synthetic passive capture fixture."],
|
||||
} satisfies ElectronPageInspection;
|
||||
return createElectronEvidence(
|
||||
"inspect_electron_page",
|
||||
input,
|
||||
{
|
||||
browser: {
|
||||
product: "Electron/fixture",
|
||||
protocol_version: "1.3",
|
||||
revision: "fixture",
|
||||
user_agent: "Electron fixture",
|
||||
js_version: "13",
|
||||
},
|
||||
target: {
|
||||
target_id: targetId,
|
||||
type: "page",
|
||||
title: "Fixture",
|
||||
file_path: join(root, "index.html"),
|
||||
attached: false,
|
||||
},
|
||||
capture_window: {
|
||||
started_at: "2026-07-15T00:00:00.000Z",
|
||||
ended_at: "2026-07-15T00:00:00.100Z",
|
||||
observation_ms: 100,
|
||||
},
|
||||
completeness: captureCompleteness(options),
|
||||
frames: [
|
||||
{
|
||||
frame_id: "frame-main",
|
||||
parent_frame_id: null,
|
||||
file_path: join(root, "index.html"),
|
||||
},
|
||||
],
|
||||
dom: { total_nodes: 0, nodes: [] },
|
||||
scripts: {
|
||||
total: 1,
|
||||
items: [
|
||||
{
|
||||
script_key: `electron_script_${"1".repeat(64)}`,
|
||||
frame_id: "frame-main",
|
||||
file_path: join(root, scriptFile),
|
||||
cdp_hash: "fixture",
|
||||
length: Buffer.byteLength(source),
|
||||
is_module: true,
|
||||
language: "JavaScript",
|
||||
source: {
|
||||
included: true as const,
|
||||
artifact: createWebTextArtifact(source, "text/javascript"),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
resources: [],
|
||||
workers: includeWorker
|
||||
? [
|
||||
{
|
||||
target_id: "worker-main",
|
||||
type: "worker",
|
||||
file_path: join(root, "worker.js"),
|
||||
attached: false,
|
||||
opener_target_id: targetId,
|
||||
parent_frame_id: "frame-main",
|
||||
},
|
||||
]
|
||||
: [],
|
||||
limitations: ["Synthetic passive capture fixture."],
|
||||
},
|
||||
{ source: jsonValueSchema.parse(report), facts: report },
|
||||
{
|
||||
id: "rea-cdp-electron",
|
||||
name: "REA Electron file-page CDP observation provider",
|
||||
|
||||
Reference in new issue
Block a user