feat(chat-admission): expose admission tunables via dashboard settings (#12038)

* feat(chat-admission): add settings store for admission tunables

* fix(chat-admission): extract parseEnvNumber to reduce cyclomatic complexity

* fix(chat-admission): repair settings store write path and add coverage

The settings store could not persist anything: `updateChatAdmissionSettings`
targeted an `updated_at` column that `key_value` does not have (the schema is
namespace/key/value — src/lib/db/core.ts), so every write threw
`table key_value has no column named updated_at`.

Also fixes, found while adding the tests:

- `getChatAdmissionSettingsSource` returned a partial map (only the keys whose
  layer differed from the default) and dropped the unset keys entirely, so a
  dashboard reading it could not render a complete row.
- env parsing used `parseFloat` for the shed ratio, so `"0.5x"` was silently
  accepted as 0.5 while `chatBodyAdmission.ts` rejects that same input — both
  paths now share one per-field predicate table.
- DB reads validated `typeof === "number"` but not integrality/range, so a
  hand-edited row could serve `2.5` or `-1` to the admission controller.
- writes persisted unvalidated input.
- malformed, non-object, and partial rows are now tolerated per field.

Adds tests/unit/db-chat-admission-settings.test.ts (17 cases) covering CRUD
round-trips, namespace isolation, reset, env parsing/validation boundaries,
env-over-DB precedence, provenance, normalization on write, and malformed-row
tolerance, per Hard Rule #8.

Verification: eslint clean; `npm run typecheck:core` clean; the new suite plus
the two sibling settings suites pass 63/63; check-complexity-ratchets reports
complexityNewCode=0; check-db-rules OK; check-env-doc-sync OK (all three vars
are already documented in .env.example).

---------

Co-authored-by: oyi77 <oyi77@users.noreply.github.com>
This commit is contained in:
Paijo
2026-09-18 22:20:53 +07:00
committed by GitHub
parent d8be3b1a77
commit 023a57476f
2 changed files with 503 additions and 0 deletions

View File

@@ -0,0 +1,211 @@
/**
* chatAdmissionSettings.ts — Persisted settings for the chat admission controller.
*
* These values live in `key_value` under the `settings` namespace so they can be
* edited from the dashboard without touching env files. Env vars still win as
* operator overrides; DB values are the portable deployment default.
*/
import { getDbInstance } from "./core";
import { invalidateDbCache } from "./readCache";
const NAMESPACE = "settings";
const SETTINGS_KEY = "chatAdmissionSettings";
export interface ChatAdmissionSettings {
chatMaxHeavyInFlight: number;
chatAdmissionHeapShedRatio: number;
chatAdmissionHealthyHeadroom: number;
}
export type ChatAdmissionSettingKey = keyof ChatAdmissionSettings;
export type ChatAdmissionSettingSource = "env" | "db" | "default";
export const DEFAULT_CHAT_ADMISSION_SETTINGS: ChatAdmissionSettings = {
chatMaxHeavyInFlight: 1,
chatAdmissionHeapShedRatio: 0.75,
chatAdmissionHealthyHeadroom: 1,
};
/**
* Env var backing each tunable. Presence of a non-blank var is what makes the
* operator override bind — an explicit `=1` must win over a DB value of `1` too,
* matching `resolveLegacyCountCap()` in `chatBodyAdmission.ts`.
*/
const ENV_KEYS: Record<ChatAdmissionSettingKey, string> = {
chatMaxHeavyInFlight: "OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT",
chatAdmissionHeapShedRatio: "OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO",
chatAdmissionHealthyHeadroom: "OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM",
};
/**
* Per-field validity predicates, shared by the env and DB paths so the two can
* never drift: a value a hand-edited DB row would reject must also be rejected
* when it arrives through the environment, and vice versa.
*/
function isValid(key: ChatAdmissionSettingKey, value: number): boolean {
switch (key) {
case "chatMaxHeavyInFlight":
return Number.isSafeInteger(value) && value >= 1;
case "chatAdmissionHeapShedRatio":
return Number.isFinite(value) && value > 0 && value <= 1;
case "chatAdmissionHealthyHeadroom":
return Number.isSafeInteger(value) && value >= 0;
}
}
/**
* Env parsing mirrors `chatBodyAdmission.ts`: `parseInt` for the integer
* tunables (so `"5x"` reads as 5), `Number` for the ratio (so `"0.5x"` is
* rejected rather than silently truncated to 0.5).
*/
function parseSetting(key: ChatAdmissionSettingKey, raw: string): number {
return key === "chatAdmissionHeapShedRatio" ? Number(raw) : Number.parseInt(raw, 10);
}
function isEnvProvided(key: ChatAdmissionSettingKey): boolean {
const raw = process.env[ENV_KEYS[key]];
return raw !== undefined && raw.trim() !== "";
}
/** Env value for one key; the default when unset or malformed. */
function readEnvValue(key: ChatAdmissionSettingKey): number {
const fallback = DEFAULT_CHAT_ADMISSION_SETTINGS[key];
if (!isEnvProvided(key)) return fallback;
const parsed = parseSetting(key, process.env[ENV_KEYS[key]] as string);
return isValid(key, parsed) ? parsed : fallback;
}
/** A field from an untrusted record, or the default when absent/out-of-range. */
function pickSetting(source: Record<string, unknown>, key: ChatAdmissionSettingKey): number {
const value = source[key];
return typeof value === "number" && isValid(key, value)
? value
: DEFAULT_CHAT_ADMISSION_SETTINGS[key];
}
/**
* The stored row as a raw record, or `null` when absent, unparsable, or not a
* JSON object. Keeps the malformed-row case distinguishable from "no row" so
* provenance reporting does not have to guess.
*/
function readStoredSettings(): Record<string, unknown> | null {
const db = getDbInstance();
const row = db
.prepare("SELECT value FROM key_value WHERE namespace = ? AND key = ?")
.get(NAMESPACE, SETTINGS_KEY) as { value?: string } | undefined;
if (!row?.value) return null;
try {
const parsed: unknown = JSON.parse(row.value);
return parsed !== null && typeof parsed === "object"
? (parsed as Record<string, unknown>)
: null;
} catch {
return null;
}
}
/**
* Coerce an untrusted settings payload (parsed DB row or caller input) into a
* fully-validated object. Absent or out-of-range fields fall back to defaults,
* so a bad write can never persist a value the readers would reject anyway.
*/
export function normalizeChatAdmissionSettings(input: unknown): ChatAdmissionSettings {
const source =
input !== null && typeof input === "object" ? (input as Record<string, unknown>) : {};
return {
chatMaxHeavyInFlight: pickSetting(source, "chatMaxHeavyInFlight"),
chatAdmissionHeapShedRatio: pickSetting(source, "chatAdmissionHeapShedRatio"),
chatAdmissionHealthyHeadroom: pickSetting(source, "chatAdmissionHealthyHeadroom"),
};
}
export function readChatAdmissionSettingsFromEnv(): ChatAdmissionSettings {
return {
chatMaxHeavyInFlight: readEnvValue("chatMaxHeavyInFlight"),
chatAdmissionHeapShedRatio: readEnvValue("chatAdmissionHeapShedRatio"),
chatAdmissionHealthyHeadroom: readEnvValue("chatAdmissionHealthyHeadroom"),
};
}
export function readChatAdmissionSettingsFromDb(): ChatAdmissionSettings {
const stored = readStoredSettings();
return stored === null ? DEFAULT_CHAT_ADMISSION_SETTINGS : normalizeChatAdmissionSettings(stored);
}
/** DB value wins only where no env override is present — resolved per key. */
export function getEffectiveChatAdmissionSettings(): ChatAdmissionSettings {
const db = readChatAdmissionSettingsFromDb();
return {
chatMaxHeavyInFlight: isEnvProvided("chatMaxHeavyInFlight")
? readEnvValue("chatMaxHeavyInFlight")
: db.chatMaxHeavyInFlight,
chatAdmissionHeapShedRatio: isEnvProvided("chatAdmissionHeapShedRatio")
? readEnvValue("chatAdmissionHeapShedRatio")
: db.chatAdmissionHeapShedRatio,
chatAdmissionHealthyHeadroom: isEnvProvided("chatAdmissionHealthyHeadroom")
? readEnvValue("chatAdmissionHealthyHeadroom")
: db.chatAdmissionHealthyHeadroom,
};
}
/**
* Per-key provenance for the dashboard: which layer supplies the effective
* value. A key reports `"db"` only when a row exists AND that field survived
* validation — a present-but-malformed field reports `"default"` rather than
* claiming the DB supplies a value it does not.
*/
export function getChatAdmissionSettingsSource(): Record<
ChatAdmissionSettingKey,
ChatAdmissionSettingSource
> {
const stored = readStoredSettings();
const storedSource = (key: ChatAdmissionSettingKey): ChatAdmissionSettingSource => {
const value = stored?.[key];
return typeof value === "number" && isValid(key, value) ? "db" : "default";
};
return {
chatMaxHeavyInFlight: isEnvProvided("chatMaxHeavyInFlight")
? "env"
: storedSource("chatMaxHeavyInFlight"),
chatAdmissionHeapShedRatio: isEnvProvided("chatAdmissionHeapShedRatio")
? "env"
: storedSource("chatAdmissionHeapShedRatio"),
chatAdmissionHealthyHeadroom: isEnvProvided("chatAdmissionHealthyHeadroom")
? "env"
: storedSource("chatAdmissionHealthyHeadroom"),
};
}
/**
* Persist the tunables and return the newly EFFECTIVE settings — when an env
* override is present the returned value reflects the override, not `next`, so
* the caller's response never advertises a value the runtime will not use.
*/
export async function updateChatAdmissionSettings(
next: ChatAdmissionSettings
): Promise<ChatAdmissionSettings> {
const db = getDbInstance();
db.prepare("INSERT OR REPLACE INTO key_value (namespace, key, value) VALUES (?, ?, ?)").run(
NAMESPACE,
SETTINGS_KEY,
JSON.stringify(normalizeChatAdmissionSettings(next))
);
invalidateDbCache("settings");
return getEffectiveChatAdmissionSettings();
}
/** Drop the stored override; effective settings fall back to env/defaults. */
export async function resetChatAdmissionSettings(): Promise<ChatAdmissionSettings> {
const db = getDbInstance();
db.prepare("DELETE FROM key_value WHERE namespace = ? AND key = ?").run(NAMESPACE, SETTINGS_KEY);
invalidateDbCache("settings");
return getEffectiveChatAdmissionSettings();
}

View File

@@ -0,0 +1,292 @@
import test from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-chat-admission-"));
process.env.DATA_DIR = TEST_DATA_DIR;
const core = await import("../../src/lib/db/core.ts");
const settings = await import("../../src/lib/db/chatAdmissionSettings.ts");
const ENV_NAMES = [
"OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT",
"OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO",
"OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM",
] as const;
const DEFAULTS = settings.DEFAULT_CHAT_ADMISSION_SETTINGS;
const SETTINGS_KEY = "chatAdmissionSettings";
function clearEnvOverrides() {
for (const name of ENV_NAMES) delete process.env[name];
}
function cleanupGlobalDb() {
const holder = globalThis as { __omnirouteDb?: { open?: boolean; close?: () => void } };
try {
if (holder.__omnirouteDb?.open) holder.__omnirouteDb.close?.();
} catch {
// ignore — the instance is being discarded either way.
}
delete holder.__omnirouteDb;
}
// `SQLITE_FILE` in src/lib/db/core.ts is a module-level constant derived from DATA_DIR at
// import time, so every test in this file must reuse the SAME directory path — a fresh
// mkdtemp per test would leave the DB pointed at the first one. Wipe and recreate it
// instead; rmSync's own retry loop absorbs a transient EBUSY from the closing handle.
function resetStorage() {
clearEnvOverrides();
cleanupGlobalDb();
core.resetDbInstance();
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 10, retryDelay: 50 });
fs.mkdirSync(TEST_DATA_DIR, { recursive: true });
core.getDbInstance();
}
function readRowValue(key: string): string | undefined {
const row = core
.getDbInstance()
.prepare("SELECT value FROM key_value WHERE namespace = ? AND key = ?")
.get("settings", key) as { value?: string } | undefined;
return row?.value;
}
/** Writes a raw row exactly as an operator hand-editing the DB would. */
function writeRawSettingsRow(value: string) {
core
.getDbInstance()
.prepare("INSERT OR REPLACE INTO key_value (namespace, key, value) VALUES (?, ?, ?)")
.run("settings", SETTINGS_KEY, value);
}
/** The stored settings blob re-parsed, or `undefined` when no row exists. */
function readStoredSettingsBlob(): unknown {
const raw = readRowValue(SETTINGS_KEY);
return raw === undefined ? undefined : JSON.parse(raw);
}
test.beforeEach(() => {
resetStorage();
});
test.after(() => {
clearEnvOverrides();
cleanupGlobalDb();
core.resetDbInstance();
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 10, retryDelay: 50 });
});
test("readers fall back to defaults when neither env nor DB supply a value", () => {
assert.deepEqual(settings.readChatAdmissionSettingsFromEnv(), DEFAULTS);
assert.deepEqual(settings.readChatAdmissionSettingsFromDb(), DEFAULTS);
assert.deepEqual(settings.getEffectiveChatAdmissionSettings(), DEFAULTS);
});
test("updateChatAdmissionSettings persists to the key_value schema and round-trips", async () => {
const next = {
chatMaxHeavyInFlight: 7,
chatAdmissionHeapShedRatio: 0.4,
chatAdmissionHealthyHeadroom: 3,
};
const returned = await settings.updateChatAdmissionSettings(next);
assert.deepEqual(returned, next);
assert.deepEqual(settings.readChatAdmissionSettingsFromDb(), next);
assert.deepEqual(readStoredSettingsBlob(), next);
});
test("updateChatAdmissionSettings leaves other settings-namespace rows untouched", async () => {
core
.getDbInstance()
.prepare("INSERT OR REPLACE INTO key_value (namespace, key, value) VALUES (?, ?, ?)")
.run("settings", "someOtherSetting", JSON.stringify("keep-me"));
await settings.updateChatAdmissionSettings({
chatMaxHeavyInFlight: 2,
chatAdmissionHeapShedRatio: 0.5,
chatAdmissionHealthyHeadroom: 1,
});
assert.equal(readRowValue("someOtherSetting"), JSON.stringify("keep-me"));
});
test("updateChatAdmissionSettings normalizes out-of-range input before persisting", async () => {
await settings.updateChatAdmissionSettings({
chatMaxHeavyInFlight: 0,
chatAdmissionHeapShedRatio: 2,
chatAdmissionHealthyHeadroom: Number.NaN,
});
assert.deepEqual(settings.readChatAdmissionSettingsFromDb(), {
chatMaxHeavyInFlight: DEFAULTS.chatMaxHeavyInFlight,
chatAdmissionHeapShedRatio: DEFAULTS.chatAdmissionHeapShedRatio,
chatAdmissionHealthyHeadroom: DEFAULTS.chatAdmissionHealthyHeadroom,
});
});
test("resetChatAdmissionSettings drops the stored row and returns effective defaults", async () => {
await settings.updateChatAdmissionSettings({
chatMaxHeavyInFlight: 4,
chatAdmissionHeapShedRatio: 0.25,
chatAdmissionHealthyHeadroom: 2,
});
assert.notDeepEqual(settings.readChatAdmissionSettingsFromDb(), DEFAULTS);
const returned = await settings.resetChatAdmissionSettings();
assert.equal(readRowValue(SETTINGS_KEY), undefined);
assert.deepEqual(returned, DEFAULTS);
assert.deepEqual(settings.readChatAdmissionSettingsFromDb(), DEFAULTS);
});
test("env parsing accepts valid values per field", () => {
process.env.OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT = "6";
process.env.OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO = "0.5";
process.env.OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM = "0";
assert.deepEqual(settings.readChatAdmissionSettingsFromEnv(), {
chatMaxHeavyInFlight: 6,
chatAdmissionHeapShedRatio: 0.5,
chatAdmissionHealthyHeadroom: 0,
});
});
test("env parsing rejects malformed values per field without discarding valid siblings", () => {
// chatMaxHeavyInFlight requires a safe integer >= 1: "Infinity" is invalid.
process.env.OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT = "Infinity";
// The ratio keeps the runtime constant's strict rule: "1.2" is outside (0, 1].
process.env.OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO = "1.2";
process.env.OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM = "5";
assert.deepEqual(settings.readChatAdmissionSettingsFromEnv(), {
chatMaxHeavyInFlight: DEFAULTS.chatMaxHeavyInFlight,
chatAdmissionHeapShedRatio: DEFAULTS.chatAdmissionHeapShedRatio,
chatAdmissionHealthyHeadroom: 5,
});
});
test("env parsing treats blank values as unset", () => {
process.env.OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT = " ";
process.env.OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO = "";
process.env.OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM = " ";
assert.deepEqual(settings.readChatAdmissionSettingsFromEnv(), DEFAULTS);
assert.deepEqual(settings.getChatAdmissionSettingsSource(), {
chatMaxHeavyInFlight: "default",
chatAdmissionHeapShedRatio: "default",
chatAdmissionHealthyHeadroom: "default",
});
});
test("env ratio boundary: 1 is accepted, 0 is rejected", () => {
process.env.OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO = "1";
assert.equal(settings.readChatAdmissionSettingsFromEnv().chatAdmissionHeapShedRatio, 1);
process.env.OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO = "0";
assert.equal(
settings.readChatAdmissionSettingsFromEnv().chatAdmissionHeapShedRatio,
DEFAULTS.chatAdmissionHeapShedRatio
);
});
test("env parsing mirrors the runtime parser for the integer tunables", () => {
// chatBodyAdmission.ts resolves these with parseInt, so "2.5" reads as 2 there too.
process.env.OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT = "2.5";
assert.equal(settings.readChatAdmissionSettingsFromEnv().chatMaxHeavyInFlight, 2);
});
test("a single env override does not collapse the other keys to their defaults", async () => {
await settings.updateChatAdmissionSettings({
chatMaxHeavyInFlight: 3,
chatAdmissionHeapShedRatio: 0.2,
chatAdmissionHealthyHeadroom: 4,
});
process.env.OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT = "9";
assert.deepEqual(settings.getEffectiveChatAdmissionSettings(), {
chatMaxHeavyInFlight: 9,
chatAdmissionHeapShedRatio: 0.2,
chatAdmissionHealthyHeadroom: 4,
});
assert.deepEqual(settings.getChatAdmissionSettingsSource(), {
chatMaxHeavyInFlight: "env",
chatAdmissionHeapShedRatio: "db",
chatAdmissionHealthyHeadroom: "db",
});
});
test("updateChatAdmissionSettings reports the effective value when an env override is set", async () => {
process.env.OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT = "11";
const returned = await settings.updateChatAdmissionSettings({
chatMaxHeavyInFlight: 2,
chatAdmissionHeapShedRatio: 0.6,
chatAdmissionHealthyHeadroom: 2,
});
// The response must not advertise a value the runtime will not honour.
assert.equal(returned.chatMaxHeavyInFlight, 11);
// The written row keeps what the operator actually asked for.
assert.deepEqual(readStoredSettingsBlob(), {
chatMaxHeavyInFlight: 2,
chatAdmissionHeapShedRatio: 0.6,
chatAdmissionHealthyHeadroom: 2,
});
});
test("readChatAdmissionSettingsFromDb tolerates unparsable and non-object rows", () => {
for (const raw of ["{not json", "null", "42", '"a string"', "[]"]) {
writeRawSettingsRow(raw);
assert.deepEqual(settings.readChatAdmissionSettingsFromDb(), DEFAULTS, `row: ${raw}`);
}
});
test("readChatAdmissionSettingsFromDb merges a partial row with defaults per field", () => {
writeRawSettingsRow(JSON.stringify({ chatMaxHeavyInFlight: 5 }));
assert.deepEqual(settings.readChatAdmissionSettingsFromDb(), {
chatMaxHeavyInFlight: 5,
chatAdmissionHeapShedRatio: DEFAULTS.chatAdmissionHeapShedRatio,
chatAdmissionHealthyHeadroom: DEFAULTS.chatAdmissionHealthyHeadroom,
});
});
test("readChatAdmissionSettingsFromDb rejects out-of-range fields in a hand-edited row", () => {
writeRawSettingsRow(
JSON.stringify({
chatMaxHeavyInFlight: 2.5,
chatAdmissionHeapShedRatio: 1.5,
chatAdmissionHealthyHeadroom: -1,
})
);
assert.deepEqual(settings.readChatAdmissionSettingsFromDb(), DEFAULTS);
});
test("readChatAdmissionSettingsFromDb ignores non-numeric field types", () => {
writeRawSettingsRow(
JSON.stringify({
chatMaxHeavyInFlight: "7",
chatAdmissionHeapShedRatio: null,
chatAdmissionHealthyHeadroom: true,
})
);
assert.deepEqual(settings.readChatAdmissionSettingsFromDb(), DEFAULTS);
});
test("getChatAdmissionSettingsSource reports db only for fields that survived validation", () => {
writeRawSettingsRow(JSON.stringify({ chatMaxHeavyInFlight: 4, chatAdmissionHeapShedRatio: 9 }));
assert.deepEqual(settings.getChatAdmissionSettingsSource(), {
chatMaxHeavyInFlight: "db",
// Present in the row but out of range, so the effective value is the default.
chatAdmissionHeapShedRatio: "default",
chatAdmissionHealthyHeadroom: "default",
});
});