Skip to content

Commit 5000973

Browse files
authored
Merge pull request #331 from pionxe/feat/gateway-epic-gw-06-ops-security-governance
feat: [EPIC-GW-06] 网关可运维性与安全治理收口
2 parents ee095ef + cc5931a commit 5000973

50 files changed

Lines changed: 5524 additions & 111 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.codecov.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@ coverage:
55
target: 80% # 告诉裁判:整体覆盖率 80% 就给我亮绿灯
66
patch:
77
default:
8-
target: 80% # 告诉裁判:这次 PR 新增的代码覆盖率达到 80% 也给我亮绿灯
8+
target: 80% # 告诉裁判:这次 PR 新增的代码覆盖率达到 95% 才给我亮绿灯

README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,27 @@ go run ./cmd/neocode --workdir /path/to/workspace
171171
- 不提交明文密钥、个人配置或会话数据
172172
- 不提交无关改动与临时文件
173173

174+
## 网关运维与安全(GW-06)
175+
176+
- 静默认证(Silent Auth):
177+
- 启动 `neocode gateway` 时会自动读取 `~/.neocode/auth.json`
178+
- 若凭证不存在或损坏,会自动生成高强度 token 并写回该文件。
179+
- `url-dispatch` 会自动读取同一 token 并先发送 `gateway.authenticate`,再发送业务请求。
180+
- 认证与授权顺序:`Auth -> ACL -> Dispatch`
181+
- 未认证返回 `unauthorized`
182+
- 已认证但不允许的方法返回 `access_denied`
183+
- 运维端点:
184+
- 免鉴权:`GET /healthz``GET /version`
185+
- 需鉴权:`GET /metrics``GET /metrics.json``Authorization: Bearer <token>`
186+
- 关键默认治理参数(可通过 `config.yaml``gateway.*` 配置):
187+
- `max_frame_bytes=1MiB`
188+
- `ipc_max_connections=128`
189+
- `http_max_request_bytes=1MiB`
190+
- `http_max_stream_connections=128`
191+
- `ipc_read/write_sec=30/30`
192+
- `http_read/write/shutdown_sec=15/15/2`
193+
- 详细设计文档:[`docs/gateway-detailed-design.md`](docs/gateway-detailed-design.md)
194+
174195
## License
175196

176197
MIT

