فهرست منبع

docs(进件): 三-A 真机读数取证

只动文档,无代码变更:spec §6.2 (e) 三条判据**就地订正** + 新增 §6.6 真机读数。

- §6.2 (e) 就地订正三条(不留错在原处,后加的 §6.6 只作依据):
  - `area_code`:原判「真实形态不是纯数字 / 会炸」**不成立** —— 那是把文档返回**示例**的
    形状当成了**服务的行为**。实测 `Integer` 483/483、非数字 0 条。**仍按 String 承载**,
    但理由换成实测撑得住的(① 要原样回传给 String 参数 `parent_code`;② `String.valueOf`
    对 Integer 无损)。**不是**因为「它可能是 100A」。
  - `manager_name`:「签名相关、上游可能判参数不合法」已**实测排除** —— 上游认的就是
    参数表那版;请求示例里的 `maneger_*` 是文档自己错拼。改判「不构成缺陷、无需改动」。
  - `level`:实测发字符串 `"1"/"2"/"3"` 全部可用,省/市/区县三级均非空。
- 新增 §6.6:取证方式(探针调的是**生产代码** `LeshouyingClient` / `LeshouyingSigner` /
  `LeshouyingResponseParser` / `LsyOnboardingService`,未重写一行签名逻辑)、7 项读数表、
  6.6.1 两条更正、6.6.2 两条文档**缺漏**(每级多一个未记录的 GB 码字段;`parent_code`
  传错是**静默空列表**而非报错)、6.6.3 三-B 缺口(`LsyAreaItemDTO` 丢了那三个 GB 码,
  届时加,本次不改代码)、6.6.4 安全读数(`/api/merchant/info` 含真人 PII,类 javadoc 里
  的 R-4 租户缺口是**真实高危暴露面**)、6.6.5 未测项(留空标注,不填推断)。

