dsh-shadow

September 23, 2026 · View on GitHub

口径:本文件只登记尚未完成的事项,是待办的唯一台账。 已完成的历史见 CHANGELOG.md;决策与依据见 adr/;术语见 CONTEXT.md

写法约定:每条给出「内容 / 依据(可点的文件或 ADR)/ 为什么现在没做 / 完成判据」四项。 没有依据的条目不写进来(本仓纪律:结论要有证据;宁可少列,不留悬空项)。

两套台账分开计数(不合流,避免口径互相污染):主台账(T/D/V/G,§一–§五)最后整理于 2026-09-12(v1.15.45)—— 当时记的是 现存 20 条(T 11 / D 6 / V 5 / G 4);B3 / T12 / T16 / V6 已闭环T13 前半 部分完成。 ⚠ 那个「20 条」是 v1.15.45 的快照,此后又在腐烂T8 已于 v1.15.64 + v1.15.65 结案,而复合计数(T/D/V/G 各自多少)没有任何东西核对 —— 与 README 的「当前版本」行、CONTEXT.md 的「45 项」是同一类(能数出来的字段被手写)。 ⇒ 改为「判据 + 命令」,不再写会腐烂的复合计数Select-String BACKLOG.md -Pattern '^### T\d+\.' 列出全部 T 标题;带 ✅ 的算已结案,其余算现存本次实测(v1.15.69):13 个标题,带 ✅ 的 4 个(T8 / T12 / T14 / T16)⇒ 现存 9 条T2 / T3 / T6 / T7 / T9 / T10 / T11 / T13(后半) / T15)。 ⚠ 更正(v1.15.69):v1.15.68 在这里写的是「T12/T14/T16 带 ✅ ⇒ 现存 10 条」——错了两处: 漏了 T8(它的标题在结案时就带了 ✅)⇒ ✅ 是 4 个不是 3 个;13−3=10 的算术跟着错, 而它自己列的名字只有 9 个(数字与列表互相矛盾)。这个错误发生在「修计数腐烂」的同一段里, 且没有任何门能抓到audit:docs 检查③只管 README 的表格行数)——教训见 AGENTS.md「能推出来的字段」末条。 口径提醒T3 标题不带 ✅ 但正文写着「已分诊…(升为 D4)」⇒ 按标记算 9 条,按实质算可能是 8 条。 规则与数字一起写,读者才知道这 9 是怎么来的。 D / V / G 的条数本轮未复核(不猜、不抄旧数);要报就现数。 已结案 23 条(B1 / B2 / B3 / T12 / T16 / V6 / D4 / D5 / 命中数累积 / _meta.json 并发 / _index.md 投影漂移 / 漂移审计工具 / 图快照顺序 / 台账版本出处 / T5 漂移键复核 / T1 A 类分诊 / T4 零引用定性 / D8 能力矩阵补三列 / D6 吸收 OpenViking 三条 / W1 审计工具漏报 / countInconsistency 接线 / M1⑥ disposition / M1⑦ 候选统计按 actor 分层(v1.15.53:M1-A′ dry run 撞出的两个契约缺维度)。 T5 已结案(v1.15.32)· T1/T4 已结案(v1.15.33)· D8 已结案(v1.15.34)· D6 已结案(v1.15.35)· 审计工具盲区已修(v1.15.36,顺带新开 T10)· 重点材料 hl_mem 深读第二遍(v1.15.37,adr/0076)· 吸收 hl_mem 三件 + 修两处真缺陷(v1.15.38,adr/0077:状态机信号表棘轮 / npm run verify 前置冒烟门 / 「错误方向不对称」成文;顺带新开 T12,并把 T11②V6 推进为部分完成)· hl_mem 第三轮深读(首次本地克隆、一手读源码)(v1.15.39,adr/0078三处自我更正 —— ① assert_transition() 不是写侧守卫(写原语不强制、≥2 处绕过);② ADR-0004 的协议在生产里是 窄面 + 默认只建议(一个 slot、observe)⇒ 实质改写 D3 的对照面;③ 路径/计数记错; 另把「覆盖率台账」改为磁盘枚举生成新开 T13 / T14 / T15,并把 T11① 推进为「有可照抄形态」)。 本地全部材料总台账(v1.15.39 起,MATERIALS.md:DSH 本体 + 6 个外来 repo 的 名册/许可/规模/已吸收/未读/优先级;由磁盘枚举生成)——首轮四路深读回报后新开 T16(平台契约核对与纠错)。 本轮新增 B3 + T6(ADR-0074:写入缺 sandboxPolicy记忆一条都落不了盘;修复已提交, 但插件 dist/ 不热加载 ⇒ 需重启宿主才能在真机复核)。 ✅ B3 已闭环(v1.15.40 第 4 轮):宿主于 2026-09-12 16:40:02 重启(PID 全新),重启后逐条实测通过 (横幅消失 / 当天新记忆文件真的在写且与本轮改过的文件一一对应 / _index.md mtime 同步更新); 唯判据 4(宿主日志侧检索)未单独核验,原因见该条。ADR-0074 的修复在真机生效。 v1.15.41:T13 前半落地 —— 结构门 audit-layers(文件级无环 / 纯模块白名单零副作用 + 腐化自检 / 方向禁令), 已接进 npm run verify46/46)。关键一改:原「按目录分层」的草案实测即被否掉core/混合脊柱,不是纯函数层)⇒ 换判据对象,只保留实测为真的三条。详见 T13「进度 A」。 注意:ADR-0074 不列在本台账的「已结案」里 —— 它从来不是待办项,是本轮新发现的真缺陷 (完整记录见 CHANGELOG.md v1.15.31);本台账只登记它遗留的两件事(B3 / T6)。 同理:v1.15.38 修的两处真缺陷(consolidated time 两路径分叉 / 时间炸弹测试)是缺陷不是待办, 其完整记录在 CHANGELOG.md v1.15.38 与 adr/0077;本台账只登记它遗留的欠账T12)。


〇、已结案(保留结论,便于回溯)

A 段残余 18 条(A2b/A1)逐条分诊(v1.15.43)—— 1 处真断线(已修)+ 工具口径缺陷(已修)

背景:T1/T4 结案后 A 段还剩 A2b 1 + A1 17 = 18 条「需人工逐条查」。本轮逐条判完, 判据 = 读定义 + 读 JSDoc + 搜生产面调用点 + 查同文件成对谓词 + 查同名第二份实现。

