Skip to content

feat(spec,runtime,metadata-protocol)!: discovery 两个生产者统一到一个 schema —— capabilities 正名、features/endpoints 退役、scoping 声明 (#4828) - #5682

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-4828-discovery-surface
Aug 6, 2026
Merged

feat(spec,runtime,metadata-protocol)!: discovery 两个生产者统一到一个 schema —— capabilities 正名、features/endpoints 退役、scoping 声明 (#4828)#5682
os-zhuang merged 1 commit into
mainfrom
claude/issue-4828-discovery-surface

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Fixes #4828

按 2026-08-05 维护者裁定(四条子决策全裁,见 issue 内 19:47Z 收口评论),四条一个 PR 落地,避免 discovery 机器可读面两次动刀。

病根:一个宽松包装同时吞掉了两类错误

/discovery 是机器可读面(AGENTS.md「Route & surface ownership」第 4 条),但此前没有任何闸门把生产者实际发出的形状和 packages/spec 声明的形状放进同一个断言。协议层唯一引用的是 GetDiscoveryResponseSchema —— DiscoverySchema.partial().required({version}).extend({apiName}):

  • .partial()缺失的必填键照样解析通过;
  • zod object 默认 strip 未知键,让未声明的多余键也照样解析通过。

于是两个生产者朝相反方向漂移,却都从同一个盲区穿过去了:一个不发 name/environment/locale,另一个凭空发 features/endpoints

现在两个 schema 的职责被拆开,并且互相对账:

  • DiscoverySchema 对生产者有约束力 —— 三个生产者包各带一个 discovery-schema-conformance.test.ts,拿线上真实形状DiscoverySchema.parse();
  • GetDiscoveryResponseSchema 是消费者侧的宽松解析,它的键集就是生产者闸门的允许集(= DiscoverySchema 的键 + 已声明的废弃别名);
  • packages/spec/src/api/discovery.test.ts 钉住两者的等价关系,任何一边单独长出一个键都会红。

这两条断言刻意回答不同的问题:parse()(必填键在不在、environment 落没落在枚举里),键集判(有没有发协议从没声明过的东西)。

四条裁定的落地

capabilities 正名,顶层 features 退役

dispatcher 的顶层 features 改为 DiscoverySchema 一直声明着的 capabilities,取声明的 { enabled } 层级形状,标志位一个不少。

这修掉了一个真实缺陷:本仓 SDK 的 client.capabilities getter 读的是 discoveryInfo.capabilities,所以面对 dispatcher 供的 discovery,它对每一个标志位都返回 undefined —— 答案其实就在一个键之外的 features 里。

注意保留的那个 features能力条目内部的子键(capabilities.某域.features),它是声明过的,不动。

endpoints —— ADR-0049 走「先测消费者」,判定为删除

endpointsroutes 的逐字副本,注释写着 "Alias for backward compatibility with some clients"。三仓普查(2026-08-05,objectstack / objectui / cloud)零消费者:

候选命中 实际是什么
packages/client/src/index.ts discoveryInfo.routes 解析路由,不读 endpoints
objectui useApiDiscovery.tscatalog.endpoints 它自己硬编码的 SERVICE_ENDPOINT_CATALOG,与本载荷无关
objectui useDiscovery.ts 只读 services
cloud 只把 /discovery 当路径前缀用,从不读载荷

阳性对照:同一套 grep 能正常命中 capabilities 的真实消费者(packages/client 的 getter)与 scoping 的真实消费者,所以「零命中」不是 grep 失灵。

故按 enforce-or-remove 直接从生产者删除,routes 是声明过的正名。

scoping —— 按实测形状补进 schema

先测再声明,不猜:enabled: boolean / resolution 三值枚举(与 RestApiConfig.projectResolution 同源)/ scoped: boolean / environmentId?: stringnormalizeConfig 保证前两个恒有值,environmentId 仅在环境作用域挂载上出现,故 optional。

它一直是真实的能力协商信息且一直有消费者(packages/clientclient.environment-scoping.test.ts 直接断言 scoping.enabled / scoping.resolution),所以是补声明而不是删。dispatcher 不挂作用域变体,不发这个键,因此整体 optional。

④ schema 为权威 —— REST 补齐三键 + environment 强制落枚举

getDiscovery() 现在产出 name / environment / locale(locale 与 dispatcher 用同一套 i18n 访问器推导,两边不会给出不同答案)。

environment 不再直塞 NODE_ENV,改为映射。映射表(实现在 @objectstack/spec/apiresolveDiscoveryEnvironment,两个生产者共用):

NODE_ENV 广播的 environment 理由
productionprod production 正式/简写拼法
sandbox sandbox 精确匹配
staging sandbox 预生产、类生产;肯定不是 production,而 sandbox 是枚举里的预生产成员
developmentdev development 正式/简写拼法
test development 临时的开发者级运行(vitest/CI),不是被 provision 出来的预生产副本
未设置 / 其它任意值 development 保持原有 getEnv('NODE_ENV', 'development') 默认;绝不在猜测时宣称 production

最后一行是安全相关的:识别不了的拼法降级到 development,这个函数不可能替一个没认出来的环境广播 production

prod/dev 简写被接受,理由与 seed-loader.ts 里既有的 NODE_ENV_TO_SEED_ENV 完全一致 —— NODE_ENV操作员提供的、第三方边界上的变量(Prime Directive #9 明列),对它做归一化不是 PD #12 禁止的「消费者侧容忍」,那条规矩管的是我们自己的元数据契约。

staging → sandbox 是本表唯一的判断题,单独标出请裁:它一定不是 production;在 sandboxdevelopment 之间,staging 是类生产的,故取 sandbox。若要改判,只需改这一行。

附:apiName 的退役时间表

getDiscovery() 此前只发 spec 标注为 deprecated 的 apiName 而不发 canonical 的 name,dispatcher 只发 name —— 同一个概念两个生产者拼法相反,消费者两边都拿不到稳定的键。现在:

  • 协议 17(当下):两个都发,值完全相同,name 为正;
  • 协议 18:生产者停发 apiName,并从 GetDiscoveryResponseSchema 移除。

需要在 18 之前迁移的消费者(三仓实测):只有 packages/client/tests/integration/01-discovery.test.ts(TC-DISC-001/002),而它被 packages/client/vitest.config.ts 显式 exclude(需要真实服务器)。三个仓的产品代码没有任何一处读 apiName。因为窗口期内两个键都发,该集成断言依然是绿的。

一处需要说明的架构判断:为什么不是 ADR-0087 D2 转换表条目

D2 转换层作用于加载期的被授权(authored)元数据 —— normalizeStackInput 那个缝、flow.node.type / page.kind / object.compactLayout 这一类键。响应载荷没有加载缝:discovery 每次请求现算,没有任何「存量形状」需要在加载时被改写,登记一条 D2 条目不会有任何代码去应用它。

所以这里走的是本仓既有的 API 面退役先例:退役时间表写进声明处 + pin 测试(参照 #4939 registry-retirement.test.ts 的做法)。同理,features/endpoints 从来没有被声明过,不存在「从 schema 里删掉」这一步(retiredKey() 的前提是键在 schema 的 shape 里),退役的动作就是「生产者停发 + 闸门钉死不许回潮」。

packages/spec/liveness/ 同样不适用:它以注册表为根、走可授权元数据类型,discovery 不在其中。

先证红

验收核心测试在未改生产者时确实按预期红,理由与预测一致。

getDiscovery()(裁定④,必填键):

AssertionError: getDiscovery() must satisfy the canonical DiscoverySchema:
  expected [ 'name: invalid_type', …(2) ] to deeply equal []
+ [
+   "name: invalid_type",
+   "environment: invalid_value",
+   "locale: invalid_type",
+ ]

同一次运行里「不得发出未声明的键」那条是绿的 —— 这正是预测的方向:getDiscovery() 当时的键集(version/apiName/routes/services/capabilities)本来就都在允许集里,它的病是少键不是多键。多键的那一半病在 dispatcher 上,由 runtime 侧的同名测试覆盖。

反向验证(方向在运行前先写下)

endpoints: routesfeatures: {...} 装回 dispatcher,预测三条:

  1. 「不得发出未声明的键」→ ,undeclared = ['endpoints', 'features'];
  2. features/endpoints 已退役」的 pin → ;
  3. DiscoverySchema.safeParse依然绿

第 3 条是最要紧的一条,也是本 PR 为什么不能只有验收核心那一条断言的原因:zod object 默认 strip 未知键,值层的 parse() 根本看不见多余键。换句话说,裁定里写的验收核心(「两个生产者的线上形状都能通过 DiscoverySchema.parse() 严格校验」)对必填键那一半是充分的,对退役那一半是不充分的 —— 所以每个生产者的闸门是两条断言而不是一条。实跑结果贴在 PR 评论。

测试

全绿,affected 包全量跑:

Test Files Tests
@objectstack/spec 316 passed (316) 8070 passed (8070)
@objectstack/metadata-protocol 44 passed (44) 407 passed (407)
@objectstack/rest 50 passed (50) 743 passed (743)
@objectstack/runtime 98 passed (98)

packages/spec 的生成物按 AGENTS.md 流程处理:buildcheck:generated(点名 3 个 stale)→ --fix 只重生成这 3 个,逐行复核过 —— authorable-surface.json 只多 Discovery:scoping / GetDiscoveryResponse:scoping 两行,json-schema.manifest.json 只多 api/DiscoveryEnvironment,api-surface.json 只多 3 个新导出(纯增,无删除),strictness ledger 的 api/ 计数 393 → 394(新增 scoping 这个 z.object,方向正确)。无任何无关漂移。

顺带一提:首次跑 spec 套件时有 18 条红,全部是 AGENTS.md §9 的陈旧生成物陷阱(build-schemas-check-mode 17 条 + strictness ledger 1 条),重生成后自行消失,与代码逻辑无关。

顺带修正的 fixture

  • packages/runtime/src/http-dispatcher.test.ts 的 8 处 info.features.X 改写为 canonical 的 info.capabilities.X.enabled —— 它们是对生产者输出的真实断言,改拼法即可,覆盖面一点没少。
  • packages/adapters/hono 的 3 处 dispatcher mock 里 endpoints: [] 改为 routes: {}(本来就没有任何断言读它,改成真实生产者的拼法,免得下一个人照抄退役键)。

范围外发现

均已另行开单、未认领,不在本 PR 修(Prime Directive #10):#5679#5672#5673#5674#5676。详见 issue 回复。

conversions/registry.ts 未改动,与 #4923 无重叠。

本 PR 同时是 #3877 Stage A 里点名的第一个族(discovery)的落地。


Generated by Claude Code

…producers (#4828)

`/discovery` is a machine-readable surface, but nothing ever compared what the
two producers emit against what `packages/spec` declares. The only schema the
protocol layer referenced was `GetDiscoveryResponseSchema` —
`DiscoverySchema.partial().required({version}).extend({apiName})` — so
`.partial()` hid every missing REQUIRED key while zod's default unknown-key
strip hid every UNDECLARED emitted one. The two producers then drifted in
opposite directions through the same blind spot.

Per the maintainer's 2026-08-05 ruling (all four sub-decisions), landed as one
PR so the surface is cut once:

1. `capabilities` is canonical. The dispatcher's undeclared top-level `features`
   map becomes `capabilities` in the declared `{ enabled }` shape. This fixes a
   real defect: the SDK's `client.capabilities` getter reads
   `discoveryInfo.capabilities`, so against a dispatcher-served host it returned
   `undefined` for every flag while the answers sat one key away.
2. `endpoints` removed (ADR-0049 enforce-or-remove). It duplicated `routes`
   verbatim; a consumer census across objectstack/objectui/cloud found no
   reader.
3. `scoping` declared as an optional key, matching what REST actually emits —
   measured, not guessed. It has a live consumer in `packages/client`.
4. `DiscoverySchema` is authoritative: `getDiscovery()` now fills the required
   `name`/`environment`/`locale`, and `environment` is mapped into its declared
   enum instead of passing `NODE_ENV` through raw (`test` and `staging` were
   landing outside it). `name` is canonical on both producers; `apiName` stays
   as a deprecated alias with the identical value, scheduled for removal in
   protocol 18.

The gate: each producer package carries a `discovery-schema-conformance.test.ts`
that parses its LIVE shape against `DiscoverySchema` (a VALUE verdict) and checks
its emitted key set against `GetDiscoveryResponseSchema`'s shape (a KEY verdict).
Both are needed — zod strips unknown keys, so the parse alone cannot see the
`features`/`endpoints` class of drift at all. `discovery.test.ts` pins the two
schemas' key sets equivalent modulo declared aliases so neither can grow a key
alone.

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

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling labels Aug 6, 2026
@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 3 package(s): @objectstack/metadata-protocol, @objectstack/runtime, @objectstack/spec.

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

  • content/docs/ai/agents.mdx (via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx (via @objectstack/spec)
  • content/docs/ai/skills.mdx (via @objectstack/spec)
  • content/docs/api/client-sdk.mdx (via packages/runtime, @objectstack/spec)
  • content/docs/api/environment-routing.mdx (via @objectstack/spec)
  • content/docs/api/error-catalog.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-client.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx (via @objectstack/spec)
  • content/docs/api/index.mdx (via @objectstack/runtime, @objectstack/spec)
  • content/docs/api/wire-format.mdx (via @objectstack/runtime)
  • content/docs/automation/approvals.mdx (via @objectstack/spec)
  • content/docs/automation/connectors.mdx (via @objectstack/spec)
  • content/docs/automation/flows.mdx (via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx (via @objectstack/runtime, packages/spec)
  • content/docs/automation/hooks.mdx (via @objectstack/spec)
  • content/docs/automation/index.mdx (via @objectstack/spec)
  • content/docs/automation/webhooks.mdx (via @objectstack/spec)
  • content/docs/automation/workflows.mdx (via @objectstack/spec)
  • content/docs/concepts/architecture.mdx (via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx (via packages/spec)
  • content/docs/concepts/index.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx (via @objectstack/metadata-protocol, @objectstack/runtime, packages/spec)
  • content/docs/concepts/north-star.mdx (via packages/runtime, @objectstack/spec)
  • content/docs/data-modeling/analytics.mdx (via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/runtime, @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx (via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx (via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx (via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx (via @objectstack/spec)
  • content/docs/data-modeling/index.mdx (via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx (via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx (via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx (via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx (via @objectstack/spec)
  • content/docs/deployment/cli.mdx (via @objectstack/spec)
  • 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/tenancy-modes.mdx (via @objectstack/spec)
  • content/docs/deployment/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx (via @objectstack/spec)
  • content/docs/deployment/vercel.mdx (via @objectstack/runtime)
  • content/docs/getting-started/build-with-claude-code.mdx (via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx (via @objectstack/spec)
  • content/docs/getting-started/examples.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx (via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/runtime, @objectstack/spec)
  • content/docs/kernel/cluster.mdx (via @objectstack/runtime, @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx (via packages/spec)
  • content/docs/kernel/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx (via packages/spec)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/metadata-protocol, @objectstack/spec)
  • content/docs/kernel/services.mdx (via @objectstack/spec)
  • content/docs/permissions/authentication.mdx (via @objectstack/runtime)
  • content/docs/permissions/authorization.mdx (via packages/runtime, @objectstack/spec)
  • content/docs/permissions/permission-sets.mdx (via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx (via @objectstack/spec)
  • content/docs/permissions/positions.mdx (via @objectstack/spec)
  • content/docs/permissions/rls.mdx (via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx (via @objectstack/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx (via @objectstack/spec)
  • content/docs/plugins/development.mdx (via @objectstack/spec)
  • content/docs/plugins/index.mdx (via @objectstack/spec)
  • content/docs/plugins/packages.mdx (via @objectstack/runtime, @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx (via @objectstack/spec)
  • content/docs/protocol/diagram.mdx (via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/http-protocol.mdx (via @objectstack/runtime, @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/runtime, @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/runtime, @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/query-syntax.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx (via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx (via @objectstack/spec)
  • content/docs/releases/implementation-status.mdx (via @objectstack/runtime, @objectstack/spec)
  • content/docs/releases/index.mdx (via @objectstack/spec)
  • content/docs/releases/v12.mdx (via @objectstack/spec)
  • content/docs/releases/v13.mdx (via @objectstack/spec)
  • content/docs/releases/v16.mdx (via @objectstack/spec)
  • content/docs/releases/v17.mdx (via @objectstack/runtime, @objectstack/spec)
  • content/docs/releases/v9.mdx (via @objectstack/metadata-protocol, @objectstack/spec)
  • content/docs/ui/actions.mdx (via @objectstack/spec)
  • content/docs/ui/apps.mdx (via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx (via @objectstack/spec)
  • content/docs/ui/dashboards.mdx (via @objectstack/spec)
  • content/docs/ui/forms.mdx (via @objectstack/spec)
  • content/docs/ui/index.mdx (via @objectstack/spec)
  • content/docs/ui/public-data-collection.mdx (via @objectstack/spec)
  • content/docs/ui/setup-app.mdx (via @objectstack/spec)
  • content/docs/ui/translations.mdx (via @objectstack/spec)
  • content/docs/ui/views.mdx (via @objectstack/spec)

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

反向验证实跑结果 —— 三条预测全部命中

endpoints: routesfeatures: {...} 装回 dispatcher 后跑 packages/runtime:

 × emits NO key the protocol does not declare 10ms
 × has retired `features` and `endpoints` (ADR-0049 enforce-or-remove) 2ms

AssertionError: undeclared top-level keys on the getDiscoveryInfo() shape:
  expected [ 'endpoints', 'features' ] to deeply equal []

 Test Files  1 failed | 97 passed (98)
      Tests  2 failed | 1434 passed (1436)

对照运行前写下的预测:

# 预测 实跑
1 键集断言 → 红,undeclared = ['endpoints', 'features'] ✅ 一字不差
2 退役 pin → 红
3 DiscoverySchema.safeParse依然绿 ✅ 1436 条里只红了 2 条,那条 parse 断言在同一个文件里、同一次运行、绿

第 3 条是这次反向验证真正的收获,值得单独说:值层的 parse() 对「多发了未声明的键」这一类漂移完全免疫,因为 zod object 默认 strip 未知键。所以裁定里写的验收核心(「两个生产者的线上形状都能通过 DiscoverySchema.parse() 严格校验」)——

  • 缺必填键那一半是充分的(先证红时它确实红了:name: invalid_type / environment: invalid_value / locale: invalid_type);
  • 退役那一半是不充分的:光靠它,features/endpoints 回潮不会有任何东西变红。

这就是每个生产者闸门写成两条断言(判值 + 判键)而不是一条的原因,也是为什么第二条的允许集是从 GetDiscoveryResponseSchema 的 shape 推导出来的,而不是手写一个键名数组 —— 手写数组会让闸门自己变成契约的第三种方言。

验证完毕后工作树已还原,git status 干净(与提交逐字节一致)。

补充:其余验证

结果
@objectstack/client 17 passed (17) / 222 passed (222) —— 含 client.environment-scoping.test.ts(直接断言线上 /discoveryscoping.enabled / scoping.resolution)与 client.hono.test.ts(起真实栈),即 REST 生产者的端到端真实消费者
@objectstack/hono 2 passed (2) / 73 passed (73)
typecheck(spec / metadata-protocol / runtime / rest / hono) TYPECHECK_EXIT=0
node scripts/check-nul-bytes.mjs OK(5581 个文件,无裸控制字节);另对本 PR 改动的每个文件做了越过该 gate 的自查 grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f]',无命中

client 那 3 个文件第一次跑是红的,原因是 packages/runtime/dist 不存在(我只建了它的依赖 runtime^...,没建它本身),报的是 Failed to resolve entry for package "@objectstack/runtime" —— AGENTS.md §9 那个陷阱,与本 PR 改动无关;补建 runtime 后 17/17 全绿。


Generated by Claude Code

@os-zhuang
os-zhuang marked this pull request as ready for review August 6, 2026 00:10
@os-zhuang
os-zhuang enabled auto-merge August 6, 2026 00:10
@os-zhuang
os-zhuang added this pull request to the merge queue Aug 6, 2026
Merged via the queue into main with commit 77022a9 Aug 6, 2026
25 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-4828-discovery-surface branch August 6, 2026 00:24
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

2 participants