★ 本次含**一次真实的上游测试环境写操作**:`/api/shop/create` 真发成功并落库(已在上游
  测试环境建出一家门店),用于定 R-2(参数表拼写 `manager_name` / `manager_phone`)。
  PII 值不入本仓任何文件;原始应答留在仓库外 `D:\lsy-regress\task7\`。
alphaH 3 هفته پیش
والد
کامیت
dcb4f9684c
1فایلهای تغییر یافته به همراه59 افزوده شده و 3 حذف شده
  1. 59 3
      docs/superpowers/specs/2026-09-22-receive-workbench-design.md

+ 59 - 3
docs/superpowers/specs/2026-09-22-receive-workbench-design.md

@@ -462,9 +462,9 @@ GET /payment/cashier/{codeNo}/auth
 
 | 页 | 缺陷 | 影响 |
 |---|---|---|
-| 创建门店 196236559 | **参数表写 `manager_name` / `manager_phone`,同页请求示例写 `maneger_name` / `maneger_phone`**("maneger" 是错拼) | **签名相关**:发错键名上游可能直接判「参数不合法」。属可实测项,已排进三-A 联调 |
-| 获取省市区 196236568 | `level` 参数表写 `String`,请求示例写**数字** `3` | 轻微,发字符串即可 |
-| 获取省市区 196236568 | **`area_code` 的真实形态不是纯数字**:返回示例里出现 `100A`…`100H`,其中 `100G`/`100H` **不是合法十六进制** | **会炸**:任何 `parseInt` / 数字型 DTO 字段在这里必崩。必须当字符串 |
+| 创建门店 196236559 | **参数表写 `manager_name` / `manager_phone`,同页请求示例写 `maneger_name` / `maneger_phone`**("maneger" 是错拼) | ~~签名相关:发错键名上游可能直接判「参数不合法」~~ → **2026-09-22 真机定案:按参数表拼写真发成功**(返回 `shop_no`),上游认参数表那版 ⇒ **不构成缺陷,无需改动**。见 §6.6 |
+| 获取省市区 196236568 | `level` 参数表写 `String`,请求示例写**数字** `3` | 轻微。~~发字符串即可~~ → **实测(2026-09-22):`level` 发字符串 `"1"/"2"/"3"` 全部可用**,返回省 / 市 / 区县三级均非空。见 §6.6 |
+| 获取省市区 196236568 | 返回**示例**里 `area_code` 出现 `100A`…`100H`(其中 `100G`/`100H` 不是合法十六进制) | ★ **原判「真实形态不是纯数字 / 会炸」不成立 —— 那是文档示例的形状,被当成了服务行为。2026-09-22 真机普查:`area_code` 是 `Integer`,483/483,非数字 0 条**(见 §6.6)。**仍按 `String` 承载**,理由是实测撑得住的:① `area_code` 要被**原样回传**给上游的 `parent_code`(那是 String 参数);② `asText` 的 `String.valueOf` 对 Integer **无损**。**不是**因为「它可能是 `100A`」 |
 | 获取门店列表 196236560 | 返回参数表 `result_code` 一行的「参数示例」列写着 `PAY_SUCCESS`,与同一行的说明「SUCCESS操作成功,FAIL操作失败」**自相矛盾**(支付类页面复制残留) | 无(说明与返回示例一致,取 `SUCCESS`/`FAIL`) |
 | 获取门店列表 196236560 | 返回示例里 `"trace_no": "123456"` 后面**缺一个逗号**,整段是**非法 JSON** | **会误导**:谁照抄这坨当夹具,Hutool 会解析失败,而失败原因看起来像「上游报文坏了」。同时 `shop_list` 第二项**没有 `shop_tel` 键** |
 | 获取商户信息 196236558 | `wx_channel_rate` / `ali_channel_rate` 在返回参数表里**各出现两次**;返回示例里 `merchant_type` / `account_type` / `license_type` / `wx_channel_id` 是**数字**而表里写 `String` | 轻微,读时按 `String.valueOf` 归一 |
@@ -512,6 +512,62 @@ GET /payment/cashier/{codeNo}/auth
 
 异步通知要「由合作机构提供一个固定的接口地址」。现有配置里已有支付回调地址(`leshouying.notify-url`),进件审核通知是**另一个**固定地址,需要单独一个配置项和一条白名单路径。上游侧配置动作需要用户去要。
 
+### 6.6 三-A 真机读数(2026-09-22)
+
+**取证方式**:探针放在仓库外(`D:\lsy-regress\LsyTask7Probe.java`),**调的是生产代码**
+(`LeshouyingClient` / `LeshouyingSigner` / `LeshouyingResponseParser` / `LsyOnboardingService`),
+**没有重写一行签名逻辑**。目标是文档给的**测试**环境与**测试**机构。原始应答留在 `D:\lsy-regress\task7\`(UTF-8)。
+**凭据只打印长度,不打印值。**
+
+★ **本节全部为真实请求** —— 含**一次真实的门店创建**(下表 #6,在上游测试环境建出了一家门店)。
+
+| # | 接口 | 实测读数 |
+|---|---|---|
+| 1 | `/api/info/area` `level=1` | **34** 条省/直辖市(`result_code: SUCCESS`)。`area_code` = **Integer**(`1`/`19`/`37`/`251`…);另有**文档未记录**的 `province_code` = **String**(`"110000"`…) |
+| 2 | `/api/info/area` `level=2` / `level=3` | **三级联动走得通**(`parent_code` 传上一级的 `area_code`)。level2 另有 `city_code`、level3 另有 `county_code`,**均为 String** |
+| 2b | 同上,`parent_code` 填 **GB 码** `"110000"` | ★ **`result_code: SUCCESS` + `area_list: []`** —— 上级传错是**静默空列表**,**不报错**。某级忽然空了先怀疑 `parent_code` 传错,**别怀疑上游挂了** |
+| 3 | `/api/merchant/info` 测试商户 | **SUCCESS** —— **「大概率回商户不存在」的预期被证伪**,拿到完整字段集 |
+| 4 | `/api/merchant/query` 同 | **SUCCESS**:`reviewed_status` 实测回 `"0"`,而同报文的 `reviewed_reason` 是「审核通过」 |
+| 5 | `/api/shop/list` 同 | **SUCCESS**,13 家门店。★ **门店电话的键是 `shop_tel`**(创建腿入参是 `shop_phone`)——**同一次真机往返内坐实** |
+| 6 | `/api/shop/create` | ★ **SUCCESS**:用**参数表拼写** `manager_name` / `manager_phone` 真发成功并返回 `shop_no` ⇒ **R-2 定案**(§6.2 (e) 第 1 行) |
+| 7 | `/api/info/upload/img` | 不带 `img_name` ⇒ **HTTP 400**(文档的「必填」实测为真);带上 ⇒ **SUCCESS** + `img_url` |
+
+#### 6.6.1 由此更正 §6.2 (e) 的两条(原判据写错了)
+
+1. **`area_code` 不是「非纯数字」。** 原写「真实形态不是纯数字 … 会炸」——那是把**文档返回示例的形状**
+   当成了**服务的行为**。实测 **Integer 483/483、非数字 0 条**。仍按 `String` 承载,但理由换成实测撑得住的
+   (① 要原样回传给 String 参数 `parent_code`;② `String.valueOf` 对 Integer 无损)。**不是**因为「它可能是 `100A`」。
+2. **`manager_name` 不构成缺陷。** 上游认的就是**参数表**那版;请求示例里的 `maneger_*` 是文档自己打错的,
+   与我们的实现无关。原「签名相关、可能被判参数不合法」的担心**已实测排除**。
+
+#### 6.6.2 文档**缺漏**(是缺漏,不是矛盾)
+
+1. **每级各多一个未记录字段**:level1 `province_code`、level2 `city_code`、level3 `county_code`(GB 行政码,**String**)。
+   ★ **三级联动的 `parent_code` 不是它们** —— 填 `"110000"` 回 0 条,填上级的 `area_code`(`"1"`)才回数据。
+   字段**存在性**已由一次 483 条的**穷举普查**覆盖(level1 34 / level2 352 / level3 97,每条都带该级 GB 码)。
+2. **`parent_code` 传错是静默空列表而不是报错**(见上表 #2b)。
+
+#### 6.6.3 三-A 用不到、三-B 要用的缺口(**本次仍然不改代码**)
+
+`LsyAreaItemDTO` **丢掉了**这三个 GB 码(DTO 只有 `areaName` / `areaCode`)。
+`/api/shop/create` 只收自由文本 `shop_address`,所以**三-A 不需要它们**;
+但三-B 的商户进件要 `address_province` / `address_city` / `address_county` ⇒ **届时给 DTO 加这三个字段**
+(加法、可逆,所以不在三-A 做)。
+
+#### 6.6.4 ★ 一条安全读数(影响实现,不只是备注)
+
+`/api/merchant/info` 的响应含**身份证号 / 法人证件号 / 真实姓名 / 手机号 / 银行卡照片 URL / 通道费率**。
+⇒ 代码类注释里那条**租户缺口(R-4)警告是真实的高危暴露面**,不是预防性免责声明:
+三-B 的端点若不先证明 `mchNo` 属于当前租户,就是一次 IDOR,泄漏的是**真人 PII**。
+**本文件不记录这些值**(只记字段名与性质)。
+
+#### 6.6.5 未测项(**留空,不填推断**)
+
+- `reviewed_status` 的**完整值域** —— 只有 1 个测试商户、1 个读数(**"0" ⇒ 审核通过** 这一条是单点,
+  不足以定整个值域,§6.2 (b) 的三套值域冲突**仍未解**)。
+- **生产环境**:本节全部读数取自**测试**环境,不得外推到生产。
+- §6.3 的 1–7 各项**一个都没被本次读数消解**(本次没碰创建商户那条腿)。
+
 ---
 
 ## 7. 全部 DDL(一条)