fix(housekeeping): preserve audit failure evidence

This commit is contained in:
Simo committed 2026-08-26 22:03:02 +02:00
1 parent 89dec9da05
commit 21cd88ccfd
4 files changed
+80 -5

No files matched your search

@@ -96,4 +96,37 @@ describe("audit command envelope", () => {
undefined,
);
});
it("preserves the original operation error when failure evidence rejects", async () => {
const original = new Error("operation failed");
const auditFailure = new Error("audit failure");
const writer: HousekeepingAuditWriter = {
write: vi
.fn()
.mockResolvedValueOnce(undefined)
.mockRejectedValueOnce(auditFailure),
};
await expect(
runWithAuditIntent(writer, auditEntry, () => Promise.reject(original)),
).rejects.toBe(original);
expect(
(original as Error & { auditOutcomeError?: unknown }).auditOutcomeError,
).toBe(auditFailure);
});
it("throws a completed-operation error when success evidence rejects", async () => {
const auditFailure = new Error("audit failure");
const writer: HousekeepingAuditWriter = {
write: vi
.fn()
.mockResolvedValueOnce(undefined)
.mockRejectedValueOnce(auditFailure),
};
await expect(
runWithAuditIntent(writer, auditEntry, () => Promise.resolve("done")),
).rejects.toMatchObject({
operationCompleted: true,
operationResult: "done",
auditOutcomeError: auditFailure,
});
});
});
@@ -5,6 +5,17 @@ import type {
} from "@/lib/services/audit";
type AuditOutcome = Exclude<NonNullable<AuditEntry["outcome"]>, "intent">;
export class AuditOutcomePersistenceError<T> extends Error {
readonly operationCompleted = true;
constructor(
readonly operationResult: T,
readonly auditOutcomeError: unknown,
) {
super("Operation completed but audit outcome could not be persisted");
this.name = "AuditOutcomePersistenceError";
}
}
export async function writeIntent(
writer: HousekeepingAuditWriter,
@@ -35,10 +46,23 @@ export async function runWithAuditIntent<T>(
try {
result = await operation();
} catch (error) {
await writeOutcome(writer, entry, "failure", transaction);
try {
await writeOutcome(writer, entry, "failure", transaction);
} catch (auditOutcomeError) {
if (error instanceof Error) {
Object.defineProperty(error, "auditOutcomeError", {
value: auditOutcomeError,
configurable: true,
});
}
}
throw error;
}
await writeOutcome(writer, entry, "success", transaction);
try {
await writeOutcome(writer, entry, "success", transaction);
} catch (auditOutcomeError) {
throw new AuditOutcomePersistenceError(result, auditOutcomeError);
}
return result;
}