test: strengthen and prune audited test coverage (#8573)
* test: strengthen and prune audited test coverage * test: require delivered DeepSeek skill instructions * test: keep schema fixture types private * test: verify Gemini receives skill instructions * test: bind Qwen skill evidence to its tool result * test: report NativeAOT verifier checks precisely * fix: verify Ghidra cleanup paths reliably * fix: verify Command Code skill activation * test: tighten cleanup and fixture checks * test: verify packet capture evidence is recorded * fix: report HTTP3 capture provenance accurately * test: preserve resource coverage through MCP * test: prune repeated assertions and retain cancellation identity * fix: validate client verifier fixture paths * test: skip Copilot verifier cases on Windows * test: keep verifier setup target relative
This commit is contained in:
1 parent
ce9095d727
commit
7bcefead69
167 files changed
+2396
-1012
No files matched your search
@@ -257,12 +257,12 @@ beyond already-connected devices (`adb connect`) are not exposed yet.
|
||||
|
||||
## Real verification
|
||||
|
||||
`npm run verify:adb` exercises every operation against a live device. It
|
||||
`npm run verify:adb` exercises observation and transfer operations against a live device. It
|
||||
requires `adb` (or `REA_ADB_PATH`) and any attached emulator or phone;
|
||||
`--serial` selects one when several are attached, `--pull PACKAGE` pulls a
|
||||
real package and verifies the returned digests against the files on disk,
|
||||
and `--install-apk PATH` drives a full install → resolve → uninstall
|
||||
lifecycle with a lane-owned probe.
|
||||
real package and verifies the returned digests against the files on disk.
|
||||
The optional `--install-apk` lifecycle is unavailable until the selected APK
|
||||
identity and device cleanup ownership can be established safely.
|
||||
|
||||
Record: verified against adb 34.0.5 on Linux against an Android 14
|
||||
(API 34) x86_64 emulator — all operations above, a 224-package inventory,
|
||||
|
||||
+5
-5
@@ -1423,12 +1423,12 @@ budgets.
|
||||
|
||||
## ADB device acquisition
|
||||
|
||||
`npm run verify:adb` exercises every ADB operation against a live device or
|
||||
`npm run verify:adb` exercises ADB observation and transfer operations against a live device or
|
||||
emulator through the caller's adb binary (`REA_ADB_PATH` or PATH). No SDK
|
||||
installation or emulator management is involved; the device-mutating tools
|
||||
run only in the explicit lifecycle section (`--install-apk` drives a
|
||||
lane-owned install → resolve → start → observe → force-stop → uninstall
|
||||
probe). `--pull` acquires a real package and re-digests the pulled files on
|
||||
installation or emulator management is involved. `--install-apk` is unavailable
|
||||
until the selected APK identity and device cleanup ownership can be established
|
||||
safely. The transfer probe creates and removes a temporary device file. `--pull`
|
||||
acquires a real package and re-digests the pulled files on
|
||||
disk against the returned SHA-256 values; `--serial` selects a device when
|
||||
several are attached.
|
||||
|
||||
|
||||
@@ -10,5 +10,10 @@ export function compareTestDiscovery(
|
||||
readonly file: string;
|
||||
readonly projects: readonly string[];
|
||||
}[];
|
||||
readonly wrongProjects: readonly {
|
||||
readonly file: string;
|
||||
readonly expected: string;
|
||||
readonly actual: string;
|
||||
}[];
|
||||
readonly discovered: number;
|
||||
};
|
||||
@@ -1,4 +1,5 @@
|
||||
import { relative, resolve } from "node:path";
|
||||
import { projectForTest } from "./test-projects.mjs";
|
||||
|
||||
/** Compare actual Vitest discovery with repository-owned test paths. */
|
||||
export const compareTestDiscovery = (expectedPaths, discovery, root) => {
|
||||
@@ -36,6 +37,14 @@ export const compareTestDiscovery = (expectedPaths, discovery, root) => {
|
||||
const projects = projectsByFile.get(file);
|
||||
return projects.length > 1 ? [{ file, projects: sorted(projects) }] : [];
|
||||
}),
|
||||
wrongProjects: sorted(expected).flatMap((file) => {
|
||||
const projects = projectsByFile.get(file);
|
||||
if (!projects || projects.length !== 1) return [];
|
||||
const expectedProject = projectForTest(file).name;
|
||||
return projects[0] === expectedProject
|
||||
? []
|
||||
: [{ file, expected: expectedProject, actual: projects[0] }];
|
||||
}),
|
||||
discovered: projectsByFile.size,
|
||||
};
|
||||
};
|
||||
@@ -539,11 +539,13 @@ class WebsiteMaintenanceChecks(unittest.TestCase):
|
||||
self.assertIn(b"color: black", response.read())
|
||||
with self.assertRaises(HTTPError) as error:
|
||||
urlopen(base + prefix + "missing")
|
||||
self.assertEqual(error.exception.code, 404)
|
||||
with contextlib.closing(error.exception) as response:
|
||||
self.assertEqual(response.code, 404)
|
||||
if prefix != "/":
|
||||
with self.assertRaises(HTTPError) as error:
|
||||
urlopen(base + "/guide/")
|
||||
self.assertEqual(error.exception.code, 404)
|
||||
with contextlib.closing(error.exception) as response:
|
||||
self.assertEqual(response.code, 404)
|
||||
finally:
|
||||
server.shutdown()
|
||||
worker.join()
|
||||
|
||||
+10
-78
@@ -15,8 +15,10 @@ if (options.help) {
|
||||
process.stdout.write(
|
||||
"Usage: npm run verify:adb [-- --serial SERIAL --pull PACKAGE]\n" +
|
||||
"Requires a caller-selected adb binary (REA_ADB_PATH or PATH) and a\n" +
|
||||
"connected device or emulator. Verifies all five operations against\n" +
|
||||
"the live device; --pull acquires a real package and re-digests the\n" +
|
||||
"connected device or emulator. Verifies observation and transfer operations\n" +
|
||||
"against the live device. --install-apk is unavailable until APK identity\n" +
|
||||
"and device cleanup ownership can be established safely.\n" +
|
||||
"--pull acquires a real package and re-digests the\n" +
|
||||
"files on disk against the returned SHA-256 values.\n",
|
||||
);
|
||||
process.exit(0);
|
||||
@@ -356,79 +358,7 @@ try {
|
||||
`versionCode=${String(details.version_code)}`,
|
||||
);
|
||||
}
|
||||
if (options.installApk !== null && options.installApk !== undefined) {
|
||||
const probePackage = "com.rea.adb.probe";
|
||||
const installedProbe = await run("install_adb_package", {
|
||||
serial,
|
||||
apk_path: options.installApk,
|
||||
replace: false,
|
||||
});
|
||||
if (installedProbe !== null) {
|
||||
check(
|
||||
"install reports the APK digest",
|
||||
typeof installedProbe.sha256 === "string" &&
|
||||
installedProbe.sha256.length === 64,
|
||||
`${installedProbe.bytes} bytes`,
|
||||
);
|
||||
const resolved = await run("resolve_adb_packages", {
|
||||
serial,
|
||||
query: "rea.adb.probe",
|
||||
});
|
||||
if (resolved !== null)
|
||||
check(
|
||||
"resolve finds the installed probe uniquely",
|
||||
resolved.matches.length === 1 &&
|
||||
resolved.matches[0]?.package_name === probePackage,
|
||||
`${resolved.matches.length} match(es)`,
|
||||
);
|
||||
const started = await run("start_adb_app", {
|
||||
serial,
|
||||
package: probePackage,
|
||||
});
|
||||
if (started !== null)
|
||||
check(
|
||||
"app start resolves and launches the launcher activity",
|
||||
typeof started.component === "string" &&
|
||||
started.component.startsWith(probePackage),
|
||||
String(started.component),
|
||||
);
|
||||
const observedProcesses = await run("list_adb_processes", { serial });
|
||||
if (observedProcesses !== null)
|
||||
check(
|
||||
"started app appears in the process listing",
|
||||
observedProcesses.processes.some(
|
||||
(process) => process.name === probePackage,
|
||||
),
|
||||
);
|
||||
const stopped = await run("stop_adb_app", {
|
||||
serial,
|
||||
package: probePackage,
|
||||
});
|
||||
if (stopped !== null)
|
||||
check(
|
||||
"force-stop completes the dynamic-analysis run",
|
||||
stopped.package_name === probePackage,
|
||||
);
|
||||
const removed = await run("uninstall_adb_package", {
|
||||
serial,
|
||||
package: probePackage,
|
||||
});
|
||||
if (removed !== null)
|
||||
check(
|
||||
"uninstall completes the lifecycle",
|
||||
removed.package_name === probePackage,
|
||||
);
|
||||
const afterRemoval = await run("resolve_adb_packages", {
|
||||
serial,
|
||||
query: "rea.adb.probe",
|
||||
});
|
||||
if (afterRemoval !== null)
|
||||
check(
|
||||
"resolve reports zero matches after removal",
|
||||
afterRemoval.matches.length === 0,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
} finally {
|
||||
await rm(transferRoot, { recursive: true, force: true });
|
||||
}
|
||||
@@ -459,7 +389,7 @@ if (failures > 0) {
|
||||
}
|
||||
|
||||
function parseArguments(argv) {
|
||||
const parsed = { help: false, serial: null, pull: null, installApk: null };
|
||||
const parsed = { help: false, serial: null, pull: null };
|
||||
for (let index = 0; index < argv.length; index += 1) {
|
||||
const argument = argv[index];
|
||||
if (argument === "--help" || argument === "-h") parsed.help = true;
|
||||
@@ -470,8 +400,10 @@ function parseArguments(argv) {
|
||||
parsed.pull = argv[index + 1] ?? null;
|
||||
index += 1;
|
||||
} else if (argument === "--install-apk") {
|
||||
parsed.installApk = argv[index + 1] ?? null;
|
||||
index += 1;
|
||||
process.stderr.write(
|
||||
"--install-apk is unavailable: APK identity and device cleanup ownership cannot be established safely\n",
|
||||
);
|
||||
process.exit(1);
|
||||
} else {
|
||||
process.stderr.write(`unknown argument: ${argument}\n`);
|
||||
process.exit(2);
|
||||
|
||||
@@ -8,7 +8,7 @@ import { withVerifierCleanup } from "./lib/verifier-cleanup.mjs";
|
||||
import { createVerifierCommands } from "./lib/verify-commands.mjs";
|
||||
|
||||
const root = await PrivateRuntimeRoot.create({ prefix: "rea-bun-verifier-" });
|
||||
const commands = await createVerifierCommands();
|
||||
let commands;
|
||||
const run = (command, args, options = {}) =>
|
||||
commands.exec(command, args, {
|
||||
timeout: 120_000,
|
||||
@@ -19,6 +19,7 @@ const run = (command, args, options = {}) =>
|
||||
// only after every owned command family has settled.
|
||||
let commandsClosed = false;
|
||||
await withVerifierCleanup(async () => {
|
||||
commands = await createVerifierCommands();
|
||||
const source = join(root.path, "hello.ts");
|
||||
await writeFile(source, 'console.log("REA_BUN_VERIFICATION");\n');
|
||||
const docker = process.argv.includes("--docker");
|
||||
@@ -104,7 +105,7 @@ await withVerifierCleanup(async () => {
|
||||
);
|
||||
}, [
|
||||
async () => {
|
||||
await commands.close();
|
||||
if (commands !== undefined) await commands.close();
|
||||
commandsClosed = true;
|
||||
},
|
||||
async () => {
|
||||
|
||||
@@ -2,7 +2,7 @@ import assert from "node:assert/strict";
|
||||
import { createHash } from "node:crypto";
|
||||
import { mkdir, mkdtemp, readFile, writeFile } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { isAbsolute, join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import Ajv2020 from "ajv/dist/2020.js";
|
||||
import { createCatalogIdentity } from "../dist/catalogIdentity.js";
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
TOOL_CONTRACTS,
|
||||
toolContract,
|
||||
} from "../dist/contracts/toolContracts.js";
|
||||
import { isPathWithinRoot } from "../dist/domain/localPath.js";
|
||||
import { createVerifierCommands } from "./lib/verify-commands.mjs";
|
||||
import { withVerifierCleanup } from "./lib/verifier-cleanup.mjs";
|
||||
import { saveVerifierCommandOutput } from "./lib/verifier-command-output.mjs";
|
||||
@@ -306,7 +307,9 @@ await withVerifierCleanup(async () => {
|
||||
assert(
|
||||
parsed.plannedActions.every(
|
||||
(a) =>
|
||||
a.target === configPath || a.target.startsWith(skillDirectory),
|
||||
a.target === configPath ||
|
||||
(isAbsolute(a.target) &&
|
||||
isPathWithinRoot(skillDirectory, a.target)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { isAbsolute, join, relative } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import Ajv2020 from "ajv/dist/2020.js";
|
||||
import { TOOL_CONTRACTS } from "../dist/contracts/toolContracts.js";
|
||||
import { isPathWithinRoot } from "../dist/domain/localPath.js";
|
||||
import { createVerifierCommands } from "./lib/verify-commands.mjs";
|
||||
import { withVerifierCleanup } from "./lib/verifier-cleanup.mjs";
|
||||
import { saveVerifierCommandOutput } from "./lib/verifier-command-output.mjs";
|
||||
@@ -54,6 +55,7 @@ const expectedNames = TOOL_CONTRACTS.map(
|
||||
).sort();
|
||||
let clientVersion = null;
|
||||
let expectedSkill;
|
||||
let skillCallId;
|
||||
let inventoryChecked = false;
|
||||
let catalogSize = null;
|
||||
let skillLoaded = false;
|
||||
@@ -180,10 +182,24 @@ await withVerifierCleanup(async () => {
|
||||
finalSeen = true;
|
||||
return { role: "assistant", content: "REA_CLIENT_COMPATIBILITY_OK" };
|
||||
}
|
||||
if (stage === 0)
|
||||
return call("activate_skill", { name: "reverse-engineer-anything" });
|
||||
if (stage === 0) {
|
||||
const request = call("activate_skill", {
|
||||
name: "reverse-engineer-anything",
|
||||
});
|
||||
skillCallId = request.tool_calls[0].id;
|
||||
return request;
|
||||
}
|
||||
assert(
|
||||
strings.some((text) => text.includes(expectedSkill)),
|
||||
skillCallId !== undefined &&
|
||||
expectedSkill !== undefined &&
|
||||
body.messages.some(
|
||||
(message) =>
|
||||
message.role === "tool" &&
|
||||
message.tool_call_id === skillCallId &&
|
||||
textValues(message.content).some((text) =>
|
||||
text.includes(expectedSkill),
|
||||
),
|
||||
),
|
||||
"The model must receive the complete activated skill body",
|
||||
);
|
||||
skillLoaded = true;
|
||||
@@ -322,8 +338,8 @@ await withVerifierCleanup(async () => {
|
||||
result.plannedActions.every(
|
||||
(action) =>
|
||||
action.target === configPath ||
|
||||
action.target === skillDirectory ||
|
||||
action.target.startsWith(skillDirectory + "/"),
|
||||
(isAbsolute(action.target) &&
|
||||
isPathWithinRoot(skillDirectory, action.target)),
|
||||
),
|
||||
"Refuse setup writes outside the disposable account's configuration and skill",
|
||||
);
|
||||
@@ -342,9 +358,16 @@ await withVerifierCleanup(async () => {
|
||||
assert.deepEqual(repeated.appliedActions, []);
|
||||
assert.equal(await readFile(configPath, "utf8"), configured);
|
||||
assert.equal(await readFile(`${configPath}.rea.backup`, "utf8"), original);
|
||||
expectedSkill = (await readFile(join(skillDirectory, "SKILL.md"), "utf8"))
|
||||
.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n/, "")
|
||||
.trim();
|
||||
const installedSkill = await readFile(
|
||||
join(skillDirectory, "SKILL.md"),
|
||||
"utf8",
|
||||
);
|
||||
const frontmatter = /^---\r?\n[\s\S]*?\r?\n---(?:\r?\n|$)/u.exec(
|
||||
installedSkill,
|
||||
);
|
||||
assert(frontmatter, "Installed skill must have authored frontmatter");
|
||||
expectedSkill = installedSkill.slice(frontmatter[0].length).trim();
|
||||
assert(expectedSkill.length > 0, "Installed skill body must not be empty");
|
||||
const response = await saveVerifierCommandOutput(
|
||||
() =>
|
||||
commands.exec(
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
import { advertisedInputJsonSchema } from "../dist/contracts/toolSchemaMetadata.js";
|
||||
import { presentAdvertisedInputSchema } from "../dist/contracts/compactInputSchema.js";
|
||||
import { compactPresentationForMcpInputSchemaProfile } from "../dist/config/mcpInputSchemaProfile.js";
|
||||
import { isPathWithinRoot } from "../dist/domain/localPath.js";
|
||||
import { createVerifierCommands } from "./lib/verify-commands.mjs";
|
||||
import { withVerifierCleanup } from "./lib/verifier-cleanup.mjs";
|
||||
import { saveVerifierCommandOutput } from "./lib/verifier-command-output.mjs";
|
||||
@@ -300,8 +301,8 @@ await withVerifierCleanup(async () => {
|
||||
parsed.plannedActions.every(
|
||||
(a) =>
|
||||
a.target === configPath ||
|
||||
a.target === skillDirectory ||
|
||||
a.target.startsWith(skillDirectory + "/"),
|
||||
(isAbsolute(a.target) &&
|
||||
isPathWithinRoot(skillDirectory, a.target)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -56,6 +56,7 @@ let catalog;
|
||||
let patternCount = 0;
|
||||
let skillRequested = false;
|
||||
let skillLoaded = false;
|
||||
let expectedSkillBody;
|
||||
let evidenceSeen = false;
|
||||
let finalSeen = false;
|
||||
let artifactDigest;
|
||||
@@ -169,11 +170,34 @@ await withVerifierCleanup(async () => {
|
||||
const toolMessages = (body.messages ?? []).filter(
|
||||
(message) => message.role === "tool",
|
||||
);
|
||||
const serialized = JSON.stringify(toolMessages);
|
||||
skillLoaded ||=
|
||||
serialized.includes(
|
||||
'<skill_content name=\\"reverse-engineer-anything\\">',
|
||||
) && serialized.includes(dirname(skillPath));
|
||||
skillLoaded ||= toolMessages.some((message) => {
|
||||
if (
|
||||
typeof message.content !== "string" ||
|
||||
expectedSkillBody === undefined
|
||||
)
|
||||
return false;
|
||||
const open =
|
||||
'<skill_content name="reverse-engineer-anything">\n<skill_resources>\n';
|
||||
const start = message.content.indexOf(open);
|
||||
if (start < 0) return false;
|
||||
const resourcesStart = start + open.length;
|
||||
const instructions = `\n</skill_resources>\n\n<skill_instructions>\n${expectedSkillBody}\n</skill_instructions>\n</skill_content>`;
|
||||
const resourcesEnd = message.content.indexOf(
|
||||
instructions,
|
||||
resourcesStart,
|
||||
);
|
||||
if (resourcesEnd < 0) return false;
|
||||
const expectedPath = dirname(skillPath)
|
||||
.replaceAll("&", "&")
|
||||
.replaceAll("<", "<")
|
||||
.replaceAll(">", ">");
|
||||
const resources = message.content.slice(resourcesStart, resourcesEnd);
|
||||
return (
|
||||
!resources.includes("</skill_content>") &&
|
||||
!resources.includes("</skill_resources>") &&
|
||||
resources.includes(expectedPath)
|
||||
);
|
||||
});
|
||||
for (const message of toolMessages) {
|
||||
const found = findClientResult(
|
||||
message.content,
|
||||
@@ -281,11 +305,15 @@ await withVerifierCleanup(async () => {
|
||||
const configured = await readFile(configPath, "utf8");
|
||||
assert.deepEqual(withoutRea(configured), withoutRea(original));
|
||||
assert(configured.includes("!!js process.cwd()"));
|
||||
assert(
|
||||
(await readFile(skillPath, "utf8")).includes(
|
||||
"name: reverse-engineer-anything",
|
||||
),
|
||||
const installedSkill = await readFile(skillPath, "utf8");
|
||||
assert(installedSkill.includes("name: reverse-engineer-anything"));
|
||||
const frontmatter = /^---\r?\n[\s\S]*?^---(?:\r?\n|$)/mu.exec(
|
||||
installedSkill,
|
||||
);
|
||||
assert(frontmatter, "Installed skill must have its authored frontmatter");
|
||||
expectedSkillBody = installedSkill.slice(frontmatter[0].length).trim();
|
||||
assert(expectedSkillBody.length > 0);
|
||||
|
||||
lifecycle.push("setup");
|
||||
const repeated = await runRea(
|
||||
["setup", "--client", "dsh", "--yes"],
|
||||
|
||||
@@ -104,6 +104,10 @@ await withVerifierCleanup(
|
||||
}
|
||||
// The same checks apply to the default selection and each remaining ABI.
|
||||
const preferred = await run("inspect_dart_aot", { path: apk });
|
||||
assert.ok(
|
||||
abis.some((abi) => abi.abi === preferred.abi),
|
||||
"Default AOT selection must be one of the identified ABIs",
|
||||
);
|
||||
for (const abi of abis) {
|
||||
const aot =
|
||||
abi.abi === preferred.abi
|
||||
|
||||
@@ -2,7 +2,7 @@ import { basename } from "node:path";
|
||||
|
||||
import { FridaInstrumentationService } from "../dist/application/frida/FridaInstrumentationService.js";
|
||||
import { FridaInstrumentationManager } from "../dist/frida/FridaInstrumentationManager.js";
|
||||
import { isValidFridaRemoteAddress } from "../dist/contracts/fridaRemoteAddress.js";
|
||||
import { isValidFridaRemoteAddress } from "../dist/domain/fridaRemoteAddress.js";
|
||||
import { withVerifierCleanup } from "./lib/verifier-cleanup.mjs";
|
||||
|
||||
const options = parseArguments(process.argv.slice(2));
|
||||
@@ -80,6 +80,10 @@ try {
|
||||
!Array.isArray(observed.messages)
|
||||
)
|
||||
throw new Error("Frida returned no message observation");
|
||||
if (!Number.isSafeInteger(observed.pid) || observed.pid <= 0)
|
||||
throw new Error("Frida returned no valid target PID");
|
||||
if (remote !== undefined && observed.pid !== options.pid)
|
||||
throw new Error("Frida observed a different target PID");
|
||||
const messageObserved = observed.messages.some(
|
||||
(message) =>
|
||||
typeof message === "object" &&
|
||||
|
||||
@@ -2,13 +2,14 @@ import assert from "node:assert/strict";
|
||||
import { createHash } from "node:crypto";
|
||||
import { mkdir, mkdtemp, readFile, writeFile } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join, resolve, sep } from "node:path";
|
||||
import { isAbsolute, join, relative, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import Ajv2020 from "ajv/dist/2020.js";
|
||||
import {
|
||||
TOOL_CONTRACTS,
|
||||
toolContract,
|
||||
} from "../dist/contracts/toolContracts.js";
|
||||
import { isPathWithinRoot } from "../dist/domain/localPath.js";
|
||||
import { createVerifierCommands } from "./lib/verify-commands.mjs";
|
||||
import { saveVerifierCommandOutput } from "./lib/verifier-command-output.mjs";
|
||||
import { withVerifierCleanup } from "./lib/verifier-cleanup.mjs";
|
||||
@@ -65,6 +66,7 @@ let fixture;
|
||||
let passed = false;
|
||||
let evidenceSeen = false;
|
||||
let skillLoaded = false;
|
||||
let expectedSkillBody;
|
||||
let skillRequested = false;
|
||||
let finalSeen = false;
|
||||
let catalogSize = null;
|
||||
@@ -150,13 +152,21 @@ await withVerifierCleanup(async () => {
|
||||
if (args.includes("--dry-run")) {
|
||||
assert(parsed.plannedActions.some((a) => a.target === configPath));
|
||||
assert(
|
||||
parsed.plannedActions.some((a) =>
|
||||
a.target.startsWith(join(profile, ".agents", "skills") + sep),
|
||||
),
|
||||
parsed.plannedActions.some((a) => {
|
||||
const skills = join(profile, ".agents", "skills");
|
||||
return (
|
||||
isAbsolute(a.target) &&
|
||||
relative(skills, a.target) !== "" &&
|
||||
isPathWithinRoot(skills, a.target)
|
||||
);
|
||||
}),
|
||||
);
|
||||
assert(
|
||||
parsed.plannedActions.every((a) =>
|
||||
a.target.startsWith(profile + sep),
|
||||
parsed.plannedActions.every(
|
||||
(a) =>
|
||||
isAbsolute(a.target) &&
|
||||
relative(profile, a.target) !== "" &&
|
||||
isPathWithinRoot(profile, a.target),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -189,11 +199,15 @@ await withVerifierCleanup(async () => {
|
||||
assert.deepEqual(JSON.parse(repeated.stdout).appliedActions, []);
|
||||
assert.equal(await readFile(configPath, "utf8"), configured);
|
||||
assert.equal(await readFile(`${configPath}.rea.backup`, "utf8"), original);
|
||||
assert(
|
||||
(await readFile(skillPath, "utf8")).includes(
|
||||
"name: reverse-engineer-anything",
|
||||
),
|
||||
const installedSkill = await readFile(skillPath, "utf8");
|
||||
assert(installedSkill.includes("name: reverse-engineer-anything"));
|
||||
const frontmatter = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n([\s\S]*))?/u.exec(
|
||||
installedSkill,
|
||||
);
|
||||
assert(frontmatter, "Installed skill must have authored frontmatter");
|
||||
expectedSkillBody = (frontmatter[2] ?? "").trim();
|
||||
assert(expectedSkillBody.length > 0);
|
||||
|
||||
const target = join(lab, "fixture α测试");
|
||||
await mkdir(target);
|
||||
await writeFile(join(target, "app.js"), CLIENT_JAVASCRIPT_FIXTURE_SOURCE);
|
||||
@@ -268,16 +282,32 @@ await withVerifierCleanup(async () => {
|
||||
.flatMap((c) => c.parts ?? [])
|
||||
.filter((p) => p.functionResponse)
|
||||
.map((p) => p.functionResponse);
|
||||
skillLoaded ||= responses.some(
|
||||
(r) =>
|
||||
r.name === "activate_skill" &&
|
||||
r.response?.output?.includes(
|
||||
`<activated_skill name="reverse-engineer-anything">`,
|
||||
) &&
|
||||
r.response.output.includes(
|
||||
skillLoaded ||= responses.some((response) => {
|
||||
const output = response.response?.output;
|
||||
if (
|
||||
response.name !== "activate_skill" ||
|
||||
typeof output !== "string" ||
|
||||
expectedSkillBody === undefined
|
||||
)
|
||||
return false;
|
||||
const start = `<activated_skill name="reverse-engineer-anything">\n <instructions>\n ${expectedSkillBody}\n </instructions>\n\n <available_resources>\n `;
|
||||
const at = output.indexOf(start);
|
||||
if (at < 0) return false;
|
||||
const resourcesStart = at + start.length;
|
||||
const resourcesEnd = output.indexOf(
|
||||
"\n </available_resources>\n</activated_skill>",
|
||||
resourcesStart,
|
||||
);
|
||||
if (resourcesEnd < 0) return false;
|
||||
const resources = output.slice(resourcesStart, resourcesEnd);
|
||||
return (
|
||||
!resources.includes("</activated_skill>") &&
|
||||
!resources.includes("</available_resources>") &&
|
||||
resources.includes(
|
||||
join(profile, ".agents", "skills", "reverse-engineer-anything"),
|
||||
),
|
||||
);
|
||||
)
|
||||
);
|
||||
});
|
||||
const observed = findClientResult(
|
||||
responses,
|
||||
isClientJavaScriptEvidence,
|
||||
|
||||
@@ -6,6 +6,7 @@ import { isAbsolute, join, relative } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import Ajv2020 from "ajv/dist/2020.js";
|
||||
import { TOOL_CONTRACTS } from "../dist/contracts/toolContracts.js";
|
||||
import { isPathWithinRoot } from "../dist/domain/localPath.js";
|
||||
import { createVerifierCommands } from "./lib/verify-commands.mjs";
|
||||
import { withVerifierCleanup } from "./lib/verifier-cleanup.mjs";
|
||||
import { saveVerifierCommandOutput } from "./lib/verifier-command-output.mjs";
|
||||
@@ -402,8 +403,8 @@ enabled = false
|
||||
parsed.plannedActions.every(
|
||||
(action) =>
|
||||
action.target === configPath ||
|
||||
action.target === skillDirectory ||
|
||||
action.target.startsWith(skillDirectory + "/"),
|
||||
(isAbsolute(action.target) &&
|
||||
isPathWithinRoot(skillDirectory, action.target)),
|
||||
),
|
||||
"Refuse writes outside the owned profile/skill",
|
||||
);
|
||||
|
||||
@@ -373,6 +373,7 @@ try {
|
||||
}
|
||||
const report = {
|
||||
run_id: run.run_id,
|
||||
input_mode: inspectExisting ? "existing-captures" : "producer-attempt",
|
||||
status: primaryError || cleanupFailures.length ? "failed" : "passed",
|
||||
finished_at: new Date().toISOString(),
|
||||
...(primaryError
|
||||
@@ -386,8 +387,11 @@ const report = {
|
||||
verifier,
|
||||
producer_audit: producerAudit,
|
||||
scope:
|
||||
"Real Linux reverse HTTP/3, mitmproxy 12.2.3, aioquic 1.2.0 and guest cURL",
|
||||
"Linux reverse HTTP/3 capture verification for mitmproxy 12.2.3, aioquic 1.2.0 and guest cURL",
|
||||
limitations: [
|
||||
...(inspectExisting
|
||||
? ["Existing capture producer provenance was not authenticated by this attempt."]
|
||||
: []),
|
||||
"No local/WireGuard, browser, QUIC v2, migration, 0-RTT, mTLS or replay claim.",
|
||||
"No HAR, interruption, certificate-failure or body-completeness coverage in this profile.",
|
||||
],
|
||||
|
||||
@@ -17,7 +17,7 @@ import { hashVerifierFile } from "./lib/verifier-file-resources.mjs";
|
||||
const args = process.argv.slice(2);
|
||||
if (args.includes("--help") || args.includes("-h")) {
|
||||
console.log(
|
||||
"Usage: npm run verify:il2cpp:metadata -- [--metadata PATH]\nPATH is a caller-owned, unencrypted global-metadata.dat (version 24, 27, 29, 31 or 39). Without --metadata, only the advertised contract is verified.",
|
||||
"Usage: npm run verify:il2cpp:metadata -- [--metadata PATH]\nPATH is a caller-owned, unencrypted global-metadata.dat (version 24, 27, 29, 31 or 39). Without --metadata, only the MCP tool name is checked.",
|
||||
);
|
||||
process.exit(0);
|
||||
}
|
||||
@@ -178,4 +178,8 @@ await withVerifierCleanup(async () => {
|
||||
);
|
||||
}
|
||||
}, [() => client.close(), () => transport.close(), () => resources.close()]);
|
||||
console.log("All asserted IL2CPP metadata CLI/MCP checks passed");
|
||||
console.log(
|
||||
path === null
|
||||
? "IL2CPP metadata MCP tool presence check passed"
|
||||
: "All asserted IL2CPP metadata CLI/MCP checks passed",
|
||||
);
|
||||
@@ -68,7 +68,9 @@ try {
|
||||
});
|
||||
if (!decompiled.ok) throw new Error(decompiled.error.message);
|
||||
const text = decompiled.value.normalized_result.text;
|
||||
if (!text.includes(target.marker))
|
||||
if (text.trim().length === 0)
|
||||
throw new Error("Decompiled pseudo-code was empty");
|
||||
if (target.marker !== undefined && !text.includes(target.marker))
|
||||
throw new Error(
|
||||
"Decompiled pseudo-code did not contain the fixture marker",
|
||||
);
|
||||
@@ -83,7 +85,7 @@ try {
|
||||
open_project_observed: openRefusal === null,
|
||||
...(openRefusal === null ? {} : { open_project_refusal: openRefusal }),
|
||||
unit_count: units.value.normalized_result.units.length,
|
||||
decompiled_marker_observed: true,
|
||||
decompiled_marker_observed: target.marker !== undefined,
|
||||
};
|
||||
}, [
|
||||
async () => {
|
||||
@@ -110,7 +112,7 @@ async function resolveTarget(selected, directory) {
|
||||
return {
|
||||
path: "/engine-side/target",
|
||||
itemAddress: selected.itemAddress,
|
||||
marker: selected.marker ?? "",
|
||||
marker: selected.marker,
|
||||
};
|
||||
await writeFile(
|
||||
join(directory, "Hello.java"),
|
||||
@@ -153,6 +155,8 @@ function parseArguments(args) {
|
||||
values.set(flag, value);
|
||||
index += 1;
|
||||
}
|
||||
if (values.get("--marker") === "")
|
||||
throw new Error("--marker must not be empty");
|
||||
return {
|
||||
help: false,
|
||||
...(values.has("--item-address")
|
||||
|
||||
@@ -2,9 +2,10 @@ import assert from "node:assert/strict";
|
||||
import { createHash } from "node:crypto";
|
||||
import { mkdir, mkdtemp, readFile, writeFile } from "node:fs/promises";
|
||||
import { homedir, tmpdir } from "node:os";
|
||||
import { join, relative } from "node:path";
|
||||
import { isAbsolute, join, relative } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { TOOL_CONTRACTS } from "../dist/contracts/toolContracts.js";
|
||||
import { isPathWithinRoot } from "../dist/domain/localPath.js";
|
||||
import { createVerifierCommands } from "./lib/verify-commands.mjs";
|
||||
import { withVerifierCleanup } from "./lib/verifier-cleanup.mjs";
|
||||
import { saveVerifierCommandOutput } from "./lib/verifier-command-output.mjs";
|
||||
@@ -314,8 +315,8 @@ await withVerifierCleanup(async () => {
|
||||
parsed.plannedActions.every(
|
||||
(action) =>
|
||||
action.target === configPath ||
|
||||
action.target === skillDirectory ||
|
||||
action.target.startsWith(skillDirectory + "/"),
|
||||
(isAbsolute(action.target) &&
|
||||
isPathWithinRoot(skillDirectory, action.target)),
|
||||
),
|
||||
"Refuse setup writes outside the owned config/skill",
|
||||
);
|
||||
|
||||
@@ -2,10 +2,11 @@ import assert from "node:assert/strict";
|
||||
import { createHash } from "node:crypto";
|
||||
import { mkdir, mkdtemp, readFile, writeFile } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { isAbsolute, join, relative, sep } from "node:path";
|
||||
import { isAbsolute, join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import Ajv2020 from "ajv/dist/2020.js";
|
||||
import { TOOL_CONTRACTS } from "../dist/contracts/toolContracts.js";
|
||||
import { isPathWithinRoot } from "../dist/domain/localPath.js";
|
||||
import { createVerifierCommands } from "./lib/verify-commands.mjs";
|
||||
import { saveVerifierCommandOutput } from "./lib/verifier-command-output.mjs";
|
||||
import { withVerifierCleanup } from "./lib/verifier-cleanup.mjs";
|
||||
@@ -49,19 +50,15 @@ const skillDirectory = join(
|
||||
"skills",
|
||||
"reverse-engineer-anything",
|
||||
);
|
||||
const isManagedSkillTarget = (target) => {
|
||||
const value = relative(skillDirectory, target);
|
||||
return (
|
||||
value === "" ||
|
||||
(value !== ".." && !value.startsWith(`..${sep}`) && !isAbsolute(value))
|
||||
);
|
||||
};
|
||||
assert.equal(
|
||||
isManagedSkillTarget(`${skillDirectory}-sibling/AGENTS.md`),
|
||||
isPathWithinRoot(skillDirectory, `${skillDirectory}-sibling/AGENTS.md`),
|
||||
false,
|
||||
);
|
||||
assert.equal(
|
||||
isManagedSkillTarget(join(skillDirectory, "..", "outside", "AGENTS.md")),
|
||||
isPathWithinRoot(
|
||||
skillDirectory,
|
||||
join(skillDirectory, "..", "outside", "AGENTS.md"),
|
||||
),
|
||||
false,
|
||||
);
|
||||
let clientVersion = null;
|
||||
@@ -271,7 +268,10 @@ await withVerifierCleanup(async () => {
|
||||
);
|
||||
assert(
|
||||
parsed.plannedActions.every(
|
||||
(a) => a.target === configPath || isManagedSkillTarget(a.target),
|
||||
(a) =>
|
||||
a.target === configPath ||
|
||||
(isAbsolute(a.target) &&
|
||||
isPathWithinRoot(skillDirectory, a.target)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -266,7 +266,6 @@ await withVerifierCleanup(async () => {
|
||||
extractedPluginMcp: true,
|
||||
targetFree: true,
|
||||
targetLifecycle: true,
|
||||
boundedRegexBridge: true,
|
||||
update,
|
||||
mcpStartup,
|
||||
mcpModuleLoading,
|
||||
|
||||
@@ -9,9 +9,10 @@ import {
|
||||
symlink,
|
||||
} from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join, resolve, sep } from "node:path";
|
||||
import { isAbsolute, join, relative, resolve, sep } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { createHash } from "node:crypto";
|
||||
import { isPathWithinRoot } from "../dist/domain/localPath.js";
|
||||
import { createVerifierCommands } from "./lib/verify-commands.mjs";
|
||||
import { saveVerifierCommandOutput } from "./lib/verifier-command-output.mjs";
|
||||
import { withVerifierCleanup } from "./lib/verifier-cleanup.mjs";
|
||||
@@ -58,9 +59,10 @@ let clientVersion = null;
|
||||
let fixture;
|
||||
let passed = false;
|
||||
let evidenceSeen = false;
|
||||
let skillSeen = false;
|
||||
let finalSeen = false;
|
||||
let skillRequested = false;
|
||||
let skillCallId;
|
||||
let expectedSkillBody;
|
||||
let searchCallId;
|
||||
let analysisCallId;
|
||||
let artifactReadByAgent = false;
|
||||
@@ -133,8 +135,11 @@ await withVerifierCleanup(async () => {
|
||||
assert(
|
||||
result.plannedActions.every(
|
||||
(a) =>
|
||||
a.target.startsWith(profile + sep) ||
|
||||
a.target.startsWith(sharedSkills + sep),
|
||||
isAbsolute(a.target) &&
|
||||
((relative(profile, a.target) !== "" &&
|
||||
isPathWithinRoot(profile, a.target)) ||
|
||||
(relative(sharedSkills, a.target) !== "" &&
|
||||
isPathWithinRoot(sharedSkills, a.target))),
|
||||
),
|
||||
);
|
||||
await writeFile(
|
||||
@@ -162,6 +167,20 @@ await withVerifierCleanup(async () => {
|
||||
command: "unrelated-server",
|
||||
disabled: true,
|
||||
});
|
||||
const skillFile = join(
|
||||
sharedSkills,
|
||||
"reverse-engineer-anything",
|
||||
"SKILL.md",
|
||||
);
|
||||
const normalizedSkill = (await readFile(skillFile, "utf8"))
|
||||
.replace(/^\uFEFF/u, "")
|
||||
.replace(/\r\n|\r/gu, "\n");
|
||||
const frontmatter = /^---\n([\s\S]*?)\n---(?:\n|$)([\s\S]*)$/u.exec(
|
||||
normalizedSkill,
|
||||
);
|
||||
assert(frontmatter, "Installed skill must have authored frontmatter");
|
||||
expectedSkillBody = frontmatter[2].trim();
|
||||
assert(expectedSkillBody.length > 0);
|
||||
clientPath = baseEnvironment.PATH;
|
||||
if (reducedPath) {
|
||||
assert.equal(
|
||||
@@ -200,9 +219,6 @@ await withVerifierCleanup(async () => {
|
||||
directory: lab,
|
||||
prefix: `${client}-${mode}`,
|
||||
onRequest: async ({ body, tools, requestIndex }) => {
|
||||
skillSeen ||= JSON.stringify(body.messages ?? []).includes(
|
||||
"reverse-engineer-anything",
|
||||
);
|
||||
const messages = (body.messages ?? []).map((message) => ({
|
||||
role: message.role,
|
||||
callId: message.tool_call_id,
|
||||
@@ -213,14 +229,14 @@ await withVerifierCleanup(async () => {
|
||||
.map((part) => part.text ?? "")
|
||||
.join("\n"),
|
||||
}));
|
||||
const messageText = messages.map((message) => message.text).join("\n");
|
||||
const toolResponseText = (callId) =>
|
||||
messages.find(
|
||||
(message) => message.role === "tool" && message.callId === callId,
|
||||
)?.text ?? "";
|
||||
selectedSkillLoaded ||= messageText.includes(
|
||||
join(sharedSkills, "reverse-engineer-anything"),
|
||||
);
|
||||
const expectedSkill = `Base directory for this skill: ${join(sharedSkills, "reverse-engineer-anything")}\nImportant: ALWAYS resolve absolute paths from this base directory when working with skills.\n\n${expectedSkillBody}\n`;
|
||||
selectedSkillLoaded ||=
|
||||
skillCallId !== undefined &&
|
||||
toolResponseText(skillCallId).includes(expectedSkill);
|
||||
const toolText = JSON.stringify(
|
||||
(body.messages ?? []).filter((m) => m.role === "tool"),
|
||||
);
|
||||
@@ -298,6 +314,7 @@ await withVerifierCleanup(async () => {
|
||||
} else if (skill && !skillRequested) {
|
||||
skillRequested = true;
|
||||
message = call(skill.name, { skill: "reverse-engineer-anything" });
|
||||
skillCallId = message.tool_calls[0].id;
|
||||
} else if (persisted && shell) {
|
||||
artifactPath = persisted[1];
|
||||
assert(
|
||||
|
||||
@@ -32,8 +32,6 @@ const runCli = async (...arguments_) => {
|
||||
return JSON.parse(stdout);
|
||||
};
|
||||
|
||||
const projectRoot = await mkdtemp(join(tmpdir(), "rea-cutter-smoke-"));
|
||||
const projectPath = join(projectRoot, "smoke.rzdb");
|
||||
const rizinQuote = (value) =>
|
||||
`"${value.replaceAll("\\", "\\\\").replaceAll('"', '\\"')}"`;
|
||||
const unavailableAnalysis = {
|
||||
@@ -44,16 +42,21 @@ const unavailableAnalysis = {
|
||||
},
|
||||
};
|
||||
const expectedPlatform = {
|
||||
linux: "linux",
|
||||
Linux: "linux",
|
||||
macOS: "darwin",
|
||||
Windows: "win32",
|
||||
}[process.env.EXPECTED_PLATFORM];
|
||||
if (expectedPlatform !== undefined)
|
||||
if (process.env.EXPECTED_PLATFORM !== undefined) {
|
||||
assert.ok(expectedPlatform, "Unrecognized Cutter runner platform.");
|
||||
assert.equal(
|
||||
process.platform,
|
||||
expectedPlatform,
|
||||
"Cutter runner did not match its declared platform.",
|
||||
);
|
||||
}
|
||||
const projectRoot = await mkdtemp(join(tmpdir(), "rea-cutter-smoke-"));
|
||||
const projectPath = join(projectRoot, "smoke.rzdb");
|
||||
try {
|
||||
const discovery = await runCli("list-cutter-sessions");
|
||||
assert.equal(
|
||||
|
||||
@@ -79,7 +79,11 @@ const output = {
|
||||
windows: capture.windows,
|
||||
verified:
|
||||
capture.coverage.status === "partial_attach" &&
|
||||
capture.actions.every(({ status }) => status === "completed") &&
|
||||
capture.actions.length === input.actions.length &&
|
||||
capture.actions.every(
|
||||
({ step_id, status }, index) =>
|
||||
step_id === input.actions[index].step_id && status === "completed",
|
||||
) &&
|
||||
capture.actions.some(
|
||||
({ kind, window_index }) =>
|
||||
kind === "renderer-reload" && window_index === 1,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { hasTypedSwitchEvidence } from "./verify-real-ghidra-switch-assertions.mjs";
|
||||
|
||||
import { access } from "node:fs/promises";
|
||||
import { lstat } from "node:fs/promises";
|
||||
|
||||
import { parseBinaryTarget } from "../dist/application/BinaryTargetResolver.js";
|
||||
import { SUPPORTED_GHIDRA_VERSION } from "../dist/ghidra/GhidraReleasePolicy.js";
|
||||
@@ -437,10 +437,11 @@ export function summary(observed) {
|
||||
|
||||
async function exists(path) {
|
||||
try {
|
||||
await access(path);
|
||||
await lstat(path);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
} catch (cause) {
|
||||
if (cause?.code === "ENOENT") return false;
|
||||
throw cause;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -415,7 +415,7 @@ async function verifyRequestCancellationAndTimeout(client, entry) {
|
||||
}
|
||||
|
||||
async function verifyConcurrentRequests(client, entry, leaf) {
|
||||
const concurrent = await Promise.all([
|
||||
await Promise.all([
|
||||
functionCall(client, "procedure_info", {
|
||||
document: null,
|
||||
procedure: entry.address,
|
||||
@@ -425,9 +425,7 @@ async function verifyConcurrentRequests(client, entry, leaf) {
|
||||
address: leaf.address,
|
||||
}),
|
||||
]);
|
||||
if (concurrent.length !== 2)
|
||||
throw new Error("Ghidra serial request queue lost a concurrent result");
|
||||
return "serialized-two-results";
|
||||
return "two-concurrent-requests-completed";
|
||||
}
|
||||
|
||||
/** Prove dense jump-table completeness on the optimized jump-table fixture. */
|
||||
|
||||
@@ -375,8 +375,13 @@ try {
|
||||
JSON.stringify(report, null, 2) + "\n",
|
||||
);
|
||||
}
|
||||
const outcome = report.typed_failure
|
||||
? "MCP refusal"
|
||||
: mode === "default-native"
|
||||
? "native load image without metadata recovery"
|
||||
: "MCP metadata";
|
||||
console.log(
|
||||
`PASS real Ghidra NativeAOT ${oracle.runtime}/${mode}: MCP metadata, source identity and owned cleanup${report?.compute ? ", independent type/slot oracle and pseudocode" : ""}${report?.cli_metadata ? ", equivalent CLI" : ""}`,
|
||||
`PASS real Ghidra NativeAOT ${oracle.runtime}/${mode}: ${outcome}, source unchanged and runtime directory empty${report?.compute ? ", independent type/slot oracle and pseudocode" : ""}${report?.cli_metadata ? ", equivalent CLI" : ""}`,
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(stderr);
|
||||
|
||||
@@ -55,7 +55,8 @@ try {
|
||||
if (
|
||||
report.missing.length ||
|
||||
report.unexpected.length ||
|
||||
report.duplicates.length
|
||||
report.duplicates.length ||
|
||||
report.wrongProjects.length
|
||||
) {
|
||||
console.error(JSON.stringify(report, null, 2));
|
||||
process.exitCode = 1;
|
||||
|
||||
@@ -41,10 +41,18 @@ const [leftPath, rightPath] = await Promise.all([
|
||||
]);
|
||||
if (leftPath === rightPath)
|
||||
throw new Error("--left and --right must resolve to distinct paths");
|
||||
const [left, right] = await Promise.all([
|
||||
const analyses = await Promise.allSettled([
|
||||
analyze(options.left),
|
||||
analyze(options.right),
|
||||
]);
|
||||
const failures = analyses.filter((result) => result.status === "rejected");
|
||||
if (failures.length === 1) throw failures[0].reason;
|
||||
if (failures.length > 1)
|
||||
throw new AggregateError(
|
||||
failures.map((result) => result.reason),
|
||||
"Application analyses failed",
|
||||
);
|
||||
const [left, right] = analyses.map((result) => result.value);
|
||||
const comparison = compareApplicationVersionsEvidence({ left, right });
|
||||
if (!comparison.ok) throw comparison.error;
|
||||
const comparisonResult = comparison.value.result;
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { readFile, realpath } from "node:fs/promises";
|
||||
import { realpath } from "node:fs/promises";
|
||||
import { parseArgs } from "node:util";
|
||||
|
||||
import { readStableArtifact } from "../../../dist/artifacts/readStableArtifact.js";
|
||||
import { ArtifactReaderFailure } from "../../../dist/artifacts/ArtifactReader.js";
|
||||
import { ArtifactResourceScope } from "../../../dist/artifacts/ArtifactResourceScope.js";
|
||||
import {
|
||||
ArtifactFileCleanupFailure,
|
||||
artifactFileCompletion,
|
||||
artifactFilePrimaryFacts,
|
||||
} from "../../../dist/artifacts/ArtifactFileOperation.js";
|
||||
|
||||
import { reconcileJavaScriptRuntimeEvidence } from "../../../dist/application/javascript/JavaScriptRuntimeReconciliationService.js";
|
||||
import { analyzeJavaScriptApplicationWithOwnedScope } from "../../lib/javascript-application-analysis.mjs";
|
||||
import {
|
||||
@@ -52,9 +61,11 @@ for (const encoded of options.mapping) {
|
||||
const mapping = parseMapping(encoded, staticLayers.length);
|
||||
staticLayers[mapping.layer].runtime_mappings.push(mapping.value);
|
||||
}
|
||||
const runtimeObservations = (
|
||||
await Promise.all(options["runtime-evidence"].map(readEvidenceFile))
|
||||
).flat();
|
||||
const runtimeObservations = [];
|
||||
for (const path of options["runtime-evidence"]) {
|
||||
const observations = await readEvidenceFile(path);
|
||||
for (const observation of observations) runtimeObservations.push(observation);
|
||||
}
|
||||
const reconciled = reconcileJavaScriptRuntimeEvidence({
|
||||
static_layers: staticLayers,
|
||||
runtime_observations: runtimeObservations,
|
||||
@@ -95,10 +106,47 @@ process.stdout.write(
|
||||
);
|
||||
|
||||
async function readEvidenceFile(path) {
|
||||
const bytes = await readFile(await realpath(path));
|
||||
if (bytes.byteLength > 64 * 1024 * 1024)
|
||||
throw new Error(`Runtime Evidence exceeds 64 MiB: ${path}`);
|
||||
const value = JSON.parse(bytes.toString("utf8"));
|
||||
const scope = new ArtifactResourceScope();
|
||||
let outcome;
|
||||
try {
|
||||
outcome = {
|
||||
ok: true,
|
||||
value: await readStableArtifact(
|
||||
await realpath(path),
|
||||
64 * 1024 * 1024,
|
||||
undefined,
|
||||
undefined,
|
||||
scope,
|
||||
),
|
||||
};
|
||||
} catch (cause) {
|
||||
outcome = { ok: false, cause };
|
||||
}
|
||||
try {
|
||||
await scope.close();
|
||||
} catch (cleanupCause) {
|
||||
if (!outcome.ok)
|
||||
throw new AggregateError(
|
||||
[outcome.cause, cleanupCause],
|
||||
"Verifier cleanup failed",
|
||||
{ cause: outcome.cause },
|
||||
);
|
||||
throw cleanupCause;
|
||||
}
|
||||
if (!outcome.ok && outcome.cause instanceof ArtifactFileCleanupFailure) {
|
||||
const completion = artifactFileCompletion(outcome.cause);
|
||||
outcome =
|
||||
completion === undefined
|
||||
? { ok: false, cause: artifactFilePrimaryFacts(outcome.cause).cause }
|
||||
: { ok: true, value: completion.value };
|
||||
}
|
||||
if (!outcome.ok) {
|
||||
const cause = outcome.cause;
|
||||
if (cause instanceof ArtifactReaderFailure && cause.reason === "limit")
|
||||
throw new Error(`Runtime Evidence exceeds 64 MiB: ${path}`, { cause });
|
||||
throw cause;
|
||||
}
|
||||
const value = JSON.parse(outcome.value.bytes.toString("utf8"));
|
||||
return Array.isArray(value) ? value : [value];
|
||||
}
|
||||
|
||||
|
||||
@@ -114,6 +114,38 @@ it.each([
|
||||
},
|
||||
);
|
||||
|
||||
it("retains completed scenario Evidence when cancellation arrives during capture", async () => {
|
||||
const caller = new AbortController();
|
||||
const reason = { requested: "stop after capture" };
|
||||
const provider: BrowserScenarioCapturePort = {
|
||||
identity: () => scenarioIdentity,
|
||||
captureScenario: async (received, options) => {
|
||||
expect(received).toBe(scenario);
|
||||
expect(options?.signal).toBe(caller.signal);
|
||||
caller.abort(reason);
|
||||
return ok(capture);
|
||||
},
|
||||
};
|
||||
const result = await captureBrowserScenario(provider, scenario, {
|
||||
signal: caller.signal,
|
||||
});
|
||||
if (result.ok) throw new Error("Expected late cancellation");
|
||||
expect(result.error).toBeInstanceOf(AnalysisCancelledError);
|
||||
expect(result.error.cause).toBe(reason);
|
||||
expect(parseEvidence(result.error.partialObservation)).toMatchObject({
|
||||
operation: "capture_browser_scenario",
|
||||
provider: scenarioIdentity,
|
||||
confidence: "observed",
|
||||
authority: "controlled-replay",
|
||||
parameters: {
|
||||
browser: scenario.browser,
|
||||
start_url: scenario.start_url,
|
||||
actions: scenario.actions,
|
||||
},
|
||||
normalized_result: capture,
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps startup cancellation and its incomplete capture", async () => {
|
||||
const reason = { requested: "stop during initial navigation" };
|
||||
const completed = scriptScenarioFixture();
|
||||
|
||||
@@ -139,6 +139,13 @@ it("retains completed execution Evidence on late abort and keeps invalid output
|
||||
expect(completed.error.cause).toBe(cancellationReason);
|
||||
expect(parseEvidence(completed.error.partialObservation)).toMatchObject({
|
||||
operation: "observe_web_execution",
|
||||
provider: { id: "web-runtime", name: "Runtime fixture", version: "1" },
|
||||
parameters: input,
|
||||
confidence: "observed",
|
||||
authority: "external-service",
|
||||
limitations: expect.arrayContaining(
|
||||
webExecutionFixture(input.target_id, input.observation_ms).limitations,
|
||||
),
|
||||
normalized_result: webExecutionFixture(
|
||||
input.target_id,
|
||||
input.observation_ms,
|
||||
|
||||
@@ -220,7 +220,8 @@ it("honors cancellation both before acquisition and across decoder completion",
|
||||
if (!response.ok) expect(response.error.cause).toBe(firstReason);
|
||||
const controller = new AbortController();
|
||||
const lateReason = { caller: "after source-map provider completion" };
|
||||
const late = await service(undefined, {
|
||||
const fixture = webSourceLocationFixture();
|
||||
const late = await service(fixture, {
|
||||
trace: (input) => {
|
||||
controller.abort(lateReason);
|
||||
return decoder.trace(input);
|
||||
@@ -232,6 +233,28 @@ it("honors cancellation both before acquisition and across decoder completion",
|
||||
expect(parseEvidence(late.error.partialObservation)).toMatchObject({
|
||||
operation: "trace_web_source_location",
|
||||
confidence: "derived",
|
||||
authority: "historical-reference",
|
||||
subject: {
|
||||
local_path: fixture.sourceFile.path,
|
||||
digest: { sha256: fixture.sourceFile.sha256 },
|
||||
},
|
||||
parameters: args,
|
||||
raw_result: { source_map_text: fixture.sourceMap.text },
|
||||
normalized_result: {
|
||||
source_map: {
|
||||
...fixture.sourceMap.file,
|
||||
url: args.source_map.url,
|
||||
association: "caller-selected",
|
||||
},
|
||||
lookup: { requested: args.generated_position },
|
||||
source_authenticity: "unknown",
|
||||
execution: "unknown",
|
||||
},
|
||||
locations: [
|
||||
fixture.sourceFile.path,
|
||||
fixture.manifestFile.path,
|
||||
fixture.sourceMap.file.path,
|
||||
].map((path) => ({ kind: "artifact-path", path })),
|
||||
});
|
||||
|
||||
const malformedDecoder: WebSourceMapPort = {
|
||||
|
||||
@@ -30,11 +30,12 @@ const report: ElectronFuseReport = {
|
||||
|
||||
it("retains completed fuse Evidence when the provider completes after abort", async () => {
|
||||
const controller = new AbortController();
|
||||
const reason = { requested: "stop after fuse inspection" };
|
||||
const result = await new ElectronFuseService({
|
||||
identity,
|
||||
close: async () => {},
|
||||
inspect: async () => {
|
||||
controller.abort();
|
||||
controller.abort(reason);
|
||||
return ok(report);
|
||||
},
|
||||
}).inspect(
|
||||
@@ -44,8 +45,19 @@ it("retains completed fuse Evidence when the provider completes after abort", as
|
||||
expect(result.ok).toBe(false);
|
||||
if (!result.ok) {
|
||||
expect(result.error._tag).toBe("AnalysisCancelledError");
|
||||
expect(result.error.cause).toBe(reason);
|
||||
expect(parseEvidence(result.error.partialObservation)).toMatchObject({
|
||||
operation: "inspect_electron_fuses",
|
||||
provider: identity,
|
||||
subject: {
|
||||
local_path: report.artifact.resolved_path,
|
||||
digest: { sha256: report.artifact.sha256 },
|
||||
format: report.artifact.format,
|
||||
},
|
||||
parameters: { path: report.artifact.selected_path },
|
||||
confidence: "observed",
|
||||
limitations: expect.arrayContaining(report.limitations),
|
||||
locations: [{ kind: "artifact-path", path: report.artifact.selected_path }],
|
||||
normalized_result: report,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -150,6 +150,7 @@ it("returns a typed output error for malformed Electron target output after abor
|
||||
|
||||
it("retains completed target facts when cancellation arrives before Evidence publication", async () => {
|
||||
const controller = new AbortController();
|
||||
const reason = { requested: "stop after listing targets" };
|
||||
const targets = electronTargetListSchema.parse({
|
||||
browser: inspection.browser,
|
||||
targets: [],
|
||||
@@ -159,7 +160,7 @@ it("retains completed target facts when cancellation arrives before Evidence pub
|
||||
const provider: ElectronObservationPort = {
|
||||
identity: () => identity,
|
||||
listTargets: async () => {
|
||||
controller.abort();
|
||||
controller.abort(reason);
|
||||
return ok(targets);
|
||||
},
|
||||
inspectPage: async () =>
|
||||
@@ -173,8 +174,22 @@ it("retains completed target facts when cancellation arrives before Evidence pub
|
||||
expect(result.ok).toBe(false);
|
||||
if (!result.ok) {
|
||||
expect(result.error).toBeInstanceOf(AnalysisCancelledError);
|
||||
expect(result.error.cause).toBe(reason);
|
||||
expect(parseEvidence(result.error.partialObservation)).toMatchObject({
|
||||
operation: "list_electron_targets",
|
||||
provider: identity,
|
||||
parameters: { cdp_endpoint: input.cdp_endpoint },
|
||||
confidence: "observed",
|
||||
authority: "external-service",
|
||||
environment: {
|
||||
id: `${inspection.browser.product}@${inspection.browser.revision}`,
|
||||
platform: "unknown",
|
||||
architecture: "unknown",
|
||||
isolation: "none",
|
||||
},
|
||||
limitations: expect.arrayContaining([
|
||||
"Browser host platform and architecture were not observed.",
|
||||
]),
|
||||
normalized_result: targets,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { setImmediate } from "node:timers/promises";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { ArtifactReaderFailure } from "../../artifacts/ArtifactReader.js";
|
||||
import { completeJavaScriptAnalysisSteps } from "./JavaScriptAnalysisControl.js";
|
||||
|
||||
describe("JavaScript cooperative computation lifecycle", () => {
|
||||
@@ -15,9 +16,19 @@ describe("JavaScript cooperative computation lifecycle", () => {
|
||||
}
|
||||
}
|
||||
const cancellation = setImmediate().then(() => controller.abort(reason));
|
||||
await expect(
|
||||
completeJavaScriptAnalysisSteps(work(), controller.signal),
|
||||
).rejects.toMatchObject({ reason: "cancelled", cause: reason });
|
||||
const failure = await completeJavaScriptAnalysisSteps(
|
||||
work(),
|
||||
controller.signal,
|
||||
).then(
|
||||
() => {
|
||||
throw new Error("Expected cancellation");
|
||||
},
|
||||
(cause: unknown) => cause,
|
||||
);
|
||||
expect(failure).toBeInstanceOf(ArtifactReaderFailure);
|
||||
if (!(failure instanceof ArtifactReaderFailure)) throw failure;
|
||||
expect(failure.reason).toBe("cancelled");
|
||||
expect(failure.cause).toBe(reason);
|
||||
await cancellation;
|
||||
expect(released).toBe(true);
|
||||
});
|
||||
@@ -32,9 +43,19 @@ describe("JavaScript cooperative computation lifecycle", () => {
|
||||
yield;
|
||||
return 42;
|
||||
}
|
||||
await expect(
|
||||
completeJavaScriptAnalysisSteps(work(), controller.signal),
|
||||
).rejects.toMatchObject({ reason: "cancelled", cause: reason });
|
||||
const failure = await completeJavaScriptAnalysisSteps(
|
||||
work(),
|
||||
controller.signal,
|
||||
).then(
|
||||
() => {
|
||||
throw new Error("Expected cancellation");
|
||||
},
|
||||
(cause: unknown) => cause,
|
||||
);
|
||||
expect(failure).toBeInstanceOf(ArtifactReaderFailure);
|
||||
if (!(failure instanceof ArtifactReaderFailure)) throw failure;
|
||||
expect(failure.reason).toBe("cancelled");
|
||||
expect(failure.cause).toBe(reason);
|
||||
expect(started).toBe(false);
|
||||
expect(await completeJavaScriptAnalysisSteps(work())).toBe(42);
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { AnalysisCapabilityUnavailableError } from "../../domain/analysisErrorCore.js";
|
||||
import { err, ok } from "../../domain/result.js";
|
||||
import { parseEvidence } from "../../domain/evidence.js";
|
||||
import type { JavaScriptRecoveryResult } from "../../domain/javascript/javascriptRecovery.js";
|
||||
import { createAnalysisExecution } from "../AnalysisProvider.js";
|
||||
import { JavaScriptRecoveryService } from "./JavaScriptRecoveryService.js";
|
||||
@@ -179,7 +180,17 @@ it("returns cancellation with the validated Evidence when the caller aborts afte
|
||||
if (result.ok) throw new Error("expected cancellation");
|
||||
expect(result.error._tag).toBe("AnalysisCancelledError");
|
||||
expect(result.error.cause).toBe(reason);
|
||||
expect(result.error.partialObservation).toMatchObject({
|
||||
expect(parseEvidence(result.error.partialObservation)).toMatchObject({
|
||||
operation: "recover_javascript_sources",
|
||||
provider: providerIdentity,
|
||||
parameters: input,
|
||||
subject: {
|
||||
local_path: artifact.path,
|
||||
digest: { sha256: artifact.sha256 },
|
||||
format: "javascript",
|
||||
},
|
||||
confidence: "derived",
|
||||
authority: "shipped-artifact",
|
||||
normalized_result: recoveryResult(),
|
||||
});
|
||||
});
|
||||
@@ -41,36 +41,6 @@ it("managed reconstruction reports UTF-8 byte totals while preserving UTF-16 tex
|
||||
});
|
||||
});
|
||||
|
||||
it("managed reconstruction rejects text hash drift", () => {
|
||||
const rejected = resolveManagedReconstructionImport({
|
||||
...exampleInput(),
|
||||
methods: [
|
||||
{
|
||||
...exampleMethod(),
|
||||
reconstruction: {
|
||||
...exampleMethod().reconstruction,
|
||||
text_sha256: "0".repeat(64),
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(rejected).toMatchObject({
|
||||
ok: false,
|
||||
error: {
|
||||
_tag: "AnalysisInputError",
|
||||
operation: "import_managed_reconstruction",
|
||||
issues: [
|
||||
{
|
||||
path: ["methods", 0, "reconstruction", "text_sha256"],
|
||||
reason: "invalid_value",
|
||||
message: expect.stringContaining("Reconstruction text hash mismatch"),
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
if (!rejected.ok) expect(rejected.error).toBeInstanceOf(AnalysisInputError);
|
||||
});
|
||||
|
||||
it("managed reconstruction rejects stale method locks", () => {
|
||||
const staleInput = {
|
||||
...exampleInput(),
|
||||
@@ -234,13 +204,15 @@ describe("managed reconstruction input diagnostics", () => {
|
||||
});
|
||||
expect(result.ok).toBe(false);
|
||||
if (result.ok) return;
|
||||
expect(result.error).toBeInstanceOf(AnalysisInputError);
|
||||
expect(result.error).toMatchObject({
|
||||
_tag: "AnalysisInputError",
|
||||
operation: "import_managed_reconstruction",
|
||||
issues: [
|
||||
{
|
||||
path: ["methods", 0, "reconstruction", "text_sha256"],
|
||||
reason: "invalid_value",
|
||||
message: expect.stringMatching(/text hash mismatch/u),
|
||||
message: expect.stringContaining("Reconstruction text hash mismatch"),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
@@ -181,7 +181,7 @@ it("retains both source spellings and the native failure in a destination collis
|
||||
expect(collision?.cause).toBe(cause);
|
||||
});
|
||||
|
||||
it.each([["app.js"], ["app.js", "App.js"], ["other.js"]])(
|
||||
it.each([["app.js", "App.js"], ["other.js"]])(
|
||||
"does not invent a destination alias from listing %j",
|
||||
(...names) => {
|
||||
expect(destinationCaseCollision("res/app.js", names)).toBeUndefined();
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
import { mkdtemp, mkdir, rm, writeFile } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { build } from "plist";
|
||||
import { afterEach, expect, it } from "vitest";
|
||||
|
||||
import { ArtifactProvider } from "./ArtifactProvider.js";
|
||||
import type { ResolvedAppBundleResourceRoot } from "../application/AppBundleExecutable.js";
|
||||
|
||||
const temporaryDirectories: string[] = [];
|
||||
|
||||
afterEach(async () => {
|
||||
await Promise.all(
|
||||
temporaryDirectories
|
||||
.splice(0)
|
||||
.map((path) => rm(path, { recursive: true, force: true })),
|
||||
);
|
||||
});
|
||||
|
||||
it("returns keyed archive evidence with root provenance and unknown executable identity", async () => {
|
||||
const bundlePath = await mkdtemp(join(tmpdir(), "rea-resource-reader-"));
|
||||
temporaryDirectories.push(bundlePath);
|
||||
await mkdir(join(bundlePath, "Resources"));
|
||||
await writeFile(
|
||||
join(bundlePath, "Info.plist"),
|
||||
'<?xml version="1.0"?><plist><dict/></plist>',
|
||||
);
|
||||
const archive = build({
|
||||
$archiver: "NSKeyedArchiver",
|
||||
$version: 100000,
|
||||
$objects: ["$null", "sample"],
|
||||
$top: { root: { UID: 1 } },
|
||||
});
|
||||
await writeFile(join(bundlePath, "Resources", "Archive.plist"), archive);
|
||||
const root: ResolvedAppBundleResourceRoot = {
|
||||
callerPath: bundlePath,
|
||||
canonicalPath: bundlePath,
|
||||
infoPlistPath: join(bundlePath, "Info.plist"),
|
||||
layout: "root",
|
||||
executableSha256: null,
|
||||
};
|
||||
const provider = new ArtifactProvider({}, "linux");
|
||||
const client = provider.createBundleResourceClient(root);
|
||||
const execution = await client.execute("inspect_keyed_archive", {
|
||||
path: "Resources/Archive.plist",
|
||||
});
|
||||
expect(execution.ok).toBe(true);
|
||||
if (execution.ok) {
|
||||
expect(execution.value.result).toMatchObject({
|
||||
target_sha256: null,
|
||||
resource_root: {
|
||||
caller_path: bundlePath,
|
||||
canonical_path: bundlePath,
|
||||
executable_sha256: null,
|
||||
},
|
||||
archive_path: "Resources/Archive.plist",
|
||||
});
|
||||
}
|
||||
expect((await client.close()).ok).toBe(true);
|
||||
});
|
||||
@@ -55,6 +55,41 @@ const firstEntry = async (reader: FitArtifactReader) => {
|
||||
return entry;
|
||||
};
|
||||
|
||||
it.each(["close", "cancel"] as const)(
|
||||
"refuses to finish a paused FIT inventory after %s",
|
||||
async (action) => {
|
||||
const reader = new FitArtifactReader(
|
||||
await writeFit("paused-inventory.itb", FIT_EXTERNAL),
|
||||
);
|
||||
const controller = new AbortController();
|
||||
const reason = { stage: "after final entry" };
|
||||
const iterator = reader.entries(controller.signal)[Symbol.asyncIterator]();
|
||||
try {
|
||||
expect((await iterator.next()).done).toBe(false);
|
||||
expect((await iterator.next()).done).toBe(false);
|
||||
if (action === "close") await reader.close();
|
||||
else controller.abort(reason);
|
||||
const failure: unknown = await iterator.next().then(
|
||||
() => {
|
||||
throw new Error("Expected the paused inventory to fail");
|
||||
},
|
||||
(cause: unknown) => cause,
|
||||
);
|
||||
expect(failure).toMatchObject(
|
||||
action === "close"
|
||||
? { reason: "unavailable" }
|
||||
: { reason: "cancelled" },
|
||||
);
|
||||
if (action === "cancel") {
|
||||
if (!(failure instanceof ArtifactReaderFailure)) throw failure;
|
||||
expect(failure.cause).toBe(reason);
|
||||
}
|
||||
} finally {
|
||||
await reader.close();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
it.each([
|
||||
["embedded", FIT_EMBEDDED, "embedded"],
|
||||
["external", FIT_EXTERNAL, "data-offset"],
|
||||
|
||||
@@ -82,6 +82,7 @@ export class FitArtifactReader implements ArtifactReader {
|
||||
this.#assertOpen(signal);
|
||||
yield copyArtifactEntry(entry);
|
||||
}
|
||||
this.#assertOpen(signal);
|
||||
}
|
||||
|
||||
async open(entry: ArtifactEntry, signal?: AbortSignal): Promise<Readable> {
|
||||
|
||||
@@ -184,21 +184,6 @@ describe("GodotPckArtifactReader", () => {
|
||||
},
|
||||
);
|
||||
|
||||
it("preserves declared MD5 for the shared inventory integrity verifier", async () => {
|
||||
const bytes = buildGodotPck(3, [
|
||||
{ path: "a.gd", data: "real", md5: Buffer.alloc(16, 1) },
|
||||
]);
|
||||
await withReader(bytes, async (reader) => {
|
||||
const [entry] = await list(reader);
|
||||
if (entry === undefined) throw new Error("expected one entry");
|
||||
expect(entry.declaredIntegrity).toEqual({
|
||||
algorithm: "md5",
|
||||
digest: "01".repeat(16),
|
||||
});
|
||||
expect(await text(await reader.open(entry))).toBe("real");
|
||||
});
|
||||
});
|
||||
|
||||
it("reports encrypted members without reading them", async () => {
|
||||
const bytes = buildGodotPck(3, [
|
||||
{ path: "secret.gd", data: "x", flags: 1 },
|
||||
|
||||
@@ -10,6 +10,8 @@ import { buildUImage, buildMultiPayload } from "../../tests/fixtures/uimage.js";
|
||||
import {
|
||||
parseUImageHeaderFields,
|
||||
parseMultiImageTable,
|
||||
UIMAGE_HEADER_SIZE,
|
||||
UIMAGE_MULTI_TABLE_WINDOW,
|
||||
} from "../domain/uimageFormat.js";
|
||||
|
||||
const fixture = async (bytes: Buffer): Promise<string> => {
|
||||
@@ -110,7 +112,31 @@ it("admits a multi-image count before scanning the remaining payload", async ()
|
||||
reason: "limit",
|
||||
boundary: "capacity",
|
||||
});
|
||||
expect(reads).toHaveBeenCalledTimes(2); // header and bounded table, no payload scan
|
||||
expect(reads.mock.calls.length).toBeGreaterThan(0);
|
||||
for (const call of reads.mock.calls) {
|
||||
const [, , length, position] = call as unknown as [
|
||||
Buffer,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
];
|
||||
expect(Number.isSafeInteger(position)).toBe(true);
|
||||
expect(Number.isSafeInteger(length)).toBe(true);
|
||||
expect(position).toBeGreaterThanOrEqual(0);
|
||||
expect(length).toBeGreaterThanOrEqual(0);
|
||||
if (length > 0)
|
||||
expect(position + length).toBeLessThanOrEqual(
|
||||
UIMAGE_HEADER_SIZE + UIMAGE_MULTI_TABLE_WINDOW,
|
||||
);
|
||||
}
|
||||
const results = await Promise.all(
|
||||
reads.mock.results.map(
|
||||
({ value }) => value as Promise<{ bytesRead: number }>,
|
||||
),
|
||||
);
|
||||
expect(
|
||||
results.reduce((sum, result) => sum + result.bytesRead, 0),
|
||||
).toBeLessThanOrEqual(UIMAGE_HEADER_SIZE + UIMAGE_MULTI_TABLE_WINDOW);
|
||||
} finally {
|
||||
await reader.close();
|
||||
reads.mockRestore();
|
||||
|
||||
@@ -5,7 +5,8 @@ import {
|
||||
pythonPycHeader,
|
||||
} from "../../python/PythonPyc.fixture.js";
|
||||
|
||||
import { PYINSTALLER_COOKIE_MAGIC } from "./CArchiveLayout.js";
|
||||
// PyInstaller CArchiveWriter's on-disk cookie signature, independent of REA's reader.
|
||||
const CARCHIVE_COOKIE_MAGIC = Buffer.from("4d45490c0b0a0b0e", "hex");
|
||||
|
||||
/** Final CPython 3.9 bytecode magic, little-endian with the CR LF trailer. */
|
||||
export const PYTHON_39_MAGIC = Buffer.from("610d0d0a", "hex");
|
||||
@@ -172,7 +173,7 @@ export const cArchiveBytecodeExecutable = () => {
|
||||
/** Cookie whose package range starts before the file. */
|
||||
export const cookieRangeOutsideFile = (): Buffer => {
|
||||
const cookie = Buffer.alloc(88);
|
||||
PYINSTALLER_COOKIE_MAGIC.copy(cookie, 0);
|
||||
CARCHIVE_COOKIE_MAGIC.copy(cookie, 0);
|
||||
cookie.writeUInt32BE(200, 8);
|
||||
cookie.writeUInt32BE(80, 12);
|
||||
cookie.writeUInt32BE(32, 16);
|
||||
@@ -255,7 +256,7 @@ const cookieBytes = (input: {
|
||||
}): Buffer => {
|
||||
const size = input.layout === "2.1" ? 88 : 24;
|
||||
const bytes = Buffer.alloc(size);
|
||||
PYINSTALLER_COOKIE_MAGIC.copy(bytes, 0);
|
||||
CARCHIVE_COOKIE_MAGIC.copy(bytes, 0);
|
||||
bytes.writeUInt32BE(input.tocOffset + input.tocLength + size, 8);
|
||||
bytes.writeUInt32BE(input.tocOffset, 12);
|
||||
bytes.writeUInt32BE(input.tocLength, 16);
|
||||
|
||||
@@ -909,67 +909,6 @@ it("retains the descriptor owner when admission cleanup fails", async () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("inventories and extracts a Windows member name under its canonical path", async () => {
|
||||
const storedName = String.raw`pkg\data.bin`;
|
||||
const pyzName = String.raw`res\icon.bin`;
|
||||
const payload = Buffer.from("nested-bytes");
|
||||
const icon = Buffer.from("icon-bytes");
|
||||
const path = await writeArchive(
|
||||
"windows",
|
||||
cArchiveExecutable({
|
||||
carrier: "pe",
|
||||
layout: "2.1",
|
||||
pythonVersion: 309,
|
||||
pythonLibrary: "python39.dll",
|
||||
members: [
|
||||
{ name: storedName, typeCode: "x", payload, compress: false },
|
||||
{
|
||||
name: "PYZ.pyz",
|
||||
typeCode: "z",
|
||||
payload: pyzArchive({
|
||||
magic: PYTHON_39_MAGIC,
|
||||
modules: [{ name: pyzName, typeCode: 2, body: icon }],
|
||||
}),
|
||||
compress: false,
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
const snapshot = await scanCanonicalArtifactInventory(path);
|
||||
const member = snapshot.occurrences.find(
|
||||
(occurrence) => occurrence.logical_path === "pkg/data.bin",
|
||||
);
|
||||
const resource = snapshot.occurrences.find(
|
||||
(occurrence) => occurrence.logical_path === "PYZ.pyz/res/icon.bin",
|
||||
);
|
||||
expect(member?.format_metadata).toMatchObject({ source_name: storedName });
|
||||
expect(resource?.format_metadata).toMatchObject({ source_name: pyzName });
|
||||
expect(member?.source_path).toBe(storedName);
|
||||
expect(resource?.source_path).toBe(pyzName);
|
||||
const directory = await createTestTempDirectory("rea-pyinstaller-out-");
|
||||
const output = join(directory, "extracted");
|
||||
await materializeArtifactInventory(
|
||||
{
|
||||
inputPath: path,
|
||||
inputFormat: "pe",
|
||||
outputRoot: output,
|
||||
environment: process.env,
|
||||
integrityPolicy: "fail",
|
||||
},
|
||||
path,
|
||||
snapshot,
|
||||
);
|
||||
expect(
|
||||
Buffer.compare(await readFile(join(output, "pkg", "data.bin")), payload),
|
||||
).toBe(0);
|
||||
expect(
|
||||
Buffer.compare(
|
||||
await readFile(join(output, "PYZ.pyz", "res", "icon.bin")),
|
||||
icon,
|
||||
),
|
||||
).toBe(0);
|
||||
});
|
||||
|
||||
const posixPathFixture = async () => {
|
||||
const backslashName = String.raw`a\b.txt`;
|
||||
const slashName = "a/b.txt";
|
||||
|
||||
@@ -227,9 +227,6 @@ describe("browserScenarioSchema", () => {
|
||||
at_end: ["dom", "storage"],
|
||||
events: ["console", "page-errors", "network", "websockets"],
|
||||
});
|
||||
expect(parsed.capture.limits).toEqual(
|
||||
DEFAULT_BROWSER_SCENARIO_CAPTURE_LIMITS,
|
||||
);
|
||||
});
|
||||
|
||||
it("allows callers to choose visible browsers and service workers", () => {
|
||||
|
||||
@@ -188,8 +188,6 @@ it("preserves local Playwright action diagnostics", async () => {
|
||||
status: "failed",
|
||||
error: `locator ${selector} failed with token=raw-secret`,
|
||||
});
|
||||
expect(result[0]?.error).toContain(selector);
|
||||
expect(result[0]?.error).toContain("raw-secret");
|
||||
});
|
||||
|
||||
it("retains complete action errors and Electron window text", async () => {
|
||||
|
||||
@@ -359,6 +359,12 @@ describe("configuration failure diagnostics", () => {
|
||||
it.each(["1", "90000", "2147483647"])(
|
||||
"accepts Swift compile deadline %s without changing Hopper's deadline",
|
||||
(value) => {
|
||||
expect(
|
||||
parseEnvironmentValues({ REA_SWIFTC_COMPILE_TIMEOUT_MS: value }),
|
||||
).toMatchObject({
|
||||
ok: true,
|
||||
value: { REA_SWIFTC_COMPILE_TIMEOUT_MS: Number(value) },
|
||||
});
|
||||
expect(
|
||||
parseConfig({ REA_SWIFTC_COMPILE_TIMEOUT_MS: value }),
|
||||
).toMatchObject({
|
||||
|
||||
@@ -77,39 +77,6 @@ describe("bundle comparison explicit cross-pairs", () => {
|
||||
});
|
||||
|
||||
describe("bundle comparison", () => {
|
||||
it("does not report display-only bundle differences as unexplained changes", () => {
|
||||
const target = {
|
||||
path: "/left/artifact.elf",
|
||||
sha256: "a".repeat(64),
|
||||
format: "elf" as const,
|
||||
architecture: "x86_64" as const,
|
||||
};
|
||||
const record = createEvidence(target, PROVIDER, {
|
||||
operation: "observe",
|
||||
parameters: {},
|
||||
result: { observed: true },
|
||||
confidence: "observed",
|
||||
authority: "shipped-artifact",
|
||||
});
|
||||
const left = createEvidenceBundle([record]);
|
||||
const rightRecord = {
|
||||
...record,
|
||||
subject: {
|
||||
...record.subject!,
|
||||
name: "renamed.elf",
|
||||
local_path: "/right/renamed.elf",
|
||||
},
|
||||
};
|
||||
const right = { ...left, records: [rightRecord] };
|
||||
const result = compareBundles(left, right);
|
||||
expect(result).toMatchObject({
|
||||
status: "unchanged",
|
||||
summary: { records_unchanged: 1, unresolved: 0 },
|
||||
changes: [],
|
||||
});
|
||||
expect(result.left_bundle_sha256).not.toBe(result.right_bundle_sha256);
|
||||
});
|
||||
|
||||
it("accepts long caller-selected bundle paths", () => {
|
||||
const path = `/${"deep/".repeat(1_000)}bundle.json`;
|
||||
expect(
|
||||
|
||||
@@ -229,14 +229,6 @@ describe("changed behavior", () => {
|
||||
expect(aggregate([changed, unknown]).result).toEqual(result);
|
||||
});
|
||||
|
||||
it("uses complete inline artifact deltas as static candidates", () => {
|
||||
const complete = comparison("compare_artifacts", artifactResult());
|
||||
expect(aggregate([complete]).result.findings.items).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({ dimension: "artifact:main.js" }),
|
||||
]),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("changed-behavior input and Evidence validation", () => {
|
||||
|
||||
@@ -6,7 +6,7 @@ export const ghidraFunctionClassification = () => ({
|
||||
});
|
||||
|
||||
/** Wire-boundary fixture, not evidence that a real Ghidra run was performed. */
|
||||
export const ghidraFunctionBody = () => ({
|
||||
const ghidraFunctionBody = () => ({
|
||||
available: true as const,
|
||||
provenance: "ghidra-function-body-address-set",
|
||||
ranges: [{ start: "0x401000", end: "0x401005" }],
|
||||
|
||||
@@ -31,7 +31,7 @@ describe("managed/native verification source", () => {
|
||||
const [observation] = source.value.nativeObservations;
|
||||
expect(observation?.supported).toBe(true);
|
||||
expect(observation?.symbols.map(({ name }) => name)).toEqual(
|
||||
legacy.exports.items.map(({ name }) => name),
|
||||
["open_native"],
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1,44 +0,0 @@
|
||||
import { expect, it } from "vitest";
|
||||
import { inspectPeImportsInputSchema, peImportsSchema } from "./peImports.js";
|
||||
|
||||
it("rejects extra caller arguments and admits an empty import inventory", () => {
|
||||
expect(
|
||||
inspectPeImportsInputSchema.safeParse({
|
||||
path: "/artifacts/app.exe",
|
||||
extra: true,
|
||||
}).success,
|
||||
).toBe(false);
|
||||
const report = peImportsSchema.parse({
|
||||
artifact: {
|
||||
path: "/artifacts/app.exe",
|
||||
sha256: "a".repeat(64),
|
||||
bytes: 512,
|
||||
},
|
||||
format: "pe32-plus",
|
||||
machine: 0x8664,
|
||||
bits: 64,
|
||||
architecture: "amd64",
|
||||
import_directory: null,
|
||||
delay_import_directory: null,
|
||||
imports: [],
|
||||
delay_imports: [],
|
||||
imphash: {
|
||||
algorithm: "pefile",
|
||||
md5: null,
|
||||
reason: "IMAGE_DIRECTORY_ENTRY_IMPORT is absent.",
|
||||
},
|
||||
coverage: {
|
||||
status: "complete",
|
||||
reason: null,
|
||||
examined_dlls: 0,
|
||||
imports: 0,
|
||||
delay_imports: 0,
|
||||
},
|
||||
issues: [],
|
||||
limitations: [
|
||||
"IMAGE_DIRECTORY_ENTRY_IMPORT is absent. That is not proof the image never imported APIs through another table or overlay.",
|
||||
],
|
||||
});
|
||||
expect(report.imports).toEqual([]);
|
||||
expect(report.imphash.md5).toBeNull();
|
||||
});
|
||||
@@ -613,23 +613,6 @@ it.each([false, true])(
|
||||
);
|
||||
|
||||
describe("managed artifact reference inventory", () => {
|
||||
it("qualifies local custom-attribute names with their enclosing types", () => {
|
||||
const bytes = buildManagedPeFixture({
|
||||
additionalEmptyTypes: 1,
|
||||
customAttributeTypeRaw: (1 << 3) | 2,
|
||||
nestedTypes: [[2, 1]],
|
||||
typeName: "InnerAttribute",
|
||||
typeNamespace: "",
|
||||
});
|
||||
const result = inspectManagedArtifactBytes(
|
||||
bytes,
|
||||
managedPeFixtureTarget(bytes),
|
||||
);
|
||||
|
||||
expect(result.attributes[0]?.type_name).toBe("Empty0+InnerAttribute");
|
||||
expect(result.coverage).toEqual({ state: "complete", issues: [] });
|
||||
});
|
||||
|
||||
it("qualifies referenced custom-attribute names from TypeRef scopes", () => {
|
||||
const bytes = buildManagedPeFixture({
|
||||
customAttributeTypeRaw: (1 << 3) | 3,
|
||||
|
||||
@@ -473,14 +473,6 @@ describe("managed complete operand decoding", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("reports a switch whose target count is itself truncated", () => {
|
||||
expect(fatBody(Buffer.from([0x45, 0x02]))).toMatchObject({
|
||||
status: "malformed",
|
||||
decoded_instruction_count: 0,
|
||||
issue: "Instruction switch at IL offset 0 leaves method body",
|
||||
});
|
||||
});
|
||||
|
||||
it("preserves instructions decoded before a truncated operand", () => {
|
||||
expect(fatBody(Buffer.from([0x00, 0x20]))).toMatchObject({
|
||||
status: "malformed",
|
||||
|
||||
@@ -192,9 +192,12 @@ it("does not invent an inspected constant on historical member Evidence", () =>
|
||||
expect(historical.fields[0]).not.toHaveProperty("constant");
|
||||
});
|
||||
|
||||
it.each([7, 8])(
|
||||
"retains the common coded-index diagnostic for invalid parent %s",
|
||||
(parentRaw) => {
|
||||
it.each([
|
||||
{ parentRaw: 7, reason: "tag 3" },
|
||||
{ parentRaw: 8, reason: "row 2" },
|
||||
])(
|
||||
"retains the coded-index diagnostic for invalid parent $parentRaw",
|
||||
({ parentRaw, reason }) => {
|
||||
const bytes = buildManagedPeFixture({
|
||||
fields: [{ name: "Default", flags: 0x8056 }],
|
||||
constants: [
|
||||
@@ -226,7 +229,7 @@ it.each([7, 8])(
|
||||
expect.objectContaining({
|
||||
code: "invalid-row",
|
||||
scope: "metadata.Constant",
|
||||
detail: parent.reason,
|
||||
detail: expect.stringContaining(reason),
|
||||
}),
|
||||
]),
|
||||
);
|
||||
|
||||
@@ -101,14 +101,6 @@ it.each([
|
||||
);
|
||||
|
||||
describe("managed compressed integer framing", () => {
|
||||
it.each([[0x80], [0xc0], [0xc0, 1], [0xc0, 1, 2]])(
|
||||
"keeps an incomplete width %j distinct from a reserved prefix",
|
||||
(...value) => {
|
||||
expect(() => readCompressed(Buffer.from(value), 0)).toThrow(
|
||||
"truncated compressed integer",
|
||||
);
|
||||
},
|
||||
);
|
||||
it("preserves complete widths and rejects reserved leading bits", () => {
|
||||
expect(readCompressed(Buffer.from([0x80, 1]), 0)).toEqual({
|
||||
value: 1,
|
||||
@@ -2418,6 +2410,9 @@ it.each([
|
||||
it.each([
|
||||
[[], "truncated compressed integer"],
|
||||
[[0x80], "truncated compressed integer"],
|
||||
[[0xc0], "truncated compressed integer"],
|
||||
[[0xc0, 1], "truncated compressed integer"],
|
||||
[[0xc0, 1, 2], "truncated compressed integer"],
|
||||
[[0xc0, 0, 0], "truncated compressed integer"],
|
||||
[[0xe0], "reserved compressed integer"],
|
||||
] as const)(
|
||||
|
||||
@@ -228,14 +228,7 @@ it("reports field imports as declarations without inventing method implementatio
|
||||
});
|
||||
|
||||
describe("managed native boundary inspection", () => {
|
||||
it("returns complete member inventories inline and reports unavailable metadata", () => {
|
||||
const bytes = buildManagedPeFixture();
|
||||
const paged = inspectManagedMembersBytes(
|
||||
bytes,
|
||||
managedPeFixtureTarget(bytes),
|
||||
);
|
||||
expect(paged.methods).toHaveLength(1);
|
||||
|
||||
it("reports absent or malformed member metadata", () => {
|
||||
const nativeBytes = buildNativePeFixture();
|
||||
const native = inspectManagedMembersBytes(
|
||||
nativeBytes,
|
||||
|
||||
@@ -610,7 +610,7 @@ describe("signature display name validity", () => {
|
||||
const result = inspectDisplayFixture({ fieldSignature: blob });
|
||||
expect(result.fields[0]?.signature.parse_status).toBe(status);
|
||||
expect(result.fields[0]?.signature.raw_sha256).toBe(
|
||||
signature(blob).raw_sha256,
|
||||
createHash("sha256").update(blob).digest("hex"),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -266,11 +266,11 @@ describe("ELF embedded ASAR integrity applicability", () => {
|
||||
it("preserves an enabled ASAR integrity fuse without claiming enforcement on ELF", async () => {
|
||||
const root = await createTestTempDirectory("rea-electron-fuses-elf-asar-");
|
||||
const fuses = [
|
||||
ELECTRON_FUSE_STATE_BYTES.disabled,
|
||||
ELECTRON_FUSE_STATE_BYTES.disabled,
|
||||
ELECTRON_FUSE_STATE_BYTES.disabled,
|
||||
ELECTRON_FUSE_STATE_BYTES.disabled,
|
||||
ELECTRON_FUSE_STATE_BYTES.enabled,
|
||||
0x30,
|
||||
0x30,
|
||||
0x30,
|
||||
0x30,
|
||||
0x31,
|
||||
];
|
||||
const image = elfFuseImage(fuses);
|
||||
const path = join(root, "electron");
|
||||
@@ -281,7 +281,7 @@ describe("ELF embedded ASAR integrity applicability", () => {
|
||||
index: 4,
|
||||
name: "EnableEmbeddedAsarIntegrityValidation",
|
||||
state: "enabled",
|
||||
raw_byte: ELECTRON_FUSE_STATE_BYTES.enabled,
|
||||
raw_byte: 0x31,
|
||||
});
|
||||
expect(
|
||||
report.runtime_implications.find(
|
||||
|
||||
@@ -136,7 +136,7 @@ describe("LabeledStringScanner", () => {
|
||||
};
|
||||
it("collects a full clang line within the 500-character bound", () => {
|
||||
assertSingleToolchainLine(
|
||||
"Android (5900059 based on r365631c1) clang version 9.0.8 (https://android.googlesource.com/toolchain/llvm-project 207d7abc1a2abf3ef8d4301736d6a7ebc224a290) (based on LLVM 9.0.8svn)",
|
||||
"Android (5900059 based on r365631c) clang version 9.0.8 (https://android.googlesource.com/toolchain/llvm-project 207d7abc1a2abf3ef8d4301736d6a7ebc224a290) (based on LLVM 9.0.8svn)",
|
||||
);
|
||||
});
|
||||
|
||||
@@ -155,12 +155,6 @@ describe("LabeledStringScanner", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("collects the labeled Android clang line", () => {
|
||||
assertSingleToolchainLine(
|
||||
"Android (5900059 based on r365631c) clang version 9.0.8 (https://android.googlesource.com/toolchain/llvm-project 207d7abc) (based on LLVM 9.0.8svn)",
|
||||
);
|
||||
});
|
||||
|
||||
it("does not claim a toolchain cap for unrelated overlong strings", () => {
|
||||
const scanner = new LabeledStringScanner();
|
||||
const license = `Apache License Version 2.0 ${"x".repeat(700)}`;
|
||||
|
||||
@@ -803,6 +803,15 @@ const malformedOutputs = (): Array<
|
||||
length: 1,
|
||||
signature: null,
|
||||
locals: [],
|
||||
body: {
|
||||
available: true,
|
||||
provenance: "ghidra-function-body-address-set",
|
||||
ranges: [{ start: "0x401000", end: "0x401000" }],
|
||||
total_bytes: 1,
|
||||
span_bytes: 1,
|
||||
non_contiguous: false,
|
||||
contains_entry: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
@@ -814,6 +823,8 @@ const malformedOutputs = (): Array<
|
||||
references: [
|
||||
{ ...edge, kind: { available: false, reason: "unknown" } },
|
||||
],
|
||||
reference_kinds_available: true,
|
||||
unresolved_calls: [],
|
||||
},
|
||||
],
|
||||
[
|
||||
|
||||
@@ -458,7 +458,10 @@ describe("headless IDA containment lookup failures", () => {
|
||||
});
|
||||
|
||||
expect(result.ok).toBe(false);
|
||||
if (!result.ok) expect(result.error._tag).toBe("AnalysisCancelledError");
|
||||
if (!result.ok) {
|
||||
expect(result.error._tag).toBe("AnalysisCancelledError");
|
||||
expect(result.error.cause).toBe(controller.signal.reason);
|
||||
}
|
||||
expect(
|
||||
producer.calls.some(({ name }) => name === "survey_binary"),
|
||||
"nothing is dispatched after cancellation",
|
||||
@@ -606,7 +609,10 @@ describe("headless IDA document identity", () => {
|
||||
);
|
||||
|
||||
expect(result.ok).toBe(false);
|
||||
if (!result.ok) expect(result.error._tag).toBe("AnalysisCancelledError");
|
||||
if (!result.ok) {
|
||||
expect(result.error._tag).toBe("AnalysisCancelledError");
|
||||
expect(result.error.cause).toBe(controller.signal.reason);
|
||||
}
|
||||
expect(producer.calls).toEqual([]);
|
||||
} finally {
|
||||
expect((await client.close()).ok).toBe(true);
|
||||
|
||||
@@ -302,6 +302,7 @@ describe("headless IDA partial operation evidence", () => {
|
||||
expect(result.ok).toBe(false);
|
||||
if (!result.ok) {
|
||||
expect(result.error._tag).toBe("AnalysisCancelledError");
|
||||
expect(result.error.cause).toBe(controller.signal.reason);
|
||||
expect(result.error.partialObservation).toMatchObject({
|
||||
analysis_profile: null,
|
||||
normalized_result: {
|
||||
|
||||
@@ -556,7 +556,11 @@ export class KunaProjectCache {
|
||||
request: KunaProjectRequest,
|
||||
): Promise<Result<KunaProject, AnalysisError>> {
|
||||
if (request.signal?.aborted === true)
|
||||
return err(new AnalysisCancelledError(KUNA_EXPORT_OPERATION));
|
||||
return err(
|
||||
new AnalysisCancelledError(KUNA_EXPORT_OPERATION, {
|
||||
cause: request.signal.reason,
|
||||
}),
|
||||
);
|
||||
const key = kunaProjectKey({
|
||||
targetSha256: request.targetSha256,
|
||||
targetBasename: basename(request.targetPath),
|
||||
@@ -878,7 +882,13 @@ export class KunaProjectCache {
|
||||
settled = true;
|
||||
leave();
|
||||
if (entry.waiters === 0) entry.controller.abort();
|
||||
resolve(err(new AnalysisCancelledError(KUNA_EXPORT_OPERATION)));
|
||||
resolve(
|
||||
err(
|
||||
new AnalysisCancelledError(KUNA_EXPORT_OPERATION, {
|
||||
cause: request.signal?.reason,
|
||||
}),
|
||||
),
|
||||
);
|
||||
};
|
||||
request.signal?.addEventListener("abort", onAbort, { once: true });
|
||||
void entry.promise.then((result) => {
|
||||
|
||||
@@ -97,7 +97,14 @@ export class KunaTargetCommands {
|
||||
if (this.#closing || request.signal.aborted) {
|
||||
request.releaseRun?.();
|
||||
return Promise.resolve(
|
||||
err(new AnalysisCancelledError(request.operation)),
|
||||
err(
|
||||
new AnalysisCancelledError(
|
||||
request.operation,
|
||||
request.signal.aborted
|
||||
? { cause: request.signal.reason }
|
||||
: undefined,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
return this.#run(request);
|
||||
@@ -106,7 +113,16 @@ export class KunaTargetCommands {
|
||||
/** Establish the immutable input before waiting for a foreground run slot. */
|
||||
prepare(): Promise<Result<null, AnalysisError>> {
|
||||
if (this.#closing || this.lifetime.signal.aborted)
|
||||
return Promise.resolve(err(new AnalysisCancelledError("kuna:workspace")));
|
||||
return Promise.resolve(
|
||||
err(
|
||||
new AnalysisCancelledError(
|
||||
"kuna:workspace",
|
||||
this.lifetime.signal.aborted
|
||||
? { cause: this.lifetime.signal.reason }
|
||||
: undefined,
|
||||
),
|
||||
),
|
||||
);
|
||||
return this.#admit("kuna:workspace", () =>
|
||||
this.#ensureWorkspace().then((workspace) =>
|
||||
workspace.ok ? ok(null) : workspace,
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { MODULE_SIGNATURE_MAGIC } from "../../domain/native/kernelModule.js";
|
||||
|
||||
const concat = (...parts: readonly Buffer[]): Buffer => Buffer.concat(parts);
|
||||
|
||||
const write16 = (
|
||||
@@ -75,7 +73,13 @@ export const moduleSignatureTrailer = (
|
||||
const info = Buffer.alloc(12);
|
||||
info[2] = 2;
|
||||
info.writeUInt32BE(sigLen >>> 0, 8);
|
||||
return concat(pkcs7, info, Buffer.from(MODULE_SIGNATURE_MAGIC, "latin1"));
|
||||
// Linux MODULE_SIG_STRING includes the final newline:
|
||||
// https://code.googlesource.com/linux/torvalds/linux/+/78273df7f646f8daf2604ec714bea0897cd03aae/include/linux/module_signature.h
|
||||
return concat(
|
||||
pkcs7,
|
||||
info,
|
||||
Buffer.from("~Module signature appended~\n", "latin1"),
|
||||
);
|
||||
};
|
||||
|
||||
export interface KernelModuleFixtureOptions {
|
||||
|
||||
@@ -551,13 +551,14 @@ it("honors cancellation during token-open exit settlement", async () => {
|
||||
lifecycle: { state: "observed", lifecycle: "non-zombie" },
|
||||
};
|
||||
const controller = new AbortController();
|
||||
const reason = new Error("cancelled");
|
||||
const host: ProcessOwnershipHost = {
|
||||
platform: "linux",
|
||||
listProcesses: async () => [row],
|
||||
environment: async () => ({}),
|
||||
signalGroup: vi.fn(),
|
||||
processIdentities: async () => {
|
||||
controller.abort(new Error("cancelled"));
|
||||
controller.abort(reason);
|
||||
return new Map([[row.pid, identity]]);
|
||||
},
|
||||
};
|
||||
@@ -576,7 +577,7 @@ it("honors cancellation during token-open exit settlement", async () => {
|
||||
{ deadline: undefined },
|
||||
controller.signal,
|
||||
),
|
||||
).rejects.toThrow("cancelled");
|
||||
).rejects.toBe(reason);
|
||||
});
|
||||
|
||||
it("does not signal a stale group after its last member exits during revalidation", async () => {
|
||||
|
||||
@@ -416,9 +416,6 @@ it("describes the effective heap and the configurable matching deadline", () =>
|
||||
regexWorkerHeapRequestRecovery(DEFAULT_REGEX_WORKER_HEAP_REQUEST),
|
||||
);
|
||||
if (heap.kind === "unknown") {
|
||||
expect(projected.remediation.action).toContain(
|
||||
"--max-old-space-size-percentage",
|
||||
);
|
||||
expect(projected.remediation.action).toContain(
|
||||
"--experimental-config-file",
|
||||
);
|
||||
|
||||
@@ -57,7 +57,7 @@ describe("unobserved internal symlink reconciliation", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("retains unreadable targets below the nearest failed directory", () => {
|
||||
it("retains unreadable targets below a failed directory", () => {
|
||||
const entries: ReferenceSourceEntry[] = [
|
||||
internalLink("alias.txt", "hidden/target.txt"),
|
||||
failedDirectory("hidden", "io"),
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
import { expect, it } from "vitest";
|
||||
import { machoImageWithUnknownCommand } from "../artifacts/apple/MachoImage.fixture.js";
|
||||
import { readRustBinaryImage } from "./RustBinaryImage.js";
|
||||
import { RustAuditableFormatFailure } from "./RustAuditableReader.js";
|
||||
|
||||
it("maps malformed 64-bit Mach-O load-command alignment to Rust input failure", () => {
|
||||
expect(() =>
|
||||
readRustBinaryImage(Buffer.from(machoImageWithUnknownCommand(true, 12))),
|
||||
).toThrow(RustAuditableFormatFailure);
|
||||
expect(() =>
|
||||
readRustBinaryImage(Buffer.from(machoImageWithUnknownCommand(true, 12))),
|
||||
).toThrow(expect.objectContaining({ kind: "malformed" }));
|
||||
});
|
||||
|
||||
it.each([
|
||||
{ wide: true, size: 16 },
|
||||
{ wide: false, size: 12 },
|
||||
])(
|
||||
"accepts $size-byte Rust Mach-O command for wide=$wide",
|
||||
({ wide, size }) => {
|
||||
expect(
|
||||
readRustBinaryImage(Buffer.from(machoImageWithUnknownCommand(wide, size)))
|
||||
.format,
|
||||
).toBe("macho");
|
||||
},
|
||||
);
|
||||
@@ -2,7 +2,7 @@ import type { Client } from "@modelcontextprotocol/client";
|
||||
import { Ajv2020 } from "ajv/dist/2020.js";
|
||||
import type { AnySchema } from "ajv";
|
||||
import { expect, it } from "vitest";
|
||||
import { mkdir, writeFile } from "node:fs/promises";
|
||||
import { mkdir, realpath, symlink, writeFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { z } from "zod";
|
||||
|
||||
@@ -178,7 +178,8 @@ it("exposes the production resource session through the MCP SDK for a keyless bu
|
||||
const parent = await createTestTempDirectory(
|
||||
"rea-bundle-resource-production-mcp-",
|
||||
);
|
||||
const bundle = join(parent, "Fixture.app");
|
||||
const bundle = join(parent, "Fixture");
|
||||
const alias = join(parent, "Caller");
|
||||
const resourceDirectory = join(bundle, "Resources");
|
||||
await mkdir(resourceDirectory, { recursive: true });
|
||||
await writeFile(join(bundle, "Info.plist"), "<plist><dict/></plist>");
|
||||
@@ -192,6 +193,9 @@ it("exposes the production resource session through the MCP SDK for a keyless bu
|
||||
}),
|
||||
);
|
||||
|
||||
await symlink(bundle, alias, "junction");
|
||||
const canonical = await realpath(bundle);
|
||||
|
||||
const session = await createArtifactBinarySession({}, process.platform);
|
||||
const { client } = await createApplicationMcpHarness({
|
||||
session,
|
||||
@@ -200,7 +204,7 @@ it("exposes the production resource session through the MCP SDK for a keyless bu
|
||||
|
||||
const opened = await client.callTool({
|
||||
name: "open_bundle_resources",
|
||||
arguments: { path: bundle },
|
||||
arguments: { path: alias },
|
||||
});
|
||||
expect(opened.isError).not.toBe(true);
|
||||
expect(
|
||||
@@ -208,7 +212,11 @@ it("exposes the production resource session through the MCP SDK for a keyless bu
|
||||
opened.structuredContent,
|
||||
),
|
||||
).toMatchObject({
|
||||
result: { canonical_path: bundle, executable_sha256: null },
|
||||
result: {
|
||||
caller_path: alias,
|
||||
canonical_path: canonical,
|
||||
executable_sha256: null,
|
||||
},
|
||||
});
|
||||
|
||||
const decoded = await client.callTool({
|
||||
@@ -223,7 +231,11 @@ it("exposes the production resource session through the MCP SDK for a keyless bu
|
||||
subject: null,
|
||||
normalized_result: {
|
||||
target_sha256: null,
|
||||
resource_root: { canonical_path: bundle, executable_sha256: null },
|
||||
resource_root: {
|
||||
caller_path: alias,
|
||||
canonical_path: canonical,
|
||||
executable_sha256: null,
|
||||
},
|
||||
},
|
||||
});
|
||||
const decodedEvidenceId = z
|
||||
@@ -252,10 +264,10 @@ it("exposes the production resource session through the MCP SDK for a keyless bu
|
||||
),
|
||||
).toMatchObject({
|
||||
normalized_result: {
|
||||
artifact: { path: bundle, sha256: null },
|
||||
artifact: { path: canonical, sha256: null },
|
||||
resource_root: {
|
||||
caller_path: bundle,
|
||||
canonical_path: bundle,
|
||||
caller_path: alias,
|
||||
canonical_path: canonical,
|
||||
executable_sha256: null,
|
||||
},
|
||||
},
|
||||
@@ -275,7 +287,11 @@ it("exposes the production resource session through the MCP SDK for a keyless bu
|
||||
normalized_result: {
|
||||
archive_path: "Resources/Archive.plist",
|
||||
target_sha256: null,
|
||||
resource_root: { canonical_path: bundle, executable_sha256: null },
|
||||
resource_root: {
|
||||
caller_path: alias,
|
||||
canonical_path: canonical,
|
||||
executable_sha256: null,
|
||||
},
|
||||
objects: expect.arrayContaining([
|
||||
expect.objectContaining({ value: "resource-only-value" }),
|
||||
]),
|
||||
@@ -288,9 +304,24 @@ it("exposes the production resource session through the MCP SDK for a keyless bu
|
||||
subject: null,
|
||||
operation: "inspect_keyed_archive",
|
||||
normalized_result: {
|
||||
resource_root: { canonical_path: bundle, executable_sha256: null },
|
||||
resource_root: {
|
||||
caller_path: alias,
|
||||
canonical_path: canonical,
|
||||
executable_sha256: null,
|
||||
},
|
||||
},
|
||||
});
|
||||
const closed = await client.callTool({ name: "close_binary", arguments: {} });
|
||||
expect(closed.isError).not.toBe(true);
|
||||
const statusAfterClose = await client.callTool({
|
||||
name: "binary_session",
|
||||
arguments: {},
|
||||
});
|
||||
expect(
|
||||
toolContract("binary_session").outputSchema.parse(
|
||||
statusAfterClose.structuredContent,
|
||||
),
|
||||
).toMatchObject({ result: { open: false } });
|
||||
});
|
||||
|
||||
const expectResourceSchemaBranches = async (client: Client): Promise<void> => {
|
||||
|
||||
@@ -125,23 +125,6 @@ describe("derived MCP operation progress and cancellation", () => {
|
||||
expect(result.ok ? undefined : result.error.cause).toBe(reason);
|
||||
});
|
||||
|
||||
it("lets cancellation win immediately before result publication", async () => {
|
||||
const controller = new AbortController();
|
||||
const result = await runDerivedOperation(
|
||||
context(controller),
|
||||
"compare",
|
||||
() => {
|
||||
controller.abort();
|
||||
return "must-not-publish";
|
||||
},
|
||||
);
|
||||
|
||||
expect(result).toMatchObject({
|
||||
ok: false,
|
||||
error: { _tag: "AnalysisCancelledError" },
|
||||
});
|
||||
});
|
||||
|
||||
it("lets cancellation win while terminal progress delivery is pending", async () => {
|
||||
const controller = new AbortController();
|
||||
let terminalReached = false;
|
||||
|
||||
@@ -3,7 +3,7 @@ import { expect } from "vitest";
|
||||
import { cliTest } from "../../support/cli/cliFixture.js";
|
||||
|
||||
cliTest.for(["", " \t "])(
|
||||
"rejects blank Electron application_root (%j) before filesystem or provider work",
|
||||
"rejects blank Electron application_root (%j) as invalid input",
|
||||
async (applicationRoot, { cli }) => {
|
||||
const response = await cli.run({
|
||||
arguments: [
|
||||
|
||||
@@ -79,7 +79,7 @@ ipcRenderer.send("app:log", 1);`,
|
||||
|
||||
for (const surface of ["CLI", "stdio MCP"] as const)
|
||||
cliTest.for(cases)(
|
||||
`keeps literal sends unknown beside $shape through ${surface}`,
|
||||
`classifies literal sends beside $shape through ${surface}`,
|
||||
async ({ main, preload, pairing }, { cli }) => {
|
||||
const root = await createTestTempDirectory("rea-electron-dynamic-ipc-");
|
||||
await writeFixtureFiles(root, {
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
symlink,
|
||||
writeFile,
|
||||
} from "node:fs/promises";
|
||||
import { createHash } from "node:crypto";
|
||||
import { join } from "node:path";
|
||||
import { createPackageWithOptions, extractFile } from "@electron/asar";
|
||||
import { expect, onTestFinished } from "vitest";
|
||||
@@ -28,6 +29,7 @@ cliTest.skipIf(process.platform === "win32")(
|
||||
const bytes = Buffer.from("selected native companion");
|
||||
await mkdir(source);
|
||||
await writeFile(join(source, "native.node"), bytes);
|
||||
await writeFile(join(source, "packed.txt"), "packed bytes");
|
||||
await createPackageWithOptions(source, selected, { unpack: "*.node" });
|
||||
await rename(selected, canonical);
|
||||
await symlink(canonical, selected);
|
||||
@@ -54,11 +56,15 @@ cliTest.skipIf(process.platform === "win32")(
|
||||
const inventory = artifactInventoryResultSchema.parse(
|
||||
inventoryEvidence.normalized_result,
|
||||
);
|
||||
const occurrence = inventory.occurrences.find(
|
||||
({ logical_path }) => logical_path === "native.node",
|
||||
);
|
||||
expect(occurrence?.hash_status).toBe("verified");
|
||||
expect(
|
||||
inventory.occurrences.find(
|
||||
({ logical_path }) => logical_path === "native.node",
|
||||
)?.hash_status,
|
||||
).toBe("verified");
|
||||
inventory.nodes.find(
|
||||
({ artifact_id }) => artifact_id === occurrence?.artifact_id,
|
||||
)?.sha256,
|
||||
).toBe(createHash("sha256").update(bytes).digest("hex"));
|
||||
|
||||
const extracted = await cli.run({
|
||||
arguments: ["extract-artifact", selected, "--json"],
|
||||
@@ -74,5 +80,8 @@ cliTest.skipIf(process.platform === "win32")(
|
||||
expect(
|
||||
(await readFile(join(result.output_root, "native.node"))).equals(bytes),
|
||||
).toBe(true);
|
||||
expect(await readFile(join(result.output_root, "packed.txt"), "utf8")).toBe(
|
||||
"packed bytes",
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
} from "../../src/domain/artifactGraph.js";
|
||||
|
||||
cliTest(
|
||||
"inventories and extracts Bun source without executing its carrier",
|
||||
"inventories and extracts Bun source from its carrier",
|
||||
async ({ cli }) => {
|
||||
const root = await createTestTempDirectory("rea-bun-cli-");
|
||||
const source = join(root, "app");
|
||||
|
||||
@@ -387,7 +387,7 @@ cliTest(
|
||||
);
|
||||
|
||||
cliTest(
|
||||
"rejects repeated-range expansion before hashing members or creating extraction output",
|
||||
"rejects repeated-range expansion without creating extraction output",
|
||||
async ({ cli }) => {
|
||||
const root = await createTestTempDirectory("rea-pck-expansion-");
|
||||
const source = join(root, "aliased.pck");
|
||||
|
||||
@@ -6,7 +6,7 @@ import { createTestTempDirectory } from "../fixtures/temporaryDirectory.js";
|
||||
import { cliTest } from "../support/cli/cliFixture.js";
|
||||
|
||||
cliTest.skipIf(process.platform === "win32")(
|
||||
"reports unresolvable source and scenario selections without permission advice",
|
||||
"reports unresolvable source and scenario selections with path-specific guidance",
|
||||
async ({ cli }) => {
|
||||
const root = await createTestTempDirectory("rea-source-path-failures-");
|
||||
await symlink("loop-b", join(root, "loop-a"));
|
||||
|
||||
@@ -58,6 +58,7 @@ cliTest(
|
||||
expect(await readFile(registrationBackupPath, "utf8")).toBe(original);
|
||||
expect(refreshed.stderr).toContain(registrationBackupPath);
|
||||
expect(after.theme).toBe("dark");
|
||||
expect(after["amp.mcpServers"].other).toEqual({ command: "other" });
|
||||
|
||||
// The equals form --agent=amp is parsed as a spaced form before Incur sees
|
||||
// it, so the Amp registration lands and keeps the environment map.
|
||||
@@ -69,6 +70,7 @@ cliTest(
|
||||
command: expect.any(String),
|
||||
env: { KEEP_ME: "1" },
|
||||
});
|
||||
expect(afterEquals["amp.mcpServers"].other).toEqual({ command: "other" });
|
||||
expect(await readFile(registrationBackupPath, "utf8")).toBe(original);
|
||||
const recoveryBackupPath = equals.stderr.match(
|
||||
/Amp MCP registration backup retained at (.+)/,
|
||||
|
||||
@@ -91,7 +91,11 @@ cliTest(
|
||||
const display = await compare(displayPath);
|
||||
expect(display.exitCode).toBe(0);
|
||||
const displayResult = bundleComparisonResultSchema.parse(display.json);
|
||||
expect(displayResult).toMatchObject({ status: "unchanged", changes: [] });
|
||||
expect(displayResult).toMatchObject({
|
||||
status: "unchanged",
|
||||
summary: { records_unchanged: 1, unresolved: 0 },
|
||||
changes: [],
|
||||
});
|
||||
expect(displayResult.left_bundle_sha256).not.toBe(
|
||||
displayResult.right_bundle_sha256,
|
||||
);
|
||||
|
||||
@@ -55,7 +55,12 @@ it("binds authenticated partial Evidence but preserves raw partial observations"
|
||||
sha256: "a".repeat(64),
|
||||
bytes: 1,
|
||||
},
|
||||
{ kind: "file-snapshot-hash", path: "/tmp/selected", bytes: 1 },
|
||||
{
|
||||
kind: "file-snapshot-hash",
|
||||
path: "/tmp/selected",
|
||||
sha256: "a".repeat(64),
|
||||
bytes: 1,
|
||||
},
|
||||
],
|
||||
] as const) {
|
||||
const result = await finishCliEvidenceCommand(
|
||||
@@ -74,5 +79,7 @@ it("binds authenticated partial Evidence but preserves raw partial observations"
|
||||
expect(result.ok).toBe(false);
|
||||
if (result.ok) throw new Error("Expected cancellation");
|
||||
expect(result.error.details?.partial_observation).toMatchObject(expected);
|
||||
if ("kind" in expected)
|
||||
expect(result.error.details?.partial_observation).toEqual(expected);
|
||||
}
|
||||
});
|
||||
@@ -85,7 +85,11 @@ const registerParserAdvertisementInventoryTests = (): void => {
|
||||
it("require exactly the inputs whose omission the parser rejects", async () => {
|
||||
const cli = createCli({});
|
||||
const mismatches: string[] = [];
|
||||
for (const command of createCliInventory(cli).primary) {
|
||||
const primary = createCliInventory(cli).primary;
|
||||
expect(primary).toEqual(
|
||||
expect.arrayContaining(["inspect-pcap-client", "inspect-pcap-capture"]),
|
||||
);
|
||||
for (const command of primary) {
|
||||
const advertised = await advertisedSchema(cli, command);
|
||||
for (const group of INPUT_GROUPS) {
|
||||
const parser = parserSchema(cli, command, group);
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
import { execFile } from "node:child_process";
|
||||
import { readFile, readdir, writeFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { promisify } from "node:util";
|
||||
import { expect, it } from "vitest";
|
||||
import { parse } from "yaml";
|
||||
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
||||
|
||||
const execute = promisify(execFile);
|
||||
|
||||
it("enforces workflow platform labels before contacting Cutter", async () => {
|
||||
const workflow = parse(
|
||||
await readFile(".github/workflows/real-cutter.yml", "utf8"),
|
||||
);
|
||||
const labels = workflow.jobs["cutter-upstream"].strategy.matrix.include.map(
|
||||
(entry: { platform: string }) => entry.platform,
|
||||
);
|
||||
expect(labels).toEqual(["linux", "macOS", "Windows"]);
|
||||
const root = await createTestTempDirectory("rea-cutter-platform-");
|
||||
const preload = join(root, "preload.mjs");
|
||||
await writeFile(
|
||||
preload,
|
||||
`
|
||||
import childProcess from 'node:child_process';
|
||||
import { syncBuiltinESMExports } from 'node:module';
|
||||
Object.defineProperty(process, 'platform', { value: process.env.REA_PROBE_PLATFORM });
|
||||
childProcess.execFile = () => { throw new Error('fixture discovery reached'); };
|
||||
syncBuiltinESMExports();
|
||||
`,
|
||||
);
|
||||
const check = async (selected: string | undefined, actual: string) => {
|
||||
const env: NodeJS.ProcessEnv = {
|
||||
...process.env,
|
||||
TMPDIR: root,
|
||||
TMP: root,
|
||||
TEMP: root,
|
||||
REA_PROBE_PLATFORM: actual,
|
||||
};
|
||||
if (selected === undefined) delete env.EXPECTED_PLATFORM;
|
||||
else env.EXPECTED_PLATFORM = selected;
|
||||
const result: unknown = await execute(
|
||||
process.execPath,
|
||||
["--import", preload, "scripts/verify-real-cutter.mjs"],
|
||||
{ env, timeout: 10_000 },
|
||||
).then(
|
||||
() => {
|
||||
throw new Error("Fixture discovery should stop the verifier");
|
||||
},
|
||||
(error: unknown) => error,
|
||||
);
|
||||
if (
|
||||
!(result instanceof Error) ||
|
||||
!("stderr" in result) ||
|
||||
typeof result.stderr !== "string"
|
||||
)
|
||||
throw new Error("Missing verifier diagnostics");
|
||||
return result.stderr;
|
||||
};
|
||||
for (const [selected, actual] of [
|
||||
["linux", "linux"],
|
||||
["macOS", "darwin"],
|
||||
["Windows", "win32"],
|
||||
] as const)
|
||||
expect(await check(selected, actual)).toContain(
|
||||
"fixture discovery reached",
|
||||
);
|
||||
expect(await check("linux", "darwin")).toContain(
|
||||
"Cutter runner did not match its declared platform",
|
||||
);
|
||||
expect(await check("unknown", "linux")).toContain(
|
||||
"Unrecognized Cutter runner platform",
|
||||
);
|
||||
expect(await check(undefined, "linux")).toContain(
|
||||
"fixture discovery reached",
|
||||
);
|
||||
expect(
|
||||
(await readdir(root)).filter((name) =>
|
||||
name.startsWith("rea-cutter-smoke-"),
|
||||
),
|
||||
).toEqual([]);
|
||||
});
|
||||
@@ -475,7 +475,7 @@ cliTest(
|
||||
);
|
||||
|
||||
cliTest(
|
||||
"diagnoses an obsolete explicit-null Evidence commitment without claiming the file was modified",
|
||||
"diagnoses an obsolete explicit-null Evidence commitment",
|
||||
async ({ cli }) => {
|
||||
const root = await createTestTempDirectory("rea-legacy-coverage-identity-");
|
||||
const path = join(root, "legacy.json");
|
||||
|
||||
@@ -2,15 +2,6 @@ import { expect, it } from "vitest";
|
||||
|
||||
import { expectConfigurationRefusal, serveCli } from "./cliServeHarness.js";
|
||||
|
||||
it.each(["inspect-pcap-client", "inspect-pcap-capture"])(
|
||||
"%s advertises a schema through the public CLI",
|
||||
async (command) => {
|
||||
const result = await serveCli([command, "--schema", "--json"]);
|
||||
expect(result.exitCode).toBe(0);
|
||||
expect(JSON.parse(result.stdout)).toBeTruthy();
|
||||
},
|
||||
);
|
||||
|
||||
it.each([
|
||||
["inspect-pcap-client"],
|
||||
["inspect-pcap-capture", "/nonexistent/capture.pcap"],
|
||||
|
||||
@@ -373,8 +373,9 @@ it("does not project cancellation cleanup as incomplete after a successful retry
|
||||
});
|
||||
|
||||
it("returns cancellation with the completed observation when the caller interrupts service close", async () => {
|
||||
const evidence = observation("complete");
|
||||
const service = {
|
||||
inspectWithObjdump: vi.fn(async () => ok(observation("complete"))),
|
||||
inspectWithObjdump: vi.fn(async () => ok(evidence)),
|
||||
close: vi.fn(async () => {
|
||||
process.emit("SIGINT");
|
||||
}),
|
||||
@@ -382,7 +383,10 @@ it("returns cancellation with the completed observation when the caller interrup
|
||||
|
||||
const result = await runObjdump(service);
|
||||
|
||||
expect(result).toMatchObject({ code: "cancelled" });
|
||||
expect(result).toMatchObject({
|
||||
code: "cancelled",
|
||||
details: { partial_observation: evidence },
|
||||
});
|
||||
expect(process.exitCode).toBe(130);
|
||||
});
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ it.runIf(unsupportedHost)(
|
||||
encoding: "hex",
|
||||
});
|
||||
if (result.ok) throw new Error("Expected a host refusal");
|
||||
expect(result.error._tag).toBe("AnalysisCapabilityUnavailableError");
|
||||
const projected = projectAnalysisError(result.error);
|
||||
expect(projected).toMatchObject({ code: "capability_unavailable" });
|
||||
expect(projected.message).toContain("Linux x64");
|
||||
@@ -715,21 +716,6 @@ it.runIf(!unsupportedHost)(
|
||||
},
|
||||
);
|
||||
|
||||
it.runIf(unsupportedHost)(
|
||||
"reports unverified host coverage before acquiring a provider",
|
||||
async () => {
|
||||
expect(
|
||||
await new EvmoleInterfaceProvider({}).inspect({
|
||||
path: "/selected.hex",
|
||||
encoding: "hex",
|
||||
}),
|
||||
).toMatchObject({
|
||||
ok: false,
|
||||
error: { _tag: "AnalysisCapabilityUnavailableError" },
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
it.runIf(!unsupportedHost)(
|
||||
"preserves actual snapshot-write cancellation and root cleanup",
|
||||
async () => {
|
||||
|
||||
@@ -38,40 +38,6 @@ const fixture = async () => {
|
||||
return { root, selected, canonical };
|
||||
};
|
||||
|
||||
it.skipIf(process.platform === "win32")(
|
||||
"inventories and extracts the same selected ASAR companion as Electron tooling",
|
||||
async () => {
|
||||
const { root, selected } = await fixture();
|
||||
expect(extractFile(selected, "native.node").equals(bytes)).toBe(true);
|
||||
const inventory = await inventoryArtifact(selected);
|
||||
const occurrence = inventory.occurrences.find(
|
||||
({ logical_path }) => logical_path === "native.node",
|
||||
);
|
||||
expect(occurrence?.hash_status).toBe("verified");
|
||||
expect(
|
||||
inventory.nodes.find(
|
||||
({ artifact_id }) => artifact_id === occurrence?.artifact_id,
|
||||
)?.sha256,
|
||||
).toBe(createHash("sha256").update(bytes).digest("hex"));
|
||||
const result = await withArtifactResourceScope((resourceScope) =>
|
||||
extractArtifact({
|
||||
inputPath: selected,
|
||||
inputFormat: "asar",
|
||||
outputRoot: join(root, "output"),
|
||||
environment: {},
|
||||
integrityPolicy: "fail",
|
||||
resourceScope,
|
||||
}),
|
||||
);
|
||||
expect(
|
||||
(await readFile(join(result.output_root, "native.node"))).equals(bytes),
|
||||
).toBe(true);
|
||||
expect(await readFile(join(result.output_root, "packed.txt"), "utf8")).toBe(
|
||||
"packed bytes",
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
it.skipIf(process.platform === "win32")(
|
||||
"does not substitute the canonical archive companion for an absent selected companion",
|
||||
async () => {
|
||||
|
||||
@@ -63,14 +63,6 @@ new RuleTester({
|
||||
expect(bytes).toEqual(value);`,
|
||||
],
|
||||
invalid: [
|
||||
{
|
||||
// Exact pre-fix ELF test shape: readFile without an encoding returns Buffer.
|
||||
code: `import { readFile } from "node:fs/promises";
|
||||
import { expect } from "vitest";
|
||||
const before = await readFile(path);
|
||||
expect(await readFile(path)).toEqual(before);`,
|
||||
errors: [{ messageId: "nativeBytes" }],
|
||||
},
|
||||
{
|
||||
code: `import { readFileSync as read } from "node:fs";
|
||||
import { expect } from "vitest";
|
||||
|
||||
@@ -269,13 +269,20 @@ describe("retention orchestration", () => {
|
||||
|
||||
it("deletes nothing when a later list is incomplete", async () => {
|
||||
const deleted: number[] = [];
|
||||
const page = Array.from({ length: 100 }, (_, index) =>
|
||||
cache(index + 1, `${prefix}${String(index)}`, "2026-10-20T01:00:00Z"),
|
||||
);
|
||||
const requests: string[] = [];
|
||||
const page = [
|
||||
cache(1, `${prefix}old`, "2026-10-20T01:00:00Z"),
|
||||
cache(2, `${prefix}mid`, "2026-10-20T02:00:00Z"),
|
||||
cache(3, `${prefix}new`, "2026-10-20T03:00:00Z"),
|
||||
];
|
||||
await expect(
|
||||
retainCaches({
|
||||
repository,
|
||||
request: async (_method, endpoint) => {
|
||||
requests.push(
|
||||
new URL(endpoint, "https://example.test").searchParams.get("key") ??
|
||||
"",
|
||||
);
|
||||
if (endpoint.includes("npm-download-"))
|
||||
throw new Error("list failed after the first family");
|
||||
return { actions_caches: page };
|
||||
@@ -283,10 +290,39 @@ describe("retention orchestration", () => {
|
||||
deleteCache: async (id) => {
|
||||
deleted.push(id);
|
||||
},
|
||||
families: [{ prefix, keepKey: `${prefix}0` }],
|
||||
families: [{ prefix, keepKey: `${prefix}new` }],
|
||||
dedupeNpmDownloads: true,
|
||||
}),
|
||||
).rejects.toThrow(/list failed|exceeded/u);
|
||||
).rejects.toThrow("list failed after the first family");
|
||||
expect(requests).toEqual([prefix, "npm-download-"]);
|
||||
expect(deleted).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("cache listing and retention", () => {
|
||||
const repository = "morluto/rea";
|
||||
const prefix = "rea-static-v1-Linux-X64-";
|
||||
|
||||
it("deletes nothing when the cache listing reaches the page cap", async () => {
|
||||
const deleted: number[] = [];
|
||||
let requests = 0;
|
||||
const page = Array.from({ length: 100 }, (_, index) =>
|
||||
cache(index + 1, `${prefix}${String(index)}`, "2026-10-20T01:00:00Z"),
|
||||
);
|
||||
await expect(
|
||||
retainCaches({
|
||||
repository,
|
||||
request: async () => {
|
||||
requests += 1;
|
||||
return { actions_caches: page };
|
||||
},
|
||||
deleteCache: async (id) => {
|
||||
deleted.push(id);
|
||||
},
|
||||
families: [{ prefix, keepKey: `${prefix}0` }],
|
||||
}),
|
||||
).rejects.toThrow(/exceeded.*pages/u);
|
||||
expect(requests).toBe(10);
|
||||
expect(deleted).toEqual([]);
|
||||
});
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ import {
|
||||
parseStaticTaskContext,
|
||||
recordStaticTaskContext,
|
||||
} from "../../../scripts/ci/static-task-context.mjs";
|
||||
import { createGhApiRequest } from "../../../scripts/ci/github-api.mjs";
|
||||
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
||||
|
||||
const execute = promisify(execFile);
|
||||
@@ -339,17 +338,6 @@ it("records the actual static task graph with explicit source identity and no am
|
||||
expect(() => parseStaticTaskContext(invalid)).toThrow();
|
||||
});
|
||||
|
||||
it("rejects mutation methods at the shared GitHub transport before invoking the CLI", async () => {
|
||||
const request = createGhApiRequest({ env: { PATH: "" } });
|
||||
await expect(
|
||||
request("POST", "repos/owner/repo/actions/runs/1/rerun", {
|
||||
requests: 0,
|
||||
startedAt: Date.now(),
|
||||
limitMs: 1000,
|
||||
}),
|
||||
).rejects.toThrow("read-only GET");
|
||||
});
|
||||
|
||||
it("collects basic CI telemetry before optional dependency installation can fail", async () => {
|
||||
const { jobs } = z
|
||||
.object({
|
||||
|
||||
@@ -91,15 +91,20 @@ it("collects the latest attempt for recent completed CI and Release runs into a
|
||||
headBranch: null,
|
||||
},
|
||||
]);
|
||||
expect(manifest.runs).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
status: "collected",
|
||||
rawPath: expect.any(String),
|
||||
reportPath: expect.any(String),
|
||||
}),
|
||||
]),
|
||||
);
|
||||
for (const { id, runAttempt, status, rawPath, reportPath } of manifest.runs) {
|
||||
const directory = join(
|
||||
fixture.outputDirectory,
|
||||
"runs",
|
||||
`${id}-${runAttempt}`,
|
||||
);
|
||||
expect({ status, rawPath, reportPath }).toEqual({
|
||||
status: "collected",
|
||||
rawPath: join(directory, "jobs.json"),
|
||||
reportPath: join(directory, "report.json"),
|
||||
});
|
||||
await expect(readFile(rawPath, "utf8")).resolves.toBeTruthy();
|
||||
await expect(readFile(reportPath, "utf8")).resolves.toBeTruthy();
|
||||
}
|
||||
expect(calls).toContainEqual([
|
||||
"api",
|
||||
"--paginate",
|
||||
|
||||
@@ -11,7 +11,6 @@ import {
|
||||
matchRunToPullRequest,
|
||||
writePrCiHealthReport,
|
||||
} from "../../../scripts/ci/pr-ci-health.mjs";
|
||||
import { createGhApiRequest } from "../../../scripts/ci/github-api.mjs";
|
||||
import type {
|
||||
PrCiPullRequest,
|
||||
PrCiWorkflowRun,
|
||||
@@ -453,23 +452,44 @@ it("public CLI uses only GET and produces a report from fixture GitHub responses
|
||||
|
||||
it("rejects non-GET methods before launching the GitHub client", async () => {
|
||||
const fixture = await createFixture();
|
||||
const request = createGhApiRequest({
|
||||
env: {
|
||||
...process.env,
|
||||
PATH: `${fixture.fakeBin}${delimiter}${process.env.PATH ?? ""}`,
|
||||
CI_GH_CALLS: join(fixture.directory, "should-not-exist"),
|
||||
},
|
||||
});
|
||||
await expect(
|
||||
request("POST", "repos/owner/rea/pulls", {
|
||||
requests: 0,
|
||||
startedAt: observedAt,
|
||||
limitMs: 1_000,
|
||||
}),
|
||||
).rejects.toThrow("read-only GET");
|
||||
await expect(
|
||||
readFile(join(fixture.directory, "should-not-exist")),
|
||||
).rejects.toMatchObject({ code: "ENOENT" });
|
||||
const marker = join(fixture.directory, "gh-invoked");
|
||||
const preload = join(fixture.directory, "watch-gh.cjs");
|
||||
await writeFile(
|
||||
preload,
|
||||
`const fs = require("node:fs");
|
||||
const util = require("node:util");
|
||||
const child = require("node:child_process");
|
||||
child.execFile = () => { throw new Error("Unexpected callback invocation"); };
|
||||
child.execFile[util.promisify.custom] = async () => {
|
||||
fs.appendFileSync(process.env.CI_GH_CALLS, "called\\n");
|
||||
return { stdout: "{}", stderr: "" };
|
||||
};
|
||||
require("node:module").syncBuiltinESMExports();
|
||||
`,
|
||||
);
|
||||
const transport = new URL(
|
||||
"../../../scripts/ci/github-api.mjs",
|
||||
import.meta.url,
|
||||
).href;
|
||||
await executeWorkflowFixture(
|
||||
process.execPath,
|
||||
[
|
||||
"--require",
|
||||
preload,
|
||||
"--input-type=module",
|
||||
"-e",
|
||||
`
|
||||
import assert from "node:assert/strict";
|
||||
import { createGhApiRequest } from ${JSON.stringify(transport)};
|
||||
const request = createGhApiRequest();
|
||||
const budget = { requests: 0, startedAt: Date.now(), limitMs: 10_000 };
|
||||
await assert.rejects(request("POST", "repos/owner/rea/pulls", budget), /read-only GET/);
|
||||
await request("GET", "repos/owner/rea/pulls", budget);
|
||||
`,
|
||||
],
|
||||
{ cwd: fixture.directory, env: { ...process.env, CI_GH_CALLS: marker } },
|
||||
);
|
||||
expect(await readFile(marker, "utf8")).toBe("called\n");
|
||||
});
|
||||
|
||||
function validPull(overrides: Partial<PrCiPullRequest> = {}): PrCiPullRequest {
|
||||
|
||||
@@ -132,23 +132,23 @@ it("selects the latest trusted full run that actually published timing history",
|
||||
expect(output).toBe("run-id=11\nartifact-name=test-timings-full\n");
|
||||
expect(calls).toContain("name=test-timings-full");
|
||||
expect(calls).toContain("per_page=100");
|
||||
expect(calls).not.toContain("actions/runs/13");
|
||||
expect(calls.match(/actions\/runs\//gu)).toHaveLength(3);
|
||||
expect(calls).not.toContain("actions/workflows");
|
||||
});
|
||||
|
||||
it("rejects expired, foreign, old, and pull-request candidates", async () => {
|
||||
it("rejects expired, old, and pull-request candidates", async () => {
|
||||
const { result, output } = await invoke(
|
||||
[
|
||||
artifact(15, { expired: true }),
|
||||
artifact(14, {
|
||||
workflow_run: { ...artifact(14).workflow_run, head_repository_id: 2 },
|
||||
}),
|
||||
artifact(13, {
|
||||
created_at: new Date(now - 31 * 24 * 60 * 60 * 1000).toISOString(),
|
||||
}),
|
||||
artifact(12),
|
||||
],
|
||||
{
|
||||
15: run(15),
|
||||
13: run(13),
|
||||
12: run(12, { event: "pull_request" }),
|
||||
},
|
||||
{ tier: "full" },
|
||||
@@ -197,22 +197,25 @@ it("bounds run lookups to five artifacts even when inventory has more", async ()
|
||||
expect(calls.match(/actions\/runs\//gu)).toHaveLength(5);
|
||||
});
|
||||
|
||||
it("returns empty history without querying for an invalid repository identity", async () => {
|
||||
const { result, output, calls } = await invoke(
|
||||
[],
|
||||
{},
|
||||
{
|
||||
repository: "owner/repo?per_page=100",
|
||||
repositoryId: "not-an-id",
|
||||
},
|
||||
);
|
||||
expect(JSON.parse(result.stdout)).toEqual({
|
||||
run_id: "",
|
||||
artifact_name: "",
|
||||
});
|
||||
expect(output).toBe("run-id=\nartifact-name=\n");
|
||||
expect(calls).toBe("");
|
||||
});
|
||||
it.each([
|
||||
{ repository: "owner/repo?per_page=100", repositoryId: "1" },
|
||||
{ repository: "example/rea", repositoryId: "not-an-id" },
|
||||
])(
|
||||
"returns empty history without querying for an invalid repository identity: %j",
|
||||
async (identity) => {
|
||||
const { result, output, calls } = await invoke(
|
||||
[],
|
||||
{},
|
||||
{ ...identity, tier: "full" },
|
||||
);
|
||||
expect(JSON.parse(result.stdout)).toEqual({
|
||||
run_id: "",
|
||||
artifact_name: "",
|
||||
});
|
||||
expect(output).toBe("run-id=\nartifact-name=\n");
|
||||
expect(calls).toBe("");
|
||||
},
|
||||
);
|
||||
|
||||
it("selects fast history without reading the full-tier artifact", async () => {
|
||||
const { result, calls } = await invoke(
|
||||
|
||||
@@ -46,6 +46,7 @@ const installation = async (
|
||||
javac,
|
||||
[
|
||||
"#!/bin/sh",
|
||||
'printf "invoked\\n" >> "$REA_JAVAC_MARKER"',
|
||||
'if [ "$1" = "-version" ]; then echo "javac 21.0.12"; exit 0; fi',
|
||||
...(compilerStatus === 0
|
||||
? ["exit 0"]
|
||||
@@ -60,8 +61,10 @@ const installation = async (
|
||||
await mkdir(scratchParent);
|
||||
return {
|
||||
scratchParent,
|
||||
javacMarker: join(root, "javac-invocations"),
|
||||
environment: {
|
||||
...process.env,
|
||||
REA_JAVAC_MARKER: join(root, "javac-invocations"),
|
||||
GHIDRA_INSTALL_DIR: install,
|
||||
JAVA_HOME: javaHome,
|
||||
TMPDIR: scratchParent,
|
||||
@@ -193,6 +196,9 @@ it.skipIf(process.platform === "win32").each([
|
||||
),
|
||||
});
|
||||
expect(await readdir(fixture.scratchParent)).toEqual([]);
|
||||
await expect(readFile(fixture.javacMarker)).rejects.toMatchObject({
|
||||
code: "ENOENT",
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -277,6 +277,7 @@ it.each(["read", "capacity", "completed"] as const)(
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(failure.cause).toBe(reason);
|
||||
} else if (outcome === "capacity") {
|
||||
expect(projectAnalysisError(failure).code).toBe("resource_constraint");
|
||||
} else {
|
||||
|
||||
@@ -218,6 +218,7 @@ it.each(["released", "still failing"] as const)(
|
||||
cause: reason,
|
||||
cleanupIncomplete: false,
|
||||
});
|
||||
expect(cancelled.error.cause).toBe(reason);
|
||||
expect(first.error.cleanupIncomplete).toBe(false);
|
||||
expect(registry.hasPendingOwnedResources()).toBe(false);
|
||||
} else {
|
||||
@@ -252,7 +253,7 @@ it("keeps the caller's reason and the catalog owner when cancellation lands duri
|
||||
cleanupIncomplete: true,
|
||||
cleanupResources: [definition],
|
||||
});
|
||||
expect(JSON.stringify(resolved.error.cause)).toContain(reason.caller);
|
||||
expect(resolved.error.cause).toBe(reason);
|
||||
expect(provider.hasPendingOwnedResources()).toBe(true);
|
||||
fault.deny = false;
|
||||
fault.onClose = undefined;
|
||||
|
||||
-7
@@ -39,13 +39,6 @@ it.each(["renderer", "browser", "module"] as const)(
|
||||
}),
|
||||
);
|
||||
const metadata = graph.nodes.find(({ kind }) => kind === "package");
|
||||
expect(metadata?.observations).toContainEqual(
|
||||
expect.objectContaining({
|
||||
properties: expect.objectContaining({
|
||||
renderer_declarations: { [field]: "entry.js" },
|
||||
}),
|
||||
}),
|
||||
);
|
||||
expect(metadata?.observations[0]?.properties).not.toHaveProperty(
|
||||
"renderer",
|
||||
);
|
||||
|
||||
Loaded 100 of 167 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user