mirror of
https://github.com/morluto/rea
synced 2026-10-27 14:42:18 -04:00
fb146bf40b
Return a closeable owner before prerequisite work begins. Share creation across independent callers, join the final departure and late handoffs, retain exact failed resource owners, and gate operation dispatch again after acquisition. Keep reuse admission separate from native health. Move shared provider ownership below application workflows and consolidate failure attribution; remove the superseded lazy client. Validation: 34 focused owner, lazy, composite and session controls; check:fast. Compiled baseline/candidate recording-provider workflow reproduces then fences dispatch after close. No native provider claim. Refs #4561, #4667
80 lines
2.7 KiB
TypeScript
80 lines
2.7 KiB
TypeScript
import { expect, it } from "vitest";
|
|
|
|
import { projectAnalysisError } from "../../domain/analysisErrorProjection.js";
|
|
import { ProviderAdapterError } from "../../domain/providerAdapterError.js";
|
|
import { ProviderCleanupError } from "../../domain/providerCleanupError.js";
|
|
import { err } from "../../domain/result.js";
|
|
import type { AnalysisClient } from "../AnalysisProvider.js";
|
|
import { closeAnalysisClient } from "../../providers/AnalysisClientCleanup.js";
|
|
import { analysisErrorWithCleanupFailure } from "../../domain/analysisErrorCleanup.js";
|
|
|
|
const clientClosingWith = (cause: unknown): AnalysisClient => ({
|
|
execute: () => Promise.resolve(err(new ProviderCleanupError("x", [], {}))),
|
|
close: () => Promise.reject(cause),
|
|
});
|
|
|
|
it("keeps the leftover resources a typed cleanup failure names", async () => {
|
|
const leftover = "/tmp/rea-ghidra-fixture";
|
|
const failure = new ProviderCleanupError("ghidra", [leftover], {
|
|
reason: "runtime directory retained",
|
|
});
|
|
|
|
const closed = await closeAnalysisClient(
|
|
clientClosingWith(failure),
|
|
"ghidra",
|
|
);
|
|
|
|
expect(closed.ok).toBe(false);
|
|
if (!closed.ok)
|
|
expect(projectAnalysisError(closed.error)).toMatchObject({
|
|
code: "cleanup_incomplete",
|
|
details: { resources: [leftover] },
|
|
});
|
|
});
|
|
|
|
it("still reports an untyped close rejection as the provider client", async () => {
|
|
const closed = await closeAnalysisClient(
|
|
clientClosingWith(new Error("unexpected")),
|
|
"ghidra",
|
|
);
|
|
|
|
expect(closed.ok).toBe(false);
|
|
if (!closed.ok)
|
|
expect(projectAnalysisError(closed.error)).toMatchObject({
|
|
code: "cleanup_incomplete",
|
|
details: { resources: ["provider-client"] },
|
|
});
|
|
});
|
|
|
|
it("preserves the original explanation and collected output alongside cleanup uncertainty", () => {
|
|
const primary = new ProviderAdapterError("fixture", "capture", {
|
|
userMessage: "Select the running target process and retry the capture.",
|
|
capturedOutput: {
|
|
stdout: "partial observation",
|
|
stderr: "native reason",
|
|
truncated: false,
|
|
},
|
|
});
|
|
const cleanup = new ProviderCleanupError("fixture", ["owned-process"], {
|
|
reason: "Process termination was not confirmed",
|
|
});
|
|
const combined = analysisErrorWithCleanupFailure(primary, cleanup);
|
|
expect(projectAnalysisError(combined)).toMatchObject({
|
|
code: "cleanup_incomplete",
|
|
message: primary.userMessage,
|
|
details: {
|
|
resources: ["owned-process"],
|
|
captured_output: primary.capturedOutput,
|
|
diagnostics: {
|
|
primary_error: projectAnalysisError(primary),
|
|
cleanup_error: projectAnalysisError(cleanup),
|
|
},
|
|
},
|
|
});
|
|
expect(combined.cause).toBeInstanceOf(AggregateError);
|
|
expect(combined.cause).toMatchObject({
|
|
cause: primary,
|
|
errors: [primary, cleanup],
|
|
});
|
|
});
|