mirror of
https://github.com/morluto/rea.git
synced 2026-11-04 05:46:17 +00:00
The atomic JSON writer reported a failed staging-directory release as cleanup state on the returned EvidenceFileError or AnalysisCancelledError, but never bound that error to the retained staging lease. A later scope retry that removed the stage could not clear the error, so `rea evidence-export` (and other writers sharing the scope, such as --evidence-output delivery) kept reporting cleanup_incomplete and a retained resource after cleanup had completed. Bind each staged-output cleanup failure, and each admission retry failure, to its exact leases so confirmed release restores the primary outcome. Export recovery now reads that bound state instead of a local close flag. A retry that still fails keeps the error incomplete with the same retained stage. Fixes #5842 Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
100 lines
3.3 KiB
TypeScript
100 lines
3.3 KiB
TypeScript
import {
|
|
EvidenceFileError,
|
|
EvidenceIntegrityError,
|
|
} from "../domain/evidenceErrors.js";
|
|
import type { AnalysisError } from "../domain/analysisErrorBase.js";
|
|
import type { EvidenceBundle } from "../domain/evidenceBundle.js";
|
|
import type { JsonValue } from "../domain/jsonValue.js";
|
|
import { jsonValueSchema } from "../domain/jsonValue.js";
|
|
import { err, ok, type Result } from "../domain/result.js";
|
|
import { compareBundles } from "../domain/bundleComparison.js";
|
|
import { ArtifactResourceScope } from "../artifacts/ArtifactResourceScope.js";
|
|
import {
|
|
readEvidenceBundle,
|
|
readImmutableEvidenceBundle,
|
|
writeEvidenceBundle,
|
|
} from "./EvidenceBundleFiles.js";
|
|
|
|
/** Validate a standalone bundle and report its complete record/revision counts. */
|
|
export const importEvidenceBundleCommand = async (
|
|
path: string,
|
|
): Promise<Result<JsonValue, AnalysisError>> => {
|
|
const loaded = await readEvidenceBundle(path);
|
|
if (!loaded.ok) return loaded;
|
|
return ok({
|
|
imported: loaded.value.records.length,
|
|
unknowns_added: loaded.value.unknowns.length,
|
|
total: loaded.value.records.length,
|
|
});
|
|
};
|
|
|
|
/** Validate a source bundle and atomically export canonical bytes. */
|
|
export const exportEvidenceBundleCommand = async (
|
|
sourcePath: string,
|
|
outputPath: string,
|
|
overwrite: boolean,
|
|
): Promise<Result<JsonValue, AnalysisError>> => {
|
|
// The sealed snapshot lets the writer reuse this authentication.
|
|
const loaded = await readImmutableEvidenceBundle(sourcePath);
|
|
if (!loaded.ok) return loaded;
|
|
const resources = new ArtifactResourceScope();
|
|
const written = await writeEvidenceBundle(
|
|
loaded.value,
|
|
outputPath,
|
|
overwrite,
|
|
resources,
|
|
);
|
|
if (written.ok) return projectWrite(loaded.value, written);
|
|
|
|
// Confirmed release resolves the bound error to its primary outcome; a
|
|
// failed retry leaves that error incomplete with its exact retained owner.
|
|
await resources.close().catch(() => undefined);
|
|
if (
|
|
!written.error.cleanupIncomplete &&
|
|
written.error instanceof EvidenceFileError &&
|
|
written.error.published
|
|
) {
|
|
return projectWrite(loaded.value, {
|
|
ok: true,
|
|
value: written.error.published,
|
|
});
|
|
}
|
|
return written;
|
|
};
|
|
|
|
/** Compare two validated canonical Evidence bundles without session state. */
|
|
export const compareEvidenceBundlesCommand = async (input: {
|
|
readonly leftPath: string;
|
|
readonly rightPath: string;
|
|
}): Promise<Result<JsonValue, AnalysisError>> => {
|
|
// Sealed snapshots let comparison and its bundle digests reuse this
|
|
// authentication instead of re-validating every record twice more.
|
|
const [left, right] = await Promise.all([
|
|
readImmutableEvidenceBundle(input.leftPath),
|
|
readImmutableEvidenceBundle(input.rightPath),
|
|
]);
|
|
if (!left.ok) return left;
|
|
if (!right.ok) return right;
|
|
try {
|
|
return ok(jsonValueSchema.parse(compareBundles(left.value, right.value)));
|
|
} catch (cause: unknown) {
|
|
return err(
|
|
new EvidenceIntegrityError("Evidence bundle comparison failed", {
|
|
cause,
|
|
}),
|
|
);
|
|
}
|
|
};
|
|
|
|
const projectWrite = (
|
|
bundle: EvidenceBundle,
|
|
written: Awaited<ReturnType<typeof writeEvidenceBundle>>,
|
|
): Result<JsonValue, AnalysisError> =>
|
|
written.ok
|
|
? ok({
|
|
path: written.value.path,
|
|
bytes: written.value.bytes,
|
|
records: bundle.records.length,
|
|
})
|
|
: err(written.error);
|