Skip to content

fix(client,service-i18n): ledger the autonomous service-mount surface; two i18n SDK calls reached nothing (#3636) - #3646

Merged
os-zhuang merged 1 commit into
mainfrom
claude/route-audit-tranche-3-service-wsxqaf
Jul 27, 2026
Merged

fix(client,service-i18n): ledger the autonomous service-mount surface; two i18n SDK calls reached nothing (#3636)#3646
os-zhuang merged 1 commit into
mainfrom
claude/route-audit-tranche-3-service-wsxqaf

Conversation

@os-zhuang

@os-zhuang os-zhuang commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Closes #3636 —— #3563 路由审计的第三批,也是最后一个未审计的服务端表面。

为什么这个表面此前谁都看不见

前两批的账本各自停在自己的包边界上:dispatcher 账本只看 http-dispatcher.ts,REST 账本只看 RouteManager + 两个 direct-mount 注册器。而有两个 service 是绕过两者的——它们在 kernel:ready 时去取 http-server 服务,直接往 IHttpServer 上挂路由,所以 RouteManagerRestServer.getRoutes() 都从未见过它们。结果是 SDK 的整个 storage 表面加上全部 i18n,一直停在 #3563 之前的状态:能用、在跑、但没有任何东西守着。

账本与守卫

每个包一份迷你账本,放在注册器旁边,不产生任何跨包依赖边(第一批的教训原样适用):

文件 路由数
service-storage/src/storage-route-ledger.ts 10
service-i18n/src/i18n-route-ledger.ts 3
client/src/service-route-ledger-coverage.test.ts 客户端一半

两侧的枚举都是真枚举,不是手写清单:让注册器对着一个会记录调用的 mock IHttpServer 跑一遍,它的注册调用本身就是路由集合。i18n 这边进一步走插件真实的 init → start → kernel:ready 生命周期,这样那个延迟注册的行为本身也持续被覆盖到。两份账本的 gapmismatch 都以 0 上棘轮。

账本跨包时按相对源码路径导入(它们是纯数据、零 import),不建 service→client 的包依赖——CI 的分包测试任务只构建自己的依赖闭包,真加一条边会直接构建不出来。

顺带查出并修掉的两个线上 404

i18n.getTranslations 发的是 /i18n/translations?locale=xx,i18n.getFieldLabels 发的是 /i18n/labels/:object?locale=xx;而所有提供服务的表面——本插件的挂载、dispatcher 的 HTTP 挂载、以及 plugin-rest-api.zod.ts 里声明的契约——挂的都只有 path 形式。这两个调用从来就没有任何东西能应答。

值得注意的是:它们从第一批起就在 dispatcher 账本里各挂着一行绿色的 sdk。因为那个守卫问的是"客户端方法在不在",而不是"它拼出来的 URL 有没有人挂"。dispatcher 的 domain handler 内部其实同时接受 query 方言,但没有任何路由会把裸 /translations 送到那段代码——这份宽容根本到达不了。

客户端改为发 path 方言(和 #3611meta.getView 的处理一致)。新增的 i18n-wire-dialect.test.ts 把真实 client 打到真实 router 上,并断言旧方言在线上就是 404,所以回退改不掉这条守卫。

三处独立佐证 path 形式才是既定契约,client 是唯一跑偏的一方:

  • plugin-rest-api.zod.ts 的 endpoint 声明:/translations/:locale/labels/:object/:locale
  • 已发布文档:content/docs/api/plugin-endpoints.mdxcontent/docs/kernel/services-checklist.mdx 写的都是 path 形式(所以本 PR 无需改文档)
  • packages/qa/http-conformance/src/adapter.test.ts 早就断言 /api/v1/i18n/labels/task 匹配不上 /labels/:object/:locale

一处响应体形状修正

service-i18n 的成功响应少了 unwrapResponse 用来判断信封的 success 标志,于是同一个 SDK 方法:对着这个 provider 返回的是没拆开的 { data: … },对着 dispatcher 返回的却是声明的解包形状——一个方法两种形状,取决于当时是哪个插件挂的路由。三个 handler 现在统一发 i18n 路由组声明的 { success: true, data } 信封(plugin-rest-api.zod.tsresponse_envelope)。data 位置没动,直接读 body 的调用方不受影响。

Storage 审计结果:干净

7 条 SDK 已表达,3 条 server-only(objectql 写进 file 字段载荷、由 <img src> 直接跟随的浏览器能力 URL,以及两条 local-driver 回环)。#3636 里点名需要甄别的分块上传家族,实际查下来 SDK 已完整表达(initChunkedUpload / uploadPart / completeChunkedUpload / resumeUpload),没有 gap 要补。

只登记未修的两项

验证

  • CI 全绿:16 项检查全部完成,14 成功 / 2 被 path filter 正常跳过 / 0 失败(含 Test Core、Build Core、TypeScript Type Check、ESLint、CodeQL、Dogfood Regression Gate ×2)
  • 本地 turbo run test --filter=...@objectstack/service-i18n --filter=...@objectstack/service-storage —— 76/76 通过
  • 新增守卫单独跑:storage 5 项、i18n 8 项、client 侧 8 项,全绿
  • check-nul-bytes / check-doc-authoring / check-role-word / check-org-identifier 均通过

三个表面至此全部入账,审计文档 docs/audits/2026-07-dispatcher-client-route-coverage.md 新增 §9 记录本批结论。objectui 不调用 SDK 的 i18n 命名空间,本任务无需改动该仓库。

…; two i18n SDK calls reached nothing (#3636)

Tranche 3 of the #3563 route audit — the last un-audited server surface.
The dispatcher ledger (#3563) and the REST ledger (#3587) each stop at
their own package boundary, and two services mount outside both: they
reach for the `http-server` service and register straight on
`IHttpServer`, so neither RouteManager nor RestServer.getRoutes() has
ever seen them. That left the SDK's whole storage surface, plus all of
i18n, in the pre-#3563 posture: expressed, working, guarded by nothing.

Ledgers + guards, one per package (no cross-package edges):

  - service-storage/src/storage-route-ledger.ts        — 10 routes
  - service-i18n/src/i18n-route-ledger.ts              —  3 routes
  - client/src/service-route-ledger-coverage.test.ts   — client half

Enumeration is real on both: the registrar runs against a capturing mock
IHttpServer and its registration calls ARE the route set — for i18n by
driving the plugin's actual init → start → kernel:ready lifecycle, so
the deferral stays exercised. Both ledgers ratchet gap and mismatch at 0.

Two wire-level 404s fixed. i18n.getTranslations sent
/i18n/translations?locale=xx and i18n.getFieldLabels sent
/i18n/labels/:object?locale=xx, while every serving surface — the
service's mounts, the dispatcher's HTTP mounts, and the
plugin-rest-api.zod.ts contract — mounts only the path form. Neither
call could ever be answered. Both had carried a green `sdk` row in the
dispatcher ledger since tranche 1, because that guard asks whether the
client METHOD exists, not whether it speaks a URL anything mounts. The
client now sends the path dialect (the resolution #3611 gave
meta.getView), and i18n-wire-dialect.test.ts drives the real client at a
real router — asserting the abandoned dialect 404s — so a revert cannot
pass quietly.

One response-shape fix. service-i18n's success bodies omitted the
`success` flag unwrapResponse keys on, so the SDK handed callers the raw
{ data: … } wrapper against that provider while returning the declared
unwrapped shape against the dispatcher: one method, two shapes, decided
by which plugin mounted the route. Its handlers now emit the
{ success: true, data } envelope the i18n route group declares; `data`
did not move, so direct body readers are unaffected.

Storage audited clean — 7 SDK-expressed, 3 reviewed server-only (the
browser capability URL objectql stamps into file-field payloads, and the
two local-driver loopbacks). The chunked-upload family, flagged for
triage, turned out fully expressed.

Filed, not fixed: GET {base}/_local/file/:key is built by three call
sites and mounted by none (#3641); the cross-surface URL conformance
guard that would have caught every bug above mechanically is the
capstone (#3642).

Refs #3636, #3563, #3587.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K35y3ovfWtCkBHYFCqUfAt
@vercel

vercel Bot commented Jul 27, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Jul 27, 2026 12:37pm

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling size/l labels Jul 27, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 4 package(s): @objectstack/client, @objectstack/rest, @objectstack/runtime, packages/services.

33 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/skills-reference.mdx (via packages/client)
  • content/docs/api/client-sdk.mdx (via @objectstack/client, packages/runtime)
  • content/docs/api/data-flow.mdx (via @objectstack/client)
  • content/docs/api/environment-routing.mdx (via @objectstack/client)
  • content/docs/api/error-catalog.mdx (via @objectstack/client, @objectstack/rest)
  • content/docs/api/error-handling-server.mdx (via @objectstack/rest)
  • content/docs/api/index.mdx (via @objectstack/rest, @objectstack/runtime)
  • content/docs/api/wire-format.mdx (via @objectstack/runtime)
  • content/docs/automation/hook-bodies.mdx (via @objectstack/runtime)
  • content/docs/automation/webhooks.mdx (via packages/services)
  • content/docs/concepts/north-star.mdx (via packages/runtime)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/runtime)
  • content/docs/deployment/index.mdx (via @objectstack/runtime)
  • content/docs/deployment/production-readiness.mdx (via @objectstack/runtime)
  • content/docs/deployment/single-project-mode.mdx (via @objectstack/runtime)
  • content/docs/deployment/vercel.mdx (via @objectstack/runtime)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/client, @objectstack/runtime)
  • content/docs/kernel/runtime-services/audit-service.mdx (via packages/services)
  • content/docs/kernel/runtime-services/data-service.mdx (via packages/client)
  • content/docs/kernel/runtime-services/index.mdx (via packages/client, packages/services)
  • content/docs/kernel/runtime-services/settings-service.mdx (via packages/services)
  • content/docs/permissions/authentication.mdx (via @objectstack/client, @objectstack/runtime)
  • content/docs/permissions/authorization.mdx (via packages/runtime)
  • content/docs/plugins/index.mdx (via @objectstack/rest)
  • content/docs/plugins/packages.mdx (via @objectstack/client, @objectstack/rest, @objectstack/runtime, packages/services)
  • content/docs/protocol/kernel/http-protocol.mdx (via @objectstack/runtime)
  • content/docs/protocol/kernel/i18n-standard.mdx (via packages/rest, packages/services)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/runtime)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/runtime)
  • content/docs/protocol/kernel/realtime-protocol.mdx (via @objectstack/client)
  • content/docs/releases/implementation-status.mdx (via @objectstack/client, @objectstack/rest, @objectstack/runtime)
  • content/docs/releases/v12.mdx (via @objectstack/rest)
  • content/docs/releases/v16.mdx (via @objectstack/client)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@os-zhuang
os-zhuang marked this pull request as ready for review July 27, 2026 12:56
@os-zhuang
os-zhuang merged commit f1a8114 into main Jul 27, 2026
17 checks passed
@os-zhuang
os-zhuang deleted the claude/route-audit-tranche-3-service-wsxqaf branch July 27, 2026 12:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Route audit tranche 3: autonomously-mounted service routes (service-storage / service-i18n) — #3587 follow-up

2 participants