264 Commits
Author SHA1 Message Date
小唯 7f7171ac14 feat(recall): 召回契约字段 + trace 回溯 + 生命周期/实体质量判据(工作树收口)
这批是 10-26~27 已在生产二进制(zhiyid-protectrails-20261027)里运行、
但一直留在工作树未提交的改动。收口原因:三件改造要开分支,工作树脏 =
分支基准与回滚点都不存在。

主要内容:
- api/routes/contract.go + trace.go:召回响应契约字段(trace_id/status/scope/
  continuation/channel)与 /api/v1/trace 回溯端点
- governance/entity_quality.go:实体质量闸
- storage/trace.go + recall.go/recall_write_buffer.go:召回写入缓冲与轨迹
- 测试:contract/recall_contract_fields/recall_lifecycle/stale_lifecycle/
  entity_quality/trace

同时修一个 flaky:recall_lifecycle_test 原本硬编码「今天 11:00 已过」,
凌晨跑就会把「已失效」fixture 当成仍有效。改成相对时间(过期=now-1h),
生命周期判据与运行时刻无关。修完 go test ./internal/api/routes ./internal/storage 全绿。
2026-10-28 02:09:19 +08:00
小唯 0b2b4f01d8 fix(storage): recall_seen_count 批更新加 COALESCE —— NULL 行不再吞召回观测值 (t_5aa7cb77)
批路径 .column("recall_seen_count", "recall_seen_count + delta") 下推 DataFusion,
SQL 语义 NULL+n=NULL ⇒ 17717 行(43.8%)观测值静默丢失。改 COALESCE(recall_seen_count,0)+delta。
逐条 fallback 路径本就 NULL-aware(读现值 unwrap_or(0))且其 split_whitespace 3 段
解析不容 COALESCE,故不动并加注释防后人误改。
2026-10-27 04:34:48 +08:00
prof-dev 37dcebfce4 fix(recall): 修 16:28 topkcap 引入的 pattern 截断回归 (t_e98cb1d6)
- capRecallResults 把 category=pattern/peer 的条目先摘出(recall.go Step 6.2/6.3
  追加在 results 末尾),主结果截断到 limit 后再把附带条目放回末尾;
  主结果仍收口,pattern/peer 不再被蒸馏结果挤掉。
- 新增 TestCapRecallResults_PatternAndPeerSurviveCap:断言 limit=50 时
  pattern 计数 ≥1、peer 不被截断、主结果仍不超过 limit。

方案取舍:选 A(cap 内先摘出),不选 B(注入移到 cap 之后)——
注入在 storage.Recall() 而 cap 在 routes 层,B 需跨 storage/routes 重排
调用链并改缓存路径;A 是纯函数、最小、且与原 topkcap 修正在同一位置。
2026-10-26 19:42:57 +08:00
小唯 d7a73d7e6c fix(recall): top_k 收口 + 恢复 main 可构建性
1) top_k 收口(t_9025b0eb)
   /api/v1/recall 无视 top_k:top_k=5 实测返回 341 条(5 distilled + 336 graph_expanded),
   top_k=10 返回 521 条。因 ExpandFromResults 扩展结果 append 无上限。
   后果:插件记忆预取被撑爆(journal 实测单次 flush 合并 900/780/613/549/351 条),
   真相关条目落在被截断段;count 失真;使用追踪按千级记账稀释指标。
   修法:capRecallResults —— results = 主结果 ≤top_k;扩展单独走 graph_expanded 字段
   (上限 top_k,能力仍可观测);主结果不足时用扩展补齐(稀疏查询仍受益)。
   + core_topk_test.go 5 例 6 断言钉住契约。

2) 恢复 main 可构建性(P0,非本卡范围但阻塞一切构建)
   rebase 丢了 metrics.ConsolLastSuccessTime / ConsolConsecutiveFailures /
   ConsolidateBusyWaitTotal / MetaNoteRejectTotal 的定义(只在 f96c828),
   而调用方已进 main,且 consolidation_track.go 整个文件不在树上 ⇒ go build 失败。
   从 f96c828 原样恢复 4 个指标定义并注册;从 2275b83 恢复 consolidation_track.go。
2026-10-26 16:29:43 +08:00
xiaoyi 9a4a734c19 fix(metrics): 计数器/率指标语义分清 + t8 阈值改由 Condition 承载(t_da03e910)
- 审计误读订正:deprecated_per_day 是计数器(无分母),不给 _samples/_valid 是对的
- 新增 deprecated_total:区分「今天 0 条」与「从未统计过」(0 的二义性)
- executor t8:原 Condition:0.1 从不生效(判据硬编码 >5)→ Condition:5.0 + 读 t.Condition,行为不变
- 单测钉住:RecordDeprecation 双计数、t8 阈值真的可配、计数器不带率键
- docs/metrics-semantics.md:counter/rate/mean 三种读法 + 0 的二义性铁律
2026-10-26 14:55:01 +08:00
xiaoyi 38d7ef147d feat(selfcheck): pattern 段可见化 —— 零产出可被机器读到(写入端 0 生产者事故)
- governance.GraphStore 增 CountNodesByType(total, withID, lastUpdated),SQLite/内存/缓存三实现
- selfcheck cheap 增 pattern 段:rows / rows_with_id / last_produced_at / verdict
  verdict ∈ producing|zero_output|no_id|unknown_age|no_graph_store|unsupported
