132 lines
3.9 KiB
TypeScript
132 lines
3.9 KiB
TypeScript
import { createHash } from "node:crypto";
|
|
import { isAbsolute } from "node:path";
|
|
|
|
import { isPathWithinRoot } from "./localPath.js";
|
|
import canonicalize from "canonicalize";
|
|
import { z } from "zod";
|
|
|
|
import {
|
|
classifyBrowserCompleteness,
|
|
type BrowserCompleteness,
|
|
} from "./browserCompleteness.js";
|
|
import {
|
|
electronActiveObservationResultSchema,
|
|
type ElectronActiveObservationResult,
|
|
} from "./electronActiveObservation.js";
|
|
import type { Evidence } from "./evidence.js";
|
|
import type { ParsedRuntimeCapture } from "./javascriptRuntimeReconciliationParsing.js";
|
|
|
|
/** Parse active Electron Evidence as a bounded, target-only runtime capture. */
|
|
export const parseActiveElectronCapture = (
|
|
evidence: Evidence,
|
|
): ParsedRuntimeCapture => {
|
|
assertIdentity(evidence);
|
|
const result = electronActiveObservationResultSchema.parse(
|
|
evidence.normalized_result,
|
|
);
|
|
const parameters = z
|
|
.object({
|
|
application_path: absolutePathSchema,
|
|
application_root: absolutePathSchema,
|
|
})
|
|
.passthrough()
|
|
.parse(evidence.parameters);
|
|
if (
|
|
result.application.application_path !== parameters.application_path ||
|
|
!isPathWithinRoot(
|
|
parameters.application_root,
|
|
result.application.application_path,
|
|
)
|
|
)
|
|
throw new TypeError(
|
|
"Active Electron Evidence application path disagrees with its configured root",
|
|
);
|
|
return {
|
|
kind: "electron-active",
|
|
evidence,
|
|
inspection: normalizeInspection(result),
|
|
captureSha256: digestCanonical(result),
|
|
scriptsCompleteWithinScope: false,
|
|
};
|
|
};
|
|
|
|
const absolutePathSchema = z.string().min(1).max(16_384).refine(isAbsolute);
|
|
|
|
const assertIdentity = (evidence: Evidence): void => {
|
|
if (
|
|
evidence.operation !== "capture_electron_scenario" ||
|
|
evidence.predicate_type !== "rea.electron-active-scenario/v1" ||
|
|
evidence.provider.id !== "rea-playwright-electron-active" ||
|
|
evidence.provider.name !==
|
|
"REA Playwright active Electron observation provider" ||
|
|
evidence.provider.version !== "1" ||
|
|
evidence.authority !== "controlled-replay" ||
|
|
evidence.confidence !== "observed"
|
|
)
|
|
throw new TypeError(
|
|
"Evidence does not match the supported capture_electron_scenario contract",
|
|
);
|
|
};
|
|
|
|
const normalizeInspection = (
|
|
result: ElectronActiveObservationResult,
|
|
): {
|
|
readonly target: {
|
|
readonly target_id: string;
|
|
readonly type: string;
|
|
readonly title: string;
|
|
readonly attached: boolean;
|
|
readonly file_path: string;
|
|
};
|
|
readonly frames: readonly [];
|
|
readonly scripts: { readonly items: readonly [] };
|
|
readonly workers: readonly [];
|
|
readonly completeness: BrowserCompleteness;
|
|
} => ({
|
|
target: {
|
|
target_id: `electron-active:${digestCanonical(result.application.application_path).slice(0, 32)}`,
|
|
type: "electron-application",
|
|
title: result.application.application_path,
|
|
attached: true,
|
|
file_path: result.application.application_path,
|
|
},
|
|
frames: [],
|
|
scripts: { items: [] },
|
|
workers: [],
|
|
completeness: classifyBrowserCompleteness({
|
|
policyFilteredSections: new Set(),
|
|
attachLimitedSections: new Set(),
|
|
truncatedSections: new Set(
|
|
result.windows_truncated ||
|
|
result.processes.truncated ||
|
|
result.ipc.truncated ||
|
|
result.timeline.truncated
|
|
? ["timeline"]
|
|
: [],
|
|
),
|
|
unavailableSections: new Set([
|
|
"frames",
|
|
"scripts",
|
|
"script_sources",
|
|
"workers",
|
|
]),
|
|
excluded: [],
|
|
droppedEvents: {
|
|
scripts: 0,
|
|
network_requests: 0,
|
|
console_events: 0,
|
|
websocket_connections: 0,
|
|
websocket_frames: 0,
|
|
webmcp_tools: 0,
|
|
timeline_events: result.timeline.truncated ? result.timeline.observed : 0,
|
|
},
|
|
}),
|
|
});
|
|
|
|
const digestCanonical = (value: unknown): string => {
|
|
const encoded = canonicalize(value);
|
|
if (encoded === undefined)
|
|
throw new TypeError("Runtime reconciliation could not canonicalize input");
|
|
return createHash("sha256").update(encoded).digest("hex");
|
|
};
|