Files
rea/scripts/verify-package-discovery.mjs
CindyGestandmorluto e1897c3c17 fix(verify-package): read Hopper installer applicability from its owner (#2973)
* fix(verify-package): read Hopper installer applicability from its owner

packageSetupSupport derived hopperSetupSupported from the general doctor
host check plus a non-Windows condition, so a Debian-family host REA
supports but the Hopper package never tested reported an automated
installer the packaged setup never offers. verifySetupApply then expected
needs_confirmation for the explicit --install-hopper request and failed
at verify-package-setup.mjs:403.

Applicability now comes from readLinuxDistribution() in the built
application, so the verifier follows the predicate the package ships, and
it stays unknown when no distribution can be read instead of collapsing
into a known-unsupported host. Generic agent configuration and idempotence
assertions stay independent of installer applicability, and the expected
status is still derived from the host rather than from the request being
asserted.

Verified on Kali 2026.3, a Debian-family host with an installer-untested
distribution and the simulated Hopper package environment: before the
change npm run verify:package fails at verify-package-setup.mjs:403 with
the packaged setup correctly returning needs_human; after it the same run
passes every stage. Both applicability branches are covered by
tests/evaluation/package/packageSetupSupport.test.ts, which runs in the
source lane without a build. No real Hopper installation was exercised,
so no engine compatibility claim follows.

Refs #2939

* fix(ci): preserve Hopper doctor readiness checks

---------

Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
2026-10-14 03:19:45 +08:00

117 lines
3.9 KiB
JavaScript

import { json, run, runWithStatus } from "./lib/verify-package-core.mjs";
const REQUIRED_HELP_COMMANDS = [
"setup",
"update",
"inspect-artifact",
"extract-artifact",
"import-reference-source",
"list-browser-targets",
"inspect-web-page",
"analyze-javascript-application",
"compare",
];
const REQUIRED_LLM_TOPICS = [
"decompile",
"function",
"search",
"inspect",
"xrefs",
"trace",
"capabilities",
"providers",
"inspect-artifact",
"list-browser-targets",
"inspect-web-page",
"analyze-javascript-application",
];
/**
* Derive agent setup support from the doctor node check and automated Hopper
* installer applicability from the facts their owners parse.
*
* Host readiness and installer applicability are different facts: a
* distribution REA supports can still be absent from the Hopper package's
* tested list, so the doctor host check cannot answer the second question.
* Applicability stays undefined when its owning fact is unavailable, which is
* distinct from a host known to lack an automated installer.
*/
export function packageSetupSupport(checks, host) {
const supportedSetupHost =
checks?.find(({ name }) => name === "node")?.ok === true;
const hopperReadinessRequired =
host.platform !== "win32" &&
checks?.find(({ name }) => name === "host")?.ok === true;
return {
supportedSetupHost,
hopperReadinessRequired,
hopperSetupSupported: hopperInstallerSupported(
host,
checks?.find(({ name }) => name === "host")?.ok,
),
};
}
/**
* Whether `rea setup --install-hopper` can install Hopper automatically here.
* On macOS the doctor host check already is the owner's installer predicate
* (macOS 12 or later); on Linux it reports REA host support instead, so the
* distribution's own parsed applicability governs.
*/
function hopperInstallerSupported(host, hostCheckOk) {
if (host.platform === "win32") return false;
if (host.platform === "linux")
return host.linuxDistribution?.hopperSetupSupported;
return hostCheckOk;
}
/**
* The distribution facts the Hopper installer owner parses, taken from the
* built application so the verifier follows the predicate the package ships.
* Loaded only where a distribution can exist, which also keeps the derivation
* above testable without a build.
*/
async function readHostLinuxDistribution() {
if (process.platform !== "linux") return undefined;
const { readLinuxDistribution } =
await import("../dist/application/LinuxHopper.js");
return readLinuxDistribution();
}
/** Validate --help, --llms, and doctor output and determine host setup support. */
export async function verifyPackageDiscovery({ cli, environment }) {
const help = await run(cli, ["--help"], environment);
const llms = await run(cli, ["--llms"], environment);
const doctorExecution = await runWithStatus(
cli,
["doctor", "--json"],
environment,
);
const doctor = json(doctorExecution.stdout);
const { supportedSetupHost, hopperReadinessRequired, hopperSetupSupported } =
packageSetupSupport(doctor.checks, {
platform: process.platform,
linuxDistribution: await readHostLinuxDistribution(),
});
const hopperReady = doctor.checks?.find(({ name }) => name === "hopper")?.ok;
const expectedDoctorHealth = doctor.checks?.every(({ ok }) => ok) === true;
const missingHelp = REQUIRED_HELP_COMMANDS.filter(
(command) => !help.includes(command),
);
const missingLlms = REQUIRED_LLM_TOPICS.filter(
(topic) => !llms.includes(topic),
);
if (
missingHelp.length !== 0 ||
missingLlms.length !== 0 ||
doctor.healthy !== expectedDoctorHealth ||
doctorExecution.status !== (expectedDoctorHealth ? 0 : 1) ||
(hopperReadinessRequired && hopperReady !== true)
)
throw new Error(
`packaged CLI discovery or doctor failed: ${JSON.stringify({ helpSetup: help.includes("setup"), llmsDecompile: llms.includes("decompile"), doctor })}`,
);
return { supportedSetupHost, hopperSetupSupported };
}