mirror of
https://github.com/morluto/rea
synced 2026-10-25 22:08:11 -04:00
fix(pcap): preserve completed Evidence on late cancellation
This commit is contained in:
@@ -1,5 +1,11 @@
|
||||
import { expect, it } from "vitest";
|
||||
|
||||
import { createAnalysisExecution } from "../AnalysisProvider.js";
|
||||
import { AnalysisOutputError } from "../../domain/analysisErrorCore.js";
|
||||
import { parseEvidence } from "../../domain/evidence.js";
|
||||
import { pcapResultSchemas } from "../../domain/pcap/pcapAnalysis.js";
|
||||
import { err, ok } from "../../domain/result.js";
|
||||
|
||||
import { PcapAnalysisService } from "./PcapAnalysisService.js";
|
||||
|
||||
it("preserves the exact caller reason when pcap admission is already aborted", async () => {
|
||||
@@ -26,3 +32,148 @@ it("preserves the exact caller reason when pcap admission is already aborted", a
|
||||
expect(result.error.cause).toBe(reason);
|
||||
expect(calls).toBe(0);
|
||||
});
|
||||
|
||||
const identity = {
|
||||
id: "pcap",
|
||||
name: "Packet capture analysis",
|
||||
version: "test",
|
||||
};
|
||||
const path = "/captures/selected.pcap";
|
||||
const target = { path, bytes: 24, sha256: "c".repeat(64) };
|
||||
const client = {
|
||||
tshark_command: "/selected/tshark",
|
||||
tshark_source: "environment",
|
||||
tshark_version: "4.6.6",
|
||||
capinfos_command: "/selected/capinfos",
|
||||
capinfos_source: "environment",
|
||||
capinfos_version: "4.6.6",
|
||||
};
|
||||
const capture = {
|
||||
client,
|
||||
target,
|
||||
file_type: "pcap",
|
||||
encapsulation: "Ethernet",
|
||||
packet_count: 0,
|
||||
capture_start: null,
|
||||
capture_end: null,
|
||||
capture_duration_seconds: null,
|
||||
snapshot_length: null,
|
||||
endpoints: [],
|
||||
endpoint_count: 0,
|
||||
dns_queries: [],
|
||||
dns_query_count: 0,
|
||||
tls_sni_values: [],
|
||||
tls_sni_count: 0,
|
||||
protocol_distribution: [],
|
||||
protocol_count: 0,
|
||||
http_request_uris: [],
|
||||
http_request_uri_count: 0,
|
||||
http_uri_redaction_count: 0,
|
||||
unattributed_dns_addresses: [],
|
||||
unattributed_dns_address_count: 0,
|
||||
unattributed_dns_packet_count: 0,
|
||||
coverage: "complete",
|
||||
};
|
||||
|
||||
it.each(["inspect_pcap_client", "inspect_pcap_capture"] as const)(
|
||||
"retains completed %s Evidence if cancellation arrives at application handoff",
|
||||
async (operation) => {
|
||||
const reason = Object.freeze({
|
||||
requested: "stop after completed observation",
|
||||
});
|
||||
for (const aborted of [false, true]) {
|
||||
const controller = new AbortController();
|
||||
const execution = createAnalysisExecution(
|
||||
operation === "inspect_pcap_client" ? { client } : capture,
|
||||
identity,
|
||||
{
|
||||
...(operation === "inspect_pcap_capture"
|
||||
? {
|
||||
subject: {
|
||||
path,
|
||||
sha256: target.sha256,
|
||||
format: "file" as const,
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
limitations: ["Fixture observation; no live tool verification"],
|
||||
rawResult: { selected_output: "preserved" },
|
||||
},
|
||||
);
|
||||
const service = new PcapAnalysisService({
|
||||
inspectAvailability: () => Promise.reject(new Error("unused")),
|
||||
close: async () => {},
|
||||
execute: async () => {
|
||||
if (aborted) controller.abort(reason);
|
||||
return ok(execution);
|
||||
},
|
||||
});
|
||||
const result = await service.execute(
|
||||
operation,
|
||||
operation === "inspect_pcap_client" ? {} : { path },
|
||||
{ signal: controller.signal },
|
||||
);
|
||||
const observation = result.ok
|
||||
? result.value
|
||||
: result.error.partialObservation;
|
||||
if (aborted) {
|
||||
if (result.ok)
|
||||
throw new Error("Expected cancellation after provider completion");
|
||||
expect(result.error._tag).toBe("AnalysisCancelledError");
|
||||
expect(result.error.cause).toBe(reason);
|
||||
} else if (!result.ok) throw result.error;
|
||||
const evidence = parseEvidence(observation);
|
||||
expect(evidence.operation).toBe(operation);
|
||||
expect(evidence.provider).toEqual(identity);
|
||||
expect(evidence.raw_result).toEqual({ selected_output: "preserved" });
|
||||
expect(evidence.limitations).toContain(
|
||||
"Fixture observation; no live tool verification",
|
||||
);
|
||||
expect(evidence.parameters).toEqual(
|
||||
operation === "inspect_pcap_client" ? {} : { path },
|
||||
);
|
||||
expect(evidence.normalized_result).toEqual(
|
||||
operation === "inspect_pcap_client" ? { client } : capture,
|
||||
);
|
||||
expect(evidence.confidence).toBe(
|
||||
operation === "inspect_pcap_capture" ? "derived" : "observed",
|
||||
);
|
||||
if (operation === "inspect_pcap_capture") {
|
||||
expect(evidence.authority).toBe("historical-reference");
|
||||
expect(evidence.subject).toMatchObject({
|
||||
local_path: path,
|
||||
digest: { sha256: target.sha256 },
|
||||
});
|
||||
}
|
||||
pcapResultSchemas[operation].parse(evidence.normalized_result);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
it.each(["inspect_pcap_client", "inspect_pcap_capture"] as const)(
|
||||
"preserves the provider's %s failure when cancellation arrives before consumption",
|
||||
async (operation) => {
|
||||
const controller = new AbortController();
|
||||
const failure = new AnalysisOutputError(
|
||||
operation,
|
||||
"The provider could not decode its output",
|
||||
{ diagnostics: { provider_detail: "retained" } },
|
||||
);
|
||||
const service = new PcapAnalysisService({
|
||||
inspectAvailability: () => Promise.reject(new Error("unused")),
|
||||
close: async () => {},
|
||||
execute: async () => {
|
||||
controller.abort({ requested: "late cancellation" });
|
||||
return err(failure);
|
||||
},
|
||||
});
|
||||
const result = await service.execute(
|
||||
operation,
|
||||
operation === "inspect_pcap_client" ? {} : { path },
|
||||
{ signal: controller.signal },
|
||||
);
|
||||
if (result.ok) throw new Error("Expected the provider failure");
|
||||
expect(result.error).toBe(failure);
|
||||
expect(result.error.diagnostics).toEqual({ provider_detail: "retained" });
|
||||
},
|
||||
);
|
||||
|
||||
@@ -11,7 +11,8 @@ import {
|
||||
import type { AnalysisError } from "../../domain/analysisErrorBase.js";
|
||||
import type { Evidence } from "../../domain/evidence.js";
|
||||
import { createPcapEvidence } from "../../domain/pcap/pcapEvidence.js";
|
||||
import { err, ok, type Result } from "../../domain/result.js";
|
||||
import { err, type Result } from "../../domain/result.js";
|
||||
import { completeProviderEvidence } from "../ProviderOperationExecution.js";
|
||||
|
||||
/** Shared CLI/MCP admission, execution, and Evidence composition for pcap. */
|
||||
export class PcapAnalysisService {
|
||||
@@ -34,7 +35,9 @@ export class PcapAnalysisService {
|
||||
return err(new AnalysisInputError(operation, { cause: request.error }));
|
||||
const executed = await this.provider.execute(request.data, options);
|
||||
if (!executed.ok) return executed;
|
||||
const execution = executed.value;
|
||||
return ok(createPcapEvidence(request.data, execution));
|
||||
return completeProviderEvidence(
|
||||
createPcapEvidence(request.data, executed.value),
|
||||
options?.signal,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user