mirror of
https://github.com/morluto/rea.git
synced 2026-11-07 05:56:19 -10:00
fix(evidence): exclude sensitive publication identities
This commit is contained in:
1 parent
5da50ab3f4
commit
f68064e46c
12 files changed
+277
-53
No files matched your search
@@ -507,13 +507,15 @@ const runAnalysis = async (
|
||||
);
|
||||
if (written.ok) publishedSnapshot = written.value;
|
||||
else {
|
||||
if (written.error instanceof EvidenceFileError)
|
||||
publishedSnapshot = written.error.published;
|
||||
const recovered =
|
||||
written.error instanceof EvidenceFileError &&
|
||||
written.error.published !== undefined &&
|
||||
(await session.closeFileResources()).ok;
|
||||
if (!recovered)
|
||||
const published =
|
||||
written.error instanceof EvidenceFileError
|
||||
? written.error.published
|
||||
: undefined;
|
||||
if (published?.path !== undefined)
|
||||
publishedSnapshot = { path: published.path, bytes: published.bytes };
|
||||
const released =
|
||||
published !== undefined && (await session.closeFileResources()).ok;
|
||||
if (!released || published?.path === undefined)
|
||||
return err(written.error.retainPartialObservation(evidence));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,9 +208,12 @@ const persistEvidenceBundleCommand = async (
|
||||
if (
|
||||
!written.error.cleanupIncomplete &&
|
||||
written.error instanceof EvidenceFileError &&
|
||||
written.error.published
|
||||
written.error.published?.path !== undefined
|
||||
) {
|
||||
return ok(written.error.published);
|
||||
return ok({
|
||||
path: written.error.published.path,
|
||||
bytes: written.error.published.bytes,
|
||||
});
|
||||
}
|
||||
return written;
|
||||
};
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
import type { JsonValue } from "../domain/jsonValue.js";
|
||||
import { projectAnalysisError } from "../domain/analysisErrorProjection.js";
|
||||
import type { AnalysisError } from "../domain/analysisErrorBase.js";
|
||||
import { redactExplicitFailure } from "../domain/explicitSensitiveFailure.js";
|
||||
import { err, ok, type Result } from "../domain/result.js";
|
||||
import { AnalysisInputError } from "../domain/analysisErrorCore.js";
|
||||
import { createEvidenceBundle } from "../domain/evidenceBundle.js";
|
||||
@@ -48,8 +49,8 @@ interface CliEvidenceDelivery {
|
||||
readonly evidenceOutput: string | undefined;
|
||||
/** Derived workflows publish their selected source closure with the result. */
|
||||
readonly sourceEvidence?: readonly Evidence[];
|
||||
/** Invocation-specific exclusions also cover errors from final service cleanup. */
|
||||
readonly redactFailure?: (error: AnalysisError) => AnalysisError;
|
||||
/** Invocation exclusions cover final cleanup and publication paths/diagnostics. */
|
||||
readonly sensitiveValues?: readonly string[];
|
||||
}
|
||||
|
||||
export const cliEvidenceDeliveryInputError = (
|
||||
@@ -130,6 +131,8 @@ export const deliverCliAnalysisEvidence = async (
|
||||
detail,
|
||||
evidenceOutput,
|
||||
} = input;
|
||||
const redactFailure = (error: AnalysisError) =>
|
||||
redactExplicitFailure(error, input.sensitiveValues ?? []);
|
||||
const inputError = cliEvidenceDeliveryInputError(input);
|
||||
if (inputError !== undefined)
|
||||
return finishAnalysisCommand(
|
||||
@@ -139,9 +142,7 @@ export const deliverCliAnalysisEvidence = async (
|
||||
operation,
|
||||
signal,
|
||||
undefined,
|
||||
input.redactFailure === undefined
|
||||
? {}
|
||||
: { redactFailure: input.redactFailure },
|
||||
{ redactFailure },
|
||||
);
|
||||
const complete = await finishAnalysisCommand(
|
||||
run,
|
||||
@@ -150,9 +151,7 @@ export const deliverCliAnalysisEvidence = async (
|
||||
operation,
|
||||
signal,
|
||||
undefined,
|
||||
input.redactFailure === undefined
|
||||
? {}
|
||||
: { redactFailure: input.redactFailure },
|
||||
{ redactFailure },
|
||||
);
|
||||
return publishCliAnalysisEvidence(complete, {
|
||||
providerId,
|
||||
@@ -160,6 +159,9 @@ export const deliverCliAnalysisEvidence = async (
|
||||
signal,
|
||||
detail,
|
||||
evidenceOutput,
|
||||
...(input.sensitiveValues === undefined
|
||||
? {}
|
||||
: { sensitiveValues: input.sensitiveValues }),
|
||||
...(input.view === undefined ? {} : { view: input.view }),
|
||||
...(input.sourceEvidence === undefined
|
||||
? {}
|
||||
@@ -173,12 +175,16 @@ export const publishCliAnalysisEvidence = async (
|
||||
input: CliEvidenceDelivery,
|
||||
): Promise<JsonValue> => {
|
||||
const { operation, signal, detail, evidenceOutput } = input;
|
||||
const projectFailure = (error: AnalysisError) =>
|
||||
projectAnalysisError(
|
||||
redactExplicitFailure(error, input.sensitiveValues ?? []),
|
||||
);
|
||||
if (!isEvidence(value)) return value;
|
||||
const complete = parseEvidence(value);
|
||||
const inputError = cliEvidenceDeliveryInputError(input);
|
||||
if (inputError !== undefined) {
|
||||
inputError.retainPartialObservation(complete);
|
||||
return projectAnalysisError(inputError);
|
||||
return projectFailure(inputError);
|
||||
}
|
||||
if (evidenceOutput === undefined) return complete;
|
||||
// Project before publication so a view failure cannot commit an
|
||||
@@ -191,9 +197,7 @@ export const publishCliAnalysisEvidence = async (
|
||||
})
|
||||
: ok(complete);
|
||||
if (!delivered.ok)
|
||||
return projectAnalysisError(
|
||||
delivered.error.retainPartialObservation(complete),
|
||||
);
|
||||
return projectFailure(delivered.error.retainPartialObservation(complete));
|
||||
// This projection answers the same direct invocation as its parent. Later
|
||||
// inspect_analysis_view calls own their own input and do not inherit it.
|
||||
const selected =
|
||||
@@ -201,8 +205,7 @@ export const publishCliAnalysisEvidence = async (
|
||||
? withEvidenceCallerInputPath(delivered.value, complete.caller_input_path)
|
||||
: delivered.value;
|
||||
const inspectionSources = analysisEvidenceSources(complete);
|
||||
if (!inspectionSources.ok)
|
||||
return projectAnalysisError(inspectionSources.error);
|
||||
if (!inspectionSources.ok) return projectFailure(inspectionSources.error);
|
||||
const published = await publishEvidenceDelivery({
|
||||
bundle: createEvidenceBundle([
|
||||
...new Map(
|
||||
@@ -217,12 +220,18 @@ export const publishCliAnalysisEvidence = async (
|
||||
operation,
|
||||
signal,
|
||||
});
|
||||
if (!published.ok) return projectAnalysisError(published.error);
|
||||
if (!published.ok) return projectFailure(published.error);
|
||||
return detail === "summary"
|
||||
? {
|
||||
summary: published.value.delivered,
|
||||
evidence_bundle: {
|
||||
path: published.value.evidenceBundle.path,
|
||||
...(input.sensitiveValues?.some(
|
||||
(literal) =>
|
||||
literal !== "" &&
|
||||
published.value.evidenceBundle.path.includes(literal),
|
||||
)
|
||||
? {}
|
||||
: { path: published.value.evidenceBundle.path }),
|
||||
bytes: published.value.evidenceBundle.bytes,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ import {
|
||||
import { localPathStringSchema } from "../domain/localPath.js";
|
||||
import { absoluteCallerPath } from "../filesystem/callerPath.js";
|
||||
import { explicitSensitiveValueOptions } from "./explicitSensitiveArguments.js";
|
||||
import { redactExplicitFailure } from "../domain/explicitSensitiveFailure.js";
|
||||
|
||||
/** Expose offline historical capture inspection with the same selection contract as MCP. */
|
||||
export const registerWebNetworkCaptureCommands = (
|
||||
@@ -117,8 +116,7 @@ export const registerWebNetworkCaptureCommands = (
|
||||
signal,
|
||||
detail: options.detail,
|
||||
evidenceOutput: options.evidence_output,
|
||||
redactFailure: (error) =>
|
||||
redactExplicitFailure(error, options.sensitiveValue ?? []),
|
||||
sensitiveValues: options.sensitiveValue ?? [],
|
||||
});
|
||||
},
|
||||
),
|
||||
|
||||
@@ -70,7 +70,9 @@ export const analysisErrorRemediationAction = (
|
||||
if (error instanceof AnalysisCapabilityUnavailableError && error.retryable)
|
||||
return "Review details.reason, then retry the same analysis after the condition clears.";
|
||||
if (error instanceof EvidenceFileError && error.published !== undefined)
|
||||
return "Use the published file at details.published_path and resolve the reported staging cleanup failure.";
|
||||
return error.published.path === undefined
|
||||
? "Resolve the reported staging cleanup failure. Publication completed; its path identity is excluded."
|
||||
: "Use the published file at details.published_path and resolve the reported staging cleanup failure.";
|
||||
if (
|
||||
error instanceof ProviderSelectionError &&
|
||||
error.remediationAction !== undefined
|
||||
|
||||
@@ -425,7 +425,9 @@ const artifactStateErrorDetails = (
|
||||
: {
|
||||
published: true,
|
||||
bytes: error.published.bytes,
|
||||
published_path: error.published.path,
|
||||
...(error.published.path === undefined
|
||||
? {}
|
||||
: { published_path: error.published.path }),
|
||||
}),
|
||||
};
|
||||
return undefined;
|
||||
|
||||
@@ -10,6 +10,12 @@ export interface PublishedEvidenceFile {
|
||||
readonly bytes: number;
|
||||
}
|
||||
|
||||
/** Publication completion survives an explicitly excluded path identity. */
|
||||
export interface PublishedEvidenceFileObservation {
|
||||
readonly path?: string;
|
||||
readonly bytes: number;
|
||||
}
|
||||
|
||||
/** Evidence identity, schema, or bundle manifests failed integrity checks. */
|
||||
export class EvidenceIntegrityError extends AnalysisError {
|
||||
readonly _tag = "EvidenceIntegrityError";
|
||||
@@ -51,7 +57,7 @@ export class EvidenceFileError extends AnalysisError {
|
||||
/** Caller-selected path that failed, resolved against the working directory. */
|
||||
readonly path: string | undefined;
|
||||
/** Present only when a write committed before cleanup failed. */
|
||||
readonly published: PublishedEvidenceFile | undefined;
|
||||
readonly published: PublishedEvidenceFileObservation | undefined;
|
||||
/** Schema issues relative to the selected file's JSON root, never the request envelope. */
|
||||
readonly contentIssues: readonly AnalysisInputIssue[] | undefined;
|
||||
|
||||
@@ -67,7 +73,7 @@ export class EvidenceFileError extends AnalysisError {
|
||||
| "io",
|
||||
options?: AnalysisErrorOptions & {
|
||||
readonly path?: string;
|
||||
readonly published?: PublishedEvidenceFile;
|
||||
readonly published?: PublishedEvidenceFileObservation;
|
||||
readonly userMessage?: string;
|
||||
readonly contentIssues?: readonly AnalysisInputIssue[];
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { AnalysisAccessDeniedError } from "./analysisErrorCore.js";
|
||||
import { expect, it } from "vitest";
|
||||
import { EvidenceFileError } from "./evidenceErrors.js";
|
||||
import {
|
||||
AnalysisAccessDeniedError,
|
||||
AnalysisInputError,
|
||||
AnalysisCapabilityUnavailableError,
|
||||
AnalysisCancelledError,
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
AnalysisOutputError,
|
||||
AnalysisResourceConstraintError,
|
||||
} from "./analysisErrorCore.js";
|
||||
import { expect, it } from "vitest";
|
||||
import { projectAnalysisError } from "./analysisErrorProjection.js";
|
||||
import { ProviderCleanupError } from "./providerCleanupError.js";
|
||||
import { ProviderAdapterError } from "./providerAdapterError.js";
|
||||
@@ -284,3 +285,25 @@ it("does not reinterpret an unknown argument name as a JSON pointer", () => {
|
||||
throw new Error("Input error required");
|
||||
expect(result.issues[0]?.path).toEqual([]);
|
||||
});
|
||||
|
||||
it("preserves completed publication and live cleanup while excluding marked file identities", () => {
|
||||
const original = new EvidenceFileError("write", "io", {
|
||||
path: "/private-marker/output.json",
|
||||
published: { path: "/private-marker/output.json", bytes: 42 },
|
||||
cleanup: {
|
||||
reason: "private-marker staging failed",
|
||||
resources: ["/private-marker/stage"],
|
||||
},
|
||||
});
|
||||
const redacted = redactExplicitFailure(original, ["private-marker"]);
|
||||
const projected = projectAnalysisError(redacted);
|
||||
expect(projected).toMatchObject({
|
||||
code: "cleanup_incomplete",
|
||||
details: { published: true, bytes: 42 },
|
||||
});
|
||||
expect(projected.details).not.toHaveProperty("path");
|
||||
expect(projected.details).not.toHaveProperty("published_path");
|
||||
expect(JSON.stringify(projected)).not.toContain("private-marker");
|
||||
original.resolveCleanup();
|
||||
expect(redacted.cleanupIncomplete).toBe(false);
|
||||
});
|
||||
@@ -1,8 +1,10 @@
|
||||
import { EvidenceFileError } from "./evidenceErrors.js";
|
||||
import type { AnalysisError } from "./analysisErrorBase.js";
|
||||
import {
|
||||
AnalysisAccessDeniedError,
|
||||
AnalysisArtifactChangedError,
|
||||
AnalysisInputError,
|
||||
type AnalysisInputIssue,
|
||||
AnalysisOutputError,
|
||||
AnalysisCapabilityUnavailableError,
|
||||
AnalysisResourceConstraintError,
|
||||
@@ -128,6 +130,18 @@ const redactFailure = (
|
||||
if (typeof value === "object" && value !== null) return diagnostics(value);
|
||||
return value;
|
||||
};
|
||||
const issueProjection = (issue: AnalysisInputIssue): AnalysisInputIssue => ({
|
||||
...issue,
|
||||
// Unknown arguments are literal names, not producer JSON pointers.
|
||||
path: path(issue.path, issue.reason === "unknown_argument"),
|
||||
...(issue.message === undefined ? {} : { message: text(issue.message) }),
|
||||
...(issue.expected === undefined
|
||||
? {}
|
||||
: { expected: visit(issue.expected) }),
|
||||
...(issue.observed === undefined
|
||||
? {}
|
||||
: { observed: visit(issue.observed) }),
|
||||
});
|
||||
const context = {
|
||||
cause: error,
|
||||
...(error.limit === undefined ? {} : { limit: error.limit }),
|
||||
@@ -157,6 +171,33 @@ const redactFailure = (
|
||||
},
|
||||
}),
|
||||
};
|
||||
if (error instanceof EvidenceFileError) {
|
||||
const identity = (value: string | undefined): string | undefined =>
|
||||
value !== undefined &&
|
||||
values.some((literal) => literal !== "" && value.includes(literal))
|
||||
? undefined
|
||||
: value;
|
||||
const retainedPath = identity(error.path);
|
||||
const publishedPath = identity(error.published?.path);
|
||||
return new EvidenceFileError(error.operation, error.reason, {
|
||||
...context,
|
||||
...(retainedPath === undefined ? {} : { path: retainedPath }),
|
||||
...(error.userMessage === undefined
|
||||
? {}
|
||||
: { userMessage: text(error.userMessage) }),
|
||||
...(error.published === undefined
|
||||
? {}
|
||||
: {
|
||||
published: {
|
||||
bytes: error.published.bytes,
|
||||
...(publishedPath === undefined ? {} : { path: publishedPath }),
|
||||
},
|
||||
}),
|
||||
...(error.contentIssues === undefined
|
||||
? {}
|
||||
: { contentIssues: error.contentIssues.map(issueProjection) }),
|
||||
});
|
||||
}
|
||||
if (error instanceof AnalysisCancelledError)
|
||||
return new AnalysisCancelledError(error.operation, {
|
||||
...context,
|
||||
@@ -190,21 +231,7 @@ const redactFailure = (
|
||||
return new AnalysisInputError(
|
||||
error.operation,
|
||||
context,
|
||||
error.issues.map((issue) => ({
|
||||
...issue,
|
||||
// Unknown-argument paths contain literal field names, including names
|
||||
// beginning with '/'; they are not producer JSON-pointer coordinates.
|
||||
path: path(issue.path, issue.reason === "unknown_argument"),
|
||||
...(issue.message === undefined
|
||||
? {}
|
||||
: { message: text(issue.message) }),
|
||||
...(issue.expected === undefined
|
||||
? {}
|
||||
: { expected: visit(issue.expected) }),
|
||||
...(issue.observed === undefined
|
||||
? {}
|
||||
: { observed: visit(issue.observed) }),
|
||||
})),
|
||||
error.issues.map(issueProjection),
|
||||
);
|
||||
if (error instanceof AnalysisOutputError)
|
||||
return new AnalysisOutputError(
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { EvidenceFileError } from "../../src/domain/evidenceErrors.js";
|
||||
import { err, ok as resultOk } from "../../src/domain/result.js";
|
||||
import { dirname, join } from "node:path";
|
||||
import { parseConfig } from "../../src/config/parseConfig.js";
|
||||
import { ok as resultOk } from "../../src/domain/result.js";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
runDirectAnalysis,
|
||||
runProviderAnalysis,
|
||||
@@ -323,3 +324,47 @@ it("direct analysis snapshot target mismatch retains actionable snapshot recover
|
||||
),
|
||||
).not.toHaveProperty("code");
|
||||
});
|
||||
|
||||
it("retries snapshot file cleanup before final close even when a published identity is excluded", async () => {
|
||||
const [path] = await createBinarySessionTargets();
|
||||
const profile = createAnalysisProfile(
|
||||
{ id: "fixture", name: "Fixture analysis provider", version: "1" },
|
||||
{ fixture: true },
|
||||
);
|
||||
const factories = recordingFactories(undefined, profile);
|
||||
const events: string[] = [];
|
||||
const publication = new EvidenceFileError("write", "io", {
|
||||
published: { bytes: 42 },
|
||||
});
|
||||
const dependencies: DirectAnalysisDependencies = {
|
||||
...factories.dependencies,
|
||||
createBinarySession: async (...args) => {
|
||||
const session = await factories.dependencies.createBinarySession(...args);
|
||||
vi.spyOn(session, "writeAnalysisSnapshot").mockResolvedValue(
|
||||
err(publication),
|
||||
);
|
||||
const closeResources = session.closeFileResources.bind(session);
|
||||
vi.spyOn(session, "closeFileResources").mockImplementation(async () => {
|
||||
events.push("file-cleanup");
|
||||
return closeResources();
|
||||
});
|
||||
const close = session.close.bind(session);
|
||||
vi.spyOn(session, "close").mockImplementation(async () => {
|
||||
events.push("session-close");
|
||||
return close();
|
||||
});
|
||||
return session;
|
||||
},
|
||||
};
|
||||
const result = await runDirectAnalysis(
|
||||
dependencies,
|
||||
path,
|
||||
"read_bytes",
|
||||
{},
|
||||
{ snapshotPath: join(dirname(path), "excluded.snapshot") },
|
||||
);
|
||||
expect(events[0]).toBe("file-cleanup");
|
||||
expect(events).toContain("session-close");
|
||||
expect(result).toMatchObject({ details: { published: true, bytes: 42 } });
|
||||
expect(result).not.toHaveProperty("details.published_path");
|
||||
});
|
||||
@@ -0,0 +1,108 @@
|
||||
import { createHash } from "node:crypto";
|
||||
import { writeFile, readFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { expect, it } from "vitest";
|
||||
import { WebNetworkCaptureService } from "../../src/application/WebNetworkCaptureService.js";
|
||||
import { decodeHarCapture } from "../../src/browser/history/HarCapture.js";
|
||||
import { deliverCliAnalysisEvidence } from "../../src/cli/analysisEvidenceDelivery.js";
|
||||
import { analysisErrorProjectionSchema } from "../../src/contracts/errorSchemas.js";
|
||||
import { ok } from "../../src/domain/result.js";
|
||||
import { historicalHar } from "../fixtures/historicalHar.js";
|
||||
import { createTestTempDirectory } from "../fixtures/temporaryDirectory.js";
|
||||
|
||||
it("excludes marked output identity from late CLI publication failure while retaining capture Evidence", async () => {
|
||||
const root = await createTestTempDirectory("rea-capture-delivery-");
|
||||
const outputPath = join(root, "private-publication-marker.json");
|
||||
await writeFile(outputPath, "existing caller file");
|
||||
const bytes = JSON.stringify(historicalHar());
|
||||
const values = ["private-publication-marker"];
|
||||
const input = {
|
||||
capture_path: join(root, "capture.har"),
|
||||
format: "har" as const,
|
||||
sensitive_values: values,
|
||||
};
|
||||
const service = new WebNetworkCaptureService({
|
||||
inspect: async () =>
|
||||
ok({
|
||||
...decodeHarCapture(bytes, values),
|
||||
artifact: {
|
||||
path: input.capture_path,
|
||||
sha256: createHash("sha256").update(bytes).digest("hex"),
|
||||
bytes: Buffer.byteLength(bytes),
|
||||
},
|
||||
format: "har" as const,
|
||||
runtime_attribution: "unknown" as const,
|
||||
limitations: [],
|
||||
}),
|
||||
});
|
||||
const output = analysisErrorProjectionSchema.parse(
|
||||
await deliverCliAnalysisEvidence({
|
||||
run: () => service.inspect(input),
|
||||
service,
|
||||
providerId: "web-network-capture",
|
||||
operation: "inspect_web_network_capture",
|
||||
signal: new AbortController().signal,
|
||||
detail: "complete",
|
||||
evidenceOutput: outputPath,
|
||||
sensitiveValues: values,
|
||||
}),
|
||||
);
|
||||
expect(output.code).toBe("invalid_request");
|
||||
expect(output.details).toHaveProperty(
|
||||
"partial_observation.normalized_result.artifact.path",
|
||||
input.capture_path,
|
||||
);
|
||||
expect(JSON.stringify(output)).not.toContain(values[0]);
|
||||
expect(await readFile(outputPath, "utf8")).toBe("existing caller file");
|
||||
});
|
||||
|
||||
it.each([true, false])(
|
||||
"publishes summary bytes while excluding only a marked receipt identity: %s",
|
||||
async (marked) => {
|
||||
const root = await createTestTempDirectory("rea-capture-publication-");
|
||||
const outputPath = join(root, "private-publication-marker.json");
|
||||
const values = marked ? ["private-publication-marker"] : [];
|
||||
const bytes = JSON.stringify(historicalHar());
|
||||
const input = {
|
||||
capture_path: join(root, "capture.har"),
|
||||
format: "har" as const,
|
||||
sensitive_values: values,
|
||||
};
|
||||
const service = new WebNetworkCaptureService({
|
||||
inspect: async () =>
|
||||
ok({
|
||||
...decodeHarCapture(bytes, values),
|
||||
artifact: {
|
||||
path: input.capture_path,
|
||||
sha256: createHash("sha256").update(bytes).digest("hex"),
|
||||
bytes: Buffer.byteLength(bytes),
|
||||
},
|
||||
format: "har" as const,
|
||||
runtime_attribution: "unknown" as const,
|
||||
limitations: [],
|
||||
}),
|
||||
});
|
||||
const output = await deliverCliAnalysisEvidence({
|
||||
run: () => service.inspect(input),
|
||||
service,
|
||||
providerId: "web-network-capture",
|
||||
operation: "inspect_web_network_capture",
|
||||
signal: new AbortController().signal,
|
||||
detail: "summary",
|
||||
evidenceOutput: outputPath,
|
||||
sensitiveValues: values,
|
||||
});
|
||||
const persisted = await readFile(outputPath);
|
||||
expect(output).toMatchObject({
|
||||
evidence_bundle: { bytes: persisted.length },
|
||||
});
|
||||
if (marked) {
|
||||
expect(output).not.toHaveProperty("evidence_bundle.path");
|
||||
expect(JSON.stringify(output)).not.toContain(values[0]);
|
||||
} else expect(output).toHaveProperty("evidence_bundle.path", outputPath);
|
||||
expect(JSON.parse(persisted.toString())).toHaveProperty(
|
||||
"records.0.operation",
|
||||
"inspect_web_network_capture",
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -46,7 +46,6 @@ import { ProviderCleanupError } from "../../src/domain/providerCleanupError.js";
|
||||
import { analysisErrorWithCleanupFailure } from "../../src/domain/analysisErrorCleanup.js";
|
||||
import { err } from "../../src/domain/result.js";
|
||||
import { deliverCliAnalysisEvidence } from "../../src/cli/analysisEvidenceDelivery.js";
|
||||
import { redactExplicitFailure } from "../../src/domain/explicitSensitiveFailure.js";
|
||||
|
||||
const services: WebNetworkCaptureService[] = [];
|
||||
afterEach(async () => {
|
||||
@@ -149,7 +148,7 @@ it("redacts repeated selected-file close failures at final CLI delivery and reta
|
||||
signal: new AbortController().signal,
|
||||
detail: "complete",
|
||||
evidenceOutput: undefined,
|
||||
redactFailure: (error) => redactExplicitFailure(error, values),
|
||||
sensitiveValues: values,
|
||||
}),
|
||||
);
|
||||
expect(output.code).toBe("cleanup_incomplete");
|
||||
|
||||
Reference in new issue
Block a user