mirror of
https://github.com/morluto/rea.git
synced 2026-11-12 09:52:27 +00:00
* fix(managed): pair members by declared identity before name-free remaps
The first matching round keyed methods on signature and normalized CIL,
and fields on signature, without the declared type or name. Identical
members of different types fell into one ambiguous group, so a file
compared with itself reported unknowns, and a lone match across types
was reported as an unchanged exact pair while the member's own
counterpart went unpaired.
Pair by declared type, name, and exact CIL/signature first, then by
declared type, name, and raw signature. The name-free CIL/signature,
field-signature, and structural rounds now run last, as remaps, and
skip a pair across declared identities while either member's own type
and name remain unpaired on the other side.
Nested types that share a bare name still share one declared identity,
so their identical members stay ambiguous in a self-comparison.
* fix(managed): keep members with kept identities out of remap rounds
The cross-identity check applied only to one-to-one groups. A member
whose own declared type and name were still unpaired on the other side
could fall into a larger remap group and be reported ambiguous, and a
blocked candidate could make an otherwise unique remap ambiguous.
A remap round now drops such members before grouping, along with
members whose declaring type is unresolved. Same-identity structural
pairs get their own round before the remaps. Each member's declared
identity is computed once.
Ambiguity limitations now name the evidence a group shares, which
stays true whether the round keyed on names or not.
* fix(managed): keep names in unresolved identities and hold ambiguous ones
A member of an unresolved declaring type had no declared identity, so
the declared-identity rounds keyed it by CIL or signature alone. Two
unresolved members with different names paired as an exact match, and
a file compared with itself reported unresolved members ambiguous.
The declared identity now always includes the name, with a null type
for an unresolved one. Remap rounds still leave such members out.
A remap round now also leaves out a member whose own identity sits in
an ambiguous group on the other side, not only one left one-sided.
The undecoded-counterpart check uses the same declared identity.
* fix(managed): keep remap groups with unresolved owners ambiguous
Remap rounds dropped members of an unresolved declaring type before
grouping. Such a member can be another member's own counterpart, so a
resolved member could remap to a copy in another type while its
unresolved counterpart was reported added.
Unresolved members now join remap grouping, and a group that contains
one stays ambiguous instead of pairing.
The undecoded-counterpart set reuses each member's computed identity.
* fix(managed): keep ownerless members out of declared rounds when shared
A member whose declaring type did not resolve has a declared identity
of a null type and its name. The declared rounds paired two such
same-named members even when the other side also held a resolved
member with the same CIL and signature, which could be the owner. The
old name-free first round reported that group as ambiguous.
An ownerless member now enters the declared rounds only when no other
member on either side shares its exact CIL/signature key, and never
the declared structural round. Otherwise it reaches the remap rounds,
which keep any group with an unresolved member ambiguous.
* fix(managed): keep competitors of an ownerless member out of the declared round
When an ownerless member shares its exact CIL/signature key with another
member on the same side, its owner may be that member. The resolved
members holding the key now stay out of the declared round too, so they
cannot pair and consume the candidate the ownerless member may own, and
the remap round keeps the whole group ambiguous as main does. A lone
ownerless member still pairs with its lone counterpart.
* test(managed): split the ownerless comparison tests under the function length limit
The ownerless describe block reached 181 lines, past oxlint's
max-lines-per-function limit of 150. Move the structural-competitor
case into its own block.
* fix(managed): contest signatures and remap only open candidates
* fix(managed): retain blocked raw-signature choices as unknown
* fix(managed): keep member names in signature contention
* fix(managed): scope residual signature contention by name once
ownerlessContested applies competitionIdentityKey itself, but
unresolvedSignatureAmbiguities passed it keys that were already
name-scoped, so the contested set never matched the groups and the
same-signature, different-body case reported additions and removals
instead of one unknown group. Pass the raw signature key.
* fix(managed): constrain matchObserved to declared members
matchObserved passes its items to matchByKeys, which needs Declared, so
its looser { declaring_type } bound failed typecheck (TS2344, TS2322).
* fix(managed): retain held ownership contention for residual matches
* fix(test): bind workspace fixture in Hopper launch cases
* fix(ci): prepare worker tests and preserve Hopper cleanup ordering
---------
Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
173 lines
7.4 KiB
TypeScript
173 lines
7.4 KiB
TypeScript
import { z } from "zod";
|
|
import { evidenceSchema } from "../../domain/evidence.js";
|
|
import { managedNativeVerificationInputSchema } from "../../domain/managed/managedNativeVerificationSchemas.js";
|
|
|
|
import { managedReconstructionImportInputSchema } from "../../domain/managed/managedReconstruction.js";
|
|
import type { ToolContract } from "../toolContractTypes.js";
|
|
import { managedWorkflowOutputSchemas } from "../toolOutputSchemaGroups.js";
|
|
import {
|
|
MANAGED_MEMBER_COMPARISON_EXAMPLE,
|
|
MANAGED_NATIVE_VERIFICATION_EXAMPLE,
|
|
MANAGED_APPLICATION_GRAPH_EXAMPLE,
|
|
MANAGED_RECONSTRUCTION_IMPORT_EXAMPLE,
|
|
} from "./managedWorkflowExamples.js";
|
|
import { toolContractMetadata } from "../toolEffects.js";
|
|
import { requireOutputSchema } from "../toolOutputSchemaPrimitives.js";
|
|
|
|
/** Inputs for comparing two managed observations carried inline. */
|
|
export const compareManagedMembersReferenceInputSchema = z
|
|
.strictObject({
|
|
left: evidenceSchema,
|
|
right: evidenceSchema,
|
|
})
|
|
.superRefine((input, context) => {
|
|
if (input.left.evidence_id === input.right.evidence_id)
|
|
context.addIssue({
|
|
code: "custom",
|
|
path: ["right"],
|
|
message: "Managed member Evidence must be distinct",
|
|
});
|
|
});
|
|
|
|
/** Inline Evidence input for importing reconstruction. */
|
|
export const managedReconstructionReferenceInputSchema =
|
|
managedReconstructionImportInputSchema.extend({
|
|
static_members: evidenceSchema,
|
|
});
|
|
|
|
const managedApplicationGraphReferenceFacts = {} as const;
|
|
|
|
const managedApplicationGraphReferenceShape = z.strictObject({
|
|
...managedApplicationGraphReferenceFacts,
|
|
managed_artifact: evidenceSchema.optional(),
|
|
managed_members: evidenceSchema.optional(),
|
|
managed_native_boundaries: evidenceSchema.optional(),
|
|
});
|
|
|
|
/** Inline Evidence sources for managed application graph projection. */
|
|
export const managedApplicationGraphReferenceInputSchema = z
|
|
.union([
|
|
managedApplicationGraphReferenceShape.extend({
|
|
managed_artifact: evidenceSchema,
|
|
}),
|
|
managedApplicationGraphReferenceShape.extend({
|
|
managed_members: evidenceSchema,
|
|
}),
|
|
managedApplicationGraphReferenceShape.extend({
|
|
managed_native_boundaries: evidenceSchema,
|
|
}),
|
|
])
|
|
.superRefine((input, context) => {
|
|
const ids = [
|
|
input.managed_artifact?.evidence_id,
|
|
input.managed_members?.evidence_id,
|
|
input.managed_native_boundaries?.evidence_id,
|
|
].filter((id): id is string => id !== undefined);
|
|
if (ids.length === 0)
|
|
context.addIssue({
|
|
code: "custom",
|
|
message: "Provide at least one managed Evidence source",
|
|
});
|
|
if (new Set(ids).size !== ids.length)
|
|
context.addIssue({
|
|
code: "custom",
|
|
message: "Managed application graph Evidence IDs must be unique",
|
|
});
|
|
});
|
|
|
|
const comparisonOutputSchema = requireOutputSchema(
|
|
managedWorkflowOutputSchemas,
|
|
"compare_managed_members",
|
|
);
|
|
const reconstructionOutputSchema = requireOutputSchema(
|
|
managedWorkflowOutputSchemas,
|
|
"import_managed_reconstruction",
|
|
);
|
|
const nativeVerificationOutputSchema = requireOutputSchema(
|
|
managedWorkflowOutputSchemas,
|
|
"verify_managed_native_boundaries",
|
|
);
|
|
const managedApplicationGraphOutputSchema = requireOutputSchema(
|
|
managedWorkflowOutputSchemas,
|
|
"project_managed_application_graph",
|
|
);
|
|
|
|
/** Provider-neutral managed-code workflow contracts. */
|
|
export const MANAGED_WORKFLOW_TOOL_CONTRACTS = [
|
|
{
|
|
name: "compare_managed_members",
|
|
...toolContractMetadata("compare_managed_members"),
|
|
description:
|
|
"Compare two authenticated inspect_managed_members Evidence records using unique-only tiers: exact declared type, name, and CIL/signature; exact declared type, name, and raw signature (decoded or not); exact declared type, name, and decoded structural method shape; then exact CIL/signature or decoded structural method shape alone, which never takes a member whose own declared type and name remain unpaired on the other side or whose declaring type is unresolved. Names alone are never a matching basis; a member whose signature was not decoded, or whose same-named counterpart's was not, stays unknown rather than added or removed. Metadata tokens remain build-local coordinates bound to each artifact SHA-256 and MVID, and the tuple digest does not itself remap them.",
|
|
kind: "application",
|
|
inputSchema: compareManagedMembersReferenceInputSchema,
|
|
outputSchema: comparisonOutputSchema,
|
|
examples: [
|
|
{
|
|
title: "Compare two managed member observations",
|
|
input: {
|
|
left: MANAGED_MEMBER_COMPARISON_EXAMPLE.left,
|
|
right: MANAGED_MEMBER_COMPARISON_EXAMPLE.right,
|
|
},
|
|
},
|
|
],
|
|
},
|
|
{
|
|
name: "verify_managed_native_boundaries",
|
|
...toolContractMetadata("verify_managed_native_boundaries"),
|
|
description:
|
|
"Verify managed P/Invoke/native-boundary declarations against authenticated native export or function Evidence without executing managed code or translating managed metadata tokens into native addresses. The workflow preserves declaration-only, verified, inferred, contradicted, and unresolved states.",
|
|
kind: "application",
|
|
inputSchema: managedNativeVerificationInputSchema,
|
|
outputSchema: nativeVerificationOutputSchema,
|
|
examples: [
|
|
{
|
|
title: "Verify a managed P/Invoke declaration against native Evidence",
|
|
input: {
|
|
managed_boundaries:
|
|
MANAGED_NATIVE_VERIFICATION_EXAMPLE.managed_boundaries,
|
|
native_observations:
|
|
MANAGED_NATIVE_VERIFICATION_EXAMPLE.native_observations,
|
|
},
|
|
},
|
|
],
|
|
},
|
|
{
|
|
name: "import_managed_reconstruction",
|
|
...toolContractMetadata("import_managed_reconstruction"),
|
|
description:
|
|
"Import decompiler-produced managed reconstruction against authenticated inspect_managed_members Evidence. The workflow locks each method to artifact SHA-256, MVID, metadata token, signature hash, and the limited decoded-IL tuple hash, records the decompiler identity and options, and marks C# or pseudocode as analyst inference rather than canonical byte observation.",
|
|
kind: "application",
|
|
inputSchema: managedReconstructionReferenceInputSchema,
|
|
outputSchema: reconstructionOutputSchema,
|
|
examples: [
|
|
{
|
|
title: "Import a decompiler reconstruction for one managed method",
|
|
input: {
|
|
static_members: MANAGED_RECONSTRUCTION_IMPORT_EXAMPLE.static_members,
|
|
decompiler: MANAGED_RECONSTRUCTION_IMPORT_EXAMPLE.decompiler,
|
|
methods: MANAGED_RECONSTRUCTION_IMPORT_EXAMPLE.methods,
|
|
notes: MANAGED_RECONSTRUCTION_IMPORT_EXAMPLE.notes,
|
|
},
|
|
},
|
|
],
|
|
},
|
|
{
|
|
name: "project_managed_application_graph",
|
|
...toolContractMetadata("project_managed_application_graph"),
|
|
description:
|
|
"Project authenticated managed artifact, member, and native-boundary Evidence into the provider-neutral application graph without executing managed code, loading assemblies, or translating managed metadata tokens to native addresses. The graph preserves managed static observations as managed-specific nodes linked to source Evidence.",
|
|
kind: "application",
|
|
inputSchema: managedApplicationGraphReferenceInputSchema,
|
|
outputSchema: managedApplicationGraphOutputSchema,
|
|
examples: [
|
|
{
|
|
title: "Project static managed observations into an application graph",
|
|
input: {
|
|
managed_members: MANAGED_APPLICATION_GRAPH_EXAMPLE.managed_members,
|
|
},
|
|
},
|
|
],
|
|
},
|
|
] as const satisfies readonly ToolContract[];
|