- 实测:rows=0 verdict=zero_output(与 graph.db 只读复核一致),cheap 22ms
- 动机:pattern 召回 0 命中时只能靠翻 7 天日志才发现「实现了≠出过数」
2026-10-26 14:55:01 +08:00
xiaoyi ab1452d06b feat(selfcheck): /api/v1/selfcheck 统一取数端点 + tools/selfcheck_full.py 全量快照(牧尘批「三档」) 2026-10-26 14:55:01 +08:00
小唯 1c7882ce34 fix(graph): SQLiteGraphStore.ExpandFromResults 恒 0 产出 — from/to 是节点ID却拿裸名比对(t_724f31ad)
816/819 行把 Navigate 返回的节点 ID(n_织忆)与裸实体名 entity 做 == ⇒ 恒不相等 ⇒ 路径全被 continue ⇒ 结构性 0 条附加。
修:比对改用 nodeID(normalizeEntityID(entity));附加结果补 Category=graph_expanded + ID=邻居节点ID(非空,修复 recall.go ID 去重与审计计数);Content 用可读显示名(displayNodeName)。
补单测:正向(n_织忆→n_图谱 链+1条recall ⇒ 补入 graph_expanded 且 ID 非空) + 反向(图外实体 ⇒ 0 补入不凭空加)。
2026-10-26 14:55:01 +08:00
小怡 3cf3858f09 fix(peer): 画像推理器支持 peer 专用 LLM 端点,避免被全局 LLM_ENDPOINT 拖去鉴权腿
起因 t_a5ef04a5:zhiyid 的 LLM_ENDPOINT 由 90-llm.conf 定为 newapi 3000 + agnes-2.5-flash
(供蒸馏/skillgen/consolidate 共用),而 NewLocal2BReasoner 按设计不发 Authorization
⇒ peer refresh 必然 401、peers 表恒 0 行、peer 召回零产出。

不采用卡面建议的「把全局 LLM_ENDPOINT 改成 8081」——那会把蒸馏同时降级成 2B。
改法:新增 ZHIYI_PEER_LLM_ENDPOINT / ZHIYI_PEER_LLM_MODEL 优先读取,未设时行为与改动前逐位一致。
回滚:删 91-peer-llm.conf 即恢复旧行为。
2026-10-26 14:55:01 +08:00
小怡 33e16c1a61 fix(recall): agent_id 派生作用域候选过少时补一次全库搜索(ZHIYI_NS_FALLBACK_MIN,默认20,0=关)
问题:请求带 agent_id ⇒ resolveRecallNamespace 派生 <agent>-main,实测该 ns 只有
default-main 6 / xiaoyi-main 6 / prof-cloud-main 7 条,不传 agent_id 则 200 条
⇒ 带 agent_id 的调用静默劣化到个位数,不报错(09-23 只修了调用方,没修被调用方)。

改法:新增 RecallWithMetaOpts(..., allowNSFallback);仅 agent_id 派生作用域传 true,
显式 namespace 调用方传 false(防跨域泄漏)。候选 < 阈值时补一次全库搜索并合并 + 打日志。
回滚:ZHIYI_NS_FALLBACK_MIN=0。单测 4 条锁死:兜底生效/显式ns不兜底/env关/候选充足不兜底。
2026-10-26 09:56:38 +08:00
小怡 c84d968b02 feat(recall): 新增 ZHIYI_FUSE_MODE=proof(第二槽改用证据条数质量)
对应 t_9acaa74d §5 A' 的 nouse 臂:融合第二槽 = QualityFromProof(确定性 x/(x+K)),
彻底不含 importance=recency×(1+ln(seen))/V —— 老卡实测 usage 特征单独是负资产
(imp-only 0.0579 / seen-only 0.0335 NDCG@5)。
默认关 ⇒ 逐位等价现状;开启需配合 FUSION_W<1,否则第二槽权重 0。
单测锁两条:默认必须 false;证据分只依赖证据条数(同一证据数下改 seen/时间不得变)。
2026-10-26 08:42:40 +08:00
小怡 f4417c2f4d fix(eval): 金标路径优先级 显式path > env > 内嵌(保留相对路径内嵌兜底)
旧顺序 env 优先 ⇒ ZHIYI_GOLDEN_SET 一设,请求里的 golden_path 全被静默忽略:
实测 7 个 chunk(304 条) 每块都报 53 条(env 那套),指标全 0 时极难察觉。
现:绝对路径读不到=报错(不许静默换题);相对路径读不到仍回落内嵌(AC5 既有行为)。
2026-10-26 01:53:50 +08:00
小怡 03cac702a8 fix(eval): 金标路径优先级改为 显式path > env > 内嵌
旧顺序 env 优先 ⇒ ZHIYI_GOLDEN_SET 一设,请求里传 golden_path 全部被静默忽略:
实测 7 个 chunk(304条) 每块都报 53 条(env 那套),且指标全 0 时极难察觉。
env 现在只作默认金标;显式路径读不到改为明确报错,不再静默回落内嵌。
2026-10-26 01:52:11 +08:00
小怡 0499c82965 feat(eval): RunGolden 支持 all_namespaces(跨 ns 评测)
旧逻辑 ns 为空强制 shared ⇒ 评的是「只看 shared 的召回」,与线上跨 ns 不是同一系统
(实测前 15 条金标里 15 条 top 结果不同)。新增 all_namespaces=true ⇒ ns=""(等同线上默认);
不传 = 旧行为,向后兼容。
2026-10-26 00:28:38 +08:00
小怡 d117102088 fix(recall): t_standing_recall 改严格相等匹配(上线即抓到换主语误命中)
留出集实测:「织忆的服务跑在哪个端口」被判为「出图的服务跑在哪个端口」的匹配
(Dice 0.82,仅主语两字之差),把 ComfyUI 8188 置顶成织忆端口——模板句换主语必错。
改为归一化后完全相等才返回;近似 ≥0.78 只打 near-miss 日志用于补变体;加回归测试。
2026-10-25 23:57:23 +08:00
小怡 1aaab764ba feat(recall): t_standing_recall 常设答案精确通道(默认关)
实测基线(53 条真实提问):correct@1 20.8% / useful@5 88.7%;6 条跑偏案例里
5 条目标文档不在向量 top-50 —— 语义通道对端口/数字/标识符类提问够不着,补精确通道。

