mirror of
https://github.com/morluto/rea.git
synced 2026-10-29 10:55:57 +00:00
Give atomic Evidence and snapshot writes an explicit resource owner, preserve primary failures and completed publication receipts, and retry retained staging cleanup at the owning lifecycle. Verify inode identity before publishing or deleting; unknown path disappearance remains a cleanup failure. Keep snapshot admission reusable when a session closes and reopens. Consolidate JavaScript source-scope classification so cancellation cannot fabricate failures for unselected files. Classify configuration failures as invalid input. Remove stale assertions and reuse shared test cleanup ownership. Validation: 266 focused tests before final ownership tightening, 108 affected tests afterward, static and documentation checks, actual invalid-configuration CLI behavior, and isolated test temporary-directory hygiene. Refs #1968
100 lines
3.0 KiB
TypeScript
100 lines
3.0 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,
|
|
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>> => {
|
|
const loaded = await readEvidenceBundle(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);
|
|
|
|
let cleanupSucceeded = false;
|
|
try {
|
|
await resources.close();
|
|
cleanupSucceeded = true;
|
|
} catch {
|
|
// The original error cause retains the same staged-output owner for diagnostics.
|
|
}
|
|
if (
|
|
cleanupSucceeded &&
|
|
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>> => {
|
|
const [left, right] = await Promise.all([
|
|
readEvidenceBundle(input.leftPath),
|
|
readEvidenceBundle(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);
|