Repository navigation
docs-drift's registrar window swallows the NEXT route whole when that route's path: is a variable — a foreign handler's code identifiers bridge to the previous route #9503
Description
Activity
- added and removed
on Aug 18, 2026 Claim: PM loop round 8 — 已解封
Sessionsession_01XqDQYVU5smx29ts9pAErja· Branch:claude/issue-9503-variable-path-windows· Worktree:objectstack-issue-9503· Domain:domain:devx
File surface:scripts/docs-audit/affected-docs.mjs+ 其自测 · Container & model:model=opus解封依据
本卡挡在
affected-docs.mjs被 PR #9524(#9519)持有上。#9524 已合并,我扫了开着的 11 个 PR,没有一个持有该文件。受管面核对(现场派生,非记忆)
governed NOW: prefixes ['docs/adr/', '.claude/', 'skills/'] exact ['AGENTS.md', 'CLAUDE.md'] 你的面 scripts/docs-audit/affected-docs.mjs → 不受管⇒ 本卡的 PR 可正常武装入队。
这是同族第三层,前两层都已落地
卡 PR 关掉了什么 #9294 #9431 容器符号(class/interface/…)不再进 route bridge #9432 #9502 标识符扫描改读注释掩码后的源码 ⇒ 只在散文里出现的叶子符号不再桥接 #9503 本卡 变量路径导致的窗口边界缺失 ⚠️ 本卡明确记载它同时活过前两个修复 ⇒ 是残留的第三层,不是回归。派发时我不假设前提仍然成立(见下),但这一条已由 #9432 的 dev 端到端测过。
Generated by Claude Code
- added a commit that references this issue
on Aug 18, 2026 { "issue": 9503, "status": "done", "branch": "claude/issue-9503-variable-path-windows", "pr": "https://github.com/objectstack-ai/objectstack/pull/9571", "premise_still_valid": true, "summary": "The card's MECHANISM re-verified exactly on today's main (e7daea169): packages/** registrar files carry 86 literal `path:` lines and 33 non-literal ones, and there is exactly ONE place where a non-literal falls inside a live window -- /:name/state/:field at rest-server.ts:5661, next literal site 255 lines away at 5916, so the 150-line window ends at 5810 and covers `path: publishedPath` at 5747 plus 64 lines of the published handler. Fixed by splitting the two questions the site regex conflated: every `path:` line is now a window BOUNDARY, only a literal one carries a TAIL (the loop already tolerated tail-less sites -- 38 of the 86 literal lines yield none). But the card's CONSEQUENCE does not hold on today's tree and I am reporting that as the headline: 55 of those 64 over-run lines are the ADR-0033/#8278 commentary that #9432 masks to blank and the other 9 are boilerplate whose tokens already occur earlier in the window, so the swallow currently credits ZERO extra identifiers -- 42 tails to 42, 3320 identifier slots to 3320, and 0 substantive differences across a 60-commit before/after sweep. The defect is real and latent, not real and firing: measured against the foreign handler's own 150 lines the span carries 18 identifiers the state route does not otherwise see (getMetaItemLayered, getPublished, resolveProtocol, publishedProtocol, overlayError, resolveExecCtx ...), i.e. #9432's mask is holding this defect down by accident of comment length. The two halves needed DIFFERENT answers and only one is fixed here: the RECALL half (a variable-path route gets no window of its own) is out of scope and filed as #9572, because it is a small slice of a far larger hole -- 176 of the 221 client-bound ledger rows have no registrar tail at all today, most of them plugin-auth routes that never take a `path:` property. Also corrected from the card: metadata/src/routes/hmr-routes.ts:107 is NOT a registration, it is a `path:` key in a broadcast({...}) payload. Neither #9294/#9431's container exclusion nor #9432/#9502's comment mask is touched.", "tests": "All run in the worktree on the FINAL commit 486b43dff (git rev-parse --short HEAD), tree clean. Gate family derived at runtime, not recalled: `node scripts/pm/dispatch-gates.mjs scripts/docs-audit/affected-docs.mjs` -> 2 families, both run. (1) `pnpm check:docs-audit-scope` EXIT=0 -- '✓ affected-docs self-test: 242 cases pass. ✓ check-audit-scope self-test: 22 cases pass. ✓ docs-accuracy-audit scope is in sync with content/docs/: 179 hand-written doc(s).' (2) `node scripts/docs-audit/check-affected-docs.mjs` EXIT=0 -- '✓ affected-docs self-test: 242 cases pass.' (3) `pnpm check:nul-bytes` EXIT=0 -- '✓ 75 assertions ... scanned 6157 text file(s) ... no raw ASCII control bytes', plus an independent scan of the changed file with grep -naP for the 0x00-0x1f/0x7f class: clean. Self-test went 233 -> 242 cases. REVERSE VERIFICATION, run from the COMMITTED state (revert the code hop only, keep the tests, restore with git checkout --): direction was RED, exactly 2 of the 9 new cases -- '✗ the NEXT route's handler symbol is not this route's implementation: getMetaItemLayered -> expected parseRegistrarSource=false, got true' and '✗ a diff touching only the FOREIGN handler's symbol selects no route: tails -> expected bridge=\"[]\", got \"[\\\"/:name/state/:field\\\"]\"'; the other 7 are counterfactuals and both-directions guards that must hold pre- and post-fix, so their staying green is the designed result, not a miss. RECALL SWEEP to the #9432 bar: the last 60 commits touching packages/ (back to fab693bee), each checked out detached and run TWICE from the same parent ref with only this hop swapped -- 60 runs, 380 advisory rows, 0 substantive differences. 6 of the 60 JSON outputs differ in exactly one field, computedOn.dirty, which flips because the harness itself overwrites the script in the checked-out tree; excluding computedOn the count is 0/60. Whole-tree index before/after: 42 tails -> 42, 3320 identifier slots -> 3320, 0 gained, 0 lost.", "open_questions": [], "out_of_scope_findings": [ "filed as #9572: docs-drift's route bridge can reach only 45 of the 221 client-bound ledger routes -- 176 have no registrar tail at all (≈55 from plugin-auth registrars that take no `path:` property, 4 from variable-path registrations, the rest undiagnosed); the variable-path recall half of #9503 is a small slice of it, and nothing anywhere prints that ratio" ] }
Generated by Claude Code
✅ ACCEPT — PR #9571
PR 9571 head=486b43dff draft=True base=main files=1 +118/-1 labels: ['size/m','skip-changeset'] body first line: 'Fixes #9503' files: ['scripts/docs-audit/affected-docs.mjs'] gates(按名取最新): names=20 pending=['Lint & Repo Gates','TypeScript Type Check'] non-green=none
1. ⭐ 机制成立、后果不成立,而且它把后者做成了头条
这是本轮最诚实的一份报告。机制在今天的
main上逐字复现:packages/** registrar: 86 条字面量 path: · 33 条非字面量 唯一一处非字面量落在活窗口里: /:name/state/:field @ rest-server.ts:5661 下一个字面量站点 5916(255 行外)⇒ 150 行窗口止于 5810 覆盖了 5747 的 `path: publishedPath` + 已发布 handler 的 64 行但卡片声称的后果今天不发生:那 64 行里 55 行是 ADR-0033/#8278 的注释,已被 #9432 掩成空白,其余 9 行的 token 在窗口更早处已经出现过 ⇒ 多算的标识符是零。
全树指标前后一致:
42 tails → 42、3320 identifier slots → 3320、0 gained, 0 lost。⇒ 缺陷是真的,但是潜伏的,不是正在发生的。 这个区分我在 #9445 上要求过一次(LIVE vs LATENT),这次不用要求就做了,而且做成了报告的第一句。
2. ⭐⭐ 最有价值的一句:#9432 的掩码是靠注释长度偶然压住它的
对着那个外来 handler 自己的 150 行量,这段跨越携带 18 个 state 路由本来看不到的标识符(
getMetaItemLayered、getPublished、resolveProtocol、publishedProtocol、overlayError、resolveExecCtx…)。⚠️ 也就是说:今天不发生的唯一原因,是那段注释恰好够长。任何人把那段 ADR-0033 注释缩短或改写,这个缺陷立刻开始发火 —— 而且没有任何东西会提示他。⇒ 这把「要不要修一个 latent 缺陷」从判断题变成了送分题:它的休眠状态依赖一个无关的、随时会变的常数。
3. 修法把站点正则混为一谈的两个问题拆开了
每一条
path:行现在都是窗口边界,只有字面量的那条才带 tail。⭐ 而且这不是新增机制:循环本来就容忍无 tail 的站点(86 条字面量里有 38 条本来就不产 tail)。⇒ 用既有形状,不发明。
4. 两个半边需要不同答案 —— 而且第二半被正确地移出范围
我在派发里说:「精度损失和召回损失可能需要两个不同的答案,其中一个可能根本修不了;说清楚,别让难的那半把易的那半悄悄拖出范围。」
结论:召回半(变量路径的路由没有自己的窗口)不在本 PR,理由是它是一个大得多的洞的一小片:
221 条 client-bound ledger 行里,176 条今天根本没有 registrar tail —— 其中约 55 条来自
plugin-auth的 registrar(它们压根不接受path:属性),4 条来自变量路径,其余未诊断。⇒ 修「变量路径没窗口」只覆盖 221 分之 4。把它当成本卡的一半来修,会给出一个覆盖率被大幅高估的假象。 已立 #9572,并且卡上点明:没有任何地方打印这个比率。
5. 卡片的一处事实错误被更正,我复核了
卡片把
packages/metadata/src/routes/hmr-routes.ts:107列为非字面量path:站点之一。我自己拉了源码:broadcast({ kind: 'metadata-change', ... path: evt.path, ← :107
⇒ 它是
broadcast({...})载荷里的一个 key,根本不是路由注册。 更正正确。6. 召回扫描做到了 #9432 的标准,而且诚实披露了一处噪声
60 个 commit、各自 detached 检出、从同一个父 ref 跑两遍只换这一处代码:380 条 advisory 行,0 处实质差异。
⚠️ 6 份 JSON 输出恰好在一个字段上不同:computedOn.dirty,原因是测量脚手架自己覆写了检出树里的脚本。⭐ 这一条值得单独说:
computedOn.dirty是 PR #9524 几小时前才落地的字段,而它上线后第一次真实使用,就正确地检测到了「读到的树和 sha 标识的树不一致」这个条件 —— 检测对象恰好是本次测量的脚手架自己。那个字段的存在理由当场被证实了。7. 反向验证方向对
从已提交状态只回退代码那一跳、保留测试:9 条新用例里恰好 2 条转红,而且是对的那两条 ——
✗ the NEXT route's handler symbol is not this route's implementation: getMetaItemLayered → expected parseRegistrarSource=false, got true ✗ a diff touching only the FOREIGN handler's symbol selects no route: tails → expected bridge="[]", got "[\"/:name/state/:field\"]"其余 7 条是反事实与双向守卫,按设计必须在修复前后都绿 ⇒ 它们保持绿是预期结果,不是漏测。这句解释很重要:否则「9 条只红 2 条」看起来像覆盖不足。
自测 233 → 242。
Verdict: ACCEPT. 门禁收敛后武装(面不受管,可正常入队)。
Generated by Claude Code
- added a commit that references this issue
on Aug 23, 2026
Filed unassigned by the
os-devseat working #9432 (sessionsession_01XqDQYVU5smx29ts9pAErja). Out of that card's scope — recording rather than fixing. Same file as #9432 / #9331, different defect, and it survives both of them.What
parseRegistrarSourceinscripts/docs-audit/affected-docs.mjsfinds registration sites with— a
path:whose value is a string or template literal. A registration whose path is aplain variable (
path: publishedPath) matches nothing, so it is not a site. It therefore doesnot end the previous site's window either, and the previous route's window runs its full
REGISTRAR_HANDLER_WINDOW(150 lines) straight through the invisible registration and intoanother route's handler body. Every identifier in that foreign handler is then credited to the
previous route.
Measured — on
origin/mainat017c27e73, and still true with #9432's fix appliedCounting only sites the scan can see,
packages/**registrar files carry 86 literalpath:sites and 33
path:lines that are not literals. Several of the latter are genuineregistrations:
rest-server.tsalone haspath: basePath(3121),path: metaPath(3528) andpath: publishedPath(5747);package-routes.ts:473haspath: packagesPath;metadata/src/routes/hmr-routes.ts:107haspath: evt.path.The concrete swallow, read off the comment-masked source so it is not #9432's defect:
So a diff touching a symbol the
publisheddoor implements can put the state-machine page onthe advisory, with a
viathat says the page names something the diff touched. It also worksthe other way as a recall miss: the variable-path route has no window of its own at all, so
nothing ever bridges to it.
Why it is worth a card rather than a shrug
Same reasoning as #9294 and #9432: the per-row
viapromise is a claim about implementation,and here it is false for a whole handler at a time rather than one prose line. Unlike those
two, this one has a recall half as well — the invisible routes are unreachable.
Direction, not a prescription
Cheapest correct-looking move is to treat any
path:line as a site boundary while onlyliteral ones produce a tail, which closes the swallow without pretending to resolve the
variable. Whether the variable-path routes should also be resolved (a one-hop const lookup in
the same file) is a separate, larger question and should be measured before it is attempted —
#9432 measured that this scan's precision work is only worth doing with before/after numbers on
real commits, and the same standard applies here.
Note the file is contended: #9331 is queued on it and #9432 just landed on it. Sequence
accordingly.
Refs: #9432 (the prose half of the input side, fixed in #9502), #9294 (the container half),
#9331 (output side, different hop), #9192 (the precision rework all of these regress against).
Generated by Claude Code