mirror of
https://github.com/morluto/rea
synced 2026-10-27 22:45:19 -04:00
fix(pcap): preserve executable read failures
This commit is contained in:
+46
-19
@@ -50,6 +50,8 @@ import { createPcapEvidence } from "../domain/pcap/pcapEvidence.js";
|
||||
import { preparePcapNativeBound } from "./PcapNativeBound.js";
|
||||
import { PcapSnapshot } from "./PcapSnapshot.js";
|
||||
import { readStableArtifact } from "../artifacts/readStableArtifact.js";
|
||||
import { ArtifactReaderFailure } from "../artifacts/ArtifactReader.js";
|
||||
import { artifactFileCancellationCause } from "../artifacts/ArtifactFileOperation.js";
|
||||
import { ArtifactAnalysisResources } from "../process/ArtifactAnalysisResources.js";
|
||||
import { readLinuxResourceLimits } from "../process/LinuxResourceLimits.js";
|
||||
import { realpath } from "node:fs/promises";
|
||||
@@ -133,6 +135,7 @@ const pcapCommandCleanupError = (
|
||||
export interface PcapProviderOptions {
|
||||
readonly environment?: Readonly<Record<string, string | undefined>>;
|
||||
readonly launcher?: PcapCommandLauncher;
|
||||
readonly artifactReader?: typeof readStableArtifact;
|
||||
}
|
||||
|
||||
interface RetainedPcapCommand {
|
||||
@@ -153,16 +156,7 @@ interface RetainedSnapshotFailure {
|
||||
|
||||
/** Inspect saved captures through bounded, owned tool processes and one stable input copy. */
|
||||
export class PcapProvider {
|
||||
readonly #resources = new ArtifactAnalysisResources(
|
||||
"pcap",
|
||||
"inspect_pcap_capture",
|
||||
readStableArtifact,
|
||||
readLinuxResourceLimits,
|
||||
{
|
||||
message: "A packet summary is already active; retry after it completes.",
|
||||
details: { max_concurrent_inspections: 1 },
|
||||
},
|
||||
);
|
||||
readonly #resources: ArtifactAnalysisResources;
|
||||
private readonly environment: Readonly<Record<string, string | undefined>>;
|
||||
private readonly launcher: PcapCommandLauncher | undefined;
|
||||
private readonly wiresharkIdentities = new Map<
|
||||
@@ -182,6 +176,17 @@ export class PcapProvider {
|
||||
constructor(options: PcapProviderOptions = {}) {
|
||||
this.environment = options.environment ?? process.env;
|
||||
this.launcher = options.launcher;
|
||||
this.#resources = new ArtifactAnalysisResources(
|
||||
"pcap",
|
||||
"inspect_pcap_capture",
|
||||
options.artifactReader ?? readStableArtifact,
|
||||
readLinuxResourceLimits,
|
||||
{
|
||||
message:
|
||||
"A packet summary is already active; retry after it completes.",
|
||||
details: { max_concurrent_inspections: 1 },
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
private async run(
|
||||
@@ -664,17 +669,39 @@ export class PcapProvider {
|
||||
);
|
||||
}
|
||||
} catch (cause) {
|
||||
result = err(
|
||||
cause instanceof AnalysisError
|
||||
? cause
|
||||
: cause instanceof PcapConfigurationFailure
|
||||
? pcapToolUnavailable(request.operation, cause.message, {
|
||||
cause,
|
||||
const classifyArtifactFailure = (failure: unknown): AnalysisError =>
|
||||
failure instanceof AnalysisError
|
||||
? failure
|
||||
: failure instanceof ArtifactReaderFailure &&
|
||||
failure.reason === "cancelled"
|
||||
? new AnalysisCancelledError(request.operation, {
|
||||
cause: artifactFileCancellationCause(failure),
|
||||
...(failure.partialObservation === undefined
|
||||
? {}
|
||||
: { partialObservation: failure.partialObservation }),
|
||||
})
|
||||
: new AnalysisProtocolError(
|
||||
`Packet-capture analysis failed: ${cause instanceof Error ? cause.message : String(cause)}`,
|
||||
{ cause },
|
||||
),
|
||||
`Packet-capture analysis failed: ${failure instanceof Error ? failure.message : String(failure)}`,
|
||||
{ cause: failure },
|
||||
);
|
||||
result = err(
|
||||
cause instanceof ArtifactReaderFailure
|
||||
? this.#resources.cleanupFailure(
|
||||
cause,
|
||||
classifyArtifactFailure,
|
||||
undefined,
|
||||
request.operation,
|
||||
)
|
||||
: cause instanceof AnalysisError
|
||||
? cause
|
||||
: cause instanceof PcapConfigurationFailure
|
||||
? pcapToolUnavailable(request.operation, cause.message, {
|
||||
cause,
|
||||
})
|
||||
: new AnalysisProtocolError(
|
||||
`Packet-capture analysis failed: ${cause instanceof Error ? cause.message : String(cause)}`,
|
||||
{ cause },
|
||||
),
|
||||
);
|
||||
}
|
||||
if (signal?.aborted && result.ok)
|
||||
|
||||
@@ -3,6 +3,8 @@ import { dirname, join } from "node:path";
|
||||
import { expect, onTestFinished, test, vi } from "vitest";
|
||||
|
||||
import { AnalysisCancelledError } from "../../../src/domain/analysisErrorCore.js";
|
||||
import { ArtifactReaderFailure } from "../../../src/artifacts/ArtifactReader.js";
|
||||
import { readStableArtifact } from "../../../src/artifacts/readStableArtifact.js";
|
||||
import { ProviderCleanupError } from "../../../src/domain/providerCleanupError.js";
|
||||
import { resolvedAnalysisError } from "../../../src/domain/analysisErrorBase.js";
|
||||
import { PrivateRuntimeRoot } from "../../../src/process/PrivateRuntimeRoot.js";
|
||||
@@ -62,6 +64,7 @@ const fixture = async (
|
||||
reason: unknown,
|
||||
failedCleanupAttempts: number,
|
||||
activeVersion = false,
|
||||
artifactReader: typeof readStableArtifact = readStableArtifact,
|
||||
) => {
|
||||
const root = await createTestTempDirectory("rea-pcap-provider-cancel-");
|
||||
const tools = await writeTools(root, mode, activeVersion);
|
||||
@@ -100,6 +103,7 @@ const fixture = async (
|
||||
REA_CAPINFOS_COMMAND: tools.capinfos,
|
||||
},
|
||||
launcher,
|
||||
artifactReader,
|
||||
});
|
||||
onTestFinished(async () => provider.close());
|
||||
return { provider, root, capture, cleanupAttempts: () => cleanupAttempts };
|
||||
@@ -123,6 +127,31 @@ it("availability preserves the producer's exact cancellation reason", async () =
|
||||
expect(failure).toMatchObject({ cause: reason });
|
||||
});
|
||||
|
||||
it("executable identity hashing preserves artifact-read cancellation", async () => {
|
||||
const controller = new AbortController();
|
||||
const reason = { caller: "cancel executable identity read" };
|
||||
const { provider } = await fixture(
|
||||
"active",
|
||||
controller,
|
||||
reason,
|
||||
0,
|
||||
false,
|
||||
async () => {
|
||||
throw new ArtifactReaderFailure("cancelled", "identity read cancelled", {
|
||||
cancellation: { reason },
|
||||
});
|
||||
},
|
||||
);
|
||||
const result = await provider.execute(
|
||||
{ operation: "inspect_pcap_client", input: {} },
|
||||
{ signal: controller.signal },
|
||||
);
|
||||
expect(result.ok).toBe(false);
|
||||
if (result.ok) return;
|
||||
expect(result.error).toBeInstanceOf(AnalysisCancelledError);
|
||||
expect(result.error.cause).toBe(reason);
|
||||
});
|
||||
|
||||
it("active cancellation stays primary through retained command and snapshot cleanup retry", async () => {
|
||||
const controller = new AbortController();
|
||||
const reason = "caller cancellation token";
|
||||
|
||||
Reference in New Issue
Block a user