diff --git a/docs/diagrams/README.md b/docs/diagrams/README.md new file mode 100644 index 0000000000..9912a4ddd4 --- /dev/null +++ b/docs/diagrams/README.md @@ -0,0 +1,48 @@ +# Diagrams + +Mermaid sources (`.mmd`) and exported SVGs for OmniRoute v3.8.0 architecture flows. + +## Canonical diagrams + +| Source | Exported | Used in | +| -------------------------------------------------- | ---------------------------------------- | ---------------------------------------------------- | +| [request-pipeline.mmd](./request-pipeline.mmd) | [SVG](./exported/request-pipeline.svg) | docs/ARCHITECTURE.md, docs/CODEBASE_DOCUMENTATION.md | +| [auto-combo-9factor.mmd](./auto-combo-9factor.mmd) | [SVG](./exported/auto-combo-9factor.svg) | docs/AUTO-COMBO.md | +| [resilience-3layers.mmd](./resilience-3layers.mmd) | [SVG](./exported/resilience-3layers.svg) | docs/RESILIENCE_GUIDE.md, CLAUDE.md | +| [i18n-flow.mmd](./i18n-flow.mmd) | [SVG](./exported/i18n-flow.svg) | docs/I18N.md | +| [mcp-tools-37.mmd](./mcp-tools-37.mmd) | [SVG](./exported/mcp-tools-37.svg) | docs/MCP-SERVER.md | +| [cloud-agent-flow.mmd](./cloud-agent-flow.mmd) | [SVG](./exported/cloud-agent-flow.svg) | docs/CLOUD_AGENT.md | +| [authz-pipeline.mmd](./authz-pipeline.mmd) | [SVG](./exported/authz-pipeline.svg) | docs/AUTHZ_GUIDE.md | +| [db-schema-overview.mmd](./db-schema-overview.mmd) | [SVG](./exported/db-schema-overview.svg) | docs/CODEBASE_DOCUMENTATION.md | + +## How to update + +1. Edit `*.mmd`. +2. Re-render: `npm run docs:render-diagrams` (uses `@mermaid-js/mermaid-cli`). +3. Commit both `.mmd` and `.svg`. + +If `@mermaid-js/mermaid-cli` is not available locally, install it once: + +```bash +npm install -g @mermaid-js/mermaid-cli +``` + +The script renders every `.mmd` in `docs/diagrams/` into `docs/diagrams/exported/*.svg` +with a white background, suitable for both dark and light themes. + +## Linking from a doc + +```markdown +![Request pipeline](./diagrams/exported/request-pipeline.svg) + +> Source: [diagrams/request-pipeline.mmd](./diagrams/request-pipeline.mmd) +``` + +## Conventions + +- One concept per diagram. Don't try to fit the whole platform in one chart. +- Keep node labels short (3–6 words). Use `
` for line breaks inside nodes. +- Prefer `flowchart LR` for pipelines and `flowchart TB` for layered models. +- Use `sequenceDiagram` for interactive (request/response) flows. +- Use `erDiagram` for database schema overviews. +- Update both `.mmd` and `.svg` in the same commit. Keep them in lock-step. diff --git a/docs/diagrams/authz-pipeline.mmd b/docs/diagrams/authz-pipeline.mmd new file mode 100644 index 0000000000..78b9b5b09c --- /dev/null +++ b/docs/diagrams/authz-pipeline.mmd @@ -0,0 +1,23 @@ +%% AuthZ pipeline (3 route classes + policy evaluation) +%% Reflects: src/middleware.ts, src/server/authz/pipeline.ts, +%% src/server/authz/policies/{public,clientApi,management}.ts +%% v3.8.0 +flowchart LR + Req["Incoming request"] --> Strip["Strip trusted internal headers
(x-omniroute-auth-*)"] + Strip --> Classify["classifyRoute()"] + Classify -->|PUBLIC| PubP["publicPolicy
(login, health, status)"] + Classify -->|CLIENT_API| CliP["clientApiPolicy
(/api/v1/*, /v1/*)"] + Classify -->|MANAGEMENT| MgmtP["managementPolicy
(dashboard, settings, admin)"] + + PubP -->|allow| Stamp["Stamp x-omniroute-auth-*
(kind / id / label / scopes)"] + CliP -->|extract Bearer| Validate["validateApiKey()"] + Validate -->|valid| Stamp + Validate -->|"invalid (REQUIRE_API_KEY=true)"| Reject401["401 AUTH_002"] + Validate -->|"absent (REQUIRE_API_KEY=false)"| Anon["anonymous fallthrough"] --> Stamp + + MgmtP -->|session ok| Stamp + MgmtP -->|Bearer w/ manage scope| Stamp + MgmtP -->|"Bearer invalid"| Reject403["403 AUTH_001"] + MgmtP -->|no auth| Reject401Mgmt["401 / 302 /login"] + + Stamp --> Handler["Handler
(uses assertAuth)"] diff --git a/docs/diagrams/auto-combo-9factor.mmd b/docs/diagrams/auto-combo-9factor.mmd new file mode 100644 index 0000000000..7b604b43d3 --- /dev/null +++ b/docs/diagrams/auto-combo-9factor.mmd @@ -0,0 +1,23 @@ +%% Auto-Combo 9-factor scoring +%% Reflects: open-sse/services/autoCombo/scoring.ts (DEFAULT_WEIGHTS, sum = 1.0) +%% v3.8.0 +flowchart TB + Request["Incoming request"] --> Candidates["Eligible candidates
(provider × model × account)"] + Candidates --> Score["Compute composite score
per candidate"] + + subgraph Factors["9-factor scoring weights (sum = 1.0)"] + f1["health (0.22)"] + f2["quota (0.17)"] + f3["costInv (0.17)"] + f4["latencyInv (0.13)"] + f5["taskFit (0.08)"] + f6["specificityMatch (0.08)"] + f7["stability (0.05)"] + f8["tierPriority (0.05)"] + f9["tierAffinity (0.05)"] + end + + Score --> Factors + Factors --> Final["Sort by score
(desc)"] + Final --> Top["Pick top-N targets"] + Top --> Dispatch["Dispatch sequentially
(short-circuit on success)"] diff --git a/docs/diagrams/cloud-agent-flow.mmd b/docs/diagrams/cloud-agent-flow.mmd new file mode 100644 index 0000000000..32b5a9cea6 --- /dev/null +++ b/docs/diagrams/cloud-agent-flow.mmd @@ -0,0 +1,32 @@ +%% Cloud Agent task lifecycle +%% Reflects: src/lib/cloudAgent/* and src/app/api/v1/agents/tasks/* +%% Supported agents: codex-cloud, devin, jules +%% v3.8.0 +sequenceDiagram + participant U as User + participant API as /v1/agents/tasks + participant Reg as Cloud Agent Registry + participant Ag as Cloud Agent
(codex-cloud / devin / jules) + participant DB as SQLite domain DB + + U->>API: POST createTask (description, sources) + API->>API: Zod validation + AuthZ (management) + API->>Reg: getAgent(providerId) + Reg->>Ag: instantiate with credentials + Ag->>Ag: createTask() returns planning + Ag-->>API: taskId + status: planning + API->>DB: insertCloudAgentTask + API-->>U: taskId + + U->>API: GET /tasks/:id (poll) + API->>Ag: getStatus(externalId) + Ag-->>API: status, plan, messages + API->>DB: updateCloudAgentTask + API-->>U: snapshot + + U->>API: POST approvePlan + API->>Ag: approvePlan(externalId) + Ag-->>API: status: running + Note over Ag: Async execution upstream + + Ag-->>DB: stream events / final result diff --git a/docs/diagrams/db-schema-overview.mmd b/docs/diagrams/db-schema-overview.mmd new file mode 100644 index 0000000000..debf71f3b1 --- /dev/null +++ b/docs/diagrams/db-schema-overview.mmd @@ -0,0 +1,16 @@ +%% Database schema overview (selected core tables) +%% Reflects: src/lib/db/* (45+ modules, 55 migrations) +%% v3.8.0 +erDiagram + api_keys ||--o{ api_key_usage : tracks + api_keys ||--o{ rate_limits : enforces + providers ||--o{ provider_connections : holds + provider_connections ||--o{ domain_circuit_breakers : protected_by + combos ||--o{ combo_targets : contains + combos ||--o{ combo_executions : logs + spend_buffer ||--o{ usage_aggregations : flushed_to + memory_documents ||--o{ memory_chunks : split_into + skills_runs ||--o{ skills_logs : produced + agent_tasks ||--o{ agent_task_events : streams + audit_log }|--|| api_keys : by + mcp_audit }|--|| api_keys : by diff --git a/docs/diagrams/i18n-flow.mmd b/docs/diagrams/i18n-flow.mmd new file mode 100644 index 0000000000..50ddb293c9 --- /dev/null +++ b/docs/diagrams/i18n-flow.mmd @@ -0,0 +1,14 @@ +%% Incremental hash-based i18n pipeline +%% Reflects: scripts/i18n/* and docs/i18n//*.md mirror layout +%% v3.8.0 +flowchart LR + Src["Source MDs
(CLAUDE.md, docs/**/*.md)"] --> Hash["sha256 hash"] + Hash --> State[".i18n-state.json"] + State -->|diff| Dirty["Mark source dirty"] + Dirty --> Loop{"For each locale
(39 langs)"} + Loop --> LLM["OmniRoute
/chat/completions
(cx/gpt-5.4-mini)"] + LLM --> Target["Write
docs/i18n/<locale>/<rel-path>.md"] + Target --> State + Target --> CI{"CI drift check
(npm run i18n:check)"} + CI -->|in sync| Pass["pass"] + CI -->|drift| Fail["fail
(exit 1)"] diff --git a/docs/diagrams/mcp-tools-37.mmd b/docs/diagrams/mcp-tools-37.mmd new file mode 100644 index 0000000000..bd0afb082c --- /dev/null +++ b/docs/diagrams/mcp-tools-37.mmd @@ -0,0 +1,23 @@ +%% MCP Server tool inventory by category +%% Reflects: open-sse/mcp-server/tools/* and docs/MCP-SERVER.md +%% v3.8.0 +flowchart LR + MCP["MCP Server
37 tools total"] --> Core["Core (30)"] + MCP --> Mem["Memory (3)"] + MCP --> Skl["Skills (4)"] + + Core --> Essential["Essential (8)
get_health, list_combos,
switch_combo, check_quota,
route_request, cost_report,
list_models_catalog,
get_combo_metrics"] + Core --> Search["Search (1)
web_search"] + Core --> Advanced["Advanced (11)
simulate_route, set_budget_guard,
set_routing_strategy,
set_resilience_profile,
test_combo, get_provider_metrics,
best_combo_for_task, explain_route,
get_session_snapshot,
db_health_check, sync_pricing"] + Core --> Cache["Cache (2)"] + Core --> Compression["Compression (5)"] + Core --> Tunnels["1Proxy / Tunnels (3)"] + + Mem --> M1["memory_search"] + Mem --> M2["memory_save"] + Mem --> M3["memory_delete"] + + Skl --> S1["skill_invoke"] + Skl --> S2["skill_list"] + Skl --> S3["skill_diagnose"] + Skl --> S4["skill_uninstall"] diff --git a/docs/diagrams/request-pipeline.mmd b/docs/diagrams/request-pipeline.mmd new file mode 100644 index 0000000000..d0883aa76c --- /dev/null +++ b/docs/diagrams/request-pipeline.mmd @@ -0,0 +1,24 @@ +%% Request pipeline for /v1/chat/completions +%% Reflects: src/app/api/v1/chat/completions/route.ts → open-sse/handlers/chatCore.ts +%% v3.8.0 +flowchart LR + Client["Client
(IDE/CLI/SDK)"] --> Route["Next.js Route
/v1/chat/completions"] + Route --> CORS["CORS preflight"] + CORS --> Validate["Zod validation
(request body)"] + Validate --> Authz["AuthZ pipeline
(extractApiKey +
isValidApiKey)"] + Authz --> Policy["API key policy
(allowlist + scopes)"] + Policy --> Guard["Prompt-injection
guardrail"] + Guard --> ChatCore["handleChatCore"] + ChatCore --> Cache{"Cache hit?"} + Cache -->|yes| Return["Return cached"] + Cache -->|no| Rate["Rate limit
(per-key, per-IP)"] + Rate --> Combo{"Combo
target?"} + Combo -->|combo| Resolve["resolveComboTargets
(14 strategies)"] + Resolve --> Single["handleSingleModel
(per target)"] + Combo -->|single| Single + Single --> Translate["translateRequest
(OpenAI↔Claude↔Gemini)"] + Translate --> Exec["getExecutor
(31 executors)"] + Exec --> Upstream["Upstream Provider
(177 providers)"] + Upstream --> Stream["SSE / JSON"] + Stream --> Transformer["responsesTransformer
(Responses↔Chat)"] + Transformer --> Client diff --git a/docs/diagrams/resilience-3layers.mmd b/docs/diagrams/resilience-3layers.mmd new file mode 100644 index 0000000000..777198946a --- /dev/null +++ b/docs/diagrams/resilience-3layers.mmd @@ -0,0 +1,21 @@ +%% 3-layer resilience model +%% Reflects: src/shared/utils/circuitBreaker.ts, src/sse/services/auth.ts, +%% open-sse/services/accountFallback.ts +%% v3.8.0 +flowchart TB + Req["Request"] --> L1{"Provider Circuit Breaker
(scope: whole provider)"} + L1 -->|CLOSED| L2 + L1 -->|OPEN| Block1["Skip provider
(or 503 retry-after)"] + L1 -->|HALF_OPEN| Probe["Allow probe"] + Probe --> L2 + + L2{"Connection Cooldown
(scope: one account/key)"} -->|available| L3 + L2 -->|cooling down| Skip2["Skip this connection
fall back to next"] + + L3{"Model Lockout
(scope: provider × conn × model)"} -->|allowed| Exec["Execute upstream"] + L3 -->|locked| Skip3["Fall back to next model"] + + Exec -->|success| Clear["Clear cooldowns/lockouts"] + Exec -->|"408 / 500 / 502 / 503 / 504"| TripL1["Trip provider breaker"] + Exec -->|"401 / 403 (account)"| TripL2["Start connection cooldown"] + Exec -->|"429 quota (model)"| TripL3["Start model lockout"]