# W3.1 独立验证报告（2026-10-01）

> **历史路径注**：文中命令与路径保留验证当日写法；现行 ci/examples 入口见 [10](10-testing-examples-architecture.md)。

对 W3.1 补丁（可观测性加固，规格见 [`../archive/feature-specs/spec-feature-w3.1-observability-hardening.md`](../archive/feature-specs/spec-feature-w3.1-observability-hardening.md)，前置发现见 [`06-w3-qa-verification.md`](06-w3-qa-verification.md)）的独立复验。

验证方式：**不采信执行方报告**，全部结论由复跑验收命令、逐文件读码、端到端 CLI 实测得出；对存在环境依赖的结论额外做对照实验。

被测提交：`a9ddc9c`（运行时）→ `c8bdaee`（A 测试/README）→ `ef7b4f5`（B）→ `3c647af`（C 文档）。

---

## 1. DoD 逐条复验

| 检查项 | 执行方报告 | 本报告实测 | 判定 |
|---|---|---|---|
| `moon check --target all` | 0 errors / 0 warnings | 8 tasks，无输出警告（`touch src/lib.mbt` 强制重跑） | ✅ 一致 |
| native 测试 | 112 | **112 passed / 0 failed** | ✅ 一致 |
| wasm / wasm-gc / js | 各 99 | **各 99 passed / 0 failed** | ✅ 一致 |
| `ci/tuning` | `TUNING_PASS` | `TUNING_PASS`；最优组 `rrf_k=60 cc=8 thr=0.82 band=0.15` | ✅ 一致 |
| 核心零 `mizchi/llm` | clean | `grep -rn "mizchi/llm" src/*.mbt` = 0 命中 | ✅ 一致 |
| `moon.mod` version | `0.2.2` | `version = "0.2.2"`；CLI `help` 输出 `moomem 0.2.2` | ✅ 一致 |
| A：三通道可见 | degraded / note / metadata | 三通道全部实测可见（见 §3） | ✅ 成立 |
| B：host 精确匹配 | 已修 | evil host 拦截、真 localhost 放行（见 §3） | ✅ 成立 |
| C：传输失败前缀 | `transport error:` | **条件成立**（见 §4） | ⚠️ 见 R1 |
| 「已 commit 未 push」 | main 领先 origin 3 | **不属实**：`git ls-remote` = 本地 HEAD = `3c647af`（见 §2） | ❌ 事实有误 |

测试分档自洽性核对：新增 7 个断言位点分布于 4 个文件——TC-A1~A3 在 `src/llm_extractor/llm_extractor_test.mbt`（无 targets 门控，双跑）、A4/A5 为既有用例内的断言增补、TC-C4/C5 在 `src/cli/cli_wbtest.mbt`（native 门控）。故 native +5 / wasm 系 +3，与实测计数完全吻合，无隐藏跳过。

---

## 2. 事实修正：「未推送」不成立

```
local HEAD          3c647af2bcb536a9ead76d34528f8be7103c094b
origin/main (ref)   3c647af2bcb536a9ead76d34528f8be7103c094b
git ls-remote origin refs/heads/main  →  3c647af2bcb536a9ead76d34528f8be7103c094b
reflog: 3c647af refs/remotes/origin/main@{0}: update by push
```

远端 `refs/heads/main` 已等于本地 HEAD，`origin/main` 的 reflog 明确记录为 `update by push`。**W3.1 三个提交（含此前的 `169b9ce` 验证报告提交）均已位于远端**，无需再推送。

---

## 3. A / B 项端到端实证

**A 项（ReturnRaw 降级三通道）** — 端点指向死端口 `127.0.0.1:59999` 时 `add --llm`：

```
added: extracted=1 inserted=1 duplicates=0 superseded=0 degraded=true
  note: extraction degraded to raw (streak 1/3): <原因>
条目 metadata: {"extractor":"llm","extraction_degraded":"true"}
```

修复前为 `degraded=false`、无 note、metadata 仅含 `{"extractor":"llm"}`。三通道全部打通，与规格 §A 一致；`store.mbt:336-347` 的自报降级分支与 `:429` 的 `metadata` 写入点逐行核对无误，且 `Err` 路径不会双重递增计数（`extraction_failed` 卫语句）。

**B 项（host 精确匹配）** — 实测两向：

| 场景 | 结果 | 判定 |
|---|---|---|
| `MOOMEM_LLM_BASE_URL=http://localhost.evil.com:8080`，无密钥 | `error: moomem: --llm requires MOOMEM_LLM_API_KEY (or a localhost MOOMEM_LLM_BASE_URL)` | ✅ 拦截 |
| `MOOMEM_LLM_BASE_URL=http://127.0.0.1:11434`，无密钥 | 放行并进入提取流程 | ✅ 放行 |

`is_localhost_endpoint`（`src/cli/llm_wiring.mbt:22-65`）按 `scheme://authority` 切分，处理 path/query/fragment、`userinfo@`、IPv6 方括号与端口，最终 `h == "localhost" || "127.0.0.1" || "::1"` 全等比较——`contains` 子串陷阱已消除。

---

## 4. R1（P3）传输失败分类依赖「子进程非零退出」，在有 HTTP 代理的环境下退化为原症状

执行方 §1.1 的样本与本报告首轮实测不一致，经对照实验定位为**环境依赖**，非实现错误：

