chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 13:39:12 +08:00
commit d8dcd5f6d1
8604 changed files with 2479390 additions and 0 deletions
File diff suppressed because it is too large Load Diff
+219
View File
@@ -0,0 +1,219 @@
---
title: "Authorization Guide"
version: 3.8.40
lastUpdated: 2026-06-28
---
# Authorization Guide
> **Source of truth:** `src/server/authz/`, `src/shared/constants/publicApiRoutes.ts`, `src/lib/api/requireManagementAuth.ts`, `src/shared/utils/apiAuth.ts`
> **Last updated:** 2026-06-28 — v3.8.40
OmniRoute has a route-aware authorization pipeline that gates every API request. Classification is **deterministic** and **fail-closed** — anything that cannot be classified ends up as `MANAGEMENT` and demands a session or management-grade token. This page explains the model for engineers maintaining routes or designing new endpoints.
![AuthZ pipeline (3 route classes + policy evaluation)](../diagrams/exported/authz-pipeline.svg)
> Source: [diagrams/authz-pipeline.mmd](../diagrams/authz-pipeline.mmd)
## Two Auth Modes
### 1. API Key (Bearer)
Used for the OpenAI/Anthropic/Gemini-compatible client APIs and a few management routes when the key has the `manage` scope.
```
Authorization: Bearer <api-key>
```
Validated by `isValidApiKey()` / `extractApiKey()` in `src/sse/services/auth.ts` and re-exported through `src/shared/utils/apiAuth.ts`. The validator also accepts the `OMNIROUTE_API_KEY` / `ROUTER_API_KEY` env vars as persistent passthrough keys (issue #1350).
### 2. Dashboard Session (auth_token cookie)
For dashboard pages and admin operations.
```
Cookie: auth_token=<JWT signed with JWT_SECRET>
```
Verified by `isDashboardSessionAuthenticated()` in `src/shared/utils/apiAuth.ts`. The pipeline auto-refreshes the JWT when it has fewer than 7 days left in its 30-day lifetime.
Some management routes accept **either** mode: cookie OR `Bearer <key>` when the API key has the `manage` (or `admin`) scope. This is what enables the "configurable via API calls" workflow added in v3.8.
## Route Classes
`src/server/authz/types.ts` defines three classes; any route that cannot be classified deterministically falls back to `MANAGEMENT`.
| Class | Description | Auth required |
| ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
| `PUBLIC` | Explicitly safe routes — login, logout, status, init, health, onboarding bootstrap. | None |
| `CLIENT_API` | Model-serving endpoints — `/api/v1/*`, `/api/v1beta/*`, plus aliases `/v1/*`, `/v1beta/*`, `/chat/completions`, `/responses`, `/models`, `/codex/*`. | Bearer key when the effective `REQUIRE_API_KEY` feature flag is enabled |
| `MANAGEMENT` | Dashboard pages, settings, providers, keys, admin and diagnostics endpoints. | Dashboard session OR Bearer with `manage` scope |
## Pipeline
```
Incoming request → src/proxy.ts
→ runAuthzPipeline() in src/server/authz/pipeline.ts
1. Strip trusted internal headers (x-omniroute-auth-*, x-omniroute-route-class)
2. Generate request id, classify route via classifyRoute()
3. If pathname == "/" → redirect /dashboard
4. If draining (graceful shutdown) and /api/* → 503
5. If non-GET /api/* → checkBodySize() guard
6. If OPTIONS → CORS preflight 204
7. If options.enforce == false → pass-through with route-class headers
8. Otherwise: POLICIES[routeClass].evaluate(ctx)
- allow → stamp x-omniroute-auth-{kind,id,label,scopes} → NextResponse.next()
- reject → JSON error w/ correlation_id (dashboard pages → 302 /login)
```
Trusted internal headers (defined in `src/server/authz/headers.ts`) are **stripped from incoming requests** before classification — clients cannot pre-populate `x-omniroute-auth-*` to impersonate a subject.
### Policy contracts
Each route class has a policy in `src/server/authz/policies/`:
- **`publicPolicy`** (`policies/public.ts`) — always returns `allow({ kind: "anonymous", id: "anonymous" })`.
- **`clientApiPolicy`** (`policies/clientApi.ts`) — extracts Bearer, validates via `validateApiKey()`. Falls through to anonymous only when the effective `REQUIRE_API_KEY` feature flag is disabled. The effective flag is resolved through `isRequireApiKeyEnabled()` (`DB feature flag override > process.env.REQUIRE_API_KEY > default`) so Dashboard Feature Flags and environment variables govern `/api/v1/*`, `/api/v1beta/*`, and aliases consistently; resolver failures fail closed. Allows dashboard-session requests on client API routes (including `/api/v1/models`, used by the dashboard model catalog).
- **`managementPolicy`** (`policies/management.ts`) — accepts dashboard session, internal model-sync requests (matched against `/api/providers/[name]/(sync-models|models)`), or skips entirely if `isAuthRequired()` returns false. Returns 403 (`AUTH_001`) when a Bearer token is present but invalid, 401 otherwise. Also enforces the route-guard tiers (LOCAL_ONLY / ALWAYS_PROTECTED) before any auth branch — see [Route Guard Tiers](../security/ROUTE_GUARD_TIERS.md). LOCAL_ONLY paths in `LOCAL_ONLY_MANAGE_SCOPE_BYPASS_PREFIXES` (today: `/api/mcp/`) may be accessed from non-loopback when the Bearer key carries the `manage` scope; all other LOCAL_ONLY paths remain strict-loopback regardless of scope.
A successful policy returns `AuthSubject` with `kind ∈ { client_api_key, dashboard_session, management_key, anonymous }`. Downstream handlers can read it via `assertAuth(request, "CLIENT_API")` in `src/server/authz/assertAuth.ts` instead of re-running auth logic.
## Public Routes List
`src/shared/constants/publicApiRoutes.ts` is the explicit allowlist:
```ts
PUBLIC_API_ROUTE_PREFIXES = [
"/api/auth/login",
"/api/auth/logout",
"/api/auth/status",
"/api/init",
"/api/v1/", // treated as CLIENT_API in classify, not as "no-auth public"
"/api/cloud/",
"/api/sync/bundle",
"/api/oauth/",
];
PUBLIC_READONLY_API_ROUTE_PREFIXES = ["/api/monitoring/health", "/api/settings/require-login"];
PUBLIC_READONLY_METHODS = new Set(["GET", "HEAD", "OPTIONS"]);
```
Read-only prefixes are public **only** for safe methods. Note: `classifyRoute()` excludes `/api/v1/*` and `/api/v1beta/*` from the PUBLIC fall-through — those are always `CLIENT_API` so the Bearer-key policy still applies.
## Adding a New Route
### Pattern 1 — Public client API endpoint (Bearer-auth)
Routes under `/api/v1/` and `/api/v1beta/` are classified `CLIENT_API` automatically. The middleware enforces the Bearer check; route handlers don't need to redo it but can read the subject if useful.
```typescript
// src/app/api/v1/your-route/route.ts
import { NextRequest, NextResponse } from "next/server";
import { assertAuth } from "@/server/authz/assertAuth";
export async function POST(req: NextRequest) {
const subject = assertAuth(req, "CLIENT_API");
// subject.kind === "client_api_key" | "anonymous" | "dashboard_session"
// ... handler logic
}
```
### Pattern 2 — Management endpoint (session or Bearer + manage)
Use `requireManagementAuth()` from `src/lib/api/requireManagementAuth.ts`:
```typescript
import { requireManagementAuth } from "@/lib/api/requireManagementAuth";
export async function POST(request: Request) {
const rejection = await requireManagementAuth(request);
if (rejection) return rejection;
// ... handler logic
}
```
`requireManagementAuth()` returns `null` on success or a JSON error `Response`:
- 401 `AUTH_001` "Authentication required" — no credentials at all
- 403 — invalid Bearer **or** Bearer present but key lacks the `manage` / `admin` scope
`hasManageScope(scopes)` returns true for `"manage"` or `"admin"`.
### Pattern 3 — Adding to the public allowlist
Add the prefix to `PUBLIC_API_ROUTE_PREFIXES` (or `PUBLIC_READONLY_API_ROUTE_PREFIXES` for GET-only). Update unit tests at `tests/unit/public-api-routes.test.ts` and `tests/unit/authz/classify.test.ts`.
## Scopes
API keys carry a `scopes` array (stored as JSON in `api_keys.scopes`, see `src/lib/db/apiKeys.ts`).
### Management scope
- `manage` / `admin` — grants the key access to management API endpoints when sent as Bearer.
### MCP scopes (`src/shared/constants/mcpScopes.ts`)
Each MCP tool requires specific scopes via `MCP_TOOL_SCOPES`. Full list (`MCP_SCOPE_LIST`):
```
read:health, read:combos, write:combos, read:quota, read:usage,
read:models, execute:completions, execute:search, write:budget,
write:resilience, pricing:write, read:cache, write:cache,
read:compression, write:compression, read:proxies
```
Scope enforcement in `open-sse/mcp-server/server.ts` passes each tool's scope list into
`evaluateToolScopes()` after `resolveCallerScopeContext()` resolves scopes from MCP auth info,
request metadata, or `OMNIROUTE_MCP_SCOPES`.
## Auth Required Toggle
`isAuthRequired()` in `src/shared/utils/apiAuth.ts` decides whether **any** auth is enforced for a request:
- `settings.requireLogin === false` → auth is globally disabled.
- No password configured **and** no `INITIAL_PASSWORD` env var → bootstrap mode allows the onboarding wizard and loopback requests, but exposed network requests still need credentials.
- Any DB error → fails closed (secure-by-default).
Client API key enforcement uses `isRequireApiKeyEnabled()` in `src/shared/utils/featureFlags.ts`, not a direct `process.env.REQUIRE_API_KEY` read. This matters for deployed instances: toggling `REQUIRE_API_KEY` in Dashboard → Feature Flags stores a DB override and immediately affects `/v1/*`, `/v1beta/*`, `/models`, `/responses`, `/chat/completions`, `/codex/*`, and other client-API auth checks that share this helper. If the feature flag store cannot be read, client API auth fails closed and requires a key.
## Breaking Change — v3.8.0
The `/api/v1/agents/tasks/*` and `/api/resilience/model-cooldowns` endpoints **now require management auth** (commit `588a0333`). Clients previously sending a normal API key without the `manage` scope receive `403`. Migration: either issue the key the `manage` scope in the API Keys dashboard, or use a logged-in dashboard session.
## Behaviour Change — v3.8.2
`/api/mcp/*` (the remote MCP server) is still LOCAL_ONLY by default but now accepts non-loopback requests when the `Authorization: Bearer <api-key>` header carries the `manage` scope. The carve-out is gated explicitly per-path via `LOCAL_ONLY_MANAGE_SCOPE_BYPASS_PREFIXES` in `src/server/authz/routeGuard.ts`; the sibling LOCAL_ONLY prefix `/api/cli-tools/runtime/*` is intentionally NOT bypassable because it can spawn arbitrary subprocesses. Anonymous requests to `/api/mcp/*` from non-loopback continue to return `403 LOCAL_ONLY` — the default for any new LOCAL_ONLY path remains strict-loopback. See [Route Guard Tiers](../security/ROUTE_GUARD_TIERS.md#manage-scope-carve-out).
## Testing
- Unit tests: `tests/unit/authz/``classify.test.ts`, `pipeline.test.ts`, `client-api-policy.test.ts`, `management-policy.test.ts`, `public-policy.test.ts`.
- Public allowlist: `tests/unit/public-api-routes.test.ts`.
- Run focused: `node --import tsx/esm --test tests/unit/authz/classify.test.ts`.
## Debugging
The pipeline always stamps responses with:
```
x-request-id: <correlation id, echoed in error bodies>
x-omniroute-route-class: PUBLIC | CLIENT_API | MANAGEMENT
```
For authenticated requests the upstream (handler-side) request headers also include:
```
x-omniroute-auth-kind: client_api_key | dashboard_session | management_key | anonymous
x-omniroute-auth-id: key_<last-4> | "dashboard" | "anonymous"
x-omniroute-auth-label: (optional)
x-omniroute-auth-scopes: comma-separated list
```
Use `assertAuth(req, expectedClass)` inside handlers — it throws `AuthzAssertionError` with code `AUTHZ_NOT_INITIALIZED` if the middleware was bypassed (helpful for catching configuration regressions in tests).
## See Also
- [API_REFERENCE.md](../reference/API_REFERENCE.md) — auth marker per endpoint
- [COMPLIANCE.md](../security/COMPLIANCE.md) — audit log for auth events
- [MCP-SERVER.md](../frameworks/MCP-SERVER.md) — MCP scope enforcement details
- Source: `src/server/authz/`, `src/lib/api/requireManagementAuth.ts`
+844
View File
@@ -0,0 +1,844 @@
---
title: "OmniRoute Codebase Documentation"
version: 3.8.40
lastUpdated: 2026-06-28
---
# OmniRoute Codebase Documentation
> **Version:** v3.8.0
> **Last updated:** 2026-06-28
> **Audience:** Engineers contributing to OmniRoute or building integrations on top of it.
>
> For high-level architecture diagrams and the reasoning behind each subsystem, read
> [ARCHITECTURE.md](./ARCHITECTURE.md). For deep dives on individual subsystems
> (Auto Combo, MCP server, A2A server, Skills, Memory, Cloud Agents, Resilience,
> Compression, etc.) see their dedicated files in this `docs/` directory.
This file describes **what exists in the repository today** so that a new engineer
can navigate the tree, understand the runtime layering, and know where to add code
without inventing new modules.
---
## 1. Tech Stack
| Concern | Choice |
| ------------- | ------------------------------------------------------------------------------------------------------------------------ |
| Web framework | **Next.js 16** (App Router, standalone output, no global middleware) |
| Language | **TypeScript 6.0+** — target `ES2022`, `module: esnext`, `moduleResolution: bundler`, `strict: false` |
| Runtime | **Node.js** `>=22.22.2 <23` or `>=24.0.0 <27` (enforced via `engines` + `SUPPORTED_NODE_RANGE`) |
| Database | **SQLite** via `better-sqlite3` (singleton, WAL journaling) |
| Desktop | **Electron 41** + `electron-builder` 26.10 (separate workspace at `electron/`) |
| Tests | **Node native test runner** (unit/integration), **Vitest** (MCP, autoCombo, cache), **Playwright** (e2e + protocols-e2e) |
| Build | Next.js standalone via `scripts/build/build-next-isolated.mjs` |
| Lint/format | ESLint flat config + Prettier (`lint-staged` via Husky pre-commit) |
| Module system | ESM everywhere (`"type": "module"`) |
| Workspaces | npm workspace — `open-sse` is the only sub-workspace |
Path aliases (`tsconfig.json`):
- `@/*``src/*`
- `@omniroute/open-sse``open-sse/index.ts`
- `@omniroute/open-sse/*``open-sse/*`
Default HTTP port: **`20128`** (API and dashboard share the same process). Data
directory is `DATA_DIR` env var, defaulting to `~/.omniroute/`.
---
## 2. Repository Layout
```
OmniRoute/
├── src/ Next.js application (App Router, libs, domain, server, shared)
├── open-sse/ Streaming engine workspace (@omniroute/open-sse)
├── electron/ Desktop wrapper (Electron 41 main + preload)
├── bin/ CLI entry points (omniroute, reset-password)
├── tests/ Unit, integration, e2e, protocols-e2e, translator, security, fixtures
├── scripts/ Build, sync, check, migration, and runtime helper scripts
├── docs/ Public documentation (this directory)
├── public/ Static assets, PWA manifest, service worker
├── config/ Runtime config samples
├── images/ Marketing/screenshot assets
├── _ideia/, _references/, _mono_repo/, _tasks/ Internal scratch / planning (not shipped)
├── CLAUDE.md Repo rules for Claude Code
├── AGENTS.md Deeper architecture reference for agents
├── package.json v3.8.0, workspace root
└── tsconfig.json Path aliases + core compiler options
```
---
## 3. `src/` — Next.js Application
```
src/
├── app/ App Router pages + API routes
├── lib/ Core libraries (DB, auth, OAuth, skills, memory, …)
├── domain/ Pure domain layer (policy, fallback, cost, lockout, …)
├── server/ Server-only modules (authz, cors, auth)
├── shared/ Types, constants, validation, contracts, utils (cross-boundary safe)
├── mitm/ Man-in-the-middle proxy helpers for CLI integration
├── models/ Local model metadata / aliasing
├── sse/ Legacy SSE handlers that still live under src/ (not open-sse/)
├── store/ Client-side state stores
├── middleware/ Route-level middleware utilities (not Next.js global middleware)
├── scripts/ In-tree scripts importable by app code
├── types/ Ambient and shared TS types
├── i18n/ Locale bundles
├── instrumentation.ts Next.js instrumentation hook
├── instrumentation-node.ts
├── server-init.ts Process-level bootstrap (env, DB, jobs, sync)
└── proxy.ts Top-level proxy bootstrap helper
```
### 3.1 `src/app/` — App Router
The App Router exposes both the dashboard UI and the public/management HTTP API.
There is **no global middleware** — interception is done per-route.
Top-level segments under `src/app/`:
| Path | Purpose |
| ----------------------------------------------------------------------------- | ----------------------------------------- |
| `api/` | All HTTP API routes (see breakdown below) |
| `a2a/` | A2A JSON-RPC 2.0 endpoint (`POST /a2a`) |
| `.well-known/agent.json/` | A2A Agent Card discovery document |
| `(dashboard)/` | Dashboard UI (route group, no URL prefix) |
| `auth/`, `login/`, `forgot-password/`, `callback/` | Auth flows |
| `landing/` | Marketing/landing page |
| `docs/` | Embedded API docs viewer |
| `status/`, `maintenance/`, `offline/` | Operational pages |
| `privacy/`, `terms/` | Legal pages |
| `400/`, `401/`, `403/`, `408/`, `429/`, `500/`, `502/`, `503/` | Static error pages |
| `error.tsx`, `global-error.tsx`, `not-found.tsx`, `forbidden/`, `loading.tsx` | Framework error/loading boundaries |
| `layout.tsx`, `page.tsx`, `globals.css`, `manifest.ts` | Root shell |
#### 3.1.1 `src/app/(dashboard)/dashboard/` — UI pages
`agents`, `analytics`, `api-manager`, `audit`, `auto-combo`, `batch`, `cache`,
`changelog`, `cli-tools`, `cloud-agents`, `combos`, `compression`, `context`,
`costs`, `endpoint`, `health`, `limits`, `logs`, `memory`, `onboarding`,
`playground`, `providers`, `search-tools`, `settings`, `skills`, `system`,
`translator`, `usage`, `webhooks`, plus root `page.tsx`, `HomePageClient.tsx`,
`BootstrapBanner.tsx`.
#### 3.1.2 `src/app/api/` — Top-level API groups
```
src/app/api/
├── a2a/{status, tasks}
├── acp/
├── admin/
├── analytics/
├── assess/
├── auth/
├── batches/
├── cache/
├── cli-tools/
├── cloud/{codex-responses-ws}
├── combos/
├── compliance/
├── compression/
├── context/
├── db/, db-backups/
├── evals/
├── fallback/
├── files/
├── health/
├── init/
├── internal/{concurrency}
├── keys/
├── logs/
├── mcp/{audit, sse, status, stream, tools}
├── memory/{health, [id]/, route.ts}
├── model-combo-mappings/
├── models/
├── monitoring/
├── oauth/
├── openapi/
├── policies/
├── pricing/
├── provider-metrics/, provider-models/, provider-nodes/
├── providers/
├── rate-limit/, rate-limits/
├── resilience/
├── restart/, shutdown/
├── search/
├── sessions/
├── settings/
├── skills/{executions, [id], install, marketplace, route.ts, skillssh}
├── storage/
├── sync/, synced-available-models/
├── system/
├── tags/
├── telemetry/
├── token-health/
├── translator/
├── tunnels/
├── services/ Embedded service management (9router, cliproxy) — LOCAL_ONLY
├── upstream-proxy/
├── usage/
├── v1/ OpenAI-compatible public API
├── v1beta/ Gemini-style compat
├── version-manager/
└── webhooks/
```
#### 3.1.2a `src/app/api/services/` — Embedded Services management
Routes for installing, starting, stopping, and monitoring 9Router and CLIProxyAPI.
All paths are classified **LOCAL_ONLY** (loopback only, hard rule #17) because they
can invoke `npm install` and spawn child processes.
```
src/app/api/services/
├── 9router/
│ ├── _lib.ts getOrInitSupervisor() helper
│ ├── install/route.ts POST — npm install via execFile
│ ├── start/route.ts POST — supervisor.start()
│ ├── stop/route.ts POST — supervisor.stop()
│ ├── restart/route.ts POST — supervisor.restart()
│ ├── update/route.ts POST — npm install newer version
│ ├── rotate-key/route.ts POST — generate new API key + restart
│ ├── status/route.ts GET — live + DB status + version metadata
│ └── auto-start/route.ts POST — toggle auto_start flag
├── cliproxy/
│ ├── _lib.ts getOrInitSupervisor() helper
│ ├── install/route.ts POST — npm install
│ ├── start/route.ts POST — supervisor.start()
│ ├── stop/route.ts POST — supervisor.stop()
│ ├── restart/route.ts POST — supervisor.restart()
│ ├── update/route.ts POST — npm install newer version
│ ├── status/route.ts GET — live + DB status + version metadata
│ └── auto-start/route.ts POST — toggle auto_start flag
└── [name]/
└── logs/route.ts GET — SSE log tail (shared by all services)
```
Corresponding dashboard UI:
`src/app/(dashboard)/dashboard/providers/services/` — two-tab page (CLIProxyAPI + 9Router).
Reverse proxy for 9Router embedded UI:
`src/app/(dashboard)/dashboard/providers/services/[name]/embed/[...path]/route.ts`
Deep-dive: `docs/frameworks/EMBEDDED-SERVICES.md`
#### 3.1.3 `src/app/api/v1/` — OpenAI-compatible public API
```
v1/
├── accounts/[id]/ account lookup
├── agents/tasks/[id]/, agents/tasks/ A2A-flavored task endpoints
├── api/ internal API helpers exposed under v1/api
├── audio/{speech, transcriptions}/ TTS + STT
├── batches/[id]/{cancel}, batches/ OpenAI Batches API
├── chat/completions/ Chat Completions (the main endpoint)
├── chatgpt-web/ ChatGPT-Web compat
├── completions/ Legacy text completions
├── embeddings/ Embeddings
├── files/[id]/, files/ Files API
├── _helpers/ Shared route helpers (no public URL)
├── images/{edits, generations}/ Image gen + edit
├── issues/ Triage helper endpoints
├── management/{proxies}/ Management-scoped routes inside v1
├── messages/{count_tokens}/ Anthropic-style messages compat
├── models/ Model listing (`route.ts`, `catalog.ts`)
├── moderations/ Moderation
├── music/ Music gen
├── providers/[provider]/ Per-provider operations
├── quotas/{check} Quota probes
├── registered-keys/ Registered key admin
├── rerank/ Reranking
├── responses/[...path]/ OpenAI Responses API (catch-all)
├── search/ Web search
├── videos/ Video gen
├── ws/ WebSocket bridge
└── route.ts Index handler
```
Every route file follows the same pattern:
```
Route → CORS preflight → Zod body validation → optional auth
→ API key policy enforcement → handler delegation (open-sse)
```
`v1beta/` is the Gemini-style compat surface (a thin wrapper that translates into
the same `open-sse/handlers/` pipeline).
### 3.2 `src/lib/` — Core libraries
Always import data, sync, OAuth, skill, memory, etc. through these modules. The
table groups the actual directories and notable top-level files.
| Module | Purpose |
| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `a2a/` | A2A protocol server: `taskManager.ts`, `streaming.ts`, `taskExecution.ts`, `routingLogger.ts`, `skills/` (6 skills: cost analysis, health report, provider discovery, quota management, smart routing, list-capabilities) |
| `acp/` | Agent-Control-Protocol: `index.ts`, `manager.ts`, `registry.ts` |
| `api/` | Internal API helpers: `requireManagementAuth.ts`, `requireCliToolsAuth.ts`, `errorResponse.ts` |
| `auth/` | `managementPassword.ts` (password reset / hashing) |
| `batches/` | OpenAI Batches API service (`service.ts`) |
| `catalog/` | OpenRouter catalog sync (`openrouterCatalog.ts`) |
| `cloudAgent/` | Cloud agent registry: `api.ts`, `baseAgent.ts`, `db.ts`, `index.ts`, `registry.ts`, `types.ts`, `agents/{codex, devin, jules}.ts` |
| `combos/` | Combo resolution helpers |
| `compliance/` | Audit + provider audit: `index.ts`, `providerAudit.ts` |
| `config/` | Runtime config glue |
| `db/` | SQLite domain modules (see §3.2.1) |
| `display/` | UI/display helpers used by API responses |
| `embeddings/` | Embedding service registry |
| `env/` | Env loading + introspection |
| `evals/` | Eval runtime |
| `guardrails/` | `piiMasker.ts`, `promptInjection.ts`, `visionBridge.ts`, `visionBridgeHelpers.ts`, `registry.ts`, `base.ts` |
| `jobs/` | Background jobs (`autoUpdate.ts`, …) |
| `memory/` | Persistent memory: `store.ts`, `cache.ts`, `retrieval.ts`, `summarization.ts`, `extraction.ts`, `injection.ts`, `qdrant.ts`, `settings.ts`, `verify.ts`, `schemas.ts`, `types.ts` |
| `monitoring/` | `observability.ts` |
| `oauth/` | OAuth providers (14): `antigravity`, `claude`, `cline`, `codex`, `cursor`, `gemini`, `github`, `gitlab-duo`, `kilocode`, `kimi-coding`, `kiro`, `qoder`, `qwen`, `windsurf` plus `services/`, `utils/{pkce, server, banner, codexAuthFile, ui}`, `constants/oauth.ts` |
| `plugins/` | Plugin loader (`index.ts`) |
| `promptCache/` | `prefixAnalyzer.ts`, `index.ts` |
| `providerModels/` | Managed model lifecycle: `modelDiscovery.ts`, `managedModelImport.ts`, `managedAvailableModels.ts`, `cursorAgent.ts` |
| `providers/` | Provider helpers: `catalog.ts`, `validation.ts`, `imageValidation.ts`, `claudeExtraUsage.ts`, `codexConnectionDefaults.ts`, `codexFastTier.ts`, `webCookieAuth.ts`, `managedAvailableModels.ts`, `requestDefaults.ts` |
| `resilience/` | `settings.ts` — settings for circuit breaker, cooldown, lockout |
| `runtime/` | Runtime feature detection |
| `search/` | `executeWebSearch.ts` |
| `services/` | Embedded services framework: `ServiceSupervisor.ts` (generic child-process supervisor with operation lock, ring buffer, health checker), `bootstrap.ts` (process-level registration and auto-start), `registry.ts` (tool → supervisor map), `apiKey.ts` (AES-256-GCM key store), `modelSync.ts` (periodic model sync), `ringBuffer.ts` (5 MB circular log buffer), `healthCheck.ts` (HTTP health probe), `types.ts`, `embedWsProxy.ts` (WebSocket proxy), `installers/{ninerouter,cliproxy}.ts`. See `docs/frameworks/EMBEDDED-SERVICES.md` |
| `agentSkills/` | Agent Skills catalog + generator: `catalog.ts` (getCatalog/getSkillById/filterCatalog/computeCoverage), `generator.ts` (generateAgentSkills → writes `skills/{id}/SKILL.md`), `openapiParser.ts` (extracts REST endpoints from OpenAPI spec), `cliRegistryParser.ts` (extracts CLI subcommands from bin/cli-registry), `schemas.ts` (Zod: AgentSkillSchema, SkillCoverageSchema, ListQuerySchema, GenerateBodySchema), `types.ts` (AgentSkill, SkillCoverage, SkillMarkdown, GeneratorReport). Consumed by REST routes (`/api/agent-skills/*`), MCP tools (`omniroute_agent_skills_*`), and A2A skill `list-capabilities`. See [AGENT-SKILLS.md](../frameworks/AGENT-SKILLS.md). |
| `skills/` | Skill framework: `registry.ts`, `executor.ts`, `interception.ts`, `injection.ts`, `sandbox.ts`, `custom.ts`, `hybrid.ts`, `builtins.ts`, `a2a.ts`, `providerSettings.ts`, `schemas.ts`, `skillssh.ts`, `types.ts`, plus `builtin/browser.ts` |
| `spend/` | `batchWriter.ts` (write-behind buffer) |
| `sync/` | `bundle.ts`, `tokens.ts` (Cloud Sync) |
| `system/` | System-level helpers |
| `translator/` | Top-level translator glue (delegates into `open-sse/translator/`) |
| `usage/` | Usage accounting: `costCalculator.ts`, `tokenAccounting.ts`, `usageHistory.ts`, `aggregateHistory.ts`, `usageStats.ts`, `callLogs.ts`, `callLogArtifacts.ts`, `fetcher.ts`, `providerLimits.ts`, `migrations.ts` |
| `versionManager/` | Auto-update + version manifest |
| `ws/` | WebSocket bridge |
| `zed-oauth/` | Zed editor OAuth flow |
Top-level files in `src/lib/`:
- `localDb.ts` — re-export layer only. **Never** add logic here.
- `proxyHealth.ts`, `proxyLogger.ts`, `tokenHealthCheck.ts`, `localHealthCheck.ts`
- `oneproxyRotator.ts`, `oneproxySync.ts`
- `apiBridgeServer.ts`, `cacheLayer.ts`, `semanticCache.ts`, `settingsCache.ts`
- `cloudSync.ts`, `initCloudSync.ts`
- `cloudflaredTunnel.ts`, `ngrokTunnel.ts`, `tailscaleTunnel.ts`
- `consoleInterceptor.ts`, `container.ts`, `gracefulShutdown.ts`, `idempotencyLayer.ts`
- `ipUtils.ts`, `logEnv.ts`, `logPayloads.ts`, `logRotation.ts`
- `modelAliasSeed.ts`, `modelCapabilities.ts`, `modelMetadataRegistry.ts`, `modelsDevSync.ts`
- `piiSanitizer.ts`, `pricingSync.ts`
- `apiKeyExposure.ts`, `cacheControlSettings.ts`, `dataPaths.ts`, `toolPolicy.ts`
- `translatorEvents.ts`, `usageDb.ts`, `usageAnalytics.ts`, `webhookDispatcher.ts`
#### 3.2.1 `src/lib/db/`
Singleton SQLite database (`getDbInstance()` in `core.ts`, WAL journaling).
**Never write raw SQL in routes or handlers** — go through these modules.
![Database schema overview (selected core tables)](../diagrams/exported/db-schema-overview.svg)
> Source: [diagrams/db-schema-overview.mmd](../diagrams/db-schema-overview.mmd)
Domain modules (each owns one or more tables): `apiKeys.ts`, `backup.ts`,
`batches.ts`, `cleanup.ts`, `cliToolState.ts`, `combos.ts`,
`commandCodeAuth.ts`, `compression.ts`, `compressionAnalytics.ts`,
`compressionCacheStats.ts`, `compressionCombos.ts`, `compressionScheduler.ts`,
`contextHandoffs.ts`, `core.ts`, `creditBalance.ts`, `databaseSettings.ts`,
`detailedLogs.ts`, `domainState.ts`, `encryption.ts`, `evals.ts`, `files.ts`,
`healthCheck.ts`, `jsonMigration.ts`, `migrationRunner.ts`,
`modelComboMappings.ts`, `models.ts`, `oneproxy.ts`, `prompts.ts`,
`providers.ts`, `providerLimits.ts`, `proxies.ts`, `quotaSnapshots.ts`,
`readCache.ts`, `reasoningCache.ts`, `registeredKeys.ts`, `secrets.ts`,
`sessionAccountAffinity.ts`, `settings.ts`, `stateReset.ts`, `stats.ts`,
`syncTokens.ts`, `tierConfig.ts`, `upstreamProxy.ts`, `versionManager.ts`,
`webhooks.ts`.
`migrations/` holds 55 versioned `.sql` files (idempotent, transactional) and is
executed by `migrationRunner.ts` at boot.
Tables created across the migrations (52 total):
`a`, `account_key_limits`, `api_keys`, `batches`, `call_logs`,
`combo_adaptation_state`, `combos`, `command_code_auth_sessions`,
`compression_analytics`, `compression_cache_stats`,
`compression_combo_assignments`, `compression_combos`, `context_handoffs`,
`daily_usage_summary`, `db_meta`, `domain_budgets`, `domain_circuit_breakers`,
`domain_cost_history`, `domain_fallback_chains`, `domain_lockout_state`,
`eval_cases`, `eval_runs`, `eval_suites`, `files`, `hourly_usage_summary`,
`key_value`, `mcp_tool_audit`, `memories`, `model_combo_mappings`,
`provider_connections`, `provider_key_limits`, `provider_nodes`,
`proxy_assignments`, `proxy_logs`, `proxy_registry`, `quota_snapshots`,
`reasoning_cache`, `registered_keys`, `request_detail_logs`,
`routing_decisions`, `semantic_cache`, `session_account_affinity`,
`skill_executions`, `skills`, `sync_tokens`, `tier_assignments`,
`tier_config`, `upstream_proxy_config`, `usage_history`, `version_manager`,
`webhooks` (plus FTS5 virtual tables for memory search).
### 3.3 `src/domain/` — Domain layer
Pure business logic, no I/O. Imported by routes and handlers.
| File | Purpose |
| ------------------------------------------ | ------------------------------------------------- |
| `policyEngine.ts` | Top-level policy resolver |
| `fallbackPolicy.ts` | Fallback decision tree |
| `costRules.ts` | Cost calculation rules |
| `lockoutPolicy.ts` | Model lockout decisions |
| `tagRouter.ts` | Tag-based routing |
| `comboResolver.ts` | Combo resolution from request → target list |
| `connectionModelRules.ts` | Per-connection model filters |
| `modelAvailability.ts` | Model availability check |
| `degradation.ts` | Degraded-mode transitions |
| `providerExpiration.ts` | Expired account/key detection |
| `quotaCache.ts` | Cached quota decisions |
| `responses.ts`, `omnirouteResponseMeta.ts` | Response shape helpers |
| `configAudit.ts` | Config change audit |
| `assessment/` | Model assessment (per RFC, partially implemented) |
| `types.ts` | Shared domain types |
### 3.4 `src/server/` — Server-only
Cannot be imported from client components.
```
server/
├── auth/loginGuard.ts
├── authz/
│ ├── classify.ts Classifies routes as public vs management
│ ├── assertAuth.ts Assertion helper
│ ├── context.ts Per-request authz context
│ ├── headers.ts
│ ├── pipeline.ts Authz pipeline
│ ├── policies/ Concrete policies
│ └── types.ts
└── cors/origins.ts CORS origin allowlist
```
### 3.5 `src/shared/` — Safe-to-share
Split into focused subdirectories:
- `constants/``providers.ts` (Zod-validated provider catalog), `models.ts`,
`modelSpecs.ts`, `modelCompat.ts`, `pricing.ts`, `cliTools.ts`,
`cliCompatProviders.ts`, `routingStrategies.ts`, `comboConfigMode.ts`,
`headers.ts`, `upstreamHeaders.ts` (denylist), `mcpScopes.ts`,
`errorCodes.ts`, `publicApiRoutes.ts`, `batch.ts`, `batchEndpoints.ts`,
`bodySize.ts`, `colors.ts`, `appConfig.ts`, `config.ts`,
`sidebarVisibility.ts`, `visionBridgeDefaults.ts`.
- `validation/``schemas.ts` (~80 Zod schemas), `compressionConfigSchemas.ts`,
`oneproxySchemas.ts`, `providerSchema.ts`, `settingsSchemas.ts`, `helpers.ts`.
- `contracts/` — public API contracts shipped to npm.
- `types/` — shared TS types.
- `utils/``circuitBreaker.ts`, `apiAuth.ts`, `apiKey.ts`, `apiKeyPolicy.ts`,
`apiResponse.ts`, `api.ts`, `classify429.ts`, `cliCompat.ts`, `clipboard.ts`,
`cloud.ts`, `cn.ts`, `cors.ts`, `costEstimator.ts`, `featureFlags.ts`,
`fetchTimeout.ts`, `formatting.ts`, `inputSanitizer.ts`, `logger.ts`,
`machine.ts`, `machineId.ts`, `maskEmail.ts`, `modelCatalogSearch.ts`,
`nodeRuntimeSupport.ts`, `parseApiKeys.ts`, `providerHints.ts`,
`providerModelAliases.ts`, `rateLimiter.ts`, `releaseNotes.ts`,
`a11yAudit.ts`, plus dashboard hooks/components under `services/`, `network/`,
`middleware/`, `schemas/`, `hooks/`, `components/`.
---
## 4. `open-sse/` — Streaming engine workspace
Separate npm workspace published as `@omniroute/open-sse`. Owns request
processing, executors, translators, services, transformer, and the MCP server.
```
open-sse/
├── index.ts Public exports
├── package.json Workspace manifest
├── tsconfig.json
├── types.d.ts
├── config/ Provider registries, header profiles, identity, …
├── handlers/ Request handlers (chat, embeddings, audio, image, …)
├── executors/ 75 provider-specific HTTP executors
├── translator/ Format conversion (OpenAI ↔ Claude ↔ Gemini ↔ Cursor ↔ Kiro)
├── transformer/ Responses API ↔ Chat Completions stream transformer
├── services/ 80+ service modules (combos, fallback, quotas, identity, …)
├── utils/ Streaming helpers, TLS client, AWS SigV4, proxy fetch, …
└── mcp-server/ MCP server (3 transports, 30 scopes, 94 tools)
```
### 4.1 `open-sse/handlers/`
| Handler | Purpose |
| ----------------------- | ------------------------------------------------------------------------ |
| `chatCore.ts` | Main chat pipeline (cache, rate limit, combo routing, executor dispatch) |
| `responsesHandler.ts` | OpenAI Responses API entry point |
| `embeddings.ts` | Embeddings |
| `imageGeneration.ts` | Image generation |
| `audioSpeech.ts` | Text-to-speech |
| `audioTranscription.ts` | Speech-to-text |
| `videoGeneration.ts` | Video generation |
| `musicGeneration.ts` | Music generation |
| `rerank.ts` | Reranking |
| `moderations.ts` | Moderation |
| `search.ts` | Web search |
| `sseParser.ts` | SSE event parser |
| `usageExtractor.ts` | Pull token counts out of upstream streams |
| `responseSanitizer.ts` | Strip provider-specific noise |
| `responseTranslator.ts` | Glue between provider response and translator layer |
### 4.2 `open-sse/executors/`
75 provider executors, each extending `BaseExecutor` (`base.ts`):
`antigravity`, `azure-openai`, `blackbox-web`, `chatgpt-web`, `cliproxyapi`,
`cloudflare-ai`, `codex`, `commandCode`, `cursor`, `default`, `devin-cli`,
`muse-spark-web`, `nlpcloud`, `opencode`, `perplexity-web`, `petals`,
`pollinations`, `puter`, `qoder`, `vertex`, `windsurf`, plus `claudeIdentity.ts`
(shared identity helper) and `index.ts` (registry).
> Note: providers not listed here are served by `default.ts` using the generic
> OpenAI-compatible executor. The full provider catalog (237 entries) lives in
> `src/shared/constants/providers.ts`.
### 4.3 `open-sse/translator/`
Hub-and-spoke translation (OpenAI is the hub).
- **9 request translators** (`translator/request/`):
`antigravity-to-openai`, `claude-to-gemini`, `claude-to-openai`,
`gemini-to-openai`, `openai-responses`, `openai-to-claude`,
`openai-to-cursor`, `openai-to-gemini`, `openai-to-kiro`.
- **9 response translators** (`translator/response/`):
`claude-to-openai`, `cursor-to-openai`, `gemini-to-claude`, `gemini-to-openai`,
`kiro-to-openai`, `openai-responses`, `openai-to-antigravity`,
`openai-to-claude`.
- **9 helpers** (`translator/helpers/`):
`claudeHelper`, `geminiHelper`, `geminiToolsSanitizer`, `maxTokensHelper`,
`openaiHelper`, `responsesApiHelper`, `schemaCoercion`, `toolCallHelper`, plus
helper tests.
- **Image helpers** (`translator/image/sizeMapper.ts`).
- Top-level: `bootstrap.ts`, `formats.ts`, `registry.ts`, `index.ts`.
### 4.4 `open-sse/transformer/`
- `responsesTransformer.ts``TransformStream`-based Responses API ↔ Chat
Completions converter (used by the `responses/` route catch-all).
### 4.5 `open-sse/services/`
Highlights (full list under `open-sse/services/`):
| Concern | Files |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Combo routing | `combo.ts` (17 strategies), `comboConfig.ts`, `comboMetrics.ts`, `comboManifestMetrics.ts`, `comboAgentMiddleware.ts` |
| Auto Combo engine | `autoCombo/``engine.ts`, `scoring.ts`, `taskFitness.ts`, `virtualFactory.ts`, `modePacks.ts`, `autoPrefix.ts`, `persistence.ts`, `providerDiversity.ts`, `providerRegistryAccessor.ts`, `routerStrategy.ts`, `selfHealing.ts`, `index.ts` |
| Resilience | `accountFallback.ts` (cooldown + lockout), `errorClassifier.ts`, `emergencyFallback.ts`, `rateLimitManager.ts`, `rateLimitSemaphore.ts`, `accountSemaphore.ts`, `accountSelector.ts` |
| Quotas | `quotaMonitor.ts`, `quotaPreflight.ts`, `bailianQuotaFetcher.ts`, `codexQuotaFetcher.ts`, `deepseekQuotaFetcher.ts`, `crofUsageFetcher.ts`, `antigravityCredits.ts` |
| Caching | `reasoningCache.ts`, `searchCache.ts`, `signatureCache.ts`, `requestDedup.ts` |
| Routing intelligence | `intentClassifier.ts`, `taskAwareRouter.ts`, `backgroundTaskDetector.ts`, `volumeDetector.ts`, `wildcardRouter.ts`, `workflowFSM.ts`, `specificityDetector.ts`, `specificityRules.ts`, `specificityTypes.ts` |
| Model handling | `modelCapabilities.ts`, `modelDeprecation.ts`, `modelFamilyFallback.ts`, `modelStrip.ts`, `model.ts`, `provider.ts`, `providerRequestDefaults.ts`, `providerCostData.ts`, `payloadRules.ts` |
| Compression | `compression/` — full compression engine wiring |
| Token + session | `tokenRefresh.ts`, `sessionManager.ts`, `apiKeyRotator.ts`, `contextManager.ts`, `contextHandoff.ts`, `systemPrompt.ts`, `roleNormalizer.ts`, `responsesInputSanitizer.ts`, `toolSchemaSanitizer.ts`, `toolLimitDetector.ts`, `thinkingBudget.ts` |
| Tier / manifest | `tierResolver.ts`, `tierConfig.ts`, `tierDefaults.json`, `tierTypes.ts`, `manifestAdapter.ts` |
| IP / network | `ipFilter.ts`, `webSearchFallback.ts` |
| Batches | `batchProcessor.ts` |
| Usage | `usage.ts` |
### 4.6 `open-sse/mcp-server/`
- **31 registered tools** wired in `server.ts` (12 scoped under `schemas/tools.ts`,
5 compression tools, 3 memory tools, 4 skills tools, plus advanced tools added
through `advancedTools.ts`).
- **3 transports**: stdio, HTTP Streamable, SSE.
- **13 scopes** declared in `src/shared/constants/mcpScopes.ts`.
- Audit table: `mcp_tool_audit` (populated by `audit.ts`).
- Files: `server.ts`, `index.ts`, `httpTransport.ts`, `audit.ts`, `scopeEnforcement.ts`,
`runtimeHeartbeat.ts`, `descriptionCompressor.ts`, `schemas/{tools, a2a, audit, index}.ts`,
`tools/{advancedTools, compressionTools, memoryTools, skillTools}.ts`,
plus tests under `__tests__/`.
- See [MCP-SERVER.md](../frameworks/MCP-SERVER.md) for the full tool catalog.
### 4.7 `open-sse/config/`
Provider registries (`providerRegistry.ts`, `providerModels.ts`,
`providerHeaderProfiles.ts`), per-format model registries (`audioRegistry.ts`,
`embeddingRegistry.ts`, `imageRegistry.ts`, `moderationRegistry.ts`,
`musicRegistry.ts`, `rerankRegistry.ts`, `searchRegistry.ts`, `videoRegistry.ts`),
identity helpers (`codexIdentity.ts`, `codexInstructions.ts`,
`anthropicHeaders.ts`, `antigravityUpstream.ts`, `antigravityModelAliases.ts`,
`cliFingerprints.ts`, `toolCloaking.ts`, `defaultThinkingSignature.ts`),
credential helpers (`credentialLoader.ts`, `codexClient.ts`), and cloud
adapters (`azureAi.ts`, `bedrock.ts`, `datarobot.ts`, `glmProvider.ts`,
`maritalk.ts`, `oci.ts`, `petals.ts`, `runway.ts`, `sap.ts`, `watsonx.ts`,
`ollamaModels.ts`, `errorConfig.ts`, `constants.ts`, `registryUtils.ts`).
### 4.8 `open-sse/utils/`
Streaming primitives and provider helpers: `stream.ts`, `streamHandler.ts`,
`streamHelpers.ts`, `streamPayloadCollector.ts`, `streamReadiness.ts`,
`sseHeartbeat.ts`, `proxyFetch.ts`, `proxyDispatcher.ts`, `tlsClient.ts`,
`networkProxy.ts`, `awsSigV4.ts`, `cacheControlPolicy.ts`,
`cursorChecksum.ts`, `cursorAgentProtobuf.ts`, `cursorVersionDetector.ts`,
`comfyuiClient.ts`, `kieTask.ts`, `bypassHandler.ts`, `aiSdkCompat.ts`,
`thinkTagParser.ts`, `urlSanitize.ts`, `usageTracking.ts`, `requestLogger.ts`,
`progressTracker.ts`, `cors.ts`, `error.ts`, `logger.ts`, `sleep.ts`,
`ollamaTransform.ts`.
---
## 5. `electron/` — Desktop wrapper
```
electron/
├── main.js Electron main process
├── preload.js Preload bridge (contextIsolation enabled)
├── types.d.ts
├── package.json electron-builder config, version 3.8.0
├── README.md
├── assets/ Build resources (icons, entitlements, …)
├── node_modules/ Dedicated node_modules (better-sqlite3, electron-updater)
└── dist-electron/ Build output (not committed)
```
Five npm scripts at the workspace root: `electron:dev`, `electron:build`,
`electron:build:{win,mac,linux}`, `electron:smoke:packaged`. Auto-update is via
`electron-updater` pointing at the GitHub release feed.
---
## 6. `bin/` — CLI
```
bin/
├── omniroute.mjs Main CLI entry (Node ESM)
├── reset-password.mjs Reset the management password from CLI
├── mcp-server.mjs MCP server launcher (stdio)
├── nodeRuntimeSupport.mjs Node version guard
└── cli/
├── program.mjs Commander program builder
├── runtime.mjs withRuntime helper (server-first/db-fallback)
├── output.mjs Output formatters (json/jsonl/table/csv)
├── i18n.mjs t() helper with locales
├── api.mjs API fetch helper
├── data-dir.mjs
├── encryption.mjs
├── sqlite.mjs
└── commands/
├── registry.mjs Command registration
├── setup.mjs
├── doctor.mjs
├── providers.mjs
└── ... (one file per command/group)
```
Two binaries are exposed in `package.json``bin`:
- `omniroute``bin/omniroute.mjs`
- `omniroute-reset-password``bin/reset-password.mjs`
---
## 7. `tests/`
| Directory | Type |
| ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------- |
| `tests/unit/` | Unit tests via Node native test runner (1821 files, plus `api/`, `auth/`, `authz/` subdirs) |
| `tests/integration/` | Cross-module + DB-state tests |
| `tests/e2e/` | Playwright UI tests |
| `tests/protocols-e2e/` | MCP/A2A protocol e2e |
| `tests/translator/` | Translator-specific tests |
| `tests/security/` | Security regressions |
| `tests/load/` | Load / stress tests |
| `tests/golden-set/` | Reference outputs for translator regressions |
| `tests/helpers/`, `tests/fixtures/`, `tests/manual/`, `tests/scratch_test.mjs` | Support |
Common commands:
| Command | What it runs |
| -------------------------------------------------------- | ---------------------------------------------------------------- |
| `npm run test:unit` | All `tests/unit/*.test.ts` via Node test runner (concurrency 10) |
| `npm run test:vitest` | Vitest suite (MCP, autoCombo, cache) |
| `npm run test:e2e` | Playwright UI suite |
| `npm run test:protocols:e2e` | MCP + A2A protocol e2e |
| `npm run test:coverage` | Coverage gate (≥60% lines/statements/functions/branches) |
| `node --import tsx/esm --test tests/unit/<file>.test.ts` | Single file run |
---
## 8. `scripts/`
Organized into 6 subfolders by purpose.
- **`scripts/build/`** — `build-next-isolated.mjs`, `prepublish.ts`,
`prepare-electron-standalone.mjs`, `pack-artifact-policy.ts`,
`validate-pack-artifact.ts`, `postinstall.mjs`, `postinstallSupport.mjs`,
`uninstall.mjs`, `bootstrap-env.mjs`, `runtime-env.mjs`,
`native-binary-compat.mjs`.
- **`scripts/dev/`** — `run-next.mjs`, `run-next-playwright.mjs`,
`run-standalone.mjs`, `standalone-server-ws.mjs`, `responses-ws-proxy.mjs`,
`v1-ws-bridge.mjs`, `smoke-electron-packaged.mjs`,
`run-playwright-tests.mjs`, `run-ecosystem-tests.mjs`,
`run-protocol-clients-tests.mjs`, `sync-env.mjs`, `healthcheck.mjs`,
`system-info.mjs`.
- **`scripts/check/`** — `check-cycles.mjs`, `check-docs-sync.mjs`,
`check-docs-counts-sync.mjs`, `check-env-doc-sync.mjs`,
`check-deprecated-versions.mjs`, `check-route-validation.mjs`,
`check-t11-any-budget.mjs`, `check-pr-test-policy.mjs`,
`check-supported-node-runtime.ts`, `test-report-summary.mjs`.
- **`scripts/docs/`** — `generate-docs-index.mjs`, `gen-provider-reference.ts`.
- **`scripts/i18n/`** — `generate-multilang.mjs`, `run-visual-qa.mjs`,
`generate-qa-checklist.mjs`, `apply-priority-overrides.mjs`,
`validate_translation.py`, `check_translations.py`, `i18n_autotranslate.py`,
`untranslatable-keys.json`.
- **`scripts/ad-hoc/`** — `cursor-tap.cjs`, `sync-cursor-models.mjs`,
`migrate-env.mjs`, `dbsetup.js`.
---
## 9. Request Pipeline (Summary)
![Request pipeline (/v1/chat/completions)](../diagrams/exported/request-pipeline.svg)
> Source: [diagrams/request-pipeline.mmd](../diagrams/request-pipeline.mmd)
```
Client request
→ /v1/chat/completions (route.ts)
CORS preflight check
Zod validation (chatCompletionsSchema in shared/validation/schemas.ts)
Auth (extractApiKey + isValidApiKey OR requireManagementAuth)
Policy engine (src/server/authz/pipeline.ts)
Guardrails (PII masker, prompt injection, vision bridge)
→ handleChatCore() (open-sse/handlers/chatCore.ts)
Cache check (semantic + read cache)
Rate limit (rateLimitManager, accountSemaphore)
Combo routing (if model resolves to a combo)
comboResolver → loop per target → handleSingleModel()
translateRequest() (open-sse/translator/request/*)
getExecutor(providerId).execute() (open-sse/executors/*)
fetch upstream → retry/backoff via accountFallback
translateResponse() (open-sse/translator/response/*)
SSE stream OR JSON response
If Responses API: TransformStream via open-sse/transformer/responsesTransformer.ts
→ Compliance audit (src/lib/compliance/)
→ Response to client
```
### Resilience runtime state (three mechanisms)
| Mechanism | Scope | Where |
| ------------------------ | ----------------------------- | ------------------------------------------------------------------------------------------------------------ |
| Provider circuit breaker | Whole provider | `src/shared/utils/circuitBreaker.ts`, persisted in `domain_circuit_breakers` |
| Connection cooldown | One account/key | `markAccountUnavailable()` in `src/sse/services/auth.ts`; consumed by `accountFallback.checkFallbackError()` |
| Model lockout | Provider + connection + model | `open-sse/services/accountFallback.ts`, persisted in `domain_lockout_state` |
See [RESILIENCE_GUIDE.md](./RESILIENCE_GUIDE.md) and the dedicated section in
[CLAUDE.md](../../CLAUDE.md).
---
## 10. How to Contribute
### Add a new provider
1. Register in `src/shared/constants/providers.ts` (Zod-validated at load).
2. Add an executor in `open-sse/executors/` if custom logic is required
(extend `BaseExecutor`).
3. Add a translator in `open-sse/translator/` if it does not speak OpenAI format.
4. If OAuth-based, add config under `src/lib/oauth/providers/` and
`src/lib/oauth/services/`.
5. Register models in `open-sse/config/providerRegistry.ts` (or the format-specific
registry under `open-sse/config/`).
6. Write tests under `tests/unit/`.
### Add a new API route
1. Create `src/app/api/your-route/route.ts`.
2. Follow the pattern: CORS → Zod body validation → auth → handler delegation.
3. If new request shape: add the Zod schema in `src/shared/validation/schemas.ts`.
4. If management-only: add the path to `src/shared/constants/publicApiRoutes.ts`
(denylist for the public API surface).
5. Add tests under `tests/unit/`.
6. Update `docs/reference/API_REFERENCE.md` and `docs/openapi.yaml`.
### Add a new DB module
1. Create `src/lib/db/yourModule.ts` and import `getDbInstance()` from `./core.ts`.
2. Export CRUD functions for your domain.
3. If new tables: add a migration under `src/lib/db/migrations/`, numbered
sequentially, idempotent, transactional.
4. Re-export from `src/lib/localDb.ts` (re-export only — **no logic**).
5. Add tests under `tests/unit/`.
### Add a new MCP tool
1. Add the tool definition under `open-sse/mcp-server/tools/` (or extend
`open-sse/mcp-server/schemas/tools.ts`).
2. Assign the appropriate scope(s) in `src/shared/constants/mcpScopes.ts`.
3. Register the tool in `open-sse/mcp-server/server.ts`.
4. Add tests under `open-sse/mcp-server/__tests__/`.
5. Update [MCP-SERVER.md](../frameworks/MCP-SERVER.md).
### Add a new A2A skill
See [A2A-SERVER.md § Adding a New Skill](../frameworks/A2A-SERVER.md). Skills live in
`src/lib/a2a/skills/` and are registered through the A2A task manager.
---
## 11. Conventions
- **Code style**: 2-space indent, double quotes, 100 char width, semicolons,
`es5` trailing commas — enforced by Prettier via `lint-staged`.
- **Imports**: external → internal (`@/`, `@omniroute/open-sse`) → relative.
- **Naming**: files `camelCase` or `kebab-case`, components `PascalCase`,
constants `UPPER_SNAKE`.
- **ESLint**: `no-eval`, `no-implied-eval`, `no-new-func` = `error` everywhere;
`no-explicit-any` = `warn` in `open-sse/` and `tests/`, error elsewhere.
- **TypeScript**: `strict: false` (legacy posture). Prefer explicit types over
inference for cross-module boundaries.
- **Database**: never write raw SQL in routes or handlers — always go through
`src/lib/db/` modules. Never add logic to `src/lib/localDb.ts`.
- **Errors**: try/catch with specific error types, log with pino context. Never
silently swallow errors in SSE streams; use abort signals for cleanup.
- **Security**: never use `eval()` / `new Function()` / implied eval. Validate
all inputs with Zod. Encrypt credentials at rest (AES-256-GCM). Keep
`src/shared/constants/upstreamHeaders.ts` denylist aligned with the
sanitize/validation layer.
- **Commits**: Conventional Commits — `feat(scope): subject`. Allowed scopes:
`db`, `sse`, `oauth`, `dashboard`, `api`, `cli`, `docker`, `ci`, `mcp`,
`a2a`, `memory`, `skills`.
- **Branches**: prefixes `feat/`, `fix/`, `refactor/`, `docs/`, `test/`,
`chore/`. Never commit directly to `main`.
- **Husky**: pre-commit runs `lint-staged` + `check:docs-sync` +
`check:any-budget:t11`; pre-push runs `check:any-budget:t11` + `check:tracked-artifacts` (fast gates; excludes `test:unit`).
---
## 12. Hard Rules (from CLAUDE.md)
1. Never commit secrets or credentials.
2. Never add logic to `src/lib/localDb.ts`.
3. Never use `eval()` / `new Function()` / implied eval.
4. Never commit directly to `main`.
5. Never write raw SQL in routes — always go through `src/lib/db/` modules.
6. Never silently swallow errors in SSE streams.
7. Always validate inputs with Zod schemas.
8. Always include tests when changing production code.
9. Coverage must stay ≥ 60% (statements, lines, functions, branches).
---
## 13. See Also
- [ARCHITECTURE.md](./ARCHITECTURE.md) — high-level architecture and module
responsibilities.
- [API_REFERENCE.md](../reference/API_REFERENCE.md) — public + management API reference.
- [FEATURES.md](../guides/FEATURES.md) — feature matrix and version highlights.
- [RESILIENCE_GUIDE.md](./RESILIENCE_GUIDE.md) — circuit breaker, cooldown,
lockout deep dive.
- [AUTO-COMBO.md](../routing/AUTO-COMBO.md) — Auto Combo scoring and strategies.
- [MCP-SERVER.md](../frameworks/MCP-SERVER.md) — full MCP tool catalog + transports.
- [A2A-SERVER.md](../frameworks/A2A-SERVER.md) — A2A protocol skills and discovery.
- [COMPRESSION_GUIDE.md](../compression/COMPRESSION_GUIDE.md) — RTK + Caveman compression.
- [CLI-TOOLS.md](../reference/CLI-TOOLS.md) — CLI integrations.
- [ELECTRON_GUIDE.md](../guides/ELECTRON_GUIDE.md) (if present), [DOCKER_GUIDE.md](../guides/DOCKER_GUIDE.md), [FLY_IO_DEPLOYMENT_GUIDE.md](../ops/FLY_IO_DEPLOYMENT_GUIDE.md), [VM_DEPLOYMENT_GUIDE.md](../ops/VM_DEPLOYMENT_GUIDE.md), [TERMUX_GUIDE.md](../guides/TERMUX_GUIDE.md), [PWA_GUIDE.md](../guides/PWA_GUIDE.md) — deployment targets.
- [TROUBLESHOOTING.md](../guides/TROUBLESHOOTING.md) — common operational issues.
- [CONTRIBUTING.md](../../CONTRIBUTING.md) — contributor workflow.
- [CLAUDE.md](../../CLAUDE.md) — repo rules for Claude Code (the source of truth
for many of the conventions above).
- [AGENTS.md](../../AGENTS.md) — deeper architecture reference used by agents.
+146
View File
@@ -0,0 +1,146 @@
---
title: "Monitoring & Costs — Navigation Structure"
version: 3.8.40
lastUpdated: 2026-06-28
---
# Monitoring & Costs — Navigation Structure
> Implemented in Group B (plan 16). See `src/shared/constants/sidebarVisibility.ts`.
---
## High-Level Navigation
The dashboard sidebar (after Group B) has these top-level sections in order:
```
Home
Providers
Combos
API Keys
Settings
Analytics
Costs ← NEW (Group B, plan 16)
Monitoring ← REORGANIZED (Group B, plan 16)
...
```
---
## Costs section (new, level 1)
Path prefix: `/dashboard/costs/`
| Item | URL | Description |
| ------------- | ------------------------------------ | ------------------------------------------------ |
| Overview | `/dashboard/costs` | Aggregated cost dashboard (moved from Analytics) |
| Pricing | `/dashboard/costs/pricing` | Per-model pricing table |
| Budget | `/dashboard/costs/budget` | Budget thresholds + alerts |
| Quota Sharing | `/dashboard/costs/quota-share` | Quota Share pools + usage |
| Plan Config | `/dashboard/costs/quota-share/plans` | Per-provider plan overrides |
**Rationale**: Pricing, Budget, and Quota Sharing were previously under
`Monitoring > Costs Parameters`. Moving them to a dedicated top-level section
makes them discoverable without navigating through observability tooling.
---
## Monitoring section (reorganized)
The Monitoring section now has **Activity at the top** followed by **3 subgroups**:
```
Monitoring
├── Activity ← Timeline feed (top-level item)
├── Logs group
│ ├── Logs (all)
│ ├── Proxy Logs
│ └── Console Logs
├── Audit group
│ ├── Audit Log
│ ├── MCP Audit
│ └── A2A Audit
└── System group
├── Health
└── Runtime
```
### What changed from the old structure
| Before | After |
| -------------------------------------------------------------------------------- | ------------------------------------------------- |
| Activity = tab inside Logs that rendered the Audit Log | Activity = dedicated feed (`/dashboard/activity`) |
| Costs Parameters group in Monitoring | Moved to Costs section |
| Flat list: Logs, Activity (logs), Audit, Health, Runtime, Pricing, Budget, Quota | Structured 3-group + dedicated Costs section |
---
## Activity vs Audit Log
These two are now distinct:
| Dimension | Activity (`/dashboard/activity`) | Audit Log (`/dashboard/audit`) |
| ---------------- | ------------------------------------------------------ | ----------------------------------------- |
| **Purpose** | User-facing event feed ("what happened recently") | Compliance / security log |
| **Data source** | `GET /api/compliance/audit-log?level=high` | `GET /api/compliance/audit-log?level=all` |
| **Format** | Timeline, grouped by day, human-readable verbs + icons | Dense paginaged table, 50/page |
| **Filters** | Event type category | Action, severity, actor, date range |
| **Export** | Not available | JSON export |
| **Actor filter** | Not applicable | Filterable by actor |
| **Events shown** | High-level actions only (allowlist) | All audit events |
### High-Level Actions allowlist
Defined in `src/lib/audit/highLevelActions.ts`. Controls which events appear in
the Activity feed. The allowlist includes:
- Provider add/remove/test events
- Combo create/update/delete
- API key lifecycle (create, revoke, rotate)
- Budget threshold reached
- Auth login/logout
- Cloud agent session creation
- MCP tool registration
- Webhook create/delete
- Quota pool/plan changes (`quota.*` actions, Group B)
- Platform events (update, deploy)
- Skill install/remove
Events not in this list appear only in the Audit Log.
### Adding a new high-level action
Edit `src/lib/audit/highLevelActions.ts` and add the action string to
`HIGH_LEVEL_ACTIONS`. This requires a PR (the list is code, not DB-configurable).
The corresponding icon can be added to `src/lib/audit/activityIcons.ts`.
---
## Redirect: `/dashboard/logs/activity`
The old path `/dashboard/logs/activity` is permanently redirected (HTTP 308) to
`/dashboard/activity` via `permanentRedirect()` in
`src/app/(dashboard)/dashboard/logs/activity/page.tsx`.
The legacy sidebar ID `logs-activity` is preserved in `HIDEABLE_SIDEBAR_ITEM_IDS`
(but removed from `SIDEBAR_DEFINITIONS`) to avoid breaking user presets that
reference the old ID.
---
## i18n
Namespaces added by Group B:
| Namespace key | Covers |
| ----------------------- | -------------------------------------------------------------- |
| `sidebar.costsSection` | Costs section label |
| `sidebar.activity` | Activity sidebar item |
| `sidebar.logsGroup` | Logs subgroup label |
| `sidebar.systemGroup` | System subgroup label |
| `sidebar.costsOverview` | Costs overview item |
| `activity.*` | All Activity page strings (title, verbs, filters, empty state) |
Source-of-truth locales: `pt-BR` and `en`. All other 39 locales fall back to
English via the `next-intl` fallback mechanism (configured in `src/i18n/config.ts`).
+279
View File
@@ -0,0 +1,279 @@
---
title: Quality Gates Reference
---
# Quality Gates Reference
This document is the authoritative reference for all CI quality gates in OmniRoute.
It describes each gate, what it validates, which CI job it runs in, whether it uses
a ratchet baseline or a pass/fail policy, and whether it blocks the build or is advisory.
For a short summary and the allowlist policy, see the "Quality Gates & Ratchets" section
in `CLAUDE.md`.
---
## Gate Inventory (~50 scripts)
Scripts live under `scripts/check/` (policy gates) and `scripts/quality/` (ratchet engine).
The CI source of truth is `.github/workflows/ci.yml`.
### Job: `lint`
Runs on every PR to `main`. Blocks merge on failure.
| Script (`npm run ...`) | Validates | Blocking |
| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------- |
| `check:node-runtime` | Node.js version is within the supported range | Yes |
| `check:cycles` | Circular imports — all `src/` + `open-sse/` modules | Yes |
| `check:route-validation:t06` | Zod schemas present on all routes (Tier 6 policy) | Yes |
| `check:any-budget:t11` | `@ts-expect-error // any` count does not exceed budget (Tier 11 catraca) | Yes |
| `check:provider-consistency` | Every provider in `providers.ts` has a matching entry in `providerRegistry.ts` (and vice-versa, within the allowlist) | Yes |
| `check:fetch-targets` | Every `fetch("/api/...")` in client-side `src/` resolves to a real `route.ts` | Yes |
| `check:deps` | All `npm install`-able deps across every `package.json` in the repo are in `dependency-allowlist.json`; new unpinned or slopsquatted packages flagged | Yes |
| `audit:deps` | `npm audit` (root + electron) — no high/critical advisories (overlaps osv `check:vuln-ratchet`; see Rationalization Backlog) | Yes |
| `check:lockfile` | `package-lock.json` integrity — https registry, integrity hashes, no host overrides | Yes |
| `check:licenses` | SPDX license allowlist for production dependencies | Yes |
| `check:tracked-artifacts` | No build artifacts / committed `node_modules` symlinks (also runs in husky pre-push) | Yes |
| `check:file-size` | No source file exceeds the per-extension cap (ratchet: frozen large files in `frozen` list) | Yes |
| `check:error-helper` | Error responses in executors/handlers use `buildErrorBody()` / `sanitizeErrorMessage()` (Hard Rule #12) | Yes |
| `check:migration-numbering` | Migration SQL files are sequentially numbered, no gaps or duplicates | Yes |
| `check:public-creds` | No literal OAuth `client_id`/`client_secret` or Firebase Web keys outside `publicCreds.ts` (Hard Rule #11) | Yes |
| `check:db-rules` | No raw SQL outside `src/lib/db/` modules; no barrel-imports from `localDb.ts` (Hard Rules #2/#5) | Yes |
| `check:known-symbols` | Provider executors, routing strategies, and translators registered in their dispatch tables match the files on disk — no orphaned or undeclared symbols | Yes |
| `check:route-guard-membership` | Every route that spawns a child process is classified by `isLocalOnlyPath()` (Hard Rules #15/#17) | Yes |
| `check:test-discovery` | Every `*.test.ts` / `*.spec.ts` file in the repo is collected by at least one test runner (ratchet: orphan list in `test-discovery-baseline.json` can only shrink) | Yes |
| `check:docs-sync` | CHANGELOG version, OpenAPI version, and `llm.txt` are in sync | Yes |
| `typecheck:core` | TypeScript compilation without errors (advisory warnings only) | Yes |
| `typecheck:noimplicit:core` | Strict `noImplicitAny` — forward-looking; many pre-existing call sites still need annotations | **Advisory** (`continue-on-error: true`) |
### Job: `quality-gate`
Runs after `test-coverage`. Blocks merge on failure.
| Script | Validates | Blocking |
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | ------------------------- |
| `quality:collect` | Emits `quality-metrics.json` (ESLint warning count, coverage from merged shard report) | Yes (upstream of ratchet) |
| `quality:ratchet` | Each metric in `quality-baseline.json` has not regressed (ESLint warnings ≤ baseline; coverage ≥ baseline) | Yes |
| `check:duplication` | Code duplication (jscpd@4) does not exceed baseline in `quality-baseline.json` | Yes |
| `check:complexity` | File-level cyclomatic complexity does not exceed the cap (core ESLint `complexity` + `max-lines-per-function`) | Yes |
| `check:cognitive-complexity` | Cognitive complexity ratchet (`eslint-plugin-sonarjs`) — separate ESLint pass; mergeable with `check:complexity` (see Backlog) | Yes |
| `check:dead-code` | Unused exports / files ratchet (knip) does not regress vs baseline | Yes |
| `check:type-coverage` | Percent-typed ratchet (`type-coverage`) does not regress; largely subsumes `typecheck:noimplicit:core` | Yes |
| `check:codeql-ratchet` | Open CodeQL alert count does not regress (reads via `gh api`; graceful-skip without token) | Yes |
### Job: `quality-extended`
Entire job is advisory (`continue-on-error: true`). The npm-based ratchets run for
real; the external scanners install via `gh release download` and self-skip (exit 0)
when a binary is still absent.
| Script | Validates | Blocking |
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------ |
| `check:circular-deps` | No circular dependencies (dpdm) | **Advisory** |
| `check:bundle-size` | Bundle size does not exceed the cap | **Advisory** |
| `check:secrets` | Secret scanning (gitleaks) — skips if binary absent | **Advisory** |
| `check:vuln-ratchet` | Dependency vulnerabilities (osv-scanner) do not regress — skips if binary absent | **Advisory** |
| `check:workflows` | Workflow lint (actionlint + zizmor) — skips if binaries absent | **Advisory** |
| `check:openapi-breaking` | Breaking changes to the public API contract (`openapi.yaml`) vs the base branch (oasdiff) — emits `openapiBreaking=N`; skips if oasdiff absent or base spec unresolvable | **Advisory** |
### Job: `docs-sync-strict`
Runs on every PR to `main`. Blocks merge on failure.
| Script | Validates | Blocking |
| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------- |
| `check:docs-all` | Meta-gate that runs the 6 sub-gates below sequentially | Yes |
| ↳ `check:docs-sync` | CHANGELOG / OpenAPI / llm.txt version consistency | Yes |
| ↳ `check:docs-counts` | Counts in prose (provider count, migration count, etc.) are within the ratchet window of the real counts | Yes |
| ↳ `check:env-doc-sync` | Every env var in `.env.example` is documented in a docs table, and vice versa | Yes |
| ↳ `check:deprecated-versions` | No deprecated version strings in docs | Yes |
| ↳ `check:doc-links` | Internal markdown links in docs resolve to real files (`[text]`/`(path)` form) | Yes |
| ↳ `check:fabricated-docs` | Routes, env vars, CLI commands, hook names, and file paths cited in docs exist in the codebase. Hard gate via `--strict`; soft-fail without flag. | Yes (via `--strict` in CI) |
| `check:cli-i18n` | CLI command strings are present in all i18n locale files | Yes |
| `check:openapi-coverage` | OpenAPI spec covers at least a ratcheted floor of real routes | Yes |
| `check:openapi-security-tiers` | Security tier annotations in `openapi.yaml` are consistent with `routeGuard.ts` classifications | **Advisory** |
| `check:openapi-routes` | Every path in `openapi.yaml` resolves to a real `route.ts` (anti-hallucination) | Yes |
| `check:docs-symbols` | Every `/api/...` reference in `docs/**/*.md` resolves to a real `route.ts` (anti-hallucination) | Yes |
| `i18n translation drift` | Untranslated keys in i18n locale files — warn only | **Advisory** |
### Job: `i18n-ui-coverage`
| Script | Validates | Blocking |
| --------------------------------- | ----------------------------- | -------- |
| `check-ui-keys-coverage` (inline) | UI i18n key coverage is ≥ 65% | Yes |
### Job: `i18n`
Full i18n validation matrix (one job per locale). Entire job is advisory.
| Script | Validates | Blocking |
| ------------------------------- | ----------------------------------- | ----------------------------------------------------- |
| `validate_translation.py quick` | Translation completeness per locale | **Advisory** (`continue-on-error: true` on whole job) |
### Job: `pr-test-policy`
Runs on pull requests only.
| Script | Validates | Blocking |
| ---------------------- | -------------------------------------------------------------------------------------------------------------------------- | -------- |
| `check:pr-test-policy` | PRs that change production code in `src/`, `open-sse/`, `electron/`, or `bin/` must include or update tests (Hard Rule #8) | Yes |
| `check:test-masking` | Changed test files do not reduce net assert count or add `assert.ok(true)` tautologies | Yes |
| `check:pr-evidence` | PR body cites test/VPS evidence for the change (mechanizes Hard Rule #18 by grepping PR prose — fragile, see Backlog) | Yes |
### Job: `test-vitest`
Runs after `build`. Blocks merge on failure.
| Suite | Validates | Blocking |
| ---------------- | ------------------------------------------------------- | -------------------------------------------------------------------------- |
| `test:vitest` | MCP server (94 tools), autoCombo, cache — vitest runner | Yes |
| `test:vitest:ui` | UI component tests — vitest runner | **Advisory** (`continue-on-error: true`) — failing until Fase 6A UI triage |
### Nightly workflows (scheduled, advisory)
These run on a cron schedule (and `workflow_dispatch`), never on PRs. All are advisory.
| Workflow | Validates | Blocking |
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ |
| `nightly-property` | fast-check property tests with a random seed + high run count | **Advisory** |
| `nightly-resilience` | heap-growth gate, chaos fault-injection, k6 load/soak | **Advisory** |
| `nightly-llm-security` | promptfoo injection guard (block mode) + garak probes (skipped without a provider secret) | **Advisory** |
| `nightly-schemathesis` | OpenAPI contract fuzzing (schemathesis) against a live OmniRoute using `docs/openapi.yaml` — surfaces spec violations / unhandled 500s (Fase 8 B.4) | **Advisory** |
---
## Ratchet Baseline (`quality-baseline.json`)
The ratchet engine (`scripts/quality/check-quality-ratchet.mjs`) reads `quality-baseline.json`
and compares it against the freshly collected `quality-metrics.json`. Any metric that regresses
beyond its epsilon fails the build.
Current tracked metrics:
| Metric | Direction | Meaning |
| --------------------- | --------- | ---------------------------------- |
| `eslintWarnings` | `down` | ESLint warning count must not grow |
| `coverage.statements` | `up` | Statement coverage must not fall |
| `coverage.lines` | `up` | Line coverage must not fall |
| `coverage.functions` | `up` | Function coverage must not fall |
| `coverage.branches` | `up` | Branch coverage must not fall |
To update the baseline after a genuine improvement:
```bash
npm run quality:ratchet -- --update
git add quality-baseline.json
```
The `--update` flag writes the current measured values into `quality-baseline.json`.
Commit this file alongside the change that improved the metric. A PR that improves a
metric without updating the baseline will be caught by `--require-tighten` (Fase 6A.5,
pending implementation).
---
## Allowlist Policy
Every gate that cannot fail on pre-existing violations uses a frozen allowlist
(e.g., `KNOWN_STALE_DOC_REFS`, `KNOWN_MISSING`, `KNOWN_RAW_SQL`). The policy is:
**Fix the root cause; use the allowlist only when the violation is pre-existing and
cannot be fixed in the same PR.**
When adding an entry to an allowlist:
1. Include a comment with the justification.
2. Reference the tracking issue (e.g., `// #3498 — Phase 2 feature, not yet implemented`).
3. Remove the entry in the same PR that fixes the violation — a stale entry that no longer
suppresses an active violation is itself a defect (6A.3 stale-enforcement will
fail the gate on an orphaned allowlist entry once implemented).
Do **not** add allowlist entries to make tests pass faster. A green gate with a growing
allowlist is a false sense of quality.
### When a gate fails on your PR
1. **Read the gate output carefully** — it tells you exactly which file or symbol violated
the rule.
2. **Fix the violation** — most gates are deterministic filesystem checks that pass as soon
as the code is correct.
3. **If the violation is pre-existing** (i.e., you did not introduce it but the gate now
covers it): add an allowlist entry with a justification comment and a tracking issue.
4. **If the gate is a ratchet** (coverage, ESLint warnings, duplication, complexity):
your change made the metric worse. Fix the underlying issue, or (rarely) run
`npm run quality:ratchet -- --update` if the change is intentional and the metric
degradation is acceptable — but document why in the PR description.
5. **Advisory gates** (`continue-on-error: true`) are informational — they do not block
merge but appear in the CI summary. Fix them anyway.
---
## Adding a New Gate
1. Create `scripts/check/check-<name>.mjs` (or `.ts`). Policy gates exit 0/1.
Ratchet-style gates emit a metric to `quality-metrics.json` via `collect-metrics.mjs`.
2. Add `"check:<name>": "node scripts/check/check-<name>.mjs"` to `package.json`.
3. Wire it in `.github/workflows/ci.yml` under the appropriate job
(policy → `lint` or `docs-sync-strict`; ratchet → `quality-gate`).
4. If it has an allowlist, apply `reportStaleEntries()` from
`scripts/check/lib/allowlist.mjs` so stale entries are detected automatically.
5. Write a test in `tests/unit/build/` covering the gate's detection logic.
6. Update this document (add a row to the relevant job table).
---
## Agent tooling: LSP-in-the-loop (opt-in)
Beyond the CI gates, OmniRoute ships an **opt-in** `agent-lsp` scaffold
(a project-level `.mcp.json`, Fase 7 Task 15). Create `.mcp.json`
to expose a TypeScript language server to coding agents, so they resolve symbols /
diagnostics **before** writing code — a compile-before-claim companion to
`typecheck:core` that cuts "invented symbol" errors at the source. It is intentionally
not auto-loaded (you pick and verify the MCP↔LSP bridge); a broken entry only logs a
connection error and never breaks sessions.
---
## Rationalization Backlog (ROI review — Fase 9 Onda 3)
This inventory was reconciled against `ci.yml` on 2026-06-17 (the prior version omitted
`audit:deps`, `check:tracked-artifacts`, `check:lockfile`, `check:licenses`,
`check:dead-code`, `check:cognitive-complexity`, `check:type-coverage`,
`check:codeql-ratchet`, `check:pr-evidence`). An ROI review of the reconciled set
identified the following rationalization candidates. **The merges are mechanical CI
changes; the flips/drops are policy decisions reserved for the operator.** Nothing below
is applied yet.
**Also undocumented above** (advisory, low signal): the `docs-lint` job
(markdownlint + Vale, whole job `continue-on-error`) and the standalone scanner workflows
`semgrep.yml` / `codeql.yml` / `scorecard.yml`. `semgrepFindings: 0` is in
`quality-baseline.json` but is not wired to a blocking ratchet in `ci.yml` — the metric is
currently orphaned.
### Merge / dedup (mechanical, lower risk)
Each candidate was validated against the live gate state on 2026-06-17 (trust-but-verify);
several "obvious" merges turned out to hide debt and are **not** clean drop-ins.
- **`check:docs-sync` runs twice** — standalone in the `lint` job and again inside `check:docs-all` (`docs-sync-strict`) and the husky pre-commit hook. ✅ **DONE** — standalone `lint` invocation removed.
- **CVE scanning** — ❌ **NOT a clean merge.** `audit:deps` hard-fails on any high/critical CVE; `check:vuln-ratchet` (osv) only fails on a _regression_ vs baseline (currently 1 MODERATE). Different semantics — dropping `audit:deps` would lose the absolute high/critical gate. Keep both.
- **Cycle detection** — ❌ **NOT a clean merge.** `check:circular-deps` (dpdm) reports **91 cycles** (that is why it is advisory); it cannot be promoted to blocking without first resolving them, and it has a broader scope than the green, curated `check:cycles`. Keep `check:cycles` blocking; resolving the 91 dpdm cycles is its own backlog.
- **Complexity** — ⏳ valid but real surgery. `check:complexity` (core ESLint) + `check:cognitive-complexity` (sonarjs) are two ESLint passes over `src` + `open-sse`; merging into one config emitting both metrics needs careful ratchet re-wiring. Deferred.
- **`/api` anti-hallucination** — ⏳ valid but script surgery. `check:openapi-routes` (spec→route) + `check:docs-symbols` (prose→route) share resolution logic; collapsing them is a non-trivial script change. Deferred.
- **`check:node-runtime` runs in 11 jobs** — ⚠️ **low ROI.** Each is a separate runner and the check is <1s; total savings ~10s, against losing a cheap per-job guard. Not worth the churn.
### Flip / decide (operator policy)
- `check:openapi-security-tiers` (advisory) — ❌ **NOT cleanly flippable.** It exits 0 but warns that several `traffic-inspector` routes under `LOCAL_ONLY_API_PREFIXES` lack the `x-loopback-only: true` annotation. Enforcing it requires adding those annotations to `openapi.yaml` first.
- `typecheck:noimplicit:core` (advisory) — largely subsumed by the blocking `check:type-coverage` ratchet. Flip to a ratchet or drop the redundant second `tsc` pass.
- `test:vitest:ui` (advisory, 14 parked fails) — fix-and-block or delete; don't leave rotting.
- `check:secrets` (gitleaks, blocking ratchet frozen at 3 documented false-positives) — allowlist the 3 to reach 0, or demote to advisory. Overlaps GitHub native secret-scanning + `check:public-creds`.
- `check:pr-evidence` (blocking, greps PR-body prose) — high false-positive risk; weakens Hard Rule #18 enforcement if dropped, so this is a genuine policy call.
- `semgrep` (advisory standalone) — overlaps CodeQL for the OWASP families; wire its baseline to a ratchet or drop.
---
## Related Documentation
- Supply-chain (provenance, SBOM, Trivy, Scorecard): [`docs/security/SUPPLY_CHAIN.md`](../security/SUPPLY_CHAIN.md)
+583
View File
@@ -0,0 +1,583 @@
---
title: "Repository Map"
version: 3.8.40
lastUpdated: 2026-06-28
---
# Repository Map
> **One-line description for every directory and root file.**
> Last updated: 2026-06-28 — OmniRoute v3.8.40
>
> Use this map to navigate the codebase quickly. For deep dives, follow links to dedicated docs.
## Top-level tree
```
OmniRoute/
├── src/ # Next.js 16 application (UI + API routes + libs + domain + server)
├── open-sse/ # Streaming engine workspace (handlers, executors, translator, MCP server)
├── electron/ # Desktop wrapper (Electron 41 + electron-builder 26.10)
├── bin/ # CLI entry point and command handlers
├── scripts/ # Build, check, sync, and one-off scripts
├── docs/ # Public documentation (you are here)
├── tests/ # All test suites (unit, integration, e2e, protocols-e2e)
├── public/ # Next.js static assets, PWA manifest, service worker, icons
├── config/ # Static config + quality-gate state (i18n, payloadRules, quality/)
├── images/ # Marketing / README image assets
├── @omniroute/ # Publishable companion packages (opencode-plugin, opencode-provider)
├── skills/ # CLI/agent skill packs (cli-* + omni-* + config-codex-cli)
├── examples/ # Sample plugins + omniroute-cmd-hello starter
├── contrib/ # Community contributions (podman/)
├── .source/ # Fumadocs source config (source.config.mjs + server/browser/dynamic)
├── .github/ # GitHub Actions workflows + issue templates + PR template
├── .husky/ # Git hooks (pre-commit, pre-push)
├── .claude/ # Claude Code slash commands (project-scoped)
├── .agents/ # Codex / generic agent workflows + skills (mirror of .claude/)
├── .vscode/ # VS Code workspace settings
├── _ideia/ # Planning notes (informal; not shipped)
├── _mono_repo/ # Historic subprojects (cloud, site, vscode-extension)
├── _references/ # Read-only reference clones from related OSS projects
├── _tasks/ # Per-release task tracking files (informal)
├── .build/ .worktrees/ dist/ # local build / git-worktree / build-output scratch (gitignored)
├── .issues/ # Local issue cache (gitignored)
├── .playwright-mcp/ # Playwright MCP test artifacts
├── coverage/ # c8 coverage output (gitignored)
├── logs/ # Runtime logs (gitignored)
├── node_modules/ # Dependencies (gitignored)
├── package/ # npm pack staging area (build artifact)
├── .next/ # Next.js build output (gitignored)
└── (root files — see below)
```
---
## Root files
| File | Purpose |
| ------------------------------------------- | ---------------------------------------------------------------------------------------- |
| **README.md** | Marketing landing page + quick start + feature matrix (see also `llm.txt`) |
| **CHANGELOG.md** | Per-release changelog (auto-generated by `/version-bump-cc` skill) |
| **LICENSE** | MIT license text |
| **CLAUDE.md** | Project rules for Claude Code agents (hard rules, conventions, scenarios) |
| **AGENTS.md** | Same as CLAUDE.md but for non-Claude AI agents (Codex, Cursor, etc.) |
| **GEMINI.md** | Concise rules for Gemini-based agents (subset of CLAUDE.md) |
| **CONTRIBUTING.md** | Contributor guide: setup, conventional commits, testing, PR flow |
| **SECURITY.md** | Vulnerability reporting policy, supported versions, threat model |
| **CODE_OF_CONDUCT.md** | Contributor Covenant — community behavior expectations |
| **llm.txt** | Plain-text landing optimized for LLM crawlers (SEO for AI assistants) |
| **package.json** | npm manifest, scripts, dependencies, engines, c8 coverage gate |
| **package-lock.json** | Locked dependency tree |
| **tsconfig.json** | Root TypeScript config |
| **tsconfig.typecheck-core.json** | Typecheck config for `src/` core |
| **tsconfig.typecheck-noimplicit-core.json** | Strict (`noImplicitAny`) typecheck |
| **tsconfig.tsbuildinfo** | TS incremental build cache (gitignored) |
| **next.config.mjs** | Next.js 16 build configuration (standalone output) |
| **next-env.d.ts** | Next.js auto-generated env types |
| **eslint.config.mjs** | ESLint flat config (rules per project area) |
| **prettier.config.mjs** | Prettier formatting rules |
| **postcss.config.mjs** | PostCSS config for Tailwind/CSS pipeline |
| **playwright.config.ts** | Playwright E2E test config |
| **vitest.config.ts** | Vitest config (default suite) |
| **vitest.mcp.config.ts** | Vitest config for MCP server / autoCombo / cache suites |
| **sonar-project.properties** | SonarQube/SonarCloud config (code quality) |
| **Dockerfile** | Multi-stage Docker build (builder → runner-base → runner-cli) |
| **docker-compose.yml** | Dev compose with 4 profiles (base, cli, host, cliproxyapi) + redis sidecar |
| **docker-compose.prod.yml** | Production compose (port 20130, redis, named volumes) |
| **.dockerignore** | Files excluded from Docker context |
| **fly.toml** | Fly.io deployment config (region `sin`, port 20128, /data volume) |
| **.env.example** | Template env file (auto-copied to `.env` on first install) |
| **.gitignore** | Git ignore patterns |
| **.npmignore** | npm publish exclusion list |
| **.npmrc** | npm config (registry, lockfile policy) |
| **.node-version** | Node version pin (used by nvm-compatible tools) |
| **.nvmrc** | Node version pin for nvm |
| **eslint.complexity.config.mjs** | ESLint config for the complexity ratchet (`scripts/check/check-complexity.mjs --config`) |
| **eslint.sonarjs.config.mjs** | ESLint config for SonarJS rules (cognitive complexity / duplication) |
| **source.config.ts** | Fumadocs `defineDocs` source config (feeds `.source/`) |
| **knip.json** | Knip config — unused files/exports/deps (feeds the dead-code gate) |
| **stryker.conf.json** | Stryker mutation-testing config |
| **.size-limit.json** | size-limit bundle budget config |
| **promptfooconfig.yaml** | promptfoo eval config |
| **.gitleaks.toml** | gitleaks secret-scan ruleset |
| **.zizmor.yml** | zizmor GitHub-Actions security-lint config |
| **socket.yml** | Socket.dev supply-chain config |
| **news.json** | In-app release-notes feed (read by `src/shared/utils/releaseNotes.ts`) |
| **flake.nix** / **flake.lock** | Nix dev-shell definition + lock |
| **.env** | Local secrets (gitignored — generated from `.env.example`) |
> **Moved out of the root in v3.8.26 (declutter):**
>
> - **→ `config/quality/`:** `quality-baseline.json`, `complexity-baseline.json`, `duplication-baseline.json`, `file-size-baseline.json`, `test-discovery-baseline.json`, `dependency-allowlist.json`, `.license-allowlist.json`, and the generated `quality-metrics.json` (gitignored). See [`## config/`](#config--static-configs--quality-gate-state).
---
## `src/` — Next.js application
```
src/
├── app/ # App Router (pages + API routes + status pages + landing)
├── lib/ # Core libraries / domain modules (~50 subdirs + ~30 top-level files)
├── domain/ # Pure domain logic (policy engine, fallback, cost, lockout, comboResolver, assessment)
├── server/ # Server-only modules (authz pipeline, cors, auth middleware) — cannot import from client
├── shared/ # Shared between server and client where safe (constants, types, validation, contracts, utils)
├── i18n/ # next-intl config + per-locale message JSON (30+ locales)
├── middleware/ # Next.js middleware (request enrichment, locale detection)
├── mitm/ # MITM proxy core: cert gen/install, handlers, targets, inspector, masks, passthrough
│ ├── handlers/ # 9 IDE-agent handler classes extending MitmHandlerBase (antigravity, kiro, copilot, codex, cursor, zed, claudeCode, openCode, trae)
│ └── inspector/ # Traffic capture layer: buffer (in-memory ring), sseMerger, conversationNormalizer, kindDetector, contextKey, httpProxyServer, systemProxyConfig
├── models/ # Model adapter glue (legacy shim)
├── scripts/ # In-tree maintenance scripts (e.g., backfillAggregation)
├── sse/ # Legacy SSE handlers/services (chat.ts, chatHelpers.ts, services/auth.ts)
├── store/ # Legacy in-memory store (being phased out for src/lib/db)
├── types/ # Shared TS type files
├── instrumentation.ts # Next.js telemetry hook (browser + edge)
├── instrumentation-node.ts # Node-only instrumentation
├── server-init.ts # Server bootstrap (DB migrations, jobs, cleanup)
└── proxy.ts # HTTP-proxy entry shim
```
### `src/app/` — App Router (Next.js 16)
| Path | Purpose |
| ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `app/api/v1/` | Public OpenAI-compat API (~25 sub-routes: chat, completions, embeddings, files, batches, audio, images, videos, music, rerank, moderations, search, ws, agents, accounts, providers, etc.) |
| `app/api/v1beta/` | Gemini-style API endpoints |
| `app/api/playground/` | Playground Studio routes: `improve-prompt/` (POST — LLM prompt rewriter), `presets/` (GET list / POST create), `presets/[id]/` (GET / PUT / DELETE) — see `docs/frameworks/PLAYGROUND_STUDIO.md` |
| `app/api/` (non-v1) | Management/admin routes (~60 directories: providers, combos, settings, mcp, a2a, evals, memory, skills, webhooks, compliance, resilience, monitoring, tunnels, cli-tools, etc.) |
| `app/api/tools/agent-bridge/` | AgentBridge REST API — 12 routes (server control, agent state/DNS/mappings, bypass, cert, upstream-CA). LOCAL_ONLY + SPAWN_CAPABLE. See `docs/frameworks/AGENTBRIDGE.md §7`. |
| `app/api/tools/traffic-inspector/` | Traffic Inspector REST + WS API — 16+ routes (requests, sessions, hosts, capture-modes, export, ws). LOCAL_ONLY + SPAWN_CAPABLE. See `docs/frameworks/TRAFFIC_INSPECTOR.md §8`. |
| `app/a2a/` | A2A JSON-RPC 2.0 entry point (`POST /a2a`) |
| `app/.well-known/agent.json/` | A2A Agent Card (discovery) |
| `app/(dashboard)/dashboard/` | Dashboard UI pages (~35 pages: providers, combos, settings, memory, skills, webhooks, evals, audit, batch, cache, costs, health, system, activity, etc.) |
| `app/(dashboard)/dashboard/search-tools/` | Search Tools Studio UI (3 tabs: Search/Scrape/Compare + SearchConceptCard + ProviderCatalog) — see `docs/frameworks/SEARCH_TOOLS_STUDIO.md` |
| `app/(dashboard)/dashboard/` | Dashboard UI pages (~30 pages: providers, combos, settings, memory, skills, webhooks, evals, audit, batch, cache, costs, health, system, etc.) |
| `app/(dashboard)/dashboard/memory/` | Memory Studio (plan 21): `page.tsx` (3-tab shell), `components/` (MemoryConceptCard, MemoryEngineStatus, EmbeddingSourceSelector, EditMemoryModal, RetrievePreview, QdrantConfigCard, RerankConfigCard), `components/tabs/` (MemoriesTab, PlaygroundTab, EngineTab), `hooks/` (useEngineStatus, useMemorySettings) |
| `app/(dashboard)/dashboard/tools/agent-bridge/` | AgentBridge dashboard page — server card, 9 agent cards, setup wizard, model mapping, bypass list. i18n PT-BR + EN. See `docs/frameworks/AGENTBRIDGE.md`. |
| `app/(dashboard)/dashboard/tools/traffic-inspector/` | Traffic Inspector dashboard page — DevTools split, 7 detail tabs, 4 capture mode toggles, session recorder, context colorization. i18n PT-BR + EN. See `docs/frameworks/TRAFFIC_INSPECTOR.md`. |
| `app/(dashboard)/dashboard/activity/` | Activity feed page (Group B): `page.tsx` (server) + `ActivityFeedClient.tsx` + `components/{ActivityFeed,ActivityItem,DayHeader,EventTypeFilter}.tsx` — see `docs/architecture/MONITORING_SECTIONS.md` |
| `app/(dashboard)/dashboard/costs/quota-share/` | Quota Sharing page (Group B): `QuotaSharePageClient.tsx` + `components/{PoolCard,DimensionBar,AllocationTable,BurnRateChart,QuotaConceptCard,CreatePoolModal,EditAllocationsModal}.tsx` + `hooks/{usePools,usePoolUsage,useLocalStoragePoolMigration}.ts` |
| `app/(dashboard)/dashboard/costs/quota-share/plans/` | Provider plan config page (Group B): `page.tsx` + `ProviderPlanConfigClient.tsx` — quota dimensions per connection override |
| `app/docs/` | Embedded documentation viewer (renders `docs/*.md`) |
| `app/landing/` | Marketing landing page |
| `app/login/`, `forgot-password/`, `forbidden/` | Auth-related pages |
| `app/{400,401,403,408,429,500,502,503}/` | HTTP error pages |
| `app/maintenance/`, `offline/`, `status/`, `privacy/`, `terms/`, `callback/` | Static/status pages |
| `app/layout.tsx`, `page.tsx`, `manifest.ts`, `globals.css` | Root layout, home, PWA manifest, global CSS |
| `app/error.tsx`, `global-error.tsx`, `not-found.tsx`, `loading.tsx` | Error boundaries |
### `src/lib/` — Core libraries (~50 modules)
| Module | Purpose |
| ---------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `a2a/` | A2A protocol task manager, skills (5), streaming |
| `acp/` | CLI Agent Registry (local CLI discovery — see `docs/frameworks/AGENT_PROTOCOLS_GUIDE.md`) |
| `api/` | Shared API helpers (`requireManagementAuth`, validation) |
| `auth/` | Session, password hashing, token validation |
| `batches/` | OpenAI Batches API handlers |
| `catalog/` | Provider catalog Zod validation + capability resolution |
| `cloudAgent/` | Cloud Agents (Codex Cloud, Devin, Jules) — see `docs/frameworks/CLOUD_AGENT.md` |
| `combos/` | Combo resolution + reorder helpers |
| `audit/` | Activity feed helpers: `highLevelActions.ts` (allowlist + `isHighLevelAction()`), `activityIcons.ts` (action → icon/verb map), `timeline.ts` (groupByDay/relativeTime) — see `docs/architecture/MONITORING_SECTIONS.md` |
| `compliance/` | Audit log + provider audit — see `docs/security/COMPLIANCE.md` |
| `compression/` | Compression engine glue (engines live in `open-sse/services/compression/`) |
| `config/` | Runtime config helpers |
| `db/` | 95+ domain DB modules + 110+ migrations (always go through here for SQLite) |
| `quota/` | Quota Sharing Engine: `dimensions.ts` (types/Zod), `types.ts` (QuotaStore interface), `sqliteQuotaStore.ts`, `redisQuotaStore.ts`, `storeFactory.ts`, `fairShare.ts`, `burnRate.ts`, `planResolver.ts`, `planRegistry.ts`, `saturationSignals.ts`, `enforce.ts`, `spendRecorder.ts` — see `docs/routing/QUOTA_SHARE.md` |
| `display/` | UI formatting helpers (cost, latency, etc.) |
| `embeddings/` | Embeddings service helpers |
| `env/` | Env variable parsing + validation |
| `evals/` | Eval framework (suites, runner, runtime) — see `docs/frameworks/EVALS.md` |
| `guardrails/` | PII masker, prompt injection, vision bridge — see `docs/security/GUARDRAILS.md` |
| `jobs/` | Background jobs (cron-like) |
| `memory/` | Conversational memory (SQLite FTS5 + sqlite-vec hybrid RRF + Qdrant tier 2) — see `docs/frameworks/MEMORY.md` |
| `memory/embedding/` | Multi-source embedding layer: `index.ts` (resolver), `remote.ts`, `staticPotion.ts`, `transformersLocal.ts`, `cache.ts`, `types.ts` (plan 21) |
| `memory/vectorStore.ts` | sqlite-vec v0.1.9 wrapper — KNN brute-force + hybrid RRF (FTS5 + vector, k=60). Lazy-init, degrades gracefully when sqlite-vec unavailable. (plan 21) |
| `memory/reindex.ts` | `runReindexBatch()` — processes memories with `needs_reindex=1` in background; called by `POST /api/memory/reindex` and lazy-backfill path. (plan 21) |
| `monitoring/` | Health checks, metrics emission |
| `oauth/` | OAuth flows for 14 providers (claude, codex, antigravity, cursor, github, gemini, kimi-coding, kilocode, cline, qwen, kiro, qoder, gitlab-duo, windsurf) |
| `plugins/` | Plugin registry |
| `promptCache/` | Anthropic-style prompt cache breakpoints |
| `skills/` | Skills framework (built-in + marketplace + SkillsSH) — see `docs/frameworks/SKILLS.md` |
| `playground/` | Playground Studio shared helpers: `codeExport.ts` (curl/Python/TS generator), `promptImprover.ts` (meta-prompt builder), `streamMetrics.ts` (pure TTFT/TPS), `types.ts` (pricing table) — see `docs/frameworks/PLAYGROUND_STUDIO.md` |
| `webhookDispatcher.ts` | HMAC webhook delivery — see `docs/frameworks/WEBHOOKS.md` |
| `cloudflaredTunnel.ts`, `ngrokTunnel.ts` | Tunnel managers — see `docs/ops/TUNNELS_GUIDE.md` |
| `oneproxySync.ts`, `oneproxyRotator.ts` | 1proxy free proxy marketplace — see `docs/ops/PROXY_GUIDE.md` |
| `cloudSync.ts`, `initCloudSync.ts` | Optional cloud sync of state |
| `localDb.ts` | Re-export barrel for db modules (no logic — re-exports only) |
| `cacheLayer.ts`, `idempotencyLayer.ts` | Request caching + idempotency |
| (~30 more top-level files) | Specialized helpers (logEnv, modelsDevSync, piiSanitizer, etc.) |
### `src/db/` — Database (94 modules + 106 migrations)
| Subdir | Purpose |
| ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `db/core.ts` | `getDbInstance()` singleton with WAL journaling |
| `db/migrations/` | Versioned SQL files (idempotent, transactional). `073_memory_vec.sql` adds `memory_vec_meta` + `needs_reindex` column (plan 21). |
| `db/playgroundPresets.ts` | CRUD module for Playground Studio presets (`listPlaygroundPresets`, `getPlaygroundPreset`, `createPlaygroundPreset`, `updatePlaygroundPreset`, `deletePlaygroundPreset`) |
| `db/memoryVec.ts` | CRUD for `memory_vec_meta` (active_dim, embedding_signature, last_reset_at, vec_loaded) + `markMemoryNeedsReindex`, `getMemoryReindexQueue`, etc. (plan 21) |
| `db/<domain>.ts` | One module per domain: providers, combos, apiKeys, users, sessions, usage, audit*log, webhooks, skills, memory_entries, cloud_agent_tasks, evals*\*, reasoning_cache, etc. |
### `src/domain/`
| Module | Purpose |
| ---------------------- | ----------------------------------------------------------------------- |
| `policy.ts` | Policy engine |
| `fallbackPolicy.ts` | Fallback decision tree |
| `costRules.ts` | Cost calculation rules |
| `lockoutPolicy.ts` | Model/connection lockout policy |
| `tagRouter.ts` | Tag-based routing |
| `comboResolver.ts` | Combo resolution (used by combo engine) |
| `modelAvailability.ts` | Per-model availability check |
| `assessment/` | Model assessment (Phase 1 of RFC-AUTO-ASSESSMENT — see `docs/archive/`) |
### `src/server/`
| Module | Purpose |
| -------- | ---------------------------------------------------------------------------------------------------- |
| `authz/` | Authorization pipeline: `classify``policies``enforce` — see `docs/architecture/AUTHZ_GUIDE.md` |
| `cors/` | CORS configuration |
| `auth/` | Session middleware |
### `src/shared/`
| Module | Purpose |
| -------------------------------- | ---------------------------------------------------------------------- |
| `constants/providers.ts` | **236 providers** with Zod validation (source of truth) |
| `constants/cliTools.ts` | External CLI tool registry |
| `constants/routingStrategies.ts` | **17 routing strategies** with priorities |
| `constants/publicApiRoutes.ts` | Routes that require Bearer (vs management) auth |
| `constants/upstreamHeaders.ts` | Header denylist for upstream requests |
| `validation/schemas.ts` | ~80 Zod schemas (single source of truth for API contracts) |
| `validation/helpers.ts` | Zod validation helpers (`validateBody`, etc.) |
| `types/` | Shared TS types |
| `contracts/` | Public API contracts (consumed by `files:` in `package.json`) |
| `utils/circuitBreaker.ts` | Provider circuit breaker (see `docs/architecture/RESILIENCE_GUIDE.md`) |
| `utils/apiAuth.ts` | API key validation, scope checking |
| `utils/fetchTimeout.ts` | Timeout/abort wrappers for upstream fetch |
---
## `open-sse/` — Streaming Engine Workspace
Separate npm workspace (`@omniroute/open-sse`). Handles request processing + provider execution.
```
open-sse/
├── handlers/ # 16 files (12 handlers + 4 helpers): chatCore, responsesHandler, embeddings, audio, image, video, music, rerank, moderations, search, etc.
├── executors/ # 67 provider-specific executors (extend BaseExecutor)
├── translator/ # Format converters (9 request, 9 response, 9 helpers)
├── transformer/ # Responses API ↔ Chat Completions (TransformStream)
├── services/ # ~80+ service modules (combo, accountFallback, autoCombo, reasoningCache, claude code/chatgpt stealth, modelDeprecation, taskAwareRouter, workflowFSM, etc.)
├── mcp-server/ # MCP server (94 tools, 3 transports, 30 scopes)
├── config/ # Provider/model registries, header config, model aliases
├── utils/ # TLS client, proxy fetch/dispatcher, network helpers
├── index.ts # Workspace entry
├── package.json # Workspace manifest
├── tsconfig.json # Workspace TS config
└── types.d.ts # Workspace type declarations
```
### `open-sse/mcp-server/`
| Path | Purpose |
| --------------------------- | ------------------------------------------------------------------------------ |
| `server.ts` | MCP server lifecycle (stdio + HTTP transports) |
| `httpTransport.ts` | HTTP Streamable + SSE transports (`/api/mcp/sse`, `/api/mcp/stream`) |
| `audit.ts` | Audit logging to `mcp_tool_audit` table |
| `scopeEnforcement.ts` | Per-tool scope validation |
| `runtimeHeartbeat.ts` | Health heartbeat to `DATA_DIR/runtime/mcp-heartbeat.json` |
| `descriptionCompressor.ts` | Compress tool description metadata to save context |
| `schemas/tools.ts` | 34 base tool definitions + scopes |
| `tools/advancedTools.ts` | Advanced tool implementations |
| `tools/memoryTools.ts` | 3 memory tools (search/add/clear) |
| `tools/skillTools.ts` | 4 skill tools (list/enable/execute/executions) |
| `tools/compressionTools.ts` | 5 compression tools |
| `README.md` | Internal MCP server README (cross-linked from `docs/frameworks/MCP-SERVER.md`) |
---
## `electron/` — Desktop Wrapper
| File | Purpose |
| ---------------- | --------------------------------------------------------------------------------- |
| `main.js` | Electron main process (BrowserWindow, embedded Next.js server, tray, auto-update) |
| `preload.js` | IPC bridge (contextBridge → `window.omniroute`) |
| `package.json` | electron-builder config + Electron 41 + electron-builder 26.10 deps |
| `assets/` | App icons (Windows .ico, macOS .icns, Linux .png) |
| `dist-electron/` | Build output (gitignored) |
| `types.d.ts` | Type declarations for renderer bridge |
| `README.md` | Internal Electron README (see also `docs/guides/ELECTRON_GUIDE.md`) |
---
## `bin/` — CLI
| File | Purpose |
| ----------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| `omniroute.mjs` | Main CLI entry — `omniroute serve`, `omniroute setup`, `omniroute doctor`, `omniroute providers`, `omniroute combos`, etc. |
| `reset-password.mjs` | Standalone password reset CLI |
| `cli/commands/setup.mjs` | Interactive + non-interactive setup wizard |
| `cli/commands/doctor.mjs` | System health diagnostics (8+ checks) |
| `cli/commands/providers.mjs` | Provider list/test/validate |
| `cli/{args,data-dir,encryption,io,provider-catalog,provider-store,provider-test,settings-store,sqlite}.mjs` | CLI helper modules |
| `cli/tray/tray.ts` | System tray integration (cross-platform: NotifyIcon on Windows, systray2 on macOS/Linux) |
| `cli/tray/tray.ps1` | PowerShell NotifyIcon backend (Windows, zero new binaries) |
| `cli/tray/autostart.ts` | Cross-platform autostart (LaunchAgent / .desktop / registry) |
| `cli/runtime/sqliteRuntime.mjs` | 5-step SQLite driver resolution chain (bundled → runtime → lazy-install → node:sqlite → sql.js) |
| `cli/runtime/magicBytes.mjs` | Binary magic-byte validation (ELF / Mach-O / Mach-O fat / PE) |
| `cli/runtime/index.mjs` | `warmUpRuntimes()` — pre-resolves drivers at postinstall / first startup |
| `nodeRuntimeSupport.mjs` | Validate supported Node.js version on install |
---
## `skills/` — Public Agent Skills
| File | Purpose |
| ---------------------------- | ---------------------------------------------------------------------------------- |
| `skills/omniroute*/SKILL.md` | 10 skill manifests for external AI agents (Claude Desktop, ChatGPT, Cursor, Cline) |
---
## `scripts/` — Build & Check Scripts
| Script | Purpose |
| ----------------------------------- | -------------------------------------------------------------------------- |
| `run-next.mjs` | Dev/start runner with env hydration |
| `build-next-isolated.mjs` | Standalone build (Next.js 16 standalone) |
| `prepublish.ts` | Package preparation before `npm pack` |
| `postinstall.mjs` | Auto-create `.env` from `.env.example` on first install |
| `sync-env.mjs` | Re-sync `.env` keys with `.env.example` |
| `check-cycles.mjs` | Detect circular dependencies |
| `check-route-validation.mjs` | Validate all API routes have Zod validation |
| `check-t11-any-budget.mjs` | Enforce explicit `any` budget per file |
| `check-docs-sync.mjs` | Validate docs version sync (existing pre-commit) |
| **`check-env-doc-sync.mjs`** | NEW: cross-check env vars in code vs `.env.example` vs `ENVIRONMENT.md` |
| **`check-docs-counts-sync.mjs`** | NEW: validate counts (executors, strategies, OAuth, A2A skills) match docs |
| **`check-deprecated-versions.mjs`** | NEW: flag stale versions/dates in docs |
| `check-supported-node-runtime.ts` | Validate current Node version is supported |
| `check-pr-test-policy.mjs` | Enforce "tests required" rule on production code changes |
| **`gen-provider-reference.ts`** | NEW: auto-generate `docs/reference/PROVIDER_REFERENCE.md` from catalog |
| `i18n/generate-multilang.mjs` | Translate UI strings + docs via Google Translate |
| `i18n_autotranslate.py` | LLM-based doc translation pipeline |
| `validate_translation.py` | Per-locale translation validation |
| `check_translations.py` | Code-side i18n key check |
| `run-playwright-tests.mjs` | Playwright E2E runner |
| `run-protocol-clients-tests.mjs` | MCP/A2A E2E runner |
| `run-ecosystem-tests.mjs` | Ecosystem (provider integration) tests |
| `test-report-summary.mjs` | Generate coverage summary markdown |
| `smoke-electron-packaged.mjs` | Smoke-test packaged Electron build |
| `native-binary-compat.mjs` | Validate native deps (`better-sqlite3`) match Electron's Node |
| `validate-pack-artifact.ts` | Validate npm pack output |
| `responses-ws-proxy.mjs` | WebSocket bridge for Codex Responses API |
| `v1-ws-bridge.mjs` | WebSocket bridge for `/api/v1/ws` endpoint |
| `standalone-server-ws.mjs` | Standalone WS server runner |
| `system-info.mjs` | Print system/runtime info for support |
| `healthcheck.mjs` | One-shot health check (used by Docker HEALTHCHECK) |
| `uninstall.mjs` | Clean uninstall script |
---
## `docs/` — Public Documentation (44 files + 4 subdirs)
### Top-level guides
| Doc | Purpose |
| --------------------------- | ------------------------------------------------------------------------------------- |
| `ARCHITECTURE.md` | High-level architecture, subsystem map, dashboard surface |
| `CODEBASE_DOCUMENTATION.md` | Engineering reference: directories, modules, conventions |
| `FEATURES.md` | Feature matrix with v3.8 highlights |
| `USER_GUIDE.md` | End-user manual (setup, models, combos, CLIs, audio, etc.) |
| `API_REFERENCE.md` | API endpoint reference with auth model |
| `openapi.yaml` | OpenAPI 3.0 spec (121 paths) |
| `SETUP_GUIDE.md` | Install methods (npm, npx, Docker, Electron, Termux, source) |
| `ENVIRONMENT.md` | All env vars (~219 used in code, ~810 lines `.env.example`) |
| `TROUBLESHOOTING.md` | Common errors + v3.8.0 known issues |
| `RELEASE_CHECKLIST.md` | Full release flow (skills, husky, conventional commits, deploy) |
| `COVERAGE_PLAN.md` | Coverage goals and current state |
| `FREE_TIERS.md` | Curated free-tier providers (48+ free + 11 OAuth) |
| `CLI-TOOLS.md` | External CLI integrations + Internal OmniRoute CLI |
| `I18N.md` | i18n architecture, adding a language, 30 locales |
| `UNINSTALL.md` | Clean uninstall steps |
| `PROVIDER_REFERENCE.md` | **Auto-generated** catalog of 236 providers (regen: `npm run gen:provider-reference`) |
### Subsystem deep-dives
| Doc | Purpose |
| -------------------------- | ------------------------------------------------------------------- |
| `MCP-SERVER.md` | MCP server: 94 tools, 3 transports, 30 scopes, REST endpoints |
| `A2A-SERVER.md` | A2A v0.3: JSON-RPC, 5 skills, REST helpers, agent card |
| `AGENT_PROTOCOLS_GUIDE.md` | Unified guide: A2A vs ACP vs Cloud Agents |
| `CLOUD_AGENT.md` | Codex Cloud / Devin / Jules orchestration |
| `SKILLS.md` | Skills framework (built-in + marketplace + SkillsSH + sandbox) |
| `MEMORY.md` | Memory system (SQLite FTS5 + Qdrant) |
| `EVALS.md` | Eval framework (suites, runs, rubrics) |
| `GUARDRAILS.md` | PII masker, prompt injection, vision bridge |
| `COMPLIANCE.md` | Audit log, retention, noLog opt-out |
| `WEBHOOKS.md` | HMAC-signed webhook delivery |
| `REASONING_REPLAY.md` | Hybrid memory/SQLite cache for `reasoning_content` |
| `AUTHZ_GUIDE.md` | Authorization pipeline (`classify``policies``enforce`) |
| `RESILIENCE_GUIDE.md` | Circuit breaker + cooldown + model lockout |
| `STEALTH_GUIDE.md` | TLS fingerprinting (JA3/JA4), Claude Code CCH, MITM cert |
| `AUTO-COMBO.md` | Auto Combo engine (9-factor scoring, 4 mode packs, virtual factory) |
### Compression
| Doc | Purpose |
| ------------------------------- | ---------------------------------------- |
| `COMPRESSION_GUIDE.md` | Overview of compression modes + roadmap |
| `COMPRESSION_ENGINES.md` | Caveman + RTK engines, registry contract |
| `COMPRESSION_RULES_FORMAT.md` | Caveman rule pack JSON schema |
| `COMPRESSION_LANGUAGE_PACKS.md` | Per-language rule pack inventory |
| `RTK_COMPRESSION.md` | RTK declarative pipeline (49 filters) |
### Deployment
| Doc | Purpose |
| ---------------------------- | ----------------------------------------------------------------- |
| `DOCKER_GUIDE.md` | Docker build, profiles (base/cli/host/cliproxyapi), Redis sidecar |
| `VM_DEPLOYMENT_GUIDE.md` | Generic VM/VPS deployment (Ubuntu/Debian + nginx + systemd) |
| `FLY_IO_DEPLOYMENT_GUIDE.md` | Fly.io deployment (currently Chinese-only) |
| `TERMUX_GUIDE.md` | Android headless via Termux |
| `PWA_GUIDE.md` | Progressive Web App install + service worker |
| `ELECTRON_GUIDE.md` | Desktop app build + sign + distribute |
| `TUNNELS_GUIDE.md` | Cloudflared + ngrok + Tailscale Funnel |
| `PROXY_GUIDE.md` | 4-level outbound proxy + 1proxy marketplace |
### Subdirectories
| Subdir | Purpose |
| --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `docs/archive/` | Archived/historical docs (e.g., `RFC-AUTO-ASSESSMENT-DRAFT.md` — superseded by EVALS) |
| `docs/i18n/` | Localized doc translations (~42 locales) |
| `docs/screenshots/` | Image assets for guides |
| `_tasks/superpowers/` | Plans/specs from superpowers (`writing-plans`/`brainstorming`) + research — isolated, separately-versioned repo, gitignored by the main tree. See CLAUDE.md → "Planning & Research Artifacts". |
---
## `tests/` — Test Suites
| Subdir | Type | Runner |
| ---------------------- | --------------------------------------- | --------------------------------------- |
| `tests/unit/` | Unit tests (~500 files, fastest) | Node native test runner |
| `tests/integration/` | Multi-module + DB integration tests | Node native test runner (concurrency 1) |
| `tests/e2e/` | UI + workflow E2E | Playwright |
| `tests/protocols-e2e/` | MCP + A2A real-client E2E | Custom protocol clients |
| `tests/ecosystem/` | Provider integration (network-touching) | Node native test runner |
---
## `public/` — Static Assets
| Path | Purpose |
| ------------------- | ---------------------------------------------------------------- |
| `public/` (root) | Favicons, robots.txt, manifest, service worker, marketing images |
| `public/providers/` | Provider logo PNG/SVG (used in dashboard) |
---
## `config/` — Static Configs + Quality-Gate State
Shipped configuration templates plus the committed quality-gate baselines
(moved here from the repo root in v3.8.26 to keep the root lean).
| Path | Purpose |
| --------------------------------------------- | -------------------------------------------------------------------------------- |
| `config/i18n.json` | Locale list + metadata (canonical source for the 42-locale count) |
| `config/i18n-schema.json` | JSON schema validating `i18n.json` |
| `config/payloadRules.json` | Upstream payload sanitization rules |
| `config/quality/quality-baseline.json` | Multi-metric ratchet baseline (`scripts/quality/check-quality-ratchet.mjs`) |
| `config/quality/complexity-baseline.json` | Frozen ESLint-complexity baseline (`check-complexity.mjs`) |
| `config/quality/duplication-baseline.json` | Frozen jscpd duplication baseline (`check-duplication.mjs`) |
| `config/quality/file-size-baseline.json` | Frozen per-file size baseline (`check-file-size.mjs`) |
| `config/quality/test-discovery-baseline.json` | Frozen orphan-test baseline (`check-test-discovery.mjs`) |
| `config/quality/dependency-allowlist.json` | Approved dependencies allowlist (`check-deps.mjs`) |
| `config/quality/.license-allowlist.json` | SPDX license allowlist (`check-licenses.mjs`) |
| `config/quality/quality-metrics.json` | Ephemeral collected metrics (generated by `collect-metrics.mjs`; **gitignored**) |
---
## `.github/` — GitHub Integration
| Path | Purpose |
| ---------------------------------- | -------------------------------------------------------------- |
| `.github/workflows/` | GitHub Actions CI/CD workflows (lint, test, coverage, release) |
| `.github/ISSUE_TEMPLATE/` | Bug/feature issue templates |
| `.github/PULL_REQUEST_TEMPLATE.md` | PR template |
| `.github/dependabot.yml` | Dependency update config |
---
## `.husky/` — Git Hooks
| File | Purpose |
| ------------ | ----------------------------------------------------------------- |
| `pre-commit` | Runs `lint-staged + check-docs-sync + check:any-budget:t11` |
| `pre-push` | Currently disabled (commented). Run `npm run test:unit` manually. |
| `_/` | Husky internals |
---
## `.claude/` — Claude Code Slash Commands
| File | Purpose |
| --------------------------------------------------- | -------------------------------------------------- |
| `commands/version-bump-cc.md` | `/version-bump-cc` — bump version + auto-changelog |
| `commands/generate-release-cc.md` | `/generate-release-cc` — full release workflow |
| `commands/deploy-vps-{local,akamai,both}-cc.md` | Deploy to VPS |
| `commands/capture-release-evidences-cc.md` | Browser-record new features as WebP |
| `commands/review-{prs,discussions}-cc.md` | Triage GitHub PRs/discussions |
| `commands/{review-issues,implement-features}-cc.md` | Issue workflows |
| `settings.local.json` | Per-project Claude Code settings |
---
## `.agents/` — Generic Agent Workflows (Codex / Cursor / etc.)
| Path | Purpose |
| ------------------------ | ------------------------------------------------------- |
| `workflows/*-ag.md` | 11 workflow definitions (mirror of `.claude/commands/`) |
| `skills/<name>/SKILL.md` | 9 skill definitions with Codex Execution Notes |
> **Note:** Workflows and commands are currently identical byte-by-byte. If `.agents/` is meant to target a different agent runtime (Codex), the variants need to diverge meaningfully.
---
## `_ideia/`, `_mono_repo/`, `_references/`, `_tasks/` — Out-of-tree
These underscore-prefixed directories hold non-shipping content:
- **`_ideia/`** — design notes (defer / notfit / viable categories)
- **`_mono_repo/`** — historic subprojects (omnirouteCloud, omnirouteSite, vscode-extension)
- **`_references/`** — read-only clones of related OSS projects (LiteLLM, 9router, ClawRouter, CLIProxyAPI, modelrelay, new-api, etc.) for cross-reference during development
- **`_tasks/`** — per-release task tracking files (informal)
Not included in `npm pack` output. See `.npmignore`.
---
## Generated / Gitignored
| Path | Purpose |
| ---------------------- | ----------------------------- |
| `node_modules/` | npm dependencies |
| `.next/` | Next.js build output |
| `coverage/` | c8 coverage reports |
| `logs/` | Runtime logs |
| `package/` | npm pack staging |
| `.playwright-mcp/` | Playwright MCP test artifacts |
| `.issues/` | Local issue cache |
| `tsconfig.tsbuildinfo` | TS incremental cache |
---
## Navigation tips
- **New contributor?** Read `CONTRIBUTING.md``CLAUDE.md``docs/architecture/ARCHITECTURE.md``docs/architecture/CODEBASE_DOCUMENTATION.md`.
- **Adding a provider?** Follow `docs/architecture/ARCHITECTURE.md § Adding a New Provider` + cross-check `docs/reference/PROVIDER_REFERENCE.md`.
- **Adding a route?** `docs/architecture/ARCHITECTURE.md § Adding a New API Route` + `src/shared/validation/schemas.ts`.
- **Adding an MCP tool?** `docs/frameworks/MCP-SERVER.md § Adding a Tool`.
- **Adding an A2A skill?** `docs/frameworks/A2A-SERVER.md § Adding a New Skill`.
- **Running locally?** `docs/guides/SETUP_GUIDE.md`.
- **Deploying?** `docs/guides/DOCKER_GUIDE.md` / `docs/ops/VM_DEPLOYMENT_GUIDE.md` / `docs/ops/FLY_IO_DEPLOYMENT_GUIDE.md`.
- **Releasing?** `docs/ops/RELEASE_CHECKLIST.md` (and `/generate-release-cc` Claude Code skill).
+248
View File
@@ -0,0 +1,248 @@
---
title: "Resilience Guide"
version: 3.8.40
lastUpdated: 2026-06-28
---
# Resilience Guide
OmniRoute has three distinct but related resilience mechanisms. Each has a different scope and purpose. Keep them separate when debugging routing behavior.
![3-layer resilience model](../diagrams/exported/resilience-3layers.svg)
> Source: [diagrams/resilience-3layers.mmd](../diagrams/resilience-3layers.mmd)
## 1. Provider Circuit Breaker
**Scope:** entire provider (e.g., `glm`, `openai`, `anthropic`).
**Purpose:** stop sending traffic to a provider that is repeatedly failing at the upstream/service level.
**Implementation:**
- Core class: `src/shared/utils/circuitBreaker.ts`
- Wiring: `src/sse/handlers/chatHelpers.ts`, `src/sse/handlers/chat.ts`
- Status API: `GET /api/monitoring/health`
- Reset API: `POST /api/resilience/reset`
- Wrappers: `open-sse/services/accountFallback.ts`
- DB table: `domain_circuit_breakers`
**States:**
- `CLOSED` — normal traffic allowed
- `DEGRADED` — traffic still allowed, but elevated provider failures are being tracked
- `OPEN` — provider temporarily blocked; combo routing skips it
- `HALF_OPEN` — reset timeout elapsed; probe request allowed
**Configurable defaults (`open-sse/config/constants.ts`, exposed in Dashboard → Settings → Resilience):**
| Class | Degraded at | Opens at | Reset timeout |
| ------- | ----------- | ----------- | ------------- |
| OAuth | 5 failures | 8 failures | 60s |
| API-key | 7 failures | 12 failures | 30s |
| Local | derived | 2 failures | 15s |
`degradationThreshold` controls when a provider enters `DEGRADED`; `failureThreshold` controls when it opens and is skipped. Local provider profiles are not exposed on the Resilience settings page yet.
**Trip codes:** only provider-level statuses `[408, 500, 502, 503, 504]`. Do NOT trip for account-level errors (most 401/403/429 — those belong to cooldown or lockout).
**Lazy recovery:** when `OPEN` expires, `getStatus()`, `canExecute()`, `getRetryAfterMs()` refresh state to `HALF_OPEN`. No background timer needed.
---
## 2. Connection Cooldown
**Scope:** single provider connection/account/key.
**Purpose:** skip one bad key while other connections for the same provider keep serving.
**Implementation:**
- Mark unavailable: `src/sse/services/auth.ts::markAccountUnavailable()`
- Selection: `getProviderCredentials*` in same file
- Cooldown calc: `open-sse/services/accountFallback.ts::checkFallbackError()`
- Settings: `src/lib/resilience/settings.ts`
**Fields per connection:**
- `rateLimitedUntil` — timestamp until cooldown expires
- `testStatus: "unavailable"`
- `lastError`, `lastErrorType`, `errorCode`
- `backoffLevel` — exponential backoff counter
**Default cooldowns:**
- OAuth base: 5s
- API-key base: 3s
- API-key 429: prefers upstream `Retry-After`/reset headers/parseable reset text
- Backoff: `baseCooldownMs * 2 ** failureIndex`
**Anti-thundering-herd guard:** prevents concurrent failures from over-extending cooldown or double-incrementing `backoffLevel`.
**Terminal states (NOT cooldowns):**
- `banned` — set by banned-keyword / account-ban detection (see [BAN_DETECTION](../security/BAN_DETECTION.md))
- `expired`
- `credits_exhausted`
These persist until credentials change or an operator resets them. Do not overwrite terminal states with transient cooldown state.
**Lazy recovery:** when `rateLimitedUntil` is past, connection becomes eligible again. On successful use, `clearAccountError()` clears all error fields.
---
## 3. Model Lockout
**Scope:** provider + connection + model triple.
**Purpose:** avoid disabling a whole connection when only one model is unavailable or quota-limited.
**Examples:**
- Per-model quota providers returning 429
- Local providers returning 404 for one missing model
- Provider-specific mode/model permission failures (e.g., Grok modes)
**Implementation:** `open-sse/services/accountFallback.ts``lockModel()`, `clearModelLock()`, `getAllModelLockouts()`.
### Model Cooldowns Dashboard (v3.8.0)
UI: Settings → Model Cooldowns (`src/app/(dashboard)/dashboard/settings/components/ModelCooldownsCard.tsx`)
Lists active lockouts with: provider, connection, model, reason, expiresAt. Operators can manually re-enable a model from the card.
**REST API:**
- `GET /api/resilience/model-cooldowns` — list active lockouts
- `DELETE /api/resilience/model-cooldowns` — manual re-enable. Body: `{provider, connection, model}`. Auth: management.
### Lockout settings UI + success-decay recovery (v3.8.23)
Model lockout went from always-on hardcoded behavior to a fully configurable,
opt-in feature with its own settings card and a self-healing recovery path.
**Settings card:** Settings → Model Lockout
(`src/app/(dashboard)/dashboard/settings/components/ModelLockoutCard.tsx`).
This is **distinct** from the read-only `ModelCooldownsCard` above (which only
_lists_ active lockouts) — the new card _configures the parameters_. Defaults
live in `DEFAULT_MODEL_LOCKOUT_SETTINGS`
(`src/lib/resilience/modelLockoutSettings.ts`):
| Setting | Default | Meaning |
| ----------------------- | -------------------------------- | -------------------------------------------------------------- |
| `enabled` | `false` | Master toggle — model lockout is **off by default**. |
| `errorCodes` | `[403, 404, 429, 502, 503, 504]` | Upstream statuses that count as a model-scoped failure. |
| `baseCooldownMs` | `120_000` (120 s) | Initial lockout duration for the first failure. |
| `maxCooldownMs` | `1_800_000` (30 min) | Cap on the escalated cooldown. |
| `maxBackoffSteps` | `10` | Max exponential-backoff escalation steps. |
| `useExponentialBackoff` | `true` | Whether repeated failures escalate the cooldown exponentially. |
Settings persist through the normal settings store and validate via the
resilience settings schema; the card clamps `baseCooldownMs`/`maxCooldownMs`
(with `maxCooldownMs ≥ baseCooldownMs`) and `maxBackoffSteps`.
**Success-decay recovery:** recovery is **not** purely timer expiry. A healthy
response walks the model's failure count back down so a model that recovered
mid-window stops escalating (and clears) before its timer would. On a successful
combo target, `open-sse/services/combo.ts` calls `decayModelFailureCount()`
(`open-sse/services/accountFallback.ts`), which **halves** the stored
`failureCount` (`Math.floor(failureCount / 2)`); when it reaches `0` the lockout
entry is deleted entirely. The counterpart `recordModelLockoutFailure()`
increments the count (and escalates the cooldown) on failures within the
escalation window. This success-decay is in addition to plain timer expiry —
either path can re-enable a model.
**State:** lockouts are held **in-memory** (per-process `Map`s of
`ModelLockoutEntry` keyed by `provider:connectionId:model`), not persisted to
the DB — they are lost on restart. The _settings_ are persisted; the active
lockout _state_ is ephemeral.
---
## 4. Quota-Share Concurrency Control (v3.8.36)
Subscription accounts (GLM, MiniMax, etc.) often accept only ~13 concurrent
requests; exceeding that triggers 429s and cooldowns. This is acute under
**quota-share** (`qtSd/…`) combos, where several API keys share one upstream
account. Three layers keep a shared account from being flooded.
### Per-connection concurrency cap (`max_concurrent`)
Each provider connection can declare a `max_concurrent` ceiling
(`provider_connections.max_concurrent`, set in the connection modal / API / DB).
Leave it empty for no limit. This is the single knob that drives the serialization
layer below — set it to the account's real concurrency (e.g. GLM ~1, MiniMax ~2).
### Quota-share request serialization
When a quota-share dispatch targets a connection that declares a positive
`max_concurrent`, concurrent requests to that **account** are serialized through a
per-connection semaphore (key `qsconn:<connectionId>`): excess requests **wait in
the queue** instead of flooding the account. It is **fail-open** — a saturated
queue or timeout proceeds without a slot rather than ever rejecting a dispatchable
request. Toggle in **Settings → Resilience → Quota-share per-connection
concurrency** (`resilienceSettings.quotaShareConcurrencyLimit.enabled`, default
on). Without a `max_concurrent` cap the behavior is unchanged.
> The quota-share routing gate (`selectQuotaShareTarget`, DRR + P2C) is itself
> fail-open and only _deprioritizes_ an at-cap connection — with a
> single-connection pool it cannot hard-limit, so this semaphore is what actually
> contains the flood.
### Combo cooldown-aware retry
For quota-share combos only, a request that would crystallize a 429 for a SHORT
transient cooldown waits it out and re-dispatches instead of returning the 429.
Bounded by `comboCooldownWait` (`enabled`, `maxWaitMs` 5s, `maxAttempts` 2,
`budgetMs` 8s) in **Settings → Resilience**. It never waits on `quota_exhausted`
(locked until midnight) or auth/not-found reasons.
---
## Other Resilience Features
- **18 routing strategies** (priority, weighted, round-robin, context-relay, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, fusion, pipeline) — see [AUTO-COMBO.md](../routing/AUTO-COMBO.md).
- **Reset-aware routing** (v3.8.0) — prioritizes connections by quota reset time.
- **Background mode degradation** — Responses API `background: true` degraded to sync with warning.
- **Dynamic tool limit detection** — backs off providers when tool count limits hit.
- **Emergency fallback** — controlled by `OMNIROUTE_EMERGENCY_FALLBACK`; operators can override it from the Feature Flags page without a restart.
---
## Debugging
- All keys for a provider skipped → check both circuit breaker state AND each connection's `rateLimitedUntil`/`testStatus`.
- Provider permanently excluded after reset window → code reading raw `state` instead of `getStatus()`/`canExecute()`.
- One key fails, others should work → prefer connection cooldown over circuit breaker.
- Only one model fails → prefer model lockout over connection cooldown.
- State should self-recover but doesn't → check for future timestamp + read path that refreshes expired state. Permanent statuses require manual changes.
---
## TLS Fingerprinting & Stealth
Provider-specific stealth (JA3/JA4, CCH, obfuscation) is separately documented — see [STEALTH_GUIDE.md](../security/STEALTH_GUIDE.md).
---
## Resilience testing (Phase 8 · Block C)
Beyond unit tests for resilience logic, three tests exercise the runtime under
real stress/failure conditions (all integration/nightly — none block PRs):
| Test | What | Run |
| ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------- |
| Chaos | Fake-upstream node injects real latency/reset/timeout/503; validates that the circuit breaker opens/recovers and `checkFallbackError` classifies 503 as recoverable fallback. | `RUN_CHAOS_INT=1 npm run test:chaos` |
| Heap-growth | ~500 streams per `createSSEStream` under `--expose-gc`; fails if the heap grows beyond the ceiling (OOM guard #3069). | `npm run test:heap` |
| k6 soak | Sustained load against `/api/monitoring/health`; p95/error thresholds. | `k6 run tests/load/k6-soak.js` (nightly) |
Orchestrated by `.github/workflows/nightly-resilience.yml` (cron + dispatch). In the
default `test:integration`, chaos and heap self-skip (without `RUN_CHAOS_INT`/`--expose-gc`).
---
## See Also
- [Architecture Guide](./ARCHITECTURE.md) — System architecture and internals
- [User Guide](../guides/USER_GUIDE.md) — Providers, combos, CLI integration
- [Auto-Combo Engine](../routing/AUTO-COMBO.md) — 12-factor scoring, mode packs
+149
View File
@@ -0,0 +1,149 @@
---
title: "Router Backends & Embedded Services (ADR)"
version: 3.8.43
lastUpdated: 2026-07-02
---
# Router Backends & Embedded Services — architecture contract (ADR)
> **Status:** Accepted · **Context:** [#5670](https://github.com/diegosouzapw/OmniRoute/issues/5670),
> [#5603](https://github.com/diegosouzapw/OmniRoute/issues/5603) · **Contract:** `domain/routing/routerBackends.ts`
> (typed registry — code lands with [#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868))
This ADR pins down how `ts` (native), `bifrost`, `cliproxy`, `9router`, and
VibeProxy-compatible engines relate to each other, so contributors stop
conflating two things that are architecturally distinct. It documents the typed
registry introduced by the router-backend-registry work as the single source of
truth for that model.
## The core distinction — two orthogonal axes
An engine's role is described by **two independent axes**, encoded together in the
registry's `RouterBackendDefinition`:
1. **Lifecycle** (`RouterBackendLifecycle`) — _how the engine runs_:
- `in-process` — runs inside the OmniRoute Node process (the native TS pipeline).
- `supervised` — a local child process OmniRoute installs/starts/stops/health-checks
via `ServiceSupervisor`, then consumes as a provider connection.
- `external` — an HTTP endpoint OmniRoute dispatches to but does **not** manage
(configured by an env base URL).
- `disabled` — registered but not selectable.
2. **Selection axis** (relay routing backend) — _whether the relay dispatches to it_:
`RelayRoutingBackend = "ts" | "bifrost" | "auto"` in
`src/app/api/v1/relay/chat/completions/routingBackend.ts`.
The mistake to avoid: treating "embedded service" and "routing backend" as one
list. They are not. A `supervised` engine (9router/cliproxy) is a **provider
connection consumed by the native pipeline**, not an alternate relay dispatch
backend. `bifrost` is the reverse — a relay dispatch backend that (historically)
was `external`-only.
## The registry — single source of truth
The `domain/routing/routerBackends.ts` contract (code lands with
[#5868](https://github.com/diegosouzapw/OmniRoute/pull/5868)) declares every engine once, with its
lifecycle, capabilities, service identity, default port, health config, and
telemetry support. Consumers look engines up via `getRouterBackend(id)`,
`listRouterBackends()`, and `listRouterBackendsByCapability(cap)` instead of
special-casing each sidecar.
| Backend | Lifecycle | Service (axis A) | Relay backend (axis B) | Health | Default port |
| ----------- | ------------ | ---------------- | ---------------------- | ------------- | ------------ |
| `ts` | `in-process` | — | `ts` (native) | — | — |
| `bifrost` | `external`¹ | —¹ | `bifrost` / `auto` | `/health` | — |
| `cliproxy` | `supervised` | `cliproxy` | — (provider) | `/v1/models` | 8317 |
| `9router` | `supervised` | `9router` | — (provider) | `/api/health` | 20130 |
| `vibeproxy` | `external` | — | — (provider adapter) | `/v1/models` | — |
¹ Bifrost's promotion to a `supervised` embedded service (installable/startable
from `/api/services/bifrost/`) is tracked in
[#5817](https://github.com/diegosouzapw/OmniRoute/pull/5817); until it merges,
Bifrost is `external`-only (reachable solely via `BIFROST_BASE_URL`).
`capabilities` (`chat`, `responses`, `streaming`, `tools`, `vision`,
`oauth-backed`, `dashboard-embed`, `model-sync`, `native-hot-path`) let callers
filter by what an engine can actually do rather than hard-coding per-id branches.
## Axis A — embedded services (supervised process side)
- **Registry of supervised processes:** `src/lib/services/bootstrap.ts` `SERVICES[]`
(today: `9router`, `cliproxy`).
- **Lifecycle owner:** `src/lib/services/ServiceSupervisor.ts``start()` spawns the
child, gates on `waitForHealthy()`, taps stdout/stderr into a ring buffer;
`stop()` SIGTERM→SIGKILL; all serialized under a lock.
- **State union** (`src/lib/services/types.ts`):
`not_installed | stopped | starting | running | stopping | error`, plus an
orthogonal `HealthState = healthy | unhealthy | unknown`.
- **Why a separate process (not an in-proc SDK)?** Process isolation is what makes
install/start/stop/health/logs independently controllable per sidecar and lets the
loopback spawn-guard apply. Modeling an in-proc adapter is future work — the
`native-hot-path` capability flag is where that would be expressed.
### Lifecycle route contract (`/api/services/<tool>/…`)
Status codes are **state/verb/path-specific by design** — this is the contract, not
inconsistency:
| Call | Condition | Status |
| ---------------------------- | ------------------------------- | ------------------------------------ |
| `POST .../start` | service `not_installed` | **409** (precondition) |
| `POST .../stop` | already stopped | **200** (idempotent no-op) |
| `GET .../status` | OK | **200** (`live ?? row ?? "unknown"`) |
| `POST .../start` | spawn failure | **503** (transient) |
| `GET .../status`, `.../stop` | uncaught error | **500** |
| `GET /api/services/<x>/logs` | unknown tool `<x>` | **404** `Service '<x>' not found` |
| `GET .../status?reveal=key` | missing `X-Reveal-Confirm: yes` | **403** (9router only) |
| **any** `/api/services/*` | caller not loopback/private-LAN | **403 LOCAL_ONLY** |
All error bodies are shaped by `createErrorResponse()`
`{ error: { message, type }, requestId }`, where `type` is derived from the status
(`500→server_error`, `404→not_found`, `409→conflict`, else `invalid_request`) and is
the machine-actionable discriminator. Messages are pre-sanitized
(`sanitizeErrorMessage()`, Hard Rule #12).
**The loopback guard** is the most common source of a `403`: `/api/services/` is in
`LOCAL_ONLY_API_PREFIXES` (`src/server/authz/routeGuard.ts`) and
`src/server/authz/policies/management.ts` rejects any non-loopback / non-private-LAN
caller **before auth**, because these routes spawn child processes (Hard Rules 15
and 17). Reaching them through a public tunnel is `403` by design.
## Axis B — relay routing backend (dispatch side)
Only the relay proxy path `/api/v1/relay/chat/completions` selects a dispatch
backend; the main `/api/v1/chat/completions` surface never consults
`routingBackend.ts`.
- **Selection** (`resolveRelayRoutingBackend`): a single global env toggle —
`OMNIROUTE_RELAY_BACKEND` / `RELAY_ROUTING_BACKEND` ∈ {`ts`, `bifrost`, `auto`}.
If unset, `auto` when Bifrost is configured+enabled, else `ts`.
- **Behavior:**
- `bifrost` (forced): Bifrost failure → hard `502`, no fallback.
- `auto`: try Bifrost, on failure/cooldown silently fall through to native.
- `ts` / post-fallback: the native `open-sse` translator/executor pipeline.
- **Cooldown:** per-`baseUrl` failure cooldown in `bifrostCooldown.ts`.
Selection is **all-or-nothing at the relay level today** — there is no per-provider
or per-request engine swap on `release/v3.8.43`. The per-request gate is being added
by the sidecar-manifest work
([#5869](https://github.com/diegosouzapw/OmniRoute/pull/5869) manifest +
[#5870](https://github.com/diegosouzapw/OmniRoute/pull/5870) `shouldTryBifrostForRequest`),
which lets `auto` route only manifest-eligible providers through Bifrost.
## Dashboard integration
The services dashboard polls `GET /api/services/<tool>/status` every 5s via
`src/app/(dashboard)/dashboard/providers/services/hooks/useServiceStatus.ts`,
returning `{ tool, state, pid, port, health, installedVersion, latestVersion,
updateAvailable, autoStart, … }`. There is no shared availability-context provider —
each component calls the hook per tool. On `!res.ok` the hook currently surfaces a
bare `HTTP <status>`; mapping the `error.type` field to a human explanation is a
tracked UX improvement, not a contract change.
## Consequences
- New engines register once in `ROUTER_BACKENDS`; consumers gain them via capability
queries without new per-id branches.
- "Is this a service or a routing backend?" is answered by the `lifecycle` field, not
by which list an id happens to appear in.
- The Bifrost supervision (#5817) and native hot-path migration (#5670) build on this
shared contract instead of special-casing each sidecar.
+101
View File
@@ -0,0 +1,101 @@
---
title: "Cluster Decisions"
version: 3.8.40
lastUpdated: 2026-06-28
---
# Cluster Decisions — Optional Sidecar Profiles
**Status:** proposal (awaiting @diegosouzapw review)
**Date:** 2026-06-20
**Refs:** [#3932](https://github.com/diegosouzapw/OmniRoute/issues/3932), PR #4381
## TL;DR
Two opt-in compose profiles (`memory`, `bifrost`) for the existing 8-service deployment in [`docker-compose.yml`](../../docker-compose.yml). Default-up behaviour is **unchanged**: 3 × `omniroute` replicas + Caddy + Redis + CliproxyAPI. The two new profiles add Qdrant and Bifrost as optional sidecars, gated by `docker compose --profile <name> up`. **No existing service is removed or replaced.**
## Why this is conservative
OmniRoute's existing deployment shape is already lean and proven:
- **`redis:7-alpine`** handles the rate-limit/cache workload at production scale.
- **SQLite + sqlite-vec + FTS5** cover local memory + vector + text-search (see [`src/lib/memory/vectorStore.ts:108`](../../src/lib/memory/vectorStore.ts)).
- **Caddy** is already the LB + TLS terminator ([`docker-compose.yml`](../../docker-compose.yml)).
- **Bifrost** is already integrated as the Tier-1 router in [`src/app/api/v1/relay/chat/completions/bifrost/route.ts`](../../src/app/api/v1/relay/chat/completions/bifrost/route.ts) (sidecar proxy with kill switch via `BIFROST_ENABLED` env var — set `=0` to bypass the sidecar and fall through to the TS path).
The two profiles here are **scale-out options for deployments that hit the SQLite ceiling** — not migrations. Both are default-off.
## The two profiles
### `memory` — Qdrant Vector Memory Sidecar
**When to flip on:**
- > 1M embeddings per deployment (sqlite-vec starts to slow at scale).
- Multi-replica deployment that needs shared vector state across `omniroute-1/2/3`.
- You already have an external Qdrant cluster (Qdrant Cloud, on-prem).
**What it adds:**
| Service | Image | Ports | Notes |
| -------- | ----------------------- | ----------- | ----------------------------------------------------- |
| `qdrant` | `qdrant/qdrant:v1.12.4` | `6333` HTTP | HNSW index; persistent volume `omniroute_qdrant_data` |
**Activation:** flip `qdrantEnabled = true` in the Settings UI **or** set `QDRANT_HOST=qdrant` env. See [`src/lib/memory/qdrant.ts:60`](../../src/lib/memory/qdrant.ts) for the precedence rules (settings table → env var → default).
**Env vars:** `QDRANT_HOST`, `QDRANT_PORT`, `QDRANT_API_KEY`, `QDRANT_COLLECTION`, `QDRANT_VECTOR_SIZE`, `QDRANT_HNSW_EF_CONSTRUCT` (see `.env.example` lines 1672-1683).
### `bifrost` — Bifrost Tier-1 Router Sidecar
**When to flip on:**
- You run ≥3 `omniroute` replicas and want provider rotation centralised in a single Go process.
- You want a single audit/logging surface for upstream-provider requests across all replicas.
- You want horizontal scaling of the Tier-1 routing layer independent of the OmniRoute replicas.
**What it adds:**
| Service | Image | Ports | Notes |
| --------- | -------------------------------- | ------ | ----------------------------------------------------------------------- |
| `bifrost` | `ghcr.io/maximhq/bifrost:1.5.21` | `8080` | Go-based Tier-1 router; persistent logs volume `omniroute_bifrost_logs` |
**Activation:** set `BIFROST_BASE_URL=http://bifrost:8080` in `.env.example`. The existing sidecar proxy route at [`src/app/api/v1/relay/chat/completions/bifrost/route.ts`](../../src/app/api/v1/relay/chat/completions/bifrost/route.ts) (added in PR #4381) will pick this up automatically.
**Env vars:** `BIFROST_BASE_URL`, `BIFROST_API_KEY`, `BIFROST_STREAMING_ENABLED`, `BIFROST_TIMEOUT_MS` (see `.env.example` lines 1685-1695).
## What this PR explicitly does NOT do
The original issue thread floated a larger cluster rewrite. After auditing the actual workload shape, the following are **rejected** for the reasons given:
| Component | Verdict | Reason |
| ------------------------------------ | -------- | ------------------------------------------------------------------------------------------------------ |
| **Dragonfly** | **DROP** | `redis:7-alpine` is already fine for the rate-limit workload at production scale; no ceiling to break. |
| **NATS** | **DROP** | Each `omniroute` replica is a single Node.js process; no multi-process pub/sub workload exists. |
| **PostgreSQL** | **DROP** | SQLite + sqlite-vec + FTS5 cover all 3 use cases; 97 migrations + Electron packaging block migration. |
| **Neo4j** | **DROP** | Routing is a 5-table join; recursive CTE on SQLite is sufficient. |
| **MinIO** | **DROP** | No multi-MB blob workload; images/audio are passthrough proxies. |
| **pgvector / pg_ai / pg_textsearch** | **DROP** | Same SQLite-ceiling reason as PostgreSQL; pgvector ecosystem fragmented. |
| **HAProxy / Envoy** | **DROP** | Caddy already does LB + TLS; both were explicitly rejected as Tier-1 routers (see `AGENTS.md`). |
If a future use case proves out one of these, this doc is the place to amend.
## 4-week rollout (if approved)
1. **Wk 1** — Land this PR + verification of opt-in profiles with a 3-replica compose stack.
2. **Wk 2** — Bifrost full activation for OpenAI/Claude/Gemini/Ollama (4 of 14+ providers) using the sidecar proxy route at [`src/app/api/v1/relay/chat/completions/bifrost/route.ts`](../../src/app/api/v1/relay/chat/completions/bifrost/route.ts) (gated by `BIFROST_ENABLED`, kill-switchable at runtime).
3. **Wk 3** — Qdrant memory profile enabled in a single test deployment; measure latency delta vs sqlite-vec.
4. **Wk 4** — Observability healthchecks (`docker compose ps` exit codes + `wget` smoke tests); 71-pillar refresh per ADR-041.
## Files changed in this PR
| File | Change |
| ---------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `docker-compose.yml` | +30 lines: `memory` profile (Qdrant), `bifrost` profile (Bifrost), persistent volumes, healthchecks. |
| `.env.example` | +24 lines: `QDRANT_*` (6 vars), `BIFROST_*` (4 vars). |
| `docs/reference/ENVIRONMENT.md` | +6 rows in section 25 for the `QDRANT_*` env vars. |
| `src/lib/memory/qdrant.ts` | +33 lines: env-var fallback chain (settings → env → default) for `QDRANT_HOST`/`QDRANT_PORT`/`QDRANT_API_KEY`/`QDRANT_COLLECTION`/`QDRANT_VECTOR_SIZE`/`QDRANT_HNSW_EF_CONSTRUCT`/`QDRANT_EMBEDDING_MODEL`. |
| `src/lib/memory/__tests__/qdrant-wiring.test.ts` | +88 lines: 9 new test cases pinning the env-var fallback precedence. |
| `docs/architecture/cluster-decisions.md` (this file) | NEW — decision record for the opt-in profiles. |
| `AGENTS.md` | +1 line: pointer to this doc in the reference documentation table. |
**Net touched code:** 4 production files (`docker-compose.yml`, `qdrant.ts`, `.env.example`, `ENVIRONMENT.md`), 1 test file (`qdrant-wiring.test.ts`), 2 doc files (`cluster-decisions.md`, `AGENTS.md`).
+11
View File
@@ -0,0 +1,11 @@
{
"title": "Architecture",
"pages": [
"ARCHITECTURE",
"AUTHZ_GUIDE",
"CODEBASE_DOCUMENTATION",
"REPOSITORY_MAP",
"RESILIENCE_GUIDE",
"QUALITY_GATES"
]
}