答复 NL_develop 第二轮:批准整改,纠正 mypy 归因,裁定 docs/00 不动
产出 docs/NL_develop评审回复-架构师答复.md(可直接转给对方)。三条核实结论: 1. docs/21 在我这边本来就重号(21-JWT密钥管理与轮换.md + 21-风控业务第二版迁移 清单.md),tools/check_authoritative_docs.py 当前就是失败的 —— 我此前只跑 ruff/mypy/pytest,没跑到文档守卫。对方的让号顺手修好了这个既有故障, 故批准 docs/26 与 docs/27 两处让号。 2. 对方的 mypy 归因需要纠正。他说主因是缺 SQLAlchemy 2.0 类型信息;我这边 mypy 1.20.2 + SQLAlchemy 2.0.52 + 未装 sqlalchemy2-stubs → 0 错,而 pyproject.toml 约束是 sqlalchemy>=2.0,<3 / mypy>=1.14,<2。SQLAlchemy 2.0 自带 py.typed,sqlalchemy2-stubs 是给 1.4 用的,装了反而按 1.4 的 API 报错(他自己也 观察到"还会换一批新错")。真实根因是他的 .venv 没满足 pyproject 约束。 3. 他的迁移警告对我不适用。我这边 alembic current = heads = 20260911_risk_rule_index (单一 head,只有我自己的迁移),schema audit 报 51 business tables —— 即"表没建、 版本号也没跑",与他那边"版本号跑了、表没建"是两种不同的坏状态。合并后我必须补跑 alembic upgrade heads。 裁定: - 补发配置(customer_service:faq 加 query_customer_profile)归我做; - memory_sync_outbox / GraphProjectionWorker 消费端归我(生产者在他那边); - 两处让号批准; - docs/00 不动,另立文档登记场外/推广域 17 张表,并更新 docs/08 审计基线口径 (依据 AGENTS.md 规则 8:场外基金运营流程独立,不得写入场内交易表)。
This commit is contained in:
@@ -0,0 +1,212 @@
|
||||
# 架构师答复 · 第二轮(对《评审意见回复》的回应)
|
||||
|
||||
> **致**:`NL_develop` 维护者
|
||||
> **被回复**:`评审意见回复-NL_develop.md`、`接手文档-NL_develop-给架构师.md`
|
||||
> **基线核对时间**:2026-09-11 晚 **本机**:`qyqy_develop_1`
|
||||
> **结论**:**你的整改我全部认可,两处让号都批准,`docs/00` 我不动。另有一条技术纠正(mypy 根因)和一条
|
||||
> "你的合并会顺带修好我这边一个既有故障"。**
|
||||
|
||||
---
|
||||
|
||||
## 0. 先说一条我欠你的:`docs/21` 重号是我这边的既有故障
|
||||
|
||||
你说"`docs/21` 这个号已被你的《风控业务第二版迁移清单》占用,所以我让到 26"。
|
||||
|
||||
**我核实了,你说得对,而且比你说的更严重**——我这台 `docs/` 顶层**就是重号的**:
|
||||
|
||||
```
|
||||
$ python tools/check_authoritative_docs.py
|
||||
docs 顶层重号 {'21': ['21-JWT密钥管理与轮换.md', '21-风控业务第二版迁移清单.md']}
|
||||
```
|
||||
|
||||
**这个守卫脚本在我这里当前是失败的**,而我此前一直没跑到它(我只跑 ruff / mypy / pytest)。
|
||||
也就是说:`docs/21` 早在你让号之前就已经是两份了,**你的让号顺手修好了我一个既有故障**。
|
||||
|
||||
⇒ **`docs/26` 让号:批准。** 理由不只是"你的文件引用少",而是**这是唯一能让我这边守卫变绿的方案**。
|
||||
|
||||
同理 **`docs/27`(NL2SQL,原 15)批准** —— 手册被 `docs/16`/`docs/17`/`AGENTS.md` 三处引用,
|
||||
依据充分。
|
||||
|
||||
---
|
||||
|
||||
## 1. 技术纠正:mypy 184 错的根因不是"缺 SQLAlchemy 2.0 类型信息"
|
||||
|
||||
你查到"装 `sqlalchemy2-stubs` 后 181→43、卸载回 184",并据此判断主因是缺类型信息。
|
||||
**方向对了一半,但结论反了。** 我这边实测:
|
||||
|
||||
```
|
||||
mypy 1.20.2 (compiled: yes)
|
||||
sqlalchemy 2.0.52
|
||||
$ pip show sqlalchemy2-stubs → WARNING: Package(s) not found
|
||||
|
||||
$ python -m mypy app
|
||||
Success: no issues found in 138 source files ← 0 错
|
||||
```
|
||||
|
||||
**`sqlalchemy2-stubs` 没有装,SQLAlchemy 也是 2.0.52,而我这边 0 错。**
|
||||
|
||||
关键在于 `pyproject.toml` 的约束:
|
||||
|
||||
```toml
|
||||
"sqlalchemy>=2.0,<3",
|
||||
"mypy>=1.14,<2",
|
||||
[tool.mypy]
|
||||
python_version = "3.13"
|
||||
strict = true
|
||||
```
|
||||
|
||||
**SQLAlchemy 2.0 自带 `py.typed`**,`Mapped[T]` / `mapped_column` / `DeclarativeBase` 的类型信息
|
||||
是随包发布的,**根本不需要 `sqlalchemy2-stubs`** —— 那个包是给 **SQLAlchemy 1.4** 用的。
|
||||
你自己也观察到了装上之后"还会换一批新错(`mapped_column`/`DeclarativeBase` 不存在)",
|
||||
那正是它在按 1.4 的 API 描述去核对 2.0 代码。
|
||||
|
||||
**所以那 184 错的真实来源是:你的 `.venv` 没满足 `pyproject.toml` 的约束**,最可能是
|
||||
SQLAlchemy 版本低于 2.0(也可能是 mypy 低于 1.14)。请这样确认:
|
||||
|
||||
```powershell
|
||||
.\.venv\Scripts\python.exe -c "import sqlalchemy; print(sqlalchemy.__version__)"
|
||||
.\.venv\Scripts\python.exe -m mypy --version
|
||||
```
|
||||
|
||||
若 SQLAlchemy < 2.0,按 `pyproject.toml` / `requirements.txt` 重建环境(`pip install -e .`),
|
||||
**mypy 应该会落到和我一样的量级**。
|
||||
|
||||
**两个具体请求**:
|
||||
|
||||
1. **不要把 `sqlalchemy2-stubs` 写进依赖**——它会让 2.0 代码按 1.4 的类型定义报错;
|
||||
2. 你 §1.4 说"不再拿这个数字当结论"是对的,但**别把它归因成"代码质量无从判断"**:
|
||||
环境对齐后这个数字是可信的,我这边就是 0 错。你文件里那 8 个真实错误的修法我看了,改得对
|
||||
(`Mapping`→`dict` 那条尤其对,回表要就地补写 `score`/`intent`,`Mapping` 是只读协议)。
|
||||
|
||||
> 顺带:`model_gateway.py` 那 2 个和 `runtime.py` 那 1 个你选择"不用 `cast` 掩盖"——
|
||||
> 我同意先放着。环境修好后大概率自己就消失了。
|
||||
|
||||
---
|
||||
|
||||
## 2. 三件待裁决的事
|
||||
|
||||
### 2.1 画像工具白名单补发 —— ✅ 归我,我出脚本
|
||||
|
||||
接受你的理由(`config_release` 是环境数据、不随代码合并,谁的环境谁发布)。合并进来后我发一版:
|
||||
|
||||
```
|
||||
customer_service:faq = [search_knowledge, query_customer_profile] ← 唯一变化
|
||||
其余 8 条原样继承
|
||||
```
|
||||
|
||||
你提醒的"**同 key 的继承项必须被本次新定义覆盖**"这个坑我记下了——这正是
|
||||
`config_release` 整版本替换语义最容易踩的地方,谢谢。脚本我会按"同 key 覆盖 + 打印被替换的旧值"写。
|
||||
|
||||
### 2.2 `memory_sync_outbox` / `GraphProjectionWorker` 归属 —— ✅ 归我
|
||||
|
||||
**判定依据**(不是分工方便,是证据):
|
||||
|
||||
| | 我这台 | 你这台 |
|
||||
|---|---|---|
|
||||
| `MemorySyncOutbox` 生产者 | **无** | 有(`profile_repository.py:142`) |
|
||||
| 表内数据 | 空 | 2 行待处理 |
|
||||
| 消费端 | 无 | 有定义、**无实例化点** |
|
||||
| `GraphProjectionWorker` | 无实例化点 | 无实例化点 |
|
||||
|
||||
**生产者在你那边,我这边什么都没有** ⇒ 缺的是**消费端**,而消费端属 `app/worker/` 装配层,
|
||||
正是我这条线在维护(我改过 `runtime.py` 的 `knowledge_writer` 那类注入点)。
|
||||
|
||||
**所以**:生产者保留你的,**消费端(`GraphProjectionWorker` 实例化 + 与 `runtime.py` 装配 +
|
||||
`ProjectionReconciliationService` 接上)归我**。你 §6.2 说"我先不动手"是对的,**继续保持不动**。
|
||||
|
||||
**时机**:合并之后我再接。现在两边都别动,避免各接一根线。
|
||||
|
||||
### 2.3 两处让号 —— ✅ 都批准
|
||||
|
||||
见 §0(`docs/26`)与 §0 末(`docs/27`)。
|
||||
|
||||
---
|
||||
|
||||
## 3. `docs/00` 基线要不要补那 17 张表 —— ❌ 不改 `docs/00`,另立文档
|
||||
|
||||
我的裁决是**不改进 `docs/00`**,依据是基线规则本身:
|
||||
|
||||
> `AGENTS.md` 规则 8:**当前系统业务功能只针对场内基金模拟交易;场外基金运营流程独立,
|
||||
> 不得写入场内交易表。**
|
||||
|
||||
同事带来的 17 张表是:
|
||||
|
||||
```
|
||||
offsite_* (10) 场外基金运营
|
||||
promotion_* ( 7) 推广域
|
||||
```
|
||||
|
||||
**这两个域都不是场内交易域**,而 `docs/00-新数据库基线设计.md` 冻结的正是场内交易域的基线。
|
||||
把独立域塞进去,等于让"基线"这个概念失效——下次有人拿 `docs/00` 当"当前全部表"的依据时就错了。
|
||||
|
||||
**但必须有人管**,否则 68 张表与文档的 51 张永远对不上、审计每次都报差异。所以:
|
||||
|
||||
1. **`docs/00` 不动**(不可变基线,规则 1);
|
||||
2. **新增一份独立登记**:建议 `docs/28-场外与推广域数据表登记.md`,逐表登记表名、归属域、
|
||||
哪个迁移建的、是否被场内代码引用;
|
||||
3. **同步 `docs/08-数据库结构审计基线.md`** 的表数口径:不是简单改成 68,而是写成
|
||||
「场内 51 + 场外/推广 17 = 68」,并说明分类依据;
|
||||
4. **`tools/audit_schema.py` 的期望值**:你这台已经报 `68 business tables, no missing or
|
||||
unexpected tables`,说明期望值已经跟着改了——**请在 PR 描述里点明这一点**,否则我合并后
|
||||
看到表数从 51 跳到 68,会以为是有人偷偷建了表。
|
||||
|
||||
**这一条我采纳你的做法,但补一个归属文档**——你的"只提示、不动手"是对的,因为它是不可变基线。
|
||||
|
||||
---
|
||||
|
||||
## 4. 你那 3 个 failed:判断都对,但请注意它们不在我这条分支上
|
||||
|
||||
| 用例 | 你的判断 | 我的意见 |
|
||||
|---|---|---|
|
||||
| `test_fund_readonly_contract` | 既有空集缺陷,双方都不修 | ✅ 同意 |
|
||||
| `test_offsite_document_recognition_adapter` ×2 | 环境相关(httpx 序列化成 `\uXXXX`) | ✅ 判断对,你建议的"断言 `json.loads(body)` 后的字段值"也对——字节级断言本来就不该用来测 JSON |
|
||||
|
||||
**但要提醒一句**:这 2 个用例**在我这条分支上根本不存在**(同事那条线没进来,我这边
|
||||
`pytest tests/unit tests/contract` 是 703 passed)。所以它们不是"双方共有的既有失败",
|
||||
而是**你这条线合并进来之后才会出现**的。合并后我会把它们当**新引入的失败**对待并跟踪,
|
||||
不会当成"历史遗留"放过。
|
||||
|
||||
---
|
||||
|
||||
## 5. 同步给你:我这边的实测状态(供你判断合并冲突面)
|
||||
|
||||
```
|
||||
分支:qyqy_develop_1,已含 origin/qyqy_develop 的 d2cdbba,并多 1 个提交(未推送)
|
||||
|
||||
alembic:current = heads = 20260911_risk_rule_index ← 单一 head,只有我自己的迁移
|
||||
(不是你说的 20260911_merge_risk_heads —— 同事那 11 个迁移不在我这条线上)
|
||||
表数: schema audit passed: 51 business tables ← 与你那边的 68 张对不上,见 §3
|
||||
门禁: ruff 干净 / mypy app 138 文件 0 错 / 703 unit+contract / 33 integration
|
||||
文档守卫:❌ 失败(docs/21 重号,见 §0)
|
||||
```
|
||||
|
||||
**⇒ 合并后我必须补做 4 件事**(记在这里,免得忘):
|
||||
|
||||
1. `alembic upgrade heads` —— 补跑你那 11 个迁移、建 17 张表。我这边是"表没建、版本号也没跑",
|
||||
和你那边"版本号跑了、表没建"是**两种不同的坏状态**,但都靠这一条收敛;
|
||||
2. 补发配置(§2.1);
|
||||
3. 接 `memory_sync_outbox` 消费端(§2.2);
|
||||
4. 补场外/推广域的表登记 + 更新审计基线口径(§3)。
|
||||
|
||||
---
|
||||
|
||||
## 6. 对你 §7 两条建议的答复
|
||||
|
||||
1. **文档体系两套口径** —— 认可你的做法(保留 `docs/04`/`06`/`10`/`13`/`99`,但列入 D 类"不要用来
|
||||
判断当前进度")。理由:你保留了文件(成本为零),又明确标注了它们的可信度(避免误用),
|
||||
这比"删掉"和"无差别都读"都好。**不需要改写索引。**
|
||||
2. **`docs/00` 补表** —— 见 §3,不动 `docs/00`。
|
||||
|
||||
---
|
||||
|
||||
## 7. 一句话总结
|
||||
|
||||
你这轮的整改**没有一条我不同意**(除了 mypy 那条归因,而且那是环境问题不是代码问题)。
|
||||
剩下的事按这个顺序就能收敛:
|
||||
|
||||
**你**:环境重建(§1)→ 推送 `NL_develop`;
|
||||
**我**:跑迁移 → 补发配置 → 接 outbox 消费端 → 补表登记 → 跑全量门禁 → 推送。
|
||||
|
||||
> 最后回应你 §1.3 那句"既然两台 schema 确实不同,必须以运行时探测为准、禁止任何形式的字段名
|
||||
> 硬编码"——**完全同意**,而且你用"双 schema 参数化测试"把它锁住这招很漂亮:任何回退到硬编码
|
||||
> 都会让其中一侧立刻变红。比我原来担心的"靠人记住"可靠得多。
|
||||
Reference in New Issue
Block a user