docs/gateway-detailed-design.md

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
# Gateway 详细设计(EPIC-GW-06)
2+
3+
## 1. 目标与边界
4+
5+
Gateway 是 NeoCode 的协议与路由中枢,职责是:
6+
7+
- 生命周期管理(IPC + HTTP/WS/SSE 并行启动、优雅关闭)
8+
- 协议归一化(外层 JSON-RPC 2.0,内层 `gateway.MessageFrame`
9+
- 鉴权与 ACL(`Auth -> ACL -> Dispatch`
10+
- 会话流式中继(session/run/channel 精准投递)
11+
12+
Gateway **不承载业务逻辑**,不会做模型推理、工具编排与 Provider 选择。业务执行仅由 Runtime 决定。
13+
14+
## 2. 架构图(含进程边界)
15+
16+
```mermaid
17+
flowchart LR
18+
subgraph ClientProcess["客户端进程边界"]
19+
CLI["CLI / TUI"]
20+
WEB["Web / Desktop UI"]
21+
EXT["External Adapter\nURL Scheme / Clipboard"]
22+
end
23+
24+
subgraph GatewayProcess["Gateway 进程边界"]
25+
IPC["IPC Listener\nUDS / Named Pipe"]
26+
NET["HTTP/WS/SSE Listener"]
27+
AUTH["Auth + ACL"]
28+
NORM["JSON-RPC -> MessageFrame\nNormalize"]
29+
ROUTER["Dispatch + Stream Relay"]
30+
OPS["Health / Version / Metrics"]
31+
end
32+
33+
subgraph RuntimeProcess["Runtime 进程边界"]
34+
RT["RuntimePort\n编排与事件流"]
35+
TOOLS["Tools"]
36+
PROVIDER["Provider Adapter"]
37+
end
38+
39+
subgraph CloudBoundary["云端边界"]
40+
CLOUD["Cloud LLM API"]
41+
end
42+
43+
CLI --> IPC
44+
WEB --> NET
45+
EXT --> IPC
46+
EXT --> NET
47+
48+
IPC --> AUTH
49+
NET --> AUTH
50+
AUTH --> NORM
51+
NORM --> ROUTER
52+
ROUTER --> RT
53+
ROUTER --> OPS
54+
55+
RT --> TOOLS
56+
RT --> PROVIDER
57+
PROVIDER --> CLOUD
58+
```
59+
60+
## 3. 核心时序图
61+
62+
### 3.1 本地控制面链路(Client -> Gateway -> Runtime -> Client)
63+
64+
```mermaid
65+
sequenceDiagram
66+
box rgb(238, 246, 255) 客户端进程
67+
participant C as "Client (CLI/WS/SSE)"
68+
end
69+
box rgb(241, 255, 241) Gateway 进程
70+
participant G as "Gateway Listener"
71+
participant A as "Auth + ACL"
72+
participant D as "Normalize + Dispatch"
73+
participant R as "Stream Relay"
74+
end
75+
box rgb(255, 249, 238) Runtime 进程
76+
participant RT as "RuntimePort"
77+
end
78+
79+
C->>G: JSON-RPC request
80+
G->>A: 校验 Token / ACL
81+
A-->>G: allow
82+
G->>D: Normalize(JSON-RPC -> MessageFrame)
83+
D->>RT: RuntimePort 调用(无业务改写)
84+
RT-->>D: 结果 / 事件
85+
D->>R: MessageFrame(event/ack/error)
86+
R-->>C: JSON-RPC response/notification
87+
```
88+
89+
### 3.2 云端调用链路(Runtime -> Provider -> Cloud API)
90+
91+
```mermaid
92+
sequenceDiagram
93+
box rgb(238, 246, 255) 客户端进程
94+
participant C as "Client"
95+
end
96+
box rgb(241, 255, 241) Gateway 进程
97+
participant G as "Gateway"
98+
end
99+
box rgb(255, 249, 238) Runtime 进程
100+
participant RT as "Runtime"
101+
participant P as "Provider Adapter"
102+
end
103+
box rgb(255, 240, 245) 云端边界
104+
participant LLM as "Cloud LLM API"
105+
end
106+
107+
C->>G: gateway.run / wake.openUrl
108+
G->>RT: 透传规范化请求
109+
RT->>P: 选择并调用 Provider
110+
P->>LLM: HTTP API
111+
LLM-->>P: streaming/result
112+
P-->>RT: 统一 Provider 结果
113+
RT-->>G: runtime events
114+
G-->>C: gateway.event / result
115+
```
116+
117+
## 4. 数据流向(本地端与云端区别)
118+
119+
- 本地控制面:
120+
- 客户端只与 Gateway 通信(IPC/HTTP/WS/SSE)。
121+
- Gateway 负责协议、连接、鉴权、路由与中继。
122+
- 本地控制面不直接触达云端。
123+
- 云端调用:
124+
- 仅 Runtime 与 Provider 层触达 Cloud API。
125+
- Gateway 不感知模型厂商细节,不拼接 Provider 私有字段。
126+
127+
## 5. 对外接口清单
128+
129+
### 5.1 面向客户端接口
130+
131+
| 接口 | 方向 | 认证 | 说明 |
132+
|---|---|---|---|
133+
| IPC (UDS / Named Pipe) | Client -> Gateway | `gateway.authenticate` 握手后复用 | 本地控制面主入口 |
134+
| `POST /rpc` | Client -> Gateway | `Authorization: Bearer <token>` | 单次 JSON-RPC 请求 |
135+
| `GET /ws` | Client <-> Gateway | `gateway.authenticate` 握手后复用 | 双向流式请求与通知 |
136+
| `GET /sse` | Client <- Gateway | `?token=<token>` | 单向流式通知与心跳 |
137+
| `GET /healthz` | Client -> Gateway || 健康检查 |
138+
| `GET /version` | Client -> Gateway || 版本信息 |
139+
| `GET /metrics` | Client -> Gateway | Bearer Token | Prometheus 指标 |
140+
| `GET /metrics.json` | Client -> Gateway | Bearer Token | JSON 指标快照 |
141+
142+
### 5.2 JSON-RPC 方法
143+
144+
| Method | 方向 | 说明 |
145+
|---|---|---|
146+
| `gateway.authenticate` | request/response | 连接级鉴权,成功后复用认证态 |
147+
| `gateway.ping` | request/response | 健康探针 |
148+
| `gateway.bindStream` | request/response | 会话流绑定 |
149+
| `wake.openUrl` | request/response | URL Scheme 唤醒入口 |
150+
| `gateway.event` | notification | Gateway 推送运行时事件 |
151+
152+
### 5.3 面向 Runtime 接口(`RuntimePort`
153+
154+
| 方法 | 说明 |
155+
|---|---|
156+
| `Run(ctx, input)` | 发起一次运行编排 |
157+
| `Compact(ctx, input)` | 执行会话压缩 |
158+
| `ResolvePermission(ctx, input)` | 回填权限审批结果 |
159+
| `CancelActiveRun()` | 取消活动运行 |
160+
| `Events()` | 订阅运行时事件流 |
161+
| `ListSessions(ctx)` | 获取会话摘要 |
162+
| `LoadSession(ctx, id)` | 加载会话详情 |
163+
164+
## 6. 安全与治理基线
165+
166+
### 6.1 Silent Auth
167+
168+
- Token 文件:`~/.neocode/auth.json`
169+
- 启动网关时自动加载;缺失或损坏自动重建
170+
- 文件结构:`version`, `token`, `created_at`, `updated_at`
171+
172+
### 6.2 ACL 与错误模型
173+
174+
- 执行顺序:`Auth -> ACL -> Dispatch`
175+
- 错误返回统一:
176+
- JSON-RPC:`error.code`
177+
- Gateway 稳定码:`error.data.gateway_code`
178+
- 关键稳定码:`unauthorized`, `access_denied`, `invalid_frame`, `unsupported_action`
179+
180+
### 6.3 默认治理参数
181+
182+
| 配置项 | 默认值 |
183+
|---|---|
184+
| `gateway.limits.max_frame_bytes` | `1048576` |
185+
| `gateway.limits.ipc_max_connections` | `128` |
186+
| `gateway.limits.http_max_request_bytes` | `1048576` |
187+
| `gateway.limits.http_max_stream_connections` | `128` |
188+
| `gateway.timeouts.ipc_read_sec` | `30` |
189+
| `gateway.timeouts.ipc_write_sec` | `30` |
190+
| `gateway.timeouts.http_read_sec` | `15` |
191+
| `gateway.timeouts.http_write_sec` | `15` |
192+
| `gateway.timeouts.http_shutdown_sec` | `2` |
193+
| `gateway.observability.metrics_enabled` | `true` |
194+
195+
## 7. 配置优先级
196+
197+
- `flags > config.yaml > default constants`
198+
- 当前支持通过 `~/.neocode/config.yaml``gateway.*` 段配置治理参数。
199+
200+
## 8. 非目标(本期)
201+
202+
- 不新增 Provider/Tools 业务能力
203+
- 不引入外网公开监听与 TLS
204+
- 不在 Gateway 内实现 Runtime 业务决策

go.mod

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ require (
2121
github.com/atotto/clipboard v0.1.4 // indirect
2222
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
2323
github.com/aymerick/douceur v0.2.0 // indirect
24+
github.com/beorn7/perks v1.0.1 // indirect
25+
github.com/cespare/xxhash/v2 v2.3.0 // indirect
2426
github.com/charmbracelet/colorprofile v0.4.3 // indirect
2527
github.com/charmbracelet/harmonica v0.2.0 // indirect
2628
github.com/charmbracelet/x/ansi v0.11.6 // indirect
@@ -45,7 +47,12 @@ require (
4547
github.com/muesli/cancelreader v0.2.2 // indirect
4648
github.com/muesli/reflow v0.3.0 // indirect
4749
github.com/muesli/termenv v0.16.0 // indirect
50+
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
4851
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
52+
github.com/prometheus/client_golang v1.23.2 // indirect
53+
github.com/prometheus/client_model v0.6.2 // indirect
54+
github.com/prometheus/common v0.66.1 // indirect
55+
github.com/prometheus/procfs v0.16.1 // indirect
4956
github.com/rivo/uniseg v0.4.7 // indirect
5057
github.com/sagikazarmark/locafero v0.11.0 // indirect
5158
github.com/sahilm/fuzzy v0.1.1 // indirect
@@ -57,10 +64,12 @@ require (
5764
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
5865
github.com/yuin/goldmark v1.7.13 // indirect
5966
github.com/yuin/goldmark-emoji v1.0.6 // indirect
67+
go.yaml.in/yaml/v2 v2.4.2 // indirect
6068
go.yaml.in/yaml/v3 v3.0.4 // indirect
6169
golang.org/x/exp/shiny v0.0.0-20250606033433-dcc06ee1d476 // indirect
6270
golang.org/x/image v0.28.0 // indirect
6371
golang.org/x/mobile v0.0.0-20250606033058-a2a15c67f36f // indirect
6472
golang.org/x/term v0.41.0 // indirect
6573
golang.org/x/text v0.35.0 // indirect
74+
google.golang.org/protobuf v1.36.8 // indirect
6675
)

go.sum

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ github.com/aymanbagabas/go-udiff v0.3.1 h1:LV+qyBQ2pqe0u42ZsUEtPiCaUoqgA9gYRDs3v
1616
github.com/aymanbagabas/go-udiff v0.3.1/go.mod h1:G0fsKmG+P6ylD0r6N/KgQD/nWzgfnl8ZBcNLgcbrw8E=
1717
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
1818
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
19+
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
20+
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
21+
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
22+
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
1923
github.com/charmbracelet/bubbles v1.0.0 h1:12J8/ak/uCZEMQ6KU7pcfwceyjLlWsDLAxB5fXonfvc=
2024
github.com/charmbracelet/bubbles v1.0.0/go.mod h1:9d/Zd5GdnauMI5ivUIVisuEm3ave1XwXtD1ckyV6r3E=
2125
github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw=
@@ -59,6 +63,7 @@ github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9L
5963
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
6064
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
6165
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
66+
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
6267
github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
6368
github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=
6469
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
@@ -90,10 +95,20 @@ github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
9095
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
9196
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
9297
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
98+
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
99+
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
93100
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
94101
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
95102
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
96103
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
104+
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
105+
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
106+
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
107+
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
108+
github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=
109+
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
110+
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
111+
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
97112
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
98113
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
99114
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
@@ -128,6 +143,8 @@ github.com/yuin/goldmark v1.7.13 h1:GPddIs617DnBLFFVJFgpo1aBfe/4xcvMc3SB5t/D0pA=
128143
github.com/yuin/goldmark v1.7.13/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
129144
github.com/yuin/goldmark-emoji v1.0.6 h1:QWfF2FYaXwL74tfGOW5izeiZepUDroDJfWubQI9HTHs=
130145
github.com/yuin/goldmark-emoji v1.0.6/go.mod h1:ukxJDKFpdFb5x0a5HqbdlcKtebh086iJpI31LTKmWuA=
146+
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
147+
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
131148
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
132149
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
133150
golang.design/x/clipboard v0.7.1 h1:OEG3CmcYRBNnRwpDp7+uWLiZi3hrMRJpE9JkkkYtz2c=
@@ -150,6 +167,8 @@ golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU=
150167
golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A=
151168
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
152169
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
170+
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
171+
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
153172
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
154173
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
155174
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

0 commit comments

Comments
 (0)