mirror of
https://github.com/morluto/rea.git
synced 2026-11-09 16:26:36 +00:00
* feat(artifacts): compare artifacts by Evidence reference and from the CLI
compare_artifacts made agents send back the complete inventory Evidence
that REA already retains, and the CLI had no artifact comparison.
Each side of compare_artifacts now takes exactly one of: complete inline
Evidence (unchanged), {kind: "retained-evidence", evidence_id}, or
{kind: "evidence-file", path, evidence_id?}. The two references are the
sources inspect_analysis_view accepts; both tools now compose one
definition in evidenceReference.ts. A summary still resolves to its exact
retained parent, and every source form gives the same comparison Evidence.
An unresolvable retained reference is refused with a typed missing
reference error.
The application layer owns source resolution and the comparison Evidence,
so MCP and the new `rea compare-artifacts` command share one workflow.
readEvidenceFileSource reports bundle-selection refusals at the caller's
own field path.
* refactor(artifacts): resolve compare_artifacts sides like compare_functions
compare_functions, compare_wasm_interfaces and the JavaScript application
comparisons name each side with evidenceInputSchema and resolve it with
resolvePairedEvidenceRequest. compare_artifacts reached the same result
for inline and retained sides through its own dispatch.
evidenceSourceInputSchema now extends evidenceInputSchema with the
Evidence-file reference, and resolvePairedEvidenceSources sends inline and
retained sides to the same resolveEvidenceInput and file sides to the
shared Evidence file reader. compare_artifacts uses both, so the forms it
shares with compare_functions are parsed and resolved by one owner. The
advertised input schema is unchanged.
* docs(cli): point rea compare to compare-artifacts for inventory contents
rea compare (compare_bundles) compares records by Evidence identity and
explicit pairs and never reads artifact contents, so two build
inventories stay unresolved there. Its help and the CLI guide now name
compare-artifacts for that comparison.
* fix(artifacts): resolve summary parents within Evidence source bundles
* perf(evidence): reuse file-owned immutable authentication proofs
* test(evidence): inject failures at both authentication boundaries
---------
Co-authored-by: Entrpi <entrpi@proton.me>
124 lines
4.4 KiB
TypeScript
124 lines
4.4 KiB
TypeScript
import { withCommandCancellation } from "./cli/commandCancellation.js";
|
|
import { safeParseJson } from "./domain/safeJson.js";
|
|
import { AnalysisInputError } from "./domain/analysisErrorCore.js";
|
|
import { analysisInputErrorFromIssues } from "./domain/inputIssueProjection.js";
|
|
import { bundleComparisonInputSchema } from "./domain/bundleComparison.js";
|
|
import { Cli, z } from "incur";
|
|
|
|
import {
|
|
compareEvidenceBundlesCommand,
|
|
exportEvidenceBundleCommand,
|
|
importEvidenceBundleCommand,
|
|
} from "./application/EvidenceBundleCommands.js";
|
|
import { logCliCommand } from "./cliLogging.js";
|
|
import type { Logger } from "pino";
|
|
import { CLI_COMMANDS } from "./cliCommandNames.js";
|
|
import type { JsonValue } from "./domain/jsonValue.js";
|
|
import { projectAnalysisError } from "./domain/analysisErrorProjection.js";
|
|
import type { AnalysisError } from "./domain/analysisErrorBase.js";
|
|
|
|
/** Register caller-path Evidence commands. */
|
|
export const registerEvidenceCommands = (
|
|
cli: ReturnType<typeof Cli.create>,
|
|
logger: Logger,
|
|
): void => {
|
|
cli.command(CLI_COMMANDS.evidenceImport, {
|
|
description: "Validate and import a bounded local Evidence bundle",
|
|
args: z.object({
|
|
path: z.string().describe("Evidence bundle JSON path"),
|
|
}),
|
|
run: ({ args }) =>
|
|
withCommandCancellation((signal) =>
|
|
logCliCommand(logger, "evidence-import", async () => {
|
|
const imported = await importEvidenceBundleCommand(args.path, signal);
|
|
return imported.ok ? imported.value : cliError(imported.error);
|
|
}),
|
|
),
|
|
});
|
|
cli.command(CLI_COMMANDS.evidenceExport, {
|
|
description: "Validate and atomically export canonical Evidence JSON",
|
|
args: z.object({
|
|
source: z.string().describe("Existing evidence bundle JSON path"),
|
|
output: z.string().describe("Canonical output JSON path"),
|
|
}),
|
|
options: z.object({
|
|
overwrite: z.boolean().default(false).describe("Replace output file"),
|
|
}),
|
|
run: ({ args, options }) =>
|
|
withCommandCancellation((signal) =>
|
|
logCliCommand(logger, "evidence-export", async () => {
|
|
const exported = await exportEvidenceBundleCommand(
|
|
args.source,
|
|
args.output,
|
|
options.overwrite,
|
|
signal,
|
|
);
|
|
return exported.ok ? exported.value : cliError(exported.error);
|
|
}),
|
|
),
|
|
});
|
|
cli.command(CLI_COMMANDS.compare, {
|
|
description:
|
|
"Compare two canonical Evidence bundles by record identity; use compare-artifacts for artifact inventory contents",
|
|
args: z.object({
|
|
left: z.string().describe("Left Evidence bundle JSON path"),
|
|
right: z.string().describe("Right Evidence bundle JSON path"),
|
|
}),
|
|
options: z.object({
|
|
record_pairs: z
|
|
.string()
|
|
.optional()
|
|
.describe(
|
|
"JSON array of explicit left_evidence_id/right_evidence_id observation pairs",
|
|
),
|
|
}),
|
|
run: ({ args, options }) =>
|
|
withCommandCancellation((signal) =>
|
|
logCliCommand(logger, "compare", async () => {
|
|
const decoded =
|
|
options.record_pairs === undefined
|
|
? { ok: true as const, value: [] }
|
|
: safeParseJson(options.record_pairs);
|
|
if (!decoded.ok)
|
|
return cliError(
|
|
new AnalysisInputError(
|
|
"compare_bundles",
|
|
{ cause: decoded.cause },
|
|
[
|
|
{
|
|
path: ["record_pairs"],
|
|
reason: "invalid_format",
|
|
expected: "JSON array",
|
|
},
|
|
],
|
|
),
|
|
);
|
|
const input = { record_pairs: decoded.value };
|
|
const selected = bundleComparisonInputSchema
|
|
.pick({ record_pairs: true })
|
|
.safeParse(input);
|
|
if (!selected.success)
|
|
return cliError(
|
|
analysisInputErrorFromIssues(
|
|
"compare_bundles",
|
|
selected.error.issues,
|
|
input,
|
|
),
|
|
);
|
|
const compared = await compareEvidenceBundlesCommand({
|
|
leftPath: args.left,
|
|
rightPath: args.right,
|
|
recordPairs: selected.data.record_pairs,
|
|
signal,
|
|
});
|
|
return compared.ok ? compared.value : cliError(compared.error);
|
|
}),
|
|
),
|
|
});
|
|
};
|
|
|
|
const cliError = (error: AnalysisError): JsonValue => ({
|
|
error: "Analysis failed",
|
|
...projectAnalysisError(error),
|
|
});
|