Files
OmniRoute/src/app/api/settings/feature-flags/route.ts
Brandon Bennett 6615a5445b feat: combo-lane awareness + activation UX + MCP visibility (Wave 2 of #9654) (#10039)
* feat(admission): per-target lane-aware probes for combo/fusion fan-out (#9654 Wave 2)

Combo and fusion fan out N targets without ever consulting the adaptive-admission
layer: the parent request holds one lease, but each fan-out target is dispatched
unconditionally. With virtual lanes enabled (OMNIROUTE_CHAT_VIRTUAL_LANES=1), a
connection whose lane queue is full now SKIPS additional fan-out targets instead
of piling more queued work onto an already-congested session.

Adds PerTargetAdmissionHook (admission/types.ts) + createPerTargetAdmissionHook
factory (chatAdmission.ts): strictly non-blocking (maxWaitMs 0 - skip, never
queue), a no-op when virtual lanes are off, keyed to the parent tenantKey, and
release-on-admit so the probe is a capacity gate, not a hold.

Threaded through every parallel fan-out path:
- priority/weighted executeTarget + round-robin skip chains (combo.ts)
- fusion panel before fan-out (fusion.ts), judge fallback prefers survivors
- chaos parallel panel (autoCombo/chaosEngine.ts)
- tryFusionDispatch / tryRuntimeUnitDispatch / buildBaseOptions (dispatchPrelude.ts)
- chat.ts primary + safety-net redirect call sites

Snapshot exposes virtualLanes so the no-op gate is cheap and honest.

Tests: tests/unit/combo-lane-awareness-9654.test.ts (10 tests) - factory
semantics, priority/RR skip, fusion panel drop + all-skipped 503, no-hook
backward-compat baseline.

* feat(flags): activation UX - env-wins adaptive virtual-lanes flag + env docs (#9654 Wave 2)

U7: make adaptive virtual admission lanes discoverable + activatable.
- New OMNIROUTE_CHAT_VIRTUAL_LANES feature flag (boolean/runtime/requiresRestart) in featureFlagDefinitions + en.json i18n key.
- lib/admissionVirtualLanes.ts: env-wins resolver (env > DB > default) + boot warm folding a DB-sourced override into the process-global runtime env via reloadAdaptiveAdmissionRuntime(options.env) - no process.env mutation, no open-sse changes. Env still wins; DB toggle gates at next boot.
- GET /api/settings/feature-flags special-cases the flag to report the gate true source (ccDiscoveryAliases precedent); flagPayload helper dedupes the payload shape.
- Wire the warm into instrumentation-node registerNodejs (non-fatal, DB-ready).
- Document the master switch in .env.example + ENVIRONMENT.md with the system-1/system-2 distinction; zero new env-doc-sync drift.
- 11 new tests (resolver precedence + warm); 60/60 across feature-flag suites; typecheck core clean; ESLint + doc gates green.

* feat(mcp): surface adaptive admission lane data in omniroute_get_health (#9654 Wave 2)

U8: make adaptive virtual-lane admission visible to agents via the MCP health tool. handleGetHealth now surfaces a curated adaptiveAdmission block from the health payload (which already carried the runtime snapshot but was dropping it): virtualLanes/pressure/utilization/laneCount/laneQueuedCount/laneQueuedCost, laneTenants capped at top-10 by queued cost, admitted/rejected/wouldReject counts, shutdown. Block omitted entirely when the health endpoint reports none.

isLaneFlagOn mirrors the runtime 1|true convention so a string serialization can never invert a boolean lane report. getHealthOutput schema extended with the matching optional shape; tool description updated.

4 new dispatch tests (full block, top-10 cap/order, omission, defensive coercion of string flags + malformed lane entries) - 22/22 in essentialTools.test.ts. README: Adaptive Admission Lane Data table + Skills & Tool Navigability audit (29/43 schema entries covered, 14 undocumented, tool_search keyword runtime discovery, full catalog in docs/frameworks/MCP-SERVER.md).

No new lint errors (4 pre-existing in server.ts), typecheck core clean, doc counts + fabricated-docs gates green.

* docs: add changelog entry for #9654 Wave 2 (#10039)

* fix(codeql): suppress js/insufficient-password-hash false positive in lane-key fingerprinting (#10039)

resolveSessionId sha256-hashes bearer/x-api-key/x-goog-api-key to derive a deterministic, non-reversible per-key lane-bucket ID for virtual admission lanes (#9654). This is not password storage or verification, so the rule is a false positive; suppress it inline (same house style as src/lib/sync/tokens.ts) to clear the codeqlAlerts ratchet (2 > baseline 1) that blocks #10039 and every PR against release/v3.8.50.

* docs(mcp): complete MCP server README tool reference (#10039)

The MCP server README covered only 29 of the 43 schema entries, listing the
remaining tools solely as a gap note with omniroute_tool_search as the runtime
fallback. Add tool-reference tables for the agent-skills trio, oneproxy trio,
web_fetch/web_search, tool_search, create_combo, set_routing_strategy,
pick_fastest_model, sync_pricing, and db_health_check so the README covers the
full schemas catalog, and fold the coverage note into the tool_search discovery
paragraph.

* fix(chat): drop unused correlationId from safety-net combo redirect (#10039)

handleComboChat's HandleComboChatOptions has no correlationId member and
the combo pipeline never consumes it; the property was copied from the
handleSingleModelChat options shape by accident and introduced a new
TS2353 under the open-sse workspace typecheck gate.

* fix(i18n): translate featureFlagChatVirtualLanesEnabledDescription into 42 locales (#10039)

en.json gained the flag description in this PR but the locale catalogs
were never mirrored, failing the pt-BR key-parity (#6695) and vi
completeness gates. Adds a real translation to every locale, keeping the
zh-CN/zh-TW glossary canonical terms (提供者/儀表板) and no ICU drift.

* chore(quality): ratchet open-sse-typecheck baseline down (#10039)

The Wave 2 admission refactor removed 66 baselined open-sse type errors;
re-freeze the baseline so the gate pins the new, tighter state.

* docs: resync provider reference to 341 and CLI tools to 34

The release branch gained an 11th no-auth provider (freeaiapikey registry
resync, #10233) and a 26th CLI Code tool without regenerating the
auto-generated docs, leaving every PR against release/v3.8.50 failing the
Docs Gates strict validator (code 341 vs doc 340, CLI 34 vs "33 tools").

Regenerate docs/reference/PROVIDER_REFERENCE.md and sync the provider/tool
counts across README.md, AGENTS.md, llm.txt plus 42 i18n mirrors,
package.json description, and the four diagram SVGs.

* fix(tests): align count expectations with live catalogs (pre-existing release drift)

Release/v3.8.50 currently fails five gates on its own tree; this PR inherits
them. Fix the stale expectations to match live code:

- feature-flags-settings: 48 -> 49 flags (Wave 2 adds OMNIROUTE_CHAT_VIRTUAL_LANES)
- cli-tools-schema / cli-catalog-counts: 33 -> 34 tools (zcode added; 26 code = 21 visible + 5 none)
- optional-transformers-dependency: onnxruntime-node ~1.24.3 -> ~1.27.0 (bump #10382)
- stryker.conf.json: register chatcore-header-drop-warn-dedupe-10315 test
- check-public-creds: freeze zcodeProtocol clientId false positive (client identifier, not a credential)

* fix(tests): follow release's onnxruntime-node revert to ~1.24.3

release/v3.8.50's #10543 pinned onnxruntime-node back to ~1.24.3 after
#10403's ~1.27.0 bump caused npm to nest a second native copy under
@huggingface/transformers and broke the Docker SONAME contract. This
PR's own drift-alignment commit (57b9c033) predates that revert and
still expected ~1.27.0; the 3-way merge did not flag it as a textual
conflict since only one side touched this exact line, but the merged
tree became internally inconsistent (package.json ~1.24.3 vs test
expecting ~1.27.0). Align the test with the now-canonical release
value.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

* fix(quality): dedupe stryker.conf.json chatcore-header-drop-warn-dedupe entry

The 3-way merge applied both sides' insertion of the same test-file entry
at different positions, producing a duplicate with broken indentation.
Adopted release's clean version of the file.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

---------

Co-authored-by: Brandon Bennett <brandonbennett@macbookair.myfiosgateway.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
Co-authored-by: Brandon Bennett <branben@users.noreply.github.com>
2026-08-18 11:31:46 -03:00

216 lines
7.3 KiB
TypeScript

import { NextRequest, NextResponse } from "next/server";
import { z } from "zod";
import { isAuthenticated } from "@/shared/utils/apiAuth";
import { isValidationFailure, validateBody } from "@/shared/validation/helpers";
import {
FEATURE_FLAG_DEFINITIONS,
type FeatureFlagDefinition,
} from "@/shared/constants/featureFlagDefinitions";
import {
ADAPTIVE_VIRTUAL_LANES_FLAG_KEY,
resolveAdaptiveVirtualLanesFlag,
} from "@/lib/admissionVirtualLanes";
import {
getFeatureFlagOverrides,
setFeatureFlagOverride,
removeFeatureFlagOverride,
clearAllFeatureFlagOverrides,
} from "@/lib/db/featureFlags";
import { resolveAllFeatureFlags } from "@/shared/utils/featureFlags";
import { getCcAliasGlobalState } from "@/lib/db/ccDiscoveryAliases";
import { sanitizeErrorMessage } from "@omniroute/open-sse/utils/error";
const ACTIVE_VALUES = new Set(["true", "1", "yes"]);
const CC_DISCOVERY_ALIASES_FLAG_KEY = "EXPOSE_CC_DISCOVERY_ALIASES";
function isActive(value: string): boolean {
return ACTIVE_VALUES.has(value);
}
/**
* Standard feature-flag payload shape for GET /api/settings/feature-flags.
* Flags whose gate resolves differently from the generic db-wins-over-env
* order pass their own effectiveValue/source (see the special cases below).
*/
function flagPayload(
definition: FeatureFlagDefinition,
effectiveValue: string,
source: "db" | "env" | "default"
) {
return {
key: definition.key,
label: definition.label,
description: definition.description,
category: definition.category,
type: definition.type,
enumValues: definition.enumValues ?? null,
defaultValue: definition.defaultValue,
effectiveValue,
source,
requiresRestart: definition.requiresRestart,
warningLevel: definition.warningLevel,
};
}
/**
* GET /api/settings/feature-flags
* Returns all feature flags with their effective values and a summary.
*/
export async function GET(request: NextRequest) {
if (!(await isAuthenticated(request))) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
try {
const resolved = resolveAllFeatureFlags();
const flags = resolved.map(({ key, effectiveValue, source, definition }) => {
// Flags whose gate resolves with env-wins-over-db precedence (the
// opposite of resolveAllFeatureFlags' generic db-wins-over-env order)
// report the gate's own resolution so the dashboard never shows a source
// that doesn't match actual gate behavior.
if (key === CC_DISCOVERY_ALIASES_FLAG_KEY) {
const gateState = getCcAliasGlobalState();
return flagPayload(definition, gateState.enabled ? "true" : "false", gateState.source);
}
// #9654 U7: the adaptive virtual-lanes gate reads env at runtime
// construction — env wins over any DB override, and a DB override gates
// at next boot (see lib/admissionVirtualLanes.ts).
if (key === ADAPTIVE_VIRTUAL_LANES_FLAG_KEY) {
const state = resolveAdaptiveVirtualLanesFlag();
return flagPayload(definition, state.enabled ? "true" : "false", state.source);
}
return flagPayload(definition, effectiveValue, source);
});
const total = flags.length;
const active = flags.filter((f) => isActive(f.effectiveValue)).length;
const inactive = total - active;
const overriddenByDb = flags.filter((f) => f.source === "db").length;
const overriddenByEnv = flags.filter((f) => f.source === "env").length;
return NextResponse.json({
flags,
summary: { total, active, inactive, overriddenByDb, overriddenByEnv },
});
} catch (error) {
return NextResponse.json({ error: sanitizeErrorMessage(error) }, { status: 500 });
}
}
const putFeatureFlagSchema = z.object({
key: z.string().min(1),
value: z.string().optional(),
});
/**
* PUT /api/settings/feature-flags
* Set or remove a feature flag override.
* Body: { key: string; value?: string }
* If value is omitted, the override is removed.
*/
export async function PUT(request: NextRequest) {
if (!(await isAuthenticated(request))) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
let rawBody: unknown;
try {
rawBody = await request.json();
} catch {
return NextResponse.json({ error: "Invalid JSON body" }, { status: 400 });
}
const validation = validateBody(putFeatureFlagSchema, rawBody);
if (isValidationFailure(validation)) {
return NextResponse.json({ error: validation.error }, { status: 400 });
}
const { key, value } = validation.data;
// Validate key against known definitions
const definition = FEATURE_FLAG_DEFINITIONS.find((d) => d.key === key);
if (!definition) {
return NextResponse.json({ error: `Unknown feature flag key: ${key}` }, { status: 400 });
}
// Validate enum values
if (value !== undefined && definition.type === "enum" && definition.enumValues) {
if (!definition.enumValues.includes(value)) {
return NextResponse.json(
{
error: `Invalid value "${value}" for enum flag ${key}. Allowed: ${definition.enumValues.join(", ")}`,
},
{ status: 400 }
);
}
}
try {
// Capture previous state before modifying
const allFlagsBefore = resolveAllFeatureFlags();
const prevFlag = allFlagsBefore.find((f) => f.key === key);
const previousValue = prevFlag?.effectiveValue ?? definition.defaultValue;
const previousSource = prevFlag?.source ?? "default";
if (value === undefined) {
removeFeatureFlagOverride(key);
} else {
setFeatureFlagOverride(key, value);
}
// After write — get new effective value
const allFlagsAfter = resolveAllFeatureFlags();
const updatedFlag = allFlagsAfter.find((f) => f.key === key);
const newEffectiveValue = updatedFlag?.effectiveValue ?? definition.defaultValue;
const newSource = updatedFlag?.source ?? "default";
// Env-wins gates resolve with env > DB > default (the opposite of the
// generic db-wins helper above), so report their true resolution here too —
// the response must never tell an operator they enabled a gate that the env
// var still overrides (#9654 U7).
let reportedEffectiveValue = newEffectiveValue;
let reportedSource = newSource;
if (key === ADAPTIVE_VIRTUAL_LANES_FLAG_KEY) {
const state = resolveAdaptiveVirtualLanesFlag();
reportedEffectiveValue = state.enabled ? "true" : "false";
reportedSource = state.source;
}
return NextResponse.json({
key,
effectiveValue: reportedEffectiveValue,
source: reportedSource,
previousValue,
previousSource,
requiresRestart: definition.requiresRestart,
});
} catch (error) {
return NextResponse.json({ error: sanitizeErrorMessage(error) }, { status: 500 });
}
}
/**
* DELETE /api/settings/feature-flags
* Clear all feature flag overrides.
*/
export async function DELETE(request: NextRequest) {
if (!(await isAuthenticated(request))) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
try {
const overrides = getFeatureFlagOverrides();
const count = Object.keys(overrides).length;
clearAllFeatureFlagOverrides();
return NextResponse.json({
cleared: count,
message: `Cleared ${count} feature flag override${count !== 1 ? "s" : ""}`,
});
} catch (error) {
return NextResponse.json({ error: sanitizeErrorMessage(error) }, { status: 500 });
}
}