test(verification): strengthen conformance and real-Hopper checks

This commit is contained in:
morluto committed 2026-07-13 03:15:46 +00:00
1 parent bce3398dbe
commit 0d36976277
4 files changed
+62 -1

No files matched your search

+22 -1
View File
@@ -3,7 +3,11 @@ import { readFile } from "node:fs/promises";
import { dirname, join, resolve } from "node:path";
import { promisify } from "node:util";
import { sha256 } from "./lib/conformance-fixtures.mjs";
import {
generateLargeFixture,
sha256,
sourceDigest,
} from "./lib/conformance-fixtures.mjs";
const exec = promisify(execFile);
const manifestPath = resolve(
@@ -16,6 +20,23 @@ const root = dirname(manifestPath);
for (const fixture of manifest.fixtures) {
const artifactPath = join(root, fixture.artifact);
const sources = [];
for (const source of fixture.sources ?? []) {
const content =
source.path === "generated/large.c"
? generateLargeFixture()
: await readFile(join(process.cwd(), "tests/conformance", source.path));
if (sha256(content) !== source.sha256)
throw new Error(
`${fixture.name}: source hash mismatch for ${source.path}`,
);
sources.push({ path: source.path, content });
}
if (
fixture.sourceSha256 !== undefined &&
sourceDigest(sources) !== fixture.sourceSha256
)
throw new Error(`${fixture.name}: source manifest digest mismatch`);
const artifact = await readFile(artifactPath);
if (sha256(artifact) !== fixture.artifactSha256)
throw new Error(`${fixture.name}: artifact hash mismatch`);
+15
View File
@@ -1,4 +1,6 @@
import { execFile } from "node:child_process";
import { createHash } from "node:crypto";
import { createReadStream } from "node:fs";
import { readdir, realpath } from "node:fs/promises";
import { promisify } from "node:util";
@@ -8,6 +10,7 @@ import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
import { TOOL_CONTRACTS } from "../dist/contracts/toolContracts.js";
import {
firstProcedureAddress,
requireDistinctTargetHashes,
requireAddressArray,
requireFunctionDossier,
requirePseudocode,
@@ -253,6 +256,11 @@ const [targetA, targetB] = await Promise.all([
]);
if (targetA === targetB)
throw new Error("Real-Hopper verification requires two distinct targets");
const [targetHashA, targetHashB] = await Promise.all([
sha256File(targetA),
sha256File(targetB),
]);
requireDistinctTargetHashes(targetHashA, targetHashB);
const serverEnvironment = { ...process.env };
delete serverEnvironment.HOPPER_TARGET_PATH;
delete serverEnvironment.HOPPER_SECOND_TARGET_PATH;
@@ -377,6 +385,7 @@ try {
diagnosticCount,
dynamicSession: true,
targets: [targetA, targetB],
targetHashes: [targetHashA, targetHashB],
switched: true,
secondOverview: verifiedSecondOverview,
};
@@ -422,3 +431,9 @@ await new Promise((resolve, reject) => {
},
);
});
async function sha256File(path) {
const hash = createHash("sha256");
for await (const chunk of createReadStream(path)) hash.update(chunk);
return hash.digest("hex");
}
+17
View File
@@ -27,6 +27,23 @@ export const firstProcedureAddress = (input: unknown): string => {
return first.address;
};
/** Reject two target paths that resolve to the same binary content. */
export const requireDistinctTargetHashes = (
firstHash: unknown,
secondHash: unknown,
): void => {
if (
typeof firstHash !== "string" ||
typeof secondHash !== "string" ||
firstHash.length === 0 ||
secondHash.length === 0
) {
throw new TypeError("Target hashes must be non-empty strings");
}
if (firstHash === secondHash)
throw new TypeError("Real-Hopper verification requires distinct binaries");
};
/** Reject empty and success-shaped embedded decompilation failures. */
export const requirePseudocode = (
input: unknown,
+8
View File
@@ -2,12 +2,20 @@ import { describe, expect, it } from "vitest";
import {
firstProcedureAddress,
requireDistinctTargetHashes,
requireAddressArray,
requireFunctionDossier,
requirePseudocode,
} from "../src/application/RealHopperAssertions.js";
describe("real Hopper semantic assertions", () => {
it("rejects two target paths containing the same binary", () => {
expect(() => requireDistinctTargetHashes("same", "same")).toThrow(
/distinct binaries/u,
);
expect(() => requireDistinctTargetHashes("", "other")).toThrow();
expect(() => requireDistinctTargetHashes("first", "second")).not.toThrow();
});
it("extracts only a real procedure address from a structured page", () => {
expect(
firstProcedureAddress({