fix(runtime): bind Inspector evidence to the selected request (#10039)

This commit is contained in:
morluto authored and GitHub committed 2026-11-11 17:15:08 +08:00
1 parent dd522bf9a7
commit 81d4f6b42a
2 files changed
+142 -34

No files matched your search

@@ -22,6 +22,41 @@ import {
expectPreemptedCancellation,
} from "../../../tests/fixtures/ownerObservationExpectation.js";
const runtimeObservation = javascriptRuntimeObservationSchema.parse({
runtime: {
product: "node.js/v24",
protocol_version: "1.3",
v8_version: "13",
},
target: {
target_id: "target",
protocol_type: "node",
attached: false,
location: { kind: "file", file_path: "/fixture.js" },
runtime_kind: "node",
runtime_kind_authority: "caller-declared-unverified",
},
capture: {
observation_ms: 0,
events_observed: 0,
events_retained: 0,
events_dropped: 0,
metadata_bytes_retained: 0,
truncated: false,
truncation_reasons: [],
},
scripts: {
items: [],
observed_total: 0,
excluded: { unsupported_location: 0, invalid_protocol_value: 0 },
},
execution_contexts: [],
directly_observed: ["Inspector target attached and queried."],
unavailable_without_instrumentation: [],
unknowns: [],
limitations: [],
});
const observeCancelled = async (partial: unknown) => {
const reason = { requested: "stop before runtime observation completed" };
const failure = new AnalysisCancelledError("observe_javascript_runtime", {
@@ -175,40 +210,7 @@ it("does not let late cancellation hide an invalid completed Inspector report",
});
it("converts a raw runtime observation retained by cleanup failure", async () => {
const observation = javascriptRuntimeObservationSchema.parse({
runtime: {
product: "node.js/v24",
protocol_version: "1.3",
v8_version: "13",
},
target: {
target_id: "target",
protocol_type: "node",
attached: false,
location: { kind: "file", file_path: "/fixture.js" },
runtime_kind: "node",
runtime_kind_authority: "caller-declared-unverified",
},
capture: {
observation_ms: 0,
events_observed: 0,
events_retained: 0,
events_dropped: 0,
metadata_bytes_retained: 0,
truncated: false,
truncation_reasons: [],
},
scripts: {
items: [],
observed_total: 0,
excluded: { unsupported_location: 0, invalid_protocol_value: 0 },
},
execution_contexts: [],
directly_observed: ["Inspector target attached and queried."],
unavailable_without_instrumentation: [],
unknowns: [],
limitations: [],
});
const observation = runtimeObservation;
const failure = new BrowserObservationError(
"observe_javascript_runtime",
"cleanup_failed",
@@ -239,6 +241,7 @@ it("converts a raw runtime observation retained by cleanup failure", async () =>
{
inspector_endpoint: "http://127.0.0.1:9229",
target_id: "target",
runtime_kind: "node",
observation_ms: 0,
},
);
@@ -349,3 +352,75 @@ it.each([{ scripts: [undefined] }, { elapsed_ms: Number.NaN }])(
);
},
);
it.each([
[
"target",
{
...runtimeObservation,
target: { ...runtimeObservation.target, target_id: "other-target" },
},
],
[
"window",
{
...runtimeObservation,
capture: { ...runtimeObservation.capture, observation_ms: 1 },
},
],
[
"declared kind",
{
...runtimeObservation,
target: {
...runtimeObservation.target,
runtime_kind: "unknown" as const,
},
},
],
[
"kind authority",
{
...runtimeObservation,
target: {
...runtimeObservation.target,
runtime_kind_authority: "not-declared" as const,
},
},
],
])(
"does not attest a provider's changed %s as the selected runtime observation",
async (_name, report) => {
const input = {
inspector_endpoint: "http://127.0.0.1:9229",
target_id: "target",
observation_ms: 0,
runtime_kind: "node" as const,
};
const provider: JavaScriptRuntimeObservationPort = {
identity: () => ({ id: "test", name: "Test", version: "1" }),
listTargets: async () => {
throw new Error("unused");
},
observe: async () => ok(report),
};
const completed = await observeJavaScriptRuntime(provider, input);
expect(completed).toMatchObject({
ok: false,
error: { _tag: "AnalysisOutputError", partialObservation: report },
});
const primary = new AnalysisCancelledError("observe_javascript_runtime", {
partialObservation: report,
});
const failed = await observeJavaScriptRuntime(
{ ...provider, observe: async () => err(primary) },
input,
);
expect(failed).toMatchObject({ ok: false, error: primary });
expect(primary.partialObservation).toEqual(report);
expect(primary.partialObservation).not.toHaveProperty("evidence_id");
expect(primary.secondaryFailures).toMatchObject([
{ _tag: "AnalysisOutputError" },
]);
},
);
@@ -11,6 +11,7 @@ import type { AnalysisError } from "../../domain/analysisErrorBase.js";
import type {
ListJavaScriptRuntimeTargetsInput,
ObserveJavaScriptRuntimeInput,
JavaScriptRuntimeObservation,
} from "../../domain/javascript/javascriptRuntimeObservation.js";
import { err, ok, type Result } from "../../domain/result.js";
import {
@@ -80,6 +81,17 @@ export const observeJavaScriptRuntime = async (
) {
const report = javascriptRuntimeObservationSchema.safeParse(partial);
if (report.success) {
const mismatch = runtimeSelectionFailure(input, report.data);
if (mismatch !== undefined) {
result.error.retainSecondaryFailure(mismatch);
return err(
keepIncompleteObservation(
result.error,
"observe_javascript_runtime",
"Inspector retained a non-JSON incomplete observation.",
),
);
}
result.error.partialObservation =
createJavaScriptRuntimeObservationEvidence(
"observe_javascript_runtime",
@@ -107,6 +119,9 @@ export const observeJavaScriptRuntime = async (
{ cause: report.error },
),
);
const mismatch = runtimeSelectionFailure(input, report.data);
if (mismatch !== undefined)
return err(mismatch.retainPartialObservation(report.data));
return runtimeEvidence(
"observe_javascript_runtime",
input,
@@ -116,6 +131,24 @@ export const observeJavaScriptRuntime = async (
);
};
/** Shape-valid observations still must identify the caller-selected request. */
const runtimeSelectionFailure = (
input: ObserveJavaScriptRuntimeInput,
report: JavaScriptRuntimeObservation,
): AnalysisOutputError | undefined =>
report.target.target_id !== input.target_id ||
report.capture.observation_ms !== input.observation_ms ||
report.target.runtime_kind !== (input.runtime_kind ?? "unknown") ||
report.target.runtime_kind_authority !==
(input.runtime_kind === undefined
? "not-declared"
: "caller-declared-unverified")
? new AnalysisOutputError(
"observe_javascript_runtime",
"Inspector changed the selected target, observation window, or caller-declared runtime kind.",
)
: undefined;
const requireProvider = (
provider: JavaScriptRuntimeObservationPort | undefined,
operation: "list_javascript_runtime_targets" | "observe_javascript_runtime",