fix(memory-b): 返工 —— last_recall 可查询 + renderIndex 按态分述
① embedFailure 有落点了(此前只有注释)
你的判断准确:degraded 是**单次调用的返回值**,调用方看得见但运营方查不到;
criteria:282 要的是「可查询」,两者不能互相顶替 —— 就像 states 不能顶替 skipped。
实现:
- StableMem.lastRecall:最近一次 recall 的 {mode, skipped_fp, skipped_dim,
embed_error, at},由 recall 路径写入
- stats() 增加 last_recall 字段(形状照 criteria:282)
- 健康召回记 'vector_ready_unused'(A 组语义),失败记 'embed_error'/'aborted'
- **不进四态**:那是从行派生的库状态,塞进单次事件会让同一时刻读两次
得到不同数字 —— 正是 D4 要避免的
判据 3 条:真 adapter 抛错后 stats().last_recall.mode 读得到该次降级;
干净召回会覆盖;D3b† 变异(删掉写入)⇒ 判据红。
② renderIndex 按态分述,不再一句包四态
你实测的四态揉成一句是真的错:旧句尾「still searchable by exact text」
对 noVector 成立、对 spaceMismatch 在生产召回路径上**不成立**
(带 fp 召回 0 条,不带 fp 浏览 1 条 —— 与 F6 裁定的 browse≠recall 同一条口径)。
改成分态子句:
spaceMismatch → 「from a retired space (reachable by browse only, not by recall)」
unknownSpace → 「without a space label (semantic recall unavailable)」
dimensionMismatch → 「width does not match this provider (fix the config, then re-embed)」
noVector → 「not yet embedded (still reachable by exact text)」
③ 无 embedder 走常态静默(P2,一并改)
无 embedder 是出货默认,不是异常。原来 2 块 noVector 每轮都被告知一次
「召回不了」,而与 exact recall 仍能命中相冲突,且与 W11e 的静默口径相反。
现在 space() === null 直接返回 ''(只留计数行);运营侧从 stablemem_stats 查。
测试用 reportNoVectorAlways 钩子单独覆盖该措辞,生产默认 false。
判据 4 条:D5b(mismatch 必须说 browse、不得说 still searchable)、
D5c(noVector 说 text recall,且先实证 exact recall 真能命中)、
D5d(无 embedder 时不重复告知)、D5b†(单句尾部 ⇒ 红)。
另把 D5/D5† 改为需要配置空间——它们原来在无 embedder 下断言,
与 D5d 的新口径直接冲突。
实测两场景
A 无 embedder:renderIndex 只报「2 durable note(s)」,无警告;states noVector=2
B 旧空间块:renderIndex 报「1 from a retired space (reachable by browse only,
not by recall)」;states spaceMismatch=1;生产召回 0 条 skipped=1
验证
- npm test 155/155(151 + 返工 4 条)
- npm run check 10/10
- 干净副本实数见后续消息