mirror of
https://github.com/morluto/rea.git
synced 2026-11-04 05:46:17 +00:00
* feat(browser): attribute native execution and listener sources Integrate finite native precise-coverage windows and direct DOM listener inspection through shared CLI/MCP workflows, exact session script identities, inline source evidence and owned instrumentation cleanup. Refs #881, #774 Assisted-by: Yukari (gensokyo-skills:yukari-boundary-analysis) Assisted-by: Kogasa (gensokyo-skills:kogasa-surprise-testing) * fix(browser): preserve runtime identity and settle fixture cleanup Retain complete reported initiator objects, reject changing loader availability, propagate the selected operation through main-frame authorization and complete effect/help inventories. Close only the synthetic owned Chrome via its native endpoint, await process-group settlement and preserve primary verification errors during cleanup. Refs #881 Assisted-by: Yukari (gensokyo-skills:yukari-boundary-analysis) Assisted-by: Kogasa (gensokyo-skills:kogasa-surprise-testing) * fix(browser): settle invalid events and preserve document evidence Accept same-document URL changes with proven stable loader identity, retain metadata without inventing ownership, terminate malformed event collection immediately, and sanitize known transport URLs without erasing sourceURL declarations. Assisted-by: Yukari (gensokyo-skills:yukari-boundary-analysis) Assisted-by: Kogasa (gensokyo-skills:kogasa-surprise-testing) * fix(browser): end detached target windows and sanitize coverage identities Reuse the existing flat-session event matcher, retain prearming termination, and apply the source identity policy to coverage URLs. Add real MCP target-close verification and producer regressions for detachment orderings. Assisted-by: Yukari (gensokyo-skills:yukari-boundary-analysis) Assisted-by: Kogasa (gensokyo-skills:kogasa-surprise-testing) * fix(browser): retain final sample metadata and settle fatal protocol failures Keep source inventory open through the resetting sample, propagate fatal wire failures without outstanding commands, and check listener collection failures after final document validation. Assisted-by: Yukari (gensokyo-skills:yukari-boundary-analysis) Assisted-by: Kogasa (gensokyo-skills:kogasa-surprise-testing) * fix(browser): require default execution worlds for website attribution Preserve native context and world metadata without treating same-frame isolated or unknown contexts as website sources. Verify actual Chrome isolated function execution before excluding its coverage, and cover ambiguous world metadata with producer regressions. Assisted-by: Yukari (gensokyo-skills:yukari-boundary-analysis) Assisted-by: Kogasa (gensokyo-skills:kogasa-surprise-testing) * fix(browser): revalidate runtime source joins and selector failures Assisted-by: Yukari (gensokyo-skills:yukari-boundary-analysis) Assisted-by: Kogasa (gensokyo-skills:kogasa-surprise-testing) --------- Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
162 lines
7.7 KiB
TypeScript
162 lines
7.7 KiB
TypeScript
import { z } from "zod";
|
|
import { BrowserObservationError } from "../../domain/browserObservationError.js";
|
|
import {
|
|
AnalysisCancelledError,
|
|
AnalysisOutputError,
|
|
} from "../../domain/analysisErrorCore.js";
|
|
import type {
|
|
ObserveWebExecutionInput,
|
|
WebExecution,
|
|
} from "../../domain/webExecution.js";
|
|
import { CdpExecutionCollection } from "./CdpExecutionCollection.js";
|
|
import { preciseCoverageSchema } from "./CdpRuntimeProtocol.js";
|
|
import { normalizePreciseCoverage } from "./CdpPreciseCoverage.js";
|
|
import type { CdpRuntimeSources } from "./CdpRuntimeSources.js";
|
|
|
|
/** Capture one explicit resetting coverage sample and its request-source relationships. */
|
|
export const captureWebExecution = async (
|
|
input: ObserveWebExecutionInput,
|
|
sources: CdpRuntimeSources,
|
|
): Promise<WebExecution> => {
|
|
const session = sources.session;
|
|
const collection = new CdpExecutionCollection(sources);
|
|
const { window, requests } = collection;
|
|
try {
|
|
for (const domain of ["Page", "Runtime", "Debugger", "Network", "Profiler"])
|
|
await session.enable(domain);
|
|
await session.assertDocument();
|
|
sources.check();
|
|
session.preciseCoverageMayBeActive = true;
|
|
const started = z.object({ timestamp: z.number().min(0) }).parse(
|
|
await session.command("Profiler.startPreciseCoverage", {
|
|
callCount: true,
|
|
detailed: true,
|
|
allowTriggeredUpdates: false,
|
|
}),
|
|
);
|
|
sources.check();
|
|
window.check();
|
|
if (window.reason !== undefined)
|
|
throw new BrowserObservationError(session.operation, "target_changed", {
|
|
detail: `Selected target ${session.target.target_id} ended with ${window.reason} before execution observation could be armed.`,
|
|
});
|
|
const waiting = window.start(input.observation_ms);
|
|
// Cancellation may arrive while asynchronous progress is being delivered.
|
|
void waiting.catch(() => undefined);
|
|
await session.options.progress?.report({
|
|
phase: "browser_execution",
|
|
completed: 1,
|
|
total: 2,
|
|
message:
|
|
"Browser execution observation armed; perform the requested action now",
|
|
});
|
|
const reason = await waiting;
|
|
session.beginCompletion();
|
|
if (session.options.signal?.aborted)
|
|
throw new AnalysisCancelledError(session.operation);
|
|
sources.check();
|
|
requests.check();
|
|
let raw: unknown;
|
|
if (reason === "window_elapsed") {
|
|
await session.assertDocument();
|
|
raw = await session.command("Profiler.takePreciseCoverage");
|
|
}
|
|
collection.freezeMetadata();
|
|
await session.command("Profiler.stopPreciseCoverage");
|
|
session.preciseCoverageMayBeActive = false;
|
|
const requestItems = requests.result();
|
|
const parsed =
|
|
raw === undefined ? undefined : preciseCoverageSchema.safeParse(raw);
|
|
if (parsed !== undefined && !parsed.success)
|
|
throw new AnalysisOutputError(
|
|
session.operation,
|
|
`Malformed precise coverage: ${parsed.error.message}`,
|
|
);
|
|
const mainScripts = [...sources.scripts.keys()].filter((id) =>
|
|
sources.belongsToDocument(id),
|
|
);
|
|
const reportedIds = new Set(
|
|
parsed?.success === true
|
|
? parsed.data.result.map((script) => script.scriptId)
|
|
: [],
|
|
);
|
|
const ids = [
|
|
...mainScripts,
|
|
...requestItems
|
|
.flatMap((request) => request.callsites.map((site) => site.script_id))
|
|
.filter((id) => id.length > 0),
|
|
];
|
|
const retained = await sources.read(ids);
|
|
if (reason === "window_elapsed") await session.assertDocument();
|
|
sources.check();
|
|
const verifiedRequests = requests.result();
|
|
const verifiedMainScripts = mainScripts.filter((id) =>
|
|
sources.belongsToDocument(id),
|
|
);
|
|
const sample =
|
|
raw === undefined
|
|
? undefined
|
|
: normalizePreciseCoverage(raw, sources, retained);
|
|
await session.options.progress?.report({
|
|
phase: "browser_execution",
|
|
completed: 2,
|
|
total: 2,
|
|
message: "Browser execution observation ended",
|
|
terminal: true,
|
|
});
|
|
return {
|
|
browser: session.discovery.version,
|
|
target: session.target,
|
|
window: {
|
|
armed_at: window.armedAt ?? new Date().toISOString(),
|
|
ended_at: window.endedAt ?? new Date().toISOString(),
|
|
requested_ms: input.observation_ms,
|
|
end_reason: reason,
|
|
producer_started_seconds: started.timestamp,
|
|
producer_sampled_seconds: sample?.timestamp ?? null,
|
|
},
|
|
coverage: sample?.coverage ?? {
|
|
state: "unavailable",
|
|
reason: `The observation ended with ${reason}; no complete final precise-coverage sample was retained.`,
|
|
offset_units: "utf16-code-units",
|
|
end_offset: "exclusive",
|
|
scripts: [],
|
|
excluded_scripts: 0,
|
|
},
|
|
requests: verifiedRequests,
|
|
excluded_requests: requests.excluded,
|
|
script_inventory: {
|
|
main_document_scripts: verifiedMainScripts.length,
|
|
not_reported_script_ids: verifiedMainScripts.filter(
|
|
(id) => !reportedIds.has(id),
|
|
),
|
|
coverage_absence: "unknown",
|
|
},
|
|
sources: retained,
|
|
instrumentation: {
|
|
resets_execution_counters: true,
|
|
disables_optimized_execution: true,
|
|
takes_one_resetting_sample: sample !== undefined,
|
|
executes_selected_code: false,
|
|
cleanup: "confirmed",
|
|
page_ownership: "external",
|
|
},
|
|
limitations: [
|
|
"Website source ownership requires the selected main frame and a proven default execution world. Isolated, worker and unknown worlds are excluded from coverage/source joins; referenced producer context metadata is retained.",
|
|
"Precise coverage resets counters, retains execution records and prevents optimized execution until stopped. Timing and JIT behavior are affected.",
|
|
"Only the selected main document's proven frame/context scripts are attributed. Child frames, workers, service workers, WebAssembly and scripts without ownership metadata remain outside this operation.",
|
|
"V8 reports nested function/block ranges and counts, not a chronological trace. Counts must not be summed across overlapping ranges; zero counts concern this instrumented window only.",
|
|
"Previously parsed scripts may be absent from a precise sample. Their source inventory is retained, but missing coverage never means zero execution or an unexecuted function.",
|
|
"A detailed-coverage request does not guarantee block granularity for already compiled functions. When is_block_coverage is false, branch execution is unknown; REA preserves the function-only producer ranges.",
|
|
"The local armed/end clock and backend coverage timestamps are distinct clocks. Counters start when the backend accepts instrumentation, before the armed receipt, and end at the resetting sample after the requested window. Counts may include execution during those command intervals; requests are restricted to the locally armed window.",
|
|
"Source metadata is collected through the final resetting sample, then frozen for source joins. Known identities remain checked during asynchronous source completion; newly parsed scripts after that cutoff are outside this evidence.",
|
|
"Request callsites are producer-reported initiators associated by session script ID; they do not establish UI causality. Unresolved asynchronous parent IDs are retained without fetching their stacks.",
|
|
"Producer hashes and UTF-8 digests of retained text are distinct identities. Inline script locations use reported resource offsets; anonymous/eval/sourceURL names are declarations.",
|
|
"Metadata has an 8 MiB script budget and 8 MiB request budget; complete retained sources have a 32 MiB budget. Resource failures return no partial success. The externally owned browser's CPU and memory are not controlled by REA.",
|
|
],
|
|
};
|
|
} finally {
|
|
collection.close();
|
|
}
|
|
};
|