# 统一修复第二轮定向复核

规格：FAIL（R4 尚未完整闭环）。质量：FAIL（仍有 1 个 P2 重要问题）。R1、R2、R3、R5 已解决；本结论不等于全项目编译或业务环境验收。

本轮以冻结 code/、verification/fix-wave.patch 及实际 green-segment / green-eligibility / green-sql 日志为对象，定向检查首轮 5 项和修复引入的接线变化；未重复运行同代码探针、未执行 Maven、未改业务代码。

| 首轮项 | 结论 | 具体核验 |
|---|---|---|
| R1 题锁删除字段 | ADDRESSED | QuestionSegmentDao.lockQuestion 已改 del_flag=0；真实 QuestionDo/AbstractDo 定义与之相符。SQLite 日志覆盖活行/删行筛选，不证明 MySQL 锁语义。 |
| R2 派生失败回滚同步 | ADDRESSED | hookSave 只发布事件；AFTER_COMMIT/fallback 监听器调用 REQUIRES_NEW invalidateAfterSave，独立 catch 后仍发送计算消息。主事务不再调用派生表写入。 |
| R3 FAILED 随计算回滚 | ADDRESSED | 公开入口移除原事务并经 QuestionSegmentTxService.compute 独立事务；SegmentAttemptFailure 在代理完成回滚后捕获，再经独立 recordFailure 重锁登记；revision/可得正文指纹核对保留。真实 Spring 拦截器+内存事务探针的事件为 begin,lock,rollback,begin,lock,failed,commit，证明原事务收口顺序；未证明真实数据库并发。 |
| R4 正式题旧序列窗口 | NOT_ADDRESSED（部分修复） | DB当前正文与分词的指纹/内容位集合门已存在，但没有绑定本次RPC实际返回的题JSON版本，具体证据如下。 |
| R5 同秒人工草稿自动发布 | ADDRESSED | 新 auto_publish_eligible 列历史默认0，saveWordForAutoFill 新建置1，人工saveWord无条件清0，publishAutoFillWord实体锁内重读等于1才发；PROD副本置0。原source不变。原guard方法探针覆盖同秒1允许、0/null拒绝。两库DDL先行是明确前置。 |

## 剩余 R4 [P2] 指纹核验的是数据库正文，而不是本次已装配的响应正文

新增代码位置：`code/application/src/main/java/cn/jojo/cc/biz/service/english/segment/EnglishSegmentDeliveryBizService.java:112-139`；`code/application/src/main/java/cn/jojo/cc/biz/service/english/segment/DefaultSegmentTextExtractor.java:150-178`。

既有实际RPC路径（以下路径相对固定基线 source/）：

- `api/api-server/src/main/java/cn/jojo/cc/server/service/impl/QuestionServiceImpl.java:1264-1269`：先 listGenStandQuestion 装配题本体，再 fillSegments。
- 同文件 :1775-1779：getQuestionGameJson 得到内容配置；:1838 将 `contentConfigBo.getOutDataJson()` 作为 `questionInfo.data` 返回。
- 同文件 :1687：getQuestionGameJson 调 `formAndDataCacheService.load(questionContentKey, redisEnabled)`。真实 FormAndDataCacheService.loadRedis 读取 Redis 的 FormAndDataCacheBo，保存的是输出 JSON 地址。
- 同文件 :1721：fillSegments 只传 keys 给 resolveByQuestionKeys，未传已选择的 contentConfigBo/输出JSON身份或其内容版本。
- 新 resolveByQuestionKeys 独立查询当前 QuestionDo，并经 SegmentContentSnapshotService -> DefaultSegmentTextExtractor 从数据库 FormData/OutputData 取正文；只证明“当前DB正文 == 分词指纹”，没有证明“返回给客户端的正文 == 分词指纹”。

可达时序无需假设缓存异常：请求 A 已装配 v1 JSON 地址后暂停；另一个请求完成发布 v2 及分词重算；A 恢复执行 fillSegments，新门读取 v2 DB正文和 v2 NORMAL，比较通过，最后仍把 v2序列与 A 早已装配好的 v1题JSON一起返回。若 Redis 内容JSON缓存仍为v1，则同类问题还有较长窗口。这里不声称已在业务环境复现，也不以未知 TTL 大小作为证据。

反证与已修部分：当前数据库正文为v2、分词仍为v1时，新门确实返回null；green-segment日志中的同审计时间正文变化和查询失败用例能够证明这一局部行为。请求始终取得同一正文版本时没有该问题。新增 readOnly事务只覆盖 resolveByQuestionKeys，未覆盖此前的 listGenStandQuestion，也不能让已取出的Redis缓存变成同一快照。

建议：将本次选定的输出JSON身份/可靠正文版本或同源指纹随装配过程传给分词解析门，再与分词的正文身份比较；无法证明一致时只省略segments。避免在响应路径抓取远端JSON。补一个确定性用例：先装配v1输出身份，再更新DB/分词到v2后执行装配，必须返回segments=null。仅扩大读取事务不能解决缓存与响应已选版本问题。

## 修复波其他检查与限制

- 共享抽取及flatten指纹确实由计算和下发复用；内容位集合另行核验，未发现该拆分本身的悬空方法。
- DefaultSegmentTextExtractor 删除预先剥离IPA字符的步骤，使后续EnglishWords能识别完整IPA token；避免“先删音标字符、后把ASCII残片当词”与前轮条件策略相冲突。
- 新BatchPublishResultBo.pendingCacheCount及Job日志把PROD已提交、缓存待outbox投递区分开；成功计数含已提交项，不宣称消费完成。HTTP Boolean契约未改。
- DDL/Job依赖、MySQL真实锁、双数据源事务、Broker及消费、旧Mockito套件执行仍属未验证边界；55文件AST仅语法，隔离探针不是全应用集成证明。
- 未创建CodeGraph索引，无需清理。
