mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-13 18:32:12 +03:00
docs(reference): sync FEATURE_FLAGS.md with featureFlagDefinitions (#12552)
The doc promised a 1:1 catalog and had drifted to 20 missing keys, two rows that were not flags at all, and a port number that disagreed with the code. Good call adding a static sync test — it caught its own drift immediately: I added the `SERVER_OWNED_TOOL_LOOP_ENABLED` row the base gained after you wrote this. --- Validated in one consolidated worktree cut from `release/v3.8.51`, boarded with the rest of this batch — zero conflicts between the 19 PRs. - `typecheck:core` clean; `check:dashboard-typecheck` OK (206 pre-existing, all within the frozen baseline); `check:changelog-integrity` OK - complexity 2802 / baseline 3218 and cognitive-complexity 1267 / baseline 1437 — both under baseline - 226 of 228 focused assertions green across the batch's 23 test files. The 2 remaining belong to #12551, which is held separately. Two batch-owned defects were found and fixed in flight, both pure base drift: `173_xp_action_counts.sql` collided with `173_call_logs_video_content_removed.sql` (renumbered to 176 on #12651 — it aborted every DB open, which is what 53 of the first run's failures were), and the feature-flag catalog was missing the `SERVER_OWNED_TOOL_LOOP_ENABLED` row the base gained after #12552 was written. ⚠️ base-red inherited: #12732 — `Docs Gates`, `Merge integrity`, `No new ESLint warnings`, `Unit Tests fast-path` and `Fast Quality Gates` reproduce on the pure tip (provider count 356 vs the 358 the modules define, SKILL.md drift, and `open-sse/utils/stream.ts` at 3115 > frozen 3098, which this batch does not touch). Thanks @pacocartones — the `file:line` citations and the explicit out-of-scope notes on every one of these made a 19-PR batch reviewable in one pass.
This commit is contained in:
1
changelog.d/fixes/12552-feature-flags-reference-sync.md
Normal file
1
changelog.d/fixes/12552-feature-flags-reference-sync.md
Normal file
@@ -0,0 +1 @@
|
||||
- **docs(reference):** bring the `FEATURE_FLAGS.md` catalog back to 1:1 with `featureFlagDefinitions.ts` — 20 missing flags added, the two `*_BLOCK_THRESHOLD` env-only knobs moved out of the flag tables, category/total counts and the Live WS port corrected, guarded by a static test (#12552 — thanks @pacocartones)
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
title: "Feature Flags"
|
||||
version: 3.8.40
|
||||
lastUpdated: 2026-06-28
|
||||
version: 3.8.51
|
||||
lastUpdated: 2026-09-03
|
||||
---
|
||||
|
||||
# Feature Flags
|
||||
@@ -46,66 +46,85 @@ A boolean flag is considered **enabled** when its effective value is `"true"`,
|
||||
|
||||
## Flag Catalog
|
||||
|
||||
37 flags across 6 categories. **Default** is the definition default — the value
|
||||
55 flags across 6 categories. **Default** is the definition default — the value
|
||||
used when neither a DB override nor an environment variable is present.
|
||||
|
||||
### Security (7)
|
||||
### Security (10)
|
||||
|
||||
| Key | Type | Default | Description |
|
||||
| --------------------------------- | ------- | --------- | ------------------------------------------------------------------------------------------------------------------- |
|
||||
| `REQUIRE_API_KEY` | boolean | `false` | Require an API key for all incoming requests. |
|
||||
| `INPUT_SANITIZER_ENABLED` | boolean | `true` | Enable input sanitization for all requests. |
|
||||
| `INJECTION_GUARD_MODE` | enum | `off` | Prompt injection guard mode. Values: `off`, `warn`, `block`, `redact`. |
|
||||
| `INPUT_SANITIZER_BLOCK_THRESHOLD` | enum | `high` | Minimum severity blocked when mode is `block` (`high`/`medium`/`low`). Medium families are observe-only at default. |
|
||||
| `INJECTION_GUARD_BLOCK_THRESHOLD` | enum | _(unset)_ | Legacy alias for `INPUT_SANITIZER_BLOCK_THRESHOLD`. |
|
||||
| `PII_REDACTION_ENABLED` | boolean | `false` | Redact PII from requests (independent of `INPUT_SANITIZER_MODE`). |
|
||||
| `PII_RESPONSE_SANITIZATION` | boolean | `false` | Sanitize PII from provider responses. |
|
||||
| `PII_RESPONSE_SANITIZATION_MODE` | enum | `redact` | Mode for PII response sanitization. Values: `redact`, `warn`, `block`, `off`. |
|
||||
| `OUTBOUND_SSRF_GUARD_ENABLED` | boolean | `true` | Block outbound requests to private/internal IP ranges. |
|
||||
| Key | Type | Default | Description |
|
||||
| --------------------------------------- | ------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `REQUIRE_API_KEY` | boolean | `false` | Require an API key for all incoming requests. |
|
||||
| `INPUT_SANITIZER_ENABLED` | boolean | `true` | Enable input sanitization for all requests. |
|
||||
| `INJECTION_GUARD_MODE` | enum | `off` | Prompt injection guard mode. Values: `off`, `warn`, `block`, `redact`. |
|
||||
| `PII_REDACTION_ENABLED` | boolean | `false` | Redact PII from requests (independent of `INPUT_SANITIZER_MODE`). |
|
||||
| `PII_RESPONSE_SANITIZATION` | boolean | `false` | Sanitize PII from provider responses. |
|
||||
| `PII_RESPONSE_SANITIZATION_MODE` | enum | `redact` | Mode for PII response sanitization. Values: `redact`, `warn`, `block`, `off`. |
|
||||
| `OUTBOUND_SSRF_GUARD_ENABLED` | boolean | `true` | Block outbound requests to private/internal IP ranges. |
|
||||
| `ALLOW_API_KEY_REVEAL` | boolean | `false` | Allow authenticated dashboard users to reveal stored API keys instead of only seeing masked values. |
|
||||
| `AUTH_LOG_INCLUDE_ACCOUNT_ID` | boolean | `false` | Include account prefix in AUTH log lines (e.g. "Using <provider> account: abc12345..."). Disabled by default so account identifiers are redacted from shared/multi-tenant process logs. Independent from Debug Mode; flipping Debug Mode does not reveal this. |
|
||||
| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | boolean | `false` | When OIDC is enabled, disable password login so users can only authenticate via OIDC Single Sign-On. When disabled (default), both password login and OIDC are available. |
|
||||
|
||||
### Network (8)
|
||||
### Network (9)
|
||||
|
||||
| Key | Type | Default | Restart | Description |
|
||||
| ----------------------------------------------- | ------- | ------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `ENABLE_TLS_FINGERPRINT` | boolean | `false` | ✓ | Enable TLS fingerprint stealth mode. |
|
||||
| `PROXY_AUTO_SELECT_ENABLED` | boolean | `false` | | When no proxy is assigned to a connection, auto-select the first working proxy from the registry. Off by default (otherwise any registry proxy becomes a global fallback — #3332). |
|
||||
| `OMNIROUTE_CONTROL_PLANE_PROXY_DIRECT_FALLBACK` | boolean | `false` | | Allow OAuth and provider validation flows to bypass a pinned proxy and connect directly when proxy reachability pre-checks fail. Off by default because this can change egress IP. |
|
||||
| `MITM_DISABLE_TLS_VERIFY` | boolean | `false` | ✓ | Disable TLS certificate verification for the MITM proxy. **Danger.** |
|
||||
| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | boolean | `false` | | Allow provider URLs pointing to private/internal networks. |
|
||||
| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | boolean | `true` | | Allow adding/validating providers on local/private addresses (127.0.0.1, localhost, LAN). On by default (local-first); disable for strict public-only blocking. Cloud-metadata stays blocked. |
|
||||
| `ENABLE_CC_COMPATIBLE_PROVIDER` | boolean | `false` | ✓ | Enable Claude Code compatible provider mode. |
|
||||
| Key | Type | Default | Restart | Description |
|
||||
| ----------------------------------------------- | ------- | ------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `ENABLE_TLS_FINGERPRINT` | boolean | `false` | ✓ | Enable TLS fingerprint stealth mode. |
|
||||
| `AUDIO_REMOTE_PROVIDER_NODES` | boolean | `false` | | Allow the /v1/audio/* routes to use OpenAI-compatible provider nodes hosted outside localhost. Off by default — routing audio to a remote host changes egress identity and must be an explicit operator decision. Loopback nodes are always allowed and unaffected. |
|
||||
| `PROXY_AUTO_SELECT_ENABLED` | boolean | `false` | | When no proxy is assigned to a connection, auto-select the first working proxy from the registry. Off by default (otherwise any registry proxy becomes a global fallback — #3332). |
|
||||
| `OMNIROUTE_CONTROL_PLANE_PROXY_DIRECT_FALLBACK` | boolean | `false` | | Allow OAuth and provider validation flows to bypass a pinned proxy and connect directly when proxy reachability pre-checks fail. Off by default because this can change egress IP. |
|
||||
| `NETWORK_ROTATION_SHARED_EGRESS_GUARD` | boolean | `true` | | On a network exception (timeout, connection refused/reset) for a multi-account rotation executor, when the failing account has no dedicated proxy, apply a short cooldown and skip other proxy-less accounts for the rest of the request instead of retrying each one. On by default (safe: no egress IP change, only reduces latency/cooldown risk on shared-egress accounts). Disable to restore immediate propagation on the first proxy-less throw. |
|
||||
| `MITM_DISABLE_TLS_VERIFY` | boolean | `false` | ✓ | Disable TLS certificate verification for the MITM proxy. **Danger.** |
|
||||
| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | boolean | `false` | | Allow provider URLs pointing to private/internal networks. |
|
||||
| `OMNIROUTE_ALLOW_LOCAL_PROVIDER_URLS` | boolean | `true` | | Allow adding/validating providers on local/private addresses (127.0.0.1, localhost, LAN). On by default (local-first); disable for strict public-only blocking. Cloud-metadata stays blocked. |
|
||||
| `ENABLE_CC_COMPATIBLE_PROVIDER` | boolean | `false` | ✓ | Enable Claude Code compatible provider mode. |
|
||||
|
||||
### Policies (3)
|
||||
### Policies (5)
|
||||
|
||||
| Key | Type | Default | Restart | Description |
|
||||
| ------------------------------- | ------- | ---------- | ------- | ---------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `TOOL_POLICY_MODE` | enum | `disabled` | | Tool-use policy enforcement mode. Values: `disabled`, `warn`, `block`. |
|
||||
| `RATE_LIMIT_AUTO_ENABLE` | boolean | `false` | | Automatically enable rate limiting based on usage patterns. |
|
||||
| `DISABLE_CONTEXT_WINDOW_CHECKS` | boolean | `false` | | Skip OmniRoute's local context-window / max-input-token check for direct single-model requests. Upstream limits still apply. |
|
||||
| Key | Type | Default | Description |
|
||||
| ------------------------------- | ------- | ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `TOOL_POLICY_MODE` | enum | `disabled` | Tool-use policy enforcement mode. Values: `disabled`, `warn`, `block`. |
|
||||
| `RATE_LIMIT_AUTO_ENABLE` | boolean | `false` | Automatically enable rate limiting based on usage patterns. |
|
||||
| `DISABLE_CONTEXT_WINDOW_CHECKS` | boolean | `false` | Skip OmniRoute's local context-window / max-input-token check for direct single-model requests. Upstream limits still apply. |
|
||||
| `CAPABILITY_FILTER_ENABLED` | boolean | `false` | Reject requests before dispatch when the target model lacks required capabilities (vision, tools, structured output, context window). Protects direct single-provider requests that bypass the combo-layer compatibility filter. |
|
||||
| `RADAR_ENABLED` | boolean | `false` | Enable the OmniRoute Radar module (catalog feed screens and sync). Off by default; enabling only unlocks the UI — data sync remains a separate opt-in. |
|
||||
|
||||
### Runtime (11)
|
||||
### Runtime (23)
|
||||
|
||||
| Key | Type | Default | Restart | Description |
|
||||
| ------------------------------------------- | ------- | ------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `EXPOSE_CC_DISCOVERY_ALIASES` | boolean | `false` | | Advertise `claude/<provider>/<model>` mirror ids on `/v1/models` so Claude Code gateway model discovery lists non-Claude models. Global level of the three-level gate (env wins over the dashboard override). See [Claude Code configuration](../guides/CLAUDE-CODE-CONFIGURATION.md#discovery-aliases--surface-non-claude-models-in-the-model-picker). |
|
||||
| `OMNIROUTE_MCP_ENFORCE_SCOPES` | boolean | `true` | | Enforce scope restrictions on MCP tool access. |
|
||||
| `OMNIROUTE_MCP_COMPRESS_DESCRIPTIONS` | boolean | `false` | | Compress MCP tool descriptions to reduce token usage. |
|
||||
| `OMNIROUTE_ENABLE_RUNTIME_BACKGROUND_TASKS` | boolean | `false` | | Enable background task processing at runtime. |
|
||||
| `OMNIROUTE_DISABLE_BACKGROUND_SERVICES` | boolean | `false` | ✓ | Disable all background services (quota refresh, sync, etc). |
|
||||
| `OMNIROUTE_RTK_TRUST_PROJECT_FILTERS` | boolean | `false` | | Trust project-level RTK filters without validation. |
|
||||
| `OMNIROUTE_ENABLE_LIVE_WS` | boolean | `true` | ✓ | Start the real-time dashboard WebSocket server on import (port 20129 by default). |
|
||||
| `OMNIROUTE_CODEX_WS_ENABLED` | boolean | `true` | | Allow Codex to use the Responses-over-WebSocket transport. When off, Codex falls back to HTTP Responses. |
|
||||
| `OMNIROUTE_EMERGENCY_FALLBACK` | boolean | `true` | | Route budget-exhausted requests to the emergency free fallback provider/model. (See [Emergency Budget Fallback](#emergency-budget-fallback) below.) |
|
||||
| `MODEL_CATALOG_INCLUDE_NAMES` | boolean | `true` | | Include display-friendly name fields in `/v1/models` responses. Disable for clients that expect model IDs only. |
|
||||
| `ARENA_ELO_SYNC_ENABLED` | boolean | `true` | | Enable periodic Arena AI leaderboard ELO sync for model intelligence rankings. |
|
||||
| Key | Type | Default | Restart | Description |
|
||||
| ------------------------------------------- | ------- | ------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `UNIVERSAL_CONTEXT_HANDOFF_ENABLED` | boolean | `true` | | Generate and inject conversation summaries when combo routing switches models. Disable to treat model switches independently and prevent background handoff requests for all existing and future combos. |
|
||||
| `RESPONSES_PASSTHROUGH_DROP_COMMENTARY` | boolean | `true` | | Drop internal commentary-phase output items from Responses API passthrough streams before forwarding to clients. Disable to receive raw upstream commentary. |
|
||||
| `OMNIROUTE_MCP_ENFORCE_SCOPES` | boolean | `true` | | Enforce scope restrictions on MCP tool access. |
|
||||
| `OMNIROUTE_MCP_COMPRESS_DESCRIPTIONS` | boolean | `false` | | Compress MCP tool descriptions to reduce token usage. |
|
||||
| `OMNIROUTE_ENABLE_RUNTIME_BACKGROUND_TASKS` | boolean | `false` | | Enable background task processing at runtime. |
|
||||
| `OMNIROUTE_DISABLE_BACKGROUND_SERVICES` | boolean | `false` | ✓ | Disable all background services (quota refresh, sync, etc). |
|
||||
| `OMNIROUTE_RTK_TRUST_PROJECT_FILTERS` | boolean | `false` | | Trust project-level RTK filters without validation. |
|
||||
| `OMNIROUTE_ENABLE_LIVE_WS` | boolean | `true` | ✓ | Start the real-time dashboard WebSocket server on import (port 20132 by default). |
|
||||
| `OMNIROUTE_CODEX_WS_ENABLED` | boolean | `true` | | Allow Codex to use the Responses-over-WebSocket transport. When off, Codex falls back to HTTP Responses. |
|
||||
| `OMNIROUTE_CODEX_APP_SERVER_ENABLED` | boolean | `true` | | Allow Codex to use the local app-server WebSocket JSON-RPC transport (codexTransport=app-server). When off, connections opted into app-server fall back to Codex's other transports. |
|
||||
| `OMNIROUTE_EMERGENCY_FALLBACK` | boolean | `true` | | Route budget-exhausted requests to the emergency free fallback provider/model. (See [Emergency Budget Fallback](#emergency-budget-fallback) below.) |
|
||||
| `STREAM_RECOVERY_ENABLED` | boolean | `false` | | Enable transparent early retry for truncated upstream SSE streams before any response bytes reach the client. |
|
||||
| `STREAM_RECOVERY_MIDSTREAM_ENABLED` | boolean | `false` | | Allow stream recovery to re-request and stitch a response after bytes have already reached the client. |
|
||||
| `MODEL_CATALOG_INCLUDE_NAMES` | boolean | `true` | | Include display-friendly name fields in `/v1/models` responses. Disable for clients that expect model IDs only. |
|
||||
| `MODELS_CATALOG_PREFIX_MODE` | enum | `dual` | | Controls how model IDs are prefixed in /v1/models. 'dual' (default) emits both alias and canonical provider-id prefixes for backward compatibility. 'alias' emits only the short alias prefix (e.g. ds-web/model, not deepseek-web/model). 'canonical' emits only the full provider-id prefix. Values: `dual`, `alias`, `canonical`. |
|
||||
| `ARENA_ELO_SYNC_ENABLED` | boolean | `true` | | Enable periodic Arena AI leaderboard ELO sync for model intelligence rankings. |
|
||||
| `EXPOSE_CC_DISCOVERY_ALIASES` | boolean | `false` | | Advertise `claude/<provider>/<model>` mirror ids on `/v1/models` so Claude Code gateway model discovery lists non-Claude models. Global level of the three-level gate (env wins over the dashboard override). See [Claude Code configuration](../guides/CLAUDE-CODE-CONFIGURATION.md#discovery-aliases--surface-non-claude-models-in-the-model-picker). |
|
||||
| `NO_THINKING_ALIAS_ENABLED` | boolean | `true` | | Master switch for the no-think/<provider>/<model> gateway aliases. On (default): /v1/models advertises a no-thinking variant for every eligible thinking-capable Claude model, and a no-think/ id sent on a request resolves back to the real model with reasoning suppressed. Off: no variants are advertised and a no-think/ id is treated like any other unknown model id. The per-model ModelSpec.noThinkingAlias opt-in/opt-out still applies while this is on. |
|
||||
| `OMNIROUTE_DISABLE_THINKING_LEVEL_VARIANTS` | boolean | `false` | | Disable the generation of thinking level variants (e.g. -low, -medium, -high) in the /v1/models catalog. |
|
||||
| `OMNIROUTE_CHAT_VIRTUAL_LANES` | boolean | `false` | ✓ | 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. |
|
||||
| `EXPOSE_FUNCTIONAL_GATEWAY_MIRRORS` | boolean | `false` | | Advertise <gateway-alias>/<model> mirror ids on /v1/models for models whose canonical owner has no active credential but a passthrough gateway with an active credential routes them. Warning: adds catalog entries for all clients when enabled globally. |
|
||||
| `NEWAPI_AGGREGATOR_BALANCE` | boolean | `false` | | Enable balance detection for New-API / One-API / Sub2API aggregator compatible nodes. When enabled, compatible nodes with the aggregator flag set will report their balance in the dashboard and quota-preflight routing. |
|
||||
| `SERVER_OWNED_TOOL_LOOP_ENABLED` | boolean | `false` | | Continue non-streaming server-owned tool calls until the model returns a client-usable response. |
|
||||
|
||||
### CLI (3)
|
||||
### CLI (5)
|
||||
|
||||
| Key | Type | Default | Restart | Description |
|
||||
| ---------------------------- | ------- | ------- | ------- | -------------------------------------------------------------------------------------------------------------- |
|
||||
| `CLI_COMPAT_ALL` | boolean | `false` | ✓ | Enable compatibility mode for all CLI clients. |
|
||||
| `MODEL_ALIAS_COMPAT_ENABLED` | boolean | `false` | | Enable model alias compatibility layer. |
|
||||
| `PRICING_SYNC_ENABLED` | boolean | `false` | | Enable automatic pricing data synchronization (also requires the `PRICING_SYNC_ENABLED` environment variable). |
|
||||
| Key | Type | Default | Restart | Description |
|
||||
| ------------------------------------- | ------- | ------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `CLI_COMPAT_ALL` | boolean | `false` | ✓ | Enable compatibility mode for all CLI clients. |
|
||||
| `MODEL_ALIAS_COMPAT_ENABLED` | boolean | `false` | | Enable model alias compatibility layer. |
|
||||
| `PRICING_SYNC_ENABLED` | boolean | `false` | | Enable automatic pricing data synchronization (also requires the `PRICING_SYNC_ENABLED` environment variable). |
|
||||
| `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` | boolean | `false` | | After a provider model sync, automatically (re)write ~/.codex/*.config.toml profile files from the live catalog. Never changes the active/default Codex config. Off by default. |
|
||||
| `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` | boolean | `false` | | After a provider model sync, automatically (re)write ~/.claude/profiles/<name>/settings.json Claude Code profiles from the live catalog. Never changes the active/default Claude config. Off by default. |
|
||||
|
||||
### Health (3)
|
||||
|
||||
@@ -115,6 +134,14 @@ used when neither a DB override nor an environment variable is present.
|
||||
| `OMNIROUTE_DISABLE_TOKEN_HEALTHCHECK` | boolean | `false` | Disable the token validation health check. |
|
||||
| `SKILLS_SANDBOX_NETWORK_ENABLED` | boolean | `false` | Enable network access in the skills sandbox environment. |
|
||||
|
||||
> [!NOTE]
|
||||
> `INPUT_SANITIZER_BLOCK_THRESHOLD` and its legacy alias
|
||||
> `INJECTION_GUARD_BLOCK_THRESHOLD` tune the `block` mode of
|
||||
> `INJECTION_GUARD_MODE`, but they are plain environment variables read by
|
||||
> [`src/shared/utils/injectionSeverity.ts`](../../src/shared/utils/injectionSeverity.ts),
|
||||
> not feature flags: they have no DB override and no dashboard toggle. See
|
||||
> [`ENVIRONMENT.md`](./ENVIRONMENT.md#4-security--authentication).
|
||||
|
||||
> [!NOTE]
|
||||
> The `Restart` column marks flags with `requiresRestart: true` — the value is
|
||||
> persisted instantly but only takes effect after the process reloads. Enum
|
||||
@@ -168,10 +195,10 @@ Returns every flag with its effective value, source, and a summary.
|
||||
"requiresRestart": false,
|
||||
"warningLevel": "caution",
|
||||
},
|
||||
// ... all 33 flags
|
||||
// ... all 55 flags
|
||||
],
|
||||
"summary": {
|
||||
"total": 33,
|
||||
"total": 54,
|
||||
"active": 0,
|
||||
"inactive": 0,
|
||||
"overriddenByDb": 0,
|
||||
|
||||
@@ -128,12 +128,6 @@ const ENV_VAR_ALLOWLIST = new Set([
|
||||
"LINUX_GPG_KEY", // electron AppImage signing key, CI/build only (ELECTRON_GUIDE.md)
|
||||
"BRANCH_LOCK_TOKEN", // release branch-protection ops token (QUALITY_GATE_PLAYBOOK.md)
|
||||
"NEXT_LOCALE", // next-intl locale cookie name (I18N.md)
|
||||
// Feature flags are resolved by key at runtime — `resolveFeatureFlag()` reads
|
||||
// `process.env[key]` (src/shared/utils/featureFlags.ts), never a literal
|
||||
// `process.env.MODELS_CATALOG_PREFIX_MODE`, so this scan cannot see the read.
|
||||
// The flag is real: defined in featureFlagDefinitions.ts, overridable from the
|
||||
// dashboard or the environment. (API_REFERENCE.md, VSCODE-COPILOT.md)
|
||||
"MODELS_CATALOG_PREFIX_MODE",
|
||||
// Telegram Mini App integration (proposal TELEGRAM-MINIAPP.md, not yet implemented): env vars named in the feasibility analysis but no code reads them yet.
|
||||
"TELEGRAM_WEBHOOK_URL", // proposal-only: Telegram webhook public endpoint (TELEGRAM-MINIAPP.md, future feature)
|
||||
"TELEGRAM_WEBHOOK_SECRET", // proposal-only: Telegram webhook HMAC secret (TELEGRAM-MINIAPP.md, future feature)
|
||||
@@ -581,6 +575,24 @@ export function buildCodebaseIndex(root = ROOT) {
|
||||
}
|
||||
readEnvContract();
|
||||
|
||||
// Feature flags are resolved by key at runtime — `resolveFeatureFlag()` reads
|
||||
// `process.env[definition.key]` (src/shared/utils/featureFlags.ts), never a
|
||||
// literal `process.env.<KEY>`, so the code-read index cannot see those reads.
|
||||
// Every key in FEATURE_FLAG_DEFINITIONS is therefore a real, env-overridable
|
||||
// knob (docs/reference/FEATURE_FLAGS.md documents the catalog 1:1).
|
||||
function readFeatureFlagContract() {
|
||||
try {
|
||||
const t = fs.readFileSync(
|
||||
path.join(root, "src", "shared", "constants", "featureFlagDefinitions.ts"),
|
||||
"utf8"
|
||||
);
|
||||
for (const m of t.matchAll(/^\s*key:\s*"([A-Z][A-Z0-9_]+)"/gm)) envVars.add(m[1]);
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
readFeatureFlagContract();
|
||||
|
||||
// Set of `omniroute <subcommand>` strings that exist in bin/
|
||||
const cliCommands = new Set();
|
||||
function walkCli(dir) {
|
||||
|
||||
100
tests/unit/feature-flags-doc-sync-static.test.ts
Normal file
100
tests/unit/feature-flags-doc-sync-static.test.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
import { test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { dirname, join } from "node:path";
|
||||
import { FEATURE_FLAG_DEFINITIONS } from "../../src/shared/constants/featureFlagDefinitions.ts";
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const root = join(__dirname, "..", "..");
|
||||
|
||||
/**
|
||||
* docs/reference/FEATURE_FLAGS.md promises that its catalog matches
|
||||
* FEATURE_FLAG_DEFINITIONS "1:1". Keep that promise checkable: every flag the
|
||||
* code defines must be a table row with the same type and default, every table
|
||||
* row must be a real flag, and the per-category / total counts must match.
|
||||
*/
|
||||
const doc = readFileSync(join(root, "docs/reference/FEATURE_FLAGS.md"), "utf8");
|
||||
const catalog = doc.slice(doc.indexOf("## Flag Catalog"), doc.indexOf("## Toggling Flags"));
|
||||
|
||||
interface DocRow {
|
||||
key: string;
|
||||
type: string;
|
||||
defaultValue: string;
|
||||
restart: boolean;
|
||||
category: string;
|
||||
}
|
||||
|
||||
function parseCatalog(): DocRow[] {
|
||||
const rows: DocRow[] = [];
|
||||
let category = "";
|
||||
for (const line of catalog.split("\n")) {
|
||||
const heading = line.match(/^### (\w+) \(\d+\)/);
|
||||
if (heading) {
|
||||
category = heading[1].toLowerCase();
|
||||
continue;
|
||||
}
|
||||
const cells = line.match(/^\| `([A-Z0-9_]+)` +\| (\w+) +\| ([^|]+?) +\|(.*)$/);
|
||||
if (!cells) continue;
|
||||
rows.push({
|
||||
key: cells[1],
|
||||
type: cells[2],
|
||||
defaultValue: cells[3].replace(/`/g, ""),
|
||||
restart: /^ *✓ *\|/.test(cells[4]),
|
||||
category,
|
||||
});
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
const docRows = parseCatalog();
|
||||
const docByKey = new Map(docRows.map((row) => [row.key, row]));
|
||||
|
||||
test("every defined feature flag has a catalog row in FEATURE_FLAGS.md", () => {
|
||||
const missing = FEATURE_FLAG_DEFINITIONS.filter((d) => !docByKey.has(d.key)).map((d) => d.key);
|
||||
assert.deepEqual(
|
||||
missing,
|
||||
[],
|
||||
`flags defined in featureFlagDefinitions.ts but absent from the doc: ${missing.join(", ")}`
|
||||
);
|
||||
});
|
||||
|
||||
test("every catalog row in FEATURE_FLAGS.md is a defined feature flag", () => {
|
||||
const known = new Set(FEATURE_FLAG_DEFINITIONS.map((d) => d.key));
|
||||
const extra = docRows.filter((row) => !known.has(row.key)).map((row) => row.key);
|
||||
assert.deepEqual(
|
||||
extra,
|
||||
[],
|
||||
`doc rows that are not feature flags (env-only knobs belong in ENVIRONMENT.md): ${extra.join(", ")}`
|
||||
);
|
||||
});
|
||||
|
||||
test("catalog rows carry the code's category, type, default and restart hint", () => {
|
||||
const mismatches: string[] = [];
|
||||
for (const def of FEATURE_FLAG_DEFINITIONS) {
|
||||
const row = docByKey.get(def.key);
|
||||
if (!row) continue;
|
||||
if (row.category !== def.category)
|
||||
mismatches.push(`${def.key}: category doc=${row.category} code=${def.category}`);
|
||||
if (row.type !== def.type) mismatches.push(`${def.key}: type doc=${row.type} code=${def.type}`);
|
||||
if (row.defaultValue !== def.defaultValue)
|
||||
mismatches.push(`${def.key}: default doc=${row.defaultValue} code=${def.defaultValue}`);
|
||||
if (row.restart !== def.requiresRestart)
|
||||
mismatches.push(`${def.key}: requiresRestart doc=${row.restart} code=${def.requiresRestart}`);
|
||||
}
|
||||
assert.deepEqual(mismatches, []);
|
||||
});
|
||||
|
||||
test("category headings and the total match the number of defined flags", () => {
|
||||
const perCategory = new Map<string, number>();
|
||||
for (const def of FEATURE_FLAG_DEFINITIONS) {
|
||||
perCategory.set(def.category, (perCategory.get(def.category) ?? 0) + 1);
|
||||
}
|
||||
for (const [, name, count] of catalog.matchAll(/^### (\w+) \((\d+)\)/gm)) {
|
||||
assert.equal(Number(count), perCategory.get(name.toLowerCase()), `heading count for ${name}`);
|
||||
}
|
||||
const total = catalog.match(/^(\d+) flags across (\d+) categories/m);
|
||||
assert.ok(total, "expected an '<N> flags across <M> categories' summary line");
|
||||
assert.equal(Number(total[1]), FEATURE_FLAG_DEFINITIONS.length, "total flag count");
|
||||
assert.equal(Number(total[2]), perCategory.size, "category count");
|
||||
});
|
||||
Reference in New Issue
Block a user