mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-19 13:42:09 +03:00
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.
This commit is contained in:
@@ -393,6 +393,12 @@ ALLOW_API_KEY_REVEAL=false
|
||||
# OMNIROUTE_CHAT_VIRTUAL_TTL_MS=60000
|
||||
# Per-connection virtual admission lanes (#9654): max concurrent sessions (lanes). Default 64.
|
||||
# OMNIROUTE_CHAT_VIRTUAL_MAX_SESSIONS=64
|
||||
# Adaptive runtime virtual admission lanes (#9654): master switch for the per-tenant
|
||||
# adaptive gate (system 2, open-sse/services/admission). NOTE: the TTL/MAX_SESSIONS
|
||||
# vars above tune the byte-level per-connection lanes (system 1); this switch enables
|
||||
# the adaptive runtime lanes. Dashboard feature flag of the same name; env wins over
|
||||
# the dashboard override; restart required. Default: off.
|
||||
# OMNIROUTE_CHAT_VIRTUAL_LANES=1
|
||||
|
||||
# Hard cap (bytes) for a non-streaming upstream response buffered fully into memory
|
||||
# (#5152). Past this the upstream reader is cancelled and the request fails fast
|
||||
|
||||
@@ -1501,9 +1501,11 @@ These settings were introduced after the previous environment-contract snapshot.
|
||||
| Variable | Default | Source File | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `OMNIROUTE_CHAT_ADMISSION_QUEUE_MS` | `2000` | `src/shared/middleware/chatBodyAdmission.ts` | Maximum wait for a heavyweight chat admission slot before a retryable `503`; a short bounded wait serializes agent bursts instead of an instant `503`. `0` restores immediate rejection. |
|
||||
<<<<<<< HEAD
|
||||
| `OMNIROUTE_CHAT_ADMISSION_MAX_QUEUED_BYTES` | `4194304` (4 MB) | `src/shared/middleware/chatBodyAdmission.ts` | Queued-bytes budget for the admission wait: bounds total buffered body bytes parked process-wide so the wait cannot amplify the heap (#4380). Over-budget waits receive a retryable `503` immediately. |
|
||||
| `OMNIROUTE_CHAT_VIRTUAL_TTL_MS` | `60000` (60 s) | `src/shared/middleware/chatBodyAdmission.ts` | Deprecated no-op since #10110: per-session admission lanes were removed in favor of one process-wide budget. Accepted for configuration compatibility; ignored. |
|
||||
| `OMNIROUTE_CHAT_VIRTUAL_MAX_SESSIONS` | `64` | `src/shared/middleware/chatBodyAdmission.ts` | Deprecated no-op since #10110: per-session admission lanes were removed in favor of one process-wide budget. Accepted for configuration compatibility; ignored. |
|
||||
| `OMNIROUTE_CHAT_VIRTUAL_LANES` | `0` (off) | `open-sse/services/admission/runtime.ts` | Adaptive runtime virtual admission lanes (#9654): master switch for the per-tenant adaptive gate (system 2). Distinct from the deprecated per-connection lane vars above (TTL_MS / MAX_SESSIONS, no-ops since #10110). Dashboard feature flag of the same name; the env var wins over the dashboard override; requires restart. |
|
||||
| `OMNIROUTE_RUNNOW_TIMEOUT_MS` | `30000` | `src/app/api/jobs/[id]/run-now/route.ts` | Bounds how long a run-now call waits for an in-flight job before starting the queued run. |
|
||||
| `ADOBE_FIREFLY_BROWSER_REFRESH` | enabled | `open-sse/services/adobeFireflySession.ts` | Keeps IMS and browser-risk state fresh through account-scoped Chrome CDP sessions; set `0` to disable. |
|
||||
| `ADOBE_FIREFLY_SESSION_DISK` | enabled | `open-sse/services/adobeFireflySession.ts` | Persists repaired Adobe sessions under `DATA_DIR`; set `0` for memory-only state. |
|
||||
|
||||
@@ -2,7 +2,14 @@ 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 } from "@/shared/constants/featureFlagDefinitions";
|
||||
import {
|
||||
FEATURE_FLAG_DEFINITIONS,
|
||||
type FeatureFlagDefinition,
|
||||
} from "@/shared/constants/featureFlagDefinitions";
|
||||
import {
|
||||
ADAPTIVE_VIRTUAL_LANES_FLAG_KEY,
|
||||
resolveAdaptiveVirtualLanesFlag,
|
||||
} from "@/lib/admissionVirtualLanes";
|
||||
import {
|
||||
getFeatureFlagOverrides,
|
||||
setFeatureFlagOverride,
|
||||
@@ -20,6 +27,31 @@ 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.
|
||||
@@ -33,41 +65,22 @@ export async function GET(request: NextRequest) {
|
||||
const resolved = resolveAllFeatureFlags();
|
||||
|
||||
const flags = resolved.map(({ key, effectiveValue, source, definition }) => {
|
||||
// EXPOSE_CC_DISCOVERY_ALIASES resolves with env-wins-over-db precedence
|
||||
// (see db/ccDiscoveryAliases.ts::getCcAliasGlobalState) — the opposite of
|
||||
// resolveAllFeatureFlags' generic db-wins-over-env order. Override the
|
||||
// reported effectiveValue/source with the gate's own resolution so the
|
||||
// dashboard never shows a source that doesn't match actual gate behavior.
|
||||
// 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 {
|
||||
key,
|
||||
label: definition.label,
|
||||
description: definition.description,
|
||||
category: definition.category,
|
||||
type: definition.type,
|
||||
enumValues: definition.enumValues ?? null,
|
||||
defaultValue: definition.defaultValue,
|
||||
effectiveValue: gateState.enabled ? "true" : "false",
|
||||
source: gateState.source,
|
||||
requiresRestart: definition.requiresRestart,
|
||||
warningLevel: definition.warningLevel,
|
||||
};
|
||||
return flagPayload(definition, gateState.enabled ? "true" : "false", gateState.source);
|
||||
}
|
||||
|
||||
return {
|
||||
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,
|
||||
};
|
||||
// #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;
|
||||
@@ -152,10 +165,22 @@ export async function PUT(request: NextRequest) {
|
||||
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: newEffectiveValue,
|
||||
source: newSource,
|
||||
effectiveValue: reportedEffectiveValue,
|
||||
source: reportedSource,
|
||||
previousValue,
|
||||
previousSource,
|
||||
requiresRestart: definition.requiresRestart,
|
||||
|
||||
@@ -980,6 +980,7 @@
|
||||
"featureFlagOmnirouteEmergencyFallbackDescription": "Route budget-exhausted requests to the emergency free fallback provider/model.",
|
||||
"featureFlagArenaEloSyncEnabledDescription": "Enable periodic Arena AI leaderboard ELO sync for model intelligence rankings.",
|
||||
"featureFlagExposeCcDiscoveryAliasesDescription": "Advertise claude/<provider>/<model> mirror ids on /v1/models so Claude Code gateway model discovery lists non-Claude models. Warning: doubles catalog entries for all clients when enabled globally.",
|
||||
"featureFlagChatVirtualLanesEnabledDescription": "Enable per-tenant adaptive virtual admission lanes for provider dispatch (#9654): one tenant's burst no longer 503s another. The OMNIROUTE_CHAT_VIRTUAL_LANES env var wins over this dashboard override; changes take effect at server restart.",
|
||||
"sidebar": {
|
||||
"home": "Home",
|
||||
"dashboard": "Dashboard",
|
||||
|
||||
@@ -242,6 +242,33 @@ export async function scanComboModelNameCollisionsAtBoot(): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* #9654 U7: fold a dashboard DB toggle for the adaptive virtual-lanes flag into
|
||||
* the process-global admission runtime's env at boot. Env-wins: no-op when the
|
||||
* operator's OMNIROUTE_CHAT_VIRTUAL_LANES env var is set (the lazy runtime
|
||||
* already reads process.env correctly). The runtime reads env only at
|
||||
* construction, so this must run before the first request touches it — hence
|
||||
* awaited here, after ensureDbReadyForBoot(). Non-fatal.
|
||||
*
|
||||
* Exported (rather than inline in registerNodejs()) so it can be unit tested
|
||||
* directly without exercising the rest of the startup sequence.
|
||||
*/
|
||||
export async function warmAdaptiveVirtualLanesIntoRuntime(): Promise<void> {
|
||||
try {
|
||||
const { warmAdaptiveVirtualLanesIntoRuntime: warm } =
|
||||
await import("@/lib/admissionVirtualLanes");
|
||||
const materialized = await warm();
|
||||
if (materialized) {
|
||||
console.log(
|
||||
"[STARTUP] Adaptive virtual lanes flag materialized from dashboard override (#9654)"
|
||||
);
|
||||
}
|
||||
} catch (err: unknown) {
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
console.warn("[STARTUP] Could not warm adaptive virtual lanes flag (non-fatal):", msg);
|
||||
}
|
||||
}
|
||||
|
||||
export async function registerNodejs(): Promise<void> {
|
||||
markServerStarting();
|
||||
|
||||
@@ -295,6 +322,7 @@ export async function registerNodejs(): Promise<void> {
|
||||
}
|
||||
|
||||
await scanComboModelNameCollisionsAtBoot();
|
||||
await warmAdaptiveVirtualLanesIntoRuntime();
|
||||
|
||||
const [
|
||||
{ initGracefulShutdown },
|
||||
|
||||
91
src/lib/admissionVirtualLanes.ts
Normal file
91
src/lib/admissionVirtualLanes.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
/**
|
||||
* Adaptive virtual admission lanes (#9654) — src-side activation surface.
|
||||
*
|
||||
* The adaptive gate itself (`open-sse/services/admission/runtime.ts`) reads
|
||||
* `OMNIROUTE_CHAT_VIRTUAL_LANES` from env at process-global construction. This
|
||||
* module is the activation layer that lives where the DB is reachable:
|
||||
*
|
||||
* - `resolveAdaptiveVirtualLanesFlag` resolves the flag with **env-wins**
|
||||
* precedence (env > DB override > default) — the OPPOSITE of the generic
|
||||
* `resolveFeatureFlag` (db-wins), for the same reason as
|
||||
* `getCcAliasGlobalState`: the runtime gate reads env directly, so the
|
||||
* dashboard must report the source that matches actual gate behavior.
|
||||
* - `warmAdaptiveVirtualLanesIntoRuntime` (called from the Node boot path)
|
||||
* folds a DB-sourced override into the process-global runtime's env at
|
||||
* construction time, so a dashboard toggle actually gates after restart.
|
||||
* No-op when env/default — the lazy runtime already reads process.env.
|
||||
*
|
||||
* Env truthiness mirrors the runtime read (`runtime.ts`): only `"1"` or
|
||||
* `"true"` enable; any other set value is explicitly off (and still wins).
|
||||
*/
|
||||
import { getFeatureFlagOverride } from "@/lib/db/featureFlags";
|
||||
|
||||
export const ADAPTIVE_VIRTUAL_LANES_FLAG_KEY = "OMNIROUTE_CHAT_VIRTUAL_LANES";
|
||||
|
||||
export type AdaptiveVirtualLanesFlagState = {
|
||||
enabled: boolean;
|
||||
source: "env" | "db" | "default";
|
||||
};
|
||||
|
||||
export type AdaptiveVirtualLanesFlagDeps = {
|
||||
env?: NodeJS.ProcessEnv;
|
||||
getOverride?: (key: string) => string | undefined;
|
||||
};
|
||||
|
||||
const ENV_ON = new Set(["1", "true"]);
|
||||
|
||||
/**
|
||||
* Resolve the effective state of the adaptive virtual-lanes flag and where it
|
||||
* came from. Env wins over the DB override (env-wins), matching the runtime
|
||||
* gate's own env-only read.
|
||||
*/
|
||||
export function resolveAdaptiveVirtualLanesFlag(
|
||||
deps: AdaptiveVirtualLanesFlagDeps = {}
|
||||
): AdaptiveVirtualLanesFlagState {
|
||||
const env = deps.env ?? process.env;
|
||||
const envValue = env[ADAPTIVE_VIRTUAL_LANES_FLAG_KEY];
|
||||
if (envValue !== undefined && envValue !== "") {
|
||||
return { enabled: ENV_ON.has(envValue), source: "env" };
|
||||
}
|
||||
|
||||
const getOverride = deps.getOverride ?? getFeatureFlagOverride;
|
||||
const dbOverride = getOverride(ADAPTIVE_VIRTUAL_LANES_FLAG_KEY);
|
||||
if (dbOverride !== undefined) {
|
||||
const enabled = dbOverride === "true" || dbOverride === "1" || dbOverride === "yes";
|
||||
return { enabled, source: "db" };
|
||||
}
|
||||
|
||||
return { enabled: false, source: "default" };
|
||||
}
|
||||
|
||||
export type AdaptiveVirtualLanesWarmDeps = {
|
||||
resolve?: (deps?: AdaptiveVirtualLanesFlagDeps) => AdaptiveVirtualLanesFlagState;
|
||||
reload?: (options: { env?: NodeJS.ProcessEnv }) => unknown;
|
||||
};
|
||||
|
||||
/**
|
||||
* Boot warm (#9654 U7): when the DB override is the effective source, fold it
|
||||
* into the process-global adaptive admission runtime's env so the dashboard
|
||||
* toggle actually gates — the runtime reads env only at construction, hence
|
||||
* `requiresRestart: true`. Returns true when it materialized, false when
|
||||
* env/default already cover the state. Never throws.
|
||||
*/
|
||||
export async function warmAdaptiveVirtualLanesIntoRuntime(
|
||||
deps: AdaptiveVirtualLanesWarmDeps = {}
|
||||
): Promise<boolean> {
|
||||
const resolve = deps.resolve ?? resolveAdaptiveVirtualLanesFlag;
|
||||
const state = resolve();
|
||||
if (state.source !== "db") return false;
|
||||
|
||||
const reload =
|
||||
deps.reload ??
|
||||
(await import("@omniroute/open-sse/services/admission/runtime.ts"))
|
||||
.reloadAdaptiveAdmissionRuntime;
|
||||
reload({
|
||||
env: {
|
||||
...(process.env ?? {}),
|
||||
[ADAPTIVE_VIRTUAL_LANES_FLAG_KEY]: state.enabled ? "1" : "0",
|
||||
},
|
||||
});
|
||||
return true;
|
||||
}
|
||||
@@ -457,6 +457,18 @@ export const FEATURE_FLAG_DEFINITIONS: FeatureFlagDefinition[] = [
|
||||
requiresRestart: false,
|
||||
warningLevel: "info",
|
||||
},
|
||||
{
|
||||
key: "OMNIROUTE_CHAT_VIRTUAL_LANES",
|
||||
label: "Adaptive Virtual Admission Lanes",
|
||||
description:
|
||||
"Enable per-tenant adaptive virtual admission lanes for provider dispatch (#9654): one tenant's burst no longer 503s another. The OMNIROUTE_CHAT_VIRTUAL_LANES env var wins over this dashboard override; changes take effect at server restart.",
|
||||
descriptionI18nKey: "featureFlagChatVirtualLanesEnabledDescription",
|
||||
category: "runtime",
|
||||
defaultValue: "false",
|
||||
type: "boolean",
|
||||
requiresRestart: true,
|
||||
warningLevel: "info",
|
||||
},
|
||||
{
|
||||
key: "EXPOSE_FUNCTIONAL_GATEWAY_MIRRORS",
|
||||
label: "Functional Gateway Mirrors",
|
||||
|
||||
132
tests/unit/admission-virtual-lanes-flag.test.ts
Normal file
132
tests/unit/admission-virtual-lanes-flag.test.ts
Normal file
@@ -0,0 +1,132 @@
|
||||
/**
|
||||
* U7 (#9654 Wave 2): adaptive virtual-lanes feature flag — env-wins resolution
|
||||
* and boot warm.
|
||||
*
|
||||
* Contract under test:
|
||||
* - resolveAdaptiveVirtualLanesFlag: env (`"1"`|`"true"`) > DB override >
|
||||
* default(false); env wins even when set to an explicit "off" value.
|
||||
* - warmAdaptiveVirtualLanesIntoRuntime: only a DB-sourced state folds into
|
||||
* the runtime env (`"1"`/`"0"`); env/default sources are no-ops.
|
||||
*
|
||||
* Run: bun test tests/unit/admission-virtual-lanes-flag.test.ts
|
||||
*/
|
||||
import assert from "node:assert/strict";
|
||||
import { describe, it } from "node:test";
|
||||
|
||||
import {
|
||||
ADAPTIVE_VIRTUAL_LANES_FLAG_KEY,
|
||||
resolveAdaptiveVirtualLanesFlag,
|
||||
warmAdaptiveVirtualLanesIntoRuntime,
|
||||
} from "../../src/lib/admissionVirtualLanes.ts";
|
||||
|
||||
const emptyEnv = {};
|
||||
const noOverride = (): string | undefined => undefined;
|
||||
|
||||
describe("resolveAdaptiveVirtualLanesFlag", () => {
|
||||
it('env "1" enables and reports env, winning over a DB override', () => {
|
||||
const state = resolveAdaptiveVirtualLanesFlag({
|
||||
env: { [ADAPTIVE_VIRTUAL_LANES_FLAG_KEY]: "1" },
|
||||
getOverride: () => "false",
|
||||
});
|
||||
assert.deepEqual(state, { enabled: true, source: "env" });
|
||||
});
|
||||
|
||||
it('env "true" enables (runtime-compatible truthiness)', () => {
|
||||
const state = resolveAdaptiveVirtualLanesFlag({
|
||||
env: { [ADAPTIVE_VIRTUAL_LANES_FLAG_KEY]: "true" },
|
||||
getOverride: noOverride,
|
||||
});
|
||||
assert.deepEqual(state, { enabled: true, source: "env" });
|
||||
});
|
||||
|
||||
it('env "0" is an explicit off that still wins over the DB', () => {
|
||||
const state = resolveAdaptiveVirtualLanesFlag({
|
||||
env: { [ADAPTIVE_VIRTUAL_LANES_FLAG_KEY]: "0" },
|
||||
getOverride: () => "true",
|
||||
});
|
||||
assert.deepEqual(state, { enabled: false, source: "env" });
|
||||
});
|
||||
|
||||
it("DB override enables when env is absent", () => {
|
||||
const state = resolveAdaptiveVirtualLanesFlag({
|
||||
env: emptyEnv,
|
||||
getOverride: () => "true",
|
||||
});
|
||||
assert.deepEqual(state, { enabled: true, source: "db" });
|
||||
});
|
||||
|
||||
it('DB override "1" enables when env is absent', () => {
|
||||
const state = resolveAdaptiveVirtualLanesFlag({
|
||||
env: emptyEnv,
|
||||
getOverride: () => "1",
|
||||
});
|
||||
assert.deepEqual(state, { enabled: true, source: "db" });
|
||||
});
|
||||
|
||||
it("DB override disables when env is absent", () => {
|
||||
const state = resolveAdaptiveVirtualLanesFlag({
|
||||
env: emptyEnv,
|
||||
getOverride: () => "false",
|
||||
});
|
||||
assert.deepEqual(state, { enabled: false, source: "db" });
|
||||
});
|
||||
|
||||
it("defaults to disabled when neither env nor DB is set", () => {
|
||||
const state = resolveAdaptiveVirtualLanesFlag({
|
||||
env: emptyEnv,
|
||||
getOverride: noOverride,
|
||||
});
|
||||
assert.deepEqual(state, { enabled: false, source: "default" });
|
||||
});
|
||||
});
|
||||
|
||||
describe("warmAdaptiveVirtualLanesIntoRuntime", () => {
|
||||
it('folds a DB-sourced enable into the runtime env as "1"', async () => {
|
||||
let reloaded = false;
|
||||
let foldedEnv: NodeJS.ProcessEnv | undefined;
|
||||
const materialized = await warmAdaptiveVirtualLanesIntoRuntime({
|
||||
resolve: () => ({ enabled: true, source: "db" }),
|
||||
reload: (options) => {
|
||||
reloaded = true;
|
||||
foldedEnv = options.env;
|
||||
},
|
||||
});
|
||||
|
||||
assert.equal(materialized, true);
|
||||
assert.equal(reloaded, true, "must reload the runtime when the DB is the source");
|
||||
assert.equal(foldedEnv?.[ADAPTIVE_VIRTUAL_LANES_FLAG_KEY], "1");
|
||||
});
|
||||
|
||||
it('folds a DB-sourced disable into the runtime env as "0"', async () => {
|
||||
let foldedEnv: NodeJS.ProcessEnv | undefined;
|
||||
const materialized = await warmAdaptiveVirtualLanesIntoRuntime({
|
||||
resolve: () => ({ enabled: false, source: "db" }),
|
||||
reload: (options) => {
|
||||
foldedEnv = options.env;
|
||||
},
|
||||
});
|
||||
|
||||
assert.equal(materialized, true);
|
||||
assert.equal(foldedEnv?.[ADAPTIVE_VIRTUAL_LANES_FLAG_KEY], "0");
|
||||
});
|
||||
|
||||
it("no-op when the source is env (operator env wins, nothing to fold)", async () => {
|
||||
const materialized = await warmAdaptiveVirtualLanesIntoRuntime({
|
||||
resolve: () => ({ enabled: true, source: "env" }),
|
||||
reload: () => {
|
||||
throw new Error("must not reload when env is the source");
|
||||
},
|
||||
});
|
||||
assert.equal(materialized, false);
|
||||
});
|
||||
|
||||
it("no-op when the source is default", async () => {
|
||||
const materialized = await warmAdaptiveVirtualLanesIntoRuntime({
|
||||
resolve: () => ({ enabled: false, source: "default" }),
|
||||
reload: () => {
|
||||
throw new Error("must not reload when the default applies");
|
||||
},
|
||||
});
|
||||
assert.equal(materialized, false);
|
||||
});
|
||||
});
|
||||
127
tests/unit/feature-flags-route-virtual-lanes.test.ts
Normal file
127
tests/unit/feature-flags-route-virtual-lanes.test.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
/**
|
||||
* U7 (#9654 Wave 2) — route-level acceptance for the adaptive virtual-lanes flag.
|
||||
*
|
||||
* Ticket acceptance: "flag appears in GET /api/settings/feature-flags; env
|
||||
* still wins." Exercises the GET + PUT handlers directly (JWT cookie auth),
|
||||
* asserting the env-wins source reporting and the requiresRestart surface.
|
||||
*
|
||||
* Run: bun test tests/unit/feature-flags-route-virtual-lanes.test.ts
|
||||
*/
|
||||
import assert from "node:assert/strict";
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import test, { after, before } from "node:test";
|
||||
import { SignJWT } from "jose";
|
||||
|
||||
const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-ff-vl-route-"));
|
||||
process.env.DATA_DIR = TEST_DATA_DIR;
|
||||
|
||||
const { GET, PUT } = await import("../../src/app/api/settings/feature-flags/route.ts");
|
||||
const { removeFeatureFlagOverride, setFeatureFlagOverride } =
|
||||
await import("../../src/lib/db/featureFlags");
|
||||
const { ADAPTIVE_VIRTUAL_LANES_FLAG_KEY } = await import("../../src/lib/admissionVirtualLanes.ts");
|
||||
|
||||
const ORIGINAL_ENV_VALUE = process.env[ADAPTIVE_VIRTUAL_LANES_FLAG_KEY];
|
||||
|
||||
type FlagPayload = {
|
||||
key: string;
|
||||
label: string;
|
||||
type: string;
|
||||
defaultValue: string;
|
||||
effectiveValue: string;
|
||||
source: string;
|
||||
requiresRestart: boolean;
|
||||
};
|
||||
|
||||
async function authCookie(): Promise<string> {
|
||||
process.env.JWT_SECRET = "test-feature-flags-route-secret";
|
||||
const secret = new TextEncoder().encode(process.env.JWT_SECRET);
|
||||
const token = await new SignJWT({ sub: "test-user" })
|
||||
.setProtectedHeader({ alg: "HS256" })
|
||||
.setIssuedAt()
|
||||
.setExpirationTime("1h")
|
||||
.sign(secret);
|
||||
return `auth_token=${token}`;
|
||||
}
|
||||
|
||||
async function buildGetRequest(): Promise<Request> {
|
||||
const cookie = await authCookie();
|
||||
return new Request("http://localhost/api/settings/feature-flags", {
|
||||
headers: { cookie },
|
||||
});
|
||||
}
|
||||
|
||||
async function buildPutRequest(value: string): Promise<Request> {
|
||||
const cookie = await authCookie();
|
||||
return new Request("http://localhost/api/settings/feature-flags", {
|
||||
method: "PUT",
|
||||
headers: { cookie, "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ key: ADAPTIVE_VIRTUAL_LANES_FLAG_KEY, value }),
|
||||
});
|
||||
}
|
||||
|
||||
async function getFlag(): Promise<FlagPayload> {
|
||||
const res = await GET(await buildGetRequest());
|
||||
assert.equal(res.status, 200);
|
||||
const json = (await res.json()) as { flags: FlagPayload[] };
|
||||
const flag = json.flags.find((f) => f.key === ADAPTIVE_VIRTUAL_LANES_FLAG_KEY);
|
||||
assert.ok(flag, `flag ${ADAPTIVE_VIRTUAL_LANES_FLAG_KEY} must appear in GET`);
|
||||
return flag;
|
||||
}
|
||||
|
||||
before(() => {
|
||||
removeFeatureFlagOverride(ADAPTIVE_VIRTUAL_LANES_FLAG_KEY);
|
||||
});
|
||||
|
||||
after(() => {
|
||||
if (ORIGINAL_ENV_VALUE === undefined) {
|
||||
delete process.env[ADAPTIVE_VIRTUAL_LANES_FLAG_KEY];
|
||||
} else {
|
||||
process.env[ADAPTIVE_VIRTUAL_LANES_FLAG_KEY] = ORIGINAL_ENV_VALUE;
|
||||
}
|
||||
removeFeatureFlagOverride(ADAPTIVE_VIRTUAL_LANES_FLAG_KEY);
|
||||
});
|
||||
|
||||
test("flag appears in GET with the requiresRestart boolean surface (default off)", async () => {
|
||||
delete process.env[ADAPTIVE_VIRTUAL_LANES_FLAG_KEY];
|
||||
const flag = await getFlag();
|
||||
assert.equal(flag.type, "boolean");
|
||||
assert.equal(flag.defaultValue, "false");
|
||||
assert.equal(flag.requiresRestart, true, "runtime reads env at construction — restart required");
|
||||
assert.equal(flag.effectiveValue, "false");
|
||||
assert.equal(flag.source, "default");
|
||||
});
|
||||
|
||||
test('env wins over a DB override in GET (env "1" + DB false -> env)', async () => {
|
||||
process.env[ADAPTIVE_VIRTUAL_LANES_FLAG_KEY] = "1";
|
||||
setFeatureFlagOverride(ADAPTIVE_VIRTUAL_LANES_FLAG_KEY, "false");
|
||||
const flag = await getFlag();
|
||||
assert.equal(flag.effectiveValue, "true");
|
||||
assert.equal(flag.source, "env");
|
||||
});
|
||||
|
||||
test('env explicit off still wins in GET (env "0" + DB true -> env off)', async () => {
|
||||
process.env[ADAPTIVE_VIRTUAL_LANES_FLAG_KEY] = "0";
|
||||
setFeatureFlagOverride(ADAPTIVE_VIRTUAL_LANES_FLAG_KEY, "true");
|
||||
const flag = await getFlag();
|
||||
assert.equal(flag.effectiveValue, "false");
|
||||
assert.equal(flag.source, "env");
|
||||
});
|
||||
|
||||
test("DB override enables when env is absent (source db)", async () => {
|
||||
delete process.env[ADAPTIVE_VIRTUAL_LANES_FLAG_KEY];
|
||||
setFeatureFlagOverride(ADAPTIVE_VIRTUAL_LANES_FLAG_KEY, "true");
|
||||
const flag = await getFlag();
|
||||
assert.equal(flag.effectiveValue, "true");
|
||||
assert.equal(flag.source, "db");
|
||||
});
|
||||
|
||||
test("PUT response reports env-wins truth when env is set (operator toggle cannot lie)", async () => {
|
||||
process.env[ADAPTIVE_VIRTUAL_LANES_FLAG_KEY] = "0";
|
||||
const res = await PUT(await buildPutRequest("true"));
|
||||
assert.equal(res.status, 200);
|
||||
const json = (await res.json()) as { effectiveValue: string; source: string };
|
||||
assert.equal(json.effectiveValue, "false", 'env "0" must still win over a dashboard PUT "true"');
|
||||
assert.equal(json.source, "env");
|
||||
});
|
||||
@@ -36,7 +36,7 @@ const EXPECTED_FEATURE_FLAG_COUNT = 48;
|
||||
// Test group 1 — Flag definitions registry
|
||||
// ──────────────────────────────────────────────────────
|
||||
describe("featureFlagDefinitions", () => {
|
||||
it("has exactly 47 flag definitions", () => {
|
||||
it("has exactly 48 flag definitions", () => {
|
||||
assert.strictEqual(FEATURE_FLAG_DEFINITIONS.length, EXPECTED_FEATURE_FLAG_COUNT);
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user