- storage/standing_match.go:归一化+bigram Dice 匹配(阈值 0.78,宁漏勿错)、30s 快照、置顶注入
- storage/recall.go:RecallWithMeta 拆为 wrapper + inner,开关 ZHIYI_STANDING_ANSWER=1 才生效
- 关闭时与旧行为逐位一致(可一行回滚)
- 单测:归一化/相似度/阈值/置顶 4 组全 PASS
2026-10-25 23:53:26 +08:00
小唯 2ac7c9d5a1 feat(write-ops): t_writeops_llm_judge — 写时判定器改 LLM 三分类 + apply 硬保护
问题(离线回放实测,非推测):
- 规则基 defaultJudge 以 ZHIYI_WRITE_SIM_UPDATE=0.70 判 UPDATE,17179 条影子日志里
  14435 条(84%)被判「新事实应覆盖旧事实」
- 人工抽检 15 条低相似度样本,15/15 不是真覆盖(「用户查看知乎链接」vs「用户查看微信文章」
  都被判成覆盖);低档 LLM 判定精度同样不足(0.70-0.85 档 REPLACE 精度约 25%)
- 结论:相似度只反映「话题相关」,不反映「是否同一属性的新旧值」

改动:
- 新增 llm_judge.go:REPLACE/DUPLICATE/KEEP 三分类,仅 sim>=ZHIYI_WRITE_JUDGE_SIM(0.95)
  才问模型,低档短路 ADD;调用失败兜底 ADD(宁可不覆盖)
- JudgeWriteOp:judge 显式注入 > apply/llm 模式用 LLMJudge > 规则基
- writeOpsApply 硬保护:apply 强制要求 ZHIYI_WRITE_JUDGE=llm,否则降级 shadow 并留痕
  (防止规则基 84% UPDATE 在 apply 下误覆盖上万条)
- 单测 8 条 + 真实 LLM e2e 3 条(ZHIYI_LIVE_JUDGE=1 才跑)

验收:go test ./internal/distill/ 全绿;live e2e 真覆盖→UPDATE / 假覆盖→ADD / 重复→NOOP 3/3
部署:shadow 模式(不改库),观察 2-3 天后 op 分布应从 84% UPDATE 降到 5-15% 再考虑 apply
2026-10-25 23:06:42 +08:00
小唯 66d5049151 feat(feedback): t_11245d30 区分人机信号 — MarkUseful 支持 source=auto → 记 auto_feedback
人工点「有用」仍 user_feedback(行为不变);插件自动反馈(_auto_feedback_for_reply)
传 source=auto → 记 auto_feedback,让 zhiyi:vprop_decisions 可辨「牧尘认可 vs 机器自评」。

纯观测,零行为变化:不改 V 权重/语义闸阈值/_MAX_PER_TURN。待 Go toolchain 重建后生效。
2026-10-24 04:34:25 +08:00
小唯 f979bd6ffc feat(recall): 融合权重可配 ZHIYI_FUSION_W(默认0.7=零变化,1.0=纯向量)
t_5624c0ff → 牧尘 2026-10-21 批准「按推荐执行」。