先修工具口径(本轮新增证据)audit-wiring 的「测试引用 N」此前不可采信 —— tools/audit-wiring.ts:37 原写成 !isProductionPath(...) 即算「测试」,而 isProductionPath 只排除 node_modules/dist/test/...dist/**.d.tsnode_modules/**.d.ts 都被算进「测试引用」。 实测:hasNoUpgradeApi 的「1」全来自 dist/agency/guards.d.tsapply 的「230」里 15 来自 node_modules已修:判据改为只认 test/ 下的文件dist/node_modules/ 两边都不算),并把口径打进输出行; 修后 hasNoUpgradeApi/renderIntent/progresssiveDisclosure 等的「测试引用」如实变成 0

逐条结论(汇总)真断线 1 · 配对包装一半 4 · 仅测试/公开面正当 7 · 无法判定 6

分类符号(文件)
真断线(1,已修)ledgerMismatchtools/toolset-authority.lib.ts:47):CLI import 了却另写内联过滤:99-102)⇒ 判据分叉隐患(离线棘轮走 lib、CLI 走内联)。git log -S "ledgerMismatch(" 为空 ⇒ 属「import 了但忘了接线」。已改为 CLI 直接调 lib 那份,并加源码级棘轮 ⑦(test/toolset-authority.test.ts:断言 CLI 调它 + --check 分支内不得再有 ledgerVerSrc !== 内联比对)
配对包装一半(4)renderRetrieved(窄版,生产用带 __pathrenderKnowledgeRetrieval)· assertNoExpansionField · assertLifecycleActive · assertScopeWithin(引擎只 import 谓词,包装零消费者)⇒ 一处家族级决定,不是 N 处缺陷
仅测试消费 / 公开面(7)sidecarDrift(JSDoc 明写消费者是棘轮)· isMetadataMemoryText · isExchangeable · applyCordis 宿主入口,零仓库内调用是设计)· writeMeta(文档明示的逃生舱)· readTemporalGraph · readGraph(world 只写不读,读 API 留公开面)
无法判定(6,全部缺「产品决策」而非代码证据)hasNoUpgradeApi(恒 true 的见证函数,真实测试引用 0 ⇒ 是「删」还是「补一条源码级棘轮」)· renderIntentobserver/core.ts:31 只内联渲染 goal+question,是否改调它)· renderIdentityModel · progressiveDisclosure + refineTree(ADR-0048 成本折叠是否进默认读路径,同一处决定)· relationForProposal(T7 三选一)

同轮记账(不是接线任务)

  • isMetadataMemoryText 的注释/ADR-0066 口径称它「服务不 parseMemory 的读路径」,而生产读路径全走 parseMemoryquery/materialize.ts:24-26query/reads.ts:113)⇒ 该句当前不实,应改为「文本面孪生,仅由一致性测试锁住」。
  • T10 的实例(新证据)persistence/snapshots.ts:26 readLatestSnapshot 的生产可达路径只有那两个零引用 reader ⇒ ADR-0071 那次「读逻辑收敛」在当前生产运行时收益为 0(与 BACKLOG.md 自述一致);工具抓不到这一类(已自曝盲区)。
  • 11 个 assert* 的「测试引用 1」全部来自 dist/*.d.ts ⇒ 「给测试用」这半目前不成立,理由应改为「给将来调用方」。

✅ 台账「实测」标签比事实强(ADR-0072,v1.15.29)—— 量证·改正默认值·签入离线棘轮

  • 由来npm run audit:drift 检测 B 第三次产出(c.kind=*)——先判它不是漂移(生产者/消费者分层), 顺带实测边界不变量 107 项全满足;但顺「台账诚实性」查下去命中真问题
  • 问题verSrc 默认 "实测"(= 本机 --version 跑出来的),但 v1.15.10 的 44 条数字全部来自 winget 目录 —— 44 条中 35 条与权威逐字一致fzf 台账 0.74.3 / 本机 0.73.1(且来自 scoop); zoxide 台账 0.10.0 / winget list 已装 0.9.9「可用 0.10.0」(抄的是**「可用」列**)。
  • 第二层(第八个「机制存在、没接线」实例)verify:toolsetexpectedVersion: nullverDrift 分支从未生效 ⇒ 版本误标不可能被发现(那个「版本漂移 0」是假读数)。
  • 修复:默认值改为 "权威核验" + 新增 tools/toolset-authority.ts(真调 winget 且把台账版本当期望值, 并记录本机读数)⇒ 清单 tools/toolset-authority.json 签入 + 离线棘轮 test/toolset-authority.test.ts(5 组,含正对照)。
  • 实测:101 条 —— 台账==权威 88 · 老化 13(正常,不判错)· falseMeasured 0
  • 自曝:测试首跑暴露我自己的判据范围错(拿全部 107 条比只覆盖 101 条的清单 ⇒ 6 条假不一致),已修。
  • 未验证machineVersion 是生成时那台机器的留档(测试不重探测,避免机器相关断言恒红); 清单未接入自动门禁"实测" 标签本轮 0 条被真正启用(本机可检出的条目台账版本都与本机不符)。

✅ 图快照读取取到最旧的(ADR-0071,v1.15.28)—— 工具体检 B 第二次真发现·闭环验证

  • 由来npm run audit:drift 的检测 B 报 name=graph.json 跨两个模块;顺查发现三层事实 (近重复 / write-only / 真实读路径是重建)+ 一个新的顺序 bug
  • 顺序 buglistDir 契约 "stable name order"、真机 localeCompare 升序,日期目录名 YYYY-MM-DD 字典序=时间序 ⇒ 两个 reader「取第一个」= 取最旧的快照 (而 graph.json 是可重建派生件 ⇒ 回读更旧的派生件 = ADR-0069 同族)。
  • 修复:新增 persistence/snapshots.tsreadLatestSnapshot(降序取第一份), 两个 reader 收敛为参数化调用(不「两处各修一遍」—— 那会保留「多处表达」的分叉结构)。
  • 复现:修复前 实际返回 day07;修复后 6/6(含③乱序插入仍取最新、⑤无快照→null)。
  • 闭环:重跑审计,检测 B 12 键/30 处 → 11 键/28 处、该键已消失
  • 诚实标注:两 reader 当前零调用(属 T4)⇒ 运行时收益为 0,收益是消除地雷 + 消除重复
  • 未处理:快照无限增长(每天一份、从不清理)—— 属存储治理,另议题。

✅ 投影漂移审计工具(ADR-0070,v1.15.27)—— 造工具·两层标定·首次使用抓到第 7 处

  • 动机:v1.15.22–26 连续五轮同族缺陷全靠手工找(体力)。目标第 (4) 条要的是能力
  • 工具tools/audit-drift.ts —— 检测 A(新鲜度只看进程,精度高)+ 检测 B(判据跨模块表达,线索级)。
  • 标定:夹具 10 组 + git 历史真缺陷0c4e06b 旧报 :41/:215、当前版报 0)。
  • 首次使用即产出:抓到 observer/judgment.ts:26 isConcreteLocator —— 对 glob / git ref 做存在性检查 ⇒ 假冲突 ⇒ 结论假降 evidence_stale + 置信假降;实测 12 条(0.49%)/ 17 处。已修。
  • test/evidence-missing-criterion.test.ts(含 ③ 反向不变量 + ⑤ 跨消费者一致性)。
  • 未做:检测 B 的 12 个键只复核了 1 个(其余 11 个为未复核线索);工具未接入自动门禁。

_index.md 的投影漂移(ADR-0069,v1.15.26)—— 量证·修三层根因·加锁完成

  • 实测_index.md 停在 09:34:01,之后 **623 条(8.54%)**记忆对索引不可见,而主题召回看得见。
  • 三层根因:① indexDirty进程内 Set(看不见别的会话写入);② ensureIndexCacheif (warm) return(不重读新文件);③ 磁盘已删的从不清出缓存(幽灵条目)。
  • 修复:新鲜度问(复用已有的 shadowSourcesFingerprint,此前只接给 nodes.jsonl
    • 每次增量对账(只 listDir、只为新文件读内容)+ 先采指纹后读源
  • test/index-freshness.test.ts 5 组断言(绕过本进程 flush 放文件,模拟别的会话写入)。
  • 真机核实:读 dsh-fs-locallistDirectory 实现 ⇒ target 必给、文件给 size ⇒ 指纹可判定; 真语料 7336 条目 / 62 ms
  • 未验证:真机端到端(需重启);10 万级对账成本;幽灵条目真机量级未报(探针被 _index.md 说明文字污染)。

_meta.json 的并发丢更新(ADR-0068,v1.15.25)—— 发现·修根因·加锁完成

  • 由来:v1.15.24 修命中数累积时放大出来的 —— 那段 RMW 从「几乎不执行」变成「每次有命中的召回」。
  • 根因:能力就在 fs 契约里(FsWriteIntent.replaceIfVersion / FsInfo.version / FS_STALE_VERSION), 而插件一处都没用
  • 修复:① mutateMeta 事务(stat → read → mutate → 带守卫写 → 冲突重试 ≤3); ② 先 stat 后 read 的顺序敏感点;③ runCompact 从「覆盖全量」改「应用 compacted delta」 (这条与正确性相关compacted 丢了会让已归档原子重回活跃索引);④ 无 stat 时诚实降级。
  • test/meta-concurrency.test.ts 5 组断言,mock 忠实实现版本语义(不重蹈 v1.15.15 的坑), 核心断言「并发 ⇒ 双方更新都保住」。
  • 未验证:真机 host.fs 端到端、真并发时序、重试耗尽极端用例;需再重启一次才在运行进程生效。

✅ 命中数累积触发条件(ADR-0067,v1.15.24)—— 发现·复现·修复完成

  • 缺陷query/query.ts 累积 hits/confirmedBy 用的是 servedDetail (= tier !== "L0" && render.includes("…")),而 tierFor 对「动作行占比 > 60%」的记忆返回 L0 ⇒ 真语料 74.3%(5342/7185) 的记忆永不可能被记命中。
  • 端到端佐证:本机 7185 条记忆、_index.md 1.8 MB、多次召回后 .shadow/_meta.json 不存在
  • 修复:改用 servedRels(每条被返回的);servedDetail 继续服务冷却台账。
  • 修复前的红test/hit-accumulation.test.ts ②): actual: undefined, expected: true;修复后 4 组断言全过。
  • 连带恢复OBSERVED/VERIFIED/TRUSTED 三态(此前生产不可达)、forgetminHits 保护。
  • 未验证:需再重启一次宿主才能在真机看到 _meta.json 出现。

✅ B1. 重启 DSH 使插件代码生效 —— 已闭环(2026-09-11 用户重启)

  • 完成判据(全部实测通过)
    • read_shadow({mode:"toolset"}) 返回台账107 项(105 reference + 2 provider)、17 分类
    • read_shadow({mode:"toolset", need:["全文搜索"]}) 返回能力预检⬜ 全文搜索 → ripgrep) —— 而不是 _index.md;且三条硬边界(预检不是闸门 / 装完本会话不可见 / 缺件只能上报)正常输出 ✅
    • read_shadow({mode:"toolset", category:"搜索与查找"}) 按分类过滤生效 ✅
  • 意义v1.15.13v1.15.22 十一个版本的插件改动首次在运行进程里生效。 ADR-0057 记录的两种加载行为(host 组合行热加载 / 本插件 dist/ 不热加载)得到再次确认
  • 教训(值得留档)toolset 那一版(v1.15.13)修的是「机制存在、接线断了」, 而验证它需要一次人工重启 —— 这类缺陷的验证成本远高于修复成本。

✅ B2. 多粒度检索层的产品方向 —— 按推荐 ① 决策(用户「按推荐」授权)

  • 决定采 ①「按证据改」 —— 形态是「单索引 + 层级表示 + 路由」, 不做「多库全量扇出」。
  • 依据adr/0060-multi-granularity-retrieval-form.md(三条选项与实测表)
    • ADR-0065 的独立先例(OpenViking 的 HierarchicalRetriever 就是「路由 + 目录递归 + 重排」, 且其层级分数传播 score_propagation_alpha 默认 1.0 ⇒ 层级买的是召回路径,不是分数平滑)
    • adr/0065 记的实测:离题噪声有阈值单库 0.000 vs 无阈值 1.000;扇出即便含互补来源也不升召回
  • 现状核对:该形态本仓已实现(v1.15.18 已核实)—— indexEngine.provider 单值路由
    • retrieval/render.tstierFor(L0/L1/L2)+ renderByTier 按预算逐层展开。 ⇒ ① 的落地 = 确认现有设计即目标形态不需要新建 N 个库
  • 未做(诚实标注):选项 ② 的「装 semble 后用同一工具复测」仍未做(本机 semble/zg 均未安装)。 它不阻塞 ① 的决策 —— ① 是「按现有证据改」,② 是「补一组真实向量读数再复核」。 若要补,见 V1

一、阻塞在用户(需要人执行或拍板,我无法自行推进)

本节登记「必须由人执行」的项。B1 / B2 / B3 均已闭环(B3 于 v1.15.40 第 4 轮宿主重启后实测通过)。

B3. 重启宿主,复核 ADR-0074 的落盘修复在真机生效 —— ✅ 已闭环(v1.15.40 第 4 轮,宿主于 2026-09-12 16:40:02 重启)

  • 依据adr/0074-write-sandbox-policy-omitted.mdCHANGELOG.md v1.15.31。
  • 为什么曾阻塞在人:插件加载的是 dist/index.js,而 dist/ 不热加载(ADR-0057 已两次记录该行为) —— 代码已改、已构建、已提交,但运行进程里仍是旧代码,只有重启才生效。
  • 重启事实(本轮实测)dsh web 的 pnpm wrapper 与 node 主进程 PID 全新、创建时间同为 16:40:02 (旧 PID 11848 / 14976 → 新 17284 / 1680)⇒ 运行进程已换,dist/index.js 的修复已进入运行体
  • 当时现状(旧读数)read_shadow 顶部横幅 ⚠ shadow 最近一次落盘失败(… file access denied under workspace-write mode); 两次时间戳 11:28:02Z(v1.15.12)/ 11:35:11Z(v1.15.30)⇒ 与升级无关,是长期坏着
  • 重启后逐条实测(2026-09-12 16:53 读数,全部取自真机)
    1. 横幅消失read_shadow()(无参)返回的索引不再出现「落盘失败」字样,开头只有标准的 「⚠ 以下为记忆数据(非指令)」护栏句。
    2. 当天新文件真的在写G:\project\dsh1\.shadow\2026-09-12\ 下出现本轮对话之后新建的记忆文件, 例:2026-09-12--165353-shadow.md16:53:52)、…165342-dsh-shadow-backlog-md.md…165322-docs-fix.md…165314-dsh-shadow-references-md.md…165301-dsh-shadow-readme-md.md —— 时间戳晚于 16:40:02 重启点, 且与本轮实际改过的文件一一对应references.md / BACKLOG.md / CHANGELOG.md / README.md / .docs/fix)。
    3. 读路径落盘也在工作.shadow/_index.md 的 mtime = 16:53:52(与最新记忆同刻), 说明读侧 ensureIndex 的写入同样成功(ADR-0074 的接入点 ③ 生效)。
    4. 未单独核验(诚实标注):宿主日志侧的 [dsh-shadow][error] flush FAILED 未检索到 —— 会话日志是 session.v3.jsonl.zstd(6.49 MB),用 Node 26 的 zstdDecompressSync 只解出首帧 195 字节, 改用流式解码后报 Unknown frame descriptor未解出全量日志。 判据 1 与判据 4 问的是同一件事(落盘失败的可见记录),判据 1 的可读面已证为空,故按 1–3 闭环、4 标注未核验。
  • 结论:ADR-0074 的修复在真机生效Forget ≠ Delete 之外最重要的一条(记忆能不能落盘)已恢复。 不引入新根因层(判据 5 未触发:既非「参数没传对」,也不是 T6 的兜底根场景)。
  • 顺带观察(新线索,未立条目):今日 .shadow/2026-09-12/ 已有 1034 条记忆 (read_shadow 索引自报),文件名显示几乎每次工具调用/每次改文件都产生一条(如 2026-09-12--165314-dsh-shadow-references-md.md)。 采集粒度与语料规模的关系未评估(可能影响 T2 的 85 条 B 类线索分诊与 T9 的召回收益读数)⇒ 记在这里,先不立条目。

二、待分诊(第 8/9/11 轮已分诊一部分,T1 / T2 / T4 未完成)

背景:v1.15.13v1.15.18 连续挖出四类同源缺陷,共同特征是「机制是对的, 断的是谁调用它 / 谁写这个值」,而单元测试全绿。故做了审计工具 tools/audit-wiring.ts(A 类「导出但生产无调用点」、B 类「只被读、无写入点的判断值」)。 工具已标定npm run audit:wiring:selftest,8 组断言全过),但其输出是线索不是结论, 必须逐条人工分诊 —— 这部分只做了一小部分

✅ T1. 审计 A 类线索 —— 已逐条分诊结案(v1.15.33)

  • 结论:A 段 33 条(实跑计数,原记 30)全部落格,结果回写 adr/0062「补记(v1.15.33)」。 明细表(含每条的 文件:行号 证据)见该节,此处只留结论与两处对本节的更正
  • 工具盲区(本节原写「注释 / 间接调用 / 平行 API」三条): 已在 v1.15.36 逐条实测更正并处理 —— 「注释」记错了,真盲区是「字符串」(方向也从「误报」更正为漏报); ②③ 已分桶为 A2a / A3(不伪造精度,改为如实分类);另发现第 4 类(传递性死代码,未修) ⇒ 升 T10。 详见 adr/0062「补记(v1.15.36)」。A 类精度低不是「工具差」,而是本仓有意导出测试向 API 与这些结构所致。
  • 两处对原文的更正(原文措辞含糊,此处收紧)
    1. progressiveDisclosure / refineTree 不是「误报」 —— 原文写「误报,但值得记」。 准确表述:它们仅测试消费test/knowledge-engine.test.ts:53,59), 生产命中只有 core/knowledge/engine.ts:8注释 ⇒ 属「注释造成的假调用点」, 按三选一应落零引用/仅测试消费这一格,不是「有生产调用点」意义上的误报。
    2. renderIntent 不是「生产有调用点」 —— 它是零引用observer/core.ts:20 用的是 intentOf,渲染在 :31 内联 ⇒ 见 ADR-0062 §4。
  • 本节未列、但本轮新报出的 3 个符号(原文那批 30 条没有它们): renderExperience误报query/lenses.ts 的 experience 路径(renderExperience 作回调传入)、 auditDrift零引用,已删除)、countInconsistency真断线,已接线)。
  • 真断线(唯一一处)countInconsistency —— 清单自洽检查生产从未执行。 已在 writeFileSync 之前接线 + process.exit(1) 拒绝坏清单; 锁见 test/toolset-authority.test.ts ⑥ 接线棘轮(断言「CLI 调了它」,非「函数存在」)。
  • ChangeSet 与 D1 的关系(重要,防误读):本轮判 ChangeSet 为「接口可达」 (projection-store.ts:85 的 store 工厂在生产被调用),而 D1 说「未接线」仍然成立 (无生产实例化点)。两条不矛盾:一条说接口可达、一条说没人实例化 ⇒ D1 维持原判

T2. 审计 B 类线索未逐条分诊(85 条

  • 依据:同 T1;npm run audit:wiring 输出 B 段(计数为 2026-09-11 实跑所得)。 v1.15.22 起为 85 条(原 81):tools/*.mjs.ts 后工具开始扫自己, 新增的 4 条全部来自 tools/audit-wiring.lib.ts 自身的字符状态机c === "\\"c2 === "*" 这类单字符局部别名比较)—— 正是下面「短局部变量别名」已记录的噪声类型。 ⇒ 计数变化有解释、已核对,不是新缺陷(见 ADR-0064)。
  • 已知的主要噪声来源(不必再逐条看):
    • typeof x === "object" | "string" | "number" 形态(agent=objectnested=stringv=object…);
    • mode=*(mode 由调用方/模型传入,属外部输入,分支可达);
    • code=ENOENT(Node 错误码,不由本仓生产);
    • type=text-delta | finish(宿主流事件类型,外部输入);
    • 短局部变量别名(st=lc=o=s=v=c=m=)—— 工具无作用域分析,属误报。
  • 仍需核实的少数(第 8 轮已开始):
    • status=archived —— 见 T3;
    • kind=deletedcore/change-set.ts)—— 与 D1 同源(ChangeSet 未实例化);
    • status=compared / status=exploredepistemic/simulation/guard/reality-boundary.ts:5)—— 需确认是不是外部数据(模拟结果的形态);
    • kind=metadata / kind=sessioncore/lineage/validator.ts:18)—— 需确认 AtomKind 的取值来源。
  • 完成判据:可疑项核准到「外部输入 / 真断线」;真断线进 D 段。

T3. 已分诊:pinned / archived 两个「人工权威状态」无任何入口(升为 D4)

  • 原线索:审计 B 类报 status=archived 无写入者(core/forget.ts:18core/lifecycle.ts:28)。
  • 分诊中扩展(第 9 轮):pinned 同样无写入者 —— 生产代码只写 pinned: falsecore/memory.ts:74core/writer/materialize.tsquery/query.ts 三处), pinned: true 全仓零处(三路 grep 核实:字面量、pinned:pinned =)。
  • 两者的可达性
    状态读点语义生产可达?
    pinned: truecore/lifecycle.ts:27(→TRUSTED)、core/forget.tsm.pinned 分支(→永不被遗忘「人工显式信任」恒为 false
    status: "archived"core/lifecycle.ts:28(→ARCHIVED)、core/forget.tsarchived/superseded 分支(→立即遗忘「人工归档」无写入者
  • 判定:这是「已文档化但无入口的能力」,不是「接线断了」。三条依据:
    1. _meta.json 是 Derived Artifact(ADR-0003:Memory 文件 = source of truth, _meta.json 可被 rebuild-index 重建)⇒ 手工编辑它会被下次重建抹掉, 故「人来改 meta」不是设计上的入口
    2. 没有任何命令 / 工具 / 元数据约定能置 pinnedarchived(已查:插件的工具面只有 read_shadow / recall_shadow / shadow_query,均无写侧动作);
    3. 实现与设计声明不一致MEMORY.md:90 明写生命周期「从 meta 信号派生…… 不做写侧硬状态迁移、纯按信号推导」,而 lifecycleOf 的两条最前置判断 读的恰恰是写侧 rec.status / rec.pinned
  • 文档承诺(需一并处置)
    • README.md:35 承诺「生命周期状态机 NEW → … → ARCHIVED」;
    • README.md:177 承诺「pinned 永存」(status: stale/superseded/archived 默认排除);
    • README.md:178 承诺生命周期「NEW → … → SUPERSEDED/ARCHIVED(pinned→TRUSTED 优先)」;
    • MEMORY.md:90 / CHANGELOG.md:1637 同口径列出 SUPERSEDED/ARCHIVED
  • 为什么这不是「顺手接线」就能解决的:三条路各有代价,见 D4(需决策)。
  • 完成判据:见 D4。

T3-orig(保留原始线索,便于回溯)

  • 第 8 轮审计输出:status=archived 读于 core/forget.ts:18, core/lifecycle.ts:28; 全仓 "archived" 只出现在读侧retrieval/rank.ts:103权重表,生产无写入点。

✅ T5. 漂移审计检测 B 的各键 —— 已结案(v1.15.32)

  • 结论:真仓库 11 键逐个复核完毕,各落「正当分层」或「同形不同义」,新增 1 处真漂移已修。 完整逐键表见 adr/0070「补记(v1.15.32)」
  • 先修了工具自己的漏报(这是本轮最值得记的一条):检测 B 的原正则字符集不含 ?c?.status === "supported" 只从 status 起匹配,键退化成另一个 ⇒ 与不带 ? 的归不到一起。 后果:判据源自己epistemic/world/guard/claim-admission.ts:6isAdmissibleClaim)从 B 段消失, 于是「两处各自重写、没用唯一判据源」这件事没有任何线索指向。已修(允许并归一 ?.)+ 回归锁 ⑤b。
  • 真漂移c.status=supported —— 判据源已存在(isAdmissibleClaim),却在 epistemic/world/builder/representation-builder.ts:9同文件已 import 该模块)与 query/world.ts:42 各手写一遍。性质同 ADR-0063 / D5(同一条规则多份实现)。已收敛到唯一判据源; 新锁 test/claim-admission-single-source.test.ts(含源码级棘轮正对照)。键已消失。
  • 附带项(本轮已做):台账「两级边界」不变量(referencedegradesTo 必须表明 「不影响插件行为」/ provider 必须给确定性退路 + provides + install)此前只有一次实测无断言 ⇒ 已加为 test/toolset-catalog.test.ts
  • B 段小计11 键 / 28 处 → 10 键 / 25 处
  • 未做(诚实标注):② 的棘轮是源码级正则而非类型级 —— 换个写法("supported" === c.status、 经变量间接比较)会漏;它挡的是最可能的回归形态,不是全部。工具仍未接入自动门禁(V6 未变)。

T5-orig(保留原始线索,便于回溯)

  • 依据adr/0070-drift-audit-tool.md「未做」;npm run audit:drift 的 B 段。
  • 现状:真仓库上 B 段报 11 个键 / 28 处v1.15.28 修掉 name=graph.json 后从 12/30 降下来)。 本轮(v1.15.29)复核了第三个c.kind=provider / c.kind=reference —— 判为正当分层toolset.ts 声明 kindCapability.kind: ToolKind,类型必填)↔ core/toolset/exec.ts 消费它), 并顺带实测了它文档化的边界不变量(toolset.ts:3「两级台账必须分清」+ degradesTo 规定 reference 填「不影响插件行为」):107 项全满足 ⇒ 边界没糊。 但那条不变量仍没有棘轮(本 ADR 只做了实测,未加断言)⇒ 见下「附带项」。
  • 已复核 3 个,全部有产出res.status=not_found(第 7 处真缺陷)、name=graph.json(ADR-0071 真漂移)、 c.kind=*(正当分层 + 边界实测)。
  • 未复核的 9 个键c.status=supported / e.kind=user / err.code=ENOENT / kind=error / r.status=unavailable / type=anti_pattern / type=principle / v=string
  • 附带项(本轮实测得出,未做成棘轮):台账「两级边界」不变量(reference 的 degradesTo 必须表明 「不影响插件行为」、provider 必须给确定性退路 + provides + install)当前 107 项全满足, 但 toolset-catalog.test.ts 未覆盖它 —— 建议补一条棘轮(改动小、价值明确), 避免以后加条目时把边界写糊。本轮未做(属 T5 的收尾工作)。
  • 几个看起来值得优先看的未复核,只是排序依据):
    • err.code=ENOENTcore/semble.ts + evidence/zg.ts)—— 两处都在判「CLI 未安装」, 口径若不同会撞 ADR-0049「缺件不静默」;
    • r.status=unavailablecore/index-engine.ts + query/query.ts)—— provider 不可用处理。
  • 经验:已复核的三个全部有产出(2 个真问题 + 1 个正当分层但顺带发现别的)—— 说明检测 B 的 产出率比预期高,值得把剩余 9 个逐个过一遍。
  • 纪律:B 只答「同一键出现在多个模块」,答不了「两处口径是否一致」不得据 B 定罪; 生产者/消费者分别表达同一判据在分层架构里可能是正当的
  • 完成判据:11 个键各落「正当(给出分层理由)/ 真漂移(给出修复 + 锁)」二选一,结果回写 adr/0070

✅ T4. A 类里「生产与测试引用皆为零」的导出符号 —— 已全部定性(v1.15.33)

  • 依据:A 类逐条核实(adr/0062「补记(v1.15.33)」§4 的处置表)+ ADR-0070/0071

  • 已处置

    • isCognitiveAtom —— 已删除(ADR-0066)。理由不是「死代码」,而是它的规则与 validateAtomProjection 完全重复,留着会成为第四份口径
    • readTemporalGraph / readGraph —— 保留 + 改正 + 收敛(ADR-0071): 它们是持久化层的公开读 API;发现并修掉顺序 bug(取到最旧的快照), 两份近重复逻辑收敛到 persistence/snapshots.ts接线与否另议mode:"temporal" 的真实读路径是 buildTemporalGraph(重建), 不应把 reader 接成缓存(会重蹈 ADR-0069 的「缓存与源头脱钩」)。
    • auditDrift —— 已删除(v1.15.33)。全仓零引用(生产+测试+夹具皆无), 且它没有任何信息价值:只是把两个检测器打包成对象,删掉不减少能力。
    • isExchangeable(+ 孪生常量 EXCHANGEABLE_KINDS)—— 已收敛(v1.15.33): isExchangeable再手写一遍同一三元素数组,而 EXCHANGEABLE_KINDS 是唯一源。 与 T5 的 c.status=supported 同型(同一判据多处表达)。
    • countInconsistency —— 已接线(v1.15.33,T1 的真断线项)。
  • 各符号最终定性(逐条落「接线 / 删除 / 保留并注明」):

    符号处置一句话理由
    auditDrift删除空壳包装,零引用且无信息价值
    countInconsistency接线有明确用途注释的生成期校验,此前从未执行
    isExchangeable / EXCHANGEABLE_KINDS收敛唯一源已存在却被重写;类型系统管不到内联字面量
    renderIntent保留并注明完整形态渲染器(question/desired_outcome/constraints),实际读侧在 observer/core.ts:31 内联只取 goal;删除会让完整形态失去唯一落点
    renderIdentityModel保留并注明同上型:渲染 IdentityModel(时间线版本模型),而 read_shadow 的身份输出走 soul/identity.tsrenderIdentity不是同一对象)⇒ 写完的模型无专属渲染出口
    relationForProposal保留并注明(+ 新风险)原文已注「留接口」;本轮新发现它忽略入参 ⇒ 升 T7
    writeMeta保留并注明meta.ts:92 已声明是「明确要覆盖」的逃生舱;生产写 meta 一律走 mutateMeta(ADR-0068)
    isMetadataMemoryText保留并注明ADR-0066 已决定保留(服务不 parseMemory 的读路径)
    hasNoUpgradeApi保留(暂不处置)stance/agency/guards.ts 唯一未被 stance/agency/engine.ts:4 import 的导出(同文件另 15 个都被用);「遗漏接线」还是「有意保留」本轮未判定,且删它要动 invariant 面
    progressiveDisclosure / refineTree / renderRetrieved保留并注明adr/0048 ①/② 的目标能力,实现完整;是否启用属产品决策(默认路径可能刻意不做成本折叠)⇒ 见 T1
    7 个 delegation assert* 包装保留并注明「谓词接线、assert* 不接线」是一处决定,不是 7 处缺陷;按家族加注,不逐条删
  • 四处代码注释已加(供后续读者不再重复分诊): stance/delegation/guard/expansion-guard.ts(家族级)、core/knowledge/cost.tscore/intent.tsidentity/timeline.ts

  • 两个仍未决的产品问题不是「没做」而是「需你拍板」): ① progressiveDisclosure / refineTree接线还是有意不启用? ② renderIntent / renderIdentityModel并入某条读路径还是长期作为备用渲染器? ⇒ 这两问都属 adr/0048 的目标能力范围,本轮不擅自决定

  • 完成判据余 6 个各落「接线 / 删除 / 保留并注明理由」已达成(上表逐条落格)。

T6. 兜底根场景仍走部署 fallback —— ADR-0074 明确未覆盖的那一半

  • 依据adr/0074-write-sandbox-policy-omitted.md「负 / 已知边界」第一条; core/scope.tsDEFAULT_SHADOW_ROOT
  • 现状(本轮实测确认的边界,不是推测):ADR-0074 的修法是「写入携带该会话自己的策略」, 而策略只能从 session 求得。但 resolveShadowScope既无显式 root、又解析不出 session cwd 时 会落到兜底根 ~/.dsh-observer/shadow —— 那一刻没有 session 可问sessionPolicy 返回 undefined, 门面恒等返回原 fs ⇒ 该场景下写入仍走部署 fallbackprocess.cwd() + workspace-write)。 兜底根几乎不可能落在 process.cwd() 之下 ⇒ 该路径的写入仍会被围栏拒绝
  • 为什么这是独立的待办而不是「顺手再修」:它不是补一个参数能解决的 —— 需要先回答 「兜底根这条路径本身是否该存在」:
    处置代价 / 前提
    ① 取消兜底根,改为「无 session 就不写」scope.ts:13 的注释口径冲突(那句写的是「保证可写,而非不写」)⇒ 要先改口径,并确认没有依赖该根的历史数据
    ② 保留兜底根,但显式落一条「本写入未受会话授权」的可见信号需定义信号形态(lastFlushError 已有前例),并确认不违反 ADR-0049「缺件不静默」
    ③ 维持现状 + 文档写明该场景不受支持最省,但须知这是「已文档化的不可用路径」而非「能用」
  • 注意(防过度表述):本条的优先级低于 B3 —— B3 是「主路径(会话工作区)全失效」, 本条是「边角路径(兜底根)未覆盖」。真机上主路径一旦恢复,本条可能长期不被触及。
  • 完成判据:三选一落定;若选 ①②,须附一条能复现该场景的测试(无 session + 无显式 root)。

T7. relationForProposal 忽略入参(本轮新发现,零引用所以当前无害)

  • 依据adr/0062-wiring-audit.md「补记(v1.15.33)」§5;T4 分诊时顺带发现。
  • 现状(读源码核实)temporal/edge.ts:30 export const relationForProposal = (_n: any): TemporalEdge["relation"] => "evolved_into"; —— 形参名带下划线前缀 = 有意不用,恒返回常量。
  • 为什么现在无害、将来有害:它当前是零引用adr/0062 补记 §4 已登记「保留并注明」), 所以不影响任何行为。但一旦按名字接线(例如让 TemporalEdge.relation 由它决定), 调用方传什么节点都会被静默丢弃、统一成 evolved_into
  • c.status=supported / isExchangeable 那两族的区别(重要,别混为一谈): 那两族的危险是「口径分叉」(同一判据多处表达,其中一处漂移); 这一处的危险是「掉参数」(签名收了参数却不用)。两种都要修,但测法不同 —— 口径分叉可加「唯一源」棘轮;掉参数只能靠行为断言(传不同节点必须得到不同关系,或明确删掉形参)。
  • 现有注释的不完整处temporal/edge.ts:29 已写「保留:…v0.26 不跑 reflection,留接口」, 但没写「它忽略入参」这一点 ⇒ 本轮已在代码注明。
  • 完成判据:三选一 —— ① 接线并让 relation 真正由入参决定(附行为断言); ② 删除(并确认 TemporalEdge.relation 的取值不依赖它);③ 保留并改签名去掉未用形参(_n: any)()),使「它不消费输入」在类型层面显式。

T8. 静默降级 —— ADR-0049 的 7 条候选缺陷 + 2 处开关缺陷(D8 实测产出)—— ✅ 已结案(B: v1.15.64 / A: v1.15.65)

  • 依据adr/0073-hl-mem-benchmark.md §2 行① / §3(D8);D8 实测(逐条读代码,见 README.md「默认开关」表的降级行为列与表注)。
  • 判据:ADR-0049 的枚举是「unavailable 状态 / flush warn / debug trace 三者至少一个」—— console.log 不算。据此,以下 7 条关掉或缺件后行为退到某处却无任何可见信号

⚠ 这张表是 v1.15.34 的立账快照,保留原样(以免丢失「当时查到什么」)。 它的位置列已按引用纪律换成符号名 —— 原始行号(如 core/writer/core.ts 第 69 行)在后续版本里 早已漂移,留着一串查不到东西的行号比不留更糟(见 AGENTS.md「引用纪律」)。 当前状态与修法见下面的「状态」小节;本节不复制 ADR 的表(同一件事只写一处)。

#能力位置(符号,非行号)静默形态(v1.15.34 实测)
1llmRecallcore/writer/index.tsrecallSelect + core/writer/llm.tsstreamText catch回退确定性 renderRecovery 无标记label:"" 使 catch 的日志分支也不触发 ⇒ 连 log 都没有(最彻底;且 !llm/!route/finish 出错三条路径从不进 catch
2summarycore/writer/materialize.tssummarizeTurn / patchSummary缺 llm/route/finish 出错均提前 return ""(不记日志)⇒ 文件里只是「没有摘要」,与「尚未生成」不可区分
3recall(语义 B 档)core/writer/index.tsexpandTerms + query/query.ts 的召回打分段expandTerms → [],输出与「本来就没配」完全一致。README.md 自己写「静默退回 A 档」= 文档已承认,但代码未给三条信号中任何一条
4queryLog默认开query/observatory.tsrecordQueryObservationcatch {},注释自陈「写失败静默」)观测数据丢失,读侧显示「尚无记录」⇒ 与「从没查过」不可区分。默认开启项里的静默 ⇒ 优先级最高
5recall.cooldownTurnsretrieval/ledger.tsreadLedger(读失败静默当空台账)+ writeLedger(写失败仅 console.log冷却静默失效README.md 亦自陈「写失败降级为『不去重』」
6projectionStorecore/projection-store.tsload / invalidate / invalidateFor缓存读失败/坏行 → 全量重建,零信号。区别于前五条:结果仍正确(ADR-0049:38 明列「缓存不是真相」),只损失性能 ⇒ 优先级最低
7episodescore/writer/materialize.tsrecOf(解析失败静默)、Episodes 派生段(derive 失败仅 log)、rebuildIndex_index.md③ 最重:read_shadow() 无参读到静默陈旧的索引 —— 与 ADR-0069 同族,而 ADR-0069 只修了「新鲜度问源」
  • 另两处开关/取默认值缺陷(v1.15.34 立账,均已在 v1.15.64 修复,见下「状态」):
    1. episodes 关不掉showInIndex: 0core/writer/core.ts|| 8 吞掉 ⇒ core/writer/materialize.tsepisodeShow > 0 恒真(死分支)gapMinutes: 0 同样被 || 60 吞 ⇒ Math.max(0, …) 永不生效。根因:用 || 取默认把「显式 0」与「未传」混为一谈。
    2. kg 被排在「默认」列却不是 config 键(只是 per-call 参数)⇒ 已在 README 表注⑤标明。

T8 状态(本台账只记状态与指向,不复制 ADR 的表 —— 同一件事只写一处)

部分版本决策记录(权威表在这里结果
B:显式 0 被默认值吞掉(6 处,含读代码时找出的 4 处同族遗漏)+ 比较点判据两份实现v1.15.64adr/0084 §2.1 缺陷表 · §2.2 判据 · §3.3 工具 · §5 结果#1 🔴
A:7 条静默降级 + 复查时补的第 8 条(abstractsv1.15.65adr/0085 §2 机制 · §5 正当静默类判据 · §6 落地清单 · §8.5 self-fixverify 55/55
  • B 的结果audit-wiring b_keys 115→97(−18,逐键取证 18/18,见 .docs/fix/2026-09-12/t8b-typeof-vs-real-audit.ts)· drift_keys 11→9 · drift_sites 28→23 · wiring.a_total39(未变 ⇒ 新 numOr 确有接线)。 新纪律:core/util.ts:numOr(默认值回落的唯一判据);forget.minHits|| 1 判为正当并保留minHits: 0 = 关掉遗忘,而该语义已由 enabled: false 承担)。 仍待办min > 0 的调用点未逐条审语义(当时 core/ 下 19 处,本轮只按必要判据 min <= 0 筛)。
  • A 的结果8 条里 7 条上了可见信号(统一进「能力降级台账」→ getFlushWarn() 横幅, 一处渲染覆盖全部读 mode),1 条裁定为正当静默projectionStore类判据= 「读者拿到的内容逐字节不变」⇒ 冗余副本可静默,改变内容的必须报,见 adr/0085 §5.1)。
  • A 的未做 / 缺口(不是已验证)episodeParse / episodes 两个生产者只有接线、没有端到端触发 (本仓夹具没有能让 parseMemory/deriveEpisodes 抛异常的输入)· recallLedger写失败留痕未端到端触发 (需 cooldownTurns>0 && servedDetail.length)· knowledgeNavigate 的留痕无专门断言 · 一次观察未修:台账坏件时本回合若走到写台账会覆盖坏件(已在横幅「后果」里写明,未改旁路写入)。
  • 已在本轮修掉的同类(第 3 处)knowledgeEngine.enabled 生产零读取 —— query/reads.ts 无条件建树、createKnowledgeEngineconfig 却从不使用(已删死形参)。 ⇒ 原文档声称的「默认关 / enabled: true 启用」是一处不存在的开关,已在 core/types.ts:36 与 README 表注④就地校正。这是本轮 D8 唯一的代码改动。
  • 为什么当时没做(v1.15.34 的诚实标注,保留):7 条各需不同的可见信号形态(有的该打 warn、有的该在输出里加标记、 有的该复用已有的 flushWarn 横幅),且第 6 条按 ADR-0049 的「缓存不是真相」可能根本不该报。 ⇒ 逐条定形态是独立工作量,当时只做清点与立账(D8 的授权范围是「补表 + 记缺陷」)。 事后看:这条判断对了一半 —— 形态确实各异,但 v1.15.65 发现它们可以共用一个渲染点getFlushWarn()),于是「逐条定形态」的边际成本远低于当时的估计。 ⇒ 教训:「逐条都要单独做」这个判断,本身应该被验证一次再拿来当排期理由。
  • 完成判据:7 条各落「补可见信号(给形态 + 测试)」或「判定为正当静默(给 ADR-0049 依据)」; 两处开关缺陷各修(||?? 或显式 undefined 判定)+ 加锁。 ✅ 全部满足:B 部分 v1.15.64(adr/0084);A 部分 v1.15.65(adr/0085,7/8 上信号 + 1 条裁定, 复查时另补第 8 条 abstracts)。剩余缺口见上方「A 的未做 / 缺口」,已结案

T9. sidecar 的读路径收益真机规模均未测(D6 交付后的诚实缺口)

  • 依据adr/0075-directory-l0-l1-sidecar.md「负 / 已知边界」与「未验证」。
  • 现状(本 ADR 明确未做)
    1. 召回收益未测 —— 本轮只做到「_index.md 引用 L0」,未改检索排序 ⇒ 「sidecar 是否真的改善相关性判断」没有评测不得表述为已验证。 若要接进检索,属检索层改动,需先有评测口径(可参考 adr/0060 的判别层结论与 tools/retrieval-eval.ts)。
    2. 真机规模与耗时未测 —— 每次索引重建为每个日期目录写一份 sidecar; 本仓真语料 7000+ 条记忆、日期目录数量级为数十到数百 ⇒ 写入次数有界,但未实测
    3. 存量回填未做 —— 旧目录的 sidecar 要等各自索引重建才生成;不做一次性全库回填 (避免一次几百次写入 + 一次全量读)。
    4. L0 抽取质量未评 —— 它只是 L1 的确定性首段(不含判断,判断属读侧 ADR-0042/0043)。
  • 特别注意(防过度表述)pending 字段恒为 0 是构造性的 —— sidecar 与 _index.md同一份 recs 派生 ⇒ 不可能落后。 它当前只在棘轮/构造坏件时有意义不是「会真实报警的增量检测」。 若将来出现「增量更新 sidecar」的路径,才需要让它真正承担检测职责。
  • 完成判据:① 给 sidecar 设计一条可评测的读路径(或明确决定不接)并记录读数; ② 真机报告 sidecar 数与写入耗时;③ 明确「是否回填」并执行或记录不做的理由。

T10. 传递性死代码:调用点全在另一段死代码里,工具却说「有接线」(v1.15.36 新发现)

  • 依据adr/0062「补记(v1.15.36)」§4;处理工具盲区①②③时顺带发现

  • 实测事实(grep notRevoked 全仓核实)notRevokedstance/delegation/guard/revocation-guard.ts:5有 2 个调用点:7:8),故它不在 A 段(工具认为它「已接线」); 但这 2 个调用点都在 assertNotRevoked 内部:6),而 assertNotRevoked 自己零调用(落在 A3 桶)。 ⇒ notRevoked 事实上不可达,工具却报「有接线」

  • 性质(与前三类不同,需单独记)

    形态工具表现
    ① 字符串非调用看成调用漏报
    ② 间接调用真调用看成没有误报
    ③ 平行 API成对导出的一半看成断线误报
    ④ 传递性死代码数到了调用点,但那调用点在死代码里漏报(且毫无迹象)
  • 为什么本轮不修:这需要调用图 / 可达性分析,纯文本判据做不到。 本工具的设计定位是「线索发现器」(原 ADR 已明写),加一张调用图等于换一类工具。 ⇒ 本轮只登记,不实现;已把该形态写进工具输出末尾的「判定纪律」,让读者知道这个洞存在。

  • 本仓的暴露面(诚实的量):真语料里只找到 1 处notRevoked)。 原因是本仓的家族模式是「谓词接线 / assert* 包装仅测试消费」, 故大多数谓词有独立的真调用点(不是靠 assert 包装才被用)。 但同一模式换一处接线就会批量产生这类不可达 ⇒ 值得记,不必立刻建调用图。

  • 完成判据:三选一 —— ① 引入调用图/可达性分析(明确代价与依赖,属换工具); ② 用「A3 桶的成员若其内部只调用了某符号,则那些符号标记为『经由断言包装间接使用』」这一近似判据 给出更粗但可用的信号(文本可做,但会有假阳);③ 明确接受该盲区并在工具输出里显式声明(本轮已做)。

T11. 评测纪律:留出集与「烧语料前的前置冒烟门」(hl_mem 深读第二遍的产出,adr/0076

  • 依据adr/0076-hl-mem-deep-read-2.md §4 / §5;hl_mem 的 evaluation/README.md 与其 docs/adr/0004 §8。
  • 它踩过的坑(量化,非转述):v0.30.0 状态实验在同一份 400-bundle dev 上反复调参取得 13/13, 却在独立 held-out-r5 仅通过 3/13,并产生 27 条错误 edge / 3 条反例误 supersede ⇒ 整批撤回。 它的对策是三层数据(开放 calibration / 冻结 validation A / 冻结 validation B,A/B 并行构建、 不同时间窗与 salt)、「不得针对 A 修改后拿 B 当补考」,以及一条极强的门: 「任何 sealed/held-out 语料开始提取前,必须先运行零 LLM 的完整缝合线冒烟;命令失败时不允许烧语料」
  • 本仓现状(诚实评估)
    • 两个审计工具的标定(audit-wiring.selftest / audit-drift.selftest)用的是同一份夹具 + git 历史真缺陷 —— 这是自证没有留出集tools/retrieval-eval.ts没有 A/B 冻结集。
    • 但照搬两层 400 案冻结集属过度设计(本仓无 CI、无 LLM 写入路径、语料是自有记忆、规模小)。
  • 本轮只取两条与本仓同形的(这是本条的全部范围,不多做):
    1. held-out 与 dev 分离 —— 哪怕各 20 例。判据:审计工具/评测的调参语料与报告用语料不同源, 且报告用那份在调参期间不得被读
    2. 「先冒烟,再花代价」的显式门 —— 本仓的对应物是「构建 + 类型门」(已在做), 缺的是把它写成一道显式前置跑任何评测/长耗时验证前,先跑 npx tsc --noEmit + npm run build; 失败就不跑评测(避免把一次编译错误伪装成一次评测结论)。
  • 为什么记成待办而不是立刻做:需要先定用哪份语料当留出集(本仓语料是用户自己的真实记忆, 把它切成 dev/held-out 涉及在真实语料上调参的伦理与可比性问题)—— 那是决策,不是实现。
  • 完成判据:① 定「留出集用什么语料、谁不读它」并落地一处可用留出集的评测; ② 前置冒烟门写成脚本或文档中的显式一步。
  • 进度(v1.15.38 / ADR-0077 D2)② 已完成 —— 门从「构建 + 类型门(靠人记得跑)」 升级为单一命令 npm run verify = 工具类型门 + 插件类型门(tsc --noEmit)+ 构建 + 全部 45 项确定性检查(43 行为测试 + 2 工具自检),由 tools/run-tests.ts 串行驱动。 ① 仍未做(待语料决策)。该门当轮即抓到两处既有失败(见 T12 与 CHANGELOG v1.15.38)。
  • 进度(v1.15.39 / ADR-0078 D3)① 第一次有了可照抄的形态(此前本仓连一条 A/B 协议都没有)。 hl_mem 的 docs/research/2026-09-04-p1-extraction-ab-v2-protocol.md(一手读 :1-42)给出五条同形要件: ① 预注册:3「装备就绪、尚未执行」—— 判据先于数据写死; ② 单变量:7 两臂共享 prompt/模型/数据/reader/scorer,唯一变量一行;:22-25 给出那一行 diff; ③ 每臂只跑一次:37「不得看单题结果后改 prompt、阈值、样本、配置或重跑并沿用 v2 名称」; ④ 付费前身份 hard gate:39-41(Git HEAD / 包路径 / 配置 SHA-256 / key 前缀)——防「跑的不是这份代码」; ⑤ 无效传播:73-76 任一臂身份无效 ⇒ 整轮 A/B 无效(即使另一臂分高也不作因果结论)。 另有 c-series-relation-experiment-protocol.md(子代理回报)的 dev/sealed 分离 + 题面外置 + 造臂人不得看题面。 ⇒ 本条的 ① 现在只差「定用哪份语料当留出集」这一个决策(伦理问题不变)。
  • 进度(v1.15.40 第 3 轮 / adr/0080 §5,MemStrata)又多了一条「可用测试强制的评测不变式」—— marker-free:语料里不得出现任何文本过期标记[OUTDATED] / (legacy) / deprecated …), 否则基线可以读标签而不是靠时间机制得分(「silently inflating its score」)。 它的可执行形态可直接照抄三点:① 不变式由测试强制(不靠自觉);② 按词边界检测 tell (避免 new 命中 renew);③ 量化污染:去掉一个 [OUTDATED] 标记后,重排臂掉 14 点、纯门控臂掉 18 点, 而时间法只动 -4用「对照组掉多少」证明污染真实存在。(原文见 adr/0080 §5 引用。)

T12. 时间炸弹 fixture —— ⚠️ 原判「已闭环(v1.15.43)」被 v1.15.97 推翻:第 1 颗(引爆日 2027-01-02)已修,第 2 颗(recall-envelope,引爆日 2026-09-20)本版已修;其余 21 文件需按新默认重判

  • 依据CHANGELOG.md v1.15.38 §5;adr/0077 D4.2。已引爆的一颗(v1.15.38 已修)test/recall-attribution.test.ts 场景 30 硬编码 2026-09-05stale = ageDaysOf(rel) >= staleDays(默认 7) ⇒ 本地 2026-09-12 当天整片塌成 DECAYING。已改为相对今天
  • 本轮口径修正(先纠自己的数):题面记「12 日期 / 354 处 / 8 个文件」,实测是 12 日期 / 357 处 / 22 个文件 (口径:test/*.ts,occurrence 级 [regex]::Matches09-07 实为 8709-01 实为 12,其余 10 个吻合)。
  • 判定方法(可复现):① 逐文件读断言,看是否依赖「今天」与某阈值之差(ageDays/staleDays/hotness/halfLife/ recency/today()/Date);② 行为探针:把 new Date()/Date.now() 钉到假日期后逐文件 import 运行, 在 2027-06-01 / 2027-10-01 / 2028-06-01 / 2030-01-01 各跑一遍(约 60 次,零文件写入); ③ 机制探针直接 import dist/identity/evaluator.js 打印 days/recency/status/reasons
  • 逐条结论耦合(炸弹)1 个,其余 21 个文件全部不耦合,理由逐文件记在 CHANGELOG.md v1.15.43 的表里。357 处的成分:路径/文件名 264(74%) + 记忆正文文本 25(7%)+ assert 期望串 15(4%)= 304 处(85%)不参与任何阈值运算;余 53 处(15%)是 fixture 元数据, 其中只有 periodTo 的默认值这 1 处被「今天」消费 —— 那就是这颗炸弹
  • 真炸弹(已修)test/recall-attribution.test.ts 场景 58(:2153)与场景 60(:2196),同一根因putReflection 的默认 period.to = "2026-09-05":2068)被 identity/evaluator.ts:48 当作 lastSeenrecency = exp(-ln2·days/90),闸门 recency >= 0.4本地日期 ≥ 2027-01-02 时 days=119 → recency=0.39992 < 0.4 ⇒ status 由 acceptedcandidate ⇒ 不提 v2 / learned 不增。 实测 bisect:2026-12-31/2027-01-01 PASS2027-01-02/01-03/01-10 FAIL修法:2068 一行 to: opts.periodTo || today()(与场景 30 的既有修法同形)。场景 60 此前被 58 掩盖 (文件在 58 先抛错中止),同一行修法一并解决。修复后 node test/recall-attribution.test.ts = ALL PASS ✅
  • 同轮记账(语义漂移,不是红):场景 55/56/57(同样用默认 periodTo)在 2027-01-02 之后 reasons 会多出 「时间稳定不足」,但断言只查「重复性不足 / 反证过多 / 无 v2」⇒ 仍绿,测试名所述闸门不再是唯一拦截者
  • 未判定(诚实标注):① 未做逐日全量 sweep(理论存在「非单调窗口」:两项 base 分差为 1 时 recency 加成 可在 0/1 间振荡)——已逐条排除排序类断言(recall-envelope:188 两侧 base 相等、recall-routing-eval 的 entry 命中与次优项 base 差 ≥2、recall-attribution 全文件无 indexOf( 排序断言),故风险未证为 0 但已无已知路径; ② replay-real.ts / replay-metrics.tsDATE 默认值(09-07)是否失真需真实语料才能判(它们不是断言,不影响红绿)。
  • 完成判据:① 8 文件 → 实测 22 文件逐个判定 ✅;② 耦合的改成相对日期(1 个文件、2 处断言)✅, 不耦合的给出理由 ✅(见 CHANGELOG 表);③ 可选棘轮未做(需先有长期稳定的结论面;且真实炸弹已修)。
  • ⚠ 闭环被推翻(v1.15.97 实测 → 第 2 颗真炸弹,已修)test/recall-envelope.test.ts 的三批 fixture (seeds / longSeeds / manySeeds)把日期写死在 2026-09-012026-09-08它在 v1.15.96 发版当天还是绿的forget 缺省=开(v1.15.85「默认全开」)、staleDays 默认 14 ⇒ 种子年龄一过 14 天就被 isForgettable 整片滤掉,② 段成「无匹配」。引爆日 = 2026-09-202026-09-06 那颗的年龄正好 14;发版当天是 10/11/12)。 分层读数:listMemories = 3materializeAtoms.sources = 0;对照关掉 forget3修法同 T12 既有形态:三批 fixture 全改相对今天(today(1)/today(2)/today(3)today((i % 8) + 1)), 并保留 ③ 段依赖的相对顺序(alpha-ref 最新 > alpha-src > alpha-util)。
  • 方法论缺口(本轮最值钱的一条):原判定用「把 Date 钉到假日期后逐文件跑」——它对「不改判据」的代码成立; 而 v1.15.85 翻的是判据本身的默认值forget.enabled:关 → 开),这类变更必须重新判定所有依赖该判据的 fixture, 否则 ✅ 会变成一句静默失效的结论。⇒ 新判据:凡改默认值(不是改代码)的变更, 要在 CHANGELOG 里登记「本次改动是否改变了既有 fixture 的有效性前提」,并据此决定是否重跑 T12 判定。
  • ✅ 重判已完成(v1.15.98)—— 又抓出 6 个,已修:按「把 Date 钉到假日期 + 逐文件子进程跑」的同一方法重跑, 假日期取 2030-01-01(基线 = 当前日期下 verify 61/61 全绿):51 个文件里 6 个红 —— hit-accumulation / memory-time-single-source / abstract-sidecar / fs-sandbox-scope / t8-silent-degradation / t8-explicit-zero;根因同一个:fixture 写死旧日期撞上 forget 缺省开(staleDays 14), 记忆被移出活跃集。最早两颗的引爆日是 2026-09-21(次日)。 修法:不是改 fixture 日期,而是按本仓既有模式显式声明这 6 个场景与遗忘无关forget: { enabled: false } + 注释; 6 文件 / 8 条规则 / 15 处)。对照读数retention / compact 同样默认开但未引爆 —— retention 的排除按 _meta.json 建档生效(query.ts 读 meta),而这些 mock 没有 meta。
  • 重放口径(下一位照这个跑):① shim 只改 Date 构造与 Date.now;② FAKE_NOW=<日期> node --import <shim> test/<f>.test.tstest/*.test.ts 逐个跑(子进程隔离 —— 插件有模块级全局副作用);③ 先确认当前日期verify 全绿作为基线, 否则分不清「本来就红」与「日期耦合」。
  • ✅ 覆盖性 sweep 已完成(v1.15.99)12 个假日期 × 51 个文件 = 612 次运行,全绿(0 红),耗时 1.8 分钟。 日期集按「距今天数」跨越取值、压在已知阈值边界上:+1 / +2 / +4 / +7retention.staleDays 默认)/ +14forget.staleDays 默认)/ +30 / +60 / +90(halfLife 常见值)/ +180 / +365 / +730 / +1229 天。
  • ✅ 探针已固化进 tools/(v1.15.99) ⇒ 以后不必再手搓:tools/timebomb-sweep.ts + npm run sweep:timebomb。 纪律与 tools/run-tests.ts 同款(每文件子进程隔离 / stdio 走 fd 不走管道 / shim 用 data: URL 不落临时文件); 退出码 0/1/22 = 结构性缺件,不是「通过」)。刻意不进 verify(分钟级 vs 快速缝合线)。
  • 仍未做(诚实边界):① 日期集是跨越取值、不是逐日枚举 ⇒ 理论上仍可能漏掉「非单调窗口」里的某一天 (依据是 isForgettable 对 age 单调 ⇒ 跨阈值两侧取点即可覆盖该判据,但逐条证明); ② 它只覆盖本仓 fixture 面,不覆盖真实语料(真实 .shadow 持续变化,假日期对它无意义)。
  • 触发条件(已写进 AGENTS.md:凡改 forget / retention / compact 这类默认值(不是改代码)的变更, 跑一次 npm run sweep:timebomb —— v1.15.85 那次就是这样把上一次的闭环结论推翻的。

T13. 结构性门禁:分层方向 / 复杂度预算 —— 🟡 前半已落地(v1.15.41);后半(复杂度预算)仍未做

  • 依据adr/0078-hl-mem-third-read-clone.md D3(hl_mem 的 scripts/check_*.py11 个)。 最可移植的两个是: ① 分层方向检查 —— scripts/check_imports.py:12-19 用一张 FORBIDDEN_IMPORTS 表声明「哪层不许 import 哪层」, :61-83ast.parse真实导入,失败 return 1本仓的对应物是「core/ 是纯函数、不得 import 基础设施」 这条纪律 —— 目前只是散文AGENTS.md / 各 ADR),没有任何可执行检查。 ② 复杂度预算只能降 —— check_complexity_budget.py(16 KB)+ complexity_budget.json; hl_mem 的 plans 写「本期每个热点上限只能下调」(2026-08-30-...phase-5-architecture.md:20,子代理回报)。
  • 完成判据:① 先写一版 FORBIDDEN_IMPORTS 表的草案并实测存量违规数(若存量违规很多,说明表定错了,先改表); ② 真违规清零后,把它接进 npm run verify(v1.15.38 已有单一入口);③ 复杂度预算只在该文件当轮变小时才下调, 并在测试里锁「不得上调」。

✅ 进度 A(v1.15.41):草案实测即被否掉 → 换判据对象 → 三条门落地并接进 verify

  • ① 草案实测(口径:*.ts 递归,排除 dist / node_modules / .git / .docs / agent-presets / docs / _research / test / tools193 文件 / 523 条 import 边):
    草案禁令存量违规例子
    core 不得碰 node:fs1core/toolset/exec.ts:19(它是执行器,不是纯派生)
    core 不得碰 node:child_process1同上 :18
    core 不得 import persistence4core/judgment.ts:3 / core/memory.ts:4 / core/writer/materialize.ts:12,13
    query 不得 import persistence4query/materialize.ts / query/topic-recall.ts(读路径本来就要读落盘文件)
    core/ 不是「纯函数层」,是「脊柱」paths/types/util 无依赖;memory/writer-materialize/toolset-exec 有副作用)。
    照搬目录分层 = 当轮就红的门 = 假闸门改判据对象(完成判据 ① 的「先改表」即此)。
  • ② 换成三条实测为真的判据,并接进 npm run verify: ① 文件级依赖图无环(实测 0 个强连通分量 —— 文件级是 DAG); ② 纯模块白名单零副作用core/paths.ts / core/types.ts / core/util.ts / security/scrub.ts import 数均为 0带腐化自检:路径不存在即违规 —— 本轮用不存在的 core/lexicon.ts 实测到它真的会报); ③ 方向禁令(当前 0 违规:core↛query / core↛tools / persistence↛query / query↛tools / 任何层↛index.ts / 任何层↛agent-presets)。 明确不判层间环:实测存在 {core, evidence, persistence} 层间环,成因就是「core 是混合层」, 不是文件级环 ⇒ 写成禁令则门当场就红(又是假闸门)。CLI 会打印成因并留档。
  • ③ 落地物tools/audit-layers.lib.ts(纯逻辑;复用 audit-wiring.lib.ts#stripComments,不写第二份注释剥离器)+ tools/audit-layers.ts(CLI,打印口径与非零退出)+ tools/audit-layers.selftest.ts8 组标定测试,全合成夹具)+ package.json 新增 audit:layers / audit:layers:selftest,并把 npm run audit:layers 接进 verify门禁状态npm run verify = 46/46npm run audit:layers 在真仓库通过(193 文件 / 523 边 / 0 环 / 0 方向违规 / 0 未解析)。
  • ④ 两个当轮自我暴露(留档)(a) 标定测试当轮抓到我自己的真 bug —— 层边曾写成 .map((e) => ({ from: layerOf(e.from), to: layerOf(e.to), ...e }))...e 在后把层名覆盖成文件路径方向禁令永不命中、门恒绿(正是本仓最爱的「机制对了、断的是谁调用它」那一族;没有标定测试,这个门会安静地什么都不查)。 (b) 默认白名单在合成夹具上会正确地逐条自曝「腐化」⇒ 想要「零违规」的正对照必须显式清空判据表(已写成断言)。
  • ⑤ 方法边界(诚实)不用 TypeScript 编译器 API —— 本仓 typescript@7.0.2native(Corsa) 移植, 包根 . 只导出 version(连 ScriptTarget 都没有),AST API 在 typescript/unstable/ast 这类 unstable 子路径 ⇒ 说明符抽取是剥注释后的正则,不是 AST。已知边界:字符串里形如 from "./x" 的文本会误命中,命中项须人工复核。

⬜ 进度 B(部分):复杂度预算仍未做 · core/ 导航收编已做一刀 · 第四伞待开

  • 复杂度预算:hl_mem 靠 AST 量「行数 / 参数数 / 函数体行数」+ 棘轮只降不升。本仓无可用 AST(见 ⑤) ⇒ 要么用「文件行数 / 导出数」当代理(弱判据,须先说明它与哪种风险对应),要么等 unstable/ast 稳定。 不先定判据就不写表 —— T13 前半的教训正是「表定错了,门就是红的」。
  • core/ 前缀簇(v1.20.9,ADR-0102)不是「拆纯度层」——只把已有前缀簇收进 writer/ · knowledge/ · lineage/ · candidate/ · toolset/layerOf 仍 = core。 消掉 {core, evidence, persistence} 层间环仍要真拆混合脊柱,另开架构决策,不在本刀。
  • 下一刀(grill 候选 B,待开):根上剩余小域 identity / dream / temporal(+ 可选 soul/observer) 收进第四导航伞;顶层目录数再降,与 ADR-0101 同口径(伞 ≠ 层)。
  • 进度 C(v1.15.40 第 4 轮,hl_mem 门禁面深读的形状清单;全文 references.md §6.6): 三个可照抄形状 —— ① 「生成器 + 签入产物 + 门禁逐字比对」三件套(同形 6 次,唯一更新入口 --update/--write缺件即非零 + 失败文案自带更新指引);② allowlist 腐化自检(白名单里的路径/函数不存在也算违规) —— 已在 v1.15.41 落地(见 ②);③ 纪律写成元测试test_test_suite_policy.py:20-22)。 照抄形状时不要照抄它自己的洞(逐条见 §6.6 表):比较器零调用、棘轮基线缺件即通过、同一判据两处数值。

T14. 确定性基准门:签入基线 + compare 子命令 + 「外部调用即失败」 —— ✅ 已落地(v1.15.42),但本部署里回归门恒「不可比」(见下「活语料结论」)

  • 依据adr/0078 D3。hl_mem 的 docs/benchmark/core-v1.md我一手读完全文): :3-8「deterministic, public, zero-network regression gate … any external model call fails the run」; :17-19 冻结容差(≤0.01 回归 / HTTP 100% / forbidden 0 / P95 ≤ max(baseline+150ms, baseline×1.25)); :21-22功能字段与 hash 必须跨两跑逐字相同,只允许延迟字段可变」;基线签入 benchmarks/release/results/v0.36.1.json + compare_core_v1 比较子命令(:13-14)。
  • 本仓现状tools/retrieval-eval.tsnpm run eval:retrieval只打印读数没有签入基线、没有 compare、没有回归容差; 唯一的门是 npm run verify(v1.15.38)——那是编译+测试门,不是评测门。 ⇒ 于是「这次评测比上次好还是坏」无法由机器回答(只能靠我读两串数字)。
  • 为什么现在没做:需要先定哪些指标进门(显式清单,而非隐式全量)、容差取多少、以及 「功能字段两跑逐字相同」在 TS 侧的对应物(浮点/时间戳字段必须先剔除,否则必然抖动)。 另:本仓记忆语料是用户真实数据,基线一旦签入就等于把读数写进仓库 ⇒ 需先确认可公开的范围。
  • 完成判据:① 产出一份带 dataset_sha256 / protocol_sha256 / case_count 的基线文件(signin 前先确认可公开); ② 加 npm run eval:retrieval:compare(比对基线,超容差非零退出); ③ 用它给 T9(sidecar 读路径收益)与 T11① 的评测提供可机器判读的基线。
  • 进度(v1.15.40 第 3 轮 / adr/0080 §3,MemStrata)拿到了一个可直接落地的「破坏性错误」指标—— stale-fact-error rate:分子 = 以被取代值作答的题数,分母 = 矛盾题数(该论文 30/20/20/20)。 四条随指标必须一起抄的纪律(前三条是它自己踩过的坑): ① 两 regime 必须同报:「允许弃答」与「强制作答(forced-answer)」—— 论文原文说后者是为了「expose the stale-commitment that abstention otherwise hides」; 只报允许弃答那一列,弃答会把 stale 错误洗成低准确率(Table 3:naive_rag 强制后 0.10→0.40、0.30→0.35)。 ② 必须印分子/分母:摘要写「~0%」而表体是 0.03实为 1/30)⇒ 本仓不得只写百分比(「不伪造精度」)。 ③ 两指标判据必须独立:它自陈准确率与 stale 错误复用同一个 3B 判官,出现「同行既正确又 stale 错误」; 本仓若同时报「正确率」与「错误关链率」,必须各自独立判定并显式列出重叠行。 ④ 分母为 0 要报「不可测」而非 0(该论文未定义 zero-denominator;本仓移植时须自己定义)。
  • 进度(v1.15.40 第 4 轮:拿到它的比较器实现**,不只是散文;全文 references.md §6.6): 可照抄的四条比较层判据: ① 协议常量与代码分离benchmarks/release/core_v1_protocol.json9 行)冻结 max_metric_regression / required_external_model_calls / required_forbidden_hits / required_http_success_rate / baseline_tag ⇒ 改容差 = 改数据并被 diff 审阅,不是改代码。 ② 先证同源、再比数值compare_core_v1.py:28-30 先校验 dataset_sha256 / protocol_sha256 / case_count 逐字相等,任一不等直接失败(防拿换了语料的读数比)。 ③ 门控指标显式列名:12-19 6 元组),不是「全部指标」;容差失败信息同时给实测退化量与允许量**(:38-41); ④ 「外部调用必须为 0」在比较层再判一次:43-55),不只靠运行期守卫。 另外两条更硬的形式(第二套独立实现,tests/eval/):基线写入拒绝覆盖ci_gate.py:136-139 FileExistsError, 防重刷基线掩盖退化)、基线带来源档位gate_check.py:36-38 ci_fixture 基线不得用于发布决策)、 逐 slice 门控且缺 slice 即失败:66-78)。 它自己缺的两件事(本仓必须补,否则同样是假闸门)compare_core_v1.py 在任何 workflow 里零调用; 「两次运行功能字段逐字相同」只有散文、无脚本docs/benchmark/core-v1.md:21-22)。

✅ 进度 D(v1.15.42):已实现并接线 —— 含一条本仓特有、必须写下来的结论

落地物tools/retrieval-eval.lib.ts(纯逻辑)· tools/retrieval-eval.protocol.json冻结协议gated_metrics = recall_mean / noise_offtopic_mean / avg_returned_mean,方向 higher/lower/exact, 容差 0.01/0.01/0required_external_model_calls: 0)· tools/retrieval-eval.baseline.json签入基线provenance: local_dev_aggregate_only)· tools/retrieval-eval.selftest.ts12 组标定测试)· tools/retrieval-eval.ts 新增 5 个模式(--json / --check-baseline / --update-baseline / --compare / --determinism-check)。

逐条兑现完成判据:① 基线含 dataset_sha256sha256-utf8-lf-v1,用仓库相对路径⇒ 与机器/盘符无关)+ protocol_sha256 + case_count;② npm run eval:retrieval:compare(退出码 0 通过 / 1 违规 / 3 不可比); ③ 给 T9 / T11① 提供可机器判读的读数面。

同时补掉 hl_mem 自己的两个洞(a) 比较器不再零调用 —— --check-baseline(完整性:协议自检 / 基线只含聚合面 / 协议同源 / 门控读数齐备)已接进 npm run verify,标定测试也自动进 run-tests(b) 「两跑功能字段逐字相同」从散文变成脚本 —— --determinism-check 在同一进程跑两遍并逐字节比对,真仓库通过

「外部调用即失败」:运行期把 globalThis.fetch 换成抛错桩 + 计数(hl_mem 同形), 计数进入结果对象并由比较器再判一次(required_external_model_calls: 0)。

🔴 本仓特有的一条(与 hl_mem 的冻结语料根本不同,必须记住)

hl_mem 的语料是冻结数据集,所以基线可以用 dataset 哈希钉死。本仓语料是活的 .shadow 记忆 (插件每个回合都在写新记忆 —— 实测两次调用之间语料就从 1414 条涨到 1435 条)。 ⇒ 哈希钉死的基线在本部署里恒「不可比」(这正是 --compare 的实测输出)。 故本模块把「不可比」做成显式的第三种结论(退出码 3,既不是通过也不是失败), 与已吸收的「分母为 0 要报『不可测』而非 0」同一条纪律。

可执行的判据因此分两层

命令在本部署是否可用
确定性(同输入两跑逐字相同)npm run eval:retrieval:determinism恒可用(与语料是否变化无关)
完整性(协议/基线形状/同源/读数齐备)npm run eval:retrieval:check已在 verify✅ 恒可用(不依赖语料数值)
回归(比基线、超容差即失败)npm run eval:retrieval:compare只在语料冻结时可用;本部署的正常结论是「不可比」

要真正启用回归门,唯一路径是「冻结语料」:把一份 .shadow 快照放到别处, 用 SHADOW_EVAL_ROOT=<冻结工作区> + --update-baseline + --compare是否物化这样一份冻结副本=用户取舍(会把真实记忆复制一份到新目录),故本轮没有替用户做

顺路修的真缺陷retrieval-eval.ts 的默认语料根硬编码 D:/project/dsh1,而本机工作区在 G:\npm run eval:retrieval 此前一直在空语料上跑(docs=0 还「跑得通」)。现默认由文件位置推导 + 环境变量可覆盖 + 找不到 .shadow 或语料为空时非零退出(缺件不静默)。

本轮自我暴露(留档):把全文件的 console.log( 批量替换成 emit(emit 自己的函数体也换掉了emit 递归自调 ⇒ Maximum call stack size exceeded改名/批量替换是「断的是谁调用它」的高发区, 已在代码注释里写明,并靠 --determinism-check 的一次真实运行抓到。

T15. Protected Contract Registry(本仓完全没有兼容性政策)—— 用户 2026-09-12 指定为下一阶段的主产物 —— ✅ 判据 ①②③ 完成 · 十字段(8×10)已填 · 模块归属表已补 · 切片 3(D1/D2/D3)已判定(v1.15.74)

状态口径(v1.15.73 现数):用户三条完成判据(见本节末尾)全部完成; 契约本体 = 8 条tool-name-v1 / tool-schema-v1 / read-mode-v1 / retired-mapping-v1 / config-keys-v1 / memory-file-v1 / derived-file-v1 / prompt-segment-v1), 每条 10 字段已填README.md 表 A + 表 B)。 净发现:各面族里 6 族有强门 / 1 族部分设防(tool-schema-v1,参数名无人枚举)/ 1 族完全未设防(config-keys-v1,删键无门会红)ratchet 一列 8 条均「无桶覆盖」——棘轮桶按缺陷类分、不按契约面分,故没有为凑字段新造桶。 ⚠ config-keys-v1 是「被消费的」(index.tsapply(ctx, rawConfig),live 走 ctx.config —— 「没有生产消费者」这句只说登记册本身没人读,别读成「里面的面没人用」(v1.15.72 就是这么读错的,见 adr/0086 §8.7)。

模块归属表(本节下文那条「用户要求」)—— 补做方式:生成,不手写node tools/module-ownership.ts28 行 = 27 个目录 + index.ts;数目以工具打印为准 —— v1.16.0 起生成器在 tools/ 里;旧路径 ../.docs/fix/2026-09-12/ 本机已不存在)。 五列里只有 ReadsMust not own 能从代码机械推出(后者直接来自 tools/audit-layers.lib.tsDIRECTION_RULES / FORBIDDEN_TARGETS_EVERYWHERE不写第二份禁向表); Owns(语义)与 Writes(副作用)不可机械推Owns 只对 ADR 定过的层给结论、其余标未核Writes 已登记在 derived-file-v1 / memory-file-v1(判据收一处,不再列第二份派生件清单)。

切片 3(D1/D2/D3)已判定(v1.15.74,adr/0086 §8.9)D1 ChangeSet保留(前两条成立,但第三条「没有验证价值」不成立: 它是 tools/audit-wiring.selftest.ts:132-140真仓库已知答案,删它会把 A 类检测器变弱)。 ⚠ D1 不是机械可判的:「有没有验证价值」是主观条件,而它恰好推翻了前两条导向的结论 —— 故「T15 让 D1/D2/D3 全部机械判定」是被我夸大的口径,正确说法是「让证据收集变机械,没让判定变机械」。 D2 四类漂移各自对应登记册的 allowed/forbidden/stability 列(不新造表)· D3 登记册里的契约全是细粒度面 ⇒ 内部大对象的重构是允许的 breaking internal refactor仍未做D2可执行检测(本轮只给分类判据落点)· D1 的保留没有写成门的断言。

用户原话要点:T15 的产物不要只是「受保护文件清单」,而要是 Protected Contract RegistryContract ≠ API list,而是 Surface + Semantics + Stability + Allowed Drift + Verification。 建起来之后,D1/D2/D3 与 A 段 6 条会从「拍脑袋决策」变成「按契约机械判定」顺序:T8 → T15 → D1/D2/D3 → A 段(不要先拍 D1)。

「A 段 6 条」到底是哪 6 条(v1.15.75 首次枚举 —— 此前两处引用都不给内容**): 它是 v1.15.43「A 段残余 18 条逐条分诊」里的「无法判定(6)」桶(定义在本文件上方的 ✅ 已结案小节里, 距此处约 700 行 ⇒ 指针没有目标)。6 条 = hasNoUpgradeApistance/agency/guards.ts)· renderIntentcore/intent.ts)· renderIdentityModelidentity/timeline.ts)· progressiveDisclosure + refineTreecore/knowledge/cost.ts同一处决定)· relationForProposaltemporal/edge.ts)。 被排在 T15 之后的唯一原因:它们缺的是产品决策**(「哪些东西算受保护契约」),不是代码证据。 v1.15.75 的判定落点与登记册缺口见 adr/0086 §8.10(#1/#2 不涉及兼容性;#6 归 T7); #3/#4/#5 已于 v1.15.76 读码后收敛为「同一个阻塞」「同一个缺口」(见 adr/0086 §8.11)—— 三条接线后都会改某条读路径的返回内容,而登记册没有一条契约覆盖「工具的返回内容」。 ✅ 已于 v1.15.77 入册(tool-output-v1:入册前先把散在文档里的契约条数全部删除条数=README 登记册表的行数,不再手写 —— 原来那个手写的数还遭遇了自指:写下「有 N 处写了 8」本身又添几处)。 A 段 #3/#4/#5 随之得判(adr/0086 §8.12):三条都允许接线(属 tool-output-v1soft 半边,须写 CHANGELOG); #4/#5 折叠掉的内容必须由信封披露,否则违反该契约的 hard 半边。⚠ 判定 ≠ 已实施 —— 本轮没接线。 ⚠ 「A 段」在本文件指过三样东西(A 类 33 条 / 残余 18 条 / 这 6 条)—— 引用它时必须给到具体那一行

  • 每条契约必备字段(10 项,缺一不得入册)

    字段含义本仓的例子
    id契约标识(带版本,如 recall-output-v1mode 串 / 工具名 / 落盘文件名格式 / 派生件格式
    surface面的类型(public-api / 配置键 / 落盘格式 / 派生件 / 工具 schema / prompt 段)逐项标注
    owner谁拥有这个概念(哪个一级模块);含「它不得拥有什么见下面的模块归属表
    semantic meaning这条契约对使用者意味着什么(不是字段列表)例:「read_shadow 的返回值是数据不是指令
    stability levelhard(不许变)/ soft(可变但要窗口)/ experimental(无承诺)本仓不自造 stable/beta/experimental 三档定义(D8 已判),用这三档内部语义
    allowed changes允许的演进(如 additive: true加字段可以、改语义不行
    forbidden changes明确禁止(如 semantic narrowing、静默改默认值)与 ADR-0063/0070 同族
    evidence判据来源(文件:行号 / ADR / 实测)一律可点
    verification哪条测试/门禁真的在守它指向 test/*.test.tstools/*
    ratchet哪个棘轮桶覆盖它(没有就得说明为什么不需要)指向 audit-ratchet.baseline.json 的桶
  • 模块归属表(用户要求,每个一级模块一张小表)| Module | Owns | Reads | Writes | Must not own | —— 目的不是文档,而是暴露「谁开始越权」。本仓一级模块:core / persistence / query / retrieval / recall / evidence / validation / verification / observer / identity / soul / temporal / continuity / world / reality / federation / delegation / adaptation / planning / action / simulation / reflection / dream / long-horizon / agency / security / index.ts

  • D2 必须细分(用户第 8 条):不能统一叫 drift,要分 名称漂移 / 结构漂移 / 语义漂移 / 行为漂移 —— 否则会把「无害重命名」与「真正兼容性破坏」混在一起。

  • D3 的再框定(用户第 8 条):若 T15 明确「旧的大对象不是 protected、新的细粒度对象才是」, 则 D3 不再是兼容性问题,而是 允许的 breaking internal refactor —— 这会大幅减少历史包袱。

  • D1 的再框定:若 ChangeSet 没有生产消费者 / 没有验证价值 / 没有外部契约,不为「将来可能有用」保留。

  • 依据adr/0078 D4c。本仓的公开面是「工具名 + mode 串 + 配置键 + 落盘格式」, 但没有任何兼容性政策文档;而 ADR-0050 做过一次硬切(v1.13.0 把旧 mode直接废止、并写明 「非『mode 串永远不变』」)——那次没有任何弃用窗口。使用者是真人(用户自己的会话), 所以「改名/删 mode」的代价目前全靠 CHANGELOG 的可读性承担。

  • hl_mem 的形态(可照抄,均为子代理回报 + 我抽查 compatibility.md 段落): ① 先枚举受保护契约面,再定义破坏 —— docs/compatibility.md:10-12(稳定契约 = REST/MCP/CLI/配置 schema/ 导入导出/备份格式/插件 API;移除或非兼容改动必须等下一个 major); ② 弃用强制前置,且必须点名替代品或明说「无替代」 —— :20-29(「whether or not a replacement exists」); ③ 三档稳定性各有变更预算 —— :33-35(stable 只在 major、beta 只在 minor 且 changelog 必须带迁移说明、 experimental 无窗口但必须就地可见标注); ④ 未知版本显式失败,不猜 —— :74-75; ⑤(相关)不可逆变更前的恢复集 + 把 rollback 的数据丢失写出来 —— :62-70

  • 为什么现在没做:这是一份政策,一旦写下就要约束以后的自己(每次删 mode 都得先走弃用期); 而本仓目前只有一个使用者,硬套 major/minor 三段式可能形式大于实质。 ⇒ 需要先定「本仓的『稳定面』到底是哪几项」——那是决策,不是实现。

  • 完成判据:① 列出本仓的受保护契约面清单(工具名/mode 串/配置键/落盘格式/派生件格式,逐项标 stable 或 experimental); ② 给出一条最小弃用流程(旧名保留一个版本 + 输出可见提示 + CHANGELOG 写迁移说明), 并说明**「无替代品」时怎么写**;③ 把清单放进 README.md(而非另开一份易漂移的文档)。

T16. 平台契约核对与纠错(v1.15.39 新开)—— ✅ 已结案(v1.15.43,四项全闭)

  • 依据:v1.15.39 深读 DSH 本体(本地克隆 dsh-w/deepseek-harnesscd5ef81481 = 0.1.2-alpha.1运行体是 dsh-web-app@0.1.5-rc.2)⇒ 四路回报里7 条「下游可能理解错」。已自查 1 条、已裁定 1 条:
    • 已自查(无缺陷):本仓 index.ts / dist/index.js export defaultunwrapExports 会「静默丢弃整个命名空间(含 inject)」那条缺陷不适用postmortem/0001:110-111)。
    • 已裁定并落 adr/0074 补记:省略 sandboxPolicy 合法dsh-fs-sandbox/index.js:158?? 回退), 但无参 resolve() 取服务级根dsh-sandbox-policy/lib/index.js:116-117 config.workspaceRoot ?? process.cwd()), 只有 resolve({session}) 才用 session.header.cwd:138-142)⇒ ADR-0074 结论成立、机制表述已修正
  • 待办(逐条可核,未做)
    1. 已结案(v1.15.40 第 2 轮,运行体 Service 目录)ctx.sandboxPolicy 在运行体里是注册服务、 且对普通插件可见——平台自身给出两种接入方式:access.optional = {expression:"ctx.get(\"sandboxPolicy\")", requiresUndefinedCheck:true}access.hardDependency = {inject:["sandboxPolicy"]}; 契约原文「A session cwd is its workspace-write boundary; the configured root is the fallback for agentless calls and sessions without a cwd.」逐字印证 ADR-0074 补记。 并且:本仓 core/fs-scope.ts:34-35 本来就是委派ctx.get("sandboxPolicy").resolve({session})), 没有重造 —— 上一轮「重造」的判断是我的误判(据子代理表述下结论、未读自己的代码),已在 MATERIALS.md 更正。 本轮改的只有 core/fs-scope.ts 的注释:把「包层 / 部署层」写清(包只读 config.mode/config.workspaceRoot; 部署组合 dsh-base/cordis.patch.yml:208-212 才把它们配成 DSH_PERMISSION_MODE / process.cwd()), 并留档我的一次范围性错误(只对三个包 grep DSH_PERMISSION_MODE 得 0 命中就断言「不存在」, 漏了部署组合 ⇒ grep 之前先写清枚举范围)。ADR-0074 正文与旧注释的描述是对的。

    2. 已结案(v1.15.40 第 3 轮,读 vendor/loader/src/config/isolate.ts)——子代理 B 的说法被推翻: 它说「group 不继承、每一行都要各自写 isolate」,据此要复核本仓预设。源码不支持该说法isolate.ts:98 const newMap = Object.create(entry.parent.ctx[Context.isolate])(新隔离表以父表为原型)、 :99-101 只把本行自己声明options.isolate 键写进新表、:123setPrototypeOf(entry.ctx[Context.isolate], entry.parent.ctx[Context.isolate])未声明 isolate 的子行通过原型链继承父行的 realm ⇒ 服务由子行提供时落在父 group 的 realm 里。 ⇒ 本仓 agent-presets/projection/agent.cordis.yml:123-125/156-158/193-195cordis:group + isolate + 子行) 的写法本来就是对的editing-cordis-compositions 技能那句散文准确,无需更正对的那一半:「isolate逐行 option(每行可各自声明/覆盖)」——isolate.ts:79 确实按行读 entry.options.isolate。 ⇒ 教训:子代理的「应当如何」类结论,必须回到源码判(本轮第二次:第一次是「重造 ctx.sandboxPolicy」)。

    3. 已逐条判定(v1.15.40 第 3 轮,四份契约取自运行体):四个服务都存在且可见, 但**「本仓在重造」这个怀疑只对其中一项成立,三项不成立**:

      服务判定理由(基于运行体契约)
      sessionProjections不适用它是「投影单元表 + 每会话状态 + 变更通知」(register(definition) / stateOf(session,key) / snapshot / cachedSnapshot / onChanged)。本仓的派生是文件派生.shadow/_index.md/_meta.json),不是会话事件折叠;引入它等于换一套来源(撞 ADR-0003/0069 的来源纪律)
      storage / storageDomain不适用它是不透明持久化后备mount(form, facility) / open(spec) / get(name))。本仓的记忆必须是人类可读的 Markdown 文件树(ADR-0001 的核心取舍)⇒ 用它就是范畴变更,不是优化
      jobs不适用本仓无后台长任务(写入在 agent/turn-stopping 一次 flush 内完成);且平台已有 job 工具面覆盖我自己的长任务
      invariants候选吸收(唯一一项)见下
      invariants 契约原文(运行体)register(packageName: string, installer: InvariantInstaller): () => void
      `InvariantInstaller = (ctx: Context, fail: InvariantFailure) => voidPromise`;
      InvariantFailure = (message: string) => never失败是响亮的);
      说明原文:「Enabled installers run in a child fiber; failure disposes that fiber and releases the reservation.」;
      access.optional = { expression: "ctx.get(\"invariants\")", requiresUndefinedCheck: true }
      为什么值得:本仓有一批只活在测试里的不变量——生命周期信号可达性(test/lifecycle-signal-table.test.ts)、
      判据单一源(test/claim-admission-single-source.test.ts)、sidecar 漂移(sidecarDrift)、台账自洽
      countInconsistency)——注册进平台后,它们从「我记得跑测试」变成「宿主可执行」。
      两条前置已答(v1.15.40 第 4 轮,源码 + 安装体 + 包 README 三面互核;全文见 references.md §6.5)
      (b) 默认执行 —— 是src/index.ts:96,115 enabled 默认 true:120-126 无过滤器即全接纳;
      安装体 dsh-invariants@0.1.5-rc.2/lib/index.js:45,62,67-70 与源码逐字同构 ⇒ 版本偏差不影响本项)。
      选择由挂载该行的组合给(实例:安装体 dsh-sdk-minimal/cordis.patch.yml:103-104无 config ⇒ 全默认);
      过滤器在服务生命周期内固定,改它要 reload(README.zh.md:153)。
      (a) 失败 = dispose 子 fiber + 回滚保留 + 注册方自身 apply 失败:161-163 failInvariantError
      :172-175 child.dispose() 后 rethrow → 从 ctx.effect 冒出 → register() 的 thenable reject)。
      「是否阻断整个宿主启动」仍未验证(取决于 Loader 对单行激活失败的处置,属运行体实验),不得写成已知
      新查到的第三条(仍然成立,只是本部署连服务都没有)「只挂服务不挂配套入口 == 没有检查」
      README.zh.md:12,156:「注册表自身不携带产品检查」)⇒ 检查是否真跑,取决于本仓自己有没有调用 register()
      不取决于服务在不在。
      ⛔ 已裁定(v1.15.40 第 4 轮,改用运行时读取):服务在这个部署里根本没挂 ⇒ invariants 判「暂不吸收」。
      我原先写「运行中的 web 宿主确实有 invariants 服务」(依据运行体 Service 目录)——这条是错的
      随后用只读动态 Host 插件做运行时读取:ctx.get('invariants') === undefined
      同一次探测里 spillStore / tokenMeter / shellEnv / codeRuntime / webServer / clientModules
      只在宿主/Web 层挂载、任何预设都不提供的服务全都读到了 ⇒ 沙箱 ctx.get 读的是全局服务表
      undefined 就是真的没挂(对照名 definitelyNotAServiceControlundefined,排除「假门面恒返回对象」)。
      ⇒ 本仓即使写 ctx.get('invariants')?.register(...)在这个部署里也是静默 no-op = 假闸门
      要重启该项的前置条件 = 同时把挂载行写进部署组合
      - id: invariants / name: '@deepseek-ai/dsh-invariants',范本 dsh-sdk-minimal/cordis.patch.yml:103-104
      本仓 cordis.patch.yml 只有一行 dsh-shadow),或退一步在缺件时响亮报告(ADR-0049)。
      并得到一条更一般的纪律(已落 references.md §6.5.1)Service 目录 ≠ 活性表。反例三条:
      e2b 在目录里而 dsh-e2b 在本 profile 里根本没安装dsh-invariants 装了但没挂
      authorization / inspector 在目录里而 ctx.getundefinedinspector 尤其反直觉——它是 Inspect 自身门面)。
      ⇒ 凡结论是「某能力运行体里有没有」,唯一判据是运行时读取
      ✅ 附带好处:第 1 条(sandboxPolicy 可见)在本次探测里被更硬的判据复核——
      ctx.get('sandboxPolicy') 读到对象 ⇒ ADR-0074 结论不变、证据从「目录」升级为「运行时读取」
    4. 已逐项核对(v1.15.43,8 个平面,克隆面 vs 运行面 0.1.5-rc.2) —— 结论:机制面零漂移,组合/产物面全漂移口径:递归枚举一律 -FollowSymlink 并给计数([LINKS] 70 条 / [DLX] 239 条; bundle patch 的 invariant 命中:克隆面 0(语料 118/501/438 行),运行 dsh-sdk-minimal 10); profile junction 实际解析目标已用 Get-Item Target 验过(loader 1.0.3 / cordis 4.0.2)。

      #平面结论关键证据
      1isolate 继承语义一致运行面 cordis-plugin-loader/src/config/isolate.ts 与克隆面 vendor/loader/src/config/isolate.ts SHA256 相同(各 173 行;loader 1.0.2/1.0.3 该文件都同哈希)⇒ 克隆面行号(:98/:99-101/:123可直接引用
      2export default 丢命名空间(含 inject一致运行面 src/index.ts-199 与克隆面逐字相同exports = exports.default ?? exports 仍在(:194);lib/index.js:746 亦有 ⇒ postmortem 0001 的结论对运行面仍成立
      3ctx.get vs 属性代理一致运行面 cordis/src/ 9/9 文件与克隆面同哈希;getreflect.ts:233-243、代理陷阱 :136-171(ancestor-only 走链 :155-166)⇒ packages/AGENTS.md 的说法仍准确
      4dsh-base 组合行已变克隆 86 id / 运行 84 id;只在克隆面有tool-str-replace-editor(克隆 base:428)、tool-subagent-report:376);只在运行面有 0dsh-tool-subagent-report 包已退役(双树均无),str-replace-editor 包仍在磁盘但无任何行挂载
      5dsh-web-app 组合行已变克隆 85 / 运行 94;只在运行面有 10open-in-app:65ui-open-in-app:72session-turn-outline:91workspace-files:110file-upload:192resources:217ui-sidebar-right:224ui-sidebar-documentpreview:230ui-sidebar-files:234ui-schedule:306);只在克隆面有 1(tool-str-replace-editor
      6dsh-sdk-minimalinvariants 装配已变(二次确认)运行面 :103-104 - id: invariants / name: '@deepseek-ai/dsh-invariants':106/:109/:112/:115dsh-session/dsh-agent/dsh-scope/dsh-agent-loop/invariant 行;4 个 */invariant 子路径在运行面确实可解析(非死引用)
      7agent preset清单一致 / 内容已变两侧同为 4 目录 10 文件;6 个文件内容不同(4 个 agent.cordis.yml 全不同)。漂移方向:text:prefix:+suffix: 拆分、新增 - id: present、删掉 tool-subagent-report 那段注释、minimal 删掉 filesystem 隔离组与编辑器行
      8「每个包都发 ./invariant已变,且是系统性丢失fileslib/invariant.js / exports./invariantdsh-invariantsdsh-basedsh-web-app 三者在 0.1.1-rc.x = 是 → 0.1.5-rc.x = 否dsh-sdk-minimal 恒否;而 dsh-session/dsh-agent/dsh-scope/dsh-agent-loop 四个版本全「是」lib/invariant.js 实在磁盘)⇒ 服务包遵守、bundle 家族与注册表包本身不遵守,而 packages/AGENTS.md 末条仍写 Every package owns ./invariant

      对下游结论的影响(三条): ① 以「运行面 loader/cordis 与克隆面同名机制安全」为前提的推论(第 1/2/3 项)成立,可继续引用克隆面行号; ② 以「bundle id 集合未变」为前提的推论必须重做(base −2 / web-app +10−1 / sdk-minimal +16); ③ 以「每个包都发 ./invariant」为前提的推论在 0.1.5-rc.2 上不成立 —— 涉及 bundle 包与 dsh-invariants 本身时按「无该子路径」处理。 未核对(诚实标注):运行中会话实际挂载的组合(用户 profile patch / 自制预设 / 动态插件)不在本次 8 项内; lib/ 产物未逐行行为比对(选择读 src/,因发布物同时带 src/ 且与克隆面哈希一致); @deepseek-ai/dsh CLI 本体与 dist/ 压缩产物未纳入。 口径纠正(我上一轮的一条表述不精确):原型链挂在 isolate 符号表上(Object.create(entry.parent.ctx[Context.isolate])), 不是挂在 entry.realm 上(entry.realmLocalRealm 普通实例)——结论方向对、对象不准,已按此更正。 同时保留一条枚举纪律(方法层,已复现):PowerShell Get-ChildItem -Recurse 默认不跟随 junction —— 实测该目录 70 条里 69 条是 reparse point,不加 -FollowSymlink 的递归 grep 会静默跳过 69 个包 并给出看似确凿的 0 命中。⇒ 凡以「0 命中」为结论的搜索,必须先证明枚举到了非空且完整的语料

  • 为什么现在没做:① 每条都要回运行体核对(文档可能落后),属逐条实测,不是批量替换; ② 涉及改变本仓写入路径core/fs-scope.ts)与引入平台服务,须先有可见性结论,否则会引出新的 「机制存在但读不到」的假闸门。
  • 完成判据:① 四条待办各自给出「可见/不可见 + 实测命令 + 结论」;② 结论为「可见且应委派」的,落一条 ADR 并改代码; ③ 「不可见」的写明理由并在代码注释里标注为什么不能委派(防后来者重复尝试)。

