mirror of
https://github.com/morluto/rea.git
synced 2026-10-18 20:10:13 -05:00
fix(analysis): retain artifact admission cancellation reasons
This commit is contained in:
1 parent
a8170163b9
commit
52b73ee857
6 files changed
+132
-41
No files matched your search
@@ -80,10 +80,11 @@ it.each([
|
||||
it("retains a completed observation on late cancellation and keeps output errors primary", async () => {
|
||||
const report = await observedReport();
|
||||
const controller = new AbortController();
|
||||
const reason = { request: "late PE cancellation" };
|
||||
const service = new PeResourcesService({
|
||||
identity: PE_RESOURCES_PROVIDER,
|
||||
inspect: () => {
|
||||
controller.abort();
|
||||
controller.abort(reason);
|
||||
return Promise.resolve(ok(report));
|
||||
},
|
||||
});
|
||||
@@ -96,6 +97,7 @@ it("retains a completed observation on late cancellation and keeps output errors
|
||||
error: { _tag: "AnalysisCancelledError" },
|
||||
});
|
||||
if (!late.ok) {
|
||||
expect(late.error.cause).toBe(reason);
|
||||
expect(parseEvidence(late.error.partialObservation)).toMatchObject({
|
||||
operation: "inspect_pe_resources",
|
||||
subject: { architecture: "x86" },
|
||||
@@ -119,3 +121,40 @@ it("retains a completed observation on late cancellation and keeps output errors
|
||||
expect(invalid.ok).toBe(false);
|
||||
if (!invalid.ok) expect(invalid.error).toBeInstanceOf(AnalysisOutputError);
|
||||
});
|
||||
|
||||
it.each([
|
||||
null,
|
||||
"caller cancellation",
|
||||
{ request: "opaque caller value" },
|
||||
new Error("caller failure"),
|
||||
new AnalysisOutputError("caller_operation", "caller-owned cleanup", {
|
||||
cleanup: {
|
||||
reason: "unresolved caller resource",
|
||||
resources: ["caller resource"],
|
||||
},
|
||||
}),
|
||||
])(
|
||||
"preserves an opaque admission cancellation reason without invoking the reader: %j",
|
||||
async (reason) => {
|
||||
const controller = new AbortController();
|
||||
controller.abort(reason);
|
||||
let calls = 0;
|
||||
const service = new PeResourcesService({
|
||||
identity: PE_RESOURCES_PROVIDER,
|
||||
inspect: async () => {
|
||||
calls += 1;
|
||||
throw new Error("cancelled admission must not invoke the reader");
|
||||
},
|
||||
});
|
||||
const result = await service.inspect(null, { signal: controller.signal });
|
||||
expect(result.ok).toBe(false);
|
||||
if (result.ok) throw new Error("expected cancelled admission");
|
||||
expect(result.error).toBeInstanceOf(AnalysisCancelledError);
|
||||
expect(result.error.cause).toBe(reason);
|
||||
expect(result.error.cleanupIncomplete).toBe(false);
|
||||
expect(result.error.partialObservation).toBeUndefined();
|
||||
expect(calls).toBe(0);
|
||||
if (reason instanceof AnalysisOutputError)
|
||||
expect(reason.cleanupIncomplete).toBe(true);
|
||||
},
|
||||
);
|
||||
@@ -37,7 +37,9 @@ export class PeResourcesService {
|
||||
): Promise<Result<Evidence, AnalysisError>> {
|
||||
const operation = "inspect_pe_resources";
|
||||
if (options?.signal?.aborted)
|
||||
return err(new AnalysisCancelledError(operation));
|
||||
return err(
|
||||
new AnalysisCancelledError(operation, { cause: options.signal.reason }),
|
||||
);
|
||||
const input = inspectPeResourcesInputSchema.safeParse(rawInput);
|
||||
if (!input.success)
|
||||
return err(
|
||||
|
||||
@@ -153,12 +153,13 @@ it("refuses reader identity changes and invalid original source ranges", async (
|
||||
|
||||
it("honors cancellation before reading and after a completed observation", async () => {
|
||||
const controller = new AbortController();
|
||||
const reason = { request: "late Go cancellation" };
|
||||
let calls = 0;
|
||||
const service = new GoBinaryService({
|
||||
identity,
|
||||
inspect: () => {
|
||||
calls += 1;
|
||||
controller.abort();
|
||||
controller.abort(reason);
|
||||
return Promise.resolve(ok(report));
|
||||
},
|
||||
});
|
||||
@@ -169,6 +170,7 @@ it("honors cancellation before reading and after a completed observation", async
|
||||
expect(completed.ok).toBe(false);
|
||||
if (!completed.ok) {
|
||||
expect(completed.error).toBeInstanceOf(AnalysisCancelledError);
|
||||
expect(completed.error.cause).toBe(reason);
|
||||
expect(parseEvidence(completed.error.partialObservation)).toMatchObject({
|
||||
operation: "inspect_go_binary",
|
||||
normalized_result: report,
|
||||
@@ -199,3 +201,40 @@ it("keeps malformed provider output ahead of a late cancellation", async () => {
|
||||
expect(result.ok).toBe(false);
|
||||
if (!result.ok) expect(result.error).toBeInstanceOf(AnalysisOutputError);
|
||||
});
|
||||
|
||||
it.each([
|
||||
null,
|
||||
"caller cancellation",
|
||||
{ request: "opaque caller value" },
|
||||
new Error("caller failure"),
|
||||
new AnalysisOutputError("caller_operation", "caller-owned cleanup", {
|
||||
cleanup: {
|
||||
reason: "unresolved caller resource",
|
||||
resources: ["caller resource"],
|
||||
},
|
||||
}),
|
||||
])(
|
||||
"preserves an opaque admission cancellation reason without invoking the reader: %j",
|
||||
async (reason) => {
|
||||
const controller = new AbortController();
|
||||
controller.abort(reason);
|
||||
let calls = 0;
|
||||
const service = new GoBinaryService({
|
||||
identity,
|
||||
inspect: async () => {
|
||||
calls += 1;
|
||||
throw new Error("cancelled admission must not invoke the reader");
|
||||
},
|
||||
});
|
||||
const result = await service.inspect(null, { signal: controller.signal });
|
||||
expect(result.ok).toBe(false);
|
||||
if (result.ok) throw new Error("expected cancelled admission");
|
||||
expect(result.error).toBeInstanceOf(AnalysisCancelledError);
|
||||
expect(result.error.cause).toBe(reason);
|
||||
expect(result.error.cleanupIncomplete).toBe(false);
|
||||
expect(result.error.partialObservation).toBeUndefined();
|
||||
expect(calls).toBe(0);
|
||||
if (reason instanceof AnalysisOutputError)
|
||||
expect(reason.cleanupIncomplete).toBe(true);
|
||||
},
|
||||
);
|
||||
@@ -45,7 +45,9 @@ export class GoBinaryService {
|
||||
options?: ExecutionOptions,
|
||||
): Promise<Result<Evidence, AnalysisError>> {
|
||||
if (options?.signal?.aborted)
|
||||
return err(new AnalysisCancelledError(OPERATION));
|
||||
return err(
|
||||
new AnalysisCancelledError(OPERATION, { cause: options.signal.reason }),
|
||||
);
|
||||
const input = inspectGoBinaryInputSchema.safeParse(rawInput);
|
||||
if (!input.success)
|
||||
return err(
|
||||
|
||||
@@ -115,10 +115,11 @@ it("rejects a cycle claim the package dependencies do not contain", async () =>
|
||||
|
||||
it("retains completed Rust Evidence on late cancellation and keeps output failure precedence", async () => {
|
||||
const controller = new AbortController();
|
||||
const reason = { request: "late Rust cancellation" };
|
||||
const service = new RustBinaryService({
|
||||
identity,
|
||||
inspect: () => {
|
||||
controller.abort();
|
||||
controller.abort(reason);
|
||||
return Promise.resolve(ok(report));
|
||||
},
|
||||
});
|
||||
@@ -154,8 +155,14 @@ it("retains completed Rust Evidence on late cancellation and keeps output failur
|
||||
error: { _tag: "AnalysisOutputError" },
|
||||
});
|
||||
expect(cancelled.ok).toBe(false);
|
||||
if (!cancelled.ok)
|
||||
if (!cancelled.ok) {
|
||||
expect(cancelled.error).toBeInstanceOf(AnalysisCancelledError);
|
||||
expect(cancelled.error.cause).toBe(reason);
|
||||
expect(parseEvidence(cancelled.error.partialObservation)).toMatchObject({
|
||||
operation: "inspect_rust_binary",
|
||||
normalized_result: report,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it.each([
|
||||
@@ -178,39 +185,39 @@ it.each([
|
||||
if (!result.ok) expect(result.error).toBeInstanceOf(AnalysisOutputError);
|
||||
});
|
||||
|
||||
it("retains a completed report on late cancellation and keeps output errors primary", async () => {
|
||||
const controller = new AbortController();
|
||||
const completed = await new RustBinaryService({
|
||||
identity,
|
||||
inspect: () => {
|
||||
controller.abort();
|
||||
return Promise.resolve(ok(report));
|
||||
it.each([
|
||||
null,
|
||||
"caller cancellation",
|
||||
{ request: "opaque caller value" },
|
||||
new Error("caller failure"),
|
||||
new AnalysisOutputError("caller_operation", "caller-owned cleanup", {
|
||||
cleanup: {
|
||||
reason: "unresolved caller resource",
|
||||
resources: ["caller resource"],
|
||||
},
|
||||
}).inspect({ path: report.artifact.path }, { signal: controller.signal });
|
||||
expect(completed.ok).toBe(false);
|
||||
if (!completed.ok) {
|
||||
expect(completed.error).toBeInstanceOf(AnalysisCancelledError);
|
||||
expect(parseEvidence(completed.error.partialObservation)).toMatchObject({
|
||||
operation: "inspect_rust_binary",
|
||||
normalized_result: report,
|
||||
}),
|
||||
])(
|
||||
"preserves an opaque admission cancellation reason without invoking the reader: %j",
|
||||
async (reason) => {
|
||||
const controller = new AbortController();
|
||||
controller.abort(reason);
|
||||
let calls = 0;
|
||||
const service = new RustBinaryService({
|
||||
identity,
|
||||
inspect: async () => {
|
||||
calls += 1;
|
||||
throw new Error("cancelled admission must not invoke the reader");
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const malformed = {
|
||||
...report,
|
||||
artifact: { ...report.artifact, path: "/artifacts/other" },
|
||||
};
|
||||
const outputController = new AbortController();
|
||||
const invalid = await new RustBinaryService({
|
||||
identity,
|
||||
inspect: () => {
|
||||
outputController.abort();
|
||||
return Promise.resolve(ok(malformed));
|
||||
},
|
||||
}).inspect(
|
||||
{ path: report.artifact.path },
|
||||
{ signal: outputController.signal },
|
||||
);
|
||||
expect(invalid.ok).toBe(false);
|
||||
if (!invalid.ok) expect(invalid.error).toBeInstanceOf(AnalysisOutputError);
|
||||
});
|
||||
const result = await service.inspect(null, { signal: controller.signal });
|
||||
expect(result.ok).toBe(false);
|
||||
if (result.ok) throw new Error("expected cancelled admission");
|
||||
expect(result.error).toBeInstanceOf(AnalysisCancelledError);
|
||||
expect(result.error.cause).toBe(reason);
|
||||
expect(result.error.cleanupIncomplete).toBe(false);
|
||||
expect(result.error.partialObservation).toBeUndefined();
|
||||
expect(calls).toBe(0);
|
||||
if (reason instanceof AnalysisOutputError)
|
||||
expect(reason.cleanupIncomplete).toBe(true);
|
||||
},
|
||||
);
|
||||
@@ -46,7 +46,9 @@ export class RustBinaryService {
|
||||
options?: ExecutionOptions,
|
||||
): Promise<Result<Evidence, AnalysisError>> {
|
||||
if (options?.signal?.aborted)
|
||||
return err(new AnalysisCancelledError(OPERATION));
|
||||
return err(
|
||||
new AnalysisCancelledError(OPERATION, { cause: options.signal.reason }),
|
||||
);
|
||||
const input = inspectRustBinaryInputSchema.safeParse(rawInput);
|
||||
if (!input.success)
|
||||
return err(
|
||||
|
||||
Reference in new issue
Block a user