fix(managed): report comparison and native-verification coverage unknown when an input cannot tell (#11257)

compare_managed_members reported partial when a side's member inspection was unavailable or when selected-scope agreement could not be established, and verify_managed_native_boundaries reported partial when the boundary inspection was unavailable. Both now fold their inputs with joinCoverageStatus, so unknown wins beside a known gap. The status enums gain unknown (additive).
This commit is contained in:
morluto authored and GitHub committed 2026-11-15 23:56:18 +08:00
1 parent 7aea9843bc
commit 067f4ff98b
8 files changed
+147 -27

No files matched your search

+8 -2
View File
@@ -388,7 +388,11 @@ name can select different nested owners. Such disjoint selected owners cannot
establish a member pair or added/removed status even when both inventories are
complete. Missing nominal identities cannot establish selected membership.
Incomplete selected inventories or missing identities leave aggregate agreement
unknown and coverage partial; known observed member pairs remain available.
unknown, and the comparison's `coverage.status` is then `unknown`; known
observed member pairs remain available. A side whose member inspection is
`unavailable` also makes the comparison `unknown`, even beside a known gap on
the other side. `partial` is reserved for known gaps such as a different
selected scope or an unequal method-row page.
Complete empty selected inventories agree even if their unmatched request names
differ. Whole-assembly and selected
inventories remain distinct scopes, and each source retains its exact request.
@@ -686,7 +690,9 @@ Windows name decorations are static inferred candidates;
exact spelling still suppresses decoration and no runtime binding is proven.
The mapping follows [Microsoft's charset guidance](https://learn.microsoft.com/en-us/dotnet/standard/native-interop/charset).
Partial input coverage records a residual question even when no unresolved
individual boundary was produced. Selected literal inventories likewise remain
individual boundary was produced. An unavailable boundary inspection read no
P/Invoke declarations, so the verification's `coverage.status` is `unknown`,
even beside non-exhaustive native exports. Selected literal inventories likewise remain
partial when unknown MethodDef ownership can hide a selected body.
Metadata tokens are not native addresses. Native body mapping for C++/CLI,
@@ -214,7 +214,11 @@ export const managedMemberComparisonResultSchema = z.strictObject({
methods: z.array(methodComparisonItemSchema),
fields: z.array(fieldComparisonItemSchema),
coverage: z.strictObject({
status: z.enum(["complete-within-inputs", "partial"]),
status: z
.enum(["complete-within-inputs", "partial", "unknown"])
.describe(
"complete-within-inputs only when both observations are complete over the same selected scope and method rows; unknown when a side is unavailable or the selected scope agreement cannot be established.",
),
left_status: z.enum(["complete", "partial", "unavailable"]),
right_status: z.enum(["complete", "partial", "unavailable"]),
}),
@@ -1,3 +1,4 @@
import { joinCoverageStatus, type CoverageStatus } from "../coverage.js";
import type {
ManagedMemberComparisonResult,
ManagedMemberComparisonSide,
@@ -17,23 +18,34 @@ export const buildComparisonCoverage = ({
right,
selectionAgreement,
}: ComparisonCoverageInput): ManagedMemberComparisonResult["coverage"] => {
const leftStatus = sideCoverageStatus(left);
const rightStatus = sideCoverageStatus(right);
const unknownInput =
left.coverage.state !== "complete" ||
right.coverage.state !== "complete" ||
selectionAgreement !== "same" ||
!sameMethodRows(left, right);
// An unavailable side examined nothing, and an unknown selection agreement
// cannot tell whether both sides cover the same types: either makes the
// comparison unknown, even beside a known gap.
const joined = joinCoverageStatus([
sideCoverageKnowledge(left),
sideCoverageKnowledge(right),
selectionAgreement === "unknown"
? "unknown"
: selectionAgreement === "different" || !sameMethodRows(left, right)
? "partial"
: "complete",
]);
return {
status:
!unknownInput && leftStatus === "complete" && rightStatus === "complete"
? "complete-within-inputs"
: "partial",
left_status: leftStatus,
right_status: rightStatus,
status: joined === "complete" ? "complete-within-inputs" : joined,
left_status: sideCoverageStatus(left),
right_status: sideCoverageStatus(right),
};
};
const sideCoverageKnowledge = (
result: ManagedMemberInspection,
): CoverageStatus =>
result.coverage.state === "unavailable"
? "unknown"
: result.coverage.state === "complete"
? "complete"
: "partial";
/** Full MethodDef coverage is required before inferring cross-build absence. */
export const hasCompleteMethodRows = (
result: ManagedMemberInspection,
@@ -1,4 +1,5 @@
import { digestCanonicalValue } from "../canonicalDigest.js";
import { joinCoverageStatus } from "../coverage.js";
import type { Evidence } from "../evidence.js";
import { machoRuntimeSymbolName } from "../native/machoSymbolName.js";
import type {
@@ -566,13 +567,19 @@ export const buildVerificationResult = ({
({ boundary_kind: kind }) =>
kind !== "pinvoke" && kind !== "runtime-provided",
).length;
const coverage: ManagedNativeVerificationResult["coverage"] = {
status:
managed.coverage.state === "complete" &&
native.unsupported === 0 &&
!native.partialExports
? "complete-within-inputs"
// An unavailable boundary inspection read no P/Invoke declarations, so the
// verification cannot tell what it would cover: unknown wins beside the
// known gaps of skipped native Evidence or non-exhaustive exports.
const status = joinCoverageStatus([
managed.coverage.state === "unavailable"
? "unknown"
: managed.coverage.state === "complete"
? "complete"
: "partial",
native.unsupported === 0 && !native.partialExports ? "complete" : "partial",
]);
const coverage: ManagedNativeVerificationResult["coverage"] = {
status: status === "complete" ? "complete-within-inputs" : status,
};
return {
algorithm: {
@@ -143,7 +143,11 @@ export const managedNativeVerificationRequestSchema = z
.superRefine(distinctNativeSources);
const verificationCoverageSchema = z.strictObject({
status: z.enum(["complete-within-inputs", "partial"]),
status: z
.enum(["complete-within-inputs", "partial", "unknown"])
.describe(
"unknown when the managed boundary inspection is unavailable; partial for a partial inspection, unsupported native Evidence or non-exhaustive exports.",
),
});
/** Provider-neutral managed/native verification result. */
@@ -138,7 +138,9 @@ cliTest(
const report = managedMemberComparisonResultSchema.parse(
parseEvidence(compared.structuredContent).normalized_result,
);
expect(report.coverage.status).toBe("partial");
// The delta's baseline-dependent selection cannot establish its scope, so
// the comparison's coverage is unknown rather than a known gap.
expect(report.coverage.status).toBe("unknown");
expect(report.summary).toMatchObject({ added: 0, removed: 0, unknown: 2 });
expect(report.evidence_links).toEqual(
sources.map(({ evidence_id }) => evidence_id),
@@ -93,7 +93,9 @@ describe("managed comparison selected nominal membership", () => {
...source,
result: partial,
});
expect(comparison.coverage.status).toBe("partial");
// The partial side cannot establish its selected scope, so scope
// agreement and the comparison's coverage are unknown.
expect(comparison.coverage.status).toBe("unknown");
expect(comparison.summary).toEqual({
added: 0,
removed: 0,
@@ -135,7 +137,7 @@ describe("managed comparison selected nominal membership", () => {
unchanged: 0,
});
expect(comparison.matching.unmatched).toBe(4);
expect(comparison.coverage.status).toBe("partial");
expect(comparison.coverage.status).toBe("unknown");
},
);
@@ -255,7 +257,9 @@ describe("managed comparison selected scope agreement", () => {
...source,
result: limited,
});
expect(comparison.coverage.status).toBe("partial");
// Scope agreement that cannot be established makes the comparison
// unknown, not a known partial gap.
expect(comparison.coverage.status).toBe("unknown");
expect(comparison.limitations).toContainEqual(
expect.stringContaining(
"Selected declaring-type scope agreement is unknown",
@@ -267,6 +271,27 @@ describe("managed comparison selected scope agreement", () => {
},
);
it("reports a whole-assembly comparison unknown when one side examined no metadata", () => {
const source = inspect(buildManagedPeFixture(), "/fixture/whole.dll");
const unavailable = structuredClone(source.result);
unavailable.metadata.status = "malformed";
unavailable.coverage.state = "unavailable";
unavailable.types = [];
unavailable.methods = [];
unavailable.fields = [];
unavailable.method_rows = null;
const sides = [source, { ...source, result: unavailable }] as const;
// Both selections are whole-assembly, so only the unavailable side, not
// scope agreement, makes the result unknown; it wins beside the partial
// method-row inventory, and in either order.
for (const [left, right] of [sides, [sides[1], sides[0]]] as const)
expect(compareManagedMembers(left, right).coverage).toEqual({
status: "unknown",
left_status: left === source ? "complete" : "unavailable",
right_status: right === source ? "complete" : "unavailable",
});
});
it("recognizes complete empty selected scopes despite different unmatched requests", () => {
const bytes = buildManagedPeFixture();
const left = inspect(bytes, "/fixture/empty-left.dll", ["Absent.Left"]);
@@ -912,6 +912,66 @@ it("retains every observed native candidate", () => {
expect(result.pinvoke_imports[0]?.candidates).toHaveLength(2);
});
it("reports coverage unknown when the managed boundary inspection is unavailable", () => {
const withManagedCoverage = (
state: "partial" | "unavailable",
nativeObservations: ReturnType<typeof nativeEvidenceWithExports>[],
) => {
const input = exampleInput();
const managed = input.managed_boundaries;
const normalized = managedNativeBoundaryInspectionSchema.parse(
managed.normalized_result,
);
return verifyManagedNativeVerificationSource(
readFixtureSource({
...input,
managed_boundaries: createEvidence(undefined, managed.provider, {
operation: managed.operation,
parameters: managed.parameters,
result: {
...normalized,
...(state === "unavailable"
? { module_refs: [], pinvoke_imports: [] }
: {}),
coverage: { ...normalized.coverage, state },
},
rawResult: null,
}),
native_observations: nativeObservations,
}),
);
};
const exhaustive = nativeEvidenceWithExports(["open_native"]);
const macho = inspectMachoSchema.parse(exhaustive.normalized_result);
const partialExports = createEvidence(
exhaustive.subject === null
? undefined
: {
path: exhaustive.subject.local_path,
sha256: exhaustive.subject.digest.sha256,
format: exhaustive.subject.format,
},
exhaustive.provider,
{
operation: exhaustive.operation,
parameters: exhaustive.parameters,
result: { ...macho, exports: { ...macho.exports, exhaustive: false } },
rawResult: null,
},
);
// Nothing was read on the managed side, so the result is unknown, and
// unknown wins beside the known gap of non-exhaustive native exports.
for (const native of [exhaustive, partialExports])
expect(withManagedCoverage("unavailable", [native]).coverage).toEqual({
status: "unknown",
});
// A partial inspection is a known gap.
expect(withManagedCoverage("partial", [exhaustive]).coverage).toEqual({
status: "partial",
});
});
describe("managed/native verification result algebra", () => {
it("rejects states without their required native match", () => {
const result = verifyManagedNativeVerificationSource(