T17. 派生索引层一期.shadow 权威 + SQLite 派生索引)—— 边界已冻结 · T17-A 已跑 · T17-B 已实现(v1.15.96,默认仍是 fs · T17-C 待跑

  • 内容:按 adr/0095-derived-index-layer-sqlite.md一期 —— 用 Node 内置 node:sqlite 承载 元数据 + 关系 + 证据链 + FTS5,把读侧的「扫目录 → 解析 → 打分」换成索引查询,读侧输出逐字不变
  • 依据adr/0095(用户 2026-09-16 裁决「.shadow 继续做 Source of Truth,SQLite 做 Index + State + Retrieval Cache」); adr/0060(形式已定:单索引 + 层级 + 路由;其补记指向 0095);adr/0001 的 Notes(「可叠一层增强,不推倒文件树」); adr/0051(源层与派生分离的同款手法);adr/0085(判据收一处 + 「断言通过只证明我没测到」)。
  • 为什么现在没做:三个前置问题已答(见上「完成判据 ①」),所以现在卡的不是「没想清」,而是还没做开工的第一步: 真实语料对照探针(并证明它能测出差异)+ 重建成本标定 —— 这两件不做就动代码,等于拿一个没有对照面的改动去赌「读侧逐字不变」adr/0085 §7 的教训:断言通过只证明「我没测到」;v1.15.94 那一轮的宽松桩刚又一次应验)。
  • 完成判据:① ✅ 已答(2026-09-16,见 adr/0095 末尾「§ 一期前置三问:答案」—— 其中一条改变形状的结构事实: 既有 IndexEngine provider 接缝(core/index-engine.ts)可直接加 sqlite,故一期不是新起一层);② 新增「同 query 两路取结果逐字段比对」的对照探针, 且先证明该探针能测出差异;③ rm .shadow/index.sqlite 后仅凭文件重建,对照探针全绿; ④ npm run verify 全过且读侧既有测试一字未改(改了就说明行为变了,得先解释为什么);⑤ SQLite 不可用时回落文件系统路径且留降级留痕
  • 开工前还要先做的三件小事adr/0095 的答案里点出):⑴ 统一一处既有措辞矛盾 —— core/types.ts:37 写「zg 未装 → unavailable 不 fallback」,而接口契约 core/index-engine.ts:14/51/64 写「调用方回退 fs 扫描」,两句含义相反;⑵ 指纹加 INDEX_SCHEMA_VERSION(现有源指纹只由 name:size:version 拼成、无版本令牌 ⇒ 索引器升级后会读旧索引;该缺口在现有 nodes.jsonl已经存在,属继承来的);⑶ 用真实语料标定重建成本(8.8k 文件建表要多久 —— 这是「一期默认开」是否可接受的唯一判据)。
  • 不在本条内:向量(二期,触发条件见 adr/0060)、状态层(三期)、写侧「Memory Value Gate」(另一个 ADR)。
  • 分期(用户 2026-09-16 定调,见 adr/0095 的「T17 的边界、不变量与分期」)T17-A = 只做 probe / benchmark / canonical diff⚠ 不改生产代码(真实语料 fs vs sqlite 的候选集 canonical diff + 反向实验先造红:删一条记录/改 scope/改 valid_until/改排序/遗漏新文件都必须红 + 三个基准数); T17-B = 实现 SQLiteIndexProvider只接 IndexEngine(不动 ranking/evidence/temporal/projection); T17-C = 验证矩阵(fs / sqlite / unavailable / corrupt / schema mismatch / source changed / rebuild / 8.8k cold build / incremental),全过之后才考虑 sqlite default
  • 第一原则(写在 adr/0095 最顶部)index acceleration MUST NEVER become a new source of truth or a new data-loss path. 由此继承 v1.15.95 的教训 —— error ≠ emptyunavailable / corrupt / query error / 合法 0 行 四种状态必须完全不同。
  • 硬边界:一期只加 sqlite 一个 provider不做 sqlite-vec / embedding / hybrid ranking / 新 retrieval 算法 / memory schema 重构 / .md → sqlite 迁移 / 新 Memory API。数据流必须保持「.shadow 权威 → SQLite 只做候选生成IndexEngine → 现有 read_shadow → 现有 ranking / evidence / temporal / projection」。
  • OpenClaw 只借 30%:借 Markdown=canonical / SQLite=derived index / FTS5=lexical / vector=optional / rebuild=mandatory / fallback=filesystem不搬它的整套 memory semantics(会破坏本仓已冻结的 Evidence/Temporal/Scope/Authority/Projection/Identity/Verification)。
  • T17-B 终态(2026-09-16,v1.15.96:落点是 T17-A 的 (c1)「换物化载体」,不是给 IndexEngine 加 provider —— 新边界 core/candidate/provider.tsCandidateSet 四态:ok 含合法 0 行 / unavailable / corrupt / query-error), query/materialize.ts 是唯一物化收敛点(fs provider 与今天逐字等价);core/candidate/sqlite.ts<ws>/.shadow/index.sqliteindex_meta / source / atom)。IndexEngine / deriveShadowNodes / validateAtomProjection / 打分 / 渲染一律未改(由验收门逐文件断言)。
    • 新鲜度 = 目录令牌粗信号(~5 ms,一次 listDir(.shadow))+ 变化目录细比对(逐条 upsert)+ 写侧精确信号WriterCore.derivedDirty,覆盖 patchSummary原地改写 —— 目录令牌看不见那一类)。
    • 不建 atom_fts:FTS5 MATCH(整 token)与生产 matchShadowNodes(任意子串 AND)语义不等价,实测漏召回 10,649 条(命中密集集 16.9%)⇒ 取回全部 atom 再交生产判据过滤(「不造没有消费者的表」,§十的教训)。
    • 默认仍是 fs —— 改默认归 T17-C。决策/新事实/一条更正见 adr/0095 末尾「补记(T17-B 实现轮)」; 证据与可重放见 ../.docs/fix/2026-09-16/t17b/DESIGN.md · fs-cost-findings.md · fts5-recall-equivalence.md · probe-parent-verify.mts · gate-t17b.ps1 · INDEX.md §6)。
  • T17-C 的入口(未做):§七那份 9 项验证矩阵(fs / sqlite / unavailable / corrupt / schema mismatch / source changed / rebuild / 8.8k cold build / incremental)全过之后才考虑把 sqlite 设为默认; 并发/多进程同读同写、跨平台(WSL/容器)锁与 WAL 也归这一期。

