178 lines
5.0 KiB
TypeScript
178 lines
5.0 KiB
TypeScript
import { createHash } from "node:crypto";
|
|
|
|
import { describe, expect, it } from "vitest";
|
|
|
|
import { importManagedReconstructionEvidence } from "../src/application/ManagedReconstructionService.js";
|
|
import { MANAGED_RECONSTRUCTION_IMPORT_EXAMPLE } from "../src/contracts/managedWorkflowExamples.js";
|
|
import {
|
|
importManagedReconstruction,
|
|
managedReconstructionImportInputSchema,
|
|
} from "../src/domain/managedReconstruction.js";
|
|
import { managedMemberInspectionSchema } from "../src/domain/managedArtifact.js";
|
|
import { createEvidence } from "../src/domain/evidence.js";
|
|
|
|
const exampleInput = () =>
|
|
managedReconstructionImportInputSchema.parse(
|
|
MANAGED_RECONSTRUCTION_IMPORT_EXAMPLE,
|
|
);
|
|
|
|
const exampleMethod = () => {
|
|
const method = exampleInput().methods[0];
|
|
if (method === undefined) throw new Error("missing example method");
|
|
return method;
|
|
};
|
|
|
|
describe("managed decompiler reconstruction import", () => {
|
|
it("imports decompiler output as inference locked to static IL evidence", () => {
|
|
const result = importManagedReconstruction(exampleInput());
|
|
|
|
expect(result).toMatchObject({
|
|
schema_version: 1,
|
|
phase: "reconstruction-import",
|
|
executed: false,
|
|
static_observation: {
|
|
artifact_sha256: "2".repeat(64),
|
|
mvid: "11112222-3333-4444-9555-666677778888",
|
|
},
|
|
decompiler: {
|
|
name: "ilspycmd",
|
|
version: "9.1.0.7988",
|
|
family: "ilspy",
|
|
},
|
|
summary: {
|
|
imported_methods: 1,
|
|
decompiled_csharp_methods: 1,
|
|
},
|
|
methods: [
|
|
{
|
|
token: "0x06000001",
|
|
signature_sha256: "3".repeat(64),
|
|
normalized_il_sha256: "5".repeat(64),
|
|
validation: {
|
|
matched_static_member: true,
|
|
exact_build_required: true,
|
|
canonical_observation: false,
|
|
confidence_floor: "inference",
|
|
},
|
|
},
|
|
],
|
|
});
|
|
expect(result.reconstruction_id).toMatch(/^mre_[a-f0-9]{64}$/u);
|
|
expect(result.methods[0]?.reconstruction.text_sha256).toBe(
|
|
createHash("sha256")
|
|
.update("internal static void Main() { }")
|
|
.digest("hex"),
|
|
);
|
|
expect(result.limitations.join(" ")).toContain(
|
|
"metadata and IL observations remain canonical",
|
|
);
|
|
});
|
|
|
|
it("rejects reconstruction text hash drift", () => {
|
|
expect(() =>
|
|
importManagedReconstruction({
|
|
...exampleInput(),
|
|
methods: [
|
|
{
|
|
...exampleMethod(),
|
|
reconstruction: {
|
|
...exampleMethod().reconstruction,
|
|
text_sha256: "0".repeat(64),
|
|
},
|
|
},
|
|
],
|
|
}),
|
|
).toThrow(/text hash mismatch/u);
|
|
});
|
|
|
|
it("rejects stale method locks", () => {
|
|
expect(() =>
|
|
importManagedReconstruction({
|
|
...exampleInput(),
|
|
methods: [
|
|
{
|
|
...exampleMethod(),
|
|
normalized_il_sha256: "6".repeat(64),
|
|
},
|
|
],
|
|
}),
|
|
).toThrow(/does not match/u);
|
|
});
|
|
|
|
it("rejects reconstruction locks for partial CIL without normalized identity", () => {
|
|
const input = exampleInput();
|
|
const observed = managedMemberInspectionSchema.parse(
|
|
input.static_members.normalized_result,
|
|
);
|
|
const method = observed.methods.items[0];
|
|
if (method === undefined) throw new Error("missing observed method");
|
|
const partial = {
|
|
...observed,
|
|
methods: {
|
|
...observed.methods,
|
|
items: [
|
|
{
|
|
...method,
|
|
body: {
|
|
...method.body,
|
|
status: "partial" as const,
|
|
normalized_il_sha256: null,
|
|
truncated_instructions: 1,
|
|
issue: "instruction limit reached",
|
|
},
|
|
},
|
|
],
|
|
},
|
|
coverage: { state: "partial" as const, issues: [] },
|
|
};
|
|
const partialEvidence = createEvidence(
|
|
undefined,
|
|
input.static_members.provider,
|
|
{
|
|
operation: "inspect_managed_members",
|
|
parameters: {},
|
|
result: partial,
|
|
rawResult: null,
|
|
limitations: partial.limitations,
|
|
},
|
|
);
|
|
|
|
expect(() =>
|
|
importManagedReconstruction({
|
|
...input,
|
|
static_members: partialEvidence,
|
|
methods: [
|
|
{
|
|
...exampleMethod(),
|
|
normalized_il_sha256: null,
|
|
},
|
|
],
|
|
}),
|
|
).toThrow(/does not match/u);
|
|
});
|
|
|
|
it("wraps imported reconstruction in Evidence v2", () => {
|
|
const evidence = importManagedReconstructionEvidence(exampleInput());
|
|
|
|
expect(evidence.ok).toBe(true);
|
|
if (!evidence.ok) return;
|
|
expect(evidence.value).toMatchObject({
|
|
operation: "import_managed_reconstruction",
|
|
provider: { id: "rea-dotnet-workflows" },
|
|
confidence: "inferred",
|
|
authority: "analyst-inference",
|
|
normalized_result: {
|
|
phase: "reconstruction-import",
|
|
methods: [
|
|
{
|
|
reconstruction: {
|
|
kind: "decompiled-csharp",
|
|
language: "csharp",
|
|
},
|
|
},
|
|
],
|
|
},
|
|
});
|
|
});
|
|
});
|