mirror of
https://github.com/morluto/rea.git
synced 2026-11-07 05:56:19 -10:00
feat(evidence): aggregate exact retained comparison references (#8267)
This commit is contained in:
1 parent
6e2404b869
commit
d9e01e5af2
12 files changed
+375
-29
No files matched your search
@@ -2,7 +2,7 @@
|
||||
name: reverse-engineer-anything
|
||||
description: Reverse engineer native, managed, Electron/JavaScript, packaged, firmware, and browser targets with REA, and build agent environments (AgentEnv bundles) of web applications from a live instance or a capture. Use shipped-artifact or requested runtime evidence to explain features, compare versions, decompile code, or guide a reconstruction. Skip REA for ordinary source-repository architecture analysis.
|
||||
metadata:
|
||||
version: "57"
|
||||
version: "58"
|
||||
---
|
||||
|
||||
# REA
|
||||
|
||||
@@ -13,6 +13,13 @@ Select columns, batch IDs and page individual facts; aliases remain unresolved.
|
||||
Unpaired observations do not prove additions/removals. Existing matched dossier
|
||||
pairs include inputs for `compare_functions`; otherwise analyze just chosen pairs.
|
||||
|
||||
Aggregate completed comparisons with `find_changed_behavior`, selecting each
|
||||
comparison's exact `retained-evidence` ID instead of resending full records.
|
||||
Inline comparison Evidence remains accepted. References belong to the current
|
||||
server: after handoff, import the original bundle including its source closure.
|
||||
Do not substitute dossier IDs or summary views. Static differences remain
|
||||
behavior candidates, with runtime behavior unknown unless separately observed.
|
||||
|
||||
For a matching representation refusal, inspect `details.limit` and raise
|
||||
`native.matching_representation_bytes` with `update_analysis_settings` before
|
||||
retrying the unchanged MCP request. CLI launch seed:
|
||||
|
||||
@@ -1130,6 +1130,25 @@ and slice commitments establish the same canonical entry. The result reports
|
||||
remain separate dimensions. Equal numeric addresses without applicable coordinate
|
||||
commitments do not establish same-entry correspondence. Inspect the retained
|
||||
source Evidence and coverage when correspondence remains ambiguous.
|
||||
To aggregate already-retained comparison results, pass their exact IDs to
|
||||
`find_changed_behavior`, for example:
|
||||
|
||||
```javascript
|
||||
await call("find_changed_behavior", {
|
||||
comparisons: comparisons.map(({ evidence_id }) => ({
|
||||
kind: "retained-evidence",
|
||||
evidence_id,
|
||||
})),
|
||||
});
|
||||
```
|
||||
|
||||
These IDs select comparison Evidence, not function dossiers or summary views.
|
||||
Complete inline comparisons remain accepted and can be mixed with references.
|
||||
The current server must retain the comparisons and their original source closure;
|
||||
import the complete source bundle after a session handoff. Missing or damaged
|
||||
records are explicit failures. Aggregating static differences does not establish
|
||||
observed runtime change.
|
||||
|
||||
`set_address_name` and `set_addresses_names` give Ghidra sessions the same
|
||||
compact naming results as Hopper: a local function entry renames that function,
|
||||
and any other mapped address, such as a global, receives a new or renamed primary
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import { readChangedBehaviorSelection } from "../../domain/changedBehavior.js";
|
||||
import { retainedEvidenceReferenceSchema } from "../../domain/evidenceReference.js";
|
||||
import { analysisInputErrorFromIssues } from "../../domain/inputIssueProjection.js";
|
||||
import type { AnalysisError } from "../../domain/analysisErrorBase.js";
|
||||
import { err, ok, type Result } from "../../domain/result.js";
|
||||
import {
|
||||
resolveEvidenceInput,
|
||||
type EvidenceLookup,
|
||||
} from "../EvidenceInputResolver.js";
|
||||
|
||||
/** Resolve exact selected identities; the domain still owns full-record authentication. */
|
||||
export const resolveChangedBehaviorEvidence = (
|
||||
input: unknown,
|
||||
lookup: EvidenceLookup | undefined,
|
||||
): Result<{ readonly comparisons: readonly unknown[] }, AnalysisError> => {
|
||||
const selection = readChangedBehaviorSelection(input);
|
||||
if (!selection.ok) return selection;
|
||||
const comparisons: unknown[] = [];
|
||||
for (const [index, item] of selection.value.entries()) {
|
||||
if (typeof item !== "object" || item === null || !("kind" in item)) {
|
||||
comparisons.push(item);
|
||||
continue;
|
||||
}
|
||||
const reference = retainedEvidenceReferenceSchema.safeParse(item);
|
||||
if (!reference.success)
|
||||
return err(
|
||||
analysisInputErrorFromIssues(
|
||||
"find_changed_behavior",
|
||||
reference.error.issues.map((issue) => ({
|
||||
...issue,
|
||||
path: ["comparisons", index, ...issue.path],
|
||||
})),
|
||||
input,
|
||||
{ cause: reference.error },
|
||||
),
|
||||
);
|
||||
const resolved = resolveEvidenceInput(reference.data, lookup);
|
||||
if (!resolved.ok) return resolved;
|
||||
comparisons.push(resolved.value);
|
||||
}
|
||||
return ok({ comparisons });
|
||||
};
|
||||
@@ -72,7 +72,14 @@ export const INVESTIGATION_EXAMPLES = {
|
||||
right: PROCESS_CAPTURE_RECONSTRUCTION,
|
||||
},
|
||||
},
|
||||
find_changed_behavior: { comparisons: [FUNCTION_COMPARISON_EVIDENCE] },
|
||||
find_changed_behavior: {
|
||||
comparisons: [
|
||||
{
|
||||
kind: "retained-evidence",
|
||||
evidence_id: FUNCTION_COMPARISON_EVIDENCE.evidence_id,
|
||||
},
|
||||
],
|
||||
},
|
||||
build_call_path: {
|
||||
functions: [FUNCTION_COMPARISON_EXAMPLE.left],
|
||||
start: { address: "0x1000" },
|
||||
|
||||
@@ -125,7 +125,7 @@ export const SESSION_TOOL_CONTRACTS = [
|
||||
),
|
||||
session(
|
||||
"find_changed_behavior",
|
||||
"Aggregate validated process, artifact, and function comparison Evidence. Runtime observations remain distinct from static behavior candidates; missing or incomplete comparisons produce unresolved findings, never causal claims. Returns every finding inline.",
|
||||
"Aggregate validated process, artifact, and function comparison Evidence, supplied inline or by exact retained-evidence references from this session. Original records and their complete source closure remain authenticated; summaries do not substitute for comparisons. Runtime observations remain distinct from static behavior candidates; missing or incomplete comparisons produce unresolved findings, never causal claims. Returns every finding inline.",
|
||||
changedBehaviorInputSchema,
|
||||
),
|
||||
session(
|
||||
|
||||
@@ -10,7 +10,9 @@ import {
|
||||
readComparisonResult,
|
||||
type ParsedComparisonEvidence,
|
||||
} from "./comparisonEvidenceResult.js";
|
||||
import { evidenceSchema, type Evidence } from "./evidence.js";
|
||||
import type { Evidence } from "./evidence.js";
|
||||
import { evidenceInputSchema } from "./evidenceReference.js";
|
||||
import { err, ok, type Result } from "./result.js";
|
||||
import type { AnalysisInputError } from "./analysisErrorCore.js";
|
||||
import { EvidenceIntegrityError } from "./evidenceErrors.js";
|
||||
import { analysisInputErrorFromIssues } from "./inputIssueProjection.js";
|
||||
@@ -29,9 +31,28 @@ const evidenceIdSchema = prefixedDigestSchema("ev");
|
||||
|
||||
/** Input for aggregating existing comparison Evidence. */
|
||||
export const changedBehaviorInputSchema = z.strictObject({
|
||||
comparisons: z.array(evidenceSchema).min(1),
|
||||
comparisons: z.array(evidenceInputSchema).min(1),
|
||||
});
|
||||
|
||||
const comparisonSelectionSchema = z.strictObject({
|
||||
comparisons: z.array(z.unknown()).min(1),
|
||||
});
|
||||
|
||||
/** Admit the selection envelope without copying or authenticating full records. */
|
||||
export const readChangedBehaviorSelection = (
|
||||
input: unknown,
|
||||
): Result<readonly unknown[], AnalysisInputError> => {
|
||||
const envelope = comparisonSelectionSchema.safeParse(input);
|
||||
return envelope.success
|
||||
? ok(envelope.data.comparisons)
|
||||
: err(
|
||||
analysisInputErrorFromIssues(OPERATION, envelope.error.issues, input, {
|
||||
cause: envelope.error,
|
||||
userMessage: "Provide at least one comparison Evidence item.",
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const findingSchema = z.object({
|
||||
scope: z.enum(["runtime", "protocol", "resource", "static_candidate"]),
|
||||
dimension: z.string().min(1),
|
||||
@@ -107,20 +128,9 @@ export const findChangedBehavior = (input: unknown): FoundChangedBehavior => {
|
||||
// The public input schema remains available to clients; this internal
|
||||
// admission only checks the list shape because readEvidence below owns
|
||||
// validation and authentication of each retained Evidence item.
|
||||
const envelope = z
|
||||
.strictObject({ comparisons: z.array(z.unknown()).min(1) })
|
||||
.safeParse(input);
|
||||
if (!envelope.success)
|
||||
throw analysisInputErrorFromIssues(
|
||||
OPERATION,
|
||||
envelope.error.issues,
|
||||
input,
|
||||
{
|
||||
cause: envelope.error,
|
||||
userMessage: "Provide at least one comparison Evidence item.",
|
||||
},
|
||||
);
|
||||
const { comparisons } = envelope.data;
|
||||
const selection = readChangedBehaviorSelection(input);
|
||||
if (!selection.ok) throw selection.error;
|
||||
const comparisons = selection.value;
|
||||
const evidence = comparisons.map((item, index) =>
|
||||
parseComparisonEvidence(item, input, index),
|
||||
);
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { ARTIFACT_COMPARISON_EXAMPLE } from "../contracts/artifactComparisonExample.js";
|
||||
import { INVESTIGATION_EXAMPLES } from "../contracts/investigationExamples.js";
|
||||
import {
|
||||
INVESTIGATION_EXAMPLES,
|
||||
FUNCTION_COMPARISON_EVIDENCE,
|
||||
} from "../contracts/investigationExamples.js";
|
||||
import {
|
||||
artifactComparisonResultSchema,
|
||||
compareArtifacts,
|
||||
@@ -20,10 +23,7 @@ import {
|
||||
staticRuntimeCorrelationResultSchema,
|
||||
} from "./staticRuntimeCorrelation.js";
|
||||
|
||||
const staticComparison =
|
||||
INVESTIGATION_EXAMPLES.find_changed_behavior.comparisons[0];
|
||||
if (staticComparison === undefined)
|
||||
throw new Error("Missing static comparison fixture");
|
||||
const staticComparison = FUNCTION_COMPARISON_EVIDENCE;
|
||||
|
||||
const artifactComparison = createEvidence(
|
||||
undefined,
|
||||
|
||||
@@ -6,5 +6,5 @@ export const PACKAGE_METADATA = {
|
||||
serverSdkVersion: "2.3.1",
|
||||
clientSdkVersion: "2.3.1",
|
||||
coreSdkVersion: "2.3.1",
|
||||
skillVersion: "57",
|
||||
skillVersion: "58",
|
||||
} as const;
|
||||
@@ -1,4 +1,5 @@
|
||||
import { resolveInvestigationEvidenceClosure } from "../application/investigation/InvestigationEvidence.js";
|
||||
import { resolveChangedBehaviorEvidence } from "../application/investigation/ChangedBehaviorEvidence.js";
|
||||
import type { EvidenceMcpServer } from "./EvidenceMcpServer.js";
|
||||
|
||||
import type { BinarySessionPort } from "../application/binary/BinarySessionPort.js";
|
||||
@@ -68,8 +69,14 @@ const registerChangedBehavior = (
|
||||
contract.name,
|
||||
context.mcpReq.signal,
|
||||
async () => {
|
||||
const resolved = resolveChangedBehaviorEvidence(
|
||||
input,
|
||||
session.evidenceForAnalysis.bind(session),
|
||||
);
|
||||
if (!resolved.ok)
|
||||
return server.delivery.toCallToolResult(resolved, contract);
|
||||
const computed = await runDerivedOperation(context, contract.name, () =>
|
||||
findChangedBehavior(input),
|
||||
findChangedBehavior(resolved.value),
|
||||
);
|
||||
if (!computed.ok)
|
||||
return server.delivery.toCallToolResult(computed, contract);
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
import { writeFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { expect, it } from "vitest";
|
||||
|
||||
import { FUNCTION_COMPARISON_EXAMPLE } from "../../src/contracts/functionComparisonExample.js";
|
||||
import { FUNCTION_COMPARISON_EVIDENCE } from "../../src/contracts/investigationExamples.js";
|
||||
import { changedBehaviorResultSchema } from "../../src/domain/changedBehavior.js";
|
||||
import { createEvidence, parseEvidence } from "../../src/domain/evidence.js";
|
||||
import { createEvidenceBundle } from "../../src/domain/evidenceBundle.js";
|
||||
import { connectLocalToolsMcp } from "../fixtures/localToolsMcp.js";
|
||||
import { parseMcpToolError } from "../fixtures/mcpToolError.js";
|
||||
import { createTestTempDirectory } from "../fixtures/temporaryDirectory.js";
|
||||
|
||||
it("aggregates selected retained observations within a caller's input budget", async () => {
|
||||
// Distinct authenticated observations may have identical derived contents.
|
||||
const comparisons = Array.from({ length: 12 }, (_, observation) =>
|
||||
createEvidence(undefined, FUNCTION_COMPARISON_EVIDENCE.provider, {
|
||||
operation: "compare_functions",
|
||||
predicateType: "rea.function-comparison",
|
||||
parameters: { ...FUNCTION_COMPARISON_EVIDENCE.parameters, observation },
|
||||
result: FUNCTION_COMPARISON_EVIDENCE.normalized_result,
|
||||
confidence: "derived",
|
||||
authority: "analyst-inference",
|
||||
limitations: FUNCTION_COMPARISON_EVIDENCE.limitations,
|
||||
evidenceLinks: FUNCTION_COMPARISON_EVIDENCE.evidence_links,
|
||||
}),
|
||||
);
|
||||
const directory = await createTestTempDirectory("rea-retained-comparisons-");
|
||||
const path = join(directory, "comparisons.json");
|
||||
await writeFile(
|
||||
path,
|
||||
JSON.stringify(
|
||||
createEvidenceBundle([
|
||||
FUNCTION_COMPARISON_EXAMPLE.left,
|
||||
FUNCTION_COMPARISON_EXAMPLE.right,
|
||||
...comparisons,
|
||||
]),
|
||||
),
|
||||
);
|
||||
const { call } = await connectLocalToolsMcp({
|
||||
REA_MCP_MAX_INPUT_ELEMENTS: "1024",
|
||||
});
|
||||
const imported = await call("import_evidence_bundle", { path });
|
||||
expect(imported.isError, JSON.stringify(imported)).not.toBe(true);
|
||||
const inline = await call("find_changed_behavior", { comparisons });
|
||||
expect(parseMcpToolError(inline).error).toMatchObject({
|
||||
code: "resource_constraint",
|
||||
details: { reported_limits: { max_input_elements: 1024 } },
|
||||
});
|
||||
const response = await call("find_changed_behavior", {
|
||||
comparisons: comparisons.map(({ evidence_id }) => ({
|
||||
kind: "retained-evidence",
|
||||
evidence_id,
|
||||
})),
|
||||
});
|
||||
expect(response.isError, JSON.stringify(response)).not.toBe(true);
|
||||
const aggregate = parseEvidence(response.structuredContent);
|
||||
const result = changedBehaviorResultSchema.parse(aggregate.normalized_result);
|
||||
expect(result.behavior_status).toBe("unknown");
|
||||
expect(result.summary.observed_changes).toBe(0);
|
||||
expect(aggregate.parameters.comparison_evidence_ids).toEqual(
|
||||
comparisons.map(({ evidence_id }) => evidence_id),
|
||||
);
|
||||
expect(
|
||||
new Set(
|
||||
result.findings.items.map(
|
||||
({ source_comparison_id }) => source_comparison_id,
|
||||
),
|
||||
),
|
||||
).toEqual(new Set(comparisons.map(({ evidence_id }) => evidence_id)));
|
||||
expect(aggregate.evidence_links).toEqual(
|
||||
expect.arrayContaining([
|
||||
FUNCTION_COMPARISON_EXAMPLE.left.evidence_id,
|
||||
FUNCTION_COMPARISON_EXAMPLE.right.evidence_id,
|
||||
...comparisons.map(({ evidence_id }) => evidence_id),
|
||||
]),
|
||||
);
|
||||
});
|
||||
@@ -24,13 +24,12 @@ import {
|
||||
import { jsonObjectSchema } from "../../../src/domain/jsonValue.js";
|
||||
import { createServer } from "../../../src/server/createServer.js";
|
||||
import { observed } from "../../fixtures/analysisExecution.js";
|
||||
import { parseMcpToolError } from "../../fixtures/mcpToolError.js";
|
||||
import type { RelationshipCoverage } from "../../../src/domain/relationshipCoverage.js";
|
||||
|
||||
it("aggregates comparison Evidence and records an approved runtime gap", async () => {
|
||||
const { session, server, client } = await connected();
|
||||
const comparison =
|
||||
INVESTIGATION_EXAMPLES.find_changed_behavior.comparisons[0];
|
||||
if (comparison === undefined) throw new Error("missing comparison fixture");
|
||||
const comparison = FUNCTION_COMPARISON_EVIDENCE;
|
||||
for (const evidence of [
|
||||
FUNCTION_COMPARISON_EXAMPLE.left,
|
||||
FUNCTION_COMPARISON_EXAMPLE.right,
|
||||
@@ -50,6 +49,19 @@ it("aggregates comparison Evidence and records an approved runtime gap", async (
|
||||
provider: { id: "rea-changed-behavior" },
|
||||
normalized_result: { behavior_status: "unknown" },
|
||||
});
|
||||
const referenced = await client.callTool({
|
||||
name: "find_changed_behavior",
|
||||
arguments: {
|
||||
comparisons: [
|
||||
{
|
||||
kind: "retained-evidence",
|
||||
evidence_id: comparison.evidence_id,
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
expect(referenced.isError, JSON.stringify(referenced)).not.toBe(true);
|
||||
expect(inlineEvidence(referenced.structuredContent)).toEqual(evidence);
|
||||
expect(session.listUnknowns({ domain: "changed-behavior" })).toMatchObject([
|
||||
{
|
||||
question: `Did both versions behave the same under a complete controlled replay? (Evidence ${evidence.evidence_id})`,
|
||||
@@ -696,3 +708,167 @@ it("keeps an aggregate function reconstruction unknown while a constituent dimen
|
||||
await close(session, server, client);
|
||||
}
|
||||
});
|
||||
|
||||
it.each([
|
||||
{
|
||||
name: "empty selection",
|
||||
input: { comparisons: [] },
|
||||
code: "invalid_request",
|
||||
},
|
||||
{
|
||||
name: "missing record",
|
||||
input: {
|
||||
comparisons: [
|
||||
{ kind: "retained-evidence", evidence_id: `ev_${"f".repeat(64)}` },
|
||||
],
|
||||
},
|
||||
code: "evidence_integrity_mismatch",
|
||||
},
|
||||
{
|
||||
name: "wrong operation",
|
||||
input: {
|
||||
comparisons: [
|
||||
{
|
||||
kind: "retained-evidence",
|
||||
evidence_id: FUNCTION_COMPARISON_EXAMPLE.left.evidence_id,
|
||||
},
|
||||
],
|
||||
},
|
||||
code: "invalid_request",
|
||||
},
|
||||
{
|
||||
name: "mixed duplicate identity",
|
||||
input: {
|
||||
comparisons: [
|
||||
FUNCTION_COMPARISON_EVIDENCE,
|
||||
{
|
||||
kind: "retained-evidence",
|
||||
evidence_id: FUNCTION_COMPARISON_EVIDENCE.evidence_id,
|
||||
},
|
||||
],
|
||||
},
|
||||
code: "invalid_request",
|
||||
},
|
||||
{
|
||||
name: "malformed envelope",
|
||||
input: { comparisons: "not-an-array" },
|
||||
code: "invalid_request",
|
||||
},
|
||||
{
|
||||
name: "malformed reference",
|
||||
input: {
|
||||
comparisons: [{ kind: "retained-evidence", evidence_id: "bad-id" }],
|
||||
},
|
||||
code: "invalid_request",
|
||||
},
|
||||
{
|
||||
name: "damaged inline observation",
|
||||
input: {
|
||||
comparisons: [
|
||||
{
|
||||
kind: "retained-evidence",
|
||||
evidence_id: FUNCTION_COMPARISON_EVIDENCE.evidence_id,
|
||||
},
|
||||
{
|
||||
...FUNCTION_COMPARISON_EVIDENCE,
|
||||
normalized_result: { tampered: true },
|
||||
},
|
||||
],
|
||||
},
|
||||
code: "evidence_integrity_mismatch",
|
||||
},
|
||||
])(
|
||||
"preserves explicit failure for $name in reference aggregation",
|
||||
async ({ input, code }) => {
|
||||
let providerCalls = 0;
|
||||
const { session, server, client } = await connected({
|
||||
execute: async () => {
|
||||
providerCalls++;
|
||||
throw new Error("Retained aggregation must not query a provider");
|
||||
},
|
||||
});
|
||||
try {
|
||||
for (const source of [
|
||||
FUNCTION_COMPARISON_EXAMPLE.left,
|
||||
FUNCTION_COMPARISON_EXAMPLE.right,
|
||||
FUNCTION_COMPARISON_EVIDENCE,
|
||||
])
|
||||
expect(session.recordEvidence(source).ok).toBe(true);
|
||||
const response = await client.callTool({
|
||||
name: "find_changed_behavior",
|
||||
arguments: input,
|
||||
});
|
||||
expect(parseMcpToolError(response).error.code).toBe(code);
|
||||
expect(providerCalls).toBe(0);
|
||||
} finally {
|
||||
await close(session, server, client);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
it("requires the original source closure even when the comparison reference exists", async () => {
|
||||
const { session, server, client } = await connected();
|
||||
try {
|
||||
expect(session.recordEvidence(FUNCTION_COMPARISON_EVIDENCE).ok).toBe(true);
|
||||
const response = await client.callTool({
|
||||
name: "find_changed_behavior",
|
||||
arguments: {
|
||||
comparisons: [
|
||||
{
|
||||
kind: "retained-evidence",
|
||||
evidence_id: FUNCTION_COMPARISON_EVIDENCE.evidence_id,
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
expect(parseMcpToolError(response).error).toMatchObject({
|
||||
code: "evidence_integrity_mismatch",
|
||||
details: { reason: "missing" },
|
||||
});
|
||||
} finally {
|
||||
await close(session, server, client);
|
||||
}
|
||||
});
|
||||
|
||||
it("preserves mixed inline and retained runtime/static comparison semantics", async () => {
|
||||
const { session, server, client } = await connected();
|
||||
try {
|
||||
for (const source of [
|
||||
FUNCTION_COMPARISON_EXAMPLE.left,
|
||||
FUNCTION_COMPARISON_EXAMPLE.right,
|
||||
FUNCTION_COMPARISON_EVIDENCE,
|
||||
PROCESS_CAPTURE_REFERENCE,
|
||||
PROCESS_CAPTURE_RECONSTRUCTION,
|
||||
PROCESS_COMPARISON_EVIDENCE,
|
||||
])
|
||||
expect(session.recordEvidence(source).ok).toBe(true);
|
||||
const complete = await client.callTool({
|
||||
name: "find_changed_behavior",
|
||||
arguments: {
|
||||
comparisons: [
|
||||
FUNCTION_COMPARISON_EVIDENCE,
|
||||
PROCESS_COMPARISON_EVIDENCE,
|
||||
],
|
||||
},
|
||||
});
|
||||
expect(complete.isError, JSON.stringify(complete)).not.toBe(true);
|
||||
const mixed = await client.callTool({
|
||||
name: "find_changed_behavior",
|
||||
arguments: {
|
||||
comparisons: [
|
||||
FUNCTION_COMPARISON_EVIDENCE,
|
||||
{
|
||||
kind: "retained-evidence",
|
||||
evidence_id: PROCESS_COMPARISON_EVIDENCE.evidence_id,
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
expect(mixed.isError, JSON.stringify(mixed)).not.toBe(true);
|
||||
expect(inlineEvidence(mixed.structuredContent)).toEqual(
|
||||
inlineEvidence(complete.structuredContent),
|
||||
);
|
||||
} finally {
|
||||
await close(session, server, client);
|
||||
}
|
||||
});
|
||||
Reference in new issue
Block a user