# 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/`;若校验,注释写清依据。 - **`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 之前的一轮小整改。