1,104 mirrors rewritten over five passes of run-translation on the 22-source core set: the 14 sources edited since their translation, the 322 mirrors that were still English copies, and the frontmatter the old extractor leaked into the newer locales' bodies. The pipeline now caches per-`## `-section hashes and retranslates only changed sections, never reuses a section that is still English, rebuilds English-copy / leaked mirrors even when the source is unchanged, merges the state on save (parallel runs), and the drift gate (scoped to the core set) is blocking. Final audit: 0 stale, 0 English copies, 0 leaked frontmatter across 1,430 core mirrors. ⚠️ base-red inherited: #12732
26 KiB
Contributing to OmniRoute (Tiếng Việt)
🌐 Languages: 🇺🇸 English · 🇪🇹 am · 🇸🇦 ar · 🇦🇿 az · 🇧🇬 bg · 🇧🇩 bn · 🇨🇿 cs · 🇩🇰 da · 🇩🇪 de · 🇬🇷 el · 🇪🇸 es · 🇪🇪 et · 🇮🇷 fa · 🇫🇮 fi · 🇫🇷 fr · 🇮🇪 ga · 🇮🇳 gu · 🇳🇬 ha · 🇮🇱 he · 🇮🇳 hi · 🇭🇷 hr · 🇭🇺 hu · 🇦🇲 hy · 🇮🇩 id · 🇳🇬 ig · 🇮🇹 it · 🇯🇵 ja · 🇬🇪 ka · 🇰🇭 km · 🇮🇳 kn · 🇰🇷 ko · 🇱🇹 lt · 🇱🇻 lv · 🇮🇳 ml · 🇮🇳 mr · 🇲🇾 ms · 🇲🇹 mt · 🇲🇲 my · 🇳🇵 ne · 🇳🇱 nl · 🇳🇴 no · 🇮🇳 or · 🇮🇳 pa · 🇵🇭 phi · 🇵🇱 pl · 🇵🇹 pt · 🇧🇷 pt-BR · 🇷🇴 ro · 🇷🇺 ru · 🇱🇰 si · 🇸🇰 sk · 🇸🇮 sl · 🇷🇸 sr · 🇸🇪 sv · 🇰🇪 sw · 🇮🇳 ta · 🇮🇳 te · 🇹🇭 th · 🇹🇷 tr · 🇺🇦 uk-UA · 🇵🇰 ur · 🇺🇿 uz · 🇳🇬 yo · 🇨🇳 zh-CN · 🇹🇼 zh-TW
Thank you for your interest in contributing! This guide covers everything you need to get started.
Thiết lập môi trường phát triển
Điều kiện tiên quyết
- Node.js
>=22.22.3 <23, hoặc>=24.0.0 <27(khuyến nghị: 24 LTS) - npm 10+
Người dùng npm v11+ (Node 24+): Sau khi chạy
npm install, hãy xác minh rằng các mô-đun native đã được cài đặt:node -e "require('better-sqlite3')". Nếu lệnh thất bại vớiMODULE_NOT_FOUND, hãy chạynpm approve-scripts better-sqlite3 && npm install. Xem Khắc phục sự cố.
- Git
Sao chép kho lưu trữ & Cài đặt
git clone https://github.com/diegosouzapw/OmniRoute.git
cd OmniRoute
npm install
Biến môi trường
# Tạo tệp .env từ mẫu
cp .env.example .env
# Tạo các khóa bí mật bắt buộc
echo "JWT_SECRET=$(openssl rand -base64 48)" >> .env
echo "API_KEY_SECRET=$(openssl rand -hex 32)" >> .env
Các biến chính dành cho phát triển:
| Biến | Giá trị mặc định khi phát triển | Mô tả |
|---|---|---|
PORT |
20128 |
Cổng máy chủ |
NEXT_PUBLIC_BASE_URL |
http://localhost:20128 |
URL cơ sở cho frontend |
JWT_SECRET |
(tạo ở trên) | Khóa bí mật để ký JWT |
INITIAL_PASSWORD |
CHANGEME |
Mật khẩu đăng nhập lần đầu |
APP_LOG_LEVEL |
info |
Mức độ chi tiết của nhật ký |
Cài đặt bảng điều khiển
Bảng điều khiển cung cấp các nút bật/tắt trên giao diện người dùng cho những tính năng cũng có thể được cấu hình thông qua biến môi trường:
| Vị trí cài đặt | Nút bật/tắt | Mô tả |
|---|---|---|
| Cài đặt → Nâng cao | Chế độ gỡ lỗi | Bật nhật ký yêu cầu gỡ lỗi (giao diện người dùng) |
| Cài đặt → Chung | Khả năng hiển thị thanh bên | Hiện/ẩn các phần của thanh bên |
Các cài đặt này được lưu trong cơ sở dữ liệu và duy trì qua các lần khởi động lại, đồng thời ghi đè các giá trị mặc định của biến môi trường khi được thiết lập.
Chạy cục bộ
# Chế độ phát triển (tải lại nóng)
npm run dev
# Bản dựng sản xuất
npm run build # next build → .build/next/ sau đó assembleStandalone → dist/
npm run start
# Biên dịch nhanh chỉ dành cho backend/API đối với các thay đổi của người đóng góp
npm run build:contributor
# Bản dựng phát hành (dựng lại từ đầu + dấu kiểm HEAD — bắt buộc để triển khai)
npm run build:release # rm -rf .build dist && build + ghi dist/BUILD_SHA
# Cấu hình cổng thường dùng
PORT=20128 NEXT_PUBLIC_BASE_URL=http://localhost:20128 npm run dev
Bản dựng dành cho người đóng góp chỉ thực hiện xác thực biên dịch: nó không lắp ráp bản phân phối độc lập hoặc dựng các tài nguyên đóng gói native tùy chọn. Hãy sử dụng bản dựng sản xuất thông thường khi bạn cần xác thực gói có thể phát hành.
Bố cục đầu ra bản dựng
| Thư mục | Nội dung | Được theo dõi |
|---|---|---|
src/ |
Mã nguồn ứng dụng (TypeScript / TSX) | Có |
.build/ |
Các tệp trung gian — đầu ra của next build (được git bỏ qua, distDir = .build/next) |
Không |
dist/ |
Gói có thể phát hành — được lắp ráp bởi assembleStandalone (được git bỏ qua) |
Không |
Quy trình dựng chỉ gồm một lượt:
npm run build
└─ next build → .build/next/standalone (đầu ra Next.js)
└─ assembleStandalone() (sao chép bản độc lập + tệp tĩnh + công khai + tài nguyên native)
└─ đầu ra: dist/ (server.js, .next/static/, public/, node_modules/)
npm run build:release còn dọn sạch cả hai thư mục trước tiên và ghi
dist/BUILD_SHA (= git rev-parse --short HEAD) làm dấu kiểm tính toàn vẹn khi triển khai.
npm run build:contributor sử dụng cấu hình dựng chỉ dành cho backend. Cấu hình này tạm thời thay thế
các tệp giao diện người dùng của bảng điều khiển bằng tệp giả trong khi dựng, giữ nguyên các trình xử lý tuyến API và khôi phục các tệp gốc
sau khi dựng. Hãy sử dụng npm run build cho các thay đổi ảnh hưởng đến giao diện người dùng của bảng điều khiển hoặc để
xác thực đầy đủ bản phát hành; cấu hình dành cho người đóng góp không thay thế cho bản dựng phát hành.
Lưu ý khi triển khai lên VPS: thư mục image từ xa
/usr/lib/node_modules/omniroute/app/không thay đổi. Các kỹ năng triển khai dùng rsync để đồng bộ nội dung củadist/vào thư mục đó. Chỉ đường dẫn đầu ra bản dựng trong kho lưu trữ được thay đổi (app/→dist/).
Các URL mặc định:
- Bảng điều khiển:
http://localhost:20128/dashboard - API:
http://localhost:20128/v1
Quy trình làm việc với Git
⚠️ KHÔNG BAO GIỜ commit trực tiếp vào
main. Luôn sử dụng các nhánh tính năng.Nhánh cơ sở của PR: nhắm đến nhánh
release/vX.Y.Zđang hoạt động (không phảimain). Xemdocs/ops/BRANCHING_MODEL.mdđể biết mô hình mỗi bản phát hành một nhánh + gắn thẻ khi phát hành.
# Tạo nhánh từ đầu nhánh phát hành đang hoạt động (ví dụ: release/v3.8.49)
git fetch origin
git checkout -b feat/your-feature-name origin/release/v3.8.49
# ... thực hiện thay đổi ...
git commit -m "feat: mô tả thay đổi của bạn"
git push -u origin feat/your-feature-name
# Mở Pull Request với base = release/v3.8.49
Quy tắc đặt tên nhánh
| Tiền tố | Mục đích |
|---|---|
feat/ |
Tính năng mới |
fix/ |
Sửa lỗi |
refactor/ |
Tái cấu trúc mã nguồn |
docs/ |
Thay đổi tài liệu |
test/ |
Bổ sung/sửa kiểm thử |
chore/ |
Công cụ, CI, các phần phụ thuộc |
Thông điệp commit
Tuân theo Conventional Commits:
feat: thêm circuit breaker cho các lệnh gọi đến nhà cung cấp
fix: xử lý trường hợp biên khi xác thực JWT secret
docs: cập nhật SECURITY.md với biện pháp bảo vệ PII
test: thêm các kiểm thử đơn vị về khả năng quan sát
refactor(db): hợp nhất các bảng giới hạn tốc độ
Phạm vi (v3.8): db, sse, oauth, dashboard, api, cli, docker, ci, mcp, a2a, memory, skills, cloud-agent, guardrails, compression, auto-combo, resilience, providers, executors, translator, domain, authz.
Chạy kiểm thử
# Tất cả kiểm thử (unit + vitest + ecosystem + e2e)
npm run test:all
# Một tệp kiểm thử duy nhất (trình chạy kiểm thử gốc của Node.js — hầu hết kiểm thử sử dụng cách này)
node --import tsx/esm --test tests/unit/your-file.test.ts
# Chỉ các kiểm thử đơn vị bị ảnh hưởng bởi thay đổi của bạn (cùng bộ chọn TIA như cổng CI, #8084)
npm run test:scoped # các thay đổi trong commit gần nhất (hoặc cây làm việc)
npm run test:scoped:staged # chỉ các thay đổi đã staged — phù hợp để chạy cùng pre-commit
npm run test:scoped:full # xây dựng lại ánh xạ đồ thị nhập trước (sau khi thêm/di chuyển tệp)
# Mã thoát 1 + "run the full suite" có nghĩa là một tệp trung tâm (tsconfig, package.json, …) hoặc
# nguồn chưa được ánh xạ đã thay đổi — bộ chọn chuyển sang chế độ an toàn, không bao giờ âm thầm bỏ qua.
# Vitest (máy chủ MCP, autoCombo, bộ nhớ đệm)
npm run test:vitest
# Kiểm thử E2E (yêu cầu Playwright)
npm run test:e2e
# E2E cho các máy khách giao thức (phương thức truyền tải MCP, A2A)
npm run test:protocols:e2e
# Kiểm thử khả năng tương thích hệ sinh thái
npm run test:ecosystem
# Ngưỡng độ bao phủ: 60% câu lệnh/dòng/hàm/nhánh
npm run test:coverage
npm run coverage:report
# Kiểm tra lint + định dạng
npm run lint
npm run check
# Kiểm thử nhanh tổ hợp thực tế có kiểm soát với upstream (yêu cầu quyền truy cập VPS + tín dụng nhà cung cấp thực)
# Gọi các nhà cung cấp THỰC — tốn một ít chi phí. KHÔNG BAO GIỜ chạy trong CI. Bỏ qua an toàn nếu không có cổng kiểm soát.
# Yêu cầu: quyền truy cập ssh root@192.168.0.15 (nạp ảnh chụp nhanh DB chỉ đọc từ VPS).
RUN_COMBO_LIVE=1 npm run test:combo:live
# Kiểm thử nhanh trực tiếp VPS giai đoạn 3 — các tập lệnh Node ESM thuần túy, gọi trực tiếp máy chủ .15 đang hoạt động.
# Yêu cầu: quyền truy cập ssh root@192.168.0.15 (các tổ hợp được tạo/xóa qua SSH sqlite).
# Gọi các nhà cung cấp THỰC (chi phí nhỏ). Chỉ tạo/xóa các tổ hợp __live_test__*. KHÔNG BAO GIỜ chạy trong CI.
# REQUIRE_API_KEY=false trên .15 nên không cần khóa API, nhưng vẫn tuân theo COMBO_LIVE_BASE_URL / COMBO_LIVE_API_KEY nếu được đặt.
npm run test:combo:live:vps # 7 kịch bản HTTP (ưu tiên/luân phiên/có trọng số/chi phí/hợp nhất/tự động + trạng thái)
npm run test:combo:live:vps:failover # thêm một kịch bản chuyển đổi dự phòng thực giữa các nhà cung cấp (tổng cộng 8)
Ghi chú về độ bao phủ:
npm run test:coverageđo độ bao phủ mã nguồn cho bộ kiểm thử đơn vị chính, loại trừtests/**và bao gồmopen-sse/**- Các pull request phải duy trì ngưỡng độ bao phủ ở mức 60%+ đối với câu lệnh/dòng/hàm/nhánh
- Nếu một PR thay đổi mã production trong
src/,open-sse/,electron/hoặcbin/, PR đó phải thêm hoặc cập nhật các kiểm thử tự động trong cùng PR npm run coverage:reportin báo cáo chi tiết theo từng tệp từ lần chạy độ bao phủ gần nhấtnpm run test:coverage:legacygiữ lại chỉ số cũ để so sánh theo lịch sử- Xem
docs/ops/COVERAGE_PLAN.mdđể biết lộ trình cải thiện độ bao phủ theo từng giai đoạn
Yêu cầu đối với Pull Request
Trước khi mở một PR, hãy sử dụng Quy trình chuẩn để đóng góp để chạy vòng lặp tập trung cho những gì bạn đã thay đổi. Toàn bộ bộ kiểm thử đơn vị (4 phân đoạn CI), Vitest, ngưỡng độ bao phủ 60%+ và bản dựng production thuộc trách nhiệm của CI — việc chạy chúng cục bộ không cung cấp thêm tín hiệu nào ngoài những gì các bước kiểm tra PR đã cung cấp, đồng thời trên các máy cấu hình thấp hơn, việc này có thể làm quá tải máy chủ (#8084):
- Chạy các tệp kiểm thử bao phủ thay đổi của bạn:
node --import tsx/esm --test tests/unit/<file>.test.ts - Chạy
npm run lint - Thêm hoặc cập nhật các kiểm thử tự động trong cùng PR bất cứ khi nào mã production thay đổi
- Liệt kê các tệp kiểm thử đã thay đổi hoặc được thêm vào trong phần mô tả PR khi mã production thay đổi
- Kiểm tra kết quả SonarQube trên PR khi các secret của dự án được cấu hình trong CI
Trạng thái kiểm thử hiện tại: 122 tệp kiểm thử đơn vị bao phủ:
- Các trình chuyển đổi nhà cung cấp và chuyển đổi định dạng
- Giới hạn tốc độ, bộ ngắt mạch và khả năng phục hồi
- Bộ nhớ đệm ngữ nghĩa, tính lũy đẳng, theo dõi tiến độ
- Các thao tác cơ sở dữ liệu và schema (21 mô-đun DB)
- Các luồng OAuth và xác thực
- Xác thực endpoint API (Zod v4)
- Các công cụ máy chủ MCP và thực thi phạm vi
- Các hệ thống Memory và Skills
Phong cách mã nguồn
- ESLint — Chạy
npm run linttrước khi commit - Prettier — Tự động định dạng qua
lint-stagedkhi commit (thụt lề 2 khoảng trắng, dấu chấm phẩy, dấu ngoặc kép, độ rộng 100 ký tự, dấu phẩy cuối theo es5) - TypeScript — Toàn bộ mã trong
src/sử dụng.ts/.tsx;open-sse/sử dụng.ts/.js; viết tài liệu bằng TSDoc (@param,@returns,@throws) - Không dùng
eval()— ESLint áp dụng các quy tắcno-eval,no-implied-eval,no-new-func - Xác thực bằng Zod — Sử dụng schema Zod v4 để xác thực toàn bộ đầu vào API
- Quy tắc đặt tên: Tệp = camelCase/kebab-case, component = PascalCase, hằng số = UPPER_SNAKE
Xử lý lỗi / khối catch rỗng
Không bao giờ để một catch mà không giải thích. Hãy phân loại nó vào một trong hai nhóm (cụ thể hóa
quy tắc nghiêm ngặt "không bao giờ âm thầm bỏ qua lỗi trong luồng SSE"):
-
Có chủ đích (hoạt động dọn dẹp/đo từ xa theo nỗ lực tối đa của chính chúng ta) — lỗi ở đây là điều được dự kiến và vô hại; thêm một dòng chú thích nêu lý do, không ghi log (quy ước này nhằm tránh việc ghi log cho mọi request gây nhiễu).
} catch {} // việc đóng một controller đã đóng sau khi client ngắt kết nối là điều được dự kiến -
Nên ghi log (mã bên ngoài/do bên gọi cung cấp, hoặc việc bỏ qua làm thay đổi luồng điều khiển) — giữ lại catch (không bao giờ để nó làm gián đoạn luồng) nhưng phát ra
console.debug/warncó ngữ cảnh để lỗi có thể được phát hiện.} catch (e) { console.debug("[STREAM] onFailure callback error:", e); }
Xem open-sse/utils/stream.ts và open-sse/utils/streamHandler.ts để biết các ví dụ đã áp dụng.
Project Structure
src/ # TypeScript (.ts / .tsx)
├── app/ # Next.js 16 App Router
│ ├── (dashboard)/ # Dashboard pages (23 sections)
│ ├── api/ # API routes (51 directories)
│ └── login/ # Auth pages (.tsx)
├── domain/ # Policy engine (policyEngine, comboResolver, costRules, etc.)
├── lib/ # Core business logic (.ts)
│ ├── a2a/ # Agent-to-Agent v0.3 protocol server
│ ├── acp/ # Agent Communication Protocol registry
│ ├── compliance/ # Compliance policy engine
│ ├── db/ # SQLite database layer (110 top-level modules + 130 migrations)
│ ├── memory/ # Persistent conversational memory
│ ├── oauth/ # OAuth providers, services, and utilities
│ ├── skills/ # Extensible skill framework
│ ├── usage/ # Usage tracking and cost calculation
│ └── localDb.ts # Re-export layer only — never add logic here
├── middleware/ # Request middleware (promptInjectionGuard)
├── mitm/ # MITM proxy (cert, DNS, target routing)
├── shared/
│ ├── components/ # React components (.tsx)
│ ├── constants/ # Provider definitions (329), MCP scopes, routing strategies
│ ├── utils/ # Circuit breaker, sanitizer, auth helpers
│ └── validation/ # Zod v4 schemas
└── sse/ # SSE proxy pipeline
open-sse/ # @omniroute/open-sse workspace
├── executors/ # 89 executor implementation modules
├── handlers/ # 11 request handlers (chat, responses, embeddings, images, etc.)
├── mcp-server/ # MCP server (107 tools, 3 transports, 32 scopes)
├── services/ # 178 top-level services (combo, autoCombo, rateLimitManager, etc.)
├── translator/ # Format translators (OpenAI ↔ Claude ↔ Gemini ↔ Responses ↔ Ollama)
├── transformer/ # Responses API transformer
└── utils/ # 22 utility modules (stream, TLS, proxy, logging)
electron/ # Electron desktop app (cross-platform)
tests/
├── unit/ # Node.js test runner (122 test files)
├── integration/ # Integration tests
├── e2e/ # Playwright tests
├── security/ # Security tests
├── translator/ # Translator-specific tests
└── load/ # Load tests
docs/ # Documentation
├── ARCHITECTURE.md # System architecture
├── API_REFERENCE.md # All endpoints
├── USER_GUIDE.md # Provider setup, CLI integration
├── TROUBLESHOOTING.md # Common issues
├── MCP-SERVER.md # MCP server (107 tools)
├── A2A-SERVER.md # A2A agent protocol
├── AUTO-COMBO.md # Auto-combo engine
├── CLI-TOOLS.md # CLI tools integration
├── COVERAGE_PLAN.md # Test coverage improvement plan
├── openapi.yaml # OpenAPI specification
└── adr/ # Architecture Decision Records
Thêm Nhà cung cấp Mới
Bước 1: Đăng ký các Hằng số của Nhà cung cấp
Thêm vào src/shared/constants/providers.ts — được Zod xác thực khi mô-đun tải.
Bước 2: Thêm Executor (nếu cần logic tùy chỉnh)
Tạo executor trong open-sse/executors/your-provider.ts, kế thừa executor cơ sở.
Bước 3: Thêm Translator (nếu không dùng định dạng OpenAI)
Tạo các translator cho yêu cầu/phản hồi trong open-sse/translator/.
Bước 4: Thêm Cấu hình OAuth (nếu dựa trên OAuth)
Thêm thông tin xác thực OAuth trong src/lib/oauth/constants/oauth.ts và dịch vụ trong src/lib/oauth/services/.
Nếu nhà cung cấp thượng nguồn phân phối OAuth client_id/secret công khai hoặc khóa Firebase Web API bên trong CLI / gói trình duyệt công khai của họ, không nhúng dưới dạng chuỗi ký tự. Sử dụng resolvePublicCred() từ open-sse/utils/publicCreds.ts và thêm một mục byte đã che vào EMBEDDED_DEFAULTS. Quy trình bắt buộc đầy đủ được ghi lại trong docs/security/PUBLIC_CREDS.md.
Bên trong các handler/executor, thông báo lỗi gửi đến máy khách phải đi qua buildErrorBody() / sanitizeErrorMessage() từ open-sse/utils/error.ts — tuyệt đối không đưa trực tiếp err.stack hoặc err.message vào phần thân Response. Xem docs/security/ERROR_SANITIZATION.md.
Bước 5: Đăng ký các Mô hình
Thêm định nghĩa mô hình trong open-sse/config/providerRegistry.ts.
Bước 6: Thêm Kiểm thử
Viết các kiểm thử đơn vị trong tests/unit/, tối thiểu bao gồm:
- Đăng ký nhà cung cấp
- Chuyển đổi yêu cầu/phản hồi
- Xử lý lỗi
Danh sách kiểm tra Pull Request
- Các bài kiểm thử đều vượt qua (
npm test) - Quá trình kiểm tra lint vượt qua (
npm run lint) - Quá trình build thành công (
npm run build) - Đã thêm kiểu TypeScript cho các hàm và interface công khai mới
- Không có secret hoặc giá trị dự phòng được mã hóa cứng
- Thông tin xác thực upstream công khai được nhúng thông qua
resolvePublicCred()(xemdocs/security/PUBLIC_CREDS.md), tuyệt đối không dùng giá trị literal - Phản hồi lỗi được xử lý thông qua
buildErrorBody()/sanitizeErrorMessage()— không đưa stack trace thô vào nội dung phản hồi (xemdocs/security/ERROR_SANITIZATION.md) - Các lệnh shell (
exec/spawn) truyền giá trị runtime quaenv, không thông qua nội suy chuỗi - Tất cả đầu vào đều được xác thực bằng schema Zod
- Đã thêm fragment changelog vào
changelog.d/{features|fixes|maintenance}/<PR>-<slug>.mdcho các thay đổi ảnh hưởng đến người dùng (xemchangelog.d/README.md) — không chỉnh sửa trực tiếpCHANGELOG.md; các fragment được tổng hợp khi phát hành và không bao giờ xung đột giữa các PR - Đã cập nhật tài liệu (nếu áp dụng)
- Không tạo cảnh báo CodeQL / Secret-Scanning mới, hoặc mỗi cảnh báo đã được loại bỏ kèm lý do kỹ thuật có tham chiếu đến tài liệu
docs/security/liên quan - Các route tạo tiến trình con (
/api/mcp/,/api/cli-tools/runtime/) được phân loại làisLocalOnlyPath()trongsrc/server/authz/routeGuard.ts— xem Quy tắc bắt buộc #15 - Không có trailer
Co-Authored-Bytrong thông điệp commit — các commit phải chỉ hiển thị danh tính Git của chủ sở hữu repository (Quy tắc bắt buộc #16)
Releasing
Releases are managed via the /generate-release workflow. When a new GitHub Release is created, the package is automatically published to npm via GitHub Actions.
Nhận trợ giúp
- Kiến trúc: Xem
docs/architecture/ARCHITECTURE.md - Tài liệu tham khảo API: Xem
docs/reference/API_REFERENCE.md - Tài liệu bảo mật:
docs/security/CLI_TOKEN.md,docs/security/ROUTE_GUARD_TIERS.md,docs/security/ERROR_SANITIZATION.md,docs/security/PUBLIC_CREDS.md - Tài liệu vận hành:
docs/ops/SQLITE_RUNTIME.md - Vấn đề: github.com/diegosouzapw/OmniRoute/issues
- ADR: Xem
docs/adr/để biết các bản ghi quyết định kiến trúc