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.
344 lines
12 KiB
TypeScript
344 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.unscopedTokenRelationships
|
|
? [
|
|
"Managed boundary tokens were not linked to member nodes because matching module MVID scope was not established.",
|
|
]
|
|
: []),
|
|
];
|