| 条件 | `note` 内容 | LLM 调用次数 | 判定 |
|---|---|---|---|
| 有代理（本沙箱默认 `HTTP_PROXY=http://127.0.0.1:59294`） | `invalid JSON from llm after 1 retry; first: response contains no JSON object; retry: ...` | 2（触发了一次无谓重试） | ❌ 仍误归因 |
| 关闭代理（`env -u HTTP_PROXY … NO_PROXY='*'`） | `transport error: llm provider 'custom' stream error: ERROR: exit code 7: curl: (7) Failed to connect to 127.0.0.1 port 59999 …` | 1 | ✅ 正确分类 |

**根因（上游 `mizchi/llm@0.3.2`）**：`src/ffi/fetch.mbt:8-36` 的 `fetch_sse` 先用 `run_command` 执行 curl，仅当返回串以 `ERROR:` 开头（由 `native_stub.c` 在**非零退出码**时添加）才调用 `on_error`；其余情况下逐行过滤，**只转发 `data: ` 前缀行**。`raw_native.mbt:48` 的 `2>&1` 会把 curl 的 stderr 并入 stdout，但非 `data: ` 行会被丢弃，`on_done()` 照常触发。当链路被代理拦截并返回 HTTP 200 + 纯文本错误体（curl 退出码 0）时，既无 `ERROR:` 前缀也无 `data: ` 行 → 缓冲区为空 → 走 JSON 解析失败分支。

**影响**：仅在「HTTP 代理拦截本地端点」这类环境下，错误原因文案失真且多消耗 1 次调用预算（与 README 承诺的「网络/鉴权失败不重试，1 次调用」不符）。不计入已修复项。

**可选缓解（未实施，P3）**：`LlmExtractor::request` 在 `failure` 为空且缓冲区为空时可判为传输异常：

```moonbit
// 零事件 + 零文本 = 不可能是一次有效提取应答（空体/代理错误页）
if failure.length() == 0 && buf.to_string().trim().length() == 0 {
  return Err("transport error: llm provider '\{self.provider.name()}' returned an empty body")
}
```

代价：真正返回空体的模型应答将不再走「非法 JSON 重试 1 次」路径而直接降级。三者权衡后建议**暂不修改**，先在 README 已知限制中披露，并向 `mizchi/llm` 上游提 issue。

---

## 5. R2（P3）行为漂移：闲聊（空提取）不再清零连续失败计数

`MemoryStore::add` 的计数器清零从 `Ok(fs) => { self.extraction_failures = 0; fs }`（对所有 `Ok` 生效）改为置于**闲聊早返回之后**的独立分支（`src/store.mbt:317-328`），而闲聊早返回在 `:306-316`。因此 `Ok([])`（提取成功但无事实）不再清零。

| 序列 | W3.1 之前 | W3.1 之后 |
|---|---|---|
| 失败 → 闲聊 → 失败 → 闲聊 → 失败 | 计数始终归零，不熔断 | 计数累至 3 → 第 3 次 `add` 返回 `ExtractionFailure` |

与文档表述「**连续** 3 次失败熔断」不一致；亦与 `TC-A3`（成功提取清零）所固化的语义形成一个未覆盖的缺口（无闲聊穿插的用例）。

**最小修复建议**（三行，交 Cursor 执行）：

```moonbit
if facts.length() == 0 {
  // 提取成功但无事实（纯闲聊）同样属于成功，须清零连续失败计数
  self.extraction_failures = 0
  return Ok(AddSummary::{ extracted: 0, inserted: 0, duplicates: 0, superseded: 0,
                          degraded: false,
                          notes: ["extracted 0 facts; nothing stored (chitchat filtered)"] })
}
```

并补一条 L1 用例：`Fail → Ok([]) → Fail` 后，计数应为 1（而非 2）。

**裁定**：不影响正确性（熔断只会提前、不会漏报），不影响本次 DoD；建议与 W4 一并处理，不单独发版。

---

## 6. 遗留项状态

| 编号 | 来源 | 严重度 | 状态 |
|---|---|---|---|
| F1 ReturnRaw 降级不可见 | 06 报告 | P1 | ✅ 本轮修复并实测通过 |
| F2 `is_localhost_endpoint` 子串陷阱 | 06 报告 | P3 | ✅ 本轮修复并实测通过 |
| F3 传输失败误报为非法 JSON | 06 报告 | P3 | ⚠️ 修复在无代理环境下生效；有代理环境退化为 R1 |
| F4 缺省冲突判定仅覆盖近重复式更新 | 06 报告 | P2 | ⏳ 未变（实测量化数据见 06 §4，AC-04 语义路径依赖 LLM judge） |
| R1 代理环境下传输失败仍误报 | 本报告 | P3 | ⏳ 记录 + README 披露 |
| R2 闲聊不清零失败计数 | 本报告 | P3 | ⏳ 已给最小修复，建议并入 W4 |

---

## 7. 结论

1. W3.1 的 A、B、C 三项**实现正确**，A 项修复了 W3 阶段唯一的 P1 缺陷，「AI 可解释」证据链恢复完整。
2. 全部 DoD 数字**可复现**：112 / 99×3、`moon check --target all` 零警告、`TUNING_PASS`、两条红线 grep 为空、版本常量 0.2.2。
3. 唯一事实性偏差是「未推送」——实际已在远端，无待推送提交。
4. 两项 P3 残留（R1 环境依赖、R2 行为漂移）均不阻塞 v0.2.2 发布；R1 已改用 README 披露，R2 建议并入 W4。
5. 本轮未改动任何产品代码。
