Compare commits

...

7 Commits

52 changed files with 475 additions and 4141 deletions

View File

@@ -10,7 +10,7 @@
## Validation
Choose the change type and focused loop from the
[Contribution Golden Path](../docs/dev/CONTRIBUTION_GOLDEN_PATH.md). The full unit suite,
[Contribution Golden Path](../docs/ops/CONTRIBUTION_GOLDEN_PATH.md). The full unit suite,
Vitest, the 60% coverage gate, and the production build all run in CI on this PR (#8329):
- [ ] Change type: provider / routing / UI / i18n / CLI / DB / build-deploy / other

View File

@@ -253,7 +253,7 @@ Read the nearest `AGENTS.md` and the linked deep-dive before making a non-trivia
- Configuration files (`vitest.config.ts`, `next.config.mjs`, `eslint.config.mjs`, `tsconfig*.json`, `playwright.config.ts`, `prettier.config.mjs`, `postcss.config.mjs`, `sonar-project.properties`, `fly.toml`, `docker-compose*.yml`, `Dockerfile`)
- Dependency files (`package.json`, `package-lock.json`)
- Documentation files (`README.md`, `CHANGELOG.md`, `LICENSE`, `AGENTS.md`, `CLAUDE.md`, `GEMINI.md`, `CONTRIBUTING.md`, `SECURITY.md`, `CODE_OF_CONDUCT.md`, `llm.txt`, `Tuto_Qdrant.md`)
- Documentation files (`README.md`, `CHANGELOG.md`, `ROADMAP.md`, `LICENSE`, `AGENTS.md`, `CLAUDE.md`, `GEMINI.md`, `CONTRIBUTING.md`, `SECURITY.md`, `CODE_OF_CONDUCT.md`, `llm.txt`, `Tuto_Qdrant.md`)
- CI/CD files and ignore definitions (`.gitignore`, `.dockerignore`, `.npmignore`, `.npmrc`, `.node-version`, `.nvmrc`, `.env.example`)
When creating _any_ validation tests or one-off logic scripts, default to `scripts/ad-hoc/` or `tests/unit/` according to your goals. Do not pollute the `/` root context.

View File

@@ -3,7 +3,7 @@
Thank you for your interest in contributing! This guide covers everything you need to get started.
For the official per-change workflow, start with the
[Contribution Golden Path](docs/dev/CONTRIBUTION_GOLDEN_PATH.md). It maps provider, routing,
[Contribution Golden Path](docs/ops/CONTRIBUTION_GOLDEN_PATH.md). It maps provider, routing,
UI/UX, i18n, CLI, database, and build/deploy changes to their contracts, focused tests, CI
coverage, and reconciliation steps.
@@ -210,7 +210,7 @@ Coverage notes:
### Pull Request Requirements
Before opening a PR, use the
[Contribution Golden Path](docs/dev/CONTRIBUTION_GOLDEN_PATH.md) to run the focused loop for
[Contribution Golden Path](docs/ops/CONTRIBUTION_GOLDEN_PATH.md) to run the focused loop for
what you changed. The full unit suite (4 CI shards), Vitest, the **60%+** coverage gate, and
the production build are CI's responsibility — running them locally adds no signal the PR
checks will not already give you, and on smaller machines it can saturate the host (#8084):

View File

@@ -844,7 +844,7 @@ npm install -g omniroute
omniroute
```
> 💡 See `npm warn ERESOLVE` or peer-dep warnings? [They're harmless](docs/getting-started/TROUBLESHOOTING.md#npm-install-warnings-eresolve--peer--deprecated).
> 💡 See `npm warn ERESOLVE` or peer-dep warnings? [They're harmless](docs/guides/TROUBLESHOOTING.md#npm-install-warnings-eresolve--peer--deprecated).
Dashboard at `http://localhost:20128` · API at `http://localhost:20128/v1`.
@@ -896,7 +896,7 @@ docker run -d --name omniroute --restart unless-stopped --stop-timeout 40 \
> `diegosouzapw/omniroute:next-web` follow the current default `release/v*`
> branch. These mutable tags are intended only for testing unreleased fixes and
> are **not supported for production**. See
> [Docker Release Channels](docs/guides/DOCKER_RELEASE_CHANNELS.md).
> [Docker Release Channels](docs/guides/DOCKER_GUIDE.md#release-channels).
**🛠️ From source**

View File

@@ -7,7 +7,7 @@ lastUpdated: 2026-08-06
# OmniRoute Roadmap
> Version-gated, not date-gated: each milestone ships when its quality gates pass.
> Current line: **v3.8.x** (this branch). Last updated: 2026-07-23.
> Current line: **v3.8.x** (this branch). Last updated: 2026-08-06.
OmniRoute is heading from a monolithic router to a **modular AI platform**: a lightweight
core engine, a typed SDK, and everything else as installable modules and plugins. The path

View File

@@ -8,7 +8,7 @@ lastUpdated: 2026-06-28
Navigable index of the OmniRoute documentation set. Topics are grouped by intent so you can find what you need quickly.
> Looking for the project overview, install steps, or release notes? See the root [README.md](../README.md), [CHANGELOG.md](../CHANGELOG.md), and [CONTRIBUTING.md](../CONTRIBUTING.md).
> Looking for the project overview, install steps, or release notes? See the root [README.md](../README.md), [ROADMAP.md](../ROADMAP.md), [CHANGELOG.md](../CHANGELOG.md), and [CONTRIBUTING.md](../CONTRIBUTING.md).
---
@@ -22,7 +22,7 @@ Simple guides for using OmniRoute — no technical background needed.
- [AUTO-COMBO-GUIDE.md](getting-started/AUTO-COMBO-GUIDE.md) — let OmniRoute pick the best AI for you.
- [PROVIDERS-GUIDE.md](getting-started/PROVIDERS-GUIDE.md) — how to connect AI providers.
- [FREE-TIERS-GUIDE.md](getting-started/FREE-TIERS-GUIDE.md) — get free AI with no credit card.
- [TROUBLESHOOTING.md](getting-started/TROUBLESHOOTING.md) — fix common issues.
- [WEB-COOKIE-GUIDE.md](getting-started/WEB-COOKIE-GUIDE.md) — web cookie providers (session-credential setup).
### guides/
@@ -42,6 +42,8 @@ Simple guides for using OmniRoute — no technical background needed.
- [CLAUDE-CODE-CONFIGURATION.md](guides/CLAUDE-CODE-CONFIGURATION.md) — Claude Code CLI with OmniRoute.
- [CODEX-CLI-CONFIGURATION.md](guides/CODEX-CLI-CONFIGURATION.md) — Codex CLI with OmniRoute.
- [KIRO_SETUP.md](guides/KIRO_SETUP.md) — Kiro setup.
- [ANTIGRAVITY-ONBOARDING.md](guides/ANTIGRAVITY-ONBOARDING.md) — Antigravity (Google One AI) onboarding.
- [MANAGEMENT-AUTH.md](guides/MANAGEMENT-AUTH.md) — management authentication.
- [I18N.md](guides/I18N.md) — translation and locale workflow.
- [TROUBLESHOOTING.md](guides/TROUBLESHOOTING.md) — detailed troubleshooting reference.
- [UNINSTALL.md](guides/UNINSTALL.md) — clean removal steps.
@@ -64,6 +66,10 @@ How the system is put together — read these to understand the runtime, code la
- [QUALITY_GATES.md](architecture/QUALITY_GATES.md) — quality-gate scripts and CI jobs inventory.
- [MONITORING_SECTIONS.md](architecture/MONITORING_SECTIONS.md) — monitoring/costs dashboard navigation.
- [cluster-decisions.md](architecture/cluster-decisions.md) — optional sidecar/cluster profile decisions.
- [DESIGN_SYSTEM.md](architecture/DESIGN_SYSTEM.md) — design system & visual identity.
- [ROUTER_BACKENDS.md](architecture/ROUTER_BACKENDS.md) — router backends & embedded services architecture contract (ADR).
- [admission-lanes.md](architecture/admission-lanes.md) — the two admission-lane systems and what gates each.
- [persistence-backend-boundary.md](architecture/persistence-backend-boundary.md) — pluggable persistence boundary (ADR).
## reference/
@@ -77,6 +83,9 @@ Lookup material — API surface, environment variables, CLI flags, provider cata
- [FEATURE_FLAGS.md](reference/FEATURE_FLAGS.md) — feature flags and their defaults.
- [CLI-TOOLS.md](reference/CLI-TOOLS.md) — bundled CLI commands.
- [FREE_TIERS.md](reference/FREE_TIERS.md) — free-tier LLM provider directory.
- [FREE_PROXIES_API.md](reference/FREE_PROXIES_API.md) — free proxies API.
- [RELAY_BACKEND_STRATEGY.md](reference/RELAY_BACKEND_STRATEGY.md) — relay backend strategy.
- [RELAY_TROUBLESHOOTING.md](reference/RELAY_TROUBLESHOOTING.md) — relay troubleshooting.
## frameworks/
@@ -97,6 +106,7 @@ Pluggable subsystems exposed to clients, agents, and operators.
- [EMBEDDED-SERVICES.md](frameworks/EMBEDDED-SERVICES.md) — embedded sidecar services (9Router, CLIProxyAPI).
- [NOTION_CONTEXT.md](frameworks/NOTION_CONTEXT.md) — Notion context source.
- [OBSIDIAN_CONTEXT.md](frameworks/OBSIDIAN_CONTEXT.md) — Obsidian context source.
- [LOCAL_CORPUS_CONTEXT.md](frameworks/LOCAL_CORPUS_CONTEXT.md) — local corpus context source (approved directory exposed to MCP).
- [OPENCODE.md](frameworks/OPENCODE.md) — OpenCode integration.
- [OPEN_SSE_ARCHITECTURE.md](frameworks/OPEN_SSE_ARCHITECTURE.md) — open-sse streaming engine internals.
- [PLAYGROUND_STUDIO.md](frameworks/PLAYGROUND_STUDIO.md) — Playground Studio UI.
@@ -114,6 +124,7 @@ Combo routing, scoring, and replay.
- [AUTO-COMBO.md](routing/AUTO-COMBO.md) — Auto-Combo (multi-factor scoring, 17 strategies).
- [QUOTA_SHARE.md](routing/QUOTA_SHARE.md) — quota sharing engine.
- [REASONING_REPLAY.md](routing/REASONING_REPLAY.md) — reasoning replay cache.
- [REASONING_ROUTING.md](routing/REASONING_ROUTING.md) — reasoning routing rules (effort/budget rule engine).
## security/
@@ -127,6 +138,9 @@ Guardrails, compliance, stealth, and the mandatory patterns for handling public
- [ROUTE_GUARD_TIERS.md](security/ROUTE_GUARD_TIERS.md) — route-guard classification tiers.
- [CLI_TOKEN.md](security/CLI_TOKEN.md) — CLI machine-ID token (HMAC + legacy SHA-256) auth.
- [EGRESS_POLICY.md](security/EGRESS_POLICY.md) — egress IP family (IPv4/IPv6) policy.
- [BAN_DETECTION.md](security/BAN_DETECTION.md) — account-ban / banned-keyword detection.
- [AGENTROUTER_WAF.md](security/AGENTROUTER_WAF.md) — agentrouter.org WAF.
- [CORS.md](security/CORS.md) — CORS configuration & security.
- [MITM-TPROXY-DECRYPT.md](security/MITM-TPROXY-DECRYPT.md) — transparent MITM decrypt.
- [SUPPLY_CHAIN.md](security/SUPPLY_CHAIN.md) — supply-chain gates (SLSA, SBOM, Trivy, osv-scanner, Scorecard).
- [SOCKET_DEV_FINDINGS.md](security/SOCKET_DEV_FINDINGS.md) — supply-chain finding attestations.
@@ -148,8 +162,11 @@ Prompt compression engines, rules, and language packs.
Provider-specific integration guides.
- [CLAUDE_WEB.md](providers/CLAUDE_WEB.md) — Claude Web (cookie-auth) provider.
- [CHATGPT_WEB.md](providers/CHATGPT_WEB.md) — ChatGPT Web (Plus/Pro + Codex) providers.
- [ALIBABA-QWEN-PROVIDER-FAMILIES.md](providers/ALIBABA-QWEN-PROVIDER-FAMILIES.md) — Alibaba and Qwen provider families.
- [AGENTROUTER.md](providers/AGENTROUTER.md) — AgentRouter setup.
- [ZED-DOCKER.md](providers/ZED-DOCKER.md) — Zed IDE integration under Docker.
- [CURSOR-DOCKER.md](providers/CURSOR-DOCKER.md) — Cursor model listing under Docker.
## comparison/
@@ -161,11 +178,17 @@ Release, deployment, proxies, tunnels, coverage, database, monitoring.
- [RELEASE_CHECKLIST.md](ops/RELEASE_CHECKLIST.md) — release flow checklist.
- [RELEASE_GREEN.md](ops/RELEASE_GREEN.md) — keeping the PR queue and release branch green.
- [BRANCHING_MODEL.md](ops/BRANCHING_MODEL.md) — branching & release model.
- [MERGE_TRAIN.md](ops/MERGE_TRAIN.md) — merge queue & manual merge-train runbook.
- [HOMOLOGATION.md](ops/HOMOLOGATION.md) — homologation suite (`npm run homolog`).
- [QUALITY_GATE_PLAYBOOK.md](ops/QUALITY_GATE_PLAYBOOK.md) — quality-gate playbook.
- [RUNNER_BOX.md](ops/RUNNER_BOX.md) — self-hosted runner box operations.
- [BRANCH_PROTECTION_MAIN.md](ops/BRANCH_PROTECTION_MAIN.md) — `main` branch protection.
- [CONTRIBUTION_GOLDEN_PATH.md](ops/CONTRIBUTION_GOLDEN_PATH.md) — contribution golden path (focused checks per change type).
- [COVERAGE_PLAN.md](ops/COVERAGE_PLAN.md) — test coverage plan.
- [DATABASE_GUIDE.md](ops/DATABASE_GUIDE.md) — DB schema and operations.
- [SQLITE_RUNTIME.md](ops/SQLITE_RUNTIME.md) — SQLite driver resolution chain.
- [REDIS_PRODUCTION_CONFIG.md](ops/REDIS_PRODUCTION_CONFIG.md) — Redis production configuration.
- [MONITORING_GUIDE.md](ops/MONITORING_GUIDE.md) — monitoring & observability.
- [FLY_IO_DEPLOYMENT_GUIDE.md](ops/FLY_IO_DEPLOYMENT_GUIDE.md) — Fly.io deployment.
- [VM_DEPLOYMENT_GUIDE.md](ops/VM_DEPLOYMENT_GUIDE.md) — generic VM deployment.

View File

@@ -9,7 +9,9 @@ It describes each gate, what it validates, which CI job it runs in, whether it u
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`.
in `CLAUDE.md`. For the critical assessment, maturity classification, and tool-agnostic
replication plan of the same system, see the
[Quality Gate Playbook](../ops/QUALITY_GATE_PLAYBOOK.md).
---

View File

@@ -6,6 +6,12 @@
"CODEBASE_DOCUMENTATION",
"REPOSITORY_MAP",
"RESILIENCE_GUIDE",
"QUALITY_GATES"
"QUALITY_GATES",
"DESIGN_SYSTEM",
"MONITORING_SECTIONS",
"ROUTER_BACKENDS",
"admission-lanes",
"cluster-decisions",
"persistence-backend-boundary"
]
}

View File

@@ -1,916 +0,0 @@
---
title: "MySQL conformance semantics and failure-mode matrix"
status: proposed-test-specification
lastUpdated: 2026-07-30
---
# MySQL conformance semantics and failure-mode matrix
- **Tracking issue:** [#8075](https://github.com/diegosouzapw/OmniRoute/issues/8075)
- **Governing proposal:** [Pluggable persistence boundary](persistence-backend-boundary.md)
- **Measured baseline:** [SQLite coupling inventory](sqlite-coupling-inventory.md)
- **Target:** MySQL 8.0 with InnoDB
- **Runtime impact:** None. This document adds no driver, dependency, configuration, schema,
migration, or support claim.
## 1. Purpose and normative language
The persistence-boundary ADR requires conformance tests to compare observable behavior, not only
repository method signatures. This document turns the MySQL/InnoDB differences that can change
OmniRoute behavior into an implementation-ready specification. It provides:
- a required server and session profile;
- evidence from the current SQLite implementation;
- minimal SQL probes that reviewers can reproduce independently;
- a backend-neutral error and retry taxonomy;
- normative decisions that a repository contract must make;
- executable acceptance specifications for a future shared conformance harness;
- a focused acceptance profile for combo definitions and model-to-combo mappings.
The terms **MUST**, **MUST NOT**, **SHOULD**, and **MAY** are normative. A proposed MySQL adapter is
not conformant merely because its SQL succeeds. It is conformant only when the same repository
fixture produces the same domain result, durable state, atomicity, ordering, and classified failure
as the SQLite implementation.
## 2. Scope and non-goals
### 2.1 In scope
This specification covers portable durable-state behavior for:
- create, read, update, delete, and missing-row results;
- uniqueness, collation, case and accent sensitivity, and `NULL`;
- stable ordering and pagination;
- no-op writes and affected-row reporting;
- insert, identity-preserving upsert, and replacement;
- IDs, JSON, exact numerics, and timestamps;
- transactions, deadlocks, lock waits, disconnects, and retry boundaries;
- foreign keys and atomic related-record changes;
- migration ownership, implicit DDL commits, recovery, and readiness.
### 2.2 Out of scope
This specification does not:
- approve PostgreSQL or MySQL runtime support;
- select a Node.js MySQL driver or pool;
- define a public environment variable or configuration UI;
- define final TypeScript repository interfaces;
- add physical MySQL schema or migration files;
- make SQLite maintenance, FTS5, `sqlite-vec`, backup files, or WAL portable;
- replace domain-specific acceptance criteria;
- permit runtime work while the governing ADR remains unapproved.
## 3. Evidence from the current repository
The current implementation establishes behavior that a portable contract must either preserve or
explicitly revise. These are source-backed observations, not proposed MySQL schema.
### 3.1 Combo identity and lookup
`src/lib/db/migrations/001_initial_schema.sql` defines `combos.id` as the primary key and
`combos.name` as unique. `src/lib/db/combos.ts` currently:
- generates UUIDs in the application;
- generates timestamps with `new Date().toISOString()`;
- performs exact name lookup first;
- provides a separate `COLLATE NOCASE` fallback lookup;
- lists by `sort_order ASC, name COLLATE NOCASE ASC`;
- treats an update of a missing ID as `null`;
- treats deletion of a missing ID as `false`;
- updates the JSON payload and deduplicated columns together;
- reorders all selected rows in one SQLite transaction.
Those choices imply that a future MySQL slice does not need database-generated numeric IDs for
combos, but it must still define Unicode collation, complete tie-breakers, update/delete results, and
reorder concurrency.
### 3.2 Model-to-combo mapping behavior
`src/lib/db/migrations/010_model_combo_mappings.sql` defines a foreign key from
`model_combo_mappings.combo_id` to `combos.id` with `ON DELETE CASCADE`.
`src/lib/db/modelComboMappings.ts` currently:
- generates mapping UUIDs and ISO timestamps in the application;
- lists by `priority DESC, created_at ASC`;
- returns a separate total count for paginated results;
- maps integer `0`/`1` values to booleans;
- treats a missing update as `null` and a missing delete as `false`;
- resolves the first enabled matching pattern;
- skips malformed combo JSON rather than failing resolution.
The current list and resolution order lacks a unique final tie-breaker. The MySQL implementation
MUST NOT preserve that accidental nondeterminism. Before portability is claimed, the contract must
add `id ASC` (or another unique stable key) after `created_at ASC` and the SQLite implementation
must adopt the same order.
### 3.3 Existing SQLite-specific signals
The measured SQLite coupling inventory records widespread use of synchronous prepared statements,
`INSERT OR REPLACE`, `lastInsertRowid`, SQLite transactions, and SQLite lifecycle operations. A
future adapter must not translate those tokens mechanically. In particular:
- `INSERT OR REPLACE` is delete-then-insert conflict handling, not an update;
- `changes` is a driver result, not a portable domain result;
- `COLLATE NOCASE` is not equivalent to a modern MySQL Unicode collation;
- SQLite numbered migration SQL is not reusable as MySQL migration SQL.
## 4. Required MySQL deployment and session profile
A conformance run MUST fail during backend initialization if the effective profile is outside the
supported envelope. Silently inheriting server defaults would make behavior depend on an operator's
installation history.
| Property | Required profile | Verification | Failure class |
| ------------------------ | ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | --------------------- |
| Server family | Oracle MySQL 8.0.x until another family passes the same suite | `SELECT VERSION()` and server metadata | `unsupported` |
| Storage engine | `InnoDB` for every portable table | `information_schema.tables` | `schema_incompatible` |
| Character set | `utf8mb4` for schema, tables, and portable text columns | `information_schema.schemata`, `tables`, and `columns` | `schema_incompatible` |
| Identity collation | Explicit per identity column; never inherited | `information_schema.columns.collation_name` | `schema_incompatible` |
| SQL mode | Strict mode and the engine-substitution guard; adapter records the effective value | `SELECT @@SESSION.sql_mode` | `unsupported` |
| Transaction isolation | Explicitly selected and verified by the backend | `SELECT @@SESSION.transaction_isolation` | `unsupported` |
| Session time zone | UTC | `SELECT @@SESSION.time_zone` | `unsupported` |
| Autocommit | Known pool default; repository transactions set boundaries explicitly | `SELECT @@SESSION.autocommit` | `unsupported` |
| Connection character set | `utf8mb4` | `SELECT @@character_set_client, @@character_set_connection, @@character_set_results` | `unsupported` |
| Found-rows behavior | One fixed pool setting, but repository results remain independent of it | Driver/pool configuration plus conformance probe | `unsupported` |
| Foreign-key checks | Enabled for normal runtime and conformance tests | `SELECT @@SESSION.foreign_key_checks` | `unsupported` |
| InnoDB page size | Recorded before validating indexed key lengths | `SELECT @@innodb_page_size` | `schema_incompatible` |
The backend readiness report SHOULD expose the verified profile without credentials. It MUST NOT
log connection strings or secrets.
### 4.1 Initialization probe
The adapter acceptance suite should run an equivalent of the following read-only probe on a newly
leased connection:
```sql
SELECT
VERSION() AS server_version,
@@SESSION.sql_mode AS sql_mode,
@@SESSION.transaction_isolation AS transaction_isolation,
@@SESSION.time_zone AS time_zone,
@@SESSION.autocommit AS autocommit,
@@SESSION.foreign_key_checks AS foreign_key_checks,
@@character_set_client AS character_set_client,
@@character_set_connection AS character_set_connection,
@@character_set_results AS character_set_results,
@@innodb_page_size AS innodb_page_size;
```
A pool MUST apply and verify session settings on every newly created physical connection. Applying
settings only to the first connection is insufficient.
## 5. Normative semantic matrix
### 5.0 Observable SQLite/MySQL difference summary
This table is the review index for the detailed rules below. It distinguishes current or common
backend behavior from the portable result the repository must expose. The MySQL column describes
InnoDB under the verified session profile; it must not be read as permission to inherit an
unverified server default.
| Concern | SQLite-shaped behavior | MySQL/InnoDB behavior | Required repository contract |
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------- |
| Text identity | Binary comparison by default; current code opts into ASCII-oriented `NOCASE` for selected reads and sorts | Equality, uniqueness, and sort order follow the selected column/expression collation | Declare byte-exact identity separately from named insensitive lookup and display order |
| Nullable unique key | Multiple SQL `NULL` values can pass a plain unique constraint | Multiple SQL `NULL` values can pass a plain unique index | Enforce any "one logical null" invariant atomically outside a plain unique key |
| Unordered/tied results | No total order without a complete `ORDER BY` | No total order without a complete `ORDER BY` | Define `NULL` position and a unique final tie-breaker for every portable list |
| No-op update | Driver change count reflects SQLite's statement behavior | Changed-row count differs from matched-row mode for identical assignments | Return domain outcomes independently of raw affected-row counts |
| Conflict write | `INSERT OR REPLACE` can delete then insert | Duplicate-key upsert updates one selected conflict | Classify every operation as insert-only, identity-preserving upsert, or replacement |
| Generated identity | SQLite row IDs and driver-local last-insert state are connection-bound | Generated IDs and last-insert state are connection-bound | Retrieve identity in the insert operation/lease and use stable idempotency identity on retry |
| JSON | Existing combo payloads are text and malformed legacy text can be observed | Native `JSON` validates and normalizes its representation | Choose text or typed JSON deliberately and compare the declared domain representation |
| Exact values/time | Current modules commonly serialize JavaScript values and ISO UTC text | Driver conversion can lose large integers/decimals; temporal types depend on type and session zone | Fix exact representations, UTC policy, and precision across backends |
| Concurrency/isolation | Deferred transactions and a database-wide single-writer model shape conflicts; read visibility depends on transaction mode and WAL state | InnoDB defaults to `REPEATABLE READ`, uses MVCC snapshots for consistent reads, and permits concurrent writers on different locked records | Select and verify isolation, then test domain-visible reads, conflicts, and retry boundaries rather than relying on either default |
| DDL/migrations | SQLite migration sequences can be wrapped according to SQLite transaction rules | DDL commonly commits implicitly; one atomic DDL statement does not make a multi-step migration atomic | Use distributed ownership, durable phase checkpoints, postcondition inspection, and readiness gating |
### 5.1 Text identity, collation, and uniqueness
MySQL equality and unique indexes use the effective collation of the indexed expression. A `_ci`
collation is case-insensitive; an `_ai` collation is also accent-insensitive. SQLite's default text
comparison and `COLLATE NOCASE` do not provide an equivalent Unicode contract.
| Concern | SQLite-shaped risk | Required portable decision | MySQL implementation rule |
| ---------------- | -------------------------------------------------------- | --------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
| IDs | Text IDs can inherit an unintended collation | IDs are byte-exact and case-sensitive | Use an explicit binary collation or binary representation |
| Combo names | Exact lookup and insensitive fallback are separate today | Exact lookup remains exact; insensitive lookup is a named operation | Exact and insensitive queries use explicit, different collations or normalized keys |
| Unique names | A server default can collapse case or accents | The domain declares whether case/accent variants conflict | Unique index uses the declared collation, never the database default |
| Pattern text | Pattern matching occurs in application code | Stored pattern bytes round-trip unchanged | Store with an explicit case-sensitive collation |
| User-facing sort | SQLite `NOCASE` order is not portable Unicode order | List order is defined by a normalized sort key or explicit collation policy | Schema and query use the selected policy and a unique tie-breaker |
Minimum probe:
```sql
CREATE TEMPORARY TABLE conformance_text (
id VARCHAR(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin PRIMARY KEY,
name VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci UNIQUE
) ENGINE=InnoDB;
INSERT INTO conformance_text (id, name) VALUES ('A', 'Résumé');
-- The next statement conflicts under utf8mb4_0900_ai_ci.
INSERT INTO conformance_text (id, name) VALUES ('a', 'resume');
```
The harness MUST repeat the probe for the exact collation selected by the eventual schema; the
example collation above is evidence, not an approval for combo names.
### 5.2 `NULL`, missing rows, and nullable unique keys
MySQL unique indexes permit multiple `NULL` values. SQLite does likewise for unique columns.
However, neither behavior implements a domain invariant such as "only one active row may have no
owner."
Repository contracts MUST distinguish:
- no row found;
- a row found with a nullable field set to SQL `NULL`;
- a JSON document containing JSON `null`;
- a missing JSON member.
Minimum probe:
```sql
CREATE TEMPORARY TABLE conformance_null (
id VARCHAR(64) PRIMARY KEY,
optional_key VARCHAR(64) NULL,
UNIQUE KEY uq_optional_key (optional_key)
) ENGINE=InnoDB;
INSERT INTO conformance_null VALUES ('one', NULL), ('two', NULL);
SELECT COUNT(*) AS row_count FROM conformance_null;
-- Expected: 2.
```
If a domain allows at most one logical `NULL`, it MUST use an explicit atomic invariant rather than
rely on a plain unique index.
### 5.3 Ordering, ties, and pagination
Without `ORDER BY`, result order is undefined. With a non-unique `ORDER BY`, tied rows still have an
undefined relative order. Offset pagination can therefore duplicate or omit records if the complete
order is not stable.
Every portable list MUST specify:
1. every user-visible sort expression;
2. the position of `NULL` values;
3. a unique final tie-breaker;
4. the cursor comparison tuple, if cursor pagination is used;
5. the snapshot/concurrency expectation across pages.
For the proposed combo/mapping slice:
```sql
-- Combo list contract candidate.
ORDER BY sort_order ASC, normalized_name ASC, id ASC
-- Mapping list and resolution contract candidate.
ORDER BY priority DESC, created_at ASC, id ASC
```
The exact `normalized_name` representation remains a contract decision. It MUST NOT be implemented
by relying on an unspecified database default.
For nullable values, use an explicit sort key rather than a backend default:
```sql
ORDER BY nullable_column IS NULL ASC, nullable_column ASC, id ASC
```
### 5.4 Update, no-op, delete, and affected rows
MySQL `UPDATE` reports rows actually changed by default. With the C API found-rows connection flag,
it reports rows matched. `INSERT ... ON DUPLICATE KEY UPDATE` reports 1 for insert, 2 for an actual
update, and 0 for an update to identical values; the found-rows flag changes the last value to 1.
These numbers MUST NOT become repository semantics.
| Repository outcome | Required meaning | Forbidden implementation shortcut |
| ------------------ | ------------------------------------------------------ | --------------------------------------------- |
| `updated` | Target existed and the operation's postcondition holds | `affectedRows > 0` alone |
| `unchanged` | Target existed and already satisfied the postcondition | Treating 0 changed rows as missing |
| `not_found` | Target identity did not exist | Treating every 0 count as unchanged |
| `conflict` | Compare/update version or invariant failed | Returning generic `false` |
| delete `true` | A row existed and was deleted | Assuming a successful statement deleted a row |
| delete `false` | No row existed | Throwing a backend-specific error |
Minimum probe, run once with each supported connection mode:
```sql
CREATE TEMPORARY TABLE conformance_update (
id VARCHAR(64) PRIMARY KEY,
value_text VARCHAR(64) NOT NULL,
version_no BIGINT NOT NULL
) ENGINE=InnoDB;
INSERT INTO conformance_update VALUES ('row', 'same', 1);
UPDATE conformance_update SET value_text = 'same' WHERE id = 'row';
UPDATE conformance_update SET value_text = 'changed' WHERE id = 'row';
UPDATE conformance_update SET value_text = 'missing' WHERE id = 'missing';
```
The harness asserts repository results and final rows, not raw driver counts. A versioned
compare/update SHOULD use a predicate such as `WHERE id = ? AND version_no = ?`, then distinguish a
missing identity from a stale version according to the domain contract.
### 5.5 Insert, upsert, and replacement
SQLite `INSERT OR REPLACE` deletes rows that conflict with a unique or primary key before inserting
the new row. MySQL `INSERT ... ON DUPLICATE KEY UPDATE` updates one conflicting row. The two forms
differ in foreign-key cascades, triggers, omitted columns, IDs, timestamps, and affected-row counts.
Every write method MUST be classified as exactly one of:
1. **insert-only:** duplicate identity returns `unique_violation`;
2. **identity-preserving upsert:** duplicate identity updates an explicit allowlist of mutable fields;
3. **replacement:** old identity is deleted and a new row is inserted, with cascade effects included
in the contract.
A generic helper MUST NOT choose among these behaviors based on SQL convenience.
Minimum difference probe. This uses ordinary InnoDB tables because MySQL temporary tables cannot
serve as the parent/child foreign-key fixture. Run it in an isolated conformance schema; cleanup is
included so the probe is repeatable:
```sql
DROP TABLE IF EXISTS conformance_child;
DROP TABLE IF EXISTS conformance_parent;
CREATE TABLE conformance_parent (
id VARCHAR(64) PRIMARY KEY,
immutable_value VARCHAR(64) NOT NULL,
mutable_value VARCHAR(64) NOT NULL
) ENGINE=InnoDB;
CREATE TABLE conformance_child (
id VARCHAR(64) PRIMARY KEY,
parent_id VARCHAR(64) NOT NULL,
CONSTRAINT fk_conformance_child_parent
FOREIGN KEY (parent_id) REFERENCES conformance_parent(id) ON DELETE CASCADE
) ENGINE=InnoDB;
INSERT INTO conformance_parent VALUES ('p', 'keep', 'old');
INSERT INTO conformance_child VALUES ('c', 'p');
INSERT INTO conformance_parent (id, immutable_value, mutable_value)
VALUES ('p', 'replacement', 'new')
ON DUPLICATE KEY UPDATE mutable_value = VALUES(mutable_value);
SELECT immutable_value, mutable_value FROM conformance_parent WHERE id = 'p';
SELECT COUNT(*) AS child_count FROM conformance_child WHERE parent_id = 'p';
-- Expected: immutable_value='keep', mutable_value='new', child_count=1.
DROP TABLE conformance_child;
DROP TABLE conformance_parent;
```
The `VALUES(mutable_value)` form is used here because the target remains MySQL 8.0 as a family and
no minimum 8.0 patch release has been approved. It is deprecated in later MySQL 8.0 releases, so an
adapter that establishes a newer minimum MAY use the supported row-alias form instead. The harness
asserts identity-preserving behavior, not either SQL spelling.
Tables with multiple unique indexes require special care because a duplicate can select an
unexpected conflicting row. Portable upsert schema SHOULD have one unambiguous conflict identity.
### 5.6 Unicode and index-size constraints
`utf8mb4` uses up to four bytes per character. InnoDB's maximum index key is 3072 bytes for common
`DYNAMIC` or `COMPRESSED` row formats with a 16 KiB page, and is lower for smaller page sizes or
legacy row formats. A prefix unique index is not equivalent to full-value uniqueness.
Schema acceptance MUST:
- set bounded lengths for all indexed identity strings;
- calculate the worst-case byte length of every composite index;
- verify the actual page size and row format;
- reject a prefix unique index for a full-identity contract;
- test maximum-length non-ASCII values before migration is accepted;
- classify an incompatible definition as `schema_incompatible`, not `unique_violation`.
Example boundary probe for a 16 KiB/DYNAMIC profile:
```sql
CREATE TEMPORARY TABLE conformance_index (
value_text VARCHAR(768) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
UNIQUE KEY uq_value_text (value_text)
) ENGINE=InnoDB ROW_FORMAT=DYNAMIC;
```
The exact accepted length MUST be derived from all key parts and the verified deployment profile;
this example is deliberately near a physical boundary and is not a proposed production column.
### 5.7 IDs and connection-local state
The current combo and mapping modules generate UUIDs in the application. A MySQL implementation
SHOULD preserve this strategy for those domains.
If another domain uses a database-generated incrementing ID, the adapter MUST observe these rules:
- ID retrieval is part of the same driver operation and physical connection as the insert;
- callers never issue a later connection-level `LAST_INSERT_ID()` query;
- multi-row inserts define whether one ID or all IDs are returned;
- an error or rollback makes a previously observed `LAST_INSERT_ID()` unsuitable as proof of commit;
- retries use a stable domain idempotency key;
- upsert defines whether it returns an existing or newly generated identity.
MySQL documents `LAST_INSERT_ID()` as per-connection state and leaves it undefined after some errors
or error-driven rollbacks. Pool leases are therefore part of correctness, not merely performance.
### 5.8 JSON representation
Current combo data is JSON text, and malformed JSON is observable: combo reads can skip malformed
rows and mapping resolution skips malformed combo payloads. Switching the MySQL column directly to
native `JSON` would reject malformed rows at write/import time and normalize duplicate keys,
whitespace, and key order.
Before choosing `LONGTEXT` or `JSON`, the combo contract MUST decide:
- whether malformed stored payloads remain representable for compatibility tests;
- whether equality is structural or byte-for-byte;
- whether duplicate object keys are rejected before persistence;
- whether serialization order is stable and application-owned;
- which fields are duplicated into typed columns and which representation is authoritative.
For the first slice, an identity-preserving migration SHOULD keep application serialization as the
domain boundary. If native `JSON` is selected, imports MUST parse and validate before writing, and
tests MUST compare parsed domain values rather than raw JSON text.
Minimum normalization probe:
```sql
CREATE TEMPORARY TABLE conformance_json (id VARCHAR(64) PRIMARY KEY, payload JSON) ENGINE=InnoDB;
INSERT INTO conformance_json VALUES ('j', '{"b": 2, "a": 1, "a": 3}');
SELECT payload FROM conformance_json WHERE id = 'j';
-- The value is normalized; original whitespace/key duplication is not preserved.
```
### 5.9 Exact numerics and timestamps
| Type | Risk | Required contract |
| ----------- | ----------------------------------------------------- | ----------------------------------------------------------------------- |
| `BIGINT` | Values can exceed JavaScript's safe integer range | Return a string or validated bigint representation across every backend |
| `DECIMAL` | Driver options may return strings or lossy numbers | Fix precision/scale and use an exact domain representation |
| `TIMESTAMP` | Session time zone conversion and fractional precision | Force UTC session time zone and specify fractional precision |
| `DATETIME` | No intrinsic time zone | Use only for explicitly zone-free civil time |
| ISO text | Lexical ordering depends on one canonical format | Validate UTC suffix and exact precision before persistence |
Combo and mapping timestamps are currently application-generated ISO strings. The first slice SHOULD
preserve their exact domain format rather than introducing server-generated local time.
### 5.10 Transaction isolation and observable concurrency
MySQL InnoDB uses `REPEATABLE READ` as its default isolation level. Within an explicit transaction,
its consistent non-locking reads normally establish and reuse an MVCC snapshot, while locking reads
and writes inspect and lock current index records or ranges. SQLite instead combines snapshot/read
transaction behavior with a database-wide single-writer model; transaction mode and WAL state affect
when a writer is admitted and when a read transaction can be upgraded. These mechanisms are not
interchangeable even when a simple CRUD fixture produces the same final row.
The backend profile MUST select and verify an isolation level rather than silently accept either
backend's default. The repository contract MUST then define observable results for each atomic
operation. It MUST NOT promise the implementation mechanism itself, such as gap locks or a
SQLite-wide writer lock.
| Scenario | SQLite-shaped risk | InnoDB `REPEATABLE READ` risk | Required conformance decision |
| --------------------------------- | --------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| Two reads in one transaction | Snapshot timing depends on when the read transaction begins and the active journal mode | Consistent reads normally reuse the transaction's first established read view | State whether the operation requires one stable snapshot or deliberately performs a current read |
| Range read plus concurrent insert | A concurrent writer may be serialized by SQLite's writer admission rules | A plain consistent read can retain its snapshot; a locking range read can lock index gaps | Define whether a later read sees the insert and whether the operation requires a locking predicate |
| Read-modify-write | Single-writer serialization can mask an unsafe application sequence | Concurrent transactions can read the same value and later contend or overwrite without a version predicate | Require compare/update, a locking read, or another explicit invariant; never rely on backend serialization |
| Writers touching different rows | SQLite still admits only one writer at a time | InnoDB can execute both until their record/range locks conflict | Do not infer portable throughput or lock order; assert only atomic effects and classified conflicts |
| Pagination across transactions | Separate page reads can observe different committed states | Separate autocommit reads get separate views; one transaction may retain one view | Declare snapshot pagination or documented live pagination and test that policy |
| Retry after conflict | Busy/locked outcomes and transaction upgrade failures are SQLite-shaped | Deadlocks and lock timeouts have different rollback scopes | Normalize the error, discard the failed context, and retry the complete idempotent operation only |
Minimum two-connection visibility probe for the selected MySQL profile:
```text
Connection A Connection B
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
START TRANSACTION;
SELECT value_no FROM conformance_isolation
WHERE id = 1; -- establishes read view: 0
START TRANSACTION;
UPDATE conformance_isolation
SET value_no = 1 WHERE id = 1;
COMMIT;
SELECT value_no FROM conformance_isolation
WHERE id = 1; -- same consistent-read view: 0
COMMIT;
SELECT value_no FROM conformance_isolation
WHERE id = 1; -- new transaction/view: 1
```
The shared harness MUST NOT assert that every backend reproduces this internal sequence. It must use
it to prove that the chosen repository operation either requests a stable snapshot explicitly or
avoids depending on repeat-read visibility. If an operation uses a current/locking read, that choice
and its conflict behavior need a separate test.
## 6. Transactions, failures, and retry policy
### 6.1 Transaction states
The backend contract should expose only opaque transaction contexts, but its implementation must
maintain the following lifecycle:
```text
idle
-> active
-> committed
-> rolled_back
-> failed_statement -> rolled_back
-> failed_transaction -> rolled_back
-> outcome_unknown -> reconciled | escalated
```
A context in `committed`, `rolled_back`, `failed_transaction`, or `outcome_unknown` MUST reject new
repository work. A context with a failed statement SHOULD be explicitly rolled back before its
connection returns to the pool, even when MySQL would technically permit more statements.
### 6.2 Error classification matrix
Numeric codes and SQLSTATE values below are MySQL 8.0 server signals. A Node.js driver can also
produce transport-specific codes; those MUST be normalized without leaking raw messages to callers.
| Condition | MySQL signal | Rollback scope | Portable class | Retry policy |
| ------------------------------ | -------------------------------------- | ------------------------------------------------- | ------------------------ | -------------------------------------------------------------- |
| Duplicate key | `1062`, SQLSTATE `23000` | Statement | `unique_violation` | No, unless contract defines idempotent create |
| Missing referenced parent | `1452`, SQLSTATE `23000` | Statement | `foreign_key_violation` | No |
| Parent still referenced | `1451`, SQLSTATE `23000` | Statement | `foreign_key_violation` | No |
| Deadlock victim | `1213`, SQLSTATE `40001` | Entire transaction | `transaction_conflict` | Retry whole atomic operation |
| Lock wait timeout | `1205`, SQLSTATE `HY000` | Statement by default; server option can change it | `lock_timeout` | Roll back explicitly, then retry whole operation if idempotent |
| Invalid JSON text | `3140`, SQLSTATE `22032` | Statement | `invalid_data` | No |
| Data too long | `1406`, SQLSTATE `22001` | Statement | `invalid_data` | No |
| Check constraint | `3819`, SQLSTATE `HY000` | Statement | `constraint_violation` | No |
| Server gone before request | Driver/server transport signal | No operation or unknown | `unavailable` | Retry only if operation definitely was not sent |
| Connection lost during request | Driver transport signal | Unknown | `outcome_unknown` | Reconcile by idempotency key; do not blind retry |
| Pool acquisition timeout | Driver/pool signal | None | `unavailable` | Bounded retry outside transaction |
| Unsupported profile | Initialization probe mismatch | None | `unsupported` | No; fail readiness |
| Migration lock timeout | Named-lock acquisition returns timeout | None | `migration_lock_timeout` | Wait/back off according to startup policy |
| Migration lock error | Named-lock acquisition returns error | None | `migration_lock_failed` | No blind retry; inspect connection state |
The adapter MUST classify by structured code and SQLSTATE where available, never by localized message
text. Public HTTP/SSE/MCP responses must still pass through the repository's existing sanitized error
helpers.
### 6.3 Retry rules
A retryable classification does not automatically make an operation safe to retry.
A retry loop MUST:
1. own the entire repository atomic operation;
2. discard the failed transaction context;
3. acquire a valid connection and begin a new transaction;
4. preserve a stable operation or entity identity;
5. use bounded attempts with jitter;
6. stop on non-retryable classifications;
7. reconcile `outcome_unknown` before issuing another write;
8. emit structured diagnostics without credentials or raw SQL values.
MySQL explicitly recommends retrying the entire transaction after a deadlock. A lock wait timeout
rolls back only the current statement by default, so explicit rollback is required to make the retry
boundary independent of server configuration.
### 6.4 Reproducible two-connection deadlock probe
Use two physical connections, not two logical operations that might share one pool connection:
```sql
CREATE TABLE conformance_deadlock (
id INT PRIMARY KEY,
value_no INT NOT NULL
) ENGINE=InnoDB;
INSERT INTO conformance_deadlock VALUES (1, 0), (2, 0);
```
```text
Connection A Connection B
START TRANSACTION; START TRANSACTION;
UPDATE ... WHERE id = 1; UPDATE ... WHERE id = 2;
UPDATE ... WHERE id = 2; UPDATE ... WHERE id = 1;
```
Exactly one transaction should become the deadlock victim. The harness asserts that the victim is
classified as retryable, its whole transaction is retried with a new context, both logical updates
occur once, and no partial result remains.
## 7. Migration ownership and DDL recovery
### 7.1 Why a normal transaction is insufficient
MySQL DDL statements commonly commit the current transaction implicitly before execution and often
afterward. Atomic DDL protects one supported DDL statement; it does not make a sequence of DDL,
data backfill, and schema-history updates one user transaction.
A MySQL migration runner therefore MUST model a migration as recoverable phases:
```text
lock acquired
-> current schema inspected
-> intent/checkpoint recorded
-> DDL phase applied and verified
-> data phase applied in bounded transactions
-> postconditions verified
-> logical milestone recorded
-> readiness allowed
-> lock released
```
A process crash at any arrow must have a deterministic resume or stop condition.
### 7.2 Ownership alternatives
| Option | Strengths | Failure modes | Decision |
| ------------------------------- | ------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- |
| Process-local mutex | Simple and useful for one process | Does not coordinate replicas | Rejected for external-backend migration ownership |
| Row lock held in a transaction | Uses normal InnoDB locking | DDL implicit commit releases transaction ownership | Rejected as the sole DDL migration lock |
| Lease row with owner and expiry | Survives pooled connections and can support takeover | Requires clock/expiry/fencing design; stale owner may continue | Candidate for scheduled jobs, not first migration mechanism |
| MySQL named lock | Server-wide, exclusive, tied to physical session, released on disconnect | Must pin one connection; not transaction-scoped; one-server scope; undefined waiter order | Recommended first MySQL migration mutex, combined with durable history |
| External coordinator | Can coordinate across database topologies | Adds an operational dependency outside the database contract | Deferred unless deployment topology requires it |
### 7.3 Recommended first mechanism
For a single writable MySQL primary, the migration runner SHOULD:
1. lease and pin one physical connection;
2. acquire one application-and-database-specific named lock of at most 64 characters;
3. distinguish acquired (`1`), timeout (`0`), and error (`NULL`);
4. inspect a durable migration-history table after acquiring the lock;
5. execute idempotent physical phases with explicit postcondition checks;
6. record completion only after all postconditions pass;
7. release the named lock explicitly in `finally`;
8. close/discard the pinned connection if release cannot be confirmed.
Named locks are released when the session ends, not on commit or rollback. They are server-wide on one
`mysqld`; topology and failover behavior must be validated before active-active support is advertised.
A durable history/checkpoint table remains necessary because lock ownership alone says nothing about
partially completed DDL.
### 7.4 Migration failure matrix
| Injection point | Required durable evidence | Restart behavior | Readiness |
| ------------------------------- | --------------------------------------------- | ----------------------------------- | --------------------------------------------- |
| Before lock | No intent | Retry lock acquisition | Not ready while required migration is pending |
| After lock, before intent | No schema change | Reinspect and restart | Not ready |
| After DDL, before checkpoint | Schema postcondition reveals DDL applied | Mark/continue only after validation | Not ready |
| During data backfill | Bounded checkpoint identifies completed range | Resume from verified checkpoint | Not ready |
| After data, before milestone | Postconditions prove completion | Record milestone idempotently | Not ready until recorded |
| After milestone, before release | History proves complete | New owner verifies and proceeds | Ready if all required milestones pass |
## 8. SQLite-to-MySQL migration validation
An offline migration tool is required before database switching can be advertised. For each migrated
domain it MUST provide a dry run and a post-import report.
### 8.1 Preflight
- verify supported SQLite and MySQL schema milestones;
- validate every source JSON payload according to the chosen target representation;
- detect names that collide under the target collation;
- validate UTF-8 and maximum indexed byte lengths;
- detect orphaned foreign keys even if the source connection had checks disabled;
- validate timestamps and numeric ranges;
- count source rows by table and logical domain;
- refuse to mutate either database during dry run.
### 8.2 Import
- preserve application-generated IDs;
- use deterministic batches and checkpoints;
- import parents before children;
- do not use replacement semantics to hide conflicts;
- classify every rejected row with a stable reason;
- keep encrypted credential ciphertext opaque and never log it;
- stop on an unclassified difference.
### 8.3 Postconditions
- row counts match for every migrated table;
- identity sets match exactly;
- foreign-key orphan counts are zero;
- canonical domain digests match for JSON-backed records;
- list ordering and mapping resolution produce the same results;
- a second dry run reports no pending changes;
- SQLite remains unchanged and available for operator rollback until cutover is accepted.
## 9. Backend-neutral conformance catalog
Each test below runs the same repository fixture against SQLite and MySQL. MySQL-specific probes may
assert error metadata internally, but the shared assertion compares only domain results and durable
state.
### 9.1 Core CRUD and representation
| Test name | Fixture/action | Required assertion |
| --------------------------------------------- | ----------------------------------------------------------------- | ------------------------------------------------------ |
| `create_round_trips_domain_values` | Create Unicode, nullable, JSON, and timestamp fields | Parsed domain object equals normalized input |
| `find_missing_distinguishes_absent_from_null` | Read an absent ID and a present nullable row | Results are distinct |
| `update_missing_returns_not_found` | Update an absent ID | Stable `not_found` result |
| `delete_is_idempotent_as_declared` | Delete the same ID twice | First and second results match the repository contract |
| `json_round_trips_structurally` | Write equivalent JSON with different whitespace/order | Parsed values are equal; raw text is not asserted |
| `timestamp_round_trips_in_utc` | Change MySQL session default before leasing a verified connection | Domain serialization remains canonical UTC |
| `decimal_round_trips_without_float_loss` | Write precision/scale boundaries | Exact representation is unchanged |
| `large_integer_does_not_cross_number_lossily` | Write beyond JavaScript safe integer range | String/bigint domain representation is exact |
### 9.2 Identity and collation
| Test name | Fixture/action | Required assertion |
| ---------------------------------------------- | ---------------------------------------------------------- | ---------------------------------------------------------- |
| `id_is_byte_exact` | Create IDs differing only by case | Both remain distinct if the ID contract is binary |
| `exact_name_lookup_is_case_sensitive` | Store `MASTER-LIGHT`, query exact lowercase | Exact lookup misses |
| `insensitive_name_lookup_uses_declared_policy` | Query the same row through the named insensitive operation | One deterministic row is returned |
| `unique_name_case_policy_is_explicit` | Insert case variants | Result matches the selected name policy on both backends |
| `unique_name_accent_policy_is_explicit` | Insert accent variants | Result matches the selected policy |
| `unique_violation_is_classified` | Concurrently create one identity | One wins; loser is `unique_violation` without backend text |
| `nullable_unique_policy_is_explicit` | Insert two `NULL` logical keys | Result matches domain rule, not accidental index behavior |
### 9.3 Ordering and pagination
| Test name | Fixture/action | Required assertion |
| --------------------------------------------------- | ---------------------------------------------- | ------------------------------------------------------ |
| `list_uses_unique_final_tiebreaker` | Insert rows with identical primary sort values | Repeated list order is identical and ID-ordered |
| `pagination_has_no_gaps_or_duplicates` | Traverse small pages across tied rows | Union equals full ID set; page intersections are empty |
| `nullable_sort_position_is_fixed` | Mix `NULL` and non-`NULL` values | `NULL` appears at the contract-defined end |
| `cursor_predicate_matches_sort_tuple` | Page forward through mixed sort keys | Every row appears exactly once in declared order |
| `concurrent_insert_pagination_behavior_is_declared` | Insert between page reads | Result matches snapshot or documented live-page policy |
### 9.4 Writes and affected rows
| Test name | Fixture/action | Required assertion |
| ------------------------------------------- | ------------------------------------------ | -------------------------------------------------- |
| `same_value_update_is_not_missing` | Update an existing row to identical values | `unchanged` or declared success, never `not_found` |
| `same_value_result_ignores_found_rows_mode` | Run fixture with both connection modes | Domain result is identical |
| `compare_update_detects_stale_version` | Two writers use one old version | One succeeds; one returns `conflict` |
| `batch_count_uses_contract_definition` | Mix changed and unchanged matches | Count means the same thing on both backends |
| `upsert_preserves_identity_and_children` | Upsert parent with a child row | ID, immutable fields, and child survive |
| `insert_only_never_silently_updates` | Repeat insert-only identity | Second call is `unique_violation` |
### 9.5 Transactions, isolation, and failure injection
| Test name | Fixture/action | Required assertion |
| ----------------------------------------------- | ----------------------------------------------------------------- | ---------------------------------------------------------------------- |
| `related_changes_commit_atomically` | Update parent and children | All postconditions commit together |
| `related_changes_roll_back_atomically` | Inject a child constraint failure | All tables equal pre-operation state |
| `stable_snapshot_behavior_is_declared` | Read, commit a concurrent update, then read in the same operation | Result follows the operation's declared snapshot/current-read policy |
| `range_insert_visibility_is_declared` | Read a range while another transaction inserts a matching row | Later visibility matches the declared snapshot/live policy |
| `read_modify_write_prevents_lost_update` | Two transactions read one version and attempt distinct updates | One declared winner; loser conflicts/retries without overwriting |
| `independent_writers_preserve_atomic_effects` | Two transactions update different identities concurrently | Both logical effects commit; no contract depends on backend lock order |
| `deadlock_retries_whole_operation` | Two physical connections lock in opposite order | One victim; final logical effect occurs once |
| `lock_timeout_discards_context` | Hold a row lock past timeout | Explicit rollback; old context rejects work |
| `duplicate_and_foreign_key_errors_are_distinct` | Trigger each constraint | Stable distinct classes |
| `disconnect_before_send_is_unavailable` | Fail connection before dispatch | Safe bounded retry is permitted |
| `disconnect_during_commit_is_outcome_unknown` | Drop connection at commit boundary | No blind retry; reconciliation is required |
| `retry_uses_stable_operation_identity` | Fail first attempt after durable write | At most one logical effect exists |
### 9.6 Migration and readiness
| Test name | Fixture/action | Required assertion |
| --------------------------------------- | ------------------------------------------- | -------------------------------------------------- |
| `only_one_instance_owns_migration` | Two backend instances acquire one name | Exactly one executes migration phases |
| `lock_timeout_is_not_reported_as_ready` | Hold migration lock from another connection | Startup waits/fails with classified state |
| `disconnect_releases_named_lock` | Terminate owner connection | Another instance can acquire and reinspect |
| `ddl_checkpoint_recovers_after_crash` | Stop after DDL before history update | Restart detects postcondition and continues safely |
| `backfill_resumes_without_duplication` | Stop between deterministic batches | Completed rows are neither skipped nor duplicated |
| `partial_migration_blocks_readiness` | Leave required milestone incomplete | Health may be alive; readiness is false |
| `completed_history_is_idempotent` | Start against fully migrated schema | No DDL/data mutation occurs |
## 10. First-slice acceptance profile: combos and model mappings
This section specializes the general catalog for the candidate first slice discussed in #8075 and
implemented experimentally in Draft PR #8757. It does not approve that runtime PR.
### 10.1 Contract decisions required before adapter code
| Decision | Current evidence | Required resolution |
| --------------------- | ------------------------------------------------------ | ------------------------------------------------------------------------------------------- |
| Combo ID | Application UUID | Preserve as byte-exact text/binary identity |
| Combo name uniqueness | SQLite unique name; exact and insensitive reads differ | Select explicit uniqueness collation independently from insensitive fallback |
| Combo list | `sort_order`, then `name NOCASE` | Add `id` as final tie-breaker and define Unicode name order |
| Next sort order | `MAX(sort_order) + 1` | Replace race-prone read-then-insert with an atomic allocation or retryable unique invariant |
| Reorder | One SQLite transaction updates all parseable rows | Define concurrent reorder serialization and all-or-nothing behavior |
| Corrupt combo JSON | Reads/resolution skip malformed payloads | Decide whether MySQL schema can represent malformed legacy rows during migration |
| Mapping order | `priority DESC, created_at ASC` | Add `id ASC` final tie-breaker |
| Mapping delete | Boolean from affected rows | Preserve `true` then `false` behavior independent of found-rows mode |
| Combo delete | Foreign key cascade removes mappings | Preserve one-operation atomic cascade |
| Timestamps | Application ISO strings | Preserve canonical UTC text or define an exact typed conversion |
### 10.2 Required combo fixtures
The shared fixture MUST include:
- combo names `Alpha`, `alpha`, `Résumé`, and `resume` to exercise selected collation policy;
- three combos with the same requested `sortOrder` to exercise the unique final order;
- one missing ID for update and delete results;
- one payload with explicit JSON `null` and one with a missing member;
- one intentionally malformed legacy payload if compatibility requires it;
- mappings with identical `priority` and `createdAt` but different IDs;
- enabled, disabled, inactive-target, and corrupt-target mappings;
- one combo with at least two dependent mappings for cascade verification.
### 10.3 Required combo assertions
A MySQL implementation cannot claim the first slice complete until the shared harness proves:
1. application UUIDs and ISO timestamps round-trip unchanged;
2. exact and insensitive combo-name lookups remain distinct operations;
3. uniqueness follows the approved name policy, not server defaults;
4. combo and mapping lists have a total deterministic order;
5. every offset page is a contiguous slice of that order;
6. update of a missing combo/mapping returns `null`;
7. first delete returns `true`, repeated delete returns `false`;
8. reorder filters unknown/duplicate requested IDs exactly as the accepted contract specifies;
9. reorder either commits every intended row or none;
10. mapping resolution uses the deterministic order and skips disabled, inactive, and malformed targets;
11. deleting a combo atomically removes all dependent mappings;
12. errors are classified without raw MySQL messages;
13. SQLite starts without loading a MySQL dependency;
14. no external-backend support is advertised by the presence of this slice alone.
### 10.4 Concurrency probes specific to the slice
#### Concurrent combo creation
Two connections create different UUIDs with the same contract-equivalent name. Exactly one succeeds;
the other receives `unique_violation`. If case/accent variants are allowed by the approved policy,
both succeed and exact lookup returns the correct identity.
#### Concurrent sort allocation
Two connections create combos without an explicit sort order. The final values MUST follow the
contract without duplicates caused by both transactions reading the same `MAX(sort_order)`. The
implementation may serialize allocation, use a separate sequence, or retry a protected invariant;
the contract must not require one specific SQL mechanism.
#### Concurrent reorder
Two connections reorder the same set in opposite orders. The accepted outcome MUST be one complete
order or the other, never a mixed sequence or mismatched JSON/column `sortOrder`. The loser may wait,
return conflict, or retry according to the approved contract.
#### Delete versus mapping creation
One connection deletes a combo while another creates a mapping to it. The final state MUST be either
an existing combo with a valid mapping or no combo and no mapping. An orphan mapping is forbidden.
## 11. Implementation gate checklist
A MySQL adapter PR for any domain MUST NOT start until reviewers can answer all applicable items:
- [ ] Identity, case, accent, and collation semantics are explicit.
- [ ] Every list has a complete order, `NULL` position, and unique tie-breaker.
- [ ] Missing, unchanged, conflict, and delete results are distinguishable.
- [ ] Every write is classified as insert-only, identity-preserving upsert, or replacement.
- [ ] ID generation and idempotency ownership are explicit.
- [ ] JSON and temporal representations are selected with migration compatibility in mind.
- [ ] Error codes map to the backend-neutral taxonomy.
- [ ] Retry ownership and maximum scope are explicit.
- [ ] Migration mutex, durable checkpoints, and readiness rules are approved.
- [ ] SQLite and MySQL fixtures run through one behavior harness.
- [ ] Offline migration preflight and postconditions exist before cutover is advertised.
- [ ] SQLite remains the zero-configuration default and clean startup path.
## 12. Reference sources
### 12.1 OmniRoute sources
- `docs/architecture/persistence-backend-boundary.md`
- `docs/architecture/sqlite-coupling-inventory.md`
- `src/lib/db/combos.ts`
- `src/lib/db/modelComboMappings.ts`
- `src/lib/db/migrations/001_initial_schema.sql`
- `src/lib/db/migrations/010_model_combo_mappings.sql`
- `src/lib/db/migrations/020_combo_sort_order.sql`
### 12.2 MySQL 8.0 reference manual
- [Character sets and collations](https://docs.oracle.com/cd/E17952_01/mysql-8.0-en/charset.html)
- [CREATE TABLE](https://docs.oracle.com/cd/E17952_01/mysql-8.0-en/create-table.html)
- [UPDATE](https://docs.oracle.com/cd/E17952_01/mysql-8.0-en/update.html)
- [INSERT ... ON DUPLICATE KEY UPDATE](https://docs.oracle.com/cd/E17952_01/mysql-8.0-en/insert-on-duplicate.html)
- [Information functions](https://docs.oracle.com/cd/E17952_01/mysql-8.0-en/information-functions.html)
- [The JSON data type](https://docs.oracle.com/cd/E17952_01/mysql-8.0-en/json.html)
- [InnoDB transaction isolation](https://docs.oracle.com/cd/E17952_01/mysql-8.0-en/innodb-transaction-isolation-levels.html)
- [InnoDB error handling](https://docs.oracle.com/cd/E17952_01/mysql-8.0-en/innodb-error-handling.html)
- [Handling deadlocks](https://docs.oracle.com/cd/E17952_01/mysql-8.0-en/innodb-deadlocks-handling.html)
- [Statements that cause an implicit commit](https://docs.oracle.com/cd/E17952_01/mysql-8.0-en/implicit-commit.html)
- [Locking functions](https://docs.oracle.com/cd/E17952_01/mysql-8.0-en/locking-functions.html)
- [InnoDB limits](https://docs.oracle.com/cd/E17952_01/mysql-8.0-en/innodb-limits.html)
### 12.3 SQLite references
- [ON CONFLICT](https://sqlite.org/lang_conflict.html)
- [`NULL` handling](https://sqlite.org/nulls.html)
- [Transactions](https://sqlite.org/lang_transaction.html)
- [SELECT and ordering](https://sqlite.org/lang_select.html#orderby)
## 13. Open decisions
This specification deliberately leaves the following decisions to the accepted first-slice design:
1. the exact collation and normalization policy for combo names;
2. the typed or text representation of combo JSON in MySQL;
3. the repository result type for an existing same-value update;
4. the isolation level selected by the backend profile;
5. the concurrency mechanism for sort-order allocation and reorder;
6. the physical MySQL migration schema and durable checkpoint format;
7. the exact retry budget and backoff policy;
8. the topology boundary within which a MySQL named migration lock is sufficient.
These are not adapter implementation details. Each changes observable behavior or operational
correctness and therefore requires explicit review before runtime support proceeds.

View File

@@ -1,226 +0,0 @@
---
title: "SQLite coupling inventory"
status: measured-snapshot
lastUpdated: 2026-07-23
---
# SQLite coupling inventory
- **Tracking issue:** [#8075](https://github.com/diegosouzapw/OmniRoute/issues/8075)
- **Snapshot revision:** `9a3b605f3420ae3ab08bd93d6443034f03a1bcbc`
- **Scanned-corpus SHA-256:** `72334620a7a18a42bcede1643fb2fdf95da6eae9ffa66a891ae14ed633ad43f6`
- **Purpose:** Measure the current persistence cut lines before proposing repository interfaces
- **Runtime impact:** None; this document and its audit script do not change database behavior
## How to reproduce
From the repository root:
```bash
node scripts/check/audit-sqlite-coupling.mjs
node scripts/check/audit-sqlite-coupling.mjs --json
node --test scripts/check/audit-sqlite-coupling.test.mjs
```
The script reads tracked files from Git, scans non-test source under `src/`, `open-sse/`,
`electron/`, and `bin/`, and scans migration SQL under `src/lib/db/migrations/`. It excludes the
top-level test tree, co-located test directories, test/spec source files, and paths outside those
configured source roots (including documentation and scripts).
The script refuses to run if tracked files in those source roots differ from `HEAD`. It reports
both the audit-tool revision and a SHA-256 over the ordered path/content corpus. The snapshot above
was taken from the listed source revision; this PR changes only excluded documentation and script
paths, so rerunning from the clean PR branch produces the same corpus digest.
This is a **lexical inventory**, not a TypeScript or SQL semantic analysis:
- counts are occurrences of defined patterns, not counts of distinct SQL statements;
- adapter-call and direct-singleton patterns mask comments and literal contents first;
- template-literal contents, including embedded expressions, are excluded from those code-syntax
counts;
- the lightweight masker is not a JavaScript parser, so unusual regular-expression literal syntax
can still require manual review;
- comments and string literals can contribute to dialect-signal counts, which intentionally search
raw text for embedded SQL;
- a `.prepare()` match outside `src/lib/db/` is a review lead, not proof that the call should move;
- calls hidden behind a differently named wrapper may not be counted;
- file counts are deduplicated, while occurrence counts are not.
The JSON output includes every matching path so reviewers can inspect or reclassify individual
results rather than trusting totals alone.
## Snapshot scope
At the recorded revision, the script scanned:
- 3,830 tracked non-test source files;
- 129 migration SQL files.
The source-file count is intentionally broad because the goal is to find persistence coupling that
has escaped the nominal database directory, including CLI and proxy/runtime code.
## Boundary signals
| Signal | Files | Occurrences |
| -------------------------------------------------------------------------------- | ----: | ----------: |
| Direct `getDbInstance()` call syntax outside comments/literals and `src/lib/db/` | 45 | 150 |
| `localDb` import consumers | 211 | — |
| `SqliteAdapter` type consumers outside comments/literals and `src/lib/db/` | 3 | — |
The `localDb` barrel already gives many callers a domain-function seam, but
`src/lib/localDb.ts` remains a re-export layer rather than a backend contract. The 45 direct
singleton consumers are the clearest first review set because they bypass that logical seam and
hold an adapter-shaped handle directly.
The three non-test source files outside `src/lib/db/` that mention the `SqliteAdapter` type in code
syntax are:
- `src/app/api/db-backups/import/route.ts`;
- `src/lib/compliance/index.ts`;
- `src/lib/compliance/noLog.ts`.
These are not equivalent migration tasks. Backup import is capability-specific; compliance
persistence may be portable domain state. The future boundary should classify them rather than
moving all three mechanically.
## Adapter-shaped call syntax
| Signal | Occurrences | Files | Outside `src/lib/db/` occurrences | Outside files |
| ----------------- | ----------: | ----: | --------------------------------: | ------------: |
| `.prepare()` | 1,219 | 163 | 252 | 52 |
| `.transaction()` | 62 | 40 | 12 | 10 |
| `.immediate()` | 3 | 3 | 0 | 0 |
| `.pragma()` | 39 | 11 | 6 | 4 |
| `.backup()` | 6 | 5 | 3 | 3 |
| `.checkpoint()` | 0 | 0 | 0 | 0 |
| `lastInsertRowid` | 15 | 7 | 1 | 1 |
This table shows why `SqliteAdapter` is a SQLite runtime compatibility layer rather than a portable
backend abstraction. Its synchronous statement and transaction shape is widely used, and some of
that shape is visible outside the nominal database layer.
The top direct `getDbInstance()` consumers outside `src/lib/db/` at this revision are:
| File | Occurrences |
| -------------------------------------------------- | ----------: |
| `src/lib/proxySubscription/subscriptionService.ts` | 12 |
| `src/lib/semanticCache.ts` | 10 |
| `src/lib/usage/callLogs.ts` | 9 |
| `src/lib/cloudAgent/db.ts` | 8 |
| `src/lib/memory/store.ts` | 8 |
| `src/lib/memory/vectorStore.ts` | 8 |
| `src/lib/modelsDevSync.ts` | 8 |
| `src/lib/gamification/badges.ts` | 5 |
| `src/lib/memory/retrieval.ts` | 5 |
| `src/lib/pricingSync.ts` | 5 |
| `src/lib/skills/registry.ts` | 5 |
| `src/lib/usage/usageHistory.ts` | 5 |
The list spans control-plane configuration, usage/audit data, cache, memory/vector search, skills,
gamification, and CLI/provider support. A single generic SQL adapter would preserve this spread;
domain repositories provide a way to reduce it slice by slice.
## SQLite dialect and lifecycle signals
| Signal | Occurrences | Files |
| --------------------- | ----------: | ----: |
| `PRAGMA` text | 97 | 41 |
| `sqlite_master` | 14 | 11 |
| `BEGIN IMMEDIATE` | 2 | 2 |
| `INSERT OR REPLACE` | 83 | 45 |
| `AUTOINCREMENT` | 34 | 24 |
| `datetime('now')` | 171 | 68 |
| `VACUUM` | 39 | 10 |
| `wal_checkpoint` | 13 | 7 |
| `fts5` | 43 | 8 |
| `vec0` | 7 | 1 |
| `last_insert_rowid()` | 1 | 1 |
These values are text signals and include comments where present. They are useful for locating
portability work, not for estimating implementation effort by multiplication.
Verified high-coupling areas include:
- `src/lib/db/core.ts`: singleton lifecycle, SQLite file paths, WAL checkpoint, recovery, schema,
compaction, and backup creation;
- `src/lib/db/migrationRunner.ts`: numbered SQL migration execution, `sqlite_master`,
`PRAGMA table_info`, transaction behavior, and optional FTS5 handling;
- `src/lib/db/optimizationSettings.ts`: page/cache settings, auto-vacuum, WAL transitions, and
`VACUUM`;
- `src/lib/db/backup.ts`: database backup and restore lifecycle;
- `src/lib/db/schemaColumns.ts`: SQLite schema introspection and compatibility columns;
- `src/lib/memory/vectorStore.ts` and `src/lib/memory/retrieval.ts`: `vec0` and FTS5 behavior;
- `src/lib/db/adapters/`: compatibility implementations for the supported SQLite runtimes.
These areas should not be forced through a lowest-common-denominator repository interface. They
need explicit SQLite capabilities or separate backend implementations.
## Migration coupling
The snapshot contains 129 tracked migration SQL files. `src/lib/db/migrationRunner.ts` does more
than execute ordered files: it owns migration discovery, version history, duplicate-version safety,
schema probes, FTS5 capability checks, pre-migration safety, and SQLite transaction execution.
Consequently:
- another SQL dialect cannot safely reuse the migration files unchanged;
- external backends need their own migration implementation and schema history;
- logical migration milestones may be shared, but physical SQL and capability probes remain
backend-specific;
- multi-replica operation requires migration ownership or locking before an external backend is
considered ready.
## Recommended cut lines
### 1. Keep SQLite runtime compatibility intact
Do not replace `SqliteAdapter` or the driver cascade in the first repository PR. Keep file recovery,
WAL, backup, optimization, FTS5, and vector behavior behind the current SQLite implementation.
### 2. Start with direct singleton consumers
Use the 45-file direct-consumer list as the initial review queue. Classify each file as:
- portable domain state;
- backend-specific maintenance or search;
- process-local or rebuildable state;
- legacy access that should call an existing domain module.
Classification must precede interface design. A path appearing in the inventory is not, by itself,
a mandate to create a repository.
### 3. Prove repositories with SQLite first
For one bounded domain:
1. define behavior-oriented repository operations;
2. adapt current SQLite queries behind that repository;
3. run behavior and transaction conformance tests against SQLite;
4. migrate callers without changing the default runtime;
5. only then implement the same repository for an external backend.
### 4. Separate portable control-plane state from capability-specific data
Provider connections, API keys, combos, and routing configuration are candidates for the first
portable slice, subject to maintainer approval and a table-ownership review. Memory vector search,
SQLite file backup/recovery, and database optimization are poor first slices because their behavior
is deliberately SQLite-specific.
### 5. Treat usage, quota, affinity, and audit as a later coordination slice
These domains have concurrency and volume semantics beyond CRUD. Their repository contracts should
be designed together with multi-replica transaction, lease, retention, and failure-mode tests rather
than copied mechanically from current SQL.
## What this inventory does not decide
This inventory does not:
- approve PostgreSQL or MySQL support;
- define repository TypeScript interfaces;
- choose the first table or domain to migrate;
- claim every lexical match is a defect;
- claim the current module boundaries are ineffective;
- change SQLite, migrations, backup, search, or runtime behavior.
Its purpose is to make the next design discussion evidence-based and reproducible.

View File

@@ -6,6 +6,7 @@
"RTK_COMPRESSION",
"COMPRESSION_LANGUAGE_PACKS",
"COMPRESSION_RULES_FORMAT",
"CONTEXT_EDITING",
"EXTENDING_COMPRESSION"
]
}

View File

@@ -888,3 +888,228 @@ To leave it off, simply keep `autoSummarize` at its default (`false`).
0 3 * * * curl -X POST http://localhost:20128/api/memory/summarize \
-H "Authorization: Bearer $OMNIROUTE_KEY"
```
---
## MemoryBackend Provider Pattern
> **Source of truth:** `src/lib/memory/backend.ts`, `src/lib/memory/genericBackend.ts`, `src/lib/memory/manager.ts`
> **Tests:** `src/lib/memory/__tests__/generic-backend.test.ts`
The MemoryBackend provider pattern introduces a **pluggable backend abstraction layer** over the existing memory engine. Instead of being tied to a single storage implementation, the memory system now supports multiple backends (SQLite, Obsidian, Notion, custom HTTP backends) with configurable primary/fallback routing.
### Architecture
```
┌──────────────────────────────────────────────────────────┐
│ API Routes │
│ (src/app/api/memory/route.ts) │
└──────────────────────┬───────────────────────────────────┘
┌──────────────────────▼───────────────────────────────────┐
│ MemoryManager │
│ Singleton orchestrator (manager.ts) │
│ │
│ Primary ──► Backend A (e.g. SQLite) │
│ Fallback ─► Backend B (e.g. Obsidian) │
│ Backend C (e.g. Notion via GenericBackend) │
└──────────────────────┬───────────────────────────────────┘
┌──────────────┼──────────────┐
▼ ▼ ▼
┌────────────┐ ┌────────────┐ ┌──────────────────┐
│ SQLite │ │ Obsidian │ │ GenericMemory │
│ Backend │ │ Backend │ │ Backend (HTTP) │
└────────────┘ └────────────┘ └──────────────────┘
```
#### Core Interface (`backend.ts`)
Every backend must implement the `MemoryBackend` interface:
```typescript
interface MemoryBackend {
readonly id: string;
readonly displayName: string;
// CRUD
create(input: CreateMemoryInput): Promise<Memory>;
get(id: string): Promise<Memory | null>;
update(id: string, updates: Partial<...>): Promise<boolean>;
delete(id: string): Promise<boolean>;
list(filter: MemoryFilter): Promise<{ data: Memory[]; total: number; byType: Record<string, number> }>;
// Search
search(config: SearchConfig): Promise<Memory[]>;
// Health
health(): Promise<HealthCheckResult>;
// Lifecycle (optional)
initialize?(): Promise<void>;
shutdown?(): Promise<void>;
}
```
#### MemoryManager (`manager.ts`)
Singleton orchestrator that:
- **Registers** backends via `register(backend)` — called at boot from `index.ts`
- **Configures** primary + fallback via `configure(primary, fallbacks)`
- **Routes** CRUD/search to the primary, with fallback chain on failure
- **Health checks** all backends periodically
**Fallback behavior:**
| Operation | Primary | Fallbacks |
| --------- | -------------------- | ----------------------- |
| `create` | ✅ Primary only | ❌ |
| `get` | ✅ Try primary first | ✅ Fallback if null |
| `update` | ✅ Primary only | ✅ Fire-and-forget sync |
| `delete` | ✅ Primary only | ✅ Fire-and-forget sync |
| `list` | ✅ Primary only | ❌ |
| `search` | ✅ Primary first | ✅ Fallback on error |
#### GenericMemoryBackend (`genericBackend.ts`)
A generic HTTP connector that adapts any REST API into a MemoryBackend. Useful for:
- **Notion** — connect via Notion API
- **Obsidian** — connect via Obsidian Local REST API
- **Custom backends** — any service that exposes a RESTful memory API
**Configuration:**
```typescript
interface GenericBackendConfig {
baseUrl: string; // Base URL of the backend API
apiKey?: string; // Bearer token for auth
headers?: Record<string, string>; // Custom HTTP headers
timeout?: number; // Request timeout (default: 30000ms)
backendType?: string; // For logging
// Endpoint overrides (defaults use REST conventions)
endpoints?: {
search?: string; // default: "/memories/search"
create?: string; // default: "/memories"
list?: string; // default: "/memories"
get?: string; // default: "/memories/{id}"
update?: string; // default: "/memories/{id}"
delete?: string; // default: "/memories/{id}"
health?: string; // default: "/health"
};
// Query parameter name mappings
queryParams?: {
query?/apiKeyId?/limit?/offset?/strategy?/maxTokens?/type?/sessionId?/orderBy?/orderDir?/options?
};
// Path parameter name mappings
pathParams?: {
id?/memoryId?
};
}
```
**Known backends** are pre-configured in `KNOWN_BACKENDS`:
```typescript
createKnownBackend("obsidian"); // → GenericMemoryBackend pointed at localhost:27123
createKnownBackend("notion"); // → GenericMemoryBackend pointed at api.notion.com/v1
```
#### Built-in Backends
##### SQLiteBackend (`sqliteBackend.ts`)
The default primary backend. Wraps the existing SQLite-based memory store using `src/lib/memory/store.ts`. Automatically registered at boot.
```typescript
import { sqliteBackend } from "./sqliteBackend";
memoryManager.register(sqliteBackend);
```
##### ObsidianBackend (`obsidianBackend.ts`)
Wraps the existing Obsidian integration (`src/lib/memory/obsidianBackend.ts`). Connects to an Obsidian vault via the Obsidian Local REST API.
### Settings
Memory backend settings are stored in the app settings table and managed via `src/lib/memory/settings.ts`:
| Setting | Env/Config Key | Default | Description |
| ----------------- | ------------------------ | ---------- | ---------------------------- |
| Primary backend | `memoryPrimaryBackend` | `"sqlite"` | ID of the primary backend |
| Fallback backends | `memoryFallbackBackends` | `[]` | Ordered fallback backend IDs |
| Backend configs | `memoryBackendConfigs` | `{}` | Per-backend config overrides |
Settings are normalized via `normalizeMemorySettings()` and cached at `getMemorySettings()`.
### Initialization Flow
```
App bootstrap
→ index.ts imports (side-effect): registers SQLiteBackend
→ initMemoryBackends() called from app lifecycle:
1. Load settings (getMemorySettings)
2. Configure primary + fallback
3. Initialize all backends (health check)
4. Ready for requests
```
### Adding a New Backend
1. **Implement `MemoryBackend`** interface in `src/lib/memory/<name>Backend.ts`
2. **Export** from `src/lib/memory/index.ts`
3. **Register** with `memoryManager.register(yourBackend)` at boot
4. **Configure** via settings: set `memoryPrimaryBackend` to your backend ID
5. **Test** with `src/lib/memory/__tests__/generic-backend.test.ts` as reference
#### Example: Brain Backend
```typescript
import { createGenericMemoryBackend } from "./genericBackend";
const brainBackend = createGenericMemoryBackend("brain", "BK-Brain", {
baseUrl: process.env.BRAIN_API_URL || "http://localhost:9099",
apiKey: process.env.BRAIN_API_KEY,
endpoints: {
search: "/api/memory/search",
create: "/api/memory",
health: "/api/health",
},
});
memoryManager.register(brainBackend);
```
### Verification
#### Unit tests
```bash
npx vitest run src/lib/memory/__tests__/generic-backend.test.ts --reporter=verbose
```
Expected output: **26 tests, all passing** covering:
- Constructor (2)
- Health check (4) — success, failure 500, network error, latency
- Initialize (2) — success, failure
- Create (2) — default endpoint, custom endpoint
- Get (4) — success, 404 → null, non-404 throw, custom path params
- Update (2) — success, 404 → false
- Delete (2) — success, 404 → false
- List (2) — query params, custom param names
- Search (3) — query params, custom endpoint, options serialization
- Auth headers (2) — Bearer token, custom headers
- Factory (1)
#### Type check
```bash
npm run typecheck:core
```
Expected: **0 errors**.

View File

@@ -1,228 +0,0 @@
---
title: "MemoryBackend Provider Pattern"
version: 3.8.49
lastUpdated: 2026-07-28
---
# MemoryBackend Provider Pattern
> **Source of truth:** `src/lib/memory/backend.ts`, `src/lib/memory/genericBackend.ts`, `src/lib/memory/manager.ts`
> **Tests:** `src/lib/memory/__tests__/generic-backend.test.ts`
The MemoryBackend provider pattern introduces a **pluggable backend abstraction layer** over the existing memory engine. Instead of being tied to a single storage implementation, the memory system now supports multiple backends (SQLite, Obsidian, Notion, custom HTTP backends) with configurable primary/fallback routing.
## Architecture
```
┌──────────────────────────────────────────────────────────┐
│ API Routes │
│ (src/app/api/memory/route.ts) │
└──────────────────────┬───────────────────────────────────┘
┌──────────────────────▼───────────────────────────────────┐
│ MemoryManager │
│ Singleton orchestrator (manager.ts) │
│ │
│ Primary ──► Backend A (e.g. SQLite) │
│ Fallback ─► Backend B (e.g. Obsidian) │
│ Backend C (e.g. Notion via GenericBackend) │
└──────────────────────┬───────────────────────────────────┘
┌──────────────┼──────────────┐
▼ ▼ ▼
┌────────────┐ ┌────────────┐ ┌──────────────────┐
│ SQLite │ │ Obsidian │ │ GenericMemory │
│ Backend │ │ Backend │ │ Backend (HTTP) │
└────────────┘ └────────────┘ └──────────────────┘
```
### Core Interface (`backend.ts`)
Every backend must implement the `MemoryBackend` interface:
```typescript
interface MemoryBackend {
readonly id: string;
readonly displayName: string;
// CRUD
create(input: CreateMemoryInput): Promise<Memory>;
get(id: string): Promise<Memory | null>;
update(id: string, updates: Partial<...>): Promise<boolean>;
delete(id: string): Promise<boolean>;
list(filter: MemoryFilter): Promise<{ data: Memory[]; total: number; byType: Record<string, number> }>;
// Search
search(config: SearchConfig): Promise<Memory[]>;
// Health
health(): Promise<HealthCheckResult>;
// Lifecycle (optional)
initialize?(): Promise<void>;
shutdown?(): Promise<void>;
}
```
### MemoryManager (`manager.ts`)
Singleton orchestrator that:
- **Registers** backends via `register(backend)` — called at boot from `index.ts`
- **Configures** primary + fallback via `configure(primary, fallbacks)`
- **Routes** CRUD/search to the primary, with fallback chain on failure
- **Health checks** all backends periodically
**Fallback behavior:**
| Operation | Primary | Fallbacks |
| --------- | -------------------- | ----------------------- |
| `create` | ✅ Primary only | ❌ |
| `get` | ✅ Try primary first | ✅ Fallback if null |
| `update` | ✅ Primary only | ✅ Fire-and-forget sync |
| `delete` | ✅ Primary only | ✅ Fire-and-forget sync |
| `list` | ✅ Primary only | ❌ |
| `search` | ✅ Primary first | ✅ Fallback on error |
### GenericMemoryBackend (`genericBackend.ts`)
A generic HTTP connector that adapts any REST API into a MemoryBackend. Useful for:
- **Notion** — connect via Notion API
- **Obsidian** — connect via Obsidian Local REST API
- **Custom backends** — any service that exposes a RESTful memory API
**Configuration:**
```typescript
interface GenericBackendConfig {
baseUrl: string; // Base URL of the backend API
apiKey?: string; // Bearer token for auth
headers?: Record<string, string>; // Custom HTTP headers
timeout?: number; // Request timeout (default: 30000ms)
backendType?: string; // For logging
// Endpoint overrides (defaults use REST conventions)
endpoints?: {
search?: string; // default: "/memories/search"
create?: string; // default: "/memories"
list?: string; // default: "/memories"
get?: string; // default: "/memories/{id}"
update?: string; // default: "/memories/{id}"
delete?: string; // default: "/memories/{id}"
health?: string; // default: "/health"
};
// Query parameter name mappings
queryParams?: {
query?/apiKeyId?/limit?/offset?/strategy?/maxTokens?/type?/sessionId?/orderBy?/orderDir?/options?
};
// Path parameter name mappings
pathParams?: {
id?/memoryId?
};
}
```
**Known backends** are pre-configured in `KNOWN_BACKENDS`:
```typescript
createKnownBackend("obsidian"); // → GenericMemoryBackend pointed at localhost:27123
createKnownBackend("notion"); // → GenericMemoryBackend pointed at api.notion.com/v1
```
### Built-in Backends
#### SQLiteBackend (`sqliteBackend.ts`)
The default primary backend. Wraps the existing SQLite-based memory store using `src/lib/memory/store.ts`. Automatically registered at boot.
```typescript
import { sqliteBackend } from "./sqliteBackend";
memoryManager.register(sqliteBackend);
```
#### ObsidianBackend (`obsidianBackend.ts`)
Wraps the existing Obsidian integration (`src/lib/memory/obsidianBackend.ts`). Connects to an Obsidian vault via the Obsidian Local REST API.
## Settings
Memory backend settings are stored in the app settings table and managed via `src/lib/memory/settings.ts`:
| Setting | Env/Config Key | Default | Description |
| ----------------- | ------------------------ | ---------- | ---------------------------- |
| Primary backend | `memoryPrimaryBackend` | `"sqlite"` | ID of the primary backend |
| Fallback backends | `memoryFallbackBackends` | `[]` | Ordered fallback backend IDs |
| Backend configs | `memoryBackendConfigs` | `{}` | Per-backend config overrides |
Settings are normalized via `normalizeMemorySettings()` and cached at `getMemorySettings()`.
## Initialization Flow
```
App bootstrap
→ index.ts imports (side-effect): registers SQLiteBackend
→ initMemoryBackends() called from app lifecycle:
1. Load settings (getMemorySettings)
2. Configure primary + fallback
3. Initialize all backends (health check)
4. Ready for requests
```
## Adding a New Backend
1. **Implement `MemoryBackend`** interface in `src/lib/memory/<name>Backend.ts`
2. **Export** from `src/lib/memory/index.ts`
3. **Register** with `memoryManager.register(yourBackend)` at boot
4. **Configure** via settings: set `memoryPrimaryBackend` to your backend ID
5. **Test** with `src/lib/memory/__tests__/generic-backend.test.ts` as reference
### Example: Brain Backend
```typescript
import { createGenericMemoryBackend } from "./genericBackend";
const brainBackend = createGenericMemoryBackend("brain", "BK-Brain", {
baseUrl: process.env.BRAIN_API_URL || "http://localhost:9099",
apiKey: process.env.BRAIN_API_KEY,
endpoints: {
search: "/api/memory/search",
create: "/api/memory",
health: "/api/health",
},
});
memoryManager.register(brainBackend);
```
## Verification
### Unit tests
```bash
npx vitest run src/lib/memory/__tests__/generic-backend.test.ts --reporter=verbose
```
Expected output: **26 tests, all passing** covering:
- Constructor (2)
- Health check (4) — success, failure 500, network error, latency
- Initialize (2) — success, failure
- Create (2) — default endpoint, custom endpoint
- Get (4) — success, 404 → null, non-404 throw, custom path params
- Update (2) — success, 404 → false
- Delete (2) — success, 404 → false
- List (2) — query params, custom param names
- Search (3) — query params, custom endpoint, options serialization
- Auth headers (2) — Bearer token, custom headers
- Factory (1)
### Type check
```bash
npm run typecheck:core
```
Expected: **0 errors**.

View File

@@ -5,17 +5,26 @@
"A2A-SERVER",
"AGENT_PROTOCOLS_GUIDE",
"ACP",
"AGENT-SKILLS",
"AGENTBRIDGE",
"CLOUD_AGENT",
"EMBEDDED-SERVICES",
"EVALS",
"GAMIFICATION",
"LOCAL_CORPUS_CONTEXT",
"MEMORY",
"NOTION_CONTEXT",
"OBSIDIAN_CONTEXT",
"OPENCODE",
"OPEN_SSE_ARCHITECTURE",
"PLAYGROUND_STUDIO",
"PLUGIN_MARKETPLACE",
"PLUGINS",
"PLUGIN_SDK",
"RADAR",
"SEARCH_TOOLS_STUDIO",
"SKILLS",
"TRAFFIC_INSPECTOR",
"WEBHOOKS"
]
}

View File

@@ -201,7 +201,7 @@ Round-robin cycles through providers in order. Auto-combo **scores each provider
- **[Connect a Provider](./PROVIDERS-GUIDE.md)** — Add your first AI provider
- **[Free Tiers Guide](./FREE-TIERS-GUIDE.md)** — Get free AI with no credit card
- **[Troubleshooting](./TROUBLESHOOTING.md)** — Fix common issues
- **[Troubleshooting](../guides/TROUBLESHOOTING.md)** — Fix common issues
- **[Technical Reference](../routing/AUTO-COMBO.md)** — Deep dive into the scoring algorithm
---

View File

@@ -272,5 +272,5 @@ No catch! Providers offer free tiers to attract users. OmniRoute just makes it e
- **[Auto-Combo Guide](./AUTO-COMBO-GUIDE.md)** — Let OmniRoute pick the best AI for you
- **[Providers Guide](./PROVIDERS-GUIDE.md)** — Connect more providers
- **[Troubleshooting](./TROUBLESHOOTING.md)** — Fix common issues
- **[Troubleshooting](../guides/TROUBLESHOOTING.md)** — Fix common issues
- **[Free Tiers Reference](../reference/FREE_TIERS.md)** — Full list of free tiers

View File

@@ -237,5 +237,5 @@ Go to Providers → click on the provider → click **Disconnect**.
- **[Auto-Combo Guide](./AUTO-COMBO-GUIDE.md)** — Let OmniRoute pick the best AI for you
- **[Free Tiers Guide](./FREE-TIERS-GUIDE.md)** — Get free AI with no credit card
- **[Troubleshooting](./TROUBLESHOOTING.md)** — Fix common issues
- **[Troubleshooting](../guides/TROUBLESHOOTING.md)** — Fix common issues
- **[Provider Reference](../reference/PROVIDER_REFERENCE.md)** — Full list of 226 providers

View File

@@ -153,7 +153,7 @@ You can see the details of the request by clicking [Monitoring/Logs](http://loca
- **[Auto-Combo Guide](./AUTO-COMBO-GUIDE.md)** — Let OmniRoute pick the best AI for you
- **[Providers Guide](./PROVIDERS-GUIDE.md)** — Connect more providers (free and paid)
- **[Free Tiers Guide](./FREE-TIERS-GUIDE.md)** — Get free AI with no credit card
- **[Troubleshooting](./TROUBLESHOOTING.md)** — Fix common issues
- **[Troubleshooting](../guides/TROUBLESHOOTING.md)** — Fix common issues
---
@@ -183,6 +183,6 @@ OmniRoute automatically skips failed providers and tries the next one. You don't
## Need Help?
- **[Troubleshooting](./TROUBLESHOOTING.md)** — Common issues and fixes
- **[Troubleshooting](../guides/TROUBLESHOOTING.md)** — Common issues and fixes
- **[Discord](https://discord.gg/U47eFqAXCn)** — Community support
- **[GitHub Issues](https://github.com/diegosouzapw/OmniRoute/issues)** — Report bugs

View File

@@ -1,498 +0,0 @@
---
title: "Troubleshooting"
version: 3.8.40
lastUpdated: 2026-06-28
---
# Troubleshooting
> **For Users**: Looking for quick fixes? See the [Quick Reference](#quick-reference) below.
🌐 **Languages:** 🇺🇸 [English](./TROUBLESHOOTING.md) | 🇧🇷 [Português (Brasil)](../i18n/pt-BR/docs/guides/TROUBLESHOOTING.md) | 🇪🇸 [Español](../i18n/es/docs/guides/TROUBLESHOOTING.md) | 🇫🇷 [Français](../i18n/fr/docs/guides/TROUBLESHOOTING.md) | 🇮🇹 [Italiano](../i18n/it/docs/guides/TROUBLESHOOTING.md) | 🇷🇺 [Русский](../i18n/ru/docs/guides/TROUBLESHOOTING.md) | 🇨🇳 [中文 (简体)](../i18n/zh-CN/docs/guides/TROUBLESHOOTING.md) | 🇩🇪 [Deutsch](../i18n/de/docs/guides/TROUBLESHOOTING.md) | 🇮🇳 [हिन्दी](../i18n/in/docs/guides/TROUBLESHOOTING.md) | 🇹🇭 [ไทย](../i18n/th/docs/guides/TROUBLESHOOTING.md) | 🇺🇦 [Українська](../i18n/uk-UA/docs/guides/TROUBLESHOOTING.md) | 🇸🇦 [العربية](../i18n/ar/docs/guides/TROUBLESHOOTING.md) | 🇯🇵 [日本語](../i18n/ja/docs/guides/TROUBLESHOOTING.md) | 🇻🇳 [Tiếng Việt](../i18n/vi/docs/guides/TROUBLESHOOTING.md) | 🇧🇬 [Български](../i18n/bg/docs/guides/TROUBLESHOOTING.md) | 🇩🇰 [Dansk](../i18n/da/docs/guides/TROUBLESHOOTING.md) | 🇫🇮 [Suomi](../i18n/fi/docs/guides/TROUBLESHOOTING.md) | 🇮🇱 [עברית](../i18n/he/docs/guides/TROUBLESHOOTING.md) | 🇭🇺 [Magyar](../i18n/hu/docs/guides/TROUBLESHOOTING.md) | 🇮🇩 [Bahasa Indonesia](../i18n/id/docs/guides/TROUBLESHOOTING.md) | 🇰🇷 [한국어](../i18n/ko/docs/guides/TROUBLESHOOTING.md) | 🇲🇾 [Bahasa Melayu](../i18n/ms/docs/guides/TROUBLESHOOTING.md) | 🇳🇱 [Nederlands](../i18n/nl/docs/guides/TROUBLESHOOTING.md) | 🇳🇴 [Norsk](../i18n/no/docs/guides/TROUBLESHOOTING.md) | 🇵🇹 [Português (Portugal)](../i18n/pt/docs/guides/TROUBLESHOOTING.md) | 🇷🇴 [Română](../i18n/ro/docs/guides/TROUBLESHOOTING.md) | 🇵🇱 [Polski](../i18n/pl/docs/guides/TROUBLESHOOTING.md) | 🇸🇰 [Slovenčina](../i18n/sk/docs/guides/TROUBLESHOOTING.md) | 🇸🇪 [Svenska](../i18n/sv/docs/guides/TROUBLESHOOTING.md) | 🇵🇭 [Filipino](../i18n/phi/docs/guides/TROUBLESHOOTING.md) | 🇨🇿 [Čeština](../i18n/cs/docs/guides/TROUBLESHOOTING.md)
Common problems and solutions for OmniRoute.
---
## Quick Reference
**New to OmniRoute?** Start here — these solve 90% of problems:
| I see this | What it means | What to do |
| ----------------------- | ----------------------------------- | ------------------------------------------------------------------------------------------------- |
| "Can't connect" | OmniRoute isn't running | Run `omniroute` or `docker restart omniroute` |
| "Invalid API key" | Your key is wrong or expired | Re-copy the key from the provider's website |
| "Rate limit exceeded" | You're sending too many requests | Wait 1 minute, or use `model: "auto"` for automatic fallback |
| "Quota exceeded" | You've used up your free/paid quota | Connect more providers, or use free providers (Kiro, Pollinations) |
| "Slow responses" | Provider is busy or far away | Use `model: "auto/fast"` or connect a faster provider (Groq, Cerebras) |
| "Wrong provider used" | `auto` picked a different provider | That's normal! `auto` picks the best one. Force a specific provider with `model: "openai/gpt-4o"` |
| "502 Bad Gateway" | Provider is down | Wait and retry, or use `model: "auto"` to switch providers |
| "401 Unauthorized" | Your credentials are wrong | Check your API key or re-authenticate with OAuth |
| "429 Too Many Requests" | Rate limited | Wait 1 minute, or connect more providers |
**Still stuck?** See the [Quick Fixes](#quick-fixes) below, or ask on [Discord](https://discord.gg/U47eFqAXCn).
---
## npm install Warnings (ERESOLVE / peer / deprecated)
When you run `npm install -g omniroute`, you may see a wall of warnings like `npm warn ERESOLVE`, peer-dependency notices, and `deprecated` messages. **These are expected and harmless.** Your install succeeded if you see `added <N> packages` in the output.
The warnings come from stale peer-dependency ranges in third-party packages OmniRoute doesn't control:
1. **`marked-terminal` wants `marked >=1 <16`, found `marked@18`** — works fine in practice; the upstream peer range is just stale.
2. **`deprecated prebuild-install@7.1.3`** — the native-binary fetch helper. Only relevant later if a web-cookie provider reports a missing `tls-client-node` native binary (a separate issue, not caused by this warning).
**No action needed** — the warnings cannot be fully silenced without forking upstream packages.
---
## Quick Fixes
| Problem | Solution |
| --------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| First login not working | Set `INITIAL_PASSWORD` in `.env` (no hardcoded default) |
| Dashboard opens on wrong port | Set `PORT=20128` and `NEXT_PUBLIC_BASE_URL=http://localhost:20128` |
| No logs written to disk | Set `APP_LOG_TO_FILE=true` and verify call log capture is enabled |
| EACCES: permission denied | Set `DATA_DIR=/path/to/writable/dir` to override `~/.omniroute` |
| Routing strategy not saving | Update to the latest v3.x release (Zod schema fix for settings persistence shipped in earlier versions) |
| Login crash / blank page | Check Node.js version — see [Node.js Compatibility](#nodejs-compatibility) below |
| `dlopen` / `slice is not valid mach-o file` (macOS) | Run `cd $(npm root -g)/omniroute/app && npm rebuild better-sqlite3 && omniroute` — see [macOS native module rebuild](#macos-native-module-rebuild) below |
| Proxy "fetch failed" | Ensure proxy config is set at the correct level — see [Proxy Issues](#proxy-issues) below |
---
## Node.js Compatibility
<a name="nodejs-compatibility"></a>
### Login page crashes or shows "Module self-registration" error
**Cause:** You are running a Node.js version outside OmniRoute's approved secure runtime floor. The most common case is running an older Node 22 or 24 patch level that falls below the patched security floor OmniRoute requires.
**Symptoms:**
- Login page shows a blank screen or a server error
- Console shows `Error: Module did not self-register` or similar native binding errors
- The login page shows an **orange warning banner** with your Node version if the runtime is outside the supported secure policy
**Fix:**
1. Install a supported Node.js LTS release (recommended: Node.js 24.x):
```bash
nvm install 24
nvm use 24
```
2. Verify your version: `node --version` should show `v24.0.0` or newer on the 24.x LTS line
3. Reinstall OmniRoute: `npm install -g omniroute`
4. Restart: `omniroute`
> **Supported secure versions:** `>=22.22.2 <23` or `>=24.0.0 <27`. Node.js 24.x LTS (Krypton) and Node.js 26 are fully supported.
### macOS: `dlopen` / "slice is not valid mach-o file"
<a name="macos-native-module-rebuild"></a>
**Cause:** After a global `npm install -g omniroute`, the `better-sqlite3` native binary inside the package may have been compiled for a different architecture or Node.js ABI than what is running locally. This is common on macOS (both Apple Silicon and Intel) when the pre-built binary does not match your environment.
**Symptoms:**
- Server fails immediately on startup with a `dlopen` error
- Error contains `slice is not valid mach-o file`
- Full example:
```
dlopen(/Users/<user>/.nvm/versions/node/v24.14.1/lib/node_modules/omniroute/app/node_modules/better-sqlite3/build/Release/better_sqlite3.node, 0x0001): tried: '...' (slice is not valid mach-o file)
```
**Fix — rebuild for your local environment (no Node.js downgrade required):**
```bash
cd $(npm root -g)/omniroute/app
npm rebuild better-sqlite3
omniroute
```
> **Note:** This recompiles the native binding against your local Node.js version and CPU architecture, resolving the binary mismatch. The officially supported runtime range is **`>=22.22.2 <23` or `>=24.0.0 <27`** (`SUPPORTED_NODE_RANGE` in `src/shared/utils/nodeRuntimeSupport.ts`, aligned with the `package.json` `engines` field). Node.js 24.x LTS (Krypton) and Node.js 26 are fully supported with `better-sqlite3` v12.x.
---
## Proxy Issues
<a name="proxy-issues"></a>
### Provider validation shows "fetch failed"
**Cause:** The API key validation endpoint (`POST /api/providers/validate`) was previously bypassing proxy configuration, causing failures in environments that require proxy routing.
**Fix (v3.5.5+):** This is now fixed. Provider validation routes through `runWithProxyContext`, honoring provider-level and global proxy settings automatically.
### Token health check fails with "fetch failed"
**Cause:** Background OAuth token refresh was not resolving proxy configuration per connection.
**Fix (v3.5.5+):** The token health check scheduler now resolves proxy config per connection before attempting refresh. Update to v3.5.5+.
### SOCKS5 proxy returns "invalid onRequestStart method"
**Cause:** On Node.js 22, the undici@8 dispatcher is incompatible with Node's built-in `fetch()` implementation.
**Fix (v3.5.5+):** OmniRoute now uses undici's own `fetch()` function when a proxy dispatcher is active, ensuring consistent behavior. Update to v3.5.5+.
---
## Provider Issues
### "Language model did not provide messages"
**Cause:** Provider quota exhausted.
**Fix:**
1. Check dashboard quota tracker
2. Use a combo with fallback tiers
3. Switch to cheaper/free tier
### Rate Limiting
**Cause:** Subscription quota exhausted.
**Fix:**
- Add fallback: `cc/claude-opus-4-6 → glm/glm-4.7 → if/qwen3.8-max-preview`
- Use GLM/MiniMax as cheap backup
### OAuth Token Expired
OmniRoute auto-refreshes tokens. If issues persist:
1. Dashboard → Provider → Reconnect
2. Delete and re-add the provider connection
### Kiro multi-account: second account invalidates the first
**Cause:** Kiro's backend enforces a single active session per OIDC client registration.
When two accounts share the same registered client (connections imported before v3.8.0),
refreshing one account's token invalidates the other's refresh token.
**Fix (v3.8.0+):** Re-import affected connections.
Starting with v3.8.0, every new Kiro connection created via **Import Token**,
**Google/GitHub social login**, or **Auto-Import** automatically registers its own
dedicated OIDC client. The connection is therefore fully isolated and refreshing one
account has no effect on any other account.
Connections that were imported _before_ v3.8.0 do not carry a per-connection client
registration. Those connections continue to use the shared social-auth refresh endpoint.
To gain isolation, delete the old connection from Dashboard → Providers and re-add it
via any of the three import flows.
For full details and step-by-step instructions for adding two Kiro accounts side by side,
see [`docs/guides/KIRO_SETUP.md`](../guides/KIRO_SETUP.md).
---
## Cloud Issues
### Cloud Sync Errors
1. Verify `BASE_URL` points to your running instance (e.g., `http://localhost:20128`)
2. Verify `CLOUD_URL` points to your cloud endpoint (e.g., `https://omniroute.dev`)
3. Keep `NEXT_PUBLIC_*` values aligned with server-side values
### Cloud `stream=false` Returns 500
**Symptom:** `Unexpected token 'd'...` on cloud endpoint for non-streaming calls.
**Cause:** Upstream returns SSE payload while client expects JSON.
**Workaround:** Use `stream=true` for cloud direct calls. Local runtime includes SSE→JSON fallback.
### Cloud Says Connected but "Invalid API key"
1. Create a fresh key from local dashboard (`/api/keys`)
2. Run cloud sync: Enable Cloud → Sync Now
3. Old/non-synced keys can still return `401` on cloud
---
## Docker Issues
### CLI Tool Shows Not Installed
1. Check runtime fields: `curl http://localhost:20128/api/cli-tools/runtime/codex | jq`
2. For portable mode: use image target `runner-cli` (bundled CLIs)
3. For host mount mode: set `CLI_EXTRA_PATHS` and mount host bin directory as read-only
4. If `installed=true` and `runnable=false`: binary was found but failed healthcheck
### Quick Runtime Validation
```bash
curl -s http://localhost:20128/api/cli-tools/codex-settings | jq '{installed,runnable,commandPath,runtimeMode,reason}'
curl -s http://localhost:20128/api/cli-tools/claude-settings | jq '{installed,runnable,commandPath,runtimeMode,reason}'
curl -s http://localhost:20128/api/cli-tools/openclaw-settings | jq '{installed,runnable,commandPath,runtimeMode,reason}'
```
---
## Cost Issues
### High Costs
1. Check usage stats in Dashboard → Usage
2. Switch primary model to GLM/MiniMax
3. Use free tier (Qoder, Kiro) for non-critical tasks
4. Set cost budgets per API key: Dashboard → API Keys → Budget
---
## Debugging
### Enable Log Files
Set `APP_LOG_TO_FILE=true` in your `.env` file. Application logs are written under `logs/`.
Request artifacts are stored under `${DATA_DIR}/call_logs/` when the call log pipeline is
enabled in settings.
When pipeline capture is enabled, set `CALL_LOG_PIPELINE_CAPTURE_STREAM_CHUNKS=false` to omit
stream chunk payloads, or tune `CALL_LOG_PIPELINE_MAX_SIZE_KB` to change the artifact cap in KB.
### Check Provider Health
```bash
# Health dashboard
http://localhost:20128/dashboard/health
# API health check
curl http://localhost:20128/api/monitoring/health
```
### Runtime Storage
- Main state: `${DATA_DIR}/storage.sqlite` (providers, combos, aliases, keys, settings)
- Usage: SQLite tables in `storage.sqlite` (`usage_history`, `call_logs`, `proxy_logs`) + optional `${DATA_DIR}/call_logs/`
- Application logs: `<repo>/logs/...` (when `APP_LOG_TO_FILE=true`)
- Call log artifacts: `${DATA_DIR}/call_logs/YYYY-MM-DD/...` when the call log pipeline is enabled
The Request Logs page's **Clean history** action clears `call_logs`, legacy
`request_detail_logs`, and the local `${DATA_DIR}/call_logs/` artifact directory.
---
## Circuit Breaker Issues
### Provider stuck in OPEN state
When a provider's circuit breaker is OPEN, requests are blocked until the cooldown expires.
**Fix:**
1. Go to **Dashboard → Settings → Resilience**
2. Check the circuit breaker card for the affected provider
3. Click **Reset All** to clear all breakers, or wait for the cooldown to expire
4. Verify the provider is actually available before resetting
### Provider keeps tripping the circuit breaker
If a provider repeatedly enters OPEN state:
1. Check **Dashboard → Health → Provider Health** for the failure pattern
2. Go to **Settings → Resilience → Provider Profiles** and increase the failure threshold
3. Check if the provider has changed API limits or requires re-authentication
4. Review latency telemetry — high latency may cause timeout-based failures
---
## Audio Transcription Issues
### "Unsupported model" error
- Ensure you're using the correct prefix: `deepgram/nova-3` or `assemblyai/best`
- Verify the provider is connected in **Dashboard → Providers**
### Transcription returns empty or fails
- Check supported audio formats: `mp3`, `wav`, `m4a`, `flac`, `ogg`, `webm`
- Verify file size is within provider limits (typically < 25MB)
- Check provider API key validity in the provider card
---
## Translator Debugging
Use **Dashboard → Translator** to debug format translation issues:
| Mode | When to Use |
| ---------------- | -------------------------------------------------------------------------------------------- |
| **Playground** | Compare input/output formats side by side — paste a failing request to see how it translates |
| **Chat Tester** | Send live messages and inspect the full request/response payload including headers |
| **Test Bench** | Run batch tests across format combinations to find which translations are broken |
| **Live Monitor** | Watch real-time request flow to catch intermittent translation issues |
### Common format issues
- **Thinking tags not appearing** — Check if the target provider supports thinking and the thinking budget setting
- **Tool calls dropping** — Some format translations may strip unsupported fields; verify in Playground mode
- **System prompt missing** — Claude and Gemini handle system prompts differently; check translation output
- **SDK returns raw string instead of object** — Resolved in v1.x; response sanitizer strips non-standard fields (`x_groq`, `usage_breakdown`, etc.) that cause OpenAI SDK Pydantic validation failures. If you still see this on v3.x+, please file an issue.
- **GLM/ERNIE rejects `system` role** — Resolved in v1.x; role normalizer automatically merges system messages into user messages for incompatible models. If you still see this on v3.x+, please file an issue.
- **`developer` role not recognized** — Resolved in v1.x; automatically converted to `system` for non-OpenAI providers. If you still see this on v3.x+, please file an issue.
- **`json_schema` not working with Gemini** — Resolved in v1.x; `response_format` is now converted to Gemini's `responseMimeType` + `responseSchema`. If you still see this on v3.x+, please file an issue.
---
## Resilience Settings
### Auto rate-limit not triggering
- Auto rate-limit only applies to API key providers (not OAuth/subscription)
- Verify **Settings → Resilience → Provider Profiles** has auto-rate-limit enabled
- Check if the provider returns `429` status codes or `Retry-After` headers
### Tuning exponential backoff
Provider profiles support these settings:
- **Base delay** — Initial wait time after first failure (default: 1s)
- **Max delay** — Maximum wait time cap (default: 30s)
- **Multiplier** — How much to increase delay per consecutive failure (default: 2x)
### Anti-thundering herd
When many concurrent requests hit a rate-limited provider, OmniRoute uses mutex + auto rate-limiting to serialize requests and prevent cascading failures. This is automatic for API key providers.
---
## Optional RAG / LLM failure taxonomy (16 problems)
Some OmniRoute users place the gateway in front of RAG or agent stacks. In those setups it is common to see a strange pattern: OmniRoute looks healthy (providers up, routing profiles ok, no rate limit alerts) but the final answer is still wrong.
In practice these incidents usually come from the downstream RAG pipeline, not from the gateway itself.
If you want a shared vocabulary to describe those failures you can use the WFGY ProblemMap, an external MIT license text resource that defines sixteen recurring RAG / LLM failure patterns. At a high level it covers:
- retrieval drift and broken context boundaries
- empty or stale indexes and vector stores
- embedding versus semantic mismatch
- prompt assembly and context window issues
- logic collapse and overconfident answers
- long chain and agent coordination failures
- multi agent memory and role drift
- deployment and bootstrap ordering problems
The idea is simple:
1. When you investigate a bad response, capture:
- user task and request
- route or provider combo in OmniRoute
- any RAG context used downstream (retrieved documents, tool calls, etc)
2. Map the incident to one or two WFGY ProblemMap numbers (`No.1` … `No.16`).
3. Store the number in your own dashboard, runbook, or incident tracker next to the OmniRoute logs.
4. Use the corresponding WFGY page to decide whether you need to change your RAG stack, retriever, or routing strategy.
Full text and concrete recipes live here (MIT license, text only):
[WFGY ProblemMap README](https://github.com/onestardao/WFGY/blob/main/ProblemMap/README.md)
You can ignore this section if you do not run RAG or agent pipelines behind OmniRoute.
---
## v3.8.0 Known Issues
Issues specific to the v3.8.0 release and their current workarounds. If a fix lands in a later patch, the entry will be updated or removed.
### Devin CLI auth failures
**Symptoms:**
- "Devin CLI not found" or "auth failed" when invoking Devin-backed tools
- CLI runtime check reports `installed=false`
**Causes:**
- `CLI_DEVIN_BIN` points to a path that does not exist
- Devin CLI is not installed on the host
**Fix:**
1. Install the Devin CLI for your platform
2. Set `CLI_DEVIN_BIN=/usr/local/bin/devin` (or the real path) in `.env`
3. Restart OmniRoute and re-test from **Dashboard → CLI Tools**
### Model cooldown stuck (manual reset)
**Symptoms:**
- A model stays listed in cooldown even after the expiration time has passed
- Requests still skip the model in combo routing despite the timestamp being in the past
**Manual reset:**
- **Dashboard:** **Settings → Model Cooldowns** → click **Re-enable** on the affected card
- **API:** `DELETE /api/resilience/model-cooldowns` with management auth headers
### Command Code provider connection fails with 403
**Symptoms:**
- 403 when testing the Command Code provider connection
- The provider card shows "unauthorized" after a fresh add
**Cause:** The OAuth flow did not complete (callback not received or token not persisted).
**Fix:**
- Run `omniroute providers` from the CLI to re-trigger the OAuth flow, or
- Re-run OAuth from **Dashboard → Providers → Command Code → Reconnect**
### ModelScope returns aggressive 429 cooldowns
**Symptoms:**
- Very short or immediate cooldowns on ModelScope after a small burst of requests
- Combo routing skips ModelScope earlier than expected
**Cause:** ModelScope emits provider-specific `Retry-After` headers. v3.8.0 ships dedicated handling for those headers, so older versions misread them as generic rate-limit hints.
**Fix:**
- Ensure you are on v3.8.0 or later
- Verify the `useUpstream429BreakerHints` toggle is enabled under **Settings → Resilience**
### OMNIROUTE_WS_BRIDGE_SECRET missing in production
**Symptoms:**
- 401 on every Codex/Responses WebSocket bridge request when running on a remote production host
- WebSocket bridge handshake closes immediately after connect
**Cause:** The `OMNIROUTE_WS_BRIDGE_SECRET` env var is missing from the production environment.
**Fix:**
1. Generate a random secret: `openssl rand -hex 32`
2. Set `OMNIROUTE_WS_BRIDGE_SECRET=<random-secret>` in the production server env (and any client that talks to the bridge)
3. Restart OmniRoute
### Responses API: background mode degraded to synchronous
**Symptoms:**
- Warning logged: `background mode degraded to synchronous`
- A `background: true` request returns a normal synchronous response instead of a background job handle
**Cause:** v3.8.0 intentionally degrades `background: true` on the Responses API to synchronous execution while emitting a warning. Full async background execution is a future deliverable.
**Fix:**
- Adjust the client to call without `background`, or
- Wait for a later release that ships full async background mode (track the changelog)
---
## Still Stuck?
- **GitHub Issues**: [github.com/diegosouzapw/OmniRoute/issues](https://github.com/diegosouzapw/OmniRoute/issues)
- **Architecture**: See [`docs/architecture/ARCHITECTURE.md`](../architecture/ARCHITECTURE.md) for internal details
- **API Reference**: See [`docs/reference/API_REFERENCE.md`](../reference/API_REFERENCE.md) for all endpoints
- **Health Dashboard**: Check **Dashboard → Health** for real-time system status
- **Translator**: Use **Dashboard → Translator** to debug format issues

View File

@@ -6,6 +6,6 @@
"AUTO-COMBO-GUIDE",
"PROVIDERS-GUIDE",
"FREE-TIERS-GUIDE",
"TROUBLESHOOTING"
"WEB-COOKIE-GUIDE"
]
}

View File

@@ -336,6 +336,57 @@ Endpoint tunnel panels (Cloudflare, Tailscale, ngrok) can be shown or hidden fro
Multi-platform manifest: `linux/amd64` + `linux/arm64` native (Apple Silicon, AWS Graviton, Raspberry Pi). Docker selects the matching architecture automatically; pass `--platform linux/amd64` if you need to force AMD64 emulation on ARM hosts.
### Release Channels
OmniRoute publishes separate Docker channels for stable releases, active release-branch testing, and development builds.
| Channel | Source | Mutability | Recommended use |
| ------------------------------- | ----------------------------------- | --------------------------- | ----------------------------------------------------------------------------------------------- |
| `:<version>` / `:<version>-web` | Signed/versioned release | Immutable | Production deployments that pin an exact release |
| `:latest` / `:latest-web` | Highest stable release | Mutable stable pointer | Production deployments that intentionally follow stable releases |
| `:next` / `:next-web` | Current default `release/v*` branch | Mutable pre-release pointer | Testing fixes that have landed on the active release branch but are not yet in a stable release |
| `:main` / `:main-web` | `main` branch | Mutable development pointer | Development and integration testing only |
#### Using the pre-release channel
The `next` channel is rebuilt on every push to the current default `release/v*` branch and is published for both AMD64 and ARM64. Older maintenance branches cannot overwrite it. The channel provides a pullable image for fixes that have merged into the active release branch before the next stable tag is cut.
```bash
docker pull diegosouzapw/omniroute:next
docker pull diegosouzapw/omniroute:next-web
```
For Docker Compose, override the image tag used by the selected profile, then pull and recreate the service:
```yaml
services:
omniroute:
image: diegosouzapw/omniroute:next
```
```bash
docker compose pull
docker compose up -d
```
#### Safety and rollback
`next` is a floating pre-release channel. It may change on any push to the active release branch and is **not supported for production use**. Pin the image digest while evaluating a specific build:
```bash
docker pull diegosouzapw/omniroute:next
docker image inspect diegosouzapw/omniroute:next --format '{{index .RepoDigests 0}}'
```
Before testing, back up the OmniRoute data volume or bind-mounted data directory. To roll back, restore the previously used stable version or digest and recreate the container:
```bash
docker pull diegosouzapw/omniroute:<stable-version>
docker compose up -d
```
A release-branch build can never move `latest`; only an eligible stable semantic version may promote the stable pointer. The `next` images retain the release image inspection and blocking CRITICAL-vulnerability gate.
## Important Notes
- **SQLite WAL Mode:** `docker stop` should be allowed to finish so OmniRoute can checkpoint the latest changes back into `storage.sqlite`. The bundled Compose files already set a 40s stop grace period. If you run the image directly, keep `--stop-timeout 40`.

View File

@@ -1,58 +0,0 @@
---
title: "Docker Release Channels"
version: 3.8.50
lastUpdated: 2026-08-06
---
# Docker Release Channels
OmniRoute publishes separate Docker channels for stable releases, active release-branch testing, and development builds.
## Channel summary
| Channel | Source | Mutability | Recommended use |
| --- | --- | --- | --- |
| `:<version>` / `:<version>-web` | Signed/versioned release | Immutable | Production deployments that pin an exact release |
| `:latest` / `:latest-web` | Highest stable release | Mutable stable pointer | Production deployments that intentionally follow stable releases |
| `:next` / `:next-web` | Current default `release/v*` branch | Mutable pre-release pointer | Testing fixes that have landed on the active release branch but are not yet in a stable release |
| `:main` / `:main-web` | `main` branch | Mutable development pointer | Development and integration testing only |
## Using the pre-release channel
The `next` channel is rebuilt on every push to the current default `release/v*` branch and is published for both AMD64 and ARM64. Older maintenance branches cannot overwrite it. The channel provides a pullable image for fixes that have merged into the active release branch before the next stable tag is cut.
```bash
docker pull diegosouzapw/omniroute:next
docker pull diegosouzapw/omniroute:next-web
```
For Docker Compose, override the image tag used by the selected profile, then pull and recreate the service:
```yaml
services:
omniroute:
image: diegosouzapw/omniroute:next
```
```bash
docker compose pull
docker compose up -d
```
## Safety and rollback
`next` is a floating pre-release channel. It may change on any push to the active release branch and is **not supported for production use**. Pin the image digest while evaluating a specific build:
```bash
docker pull diegosouzapw/omniroute:next
docker image inspect diegosouzapw/omniroute:next --format '{{index .RepoDigests 0}}'
```
Before testing, back up the OmniRoute data volume or bind-mounted data directory. To roll back, restore the previously used stable version or digest and recreate the container:
```bash
docker pull diegosouzapw/omniroute:<stable-version>
docker compose up -d
```
A release-branch build can never move `latest`; only an eligible stable semantic version may promote the stable pointer. The `next` images retain the release image inspection and blocking CRITICAL-vulnerability gate.

View File

@@ -38,6 +38,19 @@ Common problems and solutions for OmniRoute.
---
## npm install Warnings (ERESOLVE / peer / deprecated)
When you run `npm install -g omniroute`, you may see a wall of warnings like `npm warn ERESOLVE`, peer-dependency notices, and `deprecated` messages. **These are expected and harmless.** Your install succeeded if you see `added <N> packages` in the output.
The warnings come from stale peer-dependency ranges in third-party packages OmniRoute doesn't control:
1. **`marked-terminal` wants `marked >=1 <16`, found `marked@18`** — works fine in practice; the upstream peer range is just stale.
2. **`deprecated prebuild-install@7.1.3`** — the native-binary fetch helper. Only relevant later if a web-cookie provider reports a missing `tls-client-node` native binary (a separate issue, not caused by this warning).
**No action needed** — the warnings cannot be fully silenced without forking upstream packages.
---
## Quick Fixes
| Problem | Solution |

View File

@@ -11,11 +11,17 @@
"COST_TRACKING",
"I18N",
"KIRO_SETUP",
"ANTIGRAVITY-ONBOARDING",
"CLAUDE-CODE-CONFIGURATION",
"CODEX-CLI-CONFIGURATION",
"CLI-INTEGRATIONS",
"MANAGEMENT-AUTH",
"REMOTE-MODE",
"PWA_GUIDE",
"TERMUX_GUIDE",
"TIERS",
"USAGE_QUOTA_GUIDE",
"TROUBLESHOOTING",
"UNINSTALL"
]
}

View File

@@ -1,226 +0,0 @@
---
title: "Inwentaryzacja sprzężenia z SQLite"
status: measured-snapshot
lastUpdated: 2026-07-23
---
# Inwentaryzacja sprzężenia z SQLite
- **Tracking issue:** [#8075](https://github.com/diegosouzapw/OmniRoute/issues/8075)
- **Snapshot revision:** `9a3b605f3420ae3ab08bd93d6443034f03a1bcbc`
- **Scanned-corpus SHA-256:** `72334620a7a18a42bcede1643fb2fdf95da6eae9ffa66a891ae14ed633ad43f6`
- **Cel:** Zmierz aktualne linie cięcia warstwy persystencji przed zaproponowaniem interfejsów repozytoriów
- **Wpływ na runtime:** Brak; ten dokument i jego skrypt audytu nie zmieniają zachowania bazy danych
## Jak odtworzyć
Z katalogu głównego repozytorium:
```bash
node scripts/check/audit-sqlite-coupling.mjs
node scripts/check/audit-sqlite-coupling.mjs --json
node --test scripts/check/audit-sqlite-coupling.test.mjs
```
Skrypt odczytuje śledzone pliki z Gita, skanuje źródła inne niż testowe w `src/`, `open-sse/`,
`electron/` i `bin/` oraz skanuje SQL migracji w `src/lib/db/migrations/`. Wyklucza
drzewo testów najwyższego poziomu, współlokalizowane katalogi testów, pliki źródłowe test/spec oraz ścieżki poza tymi
skonfigurowanymi korzeniami źródeł (w tym dokumentację i skrypty).
Skrypt odmawia uruchomienia, jeśli śledzone pliki w tych korzeniach źródeł różnią się od `HEAD`. Raportuje
zarówno rewizję narzędzia audytu, jak i SHA-256 nad uporządkowanym korpusem ścieżka/treść. Powyższy snapshot
został wykonany z wymienionej rewizji źródeł; ten PR zmienia wyłącznie wykluczone ścieżki dokumentacji i skryptów,
więc ponowne uruchomienie z czystej gałęzi PR daje ten sam digest korpusu.
To jest **inwentaryzacja leksykalna**, a nie semantyczna analiza TypeScript lub SQL:
- liczby to wystąpienia zdefiniowanych wzorców, a nie liczby odrębnych instrukcji SQL;
- wzorce wywołań adaptera i bezpośredniego singletona najpierw maskują komentarze i treści literałów;
- treści literałów szablonowych, w tym osadzone wyrażenia, są wykluczone z tych zliczeń
składni kodu;
- lekki masker nie jest parserem JavaScript, więc nietypowa składnia literałów wyrażeń regularnych
może nadal wymagać ręcznego przeglądu;
- komentarze i literały łańcuchowe mogą wnosić wkład do zliczeń sygnałów dialektu, które celowo przeszukują
surowy tekst pod kątem osadzonego SQL;
- dopasowanie `.prepare()` poza `src/lib/db/` to trop do przeglądu, a nie dowód, że wywołanie należy przenieść;
- wywołania ukryte za inaczej nazwaną nakładką mogą nie być zliczane;
- liczby plików są deduplikowane, natomiast liczby wystąpień — nie.
Wyjście JSON obejmuje każdą pasującą ścieżkę, dzięki czemu recenzenci mogą sprawdzić lub przeklasyfikować poszczególne
wyniki zamiast opierać się wyłącznie na sumach.
## Zakres snapshota
W zapisanej rewizji skrypt przeskanował:
- 3 830 śledzonych plików źródłowych innych niż testowe;
- 129 plików SQL migracji.
Liczba plików źródłowych jest celowo szeroka, ponieważ celem jest znalezienie sprzężenia persystencji, które
wyszło poza nominalny katalog bazy danych, w tym kod CLI oraz proxy/runtime.
## Sygnały granic
| Signal | Files | Occurrences |
| -------------------------------------------------------------------------------- | ----: | ----------: |
| Direct `getDbInstance()` call syntax outside comments/literals and `src/lib/db/` | 45 | 150 |
| `localDb` import consumers | 211 | — |
| `SqliteAdapter` type consumers outside comments/literals and `src/lib/db/` | 3 | — |
Barrel `localDb` już daje wielu wywołującym szew funkcji domenowych, ale
`src/lib/localDb.ts` pozostaje warstwą re-eksportu, a nie kontraktem backendu. 45 bezpośrednich
konsumentów singletona to najczytelniejszy pierwszy zestaw do przeglądu, ponieważ omijają ten logiczny szew i
trzymają bezpośrednio uchwyt o kształcie adaptera.
Trzy pliki źródłowe inne niż testowe poza `src/lib/db/`, które w składni kodu wspominają typ `SqliteAdapter`,
to:
- `src/app/api/db-backups/import/route.ts`;
- `src/lib/compliance/index.ts`;
- `src/lib/compliance/noLog.ts`.
To nie są równoważne zadania migracyjne. Import kopii zapasowej jest specyficzny dla możliwości; persystencja
compliance może być przenośnym stanem domenowym. Przyszła granica powinna je sklasyfikować, a nie
przenosić wszystkie trzy mechanicznie.
## Składnia wywołań o kształcie adaptera
| Signal | Occurrences | Files | Outside `src/lib/db/` occurrences | Outside files |
| ----------------- | ----------: | ----: | --------------------------------: | ------------: |
| `.prepare()` | 1,219 | 163 | 252 | 52 |
| `.transaction()` | 62 | 40 | 12 | 10 |
| `.immediate()` | 3 | 3 | 0 | 0 |
| `.pragma()` | 39 | 11 | 6 | 4 |
| `.backup()` | 6 | 5 | 3 | 3 |
| `.checkpoint()` | 0 | 0 | 0 | 0 |
| `lastInsertRowid` | 15 | 7 | 1 | 1 |
Ta tabela pokazuje, dlaczego `SqliteAdapter` jest warstwą zgodności runtime SQLite, a nie przenośną
abstrakcją backendu. Jego synchroniczny kształt instrukcji i transakcji jest szeroko używany, a część
tego kształtu jest widoczna poza nominalną warstwą bazy danych.
Główni bezpośredni konsumenci `getDbInstance()` poza `src/lib/db/` w tej rewizji to:
| File | Occurrences |
| -------------------------------------------------- | ----------: |
| `src/lib/proxySubscription/subscriptionService.ts` | 12 |
| `src/lib/semanticCache.ts` | 10 |
| `src/lib/usage/callLogs.ts` | 9 |
| `src/lib/cloudAgent/db.ts` | 8 |
| `src/lib/memory/store.ts` | 8 |
| `src/lib/memory/vectorStore.ts` | 8 |
| `src/lib/modelsDevSync.ts` | 8 |
| `src/lib/gamification/badges.ts` | 5 |
| `src/lib/memory/retrieval.ts` | 5 |
| `src/lib/pricingSync.ts` | 5 |
| `src/lib/skills/registry.ts` | 5 |
| `src/lib/usage/usageHistory.ts` | 5 |
Lista obejmuje konfigurację control-plane, dane usage/audit, cache, wyszukiwanie memory/vector, skills,
gamification oraz wsparcie CLI/provider. Jeden generyczny adapter SQL utrwaliłby ten rozrzut;
repozytoria domenowe dają sposób na jego redukcję plaster po plasterku.
## Sygnały dialektu SQLite i cyklu życia
| Signal | Occurrences | Files |
| --------------------- | ----------: | ----: |
| `PRAGMA` text | 97 | 41 |
| `sqlite_master` | 14 | 11 |
| `BEGIN IMMEDIATE` | 2 | 2 |
| `INSERT OR REPLACE` | 83 | 45 |
| `AUTOINCREMENT` | 34 | 24 |
| `datetime('now')` | 171 | 68 |
| `VACUUM` | 39 | 10 |
| `wal_checkpoint` | 13 | 7 |
| `fts5` | 43 | 8 |
| `vec0` | 7 | 1 |
| `last_insert_rowid()` | 1 | 1 |
Te wartości to sygnały tekstowe i obejmują komentarze, jeśli występują. Są przydatne do lokalizowania
pracy nad przenośnością, a nie do szacowania nakładu implementacji przez mnożenie.
Zweryfikowane obszary wysokiego sprzężenia obejmują:
- `src/lib/db/core.ts`: cykl życia singletona, ścieżki plików SQLite, checkpoint WAL, recovery, schemat,
kompaktowanie i tworzenie kopii zapasowych;
- `src/lib/db/migrationRunner.ts`: wykonywanie ponumerowanych migracji SQL, `sqlite_master`,
`PRAGMA table_info`, zachowanie transakcji oraz opcjonalna obsługa FTS5;
- `src/lib/db/optimizationSettings.ts`: ustawienia page/cache, auto-vacuum, przejścia WAL oraz
`VACUUM`;
- `src/lib/db/backup.ts`: cykl życia kopii zapasowej i przywracania bazy danych;
- `src/lib/db/schemaColumns.ts`: introspekcja schematu SQLite i kolumny zgodności;
- `src/lib/memory/vectorStore.ts` oraz `src/lib/memory/retrieval.ts`: zachowanie `vec0` i FTS5;
- `src/lib/db/adapters/`: implementacje zgodności dla obsługiwanych runtime'ów SQLite.
Tych obszarów nie należy forsować przez interfejs repozytorium najniższego wspólnego mianownika. Potrzebują
wyraźnych możliwości SQLite albo osobnych implementacji backendu.
## Sprzężenie migracji
Snapshot zawiera 129 śledzonych plików SQL migracji. `src/lib/db/migrationRunner.ts` robi więcej
niż wykonywanie uporządkowanych plików: posiada odkrywanie migracji, historię wersji, bezpieczeństwo duplikatów wersji,
sondy schematu, sprawdzenia możliwości FTS5, bezpieczeństwo przed migracją oraz wykonywanie transakcji SQLite.
W konsekwencji:
- inny dialekt SQL nie może bezpiecznie ponownie użyć plików migracji bez zmian;
- zewnętrzne backendy potrzebują własnej implementacji migracji i historii schematu;
- logiczne kamienie milowe migracji mogą być współdzielone, ale fizyczny SQL i sondy możliwości pozostają
specyficzne dla backendu;
- praca multi-replica wymaga własności migracji lub blokad, zanim zewnętrzny backend zostanie
uznany za gotowy.
## Rekomendowane linie cięcia
### 1. Zachowaj nienaruszoną zgodność runtime SQLite
Nie zastępuj `SqliteAdapter` ani kaskady driverów w pierwszym PR dotyczącym repozytoriów. Zachowaj odzyskiwanie plików,
WAL, backup, optymalizację, FTS5 i zachowanie wektorów za bieżącą implementacją SQLite.
### 2. Zacznij od bezpośrednich konsumentów singletona
Użyj listy 45 plików bezpośrednich konsumentów jako początkowej kolejki przeglądu. Sklasyfikuj każdy plik jako:
- przenośny stan domenowy;
- konserwację lub wyszukiwanie specyficzne dla backendu;
- stan lokalny dla procesu lub możliwy do odbudowy;
- dostęp legacy, który powinien wywoływać istniejący moduł domenowy.
Klasyfikacja musi poprzedzać projekt interfejsu. Samo pojawienie się ścieżki w inwentaryzacji nie jest
nakazem utworzenia repozytorium.
### 3. Najpierw udowodnij repozytoria na SQLite
Dla jednej ograniczonej domeny:
1. zdefiniuj operacje repozytorium zorientowane na zachowanie;
2. zaadaptuj bieżące zapytania SQLite za tym repozytorium;
3. uruchom testy zgodności zachowania i transakcji względem SQLite;
4. zmigruj wywołujących bez zmiany domyślnego runtime;
5. dopiero potem zaimplementuj to samo repozytorium dla zewnętrznego backendu.
### 4. Oddziel przenośny stan control-plane od danych specyficznych dla możliwości
Połączenia providerów, klucze API, combos i konfiguracja routingu są kandydatami na pierwszy
przenośny plaster, z zastrzeżeniem zatwierdzenia przez maintainerów i przeglądu ownership tabel. Wyszukiwanie wektorowe memory,
backup/odzyskiwanie plików SQLite oraz optymalizacja bazy danych to słabe pierwsze plastry, ponieważ ich zachowanie
jest celowo specyficzne dla SQLite.
### 5. Traktuj usage, quota, affinity i audit jako późniejszy plaster koordynacji
Te domeny mają semantykę współbieżności i wolumenu wykraczającą poza CRUD. Ich kontrakty repozytoriów powinny
być projektowane razem z testami transakcji multi-replica, lease, retencji i trybów awarii, a nie
kopiowane mechanicznie z bieżącego SQL.
## Czego ta inwentaryzacja nie rozstrzyga
Ta inwentaryzacja nie:
- zatwierdza wsparcia PostgreSQL ani MySQL;
- definiuje interfejsów TypeScript repozytoriów;
- wybiera pierwszej tabeli ani domeny do migracji;
- twierdzi, że każde dopasowanie leksykalne jest defektem;
- twierdzi, że obecne granice modułów są nieskuteczne;
- zmienia SQLite, migracji, backupu, wyszukiwania ani zachowania runtime.
Jej celem jest uczynienie kolejnej dyskusji projektowej opartej na dowodach i odtwarzalnej.

View File

@@ -1,498 +0,0 @@
---
title: "Rozwiązywanie problemów"
version: 3.8.40
lastUpdated: 2026-06-28
---
# Rozwiązywanie problemów
> **Dla użytkowników**: Szukasz szybkich poprawek? Zobacz [Szybki przewodnik](#quick-reference) poniżej.
🌐 **Languages:** 🇺🇸 [English](./TROUBLESHOOTING.md) | 🇧🇷 [Português (Brasil)](../i18n/pt-BR/docs/guides/TROUBLESHOOTING.md) | 🇪🇸 [Español](../i18n/es/docs/guides/TROUBLESHOOTING.md) | 🇫🇷 [Français](../i18n/fr/docs/guides/TROUBLESHOOTING.md) | 🇮🇹 [Italiano](../i18n/it/docs/guides/TROUBLESHOOTING.md) | 🇷🇺 [Русский](../i18n/ru/docs/guides/TROUBLESHOOTING.md) | 🇨🇳 [中文 (简体)](../i18n/zh-CN/docs/guides/TROUBLESHOOTING.md) | 🇩🇪 [Deutsch](../i18n/de/docs/guides/TROUBLESHOOTING.md) | 🇮🇳 [हिन्दी](../i18n/in/docs/guides/TROUBLESHOOTING.md) | 🇹🇭 [ไทย](../i18n/th/docs/guides/TROUBLESHOOTING.md) | 🇺🇦 [Українська](../i18n/uk-UA/docs/guides/TROUBLESHOOTING.md) | 🇸🇦 [العربية](../i18n/ar/docs/guides/TROUBLESHOOTING.md) | 🇯🇵 [日本語](../i18n/ja/docs/guides/TROUBLESHOOTING.md) | 🇻🇳 [Tiếng Việt](../i18n/vi/docs/guides/TROUBLESHOOTING.md) | 🇧🇬 [Български](../i18n/bg/docs/guides/TROUBLESHOOTING.md) | 🇩🇰 [Dansk](../i18n/da/docs/guides/TROUBLESHOOTING.md) | 🇫🇮 [Suomi](../i18n/fi/docs/guides/TROUBLESHOOTING.md) | 🇮🇱 [עברית](../i18n/he/docs/guides/TROUBLESHOOTING.md) | 🇭🇺 [Magyar](../i18n/hu/docs/guides/TROUBLESHOOTING.md) | 🇮🇩 [Bahasa Indonesia](../i18n/id/docs/guides/TROUBLESHOOTING.md) | 🇰🇷 [한국어](../i18n/ko/docs/guides/TROUBLESHOOTING.md) | 🇲🇾 [Bahasa Melayu](../i18n/ms/docs/guides/TROUBLESHOOTING.md) | 🇳🇱 [Nederlands](../i18n/nl/docs/guides/TROUBLESHOOTING.md) | 🇳🇴 [Norsk](../i18n/no/docs/guides/TROUBLESHOOTING.md) | 🇵🇹 [Português (Portugal)](../i18n/pt/docs/guides/TROUBLESHOOTING.md) | 🇷🇴 [Română](../i18n/ro/docs/guides/TROUBLESHOOTING.md) | 🇵🇱 [Polski](../i18n/pl/docs/guides/TROUBLESHOOTING.md) | 🇸🇰 [Slovenčina](../i18n/sk/docs/guides/TROUBLESHOOTING.md) | 🇸🇪 [Svenska](../i18n/sv/docs/guides/TROUBLESHOOTING.md) | 🇵🇭 [Filipino](../i18n/phi/docs/guides/TROUBLESHOOTING.md) | 🇨🇿 [Čeština](../i18n/cs/docs/guides/TROUBLESHOOTING.md)
Typowe problemy i rozwiązania dla OmniRoute.
---
## Szybki przewodnik
**Nowy w OmniRoute?** Zacznij tutaj — te wskazówki rozwiązują 90% problemów:
| Widzę to | Co to oznacza | Co zrobić |
| ----------------------- | ---------------------------------- | ------------------------------------------------------------------------------------ |
| "Can't connect" | OmniRoute nie działa | Uruchom `omniroute` lub `docker restart omniroute` |
| "Invalid API key" | Klucz jest błędny lub wygasł | Skopiuj ponownie klucz ze strony providera |
| "Rate limit exceeded" | Wysyłasz zbyt wiele żądań | Poczekaj 1 minutę albo użyj `model: "auto"` do automatycznego fallbacku |
| "Quota exceeded" | Wykorzystałeś darmowy/płatny limit | Podłącz więcej providerów albo użyj darmowych (Kiro, Pollinations) |
| "Slow responses" | Provider jest obciążony lub daleko | Użyj `model: "auto/fast"` albo podłącz szybszego providera (Groq, Cerebras) |
| "Wrong provider used" | `auto` wybrał innego providera | To normalne! `auto` wybiera najlepszego. Wymuś konkretnego: `model: "openai/gpt-4o"` |
| "502 Bad Gateway" | Provider nie działa | Poczekaj i spróbuj ponownie albo użyj `model: "auto"`, aby przełączyć providera |
| "401 Unauthorized" | Błędne dane uwierzytelniające | Sprawdź klucz API albo ponownie uwierzytelnij się przez OAuth |
| "429 Too Many Requests" | Limit zapytań | Poczekaj 1 minutę albo podłącz więcej providerów |
**Nadal utknąłeś?** Zobacz [Szybkie poprawki](#quick-fixes) poniżej albo zapytaj na [Discordzie](https://discord.gg/U47eFqAXCn).
---
## Ostrzeżenia npm install (ERESOLVE / peer / deprecated)
Po `npm install -g omniroute` możesz zobaczyć lawinę ostrzeżeń typu `npm warn ERESOLVE`, komunikaty o peer-dependency oraz `deprecated`. **Są one oczekiwane i nieszkodliwe.** Instalacja się powiodła, jeśli w wyniku widać `added <N> packages`.
Ostrzeżenia pochodzą z przestarzałych zakresów peer-dependency w pakietach firm trzecich, których OmniRoute nie kontroluje:
1. **`marked-terminal` chce `marked >=1 <16`, znaleziono `marked@18`** — w praktyce działa poprawnie; zakres peer po stronie upstream jest po prostu nieaktualny.
2. **`deprecated prebuild-install@7.1.3`** — helper do pobierania natywnych binarek. Istotny dopiero później, jeśli provider web-cookie zgłosi brak natywnej binarki `tls-client-node` (osobny problem, nie spowodowany tym ostrzeżeniem).
**Nie trzeba nic robić** — ostrzeżeń nie da się w pełni wyciszyć bez forka pakietów upstream.
---
## Szybkie poprawki
| Problem | Rozwiązanie |
| --------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Pierwsze logowanie nie działa | Ustaw `INITIAL_PASSWORD` w `.env` (brak wbudowanego domyślnego hasła) |
| Dashboard otwiera się na złym porcie | Ustaw `PORT=20128` i `NEXT_PUBLIC_BASE_URL=http://localhost:20128` |
| Brak logów na dysku | Ustaw `APP_LOG_TO_FILE=true` i upewnij się, że przechwytywanie call log jest włączone |
| EACCES: permission denied | Ustaw `DATA_DIR=/path/to/writable/dir`, aby nadpisać `~/.omniroute` |
| Strategia routingu się nie zapisuje | Zaktualizuj do najnowszego wydania v3.x (poprawka schematu Zod dla persystencji ustawień weszła we wcześniejszych wersjach) |
| Crash logowania / pusta strona | Sprawdź wersję Node.js — zobacz [Zgodność z Node.js](#nodejs-compatibility) poniżej |
| `dlopen` / `slice is not valid mach-o file` (macOS) | Uruchom `cd $(npm root -g)/omniroute/app && npm rebuild better-sqlite3 && omniroute` — zobacz [przebudowa modułu natywnego na macOS](#macos-native-module-rebuild) poniżej |
| Proxy "fetch failed" | Upewnij się, że konfiguracja proxy jest ustawiona na właściwym poziomie — zobacz [Problemy z proxy](#proxy-issues) poniżej |
---
## Zgodność z Node.js
<a name="nodejs-compatibility"></a>
### Strona logowania się wykrzacza lub pokazuje błąd "Module self-registration"
**Przyczyna:** Uruchamiasz wersję Node.js poniżej zatwierdzonego bezpiecznego poziomu runtime OmniRoute. Najczęstszy przypadek to starszy patch Node 22 lub 24 poniżej wymaganego przez OmniRoute poziomu bezpieczeństwa.
**Objawy:**
- Strona logowania pokazuje pusty ekran lub błąd serwera
- Konsola pokazuje `Error: Module did not self-register` lub podobne błędy natywnych bindingów
- Strona logowania pokazuje **pomarańczowy baner ostrzegawczy** z Twoją wersją Node, jeśli runtime jest poza wspieraną bezpieczną polityką
**Naprawa:**
1. Zainstaluj wspierane wydanie Node.js LTS (zalecane: Node.js 24.x):
```bash
nvm install 24
nvm use 24
```
2. Sprawdź wersję: `node --version` powinno pokazać `v24.0.0` lub nowsze w linii LTS 24.x
3. Zainstaluj ponownie OmniRoute: `npm install -g omniroute`
4. Uruchom ponownie: `omniroute`
> **Wspierane bezpieczne wersje:** `>=22.22.2 <23` lub `>=24.0.0 <27`. Node.js 24.x LTS (Krypton) oraz Node.js 26 są w pełni wspierane.
### macOS: `dlopen` / "slice is not valid mach-o file"
<a name="macos-native-module-rebuild"></a>
**Przyczyna:** Po globalnym `npm install -g omniroute` natywna binarka `better-sqlite3` w pakiecie mogła zostać skompilowana pod inną architekturę lub ABI Node.js niż ta, która działa lokalnie. To częste na macOS (Apple Silicon i Intel), gdy prebuilt nie pasuje do środowiska.
**Objawy:**
- Serwer pada natychmiast przy starcie z błędem `dlopen`
- Błąd zawiera `slice is not valid mach-o file`
- Pełny przykład:
```
dlopen(/Users/<user>/.nvm/versions/node/v24.14.1/lib/node_modules/omniroute/app/node_modules/better-sqlite3/build/Release/better_sqlite3.node, 0x0001): tried: '...' (slice is not valid mach-o file)
```
**Naprawa — przebuduj pod lokalne środowisko (bez downgrade Node.js):**
```bash
cd $(npm root -g)/omniroute/app
npm rebuild better-sqlite3
omniroute
```
> **Uwaga:** To rekompiluje natywny binding względem lokalnej wersji Node.js i architektury CPU, usuwając niedopasowanie binarki. Oficjalnie wspierany zakres runtime to **`>=22.22.2 <23` lub `>=24.0.0 <27`** (`SUPPORTED_NODE_RANGE` w `src/shared/utils/nodeRuntimeSupport.ts`, zgodny z polem `engines` w `package.json`). Node.js 24.x LTS (Krypton) oraz Node.js 26 są w pełni wspierane z `better-sqlite3` v12.x.
---
## Problemy z proxy
<a name="proxy-issues"></a>
### Walidacja providera pokazuje "fetch failed"
**Przyczyna:** Endpoint walidacji klucza API (`POST /api/providers/validate`) wcześniej omijał konfigurację proxy, co powodowało błędy w środowiskach wymagających routingu przez proxy.
**Naprawa (v3.5.5+):** To już naprawione. Walidacja providera idzie przez `runWithProxyContext` i automatycznie respektuje ustawienia proxy na poziomie providera oraz globalne.
### Token health check kończy się "fetch failed"
**Przyczyna:** Tło odświeżania tokenów OAuth nie rozwiązywało konfiguracji proxy per połączenie.
**Naprawa (v3.5.5+):** Scheduler token health check rozwiązuje teraz config proxy per połączenie przed odświeżeniem. Zaktualizuj do v3.5.5+.
### Proxy SOCKS5 zwraca "invalid onRequestStart method"
**Przyczyna:** Na Node.js 22 dispatcher undici@8 jest niekompatybilny z wbudowaną implementacją `fetch()` w Node.
**Naprawa (v3.5.5+):** OmniRoute używa teraz własnej funkcji `fetch()` z undici, gdy aktywny jest dispatcher proxy, co zapewnia spójne zachowanie. Zaktualizuj do v3.5.5+.
---
## Problemy z providerami
### "Language model did not provide messages"
**Przyczyna:** Wyczerpany limit (quota) providera.
**Naprawa:**
1. Sprawdź tracker limitu w dashboardzie
2. Użyj combo z poziomami fallback
3. Przełącz się na tańszy/darmowy tier
### Rate limiting
**Przyczyna:** Wyczerpany limit subskrypcji.
**Naprawa:**
- Dodaj fallback: `cc/claude-opus-4-6 → glm/glm-4.7 → if/qwen3.8-max-preview`
- Użyj GLM/MiniMax jako taniego zapasowego
### Wygasły token OAuth
OmniRoute automatycznie odświeża tokeny. Jeśli problemy trwają:
1. Dashboard → Provider → Reconnect
2. Usuń i dodaj ponownie połączenie providera
### Kiro multi-account: drugie konto unieważnia pierwsze
**Przyczyna:** Backend Kiro wymusza jedną aktywną sesję na rejestrację klienta OIDC.
Gdy dwa konta współdzielą tego samego zarejestrowanego klienta (połączenia zaimportowane przed v3.8.0),
odświeżenie tokenu jednego konta unieważnia refresh token drugiego.
**Naprawa (v3.8.0+):** Zaimportuj ponownie dotknięte połączenia.
Od v3.8.0 każde nowe połączenie Kiro utworzone przez **Import Token**,
**Google/GitHub social login** lub **Auto-Import** automatycznie rejestruje własnego
dedykowanego klienta OIDC. Połączenie jest więc w pełni izolowane i odświeżenie jednego
konta nie wpływa na żadne inne.
Połączenia zaimportowane _przed_ v3.8.0 nie niosą rejestracji klienta per połączenie.
Te połączenia nadal używają współdzielonego endpointu odświeżania social-auth.
Aby uzyskać izolację, usuń stare połączenie z Dashboard → Providers i dodaj je ponownie
przez dowolny z trzech przepływów importu.
Pełne szczegóły i instrukcja krok po kroku dodawania dwóch kont Kiro obok siebie:
zobacz [`docs/guides/KIRO_SETUP.md`](../guides/KIRO_SETUP.md).
---
## Problemy z chmurą
### Błędy synchronizacji chmury
1. Sprawdź, czy `BASE_URL` wskazuje na działającą instancję (np. `http://localhost:20128`)
2. Sprawdź, czy `CLOUD_URL` wskazuje na endpoint chmury (np. `https://omniroute.dev`)
3. Utrzymuj wartości `NEXT_PUBLIC_*` zgodne z wartościami po stronie serwera
### Cloud `stream=false` zwraca 500
**Objaw:** `Unexpected token 'd'...` na endpoincie chmury przy wywołaniach bez streamingu.
**Przyczyna:** Upstream zwraca payload SSE, a klient oczekuje JSON.
**Obejście:** Użyj `stream=true` przy bezpośrednich wywołaniach cloud. Lokalny runtime ma fallback SSE→JSON.
### Cloud pokazuje Connected, ale "Invalid API key"
1. Utwórz świeży klucz z lokalnego dashboardu (`/api/keys`)
2. Uruchom synchronizację chmury: Enable Cloud → Sync Now
3. Stare/niesynchronizowane klucze mogą nadal zwracać `401` w chmurze
---
## Problemy z Dockerem
### Narzędzie CLI pokazuje Not Installed
1. Sprawdź pola runtime: `curl http://localhost:20128/api/cli-tools/runtime/codex | jq`
2. Dla trybu portable: użyj targetu obrazu `runner-cli` (dołączone CLI)
3. Dla trybu host mount: ustaw `CLI_EXTRA_PATHS` i zamontuj katalog bin hosta jako tylko do odczytu
4. Jeśli `installed=true` i `runnable=false`: binarka znaleziona, ale healthcheck się nie powiódł
### Szybka walidacja runtime
```bash
curl -s http://localhost:20128/api/cli-tools/codex-settings | jq '{installed,runnable,commandPath,runtimeMode,reason}'
curl -s http://localhost:20128/api/cli-tools/claude-settings | jq '{installed,runnable,commandPath,runtimeMode,reason}'
curl -s http://localhost:20128/api/cli-tools/openclaw-settings | jq '{installed,runnable,commandPath,runtimeMode,reason}'
```
---
## Problemy z kosztami
### Wysokie koszty
1. Sprawdź statystyki użycia w Dashboard → Usage
2. Przełącz model główny na GLM/MiniMax
3. Używaj darmowego tieru (Qoder, Kiro) do mniej krytycznych zadań
4. Ustaw budżety kosztów per klucz API: Dashboard → API Keys → Budget
---
## Debugowanie
### Włącz pliki logów
Ustaw `APP_LOG_TO_FILE=true` w pliku `.env`. Logi aplikacji trafiają do `logs/`.
Artefakty żądań są przechowywane w `${DATA_DIR}/call_logs/`, gdy pipeline call log jest
włączony w ustawieniach.
Gdy przechwytywanie pipeline jest włączone, ustaw `CALL_LOG_PIPELINE_CAPTURE_STREAM_CHUNKS=false`, aby pominąć
payloady chunków streamu, albo dostrój `CALL_LOG_PIPELINE_MAX_SIZE_KB`, aby zmienić limit artefaktu w KB.
### Sprawdź zdrowie providerów
```bash
# Health dashboard
http://localhost:20128/dashboard/health
# API health check
curl http://localhost:20128/api/monitoring/health
```
### Przechowywanie w runtime
- Stan główny: `${DATA_DIR}/storage.sqlite` (providers, combos, aliases, keys, settings)
- Użycie: tabele SQLite w `storage.sqlite` (`usage_history`, `call_logs`, `proxy_logs`) + opcjonalnie `${DATA_DIR}/call_logs/`
- Logi aplikacji: `<repo>/logs/...` (gdy `APP_LOG_TO_FILE=true`)
- Artefakty call log: `${DATA_DIR}/call_logs/YYYY-MM-DD/...` gdy pipeline call log jest włączony
Akcja **Clean history** na stronie Request Logs czyści `call_logs`, legacy
`request_detail_logs` oraz lokalny katalog artefaktów `${DATA_DIR}/call_logs/`.
---
## Problemy z circuit breakerem
### Provider utknął w stanie OPEN
Gdy circuit breaker providera jest OPEN, żądania są blokowane do wygaśnięcia cooldownu.
**Naprawa:**
1. Przejdź do **Dashboard → Settings → Resilience**
2. Sprawdź kartę circuit breakera dla dotkniętego providera
3. Kliknij **Reset All**, aby wyczyścić wszystkie breakery, albo poczekaj na wygaśnięcie cooldownu
4. Upewnij się, że provider jest faktycznie dostępny przed resetem
### Provider wciąż wyzwala circuit breaker
Jeśli provider wielokrotnie wchodzi w stan OPEN:
1. Sprawdź **Dashboard → Health → Provider Health** pod kątem wzorca awarii
2. Przejdź do **Settings → Resilience → Provider Profiles** i zwiększ próg awarii
3. Sprawdź, czy provider zmienił limity API lub wymaga ponownego uwierzytelnienia
4. Przejrzyj telemetrię opóźnień — wysoka latencja może powodować awarie oparte na timeoutach
---
## Problemy z transkrypcją audio
### Błąd "Unsupported model"
- Upewnij się, że używasz właściwego prefiksu: `deepgram/nova-3` lub `assemblyai/best`
- Sprawdź, czy provider jest podłączony w **Dashboard → Providers**
### Transkrypcja zwraca pusto lub się nie udaje
- Sprawdź wspierane formaty audio: `mp3`, `wav`, `m4a`, `flac`, `ogg`, `webm`
- Upewnij się, że rozmiar pliku mieści się w limitach providera (zazwyczaj < 25MB)
- Sprawdź ważność klucza API na karcie providera
---
## Debugowanie translatora
Użyj **Dashboard → Translator**, aby debugować problemy z tłumaczeniem formatów:
| Tryb | Kiedy używać |
| ---------------- | ---------------------------------------------------------------------------------------------- |
| **Playground** | Porównaj formaty wejścia/wyjścia obok siebie — wklej padające żądanie, by zobaczyć tłumaczenie |
| **Chat Tester** | Wysyłaj żywe wiadomości i przeglądaj pełny payload request/response wraz z nagłówkami |
| **Test Bench** | Uruchamiaj testy wsadowe na kombinacjach formatów, by znaleźć zepsute tłumaczenia |
| **Live Monitor** | Obserwuj przepływ żądań w czasie rzeczywistym, by wyłapać przerywane problemy z tłumaczeniem |
### Typowe problemy z formatami
- **Brak tagów thinking** — Sprawdź, czy docelowy provider wspiera thinking i ustawienie thinking budget
- **Znikające tool calls** — Niektóre tłumaczenia formatów mogą usuwać nieobsługiwane pola; sprawdź w trybie Playground
- **Brak system prompt** — Claude i Gemini obsługują system prompts inaczej; sprawdź wynik tłumaczenia
- **SDK zwraca surowy string zamiast obiektu** — Naprawione w v1.x; sanitizer odpowiedzi usuwa niestandardowe pola (`x_groq`, `usage_breakdown` itd.), które powodują błędy walidacji Pydantic w OpenAI SDK. Jeśli nadal to widzisz na v3.x+, zgłoś issue.
- **GLM/ERNIE odrzuca rolę `system`** — Naprawione w v1.x; normalizer ról automatycznie scala wiadomości system w user dla niekompatybilnych modeli. Jeśli nadal to widzisz na v3.x+, zgłoś issue.
- **Rola `developer` nierozpoznana** — Naprawione w v1.x; automatycznie konwertowana na `system` dla providerów spoza OpenAI. Jeśli nadal to widzisz na v3.x+, zgłoś issue.
- **`json_schema` nie działa z Gemini** — Naprawione w v1.x; `response_format` jest teraz konwertowany na `responseMimeType` + `responseSchema` Gemini. Jeśli nadal to widzisz na v3.x+, zgłoś issue.
---
## Ustawienia odporności (Resilience)
### Auto rate-limit się nie uruchamia
- Auto rate-limit dotyczy tylko providerów z kluczem API (nie OAuth/subskrypcja)
- Sprawdź, czy **Settings → Resilience → Provider Profiles** ma włączony auto-rate-limit
- Sprawdź, czy provider zwraca kody `429` lub nagłówki `Retry-After`
### Dostrajanie exponential backoff
Profile providerów wspierają te ustawienia:
- **Base delay** — Początkowy czas oczekiwania po pierwszej awarii (domyślnie: 1s)
- **Max delay** — Górny limit czasu oczekiwania (domyślnie: 30s)
- **Multiplier** — O ile zwiększać opóźnienie przy kolejnych awariach (domyślnie: 2x)
### Anti-thundering herd
Gdy wiele równoległych żądań trafia w providera z limitem zapytań, OmniRoute używa mutexa + auto rate-limiting, aby serializować żądania i zapobiegać awariom kaskadowym. Działa to automatycznie dla providerów z kluczem API.
---
## Opcjonalna taksonomia awarii RAG / LLM (16 problemów)
Część użytkowników OmniRoute stawia bramkę przed stackami RAG lub agentów. W takich setupach często widać dziwny wzorzec: OmniRoute wygląda na zdrowe (providery w górze, profile routingu OK, brak alertów rate limit), a ostateczna odpowiedź i tak jest błędna.
W praktyce te incydenty zwykle pochodzą z downstreamowego pipeline'u RAG, a nie z samej bramki.
Jeśli chcesz wspólnego słownika do opisu tych awarii, możesz użyć WFGY ProblemMap — zewnętrznego zasobu tekstowego na licencji MIT, który definiuje szesnaście powtarzających się wzorców awarii RAG / LLM. Na wysokim poziomie obejmuje:
- drift retrieval i zerwane granice kontekstu
- puste lub nieaktualne indeksy i magazyny wektorów
- niedopasowanie embeddingów do semantyki
- składanie promptów i problemy z oknem kontekstu
- zapaść logiki i nadmiernie pewne odpowiedzi
- awarie długich łańcuchów i koordynacji agentów
- dryf pamięci i ról w multi-agent
- problemy z deploymentem i kolejnością bootstrapu
Idea jest prosta:
1. Gdy badziesz złą odpowiedź, zbierz:
- zadanie użytkownika i żądanie
- trasę lub combo providerów w OmniRoute
- kontekst RAG użyty downstream (pobrane dokumenty, tool calls itd.)
2. Zmapuj incydent na jeden lub dwa numery WFGY ProblemMap (`No.1` … `No.16`).
3. Zapisz numer we własnym dashboardzie, runbooku lub trackerze incydentów obok logów OmniRoute.
4. Użyj odpowiadającej strony WFGY, by zdecydować, czy zmienić stack RAG, retriever, czy strategię routingu.
Pełny tekst i konkretne przepisy są tutaj (licencja MIT, tylko tekst):
- [WFGY ProblemMap README](https://github.com/onestardao/WFGY/blob/main/ProblemMap/README.md)
Możesz zignorować tę sekcję, jeśli nie uruchamiasz pipeline'ów RAG ani agentów za OmniRoute.
---
## Znane problemy v3.8.0
Problemy specyficzne dla wydania v3.8.0 i ich obecne obejścia. Gdy poprawka wejdzie w późniejszym patchu, wpis zostanie zaktualizowany lub usunięty.
### Błędy auth Devin CLI
**Objawy:**
- "Devin CLI not found" lub "auth failed" przy wywoływaniu narzędzi opartych o Devin
- Sprawdzenie runtime CLI raportuje `installed=false`
**Przyczyny:**
- `CLI_DEVIN_BIN` wskazuje na nieistniejącą ścieżkę
- Devin CLI nie jest zainstalowany na hoście
**Naprawa:**
1. Zainstaluj Devin CLI dla swojej platformy
2. Ustaw `CLI_DEVIN_BIN=/usr/local/bin/devin` (lub rzeczywistą ścieżkę) w `.env`
3. Zrestartuj OmniRoute i przetestuj ponownie w **Dashboard → CLI Tools**
### Cooldown modelu utknął (ręczny reset)
**Objawy:**
- Model pozostaje na liście cooldown nawet po upływie czasu wygaśnięcia
- Żądania nadal pomijają model w routingu combo mimo że znacznik czasu jest w przeszłości
**Ręczny reset:**
- **Dashboard:** **Settings → Model Cooldowns** → kliknij **Re-enable** na dotkniętej karcie
- **API:** `DELETE /api/resilience/model-cooldowns` z nagłówkami auth zarządzania
### Połączenie providera Command Code kończy się 403
**Objawy:**
- 403 przy testowaniu połączenia providera Command Code
- Karta providera pokazuje "unauthorized" po świeżym dodaniu
**Przyczyna:** Przepływ OAuth nie zakończył się (callback nieodebrany lub token niezapisany).
**Naprawa:**
- Uruchom `omniroute providers` z CLI, aby ponownie wywołać przepływ OAuth, albo
- Ponów OAuth z **Dashboard → Providers → Command Code → Reconnect**
### ModelScope zwraca agresywne cooldowny 429
**Objawy:**
- Bardzo krótkie lub natychmiastowe cooldowny na ModelScope po małej serii żądań
- Routing combo pomija ModelScope wcześniej niż oczekiwano
**Przyczyna:** ModelScope emituje specyficzne dla providera nagłówki `Retry-After`. v3.8.0 zawiera dedykowaną obsługę tych nagłówków, więc starsze wersje odczytują je jako generyczne wskazówki rate-limit.
**Naprawa:**
- Upewnij się, że jesteś na v3.8.0 lub nowszej
- Sprawdź, że przełącznik `useUpstream429BreakerHints` jest włączony w **Settings → Resilience**
### Brak OMNIROUTE_WS_BRIDGE_SECRET w produkcji
**Objawy:**
- 401 na każdym żądaniu mostka WebSocket Codex/Responses na zdalnym hoście produkcyjnym
- Handshake mostka WebSocket zamyka się natychmiast po połączeniu
**Przyczyna:** Zmienna środowiskowa `OMNIROUTE_WS_BRIDGE_SECRET` nie jest ustawiona w środowisku produkcyjnym.
**Naprawa:**
1. Wygeneruj losowy sekret: `openssl rand -hex 32`
2. Ustaw `OMNIROUTE_WS_BRIDGE_SECRET=<random-secret>` w env serwera produkcyjnego (oraz każdego klienta łączącego się z mostkiem)
3. Zrestartuj OmniRoute
### Responses API: tryb background zdegradowany do synchronicznego
**Objawy:**
- Zalogowane ostrzeżenie: `background mode degraded to synchronous`
- Żądanie z `background: true` zwraca zwykłą odpowiedź synchroniczną zamiast uchwytu zadania w tle
**Przyczyna:** v3.8.0 celowo degraduje `background: true` w Responses API do wykonania synchronicznego z ostrzeżeniem. Pełne asynchroniczne wykonanie w tle to przyszła funkcjonalność.
**Naprawa:**
- Dostosuj klienta, aby wywoływał bez `background`, albo
- Poczekaj na późniejsze wydanie z pełnym trybem async background (śledź changelog)
---
## Nadal utknąłeś?
- **GitHub Issues**: [github.com/diegosouzapw/OmniRoute/issues](https://github.com/diegosouzapw/OmniRoute/issues)
- **Architektura**: Zobacz [`docs/architecture/ARCHITECTURE.md`](../architecture/ARCHITECTURE.md) po szczegóły wewnętrzne
- **API Reference**: Zobacz [`docs/reference/API_REFERENCE.md`](../reference/API_REFERENCE.md) po wszystkie endpointy
- **Health Dashboard**: Sprawdź **Dashboard → Health** pod kątem statusu systemu w czasie rzeczywistym
- **Translator**: Użyj **Dashboard → Translator** do debugowania problemów z formatami

View File

@@ -1,115 +0,0 @@
---
title: "Ponowna ocena dojrzałości quality-gate (Fase 9)"
---
# Ponowna ocena dojrzałości — po falach 03 (Quality-Gate v2)
> **Czym jest ten dokument.** Ponowny pomiar dojrzałości systemu quality-gates
> **po** falach 03 programu Quality-Gate v2, w porównaniu z baseline zapisanym w
> [`QUALITY_GATE_PLAYBOOK.md`](./QUALITY_GATE_PLAYBOOK.md) (2026-06-16). Mierzy, co się zmieniło,
> względem DSOMM L5 / OpenSSF Scorecard 9 / SLSA L3, rozdzielając to, co jest **mierzalne w CI**
> (już dostarczone / możliwe do dostarczenia kodem) od tego, co jest **procesem/właścicielem** (ustawienia organizacji).
>
> **Data:** 2026-06-30. Wygenerowano ze rzeczywistego stanu repozytorium, nie z pamięci.
> **Benchmarki:** OWASP DSOMM · OpenSSF Scorecard · SLSA · SonarQube "Clean as You Code".
---
## 1. Zaktualizowany werdykt
**Ocena ogólna: A → A („Advanced”, top ~5%).** **Dwie największe słabości strukturalne**
baseline z 06-16 — _fast-gates gap_ oraz _mutation-score-not-a-ratchet_ — zostały **zamknięte**.
Pozostałe luki do „absolutnego maksimum” są niemal wyłącznie **zależne od właściciela/infrastruktury** (branch-protection,
SLSA L3, CodeQL advanced); strona kodowa programu jest zasadniczo ukończona.
| Framework referencyjny | Baseline 06-16 | Teraz 06-30 | Ruch | Dowód |
| --------------------------------- | ---------------------------- | -------------------------------------------------------------------- | ---- | --------------------------------------------------------------------------- |
| **OWASP DSOMM** (5 levels) | L3→L4 | **L4** w _Test Intensity_ i _Static Depth_; solidne L3 w pozostałych | ▲ | blocking mutation-ratchet + deterministic suite at merge gate |
| **OpenSSF Scorecard** | ~78/10 | ~78/10 (bez zmian — bramka to **właściciel**) | = | brak Branch-Protection na `main` (ustawienie właściciela) + actions pinning |
| **SLSA** | L2→L3 | **L2** (zbliżanie się do L3) | = | brak hermetic/reproducible builder (infra/właściciel) |
| **SonarQube "Clean as You Code"** | Zgodne z zastrzeżeniem | Zgodne z zastrzeżeniem | = | zastrzeżenie _sprawl_ (~46+ gates) nadal — przegląd ROI w toku |
| **Quality-Ratchet pattern** | Exemplar | **Exemplar+** | ▲ | nowy `dedicatedGate` dla `mutationScore` (direction up) |
| **Mutation testing** | „Almost there” (nie ratchet) | **Active ratchet** | ▲▲ | `check-mutation-ratchet.mjs` + seeded baseline + blocking nightly job |
---
## 2. Delty od 2026-06-16 (co dostarczyły fale 03)
### 2.1 🔴→✅ Luka fast-gates ZAMKNIĘTA (była słabość strukturalna #1)
Baseline ostrzegał: `quality.yml` (PR→`release/**`) uruchamiał **tylko filesystem gates** — bez
typecheck, tests ani build —, więc deterministyczne regresje wybuchały dopiero przy PR→`main`.
**Dziś** `.github/workflows/quality.yml` uruchamia w jobie _Fast Quality Gates_: `typecheck:core`,
**blocking impacted unit tests (TIA) z fail-safe do pełnego suite**,
vitest fast-path oraz unit shards. Bramka działa teraz **tam, gdzie następuje merge** (shift-left),
dokładnie zgodnie z zasadą cross-cutting przepisaną w playbooku.
### 2.2 🟠→✅ Mutation score stał się RATCHETEM (była słabość #3 / P0 #1)
Najsilniejsze antidotum na coverage-gaming było **advisory**. **Dziś**:
- `scripts/check/check-mutation-ratchet.mjs` (domyślnie advisory, `--ratchet` blocking, graceful skip);
- `config/quality/quality-baseline.json` ma zaseedowane wpisy `mutationScore.<module>` (`direction: up`, `dedicatedGate`);
- `.github/workflows/nightly-mutation.yml` ma job **"Mutation score ratchet (blocking)"**, który unifikuje raporty batch i ratchetuje scalone wyniki per-module.
Skutek: per-module mutation score **nie może regredować** — coverage przestał być vanity metric.
### 2.3 ✅ Bramki quick-win (Phase 6A/7) dostarczone
- **a11y axe-core „fake-green” naprawione:** `@axe-core/playwright` w devDeps; `a11y.spec.ts` z warunkowym skip `REQUIRE_AXE`; job w `nightly-resilience.yml`.
- **complexity skanuje `bin/`+`electron`:** `check-complexity.mjs` obejmuje te katalogi w `ESLINT_ARGS`.
- **tracked-artifacts w pre-commit + pre-push:** `.husky/pre-commit` + `pre-push` blokują przypadkowo śledzone artefakty.
---
## 3. 12 kategorii — status (ukierunkowany na delty)
| # | Kategoria | Status 06-30 |
| --- | -------------------------------- | ----------------------------------------------------------------------------------------- |
| 1 | Style & formatting | ✅ bez zmian (Prettier+ESLint lint-staged) |
| 2 | Types | ✅ **wzmocnione**`typecheck:core` teraz także w bramce PR→release |
| 3 | Tests (intensity) | ✅ **wzmocnione** — mutation testing stał się ratchetem; deterministic suite w merge gate |
| 4 | Test policy (anti-gaming) | ✅ bez zmian (pr-test-policy/test-masking/pr-evidence) |
| 5 | Complexity & health | ✅ **wzmocnione** — complexity skanuje bin/electron |
| 6 | Static security (SAST+secrets) | 🟡 CodeQL default-setup (advanced = właściciel); semgrep cloud nie wersjonowany |
| 7 | Supply-chain (deps) | ✅ bez zmian (osv/audit/Trivy/Dependabot + allowlist) |
| 8 | Supply-chain (build/release) | 🟡 SLSA L2 (L3 = hermetic builder, właściciel/infra) |
| 9 | Contracts & API | 🟡 oasdiff/osv advisory (kandydaci na blocking-with-scope, P1) |
| 10 | Docs & i18n (anti-rot) | ✅ **wzmocnione**`fabricated-docs --strict` blocking (exit 0 zweryfikowany) |
| 11 | Anti-hallucination / consistency | ✅ bez zmian (known-symbols/fetch-targets/docs-symbols/db-rules) |
| 12 | Resilience & domain | ✅ bez zmian (chaos/heap/k6/promptfoo/garak nightly) |
---
## 4. Pozostałe luki do „absolutnego maksimum”
### 4.1 Mierzalne w CI / możliwe do dostarczenia kodem (backlog tego programu)
- **P1 — osv/oasdiff → blocking z właściwym zakresem:** osv tylko `CRITICAL`+fixable (dwuetapowo jak Trivy); oasdiff blokuje zmiany łamiące kontrakt.
- **P1 — `require-tighten` blocking (koniec cyklu):** blokuje zyski metryk (zapobiega poluzowaniu baseline bez zapisu).
- **P1/P2 — przegląd ROI / gate sprawl:** konsolidacja micro-gates doc-sync; pomiar czasu per-gate w `ci-summary` (walka z zmęczeniem — zastrzeżenie SonarQube/DORA). Odroczone merge ROI (unified complexity; unified `/api` anti-hallucination) trafiają tutaj.
- **P2 — CodeQL config w repo + semgrep wersjonowany:** więcej kontroli/reprodukowalności.
### 4.2 Proces / właściciel (CI nie może ruszyć — ustawienia organizacji)
- **Branch-protection na `main`** (podnosi Scorecard, zamyka lukę DSOMM). Zob. [`BRANCH_PROTECTION_MAIN.md`](./BRANCH_PROTECTION_MAIN.md).
- **CodeQL Default → Advanced setup.**
- **SLSA L3** — hermetic/reproducible builder (GitHub SLSA generator). Stretch (malejące zwroty).
### 4.3 Wyraźnie poza zakresem
- **DSOMM L5** jest w dużej mierze **na poziomie org / procesu** (nie da się zakodować w CI).
- **SLSA L4** (reprodukowalność bit-for-bit) to zadeklarowany stretch goal.
---
## 5. Elementy odroczone / usunięte (porządkowanie ogona)
- **`semcheck.yaml` (warstwa LLM na semantic drift docs↔code) — USUNIĘTE.** Było **osierocone**
(żaden workflow/skrypt go nie wywoływał) i miało nieaktualne liczniki w regułach. Deterministyczne pokrycie
już istnieje (`check:fabricated-docs --strict` + `check:docs-counts-sync` + `check:docs-symbols`),
a zastrzeżenie _gate sprawl_ zniechęca do dodawania bramki LLM advisory z kosztem cyklicznym.
Może zostać ponownie wprowadzone w przyszłości jako opt-in nightly job, jeśli semantic drift stanie się realnym problemem.
- **`agent-lsp` scaffold — ODROCZONE / opt-in nie włączone.** Istnieje jako wzmianka w docs
(`docs/architecture/QUALITY_GATES.md`, CHANGELOG), ale **bez podpięcia** i bez `.mcp.json.example`
w repo. Pozostaje udokumentowanym scaffoldem opt-in; nie jest aktywną bramką ani luką dojrzałości.

View File

@@ -1,178 +0,0 @@
# Przewodnik konfiguracji Redis w produkcji
## Przegląd
Redis to **opcjonalna, miękka zależność** w OmniRoute — aplikacja degraduje się łagodnie (fallbacki
w pamięci), gdy Redis jest niedostępny. W produkcji strojenie Redis zmniejsza opóźnienia dla trzech
odrębnych obciążeń:
| Obciążenie | Sterownik | Fabryka klienta | Wzorzec kluczy |
| ------------- | -------------------- | ----------------------------------------------- | --------------------------------- |
| Rate limiting | `rateLimiter.ts` | `getRedisClient()` — leniwy singleton `ioredis` | Okna rate limit z atomowością Lua |
| Cache auth | `apiKeys.ts` | Ponownie używa klienta `rateLimiter` | `auth:api_key:<sha256>` z TTL |
| Magazyn quota | `redisQuotaStore.ts` | Osobny singleton `getRedisClient(url)` | Konfigurowalny per instancja |
---
## Bieżąca konfiguracja (domyślne wartości w kodzie)
| Ustawienie | Wartość | Gdzie |
| -------------------------------------------- | ---------------------------------------------------------- | ------------------------------------ |
| Zmienna środowiskowa `REDIS_URL` | `redis://redis:6379` (compose), opcjonalna | `rateLimiter.ts:5`, `.env.example` |
| Zmienna środowiskowa `QUOTA_STORE_REDIS_URL` | osobna, może różnić się od `REDIS_URL` | `quota/storeFactory.ts` |
| `QUOTA_STORE_DRIVER` | `"sqlite"` (domyślnie), `"redis"` opcjonalnie | `quota/storeFactory.ts` |
| ioredis `maxRetriesPerRequest` | `3` | tworzenie klienta w `rateLimiter.ts` |
| `enableReadyCheck` | nieustawione (domyślnie ioredis: `true`) | — |
| `lazyConnect` | nieustawione (domyślnie ioredis: `false`) | — |
| `retryStrategy` | nieustawione (domyślnie ioredis: baza 200 ms, wykładniczo) | — |
| TLS / hasło / indeks DB | **nieskonfigurowane** | — |
| Sentinel / Cluster | **nieskonfigurowane** — tylko samodzielny pojedynczy węzeł | — |
---
## Zalecane strojenie produkcyjne
### 1. Pula połączeń / opcje klienta (konstruktor ioredis `Redis`)
Obecny kod tworzy pojedyncze `new Redis(url)` bez własnych opcji. W produkcyjnych
wdrożeniach multireplica przekaż fabrykę klienta w kodzie albo owiń `getRedisClient()`:
```typescript
const redis = new Redis(REDIS_URL, {
maxRetriesPerRequest: null, // no retry limit; let retryStrategy decide
enableReadyCheck: true, // verify server is ready before accepting calls
lazyConnect: true, // don't connect on construction; wait for first call
retryStrategy: (times) => {
if (times > 10) return null; // give up after 10 retries → reconnect later
return Math.min(times * 200, 5000); // 200ms, 400ms, …, 5s cap
},
enableAutoPipelining: true, // coalesce concurrent commands into one TCP write
keepAlive: 10000, // TCP keepalive every 10s
});
```
**Kluczowe kompromisy:**
- `maxRetriesPerRequest: null` + `retryStrategy` — preferowane w produkcji, aby chwilowe
restarty Redis nie powodowały natychmiastowej awarii każdego żądania. Fallback w pamięci w
`checkRateLimit()` obsługuje ścieżkę błędu.
- `lazyConnect: true` — unika zależności startowej od dostępności Redis, zanim serwer
zacznie przyjmować połączenia.
- `enableAutoPipelining: true` — zmniejsza liczbę round-tripów przy współbieżnych sprawdzeniach rate-limit;
korzystne przy >50 RPS na jednym połączeniu.
### 2. Konfiguracja serwera Redis (`redis.conf`)
```
# Memory
maxmemory 80% # leave room for OS page cache
maxmemory-policy allkeys-lru # evict stale auth cache entries under pressure
# Persistence (optional — OmniRoute is crashsafe without it)
save 300 1 # snapshot at least every 5 min if ≥1 key changed
appendonly no # AOF not needed; data is regeneratable
appendfsync no # no fsync overhead (RDB is sufficient)
# Networking
timeout 0 # no idle disconnect
tcp-keepalive 300 # 5 min keepalive
tcp-backlog 511 # connection backlog for bursty load
# Performance
hz 10 # default; 100 for latencysensitive
activedefrag yes # autodefragment when fragmentation >10%
```
**Kompromis dla `maxmemory-policy allkeys-lru`:** Wpisy cache auth mogą zostać usunięte przy
presji pamięci. To bezpieczne — `setCachedApiKey` zawsze uzupełnia cache przy miss, a
fallback SQLite jest autorytatywny. Skrypt Lua rate-limitera tworzy małe klucze, które z
założenia są krótkotrwałe.
### 3. Ustawienia Docker Compose
Produkcyjny compose (`docker-compose.prod.yml`) używa `redis:8.6.2-alpine`. Dodaj:
```yaml
redis:
image: redis:8.6.2-alpine
command:
[
"redis-server",
"--maxmemory",
"512mb",
"--maxmemory-policy",
"allkeys-lru",
"--activedefrag",
"yes",
"--save",
"300 1",
]
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 3s
retries: 3
start_period: 5s
```
### 4. Uwagi dotyczące wielu instancji / skalowania
**Jeden Redis dla wszystkich replik** — skrypt Lua rate-limitera zależy od jednej
autorytatywnej przestrzeni kluczy. Wiele instancji Redis za replikami straciłoby atomowość
i podwoiłoby budżet. Używaj jednego Redis (lub klastra Redis Sentinel z failover) dla
wszystkich replik aplikacji.
**Liczba połączeń:** Każda replika aplikacji otwiera **2 połączenia TCP** do Redis
(klient rate limitera + klient magazynu quota). Przy 10 replikach → 20 połączeń, znacznie
poniżej domyślnego limitu 10k połączeń instancji Redis.
### 5. Monitoring
Udostępnij przez endpoint health-check:
```typescript
// src/app/api/monitoring/health/route.ts already calls rateLimiter functions
// Add Redis-specific checks:
// 1. PING latency via ioredis .ping()
// 2. Memory usage via INFO memory
// 3. Connection count via INFO clients
// 4. Hit rate for maxmemory-policy (evicted_keys / keyspace_hits)
```
Kluczowe metryki do obserwacji:
- **Evicted keys / sec** — jeśli trwale niezerowe, zwiększ `maxmemory`
- **Blocked clients** — wartość niezerowa sugeruje wolne skrypty Lua lub wysoką kontencję
- **Rejected connections** — osiągnięty limit połączeń; rzadkie przy 20 połączeniach
---
## Diagram architektury
```mermaid
flowchart LR
subgraph App["App Replica"]
RL[rateLimiter.ts]
AK[apiKeys.ts]
QS[redisQuotaStore.ts]
end
RL -- "REDIS_URL" --> R1[(Redis\nshared)]
AK -- "reuses RL's client" --> R1
QS -- "QUOTA_STORE_REDIS_URL" --> R2[(Redis\nquota store)]
R1 --> R2 -- "can be same instance" --> R1
```
---
## Odnośniki
| Plik | Przeznaczenie |
| ---------------------------------- | -------------------------------------------------------------- |
| `src/shared/utils/rateLimiter.ts` | Główny klient Redis, skrypt Lua rate-limit, fallback w pamięci |
| `src/lib/db/apiKeys.ts` | Cache auth — fallback Redis→SQLite |
| `src/lib/quota/redisQuotaStore.ts` | Osobny klient Redis dla opcjonalnego magazynu quota |
| `src/lib/quota/storeFactory.ts` | Przełącza sterowniki quota między `sqlite` a `redis` |
| `docker-compose.prod.yml` | Kontener Redis w prod (obraz `redis:8.6.2-alpine`) |
| `.env.example` | Dokumentacja zmiennych środowiskowych Redis |
| `src/app/api/local/redis/` | Trasy API do orkiestracji kontenera dev |
| `bin/cli/commands/redis.mjs` | Komendy CLI do orkiestracji kontenera dev |

View File

@@ -1,168 +0,0 @@
# [Feature] Pluginization Phase 1: Extract Playwright/CloakBrowser Browser Pool
**Labels:** `enhancement`, `plugin`, `architecture`
## Problem / Use Case
OmniRoute's Playwright/CloakBrowser dependency is a **large, non-essential dependency** (~1500+ LOC across 22 source files + ~35 test files) pulled into every installation regardless of whether the user needs browser-backed chat. Users who run OmniRoute purely as a proxy/router (the majority) pay for:
- **Disk space**: ~200+ MB from Playwright browsers + Chromium binaries (installed via `npx playwright install`)
- **Build complexity**: Turbopack must handle the `cloakbrowser` package
- **Bundle size**: All browser-pool code is compiled into the main codebase
- **Surface area**: 7 files with direct Playwright imports (4 dynamic, 3 static type imports)
- **CI/cache impact**: Playwright installs in CI pipelines even when not needed
Currently, only environment variables (`OMNIROUTE_BROWSER_POOL=off`) gate _runtime_ execution — the code still loads, imports get resolved, and Playwright must be installed.
## Proposed Solution
Extract the Playwright/CloakBrowser browser pool into an **optional package** loaded via dynamic `import()` at runtime, following the existing pattern used for `cloakbrowser` (computed-string dynamic import to avoid resolution). The core retains thin interface stubs that gracefully degrade when the optional package is absent.
### Architecture
```
open-sse/
interfaces/
browserPool.ts ← NEW: BrowserPoolProvider interface + types
services/
browserPool.ts ← BECOMES: thin stub, delegates to optional package
browserBackedChat.ts ← BECOMES: thin stub, delegates to optional package
grokClearance.ts ← BECOMES: thin stub
packages/browser-pool/ ← NEW: optional package
index.ts ← exports BrowserPoolProvider implementation
src/
browserPool.ts ← extracted from open-sse/services/browserPool.ts
browserBackedChat.ts ← extracted from open-sse/services/browserBackedChat.ts
grokClearance.ts ← extracted from open-sse/services/grokClearance.ts
claudeTurnstileSolver.ts ← extracted (tightly coupled, moved as-is)
inAppLoginService.ts ← extracted (own Playwright instance, separate lifecycle)
package.json
tsconfig.json
```
### Phase Breakdown
**Phase 1 — Core pool extraction (this issue):**
1. Define `BrowserPoolProvider` interface in `packages/browser-pool/src/interfaces.ts`
2. Extract `browserPool.ts` (~502 LOC), `browserBackedChat.ts` (~270 LOC), `grokClearance.ts` (~84 LOC) into `packages/browser-pool/`
3. Replace core files with thin stubs that try `import('../../../packages/browser-pool')` with graceful fallback
4. Keep `poolTools.ts` importing the core stub (unchanged from consumer perspective)
5. Make Playwright an optional dependency (not in root `package.json`)
6. Typecheck core passes with and without the package installed
7. All existing tests pass (with plugin installed)
**Phase 2 — Turnstile solver extraction (future):**
- Extract `claudeTurnstileSolver.ts` (~212 LOC) — has static Playwright type imports, needs type interface
- Move `claudeWebAutoRefresh.ts` (depends on turnstile solver)
**Phase 3 — Standalone Playwright instances (future):**
- Extract `inAppLoginService.ts` (~257 LOC)
- Refactor `gemini-web.ts` executor's own Playwright path (~553 LOC)
### Interface Design (Phase 1)
```typescript
// packages/browser-pool/src/interfaces.ts
export interface BrowserPoolProvider {
acquireBrowserContext(options?: BrowserPoolContextOptions): Promise<PooledContext>;
releaseBrowserContext(ctx: PooledContext): Promise<void>;
getBrowserPoolMetrics(): BrowserPoolMetrics;
shutdownPool(): Promise<void>;
isPoolEnabled(): boolean;
openPage(url: string, ctx?: PooledContext): Promise<{ page: any }>;
readPageResponseBody(page: any): Promise<string>;
getBrowserPoolStatus(): BrowserPoolStatus;
}
```
### Stub Pattern
```typescript
// open-sse/services/browserPool.ts — thin stub
let _impl: BrowserPoolProvider | null = null;
async function getImpl(): Promise<BrowserPoolProvider> {
if (!_impl) {
try {
const { createBrowserPoolProvider } = await import("../../packages/browser-pool");
_impl = createBrowserPoolProvider();
} catch {
// Graceful fallback — disabled
_impl = createNullBrowserPoolProvider();
}
}
return _impl;
}
export async function acquireBrowserContext(...args) {
return (await getImpl()).acquireBrowserContext(...args);
}
```
## Alternatives Considered
1. **Existing hook-based PluginManager**: Rejected. The current PluginManager operates via child-process IPC and request-pipeline hooks (`onRequest`, `onResponse`, `onError`). A browser pool is an in-process runtime service with composable lifecycle — not a request pipeline hook. Forcing it through IPC would add ~50ms+ per browser operation and break the existing synchronous pool pattern.
2. **Keep as-is, just lazy-load the import**: Minimal improvement — the dependency tree still references Playwright types, requiring it to be available. Doesn't reduce bundle size or simplify CI.
3. **Replace Playwright with a protocol-level abstraction**: Too ambitious and would change the behavior of the pool. Playwright's CDP capabilities (context isolation, cookies, screenshots) are fundamental to how the pool works.
4. **Monorepo workspace**: Too heavy for this scope. A simple extracted package avoids workspace tooling changes.
## Acceptance Criteria
1. `packages/browser-pool/src/interfaces.ts` exists and exports `BrowserPoolProvider`, `PooledContext`, `BrowserPoolMetrics` types
2. `open-sse/services/browserPool.ts` becomes a thin stub with zero Playwright imports
3. `packages/browser-pool/` contains all extracted implementation (browserPool, browserBackedChat, grokClearance)
4. Core typecheck (`npm run typecheck:core`) passes with 0 errors **without** the browser-pool package installed
5. Core typecheck passes with the package installed
6. All existing tests pass when the browser-pool package is installed
7. `poolTools.ts` `omniroute_browser_pool_status` tool works end-to-end when the package is installed
8. Graceful degradation: when the package is absent, `getBrowserPoolStatus()` returns `{ enabled: false }` without crashing
9. Playwright is moved from root `dependencies` to optional/peer in the extracted package
10. Documentation updated in `docs/reference/ENVIRONMENT.md`
## Expected Test Plan
- Unit tests for the stub fallback path (simulate import failure, verify graceful degradation)
- Unit tests moved to the extracted package
- Verify `tests/unit/browser-pool-optional-import.test.ts` passes (still validates cloakbrowser isn't statically resolved)
- Verify `tests/unit/browserPool-proxy.test.ts` passes
- Verify `tests/unit/browserBackedChat-matcher.test.ts` passes
- E2E: `npm run typecheck:core` without the package installed → 0 errors
- E2E: `npm run test:coverage` (with package installed) → existing coverage gates pass
## Additional Context
Current dependency graph (simplified):
```
open-sse/services/browserPool.ts (502 LOC, singleton Playwright/CloakBrowser pool)
├── open-sse/services/browserBackedChat.ts (270 LOC, browser-backed chat runner)
│ ├── open-sse/executors/claude-web.ts (imports tryBackedChat)
│ └── open-sse/executors/duckduckgo-web.ts (imports tryBackedChat)
├── open-sse/services/grokClearance.ts (84 LOC, CF clearance via browser)
└── open-sse/mcp-server/tools/poolTools.ts (imports getBrowserPoolMetrics)
Standalone Playwright users (separate, future phases):
├── open-sse/services/claudeTurnstileSolver.ts (212 LOC, static Playwright type imports)
├── open-sse/services/inAppLoginService.ts (257 LOC, own browser lifecycle)
└── open-sse/executors/gemini-web.ts (553 LOC, private Playwright path)
Kill switches: OMNIROUTE_BROWSER_POOL, WEB_COOKIE_USE_BROWSER (both env vars)
```
Total extracted in Phase 1: ~856 LOC, 3 files.
Total deferred to Phase 2/3: ~1022 LOC, 4 files.
This is the first pluginization step. Future targets (separate issues): memory/compression plugin, additional provider support extraction.
## Related References
- PR #8219 (model catalog connection filter + cache TTL) — same baseline `release/v3.8.49`
- `docs/reference/ENVIRONMENT.md` — browser pool env vars documentation
- Plugin system docs at `docs/PLUGINS.md` — existing PluginManager (not used here, referenced for contrast)

View File

@@ -13,7 +13,7 @@ change type to its contracts, focused checks, and CI coverage.
1. **Choose the base before editing.** Find the highest active `release/v*` branch and branch from
its tip. Target that branch, not `main`. If a release freeze is active, do not target the frozen
branch; use the next active cycle described in
[Branching & Release Model](../ops/BRANCHING_MODEL.md).
[Branching & Release Model](BRANCHING_MODEL.md).
2. **Name the contracts.** Identify every catalog, schema, generated artifact, public API, or user
interface that the change affects. The table below gives the minimum starting set.
3. **Write or update focused tests.** Production changes in `src/`, `open-sse/`, `electron/`, or
@@ -225,5 +225,5 @@ Before requesting review:
- Never weaken assertions or drop required tests merely to match a moved base.
For release-freeze and retargeting rules, use
[Branching & Release Model](../ops/BRANCHING_MODEL.md). For the complete CI inventory, use
[Branching & Release Model](BRANCHING_MODEL.md). For the complete CI inventory, use
[Quality Gates Reference](../architecture/QUALITY_GATES.md).

View File

@@ -1,115 +0,0 @@
---
title: "Quality-Gate Maturity Re-evaluation (Fase 9)"
---
# Maturity Re-evaluation — post-Waves 03 (Quality-Gate v2)
> **What this document is.** A re-measurement of the quality-gates system maturity
> **after** Waves 03 of the Quality-Gate v2 program, compared to the baseline recorded in
> [`QUALITY_GATE_PLAYBOOK.md`](./QUALITY_GATE_PLAYBOOK.md) (2026-06-16). Measures what changed,
> against DSOMM L5 / OpenSSF Scorecard 9 / SLSA L3, separating what is **CI-measurable**
> (already delivered / deliverable by code) from what is **process/owner** (organization settings).
>
> **Date:** 2026-06-30. Generated from the actual state of the repository, not from memory.
> **Benchmarks:** OWASP DSOMM · OpenSSF Scorecard · SLSA · SonarQube "Clean as You Code".
---
## 1. Updated verdict
**Overall grade: A → A ("Advanced", top ~5%).** The **two biggest structural weaknesses**
of the 06-16 baseline — the _fast-gates gap_ and the _mutation-score-not-a-ratchet_ — have been **closed**.
The residual gaps for "absolute maximum" are almost all **owner/infra-gated** (branch-protection,
SLSA L3, CodeQL advanced); the code side of the program is essentially complete.
| Reference framework | Baseline 06-16 | Now 06-30 | Movement | Evidence |
| --------------------------------- | ------------------------------ | ----------------------------------------------------------------- | -------- | --------------------------------------------------------------------- |
| **OWASP DSOMM** (5 levels) | L3→L4 | **L4** in _Test Intensity_ and _Static Depth_; solid L3 in others | ▲ | blocking mutation-ratchet + deterministic suite at merge gate |
| **OpenSSF Scorecard** | ~78/10 | ~78/10 (unchanged — gate is the **owner**) | = | missing Branch-Protection on `main` (owner setting) + actions pinning |
| **SLSA** | L2→L3 | **L2** (approaching L3) | = | missing hermetic/reproducible builder (infra/owner) |
| **SonarQube "Clean as You Code"** | Aligned with caveat | Aligned with caveat | = | _sprawl_ caveat (~46+ gates) persists — ROI review pending |
| **Quality-Ratchet pattern** | Exemplar | **Exemplar+** | ▲ | new `dedicatedGate` for `mutationScore` (direction up) |
| **Mutation testing** | "Almost there" (not a ratchet) | **Active ratchet** | ▲▲ | `check-mutation-ratchet.mjs` + seeded baseline + blocking nightly job |
---
## 2. Deltas since 2026-06-16 (what Waves 03 delivered)
### 2.1 🔴→✅ Fast-gates gap CLOSED (was structural weakness #1)
The baseline warned: `quality.yml` (PR→`release/**`) ran **only filesystem gates** — no
typecheck, tests, or build —, so deterministic regressions only exploded on PR→`main`.
**Today** `.github/workflows/quality.yml` runs, in the _Fast Quality Gates_ job: `typecheck:core`,
**blocking impacted unit tests (TIA) with fail-safe to the full suite**, the
vitest fast-path, and unit shards. The gate now runs **where the merge happens** (shift-left),
exactly the cross-cutting principle the playbook prescribes.
### 2.2 🟠→✅ Mutation score became a RATCHET (was weakness #3 / P0 #1)
The strongest antidote against coverage-gaming was **advisory**. **Today**:
- `scripts/check/check-mutation-ratchet.mjs` (advisory by default, `--ratchet` blocking, graceful skip);
- `config/quality/quality-baseline.json` has seeded `mutationScore.<module>` entries (`direction: up`, `dedicatedGate`);
- `.github/workflows/nightly-mutation.yml` has the **"Mutation score ratchet (blocking)"** job that unifies batch reports and ratchets merged per-module scores.
Result: the per-module mutation score **cannot regress** — coverage has ceased to be a vanity metric.
### 2.3 ✅ Quick-win gates (Phase 6A/7) delivered
- **a11y axe-core "fake-green" fixed:** `@axe-core/playwright` in devDeps; `a11y.spec.ts` with conditional `REQUIRE_AXE` skip; job in `nightly-resilience.yml`.
- **complexity scans `bin/`+`electron`:** `check-complexity.mjs` includes those directories in `ESLINT_ARGS`.
- **tracked-artifacts in pre-commit + pre-push:** `.husky/pre-commit` + `pre-push` block accidentally tracked artifacts.
---
## 3. The 12 categories — status (delta-focused)
| # | Category | Status 06-30 |
| --- | -------------------------------- | ---------------------------------------------------------------------------------------- |
| 1 | Style & formatting | ✅ unchanged (Prettier+ESLint lint-staged) |
| 2 | Types | ✅ **reinforced**`typecheck:core` now also in the PR→release gate |
| 3 | Tests (intensity) | ✅ **reinforced** — mutation testing became a ratchet; deterministic suite at merge gate |
| 4 | Test policy (anti-gaming) | ✅ unchanged (pr-test-policy/test-masking/pr-evidence) |
| 5 | Complexity & health | ✅ **reinforced** — complexity scans bin/electron |
| 6 | Static security (SAST+secrets) | 🟡 CodeQL default-setup (advanced = owner); semgrep cloud not versioned |
| 7 | Supply-chain (deps) | ✅ unchanged (osv/audit/Trivy/Dependabot + allowlist) |
| 8 | Supply-chain (build/release) | 🟡 SLSA L2 (L3 = hermetic builder, owner/infra) |
| 9 | Contracts & API | 🟡 oasdiff/osv advisory (candidates for blocking-with-scope, P1) |
| 10 | Docs & i18n (anti-rot) | ✅ **reinforced**`fabricated-docs --strict` blocking (exit 0 verified) |
| 11 | Anti-hallucination / consistency | ✅ unchanged (known-symbols/fetch-targets/docs-symbols/db-rules) |
| 12 | Resilience & domain | ✅ unchanged (chaos/heap/k6/promptfoo/garak nightly) |
---
## 4. Residual gaps for "absolute maximum"
### 4.1 CI-measurable / deliverable by code (this program's backlog)
- **P1 — osv/oasdiff → blocking with the right scope:** osv only `CRITICAL`+fixable (two-step like Trivy); oasdiff blocks contract-breaking changes.
- **P1 — `require-tighten` blocking (end of cycle):** locks metric gains (prevents loosening the baseline without recording).
- **P1/P2 — ROI review / gate sprawl:** consolidate doc-sync micro-gates; measure per-gate timing in `ci-summary` (combats fatigue — SonarQube/DORA caveat). Deferred ROI merges (unified complexity; unified `/api` anti-hallucination) fall here.
- **P2 — CodeQL config committed + semgrep versioned:** more control/reproducibility.
### 4.2 Process / owner (CI cannot move — organization settings)
- **Branch-protection on `main`** (raises Scorecard, closes the DSOMM gap). See [`BRANCH_PROTECTION_MAIN.md`](./BRANCH_PROTECTION_MAIN.md).
- **CodeQL Default → Advanced setup.**
- **SLSA L3** — hermetic/reproducible builder (GitHub SLSA generator). Stretch (diminishing returns).
### 4.3 Explicitly out of scope
- **DSOMM L5** is largely **org-level / process** (not CI-encodable).
- **SLSA L4** (bit-for-bit reproducibility) is a declared stretch goal.
---
## 5. Deferred / removed items (tail housekeeping)
- **`semcheck.yaml` (LLM layer for semantic drift docs↔code) — REMOVED.** It was **orphaned**
(no workflow/script invoked it) and had stale counts in the rules. Deterministic coverage
already exists (`check:fabricated-docs --strict` + `check:docs-counts-sync` + `check:docs-symbols`),
and the _gate sprawl_ caveat discourages adding an LLM advisory gate with recurring cost.
It may be re-introduced in the future as an opt-in nightly job if semantic drift becomes a real problem.
- **`agent-lsp` scaffold — DEFERRED / opt-in not enabled.** Exists as a mention in docs
(`docs/architecture/QUALITY_GATES.md`, CHANGELOG) but **without wiring** and without `.mcp.json.example`
in the repo. Remains as a documented opt-in scaffold; it is not an active gate nor a maturity gap.

View File

@@ -11,6 +11,10 @@ title: "Quality Gate Playbook"
>
> Benchmarks: OWASP DSOMM · OpenSSF Scorecard · SLSA · SonarQube "Clean as You Code" ·
> Quality-Ratchet pattern · DORA 2024 · OWASP LLM Top 10 (2025) · mutation-testing best practices.
>
> For the gate-by-gate authoritative reference (what each gate validates, CI job, ratchet vs
> policy, blocking vs advisory), see the
> [Quality Gates Reference](../architecture/QUALITY_GATES.md).
---

View File

@@ -3,11 +3,21 @@
"pages": [
"RELEASE_CHECKLIST",
"RELEASE_GREEN",
"BRANCHING_MODEL",
"BRANCH_PROTECTION_MAIN",
"MERGE_TRAIN",
"HOMOLOGATION",
"QUALITY_GATE_PLAYBOOK",
"RUNNER_BOX",
"VM_DEPLOYMENT_GUIDE",
"FLY_IO_DEPLOYMENT_GUIDE",
"TUNNELS_GUIDE",
"PROXY_GUIDE",
"DATABASE_GUIDE",
"SQLITE_RUNTIME",
"REDIS_PRODUCTION_CONFIG",
"MONITORING_GUIDE",
"CONTRIBUTION_GOLDEN_PATH",
"COVERAGE_PLAN"
]
}

View File

@@ -1,143 +0,0 @@
---
title: "Feasibility — Telegram Mini App Integration"
version: 3.8.49
lastUpdated: 2026-08-08
---
# Telegram Mini App Integration — Feasibility Analysis
**Status: FEASIBLE with moderate effort (estimated 24 dev-days for a working slice)**
## 1. What "Telegram Mini App" means here
A Telegram Mini App is an iframe-hosted web app opened inside Telegram (via
inline buttons / bot menu buttons) that talks to a bot backend through the
[Telegram WebApp SDK](https://core.telegram.org/bots/webapps). For OmniRoute
the natural shape is:
- **Bot backend** (new): receives Telegram updates (webhook), validates the
Mini App's `initData` signature, and proxies chat requests to OmniRoute's
existing OpenAI-compatible `/v1/chat/completions` surface.
- **Mini App frontend** (new): a small chat UI served by OmniRoute (Next.js
route or `public/` static bundle), using the Telegram WebApp JS SDK.
## 2. Current state of the codebase (verified against `main` @ 918fba5e3)
### Already present — outbound notifications only
| Piece | Location | What it does |
| ---------------------------- | ------------------------------------------- | ----------------------------------------------------------------------------------------------- |
| Telegram webhook integration | `src/lib/webhooks/integrations/telegram.ts` | Builds `sendMessage` payloads for **outbound** gateway events (model, provider, latency, error) |
| Webhook dispatcher | `src/lib/webhookDispatcher.ts` | Routes by kind; decrypts `botToken` from DB metadata for telegram |
| Webhook kinds | `src/lib/db/webhooks.ts` | `slack \| telegram \| discord \| custom` |
| Webhook CRUD + test | `src/app/api/webhooks/*` | Create/update/test; telegram kind skips `url` (uses bot token + chat_id) |
| Bot token validation | `telegram.ts:18` | `BOT_TOKEN_RE = /^\d+:[A-Za-z0-9_-]{35,}$/` |
| Encryption requirement | `webhooks/route.ts:77` | Telegram webhooks require DB encryption enabled (bot tokens stored at rest) |
### Missing — what a Mini App needs that does not exist yet
| Gap | Detail |
| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Inbound Bot API listener** | No `setWebhook` registration, no `/bot<token>/getUpdates` polling, no update handling anywhere. Only the `sendMessage` direction exists. |
| **WebApp `initData` validation** | No HMAC-SHA256 check of `initData` against the bot token (`WebAppData` hash validation from the Bot API docs). |
| **Telegram bot library** | `package.json` has no `telegraf`/`grammy`/`telegram-bot-api` dependency. Would need to add one or hand-roll the (small) HMAC + fetch logic. |
| **Mini App hosting surface** | `public/` exists (static assets) and Next.js routes exist; no `/miniapp` route or static bundle yet. |
| **Session → API key mapping** | Mini App users need to authenticate to `/v1/chat/completions`. Two options: per-user generated OmniRoute API keys (via `src/lib/db/apiKeys`) or a bot-side proxy that injects a shared key. |
## 3. Constraints
### 3.1 Architectural
- **No existing inbound-bot layer.** The webhook system is strictly
event→outbound. A Mini App needs a _new_ Bot API webhook endpoint
(`POST /api/telegram/webhook/<botToken-prefix>` or a dedicated route) plus
update dispatch. This is additive — no conflicts with the existing
`webhooks/` subsystem, but the two must not share the `botToken` storage
semantics blindly (webhooks store bot tokens for _outbound_; the Mini App
needs the same token for _inbound_ signature checks — same token, new use).
- **Public HTTPS required.** Telegram only delivers updates to an HTTPS
endpoint with a valid cert. Self-hosted OmniRoute behind Tailscale/ngrok
needs a public tunnel or Cloudflare Tunnel for the webhook path
(`TELEGRAM_WEBHOOK_URL`-style env). The dashboard can render the current
public origin (`OMNIROUTE_PUBLIC_BASE_URL`) but no webhook registration
helper exists.
- **Encryption gate.** `webhooks/route.ts:77` already refuses telegram
kinds without DB encryption. The Mini App bot token has the same
sensitivity (it _is_ the HMAC secret for initData validation) — same gate
applies, which is a _good_ constraint (no plaintext tokens).
### 3.2 Telegram platform
- **initData is the only trust anchor.** Mini App auth = verify
`hash` field of `initData` using HMAC-SHA256(key = SHA256(bot_token),
data = sorted `key=value` pairs minus `hash`). Must be implemented
server-side; never trust the client.
- **No inbound push to arbitrary users.** Telegram bots cannot initiate
conversations. The Mini App works for users who _already_ have the bot —
or you add a `/start` command handler + deep-link (`t.me/bot?startapp=`).
- **Rate limits.** Bot API ~30 msg/s per bot, 20 msg/min per chat group.
Chat responses via `sendMessage`/`answerWebAppQuery` are fine at gateway
scale, but streaming must be emulated (send progressive edits or chunked
messages) — no native SSE into Telegram.
- **WebApp SDK quirks.** `Telegram.WebApp.ready()` must be called; theme
params come from the SDK; the mini app is sandboxed iframe (no
`window.open` to external, clipboard limited). For a chat UI this is fine.
### 3.3 Security / policy
- **Per-user key issuance is the clean model.** Rather than exposing the
admin's own API keys, mint a scoped OmniRoute API key per Telegram user
(`apiKeys` table + `isModelAllowedForKey` policy), or proxy with a single
gateway key and map `user_id` → account. Recommendation: per-user keys so
existing rate-limit / model-allowlist / policy code applies unchanged.
- **initData expiry.** `auth_date` in initData must be checked (Telegram
recommends < 24h; short TTLs for chat flows).
- **Secret handling.** Bot token must stay in the encrypted DB / env —
mirror the existing `isEncryptionEnabled()` gate.
## 4. Required next steps (implementation plan)
### Phase 0 — Spike (½1 dev-day)
1. Add `grammy` or `telegraf` (or ~60 lines of hand-rolled HMAC + fetch).
2. Implement `src/lib/telegram/initData.ts``verifyInitData(initData, botToken)`.
3. Stand up a throwaway `POST /api/telegram/miniapp/webhook` route behind
`TELEGRAM_WEBHOOK_SECRET`; register via `setWebhook` once, locally.
### Phase 1 — Minimal chat slice (12 dev-days)
1. **Webhook endpoint** `POST /api/telegram/bot/update` (or
`/api/telegram/miniapp/update`): parse Update, verify initData, dispatch.
2. **Command handler**: `/start` → reply with deep link
`https://t.me/<bot>?startapp=<userKey>`; `startapp` param carries a
one-time token that maps to a generated OmniRoute API key.
3. **Chat proxy**: map `initData.user.id` → API key → call
`handleChat` (same path as `/v1/chat/completions`) → reply via
`sendMessage` (non-stream) or chunked edits (fake streaming).
4. **Mini App page**: `src/app/(dashboard)/miniapp/page.tsx` (or static
bundle in `public/miniapp/`) — Telegram WebApp SDK init + minimal chat
UI posting to the bot webhook.
5. **Config**: `TELEGRAM_BOT_TOKEN` env (or reuse webhook metadata),
`OMNIROUTE_PUBLIC_BASE_URL` for webhook URL display; doc in
`.env.example` + `ENVIRONMENT.md` (env-doc-sync check).
### Phase 2 — Production hardening (1 dev-day)
- Streaming emulation (message edits), error/backpressure mapping to Bot API
limits, per-user key revocation (`/logout` command → revoke API key),
usage/rate-limit surfacing (reuse `enforceApiKeyPolicy`), webhook
registration helper in dashboard settings, i18n for the mini app UI.
## 5. Verdict
**Feasible.** The gateway already exposes the exact API a Mini App chat
needs (`/v1/chat/completions` with per-key policy), and the outbound
Telegram webhook shows the team already handles bot tokens safely
(encryption gate + token format validation). The genuinely new surface is
small: an inbound update webhook + initData HMAC verification + a thin
chat proxy + a static Mini App page. No changes to the core SSE/relay
pipeline are required.
**Primary risks:** (1) public HTTPS requirement for the webhook (tunnel
needed on self-hosted installs), (2) no native streaming to Telegram
(UX tradeoff), (3) initData trust must be strictly server-side.

View File

@@ -123,3 +123,99 @@ If you changed the credential contract (new storage key, new cookie name, change
| Token missing from live request | Request is not authenticated | Sign in and send a chat message first |
| 401 after Test Connection passed | Expired or rotated session | Re-copy from a fresh live request |
| Chunked token fails | Only one chunk pasted | Select all `__Secure-next-auth.session-token.*` chunks |
---
## ChatGPT Web (Codex)
`ChatGPT Web (Codex)` is an additional provider. The existing
`ChatGPT Web (Plus/Pro)` provider described above stays unchanged for regular
chats, images, and its existing tool emulation.
### Prerequisites
- a full Cookie header from a signed-in ChatGPT session;
- Chrome or Chromium for npm, systemd, and PM2 installs;
- with the Docker `web` profile, the internal Chromium service from `docker-compose.yml`;
- an OpenAI tunnel and a ChatGPT custom connector for local Codex tools.
The tunnel is only needed for tool turns. `pro` is read-only and does not need a
local tool connector.
### Dashboard setup
1. Open the **ChatGPT Web (Codex)** provider and add a connection.
2. Paste the full ChatGPT cookie, the tunnel ID, the runtime key, and the name of
the custom connector.
3. Start the check. OmniRoute opens a headless Temporary Chat and also detects
whether `pro` is available for the account.
4. Save the connection. OmniRoute replaces the pasted cookie with the verified
Playwright storage state and stores it together with the runtime key through
the encrypted credential abstraction.
The raw cookie is not retained after a successful save. When the session expires,
open the connection, paste a fresh full cookie, and re-run the check. The doctor
status in the edit dialog reports browser, storage state, sign-in, Temporary
Chat, tunnel, connector, and tool round-trip separately.
### Models and combos
The fixed models are:
- `chatgpt-web-codex/instant`
- `chatgpt-web-codex/medium`
- `chatgpt-web-codex/high`
- `chatgpt-web-codex/extra-high`
- `chatgpt-web-codex/pro`
Add one of them to a combo like any other model. The Codex app sends only the
combo name as `model` to the regular Responses endpoint `/v1/responses`. There is
no special endpoint and no Codex-mode switch.
`pro` does not run local tools. A forced tool makes that combo target
incompatible; with optional tools the turn runs read-only and reports that
limitation as commentary.
### Security model
- The native path requires a Responses request, a recognized Codex client, and
matching thread and turn identities.
- Workspace, sandbox, approval policy, and the tool catalog come from the native
Codex shell. Free-form prompt text is not an authority for them.
- ChatGPT receives only a short-lived capability per turn. The MCP broker accepts
only tools that Codex offered in exactly that turn.
- Auto-confirming "Allow once" only returns the tool request to Codex. Codex
alone decides on approval and execution.
- Before the first output, the combo may fall back to another compatible target.
After that, provider, model, connection, and browser turn stay pinned until the
turn completes.
- Cookies, runtime keys, storage state, and capability tokens do not appear in
provider responses or request logs.
### Headless VPS and Docker
For npm, systemd, and PM2 installs, OmniRoute detects common Chrome and Chromium
paths. Alternatively, set `CHATGPT_WEB_CODEX_CHROME_PATH`.
The Docker `web` profile starts `chatgpt-web-codex-browser` on the internal
Compose network. Its CDP port is not published on the host. The protected profile
volume stays separate from the OmniRoute data volume, and the browser gets enough
shared memory. The internal CDP proxy listens only on the Compose network on port
`9223`; Chrome itself stays bound to loopback inside the sidecar.
A supervisor lease under `DATA_DIR` prevents multiple OmniRoute processes from
owning the same tunnel and broker state. A conflict shows up in the doctor.
### Interactive recovery
The normal path is fully headless. When ChatGPT demands an interactive sign-in or
challenge, the existing VNC browser infrastructure can be used as a recovery
path. Browser UI and CDP must then only be reachable over loopback, an
authenticated management connection, or an SSH tunnel; noVNC stays disabled in
normal operation.
### WebSocket fallback
When a combo contains `ChatGPT Web (Codex)`, the Responses WebSocket bridge
requests the HTTP/SSE fallback before connecting upstream. The actual transfer
then goes through `/v1/responses`.

View File

@@ -1,94 +0,0 @@
# ChatGPT Web (Codex)
`ChatGPT Web (Codex)` ist ein zusätzlicher Provider. Der bestehende Provider
`ChatGPT Web (Plus/Pro)` bleibt für normale Chats, Bilder und dessen bisherige
Tool-Emulation unverändert.
## Voraussetzungen
- ein vollständiger Cookie-Header einer angemeldeten ChatGPT-Sitzung;
- Chrome oder Chromium bei npm-, systemd- und PM2-Installationen;
- beim Docker-Profil `web` der interne Chromium-Dienst aus `docker-compose.yml`;
- ein OpenAI-Tunnel und ein ChatGPT-Custom-Connector für lokale Codex-Tools.
Der Tunnel ist nur für Tool-Runden nötig. `pro` ist read-only und benötigt keinen
lokalen Tool-Connector.
## Einrichtung in der Weboberfläche
1. Öffne den Provider `ChatGPT Web (Codex)` und füge eine Connection hinzu.
2. Füge den vollständigen ChatGPT-Cookie, die Tunnel-ID, den Runtime-Key und den
Namen des Custom Connectors ein.
3. Starte die Prüfung. OmniRoute öffnet headless einen Temporary Chat und erkennt
dabei auch, ob `pro` für das Konto verfügbar ist.
4. Speichere die Connection. OmniRoute ersetzt den eingegebenen Cookie durch den
geprüften Playwright-Storage-State und speichert ihn zusammen mit dem Runtime-Key
über die verschlüsselte Credential-Abstraktion.
Der rohe Cookie wird nach erfolgreichem Speichern nicht zusätzlich aufbewahrt.
Wenn die Sitzung abläuft, öffne die Connection, gib einen frischen vollständigen
Cookie ein und prüfe sie erneut. Der Doctor-Status im Edit-Dialog zeigt Browser,
Storage-State, Anmeldung, Temporary Chat, Tunnel, Connector und Tool-Roundtrip
getrennt an.
## Modelle und Combos
Die festen Modelle sind:
- `chatgpt-web-codex/instant`
- `chatgpt-web-codex/medium`
- `chatgpt-web-codex/high`
- `chatgpt-web-codex/extra-high`
- `chatgpt-web-codex/pro`
Füge eines davon wie jedes andere Modell zu einer Combo hinzu. Die Codex-App
sendet nur den Combo-Namen als `model` an den normalen Responses-Endpunkt
`/v1/responses`. Es gibt keinen Sonderendpoint und keinen Codex-Modus-Schalter.
`pro` führt keine lokalen Tools aus. Ein erzwungenes Tool macht dieses Combo-Ziel
inkompatibel; bei optionalen Tools läuft der Turn read-only und meldet diese
Einschränkung als Commentary.
## Sicherheitsmodell
- Der native Pfad verlangt einen Responses-Request, einen erkannten Codex-Client
sowie zusammenpassende Thread- und Turn-Identitäten.
- Workspace, Sandbox, Approval-Policy und Toolkatalog stammen aus der nativen
Codex-Hülle. Freier Prompttext ist dafür keine Autorität.
- ChatGPT erhält pro Turn nur eine kurzlebige Capability. Der MCP-Broker akzeptiert
ausschließlich Tools, die Codex in genau diesem Turn angeboten hat.
- Das automatische Bestätigen von „Allow once“ gibt nur den Tool-Wunsch an Codex
zurück. Codex allein entscheidet über Freigabe und Ausführung.
- Vor dem ersten Output darf die Combo auf ein anderes kompatibles Ziel fallen.
Danach bleiben Provider, Modell, Connection und Browserturn bis zum Abschluss
gepinnt.
- Cookies, Runtime-Keys, Storage-State und Capability-Tokens erscheinen nicht in
Providerantworten oder Request-Logs.
## Headless VPS und Docker
Bei npm-, systemd- und PM2-Betrieb erkennt OmniRoute übliche Chrome- und
Chromium-Pfade. Alternativ kann `CHATGPT_WEB_CODEX_CHROME_PATH` gesetzt werden.
Das Docker-Profil `web` startet `chatgpt-web-codex-browser` im internen
Compose-Netz. Sein CDP-Port wird nicht auf dem Host veröffentlicht. Das geschützte
Profilvolume bleibt getrennt vom OmniRoute-Datenvolume und der Browser erhält
ausreichend Shared Memory. Der interne CDP-Proxy lauscht nur im Compose-Netz auf
Port `9223`; Chrome selbst bleibt im Sidecar an Loopback gebunden.
Eine Supervisor-Lease unter `DATA_DIR` verhindert, dass mehrere OmniRoute-Prozesse
denselben Tunnel- und Brokerzustand besitzen. Ein Konflikt erscheint im Doctor.
## Interaktive Wiederherstellung
Der normale Pfad ist vollständig headless. Wenn ChatGPT eine interaktive
Anmeldung oder Challenge verlangt, kann die bestehende VNC-Browser-Infrastruktur
als Recovery-Weg verwendet werden. Browser-UI und CDP dürfen dabei nur über
Loopback, eine authentifizierte Managementverbindung oder einen SSH-Tunnel
erreichbar sein; noVNC bleibt im normalen Betrieb deaktiviert.
## WebSocket-Fallback
Enthält eine Combo `ChatGPT Web (Codex)`, fordert die Responses-WebSocket-Brücke
vor der Upstream-Verbindung den HTTP/SSE-Fallback an. Die eigentliche Übertragung
erfolgt dann über `/v1/responses`.

View File

@@ -4,6 +4,7 @@
"pages": [
"ALIBABA-QWEN-PROVIDER-FAMILIES",
"CLAUDE_WEB",
"CHATGPT_WEB",
"AGENTROUTER",
"ZED-DOCKER",
"CURSOR-DOCKER"

View File

@@ -7,6 +7,9 @@
"FEATURE_FLAGS",
"FREE_TIERS",
"FREE_PROXIES_API",
"PROVIDER_REFERENCE"
"PROVIDER_REFERENCE",
"PROVIDER_PLUGIN_MANIFEST",
"RELAY_BACKEND_STRATEGY",
"RELAY_TROUBLESHOOTING"
]
}

View File

@@ -1,4 +1,4 @@
{
"title": "Routing",
"pages": ["AUTO-COMBO", "QUOTA_SHARE", "REASONING_REPLAY"]
"pages": ["AUTO-COMBO", "QUOTA_SHARE", "REASONING_REPLAY", "REASONING_ROUTING"]
}

View File

@@ -6,8 +6,12 @@
"ERROR_SANITIZATION",
"ROUTE_GUARD_TIERS",
"BAN_DETECTION",
"AGENTROUTER_WAF",
"CORS",
"STEALTH_GUIDE",
"EGRESS_POLICY",
"MITM-TPROXY-DECRYPT",
"SUPPLY_CHAIN",
"COMPLIANCE",
"SOCKET_DEV_FINDINGS",
"CLI_TOKEN"

View File

@@ -1,42 +0,0 @@
---
title: "Issue-Agent Executable Triage: Session Overview"
version: 3.8.50
lastUpdated: 2026-08-06
---
# Issue-Agent Executable Triage: Session Overview
Machine status: `in_progress`
Updated at: `2026-07-14`
Issue: `https://github.com/diegosouzapw/OmniRoute/issues/5980`
PR: `https://github.com/diegosouzapw/OmniRoute/pull/7002`
## Goal
Deliver GitHub issue #5980 as a production issue-agent workflow. The workflow
must execute recorded GitHub triage through OmniRoute routing, persist a complete
audit trail, return an actionable result, and cover all terminal outcomes.
## Current State
| artifact_id | requirement | status | current evidence | next proof |
| ----------- | ---------------------------------------------------------------------- | -------------------------------- | ---------------------------------------------------------------------------------------------------- | ------------------------------------------------------------ |
| AC1 | configured provider/model/policy use normal chat routing | `implemented_pending_acceptance` | `623e0d541`, `fa2c1d7c6`; real-route test invokes the issue-agent route and mocks only provider HTTP | prove routing-policy semantics and terminal failure handling |
| AC2 | persist lifecycle, request, output, usage/cost/runtime, terminal error | `not_started` | audit JSONL currently records only pre-execution run context | lifecycle persistence tests |
| AC3 | return actionable triage result | `not_started` | route forwards raw completion body | result contract and integration test |
| AC4 | success, provider failure, timeout, budget stop | `not_started` | only success-route coverage exists | terminal-outcome test matrix |
| release | CI/review evidence | `in_progress` | route-validation and focused tests have prior passing evidence | rerun final gates on PR head |
## Decisions
| decision_id | decision | rationale | status |
| ----------- | -------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | ------------- |
| DEC-001 | Use the in-process `POST` export from `/api/v1/chat/completions` | preserves existing admission, initialization, guardrails, and provider routing | `implemented` |
| DEC-002 | Keep issue-agent execution opt-in with `OMNIROUTE_ISSUE_AGENT_ENABLED=true` | prevents unrequested autonomous execution | `implemented` |
| DEC-003 | Treat AC1 as incomplete until policy and error semantics are verified end-to-end | request construction alone does not prove the chat route consumes the policy or returns correct terminal state | `active` |
## Traceability
The canonical WBS is `03_DAG_WBS.md`; the canonical QA matrix is
`06_TESTING_STRATEGY.md`. Every status change must identify its commit SHA,
exact command, observed result, and PR head.

View File

@@ -1,34 +0,0 @@
---
title: "Issue-Agent Executable Triage: Research"
version: 3.8.50
lastUpdated: 2026-08-06
---
# Issue-Agent Executable Triage: Research
Machine status: `complete_for_current_phase`
## In-Repository Findings
| research_id | source | finding | consequence |
| ----------- | ------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
| RES-001 | `src/app/api/issue-agent/runs/route.ts` | the endpoint validates body, rejects unsupported mode/disabled execution, builds recorded context, writes audit JSONL, then delegates non-dry runs | execution behavior is centralized at the issue-agent route |
| RES-002 | `src/app/api/v1/chat/completions/route.ts` | standard chat entrypoint exports `POST` and owns the normal chat request path | AC1 must exercise this export rather than a fake internal seam |
| RES-003 | `src/lib/issueAgent/execution.ts` | provider and model are resolved into the chat request; policy is only encoded as `X-OmniRoute-Mode` | an implementation review must establish that this header is a consumed routing-policy contract |
| RES-004 | `src/lib/issueAgent/audit.ts` | audit persistence occurs before execution and writes run context/steps only | AC2 is unsatisfied: no transition, completion, usage/cost/runtime, or terminal-error record exists |
| RES-005 | `tests/unit/issue-agent-route-execution.test.ts` | live route test initializes isolated DB, calls the actual issue-agent `POST`, and mocks only `globalThis.fetch` at provider boundary | strong AC1 path evidence, but it verifies success only and does not prove policy consumption |
## Validation Evidence
| evidence_id | command | observed | scope | evidence_sha |
| ----------- | -------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------- | ----------------------------------------------------- | ------------ |
| EVD-001 | `bun test tests/unit/issue-agent-execution.test.ts tests/unit/issue-agent-route-execution.test.ts tests/unit/issue-agent-runs-route.test.ts` | prior focused run reported green | AC1 focused path | `fa2c1d7c6` |
| EVD-002 | `npm run check:route-validation:t06` | prior run reported pass | request route validation | `e6a63eb33` |
| EVD-003 | `npm run typecheck:core` | unresolved `omniglyph` declarations outside issue-agent paths | release gate blocked by pre-existing unrelated errors | pre-existing |
## Research Conclusions
The normal chat route is correctly selected as the AC1 integration seam. The
remaining design work must use a persisted run-lifecycle model rather than
extending the pre-execution JSONL row. No external API research was needed:
the implementation uses existing in-repository routes and provider adapters.

View File

@@ -1,49 +0,0 @@
---
title: "Issue-Agent Executable Triage: Specifications"
version: 3.8.50
lastUpdated: 2026-08-06
---
# Issue-Agent Executable Triage: Specifications
Machine status: `in_progress`
## Acceptance Contract
| ac_id | requirement | acceptance evidence | status |
| ----- | --------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | -------------------------------- |
| AC1 | Non-dry recorded triage executes through normal chat routing with selected provider, model, and policy | actual issue-agent route reaches chat `POST`; provider-boundary mock observes selected target; policy is proven consumed by routing | `implemented_pending_acceptance` |
| AC2 | Persist `accepted`, `running`, and terminal state plus sanitized request/prompt, model output, usage, cost, runtime, and terminal error | durable queryable record contains each field for success and failures | `pending` |
| AC3 | API returns a useful, structured triage result derived from model output | response has stable triage schema and is not a raw opaque provider payload | `pending` |
| AC4 | Tests cover success, provider/model failure, timeout, and budget stop | each outcome asserts HTTP response and persisted terminal record | `pending` |
## API Contract (Target)
| field | rule |
| ------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| `mode` | must be `recorded-triage` |
| execution selection | accepts configured `provider`, `model`, `routingPolicy`, and bounded `timeoutMs` |
| `runId` | stable execution identifier returned for every accepted run |
| result | includes structured triage decision/summary/actions and execution metadata |
| errors | return sanitized terminal error with explicit terminal status; never leak provider credentials or unredacted issue content |
## Persistence Contract (Target)
| field group | required values |
| -------------- | --------------------------------------------------------------------------------------------------------- |
| identity | run ID, issue URL/repository/number, mode, timestamps |
| lifecycle | `accepted`, `running`, `succeeded`, `failed`, `timed_out`, or `budget_stopped` with transition timestamps |
| input | redacted recorded context and rendered prompt fingerprint/content according to retention policy |
| routing | requested provider/model/policy and resolved execution target |
| output | sanitized model output and structured triage result |
| accounting | input/output/total tokens, cost, and runtime when available |
| terminal error | normalized code/message for failure, timeout, and budget stop |
## Assumptions, Risks, Uncertainties
| aru_id | type | statement | mitigation | status |
| ------- | ----------- | ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- | ------ |
| ARU-001 | risk | `X-OmniRoute-Mode` may not be a consumed routing-policy input in the chat route | trace the policy contract and test an observable policy effect | `open` |
| ARU-002 | risk | current catch maps all thrown execution errors to HTTP 400 and does not persist them | introduce typed terminal outcomes and persistence before response mapping | `open` |
| ARU-003 | risk | current audit row is emitted before execution and cannot represent final execution state | replace/extend with append-only lifecycle records or durable run storage | `open` |
| ARU-004 | uncertainty | provider response metadata may differ by adapter | normalize accounting fields and preserve unknowns explicitly | `open` |

View File

@@ -1,31 +0,0 @@
---
title: "Issue-Agent Executable Triage: DAG and WBS"
version: 3.8.50
lastUpdated: 2026-08-06
---
# Issue-Agent Executable Triage: DAG and WBS
Machine status: `in_progress`
| id | phase | acceptance criterion | status | source paths | test paths | evidence_sha | depends_on |
| ------- | ----------- | ---------------------------------------------------------------- | -------- | ----------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | --------------------------- | ------------------------- |
| WBS-001 | contract | AC1-AC4 | complete | `src/app/api/issue-agent/runs/route.ts` | `tests/unit/issue-agent-runs-route.test.ts` | `a4378a26d` | - |
| WBS-002 | execution | AC1: execute through normal chat-completions routing/policy seam | pending | `src/app/api/issue-agent/runs/route.ts`; `src/app/api/v1/chat/completions/route.ts` | `tests/unit/issue-agent-runs-route.test.ts` | `e6a` (reconciled baseline) | WBS-001 |
| WBS-003 | persistence | AC2: persist lifecycle, input, output, usage, and terminal error | pending | `src/lib/issueAgent/*`; `src/app/api/issue-agent/runs/route.ts` | `tests/unit/issue-agent-audit.test.ts`; `tests/unit/issue-agent-runner.test.ts` | `e6a` (reconciled baseline) | WBS-002 |
| WBS-004 | result | AC3: return an actionable triage result from execution | pending | `src/lib/issueAgent/*`; `src/app/api/issue-agent/runs/route.ts` | `tests/unit/issue-agent-runner.test.ts`; `tests/unit/issue-agent-runs-route.test.ts` | `e6a` (reconciled baseline) | WBS-002, WBS-003 |
| WBS-005 | acceptance | AC4: cover success, provider failure, timeout, and budget stop | pending | `src/lib/issueAgent/*` | `tests/unit/issue-agent-*.test.ts` | `e6a` (reconciled baseline) | WBS-002, WBS-003, WBS-004 |
| WBS-006 | release | PR validation and maintainer review | pending | `.github/workflows/*` | CI checks | `a4378a26d` | WBS-005 |
## Dependency Graph
`WBS-001 -> WBS-002 -> WBS-003 -> WBS-004 -> WBS-005 -> WBS-006`
`a4378a26d` is a prerequisite validation repair: it validates the issue-agent request body through the shared route validator and passes `npm run check:route-validation:t06` (535 routes). It does not satisfy AC1-AC4.
## Machine Evidence Contract
Every WBS item must maintain: `id`, `acceptance_criterion`, `status`, `source_paths`, `test_paths`, `command`, `expected`, `observed`, `evidence_sha`, `updated_at`, and `pr_url`.
PR: `https://github.com/diegosouzapw/OmniRoute/pull/7002`
Issue: `https://github.com/diegosouzapw/OmniRoute/issues/5980`

View File

@@ -1,43 +0,0 @@
---
title: "Issue-Agent Executable Triage: Implementation Strategy"
version: 3.8.50
lastUpdated: 2026-08-06
---
# Issue-Agent Executable Triage: Implementation Strategy
Machine status: `in_progress`
## Phase Plan
| phase | work package | dependency | exit evidence | status |
| ----- | ----------------------------------------------------------- | ----------------- | ----------------------------------------------------------------------- | ------------- |
| P1 | verify/finish routing-policy contract and failure semantics | existing AC1 seam | actual chat route test proves policy consumption and non-2xx mapping | `in_progress` |
| P2 | introduce durable execution lifecycle persistence | P1 | records transitions, request/prompt, output, accounting, terminal error | `pending` |
| P3 | normalize actionable triage result | P2 | stable API result schema derived from completion | `pending` |
| P4 | implement terminal outcome controls | P2 | provider failure, timeout, budget stop transition tests | `pending` |
| P5 | release validation and PR review | P1-P4 | focused tests, route gate, relevant typecheck/CI evidence | `pending` |
## Architecture
1. Keep `src/app/api/issue-agent/runs/route.ts` as the API adapter: validation,
feature gate, and response formatting only.
2. Keep the standard chat `POST` as the routing boundary; do not add a parallel
provider invocation path.
3. Extract lifecycle persistence and result normalization into focused
`src/lib/issueAgent/` modules. Do not overload the existing pre-execution audit
writer with unrelated transport behavior.
4. Use typed execution outcomes so provider failure, abort/timeout, and budget
termination are distinguishable before HTTP mapping and persistence.
5. Add tests from the actual route down to a mocked external provider boundary;
use unit tests for pure normalization and lifecycle state transitions.
## Quality Controls
| control | command or review | threshold |
| ------------------ | ----------------------------------------------------- | ---------------------------------------------------------- |
| route contract | `npm run check:route-validation:t06` | pass |
| AC1 route behavior | focused `bun test` issue-agent route/execution suites | policy and provider/model assertions pass |
| AC2-AC4 | lifecycle/result/terminal-outcome suites | all required states persist and API matches |
| static safety | `npm run typecheck:core` | distinguish new failures from existing `omniglyph` blocker |
| patch integrity | `git diff --check origin/main...HEAD` | pass |

View File

@@ -1,27 +0,0 @@
---
title: "Issue-Agent Executable Triage: Known Issues"
version: 3.8.50
lastUpdated: 2026-08-06
---
# Issue-Agent Executable Triage: Known Issues
Machine status: `open`
| issue_id | severity | status | evidence | impact | resolution owner |
| -------- | -------- | ------ | ------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | ------------------------- |
| KI-001 | P1 | `open` | `execution.ts` places `routingPolicy` in `X-OmniRoute-Mode`; the researched chat route has no observed consumer in the AC1 path | AC1 does not yet prove configured routing policy affects routing | AC1 implementation/review |
| KI-002 | P1 | `open` | issue-agent route catches execution errors and returns `{ error }` with HTTP 400 after writing only pre-execution audit | provider failure, timeout, and budget stop lack correct terminal semantics and persistence | AC2/AC4 implementation |
| KI-003 | P1 | `open` | `audit.ts` serializes only run context/steps before execution | AC2 fields for lifecycle, prompt, output, token/cost/runtime, and error are missing | AC2 implementation |
| KI-004 | P1 | `open` | API returns raw `completion.body` | AC3 has no stable actionable triage result contract | AC3 implementation |
| KI-005 | P2 | `open` | `npm run typecheck:core` has unresolved `omniglyph` declarations in `open-sse/services/compression/*` | full typecheck cannot be used as issue-agent completion evidence until separately resolved or excluded with provenance | release validation |
## Resolved/Verified
| issue_id | status | evidence |
| -------- | ---------- | ----------------------------------------------------------------------------------------------------------------------------- |
| KI-R001 | `verified` | `fa2c1d7c6` adds an isolated test that invokes the actual issue-agent route and mocks only provider HTTP for the success path |
| KI-R002 | `verified` | `e6a63eb33` applies shared request-body validation to the issue-agent route; prior route-validation gate passed |
No workaround in this document changes the acceptance contract. Open P1 items
block declaring AC1-AC4 complete.

View File

@@ -1,31 +0,0 @@
---
title: "Issue-Agent Executable Triage: Testing Strategy"
version: 3.8.50
lastUpdated: 2026-08-06
---
# Issue-Agent Executable Triage: Testing Strategy
Machine status: `in_progress`
## QA Matrix
| qa_id | AC | scenario | command | expected | observed | status | evidence_sha |
| ------ | ------------ | ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ | ----------------------------------------------------- | --------------------------------------- | ------------- | ------------ |
| QA-001 | prerequisite | request schema validation | `npm run check:route-validation:t06` | all routes pass | 535 routes scanned; pass | pass | `a4378a26d` |
| QA-002 | AC1 | selected provider/model/policy reaches normal chat-completions seam | `bun test tests/unit/issue-agent-runs-route.test.ts` | captured request uses configured routing inputs | not implemented | pending | `e6a` |
| QA-003 | AC2 | run lifecycle persists input, output, usage, terminal error | `bun test tests/unit/issue-agent-audit.test.ts tests/unit/issue-agent-runner.test.ts` | durable records for every terminal state | not implemented | pending | `e6a` |
| QA-004 | AC3 | successful execution returns actionable triage output | `bun test tests/unit/issue-agent-runner.test.ts tests/unit/issue-agent-runs-route.test.ts` | output derives from routed execution, not placeholder | not implemented | pending | `e6a` |
| QA-005 | AC4 | provider/model failure | `bun test tests/unit/issue-agent-runner.test.ts` | failed lifecycle and sanitized error persisted | missing coverage | pending | `e6a` |
| QA-006 | AC4 | timeout | `bun test tests/unit/issue-agent-runner.test.ts` | timed-out lifecycle and terminal error persisted | missing coverage | pending | `e6a` |
| QA-007 | AC4 | budget stop | `bun test tests/unit/issue-agent-runner.test.ts` | budget stop is explicit and persisted | missing coverage | pending | `e6a` |
| QA-008 | release | core type safety | `npm run typecheck:core` | pass | pending rerun after dependency recovery | pending | `e6a` |
| QA-009 | release | whitespace integrity | `git diff --check origin/main...HEAD` | no errors | passed before remote rewrite | pass/reverify | `a4378a26d` |
## Test Rules
Tests must mock only the external provider boundary. AC1 must exercise the in-process `POST` export from `src/app/api/v1/chat/completions/route.ts` so admission, policy, translator initialization, and routing remain in the execution path. Each terminal outcome asserts both API behavior and persisted audit state.
## Evidence Requirements
Before a WBS item is marked complete, record the exact command output, commit SHA, test identifiers, and whether the test environment had a lockfile-compatible dependency set. The current recovered environment has incomplete dependencies due to `npm ci` disk exhaustion; no pending test may be reported as passing until rerun.

View File

@@ -1,252 +0,0 @@
# Devin Claude Bridge Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Build a fail-closed `devin-cli-agentic` provider that serves local Anthropic Messages requests through Devin CLI ACP stdio while preserving Claude Code tool-use semantics.
**Architecture:** Add a separate Claude-format provider and executor instead of changing the existing OpenAI-format `devin-cli` summarizer. Keep parsing, prompt serialization, Anthropic response rendering, and ACP process handling in focused files under `open-sse/executors/devin-agentic/`, then wire them into the existing provider and executor registries.
**Tech Stack:** TypeScript ES modules, Node child process stdio, Anthropic Messages JSON/SSE, JSON-RPC 2.0 ACP, Node test runner.
---
### Task 1: Agentic Bridge Core
**Files:**
- Create: `open-sse/executors/devin-agentic/types.ts`
- Create: `open-sse/executors/devin-agentic/serializer.ts`
- Create: `open-sse/executors/devin-agentic/toolParser.ts`
- Create: `open-sse/executors/devin-agentic/anthropicResponse.ts`
- Test: `tests/unit/executor-devin-cli-agentic-core.test.ts`
- [ ] **Implement and prove serialization, parsing, validation, and Anthropic rendering**
Interfaces:
```ts
export function serializeAnthropicForDevin(body: unknown): DevinPrompt;
export function parseDevinToolRequest(text: string, tools: AnthropicTool[]): ParsedToolRequest | null;
export function buildClaudeTextResponse(args: ClaudeResponseArgs): Record<string, unknown>;
export function buildClaudeToolUseResponse(args: ClaudeToolUseArgs): Record<string, unknown>;
export function buildClaudeSseFrames(message: Record<string, unknown>): string;
```
Invariants:
- Preserve `text`, `tool_use`, `tool_result`, `thinking`, and `redacted_thinking`.
- Reject `image` with a clear error.
- Reject unknown content block types.
- Allow only one tool request per model turn.
- Validate tool arguments against object JSON Schema with `required`, `type`, `properties`, `additionalProperties`, `enum`, `items`, and scalar types.
- Generate deterministic ids from tool name and canonicalized arguments.
Run: `node --import tsx/esm --test tests/unit/executor-devin-cli-agentic-core.test.ts`
Expected: core tests pass after dependencies are installed.
### Task 2: ACP Executor And Provider Wiring
**Files:**
- Create: `open-sse/executors/devin-cli-agentic.ts`
- Modify: `open-sse/executors/index.ts`
- Create: `open-sse/config/providers/registry/devin-cli-agentic/index.ts`
- Modify: `open-sse/config/providers/index.ts`
- Test: `tests/unit/executor-devin-cli-agentic-acp.test.ts`
- [ ] **Implement and prove fail-closed ACP execution**
Behavior:
- `buildUrl()` returns `devin://acp/stdio`.
- `buildHeaders()` returns `{}`.
- `execute()` spawns only `devin acp` by default or the explicit `CLI_DEVIN_AGENTIC_BIN`/`CLI_DEVIN_BIN` override.
- The child environment removes Anthropic and Claude routing credentials before spawn.
- The executor sends `initialize`, `session/new`, and `session/prompt`.
- The executor collects `agent_message_chunk` text and `session/prompt` final result.
- Non-streaming Claude clients receive native Anthropic JSON.
- Streaming Claude clients receive native Anthropic SSE lifecycle frames.
- Spawn failure, ACP error, timeout, and early exit produce non-2xx responses with sanitized messages.
Run: `node --import tsx/esm --test tests/unit/executor-devin-cli-agentic-acp.test.ts`
Expected: ACP mock tests pass after dependencies are installed.
### Task 3: Isolation Scripts And Documentation
**Files:**
- Create: `scripts/devin-bridge/verify-anthropic-isolation`
- Create: `scripts/devin-bridge/test-unit`
- Create: `scripts/devin-bridge/launch`
- Create: `docs/DEVIN_CLAUDE_BRIDGE.md`
- Modify: `.gitignore`
- [ ] **Implement offline guardrails and operator docs**
Behavior:
- `verify-anthropic-isolation` fails if `CLAUDE_CONFIG_DIR` is missing, points outside an isolated path, or if Anthropic routing env vars are present.
- `test-unit` runs the focused unit tests.
- `launch` refuses to start unless `ENABLE_LIVE_DEVIN_TESTS=1` for live Devin or `DEVIN_BRIDGE_OFFLINE=1` for offline mock mode.
- Documentation distinguishes tested offline behavior from live Devin opt-in behavior.
Run: `./scripts/devin-bridge/verify-anthropic-isolation` with explicit isolated env.
Expected: exits 0 with isolated env and non-zero without it.
### Task 4: Verification
**Files:**
- No additional source files.
- [ ] **Run proportional checks and capture real output**
Commands:
```bash
./scripts/devin-bridge/test-unit
npm test
```
Expected in this workspace before installing dependencies: both commands fail with `ERR_MODULE_NOT_FOUND` for `tsx`. Expected after `npm install`: focused tests pass; `npm test` outcome must be reported from real output.
### Task 5: Close Core Security And Protocol Gaps
**Files:**
- Modify: `open-sse/executors/devin-cli-agentic.ts`
- Modify: `open-sse/executors/devin-agentic/*.ts`
- Modify: `tests/unit/executor-devin-cli-agentic-*.test.ts`
- [ ] **Prove environment allowlisting, response-id correlation, strict standalone tool envelopes, unique ids, bounded repair, size limits, cancellation cleanup, sanitized errors, and explicit `devin://acp/stdio` validation**
Run with `HOME`, `DATA_DIR`, and `SQLITE_FILE` under `.sandbox`; expected: all focused tests pass and an outside-path test fails closed.
### Task 6: Build Reproducible Containers And Network Guard
**Files:**
- Create: `docker/devin-bridge/Dockerfile`
- Create: `docker/devin-bridge/compose.yml`
- Create: `docker/devin-bridge/network-guard/*`
- Create: `docker/devin-bridge/mock-devin/*`
- Create: `.env.devin-bridge.example`
- [ ] **Pin Claude Code 2.1.220 and Devin CLI 3000.2.17, create non-root offline/live profiles, separate auth/config volumes, explicit env allowlist, no host credential mounts, and denied-domain telemetry**
Run: `docker compose -f docker/devin-bridge/compose.yml --profile offline config`; expected: no forbidden mounts/env inheritance and only internal runtime networks.
### Task 7: Deliver Isolation And Operator Scripts
**Files:**
- Create/modify: `scripts/devin-bridge/{build,test-unit,test-contract,test-e2e-mock,verify-anthropic-isolation,login-devin,test-live-devin,launch,clean}`
- [ ] **Make every command idempotent, sandbox-scoped, fail-closed, and secret-safe**
Run: `./scripts/devin-bridge/verify-anthropic-isolation`; expected: positive offline proof passes and each deliberately removed guard returns non-zero.
### Task 8: Real Claude Code Offline E2E
**Files:**
- Create: `tests/fixtures/devin-bridge/e2e-workspace/*`
- Create: `tests/e2e/devin-claude-bridge.e2e.*`
- [ ] **Run pinned Claude Code in the offline container through local `/v1/messages` and mock ACP, proving CLAUDE.md, skill, command, hook, Read/Edit/Bash, tests, multi-turn continuation, and no Anthropic traffic**
Run: `./scripts/devin-bridge/test-e2e-mock`; expected: workspace diff and tests prove Claude Code executed tools while mock Devin only requested them.
### Task 9: Regression, Documentation, Live Gate, And Delivery
**Files:**
- Modify: `docs/DEVIN_CLAUDE_BRIDGE.md`
- Create: `docs/DEVIN_CLAUDE_BRIDGE_PROGRESS.md`
- [ ] **Run focused suites, typecheck, lint, build, docs checks, offline E2E, and isolation proof with fresh output; then run live only after official in-container Devin login**
If login is unavailable, record live as not tested and expose exactly `./scripts/devin-bridge/login-devin` followed by `./scripts/devin-bridge/test-live-devin`. Commit each reversible unit; do not merge or publish until all offline critical checks are green.
### Task 10: Close The Authenticated Live Runtime
**Files:**
- Modify: `open-sse/executors/devin-cli-agentic.ts`
- Modify: `docker/devin-bridge/compose.yml`
- Create: `docker/devin-bridge/network-guard/policy.mjs`
- Modify: `docker/devin-bridge/network-guard/proxy.mjs`
- Modify: `scripts/devin-bridge/select-live-model.mjs`
- Modify: `scripts/devin-bridge/common`
- Modify: `scripts/devin-bridge/login-devin`
- Modify: `scripts/devin-bridge/test-live-devin`
- Modify: `scripts/devin-bridge/verify-anthropic-isolation`
- Modify: `tests/unit/executor-devin-cli-agentic-acp.test.ts`
- Create: `tests/unit/devin-bridge-live-runtime.test.ts`
- [ ] **Implement and prove the authenticated network, auth, and catalog boundaries with block-level TDD**
Invariants:
- The ACP child receives proxy variables only when `DEVIN_BRIDGE_PROXY_URL` is exactly
`http://network-guard:8080`; arbitrary inherited proxy and credential variables stay absent.
- The guard permits suffixes `.devin.ai` and `.cognition.ai`, exact hosts
`server.codeium.com` and `unleash.codeium.com`, and nothing else.
- Claude services cannot mount `devin-auth`; non-Claude services cannot mount the Claude config.
- A zero exit from `devin auth status` is insufficient when output contains a server-fetch failure.
- `family_uid: swe-1.7-lightning` resolves to catalog id `swe-1-7-lightning`; unknown normalized
values fail instead of becoming model ids.
- Login uses the official manual-token flow so no container loopback callback is required.
Run:
```bash
./scripts/devin-bridge/test-unit
node --import tsx/esm --test tests/unit/devin-bridge-live-runtime.test.ts
./scripts/devin-bridge/verify-anthropic-isolation --static
```
Expected: focused tests and static isolation pass; deliberate untrusted proxy, host, mount, auth
status, and model fixtures fail closed.
- [ ] **Commit the reversible live-runtime repair**
```bash
git add open-sse/executors/devin-cli-agentic.ts docker/devin-bridge \
scripts/devin-bridge tests/unit/devin-bridge-live-runtime.test.ts \
tests/unit/executor-devin-cli-agentic-acp.test.ts
git commit -m "fix: close Devin bridge live runtime gaps"
```
### Task 11: Prove Offline And Live Completion
**Files:**
- Modify: `docker/devin-bridge/run-claude-live-e2e.sh`
- Modify: `docs/DEVIN_CLAUDE_BRIDGE.md`
- Modify: `docs/DEVIN_CLAUDE_BRIDGE_PROGRESS.md`
- [ ] **Run the complete deterministic bridge proof before any paid request**
```bash
./scripts/devin-bridge/test-unit
./scripts/devin-bridge/test-contract
./scripts/devin-bridge/test-e2e-mock
./scripts/devin-bridge/verify-anthropic-isolation
npm run typecheck:core
npm run lint
npm run build
npm run check:docs-all
```
Expected: all bridge-specific checks, typecheck, lint, build, and documentation checks pass with
isolated data paths. Any unrelated full-suite infrastructure hang is recorded separately and is
not converted into a pass.
- [ ] **Run exactly the three authorized live scenarios and the no-fallback failure probe**
```bash
ENABLE_LIVE_DEVIN_TESTS=1 ./scripts/devin-bridge/test-live-devin
```
Expected: dynamic discovery selects a returned Devin catalog model; Claude Code reads without
editing, then edits and runs the fixture test, then executes the fixture command. Evidence shows
native tool use by Claude Code, only `devin-cli-agentic` routing, no allowed non-Devin egress,
and an Anthropic-shaped error after the Devin backend is deliberately made unavailable.
- [ ] **Update verified documentation and commit the evidence-backed delivery state**
```bash
git add docker/devin-bridge/run-claude-live-e2e.sh docs/DEVIN_CLAUDE_BRIDGE.md \
docs/DEVIN_CLAUDE_BRIDGE_PROGRESS.md
git commit -m "docs: record verified Devin bridge live delivery"
```

View File

@@ -1,134 +0,0 @@
# Devin Claude Bridge Design
## Baseline
- Branch: `release/v3.8.49`
- HEAD: `ed7db3ee5f89a144b2d931d8605534522f83de30`
- Package version: `3.8.49`
- Node: `v26.0.0`
- npm: `11.12.1`
- Pre-existing worktree state: `.tug/` untracked
- Dependency state: `node_modules` is absent; the first focused test run failed before loading tests because `tsx` was not installed.
- Tugline state: `tug` exists, but `tug search` failed with MCP connection closed and `tug doctor` hung; it was interrupted.
- Upstream check: `git ls-remote` failed because GitHub DNS was unavailable. Web search of the public repository showed the existing `devin-cli` summarizer provider, but no evidence of `devin-cli-agentic`.
## Source Anchors
- `/v1/messages`: `src/app/api/v1/messages/route.ts`
- Existing Devin provider: `open-sse/config/providers/registry/devin-cli/index.ts`
- Existing Devin executor: `open-sse/executors/devin-cli.ts`
- Executor registry: `open-sse/executors/index.ts`
- Provider registry: `open-sse/config/providers/index.ts`
- Format detection: `open-sse/services/provider.ts`
- Claude non-streaming response conversion: `open-sse/handlers/responseTranslator.ts`
- Existing Devin ACP unit test: `tests/unit/executor-devin-cli-acp-protocol-8406.test.ts`
## Findings
The existing `devin-cli` provider is intentionally OpenAI-format and summarizer-oriented. Its executor spawns `devin acp --agent-type summarizer`, flattens the message history into a single text prompt, and emits OpenAI SSE text chunks. It does not preserve Anthropic `tool_use` and `tool_result` blocks.
The safest implementation is a new provider id, `devin-cli-agentic`, with a separate executor. This leaves `devin-cli`, Anthropic OAuth, Claude OAuth, Claude Web, and all host Claude configuration code untouched. The new provider is fail-closed: it only resolves to `devin://acp/stdio`, uses the official Devin CLI ACP stdio path, and has no fallback provider.
## Architecture
Claude Code sends Anthropic Messages requests to local OmniRoute. OmniRoute resolves model ids prefixed with `devin-cli-agentic/` to a new Claude-format provider. The new executor translates the complete Anthropic request into an explicit text prompt for Devin ACP, including system text, structured message history, tool schemas, and prior tool results.
Devin remains a model backend. The executor starts the official fixed no-tools summarizer
role with `devin acp --agent-type summarizer` and frames the serialized request as an
execution trace. Devin must request client-owned tool execution by emitting a strict
XML-wrapped JSON block:
```xml
<tool>
{"name":"Read","arguments":{"file_path":"src/index.ts"}}
</tool>
```
The bridge parses exactly one tool request per model turn, validates that the tool name was supplied in the incoming request, validates arguments against a minimal JSON Schema validator, generates a stable `tool_devin_...` id, and returns a native Anthropic `tool_use` block. If no valid tool request is present, the bridge returns text with `stop_reason: "end_turn"`.
## Error And Safety Rules
- Unsupported Anthropic content blocks fail explicitly; images are rejected.
- Unknown tools fail explicitly.
- Invalid tool arguments fail explicitly.
- Invalid tool XML/JSON fails explicitly.
- Narrative claims that a tool was executed are returned as text, not actions.
- ACP spawn, timeout, early exit, and stderr-only failures return explicit Devin errors.
- The executor never reads `~/.claude`, `~/.claude.json`, macOS Keychain paths, or host Claude config.
- Live Devin is outside normal tests and remains opt-in via `ENABLE_LIVE_DEVIN_TESTS=1`.
## Test Strategy
Focused unit tests cover serialization, tool parsing, validation, Anthropic JSON, Anthropic SSE, malformed tool output, unknown tools, invalid arguments, image rejection, timeout, and spawn failure. Environment scripts provide an offline isolation verifier without reading host Claude credentials.
## Mandatory Runtime Isolation
The bridge runs only through `docker/devin-bridge/compose.yml`. The runtime image is non-root, uses a private `/home/bridge`, and mounts only disposable workspaces, evidence, and bridge harness files. Application source is copied into the image. It never mounts the host home, Docker socket, SSH, cloud credentials, or global Claude configuration. The container receives an explicit environment allowlist; the executor also constructs an allowlisted child environment instead of copying `process.env`.
Build-time network access installs Claude Code `2.1.220` and Devin CLI `3000.2.17` with pinned integrity/checksum. Runtime profiles are separate: `offline` uses only an internal Compose network; `live-devin` exposes egress only through a proxy guard whose allowlist contains Devin/Cognition suffixes and whose default is denial. Devin authentication lives only in the named `devin-auth` volume. Claude configuration lives in a different named volume and is initialized empty.
## Fail-Closed Routing
`devin-cli-agentic` accepts only the synthetic `devin://acp/stdio` target and an explicit Devin binary path inside the container. It cannot use provider combos, auto routing, account fallback, fallback URLs, or an HTTP upstream. Model aliases resolve only to models returned by the Devin catalog or explicitly configured Devin model ids. An ACP failure, timeout, cancellation, invalid frame, unavailable model, or stopped sidecar becomes an Anthropic-shaped error response; no secondary provider is attempted.
## Agentic Contract
The serializer preserves request order, `system`, `tool_choice`, exact tool schemas, `text`, `tool_use`, `tool_result`, `thinking`, and `redacted_thinking`. It rejects unsupported blocks and caps large tool results with an explicit truncation marker and original size. The parser accepts exactly one standalone `<tool>` envelope, validates with Zod/JSON Schema infrastructure already present in OmniRoute, rejects unknown tools and mixed narrative/action output, and performs at most one bounded repair prompt. Tool ids combine a per-request nonce with canonical arguments so repeated identical calls remain unique while their association is stable within the turn.
## Required Proof
The offline profile must prove the ACP lifecycle, fragmented frames, stderr, early exit, hang/cancel, Anthropic JSON/SSE order, no fallback, and a real pinned Claude Code run that reads, edits, runs tests, observes `CLAUDE.md`, loads a skill and command, fires a hook, and completes at least one `tool_use -> tool_result -> continuation` loop. The isolation verifier checks env, mounts, UID, config paths, DNS/connection logs, local inference destination, selected provider, and fail-closed behavior. Live Devin is proved only by official in-container login and three isolated agentic scenarios.
## Safety Incident During Baseline
The first focused test was run without `DATA_DIR` isolation and initialized `/Users/lucasisrael/.omniroute/storage.sqlite`; logs reported schema-column additions. No Anthropic data was accessed. The external database will not be touched again or destructively rolled back. Every bridge command and test now must set `HOME`, `DATA_DIR`, `SQLITE_FILE`, and temporary directories inside `.sandbox`, and an automated guard must reject paths outside the task workspace.
## Live Completion Repair
The first authenticated live attempt disproved four assumptions in the initial container
design. The official CLI reports a valid login even when its server-status request fails;
that request uses the exact hosts `server.codeium.com` and `unleash.codeium.com`, which the
guard denied. The OmniRoute executor also built a fresh allowlisted child environment that
omitted the proxy, so `devin acp` could not leave the internal network. Model discovery emits
family identifiers such as `swe-1.7`, while the OmniRoute catalog uses canonical ids such as
`swe-1-7`. Finally, browser login redirects to a loopback listener inside the one-off
container, which is not reachable from the host browser.
The repair keeps the fully containerized architecture and does not weaken the deny-by-default
network. The guard gains an exact-host allowlist for the two Codeium control-plane hosts while
retaining suffix-based access only for Devin and Cognition; telemetry destinations such as
Sentry remain denied. Compose supplies `DEVIN_BRIDGE_PROXY_URL` with the single accepted value
`http://network-guard:8080`, and the executor derives `HTTP_PROXY` and `HTTPS_PROXY` from that
explicit bridge setting instead of inheriting arbitrary host proxy variables. Claude services
mount only the Claude config volume, and only the OmniRoute live service mounts the Devin auth
volume.
Fresh login uses the official `devin auth login --force-manual-token-flow`, which is intended
for remote environments where localhost redirects cannot work. The credential is pasted only
into the interactive CLI terminal and never appears in arguments, logs, evidence, or Git.
Authentication validation requires both the logged-in marker and the absence of a server-fetch
failure. Model discovery accepts the real `family_uid`/`model_uid` fields, maps punctuation to a
catalog id only after an exact normalized match, and prefers the already-proved lightning model
when available.
Tests first prove the trusted proxy boundary, exact host policy, volume separation, strict auth
status gate, and catalog normalization. The live gate then runs three real Claude Code scenarios
through the authenticated in-container Devin CLI and requires local Read/Edit/Bash activity,
passing fixture tests, Devin-only routing, no allowed non-Devin egress, and an explicit error
when the Devin backend is stopped.
## Final Live Result
The default-agent design was rejected after live evidence showed that `ask` mode can still
emit Devin-owned ACP tool calls. The pinned CLI does not apply its top-level agent
configuration to `devin acp`, so an `allowed-tools: []` configuration could not create a
neutral backend. The fixed summarizer role is the only official ACP role in this version that
is structurally no-tools.
The execution-trace adaptation passed the authenticated live gate with
`swe-1-7-lightning`. Three Claude Code processes completed analysis, edit/test, and local
command/skill scenarios. Structured evidence proved that Claude Code issued `Read`, `Edit`,
and `Bash` tool calls; two client-owned `npm test` calls succeeded. The guard audit proved
Devin-only outbound access and zero Claude egress. Intermediate summary-shaped responses and
transient ACP timeouts remain explicit failure modes; the adapter performs one bounded repair
and the harness spaces scenarios to avoid bursty session creation.

View File

@@ -36,7 +36,6 @@ const DOCS_ROOT = path.join(REPO_ROOT, "docs");
const EXCLUDE_PREFIXES = [
path.join(DOCS_ROOT, "i18n") + path.sep,
path.join(DOCS_ROOT, "screenshots") + path.sep,
path.join(DOCS_ROOT, "superpowers") + path.sep,
path.join(DOCS_ROOT, "diagrams", "exported") + path.sep,
];

View File

@@ -360,19 +360,6 @@ const SKIP_DOC_FILES = new Set([
"docs/reference/PROVIDER_REFERENCE.md", // auto-generated from providers.ts
"docs/openapi.yaml",
"docs/i18n", // translations — separate workflow
// Design / research / plan docs: by definition describe not-yet-built files and
// proposed (not-yet-shipped) endpoints (each carries a `Status: Design`/`Active
// research`/`Plano` header). Same rationale as the audit report above — these are
// forward-looking specs, not living API docs, so their forward references are
// expected, not fabrications.
"docs/research", // DISCOVERY_TOOL_DESIGN.md, UNLIMITED_LLM_ACCESS.md, …
"docs/superpowers/plans", // dated implementation plans (files described before they exist)
"docs/superpowers/specs", // dated research/spec reports (point-in-time findings, may cite proposed/not-yet-built endpoints, env vars, and files) — same rationale as the plans/research dirs above
// Release notes are historical, point-in-time records: they intentionally describe
// modules/paths as they were at that release (e.g. a module later moved or renamed).
// Rewriting them to today's layout would falsify history — out of scope for a
// living-docs accuracy gate.
"docs/releases",
// Forward-looking coverage plan: a `- [ ]` checklist of test targets and helper
// components to be created. Same rationale as the design/plan docs above.
"docs/ops/COVERAGE_PLAN.md",