feat: V1.1 - 文件结构修改

This commit is contained in:
2026-08-03 13:42:31 +08:00
parent 875c0f3ccb
commit a41176c6ce
6 changed files with 3 additions and 3 deletions

View File

@@ -0,0 +1,268 @@
# V1.1 新增写入点展示 — 整改方案
> 版本v1.1
> 日期2026-08-03
> 状态:**已落地**
> 关联:`docs/配置说明.md` §5、`docs/V1.1-非结构写入收敛整改方案.md`(建点收敛已落地,本方案只改**展示层**
> 约束:**不新增配置项**;文案与染色为工具默认行为
---
## 1. 背景与实跑样本
### 1.1 企微原文(问题形态)
```text
- Topic --> duty_im_notice_topic
通道: RocketMQ
位置: DutyDelayQueueImNoticeService#addMq:43
类型: DutyImNotice
value 新增为: “{"objectId":"","content":"","type":0,"pushStatus":0,"tenantId":"","deleteMark":0,"executeTime":0}”
```
含义:对比区间内**首次出现**该 Topic 的投递点(`WRITE_POINT_ADDED`),消息体类型与骨架已正确解析,但展示层体验差。
### 1.2 问题拆解
| # | 现象 | 影响 |
|---|------|------|
| P1 | 文案「value 新增为」语义含糊 | 看不出是「新增投递 / 首次写入」,与字段级「变更为」易混淆 |
| P2 | 新骨架整段无绿色 | 字段级变更有 `<font color="info">`,新增整 Key/Topic 时全是明文,**结构颜色未渲染** |
| P3 | 缺少「变更类型」明示 | 读者需自行推断这是新增写入点还是结构变更 |
| P4 | (次要)删除写入点文案不对称 | 「value 原结构 + 已删除投递/写入」可读,但可与新增侧统一语气 |
Redis 侧新增 Key 走同一 `ReportBuilder#renderKeyBlock` 分支(`oldJson` 空、`newJson` 非空),**同病**,须一并改。
### 1.3 非目标
- 不改检测 / Schema 提取 / Diff 逻辑(建点收敛见另一文档)
- 不新增 YAML 开关
- 不改字段级变更的片段染色规则(`FIELD_ADDED` 等仍按路径染色)
- 不改企微 4096 拆条策略
---
## 2. 根因分析
### 2.1 调用链
```text
SchemaCheckAnalyzer
→ WRITE_POINT_ADDED + newSkeletonJson完整骨架
→ KeyStructureChange.fieldDetails = [WRITE_POINT_ADDED] // 无 fieldPath
ReportBuilder.renderKeyBlock
→ pathsForNewSkeleton(details) // 只认 FIELD_ADDED / FIELD_PATH_MOVED
→ newGreen = ∅
→ annotateNewForWecom(json, ∅) → 原样明文
→ 文案固定「value 新增为:**
```
### 2.2 根因表
| 编号 | 根因 | 证据 |
|------|------|------|
| R1 | 新增写入只有 `ChangeType.WRITE_POINT_ADDED`**无字段路径** | `SchemaCheckAnalyzer` 建点逻辑 |
| R2 | `SkeletonAnnotator.pathsForNewSkeleton` **不处理** `WRITE_POINT_ADDED` | 仅 `FIELD_ADDED` / `FIELD_PATH_MOVED` |
| R3 | `ReportBuilder` 对「旧空新有」写死 `value 新增为`,且 **不区分 Redis / MQ** | `renderKeyBlock` 约 146~147 行 |
| R4 | 无「变更类型」行;控制台 `formatDetailMessage` 对 MQ 新增前缀也不完整 | `ReportBuilder` |
结论:**数据已够用(有完整 newJson + 通道 + 类型),缺口全在报告渲染。**
---
## 3. 整改目标
1. 一眼可读:这是 **新增投递 / 首次写入该 Key**,不是字段 diff。
2. 新骨架在企微中有 **绿色** 渲染(整段 `info`)。
3. Redis / MQ 文案区分清楚与字段级「value值由 / 变更为」不打架。
4. 删除写入点展示与新增侧语气对称;控制台明细前缀对齐。
5. 既有字段级染色回归不变。
---
## 4. 方案设计
### 4.1 判定:何时走「整段新增/删除」展示
`renderKeyBlock` 中:
```text
仅当 fieldDetails 全部为 WRITE_POINT_ADDED或全部为 WRITE_POINT_REMOVED
且不混有 FIELD_* / WRAPPER_* / TYPE_CHANGED 时
→ 走「整段染色 + 专用文案」
否则
→ 保持现有片段染色逻辑
```
实跑新增 Topic/Key 场景几乎总是「仅一条 WRITE_POINT_ADDED」命中上述分支。
### 4.2 文案(定稿)
| 场景 | 通道 | 现行 | 整改后 |
|------|------|------|--------|
| 旧空、新有 | Redis | `value 新增为:` | `首次写入该 Keyvalue 结构为:` |
| 旧空、新有 | MQ | 同上 | `新增投递,消息体结构为:` |
| 旧有、新空 | Redis | `value 原结构:…(已删除写入)` | `原 value 结构:…(写入点已删除)` |
| 旧有、新空 | MQ | `…(已删除投递)` | `原消息体结构:…(投递点已删除)` |
| 旧有、新有 | 共用 | `value值由` / `变更为` | **保持不变** |
建议在 meta位置/类型)之后、骨架之前增加一行:
```markdown
> **变更类型**: 新增写入点
```
删除侧:
```markdown
> **变更类型**: 删除写入点
```
字段级变更块不加此行(避免噪音);仅整段新增/删除分支输出。
### 4.3 颜色渲染(定稿)
| 场景 | 染色 |
|------|------|
| 整段新增(仅 WRITE_POINT_ADDED | 新骨架整段包裹 `<font color="info">…</font>`,再外包中文引号 `“…”` |
| 整段删除(仅 WRITE_POINT_REMOVED | 旧骨架整段包裹 `<font color="warning">…</font>` |
| 字段级变更 | **不改**:仍按路径片段染色 |
实现建议(择一,推荐 A
**A. ReportBuilder 内直接整段 wrap简单**
```text
if (isWritePointAddedOnly(details) && !newJson.isEmpty()) {
newRendered = "“" + fontInfo(newJson) + "”";
}
```
**B. SkeletonAnnotator 增加 `wrapAll(json, color)`**
供 ReportBuilder 调用,便于单测与复用。
本方案采用 **A + 可选抽出 wrap 小方法到 SkeletonAnnotator**,避免改路径匹配算法。
企微颜色约定与现网一致:
| 语义 | color |
|------|-------|
| 新增 / 新结构 | `info`(绿) |
| 删除 / 旧结构 | `warning`(橙) |
### 4.4 目标企微形态(对照样本)
整改后期望接近:
```markdown
- Topic --> `duty_im_notice_topic`
> **通道**: RocketMQ
> **位置**: DutyDelayQueueImNoticeService#addMq:43
> **类型**: DutyImNotice
> **变更类型**: 新增写入点
> **新增投递,消息体结构为:** “<font color="info">{"objectId":"","content":"","type":0,"pushStatus":0,"tenantId":"","deleteMark":0,"executeTime":0}</font>”
```
Redis 新增 Key 对称示例:
```markdown
- Key --> `saas:period-config:migration:current`
> **位置**: …
> **类型**: MigrationCurrentVo
> **变更类型**: 新增写入点
> **首次写入该 Keyvalue 结构为:** “<font color="info">{…}</font>”
```
### 4.5 未解析 Key / Topic顺带小优化可选同批
`keyUnresolved`
- 提示由 `key 无法解析)` / `destination 未解析)`
调整为:`key 无法解析,建议 manual_mappings 补充)`MQ 用 destination 措辞)
- 表达式长度 > 80 时截断为前 77 字符 + `...`(常量即可,**不进配置**
本项为体验增强,可与主改动同 PR若排期紧可二期。
### 4.6 控制台明细
`formatDetailMessage``knownPrefixes` 补齐:
```text
"新增 MQ 投递点,"
"删除 MQ 投递点,原 value 类型: "
```
与 analyzer 现有 `setMessage` 对齐,避免 CI 明细不加粗。
---
## 5. 涉及文件
| 文件 | 变更 |
|------|------|
| `report/ReportBuilder.java` | 文案分支、变更类型行、整段染色、可选未解析截断、prefixes |
| `report/SkeletonAnnotator.java` | 可选:`wrapEntire(json, color)` |
| `report/ReportBuilderTest.java` | 新增 WRITE_POINT_ADDEDMQ/Redis展示断言字段级染色回归 |
| `docs/配置说明.md` §5 | 同步新增/删除写入点文案与整段绿色约定 |
**不改**`SchemaCheckAnalyzer` 建点逻辑、`SkeletonJsonRenderer`、检测器。
---
## 6. 测试计划
| 用例 | 输入要点 | 期望 |
|------|----------|------|
| T1 | 仅 `WRITE_POINT_ADDED` + MQ + 非空 newJson对齐 duty_im 样本) | 含「新增投递,消息体结构为」;含「变更类型: 新增写入点」newJson 外包 `<font color="info">` |
| T2 | 仅 `WRITE_POINT_ADDED` + Redis | 含「首次写入该 Keyvalue 结构为」;整段绿 |
| T3 | 仅 `WRITE_POINT_REMOVED` + 非空 oldJson | 「原…结构」+ 整段 `<font color="warning">` |
| T4 | `FIELD_ADDED` 字段级变更(既有) | 仍为片段绿,**不**整段包 info文案仍为 value值由/变更为 |
| T5 | `toConsole` 含「新增 MQ 投递点」 | 明细前缀加粗正确 |
| T6 | (若做)超长未解析 key 表达式 | 截断 + mapping 提示 |
可用最小化 `CheckReport` / `KeyStructureChange` 构造,不必起 git骨架 JSON 可用样本中的 DutyImNotice 字段串。
---
## 7. 风险与回滚
| 风险 | 缓解 |
|------|------|
| 整段绿色在超长 JSON 下刺眼 | 骨架已有 maxLen新增点通常可接受 |
| 企微对超长 font 标签不友好 | 保持 4096 拆条;单块过长仍按 key 拆 |
| 文案变更影响阅读习惯 | 发布说明给对照表§4.2 |
| 误把字段级变更整段染色 | 严格 `isWritePointAddedOnly` 判定 |
回滚:还原 `ReportBuilder` / `SkeletonAnnotator` 展示逻辑即可,与检测无关。
---
## 8. 与「非结构收敛」的边界
| | 非结构写入收敛(已落地) | 本方案(展示) |
|--|-------------------------|----------------|
| 解决什么 | 不该建的点String/`{}`)不再告警 | **该告的点**怎么读得懂、看得见绿 |
| 样本 | RecordingTodo 原文缓存 | DutyImNotice 新增投递(类型/骨架正确) |
| 落点 | Detector + Analyzer | ReportBuilder+ Annotator |
两者互补:收敛后留下的新增写入点,更需要本方案的文案与整段染色。
---
## 9. 实施检查表
- [x] 认可 §4.2 文案与 §4.3 整段染色(不新增配置)
- [x] 同批做未解析 Key 截断§4.5
- [x] 代码落地 + T1~T6
- [x] 同步 `docs/配置说明.md` §5
- [x] 全量 `mvn test`
---
## 10. 文案对照速查
| 场景 | V1.0 | V1.1(本方案) |
|------|------|----------------|
| MQ 新增投递 | `value 新增为:“{…}”`(无色) | `变更类型: 新增写入点` + `新增投递,消息体结构为:“<font color="info">{…}</font>”` |
| Redis 新增写入 | 同上 | `首次写入该 Keyvalue 结构为:“<font color="info">{…}</font>”` |
| 字段级变更 | `value值由` / `变更为` + 片段染色 | 保持 |

View File

@@ -0,0 +1,287 @@
# V1.1 非结构写入收敛 — 整改方案
> 版本v1.1
> 日期2026-08-03
> 状态:**已落地**
> 依据:业务仓实跑误报 + 单测复现 `RecordingTodoReplaceScenarioTest`
> 关联:`docs/redis序列化结构检测实施方案.md`、`docs/配置说明.md`
> 约束:**不新增配置项**;收敛为工具默认行为,随 jar 升级生效
---
## 1. 背景与已证实问题
### 1.1 实跑样本jnpf-java-cloud
```text
【序列化结构变更】 jnpf-java-cloud
分支: patrol/dev_patrol_2.0
提交: adbb15c6 → 6ddc72dc
- Key --> recording:todo:replace::
位置: RecordingTodoDomainServiceImpl#replaceText:437
类型: <未解析>
value 新增为: “{}”
- Key --> recording:todo:replace:title::
位置: RecordingTodoDomainServiceImpl#replaceText:452
类型: <未解析>
value 新增为: “{}”
```
业务代码(摘要):
```java
// 缓存替换前原文,纯 String无 JSON / VO 序列化
stringRedisTemplate.opsForValue().set(contentRedisKey, originalContent);
stringRedisTemplate.opsForValue().set(titleRedisKey, originalTitle);
```
### 1.2 复现结论
单测 `RecordingTodoReplaceScenarioTest` 已稳定复现:
| 环节 | 现状 |
|------|------|
| 检测 | 命中 **W04**(直写对象) |
| 类型 | `String` 不在本仓 `SourceIndex``resolvedValueType = null` → 展示 `<未解析>` |
| Schema | `extract(null)` 空结构 → 骨架 `"{}"` |
| 报告 | 旧提交无该写入点 → `WRITE_POINT_ADDED` →「value 新增为」 |
**核心缺口**:新增写入点(`WRITE_POINT_ADDED`)路径上,**没有对「无结构 / 不可展开」的 value 做收敛**;只要 AST 命中 W04/W05及部分边缘写法就会告警。
### 1.3 本方案范围
| 纳入 | 不纳入(另案) |
|------|----------------|
| Redis 非结构写入误报收敛 | MQ `asyncSend` 漏检(作用域过窄) |
| 同类场景枚举与统一收敛规则 | 新增 Key 企微文案 / 整段染色体验 |
| 检测层 + 分析层双闸门设计 | 业务仓 ignore 配置补丁(仅作临时手段) |
---
## 2. 问题本质
工具目标是监控 **可 JSON/对象 Schema 化的序列化结构变更**
当前实现把「能调用 `set/insert/put`」近似当成「值得监控」,收敛只做了很窄的一层:
```text
现有收敛
├─ 方法黑名单setIfAbsent / increment / delete …
├─ isTrivialValue字面量、UUID、toString/valueOf/randomUUID
└─ ignore.key_patternslock / loginCount / Authorization …
缺失收敛
├─ value 声明类型为 String / 数字 / 布尔 / byte[](变量,非字面量)
├─ value 类型无法在本仓展开null / 空 Schema
├─ 先 JSON 序列化到局部 String 再写入Redis 未 unwrap误成 String-W04
└─ WRITE_POINT_ADDED 不检查「是否有可对比结构」
```
因此:**凡是「新增一段无结构写入」都会变成「类型未解析 + value 新增为 {}」**,与 RecordingTodo 同形。
---
## 3. 同类场景排查(是否会中招)
判定标准能否在默认配置W01~W05 全开)下产出与实跑同形的告警
`WRITE_POINT_ADDED` + `<未解析>` 或空/`{}` 骨架,或明显无业务结构)。
### 3.1 高概率同形(与实跑同类,应优先收敛)
| ID | 场景 | 典型代码 | 命中模式 | 结果形态 | 与实跑关系 |
|----|------|----------|----------|----------|------------|
| S1 | **String 变量直写** | `stringRedisTemplate.opsForValue().set(k, originalContent)` | W04 | `<未解析>` + `{}` | **已证实** |
| S2 | **String 方法返回值直写** | `set(k, todo.getContent())` | W04 | 同 S1返回类型 String 时) | 同形;`isTrivialValue` 不认 `getXxx` |
| S3 | **数字 / 布尔变量直写** | `set(k, ttlSeconds)` / `set(k, enabled)` | W04 | `<未解析>` + `{}` | 同形;标量无字段 |
| S4 | **Hash 写入 String/标量** | `opsForHash().put(k, f, nameStr)` | W05 | 同 S1 | W05 仅挡字面量,不挡变量 |
| S5 | **redisUtil.insert 写 String 变量** | `redisUtil.insert(k, token, ttl)` | W04 | 同 S1 | `insert` + 含 redis 的 scope → 走 W04 分支 |
| S6 | **外部依赖类型直写** | `set(k, externalDto)` 且 DTO 不在本仓 | W04 | `<未解析>` + `{}` | 同形;「有对象语义」但工具无法展开,告警无信息量 |
| S7 | **类型擦除 Object / 原始 Map** | `set(k, (Object) x)` / `set(k, map)` 且无法解析元素 | W04 | 常为 `<未解析>` / 空或弱 Schema | 同形或近似 |
### 3.2 中概率变形(会误报,但表现略不同)
| ID | 场景 | 典型代码 | 命中模式 | 结果形态 | 说明 |
|----|------|----------|----------|----------|------|
| S8 | **局部 JSON 字符串再写入** | `String json = JSON.toJSONString(vo); set(k, json)` | W04 | 类型按 String → `<未解析>`+`{}` | Redis **无** `unwrapJsonStringVar`MQ 已有);应归 W01~W03却被当成无结构 |
| S9 | **显式序列化标量** | `set(k, JSON.toJSONString(someString))` | W02 | 偶发低价值点 | 少见unwrap 后仍是 String应丢弃 |
| S10 | **本仓 VO 但字段全 transient / 全忽略注解** | `set(k, emptyVo)` | W04 | 类型有名,骨架仍可能 `{}` | 有类型名,但「新增 {}」仍噪音 |
| S11 | **删除无结构写入点** | 删掉 S1 类代码 | WRITE_POINT_REMOVED | 「原结构 {}」 | 对称噪音,收敛规则应一并覆盖 |
### 3.3 低概率 / 已部分防护(记录边界)
| ID | 场景 | 现状 | 结论 |
|----|------|------|------|
| S12 | 字面量 `"1"` / `UUID.toString()` | `isTrivialValue` 已忽略 | 一般不中招 |
| S13 | key 命中 `*:lock` / `loginCount:*` 等 | `ignore.key_patterns` | 仅覆盖命名约定,**挡不住** `recording:todo:replace:*` |
| S14 | MQ 直传 String / byte[] | `isBareStringOrBytesPayload` 已忽略 | Redis 应对齐MQ 数字变量仍可能漏(见下) |
| S15 | MQ 直传业务 VO | 正常监控 | 不属本问题 |
| S16 | Redis W01~W03 + 本仓 VO | 正常监控 | 不属本问题 |
### 3.4 MQ 侧对照(同类思想,程度不同)
| 点 | Redis现状 | MQ现状 |
|----|---------------|------------|
| 裸 String / byte[] 变量 | **不忽略** → S1 | **忽略** |
| 数字 / 布尔变量 | **不忽略** → S3 | **不忽略**(仅 trivial 字面量)→ **MQ 也有 S3 风险** |
| 局部 `toJSONString` 再 send/set | Redis **不 unwrap** → S8 | **已 unwrap** |
| 无法展开类型仍建点 | 会建点 → S6 | 会建点(直传对象时) |
| WRITE_POINT_ADDED 空 Schema 闸门 | **无** | **无**(同样会「新增 {}」) |
结论:
- Redis 是重灾区;
- MQ 在 String 上已收敛,但 **Integer/Long/Boolean 变量****空 Schema 仍告警** 与 Redis 同类,应在统一规则里一并处理。
### 3.5 分析层放大效应(与模式无关)
`SchemaCheckAnalyzer` 在「新写入点、旧无配对」时:
```text
fileChanged && oldWritePoint == null
→ 无条件 WRITE_POINT_ADDED
→ extract(type) 即使为空也 render → "{}"
→ 进入企微
```
对比「已有写入点」路径:`differ.diff` 空则 `continue`**不会**为空 Schema 刷屏。
因此同类问题在 **新增代码** 时被放大;存量无结构写入若从未变更,不一定出告警。整改必须同时考虑:
1. **检测层少建点**(源头)
2. **分析层对无结构新增/删除做闸门**(兜底)
---
## 4. 收敛规则设计
### 4.1 原则
1. **只监控有结构的序列化 value**(本仓可展开的业务类型,或 unwrap 后得到的业务类型)。
2. **宁漏勿滥**:无法证明「有结构」则不告警。
3. **检测层为主、分析层为辅**:避免脏 WritePoint 进入报告链路。
4. **Redis / MQ 规则对齐**,避免一边收敛一边漏。
### 4.2 规则表(建议落码)
| 规则 | 适用 | 行为 |
|------|------|------|
| R-A 标量类型忽略 | Redis W04/W05MQ 直传 payload | value/payload 声明类型 ∈ `{String, CharSequence, 原始/包装数字, Boolean, UUID, byte/Byte/byte[]}`**不建点** |
| R-B 本仓可展开 | Redis W04/W05MQ 直传对象 | `resolvedFqn == null``SourceIndex` 无此类 → **不建点** |
| R-C 空 Schema 闸门 | AnalyzerADDED / REMOVED | `extract``schema.isEmpty()`**不产生** WRITE_POINT_ADDED/REMOVED字段级 diff 路径保持:空 diff 已 skip |
| R-D 局部 JSON unwrap | Redis对齐 MQ | `String json = toJSONString(x); set/insert(k, json)` → 按 W01~W03 建点,类型为 `x`;若 `x` 仍为标量 → 再走 R-A |
| R-E 方法返回值 | 与 R-A/R-B 相同 | `set(k, obj.getFoo())` 按方法返回类型判定,不因「非字面量」直接放行 |
| R-F 集合 | W04/W05 / MQ | `List<T>` / `Set<T>`:元素 `T` 满足 R-B 才保留;`List<String>` 丢弃 |
### 4.3 明确保留(不应误杀)
| 写法 | 期望 |
|------|------|
| `set(k, JSON.toJSONString(vo))` / `insert` + 序列化 | W01~W03监控 VO |
| `redisTemplate.set(k, demoVo)` 且 DemoVo 在本仓 | W04监控 |
| `opsForHash().put(k, f, demoVo)` 本仓 VO | W05监控 |
| MQ `syncSend/asyncSend(topic, dto)` 本仓 DTO | 监控 |
| 局部 JSON unwrap 后得到本仓 VO | 监控 |
### 4.4 配置策略
**不新增任何 YAML / 检测开关。** R-A~R-F 均为工具内置默认行为,升级 jar 即生效。
业务侧若需临时止血,仍可沿用既有 `ignore.key_patterns` / `ignore.writer_methods` / `suppressions`,但不作为本问题根治手段。
---
## 5. 落地设计
### 5.1 推荐分层
```text
┌─────────────────────────────────────────┐
│ RedisWritePointDetector / MqWritePointDetector
│ R-A 标量忽略
│ R-D Redis 局部 JSON unwrap
│ R-B 本仓类型门槛(直写对象)
│ R-E/R-F 返回值与集合元素
└──────────────────┬──────────────────────┘
┌─────────────────────────────────────────┐
│ SchemaCheckAnalyzer
│ R-C 空 Schema → 跳过 ADDED/REMOVED
│ (字段级 diff 逻辑不变)
└─────────────────────────────────────────┘
```
### 5.2 涉及文件(实施时)
| 文件 | 变更要点 |
|------|----------|
| `RedisWritePointDetector.java` | R-A / R-B / R-D / R-E / R-F |
| `MqWritePointDetector.java` | R-A 扩展数字/布尔;直传对象可加 R-B与 Redis 对齐) |
| `SchemaCheckAnalyzer.java` | R-C 空 Schema 闸门 |
| (可选)抽取 `BareValueTypes` 工具类 | Redis/MQ 共用标量集合,防漂移 |
| `RecordingTodoReplaceScenarioTest` | 修复后期望 **0 写入点**(或拆「修复前/后」) |
| 新增场景单测 | 覆盖 §3.1 / §3.2 表中 S2~S8、S11 |
| `docs/配置说明.md` | 同步 W04/W05 与空 Schema 行为 |
### 5.3 实施顺序建议
本版本一次性落地 R-A~R-FRedis + Analyzer + MQ 对齐),无分阶段开关、无灰度配置。
---
## 6. 测试与验收
### 6.1 复现用例(已有)
- `RecordingTodoReplaceScenarioTest`:当前断言「会误报」。
- 整改后应改为:检测结果为空,或 Analyzer 不产出 keyChanges。
### 6.2 建议补充用例
| 用例 | 期望(整改后) |
|------|----------------|
| `set(k, getContent())` 返回 String | 0 点 |
| `set(k, longVar)` | 0 点 |
| `opsForHash().put(k, f, strVar)` | 0 点 |
| `redisUtil.insert(k, tokenStr, ttl)` | 0 点 |
| `set(k, externalDto)` 不在 SourceIndex | 0 点 |
| `String json = toJSONString(vo); set(k, json)` | 1 点,类型 = VOW02/W01 |
| `set(k, demoVo)` 本仓 VO | 仍 1 点 W04 |
| 删除 String 直写 | 不产生 WRITE_POINT_REMOVED |
| MQ `send(topic, longVar)` | 0 点Phase 4 |
### 6.3 回归
- `TenantScenarioTest`、既有 W01~W05 / MQ fixture 全绿。
- 业务仓抽样RecordingTodo 类告警消失;真实 VO 包装层变更仍能检出。
---
## 7. 临时手段与风险
| 项 | 说明 |
|----|------|
| 无新配置 | 不增加 `keep_empty_schema_*` 等开关;行为固化在检测/分析逻辑 |
| 业务 ignore | 可对 `recording:todo:replace:*` 加 key 忽略,仅止血,不解决 S1~S7 类问题 |
| 误杀风险 | 依赖 jar 内 DTO 的 W04 将被忽略 → 可用既有 `manual_mappings` 或把类型源码纳入仓 |
| Object 擦除 | 可能漏检真实结构 → 接受宁漏勿滥;或要求业务写明 VO 类型 |
| 与「展示优化」关系 | 即使将来给新增 Key 染色,空 `{}` 仍无意义;**必须先收敛再建点** |
---
## 8. 总结
| 问题 | 结论 |
|------|------|
| RecordingTodo 为何告警? | W04 把 String 原文当成对象写入;新增路径无空结构闸门 |
| 是否只有这一处? | **否**。S2~S7 同形S8 变形S11 对称MQ 在数字变量与空 Schema 上同类 |
| 根因一句话 | **新增写入检测缺少「有结构才告警」的收敛** |
| 整改抓手 | 检测层标量/本仓类型收敛 + Analyzer 空 Schema 闸门 + Redis JSON 局部 unwrap |
---
## 9. 检查表
- [x] 认可 R-A~R-F 与「宁漏勿滥」、**不新增配置**
- [x] 外部 jar DTOS6默认不监控
- [x] 代码落地 + `RecordingTodoReplaceScenarioTest` 期望 0 点
- [x] 同步 `配置说明.md`
- [x] 全量 `mvn test` 通过