docs: add architecture overview

Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
Zyy
2026-06-16 17:46:48 +08:00
co-authored by Codex
parent dc7f2d0483
commit 0f2b199fde
4 changed files with 1343 additions and 0 deletions
+18
View File
@@ -0,0 +1,18 @@
target
web/node_modules
web/dist
data
logs
local.yaml
local.yml
.git
.codegraph
.worktrees
worktrees
*.sqlite
*.sqlite3
*.db
*.db-shm
*.db-wal
*.log
*.tmp
+2
View File
@@ -17,3 +17,5 @@
*.codegraph
*/node_modules
*/dist
.agents
.cargo
+644
View File
@@ -24752,6 +24752,650 @@
]
}
]
},
{
"type": "frame",
"id": "ezRqM",
"x": 16020,
"y": 0,
"name": "登录页 - Route Chamber",
"clip": true,
"width": 1920,
"height": 1080,
"fill": {
"type": "gradient",
"gradientType": "linear",
"enabled": true,
"rotation": 115,
"size": {
"height": 1
},
"colors": [
{
"color": "#F8FAFC",
"position": 0
},
{
"color": "#FFFFFF",
"position": 0.46
},
{
"color": "#F1F7FF",
"position": 1
}
]
},
"gap": 88,
"padding": [
72,
96
],
"alignItems": "center",
"children": [
{
"type": "frame",
"id": "fptiY",
"name": "左侧律动区域",
"width": 980,
"height": "fill_container",
"fill": "#00000000",
"stroke": "#00000000",
"effect": {
"type": "shadow",
"shadowType": "outer",
"enabled": false,
"color": "#000000"
},
"layout": "none",
"children": [
{
"type": "frame",
"id": "hGhiA",
"x": 44,
"y": 42,
"name": "左侧品牌",
"width": 420,
"height": 52,
"fill": "#00000000",
"gap": 13,
"alignItems": "center",
"children": [
{
"type": "frame",
"id": "k88pD",
"name": "左侧品牌图标底",
"width": 44,
"height": 44,
"fill": "#111827",
"cornerRadius": 13,
"layout": "none",
"children": [
{
"type": "icon",
"id": "inEW3",
"x": 11,
"y": 11,
"name": "左侧品牌图标",
"width": 22,
"height": 22,
"icon": "square-terminal",
"library": "lucide",
"fill": "#FFFFFF"
}
]
},
{
"type": "frame",
"id": "XNdOV",
"name": "左侧品牌文字",
"height": 42,
"fill": "#00000000",
"layout": "vertical",
"gap": 4,
"justifyContent": "center",
"children": [
{
"type": "text",
"id": "PMXVU",
"name": "左侧品牌名称",
"fill": "#0E1726",
"content": "Codex Proxy RS",
"lineHeight": 1.1,
"fontFamily": "Inter",
"fontSize": 17,
"fontWeight": "760"
},
{
"type": "text",
"id": "ImuER",
"name": "左侧品牌版本",
"fill": "#64748B",
"content": "Proxy RS · v0.1.0",
"lineHeight": 1.1,
"fontFamily": "Inter",
"fontSize": 11,
"fontWeight": "650"
}
]
}
]
},
{
"type": "frame",
"id": "ffamL",
"x": 56,
"y": 744,
"name": "左侧短文案",
"width": 580,
"height": 128,
"fill": "#00000000",
"layout": "vertical",
"gap": 14,
"children": [
{
"type": "text",
"id": "VNWwS",
"name": "左侧标题",
"fill": "#0E1726",
"textGrowth": "fixed-width",
"width": "fill_container",
"content": "安静进入控制台。",
"lineHeight": 1.08,
"fontFamily": "Inter",
"fontSize": 38,
"fontWeight": "780"
},
{
"type": "text",
"id": "cRXIR",
"name": "左侧说明",
"fill": "#64748B",
"textGrowth": "fixed-width",
"width": "fill_container",
"content": "几条请求轨迹,连接账号池、模型目录与管理入口。",
"lineHeight": 1.45,
"fontFamily": "Inter",
"fontSize": 14,
"fontWeight": "500"
}
]
},
{
"type": "path",
"id": "i0RKwl",
"x": 8,
"y": 264,
"name": "信号谱线 蓝主线",
"geometry": "M0 134c154-104 304-80 440-18 152 70 286 62 470-62",
"viewBox": [
0,
0,
910,
238
],
"fill": "#00000000",
"width": 910,
"height": 238,
"stroke": "#2563EB",
"strokeWidth": 2.2,
"strokeLinejoin": "round",
"strokeLinecap": "round"
},
{
"type": "path",
"id": "t7Obu",
"x": 24,
"y": 356,
"name": "信号谱线 青主线",
"geometry": "M0 62c138 88 286 92 424 32 156-68 284-46 446 56",
"viewBox": [
0,
0,
870,
210
],
"fill": "#00000000",
"width": 870,
"height": 210,
"stroke": "#0F9F9A",
"strokeWidth": 2,
"strokeLinejoin": "round",
"strokeLinecap": "round"
},
{
"type": "path",
"id": "Ih3hQ",
"x": 42,
"y": 304,
"name": "信号谱线 轻回声一",
"geometry": "M0 118c132-64 266-42 392 6 148 54 274 32 428-38",
"viewBox": [
0,
0,
820,
210
],
"fill": "#00000000",
"width": 820,
"height": 210,
"stroke": "#93C5FD66",
"strokeWidth": 1.2,
"strokeLinejoin": "round",
"strokeLinecap": "round"
},
{
"type": "path",
"id": "SlACI",
"x": 60,
"y": 460,
"name": "信号谱线 轻回声二",
"geometry": "M0 78c118-36 224 40 352 0 148-46 280 18 428-16",
"viewBox": [
0,
0,
780,
178
],
"fill": "#00000000",
"width": 780,
"height": 178,
"stroke": "#99F6E466",
"strokeWidth": 1.2,
"strokeLinejoin": "round",
"strokeLinecap": "round"
},
{
"type": "path",
"id": "dgWuE",
"x": 96,
"y": 574,
"name": "信号谱线 静默底线",
"geometry": "M0 62c108 26 192-32 316-4 126 28 232-16 364 8",
"viewBox": [
0,
0,
680,
130
],
"fill": "#00000000",
"width": 680,
"height": 130,
"stroke": "#CBD5E180",
"strokeWidth": 1.1,
"strokeLinejoin": "round",
"strokeLinecap": "round"
},
{
"type": "ellipse",
"id": "wgWMY",
"x": 446,
"y": 378,
"name": "谱线节点 蓝",
"fill": "#2563EB",
"width": 7,
"height": 7,
"effect": {
"type": "shadow",
"shadowType": "outer",
"color": "#2563EB66",
"blur": 14
}
},
{
"type": "ellipse",
"id": "D17Lp",
"x": 602,
"y": 448,
"name": "谱线节点 青",
"fill": "#0F9F9A",
"width": 7,
"height": 7,
"effect": {
"type": "shadow",
"shadowType": "outer",
"color": "#0F9F9A66",
"blur": 14
}
},
{
"type": "ellipse",
"id": "TxcTy",
"x": 262,
"y": 338,
"name": "谱线节点 淡",
"fill": "#94A3B8",
"width": 4,
"height": 4
}
]
},
{
"type": "frame",
"id": "fmwBG",
"name": "右侧登录区",
"width": 520,
"height": "fill_container",
"fill": "#00000000",
"layout": "vertical",
"gap": 22,
"justifyContent": "center",
"children": [
{
"type": "frame",
"id": "LBb4m",
"name": "登录表单",
"width": 480,
"fill": "#FFFFFF",
"cornerRadius": 22,
"stroke": "#E2E8F0",
"strokeWidth": 1,
"effect": [
{
"type": "shadow",
"shadowType": "outer",
"color": "#0E17261F",
"offset": {
"x": 0,
"y": 12
},
"blur": 26,
"spread": -18
},
{
"type": "shadow",
"shadowType": "outer",
"color": "#0E172629",
"offset": {
"x": 0,
"y": 34
},
"blur": 70,
"spread": -40
}
],
"layout": "vertical",
"gap": 22,
"padding": 36,
"children": [
{
"type": "frame",
"id": "Vh4qO",
"name": "登录表单头部",
"width": "fill_container",
"height": 84,
"fill": "#00000000",
"layout": "vertical",
"gap": 10,
"children": [
{
"type": "text",
"id": "WKbRM",
"name": "登录标题",
"fill": "#0E1726",
"content": "登录控制台",
"lineHeight": 1.12,
"fontFamily": "Inter",
"fontSize": 34,
"fontWeight": "800"
},
{
"type": "text",
"id": "azMth",
"name": "登录说明",
"fill": "#64748B",
"textGrowth": "fixed-width",
"width": "fill_container",
"content": "使用管理员账号和访问密钥进入 Codex Proxy RS。",
"lineHeight": 1.55,
"fontFamily": "Inter",
"fontSize": 14,
"fontWeight": "500"
}
]
},
{
"type": "frame",
"id": "Rre6N",
"name": "管理员账号",
"width": "fill_container",
"height": 72,
"fill": "#00000000",
"layout": "vertical",
"gap": 8,
"children": [
{
"type": "text",
"id": "OGamD",
"name": "管理员账号 标签",
"fill": "#0E1726",
"content": "管理员账号",
"lineHeight": 1.1,
"fontFamily": "Inter",
"fontSize": 12,
"fontWeight": "700"
},
{
"type": "frame",
"id": "alIMQ",
"name": "管理员账号 输入框",
"width": "fill_container",
"height": 46,
"fill": "#EEF2F7",
"cornerRadius": 12,
"gap": 10,
"padding": [
0,
14
],
"alignItems": "center",
"children": [
{
"type": "icon",
"id": "Y2gc9C",
"name": "管理员账号 图标",
"width": 17,
"height": 17,
"icon": "mail",
"library": "lucide",
"fill": "#64748B"
},
{
"type": "text",
"id": "DrsCN",
"name": "管理员账号 值",
"fill": "#0E1726",
"content": "admin@example.com",
"lineHeight": 1.1,
"fontFamily": "Inter",
"fontSize": 13,
"fontWeight": "500"
}
]
}
]
},
{
"type": "frame",
"id": "uOcjS",
"name": "访问密钥",
"width": "fill_container",
"height": 72,
"fill": "#00000000",
"layout": "vertical",
"gap": 8,
"children": [
{
"type": "text",
"id": "GQaue",
"name": "访问密钥 标签",
"fill": "#0E1726",
"content": "访问密钥",
"lineHeight": 1.1,
"fontFamily": "Inter",
"fontSize": 12,
"fontWeight": "700"
},
{
"type": "frame",
"id": "ZTZ2u",
"name": "访问密钥 输入框",
"width": "fill_container",
"height": 46,
"fill": "#EEF2F7",
"cornerRadius": 12,
"gap": 10,
"padding": [
0,
14
],
"alignItems": "center",
"children": [
{
"type": "icon",
"id": "BWHck",
"name": "访问密钥 图标",
"width": 17,
"height": 17,
"icon": "key-round",
"library": "lucide",
"fill": "#64748B"
},
{
"type": "text",
"id": "U168Qy",
"name": "访问密钥 值",
"fill": "#94A3B8",
"content": "输入会话密钥",
"lineHeight": 1.1,
"fontFamily": "Inter",
"fontSize": 13,
"fontWeight": "500"
}
]
}
]
},
{
"type": "frame",
"id": "Z8uAB",
"name": "表单选项",
"width": "fill_container",
"height": 30,
"fill": "#00000000",
"justifyContent": "space_between",
"alignItems": "center",
"children": [
{
"type": "frame",
"id": "V6QXQ",
"name": "保持登录",
"height": 22,
"fill": "#00000000",
"gap": 8,
"alignItems": "center",
"children": [
{
"type": "frame",
"id": "XqPpK",
"name": "保持登录勾选框",
"width": 16,
"height": 16,
"fill": "#FFFFFF",
"cornerRadius": 5,
"stroke": "#CBD5E1",
"strokeWidth": 1
},
{
"type": "text",
"id": "fqCp3",
"name": "保持登录文字",
"fill": "#64748B",
"content": "保持登录",
"lineHeight": 1.1,
"fontFamily": "Inter",
"fontSize": 12,
"fontWeight": "650"
}
]
},
{
"type": "text",
"id": "rVrLI",
"name": "忘记密钥",
"fill": "#2563EB",
"content": "忘记密钥",
"lineHeight": 1.1,
"fontFamily": "Inter",
"fontSize": 12,
"fontWeight": "700"
}
]
},
{
"type": "frame",
"id": "Q2vJi",
"name": "登录按钮",
"width": "fill_container",
"height": 48,
"fill": "#2563EB",
"cornerRadius": 12,
"effect": {
"type": "shadow",
"shadowType": "outer",
"color": "#2563EB66",
"offset": {
"x": 0,
"y": 12
},
"blur": 24,
"spread": -16
},
"gap": 10,
"justifyContent": "center",
"alignItems": "center",
"children": [
{
"type": "text",
"id": "ka5Jn",
"name": "登录按钮文字",
"fill": "#FFFFFF",
"content": "登录",
"lineHeight": 1.1,
"fontFamily": "Inter",
"fontSize": 15,
"fontWeight": "760"
},
{
"type": "icon",
"id": "j7l5g",
"name": "登录按钮图标",
"width": 18,
"height": 18,
"icon": "arrow-right",
"library": "lucide",
"fill": "#FFFFFF"
}
]
},
{
"type": "text",
"id": "dZM17",
"name": "登录安全说明",
"fill": "#64748B",
"textGrowth": "fixed-width",
"width": "fill_container",
"content": "本地会话将加密保存,仅用于当前管理端。",
"lineHeight": 1.35,
"textAlign": "center",
"fontFamily": "Inter",
"fontSize": 11,
"fontWeight": "600"
}
]
}
]
}
]
}
],
"variables": {
+679
View File
@@ -0,0 +1,679 @@
# Architecture
This document is the canonical Rust workspace architecture for
`codex-proxy-rs`. The Rust implementation must converge to this directory
shape and naming exactly. A migration is not complete while any extra Rust
source file, extra Rust source directory, or forbidden transitional name remains.
This document intentionally governs Rust workspace source layout. The `web/`
frontend has its own package layout and is not part of the Rust source
white-list below.
## Architectural Style
The application is a modular monolith with hexagonal boundaries.
- `core` owns domain model, use cases, protocol semantics, policy, and ports.
- `adapters` owns concrete implementations of `core` ports.
- `runtime` owns composition, application state, and background task wiring.
- `server` owns the Axum HTTP boundary.
- `platform` owns shared infrastructure primitives.
- `assets` owns compiled frontend asset serving.
- `xtask` owns local automation.
## Non-Negotiable Rules
- Rust source directories and files must match this document exactly at
completion.
- Do not keep transitional module names after migration.
- Do not hide structure drift behind compatibility re-exports.
- `core` must not depend on any project crate in the final architecture.
- `core` must not own concrete SQLx, Reqwest, Axum, filesystem path, TLS, or
upstream IO implementations.
- `server` must not decide business policy.
- `runtime` must not contain raw SQL, HTTP handlers, or domain rules.
- `adapters` must not define domain policy.
- Architecture tests must enforce directory shape, dependency direction,
forbidden names, and forbidden imports.
## Root Workspace Layout
```text
codex-proxy-rs/
├── Cargo.toml
├── Cargo.lock
├── README.md
├── AGENTS.md
├── src/
│ └── lib.rs
├── crates/
│ ├── core/
│ ├── adapters/
│ ├── runtime/
│ ├── server/
│ ├── platform/
│ ├── assets/
│ └── xtask/
├── tests/
├── web/
└── docs/
```
Root `src/lib.rs` is the only root facade. The root `Cargo.toml` must use the
default library path or explicitly point to `src/lib.rs`. Files such as
`crates/server/facade.rs` are forbidden.
## Dependency Direction
Final dependency graph:
```text
root facade -> server
server -> runtime + core + assets + platform
runtime -> core + adapters + platform
adapters -> core + platform
core -> no project crate dependencies
assets -> no project crate dependencies
xtask -> workspace tooling only
platform -> no domain crate dependencies
```
Forbidden project-crate dependencies:
- `core` must not depend on `platform`, `server`, `runtime`, `adapters`, or
`assets`.
- `platform` must not depend on `core`, `server`, `runtime`, `adapters`, or
`assets`.
- `adapters` must not depend on `runtime` or `server`.
- `runtime` must not depend on `server`.
- `assets` must not depend on project crates.
`runtime` maps `platform` configuration into `core` configuration DTOs before
constructing core services. This keeps `core` independent from config loading,
filesystem paths, environment variables, and storage bootstrap concerns.
## Crate Responsibilities
### `crates/core`
Owns domain concepts, invariants, use case orchestration, protocol semantics,
and ports.
Allowed:
- account pool and scheduling policy
- admin business use cases
- authentication/session domain behavior
- model catalog rules
- usage and event policy
- OpenAI/Codex protocol data structures and pure conversion
- request dispatch, fallback, retry, quota, affinity, recovery, and usage policy
- port traits
- typed domain and application errors
Forbidden:
- Axum extractors, routers, responses, or `IntoResponse`
- SQLx pools, queries, rows, transactions, or migrations
- concrete `reqwest::Client`
- concrete WebSocket/TLS connection setup
- concrete filesystem path decisions
- platform config loader/types
- public re-exports of project crates
### `crates/adapters`
Owns concrete implementations of ports defined by `core`.
Allowed:
- SQLx implementations of storage ports
- encrypted SQLite stores
- Reqwest HTTP clients
- concrete OpenAI OAuth client
- concrete Codex upstream HTTP/SSE/WebSocket clients
- concrete fingerprint update client
- adapter-specific translation from framework errors into `core` errors
Forbidden:
- business policy
- Axum route handlers
- runtime task orchestration
### `crates/runtime`
Owns application composition.
Allowed:
- `AppState`
- service construction
- repository construction
- adapter construction
- dependency injection
- background task startup and shutdown
- startup restore flows
- platform-to-core config mapping
Forbidden:
- HTTP request/response mapping
- raw SQL query strings
- concrete upstream protocol side effects
- account selection, fallback, quota, model, or recovery policy
### `crates/server`
Owns the HTTP boundary.
Allowed:
- Axum routers and route handlers
- extractors and middleware
- request IDs, tracing, CORS, and auth extraction
- admin response envelopes
- OpenAI-compatible HTTP response bodies
- HTTP status and header mapping
- SSE framing for client responses
Forbidden:
- SQL queries
- concrete upstream Codex IO
- account selection, fallback, quota, model, or recovery policy
- background task orchestration
### `crates/platform`
Owns cross-cutting infrastructure primitives.
Allowed:
- config loading and config schema types
- crypto primitives
- identity hashing primitives
- SQLite connection setup and schema files
- filesystem path primitives
- logging primitives
- JSON and pagination helpers
Forbidden:
- Codex/OpenAI business use cases
- admin business use cases
- Axum handlers
- implementations of `core` ports
### `crates/assets`
Owns static frontend asset serving.
Allowed:
- root `index.html`
- `/assets/*`
- SPA fallback after API routes
- static cache and security headers
### `crates/xtask`
Owns local automation.
Allowed:
- frontend build orchestration
- architecture checks
- release/package commands
- developer maintenance commands
## Exact Rust Source Shape
### Root Facade
```text
src/
└── lib.rs
```
### Core
```text
crates/core/
├── Cargo.toml
└── src/
├── lib.rs
├── error.rs
├── admin/
│ ├── mod.rs
│ ├── ports.rs
│ ├── auth.rs
│ ├── client_keys.rs
│ └── settings.rs
├── accounts/
│ ├── mod.rs
│ ├── model.rs
│ ├── ports.rs
│ ├── service.rs
│ ├── pool.rs
│ ├── lifecycle.rs
│ ├── cookies.rs
│ ├── jwt.rs
│ └── usage.rs
├── auth/
│ ├── mod.rs
│ ├── ports.rs
│ ├── oauth.rs
│ └── session.rs
├── models/
│ ├── mod.rs
│ ├── model.rs
│ ├── ports.rs
│ ├── catalog.rs
│ └── service.rs
├── events/
│ ├── mod.rs
│ ├── model.rs
│ ├── ports.rs
│ └── service.rs
├── usage/
│ ├── mod.rs
│ ├── model.rs
│ ├── ports.rs
│ └── service.rs
├── protocol/
│ ├── mod.rs
│ ├── openai/
│ │ ├── mod.rs
│ │ ├── chat.rs
│ │ ├── responses.rs
│ │ ├── models.rs
│ │ ├── errors.rs
│ │ └── schema.rs
│ └── codex/
│ ├── mod.rs
│ ├── chat.rs
│ ├── responses.rs
│ ├── events.rs
│ ├── sse.rs
│ ├── websocket.rs
│ └── schema.rs
├── gateway/
│ ├── mod.rs
│ ├── ports.rs
│ ├── fingerprint.rs
│ ├── conversation.rs
│ └── installation.rs
└── serving/
├── mod.rs
├── chat.rs
├── responses.rs
├── errors.rs
├── routing.rs
├── fallback.rs
├── affinity.rs
├── quota.rs
├── stream.rs
├── recovery.rs
└── usage.rs
```
`protocol` contains only protocol data structures and pure conversion.
`gateway` contains upstream-facing ports and transport-neutral request context.
Concrete HTTP, SSE, WebSocket, TLS, and cookie-jar behavior lives in adapters.
### Adapters
```text
crates/adapters/
├── Cargo.toml
└── src/
├── lib.rs
├── sqlite/
│ ├── mod.rs
│ ├── accounts.rs
│ ├── account_tokens.rs
│ ├── account_usage.rs
│ ├── refresh_leases.rs
│ ├── cookies.rs
│ ├── events.rs
│ ├── models.rs
│ ├── session_affinity.rs
│ ├── admin_sessions.rs
│ └── client_keys.rs
├── http/
│ ├── mod.rs
│ ├── reqwest_client.rs
│ └── headers.rs
├── codex/
│ ├── mod.rs
│ ├── client.rs
│ ├── sse.rs
│ ├── models.rs
│ ├── fingerprint.rs
│ └── websocket/
│ ├── mod.rs
│ ├── client.rs
│ ├── pool.rs
│ ├── codec.rs
│ ├── deflate.rs
│ └── opening.rs
└── oauth/
├── mod.rs
└── openai.rs
```
### Runtime
```text
crates/runtime/
├── Cargo.toml
└── src/
├── lib.rs
├── bootstrap.rs
├── state.rs
├── services.rs
├── repositories.rs
├── upstream.rs
├── config.rs
└── tasks/
├── mod.rs
├── coordinator.rs
├── token_refresh.rs
├── quota_refresh.rs
├── model_refresh.rs
├── cookie_cleanup.rs
├── session_cleanup.rs
└── fingerprint_update.rs
```
`runtime/src/config.rs` maps `platform` configuration into `core` configuration
DTOs.
### Server
```text
crates/server/
├── Cargo.toml
└── src/
├── main.rs
├── lib.rs
├── router.rs
├── error/
│ ├── mod.rs
│ ├── admin.rs
│ └── openai.rs
├── middleware/
│ ├── mod.rs
│ ├── request_id.rs
│ ├── trace.rs
│ ├── auth.rs
│ └── cors.rs
├── admin_api/
│ ├── mod.rs
│ ├── router.rs
│ ├── response.rs
│ ├── session.rs
│ ├── settings.rs
│ ├── diagnostics.rs
│ ├── models.rs
│ ├── usage.rs
│ ├── accounts/
│ │ ├── mod.rs
│ │ ├── list.rs
│ │ ├── create.rs
│ │ ├── import.rs
│ │ ├── export.rs
│ │ ├── lifecycle.rs
│ │ ├── quota.rs
│ │ ├── cookies.rs
│ │ ├── oauth.rs
│ │ └── health.rs
│ ├── client_keys/
│ │ ├── mod.rs
│ │ ├── list.rs
│ │ ├── create.rs
│ │ ├── import.rs
│ │ ├── export.rs
│ │ └── lifecycle.rs
│ └── logs/
│ ├── mod.rs
│ ├── query.rs
│ ├── detail.rs
│ └── state.rs
└── openai_api/
├── mod.rs
├── router.rs
├── auth.rs
├── chat.rs
├── responses.rs
├── models.rs
├── diagnostics.rs
├── error.rs
└── sse.rs
```
`server/src/error/openai.rs` maps application errors into OpenAI-compatible HTTP
responses. `server/src/openai_api/error.rs` and `server/src/openai_api/sse.rs`
own route-local OpenAI API error/event framing.
### Platform
```text
crates/platform/
├── Cargo.toml
└── src/
├── lib.rs
├── config/
│ ├── mod.rs
│ ├── loader.rs
│ └── types.rs
├── crypto/
│ ├── mod.rs
│ ├── secret_box.rs
│ └── hash.rs
├── identity/
│ ├── mod.rs
│ ├── admin_password.rs
│ └── client_key.rs
├── storage/
│ ├── mod.rs
│ ├── sqlite.rs
│ ├── schema.sql
│ └── paths.rs
├── logging/
│ ├── mod.rs
│ └── rotation.rs
└── json/
├── mod.rs
└── pagination.rs
```
### Assets
```text
crates/assets/
├── Cargo.toml
└── src/
├── lib.rs
├── router.rs
└── headers.rs
```
### Xtask
```text
crates/xtask/
├── Cargo.toml
└── src/
├── main.rs
├── build_web.rs
├── check_architecture.rs
└── release.rs
```
## Cargo Integration Test Shape
Cargo integration test entry files must remain at `tests/*.rs`; submodules live
under matching directories.
```text
tests/
├── admin.rs
├── openai_api.rs
├── accounts.rs
├── codex_upstream.rs
├── runtime.rs
├── architecture.rs
├── admin/
├── openai_api/
├── accounts/
├── codex_upstream/
├── runtime/
├── architecture/
│ ├── mod.rs
│ ├── dependency_direction.rs
│ ├── forbidden_imports.rs
│ └── directory_shape.rs
└── support/
├── mod.rs
├── app.rs
├── fixtures.rs
└── wiremock.rs
```
Additional fixture files are allowed under `tests/fixtures/`.
## Docs Shape
```text
docs/
├── architecture.md
├── adr/
├── api/
└── operations/
```
Historical migration plans may remain under `docs/superpowers/` while active
development is ongoing. They are not part of the final architecture white-list.
## Web Shape
The `web/` directory is outside this Rust workspace architecture white-list.
It is governed by frontend build tooling and must not be checked by Rust
directory-shape tests. `web/node_modules/` and `web/dist/` are build artifacts
from the frontend toolchain, not Rust architecture inputs.
## Required Names
- Root facade: `src/lib.rs`
- HTTP admin boundary: `server/src/admin_api`
- HTTP OpenAI-compatible boundary: `server/src/openai_api`
- Core port files: `ports.rs`
- Concrete SQLite adapters: `adapters/src/sqlite`
- Concrete OAuth adapter: `adapters/src/oauth/openai.rs`
- Concrete Codex upstream adapters: `adapters/src/codex`
- Concrete Codex WebSocket adapter: `adapters/src/codex/websocket`
- Shared middleware: `server/src/middleware`
- HTTP response errors: `server/src/error`
- OpenAI API route-local SSE framing: `server/src/openai_api/sse.rs`
- Business errors: `core/src/error.rs` or domain-local `errors.rs`
## Forbidden Names
- `crates/admin`
- `crates/server/facade.rs`
- `server/src/admin.rs`
- `server/src/codex_http`
- `server/src/openai_api/stream.rs`
- `core/src/**/repository.rs`
- `core/src/**/repository/`
- `adapters/src/*_repository.rs`
- `runtime/src/*_repository.rs`
- `core/src/codex/gateway/transport`
- `core/src/codex/gateway/oauth/client.rs`
- `core/src/codex/serving/http`
- `platform/src/platform`
- `platform/src/utils`
## Port Placement
Ports are traits owned by `core`.
- account storage ports live in `core/src/accounts/ports.rs`
- admin session/client-key ports live in `core/src/admin/ports.rs`
- OAuth and token refresh ports live in `core/src/auth/ports.rs`
- model snapshot ports live in `core/src/models/ports.rs`
- event log ports live in `core/src/events/ports.rs`
- usage ports live in `core/src/usage/ports.rs`
- upstream Codex and fingerprint ports live in `core/src/gateway/ports.rs`
Concrete implementations live in `adapters`.
- `SqliteAccountStore` lives in `adapters/src/sqlite/accounts.rs`
- `SqliteAdminSessionStore` lives in `adapters/src/sqlite/admin_sessions.rs`
- `OpenAiOAuthClient` lives in `adapters/src/oauth/openai.rs`
- `ReqwestCodexClient` lives in `adapters/src/codex/client.rs`
- `CodexWebSocketPool` implementation lives in `adapters/src/codex/websocket/pool.rs`
## Migration Rules
Every migration step must move the codebase closer to this document.
Required order:
1. Add architecture tests for directory shape and forbidden names.
2. Move the root facade to `src/lib.rs`.
3. Rename server HTTP boundaries to `admin_api` and `openai_api`.
4. Move admin business code into `core/src/admin`.
5. Move all core repository traits into `ports.rs`.
6. Move all SQLx implementations into `adapters/src/sqlite`.
7. Move all Reqwest/SSE/WebSocket/OAuth implementations into `adapters`.
8. Collapse runtime into composition-only files.
9. Collapse platform into config/crypto/identity/storage/logging/json primitives.
10. Update facade exports after internal structure is correct.
11. Run full verification only after the structure is fully aligned.
Do not use transitional aliases as a substitute for migration. A transitional
alias is acceptable only if it is removed in the same migration series before
claiming completion.
## Architecture Tests
Architecture tests must enforce:
- every required Rust source file exists
- every required Rust source directory exists
- forbidden Rust source files and directories do not exist
- no extra Rust source files exist under `src/` and `crates/*/src/`
- no extra Rust source directories exist under `src/` and `crates/*/src/`
- `Cargo.toml` workspace members match this document
- root library path is `src/lib.rs`
- `core` has no project-crate dependencies
- `core` has no `axum`, `sqlx`, or concrete `reqwest::Client`
- `server` has no SQLx queries
- `runtime` has no SQLx query strings and no Axum handlers
- `adapters` owns concrete SQLx/Reqwest/OAuth/Codex IO
- `core` ports are declared in `ports.rs`
- root facade does not hide invalid internal directory names
The required architecture test files are:
```text
tests/architecture/dependency_direction.rs
tests/architecture/forbidden_imports.rs
tests/architecture/directory_shape.rs
```
## Completion Criteria
The migration is complete only when all of the following are true:
- The Rust workspace source tree matches this document exactly.
- Forbidden names are absent from Rust source directories.
- Architecture tests enforce this document.
- `cargo fmt --check` passes.
- `cargo check --workspace --all-targets` passes.
- `cargo test --workspace` passes.
- `cargo clippy --workspace --all-targets --all-features --locked -- -D warnings` passes.