Repository navigation
Description
TL;DR
修 #4772 时确认了一个更大的、从未被触发过所以从未被发现的冲突:只要把 kernel cache 服务接成 better-auth 的 secondaryStorage,ADR-0069 D4 的会话管控(空闲超时 / 绝对时长上限 / 并发会话数上限)就会静默失效,同时 sys_session 表会变空。
这不是 #4772 的功能洞(那个是限流计数器,已在 #4772 里修掉)。这是「会话的记录之处(session of record)在数据库还是在缓存」这个架构问题,需要维护者裁定 + 一条 ADR 修订。本 issue 只记录事实与选项,不自行决定。
事实(better-auth 1.7.0-rc.2,代码证据)
node_modules/better-auth/dist/db/internal-adapter.mjs:
createSession:if (secondaryStorage && !storeInDb) { ... }—— 设了secondaryStorage且未设session.storeSessionInDatabase时,不写sys_session行,会话只存在于缓存里。findSession:先读secondaryStorage.get(token);命中就直接返回,完全不查数据库。即使打开storeSessionInDatabase(两边都写),读路径依然以缓存快照为准。
而 ObjectStack 这一侧(packages/plugins/plugin-auth/src/auth-manager.ts):
enforceSessionControls()(ADR-0069 D4 空闲/绝对超时):engine.findOne('sys_session', ...),超限时engine.update('sys_session', { expires_at: 过去, revoked_at: now, revoke_reason });enforceConcurrentCap()(D4 并发上限):engine.find('sys_session', ...)后同样用 update 撤销。
两者都是靠写数据库行来撤销会话,且都是 best-effort(异常吞掉)。所以一旦会话读路径走缓存:
findOne查不到行 → 直接 return,空闲/绝对超时不再生效;- 即便查得到(
storeSessionInDatabase: true),写进数据库的撤销不会被 better-auth 读到,缓存快照最长活到会话 TTL(默认 7 天); sys_session空表还会牵连sys_presence/sys_oauth_access_token/sys_oauth_refresh_token三个对象上的 lookup 外键,以及会话列表/「登出其他会话」这类 UI。
为什么一直没人发现
AuthPlugin.init() 里的那次 getServiceAsync('cache') 探测跑在 CacheServicePlugin 注册之前(#4772 现场:早 21ms),所以标准 serve 组合下这个绑定从来没成功过。ADR-0069 的实现状态表却写着「shared multi-node rate-limit + session store … are landed」——声明与运行时不一致(Prime Directive #10 的经典形态)。#4772 里我把这条状态行改成了事实描述,并把该 issue 的修复限定在限流计数器上,没有替维护者决定会话该放哪。
packages/plugins/plugin-auth/src/auth-plugin.test.ts 里有一条测试把当前行为钉住了:接上 cache 后不把它绑成 secondaryStorage。要改方向,先改这条测试。
选项
A. 会话记录之处永远是 sys_session(数据库),缓存只做限流计数器(= #4772 落地后的现状)
- 长远合理性:
sys_session是一等平台对象(有视图、动作、三处外键、D4 管控)。只有一个权威存储,撤销就是写行,管控真的可执行。多节点下会话读走数据库(有 join),比缓存慢,但正确。 - 防错:声明=强制。D4 说「写行即撤销」,运行时就真的这么做。
- 代价:拿不到 better-auth 的会话缓存加速;
ADR-0092 D6的 identity-write-guard 快照刷新在默认组合下永远是 no-op(今天也是)。
B. 会话搬进缓存(真正启用 secondaryStorage),并把 D4 改写成通过 better-auth API 撤销 + 主动失效缓存
- 长远合理性:性能更好,多节点会话共享;但 D4 三个管控、
sys_session的外键与 UI 都要重做,且撤销必须双写(DB + 缓存失效),任何漏网路径都是安全性问题而不是性能问题。 - 防错:更难写对 —— 「撤销」从一次写变成两处一致性,AI 生成的新管控极易只写一半。
- 代价:工作量大,需要新 ADR。
C. 双写(session.storeSessionInDatabase: true)
- 看起来两全,实际是最差:行在库里(D4 的
findOne查得到,看起来在工作),但读路径仍以缓存为准,所以撤销看起来生效、实际不生效。请勿选。
建议
A,并把 ADR-0069 D2 的「shared store」明确限定为限流计数器,会话存储另开一条决策(若将来要做 B,配套的撤销一致性要求写进同一条 ADR)。
验收(无论选哪个)
- ADR-0069 的相应 D 行 / 状态行更新,说明会话记录之处;
- 有测试证明 D4 的三个管控在选定架构下真的能撤销一个活动会话(今天没有这样的端到端测试,这也是冲突长期隐形的原因之一)。
相关:#4772(限流计数器惰性解析 cache)、ADR-0069 D2/D4、ADR-0092 D6。
Activity
os-zhuang commentedon Aug 3, 2026
PM 补充:#4788 的 docs-drift 检查(advisory)让我去查了文档侧,查出两条与本决策直接相关的事实,记在这里作为裁定的输入。
一、
content/docs/kernel/contracts/cache-service.mdx:8也在复述同一句失准声明:「session storage」这一项从来没有成立过 —— 标准组合下那次绑定从未触发(本 issue 的根因),而 #4788 之后它是刻意不自动接线的。所以这句话在过去是「声明了但没兑现」,在未来则取决于你在本 issue 怎么裁:
sys_session,fix(plugin-auth): 限流计数器惰性解析 kernel cache —— 误报的告警,与它掩盖的共享限流功能洞 #4788 落地后的现状):这一项应当删掉,或改写为「限流计数器;会话存储需宿主显式选用cacheSecondaryStorage(),并知晓它会停用 ADR-0069 D4 的会话管控」;不建议现在单独改这一行 —— 它的正确内容由本 issue 的裁定决定,先改就是再赌一次。等你定了方向,它跟着一起改。(按同样的理由,我没有为它单开 issue,而是记在这里 —— 决策未定之前它没有独立的正确答案。)
二、另一处相反的情况,不需要动作,但值得你知道:
content/docs/permissions/authorization.mdx:403-406写着 ADR-0069 的「session lifecycle + global IP allowlist + shared multi-node rate-limit store (P2) are landed」。这句话在 #4788 之前是假的(限流计数器从未真的共享),合并之后才第一次成真。也就是说:文档早就在向用户承诺跨节点限流,而运行时一直没做到 —— #4772 的影响面比它的 issue 正文描述的更大,那不只是一条误导性的启动日志,而是一个已经写进公开文档的能力承诺。这一行不需要修改,#4788 让它变准了。
这两条合起来正好是这一族问题的两个方向:一处是文档承诺了运行时没做的事(已被 #4788 补上),另一处是文档承诺了运行时刻意不做的事(等你裁定)。
Generated by Claude Code
fix(plugin-auth): resolve the kernel cache at counting time, not at i…
fix(plugin-auth): count the per-number OTP send budget in the shared …
claude commentedon Aug 4, 2026
维护者裁定(2026-08-04):方案 A —— 会话记录之处永远是
sys_session(数据库);cache 之于 auth 只是限流计数器。C(双写)明确否决。范围:
content/docs/kernel/contracts/cache-service.mdx:8的「session storage」按裁定改写(删除或改为「显式选用 + 明示停用 D4 管控」,以代码实际存在的 seam 为准 —— dev 核实后写实);auth-plugin.test.ts那条「cache 不绑 secondaryStorage」的钉扎测试保留并按需加注。摘
needs-user-decision,本轮派发。(文件面含docs/adr/**与content/docs/kernel/contracts/一行 —— 认领注释里声明,供 devx 车道的全局在飞检查读取。)Generated by Claude Code
claude commentedon Aug 4, 2026
认领:PM 循环第 5 轮(identity 车道)
会话:
session_015W6nhsDrz6zWQc8je12a1t分支:
claude/issue-4785-session-of-recordWorktree:
objectstack-issue-4785域:
domain:identity文件面:
packages/plugins/plugin-auth/**(测试为主)+docs/adr/0069-*.md+content/docs/kernel/contracts/cache-service.mdx一行(裁定配套,声明供 devx 车道在飞检查;devx PM 若有异议请说)+ changesetGenerated by Claude Code
claude commentedon Aug 4, 2026
PM 验收(第 5 轮,identity 车道):ACCEPT — PR #5101,CI 绿后转 ready 入队
auth-manager.ts与 origin/main 字节一致(mutation 后已还原并核对)。会话记录之处 =sys_session由测试与 ADR 双面钉住。revoked_at不写expires_at过去时 → 2 红(「盖了章没人读」被真实检出);(b)两个 enforce 置空 → 4 红。这个测试套件能检出它要防的缺陷,不是仪式。cacheSecondaryStorage()显式 opt-in 已存在;D4 在裁定架构下无真实缺陷(读路径认expires_at < now;未启用 cookieCache,无残活窗口)—— 「没找到缺陷」是用检查证出来的,不是假设。storeSessionInDatabase从未被接线);默认组合 OIDC 开启时 better-auth 对secondaryStorage拒绝启动而非静默降级 —— 标准 serve 从未可能静默走进坏架构。CI 全绿后转 ready 入队,本单随 PR 关闭。
Generated by Claude Code
test(plugin-auth): prove ADR-0069 D4 revokes live sessions; record sy…
disclose what the cookieCache door costs, measured against better-aut…
disclose what the `session.cookieCache` door costs, measured against …