Truncate execution.log patches and keep members aligned with patch bodies so verbose MiniMax-M3 runs no longer exceed the Go 8MB fail-closed limit. Surface body-too-large vs typed-reject reasons and map contract failures to fatal restart instead of misleading resumable TASK_INTERRUPTED.
129 lines
4.4 KiB
JavaScript
129 lines
4.4 KiB
JavaScript
import { test } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import {
|
|
withResultV1,
|
|
membersDigest,
|
|
typedErrorCode,
|
|
capPatches,
|
|
truncateExecutionLogContent,
|
|
estimateWireBytes,
|
|
MAX_RESULT_MEMBER_BYTES,
|
|
MAX_RESULT_WIRE_BYTES,
|
|
EXECUTION_LOG_PATCH_BYTES,
|
|
} from "./result-v1.mjs";
|
|
|
|
test("withResultV1 is additive and strips stderr", () => {
|
|
const out = withResultV1({
|
|
ok: true,
|
|
run_id: "run-1",
|
|
stderr: "secret-trace",
|
|
patches: [{ rel_path: "output.md", content: "hello" }],
|
|
});
|
|
assert.equal(out.ok, true);
|
|
assert.equal(out.patches[0].rel_path, "output.md");
|
|
assert.equal(out.stderr, undefined);
|
|
assert.equal(out.capabilities.typed_result, true);
|
|
assert.equal(out.runner_protocol, "typed-v1");
|
|
assert.equal(out.result_v1.schema_version, 1);
|
|
assert.equal(out.result_v1.run_id, "run-1");
|
|
assert.equal(out.result_v1.outcome, "success");
|
|
assert.equal(out.result_v1.members.length, 1);
|
|
assert.equal(out.result_v1.content_digest, membersDigest(out.result_v1.members));
|
|
});
|
|
|
|
test("typedErrorCode is allowlisted and ignores raw stderr", () => {
|
|
assert.equal(typedErrorCode({ ok: false, fallback_reason: "prompt_empty" }), "PROMPT_EMPTY");
|
|
assert.equal(typedErrorCode({ ok: false, fallback_reason: "Connection error." }), "TRANSIENT");
|
|
const out = withResultV1({
|
|
ok: false,
|
|
run_id: "run-2",
|
|
retryable: true,
|
|
fallback_reason: "Connection error. /tmp/secret",
|
|
stderr: "token=abc",
|
|
patches: [],
|
|
});
|
|
assert.equal(out.stderr, undefined);
|
|
assert.equal(out.result_v1.error_code, "TRANSIENT");
|
|
assert.equal(out.result_v1.retryable, true);
|
|
assert.equal(out.result_v1.outcome, "error");
|
|
});
|
|
|
|
test("path escape members are omitted", () => {
|
|
const out = withResultV1({
|
|
ok: true,
|
|
run_id: "run-3",
|
|
patches: [
|
|
{ rel_path: "../etc/passwd", content: "x" },
|
|
{ rel_path: "/abs.md", content: "y" },
|
|
{ rel_path: "output.md", content: "ok" },
|
|
],
|
|
});
|
|
assert.deepEqual(out.result_v1.members.map((m) => m.path), ["output.md"]);
|
|
assert.deepEqual(out.patches.map((p) => p.rel_path), ["output.md"]);
|
|
});
|
|
|
|
test("truncateExecutionLogContent keeps only the tail", () => {
|
|
const big = "A".repeat(EXECUTION_LOG_PATCH_BYTES + 4096);
|
|
const out = truncateExecutionLogContent(big);
|
|
assert.ok(out.startsWith("[truncated "));
|
|
assert.ok(out.length < big.length);
|
|
assert.ok(Buffer.byteLength(out, "utf8") <= EXECUTION_LOG_PATCH_BYTES + 64);
|
|
});
|
|
|
|
test("oversized patch content is dropped from both members and patches", () => {
|
|
const huge = "x".repeat(MAX_RESULT_MEMBER_BYTES + 1);
|
|
const out = withResultV1({
|
|
ok: true,
|
|
run_id: "run-4",
|
|
patches: [
|
|
{ rel_path: "output.md", content: "# ok\n" },
|
|
{ rel_path: "blob.bin", content: huge },
|
|
],
|
|
});
|
|
assert.deepEqual(out.patches.map((p) => p.rel_path), ["output.md"]);
|
|
assert.deepEqual(out.result_v1.members.map((m) => m.path), ["output.md"]);
|
|
});
|
|
|
|
test("capPatches truncates execution.log and enforces total wire budget", () => {
|
|
const log = "L".repeat(EXECUTION_LOG_PATCH_BYTES * 4);
|
|
const patches = capPatches([
|
|
{ rel_path: "output.md", content: "# deliverable\n" },
|
|
{ rel_path: "execution.log", content: log },
|
|
]);
|
|
assert.equal(patches.length, 2);
|
|
assert.ok(patches.find((p) => p.rel_path === "execution.log").content.startsWith("[truncated "));
|
|
const total = patches.reduce((n, p) => n + Buffer.byteLength(p.content, "utf8"), 0);
|
|
assert.ok(total <= MAX_RESULT_WIRE_BYTES);
|
|
|
|
// Force total budget with a custom soft cap under the member limit.
|
|
const budget = 1024;
|
|
const filler = "F".repeat(budget - 2);
|
|
const tight = capPatches(
|
|
[
|
|
{ rel_path: "a.md", content: filler },
|
|
{ rel_path: "b.md", content: "too-much" },
|
|
],
|
|
{ maxTotalBytes: budget, maxMemberBytes: budget },
|
|
);
|
|
assert.deepEqual(tight.map((p) => p.rel_path), ["a.md"]);
|
|
});
|
|
|
|
test("withResultV1 wire estimate stays under soft budget for truncated logs", () => {
|
|
const log = "E".repeat(8 * 1024 * 1024);
|
|
const out = withResultV1({
|
|
ok: true,
|
|
run_id: "run-big",
|
|
patches: [
|
|
{ rel_path: "output.md", content: "# intake\n".repeat(100) },
|
|
{ rel_path: "execution.log", content: log },
|
|
],
|
|
});
|
|
assert.ok(estimateWireBytes(out) < MAX_RESULT_WIRE_BYTES);
|
|
assert.equal(out.result_v1.members.length, out.patches.length);
|
|
for (const m of out.result_v1.members) {
|
|
const body = out.patches.find((p) => p.rel_path === m.path);
|
|
assert.ok(body);
|
|
assert.equal(Buffer.byteLength(body.content, "utf8"), m.size);
|
|
}
|
|
});
|