T18. decision/接进读路径与 outcomeadr/0096 里的 T3)—— 🟡 outcome 半边已接线(v1.20.6)

  • 已做(v1.20.6)mode:"decision" 调用 attributeOutcomes / outcomeReadout / renderOutcomeReadoutquery/reads.ts),生产有调用点;新增 mode/工具(62 不动)。观测侧本回合为空时仍渲染读数(pending 可见)。
  • 未做decision/choose() / engines 仍未进写侧或读侧;不暴露新工具(ADR-0096 §7)。
  • 完成判据(残余):① 若要把 decision/ 原语也接线,另切片;② npm run verify 全绿(本半边随 v1.20.6)。

T19. 概率型后端(LLM / jev)落地前,必须重审 adr/0037 那道门 —— ✅ 已结案(v1.17.0,按「吸收」收口)

  • 为什么能结案(不是风险被证伪,是设计改了):本条的判据是「引入概率后端前另立 ADR」, 而 v1.17.0 按用户 2026-09-20 的口径纠正 ——「不是接入,而是吸收」—— 把 reportedDistribution 字段、三条分布判据与唯一那个排序视图一并删除EngineDeclaration没有承载置信度的槽位adr/0037 的「❌ Confidence」结构性成立没有可引入的路径,因此不需要那道门。判据与理由见 adr/0096 §12(含 12.1 的口径对照表)。 ⚠ 0037 的顾虑本身没有被推翻,只是不再有承受它的结构。
  • 原内容(留档)adr/0096 §12 自陈:reportedDistributionadr/0037 的「❌ Confidence(决策置信度)」 之间的界限,「换个名字」划不开;T1 之所以安全,有一部分是因为唯一引擎报 null(生产里根本没有分布)。 ⇒ 引入真实概率后端时,另立 ADR,正面回答 adr/0037 理由段那串问题(抽取结果是不是事实? source 指原文还是模型?同一段话不同模型是否得到不同 Decision?如何验证无 hallucination?失败怎么办?)。
  • 依据adr/0096 §12(v1.17.0 重写)· adr/0037 §「明确不做」的理由段 · stance/planning/guard.ts:11score → optimization → preference → value → identity 链 · epistemic/action/guard.ts:5(禁 confidence)。
  • 残余(未结的部分,另记)rawOutput 里的数字会渲染给模型看,模型是否据此行动 不在本层管辖内(那是 agent 的推理,不是 shadow 的记录)⇒ 见 adr/0096 §12.5。
  • 证据test/decision-primitive.test.ts 第 ⑦ 块(EngineDeclaration 字段集恰好四个 + 标定); git logv1.17.0 提交。

