Skip to content

评估:plugin-dev「stub 填满槽位」这个早期设计是否整体作废 —— #4000/#4058 逐个退役到第 4 个之后,该问的是台面本身 #4093

Description

@os-zhuang

按 Prime Directive #10 记录。#4000 退役 1 个 dev stub,#4058 / PR #4086 又退役 3 个并把剩下的分了类 —— 两轮都是"逐个判断"。这条 issue 问上一层的问题:「任何未被真实插件占用的核心服务槽位,都注册一个 dev stub 填满」这个设计本身是否还应该存在。

不是要求立刻删,是要求评估。下面是评估所需的证据,以及一个分档建议。

现状(#4086 之后)

槽位 plugin-dev 里的实现
degraded(真干活,内存内) cache, queue, job, i18n 委托 core 的 createMemory*
metadata 手写第二份(见证据 3)
file-storage, search, realtime, workflow 只有 plugin-dev 有
stub(编造) data, auth, security.permissions, security.rls, security.fieldMasker 手写
ui 无 factory → 无形状占位 {}
已退役 analytics(#4000), automation, notification, ai(#4058)

证据

1. 原设计理由已被反证

代码里的理由是"填满完整的 kernel service map,让下游拿到正确的返回类型(数组、布尔、对象,而不是 undefined)"。

生产环境本来就是空槽 —— 所有这些服务都是 optional,没装插件就没有。所以每个下游本来就必须处理缺失,否则生产早就崩了。#4086 是直接反证:一次清空 4 个槽位,runtime 923 / plugin-dev 8 / objectql 1183 / metadata-protocol 99 全绿,端到端起 kernel + dispatcher 正常服务。没有任何"拿到 undefined 就崩"的下游冒出来。

这个理由成立的前提,恰恰是它想避免的那个 bug —— 如果真有下游不处理空槽,那个下游在生产里就是错的,应该修它,而不是在 dev 里喂假数据把它盖住。

2. 安全形状的假实现,撞在 D12/#3891 自己定的红线上

ADR-0076 D12 从 #3891 学到的结论写得很明确:「A fallback may degrade features, never security semantics」。plugin-dev 的这三个 stub 做的正好是后者:

security.permissions  checkObjectPermission() { return true; }   // allow-all
security.rls          compileFilter()        { return null; }    // 无行过滤
security.fieldMasker  maskResults(r)         { return r; }       // 不脱敏

packages/spec/src/contracts/security-service.ts:20 明确说这三个句柄"are implementation internals and deliberately NOT part of this contract",由 plugin-security 注册 —— plugin-dev 却给别人的实现内部造了假,且方向与同一份契约要求的 fail-closed 相反("Access-narrowing answers fail CLOSED … A consumer must never treat a thrown error or a deny filter as 'no restriction'")。

触发条件是现实的:plugin-dev 对每个子插件都是 try/catch 优雅降级,所以只要 @objectstack/plugin-security 没装上(一条 warn 日志),allow-all + 无 RLS + 不脱敏就静默上线。这一档与"该不该保留 stub 台面"无关,本身就该走。

补充一个精确性:auth stub 在真实身份路径上是死代码 —— resolve-execution-context.ts:104/120 走的是 authService.api.getSession() / getApi() / verifyMcpAccessToken(),而 stub 只实现了 verify() / getCurrentUser() / handleRequest(),所以实际降级为匿名(fail-closed)。它不是活的越权洞。但 verify() → { success: true, user: { roles: ['admin'] } } 正是下一个消费者会直接信的形状,而且它占着槽位让 discovery 声称 auth 在场、让 /api/v1/auth 被广告出去。潜在,而非当下。

3. 与 packages/core/src/fallbacks/ 重复,且有"同一个 bug 修两处"的收据

cache/queue/job/i18n 是委托 core 的 createMemory*(这没问题)。但 metadata 是 plugin-dev 手写的第二份内存注册表,与 packages/core/src/fallbacks/memory-metadata.ts(63 行)并存。代价已经付过一次,packages/core/CHANGELOG.md:1386 原文:

Both in-memory metadata fallbacks (@objectstack/core's createMemoryMetadata and @objectstack/plugin-dev's dev stub) now implement registerInMemory

一个 registerInMemory 缺失的 bug,要在两个地方修 —— 与 #3891 记录的"同一个安全门被造了两遍"完全同形,只是这次代价小。

4. 这个包已发布、无护栏、仓库内无人使用、文档描述还是错的

  • @objectstack/plugin-dev 不是 private17.0.0-rc.0),会发到 npm。
  • 没有任何 NODE_ENV / 环境护栏dev-plugin.ts 里没有一处检查运行环境,装上就注册整套假实现(包括第 2 条那三个)。
  • 仓库内没有真实使用者os dev 走的是 serve 的真实 capability 装配(commands/dev.ts 不引用 DevPlugin),examples / create-objectstack 模板 / apps 全无引用;只剩 CLI 一处测试注释和文档。
  • 文档描述与实际不符content/docs/plugins/packages.mdx:347-350 说它是"Metadata validation, schema introspection, debugging tools"。实际它是"一键装配 objectql + driver-memory + auth + security + hono + rest + dispatcher"外加 stub 台面 —— 两件事都不是文档写的那件。

建议:分三档,不一刀切

A. 造假类 → 直接删dataauthsecurity.permissionssecurity.rlssecurity.fieldMasker、无形状 ui
空槽就是生产语义,#4000/#4058 已经把这条路走通四次。第 2 条的三个安全槽位是这一档里最该先走的。

B. core 已有 fallback 的 → 删掉 plugin-dev 那一份metadata,以及把 cache/queue/job/i18n 的包装收干净)
一份实现,一处修。与 #4089 合并做更省:那条要给 core 的五个 fallback 打 __serviceInfo,正好一起把"谁是唯一那份"定下来。

C. 只有 plugin-dev 有的真实内存实现 → 唯一值得讨论保留的一档file-storagesearchrealtimeworkflow
它们确实有 dev 价值(不装服务也能试功能)。但正确归宿是真实服务自己的 InMemory 策略@objectstack/service-analytics 已是先例 —— #4000 的迁移路径就是"装真引擎,它有 InMemory 策略")或 core fallback,而不是一份平行的假台面。所以这一档的问题不是"删不删",而是"退役前要不要先把对应服务的 InMemory 策略补上"。

剩下的 plugin-dev = 纯装配插件。那才是它真正的价值,也应该顺手把文档描述改对。

待定问题

  1. 已发布包的 breaking 面:外部使用者可能真的依赖某个 stub。17.x 还在 rc,是直接切 + changeset 写清 FROM→TO,还是留一个 deprecation 窗口(stub 保留但打 stub 标记 + 启动 warn)?
  2. C 档的顺序:先补 InMemory 策略再退役,还是先退役、把"dev 里试不了"当可接受的短期回退?前者更慢但不产生体验回退。
  3. 是否顺带加环境护栏:给 plugin-dev 加 NODE_ENV !== 'production' 断言(逃逸阀按 PD [WIP] Create a new release version #9OS_ALLOW_*)。考虑到第 2 条的安全语义问题,这条可能不该等这个评估 —— 也许应该独立成 issue 先走。

关联:#4058#4000#4089#4087#3891#3989、ADR-0076 D12。

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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