test(providers): assert runtime and snapshot ownership contracts

Keep the selected running JVM visible when its version is unsupported, while retaining missing-runtime fallback controls. Assert the native snapshot binding and composed workflow Evidence profiles separately, preserving their exact commitments through a rejected snapshot switch.
This commit is contained in:
morluto
2026-10-24 06:37:46 +08:00
parent f8ff1579ad
commit 60bb202ca3
3 changed files with 45 additions and 4 deletions
+4
View File
@@ -436,6 +436,10 @@ keeps that target's analysis commitments with the replacement client. This
includes the native dispatch workflow profile on a profileless route, so its
existing snapshot and fresh observations retain the same provenance.
For native dispatch, the snapshot binding retains the reader profile; workflow
Evidence and `workflow_entries` retain the composed profile, which commits that
reader and the workflow's analysis caveats. Restoration preserves both profiles.
The MCP save receipt reports `primitive_entries`, `workflow_entries`, and
`evidence_records` separately. Zero primitive bindings can still accompany retained workflow
results and Evidence. These are cached observations, not a saved provider
+22 -2
View File
@@ -1847,7 +1847,7 @@ it.each([
async (platform, javaHome, pathJava, expected) => {
const checked: string[] = [];
const probed: string[] = [];
await inspectGhidraInstallation(
const inspection = await inspectGhidraInstallation(
{
resourceOwner: inspectionResources.owner,
environment: { PATH: "/selected-bin" },
@@ -1857,17 +1857,37 @@ it.each([
...(javaHome === undefined ? {} : { javaHome }),
},
host({
readText: () =>
`application.version=${SUPPORTED_GHIDRA_VERSION}\napplication.java.min=21\n`,
executable: (candidate) => {
checked.push(candidate);
return candidate === pathJava || host().executable(candidate);
},
probeJava: (command) => {
probed.push(command);
return missingJavaProbe;
// A running JVM remains selected even when outside Ghidra's bounds.
// An absent PATH runtime instead permits macOS fallback discovery.
return javaProbeOk({
...JAVA,
home: "/selected-jdk",
major: 17,
version: "17.0.20.1",
});
},
}),
);
expect(probed).toEqual([expected]);
expect(inspection).toMatchObject({
status: "unavailable",
javaCommand: expected,
javaVersion: "17.0.20.1",
});
expect(inspection.checks.find(({ name }) => name === "java")).toMatchObject(
{
status: "failed",
code: "unsupported_version",
},
);
expect(
checked.some((candidate) => candidate.includes("/opt/openjdk/")),
).toBe(false);
@@ -5,6 +5,7 @@ import { expect } from "vitest";
import { toolContract } from "../../src/contracts/toolContracts.js";
import { parseEvidence } from "../../src/domain/evidence.js";
import { readAnalysisSnapshot } from "../../src/application/binary/AnalysisSnapshotFiles.js";
import { workflowAnalysisProfile } from "../../src/application/WorkflowAnalysisProfile.js";
import { APPLE_DISPATCH_METADATA_ANALYSIS_PROFILE } from "../../src/native/NativeMacOSProviderMetadata.js";
import {
CPU,
@@ -66,10 +67,21 @@ cliTest.skipIf(process.platform !== "darwin")(
.structuredContent,
);
expect(original.analysis_profile).toEqual(
APPLE_DISPATCH_METADATA_ANALYSIS_PROFILE,
workflowAnalysisProfile(
APPLE_DISPATCH_METADATA_ANALYSIS_PROFILE,
"inspect_native_dispatch_metadata",
),
);
await good("record_unknown", { ...unknown, severity: "medium" });
await good("close_binary", { snapshot_path: saved, overwrite: true });
const savedSnapshot = await readAnalysisSnapshot(saved);
if (!savedSnapshot.ok) throw savedSnapshot.error;
expect(savedSnapshot.value.binding.analysis_profile).toEqual(
APPLE_DISPATCH_METADATA_ANALYSIS_PROFILE,
);
expect(
savedSnapshot.value.workflow_entries[0]?.execution.analysis_profile,
).toEqual(original.analysis_profile);
await good("open_binary", { path: first, snapshot_path: saved });
const rejected = await call("open_binary", {
path: second,
@@ -94,7 +106,12 @@ cliTest.skipIf(process.platform !== "darwin")(
const snapshot = await readAnalysisSnapshot(after);
if (!snapshot.ok) throw snapshot.error;
expect(snapshot.value.binding.analysis_profile).toEqual(
original.analysis_profile,
savedSnapshot.value.binding.analysis_profile,
);
expect(
snapshot.value.workflow_entries[0]?.execution.analysis_profile,
).toEqual(
savedSnapshot.value.workflow_entries[0]?.execution.analysis_profile,
);
expect(await readFile(first)).toEqual(Buffer.from(bytes));
expect(await readFile(second)).toEqual(Buffer.from(bytes));