fix(evidence): retain owner finalization reports across cleanup envelopes

This commit is contained in:
morluto committed 2026-10-24 04:38:08 +08:00
1 parent c2cc81927a
commit 640a0cf423
3 files changed
+54 -3

No files matched your search

+3 -1
View File
@@ -195,7 +195,9 @@ providers need not expose identical state machines.
owner's recovery instructions. While cleanup remains unconfirmed, canonical
errors keep `cleanup_incomplete` and live resources while taking category,
operation and failure details from the retained typed primary. Nested cleanup
composition does not replace that semantic identity. CLI adapters should reuse
composition does not replace that semantic identity. Finalization reports
remain attached to their owning envelope while cleanup is pending and after
confirmed release. CLI adapters should reuse
[`finishAnalysisCommand`](https://github.com/morluto/rea/blob/main/src/cli/finishAnalysisCommand.ts) where its contract
applies rather than duplicate close/finally handling.
- Give descriptor-backed reads an owner before opening the file. A rejected
+40
View File
@@ -318,3 +318,43 @@ it("retains an observation attached to the composite after the primary failed",
details: { partial_observation: observation },
});
});
it("retains an owner's finalization report independently of pending primary semantics", () => {
const report = {
owned_process_group: { state: "not_required" as const, reason: null },
terminal_renderer: { state: "not_required" as const, reason: null },
temporary_root: { state: "cleaned" as const, reason: null },
};
class ReportedCleanup extends ProviderCleanupError {
override readonly cleanupReport = report;
}
const primary = new AnalysisInputError("inspect");
const finalized = new ReportedCleanup(
"fixture",
["input"],
{},
{
cleanupPrimary: primary,
},
);
const later = new ProviderCleanupError("fixture", ["workspace"], {});
const outer = analysisErrorWithCleanupFailure(
finalized,
later,
).bindCleanupDependencies([finalized, later]);
const assertReport = (error: typeof outer) => {
expect(projectAnalysisError(error)).toMatchObject({
category: "invalid_input",
details: { operation: "inspect", cleanup_report: report },
});
};
assertReport(finalized);
assertReport(outer);
expect(outer.cleanupIncomplete).toBe(true);
later.resolveCleanup();
assertReport(outer);
expect(outer.cleanupIncomplete).toBe(true);
finalized.resolveCleanup();
assertReport(outer);
expect(outer.cleanupIncomplete).toBe(false);
});
+11 -2
View File
@@ -75,6 +75,15 @@ const projectError = (
const capturedOutput = composed.capturedOutput ?? error.capturedOutput;
const partialObservation =
composed.partialObservation ?? error.partialObservation;
// A finalizer can add a report above the semantic primary, including while
// its cleanup is pending or beneath another cleanup composition.
let cleanupReport = composed.cleanupReport;
for (
let owner = composed.cleanupPrimary;
cleanupReport === undefined && owner !== undefined;
owner = owner.cleanupPrimary
)
cleanupReport = owner.cleanupReport;
assertKnownAnalysisErrorTag(error._tag);
const primary = primaryAnalysisError(error);
assertKnownAnalysisErrorTag(primary._tag);
@@ -83,9 +92,9 @@ const projectError = (
const primaryDetails = errorDetails(primary);
const details = {
...primaryDetails,
...(composed === error || composed.cleanupReport === undefined
...(cleanupReport === undefined
? {}
: { cleanup_report: jsonValueSchema.parse(composed.cleanupReport) }),
: { cleanup_report: jsonValueSchema.parse(cleanupReport) }),
...(diagnostics === undefined
? {}
: {