Skip to content

docs(runtime): ADR-0076 D11 四条在场锚点入账 —— registry + dispatcher 门序 + 两个代表域 (#5357) - #5940

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-5357-adr0076-anchors
Aug 6, 2026
Merged

docs(runtime): ADR-0076 D11 四条在场锚点入账 —— registry + dispatcher 门序 + 两个代表域 (#5357)#5940
os-zhuang merged 1 commit into
mainfrom
claude/issue-5357-adr0076-anchors

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Closes #5357

ADR-0076 在 scripts/adr-anchors.json 中此前对 0076 零命中(实测锚点数 30,issue 正文记的 26 是 8-05 的快照;零命中这一前提在本 worktree 的基线 origin/main a6b3ee7 上复核仍成立),而 D11 的产物是一整片实打实的代码:14 个域模块、DomainHandlerRegistry、收缩到约 1.9k 行的 dispatcher。缺的就是「在场门」这一层:被治理的文件自己不提它所遵从的决定,作者就无从知晓(#3723 的机制)。

选点判据(判断题,不是清单题)

check-adr-anchors.mjs 自己的纪律是 "Do not anchor everything; a map of everything is a map of nothing, and each entry must earn its failure mode."。所以不锚全部 14 个域,只锚四处满足「单看文件会觉得可以合理地『优化』掉,而改了会静默逆转 D11」的落点,且四处的失败模式互不重复:

锚点 它挣到的失败模式(有人会怎么"改进"它)
packages/runtime/src/domain-handler-registry.ts 给注册表加通配/参数/中间件("路由能力不够用");从插件空间直接注册框架特定路由;把某槽位的注册搬进"拥有它的那个包";给 deps 加一个不收请求就能读内核的设施
packages/runtime/src/http-dispatcher.ts 把域注册表的 resolve 提到三道门之前当"已迁移域的快路径";往已经对域为空的 if 链里再加一个 startsWith 分支
packages/runtime/src/domains/data.ts 把域体折回 HttpDispatcher.handleData 那个薄委托("这层间接没买到任何东西")
packages/runtime/src/domains/i18n.ts /i18n 的注册搬进 service-i18n("拥有该能力的包理应拥有自己的路由")

四条失败文案都携带不变量——违反了哪条决定、为什么不能这么改——而不是只写一个 ADR id。要点分别是:

  1. registry 是 D11 的框架无关端口:进来的必须是规范化 handler,不是框架特定路由(从插件空间注册 Hono app.route 会让每个插件耦合 Hono,葬掉 packages/qa/http-conformance 在第二个零依赖 node:http 适配器上验证过的多适配器性质);无通配、无参数、无中间件是刻意的,路由能力属于端口下方的适配器;注册权留在 dispatcher,因为多数槽位是多提供方(i18n 由 service-i18n 或 AppPlugin 内存兜底填充,analytics 由 service-analytics 或 ObjectQLPlugin 兜底填充),路由桥的是槽位不是包,搬进某一个提供方就 404 掉由另一个提供方服务的每个栈;DomainHandlerDeps 的每个读内核设施都先收请求(HttpDispatcher 把「本请求解析出的 kernel」存在实例字段上(this.kernel),多租户 host 上并发请求会互相串改 #5155:一台主机只造一个 dispatcher,缓存"当前请求的内核"会让 await 之后恢复的请求读到另一租户的数据源)。
  2. dispatcher 里两处次序是承重的,而且两处都长得像可以省掉的开销:(1) scope 解析 → ADR-0069 认证门 → 成员门跑在域注册表之前;(2) 注册表跑在 legacy if 链之前,而 if 链现在对域是空的、必须保持空的(one route, one owner —— 两个标本 GET /openapi.jsonapis:handleApiEndpoint 当初是被删掉而不是被修好的,E6(#5040 执行器):endpoint 文档进 rest-server enrichment 管线 + 摘除 dispatcher generateOpenApi 死分支 + 修正台账注记(并入 #5078) #5093 / 声明式 apis:(ApiEndpoint)入站面全链路零执行:元数据装载成功、路由从未挂载、matchEndpoint 全仓无实现 #4936)。
  3. domains/data.ts 是 D11 step ③ 的收尾一刀,也正是 ADR 描述 god implementation 时点名的那个 handler;多租户的 428 属于域体本身而非上游门。
  4. domains/i18n.ts 是注册权归属规则的具体实例(多提供方槽位),同时刻意保留了 legacy 的 match: 'prefix' 粗糙边(/i18nxx 也匹配),规范化它是要由 http-conformance 重新钉的行为变更,不是能顺手塞进无关 diff 的整理。

文件面(逻辑零改动)

  • scripts/adr-anchors.json:+4 条锚点(30 → 34)。
  • packages/runtime/src/http-dispatcher.ts:仅注释。四个锚点文件本来都已提及 ADR-0076(无需补头注),但门序不变量此前在三道门旁没有任何文字——本文件现有的 10 处 ADR-0076 提及都在别处,所以在 dispatch() 的门段补了一段注释把不变量写在它生效的地方。
  • packages/runtime/src/domains/data.ts:仅注释,一句「域体留在这里」+ 折回薄委托的后果。
  • ⛔ 未触碰 rest-server.ts 的 7693 行实现问题(分诊明令不属本单),未触碰 content/docs/releases/**

验证

$ node scripts/check-adr-anchors.mjs
check-adr-anchors: OK (34 anchored file(s), every governing ADR still referenced).

$ pnpm --workspace-concurrency=2 --filter @objectstack/runtime test src/domain-handler-registry.test.ts src/http-dispatcher.test.ts src/route-ledger.conformance.test.ts --maxWorkers=2
Test Files  3 passed (3) / Tests  290 passed (290)

$ pnpm --workspace-concurrency=2 --filter @objectstack/runtime typecheck   # tsc --noEmit,退出 0
$ node scripts/check-nul-bytes.mjs
check-nul-bytes: OK (scanned 5723 tracked text file(s); ... no raw ASCII control bytes).

反向验证(方向先预判,再跑)

预判:这道门是纯在场检查,domains/data.ts 恰好只有 1 处 ADR-0076 提及(新增注释里写的是 "D11 invariant",不含 id),删掉它必然红一条且只红一条。实测一致:

  • packages/runtime/src/domains/data.ts: no longer references ADR-0076.
      The `/data` body — D11 step ③'s terminal cut (PR-10) and the very handler ADR-0076 names ...

失败输出把不变量整段带了出来(而非只叫人把字符串粘回去),这正是这道门的价值所在;恢复后复绿。

顺带实测了这道门的粒度,并如实记录:domains/i18n.ts 有 2 处提及(D11 头注 + 第 44 行的 D12),只删头注那处仍然绿,两处都删才红。也就是说在场门守的是"文件仍然指向这个决定",不是"某一行注释仍在原位"——它不能替代行为门,门序与路由归属的行为面仍由 http-dispatcher.*.test.ts(multi-tenant-concurrency / kernel-resolver / requireauth)与 route-ledger.conformance.test.ts 守。这与脚本自己的声明一致:"It does NOT verify the code still obeys the ADR."

无发布物

仅治理账本 + 注释行,不改任何包的行为,故不写 changeset,需要 skip-changeset 标签(建 PR 后立即回读并写并集)。


Generated by Claude Code

…代表域 (#5357)

D11 的产物是一整片实打实的代码(14 个域模块 + `DomainHandlerRegistry` + 收缩后的
dispatcher),而 `scripts/adr-anchors.json` 里对 ADR-0076 零命中 —— 被治理的文件
自己不提它所遵从的决定,作者就无从知晓(#3723 的机制)。

按 `check-adr-anchors.mjs` 自己的纪律选点("a map of everything is a map of
nothing,每条锚点必须挣得它的失败模式"),不锚全部 14 个域,只锚四处「单看文件会
觉得可以『优化』掉、而改了会静默逆转 D11」的落点,每处的失败模式互不重复:

1. `domain-handler-registry.ts` —— 端口本体:规范化 handler 而非框架特定路由
   (从插件空间注册 Hono `app.route` 会让每个插件耦合 Hono,葬掉 http-conformance
   验证的多适配器性质);无通配/无参数/无中间件是刻意的;注册权留在 dispatcher,
   因为多数槽位是多提供方(i18n、analytics),搬进某一个提供方会 404 掉其它栈;
   `DomainHandlerDeps` 的每个读内核设施都先收请求(#5155 的跨租户读)。
2. `http-dispatcher.ts` —— `dispatch()` 里两处「像是可以省掉的开销」的次序:
   scope 解析 + ADR-0069 认证门 + 成员门跑在域注册表**之前**;注册表跑在 if 链
   之前,而 if 链现在对域是空的、必须保持空的(one route, one owner)。
3. `domains/data.ts` —— 代表域之一,D11 的收尾一刀,也正是 ADR 点名的 god
   implementation 核心 `handleData`;失败模式是把域体折回 dispatcher 的薄委托。
4. `domains/i18n.ts` —— 代表域之二,注册权归属规则的具体实例:该槽位由
   service-i18n 或 AppPlugin 内存兜底二者之一填充,把 `/i18n` 注册搬进
   service-i18n 这个「看起来天然正确」的清理,会 404 掉另一提供方服务的每个栈。

失败文案携带不变量(违反了什么决定、为什么不能这么改),不是只写 ADR id。

代码面只加注释:`http-dispatcher.ts` 的门序不变量此前在门旁无任何文字(现有的
ADR-0076 提及都在别处),`domains/data.ts` 补一句「域体留在这里」。逻辑零改动。
`rest-server.ts` 的 7693 行实现问题按分诊不属本单,未触碰。

Claude-Session: https://claude.ai/code/session_01GX3sL71LFq8m2usg6VqTSE
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Aug 6, 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 Aug 6, 2026 12:38pm

Request Review

@github-actions github-actions Bot added the size/s label Aug 6, 2026
@os-zhuang os-zhuang added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 6, 2026 — with Claude
@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/runtime.

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

  • content/docs/api/client-sdk.mdx (via packages/runtime)
  • content/docs/api/index.mdx (via @objectstack/runtime)
  • content/docs/api/wire-format.mdx (via @objectstack/runtime)
  • content/docs/automation/hook-bodies.mdx (via @objectstack/runtime)
  • content/docs/concepts/metadata-lifecycle.mdx (via @objectstack/runtime)
  • 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/runtime)
  • content/docs/kernel/cluster.mdx (via @objectstack/runtime)
  • content/docs/permissions/authentication.mdx (via @objectstack/runtime)
  • content/docs/permissions/authorization.mdx (via packages/runtime)
  • content/docs/plugins/packages.mdx (via @objectstack/runtime)
  • content/docs/protocol/kernel/http-protocol.mdx (via @objectstack/runtime)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/runtime)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/runtime)
  • content/docs/releases/implementation-status.mdx (via @objectstack/runtime)
  • content/docs/releases/v17.mdx (via @objectstack/runtime)

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.

Copy link
Copy Markdown
Contributor Author

两点收尾更正/补充,记在这里而不是改正文:

  1. 正文里「rest-server.ts 的 7693 行」引的是 ADR-0076 D11 的落点没有 adr-anchors 锚点:dispatcher 拆解后的 runtime/src/domains/*domain-handler-registry.ts 未被在场门守住 #5357 的 8-05 快照,本次实测已是 8593 行(origin/main a6b3ee7)。仍按分诊未触碰,但这条观察的唯一书面记录原本挂在 ADR-0076 D11 的落点没有 adr-anchors 锚点:dispatcher 拆解后的 runtime/src/domains/*domain-handler-registry.ts 未被在场门守住 #5357 上、会随本 PR 关闭而消失,故单独落了不指派的 observation-class finding:[finding] ADR-0076 D11 的第二半从未落地:packages/rest/src/rest-server.ts 已 8593 行(ADR 记录约 5.1k),且无 issue 承接 #5949(含三时点行数表:约 5100 → 7693 → 8593,以及"拆 vs. 在 ADR 上写明不拆"的处置选项 —— 那是待维护者裁决的决定,不是开发能自选的)。
  2. CI 已收敛:head 0618944 上 26 个 check run,0 pending,无 failure(Check Changeset / ESLint / TypeScript Type Check / Test Core / Dogfood Regression Gate 全绿,其余 skipped)。skip-changeset 在建 PR 后立即写入(并集 size/s + skip-changeset),CI 收敛后回读仍为 ["size/s","skip-changeset"],未被覆盖。

另核对了一处选点时顺手起疑的地方,结论是无缺陷、不需要 issue:__aiRoutes 有两个消费者(dispatcher-plugin.tsmountRouteOnServer 挂到 host IHttpServer,以及 /ai 域在 dispatch() 里走的表),看起来像 one-route-one-owner 隐患;实测两条路径都强制了路由自声明的 auth 契约(dispatcher-plugin.ts 的 handler 内 route.auth !== false && !userdomains/ai.tsshouldDenyAnonymous,同一套 #3963 修法),匿名调用者到不了 auth: true 的 handler,所以不存在绕过面。


Generated by Claude Code

@os-zhuang
os-zhuang marked this pull request as ready for review August 6, 2026 12:56
@os-zhuang
os-zhuang added this pull request to the merge queue Aug 6, 2026
Merged via the queue into main with commit b7baf6f Aug 6, 2026
29 of 30 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-5357-adr0076-anchors branch August 6, 2026 13:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/s skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ADR-0076 D11 的落点没有 adr-anchors 锚点:dispatcher 拆解后的 runtime/src/domains/*domain-handler-registry.ts 未被在场门守住

2 participants