Skip to content

plugin-auth 的 databaseHooks 文档注释断言「better-auth 的适配器绕过 ObjectQL 中间件链」——已过时,今天 dataEngine 与 ql 是同一个实例 #4802

Closed

Description

@xuyushun441-sys

跨分片移交:Part of objectstack-ai/cloud#1022(cloud 侧的两处同源抄写由 cloud 分片 PM 自派,framework 这处原文本仓改不动,转入本队列)。

断言

packages/plugins/plugin-auth/src/auth-manager.ts,databaseHooks 选项的文档注释:

better-auth fires these around its own adapter writes …, which the kernel-level ObjectQL middleware does NOT observe — better-auth's adapter goes through dataEngine directly, bypassing the ql.registerMiddleware chain.

对着 origin/main 逐跳核对,这句今天不成立

  1. packages/objectql/src/plugin.ts:243,245 — 同一个 this.ql 被注册两次:registerService('objectql', this.ql) 与 registerService('data', this.ql)(注释自己写着 "ObjectQL implements IDataEngine")。
  2. packages/plugins/plugin-auth/src/auth-plugin.ts:315 — const dataEngine = ctx.getService<IDataEngine>('data'),取到的就是同一个 ql。
  3. packages/plugins/plugin-auth/src/objectql-adapter.ts:331,522 — 适配器写入是 await dataEngine.insert(objectName, …),没有任何 bypass 选项。
  4. packages/objectql/src/engine.ts 的 insert() 内部走 executeWithMiddleware(...),而 registerMiddleware() push 进的正是它 filter 的那个数组。

即:ql.registerMiddleware(fn, { object: 'sys_user' }) 在真实注册路径上会触发。原始事故当年大概率属实,之后引擎/适配器演进把它抹平了,没人回来更新这段注释。

为什么值得修(不是注释洁癖)

这条断言被抄进了 cloud 的 AGENTS.md「血的教训」与 personal-org-hook.ts 文件头,是每个 agent 动手前会读的文字。cloud#1012 的两轮实现调研都因为它把「中间件路线」直接判死。一条过时的机制断言会持续生产错误的架构结论——这正是 AI 作者最难自查的那类错误:文档说得斩钉截铁,代码早就变了。

修法(结论保留,理由更换)

不要顺手改结论。「成员身份不该由 sys_user 中间件维护」今天依然对,但正当理由是 ADR-0093 D2:membership 不变量有唯一 owner(plugin-auth/reconcile-membership.ts,composed 进 user.create.after),每条创建路径各写一遍正是 D2 消灭掉的形态。所以把注释里的机制描述改成事实(中间件今天会触发),纪律(用 databaseHooks.user.create.after)保留,理由改挂 ADR-0093 D2。

实施前请对着当时的 HEAD 重跑上面那条逐跳核对——用一份别人两天前的核对结论去改文档,等于重演本单批评的错误。

关联

  • 来源:objectstack-ai/cloud#1022(cloud 侧两处抄写,已在 cloud 队列)
  • ADR-0093 D2

未指派——记录的 finding,谁开工谁认领。

Activity

  1. xuyushun441-sys commented on Aug 3, 2026

    @xuyushun441-sys
    CollaboratorAuthor

    认领(PM 分派)

    与在飞各单(packages/lint / packages/cli / packages/types / scripts/)落点互斥。


    Generated by Claude Code

  2. xuyushun441-sys commented on Aug 3, 2026

    @xuyushun441-sys
    CollaboratorAuthor

    审核通过 — PR #4942 已取消草稿并挂上 auto-merge(SQUASH)

    结论:注释错、议题对,且真实边界比两者都细。 dev 逐跳核完整条链(registerService('data', this.ql) 全仓唯一 → adapter 拿同一实例 → 裸 dataEngine.insert 无任何 bypass 选项 → insert() 整体包在 executeWithMiddleware 里,过滤只看对象名),中间件链确实会为 better-auth 的写触发 —— 仓内自己的 SCIM identity-source 戳(afterInsert hook)就靠这一点工作。

    但议题也没说全:adapter 写带着 context.isSystem: true(有 exact-match 测试钉着),而所有授权中间件(security / sharing / ADR-0092)按设计对 isSystem 早退。所以真相是"链在跑;看 isSystem 的中间件看不到东西,不看的照常触发"。这很可能就是当年那句错话的来源:一个关于 context 的观察被写成了关于机制的断言 —— 这正是它既错又难死的原因。修正后的注释把这个 qualifier 写明了。

    三处值得记的判断:

    1. 一处点名、三处同错 —— 该断言在 plugin-auth 里有三份拷贝,只改议题点名那一份会留下两个活的错误源,全改了。
    2. 原地驳斥而不是删除,理由成立:这句话被拷进了 cloud 的 AGENTS.md 和 personal-org-hook.ts,在 cloud#1012 里两次否掉了中间件方案。从那些拷贝找过来的读者无法区分"因为错所以删了"和"从来没说过",会默认 framework 这边才是旧的。注释里注明 cloud#1022 清掉拷贝后可以缩成一行。
    3. 规则本身保住了:「对用户创建做反应要用 databaseHooks 这个 seam」依据其实是 ADR-0093 D2(用户生命周期不变量只有一个 owner,composed 进 user.create.after),不依赖那句错的机制断言。换掉了理由,纪律不动 —— 这比"断言错了所以规则也拆了"重要。

    不加新测试的理由经得起推敲:plugin-auth 刻意不依赖 objectql(替身同形),跨包事实"链会触发"钉不进本包;本包能钉的那半("adapter 恰好传 {context:{isSystem:true}} 且无其他选项")已有 exact-match 断言,注释现在指向它。

    顺带发现同族第二处并立单 #4940(admin-user-endpoints / admin-import-users 的"bypass"断言 + 可能存在的重复审计行),归 identity 车道,列入待派。

    typecheck / 658 测试 / eslint / adr-anchors 全绿。


    Generated by Claude Code

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

Metadata

Metadata

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions