Files
rea/src/domain/managed/managedApplicationGraph.ts
T
a149d183ef feat(managed): select declaring types in inspect_managed_members
Select exact declaring types before managed signatures and CIL bodies are decoded, preserving caller choices in CLI/MCP Evidence. Keep unmatched names and selected coverage explicit; comparisons and graphs retain unknowns outside their observed scope. Reuse existing metadata ownership/accounting and advertise unique selections through the actual SDK Schema.

Validated with 229 focused tests across 21 files, 11 managed conformance controls, fullstatic/docs checks and real Linux CLI and fresh MCP inspection of two unchanged DLLs. All 118 selected CIL hashes matched original PE-mapped bytes; result/caller-argument parity and unmatched controls passed. Full artifact/native-boundary metadata is complete; whole-assembly members retain the unchanged 64 MiB resource constraint. No target code executes.

Refs #1878

Co-authored-by: skared <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Ysopwnable <[email protected]>
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Co-authored-by: Kaoru0822 <[email protected]>
2026-10-12 08:31:37 +08:00

349 lines
12 KiB
TypeScript

import { createHash } from "node:crypto";
import canonicalize from "canonicalize";
import { z } from "zod";
import { evidenceSchema, type Evidence } from "../evidence.js";
import {
createJavaScriptApplicationGraph,
javascriptApplicationGraphSchema,
} from "../javascript/javascriptApplicationGraph.js";
import type {
ApplicationEdge,
ApplicationNode,
} from "../javascript/javascriptApplicationGraphSchemas.js";
import {
managedArtifactInspectionSchema,
managedMemberInspectionSchema,
managedNativeBoundaryInspectionSchema,
type ManagedArtifactInspection,
type ManagedMemberInspection,
type ManagedNativeBoundaryInspection,
} from "./managedArtifact.js";
import type { JsonValue } from "../jsonValue.js";
import {
assessManagedGraphOmissions,
managedGraphEvidenceCoverage,
managedGraphResultCoverage,
} from "./managedApplicationGraphCoverage.js";
import {
addArtifactNode,
addArtifactIdentityNodes,
addBoundaryNodes,
addMemberNodes,
} from "./managedApplicationGraphNodes.js";
import { digestSchema } from "../digests.js";
import { prefixedDigestSchema } from "../digests.js";
import { parseManagedInspectionEvidence } from "./managedInspectionEvidence.js";
const evidenceIdSchema = prefixedDigestSchema("ev");
const textSchema = z.string().min(1);
/** Authenticated managed Evidence records projected into the application graph. */
export const projectManagedApplicationGraphInputSchema = z
.strictObject({
managed_artifact: evidenceSchema.optional(),
managed_members: evidenceSchema.optional(),
managed_native_boundaries: evidenceSchema.optional(),
})
.superRefine((input, context) => {
if (
input.managed_artifact === undefined &&
input.managed_members === undefined &&
input.managed_native_boundaries === undefined
)
context.addIssue({
code: "custom",
message:
"At least one managed artifact, member, or native-boundary Evidence record is required",
});
});
/** Managed-code projection result containing a validated application graph. */
export const managedApplicationGraphResultSchema = z.strictObject({
projection_id: prefixedDigestSchema("magp"),
root_artifact_sha256: digestSchema,
source_evidence: z.strictObject({
managed_artifact_evidence_id: evidenceIdSchema.nullable(),
managed_members_evidence_id: evidenceIdSchema.nullable(),
managed_native_boundaries_evidence_id: evidenceIdSchema.nullable(),
}),
summary: z.strictObject({
graph_nodes: z.number().int().min(0),
graph_edges: z.number().int().min(0),
assemblies: z.number().int().min(0),
modules: z.number().int().min(0),
types: z.number().int().min(0),
methods: z.number().int().min(0),
fields: z.number().int().min(0),
pinvoke_imports: z.number().int().min(0),
native_implementations: z.number().int().min(0),
}),
graph: javascriptApplicationGraphSchema,
coverage: z.strictObject({
status: z.enum(["complete-within-inputs", "partial"]),
}),
evidence_links: z.array(evidenceIdSchema).min(1).max(3),
limitations: z.array(textSchema),
});
export type ProjectManagedApplicationGraphInput = z.infer<
typeof projectManagedApplicationGraphInputSchema
>;
export type ManagedApplicationGraphResult = z.infer<
typeof managedApplicationGraphResultSchema
>;
export interface ParsedManagedGraphInput {
readonly artifact: {
readonly evidence: Evidence;
readonly result: ManagedArtifactInspection;
} | null;
readonly members: {
readonly evidence: Evidence;
readonly result: ManagedMemberInspection;
} | null;
readonly boundaries: {
readonly evidence: Evidence;
readonly result: ManagedNativeBoundaryInspection;
} | null;
}
export interface GraphBuildState {
readonly nodes: ApplicationNode[];
readonly edges: ApplicationEdge[];
readonly artifactSha256: string;
readonly artifactPath: string;
readonly evidenceLinks: string[];
readonly typeNodes: Map<string, ApplicationNode>;
readonly methodNodes: Map<string, ApplicationNode>;
readonly fieldNodes: Map<string, ApplicationNode>;
}
const sha256 = (value: JsonValue): string => {
const serialized = canonicalize(value);
if (serialized === undefined)
throw new TypeError("Managed application graph canonicalization failed");
return createHash("sha256").update(serialized).digest("hex");
};
/** Project authenticated managed Evidence records into the application graph. */
export const projectManagedApplicationGraph = (
input: ProjectManagedApplicationGraphInput,
): ManagedApplicationGraphResult => {
const parsed = parseManagedInputs(input);
const artifactIdentity = chooseArtifact(parsed);
assertSameArtifact(parsed, artifactIdentity.sha256);
assertConsistentModuleIdentity(parsed);
const evidenceLinks = [
parsed.artifact?.evidence.evidence_id,
parsed.members?.evidence.evidence_id,
parsed.boundaries?.evidence.evidence_id,
].filter((id): id is string => id !== undefined);
const state: GraphBuildState = {
nodes: [],
edges: [],
artifactSha256: artifactIdentity.sha256,
artifactPath: artifactIdentity.path,
evidenceLinks,
typeNodes: new Map(),
methodNodes: new Map(),
fieldNodes: new Map(),
};
const artifactNode = addArtifactNode(state);
addArtifactIdentityNodes(state, artifactNode, parsed);
addMemberNodes(state, artifactNode, parsed);
addBoundaryNodes(state, artifactNode, parsed);
const omissions = omittedCounts(parsed);
const limitations = projectionLimitations(parsed, omissions);
const graph = createJavaScriptApplicationGraph({
schema: "JavaScriptApplicationGraph",
root_node_ids: [artifactNode.node_id],
nodes: state.nodes,
edges: state.edges,
coverage: managedGraphEvidenceCoverage(omissions),
limitations,
});
const withoutId = {
root_artifact_sha256: state.artifactSha256,
source_evidence: {
managed_artifact_evidence_id:
parsed.artifact?.evidence.evidence_id ?? null,
managed_members_evidence_id: parsed.members?.evidence.evidence_id ?? null,
managed_native_boundaries_evidence_id:
parsed.boundaries?.evidence.evidence_id ?? null,
},
summary: {
graph_nodes: graph.nodes.length,
graph_edges: graph.edges.length,
assemblies: countNodes(graph.nodes, "managed-assembly"),
modules: countNodes(graph.nodes, "managed-module"),
types: countNodes(graph.nodes, "managed-type"),
methods: countNodes(graph.nodes, "managed-method"),
fields: countNodes(graph.nodes, "managed-field"),
pinvoke_imports: countNodes(graph.nodes, "managed-pinvoke-import"),
native_implementations: countNodes(
graph.nodes,
"managed-native-implementation",
),
},
graph,
coverage: managedGraphResultCoverage(omissions),
evidence_links: evidenceLinks,
limitations,
};
return managedApplicationGraphResultSchema.parse({
...withoutId,
projection_id: `magp_${sha256(withoutId)}`,
});
};
const countNodes = (
nodes: readonly ApplicationNode[],
kind: ApplicationNode["kind"],
): number => nodes.filter((node) => node.kind === kind).length;
const parseManagedInputs = (
input: ProjectManagedApplicationGraphInput,
): ParsedManagedGraphInput => ({
artifact:
input.managed_artifact === undefined
? null
: parseManagedInspectionEvidence(
input.managed_artifact,
"inspect_managed_artifact",
managedArtifactInspectionSchema,
),
members:
input.managed_members === undefined
? null
: parseManagedInspectionEvidence(
input.managed_members,
"inspect_managed_members",
managedMemberInspectionSchema,
),
boundaries:
input.managed_native_boundaries === undefined
? null
: parseManagedInspectionEvidence(
input.managed_native_boundaries,
"inspect_managed_native_boundaries",
managedNativeBoundaryInspectionSchema,
),
});
const chooseArtifact = (
parsed: ParsedManagedGraphInput,
): { readonly sha256: string; readonly path: string } => {
const result =
parsed.artifact?.result ??
parsed.members?.result ??
parsed.boundaries?.result;
if (result === undefined)
throw new TypeError("Managed graph projection requires managed Evidence");
return { sha256: result.artifact.sha256, path: result.artifact.path };
};
const assertSameArtifact = (
parsed: ParsedManagedGraphInput,
sha256Value: string,
): void => {
for (const result of [
parsed.artifact?.result,
parsed.members?.result,
parsed.boundaries?.result,
])
if (result !== undefined && result.artifact.sha256 !== sha256Value)
throw new TypeError(
"Managed graph projection inputs must describe the same artifact SHA-256",
);
};
/** Reject contradictory known module identities while leaving null fields unknown. */
const assertConsistentModuleIdentity = (
parsed: ParsedManagedGraphInput,
): void => {
const mvidClaims = [
parsed.artifact?.result.module?.mvid,
parsed.members?.result.module?.mvid,
parsed.members?.result.identity_scope.requires_mvid,
parsed.boundaries?.result.module?.mvid,
parsed.boundaries?.result.identity_scope.requires_mvid,
]
.filter((mvid): mvid is string => mvid !== undefined && mvid !== null)
.map((mvid) => mvid.toLowerCase());
if (new Set(mvidClaims).size > 1)
throw new TypeError(
"Managed graph inputs report conflicting known module MVIDs or token scopes",
);
const modules = [
parsed.artifact?.result.module,
parsed.members?.result.module,
parsed.boundaries?.result.module,
].filter(
(module): module is NonNullable<ManagedArtifactInspection["module"]> =>
module !== undefined && module !== null,
);
for (const field of ["name", "generation", "token", "row_offset"] as const) {
const values = new Set(modules.map((module) => module[field]));
if (values.size > 1)
throw new TypeError(
`Managed graph inputs report conflicting module ${field} values`,
);
}
for (const field of ["enc_id", "enc_base_id"] as const) {
const values = new Set(
modules
.map((module) => module[field]?.toLowerCase())
.filter((value): value is string => value !== undefined),
);
if (values.size > 1)
throw new TypeError(
`Managed graph inputs report conflicting module ${field} values`,
);
}
};
const omittedCounts = (parsed: ParsedManagedGraphInput) =>
assessManagedGraphOmissions({
artifact: parsed.artifact?.result ?? null,
members: parsed.members?.result ?? null,
boundaries: parsed.boundaries?.result ?? null,
});
const projectionLimitations = (
parsed: ParsedManagedGraphInput,
omissions: ReturnType<typeof omittedCounts>,
): string[] => [
"Managed graph projection preserves static metadata observations only; it does not execute managed code, load assemblies, or map managed tokens to native addresses.",
...(parsed.members === null
? [
"Managed member Evidence was not supplied; type, method, and field nodes are absent.",
]
: []),
...(parsed.artifact === null
? [
"Managed artifact Evidence was not supplied; assembly identity observations are absent.",
]
: []),
...(parsed.boundaries === null
? [
"Managed native-boundary Evidence was not supplied; P/Invoke and native-implementation nodes are absent.",
]
: []),
...(omissions.partialInput
? ["At least one supplied managed Evidence record has partial coverage."]
: []),
...(omissions.selectedTypes === null
? []
: [
`Managed member Evidence selected only these declaring types: ${omissions.selectedTypes.join(", ")}; type, method, and field nodes of other types are omitted, not absent.`,
]),
...(omissions.unscopedTokenRelationships
? [
"Managed boundary tokens were not linked to member nodes because matching module MVID scope was not established.",
]
: []),
];