diff --git a/src/browser/history/QlogCapture.test.ts b/src/browser/history/QlogCapture.test.ts index 97572d671b..bb2a7f00e6 100644 --- a/src/browser/history/QlogCapture.test.ts +++ b/src/browser/history/QlogCapture.test.ts @@ -332,6 +332,23 @@ it.each([ }, ); +it("counts hidden payloads only for events interpreted as transport facts", () => { + const text = capture([ + { name: "extension:custom", time: 0, data: { body: "ordinary" } }, + event([frame(0, 10, false)]), + { name: "recovery:packet_lost", time: 2, data: { packet_number: 5 } }, + ]); + const control = facts(text); + expect(control.traces[0]?.incomplete_fact_records).toEqual([]); + const hidden = facts(text, ["data"]); + expect(hidden.traces[0]?.incomplete_fact_records).toEqual([1, 2]); + expect(hidden.decoded.records.map((record) => record.reported)).toEqual([ + { name: "extension:custom", time: 0 }, + { name: "transport:packet_sent", time: 1791819895000.25 }, + { name: "recovery:packet_lost", time: 2 }, + ]); +}); + it("omits a hidden derived total for a stream with more contributions than call arguments allow", () => { const events = Array.from({ length: 130 }, (_, index) => event( diff --git a/src/browser/history/QlogCapture.ts b/src/browser/history/QlogCapture.ts index b66b728fb3..61a22d9ee7 100644 --- a/src/browser/history/QlogCapture.ts +++ b/src/browser/history/QlogCapture.ts @@ -228,6 +228,9 @@ const collectTransportFacts = ( }, ): void => { const { fact, streams, excluded, name } = state; + // Unknown event payloads carry no facts under this interpretation profile. + // Their omission cannot make the transport-fact inventory incomplete. + if (name !== "recovery:packet_lost" && !packetEvent(name)) return; if (!object(projection.reported) || !object(projection.reported.data)) { fact.incomplete_fact_records.push(ordinal); return; @@ -235,10 +238,7 @@ const collectTransportFacts = ( const visible = projection.reported; // Facts are selected by event name, so a declaration that hides the name // of a loss or packet event omits its facts like any other hidden field. - if ( - (name === "recovery:packet_lost" || packetEvent(name)) && - visible.name !== name - ) { + if (visible.name !== name) { fact.incomplete_fact_records.push(ordinal); return; } diff --git a/tests/acceptance/tools/qlogCapture.test.ts b/tests/acceptance/tools/qlogCapture.test.ts index 3691c685dc..6c5a8bb0d1 100644 --- a/tests/acceptance/tools/qlogCapture.test.ts +++ b/tests/acceptance/tools/qlogCapture.test.ts @@ -45,6 +45,54 @@ const qlog = (events: unknown[]) => ], }); +it("does not classify hidden custom event data as incomplete transport facts", async () => { + const root = await createTestTempDirectory("rea-qlog-hidden-extension-"); + const path = join(root, "input.qlog"); + await writeFile( + path, + qlog([ + { name: "extension:custom", time: 1, data: { body: "ordinary" } }, + { + ...event, + data: { + ...event.data, + frames: [{ ...event.data.frames[0], fin: false }], + }, + }, + ]), + ); + const { call } = await connectLocalToolsMcp(); + for (const sensitive of [[], ["data"]]) { + const response = await call("inspect_web_network_capture", { + capture_path: path, + format: "qlog", + sensitive_values: sensitive, + }); + expect(response.isError, JSON.stringify(response)).not.toBe(true); + const mcp = parseEvidence(response.structuredContent); + const cliResult = await execute(process.execPath, [ + cli, + "inspect-web-network-capture", + path, + "qlog", + ...sensitive.flatMap((value) => ["--sensitive-value", value]), + "--json", + ]); + const cliEvidence = parseEvidence(JSON.parse(cliResult.stdout)); + expect(cliEvidence.normalized_result).toEqual(mcp.normalized_result); + const capture = webNetworkCaptureSchema.parse(mcp.normalized_result); + if (capture.format !== "qlog") throw new Error("qlog format required"); + expect(capture.qlog.traces[0]?.incomplete_fact_records).toEqual( + sensitive.length === 0 ? [] : [1], + ); + if (sensitive.length > 0) + expect(capture.records[0]?.reported).toEqual({ + name: "extension:custom", + time: 1, + }); + } +}); + const assertAdditionalQlogFacets = async ( call: Awaited>["call"], retained: { kind: string; evidence_id: string },