Files
rea/src/domain/javascript/javascriptRuntimeReconciliationResult.ts
T
Kaoru0822 0ecf3f2438 perf(mcp): keep Playwright and type-only imports off MCP startup (#1691)
* fix(build): emit no runtime imports for type-only bindings

Under verbatimModuleSyntax, an import whose specifiers all carry inline
`type` modifiers compiles to `import {} from "..."`, a side-effect import
that still resolves and evaluates the module. 78 compiled modules carried
one, including `import {} from "playwright-core"` in the browser scenario
session, which loaded Playwright's module graph at MCP startup.

Use top-level `import type` for these bindings and enable oxlint's
typescript/no-import-type-side-effects rule so the pattern cannot return.

Refs #1685

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* perf(mcp): load Playwright when a scenario launches, not at startup

The browser scenario and Electron active providers are constructed before
the stdio server serves, and both bound Playwright's launchers at module
load. Resolve `chromium` and `_electron` from playwright-core when a
capture opens instead, as the web module resolver already does.

MCP startup now resolves 1,312 modules (4,902 resolutions) instead of
1,598 (6,076) before reading the first request, and no longer loads
playwright-core. Provider construction, the tool catalog, and capability
inventory are unchanged.

A boundary test spawns the stdio server, initializes, lists tools, and
asserts playwright-core and isomorphic-git were not resolved; the CLI
probe now exercises the real launch path to prove detection.

Closes #1685

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 21:04:05 -07:00

209 lines
8.2 KiB
TypeScript

import type { JavaScriptApplicationGraph } from "./javascriptApplicationGraph.js";
import { digestCanonicalValue } from "../canonicalDigest.js";
import { uniqueSorted } from "../canonicalOrdering.js";
import type { StaticLoadStateProjection } from "./javascriptRuntimeLoadState.js";
import type { RuntimeMatchingProjection } from "./javascriptRuntimeReconciliationMatching.js";
import type { ParsedStaticLayer } from "./javascriptRuntimeReconciliationParsing.js";
import type { ParsedRuntimeCapture } from "./javascriptRuntimeReconciliationCaptureParsing.js";
import {
javascriptRuntimeReconciliationResultSchema,
type JavaScriptRuntimeReconciliationResult,
} from "./javascriptRuntimeReconciliationSchemas.js";
import type { RuntimeProjection } from "./javascriptRuntimeReconciliationRuntime.js";
interface ReconciliationProjection {
readonly layers: readonly ParsedStaticLayer[];
readonly captures: readonly ParsedRuntimeCapture[];
readonly runtime: RuntimeProjection;
readonly matching: RuntimeMatchingProjection;
readonly loadStates: StaticLoadStateProjection;
readonly graph: JavaScriptApplicationGraph;
readonly omittedGraphItems: number;
}
interface CompletionFlags {
readonly outputTruncated: boolean;
readonly inputStatus: "complete" | "partial" | "truncated";
}
/** Build and verify the deterministic caller-visible reconciliation result. */
export const createJavaScriptRuntimeReconciliationResult = (
input: ReconciliationProjection,
): JavaScriptRuntimeReconciliationResult => {
const completion = completionFlags(input);
const semantic = {
static_layers: staticLayerSummaries(input.layers),
runtime_captures: captureSummaries(input.captures, input.runtime),
graph: input.graph,
summary: reconciliationSummary(input),
reconciliations: input.matching.items,
static_load_states: input.loadStates.states,
source_map_authority: sourceMapAuthority(input),
coverage: reconciliationCoverage(input, completion),
evidence_links: uniqueSorted([
...input.layers.map(({ evidence }) => evidence.evidence_id),
...input.captures.map(({ evidence }) => evidence.evidence_id),
]),
limitations: reconciliationLimitations(completion),
};
return javascriptRuntimeReconciliationResultSchema.parse({
reconciliation_id: `jrr_${digestCanonicalValue(semantic, "Runtime reconciliation")}`,
...semantic,
});
};
const staticLayerSummaries = (
layers: readonly ParsedStaticLayer[],
): JavaScriptRuntimeReconciliationResult["static_layers"] =>
layers.map((layer) => ({
layer_id: layer.layerId,
role: layer.role,
evidence_id: layer.evidence.evidence_id,
graph_id: layer.graph.graph_id,
root_artifact_sha256: layer.result.root_artifact_sha256,
input_path: layer.result.input_path,
format: layer.result.format,
runtime_mappings: [...layer.runtimeMappings],
}));
const captureSummaries = (
captures: readonly ParsedRuntimeCapture[],
runtime: RuntimeProjection,
): JavaScriptRuntimeReconciliationResult["runtime_captures"] =>
captures.map((capture) => {
const targetNode = runtime.targetNodeByEvidenceId.get(
capture.evidence.evidence_id,
);
if (targetNode === undefined)
throw new TypeError("Runtime projection omitted a required target node");
return {
evidence_id: capture.evidence.evidence_id,
capture_sha256: capture.captureSha256,
kind: capture.kind,
target_node_id: targetNode.node_id,
target_key: capture.inspection.target.target_id,
target_location:
"unresolved_location" in capture.inspection.target
? capture.inspection.target.unresolved_location
: "url" in capture.inspection.target
? capture.inspection.target.url
: capture.inspection.target.file_path,
frames: capture.inspection.frames.length,
scripts: capture.inspection.scripts.items.length,
workers: capture.inspection.workers.length,
scripts_complete_within_scope: capture.scriptsCompleteWithinScope,
};
});
const reconciliationSummary = (
input: ReconciliationProjection,
): JavaScriptRuntimeReconciliationResult["summary"] => ({
runtime_targets: countBy(input.runtime.entities, "kind", "target"),
runtime_frames: countBy(input.runtime.entities, "kind", "frame"),
runtime_scripts: countBy(input.runtime.entities, "kind", "script"),
runtime_workers: countBy(input.runtime.entities, "kind", "worker"),
matched: countBy(input.matching.items, "status", "matched"),
ambiguous: countBy(input.matching.items, "status", "ambiguous"),
unmatched: countBy(input.matching.items, "status", "unmatched"),
unknown: countBy(input.matching.items, "status", "unknown"),
static_loaded: countBy(input.loadStates.states, "status", "loaded"),
static_resident: countBy(
input.loadStates.states,
"status",
"resident-in-loaded-asset",
),
static_not_observed: countBy(
input.loadStates.states,
"status",
"not-observed-in-capture",
),
static_unknown: countBy(input.loadStates.states, "status", "unknown"),
});
const sourceMapAuthority = (
input: ReconciliationProjection,
): JavaScriptRuntimeReconciliationResult["source_map_authority"] => ({
used_for_primary_matching: false,
static_layer_count: input.layers.length,
runtime_script_declarations: input.captures.reduce(
(total, capture) =>
total +
(capture.kind === "browser"
? capture.inspection.scripts.items.filter(
({ source_map_url: url }) => url !== null,
).length
: 0),
0,
),
limitation:
"Source-map declarations and original-source content retain separate static authority and are not used as primary runtime byte matches.",
});
const reconciliationCoverage = (
input: ReconciliationProjection,
completion: CompletionFlags,
): JavaScriptRuntimeReconciliationResult["coverage"] => ({
status:
completion.outputTruncated || completion.inputStatus === "truncated"
? "truncated"
: completion.inputStatus === "partial"
? "partial"
: "complete-within-inputs",
truncated:
completion.outputTruncated || completion.inputStatus === "truncated",
omitted_runtime_entities: input.runtime.omittedEntities,
omitted_reconciliation_items: input.matching.omittedItems,
omitted_static_load_states: input.loadStates.omittedStates,
omitted_graph_items: input.omittedGraphItems,
});
const completionFlags = (input: ReconciliationProjection): CompletionFlags => {
const inputTruncated =
input.layers.some(({ graph }) => graph.coverage.truncated) ||
input.captures.some(({ inspection }) =>
inspection.completeness.conditions.includes("truncated"),
);
const inputPartial =
input.layers.some(({ graph }) => graph.coverage.status !== "complete") ||
input.captures.some(
({ inspection }) =>
inspection.completeness.status !== "complete_within_window",
);
return {
outputTruncated:
input.runtime.omittedEntities > 0 ||
input.matching.omittedItems > 0 ||
input.loadStates.omittedStates > 0 ||
input.omittedGraphItems > 0,
inputStatus: inputTruncated
? "truncated"
: inputPartial
? "partial"
: "complete",
};
};
const reconciliationLimitations = (completion: CompletionFlags): string[] =>
uniqueSorted([
"Only caller-supplied, semantically verified Evidence records were reconciled.",
"Digest equality establishes byte identity but not code reachability, module initialization, or feature execution.",
"A static module resident in an observed bundle is not reported as executed.",
"Not-observed means absent from the bounded captures in scope, never globally unloaded.",
"Caller-declared file and URL mappings are inference inputs and do not expand CDP origin or filesystem-root authority.",
"Source-map authority remains separate from passive runtime authority.",
...(completion.inputStatus !== "complete"
? ["One or more static graphs or passive captures were incomplete."]
: []),
...(completion.outputTruncated
? [
"Runtime reconciliation omitted entities or classifications during projection.",
]
: []),
]);
const countBy = <Item extends Record<Key, string>, Key extends keyof Item>(
items: readonly Item[],
key: Key,
value: Item[Key],
): number => items.filter((item) => item[key] === value).length;