Files
fusion/scripts/lib/cli-runtime-routing-check.mjs
gsxdsm 1d3f6c198c FN-9096: route CLI models through installed runtimes
Route every CLI-provider selection through an explicit installed-runtime policy.

- Centralize CLI provider classifications, runtime hints, fallback behavior, and actionable missing-runtime errors.
- Validate routing coverage statically and add conformance and integration tests for CLI runtime paths.
- Document runtime routing behavior and add a published CLI changeset.

Files changed: .changeset/fn-9096-cli-runtime-routing.md          |   7 +
 docs/settings-reference.md                         |  29 +++
 docs/testing.md                                    |   6 +-
 package.json                                       |   6 +-
 .../src/__tests__/cli-provider-routing.test.ts     |  74 ++++++++
 .../__tests__/cli-runtime-routing-check.test.ts    |  25 +++
 .../cli-runtime-routing-conformance.test.ts        | 210 +++++++++++++++++++++
 .../__tests__/hermes-runtime-integration.test.ts   |  28 +++
 .../engine/src/agents/agent-session-helpers.ts     | 166 ++++------------
 packages/engine/src/agents/cli-provider-routing.ts | 174 +++++++++++++++++
 scripts/check-cli-runtime-routing.mjs              |  26 +++
 scripts/lib/cli-runtime-routing-check.mjs          |  84 +++++++++
 12 files changed, 701 insertions(+), 134 deletions(-)

Fusion-Task-Id: FN-9096

Fusion-Task-Lineage: f9f6a434-b28d-4ebb-816a-53ca75efc2c4

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-08-15 07:19:29 -07:00

85 lines
3.7 KiB
JavaScript

/*
FNXC:CliRuntimeRouting 2026-08-15-13:51:
The dashboard owns picker admission and engine deliberately cannot import it.
Parse the explicit `configuredProviders.add(...)` forms instead, so a new
selectable provider cannot become executable only through pi by accident.
Unrecognised syntax is a violation: this guard must fail closed, not quietly
skip a catalog form it no longer understands.
*/
const ADD = /configuredProviders\.add\(([^\n;]+)\)/g;
const STRING = /^\s*["']([^"']+)["']\s*$/;
const PICKER = /^\s*([A-Z][A-Z0-9_]*_PICKER_PROVIDER_ID)\s*$/;
const DYNAMIC = /^\s*customProviderRegistryKey\(/;
function censusEntries(source) {
const entries = [];
const object = /\{\s*providerId:\s*["']([^"']+)["']([\s\S]*?)\}/g;
for (const match of source.matchAll(object)) {
const body = match[2];
const value = (name) => new RegExp(`${name}:\\s*["']([^"']+)["']`).exec(body)?.[1];
entries.push({
providerId: match[1],
classification: value("classification"),
autoDerive: value("autoDerive"),
guardNotApplicable: value("guardNotApplicable"),
onExplicitHint: value("onExplicitHint"),
hasBuilder: /missingRuntimeError\s*:/.test(body),
externalFailFastOwner: value("externalFailFastOwner"),
});
}
return entries;
}
function constantsFromSources(sources) {
const constants = new Map();
for (const source of sources) {
for (const match of source.matchAll(/export const ([A-Z][A-Z0-9_]*_PICKER_PROVIDER_ID)\s*=\s*["']([^"']+)["']\s+as const/g)) {
constants.set(match[1], match[2]);
}
}
return constants;
}
/** @param {{routeSource:string,censusSource:string,constantSources?:string[]}} input */
export function checkCliRuntimeRouting(input) {
const violations = [];
const constants = constantsFromSources(input.constantSources ?? []);
const admitted = new Set();
let calls = 0;
for (const match of input.routeSource.matchAll(ADD)) {
calls += 1;
const expression = match[1].trim();
const literal = STRING.exec(expression)?.[1];
if (literal) { admitted.add(literal); continue; }
const name = PICKER.exec(expression)?.[1];
if (name) {
const provider = constants.get(name);
if (!provider) violations.push(`could not resolve ${name} to a picker provider string literal`);
else admitted.add(provider);
continue;
}
if (DYNAMIC.test(expression)) continue;
violations.push(`unrecognised configuredProviders.add expression: ${expression}`);
}
if (calls === 0) violations.push("zero configuredProviders.add call sites found");
const census = censusEntries(input.censusSource);
if (census.length === 0) violations.push("CLI provider routing census is empty or unparseable");
const byProvider = new Map(census.map((entry) => [entry.providerId, entry]));
for (const provider of admitted) {
if (!byProvider.has(provider)) violations.push(`admitted provider ${provider} has no CLI routing census entry (valid classifications: registry-native, runtime-routed, non-cli, withheld-unsupported)`);
}
for (const entry of census) {
if (!admitted.has(entry.providerId) && entry.classification !== "withheld-unsupported") violations.push(`stale census entry ${entry.providerId} is no longer admitted by the catalog`);
for (const field of ["autoDerive", "guardNotApplicable", "onExplicitHint"]) {
if (!entry[field]) violations.push(`census entry ${entry.providerId} is missing ${field} policy`);
}
const policies = [entry.autoDerive, entry.guardNotApplicable, entry.onExplicitHint];
if (policies.includes("fail-fast") && !entry.hasBuilder && !entry.externalFailFastOwner) {
violations.push(`fail-fast census entry ${entry.providerId} has neither an error builder nor externalFailFastOwner`);
}
}
return violations;
}