Merge pull request #1293 from morluto/fix/process-capture-terminal-byte-accounting

fix(process): validate terminal byte totals against retained frames
This commit is contained in:
Kaoru0822 authored and GitHub committed 2026-10-09 07:20:08 -07:00
commit 69833fb0d0
2 files changed
+48 -2

No files matched your search

+24 -2
View File
@@ -140,17 +140,39 @@ const validateCoverage = (
hasCaptureTruncation(
details,
), "truncation_details", "aggregate truncation must match producer coverage");
for (const [name, retention, count] of [
["raw_terminal", details.raw_terminal, capture.frames.length],
for (const [name, retention, count, bytes] of [
[
"raw_terminal",
details.raw_terminal,
capture.frames.length,
capture.frames.reduce(
(total, frame) =>
total + Buffer.byteLength(frame.raw_data ?? frame.data),
0,
),
],
[
"rendered_terminal",
details.rendered_terminal,
capture.rendered_frames.length,
capture.rendered_frames.reduce(
(total, frame) =>
frame.lines.reduce(
(sum, line) => sum + Buffer.byteLength(line),
total + Buffer.byteLength(frame.serialized_state),
),
0,
),
],
] as const) {
// Whole frames are retained or omitted, so retained bytes are exactly the
// retained frames' bytes, and nothing is unretained when no frame was omitted.
require(retention.retained_frames === count &&
retention.observed_frames >= count &&
retention.retained_bytes === bytes &&
retention.observed_bytes >= retention.retained_bytes &&
(retention.observed_frames > count ||
retention.observed_bytes === retention.retained_bytes) &&
retention.retained_bytes <=
retention.budget_bytes, `truncation_details.${name}`, "retained observations and budget accounting do not agree");
}
@@ -133,6 +133,30 @@ itWithCaptureCapability.each(["cli", "mcp"] as const)(
expect(
processCaptureSchema.safeParse({ ...result, truncated: false }).success,
).toBe(false);
const withTerminal = (
name: "raw_terminal" | "rendered_terminal",
change: Partial<typeof details.raw_terminal>,
) =>
processCaptureSchema.safeParse({
...result,
truncation_details: {
...details,
[name]: { ...details[name], ...change },
},
}).success;
// Byte totals must match the retained frames, and with no omitted frame
// nothing observed can be missing from retention.
expect(withTerminal("raw_terminal", { retained_bytes: 0 })).toBe(false);
expect(
withTerminal("raw_terminal", {
observed_bytes: details.raw_terminal.observed_bytes + 1,
}),
).toBe(false);
expect(
withTerminal("rendered_terminal", {
retained_bytes: details.rendered_terminal.retained_bytes - 1,
}),
).toBe(false);
},
20_000,
);