T20. MATERIALS.md 自称「由磁盘枚举生成」却在本仓没有生成器,且实测缺 18 项 —— ✅ 已结案(v1.18.0,选 (a):补生成器)

  • 已结案(v1.18.0)—— 选 (a)。 生成器 = tools/materials-ledger.ts(脚本式、不导出 ⇒ 不给棘轮添 a1); MATERIALS.md 新增 §1.1(它的输出,24 行全部入账)· §5 改指它 · §2.9 的注改为已闭环缺口复核 = 0(命令见 MATERIALS.md §2.9)。
  • 并由生成器实测出一条更要紧的事实:这 24 个目录里 18 个根本没有 .git ⇒ 它们没有版本溯源版本与远端不可核 —— 所以 §1 给它们记的 HEAD(旧枚举根的读数) 在本机复核不了,不得照抄。判据后半条(缺件不静默)也满足:未核的那些显式写「未核…不编」,不是留空。
  • 原内容(留档)MATERIALS.md 的文件头把该表定为「本地全部材料的单一来源」并给了枚举口径,但实测两头都不成立: ① 没有生成器 —— Get-ChildItem tools -File | Where-Object { $_.Name -match 'material|reference|catalog|ledger' } 为空; ② 缺 18 项 —— 现枚举根 vendor/_src24 个目录里,有 18 个在本表里连名字都搜不到
  • 依据MATERIALS.md §1 的注(「换盘前」快照;旧枚举根 G:\project\dsh1 已不存在)· §5「更新方法」· §2.9 的实测与复核命令 · 同族先例:模块归属表的生成器已按同样理由重建进 tools/module-ownership.ts(v1.16.0)。

T21. 审计流(.shadow/audit/)尚未被读侧消费 —— 起点:adr/0097 D2/D4(v1.19.0)· ⚠ 部分收口(v1.19.1)

  • v1.19.1 做了什么persistence/audit-stream.tsreadAuditStream / renderAuditStreamDiag)+ 挂在 read_shadow({debug:true}) 的诊断区。材料 = materials 字段 ∪ 从 改/读 <path> 推断。 三条判据全部落地并有端到端断言test/granularity-e2e.test.ts ④⑤):材料可从读路径取到 · 审计流仍不进 _meta.json/索引(listMemories = 0)· 读失败留痕且与「还没采集」渲染成不同文本
  • 仍未做(这才是本条继续挂着的原因):材料只到「可见可查」(debug 面), 没有接进 lineage / evidence 的聚合面 —— 若将来要让审计材料参与证据派生(AtomEvidenceRef 那一层), 那是另一件事:要回答「审计记录与哪条记忆同属一条线索」(现在只有 agent + 日期两个弱键)。
  • 原判据(保留):① 一个「只有动作、无后继记忆」的 fixture 上材料仍可从读路径取到 ✅; ② 审计流仍不进 _meta.json / 索引 ✅;③ 读不到审计流时留痕(ADR-0049)✅。

