mirror of
https://github.com/morluto/rea.git
synced 2026-10-12 02:19:24 +00:00
fix(managed): enforce module identity before joining token facts
Validate declared artifact and MVID scopes at managed Evidence admission, and reject conflicting known module declarations across graph inputs. Join boundary tokens to members only under matching known MVID scopes; preserve facts and report partial coverage when scope is unknown. Prefer an observed MVID-bearing declaration with its supplying Evidence. Normalize GUID casing for module node identity while preserving original strings in observations. Validation: 285 tests across 34 focused suites passed, followed by the final 12-test managed graph suite and managed CLI/MCP regressions. check:fast and architecture guards passed; no real native providers were run.
This commit is contained in:
@@ -134,6 +134,7 @@ export const projectManagedApplicationGraph = (
|
||||
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,
|
||||
@@ -258,6 +259,52 @@ const assertSameArtifact = (
|
||||
);
|
||||
};
|
||||
|
||||
/** 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,
|
||||
@@ -288,4 +335,9 @@ const projectionLimitations = (
|
||||
...(omissions.partialInput
|
||||
? ["At least one supplied managed Evidence record has partial coverage."]
|
||||
: []),
|
||||
...(omissions.unscopedTokenRelationships
|
||||
? [
|
||||
"Managed boundary tokens were not linked to member nodes because matching module MVID scope was not established.",
|
||||
]
|
||||
: []),
|
||||
];
|
||||
|
||||
@@ -4,6 +4,7 @@ import type {
|
||||
ManagedMemberInspection,
|
||||
ManagedNativeBoundaryInspection,
|
||||
} from "./managedArtifact.js";
|
||||
import { managedTokenScopesMatch } from "./managedInspectionEvidence.js";
|
||||
|
||||
type ManagedCoverageState = "complete" | "partial" | "unavailable";
|
||||
|
||||
@@ -17,6 +18,7 @@ export interface ManagedGraphCoverageSources {
|
||||
/** Whether any supplied source has incomplete coverage. */
|
||||
export interface ManagedGraphProjectionOmissions {
|
||||
readonly partialInput: boolean;
|
||||
readonly unscopedTokenRelationships: boolean;
|
||||
}
|
||||
|
||||
const completeCoverage = (): ApplicationGraphEvidence["coverage"] => ({
|
||||
@@ -43,6 +45,17 @@ export const managedSourceCoverage = (
|
||||
export const assessManagedGraphOmissions = (
|
||||
sources: ManagedGraphCoverageSources,
|
||||
): ManagedGraphProjectionOmissions => ({
|
||||
unscopedTokenRelationships:
|
||||
sources.members !== null &&
|
||||
sources.boundaries !== null &&
|
||||
(sources.boundaries.pinvoke_imports.some(
|
||||
({ member_token }) => member_token !== null,
|
||||
) ||
|
||||
sources.boundaries.native_implementations.length > 0) &&
|
||||
!managedTokenScopesMatch(
|
||||
sources.members.identity_scope.requires_mvid,
|
||||
sources.boundaries.identity_scope.requires_mvid,
|
||||
),
|
||||
partialInput: [
|
||||
sources.members?.coverage.state,
|
||||
sources.boundaries?.coverage.state,
|
||||
@@ -54,7 +67,7 @@ export const assessManagedGraphOmissions = (
|
||||
export const managedGraphEvidenceCoverage = (
|
||||
omissions: ManagedGraphProjectionOmissions,
|
||||
): ApplicationGraphEvidence["coverage"] =>
|
||||
omissions.partialInput
|
||||
omissions.partialInput || omissions.unscopedTokenRelationships
|
||||
? {
|
||||
status: "partial",
|
||||
truncated: false,
|
||||
@@ -67,7 +80,8 @@ export const managedGraphEvidenceCoverage = (
|
||||
export const managedGraphResultCoverage = (
|
||||
omissions: ManagedGraphProjectionOmissions,
|
||||
) => ({
|
||||
status: omissions.partialInput
|
||||
? ("partial" as const)
|
||||
: ("complete-within-inputs" as const),
|
||||
status:
|
||||
omissions.partialInput || omissions.unscopedTokenRelationships
|
||||
? ("partial" as const)
|
||||
: ("complete-within-inputs" as const),
|
||||
});
|
||||
|
||||
@@ -7,6 +7,7 @@ import type { ApplicationGraphEvidence } from "../javascript/javascriptApplicati
|
||||
import type { ApplicationNode } from "../javascript/javascriptApplicationGraphSchemas.js";
|
||||
import { managedSourceCoverage } from "./managedApplicationGraphCoverage.js";
|
||||
import type { Evidence } from "../evidence.js";
|
||||
import { managedTokenScopesMatch } from "./managedInspectionEvidence.js";
|
||||
import {
|
||||
type ManagedArtifactInspection,
|
||||
type ManagedMemberInspection,
|
||||
@@ -106,7 +107,7 @@ export const addArtifactIdentityNodes = (
|
||||
identity: artifactLocalIdentity(
|
||||
state.artifactSha256,
|
||||
"managed-module",
|
||||
module.mvid ?? module.token,
|
||||
module.mvid?.toLowerCase() ?? module.token,
|
||||
),
|
||||
observations: [
|
||||
{
|
||||
@@ -138,25 +139,32 @@ interface ManagedModuleSource {
|
||||
const selectModuleSource = (
|
||||
parsed: ParsedManagedGraphInput,
|
||||
): ManagedModuleSource | null => {
|
||||
if (parsed.artifact?.result.module != null)
|
||||
return {
|
||||
module: parsed.artifact.result.module,
|
||||
evidence: parsed.artifact.evidence,
|
||||
coverageState: parsed.artifact.result.coverage.state,
|
||||
};
|
||||
if (parsed.members?.result.module != null)
|
||||
return {
|
||||
module: parsed.members.result.module,
|
||||
evidence: parsed.members.evidence,
|
||||
coverageState: parsed.members.result.coverage.state,
|
||||
};
|
||||
if (parsed.boundaries?.result.module != null)
|
||||
return {
|
||||
module: parsed.boundaries.result.module,
|
||||
evidence: parsed.boundaries.evidence,
|
||||
coverageState: parsed.boundaries.result.coverage.state,
|
||||
};
|
||||
return null;
|
||||
const sources = [
|
||||
parsed.artifact?.result.module == null
|
||||
? null
|
||||
: {
|
||||
module: parsed.artifact.result.module,
|
||||
evidence: parsed.artifact.evidence,
|
||||
coverageState: parsed.artifact.result.coverage.state,
|
||||
},
|
||||
parsed.members?.result.module == null
|
||||
? null
|
||||
: {
|
||||
module: parsed.members.result.module,
|
||||
evidence: parsed.members.evidence,
|
||||
coverageState: parsed.members.result.coverage.state,
|
||||
},
|
||||
parsed.boundaries?.result.module == null
|
||||
? null
|
||||
: {
|
||||
module: parsed.boundaries.result.module,
|
||||
evidence: parsed.boundaries.evidence,
|
||||
coverageState: parsed.boundaries.result.coverage.state,
|
||||
},
|
||||
].filter((source): source is ManagedModuleSource => source !== null);
|
||||
return (
|
||||
sources.find(({ module }) => module.mvid !== null) ?? sources[0] ?? null
|
||||
);
|
||||
};
|
||||
|
||||
/** Add managed type, method, and field nodes from member evidence. */
|
||||
@@ -332,11 +340,17 @@ export const addBoundaryNodes = (
|
||||
boundaryInput.evidence,
|
||||
boundaries.coverage.state,
|
||||
);
|
||||
const canJoinMemberTokens =
|
||||
parsed.members !== null &&
|
||||
managedTokenScopesMatch(
|
||||
parsed.members.result.identity_scope.requires_mvid,
|
||||
boundaries.identity_scope.requires_mvid,
|
||||
);
|
||||
for (const pinvoke of pinvokes) {
|
||||
const node = pinvokeNode(state, pinvoke, sourceEvidence);
|
||||
state.nodes.push(node);
|
||||
const method =
|
||||
pinvoke.member_token === null
|
||||
pinvoke.member_token === null || !canJoinMemberTokens
|
||||
? undefined
|
||||
: state.methodNodes.get(pinvoke.member_token);
|
||||
const memberEvidence =
|
||||
@@ -370,7 +384,10 @@ export const addBoundaryNodes = (
|
||||
sourceEvidence,
|
||||
);
|
||||
state.nodes.push(node);
|
||||
const owner = state.methodNodes.get(implementation.token) ?? artifactNode;
|
||||
const owner =
|
||||
(canJoinMemberTokens
|
||||
? state.methodNodes.get(implementation.token)
|
||||
: undefined) ?? artifactNode;
|
||||
addContainsEdge(state, owner, node, {
|
||||
kind: "declares-managed-native-implementation",
|
||||
coverage: implementationCoverage,
|
||||
|
||||
@@ -1,12 +1,28 @@
|
||||
import { parseEvidence, type Evidence } from "../evidence.js";
|
||||
import type { z } from "zod";
|
||||
|
||||
/** Build-local metadata tokens join only under a known, matching module MVID. */
|
||||
export const managedTokenScopesMatch = (
|
||||
leftMvid: string | null,
|
||||
rightMvid: string | null,
|
||||
): boolean =>
|
||||
leftMvid !== null &&
|
||||
rightMvid !== null &&
|
||||
leftMvid.toLowerCase() === rightMvid.toLowerCase();
|
||||
|
||||
/**
|
||||
* Parse managed inspection Evidence and bind its artifact to any available subject.
|
||||
* A null subject remains valid and means there is no digest to cross-check.
|
||||
*/
|
||||
export const parseManagedInspectionEvidence = <
|
||||
Result extends { readonly artifact: { readonly sha256: string } },
|
||||
Result extends {
|
||||
readonly artifact: { readonly sha256: string };
|
||||
readonly identity_scope?: {
|
||||
readonly requires_artifact_sha256: string;
|
||||
readonly requires_mvid: string | null;
|
||||
};
|
||||
readonly module?: { readonly mvid: string | null } | null;
|
||||
},
|
||||
>(
|
||||
rawEvidence: unknown,
|
||||
operation: string,
|
||||
@@ -22,5 +38,20 @@ export const parseManagedInspectionEvidence = <
|
||||
throw new TypeError(
|
||||
`Managed Evidence ${operation} (${evidence.evidence_id}) subject SHA-256 ${subjectSha256} does not match normalized artifact SHA-256 ${artifactSha256}`,
|
||||
);
|
||||
const scope = result.identity_scope;
|
||||
if (scope !== undefined && scope.requires_artifact_sha256 !== artifactSha256)
|
||||
throw new TypeError(
|
||||
`Managed Evidence ${operation} (${evidence.evidence_id}) token scope SHA-256 ${scope.requires_artifact_sha256} does not match normalized artifact SHA-256 ${artifactSha256}`,
|
||||
);
|
||||
if (
|
||||
scope?.requires_mvid !== undefined &&
|
||||
scope.requires_mvid !== null &&
|
||||
result.module?.mvid !== undefined &&
|
||||
result.module.mvid !== null &&
|
||||
scope.requires_mvid.toLowerCase() !== result.module.mvid.toLowerCase()
|
||||
)
|
||||
throw new TypeError(
|
||||
`Managed Evidence ${operation} (${evidence.evidence_id}) token scope MVID ${scope.requires_mvid} does not match module MVID ${result.module.mvid}`,
|
||||
);
|
||||
return { evidence, result };
|
||||
};
|
||||
|
||||
@@ -8,6 +8,10 @@ import { MANAGED_STATIC_PROVIDER } from "../../../src/application/InvestigationP
|
||||
import { createEvidence, parseEvidence } from "../../../src/domain/evidence.js";
|
||||
import { parseJavaScriptApplicationGraph } from "../../../src/domain/javascript/javascriptApplicationGraph.js";
|
||||
import { managedApplicationGraphResultSchema } from "../../../src/domain/managed/managedApplicationGraph.js";
|
||||
import {
|
||||
managedMemberInspectionSchema,
|
||||
managedNativeBoundaryInspectionSchema,
|
||||
} from "../../../src/domain/managed/managedArtifact.js";
|
||||
import { inspectManagedArtifactBytes } from "../../../src/dotnet/ManagedArtifactInspector.js";
|
||||
import { inspectManagedMembersBytes } from "../../../src/dotnet/ManagedMemberInspector.js";
|
||||
import { inspectManagedNativeBoundariesBytes } from "../../../src/dotnet/ManagedNativeBoundaryInspector.js";
|
||||
@@ -299,6 +303,282 @@ describe("managed application graph identity facts", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("normalizes MVID casing for node identity while preserving observed values", () => {
|
||||
const { memberEvidence, boundaryEvidence } = createManagedInteropEvidence();
|
||||
const members = managedMemberInspectionSchema.parse(
|
||||
parseEvidence(memberEvidence).normalized_result,
|
||||
);
|
||||
if (members.module === null)
|
||||
throw new Error("Expected fixture module identity");
|
||||
const uppercaseMvid = members.module.mvid?.toUpperCase();
|
||||
if (uppercaseMvid === undefined || uppercaseMvid === null)
|
||||
throw new Error("Expected fixture module MVID");
|
||||
const uppercaseMembers = createEvidence(undefined, MANAGED_STATIC_PROVIDER, {
|
||||
operation: "inspect_managed_members",
|
||||
parameters: {},
|
||||
result: {
|
||||
...members,
|
||||
module: {
|
||||
...members.module,
|
||||
mvid: uppercaseMvid,
|
||||
enc_id: null,
|
||||
enc_base_id: null,
|
||||
},
|
||||
identity_scope: {
|
||||
...members.identity_scope,
|
||||
requires_mvid: uppercaseMvid,
|
||||
},
|
||||
},
|
||||
rawResult: null,
|
||||
limitations: members.limitations,
|
||||
});
|
||||
const baseline = projectManagedApplicationGraphEvidence({
|
||||
managed_members: memberEvidence,
|
||||
managed_native_boundaries: boundaryEvidence,
|
||||
});
|
||||
const uppercase = projectManagedApplicationGraphEvidence({
|
||||
managed_members: uppercaseMembers,
|
||||
managed_native_boundaries: boundaryEvidence,
|
||||
});
|
||||
if (!baseline.ok) throw baseline.error;
|
||||
if (!uppercase.ok) throw uppercase.error;
|
||||
const baselineResult = managedApplicationGraphResultSchema.parse(
|
||||
parseEvidence(baseline.value).normalized_result,
|
||||
);
|
||||
const uppercaseResult = managedApplicationGraphResultSchema.parse(
|
||||
parseEvidence(uppercase.value).normalized_result,
|
||||
);
|
||||
const baselineModule = parseJavaScriptApplicationGraph(
|
||||
baselineResult.graph,
|
||||
).nodes.find(({ kind }) => kind === "managed-module");
|
||||
const uppercaseGraph = parseJavaScriptApplicationGraph(uppercaseResult.graph);
|
||||
const uppercaseModule = uppercaseGraph.nodes.find(
|
||||
({ kind }) => kind === "managed-module",
|
||||
);
|
||||
expect(uppercaseGraph.edges).toContainEqual(
|
||||
expect.objectContaining({
|
||||
relation: "imports",
|
||||
source_node_id: uppercaseGraph.nodes.find(
|
||||
({ kind }) => kind === "managed-method",
|
||||
)?.node_id,
|
||||
}),
|
||||
);
|
||||
expect(uppercaseModule?.node_id).toBe(baselineModule?.node_id);
|
||||
expect(uppercaseModule?.observations[0]?.properties.mvid).toBe(uppercaseMvid);
|
||||
});
|
||||
|
||||
describe("managed application graph module identity consistency", () => {
|
||||
it("rejects contradictory known module MVIDs across same-artifact inputs", () => {
|
||||
const { artifactEvidence, memberEvidence, boundaryEvidence } =
|
||||
createManagedInteropEvidence();
|
||||
const boundaries = managedNativeBoundaryInspectionSchema.parse(
|
||||
parseEvidence(boundaryEvidence).normalized_result,
|
||||
);
|
||||
const conflictingMvid = "ffffffff-ffff-ffff-ffff-ffffffffffff";
|
||||
const conflictingBoundaries = createEvidence(
|
||||
undefined,
|
||||
MANAGED_STATIC_PROVIDER,
|
||||
{
|
||||
operation: "inspect_managed_native_boundaries",
|
||||
parameters: {},
|
||||
result: {
|
||||
...boundaries,
|
||||
module:
|
||||
boundaries.module === null
|
||||
? null
|
||||
: { ...boundaries.module, mvid: conflictingMvid },
|
||||
identity_scope: {
|
||||
...boundaries.identity_scope,
|
||||
requires_mvid: conflictingMvid,
|
||||
},
|
||||
},
|
||||
rawResult: null,
|
||||
limitations: boundaries.limitations,
|
||||
},
|
||||
);
|
||||
|
||||
const projected = projectManagedApplicationGraphEvidence({
|
||||
managed_artifact: artifactEvidence,
|
||||
managed_members: memberEvidence,
|
||||
managed_native_boundaries: conflictingBoundaries,
|
||||
});
|
||||
expect(projected.ok).toBe(false);
|
||||
if (projected.ok) throw new Error("conflicting MVIDs were accepted");
|
||||
expect(JSON.stringify(projected.error)).toContain(
|
||||
"conflicting known module MVIDs or token scopes",
|
||||
);
|
||||
});
|
||||
|
||||
it("rejects contradictory known module declaration fields", () => {
|
||||
const { artifactEvidence, memberEvidence } = createManagedInteropEvidence();
|
||||
const members = managedMemberInspectionSchema.parse(
|
||||
parseEvidence(memberEvidence).normalized_result,
|
||||
);
|
||||
if (members.module === null)
|
||||
throw new Error("Expected fixture module identity");
|
||||
const conflictingMembers = createEvidence(
|
||||
undefined,
|
||||
MANAGED_STATIC_PROVIDER,
|
||||
{
|
||||
operation: "inspect_managed_members",
|
||||
parameters: {},
|
||||
result: {
|
||||
...members,
|
||||
module: {
|
||||
...members.module,
|
||||
generation: members.module.generation + 1,
|
||||
},
|
||||
},
|
||||
rawResult: null,
|
||||
limitations: members.limitations,
|
||||
},
|
||||
);
|
||||
|
||||
const projected = projectManagedApplicationGraphEvidence({
|
||||
managed_artifact: artifactEvidence,
|
||||
managed_members: conflictingMembers,
|
||||
});
|
||||
expect(projected.ok).toBe(false);
|
||||
if (projected.ok)
|
||||
throw new Error("conflicting module declaration was accepted");
|
||||
expect(JSON.stringify(projected.error)).toContain(
|
||||
"conflicting module generation values",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("managed application graph token scope", () => {
|
||||
it("checks declared token artifact and MVID scopes without a subject digest", () => {
|
||||
const { memberEvidence } = createManagedInteropEvidence();
|
||||
const members = managedMemberInspectionSchema.parse(
|
||||
parseEvidence(memberEvidence).normalized_result,
|
||||
);
|
||||
const wrongMvidValue = "ffffffff-ffff-ffff-ffff-ffffffffffff";
|
||||
const wrongMvidEvidence = createEvidence(
|
||||
undefined,
|
||||
MANAGED_STATIC_PROVIDER,
|
||||
{
|
||||
operation: "inspect_managed_members",
|
||||
parameters: {},
|
||||
result: {
|
||||
...members,
|
||||
identity_scope: {
|
||||
...members.identity_scope,
|
||||
requires_mvid: wrongMvidValue,
|
||||
},
|
||||
},
|
||||
rawResult: null,
|
||||
limitations: members.limitations,
|
||||
},
|
||||
);
|
||||
const wrongArtifactEvidence = createEvidence(
|
||||
undefined,
|
||||
MANAGED_STATIC_PROVIDER,
|
||||
{
|
||||
operation: "inspect_managed_members",
|
||||
parameters: {},
|
||||
result: {
|
||||
...members,
|
||||
identity_scope: {
|
||||
...members.identity_scope,
|
||||
requires_artifact_sha256: "b".repeat(64),
|
||||
},
|
||||
},
|
||||
rawResult: null,
|
||||
limitations: members.limitations,
|
||||
},
|
||||
);
|
||||
|
||||
const wrongMvidResult = projectManagedApplicationGraphEvidence({
|
||||
managed_members: wrongMvidEvidence,
|
||||
});
|
||||
expect(wrongMvidResult.ok).toBe(false);
|
||||
if (wrongMvidResult.ok)
|
||||
throw new Error("mismatched token MVID was accepted");
|
||||
expect(JSON.stringify(wrongMvidResult.error)).toContain("token scope MVID");
|
||||
|
||||
const wrongArtifact = projectManagedApplicationGraphEvidence({
|
||||
managed_members: wrongArtifactEvidence,
|
||||
});
|
||||
expect(wrongArtifact.ok).toBe(false);
|
||||
if (wrongArtifact.ok)
|
||||
throw new Error("mismatched token artifact digest was accepted");
|
||||
expect(JSON.stringify(wrongArtifact.error)).toContain(
|
||||
"token scope SHA-256",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("unknown managed application graph token scope", () => {
|
||||
it("keeps absent MVID scope unknown and marks cross-source token joins partial", () => {
|
||||
const { memberEvidence, boundaryEvidence } = createManagedInteropEvidence();
|
||||
const members = managedMemberInspectionSchema.parse(
|
||||
parseEvidence(memberEvidence).normalized_result,
|
||||
);
|
||||
const boundaries = managedNativeBoundaryInspectionSchema.parse(
|
||||
parseEvidence(boundaryEvidence).normalized_result,
|
||||
);
|
||||
const unknownMembers = createEvidence(undefined, MANAGED_STATIC_PROVIDER, {
|
||||
operation: "inspect_managed_members",
|
||||
parameters: {},
|
||||
result: {
|
||||
...members,
|
||||
module:
|
||||
members.module === null
|
||||
? null
|
||||
: {
|
||||
...members.module,
|
||||
mvid: null,
|
||||
enc_id: null,
|
||||
enc_base_id: null,
|
||||
},
|
||||
identity_scope: { ...members.identity_scope, requires_mvid: null },
|
||||
},
|
||||
rawResult: null,
|
||||
limitations: members.limitations,
|
||||
});
|
||||
|
||||
const projected = projectManagedApplicationGraphEvidence({
|
||||
managed_members: unknownMembers,
|
||||
managed_native_boundaries: boundaryEvidence,
|
||||
});
|
||||
if (!projected.ok) throw projected.error;
|
||||
const result = managedApplicationGraphResultSchema.parse(
|
||||
parseEvidence(projected.value).normalized_result,
|
||||
);
|
||||
const graph = parseJavaScriptApplicationGraph(result.graph);
|
||||
expect(result.summary.modules).toBe(1);
|
||||
expect(result.coverage.status).toBe("partial");
|
||||
expect(result.limitations).toContain(
|
||||
"Managed boundary tokens were not linked to member nodes because matching module MVID scope was not established.",
|
||||
);
|
||||
const moduleObservation = graph.nodes.find(
|
||||
({ kind }) => kind === "managed-module",
|
||||
)?.observations[0];
|
||||
expect(moduleObservation?.properties.mvid).toBe(boundaries.module?.mvid);
|
||||
expect(moduleObservation?.evidence.extractor.operation).toBe(
|
||||
"inspect_managed_native_boundaries",
|
||||
);
|
||||
const method = graph.nodes.find(
|
||||
({ kind, observations }) =>
|
||||
kind === "managed-method" &&
|
||||
observations[0]?.label === "Fixture.Program.Main",
|
||||
);
|
||||
expect(method).toBeDefined();
|
||||
const importEdge = graph.edges.find(
|
||||
({ relation, target_node_id }) =>
|
||||
relation === "imports" &&
|
||||
graph.nodes.find(({ node_id }) => node_id === target_node_id)?.kind ===
|
||||
"managed-pinvoke-import",
|
||||
);
|
||||
expect(importEdge).toBeDefined();
|
||||
expect(
|
||||
graph.nodes.find(({ node_id }) => node_id === importEdge?.source_node_id)
|
||||
?.kind,
|
||||
).toBe("artifact");
|
||||
});
|
||||
});
|
||||
|
||||
describe("managed application graph coverage", () => {
|
||||
it("preserves partial parser coverage in graph and per-fact coverage", () => {
|
||||
const bytes = buildManagedPeFixture();
|
||||
|
||||
Reference in New Issue
Block a user