Skip to content

test(plugin-auth): 证明 ADR-0069 D4 真能撤销活动会话,并把「会话的记录之处」定为 sys_session (#4785) - #5101

Merged
os-zhuang merged 2 commits into
mainfrom
claude/issue-4785-session-of-record
Aug 4, 2026
Merged

test(plugin-auth): 证明 ADR-0069 D4 真能撤销活动会话,并把「会话的记录之处」定为 sys_session (#4785)#5101
os-zhuang merged 2 commits into
mainfrom
claude/issue-4785-session-of-record

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Fixes #4785

按维护者 2026-08-04 的裁定落地方案 A:会话的记录之处永远是 sys_session(数据库);cache 之于 auth 只是限流计数器。方案 C(双写)明确否决。

运行时零改动 —— 本 PR 做的是:把决策记录下来,并证明依赖这条决策的行为真的成立。

核心交付:那条一直缺席的端到端测试

packages/plugins/plugin-auth/src/session-of-record.test.ts(新增,12 个用例)。

ADR-0069 D4 声明了三个会话管控(空闲超时 / 绝对时长 / 并发上限),三者都靠sys_session来撤销,而此前没有任何测试断言过「这次写真的让一个活动会话失效了」—— 顶多断言了某个列被盖上了戳。盖了戳却没人读的行,正是 #4785 描述的失效形态,所以每个用例断言的都是链路的终点:一个带真实会话 cookie 的请求不再是已认证状态,而不是某列被写了。

沿用 #3585 EdDSA 测试确立的范式:跑真实的 better-auth 管线(真实 RequestAuthManager.handleRequest → better-auth 签发的真实 cookie),不 stub 我们自己的代码。

覆盖:

  • 记录之处:注册真的写出 sys_session 行;把行删掉,cookie 立刻失认证 —— 这条反向断言才真正证明「读的是数据库」。
  • 空闲超时 / 绝对时长 / 并发上限:各自真的终结一个活动会话,并各自有「不该撤销时不撤销」和「设为 0 时关闭」的对照用例。
  • 三个管控的 revoke_reason 各不相同,且 expires_at 确实被写到过去。

变异测试验证过它不是空转(都能变红):

  • enforceSessionControls / enforceConcurrentCap 改成 no-op → 4 个用例失败;
  • 只盖 revoked_at 而不把 expires_at 写到过去(即「盖戳但读路径不看」)→ 2 个用例失败。

顺带查实的两个事实(都已钉住)

排查中发现,这个洞比 issue 正文估计的更难被误踩,两点都写成了测试:

  1. AuthManager 根本不透传 session.storeSessionInDatabase —— 它构造 better-auth 的 session 块时只取 expiresIn/updateAge被否决的方案 C 在配置层面就不可达:宿主即使传了这个标志,拿到的仍是 cache-only 形态。
  2. 默认组合下 OIDC provider 插件是开的,而 better-auth 1.7 在有 secondaryStorage 却没有 storeSessionInDatabase直接拒绝启动。加上第 1 点,标准 serve 组合根本无法悄悄走到出问题的架构上 —— 是响亮的启动失败,不是静默降级(符合 AGENTS.md「Absence must be loud」)。

D4 本身有没有真缺陷?没有

按任务要求核过了:在裁定的架构(DB 为记录之处)下三个管控都真的生效。读路径确实认写进去的过去 expires_at(better-auth getSession 第 190 行判 expiresAt < now),且 ObjectStack 没有开启 session.cookieCache,所以不存在「撤销了但 cookie 缓存还认」的窗口。auth-manager.ts 未作任何修改。

一个值得记录的语义(已写进 ADR 与测试注释):空闲/绝对两项在 customSession 里执行,而它跑在本次请求的会话已被校验之后,所以发现超时的那次请求仍然成功,下一次才失认证;并发上限在登录 after-hook 里跑,立即生效。测试把这个一次请求的延迟钉住了 —— 免得有人为了「修掉延迟」把检查挪到撤销写不再发生的地方。

文档侧

docs/adr/0069-*.md

  • D2 的「shared store」明确限定为限流计数器:它是 rateLimit.customStorage不是 secondaryStorage(后者会连带搬走会话记录之处);并写明若将来要把会话搬进缓存,那是一条新决策,必须在同一条 ADR 里给出撤销一致性要求(撤销如何让每个节点的缓存快照失效,失效失败时怎么办)。
  • D4 增加说明:sys_sessionD4 的前置条件,不是部署偏好;点明真正起撤销作用的是 expires_atrevoked_at/revoke_reason 是诊断用),只盖后两者的撤销是无效的。
  • 状态行改为事实描述,并记录 C 被否决及其理由。

content/docs/kernel/contracts/cache-service.mdx(按裁定只改开头那一段):从 cache 的用途列表里去掉 "session storage",改为写实 —— 会话记录之处是 sys_session;宿主可以用 cacheSecondaryStorage() 显式选用,但选用即停用 D4 三个会话管控,被撤销的会话会一直可用到缓存副本过期。

auth-plugin.test.ts 那条「cache 不绑 secondaryStorage」的钉扎测试保留,加注指向新测试 —— 原注释说的是「我们怎么做」,新测试说的是「为什么这件事重要」。

验证

pnpm --filter @objectstack/plugin-auth test       # 32 files, 711 passed
pnpm --filter @objectstack/plugin-auth typecheck  # tsc --noEmit, 干净
pnpm check:adr-anchors / check:doc-authoring / check:role-word
pnpm check:release-notes / check:nul-bytes        # 全绿
eslint(改动的两个测试文件)                        # 干净

已合入 origin/main(合入内容只动 packages/lint,与本 PR 不相交),合后重跑上述用例仍全绿。

范围

packages/plugins/plugin-auth/**(纯测试)+ docs/adr/0069-*.md + cache-service.mdx 开头一段 + changeset。packages/spec/**content/docs/releases/ 零改动

一处可选的后续(未做,因超出认领的文件面):可以给 secondary-storage.tsscripts/adr-anchors.json 里加一条 ADR-0069 锚点。目前 ADR 正文点名了测试文件、测试文件通篇点名 ADR-0069 D4,双向可发现性已经成立,check:adr-anchors 也是绿的。


Generated by Claude Code

claude added 2 commits August 4, 2026 04:33
…s_session as the session of record (#4785)

The maintainer's ruling on #4785: the session of record is ALWAYS `sys_session`
(the database); the kernel `cache` service serves auth as the rate-limit counter
store only. Dual-write (`session.storeSessionInDatabase: true`) is rejected.

No runtime change — this records the decision and proves the behaviour that
depends on it.

`session-of-record.test.ts` is the deliverable the conflict was hiding behind:
ADR-0069 D4 declares three session controls (idle timeout, absolute lifetime,
concurrent cap) that all revoke by writing the `sys_session` row, and nothing
asserted that write actually ends a LIVE session. Every test here therefore
asserts de-authentication of a real session cookie through the real better-auth
pipeline, not that a column was stamped — a stamped row nobody reads is exactly
the failure mode #4785 describes.

Verified by mutation: neutering either enforcement path, or stamping
`revoked_at` without expiring `expires_at`, turns the corresponding tests red.

Two facts found while writing it, both pinned:
- `AuthManager` never plumbs `storeSessionInDatabase`, so the rejected
  dual-write shape is unreachable through configuration;
- the default composition (OIDC provider on) makes better-auth REFUSE to boot
  with a `secondaryStorage` rather than degrade quietly — so the standard
  `serve` cannot silently reach the broken architecture.

ADR-0069: D2's "shared store" scoped to rate-limit counters, with a cache-backed
session store named as a NEW decision requiring its own revocation-consistency
requirements; D4 records `sys_session` as a precondition rather than a
deployment preference; status lines made factual.

`content/docs/kernel/contracts/cache-service.mdx` no longer lists session
storage among the cache's uses, and says what `cacheSecondaryStorage()` costs.

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

vercel Bot commented Aug 4, 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 Aug 4, 2026 4:34am

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling size/l labels Aug 4, 2026
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

No hand-written docs reference the 0 changed package(s). ✅

@os-zhuang
os-zhuang marked this pull request as ready for review August 4, 2026 04:39
@os-zhuang
os-zhuang enabled auto-merge August 4, 2026 04:39
@os-zhuang
os-zhuang added this pull request to the merge queue Aug 4, 2026
Merged via the queue into main with commit b40f81c Aug 4, 2026
25 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-4785-session-of-record branch August 4, 2026 04:52
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/l tests tooling

Projects

None yet

2 participants