Files
chis_osi/docs/review/2026-07-06-phase0-review.md
ilaandClaude Opus 4.8 12039d8c46 docs: 新增 Phase 0 代码评审文档(T-001~T-005)
按全栈视角审核脚手架/签名/传输/Call/验收,记录达标判定与分级整改项

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 23:23:15 +08:00

104 lines
8.1 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Phase 0 代码评审 · 脚手架与契约骨架(T-001~T-005)
- 评审人:Claude(全栈视角)
- 日期:2026-07-06
- 范围:`tasks.md` Phase 0 全部任务 T-001~T-005,对应 `docs/05` 阶段 0
- 方法:静态代码审读 + git 历史核对。**本环境离线无 go 1.24 工具链,未能动态跑 `go build/test`**;涉及运行时行为处已标注"未动态复验"。
---
## 结论(Gate 判定)
**Phase 0 未通过验收门槛(有条件达标)。**
T-001~T-004 的设计与实现质量良好、分层清晰、测试到位。但 **T-005 及其修复代码整体未提交**,导致仓库层面无法复现"Go 侧真实握手成功",违反看板自身的"DONE 需可复现证据 + clean-state"规则;且传输层存在**生产阻断项**(不支持 https、未处理 gzip)。
| 任务 | 达标判定 | 说明 |
|------|---------|------|
| T-001 骨架 | ✅ 达标 | go module + 双 mode + viper config + 占位示例,测试覆盖 mode 解析/配置加载 |
| T-002 签名 | ✅ 达标 | MD5 正确、向量与 Python 一致、`ask` 不进 headers/日志 |
| T-003 传输(HEAD 版) | ✅ 达标(但已被工作区改写取代) | 提交版是干净 net/http + SOCKS5、无拟态头 |
| T-004 Call/codes/envelope | ✅ 达标(有小瑕疵) | 信封/判码/路径正确,测试充分;`Envelope` 顶层 `BaseInfo` 冗余 |
| T-005 验收 | ❌ 不达标(仓库层面) | **代码全部未提交**;HEAD 无法复现验收;传输层 gzip/https/死代码 |
---
## P1 · 阻断项(必须先解决)
### P1-1 T-005 代码整体未提交,仓库与"DONE"不符
`tasks.md`/`progress.md`/`docs/05` 均标 T-005 `DONE`、里程碑 M1"真实握手成功"已宣告,但以下文件**全部停留在工作区未入库**(HEAD 停在 T-004 `2a77a4d`):
```
M main.go (新增 -verify-jkda-id-card 入口)
M osi/transport.go (改写为裸 HTTP/1.1,+110 行)
M config/config.go (OSI_* 环境变量绑定)
M osi/transport_test.go
?? verify_jkda.go verify_jkda_test.go config/env_test.go
```
后果:**从干净检出 `git checkout HEAD` 拿到的是 T-004 的 net/http 传输层——正是 progress 记录里"连真机报 EOF"的那一版**。也就是说,仓库里的可复现实现**不满足** T-005 验收,通过验收的证据只存在于一份未提交的工作区。按看板规则(标 DONE 前须有可运行证据入库 + 过 clean-state 检查),T-005 未真正完成。
**整改**:把上述文件作为 T-005 的独立 commit 提交;或在提交前先撤回 T-003/004/005 的 DONE 标记。二选一,不能让"文档说完成、仓库无实现"的漂移留存。
### P1-2 传输层仅支持 http,https 完全不可用
`osi/transport.go`(工作区版)第 63-65 行:`if target.Scheme != "http" { return ...unsupported }`,且请求是**明文写裸 TCP conn(无 TLS)**。沙箱是 http 能过,但**生产 OSI 若为 https,此传输层直接无法连接**。这是被裸写方案引入的能力倒退——HEAD 的 net/http 版本反而天然支持 https。
**整改**:在 Phase 0 收口前至少 fail-fast 明确报错并登记为"生产前必须解决"任务;理想是让 https 走 net/http、仅对需要保头大小写的 http 明文路径用裸写。
### P1-3 声明 gzip 却不解压(真机一旦压缩即解析失败)
裸写请求固定发送 `Accept-Encoding: gzip, deflate, br`(第 110 行默认值),但响应用 `http.ReadResponse` + `io.ReadAll` 直接读——**`http.ReadResponse` 不会自动解压**(自动解压只发生在 `http.Transport` 自己加 gzip 头时)。当前能跑仅因沙箱返回未压缩;一旦服务端/代理按声明返回 `Content-Encoding: gzip`,`json.Unmarshal` 会拿到压缩字节而失败。未动态复验,但逻辑上是确定的定时炸弹。
**整改**:要么不声明压缩(发 `Accept-Encoding: identity`),要么按 `Content-Encoding` 真解压。Python `requests` 是自动解压的,照抄它的头却不照抄它的解压是半截移植。
---
## P2 · 重要(尽快处理)
### P2-1 `t.client`/roundTripper 是死代码
工作区 `NewTransport` 仍构造 `roundTripper` 和 `http.Client` 存入 `t.client`(第 37-50 行),并在其上接了 SOCKS5 `DialContext`——但裸写版 `PostJSON` 从头到尾只用 `t.dialer`,**`t.client` 与 roundTripper 上的代理接线全部不被执行**。初级维护者会被"这里有个 http.Client 却什么都不做"严重误导,也埋下"改了 client 以为生效实则没用"的坑。
**整改**:删除 `t.client`/roundTripper 构造,只保留 `dialer`+`timeout`;或反过来回到 net/http 由 client 承载代理。二者留一,不要并存。
### P2-2 裸写方案的根因未定论,且立论未被"真机级"验证
progress 记"net/http 规范化头名 → EOF"。但 servlet 的 `getHeader` 本就大小写不敏感,"头名大小写导致 EOF"的因果并不牢靠,EOF 更可能来自代理/keep-alive/Content-Length 等。头大小写保留本身**有测试**(`transport_test.go:90-122` 裸 TCP 断言 `\r\norgCode:`,这点做得好),但"保留大小写才能连上真机"这个**因果**没有留下失败/成功对照的抓包证据。
**整改**:在 `progress` 或本目录补一份"net/http 版失败请求 vs 裸写版成功请求"的抓包/头部对照,坐实根因;否则这段重量级 hack 的存续缺乏依据,后续无人敢动。
### P2-3 验收入口把真实身份证放上命令行
`-verify-jkda-id-card <身份证>` 会让真实身份证进入 shell history、`ps` 进程列表、`init.sh` 回显。属 PII 泄露面,与项目安全红线(不落真实身份证)相抵。
**整改**:改从环境变量或 stdin 读取待查身份证,命令行不接收 PII。
---
## P3 · 建议(可排期)
- **`contract.Envelope` 顶层 `BaseInfo` 字段冗余**:真实契约里 `baseInfo` 只存在于 `uploadinfo` 内,顶层这个 `BaseInfo any`(envelope.go:6)是早期"查询发裸 baseInfo"误解的残留,留着易被误用。建议删除。
- **`Client.Call` 对 nil transport 静默兜底**(client.go:52-58):自动建一个**无代理**传输,真机(内网 host 必须走代理)会连不上却不报配置缺失。建议 nil 时直接报错,逼调用方显式注入。
- **UA 伪装 `python-requests/2.32.4`**(transport.go:109 默认值):与"去掉网页拟态头"的设计取向轻微冲突,且写死版本号。若真机不校验 UA,建议用中性 `chis_osi/<ver>`;若校验,注释写清依据。
- **`init.sh` 在本机 Git Bash 跑不通**(progress T-005 记:标准库路径/构建缓存权限异常)。统一验证入口在主力开发机上无法运行,等于形同虚设,建议要么修好要么在 `current-state` 显著标注"本机用 PowerShell 等价命令"。
---
## 做得好的地方(保持)
- **签名层**干净正确:`SignPassword` 与 Python `hashlib` 向量一致(`008ace...`),`ask` 严格不进 headers。
- **判码**按去前导零 == `"1"` 实现,正确兼容实测 `"01"`;`405` 归可重试;HTTP 5xx/429 归可重试——与 `docs/01 §1`、`docs/04 §6` 对齐。
- **信封**结构与 `docs/04 §8` 实测契约一致(`serviceId`+`uploadinfo{baseInfo,manageInfo}`,`operateUnit` 缺省回落 `orgCode`)。
- **测试**覆盖到位:签名向量、请求头大小写(裸 TCP)、信封字段、成功/405 分支、时间戳 13 位、URL 拼接、env 覆盖。
- **配置**:`config.yaml.example` 全占位无密钥;`OSI_*` 环境变量覆盖;真实 `config.yaml` 已 gitignore。
---
## 收口所需动作(建议作为 Phase 0 补完任务)
1. **[P1-1]** 提交 T-005 全部代码为独立 commit,或撤回 DONE 标记(二选一)。
2. **[P1-3]** 传输层去掉 gzip 声明或实现解压。
3. **[P1-2]** https 支持或 fail-fast + 登记生产前任务。
4. **[P2-1]** 删除 `t.client` 死代码。
5. **[P2-3]** 验收身份证改走 env/stdin。
6. **[P2-2]** 补根因抓包对照。
7. **[P3]** 删 `Envelope.BaseInfo`、nil-transport 改报错、UA 处理、init.sh 说明。
> 完成 1~3 后,Phase 0 方可视为真正达标、里程碑 M1 成立;4~7 可并入 T-201 之前的一轮小整改。