test(client): close the route audit's reverse direction — every SDK URL must match a route some surface mounts (#3642) - #3658
Merged
Conversation
…RL must match a route some surface mounts (#3642) The capstone of the #3563 audit. The dispatcher (#3563), REST (#3587) and service-mount (#3636) ledgers all run server → client: enumerate what a surface mounts, demand a reviewed disposition, and for `sdk` rows demand the named client method exists. None asked the reverse question — does the URL the client BUILDS match anything a server MOUNTS? — so a method could name a real function, carry a green ledger row, and 404 everywhere. That shipped four times, found one at a time by hand: analytics.explain and analytics.meta (#3584), meta.getView (#3611), and i18n.getTranslations / getFieldLabels (#3636) — the last pair having carried green `sdk` rows since tranche 1. client-url-conformance.test.ts drives every method on a real client with a recording fetch and matches each captured URL against the UNION of all four ledgers (a union, not an intersection — a route mounted by one surface is still reachable). A real drive rather than a hand-written "method X targets route Y" table: such a table is an assertion ABOUT the code that the code can drift away from, which is the very failure being fixed. Mutation-checked — re-injecting the #3636 dialect bug fails the suite. The sweep's own completeness is asserted, because that is the part that rots silently: - every method is driven or declared NON_HTTP with a reason, so a new SDK method cannot escape coverage; - a driven method emitting ZERO requests fails — stale placeholder args are exactly how a sweep stops covering anything while staying green; - a URL containing undefined / [object Object] fails, so a placeholder that is accepted but wrong cannot masquerade as coverage; - the __api-endpoint `(unmatched)` catch-all is excluded from the pattern set, since treating it as a pattern would match every URL and make the suite vacuous. 196 of ~219 methods matched. Two bounds are reported rather than papered over: /api/v1/cloud/* (23 projects.* methods) is served by the sibling cloud repo — this repo's dispatcher explicitly refuses those paths — so it is exempt by prefix and bounded from both ends, with a non-projects method reaching it failing the suite (#3655); and 60 of ~196 matched calls rest only on a `**` prefix claim rather than a resolvable route, 54 of them on `* /auth/**`, a count the guard ratchets so it can only shrink (#3656). No runtime change: a guard, plus the ledger-header and audit-doc notes recording what it does and does not cover. Refs #3642, #3563, #3587, #3636. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K35y3ovfWtCkBHYFCqUfAt
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 2 package(s): 26 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
os-zhuang
marked this pull request as ready for review
July 27, 2026 13:26
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #3642 —— #3563 路由审计的收口项。
前四个守卫都没问的那个问题
dispatcher(#3563)、REST(#3587)、服务自主挂载(#3636)三份账本走的都是 server → client:枚举某个表面挂了什么、要求每条有过审的处置、
sdk行要求那个客户端方法存在。"方法存在"不等于"方法调得通"。 没有任何一个守卫拿客户端拼出的 URL 去比对各表面挂载的 pattern。所以一个方法可以指向真实函数、挂着绿色账本行,同时在所有表面上 404。
这个洞已经发作过四次,每次都是人肉撞见的:
analytics.explain/explainanalytics.meta/meta/:cubemeta.getView?type=/ui/view/:object/:typei18n.getTranslations/getFieldLabels?locale=最后那一对从第一批起就各挂着一行绿色的
sdk,整整三批都是线上 404。做法:真跑,而不是声明表
client-url-conformance.test.ts用一个会记录请求的fetch驱动客户端上的每一个方法,再把捕获到的 URL 去比对四份账本的并集(是并集不是交集——只被一个表面挂载的路由同样可达)。没有采用"方法 X 打路由 Y"的手写声明表:那种表是关于代码的断言,代码可以从它底下漂走——而那正是本次要修的失败模式本身。真跑一遍、抓它实际放到线上的东西,漂不走。
做了变异测试:把 #3636 那个方言 bug 重新注进去,守卫立刻失败并精确点名 ——
一个不会失败的守卫没有价值,所以这步是必须的。
扫描自身的完备性也被断言了
这是最容易烂掉、也是 issue 里点名"绝不能跳过"的部分:
NON_HTTP并给出理由undefined/[object Object]→ 失败(unmatched)排除出 pattern 集合__api-endpoint那条 catch-all 匹配一切,让整个套件变成空转第二条不是假想:调试过程中
connect和auth.signInWithProvider就是这样静默的——前者是原型方法被按名取出后丢了this,后者在 node 下卡在window守卫上。两个都是真会发 HTTP 的方法,靠这条断言才暴露出来。signInWithProvider我给了浏览器桩而不是塞进NON_HTTP——把一个真发请求的方法记为豁免,就是这个守卫要防的那种腐烂。结果与两处如实标注的边界
~219 个方法中 196 个匹配成功。 未匹配的只有
projects.*一族。projects.*SDK namespace targets the control plane, which no in-repo ledger can vouch for #3655) ——/api/v1/cloud/*共 23 个方法由兄弟仓库cloud提供,本仓库不但没挂,dispatcher 还显式拒绝这些路径(http-dispatcher.ts:"Guard against matching control-plane routes like /cloud/environments")。本仓库任何账本都无法为它们背书,故按前缀豁免。豁免两头都收紧:只有projects.*可以用这个前缀,别的命名空间碰到/cloud/直接失败,免得这个洞变成停放未匹配 URL 的地方。/auth/**dynamic route family — 54 SDK methods rest on a prefix claim, not a route #3656) ——**行声明的是"这个前缀家族有人认领",不是"这个具体 URL 能解析"。196 个匹配里有 60 个(约 31%)只靠这个,其中 54 个压在* /auth/**上,而那批路由来自一个按自己节奏发版的第三方依赖。守卫把这个数字数出来并上棘轮,只能降不能升。这两个边界我选择数出来写进断言,而不是让它们隐没在"全绿"里——否则这个守卫会显得比它实际的强度更强。
验证
client-url-conformance.test.ts3 项通过;变异测试确认可失败packages/client全量 13 文件 175 项通过turbo run test --filter=@objectstack/client --filter=@objectstack/runtime --filter=@objectstack/rest—— 29/29eslint --no-inline-config、check-doc-authoring、check-nul-bytes均通过无运行时改动:一个守卫,加上账本头部与审计文档(新增 §10)对"它覆盖了什么、没覆盖什么"的记录。
Generated by Claude Code