From ecf6ac9f8d249fbb0daf8dd80720d9f0fad9a5ea Mon Sep 17 00:00:00 2001 From: morluto <76467478+morluto@users.noreply.github.com> Date: Wed, 11 Nov 2026 17:15:54 +0800 Subject: [PATCH] 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 --- .../ElectronActiveObservationService.test.ts | 141 ++++++++++++++++++ .../ElectronActiveObservationService.ts | 69 +++++++-- .../javascript/ElectronEvidence.ts | 17 ++- .../ElectronObservationService.test.ts | 129 ++++++++++++++++ .../javascript/ElectronObservationService.ts | 73 +++++++-- src/domain/analysisErrorBase.ts | 12 +- tests/boundary/mcp/electronMcp.test.ts | 18 ++- .../javascriptRuntimeReconciliation.ts | 127 ++++++++-------- 8 files changed, 489 insertions(+), 97 deletions(-) diff --git a/src/application/javascript/ElectronActiveObservationService.test.ts b/src/application/javascript/ElectronActiveObservationService.test.ts index 11e24dd6c9..7c139bb897 100644 --- a/src/application/javascript/ElectronActiveObservationService.test.ts +++ b/src/application/javascript/ElectronActiveObservationService.test.ts @@ -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" }, + }); +}); diff --git a/src/application/javascript/ElectronActiveObservationService.ts b/src/application/javascript/ElectronActiveObservationService.ts index 6bfeb57709..aef16811ed 100644 --- a/src/application/javascript/ElectronActiveObservationService.ts +++ b/src/application/javascript/ElectronActiveObservationService.ts @@ -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> => { + 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, + ); }; diff --git a/src/application/javascript/ElectronEvidence.ts b/src/application/javascript/ElectronEvidence.ts index 22237bde36..b789c093f3 100644 --- a/src/application/javascript/ElectronEvidence.ts +++ b/src/application/javascript/ElectronEvidence.ts @@ -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, diff --git a/src/application/javascript/ElectronObservationService.test.ts b/src/application/javascript/ElectronObservationService.test.ts index 6a6302ce58..e690f2d19b 100644 --- a/src/application/javascript/ElectronObservationService.test.ts +++ b/src/application/javascript/ElectronObservationService.test.ts @@ -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(); +}); diff --git a/src/application/javascript/ElectronObservationService.ts b/src/application/javascript/ElectronObservationService.ts index ead41e418f..c3b4ca68ac 100644 --- a/src/application/javascript/ElectronObservationService.ts +++ b/src/application/javascript/ElectronObservationService.ts @@ -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", diff --git a/src/domain/analysisErrorBase.ts b/src/domain/analysisErrorBase.ts index 5571feb424..5f04101798 100644 --- a/src/domain/analysisErrorBase.ts +++ b/src/domain/analysisErrorBase.ts @@ -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 diff --git a/tests/boundary/mcp/electronMcp.test.ts b/tests/boundary/mcp/electronMcp.test.ts index bd5369365b..d040ff5cff 100644 --- a/tests/boundary/mcp/electronMcp.test.ts +++ b/tests/boundary/mcp/electronMcp.test.ts @@ -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( - 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 = { diff --git a/tests/fixtures/javascriptRuntimeReconciliation.ts b/tests/fixtures/javascriptRuntimeReconciliation.ts index 2f0310ce46..96faa251ab 100644 --- a/tests/fixtures/javascriptRuntimeReconciliation.ts +++ b/tests/fixtures/javascriptRuntimeReconciliation.ts @@ -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",