依据(strict n=134,golden-v4):
- 现状 fuse 0.1125 vs 纯向量 0.1898 ⇒ 现状是负收益,比纯向量低 39%
- 去 usage 0.2019 但逐 query worst −100%(12/134 从答对变彻底答错)
- MoA expert-panel 4 槽:3 槽指向止血 + intern-s2 指出根因是召回不足(相关文档在 pool#10-14)
- A/C 绝对差 0.0121 在 n=134 上不显著 ⇒ 不为它背 9% 硬失败率

改动:三处硬编码 0.7/0.3 参数化为 fusionW(candidates.QualityScore / reranked.Score / base 展示分)。
回滚:ZHIYI_FUSION_W=0.7 或删除该 env(逐位回到改动前)。
2026-10-21 20:35:30 +08:00
小唯andClaude Opus 4.8 8ea82fbec3 fix(storage): t_cf680fad SoftDelete 冲突耗尽静默丢 — 修「重试成功=0」+ 残留根治
根因(实证):runMerges O(n²) 软删 + 并发遗忘碎片器 在同一张 memories 单表上
制造 MVCC 写风暴;旧 3 次/~350ms 无 jitter 预算下每次重放都撞已推进的新版本
(单次 op 3 次重放 version 598346→598347→598348→598350 逐版推进仍耗尽),
失败软删被 consolidation_pipe.go:252 的 `_ =` 静默丢弃 ⇒ 源记录 is_deleted 停
false,每次重启窗口留新残留(检索重复命中)。

修(3 件,均 Go 侧 zhiyid):
① durable pending-softdelete 队列(softdelete_pending.go):MVCC 冲突耗尽 ⇒
   把 (id, reason) 追加进 /var/lib/memoryweave/pending-softdelete.jsonl
   (单写者 append + 原子 tmp/rename + 同 id 去重);zhiyid 启动 + 每 30s 触发器
   tick 各 drain 一次(SET is_deleted=true 幂等、重读最新已提交版本),风暴退去
   后重放必落盘 —— 根治「每次重启窗口都留新残留」。
② retrySoftDeleteRPC 预算 3→5 + 退避加 0~50% jitter:破 50+ 并发软删锁步雷群
   (锁步本身是冲突持续的主因),多两次机会跨风暴窗口。
③ consolidation_pipe.go:252 `_ =` → 显式 log(观测、禁静默);队列负责最终落盘。

测试:storage 全绿(耗尽入队 / 非可重试不入队 / 同 id 去重 / 风暴退去 drain 落盘
清空 / 风暴未退 drain 保留队列)。6 文件:2 新 + 4 改。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-21 09:45:06 +08:00
小唯 2862f68631 feat(gate): 补提 t_bd63b641 门控复核资产 — quarantine 过滤 + junk 实体拒写 + ListPatterns 三返回值
- ListPatterns 改三返回值 (pats, skipped, err):单行 NULL/空 id 跳过计数不致全盘失败;
  排除 quarantine 名空间;COALESCE 防 NULL Scan 错误
- SQLiteGraphStore 增 isQuarantinedNode + Navigate/NavigateBiDir 过滤 quarantine 节点
- graph_auto.go 蒸馏写库前拒 junk 实体 (IsJunkName)
- 新增 entity_guard.go (实体名拒写门禁) + shadow_diff_test.go (影-复量具)
- docs/ac-evidence/t_bd63b641-shadow-gate/ 验收证据
- .gitignore 增 /go/tmp/ /verify.go,删 17.9M 编译二进制临时产物

build+test 均通过:go build ./... exit0;pattern+routes 两包 ok
2026-10-20 07:10:29 +08:00
小唯 b8d77547de chore: mark 79019bf (A10 huc 读侧+e2e) approved 2026-10-19 21:29:04 +08:00
小唯 79019bfc40 A10(t_d618dc7c): 补 human_useful_count 读侧接线 + 真实 LanceDB e2e 闸门
读侧静默 no-op 修复(t_4c810e02 同类:写进库但读不回):
- lancedb_ipc.go 读路径 unmarshal 结构体缺 human_useful_count 字段,
  导致 /recall、/memories 读回恒 null(列真实写入但读侧解包丢弃,无报错)。
  补 4 处:SearchWithTruncation 结构体+输出映射、Scan 结构体+输出映射、
  GetSkillCandidates(mapInt)、新增 mapIntPtr 可空整数映射(NULL≠0)。
- ScanAll/embedded-model 路径经 models.MemoryRecord 已带字段,无需改。

新增 human_useful_count_e2e_test.go(真实 sidecar 写→读回闸门,
仿 bitemporal_e2e 模式,未设 ZHIYI_HUC_E2E_SOCK 自动 skip):
- AC#4 读非空:存量行读回 NULL(nil);
- AC#5 同步+反例:人工 useful×2⇒huc=2/useful=2;机器 dedup 路径不污染 huc;
  人工 not-useful 只动 human_not_useful。

验证:go build 通过 + 全 storage 套件绿;真实 sidecar e2e 全绿
(AC4 读回 nil / AC5 huc=2 useful=2 / 反例隔离成立)。

git show --stat 212624a4341fac
2026-10-19 21:28:33 +08:00
小唯 a5bc27c3a4 feat(distill): 蒸馏落库前拒写空壳(t_e11207f8 治源①)— 共享判据 zhiyi_payload_guard_rules.json + Go payload_guard + OnDistillComplete 拦截 + /api/v1/payload-guard/stats + 3 单测;顺带修复 deploy-approved 清单坏行
- distill/payload_guard.go: 读共享 JSON(与 Python 同源),ShellCheck 带心跳/拦截计数
- api/server.go: OnDistillComplete fact 循环红act 后 ShellCheck 拒写空壳 + 启动加载 + stats 端点
- main_test.go: TestMain 加载共享规则供单测
- 修复 scripts/deploy-approved-commits.txt 尾行坏数据('appending:' 前缀 → 两条合法 SHA)
2026-10-19 21:16:57 +08:00
小唯 83c41963cf deploy: mark a293f7b+3533e55 (A10 human_useful_count) approved for deploy [t_d618dc7c] 2026-10-19 20:45:08 +08:00
小唯 3533e55801 feat(api): A10 recall/search 出口直通 human_useful_count(t_d618dc7c AC#4 读侧)
- models.RecallResult 加 HumanUsefulCount *int(nil=NULL=从未被人工点过,non-omitempty)
- storage/recall.go 主路径(800) + FTS词面通道(891) 两条 RecallResult 构造处接线 c.HumanUsefulCount
- api/routes/core.go supplement(shared 0.5 / notes 0.45) + keyword 通道三处接线 m/c.HumanUsefulCount
- 合成路径(graph_* / peer / pattern / graph fallback)无 MemoryRecord 来源,保持 nil(正确)
- 仅补『响应值==库值』显示缺口,不进排序公式(AC#6 排序零变化)
- 修复 t_4c810e02『加列不接 IPC=静默 no-op』同款坑:列现在真能读出
2026-10-19 20:06:12 +08:00
小唯 a293f7b784 feat(storage): A10 拆出人工有用 human_useful_count 单独列(t_d618dc7c)
- Rust lance schema 追加 human_useful_count (Int64, nullable);open() 自动加列
  迁移,存量回填 NULL(与 confidence/human_not_useful_count 迁移模式一致)
- MemoryRecord 读侧接线(get_by_id/search/scan_all/scan_deleted/record_from_batch)
  走 col_i64_opt(NULL 读回 None,区别于 0)
- insert_batch 写入侧:未声明/NULL → 写 NULL;显式整数 → 写值(与 confidence 同款)
- update() $inc 基数改 col_i64_opt(NULL 行按 0 起算,首次人工 useful NULL→1)
- Go IncrementUseful 同一次 Update 三 $inc:useful_count + recall_verified_count
  + human_useful_count(只有人工 feedback 路径调用,机器自评路径不写此列)
- Go MemoryRecord/memEntry 加 *int 字段(nil=NULL)
- 新增 human_useful_count_test.go:人工 useful 同时 +1、机器路径不动反例
- 排序公式/权重零改动(AC#6 排序零变化)
2026-10-19 19:42:29 +08:00
小唯 754976db52 deploy: mark c954aa2 (AC3/AC7 PosMode) approved for deploy [t_22b4e9d0] 2026-10-19 12:58:26 +08:00
小唯 c954aa2917 fix(storage): t_23b6a940 热度≠质量 — FeedbackScore pos 拆出 recall_verified_count(env ZHIYI_POS_MODE 四态, 默认 legacy 逐位一致)+ AC7 热门度运行时看门狗 + 5 Go 测试
金标 119 条离线 A/B(固定候选集 cands-119.json, 确定性 σ=0):
- baseline(pos含verified) MRR 0.7265
- champion(pos只信useful + importance w=0.15 + B=0.2) MRR 0.7699 (Δ+0.0434)
- 与 t_5d28ad74 已部署公式收敛(pos 去掉 recall_verified)
分层: chunk1 +0.0729 / chunk2 +0.0562 / chunk3 持平(无新增伤害)
门关(ZHIYI_FEEDBACK_BIAS=0)逐位等价; 回滚=置0零代码变更
AC4: 不读 deprecated not_useful_count ✓
5 Go 测试全 PASS: 门关delta=0 / 门开方向(none/weight/tiebreak) / 反向对照 / AC1 tiebreak同档
2026-10-19 03:33:01 +08:00
小唯 772a597a53 test(storage): t_7ba5e479 AC2 反馈信号进排序证明 — feedbackBias 门控 3 用例(门关 delta=0 / 门开 rank 下滑 / 正向对照)+ IncrementHumanNotUseful 走生产同一路径 2026-10-18 21:52:47 +08:00
小唯 1f30474cf3 feat(storage): t_789898bd importance 温度计 — 写入侧规则化 importance + env 回滚开关 + 存量回填工具
AC1 lancedb_ipc.go:159/:200 硬编码 importance 1.0 改为规则调用:
  EffectiveMemoryImportance(m)   显式值优先; 规则开⇒档位; 关(默认)⇒回落1.0(零行为变化)
  EffectiveEpisodeImportance()   episode 原始日志 规则开⇒0.3 预留档; 关⇒1.0
AC2 规则档位(全库实测标定, 每档>=1%, 无值==1.0): core=0.9(4.11%) / normal×wiki,llm_distill=0.7(70.06%) /
  normal×legacy,unknown=0.4(25.84%) / normal×recovery=0.3(预留档)
  未纳入: 内容深度(78.1%<50字⇒切档违反>=1%) / 时间衰减(排序已有运行时recency项, 避免双衰减) / 图谱连接度(红线不碰图谱)
AC3 存量回填: rust bin importance_backfill(仅写 importance 一列, id IN 组提交 25 版本,
  提交冲突有界退避, verify 逐行复算, rollback=整表1.0 一个版本)。
  生产执行: 46,930 行全部回填, 恒1.0 行数 46,930 -> 0 (verify mismatch=0)
  备份: ~/.hermes/state/t_789898bd-importance-backup/memories-id-importance.csv (46,913 行, 全部 1.0)
AC4 golden 集合 before/after: mrr 0.4345→0.4345, ndcg 0.4722→0.4722, keyword 14/14 不变(差 0%, <±10%)
AC5 回滚开关 ZHIYI_IMPORTANCE_RULE(off=默认, 逐位等同改动前); 回滚命令:
  importance_backfill --data-dir /var/lib/memoryweave --mode rollback
单测: go test ./internal/storage/ 全过(5 个新 importance 用例); 回归: storage+models+api/routes 全过
2026-10-18 21:27:17 +08:00
小唯 a1021bb70a feat(storage): t_7c0538eb AC5 低分可信评分记忆检索降权(可逆, env-gated, 默认关)
fuseQuality 对「可信且 0<qs<0.30」的行, rawQuality 贡献乘低分降权系数
lowScoreDownweight(qs)=max(floor=0.5, qs/0.30)(随分单调递增, 高分不动,
0.0/0.755-echo 走不可信分支不受影响)。

回滚开关 ZHIYI_LOWSCORE_DOWNWEIGHT: 缺省/0/false 全关→系数恒 1.0,
行为与改前逐位一致(go test TestFuseQuality 基线未变)。
测试 quality_fusion_lowscdw_test.go: 同一候选集 gate 关/开/再关,
验证 L(低分高重要性) 被 M(普通分) 反超 + 回滚复原。
2026-10-18 12:54:51 +08:00
小唯 e0412747f0 chore(deploy): t_20bfbd9a 批准清单补齐 14 个历史已评审提交 + HEAD 5f63b35 2026-10-18 02:22:18 +08:00
小唯 5f63b35602 chore(deploy): t_20bfbd9a 修正批准清单行粘连(每行一SHA) — 补齐 71d5f2a/941b7ee/3e29617 批准基线 2026-10-18 02:21:38 +08:00
小唯 3e29617cbe chore(deploy): t_20bfbd9a 追加批准部署基线 71d5f2a(优雅停止)+941b7ee(线程风暴根治) 2026-10-18 02:20:20 +08:00
小唯 941b7eec32 fix(storage): t_20bfbd9a 边车线程风暴根治 — Rust 连接并发上限+RSS护栏降级串行;命中事件重试跨commit指数退避+积压上限
AC1 Rust accept 循环对每连接 thread::spawn 无上限,Go 侧每 rpc 新 dial ⇒
   IPC 洪泛线程线性涨到 736。加 AtomicUsize 连接门(默认64, env ZHIYI_CONN_MAX),
   超限回 heavy_conn_busy;RSS 超上限(默认3000MB, env ZHIYI_RSS_LIMIT_MB)降级串行。
AC3 usage_recall 加 retry_count/next_retry_at:持久失败行指数退避(1s..600s)不再
   每 commit 热重扫;积压超 ZHIYI_USAGE_BACKLOG_MAX(默认500)本轮跳过扫描。
AC4 新增 usageBackoff/backlog 单测。Go 全仓测试通过。
2026-10-18 02:19:03 +08:00
小唯 71d5f2a7cd fix(storage): t_fa797edd 优雅停止全程有界 — Stop 最终落盘不再无限阻塞
根因:SIGTERM 到 StopRecallWriteBuffer(),Stop() 同步调用无界的 b.Flush()。
Rust/LanceDB 批量更新在活 IPC 流量下被卡住(生产实测 20:44 停止窗口内 Rust 侧
update_recall_batch 80s 未完成),Stop 永不返回,信号协程到不了 srv.Shutdown,
进程跑满 systemd 90s TimeoutStopSec 被 SIGKILL(丢写风险 + 重启不可控)。

修复:Stop() 全程有界 — 等后台协程 <=5s + 最终落盘 <=8s(goroutine+超时)。
召回统计丢失可接受(源码注释本就声明崩溃会丢最后一窗口),超时即放行退出。
新增反向用例 TestRecallWriteBufferStopBoundedWhenFlushHangs:flush 挂起时
Stop 在 8.0s 内放行(原实现无限阻塞)。storage 包全量测试通过,zhiyid 构建
exit=0。未部署(AC5 边界:仅改源码 + 隔离验证)。
2026-10-18 00:51:46 +08:00
小唯 f6c373ea94 fix(governance): t_818f77be ExpandFromResults 导航起点 r.Category→正文实体
InMemoryGraph(graph_expander.go:24)+FileGraph(graph_file.go:466) 旧实现拿
r.Category(类目名,如 distilled)当实体去 Navigate, 永远匹配不到任何图节点
⇒ 结构性零产出(实测 10/10 全 distilled, 0 graph_expanded)。InMemory 版另读
p[to]/p[from] 而 Navigate 返回 source/target, 双重零产出。

修复: 导航起点改为从 r.Content 提实体——复用现有 extract*Entities +
normalize*EntityID(与同文件 ExpandWithSummary 同一套), 不新造抽取逻辑;
InMemory 版路径键 to/from → source/target 一并对齐。Score:0.5 降权/去重/
pattern-peer 门控 均不动。

可观测性(AC2): 加 env 控 ZHIYI_GRAPH_EXPAND_DEBUG=1 的单行 debug 日志
(起点实体 N/命中 M/补入 K), 默认关低频, 保证下次坏了不沉默。

测试(AC4+AC3): graph_expand_e2e_test.go 4 用例锁死——小图(中文实体链)+
recall ⇒ 断言含 category=graph_expanded 且 Score=0.5; 10-recall 批量补入
改后 total=38(InMemory)/38(FileGraph); namespace 不匹配 ⇒ 补 0(次级阻塞
实证)。改前树副本跑同测试 ⇒ 全 0(复现结构零), 改后 ⇒ 38, 判别力成立。

FileGraph.AddNode/AddEdge 潜伏 RWMutex 自死锁(save 在写锁内取 RLock)不属
本卡, 测试改走 JSON 文件加载绕开。
2026-10-17 23:52:51 +08:00
小唯 978f27a164 feat(api): /api/v1/recall 暴露 Rust 侧单次重读行数上限截断标志 truncated(t_3aa2a8c1)
引擎已算(Rust ConsolidateResponse.truncated,t_576714de)且边车日志已打,
但 Go IPC 客户端只打日志不上抛 ⇒ /api/v1/recall 响应无法观测「结果被截断」。

- lancedb_ipc.go: 新增 SearchWithTruncation(上抛 ipcResp.Truncated;本地兜底/零结果恒 false);
  旧签名 Search 委托之(行为不变)。
- recall.go: RecallMeta 加 Truncated;可选扩展接口 SearchTruncationReporter
  (纯 Go 后端/测试 fake 不实现 ⇒ 恒未截断);粗检索两处调用点 OR 聚合。
- core.go: Recall 路由透传(hybrid/semantic/keyword + E4.2/wiki 补充搜 OR);
  响应语义 = truncated 只在 true 时暴露(键缺失 = 未截断,等价 omitempty,注释已写明)。
- 不改:阈值 20000/ZHIYI_HEAVY_READ_MAX_ROWS、降级行为、其他响应字段。
- 测试:假 sidecar 协议级验证透传 + 路由级 4 例(截断/未截断/纯Go后端/keyword),
  逐测试摘除 SearchCacheInstance(storage init() 全局 + 共享 Redis L2 ⇒ 跨 run flaky 的根因)。

实测(独立数据副本 /tmp + 独立 socket,未部署生产):
- cap=300:top_k=3/25000 响应均含 truncated:true;边车日志
  [zhiyi-ipc] search top_k=400 CLAMPED to 300 (single-read cap 300), truncated=true
- 默认 cap:truncated 键缺失;边车 CLAMPED 0 行
- 既有字段 cache_hit/candidates_scanned/count/mode/results 不变
2026-10-17 23:48:26 +08:00
小唯 0bfa7a4bc1 fix(storage): t_bd718662 /recall topK∈{1,2} 让位截断负索引 panic — FTS 尾部位让位数封顶 min(3,F,现有条数)
根因:results 已满(len==topK)时 budget 硬编码 3,topK∈{1,2} 且 fresh≥2/≥3 时
cut=len(results)-nAppend<0 ⇒ results[:cut] 负索引(线上 trace [:-2] @ recall.go:846,
48h 15 次 / 本次复现 20/20 确定性触发)。
修法(附录 C):让位前 nAppend=min(nAppend,len(results)) ⇒ cut≥0 恒成立;
让位只做等量替换不追加扩张(全仓唯一裁剪点仍 recall.go:854,AC-D 计数=1)。
topK≥3 路径 nAppend≤3≤len(results) 封顶不生效 ⇒ 零行为变化(golden diff 为空)。
新增 TestFTS_SmallTopKTailMatrix(topK∈{1,2,3}×len(results)∈{0,1,2,topK}×len(fresh)∈{0,1,2,3,5}
=50 格,断言不 panic+len≤topK+无重复 ID+尾部 FTS 集合精确)+ TestFTS_SmallTopKGolden
(topK 3/4/5 逐字输出,修前修后 diff 空)。修前 topK1_P1_F2 格 panic [:-1] 复现。
部署另开卡(本卡边界:源码+单测+本地验证,不部署)。
2026-10-17 23:15:40 +08:00
小唯 25f68831fd fix(test): 合并两个 TestMain 为单一合并体(t_e5997ca4 落盘点护栏 + t_b7a8ed9c 打分缓存护栏)
两卡分支各自在 routes 包加了 TestMain,合入 main 后 multiple definitions of TestMain
⇒ routes 包 setup failed(全套测试拦下)。按 t_b7a8ed9c 文件顶部的合并提示,把两个
TestMain 体合进 testmain_test.go 并删除 scorecache_guard_test.go。

验收(小怡实跑):
- go test ./internal/api/routes/ → ok 50.344s(修前 setup failed)
- E2E TestCommitConflictGate_E2E → PASS 3.70s;日志 '[scorecache] 已禁用(ZHIYI_SCORE_CACHE=0)'
- 测试不再污染生产 Redis:distill-score 键数 3096 → 3096(不变)
2026-10-17 22:53:43 +08:00
小唯 42fafc495f merge: t_b7a8ed9c 测试隔离(ffc5b10) —— 打分缓存测试不再写生产 Redis,经小怡在分支上实跑验证 2026-10-17 22:49:05 +08:00
小唯 d4b8688148 merge: t_e5997ca4 gap 计数器写侧统一(45726d2) —— 经小怡终审通过(+1 收敛到编译期唯一入口) 2026-10-17 22:49:05 +08:00
小唯 bd399dd007 merge: t_576714de 单次重读设界+IPC重连(c015feb) + t_c70f385c 指标N/A护栏(44fd0e9) —— 均经小怡终审通过 2026-10-17 22:49:05 +08:00
小唯 ffc5b108c6 fix(test): t_b7a8ed9c routes E2E 被 distill 打分缓存 Redis L2 静默短路 — TestMain 关缓存 + Once 活读 env
根因(非卡内假设的 flush 触发条件重构):
- a162e27 打分记忆化 + ce38465 L2 改生产 Redis(zhiyi:distill-score:*,TTL 168h)
  后,routes 包测试(无 TestMain 关闭开关)把「测试内容哈希→结果」写进生产 Redis;
  7 天内后续 run L2 命中 → mock LLM 被短路 → 计数断言 base+1 必挂。
- 实测 TestCommitConflictGate_E2E 16.5s 固定 FAIL(got 0);日志证明 flush 正常
  触发且跑完(batch=1 → DONE),缺的只是 LLM 那次 HTTP 调用。
  生产残留键 TTL≈40h(2026-10-17 复核 143207s)即污染源,TTL 自然过期。
- 对齐门禁规矩③(加缓存须给该包 TestMain 加关闭开关):
  routes 新增 scorecache_guard_test.go TestMain 设 ZHIYI_SCORE_CACHE=0;
  distill.getScoreCache 的 Once 闭包活读 env(包级变量 init 时定型,跨包测试设不了;
  生产语义不变,启动时 env 仍在此处生效)。

验证:go test ./internal/api/routes/ -count=1(不 -skip)ok 34.9s;
go test ./internal/distill/... ./internal/api/... ./internal/selfoptimize/... 全 ok。
⚠️ t_e5997ca4 分支同包加了 testmain_test.go(ZHIYI_DASHBOARD_PATH 护栏 TestMain),
两分支合并时须合并成一个 TestMain。
2026-10-17 22:34:01 +08:00
小唯 45726d26a7 fix(selfoptimize): t_e5997ca4 gap 计数器写侧统一 — +1 只在 open→closed 迁移点 + 计数器 v2 版本化重置
根因(小怡 2026-10-17 终审实测定位):写侧仍有未收口的事件式自增点,
ClosedGaps(9) 越过 TotalGaps(2),gap_closure_rate 被 -1 断言拦下但指标永久不可解读。
  ① routes/gaps.go:44  POST /close 在 Close() 之后无条件 Dash.RecordGapClosed()
     ⇒ 同一缺口每次调用都 +1(无限计数)。且实测现行注册为前缀路由
     /api/v1/gaps/close/(无 {topic} 通配)⇒ PathValue 恒为 ""、端点恒 400,
     该自增点当时不可达,但一旦注册改通配即复活。
  ② server.go:1950  gap_scan 自动修复循环 AutoRepair 成功只计数不关缺口
     ⇒ 同一缺口每轮再 +1(无限虚增,线上 9 vs 2 的主要来源)。

修复:
  - Close/RecordHit 返回 bool(是否真发生 open→closed 迁移);+1 唯一入口
    收敛到迁移点内部;RecordGapClosed 降为包内 recordGapClosed(编译期堵死
    跨包事件式自增)。
  - gaps.go 删无条件 +1;补前缀路由 topic 解析(端点自此真正可用),
    响应如实区分 closed / already_closed。
  - server.go 修复循环改「修复成功 ⇒ 关闭缺口」(RepairHandler 同语义),
    下一轮 !g.Closed 即跳过,计数只在迁移点。
  - 历史 9 vs 2 处置选 (b) 计数器版本化重置:dashboard_schema_version=
    dashboard_schema_v2 标记入 Redis HASH + 本地 JSON(既有 90 天 TTL 键,
    不新增键);首载无标记 ⇒ ClosedGaps/TotalGaps 归零并回写标记(一次性、
    可机械复核),标记在位 ⇒ 保真。代价:丢失不可信的 gap 历史累计。
  - 口径不变:ClosedGaps/TotalGaps = 缺口个数;Metrics() 越界断言保留作
    安全网(不当修复用)。

测试:
  - selfoptimize: Close×3 / RecordHit×3 / 修复循环 3 轮 ⇒ 各只 +1、rate<=1;
    SchemaV2 一次性重置 + 标记在位保真(v1 文件 9/2 载入归零、v2 值保真)。
  - routes: POST /close ×2(现行前缀注册 + {topic} 通配注册两形态)⇒ 只 +1、
    auto_* 计数器快照不变;新增 TestMain 把 ZHIYI_DASHBOARD_PATH 指到临时目录
    (先例:selfoptimize 包同款,防测试写生产落盘点)。
  - 判别力证明(/tmp 改前树 c015feb 旁证副本,不进仓库):逐字复刻改前两写
    路径 ⇒ ClosedGaps diff=6 > TotalGaps=2(rate 3.0 越界,线上 9 vs 2 同型)
    FAIL;改后同场景 diff=2、rate=1.0。

边界:只动 gap 计数器与调用点;部署不在本卡(t_ad887e7c,待牧尘放行)。
2026-10-17 22:12:40 +08:00
小唯 c015febb49 zhiyi: 单次重读行数上限 + Go IPC 读 deadline + 每 rpc 新 dial (t_576714de)
N=2 准入只拦并发堆叠、不拦单请求自身内存:一次 top_k 大 / scan_all
仍能把单条 zhiyi-consolidate 顶到 1~2GB+ 致 swap thrash(t_eedb2d39 根因)。

改动:
- rust/main.rs: ZHIYI_HEAVY_READ_MAX_ROWS(默认 20000,可覆盖)clamp
  lancedb_search top_k + scan_all/scan_deleted;响应体加 truncated:bool
  (#[serde(default)] 向后兼容旧 Go 客户端);zhiyi_ping 暴露当前 cap。
- rust/lancedb_ops.rs: scan_all/scan_deleted 的 .limit(200_000) 参数化为 max_rows。
- go/lancedb_ipc.go: ipcReq/ipcResp 加 Truncated;rpc() 用 SetWriteDeadline(30s)/
  SetReadDeadline(120s)(原 io.ReadFull 无 deadline = 侧车挂死永久阻塞的根源);
  Search/ScanAll/ScanDeleted 截断时打日志(不静默)。
- go/ipc_reconnect_test.go: TestIPCReadDeadlineBoundsHungSidecar(纯 Go,挂死侧车
  被读 deadline 兜住 ~801ms)+ TestIPCReconnectAfterSidecarRestart(env 门控,
  真 sidecar 杀→快速失败 37µs→重启→重连成功)。

隔离副本 /tmp 实测:封顶 20k peak RSS ~1.17GB vs 不封顶 200k ~1.60GB/80s。
不部署(部署另批,需小怡终审 + 牧尘放行)。
2026-10-17 21:52:06 +08:00
小唯 44fd0e95b4 fix(metrics): t_c70f385c 消费者 N/A 护栏 + daily-check N/A 显示 + 反例测试
小怡终审 2026-10-17 21:05 必须修的 4 项之一(-1 哨兵被当低分 ⇒ 触发器恒 fire):

1. selfoptimize.go: 新增 IsNA(v) 判 N/A(metricNA<0)。
2. executor.go: TGapDetected/TQualityDrop/TVValueLow 阈值比较前显式判 N/A,
   -1 不再被当「低于阈值」每轮触发(与旧 recall_hit_rate 恒=1 同类失真、方向相反)。
   配 executor_na_test.go 反例(v=-1 断言不触发)+ 正向对照。
3. tuning.go: AnalyticsHandler 建议 builder 抽为纯函数 AnalyticsSuggestions,
   统一走 ratioFor(-1/valid=0/样本不足 ⇒ 不产生建议);配 tuning_analytics_test.go 反例。
4. 两个 daily-check.sh: gap/auto/usefulness 值为 -1 时显示 —(N/A),不再打印误导性 -1/天。

测试命令(go.mod 在 go/ 下,须 cd go):
  cd go && go test ./internal/selfoptimize/... ./internal/api/routes/
(TestCommitConflictGate_E2E 为既有环境性 LLM 蒸馏用例,非本次改动路径,单独 FAIL)

生产部署仍待小怡终审 + 牧尘放行(本卡边界)。
2026-10-17 21:22:28 +08:00
小唯 b2a016e90f fix(metrics): t_c70f385c 指标口径修复 — recall_hit_rate 退役 + gap_closure_rate 断言 + 最小样本门 N=20
- recall_hit_rate 退役:分子原为 core.go 的 len(results)>0(恒=1、无区分度),
  「返回且被使用」语义已由 recall_usefulness_rate 承担(207 样本 0.98)。
  Metrics()/distributed/metrics-gauge/executor/tuning/CLI/daily-check 全链移除该键。
- gap_closure_rate 统一为「缺口个数」口径:Close() 加 !g.Closed 守卫(与 RecordHit 一致),
  根治同一缺口重复关闭导致 ClosedGaps 虚增、越界 4.5;Metrics() 加越界断言 gapRate>1 → -1。
- 所有率类指标加最小样本门 minRateSamples=20:样本<20 ⇒ -1 + valid=0,
  杜绝 auto_resolve_rate 2 样本判满分。
- 测试:metrics_retire_regression_test.go 用生产同款计数复现改前/改后 JSON;
  selfoptimize 全套通过。
2026-10-17 20:32:34 +08:00
小唯 cee68592e3 feat(deploy): 部署夹带闸门 preflight_reviewed_commits(t_3f83df64)——构建基准 HEAD 相对 tracked 批准清单比对,未评审提交默认拒绝(--allow-unreviewed / --mark-approved 显式放行);起因:01:18 生产二进制夹带 584fe37 等未评审提交 2026-10-17 08:28:01 +08:00