mirror of
https://github.com/zyycn/codex-proxy-rs.git
synced 2026-11-07 00:05:49 +00:00
docs: add architecture overview
Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
@@ -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
|
||||
@@ -17,3 +17,5 @@
|
||||
*.codegraph
|
||||
*/node_modules
|
||||
*/dist
|
||||
.agents
|
||||
.cargo
|
||||
@@ -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": {
|
||||
|
||||
@@ -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.
|
||||
Reference in New Issue
Block a user