Skip to content

fix(plugin-hono-server): compute the standalone discovery routes from real registrations (#4018) - #4063

Merged
os-zhuang merged 2 commits into
mainfrom
claude/hono-static-discovery-hardcoded-2ysa9f
Jul 30, 2026
Merged

os-zhuang merged 2 commits into
mainfrom
claude/hono-static-discovery-hardcoded-2ysa9f

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Closes #4018。

问题

plugin-hono-server 的 registerStandardEndpoints 便捷面返回一份完全静态的 discovery:routes 表硬编码 auth/packages/analytics/workflow/automation/ai/notifications/i18n/storage/ui,不看任何东西是否真的挂上。standalone Hono 部署因此宣传十个路由族、而其中没有插件桥接的每一个都 404 —— 正是 ADR-0076 D12 要修掉的那一类,也是它与两个真 discovery 面(dispatcher 的 getDiscoveryInfo、protocol 的 getDiscovery,两者都按运行时计算)不一致的原因。

补充一点 issue 里没提到的:registerStandardEndpoints 的构造函数默认值是 true(hono-plugin.ts 的 constructor),所以"仅显式开启时才有"这个缓解因素比记录的要弱——任何直接 new HonoServerPlugin() 的嵌入方都拿到了这张静态表。

改法

采用 issue 建议里倾向的方向(退役这份重复供给),但保留 bare-standalone 这唯一真正需要它的形态;没有引入第三份需要长期同步的 discovery 逻辑:

  • 单一 owner(D11 / OQ#9):kernel 上有 @objectstack/rest 或 runtime dispatcher 时,本面不再注册 ${prefix}/discovery。两者都在插件 start() 期间注册,早于本处的 kernel:ready hook,而 Hono 是先注册先赢——也就是说它们在任何组合部署里本来就已经遮蔽了这个 handler。所以 cede 不改变任何客户端看到的 payload,只是不再产出第三份没人读的表。/.well-known/objectstack 只让给 dispatcher(REST 从不注册它),这样 REST-without-dispatcher 的组合仍保留这个跳转。
  • 按注册计算(D12):真正由本面提供 /discovery 时,routes 改为每次请求从 app 的实时 Hono 路由表推导——某个族被宣传,当且仅当确实有路由注册在它的 base 上或其下。之所以按请求算:兄弟插件在 kernel:ready 余下的过程里还会继续挂路由,注册时刻拍的快照会漏掉;要求命中 base 本身或 / 分隔的子路径,则挂在 base 之上的通配(全局 /*、/api/v1/*)不会被误判成一次挂载。

选"按注册"而不是照抄 hasXxx 的"按服务":在这个面上服务注册并不蕴含路由挂载(把服务桥接到 HTTP 的正是 dispatcher,而这里没有它),所以"注册了什么路由"才是更严格、也更诚实的那个问题。

验证

测试直接驱动插件构建的真实 Hono app,而不是 mock 一张路由表:

  • 新增 hono-discovery.test.ts(10 例):bare host 只宣传自己挂的(data + /auth/me/* 两个 helper,其余十族全部缺席)、被通配挂载的族会出现、discovery 接好之后才落地的挂载也能反映、base 之上的通配不算数、realtime 永不宣传、transactionalBatch: false 保持(discovery 广播「跨对象原子 batch」能力位(让客户端声明式协商,取代 404/405/501 运行时探测) #3298),以及两条 cede 路径。
  • client.hono.test.ts 改为断言诚实形态:该 boot 上 routes.metadata 缺席,并同时断言 GET /api/v1/meta/objects 真的是 404(证明这个缺席是诚实的,不是漏报)。

跑过并全绿:

范围 结果
plugin-hono-server 全量 126 passed
client 全量 200 passed
runtime:dispatcher-plugin.ready / route-parity / notifications.hono 集成 16 passed
qa/http-conformance 全量 46 passed
tsc --noEmit + eslint clean

route-parity 的组合 boot 日志里能看到 cede 生效:

INFO /.well-known/objectstack ceded to com.objectstack.runtime.dispatcher (single owner)
INFO /api/v1/discovery ceded to com.objectstack.runtime.dispatcher (single owner)

行为变化

standalone HonoServerPlugin(无 REST、无 dispatcher)上,GET /api/v1/discovery 不再列出没人挂的族——最显眼的是 routes.metadata(/api/v1/meta 随 @objectstack/rest/dispatcher 出货)。@objectstack/client 对缺失的 key 会回落到约定路径,connect() 不受影响。组合部署(os serve、cloud)零变化:那里服务的一直是 dispatcher 的 service-aware discovery。

已在 ADR-0076 D12 追加一条 (Update #4018: …),把这第三个 publisher 的收口记进 D12 残留盘点。


Generated by Claude Code

… real registrations (#4018)

`registerStandardEndpoints` served a fully STATIC discovery: a hardcoded
`routes` table listing auth/packages/analytics/workflow/automation/ai/
notifications/i18n/storage/ui regardless of what the host mounted. A standalone
Hono deployment advertised ten route families and 404'd on every one no plugin
bridged — the "advertise a route that doesn't exist" class ADR-0076 D12 exists
to kill, and why this surface disagreed with the two real discovery builders
(`HttpDispatcher.getDiscoveryInfo`, `metadata-protocol`'s `getDiscovery`), which
both compute at runtime.

Closed on both axes, without adding a third service-registry walk to keep in
sync with the other two:

- Single owner (D11 / OQ#9): when `@objectstack/rest` or the runtime dispatcher
  is on the kernel, this surface no longer registers `${prefix}/discovery`.
  Both register during plugin `start()` — before this `kernel:ready` hook — and
  Hono is first-registration-wins, so they already shadowed this handler in
  every composed deployment: the cede changes no served payload, it removes a
  third one nobody read. `/.well-known/objectstack` is ceded to the dispatcher
  only, since REST never registers it.

- Computed, not hardcoded (D12): when this surface does own `/discovery`,
  `routes` is derived per request from the app's live Hono route table — a
  family is advertised iff a route is really registered at or under its base.
  Per request, because siblings keep mounting through the rest of
  `kernel:ready`; requiring the base or a `/`-separated child, so a wildcard
  ABOVE the base (global `/*`, `/api/v1/*`) never counts as a mount.

Tests drive the real Hono app rather than a mocked route table:
`hono-discovery.test.ts` covers the bare host advertising only what it mounts,
late mounts and wildcards, and both cede paths. `client.hono.test.ts` now
asserts the honest shape — `routes.metadata` absent on a host where
`GET /api/v1/meta/objects` really is 404.

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

vercel Bot commented Jul 30, 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 30, 2026 7:52am

Request Review

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

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/client, @objectstack/plugin-hono-server.

14 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)
  • 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)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/client, @objectstack/plugin-hono-server)
  • content/docs/kernel/runtime-services/data-service.mdx (via packages/client)
  • content/docs/kernel/runtime-services/index.mdx (via packages/client)
  • content/docs/permissions/authentication.mdx (via @objectstack/client, @objectstack/plugin-hono-server)
  • content/docs/plugins/index.mdx (via @objectstack/plugin-hono-server)
  • content/docs/plugins/packages.mdx (via @objectstack/client, @objectstack/plugin-hono-server)
  • content/docs/protocol/kernel/realtime-protocol.mdx (via @objectstack/client)
  • content/docs/releases/implementation-status.mdx (via @objectstack/client, @objectstack/plugin-hono-server)
  • content/docs/releases/v16.mdx (via @objectstack/client, @objectstack/plugin-hono-server)

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.

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

2 participants