mirror of
https://github.com/morluto/rea.git
synced 2026-10-27 09:55:46 +00:00
162 lines
4.9 KiB
TypeScript
162 lines
4.9 KiB
TypeScript
import { requireAuthenticationHeadroom } from "./JsonAuthenticationHeadroom.js";
|
|
import { absoluteCallerPath } from "../filesystem/callerPath.js";
|
|
|
|
import { describeValidationFailure } from "../domain/validationFailure.js";
|
|
import {
|
|
safeParseImmutableEvidenceBundle,
|
|
safeParseEvidenceBundle,
|
|
type EvidenceBundle,
|
|
type EvidenceBundleFailure,
|
|
} from "../domain/evidenceBundle.js";
|
|
import {
|
|
EvidenceFileError,
|
|
EvidenceIntegrityError,
|
|
} from "../domain/evidenceErrors.js";
|
|
import { err, ok, type Result } from "../domain/result.js";
|
|
import {
|
|
AnalysisCancelledError,
|
|
type AnalysisResourceConstraintError,
|
|
} from "../domain/analysisErrorCore.js";
|
|
import { safeParseProcessCapture } from "../domain/process/processCaptureParsing.js";
|
|
import {
|
|
bufferedJsonParts,
|
|
canonicalJsonParts,
|
|
} from "../domain/jsonSerialization.js";
|
|
import { readJsonFile, writeTextParts } from "./JsonFiles.js";
|
|
import type { ArtifactResourceScope } from "../artifacts/ArtifactResourceScope.js";
|
|
|
|
type EvidenceReadFailure =
|
|
| EvidenceFileError
|
|
| EvidenceIntegrityError
|
|
| AnalysisResourceConstraintError
|
|
| AnalysisCancelledError;
|
|
type EvidenceWriteFailure =
|
|
| EvidenceFileError
|
|
| EvidenceIntegrityError
|
|
| AnalysisCancelledError;
|
|
|
|
/** Read and validate an evidence bundle at the caller-supplied path. */
|
|
export const readEvidenceBundle = (
|
|
path: string,
|
|
signal?: AbortSignal,
|
|
resources?: ArtifactResourceScope,
|
|
operation = "import_evidence_bundle",
|
|
): Promise<Result<EvidenceBundle, EvidenceReadFailure>> =>
|
|
readBundle(path, safeParseEvidenceBundle, signal, resources, operation);
|
|
|
|
/** Read an authenticated immutable snapshot for a subsequent session import. */
|
|
export const readImmutableEvidenceBundle = (
|
|
path: string,
|
|
signal?: AbortSignal,
|
|
resources?: ArtifactResourceScope,
|
|
operation = "import_evidence_bundle",
|
|
): Promise<Result<EvidenceBundle, EvidenceReadFailure>> =>
|
|
readBundle(
|
|
path,
|
|
safeParseImmutableEvidenceBundle,
|
|
signal,
|
|
resources,
|
|
operation,
|
|
);
|
|
|
|
const readBundle = async (
|
|
path: string,
|
|
parseBundle: (
|
|
input: unknown,
|
|
) => Result<EvidenceBundle, EvidenceBundleFailure>,
|
|
signal?: AbortSignal,
|
|
resources?: ArtifactResourceScope,
|
|
operation = "import_evidence_bundle",
|
|
): Promise<Result<EvidenceBundle, EvidenceReadFailure>> => {
|
|
if (signal?.aborted)
|
|
return err(
|
|
new AnalysisCancelledError(operation, {
|
|
cause: signal.reason,
|
|
}),
|
|
);
|
|
const loaded = await readJsonFile(path, signal, resources, operation);
|
|
if (!loaded.ok) return loaded;
|
|
if (signal?.aborted)
|
|
return err(
|
|
new AnalysisCancelledError(operation, {
|
|
cause: signal.reason,
|
|
}),
|
|
);
|
|
const headroom = requireAuthenticationHeadroom(loaded.value, {
|
|
operation,
|
|
path: absoluteCallerPath(path),
|
|
description:
|
|
"Insufficient heap headroom to authenticate the Evidence bundle",
|
|
});
|
|
if (!headroom.ok) return headroom;
|
|
const parsed = parseBundle(loaded.value);
|
|
if (!parsed.ok) {
|
|
const failure = parsed.error;
|
|
if (failure.kind === "single-record")
|
|
return err(
|
|
new EvidenceFileError("read", "wrong-kind", {
|
|
path: absoluteCallerPath(path),
|
|
cause: failure.cause,
|
|
userMessage: failure.message,
|
|
}),
|
|
);
|
|
return err(
|
|
new EvidenceIntegrityError("Evidence bundle validation failed", {
|
|
cause: failure.cause,
|
|
userMessage: failure.message,
|
|
}),
|
|
);
|
|
}
|
|
const bundle = parsed.value;
|
|
for (const record of bundle.records) {
|
|
if (record.predicate_type !== "rea.process-capture") continue;
|
|
const parsedCapture = safeParseProcessCapture(record.normalized_result);
|
|
if (!parsedCapture.success) {
|
|
return err(
|
|
new EvidenceIntegrityError("Evidence bundle validation failed", {
|
|
cause: parsedCapture.error,
|
|
userMessage: `Process capture record ${record.evidence_id} has an invalid normalized_result (${describeValidationFailure(parsedCapture.error)}). Recreate or re-export the bundle, then try again.`,
|
|
}),
|
|
);
|
|
}
|
|
}
|
|
return ok(bundle);
|
|
};
|
|
|
|
/** Atomically write deterministic evidence JSON at the caller-supplied path. */
|
|
export const writeEvidenceBundle = async (
|
|
bundle: EvidenceBundle,
|
|
path: string,
|
|
overwrite: boolean,
|
|
resources: ArtifactResourceScope,
|
|
signal?: AbortSignal,
|
|
operation = "export_evidence_bundle",
|
|
): Promise<
|
|
Result<
|
|
{ readonly path: string; readonly bytes: number },
|
|
EvidenceWriteFailure
|
|
>
|
|
> => {
|
|
if (signal?.aborted === true)
|
|
return err(
|
|
new AnalysisCancelledError(operation, {
|
|
cause: signal.reason,
|
|
}),
|
|
);
|
|
const checked = safeParseEvidenceBundle(bundle);
|
|
if (!checked.ok) {
|
|
return err(
|
|
new EvidenceIntegrityError("Evidence bundle validation failed", {
|
|
cause: checked.error.cause,
|
|
}),
|
|
);
|
|
}
|
|
return writeTextParts(
|
|
bufferedJsonParts(canonicalJsonParts(checked.value)),
|
|
path,
|
|
overwrite,
|
|
resources,
|
|
signal === undefined ? undefined : { signal, operation },
|
|
);
|
|
};
|