Files
rea/src/domain/javascript/javascriptRuntimeReconciliationCaptureParsing.ts
T
morluto e55ef72135 fix(runtime)!: preserve unknown debugger attachment state
Keep omitted discovery attachment state as null through Node, browser, Electron, and reconciliation evidence. Report the uncertainty while retaining Node observation unless discovery explicitly marks the target attached. Reject worker TargetInfo records missing the required boolean with partial coverage.

Validate omitted and explicit states through provider, CLI, and MCP boundaries and real Node 24 Inspector workflows. Consolidate Electron regression coverage into the existing list-and-inspect workflow.

BREAKING CHANGE: attached fields in JavaScript runtime, browser, and Electron target discovery and inspection results may be null when discovery does not report attachment state.
2026-10-12 12:37:00 +08:00

482 lines
14 KiB
TypeScript

import { z } from "zod";
import {
browserAllowedOriginsSchema,
browserEndpointSchema,
} from "../browserObservation.js";
import {
webPageInspectionSchema,
type WebPageInspection,
} from "../browserObservationSchemas.js";
import {
classifyBrowserCompleteness,
type BrowserCompleteness,
} from "../browserCompleteness.js";
import {
electronPageInspectionSchema,
type ElectronPageInspection,
} from "./electronObservation.js";
import {
javascriptRuntimeObservationSchema,
javascriptRuntimeKindSchema,
type JavaScriptRuntimeObservation,
type JavaScriptRuntimeTargetLocation,
} from "./javascriptRuntimeObservation.js";
import { digestCanonicalValue } from "../canonicalDigest.js";
import { compareUnicodeCodePoints } from "../unicodeCodePointOrder.js";
import { parseEvidence, type Evidence } from "../evidence.js";
import { parseActiveElectronCapture } from "./javascriptRuntimeReconciliationActive.js";
import {
assertEvidenceIdentity,
invalidInput,
parseAtPath,
} from "./javascriptRuntimeReconciliationInputValidation.js";
interface RuntimeCaptureBase {
readonly evidence: Evidence;
readonly captureSha256: string;
readonly scriptsCompleteWithinScope: boolean;
}
interface NormalizedV8Inspection {
readonly target:
| {
readonly target_id: string;
readonly type: string;
readonly title: string;
readonly attached: boolean | null;
readonly file_path: string;
}
| {
readonly target_id: string;
readonly type: string;
readonly title: string;
readonly attached: boolean | null;
readonly url: string;
}
| {
readonly target_id: string;
readonly type: string;
readonly title: string;
readonly attached: boolean | null;
readonly unresolved_location: Extract<
JavaScriptRuntimeObservation["target"]["location"],
{ kind: "unresolved" }
>;
};
readonly frames: readonly [];
readonly scripts: {
readonly items: readonly (Readonly<{
script_key: string;
frame_id: string | null;
cdp_hash: string | null;
length: number | null;
is_module: boolean | null;
language: null;
source: { readonly included: false; readonly reason: string };
}> &
(
| { readonly file_path: string }
| { readonly url: string }
| {
readonly unresolved_location: Extract<
JavaScriptRuntimeObservation["scripts"]["items"][number]["location"],
{ kind: "unresolved" }
>;
}
))[];
};
readonly workers: readonly [];
readonly completeness: BrowserCompleteness;
}
export type ParsedRuntimeCapture =
| (RuntimeCaptureBase & {
readonly kind: "browser";
readonly inspection: WebPageInspection;
})
| (RuntimeCaptureBase & {
readonly kind: "electron";
readonly inspection: ElectronPageInspection;
})
| (RuntimeCaptureBase & {
readonly kind: "v8-inspector";
readonly inspection: NormalizedV8Inspection;
})
| (RuntimeCaptureBase & {
readonly kind: "electron-active";
readonly inspection: {
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;
};
});
/** Parse only supported passive web/Electron inspection Evidence. */
export const parseRuntimeCaptures = (
observations: readonly Evidence[],
path: readonly (string | number)[] = ["runtime_observations"],
): ParsedRuntimeCapture[] =>
observations
.map((observation, index) =>
parseRuntimeCapture(observation, [...path, index]),
)
.sort((left, right) =>
compareUnicodeCodePoints(
left.evidence.evidence_id,
right.evidence.evidence_id,
),
);
const parseRuntimeCapture = (
input: Evidence,
evidencePath: readonly (string | number)[],
): ParsedRuntimeCapture => {
const evidence = parseAtPath(parseEvidence, input, evidencePath);
if (evidence.operation === "inspect_web_page")
return parseBrowserCapture(evidence, evidencePath);
if (evidence.operation === "inspect_electron_page")
return parseElectronCapture(evidence, evidencePath);
if (evidence.operation === "observe_javascript_runtime")
return parseV8Capture(evidence, evidencePath);
if (evidence.operation === "capture_electron_scenario") {
return parseAtPath(
() => parseActiveElectronCapture(evidence),
evidence,
evidencePath,
);
}
throw invalidInput(
evidencePath,
"Runtime reconciliation requires inspect_web_page, inspect_electron_page, observe_javascript_runtime, or capture_electron_scenario Evidence",
);
};
const parseBrowserCapture = (
evidence: Evidence,
path: readonly (string | number)[],
): ParsedRuntimeCapture => {
assertEvidenceIdentity(
evidence,
{
operation: "inspect_web_page",
predicate: "rea.web-page-inspection",
providerId: "rea-cdp-browser",
providerName: "REA Chrome DevTools Protocol observation provider",
providerVersion: "2",
authority: "external-service",
confidence: "observed",
},
path,
);
const inspection = parseAtPath(
(value) => webPageInspectionSchema.parse(value),
evidence.normalized_result,
[...path, "normalized_result"],
);
assertRuntimeParameters(
evidence,
{
kind: "browser",
targetId: inspection.target.target_id,
targetOrigin: inspection.target.origin,
sourceIncluded: inspection.scripts.items.some(
({ source }) => source.included,
),
},
path,
);
return {
kind: "browser",
evidence,
inspection,
captureSha256: digestCanonicalValue(inspection, "Runtime reconciliation"),
scriptsCompleteWithinScope: scriptsComplete(inspection.completeness),
};
};
const parseElectronCapture = (
evidence: Evidence,
path: readonly (string | number)[],
): ParsedRuntimeCapture => {
assertEvidenceIdentity(
evidence,
{
operation: "inspect_electron_page",
predicate: "rea.electron-page-inspection",
providerId: "rea-cdp-electron",
providerName: "REA Electron file-page CDP observation provider",
providerVersion: "1",
authority: "external-service",
confidence: "observed",
},
path,
);
const inspection = parseAtPath(
(value) => electronPageInspectionSchema.parse(value),
evidence.normalized_result,
[...path, "normalized_result"],
);
assertRuntimeParameters(
evidence,
{
kind: "electron",
targetId: inspection.target.target_id,
sourceIncluded: inspection.scripts.items.some(
({ source }) => source.included,
),
},
path,
);
return {
kind: "electron",
evidence,
inspection,
captureSha256: digestCanonicalValue(inspection, "Runtime reconciliation"),
scriptsCompleteWithinScope: scriptsComplete(inspection.completeness),
};
};
const parseV8Capture = (
evidence: Evidence,
path: readonly (string | number)[],
): ParsedRuntimeCapture => {
assertEvidenceIdentity(
evidence,
{
operation: "observe_javascript_runtime",
predicate: "rea.javascript-runtime-observation",
providerId: "rea-v8-inspector",
providerName: "REA passive Node/Electron V8 Inspector provider",
providerVersion: "1",
authority: "external-service",
confidence: "observed",
},
path,
);
const result = parseAtPath(
(value) => javascriptRuntimeObservationSchema.parse(value),
evidence.normalized_result,
[...path, "normalized_result"],
);
assertV8RuntimeParameters(evidence, result, path);
return {
kind: "v8-inspector",
evidence,
inspection: normalizeV8Inspection(result),
captureSha256: digestCanonicalValue(result, "Runtime reconciliation"),
scriptsCompleteWithinScope: false,
};
};
const assertV8RuntimeParameters = (
evidence: Evidence,
result: JavaScriptRuntimeObservation,
path: readonly (string | number)[],
): void => {
const parameters = parseAtPath(
(value) =>
z
.object({
inspector_endpoint: browserEndpointSchema,
target_id: z.string().trim().min(1),
runtime_kind: javascriptRuntimeKindSchema.optional(),
})
.passthrough()
.parse(value),
evidence.parameters,
[...path, "parameters"],
);
if (
parameters.target_id !== result.target.target_id ||
(parameters.runtime_kind !== undefined &&
parameters.runtime_kind !== result.target.runtime_kind)
)
throw invalidInput(
[...path, "parameters", "target_id"],
"Runtime Evidence target or declared role disagrees with its result",
);
if (
result.target.location.kind === "builtin" &&
!result.target.location.specifier.startsWith("node:")
)
throw invalidInput(
[...path, "normalized_result", "target", "location"],
"Runtime Evidence contains an invalid builtin location",
);
for (const [index, { location }] of result.scripts.items.entries())
if (location.kind === "builtin" && !location.specifier.startsWith("node:"))
throw invalidInput(
[...path, "normalized_result", "scripts", "items", index, "location"],
"Runtime Evidence contains an invalid builtin location",
);
};
const normalizeV8Inspection = (
result: JavaScriptRuntimeObservation,
): NormalizedV8Inspection => ({
target: {
target_id: result.target.target_id,
type: result.target.protocol_type,
title: result.target.runtime_kind,
attached: result.target.attached,
...(result.target.location.kind === "unresolved"
? { unresolved_location: result.target.location }
: runtimeLocation(result.target.location)),
},
frames: [],
scripts: {
items: result.scripts.items.map((script) => ({
script_key: script.script_key,
frame_id: script.execution_context_key,
...runtimeLocation(script.location),
cdp_hash: script.cdp_hash,
length: script.length,
is_module: script.is_module,
language: null,
source: {
included: false,
reason: "V8 Inspector source capture is outside passive authority.",
},
})),
},
workers: [],
completeness: classifyBrowserCompleteness({
policyFilteredSections: new Set(
excludedV8Scripts(result) > 0 ? ["scripts"] : [],
),
attachLimitedSections: new Set([
"frames",
"scripts",
"script_sources",
"workers",
]),
truncatedSections: new Set(result.capture.truncated ? ["scripts"] : []),
unavailableSections: new Set(["frames", "script_sources", "workers"]),
excluded:
excludedV8Scripts(result) === 0
? []
: [
{
section: "scripts",
reason: "out_of_target_scope",
count: excludedV8Scripts(result),
},
],
droppedEvents: {
scripts: result.capture.events_dropped,
network_requests: 0,
console_events: 0,
websocket_connections: 0,
websocket_frames: 0,
webmcp_tools: 0,
timeline_events: 0,
},
}),
});
const runtimeLocation = (
location: JavaScriptRuntimeTargetLocation,
):
| { readonly file_path: string }
| { readonly url: string }
| {
readonly unresolved_location: Extract<
JavaScriptRuntimeTargetLocation,
{ kind: "unresolved" }
>;
} => {
if (location.kind === "unresolved") return { unresolved_location: location };
if (location.kind === "file") return { file_path: location.file_path };
if (location.kind === "url") return { url: location.sanitized_url };
return { url: location.specifier };
};
const excludedV8Scripts = (result: JavaScriptRuntimeObservation): number =>
Object.values(result.scripts.excluded).reduce(
(total, count) => total + count,
0,
);
const assertRuntimeParameters = (
evidence: Evidence,
expected:
| {
readonly kind: "browser";
readonly targetId: string;
readonly targetOrigin: string;
readonly sourceIncluded: boolean;
}
| {
readonly kind: "electron";
readonly targetId: string;
readonly sourceIncluded: boolean;
},
path: readonly (string | number)[],
): void => {
const common = {
target_id: z.string().trim().min(1),
cdp_endpoint: browserEndpointSchema,
include_script_sources: z.boolean(),
};
if (expected.kind === "browser") {
const parameters = parseAtPath(
(value) =>
z
.object({ ...common, allowed_origins: browserAllowedOriginsSchema })
.passthrough()
.parse(value),
evidence.parameters,
[...path, "parameters"],
);
if (parameters.target_id !== expected.targetId)
throw invalidInput(
[...path, "parameters", "target_id"],
"Runtime Evidence target disagrees with its captured result",
);
if (!parameters.allowed_origins.includes(expected.targetOrigin))
throw invalidInput(
[...path, "parameters", "allowed_origins"],
"Browser Evidence target is outside its recorded origin scope",
);
if (expected.sourceIncluded && !parameters.include_script_sources)
throw invalidInput(
[...path, "parameters", "include_script_sources"],
"Runtime Evidence contains source without source-capture selection",
);
return;
}
const parameters = parseAtPath(
(value) => z.object(common).passthrough().parse(value),
evidence.parameters,
[...path, "parameters"],
);
if (parameters.target_id !== expected.targetId)
throw invalidInput(
[...path, "parameters", "target_id"],
"Runtime Evidence target disagrees with its captured result",
);
if (expected.sourceIncluded && !parameters.include_script_sources)
throw invalidInput(
[...path, "parameters", "include_script_sources"],
"Runtime Evidence contains source without source-capture selection",
);
};
const scriptsComplete = (completeness: {
readonly truncated_sections: readonly string[];
readonly unavailable_sections: readonly string[];
readonly dropped_events: { readonly scripts: number };
}): boolean =>
!completeness.truncated_sections.includes("scripts") &&
!completeness.unavailable_sections.includes("scripts") &&
completeness.dropped_events.scripts === 0;