Files
reverse-engineering-anything/src/process/ArtifactAnalysisResources.test.ts
T
8413ec08f7 fix(process): report the effective regex heap and uncertain signal retry (#6099)
* fix(process): report the effective regex worker heap

Node gives --max-old-space-size, including NODE_OPTIONS, precedence over
the worker's 128 MiB resourceLimits request. Name that effective
old-generation heap and stop saying the request has no runtime override.
The matching deadline stays a fixed cap.

Co-authored-by: morluto <morluto@users.noreply.github.com>

* fix(process): keep SIGXCPU and SIGXFSZ retries uncertain

The signal does not prove the retained rlimit was exhausted, so an
unchanged retry may succeed. Project that retry as possible instead of
saying it fails the same way.

Co-authored-by: morluto <morluto@users.noreply.github.com>

* fix(process): leave percentage heap overrides unestablished

--max-old-space-size-percentage takes precedence over the absolute flag
in either order. Node computes that MiB from startup memory, which a
later read does not recover, so the effective heap stays unknown instead
of the 128 MiB request or an absolute flag.

Co-authored-by: morluto <morluto@users.noreply.github.com>

* fix(process): read launch NODE_OPTIONS, not process.env

A preload can rewrite process.env.NODE_OPTIONS after V8 has applied the
original flags, and those flags are not copied into execArgv. Use the
initial environment block when it can be read, and leave the effective
heap unestablished when that provenance cannot be recovered.

Co-authored-by: morluto <morluto@users.noreply.github.com>

* fix(process): leave env-file heap overrides unestablished

--env-file and --env-file-if-exists can supply NODE_OPTIONS when that
variable was absent from the initial environment. The file is not in
/proc/self/environ, so a missing entry is not the 128 MiB request.
An initial NODE_OPTIONS is still parsed, because Node does not let the
file replace it.

Co-authored-by: morluto <morluto@users.noreply.github.com>

* fix(process): leave --max-heap-size old generation unestablished

A positive --max-heap-size or --max_heap_size sets the total heap, not the
old generation, and can stay below the 128 MiB worker request when an
old-space flag is also present. The effective old generation stays
unestablished while that value is in effect. Zero or empty clears it.

Co-authored-by: morluto <morluto@users.noreply.github.com>

* fix(process): leave config-file heap options unestablished

--experimental-config-file and --experimental-default-config-file can
apply a heap percentage from a file that is not launch provenance. The
effective old generation stays unestablished when either flag is present,
including beside an absolute --max-old-space-size.

Co-authored-by: morluto <morluto@users.noreply.github.com>

* fix(process): read launch exec argv from the command line

process.execArgv can be cleared after V8 applies --max-old-space-size.
Linux recovers those tokens from /proc/self/cmdline and leaves the heap
unestablished when that vector cannot be separated from the program entry.
Heap probes compare pinned absolute limits instead of a percentage of host memory.

Co-authored-by: morluto <morluto@users.noreply.github.com>

* fix(process): leave a title-overwritten command line unestablished

process.title and --title replace /proc/self/cmdline with the title.
A buffer without a program entry is not an empty option list, so the
heap stays unknown instead of the 128 MiB request. The command-line
probe does not read /proc off Linux.

Co-authored-by: morluto <morluto@users.noreply.github.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: morluto <morluto@users.noreply.github.com>
2026-10-22 07:40:14 +08:00

1065 lines
32 KiB
TypeScript

import { AnalysisCleanupLease } from "../artifacts/AnalysisCleanupLease.js";
import { ArtifactResourceRetryFailure } from "../artifacts/ArtifactResourceScope.js";
import { ArtifactResourceScope } from "../artifacts/ArtifactResourceScope.js";
import { withArtifactRegularFile } from "../artifacts/ArtifactFileOperation.js";
import { writeFile } from "node:fs/promises";
import { join } from "node:path";
import { createTestTempDirectory } from "../../tests/fixtures/temporaryDirectory.js";
import { OwnedCommandFailure } from "./OwnedCommand.js";
import { EventEmitter } from "node:events";
import { PassThrough } from "node:stream";
import { expect, it } from "vitest";
import { readStableArtifact } from "../artifacts/readStableArtifact.js";
import { ArtifactReaderFailure } from "../artifacts/ArtifactReader.js";
import {
AnalysisCancelledError,
AnalysisOutputError,
} from "../domain/analysisErrorCore.js";
import { createEvidence } from "../domain/evidence.js";
import { projectAnalysisError } from "../domain/analysisErrorProjection.js";
import { ProviderAdapterError } from "../domain/providerAdapterError.js";
import { ProviderCleanupError } from "../domain/providerCleanupError.js";
import { err, ok } from "../domain/result.js";
import { ArtifactAnalysisResources } from "./ArtifactAnalysisResources.js";
import { readLinuxResourceLimits } from "./LinuxResourceLimits.js";
import { ProviderProcessSupervisor } from "./ProviderProcess.js";
it("projects the specific reason and member evidence for retained cleanup", async () => {
const producer = Object.assign(new EventEmitter(), {
stdout: new PassThrough(),
stderr: new PassThrough(),
exitCode: null,
signalCode: null,
kill: () => true,
});
const owner = new ProviderProcessSupervisor({
process: producer,
ownsProcessLifetime: true,
ownership: {
runId: "rea-tshark-run-1",
leaderPid: 4242,
processGroupId: 4242,
},
cleanup: async () => ({
cleaned: false as const,
reason: "process ownership token could not be read for 1 live process",
failures: [
{
pid: 101,
reason: "environment-unreadable" as const,
diagnostic: "sysctl_errno_22",
},
],
unverified: [
{ pid: 900, diagnostic: "platform_binary_environment_withheld" },
],
}),
});
const resources = new ArtifactAnalysisResources(
"fixture-provider",
"fixture-operation",
readStableArtifact,
readLinuxResourceLimits,
{ message: "busy", details: {} },
);
resources.retain({ path: "runtime-root", close: async () => {} }, owner);
const result = await resources.finish(
err(new ProviderAdapterError("fixture-provider", "fixture-operation")),
() => {
throw new Error("unexpected success observation");
},
);
owner.dispose();
if (result.ok) throw new Error("expected cleanup failure");
expect(projectAnalysisError(result.error)).toMatchObject({
code: "cleanup_incomplete",
details: {
resources: [
"rea-tshark-run-1",
"runtime-root",
"pid:4242",
"process-group:4242",
],
diagnostics: {
cleanup_error: {
details: {
diagnostics: {
reason:
"process ownership token could not be read for 1 live process",
failures: [
{
pid: 101,
reason: "environment-unreadable",
diagnostic: "sysctl_errno_22",
},
],
unverified: [
{
pid: 900,
diagnostic: "platform_binary_environment_withheld",
},
],
},
},
},
},
},
});
});
it("preserves the primary error and resolves cleanup status after retry", async () => {
let closes = 0;
const resources = new ArtifactAnalysisResources(
"fixture-provider",
"fixture-operation",
readStableArtifact,
readLinuxResourceLimits,
{ message: "busy", details: {} },
);
resources.retain({
path: "runtime-root",
close: async () => {
closes++;
if (closes === 1) throw new Error("temporary close failure");
},
});
const primary = new AnalysisOutputError(
"fixture-operation",
"producer returned malformed output",
{
partialObservation: createEvidence(
undefined,
{
id: "fixture",
name: "Fixture",
version: null,
},
{
operation: "inspect_signal_decoders",
parameters: {},
result: { decoders: [] },
confidence: "observed",
},
),
},
);
const result = await resources.finish(err(primary), () => {
throw new Error("unexpected success observation");
});
if (result.ok) throw new Error("expected cleanup failure");
expect(result.error.cause).toBeInstanceOf(AggregateError);
expect((result.error.cause as AggregateError).errors).toEqual([
primary,
expect.any(ProviderCleanupError),
]);
expect(result.error.partialObservation).toEqual(primary.partialObservation);
expect(result.error.cleanupIncomplete).toBe(true);
expect(result.error.diagnostics).toMatchObject({
primary_error: { code: "unreadable_output" },
});
await resources.release();
expect(result.error.cleanupIncomplete).toBe(false);
expect(closes).toBe(2);
await resources.close();
});
it("resolves each returned cleanup error with its own retained root", async () => {
let closesA = 0;
let closesB = 0;
const resources = new ArtifactAnalysisResources(
"fixture-provider",
"fixture-operation",
readStableArtifact,
readLinuxResourceLimits,
{ message: "busy", details: {} },
);
resources.retain({
path: "runtime-root-a",
close: async () => {
closesA++;
if (closesA === 1) throw new Error("root A close failure");
},
});
const first = await resources.finish(
err(new AnalysisOutputError("fixture-operation", "primary failure")),
() => {
throw new Error("unexpected success observation");
},
);
if (first.ok) throw new Error("expected root A cleanup failure");
resources.retain({
path: "runtime-root-b",
close: async () => {
closesB++;
if (closesB === 1) throw new Error("root B close failure");
},
});
const partial = createEvidence(
undefined,
{ id: "fixture", name: "Fixture", version: null },
{
operation: "fixture-operation",
parameters: {},
result: { state: "partial" },
confidence: "observed",
},
);
const second = await resources.finish(ok("completed"), () => partial);
if (second.ok) throw new Error("expected root B cleanup failure");
expect(second.error.partialObservation).toEqual(partial);
expect(first.error.cleanupIncomplete).toBe(false);
expect(second.error.cleanupIncomplete).toBe(true);
await resources.release();
expect(second.error.cleanupIncomplete).toBe(false);
await resources.close();
});
it("resolves an artifact cleanup error when its scope owner later closes", async () => {
let closes = 0;
const readArtifact: typeof readStableArtifact = async (
_path,
_maximumBytes,
_signal,
_fileSystem,
scope,
) => {
if (scope === undefined) throw new Error("expected scoped artifact read");
if (closes === 0) {
const owner = {
kind: "lease",
resource: "fixture descriptor",
lease: new AnalysisCleanupLease(async () => {
closes++;
if (closes === 1) throw new Error("descriptor remains open");
}, ["fixture descriptor"]),
} as const;
expect(await scope.release(owner)).toMatchObject({ kind: "failed" });
throw new ArtifactResourceRetryFailure(
[{ owner, lease: owner.lease }],
"Descriptor close failed",
{
cleanup: {
reason: "descriptor remains open",
resources: ["fixture descriptor"],
},
},
);
}
return { bytes: Buffer.alloc(0), sha256: "0".repeat(64) };
};
const resources = new ArtifactAnalysisResources(
"fixture-provider",
"fixture-operation",
readArtifact,
readLinuxResourceLimits,
{ message: "busy", details: {} },
);
const first = await resources.run(async () =>
ok(await resources.read("fixture", 1)),
);
if (first.ok) throw new Error("expected descriptor cleanup failure");
expect(first.error.cleanupIncomplete).toBe(true);
const retried = await resources.run(async () =>
ok(await resources.read("fixture", 1)),
);
expect(retried.ok).toBe(true);
expect(first.error.cleanupIncomplete).toBe(false);
expect(closes).toBe(2);
await resources.close();
});
it("keeps a runtime root until its descriptor cleanup owner is released", async () => {
let descriptorCloses = 0;
let rootCloses = 0;
const readArtifact: typeof readStableArtifact = async (
_path,
_maximumBytes,
_signal,
_fileSystem,
scope,
) => {
if (scope === undefined) throw new Error("expected scoped artifact read");
if (descriptorCloses === 0) {
const owner = {
kind: "lease",
resource: "fixture descriptor",
lease: new AnalysisCleanupLease(async () => {
descriptorCloses++;
if (descriptorCloses === 1)
throw new Error("descriptor remains open");
}, ["fixture descriptor"]),
} as const;
expect(await scope.release(owner)).toMatchObject({ kind: "failed" });
throw new ArtifactResourceRetryFailure(
[{ owner, lease: owner.lease }],
"Descriptor close failed",
{
cleanup: {
reason: "descriptor remains open",
resources: ["fixture descriptor"],
},
},
);
}
return { bytes: Buffer.alloc(0), sha256: "0".repeat(64) };
};
const resources = new ArtifactAnalysisResources(
"fixture-provider",
"fixture-operation",
readArtifact,
readLinuxResourceLimits,
{ message: "busy", details: {} },
);
const first = await resources.run(async () =>
ok(await resources.read("fixture", 1)),
);
if (first.ok) throw new Error("expected descriptor cleanup failure");
resources.retain({
path: "runtime-root",
close: async () => {
rootCloses++;
},
});
const outer = await resources.finish(first, () => {
throw new Error("unexpected success observation");
});
if (outer.ok) throw new Error("expected combined cleanup failure");
expect(rootCloses).toBe(0);
expect(outer.error.cleanupResources).toEqual([
"fixture descriptor",
"runtime-root",
]);
expect(outer.error.cause).toBeInstanceOf(AggregateError);
expect((outer.error.cause as AggregateError).errors[0]).toBe(first.error);
expect(first.error.cleanupIncomplete).toBe(true);
const retried = await resources.run(async () =>
ok(await resources.read("fixture", 1)),
);
expect(retried.ok).toBe(true);
expect(first.error.cleanupIncomplete).toBe(false);
await resources.release();
expect(rootCloses).toBe(1);
expect(outer.error.cleanupIncomplete).toBe(false);
await resources.close();
});
it.each([
null,
"opaque reason",
new ProviderCleanupError("foreign", ["foreign-resource"], {
reason: "caller cleanup",
}),
])(
"preserves caller cancellation at resource admission: %s",
async (reason) => {
const resources = new ArtifactAnalysisResources(
"fixture",
"inspect_signal_decoders",
readStableArtifact,
readLinuxResourceLimits,
{ message: "busy", details: {} },
);
let calls = 0;
try {
const result = await resources.run(async () => {
calls++;
return ok("unexpected");
}, AbortSignal.abort(reason));
expect(result.ok).toBe(false);
if (result.ok) throw new Error("Expected cancelled admission");
expect(result.error.cause).toBe(reason);
expect(result.error.cleanupResources).toEqual([]);
expect(calls).toBe(0);
if (reason instanceof ProviderCleanupError)
expect(reason.cleanupIncomplete).toBe(true);
} finally {
await resources.close();
}
},
);
it.each([
null,
"opaque reason",
new ProviderCleanupError("foreign", ["foreign-resource"], {
reason: "caller cleanup",
}),
])(
"projects the command owner's exact cancellation independently of physical causes: %s",
async (reason) => {
const resources = new ArtifactAnalysisResources(
"fixture",
"inspect_signal_decoders",
readStableArtifact,
readLinuxResourceLimits,
{ message: "busy", details: {} },
);
const physical = new Error("physical collection interruption");
const failure = new OwnedCommandFailure(
"cancelled",
"cancelled command",
null,
null,
{ cause: physical, cancellation: { reason } },
);
try {
const error = resources.commandFailure(
failure,
{ command_timeout_ms: 100, output_bytes: 1024 },
undefined,
);
expect(error).toMatchObject({ _tag: "AnalysisCancelledError" });
expect(error.cause).toBe(reason);
expect(error.cleanupResources).toEqual([]);
expect(failure.cause).toBe(physical);
if (reason instanceof ProviderCleanupError)
expect(reason.cleanupIncomplete).toBe(true);
} finally {
await resources.close();
}
},
);
it.each([false, true])(
"preserves artifact cancellation and independent failed cleanup (failed close: %s)",
async (failedClose) => {
const reason = { request: "artifact cancellation" };
const root = await createTestTempDirectory("rea-resource-cancel-");
const path = join(root, "descriptor");
await writeFile(path, "authored bytes");
let mayClose = false;
const readArtifact: typeof readStableArtifact = async (
_path,
_limit,
_signal,
_fs,
scope,
) => {
const primary = new ArtifactReaderFailure("cancelled", "read cancelled", {
cause: reason,
});
if (!failedClose) throw primary;
if (scope === undefined) throw new Error("Expected retained file scope");
return withArtifactRegularFile(path, scope, _signal, async (file) => {
const close = file.close.bind(file);
file.close = async () => {
if (!mayClose) throw new Error("injected descriptor close failure");
await close();
};
throw primary;
});
};
const resources = new ArtifactAnalysisResources(
"fixture",
"inspect_signal_decoders",
readArtifact,
readLinuxResourceLimits,
{ message: "busy", details: {} },
);
try {
const result = await resources.run(async () =>
ok(await resources.read("fixture", 1)),
);
if (result.ok) throw new Error("Expected cancelled artifact read");
const primary = failedClose ? result.error.cleanupPrimary : result.error;
expect(primary).toMatchObject({ _tag: "AnalysisCancelledError" });
expect(primary?.cause).toBe(reason);
expect(result.error.cleanupIncomplete).toBe(failedClose);
mayClose = true;
await resources.close();
expect(result.error.cleanupIncomplete).toBe(false);
expect(primary?.cause).toBe(reason);
} finally {
mayClose = true;
await resources.close();
}
},
);
it("names the fixed producer command deadline and says it has no override", async () => {
const resources = new ArtifactAnalysisResources(
"fixture",
"inspect_packet_fields",
readStableArtifact,
readLinuxResourceLimits,
{ message: "busy", details: {} },
);
const failure = new OwnedCommandFailure("timeout", "command timed out", null);
try {
const error = resources.commandFailure(
failure,
{ command_timeout_ms: 30_000, output_bytes: 1024 },
undefined,
);
const projected = projectAnalysisError(error);
expect(projected.message).toContain("30000 ms");
expect(projected.remediation.action).toContain(
"this release has no runtime override",
);
expect(projected.remediation.action).not.toContain(
"REA_SWIFTC_COMPILE_TIMEOUT_MS",
);
} finally {
await resources.close();
}
});
it("says REA retains the tighter inherited limit when a producer signal fires", async () => {
const resources = new ArtifactAnalysisResources(
"fixture",
"inspect_packet_fields",
readStableArtifact,
readLinuxResourceLimits,
{ message: "busy", details: {} },
);
const empty = { text: "", bytes: 0, observedBytes: 0 };
const failure = new OwnedCommandFailure("process", "producer signal", {
stdout: empty,
stderr: empty,
exitCode: null,
signal: "SIGXCPU",
});
try {
const error = resources.commandFailure(
failure,
{ command_timeout_ms: 30_000, output_bytes: 4_194_304 },
{
address_space_bytes: 1_073_741_824,
file_bytes: 67_108_864,
cpu_seconds: 30,
},
);
const projected = projectAnalysisError(error);
expect(projected.remediation.action).toContain(
"REA retains the tighter inherited limit",
);
expect(projected.remediation.action).toContain("1073741824 bytes");
expect(projected.remediation.action).toContain("67108864 bytes");
expect(projected.remediation.action).toContain("30 CPU seconds");
expect(projected.remediation.action).toContain(
"This release has no runtime override",
);
expect(projected.remediation.action).toContain(
"an unchanged retry may succeed or may fail the same way",
);
expect(projected.remediation.action).not.toContain(
"Repeating the unchanged request fails the same way.",
);
expect(projected.retryable).toBe(true);
expect(projected.details).toMatchObject({
reported_limits: { cpu_seconds: 30 },
});
} finally {
await resources.close();
}
});
it("keeps an unchanged SIGXFSZ retry uncertain when the inherited limit was not reported", async () => {
const resources = new ArtifactAnalysisResources(
"fixture",
"inspect_packet_fields",
readStableArtifact,
readLinuxResourceLimits,
{ message: "busy", details: {} },
);
const empty = { text: "", bytes: 0, observedBytes: 0 };
const failure = new OwnedCommandFailure("process", "producer signal", {
stdout: empty,
stderr: empty,
exitCode: null,
signal: "SIGXFSZ",
});
try {
const projected = projectAnalysisError(
resources.commandFailure(
failure,
{ command_timeout_ms: 30_000, output_bytes: 4_194_304 },
undefined,
),
);
expect(projected.remediation.action).toContain(
"The effective inherited limit was not reported.",
);
expect(projected.remediation.action).toContain(
"an unchanged retry may succeed or may fail the same way",
);
expect(projected.remediation.action).not.toContain(
"Repeating the unchanged request fails the same way.",
);
expect(projected.retryable).toBe(true);
expect(projected.details).toMatchObject({
resource: "file-size",
reported_limits: null,
});
} finally {
await resources.close();
}
});
it("treats a fixed command-output cap as a non-retryable capture allowance", async () => {
const resources = new ArtifactAnalysisResources(
"fixture",
"inspect_packet_fields",
readStableArtifact,
readLinuxResourceLimits,
{ message: "busy", details: {} },
);
const failure = new OwnedCommandFailure(
"output-limit",
"command output exceeded the retained limit",
null,
);
try {
const error = resources.commandFailure(
failure,
{ command_timeout_ms: 100, output_bytes: 1024 },
undefined,
);
expect(error).toMatchObject({
_tag: "AnalysisResourceConstraintError",
resource: "memory",
reportedLimits: { output_bytes: 1024 },
retryable: false,
});
const projected = projectAnalysisError(error);
expect(projected.retryable).toBe(false);
expect(projected.remediation.action).toContain(
"The retained command-output allowance of 1024 bytes",
);
expect(projected.remediation.action).toContain(
"this release has no runtime override",
);
expect(projected.remediation.action).not.toMatch(
/Review the reported worker memory limits/u,
);
expect(projected.remediation.action).not.toMatch(
/available host memory\. Retry/u,
);
} finally {
await resources.close();
}
});
it("retains a physical command failure when the owner also observed late cancellation", async () => {
const resources = new ArtifactAnalysisResources(
"fixture",
"inspect_signal_decoders",
readStableArtifact,
readLinuxResourceLimits,
{ message: "busy", details: {} },
);
const reason = { request: "late cancellation" };
const failure = new OwnedCommandFailure(
"process",
"physical failure",
null,
null,
{ cancellation: { reason } },
);
try {
const error = resources.commandFailure(
failure,
{ command_timeout_ms: 100, output_bytes: 1024 },
undefined,
);
expect(error).toMatchObject({ _tag: "AnalysisOutputError" });
expect(error.cause).toBe(failure);
} finally {
await resources.close();
}
});
it("preserves typed caller cancellation through failed workspace cleanup and retry", async () => {
let denied = true;
const resources = new ArtifactAnalysisResources(
"fixture",
"inspect_signal_decoders",
readStableArtifact,
readLinuxResourceLimits,
{ message: "busy", details: {} },
);
const reason = { request: "cancelled before workspace cleanup" };
const primary = new AnalysisCancelledError("inspect_signal_decoders", {
cause: reason,
});
resources.retain({
path: "workspace",
close: async () => {
if (denied) throw new Error("workspace close denied");
},
});
try {
const result = await resources.finish(err(primary), () => {
throw new Error("Must not produce success Evidence");
});
if (result.ok) throw new Error("Expected retained workspace");
expect(result.error.cleanupPrimary).toBe(primary);
expect(result.error.cleanupPrimary?.cause).toBe(reason);
await expect(resources.close()).rejects.toMatchObject({
cleanupIncomplete: true,
});
denied = false;
await resources.close();
expect(result.error.cleanupIncomplete).toBe(false);
expect(result.error.cleanupPrimary?.cause).toBe(reason);
} finally {
denied = false;
await resources.close();
}
});
it("observes cancellation after pending descriptor cleanup before admitting replacement work", async () => {
let closes = 0;
let cleanupStarted: () => void = () => {};
const started = new Promise<void>((resolve) => {
cleanupStarted = resolve;
});
let releaseCleanup: () => void = () => {};
const held = new Promise<void>((resolve) => {
releaseCleanup = resolve;
});
const readArtifact: typeof readStableArtifact = async (
_path,
_limit,
_signal,
_fs,
scope,
) => {
if (scope === undefined) throw new Error("Expected file scope");
const retained = {
kind: "lease",
resource: "descriptor",
lease: new AnalysisCleanupLease(async () => {
closes++;
if (closes === 1) throw new Error("descriptor close denied");
cleanupStarted();
await held;
}, ["descriptor"]),
} as const;
await scope.release(retained);
throw new ArtifactResourceRetryFailure(
[{ owner: retained, lease: retained.lease }],
"descriptor cleanup failed",
{
cleanup: {
reason: "descriptor close denied",
resources: ["descriptor"],
},
},
);
};
const resources = new ArtifactAnalysisResources(
"fixture",
"inspect_signal_decoders",
readArtifact,
readLinuxResourceLimits,
{ message: "busy", details: {} },
);
let calls = 0;
try {
const first = await resources.run(async () =>
ok(await resources.read("fixture", 1)),
);
expect(first).toMatchObject({
ok: false,
error: { cleanupIncomplete: true },
});
const caller = new AbortController();
const reason = { request: "cancel while old descriptor closes" };
const pending = resources.run(async () => {
calls++;
return ok("Must not run");
}, caller.signal);
await started;
caller.abort(reason);
releaseCleanup();
const result = await pending;
if (result.ok) throw new Error("Expected cancelled replacement");
expect(result.error.cause).toBe(reason);
expect(calls).toBe(0);
if (!first.ok) expect(first.error.cleanupIncomplete).toBe(false);
} finally {
releaseCleanup();
await resources.close();
}
});
it.each([false, true])(
"keeps an existing physical primary across cleanup-time cancellation (failed close: %s)",
async (failsClose) => {
const resources = new ArtifactAnalysisResources(
"fixture",
"inspect_signal_decoders",
readStableArtifact,
readLinuxResourceLimits,
{ message: "busy", details: {} },
);
const caller = new AbortController();
const primary = new AnalysisOutputError(
"inspect_signal_decoders",
"physical producer report failure",
);
let denied = failsClose;
resources.retain({
path: "workspace",
close: async () => {
caller.abort({ request: "late cancellation" });
if (denied) throw new Error("workspace close denied");
},
});
try {
const result = await resources.finish(
err(primary),
() => {
throw new Error("Failed work must not produce success Evidence");
},
"inspect_signal_decoders",
caller.signal,
);
if (result.ok) throw new Error("Expected original physical failure");
expect(failsClose ? result.error.cleanupPrimary : result.error).toBe(
primary,
);
denied = false;
await resources.close();
expect(result.error.cleanupIncomplete).toBe(false);
expect(failsClose ? result.error.cleanupPrimary : result.error).toBe(
primary,
);
} finally {
denied = false;
await resources.close();
}
},
);
it("keeps cleanup with no reported capability unresolved instead of clearing it from an empty scope", async () => {
const resources = new ArtifactAnalysisResources(
"fixture",
"inspect_signal_decoders",
readStableArtifact,
readLinuxResourceLimits,
{ message: "busy", details: {} },
);
const source = new ArtifactReaderFailure("io", "unverified cleanup", {
cleanup: {
reason: "no cleanup capability reported",
resources: ["unverified-resource"],
},
});
const result = await resources.run(async () => {
throw source;
});
if (result.ok) throw new Error("Expected unresolved cleanup");
expect(result.error.cleanupIncomplete).toBe(true);
expect(result.error.cleanupResources).toEqual(["unverified-resource"]);
let calls = 0;
const blocked = await resources.run(async () => {
calls++;
return ok("Must not run");
});
expect(blocked).toMatchObject({
ok: false,
error: { cleanupIncomplete: true },
});
expect(calls).toBe(0);
await expect(resources.close()).rejects.toMatchObject({
cleanupIncomplete: true,
});
expect(result.error.cleanupIncomplete).toBe(true);
// No OS resource was supplied. An unresolved observation never grants cleanup authority.
});
it("joins mixed runtime cleanup and resolves each error only through its exact lease", async () => {
const resources = new ArtifactAnalysisResources(
"fixture",
"inspect_signal_decoders",
readStableArtifact,
readLinuxResourceLimits,
{ message: "busy", details: {} },
);
let denied = true;
let healthyCloses = 0;
const bad = {
path: "bad-root",
close: async () => {
if (denied) throw new Error("bad root denied");
},
};
const good = {
path: "good-root",
close: async () => {
healthyCloses++;
},
};
resources.retain(bad);
resources.retain(good);
try {
const failure: unknown = await resources
.release()
.catch((cause: unknown) => cause);
expect(failure).toMatchObject({
cleanupIncomplete: true,
cleanupResources: ["bad-root"],
});
expect(healthyCloses).toBe(1);
expect(resources.pendingPaths).toEqual(["bad-root"]);
denied = false;
await resources.close();
expect(failure).toMatchObject({
cleanupIncomplete: false,
cleanupResources: [],
});
expect(healthyCloses).toBe(1);
} finally {
denied = false;
await resources.close();
}
});
it("retains producer output when a completed read is cancelled during failed close", async () => {
const root = await createTestTempDirectory("rea-completed-read-cancel-");
const path = join(root, "descriptor");
await writeFile(path, "authored bytes");
const caller = new AbortController();
const reason = { request: "cancel during close" };
const scope = new ArtifactResourceScope();
const resources = new ArtifactAnalysisResources(
"fixture",
"inspect_signal_decoders",
readStableArtifact,
readLinuxResourceLimits,
{ message: "busy", details: {} },
);
let mayClose = false;
const output = {
stdout: "completed producer output",
stderr: "",
truncated: false,
exit_code: 0,
};
try {
const cause: unknown = await withArtifactRegularFile(
path,
scope,
caller.signal,
async (file) => {
const close = file.close.bind(file);
file.close = async () => {
caller.abort(reason);
if (!mayClose) throw new Error("controlled descriptor close failure");
await close();
};
return await file.readFile();
},
).catch((cause: unknown) => cause);
if (!(cause instanceof ArtifactReaderFailure))
throw new Error("Expected retained artifact failure");
const error = resources.cleanupFailure(
cause,
() => {
throw new Error("Completed read has no physical failure");
},
output,
);
const primary = error.cleanupPrimary;
expect(primary).toMatchObject({
_tag: "AnalysisCancelledError",
cause: reason,
capturedOutput: output,
});
expect(error.capturedOutput).toEqual(output);
expect(error.cleanupIncomplete).toBe(true);
await expect(scope.close()).rejects.toMatchObject({
reason: "unavailable",
});
mayClose = true;
await scope.close();
expect(error.cleanupIncomplete).toBe(false);
expect(error.cleanupPrimary).toBe(primary);
expect(error.capturedOutput).toEqual(output);
expect(projectAnalysisError(error)).toMatchObject(
projectAnalysisError(primary!),
);
} finally {
mayClose = true;
await scope.close();
await resources.close();
}
});
it("keeps aggregate cleanup live through partial recovery without reclosing healthy roots", async () => {
const resources = new ArtifactAnalysisResources(
"fixture",
"inspect_signal_decoders",
readStableArtifact,
readLinuxResourceLimits,
{ message: "busy", details: {} },
);
const denied = new Set(["first", "second"]);
const closes = new Map<string, number>();
for (const path of ["first", "healthy", "second"])
resources.retain({
path,
close: async () => {
closes.set(path, (closes.get(path) ?? 0) + 1);
if (denied.has(path)) throw new Error(`Close denied: ${path}`);
},
});
try {
const original: unknown = await resources
.release()
.catch((cause: unknown) => cause);
expect(original).toMatchObject({
cleanupIncomplete: true,
cleanupResources: ["first", "second"],
});
expect(closes.get("healthy")).toBe(1);
denied.delete("first");
const retry: unknown = await resources
.release()
.catch((cause: unknown) => cause);
expect(original).toMatchObject({
cleanupIncomplete: true,
cleanupResources: ["second"],
});
expect(retry).toMatchObject({
cleanupIncomplete: true,
cleanupResources: ["second"],
});
expect(resources.pendingPaths).toEqual(["second"]);
denied.clear();
await resources.close();
for (const error of [original, retry])
expect(error).toMatchObject({
cleanupIncomplete: false,
cleanupResources: [],
});
expect(closes).toEqual(
new Map([
["first", 2],
["healthy", 1],
["second", 3],
]),
);
} finally {
denied.clear();
await resources.close();
}
});