Skip to content

[bug] security-doc.test.js 的 bullet() 只校验第一条命中:SECURITY.md 里重复出来的路由清单副本对守卫隐形(#509 撞到) #523

Closed

Description

@modusensus

现象

SECURITY.md 的路由清单靠 test/security-doc.test.js 与代码里的 policy 声明比对,但取文档条目那一步只读第一条命中前缀的行——dsh-mneme/test/security-doc.test.js:107-111:

function bullet(prefix) {
  const line = doc.split(/\r?\n/).find((l) => l.startsWith(prefix));   // ← 只看第一条
  assert.ok(line, `SECURITY.md 里应有以「${prefix}」开头的清单行`);
  return routesInBullet(line);
}

于是「同一条目出现两份」这件事守卫完全无感:改其中一份就全绿,另一份可以一直过期。AGENTS.md 自查 9 那句「那张表是代码的投影,不是愿望清单」,在投影只验一份的时候,第二份就是没有守卫的谎话。

实到的证据(#509 撞到,我核过形状)

提交 - **Gated routes 条数 - **Open reads 条数
3bf0ed7(他 merge main 之前) 1 1
b5c0b09 Merge remote-tracking branch 'upstream/main' 2 2
52723c6(当前 head) 2 2

两份 Open reads 逐字相同(各 1065 字符);两份 Gated routes 不同——第一份含 /memory/provenance,第二份不含,且没有任何独有内容(严格子集)。这条 PR 的 CI 在这个形状下全绿,因为 .find() 读的是第一份。

值得单拎出来的是:这份陈旧副本的危险不在于它写错了什么(读者看得到,也看得懂),而在于守卫结构上看不到它。我们所有「文档与代码平价」的信心都来自那条测试,而它对重复条目零感知。

为什么这不是 contributor 的欠账

「清单只许有一份」是我们从 #411 起立的纪律(reclaim-http.js / share-http.js 那条「两张皮只许剩鉴权与路径不同」)。纪律立了,守卫却只看第一份——所以任何一次把 SECURITY.md 解成「两边都要」的 merge 都会静默留下第二份。这条归我们自己修。

修法

bullet() 改成先收集全部命中,再判两件事:

  1. 恰好一条——多于一条直接红,并把重复的行号报出来(不然是个「哪份算数」的新问题);
  2. 内容仍是代码里 policy 声明的投影(现有 missing / stale 两条断言不动)。

四个 prefix 都走这条路:- **Gated routes / - **受门控的路由、- **Open reads / - **开放读。

验收(变异自证,缺一条不算修完)

  • 把 SECURITY.md 的 Gated 那一行原样复制成两份 → 必须红;
  • 复制成两份且内容不同(第二份漏掉 /memory/provenance)→ 也必须红;
  • 清单恢复唯一 → 绿,且其余断言不受影响。

关联

Activity

  1. added
    bug功能异常、崩溃、数据错误(agent 诊断 → 人工确认)
    on Oct 29, 2026
  2. moved this from 📋 Backlog to 🔨 In Progress in dsh-mneme 任务看板on Nov 2, 2026
  3. modusensus commented on Nov 2, 2026

    @modusensus
    MemberAuthor

    修好了,PR 在这:#544

    你诊断得对,我先把证据链核了一遍再动手:

    • bullet() 确实是 doc.split(/\r?\n/).find((l) => l.startsWith(prefix)),.find() 只回第一命中;
    • b5c0b09(feat(api): distill_records 补 HTTP 读出口,记忆溯源可查(#421) #509 那次 merge)确实把两份 Open reads / Gated routes 一起解了进来,第二份 Gated routes 是严格子集(少 /memory/provenance);
    • CI 四条腿全绿,因为守卫读的是第一份;
    • b1491ff 只删掉了重复副本,.find() 原样留在那里。

    所以这条 issue 没被治,只是被扫到了地毯下——你说的是对的。

    修法

    bullet() 先收全部命中(带行号),空了照旧报「应有以…开头的清单行」,多于一条直接红并列出行号。四个 prefix 全走这条路。内容侧那两条 missing / stale 反向断言一字未动:这次补的是「有没有第二份」,不是「这一份对不对」。

    两个刻意的地方:

    1. 报行号而不只报条数。 第二份被抓住之后,「哪一份才算数」会变成下一个要人来回翻文件才能回答的问题,行号把这步省掉。
    2. 不去重后校验合并结果。 把两份并起来再比等于默许拆分清单(一份写一半),而这张表是给人读的一份东西,拆开之后没有任何一处能读出全貌——那比红更糟。

    变异自证

    按你要求的三种形状都验了:逐字相同两份 → 红;第二份把 /memory/provenance 换成陈旧值 → 也红;恢复唯一 → 绿。

    另外把守卫本身做了变异:assert.equal(hits.length, 1, …) 改成 assert.equal(hits.length, hits.length, …) 后新用例立刻红(6 pass / 1 fail),恢复后 7/7 绿。这条不验的话,绿只能证明「测试跑过了」,证明不了「守卫咬得住」。

    全量 npm test 2610 tests / 2609 pass / 0 fail / 1 skipped。

    SECURITY.md 本身一个字没改(b1491ff 已经删干净了),不新增配置键、不碰 src/。


    顺带一句流程上的事:#509 那个 PR 没带 Fixes #N(可能它本来就不打算关这条),所以重复清单进 main 时 issue 本身没被联动。你这轮报告是唯一把它拦下来的东西,谢谢。

  4. added a commit that references this issue on Nov 2, 2026
  5. moved this from 🔨 In Progress to ✅ Done in dsh-mneme 任务看板on Nov 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bug功能异常、崩溃、数据错误(agent 诊断 → 人工确认)

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions