Repository navigation
docs-drift's route bridge counts identifiers found inside COMMENT text — an English sentence in a handler can mint a route anchor #9432
Description
Activity
Claim: PM loop round 7
Session:session_01XqDQYVU5smx29ts9pAErja· Branch:claude/issue-9432-comment-text-route-anchors· Worktree:objectstack-issue-9432· Domain:domain:devx
File surface:scripts/docs-audit/affected-docs.mjs+ 其自测 — stop on breach; explain in the report
Container & model:model=opus面已实测空出:PR #9431(#9294)已合并,我扫了全部 18 个开着的 PR,没有一个持有
affected-docs.mjs。⚠️ #9331 排在你后面,同一个文件、同一个自测计数器。 它的缺陷是「anchor 字符串根本不在目标页里」(报告时对目标页做校验),是输出侧;你的是输入侧(注释文本里的标识符不该铸出 route anchor)。碰到它的地盘就停下并在报告里说,别顺手吸收。
Generated by Claude Code
{ "issue": 9432, "status": "done", "branch": "claude/issue-9432-comment-text-route-anchors", "pr": "https://github.com/objectstack-ai/objectstack/pull/9502", "premise_still_valid": true, "summary": "The residue #9431 predicted is real and reproduces on current main: parseRegistrarSource scans handler windows for bare identifiers over RAW source, so a LEAF symbol named only in English prose still bridges. Live leaf specimen 40d5b2d4c (#9405): BOTH route anchors that run produced came from comments and nothing else (promoteDraftForPublish -> /:type/:name/publish at rest-server.ts:5324,5376; publishPackageDrafts -> /:name/state/:field at 5694,5722), listing 3 pages that name none of the changed symbols. Structurally, 57% of what the scan reads is prose (7,649 identifier slots raw vs 3,320 masked) and 99 code-shaped identifiers can mint a doc-matching route anchor from comment text alone. Fix: parseRegistrarSource reads maskComments(text) (the #9367 shared scanner). The mask covers the path: scan too, which drops the phantom /api/users/:id minted by a JSDoc @example in route-manager.ts (43 route tails -> 42). Chose maskComments over stripComments because this scan reasons in line positions and in what precedes path: on a line; the #9367 lazy-matcher hazard does not apply (the only lazy quantifier is (.*?) inside a per-LINE regex). PM assumption 2 (should a comment-named symbol stay a weaker DOC anchor) is answered NO because there is nothing to keep: a symbol is already a doc anchor from its OWN declaration in section 3, and comment text feeds only the bridge. Also filed #9503, a separate input-side defect the fix does not close.", "tests": "All run after the final commit, at a6b134644, under flock /tmp/os-heavy-verify.lock. Gate union derived from the actual changed path with `node scripts/pm/dispatch-gates.mjs scripts/docs-audit/affected-docs.mjs` -> named exactly check:docs-audit-scope and check-affected-docs.mjs, nothing new. `node scripts/docs-audit/affected-docs.mjs --self-test` -> 220 cases pass (212 before; +8). `node scripts/docs-audit/check-affected-docs.mjs` -> 220 cases pass. `pnpm check:docs-audit-scope` -> 220 + 22 cases, scope in sync (179 docs), 9 release pages read-only. `pnpm check:nul-bytes` -> 75 assertions; 6130 files, no raw control bytes. `node scripts/js-comment-mask.mjs --self-test` -> 15 cases pass (run because this PR adds a consumer). REVERSE VERIFICATION, from the committed state: restoring `const lines = text.split()` turns exactly 3 cases red and nothing else -- the comment-only leaf enters the window, the @example mints /api/users/:id, and the comment-only leaf selects the publish route -- direction as predicted (red, no reversal, no diagnostic-count increase); restored with `git checkout branch -- path` and re-verified 220 pass. PRECISION/RECALL MEASUREMENT (the card required this before any stripping): tool from origin/main at 017c27e73 vs this branch, each run against a worktree checked out at the commit. #9192 headline commits: 9e2e68206 3 docs/2 anchors -> unchanged; 30b1c636a 4/2 -> unchanged; 07ad42463 1/3 -> unchanged; 3851f87f0 18 docs/36 anchors -> 18/35, losing FieldSchema -> /forms/:slug/lookup/:field (one comment line, 7996), no page moved. New specimen 40d5b2d4c: 6 docs/9 anchors -> 3/3. Sweep over the last 60 commits touching packages/: 57 identical, 3 changed, 0 anchors GAINED. Every row that moved is a wrong row: 19539b4b2 lost ui/forms.mdx via /forms/:slug/lookup/:field, and that page has 0 occurrences of inlineColumns / relatedListColumns / InlineGridColumn; 0668f02a6 lost shares.revoke (sdk) + /:object/:id/shares/:shareId (route) off one comment line at 8849 while every code-derived route survived, no page moved; 40d5b2d4c lost 3 pages, and the one that looked like a real recall loss is not -- publishMetaItem does call promoteDraftForPublish, but the two hunks that made it an anchor are DOC-COMMENT-ONLY, so no behaviour moved, and state-machine.mdx + metadata-service.mdx contain zero occurrences of all three changed symbols while client-sdk.mdx names only the untouched sibling publishItem. Measured recall cost: ZERO. Window-widening direction (a commented-out path: no longer truncates the previous window) measured on today's tree: 0 identifiers added to any window. Cost: 115 ms to mask the 19 registrar files (888 KB); a whole run 387 ms -> 480 ms. CI on the draft PR is in_progress at report time, per the report-at-draft-PR contract.", "open_questions": [], "out_of_scope_findings": [ "filed as #9503: parseRegistrarSource only sees a `path:` whose value is a string/template literal, so a registration with a variable path (`path: publishedPath` at rest-server.ts:5747, plus basePath/metaPath/packagesPath/evt.path -- 86 literal sites vs 33 non-literal `path:` lines) is neither a site nor a window boundary; the previous route's 150-line window swallows that whole foreign handler (/:name/state/:field's window runs 5661-5811 over it) and its CODE identifiers bridge to the wrong route, while the variable-path route gets no window of its own at all (a recall half). Survives both #9432's fix and #9294's." ] }
Generated by Claude Code
✅ ACCEPT — PR #9502(已武装入队)
PR 9502 head=a6b134644 base=main files=1 +117/-4 labels: ['size/m','skip-changeset'] body first line: 'Fixes #9432' files: ['scripts/docs-audit/affected-docs.mjs'] gates(按名取最新): names=21 pending=none non-green=none
1. 前提成立 —— 而且是我在派发里说「证伪它也是合格答案」的那一条
我提醒过:#9431 可能已经关掉了这张卡的标本(
SecurityPlugin是容器),⛔ 别为一个不存在的缺陷造修复。结论:#9431 预言的残留是真的,且在当前
main上复现。parseRegistrarSource在裸源码上扫 handler 窗口里的裸标识符 ⇒ 只在英文散文里出现过的叶子符号仍然会桥接。活标本
40d5b2d4c(#9405):产出的两个 route anchor 全部来自注释,别无来源——promoteDraftForPublish -> /:type/:name/publish (rest-server.ts:5324, 5376) publishPackageDrafts -> /:name/state/:field (5694, 5722)列出 3 个页面,而它们没有一个提到被改动的符号。
结构性数字更说明问题:扫描器读到的东西里 57% 是散文(裸 7,649 个标识符槽位 vs 掩码后 3,320),并且 99 个代码形状的标识符能仅凭注释文本铸出一个能匹配文档的 route anchor。
2. ⭐ 召回代价实测为零,而且是在 60 个 commit 上测的
我要求「两个方向都钉」,因为只断言删除的测试会在过度修正上通过。给的证据比要求的强:
#9192 的头条 commit 全部不变:
9e2e682063/2 ·30b1c636a4/2 ·07ad424631/3 —— 三个原地不动。最近 60 个动
packages/的 commit 扫一遍:57 个完全相同,3 个变化,0 个 anchor 被新增。而且每一个移动的行都被逐个证明是错行:
commit 丢了什么 为什么它是错的 19539b4b2ui/forms.mdxvia/forms/:slug/lookup/:field该页 inlineColumns/relatedListColumns/InlineGridColumn出现 0 次0668f02a6shares.revoke+/:object/:id/shares/:shareId来自 8849 行一行注释;所有代码派生的路由全部存活,无页面移动 40d5b2d4c3 页 ⭐ 看起来像真召回损失的那个不是 第三行值得展开,因为它是最容易糊弄过去的:
publishMetaItem确实调用promoteDraftForPublish——看上去是真链路。但使它成为 anchor 的那两个 hunk 纯粹是 doc-comment ⇒ 没有任何行为移动;而state-machine.mdx+metadata-service.mdx对三个被改符号的出现次数是 0,client-sdk.mdx只提到未被触碰的兄弟publishItem。⇒ 「它调用了它」不等于「这个 diff 动了它」。 分清这一点才是这张卡的核心。
3. 掩码顺带关掉的一个额外幻影
掩码也覆盖
path:扫描 ⇒ 干掉了route-manager.ts里一个 JSDoc@example铸出的幻影/api/users/:id(route tail 43 → 42)。⭐ 一个文档示例里的路径,被当成了真实注册的路由。这不在卡片范围内,是修复顺带关掉的。
4. 投影选择有理由,而且检查了 #9367 的悬崖
选
maskComments而非stripComments,理由:这个扫描按行位置推理,并且关心path:之前有什么。并且明确检查了 #9367 那个 51× 悬崖不适用:唯一的惰性量词是每行正则内部的
(.*?),不是拖过整个文件的[\s\S]*?。实测代价:掩码 19 个 registrar 文件(888 KB)115 ms,整轮 387 ms → 480 ms。⇒ 报了数字,没有含糊说「可忽略」。窗口变宽方向也测了(注释掉的
path:不再截断上一个窗口):今天的树上新增 0 个标识符进入任何窗口。5. 我的第二个假设被回答为「否」,理由充分
我问:只在注释里出现的符号,是否该保留为较弱的 doc anchor、只禁止进 route bridge(#9431 对容器就是这么做的)。
答:否,因为没有东西可保留 —— 一个符号在第 3 节从它自己的声明就已经是 doc anchor 了,注释文本只喂 bridge。⇒ 不是价值判断,是机制上根本没有这条通路。
6. 反向验证干净
从已提交状态还原
const lines = text.split():恰好 3 个用例转红,别无其他——注释里的叶子进了窗口、@example铸出/api/users/:id、注释里的叶子选中 publish 路由。方向如预测(红,无反转,诊断数不增)。还原后 220 全过。自测 212 → 220(+8)。
7. 外溢卡 #9503(已分诊)
domain:devx+pm:queue+pm:blocked。变量路径(path: publishedPath)既不是 site 也不是窗口边界 ⇒ 上一个路由的 150 行窗口整个吞掉那个外来 handler,它的代码标识符桥到错误的路由;而变量路径的路由自己根本没有窗口(召回的那一半)。⚠️ 定性准确:它同时活过 #9432 和 #9294 的修复 ⇒ 是同族第三层,不是回归。86 个字面量站点 vs 33 个非字面量path:行——数量级说明它不是边角。
Verdict: ACCEPT. 已翻 ready、已武装、已用
ls-remote确认入队。
Generated by Claude Code
Filed unassigned by the
os-devseat working #9294 (sessionsession_01XqDQYVU5smx29ts9pAErja). Out of that card's scope — recording rather than fixing.What
parseRegistrarSourceinscripts/docs-audit/affected-docs.mjsbuilds theroute anchor -> handler identifiersmap by scanning each handler window with a bare token regex:Nothing excludes comment lines. So a name that appears in a handler only as prose in a
//comment is indistinguishable, to the bridge, from a name the handler actually calls — and the bridge's whole premise is "this symbol IS this route's implementation, so the handler mentions it".Measured
On
3851f87f0(the field-masking PR, one of #9192's own headline measurements),SecurityPluginreached/forms/:slug/submitthrough exactly two lines, both prose:That put
content/docs/ui/public-data-collection.mdxon the advisory. The page has zero occurrences ofmaskingRule,FieldMasker,keepHead,keepTailormask— it documents nothing that diff touched.Relationship to #9294 (and what is already fixed)
PR #9431 (#9294) removes container symbols from the bridge, which closes this particular specimen —
SecurityPluginis a class, so it no longer bridges regardless of where its name appears. It does not make the scan comment-aware. A leaf symbol named only in a comment still bridges: a handler carrying// see maskFieldValue for the ruleis enough to attach that route to any diff that touchedmaskFieldValue.So this is the same family, one layer down, and it is narrower than it was: the reachable cases now need a leaf name in comment text rather than any name.
Why it is worth a card rather than a shrug
Same reasoning as #9294: the advisory's per-row
viapromise says a page is listed because it names something this diff touched, and for a comment-carried anchor that sentence is false. It over-reports and misses nothing, so severity is judgment-tier, not a coverage hole.Direction, not a prescription
Stripping
//and/* */bodies before the identifier scan is the obvious move and looks mechanical, but it should be measured first — comments inside handlers routinely name the very symbol the handler calls one line later, so stripping them may cost nothing, or may cost recall in the cases where the call is behind an indirection the comment names directly. Whoever takes it should re-derive #9192's headline numbers (30b1c636a,3851f87f0,07ad42463) plus9e2e68206the way #9431 did, and report the delta both ways.Note the file is contended: #9331 is queued on
affected-docs.mjsand #9431 is in flight on it. Sequence accordingly.Refs: #9294 (the container half of this family), #9431 (the PR that closed the measured specimen), #9192 (the precision rework both regress against), #9331 (queued on the same file, different defect).
Generated by Claude Code