mirror of
https://github.com/morluto/rea.git
synced 2026-10-26 17:35:11 +00:00
fix(cutter): retain command attribution through descriptor cleanup
Pass the requesting operation into the descriptor failure owner. Discovery retains its own identity; command execution and completion confirmation retain cutter_command with exact cleanup dependencies. Validated 61 focused tests across five files, including denied retry and successful native descriptor release. Controlled close faults; no native Cutter engine verified. Refs: #4634
This commit is contained in:
1 parent
5c0472c571
commit
199396f61c
2 files changed
+61
-5
No files matched your search
@@ -338,7 +338,7 @@ export class CutterBridgeClient {
|
||||
options: CutterDiscoveryOptions = {},
|
||||
): Promise<CutterBridgeDiscovery> {
|
||||
const { signal } = options;
|
||||
const loaded = await this.#readDescriptors(signal);
|
||||
const loaded = await this.#readDescriptors("list_cutter_sessions", signal);
|
||||
if (loaded.error !== undefined)
|
||||
return {
|
||||
sessions: [],
|
||||
@@ -653,7 +653,7 @@ export class CutterBridgeClient {
|
||||
sessionId: string,
|
||||
signal?: AbortSignal,
|
||||
): Promise<z.infer<typeof descriptorSchema>> {
|
||||
const loaded = await this.#readDescriptors(signal);
|
||||
const loaded = await this.#readDescriptors("cutter_command", signal);
|
||||
if (loaded.error === "bridge_directory_capacity_exceeded")
|
||||
throw new AnalysisResourceConstraintError(
|
||||
"cutter_command",
|
||||
@@ -681,17 +681,20 @@ export class CutterBridgeClient {
|
||||
async listDescriptors(): Promise<
|
||||
readonly z.infer<typeof descriptorSchema>[]
|
||||
> {
|
||||
const loaded = await this.#readDescriptors();
|
||||
const loaded = await this.#readDescriptors("list_cutter_sessions");
|
||||
return loaded.descriptors;
|
||||
}
|
||||
|
||||
async #readDescriptors(signal?: AbortSignal) {
|
||||
async #readDescriptors(
|
||||
operation: "list_cutter_sessions" | "cutter_command",
|
||||
signal?: AbortSignal,
|
||||
) {
|
||||
try {
|
||||
return await this.#descriptorResources.run(() =>
|
||||
this.#loadDescriptors(signal),
|
||||
);
|
||||
} catch (cause: unknown) {
|
||||
throw descriptorFailure(cause, "list_cutter_sessions");
|
||||
throw descriptorFailure(cause, operation);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ vi.mock("node:fs/promises", async (importOriginal) => {
|
||||
import { CutterBridgeClient } from "../../src/cutter/CutterBridgeClient.js";
|
||||
import { AnalysisCancelledError } from "../../src/domain/analysisErrorCore.js";
|
||||
import { AnalysisError } from "../../src/domain/analysisErrorBase.js";
|
||||
import { projectAnalysisError } from "../../src/domain/analysisErrorProjection.js";
|
||||
import { createTestTempDirectory } from "../fixtures/temporaryDirectory.js";
|
||||
import { runCliJson } from "../fixtures/cliJsonProcess.js";
|
||||
|
||||
@@ -263,3 +264,55 @@ it("does not assign a peer's later cancellation to a descriptor that already fai
|
||||
await client.close();
|
||||
}
|
||||
});
|
||||
|
||||
it.each(["command", "completion"] as const)(
|
||||
"attributes retained descriptor cleanup to the requesting %s",
|
||||
async (operation) => {
|
||||
const root = await createTestTempDirectory(
|
||||
"rea-cutter-command-descriptor-",
|
||||
);
|
||||
await descriptor(root, 1);
|
||||
fault.root = root;
|
||||
const client = new CutterBridgeClient({ REA_CUTTER_BRIDGE_DIR: root });
|
||||
try {
|
||||
const original =
|
||||
operation === "command"
|
||||
? await client
|
||||
.execute({
|
||||
sessionId: "00000000-0000-4000-8000-000000000001",
|
||||
expectedGeneration: 0,
|
||||
command: "ij",
|
||||
json: false,
|
||||
})
|
||||
.catch((cause: unknown) => cause)
|
||||
: await client
|
||||
.confirmCommandCompletion(
|
||||
"00000000-0000-4000-8000-000000000001",
|
||||
"4b18ce4d-bf31-456e-ae31-e8c547b2f9db",
|
||||
)
|
||||
.then((result) => {
|
||||
if (result.ok) throw new Error("Expected descriptor failure");
|
||||
return result.error;
|
||||
});
|
||||
if (!(original instanceof AnalysisError))
|
||||
throw new Error("Expected typed descriptor cleanup");
|
||||
expect(projectAnalysisError(original)).toMatchObject({
|
||||
code: "cleanup_incomplete",
|
||||
details: { operation: "cutter_command" },
|
||||
});
|
||||
expect(fault.handles).toHaveLength(1);
|
||||
expect(fault.handles[0]?.fd).toBeGreaterThanOrEqual(0);
|
||||
await expect(client.retryDescriptorCleanup()).rejects.toMatchObject({
|
||||
cleanupIncomplete: true,
|
||||
});
|
||||
expect(original.cleanupIncomplete).toBe(true);
|
||||
fault.blocked.clear();
|
||||
await client.close();
|
||||
expect(fault.handles[0]?.fd).toBe(-1);
|
||||
expect(original.cleanupIncomplete).toBe(false);
|
||||
} finally {
|
||||
fault.blocked.clear();
|
||||
await client.close();
|
||||
}
|
||||
},
|
||||
);
|
||||
Reference in new issue
Block a user