mirror of
https://github.com/morluto/rea.git
synced 2026-11-15 19:23:24 +00:00
* refactor(cli): project JSON-document workflow commands from their contracts * test(cli): include WASM interface and native matching documents in the JSON-input guard
309 lines
11 KiB
TypeScript
309 lines
11 KiB
TypeScript
import { projectCliAnalysisResult } from "./projectCliAnalysisResult.js";
|
|
import { z } from "incur";
|
|
|
|
import { compareManagedMemberPaths } from "../application/managed/ManagedMemberComparisonService.js";
|
|
import { executeManagedNativeBoundaries } from "../application/managed/ManagedNativeVerificationService.js";
|
|
import { importManagedReconstructionEvidence } from "../application/managed/ManagedReconstructionService.js";
|
|
import { projectManagedApplicationGraphEvidence } from "../application/managed/ManagedApplicationGraphService.js";
|
|
import type { EvidenceLookup } from "../application/EvidenceInputResolver.js";
|
|
import { MANAGED_WORKFLOW_TOOL_CONTRACTS } from "../contracts/managed/managedWorkflowToolContracts.js";
|
|
import { contractNamed } from "./contractCliDescription.js";
|
|
import { registerContractDocumentCommand } from "./contractCommand.js";
|
|
import type { DirectAnalysis } from "../composition/directAnalysis.js";
|
|
import { ArtifactResourceScope } from "../artifacts/ArtifactResourceScope.js";
|
|
import { MANAGED_STATIC_PROVIDER } from "../domain/investigationProviders.js";
|
|
import { ok } from "../domain/result.js";
|
|
import {
|
|
evidenceBundleOption,
|
|
runEvidenceInputCommand,
|
|
} from "./evidenceInputCommand.js";
|
|
import { CLI_COMMANDS } from "../cliCommandNames.js";
|
|
import { logCliCommand } from "../cliLogging.js";
|
|
import type { Logger } from "pino";
|
|
import type { CliInstance } from "./types.js";
|
|
import { managedMembersInputSchema } from "../contracts/managed/managedToolContracts.js";
|
|
import { localPathStringSchema } from "../domain/localPath.js";
|
|
import { withCommandCancellation } from "./commandCancellation.js";
|
|
import { finishAnalysisCommand } from "./finishAnalysisCommand.js";
|
|
import { managedInspectionBudgetShape } from "../domain/managed/managedInspectionLimits.js";
|
|
import { cliDecimalField } from "./options.js";
|
|
|
|
const managedBudgetOptions = {
|
|
maxSnapshotBytes: cliDecimalField(
|
|
managedInspectionBudgetShape.max_snapshot_bytes,
|
|
),
|
|
maxMetadataRepresentationBytes: cliDecimalField(
|
|
managedInspectionBudgetShape.max_metadata_representation_bytes,
|
|
),
|
|
};
|
|
|
|
const managedBudgetParameters = (options: {
|
|
readonly maxSnapshotBytes?: number | undefined;
|
|
readonly maxMetadataRepresentationBytes?: number | undefined;
|
|
}) => ({
|
|
...(options.maxSnapshotBytes === undefined
|
|
? {}
|
|
: { max_snapshot_bytes: options.maxSnapshotBytes }),
|
|
...(options.maxMetadataRepresentationBytes === undefined
|
|
? {}
|
|
: {
|
|
max_metadata_representation_bytes:
|
|
options.maxMetadataRepresentationBytes,
|
|
}),
|
|
});
|
|
|
|
export const registerManagedCommands = (
|
|
cli: CliInstance,
|
|
logger: Logger,
|
|
runProviderAnalysis: DirectAnalysis["runProviderAnalysis"],
|
|
): void => {
|
|
registerInspectManagedArtifact(cli, logger, runProviderAnalysis);
|
|
registerInspectManagedMembers(cli, logger, runProviderAnalysis);
|
|
registerInspectManagedNativeBoundaries(cli, logger, runProviderAnalysis);
|
|
registerCompareManagedMembers(cli, logger);
|
|
registerManagedWorkflowCommands(cli, logger);
|
|
};
|
|
|
|
const registerInspectManagedArtifact = (
|
|
cli: CliInstance,
|
|
logger: Logger,
|
|
runProviderAnalysis: DirectAnalysis["runProviderAnalysis"],
|
|
): void => {
|
|
cli.command(CLI_COMMANDS.inspectManagedArtifact, {
|
|
description: "Inspect PE/CLI identity without loading target code",
|
|
args: z.object({
|
|
path: localPathStringSchema.describe(
|
|
"Managed PE executable or assembly path",
|
|
),
|
|
}),
|
|
options: z.object(managedBudgetOptions),
|
|
run: ({ args, options }) =>
|
|
logCliCommand(logger, "inspect-managed-artifact", () =>
|
|
runProviderAnalysis(
|
|
args.path,
|
|
"inspect_managed_artifact",
|
|
managedBudgetParameters(options),
|
|
logger,
|
|
).then(projectCliAnalysisResult),
|
|
),
|
|
});
|
|
};
|
|
|
|
const registerInspectManagedMembers = (
|
|
cli: CliInstance,
|
|
logger: Logger,
|
|
runProviderAnalysis: DirectAnalysis["runProviderAnalysis"],
|
|
): void => {
|
|
cli.command(CLI_COMMANDS.inspectManagedMembers, {
|
|
description:
|
|
"Inspect PE/CLI metadata members, signatures, CIL anchors, and ldstr string literals without loading target code",
|
|
args: z.object({
|
|
path: localPathStringSchema.describe(
|
|
"Managed PE executable or assembly path",
|
|
),
|
|
}),
|
|
options: z.object({
|
|
...managedBudgetOptions,
|
|
type: managedMembersInputSchema.shape.type_names.describe(
|
|
"Inspect only members declared by this exact type full name, and the string literals they load, such as System.Threading.Monitor; a nested type is named by its bare name, which selects every nested type of that name (repeatable)",
|
|
),
|
|
detail: managedMembersInputSchema.shape.detail.describe(
|
|
"complete returns full Evidence (default); summary returns identity, selection, coverage, limitations and collection counts and requires --evidence-bundle",
|
|
),
|
|
evidenceBundle: localPathStringSchema
|
|
.optional()
|
|
.describe(
|
|
"Write complete parent Evidence to a new local bundle for later inspect-analysis-view calls; never replaces an existing path",
|
|
),
|
|
}),
|
|
run: ({ args, options }) =>
|
|
withCommandCancellation((signal) =>
|
|
logCliCommand(logger, "inspect-managed-members", () =>
|
|
runProviderAnalysis(
|
|
args.path,
|
|
"inspect_managed_members",
|
|
{
|
|
...managedBudgetParameters(options),
|
|
...(options.type === undefined
|
|
? {}
|
|
: { type_names: options.type }),
|
|
},
|
|
logger,
|
|
signal,
|
|
{
|
|
...(options.detail === undefined
|
|
? {}
|
|
: { detail: options.detail }),
|
|
...(options.evidenceBundle === undefined
|
|
? {}
|
|
: { evidenceBundlePath: options.evidenceBundle }),
|
|
},
|
|
).then(projectCliAnalysisResult),
|
|
),
|
|
),
|
|
});
|
|
};
|
|
|
|
const registerInspectManagedNativeBoundaries = (
|
|
cli: CliInstance,
|
|
logger: Logger,
|
|
runProviderAnalysis: DirectAnalysis["runProviderAnalysis"],
|
|
): void => {
|
|
cli.command(CLI_COMMANDS.inspectManagedNativeBoundaries, {
|
|
description:
|
|
"Inspect PE/CLI PInvoke and native implementation boundary declarations without loading target code",
|
|
args: z.object({
|
|
path: localPathStringSchema.describe(
|
|
"Managed PE executable or assembly path",
|
|
),
|
|
}),
|
|
options: z.object(managedBudgetOptions),
|
|
run: ({ args, options }) =>
|
|
logCliCommand(logger, "inspect-managed-native-boundaries", () =>
|
|
runProviderAnalysis(
|
|
args.path,
|
|
"inspect_managed_native_boundaries",
|
|
managedBudgetParameters(options),
|
|
logger,
|
|
).then(projectCliAnalysisResult),
|
|
),
|
|
});
|
|
};
|
|
|
|
const compareManagedMemberArgs = z.object({
|
|
leftPath: localPathStringSchema.describe(
|
|
"Baseline managed PE executable or assembly",
|
|
),
|
|
rightPath: localPathStringSchema.describe(
|
|
"Candidate managed PE executable or assembly",
|
|
),
|
|
});
|
|
|
|
const registerCompareManagedMembers = (
|
|
cli: CliInstance,
|
|
logger: Logger,
|
|
): void => {
|
|
cli.command(CLI_COMMANDS.compareManagedMembers, {
|
|
description:
|
|
"Compare managed PE/CLI member inventories by exact declared type, name, and signature; names alone never form a match",
|
|
args: compareManagedMemberArgs,
|
|
options: z.object({
|
|
...managedBudgetOptions,
|
|
type: managedMembersInputSchema.shape.type_names.describe(
|
|
"Compare only members declared by this exact type full name on both sides, as selected by inspect-managed-members --type; use it when a whole-assembly comparison exceeds capacity (repeatable)",
|
|
),
|
|
}),
|
|
run: ({ args, options }) =>
|
|
withCommandCancellation((signal) =>
|
|
logCliCommand(logger, "compare-managed-members", async () => {
|
|
const resources = new ArtifactResourceScope();
|
|
const result = await finishAnalysisCommand(
|
|
() =>
|
|
compareManagedMemberPaths(
|
|
{
|
|
leftPath: args.leftPath,
|
|
rightPath: args.rightPath,
|
|
...(options.maxSnapshotBytes === undefined
|
|
? {}
|
|
: { maxSnapshotBytes: options.maxSnapshotBytes }),
|
|
...(options.maxMetadataRepresentationBytes === undefined
|
|
? {}
|
|
: {
|
|
maxRepresentationBytes:
|
|
options.maxMetadataRepresentationBytes,
|
|
}),
|
|
...(options.type === undefined
|
|
? {}
|
|
: { typeNames: options.type }),
|
|
},
|
|
{},
|
|
resources,
|
|
),
|
|
resources,
|
|
MANAGED_STATIC_PROVIDER.id,
|
|
"compare_managed_members",
|
|
signal,
|
|
"Managed member comparison resources",
|
|
);
|
|
return result.ok
|
|
? result.value
|
|
: { error: "Managed member comparison failed", ...result.error };
|
|
}),
|
|
),
|
|
});
|
|
};
|
|
|
|
/** Managed Evidence workflows, each taking its contract input as one JSON document. */
|
|
const MANAGED_WORKFLOWS = [
|
|
{
|
|
name: "import_managed_reconstruction",
|
|
command: CLI_COMMANDS.importManagedReconstruction,
|
|
description:
|
|
"Import decompiler-produced managed reconstruction against exact static member evidence",
|
|
document: "Inline managed reconstruction JSON or JSON file path",
|
|
workflow: (
|
|
input: unknown,
|
|
lookup: EvidenceLookup | undefined,
|
|
signal: AbortSignal,
|
|
) => importManagedReconstructionEvidence(input, lookup, { signal }),
|
|
},
|
|
{
|
|
name: "verify_managed_native_boundaries",
|
|
command: CLI_COMMANDS.verifyManagedNativeBoundaries,
|
|
description:
|
|
"Verify managed P/Invoke declarations against authenticated native Evidence",
|
|
document: "Inline managed/native verification JSON or JSON file path",
|
|
workflow: (
|
|
input: unknown,
|
|
lookup: EvidenceLookup | undefined,
|
|
signal: AbortSignal,
|
|
) => {
|
|
const result = executeManagedNativeBoundaries(input, lookup, { signal });
|
|
return result.ok ? ok(result.value.evidence) : result;
|
|
},
|
|
},
|
|
{
|
|
name: "project_managed_application_graph",
|
|
command: CLI_COMMANDS.projectManagedApplicationGraph,
|
|
description:
|
|
"Project authenticated managed static Evidence into an application graph",
|
|
document: "Inline managed application graph JSON or JSON file path",
|
|
workflow: (
|
|
input: unknown,
|
|
lookup: EvidenceLookup | undefined,
|
|
signal: AbortSignal,
|
|
) => projectManagedApplicationGraphEvidence(input, lookup, { signal }),
|
|
},
|
|
] as const;
|
|
|
|
const registerManagedWorkflowCommands = (
|
|
cli: CliInstance,
|
|
logger: Logger,
|
|
): void => {
|
|
for (const {
|
|
name,
|
|
command,
|
|
description,
|
|
document,
|
|
workflow,
|
|
} of MANAGED_WORKFLOWS)
|
|
registerContractDocumentCommand(
|
|
cli,
|
|
logger,
|
|
contractNamed(MANAGED_WORKFLOW_TOOL_CONTRACTS, name),
|
|
{
|
|
command,
|
|
description,
|
|
document,
|
|
cliOptions: { evidenceBundle: evidenceBundleOption },
|
|
},
|
|
(input, { signal, operation, cliOptions }) =>
|
|
runEvidenceInputCommand(
|
|
operation,
|
|
cliOptions.evidenceBundle,
|
|
signal,
|
|
(lookup) => Promise.resolve(workflow(input, lookup, signal)),
|
|
).then(projectCliAnalysisResult),
|
|
);
|
|
};
|