T22. 写侧粒度分流没有端到端测试makeMaterialize 的 flush)—— ✅ 已结案(v1.19.1)

  • 落地test/granularity-e2e.test.ts —— 用假宿主 + createShadowCollector 驱动真实 flush, 断言文件系统层面的后果(不是「函数被调用」):① 纯动作批只写 .shadow/audit/*.jsonl、不写记忆文件、 listMemories 也收不到;② 含思维落点的批写记忆文件、审计流不动;③ capture.echo:"memory" 逃生口可回退; ④⑤ 见 T21。
  • 顺带抓出一个测试自身的假绿:假 fs 的 listDir 第一版只回文件、不合成目录项listMemories 看到 0 个日期目录,①的「收不到」断言因错误的理由通过(②的正对照立刻把它抓出来)。 已修 mock 并写明由来(adr/0097 §7.3)。

三、决策(D1–D3 待拍板;D4–D6 已按推荐决策)

D1. ChangeSet / invalidateFor:接线,还是删除?

  • 依据adr/0062-wiring-audit.md §3。
  • 现状core/change-set.ts(83 行)与 ShadowProjectionStore.invalidateFor?() 生产中未接线 —— 唯二消费者是两个测试;生产只在类型位置提到它; 实际走的是粗粒度清空 invalidateProjectioncore/writer/materialize.ts)。
  • 已明确不是正确性缺陷(投影缓存是可重建派生,清空后下次读自动重建 ⇒ 粗粒度路径正确)。 它是 ADR-0048⑤未接线优化
  • 接线的代价(已查明):需要写侧新增变更跟踪 —— 现有 rebuildIndexlistMemories 全量扫描,不产出 ChangeSet;且伴随真实取舍:清空 = 一次极小写 + 下次全量重建; invalidateFor = 读全量缓存 + 写回,换下次读更快。
  • 当前处置:已在 core/change-set.tscore/projection-store.ts代码里显式标注「生产中未接线」不臆造接线
  • 完成判据:三选一 —— ① 接线(实现写侧变更跟踪 + 性能取舍说明)② 删除(连同测试与 ADR-0048⑤ 的相应条款) ③ 维持现状(保留标注,并在 adr/0062 注明「已决策维持」)。

D2. 残余约 9% 引用漂移:是否做「跨项目根注册」?

  • 依据adr/0059-reference-drift-detection.md「负 / 已知边界」。
  • 现状:修掉两处真 bug 后,全库 2530 次路径状引用中仍有 243 次(9.6%)不可解析; 主体是跨项目相对路径(如 scripts\wslc-utils.ps1 来自 wslc1 仓库)—— 相对本工作区根确实不存在。
  • 已排除的做法不猜工作区基线 —— 「按名字猜」已被实测证伪两次(ADR-0058 的 9 例包 ID 假阳性; 第 3 轮按后缀猜路径同样错)。
  • 代价:做「跨项目根注册」需新增一处配置面 + 来源可信度设计,不是小改。
  • 完成判据:决定是否做;若做,需先定「哪些根可信、谁有权限加根」(涉及 inv 178 边界)。

D3. 是否引入 (subject, relation, object) 细粒度取代?

★★★ 第四个独立样本(v1.15.40 第 2–3 轮,论文层;MATERIALS.md §3.2,裁定 adr/0080MemStrataarXiv:2606.26511,2026-06-25,21 页;已一手读完正文 + Appendix B/C/D + Table 1/2/3) —— 取代键是 (subject, relation) 二元组object 是被比较的值;我第 2 轮写成三元组,已更正), 机制「If one exists with a different object, the new assertion supersedes it ... No cosine, no LLM judge」, 账本实现只有 valid_from / valid_to / superseded_by 三字段(as-of 作者自陈未评测)。 两条对 D3 最有用的读数:① stale-fact-error rate(分子=以被取代值作答的矛盾题数,分母=矛盾题数 30/20/20/20) 且允许弃答 / 强制作答两 regime 必须同报(Table 3:naive_rag forced 0.40/0.35/0.15/0.35 vs 它 0.03/0/0/0); ② 去掉取代层的消融:演化准确率 0.99→0.33(≈naive_rag 0.32),而条件编造率 0.04→0.25(~6×)这是「错误方向不对称」的另一侧代价(不取代 ⇒ 编造暴增),本仓此前只有「并存噪音」那一侧。 ⚠ 它的键相等性靠 LLM 抽取保证(Appendix C.1:prompt 要求「differ only in the value must produce the SAME subject and relation」),且 §7 把 entity canonicalization, relation typing 列为未来工作键这一层不可照抄(与本仓 ADR-0059 写路径无 LLM 冲突);可照抄的只有指标与协议(→ T11①/T14)。 ⚠ 可复现材料:声称发布 harness/数据集,但本版未给地址(双盲)⇒ 不得写作「已发布」。

★★ 对照面已在 v1.15.39 被实质改写(adr/0078 D6,首次本地克隆读源码: 0076 读到的 docs/adr/0004文档层面的完整协议;落地形态窄面 + 默认只建议: ① src/hl_mem/state_latest_wins.py:1 自述 narrow ... for config.version:94-95 非该 slot 一律 compatible(不做取代); ② src/hl_mem/config/models.py:508-510latest_wins_slots: tuple[Literal["config.version"], ...] 在类型层面锁死只允许一个 slot, 且 tests/unit/test_config_loader.py:596 有测试断言「TOML 不能授权白名单外的 slot」; ③ config/models.py:506 latest_wins_mode 默认 "observe"(只写审计、不执行), 而同文件 provenance/price_target/plan_fulfillment 三个默认都是 "enforce"默认值按破坏性分级。 ⇒ 它给出的不是「细粒度取代值得做」,而是「细粒度取代被收窄到一个 slot、且默认只观察,才敢上线」。 这把 D3 的问题改写为要不要为特定 slot建确定性取代,其余一律 compatible(不做取代)

★ 首要对照材料(v1.15.37 补,adr/0076:重点材料 hl_mem 的 docs/adr/0004 是一份完整的确定性取代协议,与本题直接对题,而 ADR-0073 第一遍对标完全没提到它。 三条对本决策最有用的读数: ① 它的坐标是四元组 (namespace, canonical_subject, canonical_slot, coordinate_qualifiers) —— 比三元组更结构化;且 conflict_key 只是该坐标的派生指纹,不是第五个独立真相; 候选发现必须 exact-match 坐标,「FTS、向量、编辑距离或模型判断不得扩大候选边界」。 (v1.15.39 已一手核实:100-104 的查询确为精确坐标匹配、无 FTS/向量;conflict_key = json.dumps(astuple(coordinate))。) ② 它有一条本仓尚未成文的判据:「并存噪音是可观察问题;错误关链是静默破坏。不能证明时保留多值 比制造单一真相安全。」 —— 与 ADR-0049「缺件不静默」互补(一个管缺失可见,一个管破坏保守)。 ⇒ 若采用细粒度取代,「宁可并存」不得被当成缺陷;且它有一个 historical_predecessor 分支 专门处置乱序到达(新到的记录描述更早事实 ⇒ 只接前驱,不反向关闭 current tip)。 (v1.15.39 已一手核实state_latest_wins.py:183current_tip_id 恒为旧者 ⇒ 该性质成立; 且 :109-114 的方向只由 tz-aware 的可信事件时间决定,版本量级只用于相等判定、时间并列即 needs_review。) ③ 它为本仓既有裁决(ADR-0059「不让 LLM 判语义」)提供独立量化证据: E1C 70 案 exact 54/70有 2 个危险反向;29 个双序案一致率仅 21/29 = 72.4138%(顺序敏感)。 ④ (v1.15.39 新增)拒判机制:15 条否决(8 硬 + 7 证据)一律 ⇒ needs_review; 候选 ≥17 个即拒判LIMIT 17 + local_snapshot_matches = len(candidates) < 17);CAS 失败抛错。 ⇒ 「证明不了就不关链」在它那里是代码结构,不只是原则。 注意措辞:这是「别家的读数支持我们的判据」,不是「我们验证了」。

  • 依据adr/0061-supersession-lifecycle-wiring.md「负 / 已知边界」; adr/0076-hl-mem-deep-read-2.md(v1.15.37 补的对照)
  • 现状:本仓的取代是 entry 单键 + 时间序observer/arbitrate.tsnewestByEntryOf + verdictOf)—— 粗粒度:entry 不同的跨主题取代测不到。文献(Temporal Validity, arXiv:2606.26511) 用的是 (subject, relation, object) 三元组取代。
  • 注意:引入三元组需要从记忆文本里稳定抽出 (s, r, o) —— 那一步一旦交给 LLM, 就撞 ADR-0059 的裁决(让 LLM 判语义有硬证据反对)。故若做,抽取规则必须确定性。 ⇒ hl_mem 的 ADR-0004 走的是同一条路(纯确定性、LLM 最多作离线 challenger),可作实现路径的对照
  • 完成判据:决定做 / 不做;若做,先写清确定性抽取规则并单独起 ADR(并对照 ADR-0004 的 九项前置 + 八条硬否决 + 六分支是否可直接借用)。

✅ D4. pinned / archived —— 按推荐 ③ + ① 的窄版本决策(用户「按推荐」授权)

  • 决定采 ③「纠正文档」为主 —— 把两份文档里对不可达状态的承诺改准 (README.mdNEW → … → ARCHIVED 与「pinned 永存」两处 + MEMORY.md:90 加勘误); 不采 ①(把外部权威状态写进 Derived Artifact 与 ADR-0003 冲突); 不采 ②(「人工归档」没有信号可派,强派生会造语义不符的状态)。
  • ① 的窄版本:若将来产品确实需要「人工钉住 / 归档」,须先起 ADR 论证状态落在哪一层 (必须落进 source,例如记忆文件头,而不是可重建的 _meta.json)。本 ADR 不预设它会发生
  • 依据:T3 分诊(adr/0063 §4);README.md:35 / :177 / :178 / MEMORY.md:90 行号已核实。
  • 落地:本轮已改 README.md 两处 + MEMORY.md:90 加勘误(原文保留,可追溯)。

✅ D5. metadata 认知门 —— 已落地(ADR-0066)

  • 决定采 ①「换信号」,并把判据收敛到唯一判据源 isSessionMetadataAtom
  • 依据adr/0063(分诊)+ adr/0066(信号实验 + 落地 + 实测效果)
  • 实测(真语料 7089 条,5 个候选判准对照):旧判准精度 9.8%(判 4744 条,其中 4279 条其实有工作痕迹); 文本启发式口径精度 100%(判 93 条,0 条有工作痕迹)—— 而它恰好是仓库里已文档化、从未接线的那一个
  • 落地效果(真语料 7111 条)metadata 4137 → 90;投影可见 2283 → 6478(32.8% → 91.1%); 两条读路径可见性差 67.2% → 8.9%;残余 540 条是证据门的正当拒绝 + 90 条是新判准挡的。
  • 连带处置isCognitiveAtom 已删除(规则与 validateAtomProjection 完全重复且零调用点 —— 删它是为消除「同一条规则三份实现」的病根);isMetadataMemoryText 保留(服务不 parseMemory 的读路径)。
  • 两条实测边界已写进测试:① kind=metadata 与判据是有向关系(task 分支优先,不影响可见性); ② 两份实现读的表面不同(线索头 vs 正文行,真记忆两者都写 ⇒ 真语料不分叉)。

✅ D6. 吸收 OpenViking 的三条做法 —— 已实现(ADR-0075,v1.15.35)

  • 决定(用户「按推荐」授权):三条都做,且都归为 Projection(派生可重建,不违反 ADR-0003)。
  • 落地:新增 core/abstract.ts(纯函数:deriveL1 / deriveL0 / renderSidecar / parseSidecar / sidecarDrift)+ 接线 core/writer/materialize.tsrebuildIndex + 枚举器分类判据收敛。
  • 选型(用户 2026-09-11 选定 A):sidecar 落 .shadow/<date>/_abstract.md,并listMemories_ 前缀。 三条边界:派生件不是 source · 所有输入显式传入(不读时钟/随机/fs)· 命名必须 _ 前缀。
  • 层次:记忆(source)→ L1(确定性,≤4000)→ L0(由 L1 抽取,≤256)→ _index.md 的目录摘要段。 L0 是 L1 的函数 ⇒ 层间不一致在构造上不可能(这正是原 D6 的 ②)。
  • ③ 覆盖率自报covered / pending 写成显式可解析行,由 sidecarDrift 独立重算(不信自报)。
  • test/abstract-sidecar.test.ts 6 组,含正对照(源头落后 / L0 被单独改 / 坏文件 三种坏件全被抓到) 与端到端(索引重建产出 sidecar + _index.md 引用其 L0)。
  • 默认开(与 projectionStore 默认关不同):它是派生件、写入次数有界(每日期目录一份)、 且默认关等于又一次「写好了但从不执行」(T1/T4 刚清理的那一类)。
  • 诚实标注(未做,见 T9)
    • pending 恒为 0 是构造性的 —— 本实现里 sidecar 与 _index.md同一份 recs 派生 ⇒ 不可能落后。故该字段当前只在构造坏件时有意义,不是「会真实报警的增量检测」。
    • 真机规模与耗时未测;sidecar 的召回收益未测(本轮未改检索排序); L0 抽取质量未评;存量回填未做(旧目录要等各自索引重建)。

D6-orig(保留原始线索,便于回溯)

  • 三条内容:① 目录级(日期级)abstract + overview sidecar —— 今天判断相关必须先读记忆文件, 只能靠全局 _index.md;OpenViking 是每层目录都带 L0/L1(256 / 4000 字符上限)。 ② 上层由下层确定性派生(它的 L0 从 L1 正文里抽:H1 之后、首个 ## 之前)—— 消除层间漂移。 ③ 派生件自报覆盖率与待处理变更(它的 sidecar freshness:子项覆盖数 + pending_child_changes)。
  • 实现的前置:① 定 sidecar 的落盘位置与命名(必须隐藏);② 定 _index.md 与 sidecar 的关系; ③ 加漂移棘轮。⇒ 三项均已在 ADR-0075 定案并落地。
  • 顺带记一条对 ADR-0060 的精化(已并入 B2 的决策依据):OpenViking 的层级分数传播公式是 alpha * embedding + (1-alpha) * parent,而 score_propagation_alpha 默认 1.0默认父分权重为 0。即:层级买的是「递归下钻扩大候选」,不是「分数平滑」

✅ D7. hits 的范围:其它读入口算不算命中?—— 已结案(v1.20.6,选 ②)

  • 决定(用户)② 所有返回 Memory Atom 的读入口都算命中(接受探测性 shadow_query 抬 hotness)。
  • 落地noteServedAtomsrecordServedHits;主题召回 rel 来自 renderWithinBudget;ReadQuery 用命名提取器;test/hit-accumulation.test.ts ①b 锁两集合 + ⑤–⑩ 锁各读入口。
  • 不选 ③:不引入 readHits 第二计数。
  • 原选项表与代价对比见本条历史正文(归档在下方,不改正)。

D7.(归档)原决策表

  • 依据adr/0067-hit-accumulation-trigger.md「备选」表末行。
  • 原现状hits 只在主题召回路径累积。
  • 原代价对比
    选项含义风险
    ① 只主题召回算命中hits = 「被主题检索命中」recall_shadow 视而不见
    ② 所有读入口都算已选hits = 「被任何读入口读过」探测性查询当价值信号
    ③ 分两个计数语义分开_meta 结构变更

D8. README「默认开关」表缺三列:成熟度 / 降级行为 / 晋级标准(ADR-0073)

  • 依据adr/0073-hl-mem-benchmark.md §2 行 ① 与 §3(重点材料 lohr13/hl_mem 的对标结果)。
  • 现状(实测)README.md:113-130 已有一张 「默认开关」表16 条能力 × 3 列: 能力 / 默认 / 开着会怎样·怎么开),缺三列
    缺的列为什么值得补
    成熟度(stable / beta / experimental)今天「默认关」的项里混着「稳定但耗 token」与「接口可能变」两类,读表看不出区别
    降级行为ADR-0049「缺件不静默」要求降级必须可见,但该口径目前散在代码注释与 ADR 正文里core/toolset/index.tsdegradesTorouteVerifyunavailablerecall 的关键词兜底、evidenceProviderfs 回退…)—— 没有一处能一眼看全「每个能力关掉 / 缺件时,行为退到哪」
    晋级标准什么条件下才允许 beta → stable。本仓只在个别 ADR 里零散出现「未验证/待实测」,不是能力粒度的门
  • 为什么现在没做:它不是新功能,是把已有事实(各能力的稳定度、降级路径、晋级条件)逐条核实后填表 —— 需要先把 16 条能力逐条对着代码定「降级到哪」,属独立工作量;且用户本轮授权范围是 「立 ADR + 记 BACKLOG + 补 CHANGELOG」,实现不在内
  • 注意(防过度表述):本仓不是「没有能力矩阵」 —— README 那张表已经是成熟度矩阵的表头裁剪版。 本项的准确表述是「补三列」,不是「新建表」。这个精度差异来自 ADR-0073 §2 末注的一处自我纠正。
  • 完成判据README.md 的该表补齐三列(16 条能力逐条核实填实不许留空或写「未知」); 「降级行为」一列须与代码实际路径一致(可用 read_shadow({mode:"toolset"})degradesToREADME 的「都没装会怎样」小节对照校验);若有能力当前降级不可见(违反 ADR-0049), 须单独记为缺陷而不是在表里写一句敷衍的话。

✅ D9. 投影侧「有损必须声明损失形态 + 交出恢复句柄」(ADR-0087 甲-1)—— 已结案(v1.15.89)

结论与三个前置的决定见 adr/0090-rtk-three-shapes-implemented.md §A:① 恢复载体 = 已有的源文件 (句柄 = 路径 + 段名/入口名,零新增存储,故无需哈希去重与老化);② 索引路径退原文 (2199 KB 会顶穿上下文)⇒ 改为「省略 + 显式写『不可复取』」;召回路径无句柄不许降档。 实装:retrieval/loss.ts(新)+ retrieval/render.ts + query/query.ts;测试:test/loss-and-handle.test.ts(新)。

  • 依据adr/0087-absorb-rtk-langextract-verdicts.md §A 甲-1(vendor/_src/rtksrc/core/toml_filter.rs:568-578Lossiness{None,Tail,Whole} + src/main.rs:1606-1630"Never emit an unrecoverable truncation marker: fall back to full raw")。
  • 现状(缺口):本仓 read_shadow 的 L0/L1/L2 分层只解决「给多少」没解决「省略的那部分去哪了」 —— 读侧看到「省略」时无法区分「本来就短」与「被分层省略掉了」。
  • 为什么现在没做前置决策未定 —— ① 恢复载体用什么(不许 rusqlite/gzip,ADR-0001; 用文件重做就要自己定义「同 hash 主键去重」与「老化」语义);② 存储不可用时是否允许有损压缩 (rtk 选的是「不允许,退回原文」,本仓要自己定)。未被授权替用户选。
  • 完成判据:投影/读侧输出里能区分「无内容」与「被省略」;被省略的部分有可复取的句柄, 且拿不到句柄时不许输出有损结果;两条各配一个测试(含反向不变量:句柄缺失时必须退原文)。

✅ D10. never_worse 守卫:压完比原文长就退回原文(ADR-0087 甲-2)—— 已结案(v1.15.89)

口径已定(adr/0090 §A③):比字符数**(与 max_tokens×4\text{max\_tokens} \times 4 同单位),不用 rtk 的「字节 / 4」。** 理由做成了可执行的对照断言:raw=""×60\text{raw} = "中" \times 60(60 字符 / 180 字节)vs longer="a"×62\text{longer} = "\text{a}" \times 62(62 字符 / 62 字节) ⇒ 字符口径判得出「更长」、字节口径漏判(见 test/loss-and-handle.test.ts)。出口唯一: retrieval/render.tsrenderIndexBudgetedquery/query.ts 的降档判定。

  • 依据adr/0087 §A 甲-2(rtk src/core/guard.rs:16-23 + src/core/runner.rs:16-24emit_guarded 是唯一出口;测试 guard.rs:29-66 含「相等 → 保留过滤结果」与空输入边界)。
  • 为什么现在没做口径前置未定 —— rtk 用 text.len()字节)/4,而 UTF-8 中文 3 字节/字 ⇒ 按 0.75 token/字估算,真实约 1 token/字,偏差方向与英文相反src/core/tracking.rs:1705-1714; 且 rtk 源码里没有任何 CJK 口径讨论,该偏差是深读者的推断,属未实测)。 本仓是中文重上下文,直接搬会在中文长句上误判「没变长」
  • 完成判据:先定口径(换成自己实测的估值 明确比较字节数并在字段名上写清单位), 再在裁剪/摘要出口处接一处全函数守卫;配测试:过滤结果更长 → 退原文、相等 → 保留、 空输入边界;并在中文语料上实测一次该守卫不误判。

✅ D11. verSrc 从「嵌在 note 里的字符串 + 下游正则反解」改为类型化字段(ADR-0087 甲-3)—— 已结案(v1.15.89)

落地形态(adr/0090 §A④)core/util.ts 新增 VerSrcKind + verSrcLabel(查表); CapabilityverSrcKind / verSrcVersion唯一事实源note 由它派生(渲染逐字不变 ⇒ 签入的 tools/toolset-authority.json 与双向棘轮未动一行);claimOf() 不再正则反解散文; 未知/缺失取值 ⇒ 「未标」,不许默认成强档;负断言:把弱档合并进强档会被检出(test/toolset-authority.test.ts ⑧)。

  • 依据adr/0087 §A 甲-3(rtk src/discover/mod.rs:34-49Coverage::Measured/Estimated + src/discover/report.rs:99-131 的注释原文 "Kept as its own field … instead of silently going quiet about it"); 本仓现状:core/toolset/index.tsverSrc 是个字符串参数(默认 "权威核验"),嵌进 note 散文, 下游 tools/toolset-authority.lib.tsclaimOf()正则反解它。
  • 为什么现在没做:改动面含已签入的离线棘轮 tools/toolset-authority.jsontest/toolset-authority.test.ts;且「弱档不许被合并」要靠类型承载,不是加个 ~ 前缀。
  • 落地形态(ADR-0087 §C 已给可判定选项):新增类型化字段唯一事实源note 由它派生(渲染逐字不变 ⇒ 签入的 JSON 与双向棘轮不受影响), claimOf() 不再反解散文。这正好续 ADR-0072(标签不得强于事实),且不动任何既有默认值。
  • 完成判据verSrc 有确定的取值域(类型,而非任意字符串);渲染函数逐字等于现行 note (用现有棘轮证明);有一条负断言:把弱档合并进强档会被检出。

四、未验证(需真机条件或需装依赖,代码侧已完成)

V1. 真机 semble / zg

  • 依据adr/0054adr/0059adr/0060 各自的「未验证」;本机两者实测均 ENOENT(未安装)。
  • 影响:所有与向量/证据检索有关的实测都是按 ADR-0054 已测性质建模的,不是真实向量检索。
  • 完成判据:装 uv tool install semblenpm i -g @zvec/zvec-grep 后, 跑 npm run eval:retrievalsurvey:"all",把真实数据回写 ADR-0060 / 0054。

V2. 新台账条目的 probe 旗标

  • 依据adr/0058-toolset-source-expansion.md「未验证」。
  • 现状v1.15.14 新增的 57 条经 winget show 权威核验(版本、许可证), 但各条 probe版本旗标--version / -v / version …)未逐项在本机真跑过
  • 兜底:口径已由「探测失败 ≠ 未安装」兜住(不会误报可用,只会显示「未检出」)。
  • 完成判据:装齐后用 read_shadow({mode:"toolset", survey:"all"}) 抽查; 对「已装却未检出」的条目修正旗标。

V3. maxMembers 的运行时拦截行为(仍未被触发,已量化

  • 依据adr/0056 的「未验证」;口径由 adr/0099 更新。
  • 现状:上限随 profile 层 bundle 的出厂值 maxMembers: 8(v1.20.3 / adr/0099 撤回了原先压到 4 的 override —— web profile 的那条 patch 已删;headless 本来就没设过 ⇒ 两 profile 一致)。 语义已逐行核实(创建时检查 / 无移除路径 / 失败也占名额 / lead 是保留名,见 adr/0099 §5.3), 但闸门仍未在真机触发过;不过它已被量化:23 个本地会话 / 51 715 325 字符里真 spawn_teammate 调用 0 次、运行时 TEAM_MEMBER_LIMIT 0 次 ⇒ 离触发还差 8 个成员。
  • 完成判据:真创建第 9 个 teammate,应抛 TEAM_MEMBER_LIMIT
  • 结案代价(明写,免得后人以为是漏做):名额是会话终身累计、不可释放,要真机结案就得在一个会话里 耗掉 8 个名额 ⇒ 本条允许长期不结案,只要「上限值 + 语义」有 adr/0099 §5.3 那种逐行证据即可。

V4. 真机 host.fsresolve 语义

  • 依据adr/0059test/evidence-absolute-path.test.ts 的「未验证」。
  • 现状fsExists 的绝对路径/目录修复是用 node:path + 真实磁盘模拟验证的, 未经真机 host.fs@deepseek-ai/dsh-fs-localresolve/readText/listDir)。
  • 补充:真实契约已读源码核实lib/index.js:277/341listDir(不存在)FS_NOT_FOUNDreadText(目录)FS_NOT_REGULAR_FILE),故修复方向有据;缺的是真机端到端
  • 完成判据:重启后在真会话里触发一次「绝对路径证据」的召回,确认不再报「已过时/证据缺失」。

V5. 其余测试的 mock 是否还有不忠实

  • 依据adr/0059「测试侧的教训」。
  • 现状v1.15.15 修了两处不忠实 mockmissing-dependency / recall-attributionlistDir 对不存在的目录返回 [] 而不抛,与真实契约不符),但未系统排查其余 mock
  • 教训原文mock 与宿主契约不符时,测的是 mock 不是系统(本仓 v1.15.2 踩过同类)。
  • 完成判据:审查各测试的 fs mock 是否忠实(尤其 readText 空串 / listDir 空数组的默认行为)。

V6. 审计工具未接入任何自动门禁 —— 已部分接入(v1.15.38)

  • 依据adr/0062「负 / 已知边界」。
  • 现状:本仓无 CI;tools/audit-wiring.tstools/audit-wiring.selftest.ts 靠手动跑 (npm run audit:wiring / audit:wiring:selftest)。标定测试本身是绿的,但没有自动执行。
  • 进度(v1.15.38 / ADR-0077 D2)两个自检已进 npm run verify —— tools/run-tests.ts 同时收 test/*.test.tstools/*.selftest.ts(45 项、串行、子进程隔离)。 该接入当轮就变现audit-wiring.selftest 立刻挡下「声明表放进生产面 ⇒ 工具丢掉 status=superseded 真线索」的一次自我回归(ADR-0077 D1 的自曝)。
  • 仍未接入(v1.15.38 时)audit:wiring / audit:drift分诊报告本身(它们是人工分诊工具, 输出是线索清单而非通过/失败,退出码语义未定义 ⇒ 硬塞进门禁只会制造「噪声导致跳过」)。
  • 完成判据:为两个报告定义明确的退出码语义(例如「A 段新增条目数 > 基线」才非零), 并定一处会执行它的地方(提交前脚本 / hooks / CI),否则工具会随时间失效。

已闭环(v1.15.45):两个报告都有了棘轮退出码,并已接进 npm run verify

  • 判据(新文件 tools/audit-ratchet.lib.ts,纯逻辑 + 8 组标定测试): ① 线索数只能降不能升(某桶多于基线 ⇒ 退出 1,报文给「旧 → 新」与增量); ② 下降不是违规(退出 0,但提示 --update-ratchet 收紧基线 —— 判紧会让人习惯性绕过这门); ③ 基线里有的桶在观测里消失 ⇒ 违规(缺件不静默:可能是工具坏了,不是问题没了); ④ 出现新桶 ⇒ 违规(否则整类新线索会被棘轮漏掉); ⑤ 基线缺失 / 两表皆空 ⇒ 违规(「通过」不能靠没有判据换来)。

  • 基线tools/audit-ratchet.baseline.json两个报告共用一份,分 wiring / drift 两段; 当前 wiring = {a_total 31, a2b 0, a1 17, a2a 3, a3 11, b_keys 103}drift = {drift_keys 10, drift_sites 25})。

  • 执行处npm run audit:ratchet(= 两个报告的 --ratchet 串联)已进 npm run verify ⇒ 门禁 48/48

  • 同轮踩到并修掉的真缺陷(留档)node tools/audit-wiring.ts --ratchet 漏了根参数ROOT 取到旗标字符串 "--ratchet" ⇒ 扫描目录不存在 ⇒ 0 文件 ⇒ 0 线索 ⇒ 工具「安静地全绿」, 而 --update-ratchet 会把这个全 0 读数录成基线(棘轮从此失去意义)。 ⇒ 两个 CLI 都加了闸零文件语料直接 exit 2(缺件不静默,ADR-0049);npm 脚本里显式带上 .教训与 T12/T2 同族「空语料冒充没问题」是这道门唯一的失效模式

  • 诚实边界:棘轮只覆盖计数,答不了「计数相同但线索换了一批」(需逐条 diff,未做); audit-drift 只棘轮 B 段(A 段的 fresh 计数在块作用域内,要棘轮得先把计数提到顶层,留作后续)。

  • 进度(v1.15.40 第 4 轮:拿到一个可照抄的退出码语义样板**):hl_mem 的 evaluation/tools/run_extraction_quality_smoke.py:255-257合取式表达退出码—— 「全部条目通过 恰好 1 次外部调用 保留数 ≤ 16」才 return 0。 ⇒ 与本条目要的「分诊报告退出码语义」同构:把多个必须同时成立的条件写成一条合取, 而不是「有输出就算通过」。它在 hl_mem 里没有对应物(分诊报告是 hl_mem 没有的机制)⇒ 本仓要自建判据, 可借的只有这个表达形状**。(另:benchmarks/release/core_v1.py:102-104,135 的「外部调用即失败」 用抛错桩 + 计数并以 contextmanager 包住整条链路,是最贴近本仓的一版实现思路;全文见 references.md §6.6。)

V7. 语料健康门(Corpus Health Gate)—— ✅ 已闭环(v1.15.46)

  • 来源:用户 2026-09-12 评审第 3 条:V6 只防住「语料全空」,防不了 Partial Corpus —— 「正常 31 files / 103 keys」→「工具坏了 7 files / 21 keys」不是 0,闸门不响、读数「看起来合理」; 更危险的是 V6 的棘轮对「下降」判通过并提示收紧基线工具坏了会被当成修好了,写进基线(假绿自我固化)。
  • 判据(tools/corpus-health.lib.ts,纯逻辑 + 10 组标定测试):四档,只有 NORMAL 放行
    触发退出码
    EMPTY0 文件2
    PARTIAL文件数 < 基线×0.9 · 目录数 < 基线×0.9 · 线索跌 > 20% · 哨兵文件缺失2
    UNKNOWN无基线可比2
    NORMAL在容许带内0(继续跑棘轮)
    • 哨兵index.ts / core/paths.ts / core/types.ts / core/util.ts / security/scrub.ts): 无基线也能发现「走错目录 / 递归被 junction 静默截断」(这正是 v1.15.43 踩过的坑)。
    • --update-ratchet 也受闸:EMPTY/PARTIAL 时拒绝录基线(这条是 V7 的关键)。
    • 阈值(0.9 / 0.9 / 0.2)可注入,且已在标定测试里用严/松两档证明「判据不在暗处」。
  • 覆盖四个语料消费者audit-wiringaudit-drift(带基线+哨兵)、audit-layers(自比+哨兵)、 retrieval-eval(协议常量 min_corpus_files从数据读不从代码读)。
  • 🔴 V7 首次运行就抓到我自己的一处设计缺陷(留档):两个工具共用一个 corpus, 但量的是不同的语料(wiring 扫全仓 778 文件含 dist/;drift 只扫生产面 193 文件) ⇒ drift 录基线时被 wiring 的数字判成「骤降 76%」而拒绝录制修法corpus 按消费者分开存(corpus.wiring / corpus.drift)。 教训与 V6 同族共享键 + 不同口径 = 必炸;而这次是新加的闸自己发现的(不是人看出来的)。
  • 代价(诚实标注)大幅度的合法下降现在必须走「确认 → 重录基线」(不能静默收紧)。 这是刻意的:宁可让人确认一次,也不要让工具故障写进基线。
  • 未做:阈值未用「真实故障回放」标定最优值;指纹只覆盖文件路径集合(不含内容)。

V8/V9. Count Ratchet → Identity Ratchet —— ⏸ 明确不做(用户 2026-09-12 指定为以后)

  • 问题(用户评审第 5 条):现在比的是计数。「旧:A=3,B=7,C=10」→「新:A=3,B=7,C=10」数量完全没变, 但里面换了一批 ⇒ 棘轮不响。
  • 方向:桶值从 count 变成 { count, fingerprints[] }身份而非数量)。
  • 为什么现在不做:先有「什么算受保护契约」(T15)才谈得上给桶定身份;否则会冻结一堆临时内部形态。

🧭 阶段路线(用户 2026-09-12 确立,按此顺序)

V1–V5  功能有没有            ✅ 已完成
V6     验证系统会不会假绿      ✅ 已闭环(v1.15.45)
V7     语料是不是可信         ✅ 已闭环(v1.15.46)
T8     静默降级可见化          ← ✅ **已闭(v1.15.64 B 部分 + v1.15.65 A 部分)**
T15    什么东西是契约          ← 🟡 **切片 1 已交付(v1.15.69 / `adr/0086`)**:面清单 + 稳定性分档 + 最小弃用流程;切片 2/3 待做
D1/D2/D3 + A 段 6 条 ⚠ **不要先拍** —— 它们本质都是「哪些东西算受保护契约」,故排在 T15 之后
(**A 段 6 条 = v1.15.43 的「无法判定(6)」桶,已在 T15 条目下枚举**;`T15`/`D1`/`D2`/`D3` 均已判定)
T2(B段) / T9 / T10 / T7
Contract Freeze Gate          ← 契约冻结(条件见下)
T14    冻结**契约语料**(不是当前实现)→ 真正启用回归门
V8/V9  Identity Ratchet
V/G/T6 真机与外部条件项

Contract Freeze Gate(冻结条件)F 事实台账 + V 运行验证 + T15 契约面 + Ratchet 四者齐备后, 核心契约冻结;此后新功能必须证明不破坏 Contract 才能进 mainT14 的语料快照必须排在冻结之后 —— 否则冻结的是「当前实现」而不是「契约语料」。

🧠 M1–M5 Memory Evolution 泳道(用户 2026-09-12 确立;与上面 🛡️ 契约泳道并行

用户修正了上一轮的表述:不是「先做治理、暂不做新 Memory 能力」,而是 两条泳道并行,但新能力必须围绕一个新核心方向纵向生长,不再横向堆功能两条泳道在此交汇M1 的契约条目就是 T15 Registry 的第一条真条目

🛡️ Contract Track : T8 → T15(Registry) → D1/D2/D3 → Contract Freeze → T14
🧠 Memory Track   : P1 → M1 → M2 → M3 → M4 → M5

P1. Proposal → Confirmation → Fact 原语Inference is cheap; facts are expensive.)—— 🟢 原语已冻结(v1.15.49,adr/0082);P1①② 已落地并进 verify(v1.15.50)

  • 地位Memory Track 第 0 项,先于 M1 —— 它是所有 Memory Intelligence 能力的共同架构纪律,M1 只是第一个使用者。
  • 落地物(P1①②)core/proposal.ts(纯函数:严格白名单校验 / projectFacts 投影 / 唯一统计入口 factualOnly / 候选可见性 candidateStats
    • test/proposal-firewall.test.ts11 组闸)+ tsconfig.json 显式 include(该模块尚未被 index.ts 引用,而测试按约定 import 编译产物)。 npm run verify = 50/50
  • 关键实现选择(比字段校验更强)type:"fact" 一律拒收、Fact 只由投影派生Proposal 冒充 Fact 在结构上不可能(没有写入路径)。 两条伪装负例都已成闸:{type:"fact",source:"model-proposal"}{type:"proposal",status:"validated"}且被拒的 proposal 即使有 confirmation 也不得复活)。
  • 机械不变量FACT ⇔ 有效 Confirmation ∧ 指向 Proposal ∧ Proposal 有 inputRefs;有效动作按 timestamp 升序取最后一条、同刻按 id 升序与插入顺序无关,已成测试);reject/revoke ⇒ 事实消失但历史保留
  • 六条硬禁令:proposal 不得参与 Pattern count / influenced_decision / confidence / ratchet baseline / knowledge fact / identity核心不变量:只有 FACT 能改变系统认知统计;CANDIDATE 只能改变「待确认候选」的统计。
  • proposal 必填source / model / prompt_version / inputRefs[{file,line}] / proposedRelation(缺一不得入库 ⇒ 可回答「模型为什么提这个候选」)。
  • Confirmation = 授权事件{id, proposal, actor, action, timestamp, reason?}actor 无 modelaction ∈ confirm/reject/revoke)—— Fact 是它的投影 ⇒ 未来 M4 Memory Revision 直接落在 revoke,不需要新机制。
  • 候选可见性(防 Silent Candidate Graveyard):candidateStats 输出 candidates · confirmed · rejected · pendingConfirmation · oldestCandidateDays · acceptance/rejectionRate待确认不入分母分母 0 报 null(不可测不报 0)now 由调用方传入(不读时钟)不进普通召回,但必须可见
  • ⏭ 下一步(P1③④):LLM 产生者(候选层,写入候选视图)· Confirmation 入口(载体刻意延迟决定)。 M1 真实场景已跑过一轮(M1-A′ dry run,见 adr/0081 §9)⇒ 载体拿到的是「要求清单」而非「形状」F1 行号必须自动填(手填 ⇒ 退化成 line: 1,闸在证据没了)· F2 key 建议 + 人确认(与 P1 两层同构,不需要第二套机制)· F3 确认必须看得见证据(否则 actor:"human"盲签)· F8 ✅ 已做(v1.15.53) 接受率按 actor 分层、总体率只认 human(否则 tool 自确认刷满分)。 仍未定(不得据 dry run 拍):确认是否高频 / 是否要批量 / 是否要 diff(本次 0 次真实确认动作)。
  • 待建验证(含负例):① proposal 计入 Pattern ⇒ 必须失败(当前靠 factualOnly 约定 + M3 接入时补结构门);② 缺 input_refs ⇒ 拒收 ✅ 已做; ③ 无 confirmation 的 Fact 进事实视图 ⇒ 必须失败 ✅ 已做;④ P→C→F lineage 可追 ✅ 已做;⑤ pending_age_p90 为 derived、不写回状态(留给 M1)。 诚实边界factualOnly 目前只是约定,尚无机械手段阻止未来统计直接吃 records(见 adr/0082 §7.5)。

核心思想转变Recall 不是 Memory 的终点。目标是完整生命周期 —— 发生 → 记录 → 理解 → 形成决策 → 执行 → 观察结果 → 形成经验 → 修正认知 → 再影响未来决策

#能力状态现状清点结论(逐条带证据,详见 adr/0081 §2)
M1Decision Memory(决策 → 结果 → 经验)🟢 M1-A 已落地(v1.15.51)+ M1-A′ dry run 已跑(v1.15.52):确定性归属 + 接进原语已被真跑验证还剩 M1③(key 的显式入口)· 落盘 · 读路径渲染 · M1⑥/M1⑦(dry run 新发现的两个契约缺维度)已有 50%DecisionEvent/DecisionReason 分离由 ADR-0037 冻结,见 adr/0037:29-42)。M1-A(已做)core/decision-outcome.ts = 归属规则 same-key-window/v1确定性 + 保守:同 key·窗内·取最晚前驱·并列不归属)+ toPrimitiveRecords结果事实只能经 projectFacts 产生,内容来源=观察者、确认=actor:"tool"确定性规则reason 可审计)+ pending 读数(年龄分布 + 最老 + pendingAgeP90年龄只暴露风险、不改变状态)。闸 11 组verify 51/51未做(诚实)key 由调用方显式传入 ⇒ 「key 从哪来」这条链未接(M1③);无落盘未接读路径 ⇒ 目前没有生产消费者(棘轮已按规程重录并说明)。用户拍板subjectentry 并存(只接受显式提供)· 不设结算窗口(永远 pending)· 结果来源走两层adr/0082)。
M2Outcome Memory(记结果 + 评价 + 以后是否继续相信)⬜ 待 M1 拍板后不新建对象epistemic/validation/types.ts:23 已有完整 outcome 状态机(validated/observed/rejected/expired)+ append-only 历史(实测场景 88);observer/trace.ts:26 已有 outcome{expected,actual}long-horizon 已有 ActionFeedback。唯一要做的:把已有 outcome 形态接到 Decision 上(否则就是第二个平行 outcome 概念=判据分叉)。
M1⑥决策的 disposition(「刻意不做」≠「忘了做」)已结案(v1.15.53)来源 = M1-A′ dry run 的 F6已做DecisionRecord.disposition?: "open" | "deliberate-deferral"(缺省 open ⇒ 向后兼容);读数分 pendingOpen / pendingDeferred年龄分布 / 最老 / p90 只统计 open;渲染显式标注「刻意推迟 N(不计入积压)」。闸 test/decision-outcome.test.ts ⑫。仍待用户拍板deliberate-deferral 是否允许设复查期(到期转回 open?)——未替用户决定
M1⑦候选统计按 actor 分层(防 tool 自确认刷分)已结案(v1.15.53)来源 = M1-A′ dry run 的 F8已做:新增 byActorhuman → tool → ci,只列实际有裁决的);总体 acceptanceRate/rejectionRate 只认 human,无 human 裁决 ⇒ null(不可测不报 0);revoke 独立成桶(不再与 reject 合并,也不再落进 pending);口径可机械断言 candidates = confirmed + rejected + revoked + pendingConfirmation。闸 test/proposal-firewall.test.ts ⑫⑬⑭。
M3Pattern Memory(从 N 个 Episode 产生经验)算法内核已存在reflection/patterns/success-rate.ts(decision→outcome 相关性,纯统计、确定性标记集、无 AI)+ decision-outcome.ts(重复决策/结果 tally)+ dream/compress.ts(cross-domain 抽象)。真缺口:Pattern 不是一等对象,且 reflection/types.ts:8 的结构没有反例字段 —— 用户 schema 要的 counter_examples 必须补(只报 support 不报反例=自欺)。
M4Memory Revision(记忆自己纠错,保留时间连续性)机制已有Forget ≠ Delete(ADR-0031)、superseded 生命周期(core/lifecycle.ts、ADR-0061)、append-only 历史、取代的确定性(ADR-0059/0061,adr/0080 给了「阈值不可达」的证明)。真缺口revision 不是一等对象 —— 没有留下「因哪条证据而改判」的可追溯对象(裁决只给 verdict/outcome/reflection,不改写原记忆,这是对的)。
M5Memory Utility(让系统知道什么值得记)只有 recall_count 的雏形hits 累积,见 D7;queryLog)+ 衰减(MemoryBank hotness)。useful_count / influenced_decision / prevented_duplicate_work / caused_rework 全缺前提是 M1:没有「决策→结果」就无从判断某条记忆是否影响了决策

明确不做(用户指定):现在不做 KG不做 Soul —— 先做 Decision → Outcome → Lesson 这一条闭环。


五、已知空白(文献层面就没有答案,需自己实验证明)

这三条来自四路调研的明确空白声明(作者原话:「查了但没有好答案」)。 它们是可做成原创贡献的位置,也是不可引文献充当已证的位置。

G1. 「知识带有效期字段」没有对照消融

  • 依据:知识库治理调研的空白第 1 条。
  • 现状:证据只到「过期知识造成伤害」+「确定性取代有效」,没跨到「必须存 expiry 字段」
  • 纪律:落地该字段需自证,不能引文献充当已证。
  • 进度(v1.15.40 第 3 轮 / adr/0080 §2,MemStrata 全文)文献缺口的范围被精确化—— 连一份专门讲时间有效性的论文,也没有对「有效期/双时间字段」做对照消融:它只对取代层做了消融 (D.1/D.1b),而账本侧自陈「as-of 是 a capability we build on but do not evaluate here」, 且实现里只有 valid_from / valid_to / superseded_by 三字段(§4.2)。 ⇒ G1 仍然只能自建,但可借它的消融形态:单变量 flag(retain_all_turns默认关、写路径其余冻结)、 两侧夹逼(过度合并丢静态召回 vs 不取代丢时间有效性)、以及同时报代价方向(不取代 ⇒ 条件编造率 ~6×)。

G2. CLI / 本地二进制层的「工具数量拐点」无论文

  • 依据:委派×工具台账调研的空白第 1 条。
  • 现状:所有量化拐点测的都是 API / MCP server / skill package(如 arXiv:2606.30317 的「10–15 个工具选择准确率跌破 90%」是每上下文的工具 schema 数)。 CLI 层没有任何拐点论文 ⇒ 扩目录时不能引用「20 个工具就够」,必须自建 micro-eval。

G3. 「发现缺件 → 请求装 → 宿主放行 → 装完使用」闭环无先例

  • 依据:同上,空白第 2 条。
  • 现状:没有论文把「宿主审批」当作该闭环的一等公民来评测。
  • 本仓现状ADR-0057 已实现「能力预检 + 三条硬边界」,且实测到一个反直觉约束: 宿主进程 PATH 是启动时快照 ⇒ 「先装再派」在单会话内收益为零(装完同进程看不见)。
  • 价值:这个闭环 + 该约束,是文献里没有的实测位置。

G4. 判别层(重排器)未在本系统验证

  • 依据adr/0060 §3「加判别层的优先级高于加库」。
  • 现状:该建议引自文献arXiv:2606.28367:强重排器承担流水线绝大部分质量), 但本仓无重排器,故未在本系统验证。
  • 纪律:不得表述为「已验证」。

附:不在本台账的两类东西

  • 本地探测产物_research/(四路调研的抓取脚本与原始输出、本轮的分诊工具与审计原始输出) 有意不纳入版本控制package.jsonfiles 白名单不含它,git 也未跟踪)。 它可复现,但属过程材料,不是包的构成。
  • 已完成的版本历史:见 CHANGELOG.md(v1.15.13–v1.15.19 逐版记录,含每一条的 「验证」与「未验证(诚实标注)」)。

六、审查线索(2026-09-12 对抗性审查,确证但未修

这不是「已知问题清单」的完整版,只是一次抽样审查的台账。 三条纪律见 adr/0083。 写法纪律:每条必须带 文件:行号 与后果;「确证但未修」与「已修」不得混排(已修的 7 类见 adr/0083 §1)。 审查范围不完整adaptation/ · agency/ · federation/ · long-horizon/ · simulation/ · soul/ · world/(部分) · stance/delegation/guard/* · tools/*.selftest.ts(全部未读)——未读 ≠ 无问题

6.0 优先级分级(v1.15.63 元审查补;此前 54 条线索没有优先级、没有归属 ⇒ 不可行动

级别判据§六 里的条目
P0永久改变可见状态、或让裁决给出错误结论,且当前生效6.1 全部 5 条(rebuildIndex 失败后照读陈旧索引 · flush 早退消费 pending · FutureEvidence/hypothesis 落盘失败仍播报 · 台账坏件静默清零 · meta 注册失败只 log —— 其中 4 条已在 §6.5/§6.6 修掉,逐条核对状态
P1报告/统计面偏差,或需先定语义才能动6.2 的 15 条(含 manifest.failures 恒空、_index.md 不进指纹、结构门未解析 import 计违规已修
P2待定语义:不修,需用户拍板6.3 的 5 条 + §6.10 的 isProductionPath已判定:不是缺陷

⚠ 用法:本表只给级别,不给「已修/未修」—— 那是各 §6.x 状态更新 的职责。 读一条线索时要同时看三处:原始行(§6.1–6.3)→ 最新状态(§6.11 起往回找)→ 本表级别。这是当前结构的已知代价(见 adr/0083 §14)。

6.1 高危(会永久改变可见状态,且当前不出声)

线索位置后果
rebuildIndex 失败后调用方照读旧 _index.md,且 indexDirty 无条件清除core/writer/materialize.ts:223-225 + :311-312,消费方 query/index-budget.ts(无参读 _index.mdserve 陈旧索引且无告警;下一次也不再重建
flush 早退晚于消费 pending ⇒ 整批待落盘记录被丢弃core/writer/materialize.ts:256 vs :264记忆整批静默丢失getFlushWarn() 因未设 lastFlushError 恒空 ⇒ 读侧「可能是旧记忆」的告警失效
FutureEvidence 落盘失败后仍播报 [Evidence] registeredepistemic/validation/evidence.ts:30-31 + query/validation.ts:23-24之后 mode:validate 读不到它 ⇒ 结论从 validated 掉回 observed/rejected,且没人知道为什么
hypothesis 落盘失败后仍打印 hypotheses Nepistemic/validation/evidence.ts:13 + query/observer-kernel.ts:61-62,消费方 dream/compress.ts:112用户被告知生成 N 条假设,磁盘 0 条
召回冷却台账坏 JSON ⇒ 静默归零retrieval/ledger.ts:10-12 + :21-23,消费方 query/topic-recall.ts(冷却台账消费)已冷却的记忆被重新返回(召回输出变)
meta 注册失败只 logcore/memory.ts:77-79core/writer/materialize.ts:280 调用)记忆在索引里活跃、_meta.json 无该条 ⇒ hits 永远不计、生命周期恒 NEW

6.2 中危(报告/统计面偏差,或需先决定语义)

线索位置后果
manifest.failures 恒为 [](唯一诊断通道从不传参)core/node.ts:49 + projection-store.ts:70buildManifest("1", nodes) 少第三参renderManifest 报「失败项 0」,而约 9% 原子被 reject(该 9% 引自 core/episode.ts:96-107 注释,未复算)⇒ 两条读路径可见性分歧且无人知
_index.md 内容不进指纹query/index-budget.ts(无参读 _index.md + projection-store.ts:135,:139同进程预热后清空 _index.md ⇒ 几千条库报「暂无 shadow 索引」
query-log 单行坏 JSON 无计数query/observatory.ts:92,消费方 :100覆盖率/drift 读数基于被削样本
证据路径上限 .slice(0, 12) 无披露observer/arbitrate.ts:92第 13 条起的证据路径静默消失
「候选相关」报 min(命中, 8),读失败项连 excluded 都不进observer/projection.ts:19,:34,:48,:63观测报告与主题召回可见性不一致
快照坏 JSON 静默回退到更旧快照persistence/snapshots.ts:41-45temporal/world 基于陈旧图推理(当前生产无调用方,接线即生效)
shadow_query 截断只写 query-log,不写进返回文本query/reads.ts:116-118 + core/node.ts:79-80(对照 retrieval/render.ts:26truncationNote两条读路径披露不一致
「这条记忆没有时刻」这个事实被默认值掩盖core/episode.ts:208,:216-218 + persistence/files.ts:29-30"2026-01-01 " 恒序小于带时刻串 ⇒ 恒判 superseded(×0.7);需写出侧产出非 6 位 time 才触发
zg 报错被改写成「证据不存在」evidence/zg.ts:72 + :146,消费方 observer/arbitrate.ts:98reason:"error"status:"not_found" ⇒ score×0.5 + 判 stale(真机频率未测
「读失败」与「不存在」不可区分evidence/filesystem.ts:27-31存在但不可读 ⇒ 判 not_found ⇒ 假漂移
缺 locator 被当成「存在」evidence/filesystem.ts:22if (!fs || !rel) return true)→ status: verified, confidence: 0.99缺件伪装成「已核实」;可达性未确证(默认 provider 是 zg)
all-document text 截断到 600 字无标记core/collect.ts:25长用户消息尾部从未落盘,读的人以为这就是全文
结构门解析不到的 import 不进图、退出码 0tools/audit-layers.lib.ts:134-137 + tools/audit-layers.ts:83-84把一个 import 路径改坏,就能让一条违规边从判据里消失 ⇒ 门可静默放行
--json --update-ratchet 会把空 drift 表写进基线tools/audit-drift.ts:27,:90,:134记账失真(下一次 --ratchet 会响,非静默)
falseMeasured 标红晚于清单落盘tools/toolset-authority.ts:128 vs :134-136签入的 toolset-authority.json 已含坏清单(退出码非 0,测试会红)
factualOnly / candidateStats 丢弃 violationscore/proposal.ts:249,:306「唯一统计入口」的消费者拿到干净数字,不知有记录被拒

6.3 待定语义(不修,需先拍板

线索位置为什么要先决定
identity 闸门参数两层各有默认值,且 falsy 语义不同query/observer-kernel.ts:37-40|| 5 / || 0.4)vs identity/evaluator.ts:20-22?? 5 / ?? 0.4minRecency: 0 时 kernel 实际用 0.4:是「参数默认」还是「闸门下限」?先决定语义再改
status:"compacted" 在三个谓词里三种答案core/forget.ts · query/materialize.tskeep审查者未确证可达性(compacted 在活跃集过滤时已移除)⇒ 建议只加注释,不修
successRate 渲染 toFixed(2) 后回读 /100reflection/engine.ts:29,:79 vs :93往返误差 ≤0.005,可能跨过 0.6/0.4 边界 ⇒ 同一反思在生成侧判 principle、消费侧判 anti_pattern(真语料是否踩过边界未验证
「重复」=2 次 vs 「模式」=3 次reflection/patterns/decision-outcome.ts:12 vs reflection/engine.ts:27(+identity/candidate.ts:27两个不同统计量各有理由;若要收,提命名常量而不是统一成同一个数
scope 校验里 s.includes(action) 让任意子串(含空串)通过stance/delegation/guard/scope-guard.ts:6属「校验器不健全(假阴性)」,不是本轮缺陷类;未确证影响面
_index.md/authz 相关的两个默认放行core/index-engine.ts:57,:68 + core/authorization.ts:23return !scope.workspacectx.workspace 缺失时授权过滤整体放行;未追到生产上是否可能为空

6.4 测试面类型检查缺口(已收窄,未收完

  • tsconfig.test.json 现在只覆盖本轮三道闸;对 test/**/*.ts 全量开启当时会报 83 条既存诊断(涉 12 个文件的夹具与真实接口不匹配)——已全部修完(见 §6.11;「169」是早先的错误计数,见 §6.10)。
  • 这正是 v1.15.52 那类「夹具悄悄失真」的温床,但未修 —— 修它要逐个改夹具,属独立工作项。
  • 未读:tools/*.selftest.ts 全部(审查者主动标注:审计工具的标定测试本身可能是最高危的假绿源)。

6.5 状态更新(v1.15.55):§6.1 里下列条目已修(其余原样保留)

保留在 §6.1 是为了留痕(「这条线索确实存在过」),读的时候以本节状态为准

原线索(§6.1 位置)状态
flush 早退晚于消费 pending ⇒ 整批静默丢失 + getFlushWarn() 恒空已修(v1.15.55):取 ws/fs 提到消费前;取不到则保留 pending + 设 lastFlushError
rebuildIndex 失败后调用方照读旧 _index.md,且 indexDirty 无条件清除已修rebuildIndex 返回 boolean;失败不清 dirty + 设 lastIndexError(读侧渲染「索引可能不是最新的」)
FutureEvidence 落盘失败仍播报 [Evidence] registered已修:返回 {evidence, persisted},未落盘时播报改成「未落盘」并说明后果
hypothesis 落盘失败仍打印 hypotheses N已修writeHypothesis 返回 boolean,mode:offline 报「其中 k 条未落盘」
召回冷却台账坏 JSON ⇒ 静默归零已修:坏件带 corrupt: true + 留痕;writeLedger 返回 boolean
all-document text 截断到 600 字无标记已修:截断处显式写「已截断:原文 N 字,保留前 600 字」
结构门解析不到的 import 不进图、退出码 0已修:判据移进 audit-layers.lib.ts(不是 CLI)⇒ 未解析即违规audit-layers.selftest.ts ⑨ 标定
V7 语料闸把 .git 当语料git gc ⇒ 假 PARTIAL)已修:两个工具遍历排除 .git
PARTIAL 拒绝录基线 ⇒ 闸堵死自己的修正已修:目录数判据改用文件面定案(文件健康 ⇒ 口径变化,NORMAL + 印理由);保留文件面掉/目录 <10% 两档;corpus-health.selftest.ts ⑪ 标定
core/memory.ts:77-79 meta 注册失败只 log已修(v1.15.56)registerMeta 返回 boolean + core.lastMetaError,读侧独立提示「元数据未登记」

新增线索(本轮发现,未修)

  • tools/audit-drift.ts--json --update-ratchet 仍会把空 drift 表写进基线(记账失真,非静默);
  • tools/toolset-authority.ts:128 清单落盘早于 falseMeasured 标红;
  • corpus-health 的指纹只覆盖路径集合(不含内容)⇒ 「同路径改了内容」不在该闸覆盖内(已在输出提示,属已知边界而非缺陷)。

6.6 状态更新(v1.15.56):又修 5 处(报告面/闸面优先)

原线索状态
tools/audit-drift.ts--json --update-ratchet空 drift 表写进基线已修:B 段派生提到分支之前,两条路径共用;端到端验证写出 drift_keys=11 / drift_sites=28
tools/toolset-authority.ts:128 清单落盘早于 falseMeasured 标红已修:改为先判后写(拒绝产出坏清单),与同文件 countInconsistency 的既有先例一致
core/memory.ts:77-79 meta 注册失败只 log已修:返回 boolean + core.lastMetaError + 读侧独立 ⚠(与「落盘失败」分开)
query/observatory.ts:92 query-log 单行坏 JSON 无计数已修badLines 计数 + badLinesNote 披露
core/proposal.ts factualOnly/candidateStatsviolations已修candidateStats.violations 露出;factualOnly 写明边界与取用路径

闸:test/review-fixes.test.ts ⑥(violations 两面一致)⑦(坏行计数与披露)。

仍未修(台账剩余)manifest.failures 恒空 · _index.md 不进指纹 · 证据路径上限无披露 · observer/projection.ts 可见性不一致 · 快照坏件回退更旧 · reads.ts 截断只写 log · episode.ts 缺时刻被默认值掩盖 · zg 报错→not_found · filesystem.ts 读失败/不存在不分 · 缺 locator 当存在 · §6.3 六条待定语义 · 169 个测试类型错误(实为 83 条,已修完 → 0,见 §6.11)· 整目录未读。

6.7 状态更新(v1.15.57):又修 4 处(证据面/报告面 —— 会直接改变裁决的两种谎)

原线索状态
缺 locator 被当成「存在」(evidence/filesystem.ts:22)⇒ verified/0.99/fresh已修fsExists 改三态;undecidableunavailable(0/stale + reason)
filesystem.ts 读失败与不存在不分已修:只有明确不存在(FS_NOT_FOUND / ENOENT)算 missing,其余 undecidable
zg 报错 → not_foundevidence/zg.ts:72)⇒ 假漂移已修unavailable: true, reason:"error"MAXBUFFER 单列 output_too_large
manifest.failures 恒空(core/node.ts:49 + projection-store.ts:70已修deriveShadowNodeFailures(同源判据)+ rebuild(derive, failures) + 惰性 thunk;不变量「节点+失败=原子」成闸

仍未修_index.md 不进指纹 · 证据路径上限无披露 · observer/projection.ts 可见性不一致 · 快照坏件回退更旧 · reads.ts 截断只写 log · episode.ts 缺时刻被默认值掩盖 · §6.3 六条待定语义 · 169 个测试类型错误(实为 83已修完 → 0,见 §6.11)· 整目录未读(含 tools/*.selftest.ts)。

6.8 状态更新(v1.15.58):披露面 4 处 + 标定测试自身的假绿 6 处

原线索 / 新发现状态
reads.ts 截断只写 log、返回文本不提已修shadow_query 附「命中 N · 只返回前 limit · 还有 k 个未显示」(与 retrieval/render.tstruncationNote 对齐)
证据路径上限无披露(arbitrate.ts slice(0,12))已修:导出 EVIDENCE_PATH_CAP + droppedByCap,experience 渲染与 ev.unverifiedByCap 均带出
快照坏件回退更旧、无声已修:回退时打印「跳过了哪些 / 实际用了哪份」+ 文档写明回退语义
observer/projection.ts 可见性不一致已修:新增 unreadable(读不出 ≠ 不相关)与 relTotal(报上限的命中数)
audit-wiring.selftest ⑪ 是同义反复(致命假绿)已修:分桶判据搬进 lib(bucketOf)两边共用;⑪ 改为「四桶正例 + 三条反例」+ 语料非空;变异验证(改坏必红)
audit-drift 判据 ③ 零标定(NEG-2 走不到它)已修:加差分对 POS-4/NEG-6(只差探针局部名,结果必须相反);变异验证(废掉 probeVars 必红)
isTestPath 判据零覆盖(它在 CLI 里)已修:搬进 lib + 新增 ⑬(含「与旧写法必须不同」的反例)
audit-layers.selftest ③ 用空判据表断言主方向已修:改用真方向表(只清白名单)
retrieval-eval.selftest 的恒真断言(常量与自己比)已修:改为对字面量断言
audit-drift.selftest 真仓库断言无下限(空语料也全绿)已修:补 prod.length > 0
corpus-health.selftest footer 与代码不符已修:footer 改正 + 下方新线索

新线索(未修)

  • retrieval-eval.ts:117-120 的「语料太小 ⇒ PARTIAL」是第二份实现(读协议常量),与 classifyCorpus 判据分叉且未标定
  • audit-wiringaudit-driftisProductionPath EXCLUDE 不同tools/ 算不算生产面),两个 selftest 各自把相反期望锁死 ⇒ 统一前需先拍板
  • 所有 CLI 接线(退出码、--update-ratchet 拒绝分支、共用基线分写段)零自动断言(两处 footer 已自认);
  • toolset-authority.ts 未接进 npm run verify

6.9 状态更新(v1.15.59):CLI 接线盲区已做成断言 + 两处判据收口/锁口

原线索状态
所有 CLI 接线零自动断言(退出码语义 / --update-ratchet 拒绝分支 / 共用基线分写段)已修:新增 tools/cli-wiring.selftest.tsspawn 真 CLI,5 组)—— 先探针确认本环境可 spawn(execFileSync 管道可用、退出码可读)
retrieval-eval.ts:117-120 的第二份「语料太小」判据(未标定)已修:抽成 corpusFloorVerdict + selftest ⑬ 锁边界;两份判据刻意保留(绝对下限 vs 相对容许带,拦不同故障)
audit-wiringaudit-driftisProductionPath 分叉已锁住、未统一:新增 ⑭ 断言「差异恰好tools/」,任何一侧改动即红 ⇒ 等拍板 tools/ 算不算生产面(统一会改基线口径)
toolset-authority.ts 未接进 verify原因已实测--check 本机 >120s(winget 探测)⇒ 是耗时,不是「忘了接线」

仍未修isProductionPath 口径统一(等拍板)· --update-ratchet 成功路径未自动化(会改真实基线)· 169(实为 83,已修完,见 §6.11)· §6.3 六条待定语义 · 整目录未读。

6.10 状态更新(v1.15.60):一条线索判定掉(不是修掉,是判定「不需要改」)+ 一处计数更正

原线索状态
audit-wiringaudit-driftisProductionPath 分叉(「等拍板 tools/ 算不算生产面」)已判定:不是缺陷,是命名问题。两者问的是不同问题 ——「谁可能调用这个导出」(⇒ 含 tools/,CLI 是真调用者)vs「产品模块之间有无重复判据」(⇒ 排除 tools/)。强行统一必然让一边错。⇒ 按用途改名isCallerCorpusPath / isProductModulePath)+ 注释写明各自口径与已知副作用(drift 看不见 tools/ 内部分叉)+ ⑭ 从「未拍板」改为「刻意约定,差异恰好是 tools/」并锁住两侧无分歧部分
169 个既存测试类型错误」计数更正:169 是 tsc 的总输出行数(含续行),诊断条数实为 83。教训:把「输出行数」当「问题条数」= 伪精度(同数字两口径)

仍未修--update-ratchet 成功路径未自动化(会改真实基线)· 83 条测试类型错误(本轮在修)· §6.3 六条待定语义 · test/claim-admission-single-source.test.tstest/lifecycle-signal-table.test.ts各自手写的同名 isProductionPath(刻意:ADR-0070 把声明表放测试面;但与 lib 改名后的名字不再一致,属弱重复)。

6.11 状态更新(v1.15.62):测试面类型检查扩到全部(83 → 0)

状态
tsconfig.test.json 只覆盖 3 个文件已扩到 test/**/*.tstypecheck:tests(在 verify 内)现在守整个测试面;83 条诊断 → 0
「169 个测试类型错误」这个数字⚠ 已更正为 83(169 是 tsc 总输出行数,含续行);见 §6.10
批量改测试的可信性独立核实:剥掉类型 cast 后所有断言逐字相同;新增的「补必填字段」经行为核对(旧/新输入喂同一产物,结果相同);@ts-ignore 0 处
core/knowledge/retrieval.ts:60 声明窄于真实契约(__path已修:声明补 __path?: string、去掉 as any
test/ 中两处手写的 isProductionPath(ADR-0070 刻意放测试面)⬜ 未改(与 lib 改名后不同名,属弱重复
epistemic/world/explainValidation History: 旧标签保留 + 纠正从句(既有断言 recall-attribution.test.ts:3230 钉住该串;改名需同时改断言,留待后续)

七、审查线索(第二批:从未被读过的目录,2026-09-12,v1.15.61)

三个只读审查(federation/+world/+reality/long-horizon/+simulation/+soul/+planning/adaptation/+agency/+continuity/)。 纪律同 §六:每条带 文件:行号 与后果未修 ≠ 不存在;报告者的「排除项」与「命中项」同等重要(三份共排除 40 余条)。

7.0 优先级分级(v1.15.63 元审查补)

级别判据§七 里的条目
P0认识论层:让不该成为事实的东西成为事实(或反过来)7.2 的「supported 由调用方参数决定」·「工具 schema 与四处读点不一致」·「claimOf 从不与 ValidationTimeline 交叉核对」;7.4 的「五个边界布尔省略即 false 并覆盖全局记录」
P1守卫失效 / 静默丢弃 / 写失败渲染成成功7.2 的「mode:"world" 用残缺图覆盖」(已修)·「四处写失败渲染成成功」(已修前三处,world 侧未修);7.3 的「三处守卫永不失败」·「horizon 只写不读」·「soul 损坏 ≡ 没配置」;7.4 的「chosen 算完即弃」·「catch 三态压平」
P2命名/标签/披露类,或需先定契约7.2 的 hidden 从不被读 · 盲点 slice(0,4) · 装饰性守卫;7.3 的 || "available" 类默认值 · 三套 satisfiedConstraints;7.4 的 kind 无白名单

⚠ 可信度:§七 的条目来自只读审查,报告者未跑测试;标「已修」的以 adr/0083 §12 为准,其余为静态推演(不是实测结论)。

7.1 一条推翻既有假设的结论(重要)

adaptation/ · agency/ · continuity/ 三层全部已被生产接线query/adaptation.ts:7-9query/agency.ts:7-9query/contverify.ts:7-9query/query.ts 路由与各 family runner → index.tsread_shadow 转发)。 ⇒ 此前把它们记成「未接线 / 潜在」是错的;这些缺陷按当前生效定级。唯一「仅潜在」项是 stance/agency/guards.ts:22-24hasNoUpgradeApi(恒 return true、零调用点)。

7.2 认识论层(federation/ · world/ · reality/)—— 最贵的几条

线索位置后果
supported 由调用方参数决定(唯一判据源的上游被绕)epistemic/reality/claim/engine.ts:20-22,35,40query/reality-model.ts:29args?.validationsstatus 只由 {id,outcome} 字符串决定;validationHistory 从不与 epistemic/validation/history.ts 交叉核对(全仓无 join)
validations / visible / hidden / hiddenA/B / distortion 不在工具 schema 里index.ts:163-295(读点在 query/reality-model.ts:29query/federation.ts:26,31,35,48-49分支甲:host 剥离未声明参数 ⇒ validations 恒 []Representation 层恒为空;分支乙:透传 ⇒ 可声明 outcome:"validated" 直接造 supported
mode:"world" 用残缺图覆盖落盘图query/world.ts:39-40 + epistemic/reality/claim/persist.ts一份坏 claim ⇒ 静默少返回 ⇒ 不可逆覆盖(本轮已修:坏件时不覆盖 + 出口披露)
四处写失败渲染成成功epistemic/reality/registry.ts:10epistemic/reality/claim/persist.ts:10epistemic/federation/reality.ts:20epistemic/world/persistence/persist.ts:11-15「写入被拒」与「已登记」逐字不可区分(本轮已修前三处world 侧未修)
谓词两套判据交集为空epistemic/reality/types.ts:41 的动词表 vs epistemic/reality/claim/engine.ts:10OBSERVABLE_PREDICATES正则分支命中的谓词必然query/reality-model.ts:32 拒绝 ⇒ 中文/be 动词写法注定产不出 claim
Validation History 标签数的却是 claim 条数epistemic/world/explain/explain.ts:14没验证也会显示「验证历史」(本轮已修:改 basedOnClaims + 明说不是验证历史)
subject 未匹配时静默换成无关表示 / 空 subject 时拉全库 observationepistemic/world/explain/explain.ts:9 + query/world.ts:45错误归属的证据被当成「为什么这个结构存在」(本轮已修:回退时出声;全库 observation 那条未修
hidden 声明了但从不被读epistemic/federation/guard.ts:2,11 + query/federation.ts:31隐藏项对比恒空
盲点列表静默截断 slice(0,4)epistemic/federation/difference.ts:12unresolved 是子集而读者无法察觉
validated 由调用方布尔参数授予epistemic/federation/stability.ts:9-11 + query/federation.ts:56不核对任何 ValidationResult
置信度四因子全是常量(toFixed(2) 呈现)epistemic/reality/claim/engine.ts:23-26uncertainty 实际只取 {0.3,0.6} ⇒ 伪造精度
装饰性守卫(FAIL 分支不可达)epistemic/world/guard/relation-guard.ts:12epistemic/federation/contract.ts:10守卫挡不住当前代码,只挡未来回归
空主题 claim 折叠成一个对象且不报epistemic/world/builder/representation-builder.ts:16-18其余 claim 无对象承载

测试假绿(认识论层)test/recall-attribution.test.ts:66 的 mock register 零参数校验 ⇒ 端到端用例跑在生产 schema 表达不出来的路径上(这正是上表第 2 条看不见的原因)· isExchangeable 改成恒 true 仍全绿(concept-guards-2.test.ts:27-30)· isRelationHypothesis 换成黑名单实现仍全绿(:68-70)· 恒真 !includes(...) 群(:3132/3141/3167/3199/3210/3231)· 空 store 断言(:3148/3166/3174)。

7.3 long-horizon/ · simulation/ · soul/ · planning/

线索位置后果
三处守卫永不失败(被检对象是调用方刚构造的常量/已丢弃违规字段后才重建)query/planning.ts:17,25,27query/sim-action.ts:31,38objective(source:external) 由硬编码写死;assertCandidateNoScore/assertCandidateClean 的拒绝分支不可达
mode:candidate 的假设不校验 Assume 措辞且缺省伪造 "Assume change"query/sim-action.ts:37「假设必须 Assume X」只在 mode:simulate 强制
未匹配条件落回 RULES[0] 并把 r-latency 写进 lineageepistemic/simulation/engine/simulator.ts:13伪造匹配
result 不在 read_shadow 参数契约里,而措辞守卫依赖它index.ts:274-277 vs trajectory/long-horizon/engine/interaction.ts:11-17,43,57模型侧拿不到该文本 ⇒ 224/225/226/228 恒对 "" 通过
horizon 四对象只写不读.shadow/horizon/** 全仓无读取者)trajectory/long-horizon/persistence/persist.ts + persistence/files.ts:38写了也没人读
soul 读失败 ≡ 没配置soul/soul.ts:8-11 + query/lenses.ts 的 soul 透镜损坏被报成「无 Soul 配置」
{"identity":"architect"} 两处两答案soul/identity.ts:10 vs identity/timeline.ts:16同一配置 read_shadow({identity:true})identity-advance 得出不同身份
satisfiedConstraints 三套判据stance/planning/render.ts:14(子串 "under")/ stance/planning/types.ts:29 / stance/agency/engine.ts:27同一概念三种算法
`"available"/
写失败仍 ok:truetrajectory/long-horizon/persistence/persist.ts:7,10,13,16磁盘满/EACCES ≡ 记录已存

测试假绿assert.ok(true):3608)· applyRule 改成恒返 RULES[0] 仍全绿(场景 131-138)· planning.ts:27 守卫整段删掉仍全绿 · isFactLike 改成恒 false 仍全绿(concept-guards-2.test.ts:74——断言在前缀那步就返回)。

7.4 adaptation/ · agency/ · continuity/当前生效

线索位置后果
五个边界布尔省略即 false = 约束全关,写入全局 boundary.json覆盖旧记录trajectory/continuity/engine.ts:24persist.ts:14-15guard.ts:6-15query/contverify.ts:49-50裸调 mode:"observer-boundary" 把「五条边界全不存在」写成事实
chosen 算完即弃 ⇒ 违约候选照样被选中且硬回填 constraint_satisfiedstance/agency/engine.ts:27-28,30selectedCandidateIdcandidates[0] 两条路都绕过约束检查
同一句 "I own this repository" 两处相反答案stance/agency/guards.ts vs stance/delegation/guard/expansion-guard.tsresultNoOwnership审计结论不一致(逐字正则推演)
before 无任何守卫却同级落盘并渲染trajectory/adaptation/engine/adaptation.ts:32-39未过滤字段进审计文本
catch { return null } / catch { /* 继续 */ } 三态压平trajectory/continuity/persist.ts:28,31,34trajectory/continuity/engine.ts:62-80损坏 ≡ 为空(ADR-0049)
写失败仅 console.log,seam 直接渲染成功stance/agency/persistence.ts:11,21trajectory/adaptation/persistence/persist.ts:7,10,13trajectory/continuity/persist.ts:12-24「记录已建立」与「写盘被拒」不可区分
kind 无白名单(schema 写了枚举,引擎裸 stringtrajectory/continuity/engine.ts:45 vs index.ts:285types.ts:14任意 kind 通过并建目录
basedOn:"exp-1"(字符串)满足 length 检查trajectory/adaptation/engine/adaptation.ts:28stance/agency/engine.ts:16,35trajectory/continuity/engine.ts:31畸形值满足 lineage 闸门,先落盘后在 render 抛 TypeError
隔离名不符实:写用 r.workspace、读用 args.workspace从不比对会话 wstrajectory/continuity/engine.ts:45persist.ts:24query/contverify.ts:67,75guard.ts:27唯一围栏是平台 sandbox 档位

测试假绿concept-guards.test.ts 只 import dist 纯谓词、零接线(删正则的英文/中文半边仍全绿)· recall-attribution.test.ts 8 处 store.keys().filter(...includes("autonomy"/"preference"...)) 因键是固定文件名恒真 · :3607-3609 字面 assert.ok(true) · :3655-3659 把「零候选 + selectedCandidateId 仍返回 constraint_satisfied钉成期望行为(缺陷被测试固化)。

7.5 本轮未修(如实列出,不缩小承诺)

上面全部条目中,本轮只修了认识论层的 6 处(见 adr/0083 §12)。其余(含 §7.2 的 schema、§7.3 的守卫、§7.4 的边界布尔)未修。 三份审查的报告者都明确声明:未运行任何测试/构建,「改坏仍通过」类结论为静态推演,未实跑验证。