mirror of
https://github.com/morluto/rea.git
synced 2026-10-27 18:05:45 +00:00
* perf(evidence): reuse sealed bundle reads in compare and export `rea compare`, the compare_bundles MCP tool and `rea evidence-export` read bundles through the mutable reader, so each consumer re-authenticated every record again: compareBundles parsed both bundles a second time and bundleDigest's serializeEvidenceBundle a third time; export's writer parsed the source once more before serializing. These consumers only read the bundle, so read it through the existing sealed reader (readImmutableEvidenceBundle). Later parses hit the immutable bundle cache. Ordinary mutable reads, kept deliberately by #1721, are unchanged. Output is byte-identical. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(process): report no remaining time after a provider deadline fires Node timers can fire while performance.now() is still a fraction of a millisecond short of the monotonic deadline. ProviderStartupDeadline then reported interruption "timeout" and an aborted signal while remainingMs() returned ceil(0.x) = 1, so polling loops guarded by remainingMs() > 0 (Hopper and Ghidra startup) could run past an elapsed deadline, and the real-timer test "aborts a bounded wait when its real monotonic deadline expires" failed intermittently in CI (expected 1 to be 0). Once the deadline's own timer has classified a timeout, report zero remaining time. A deterministic regression test fires the timer with the monotonic clock 0.4 ms short of the deadline. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
104 lines
3.3 KiB
TypeScript
104 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);
|
|
|
|
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>> => {
|
|
// 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);
|