## 现象 `SECURITY.md` 的路由清单靠 `test/security-doc.test.js` 与代码里的 `policy` 声明比对,但取文档条目那一步只读**第一条**命中前缀的行——`dsh-mneme/test/security-doc.test.js:107-111`: ```js 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`)→ 也必须红; - 清单恢复唯一 → 绿,且其余断言不受影响。 ## 关联 - #509:撞出这件事的 PR。他分支里那对重复由他自己删(第二份,含那条旧 Gated),不在本 issue 范围内。 - AGENTS.md 自查 9、#411「两张皮只许剩鉴权与路径不同」。
Activity
docs(security): 删掉 merge main 时重复出来的那对路由清单
modusensus commentedon Nov 2, 2026
修好了,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);b1491ff只删掉了重复副本,.find()原样留在那里。所以这条 issue 没被治,只是被扫到了地毯下——你说的是对的。
修法
bullet()先收全部命中(带行号),空了照旧报「应有以…开头的清单行」,多于一条直接红并列出行号。四个 prefix 全走这条路。内容侧那两条missing/stale反向断言一字未动:这次补的是「有没有第二份」,不是「这一份对不对」。两个刻意的地方:
变异自证
按你要求的三种形状都验了:逐字相同两份 → 红;第二份把
/memory/provenance换成陈旧值 → 也红;恢复唯一 → 绿。另外把守卫本身做了变异:
assert.equal(hits.length, 1, …)改成assert.equal(hits.length, hits.length, …)后新用例立刻红(6 pass / 1 fail),恢复后 7/7 绿。这条不验的话,绿只能证明「测试跑过了」,证明不了「守卫咬得住」。全量
npm test2610 tests / 2609 pass / 0 fail / 1 skipped。SECURITY.md本身一个字没改(b1491ff已经删干净了),不新增配置键、不碰src/。顺带一句流程上的事:#509 那个 PR 没带
Fixes #N(可能它本来就不打算关这条),所以重复清单进 main 时 issue 本身没被联动。你这轮报告是唯一把它拦下来的东西,谢谢。Merge pull request #544 from slow-stack/fix/523-security-bullet