mirror of
https://github.com/morluto/rea
synced 2026-10-25 22:08:11 -04:00
fix(cutter): retain completion-read failures during cleanup retry
This commit is contained in:
@@ -135,6 +135,9 @@ block new commands until they can be retried successfully. Status proof failures
|
||||
identify `cutter_command` and retain `status` in
|
||||
`details.diagnostics.auxiliary_operation`; malformed proof still blocks dispatch.
|
||||
Descriptor failures likewise retain the requesting operation through cleanup.
|
||||
If a completion-proof read fails while an earlier command is unresolved, retry
|
||||
admission preserves both errors and their independently owned resources. Releasing
|
||||
one owner does not settle the other, and the command is not replayed to recover proof.
|
||||
Command admission reports typed cancellation and treats the caller's abort reason
|
||||
as opaque metadata, without inferring cleanup ownership from it. A generation
|
||||
mismatch is an input correction only when the authenticated reply confirms
|
||||
|
||||
@@ -73,7 +73,15 @@ export class CutterBridgeService {
|
||||
requestId,
|
||||
pending.completionToken,
|
||||
);
|
||||
if (!receipt.ok || !receipt.value) return err(pending.failure);
|
||||
if (!receipt.ok)
|
||||
return err(
|
||||
analysisErrorWithCleanupFailure(
|
||||
pending.failure,
|
||||
receipt.error,
|
||||
"cutter_command",
|
||||
).bindCleanupDependencies([pending.failure, receipt.error]),
|
||||
);
|
||||
if (!receipt.value) return err(pending.failure);
|
||||
const { failure } = pending;
|
||||
failure.resolveCleanup();
|
||||
this.#pendingCleanup.delete(requestId);
|
||||
|
||||
@@ -13,6 +13,93 @@ const cutterInput = {
|
||||
command: "pd 4",
|
||||
};
|
||||
|
||||
it.each(["descriptor-first", "command-first"] as const)(
|
||||
"retains receipt-read cleanup alongside pending command ownership: %s",
|
||||
async (order) => {
|
||||
const descriptor = new ProviderCleanupError(
|
||||
"cutter",
|
||||
["descriptor-fixture"],
|
||||
{ reason: "controlled receipt descriptor close failure" },
|
||||
{ operation: "cutter_command" },
|
||||
);
|
||||
let readFails = true;
|
||||
let terminal = false;
|
||||
const execute = vi.fn(async () => ({
|
||||
requestId: "4b18ce4d-bf31-456e-ae31-e8c547b2f9db",
|
||||
output: null,
|
||||
currentFile: null,
|
||||
documentGeneration: 7,
|
||||
identityStatus: "partial" as const,
|
||||
cutterVersion: null,
|
||||
executionState: "unknown" as const,
|
||||
error: "missing-reply",
|
||||
message: "completion remains unknown",
|
||||
outputTruncated: false,
|
||||
}));
|
||||
const service = new CutterBridgeService({
|
||||
listSessions: async () => ({
|
||||
sessions: [],
|
||||
bridge_directory: "/fixture",
|
||||
discovery_status: "no_live_bridge_found",
|
||||
bridge_directory_security: "private_verified",
|
||||
}),
|
||||
confirmCommandCompletion: async () =>
|
||||
readFails
|
||||
? { ok: false, error: descriptor }
|
||||
: { ok: true, value: terminal },
|
||||
execute,
|
||||
});
|
||||
const dispatched = await service.execute(cutterInput);
|
||||
if (dispatched.ok) throw new Error("Expected pending completion");
|
||||
const retried = await service.retryCleanup();
|
||||
if (retried.ok) throw new Error("Expected receipt-read failure");
|
||||
expect(retried.error.cleanupResources).toEqual(
|
||||
expect.arrayContaining([
|
||||
"descriptor-fixture",
|
||||
`cutter-session:${cutterInput.session_id}`,
|
||||
]),
|
||||
);
|
||||
expect(retried.error.partialObservation).toBe(
|
||||
dispatched.error.partialObservation,
|
||||
);
|
||||
expect(retried.error.diagnostics).toMatchObject({
|
||||
cleanup_error: {
|
||||
details: {
|
||||
diagnostics: {
|
||||
reason: "controlled receipt descriptor close failure",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
if (order === "descriptor-first") {
|
||||
descriptor.resolveCleanup();
|
||||
expect(retried.error.cleanupResources).toEqual([
|
||||
`cutter-session:${cutterInput.session_id}`,
|
||||
]);
|
||||
readFails = false;
|
||||
expect(await service.retryCleanup()).toEqual({
|
||||
ok: false,
|
||||
error: dispatched.error,
|
||||
});
|
||||
expect(retried.error.cleanupIncomplete).toBe(true);
|
||||
terminal = true;
|
||||
expect(await service.retryCleanup()).toEqual({ ok: true, value: null });
|
||||
} else {
|
||||
readFails = false;
|
||||
terminal = true;
|
||||
expect(await service.retryCleanup()).toEqual({ ok: true, value: null });
|
||||
expect(dispatched.error.cleanupIncomplete).toBe(false);
|
||||
expect(retried.error.cleanupIncomplete).toBe(true);
|
||||
expect(retried.error.cleanupResources).toEqual(["descriptor-fixture"]);
|
||||
descriptor.resolveCleanup();
|
||||
}
|
||||
expect(retried.error.cleanupIncomplete).toBe(false);
|
||||
expect(retried.error.cleanupResources).toEqual([]);
|
||||
expect(execute).toHaveBeenCalledTimes(1);
|
||||
await service.close();
|
||||
},
|
||||
);
|
||||
|
||||
it("retains uncertain command completion as Evidence without encouraging a retry", async () => {
|
||||
const service = new CutterBridgeService({
|
||||
listSessions: async () => ({
|
||||
|
||||
Reference in New Issue
Block a user