Skip to content

fix(rest): 清扫 REST 组合根的槽查找 —— 16 处类型化,契约由 implements 背书 (#4251 B4) - #5953

Merged
os-zhuang merged 2 commits into
mainfrom
claude/issue-4251-slot-sweep-b4
Aug 6, 2026
Merged

fix(rest): 清扫 REST 组合根的槽查找 —— 16 处类型化,契约由 implements 背书 (#4251 B4)#5953
os-zhuang merged 2 commits into
mainfrom
claude/issue-4251-slot-sweep-b4

Conversation

@os-zhuang

@os-zhuang os-zhuang commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Part B4 of #4251 —— REST 组合根批次。本 PR 不 Close 本单,B5–B11 批次留后续。

棘轮基线 159 → 143 站点 / 34 → 32 文件,两个文件全清并退出祖父名单,无新增 key(baseline key set verified against 9ce0ca9: no files added)。无行为变更。

站点分布(实测 16 处,比派发单的 15 处多 1)

派发范围写的是 rest-api-plugin.ts 14 + external-datasource-routes.ts 1;基线文件记的是 15 + 1,实测也是 16。多出来的那一处是 env-registry:同一行同时命中第四形态(let envRegistry: any; 分体声明)与类型实参形态,两条规则各报一次。

处置 依据
auth / objectql / i18n / analytics / security / metadata 台账契约 ServiceSlotContracts
email / sharing / sharingRules / reports / approvals / external-datasource spec 契约 契约早已存在,且注册进槽的类都写了 implements
env-registry RestEnvRegistry RestServer 构造函数自己声明的形参类型
settings 具名本地 surface,不入 ledger B2 决策:service-settings 可选
default-project 具名窄切片 只声明本文件读的一个字段
服务存在性探测 unknown 槽名是运行期参数

objectql#4404 落地的 IObjectQLEngine(而非 IDataEngine):消费方够到的是完整引擎——batch 路由背后探测的是 ql.transaction

六个"无契约"的槽其实都有契约,而且是受检的

这批最大的发现不是缺陷而是盘点结果:email / sharing / sharingRules / reports / approvals / external-datasourcepackages/spec/src/contracts/ 里本来就有契约,并且各自 provider 注册进槽的那个类都声明了 implements(EmailService implements IEmailServiceExternalDatasourceService implements IExternalDatasourceService …)。所以生产侧形状由编译器每次构建校验,本文件只需具名——即 #4404 用一份受检 claim 取代七个未受检 stand-in 的纪律。没有为它们新增 ledger 条目(不动 packages/spec),入账与否留给后续决策。

包装函数返回注解一并收窄

十处查找位于 async (environmentId?) => Promise< any | undefined > 的 provider 内。只改查找会在下一行把契约重新擦掉——正是规则文档里记的 KNOWN RESIDUAL(注解在外层函数上,AST 规则原理上看不见)。每个 provider 的返回类型因此一并收窄为自己那个槽的契约。

本批未发现死探测 —— 如实记录,而不是凑一个产出

这一族历史上每批都挖出真缺陷(#4361getMetaItem 指着一个从来没有该方法的服务、#4321registerInMemory),所以我逐一核对了类型化后消费方做的每一处探测:emailService.sendauthService.getApi / isAuthGateActivesvc.queryDatasetql.transaction、六个 approval 动词、五个 security 方法、五个 federation 方法——全部命中真实成员与真实 arity

一个值得记的次级结论:external-datasource-routes.ts 的五处 svc?.method 探测在类型化后显出是冗余但正确的——契约的方法都是必需成员,所以只要服务解析成功探测必为真,503 分支只由"服务不存在"到达,而那正是它的用途。不是恒假分支,不改行为。

钉子为什么是运行期测试,而不是类型级断言

packages/rest/tsconfig.json 排除自己的 test 文件,且该包没有 typecheck script(它是 DEBT / TEST_DEBT 台账条目)——所以没有任何 tsc program 编译这些文件。在这里写 @ts-expect-errorAssert< Equal< … > >永不求值,删掉它每道闸门照样绿:正是 AGENTS.md 禁止、#5286 / #5449 付过代价的 phantom check。

而这次改动真正携带的风险恰好是运行期可测的:provider 是二十参构造函数的第 6..19 个位置参数,形状全都是 (environmentId?) => Promise< unknown >,所以接错槽处处可赋值、编译器一声不响。新测试(rest-api-plugin-slot-lookups.test.ts,5 例)因此钉住:

  1. 每个 provider 取回自己那个槽注册的实例(形状相同,只有实例同一性能证明);
  2. boot 解析的槽名集合精确相等('sharingRules' 手滑成 'sharing-rules' 在这里红,而不是在生产上让一条路由永久 501);
  3. env-registry / default-project 两个 seam 按 RestServer 声明的形态传入;
  4. 存在性探测不碰占位对象;
  5. 全部可选槽为空时逐个 provider 降级为 undefined 且不抛。

反向验证(方向先定后验)

预期方向是——因为这正是类型抓不到的那一类。把 getService< IReportService >('reports') 改成 'report' 一个字符,5 例中 2 例失败,报文直接点名:

AssertionError: reportsServiceProvider must resolve 'reports': expected undefined to be { __slot: 'reports' }
AssertionError: expected Set{ 'manifest', 'http.server', …(18) } to deeply equal Set{ … }

已还原。

验证

检查 结果
pnpm --filter @objectstack/rest test 61 个测试文件 / 838 passed
tsc --noEmit -p packages/rest/tsconfig.json 2 errors,与 DEBT 台账记录的 errors: 2 精确相等,且都在我没动的 package-routes.ts;两个改动文件 0 error
TEST_DEBT 维度 临时解除 test 排除后测量:新测试文件贡献 0 error(#5827 若落地不会因本 PR 变红)
pnpm check:slot-lookup(强制模式) ✓ 143 / 32,none new,no files added
pnpm eslint(三个改动文件) exit 0
node scripts/check-nul-bytes.mjs OK

合并 main(2 个 commit)后复跑:rest-meta-save-receipt-envelope.test.ts(merge 带进来的 #5265 新测试)先报 2 例失败,根因是本 worktree 的 metadata-protocol dist 过期(该测试经其 dist 走 save-receipt 路径),重建依赖后 838/838 全绿——与本单勘误记录的同源陷阱,不是 main 红也不是本改动所致。

范围外发现

default-project 槽有 3 个生产消费方、0 个生产注册方:rest-api-plugin.tsruntime/http-dispatcher.ts:618、cloud 侧 objectos-runtime/kernel-resolver.ts:193;而 6 处注释(含 rest-server.ts 两处、http-dispatcher.ts 两处、domain-handler-registry.ts)都把注册方写成 createSingleEnvironmentPlugin,该函数在 objectstack 与 cloud 两个仓库里都不存在,cloud 仅在一处测试里 stub 了这个槽。三个消费方都有后续 fallback、降级路径完整,所以今天没有用户可感的故障——但"单环境默认"这一级解析永远不会触发。本 PR 只按本文件实际读到的字段具名类型化,不改行为;已另行开单,不在本批扩面。

claude added 2 commits August 6, 2026 12:46
#4251 的 B4 批次:REST 组合根的全部槽查找擦除。`rest-api-plugin.ts`(15)与
`external-datasource-routes.ts`(1)改为传入槽的契约类型,棘轮基线
159 → 143 站点 / 34 → 32 文件,两个文件退出祖父名单。无行为变更。

`email` / `sharing` / `sharingRules` / `reports` / `approvals` /
`external-datasource` 六个槽在 packages/spec 里本来就有契约,而各自 provider
注册进槽的那个类都写了 implements(`EmailService implements IEmailService` 等),
所以生产侧的形状由编译器每次构建校验,本文件只需具名 —— 即 #4404 用一份受检
claim 取代七个未受检 stand-in 的纪律。`auth` / `objectql` / `i18n` /
`analytics` / `security` / `metadata` 取自 ServiceSlotContracts 台账;`objectql`
是 IObjectQLEngine 而非 IDataEngine,因为消费方够到的是完整引擎。

十处查找位于 `Promise<any | undefined>` 的 provider 内,只改查找会在下一行把
契约重新擦掉(规则看不见的包装返回注解残留),故 provider 返回类型一并收窄。

三个无契约的槽分别用三种方式如实表达:`env-registry` 用 RestServer 构造函数
自己声明的 RestEnvRegistry(实参因此受检);`settings` 按 B2 决策给具名本地
surface(service-settings 可选,REST 层不能对它产生依赖),返回类型复用公开的
ResolvedSettingValue;`default-project` 只声明本文件读的那一个字段。服务存在性
探测的槽名是运行期参数,用 unknown —— 它只问槽里有没有东西,从不碰形状。

本批未发现死探测,如实记录而非暗示:逐一核对了 emailService.send、
authService.getApi/isAuthGateActive、svc.queryDataset、ql.transaction、六个
approval 动词、五个 security 方法与五个 federation 方法,全部命中真实成员与
真实 arity。

新增的钉子是运行期测试,这是刻意选择:packages/rest 的 tsconfig 排除 test 文件
且该包无 typecheck script,没有任何 tsc program 编译它们,写类型级断言会是
#5286 / #5449 付过代价的 phantom check。真正的风险是接线 —— provider 是二十参
构造函数的第 6..19 个位置参数、形状完全相同,接错槽处处可赋值且编译器看不见,
所以测试驱动每个 provider 并断言它取回自己那个槽注册的实例,钉住 boot 解析的
槽名集合,以及全部可选槽为空时的降级路径。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GX3sL71LFq8m2usg6VqTSE
@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:54pm

Request Review

@github-actions github-actions Bot added the size/m label Aug 6, 2026
@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

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

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

  • content/docs/ai/connect-mcp.mdx (via @objectstack/rest)
  • content/docs/api/error-handling-server.mdx (via @objectstack/rest)
  • content/docs/api/index.mdx (via @objectstack/rest)
  • content/docs/permissions/authentication.mdx (via @objectstack/rest)
  • content/docs/plugins/index.mdx (via @objectstack/rest)
  • content/docs/plugins/packages.mdx (via @objectstack/rest)
  • content/docs/protocol/kernel/http-protocol.mdx (via @objectstack/rest)
  • content/docs/protocol/kernel/i18n-standard.mdx (via packages/rest)
  • content/docs/releases/implementation-status.mdx (via @objectstack/rest)
  • content/docs/releases/v12.mdx (via @objectstack/rest)
  • content/docs/releases/v17.mdx (via @objectstack/rest)

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.

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling labels Aug 6, 2026
@os-zhuang
os-zhuang marked this pull request as ready for review August 6, 2026 13:02
@os-zhuang
os-zhuang added this pull request to the merge queue Aug 6, 2026
Merged via the queue into main with commit 1216dcc Aug 6, 2026
25 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-4251-slot-sweep-b4 branch August 6, 2026 13:08
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/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants