Skip to content

plugin-hono-server 的 registerStandardEndpoints 把「重复供给」和「独家供给」绑在同一个开关上 —— 拆开是退役的前置条件 #4073

Description

@os-zhuang

⚠️ 本 issue 已订正(见下方评论)。 初版标题与建议是"零非测试消费者,默认翻 false" —— 错的:我当时查的是"谁传这个选项",没查"谁依赖这个默认值"。os serve 正是靠默认值拿到这个面,且其中三个端点没有第二个提供者。照初版执行会打断 console。下文已按证据重写。

按 Prime Directive #10 记录,#4018(静态 discovery 按注册计算,#4063 已合并)实施中发现。#4018 只收口了 discovery 那一块。

真实现象

HonoPluginOptions.registerStandardEndpoints(默认 true)用一个开关同时控制两类性质完全不同的路由:

路由 性质 谁还提供
POST/GET /api/v1/data/:objectGET /data/:object/:id(只有 C+R) 重复供给 @objectstack/rest/data(完整 CRUD + 全部闸门),且因先注册先赢而实际生效
GET /api/v1/discovery/.well-known/objectstack 重复供给 dispatcher / REST(#4018 后已显式 cede)
GET /api/v1/auth/me/permissions 独家供给
GET /api/v1/auth/me/localization 独家供给
GET /api/v1/me/apps 独家供给

三条独家供给的证据:

  • os serve 靠默认值拿到它们 —— packages/cli/src/commands/serve.ts:1057new HonoServerPlugin({ port }),不传这个选项。
  • console 硬依赖其中两条 —— objectui packages/permissions/src/MePermissionsProvider.tsx:72DEFAULT_ENDPOINT = '/api/v1/auth/me/permissions'(整套 PermissionProvider / ObjectForm / RecordDetailView / 相关列表的 apiOperations 都建在这份 payload 上);apps/console/src/AppContent.tsx:121/api/v1/auth/me/localization
  • core 把它们当一等公民 —— packages/core/src/security/auth-gate.ts:30ALLOW_SUFFIXES/me/apps/me/localization,注释写明是"被门禁拦住的用户必须仍能访问,用以补救或引导补救 UI"。
  • grep 确认 packages/restpackages/runtime 都不挂任何 /me/*

所以默认值不能直接翻 false —— 那会让 os serve 上的 console 权限层与本地化整个断掉。

仍然成立的部分

「重复供给」那一半的论据不变,而且是这个面反复付税的根源 —— 每条平台级不变量都要在这里再实现一遍,且每次都是事后补的:

修法:先拆,才谈退役

前置步骤(本 issue 的核心):把三条 /me/*registerStandardEndpoints 门里挪出去,让这个开关名副其实地只覆盖重复供给。两条路线:

  • A. 就地拆分 —— 在 plugin-hono-server 内部改为无条件注册(需把 registerDiscoveryAndCrudEndpoints 里的 resolveCtx / getObjectQL / denyAnonymous 提成共享 helper)。小、安全、对 os serve 行为不变。
  • B. 归还真正的 owner —— /auth/me/* 归 plugin-auth(它已经拥有 /api/v1/auth/*),/me/apps 归 REST 或 dispatcher。契合 ADR-0076 D11「单一 owner」,但跨插件,且 /me/permissions 带着约 195 行权限合并逻辑(含 foldWildcardSuperUser)与 objectql/spec 依赖。

拆完之后才是原计划:默认值翻 false → 观察一个 release → 删除 registerDiscoveryAndCrudEndpoints 余下部分。

顺带发现:/auth/me/* 目前是靠加载顺序活着的

plugin-auth 在 kernel:ready 里注册 rawApp.all('${basePath}/*')auth-plugin.ts:1817),终结式转发给 better-auth、不会 next();hono 的 /auth/me/permissions 也在 kernel:ready 注册。两者谁先,取决于 kernel.use() 顺序(今天是 HonoServerPlugin 先于 AuthPlugin,所以 hono 的具体路由先注册、赢下匹配)。一旦顺序调换,/auth/me/permissions 会落进 better-auth 的 catch-all 拿到 404。

这与 #2567#4018 是同一类「靠顺序侥幸成立」的问题 —— 也是选 B 的最强论据:让 auth 拥有 /auth/* 下的全部路由,这个碰撞根本不存在。

顺带:三个死常量

hono-plugin.tsDEFAULT_ENDPOINT_PRIORITY = 100 / CORE_ENDPOINT_PRIORITY = 950 / DISCOVERY_ENDPOINT_PRIORITY = 900 声明后全仓零引用DISCOVERY_ENDPOINT_PRIORITY 尤其误导:它暗示存在一套 discovery 优先级机制,而真实机制是 Hono 的先注册先赢。

相邻的、可独立处理的缝

该面的 discovery payload 是 { version, apiName, routes, capabilities }不满足 DiscoverySchema(缺 name / environment / locale / services)。services 恰是 D12 定的 single source of truth(handlerReady),standalone 客户端因此读不到 D12 的那一半信息。走完退役路线后这条自动消失。

关联:#4018#4063 已合并)、#2567#3298、ADR-0076 D11/D12、OQ#9。

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions