feat(session): add explicit provider registry and target binding

Introduce deterministic deep-provider discovery and immutable per-target routing while retaining disjoint artifact and native capability families.

Expose provider selection consistently through MCP, CLI, environment configuration, session status, prompt completion, typed diagnostics, and generated documentation. Preserve exact provider/profile provenance without request racing or transparent fallback.

Cover overlapping providers, ambiguity, rejection details, cancellation, restoration, package installation, and real Hopper target switching.
This commit is contained in:
N0zoM1z0 authored and N0zoM1z0 committed 2026-07-15 22:13:48 +09:00
1 parent bd3cf964d6
commit 941d7101dd
174 files changed
+5370 -648

No files matched your search

+4
View File
@@ -53,6 +53,10 @@ export function createCliInventory(cli: unknown): {
readonly target: string;
}[];
};
export function cliCommandOptionNames(
cli: unknown,
name: string,
): readonly string[];
export function serializeProductCatalog(
catalog: ProductCatalog,
): Promise<string>;
+17
View File
@@ -62,6 +62,23 @@ export const createCliInventory = (cli) => {
};
};
/** Read one primary command's declared option names from the Incur router. */
export const cliCommandOptionNames = (cli, name) => {
const commands = Cli.toCommands.get(cli);
const command = commands?.get(name);
if (
command === undefined ||
"_alias" in command ||
!("options" in command) ||
command.options === undefined
)
return [];
const schema = z.toJSONSchema(command.options);
return Object.keys(schema.properties ?? {}).sort((left, right) =>
left.localeCompare(right),
);
};
const assertSameNames = (label, actual, expected) => {
const actualSet = new Set(actual);
const expectedSet = new Set(expected);
+50 -2
View File
@@ -106,6 +106,7 @@ try {
HOME: home,
XDG_CONFIG_HOME: join(home, ".config"),
PATH: `${fakeBin}:${process.env.PATH ?? ""}`,
REA_ANALYSIS_PROVIDER: "auto",
HOPPER_LAUNCHER_PATH: process.execPath,
HOPPER_LOADER_ARGS_JSON: JSON.stringify([
join(root, "tests/fixtures/fakeLauncher.mjs"),
@@ -180,6 +181,20 @@ try {
throw new Error("packaged artifact inventory CLI failed");
// prettier-ignore
const investigationReplay = await verifyPackagedInvestigation({ cli, workspace, evidenceRoot, artifactArchive, environment });
const unknownProviderExecution = await runWithStatus(
cli,
["analyze", process.execPath, "--provider", "missing-provider", "--json"],
environment,
);
const unknownProvider = json(unknownProviderExecution.stdout);
if (
unknownProviderExecution.status !== 1 ||
unknownProvider.details?.selection_reason !== "unknown_provider" ||
unknownProvider.details?.requested_provider_id !== "missing-provider"
)
throw new Error(
`packaged CLI provider selection failed: ${JSON.stringify(unknownProvider)}`,
);
if (process.platform === "linux") {
const unsupportedExecution = await runWithStatus(
cli,
@@ -270,7 +285,11 @@ try {
const providers = json(await run(cli, ["providers", "--json"], environment));
if (
!Array.isArray(providers.providers) ||
providers.providers.some(({ id }) => typeof id !== "string")
providers.providers.some(({ id }) => typeof id !== "string") ||
providers.analysis_provider_binding !== null ||
providers.analysis_provider_candidates?.find(
({ provider }) => provider?.id === "hopper",
)?.target_support?.status !== "unknown"
)
throw new Error("packaged providers CLI failed");
if (process.platform !== "linux") {
@@ -613,12 +632,41 @@ try {
);
if (result.isError !== true)
throw new Error("packaged target-free MCP omitted no-target error");
const unknownProvider = await client.callTool(
{
name: "open_binary",
arguments: { path: process.execPath, provider_id: "missing-provider" },
},
mcpOptions,
);
if (
unknownProvider.isError !== true ||
unknownProvider.structuredContent?.error?.details?.selection_reason !==
"unknown_provider"
)
throw new Error("packaged MCP accepted an unknown analysis provider");
const opened = await client.callTool(
{ name: "open_binary", arguments: { path: process.execPath } },
{
name: "open_binary",
arguments: { path: process.execPath, provider_id: "hopper" },
},
mcpOptions,
);
if (opened.isError === true)
throw new Error("packaged MCP could not open a binary");
const providerStatus = json(
prompts.mcpText(
await client.callTool(
{ name: "binary_session", arguments: {} },
mcpOptions,
),
),
);
if (
providerStatus.analysis_provider_binding?.provider?.id !== "hopper" ||
providerStatus.analysis_provider_binding?.selection_source !== "request"
)
throw new Error("packaged MCP omitted its explicit Hopper binding");
await prompts.verifyPromptCompletion(
client,
mcpOptions,
+82 -1
View File
@@ -15,6 +15,8 @@ import {
requireFunctionDossier,
requirePseudocode,
} from "../dist/application/RealHopperAssertions.js";
import { HOPPER_PROVIDER_IDENTITY } from "../dist/hopper/HopperProvider.js";
import { REA_WORKFLOW_PROVIDER } from "../dist/application/InvestigationProviders.js";
const execFileAsync = promisify(execFile);
const timeout = 180_000;
@@ -56,6 +58,65 @@ const requireSuccessfulTool = (result, operation) => {
return value.normalized_result;
};
const requireHopperEvidence = (result, operation) => {
const envelope = jsonValue(result);
if (
envelope?.provider?.id !== HOPPER_PROVIDER_IDENTITY.id ||
envelope?.analysis_profile?.provider?.id !== HOPPER_PROVIDER_IDENTITY.id ||
typeof envelope.analysis_profile.provider.version !== "string"
)
throw new Error(`${operation} omitted its concrete Hopper provenance`);
};
const requireHopperWorkflowEvidence = (result, operation) => {
const envelope = jsonValue(result);
const profile = envelope?.analysis_profile;
const upstream = profile?.parameters?.upstream_analysis_profile;
if (
envelope?.provider?.id !== REA_WORKFLOW_PROVIDER.id ||
profile?.provider?.id !== REA_WORKFLOW_PROVIDER.id ||
upstream?.provider?.id !== HOPPER_PROVIDER_IDENTITY.id ||
typeof upstream.provider.version !== "string"
) {
throw new Error(
`${operation} omitted its composed workflow or upstream Hopper provenance`,
);
}
};
const requireHopperSelection = (status, expected) => {
const candidates = status.analysis_provider_candidates;
const hopper = Array.isArray(candidates)
? candidates.find(
(candidate) => candidate?.provider?.id === HOPPER_PROVIDER_IDENTITY.id,
)
: undefined;
if (
hopper === undefined ||
hopper.availability?.status !== "available" ||
hopper.target_support?.status !== expected.targetSupport ||
hopper.selected !== expected.selected
) {
throw new Error("binary_session omitted truthful Hopper candidate status");
}
if (!expected.selected) {
if (status.analysis_provider_binding !== null)
throw new Error("Target-free status unexpectedly selected a provider");
return null;
}
const binding = status.analysis_provider_binding;
if (
binding?.provider?.id !== HOPPER_PROVIDER_IDENTITY.id ||
typeof binding.provider.version !== "string" ||
binding.selection_source !== "auto-single-candidate" ||
binding.analysis_profile?.provider?.id !== HOPPER_PROVIDER_IDENTITY.id ||
binding.analysis_profile.provider.version !== binding.provider.version
) {
throw new Error("binary_session omitted its concrete Hopper binding");
}
return binding;
};
const requireOverview = (value, operation) => {
if (
value === null ||
@@ -268,6 +329,7 @@ const [targetHashA, targetHashB] = await Promise.all([
]);
requireDistinctTargetHashes(targetHashA, targetHashB);
const serverEnvironment = { ...process.env };
serverEnvironment.REA_ANALYSIS_PROVIDER = "auto";
delete serverEnvironment.HOPPER_TARGET_PATH;
delete serverEnvironment.HOPPER_SECOND_TARGET_PATH;
delete serverEnvironment.REA_VERIFY_SERVER_COMMAND;
@@ -308,13 +370,25 @@ try {
{ name: "binary_session", arguments: {} },
options,
);
if (jsonValue(initialSession).open !== false)
const initialStatus = jsonValue(initialSession);
if (initialStatus.open !== false)
throw new Error("The verifier did not start without a target");
requireHopperSelection(initialStatus, {
targetSupport: "unknown",
selected: false,
});
const opened = await client.callTool(
{ name: "open_binary", arguments: { path: targetA } },
options,
);
if (opened.isError === true) throw new Error(textValue(opened));
const firstStatus = jsonValue(
await client.callTool({ name: "binary_session", arguments: {} }, options),
);
const providerBinding = requireHopperSelection(firstStatus, {
targetSupport: "supported",
selected: true,
});
const documents = await client.callTool(
{ name: "list_documents", arguments: {} },
options,
@@ -323,6 +397,8 @@ try {
{ name: "binary_overview", arguments: {} },
options,
);
requireHopperEvidence(documents, "list_documents");
requireHopperWorkflowEvidence(overview, "binary_overview");
const segments = await client.callTool(
{ name: "list_segments", arguments: {} },
options,
@@ -348,6 +424,10 @@ try {
);
if (secondSession.path !== targetB)
throw new Error("The real session did not switch to target B");
requireHopperSelection(secondSession, {
targetSupport: "supported",
selected: true,
});
const secondOverview = await client.callTool(
{ name: "binary_overview", arguments: {} },
options,
@@ -390,6 +470,7 @@ try {
stderrBytes,
diagnosticCount,
dynamicSession: true,
providerBinding,
targets: [targetA, targetB],
targetHashes: [targetHashA, targetHashB],
switched: true,