feat(spec)!: 双源 C4 收敛 — Session 归 ./api,./identity 侧死删 (#4641) - #4643
Merged
Conversation
`Session` / `SessionSchema` 各有两处声明,一处在 `api/auth.zod.ts`,一处在 `identity/identity.zod.ts`。消费者拿到哪个形状只取决于 import 路径(#4411 陷阱),而两者连字段名都不一致 —— 写错的表现是运行时 `undefined`,不是类型 错误。 三仓(framework / cloud / objectui)import 语句级扫描: - `./api` 侧是活的:形状 `{ id, expiresAt, token?, ipAddress?, userAgent?, userId }`,被接进 `SessionResponseSchema` —— `AuthEndpointPaths.getSession` (`/get-session`、`/me`、`/refresh`)的响应体,是真正的 runtime 读取点。 - `./identity` 侧零消费方:形状 `{ id, sessionToken, userId, activeOrganizationId?, expires, createdAt, updatedAt, ipAddress?, userAgent?, fingerprint? }`,除自身单测外无任何 importer,未接进任何父 schema。它还偏离 了自己声称描述的那张表 —— **被强制执行**的会话记录是 platform-objects 的 `sys_session` 对象,列名是 `token` / `expires_at`(与 `./api` 一致,而非 `./identity`),且根本没有 `fingerprint`。cloud 侧读 `activeOrganizationId` 走 better-auth 自己的类型,不经 spec。 处置(路线一,死删无消费方一侧,v17 major 窗口):`./identity` 的 `SessionSchema` 与 `Session` 移除,`./api` 成为裸名唯一所有者。 dual-source-exports.baseline.json 恰好删掉指名的 2 行(24 -> 22)。 回归 pin 用**运行时**断言而非 C1/C3 的编译期条件类型 —— 后者在这里是空转: `packages/spec/tsconfig.json` 排除了 `**/*.test.ts`,vitest 也不做类型检查, 所以那类 pin 不可能失败(已另立 #4642 记录,影响 #4581/#4638 已落地的 pin)。 本 PR 的断言经过 sabotage 验证:把声明加回去,测试立刻红。 连带更新:json-schema.manifest 去掉 identity/Session;authorable-surface 去掉 该 schema 的 10 个 key(整形状移除,同 #4638 先例);api-surface 重新生成。 reference docs 跟着声明走 —— `Session` 现在文档化在 `references/api/auth` (真正声明它的模块)上,名字碰撞产生的 `references/api/identity` 页随之消失。 严格性台账 `identity/` 粗粒度行 34 -> 33 并写明掉站点的原因。 docs-import-surface 基线未触发。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M9uWvoEp9CoLzYjNExj9sL
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 1 package(s): 107 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
os-zhuang
marked this pull request as ready for review
August 2, 2026 13:37
os-zhuang
enabled auto-merge
August 2, 2026 13:37
This was referenced Aug 2, 2026
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Aug 3, 2026
…elves (objectstack-ai#4650) (objectstack-ai#4726) Check (a) reads authorable-surface.json from the commit under check, so hand-deleting a baseline line deleted the evidence it runs on (objectstack-ai#4638, objectstack-ai#4643 landed exactly that way; objectstack-ai#4662 proved the file was hand-edited). gen:schema / check:authorable-surface now add check (c): every key present at the merge base with origin/main but absent from this build must carry one of three in-gate proofs — 1. aged-out tombstone: base entry [RETIRED] + an ADR-0087 conversion/migration registered >= 2 majors ago; 2. def not reachable from the metadata-type roots (2026-08-02 ruling): BFS over the build's in-memory Zod graph from BUILTIN_METADATA_TYPE_SCHEMAS + EXTRA_METADATA_TYPE_SCHEMAS, with derived-clone bridging so .refine()/.extend() copies keep their originals protected; waives ONLY this file's tombstone requirement; 3. whole def no longer emitted (manifest ratchet / api-surface jurisdiction). Anchoring on the merge base (not HEAD) keeps the check alive in CI, where HEAD is the PR's own commit and a HEAD-relative diff is always empty. --check further rejects any byte of the file that is not the generator's output (objectstack-ai#4662 description drift class); write mode regenerates it. Checks (a0)/(a)/(b) unchanged and pinned by tests. Fixes objectstack-ai#4650 Claude-Session: https://claude.ai/code/session_0176qgxgCXTJCUv4YFLtusP9 Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #4641
#4535 C 组第四簇(C4)。
Session/SessionSchema各有两处声明,消费者拿到哪个形状只取决于 import 路径(#4411 陷阱)—— 而两侧连字段名都不一致,所以写错的表现是运行时undefined,不是类型错误。判真源:三仓 import 语句级扫描
./api(api/auth.zod.ts){ id, expiresAt, token?, ipAddress?, userAgent?, userId }SessionResponseSchema,即AuthEndpointPaths.getSession(/get-session、/me、/refresh)的响应体./identity(identity/identity.zod.ts){ id, sessionToken, userId, activeOrganizationId?, expires, createdAt, updatedAt, ipAddress?, userAgent?, fingerprint? }判定
./identity侧为死侧的决定性证据不只是「没人 import」,还有它已经偏离了自己声称描述的那张表:被强制执行的会话记录是packages/platform-objects/src/identity/sys-session.object.ts的sys_session对象,列名是token/expires_at(与./api一致,而非./identity的sessionToken/expires),且根本没有fingerprint。cloud 侧确实读activeOrganizationId,但走的是 better-auth 自己的类型(active-org-session-hook.ts只 import 了@objectstack/spec/contracts),不经 spec 的Session。也就是说
./identity的Session是一份没人执行、且已经漂移的 better-auth 词汇复述 —— 正是 ADR-0049「declared but unenforced」要清掉的东西。处置
路线一:死删无消费方一侧(v17 major 窗口)。
./identity的SessionSchema/Session移除,./api成为裸名唯一所有者。未做收敛+re-export:两者是「存储行」与「线上投影」两个概念,收敛要么把sessionToken/fingerprint泄进 REST 响应体,要么收窄记录 —— 都不对。也未改名:改名会把一个零消费方的死声明换个名字留下来。dual-source-exports.baseline.json恰好删掉 issue 指名的 2 行,24 → 22,只减不增:回归 pin —— 顺带发现手册推荐的 pin 是空转的
手册(C1 #4581 / C3 #4638 先例)推荐用编译期条件类型做 pin。落之前我实测了一下,它在
packages/spec里不可能失败:packages/spec/tsconfig.json的exclude含**/*.test.ts——pnpm typecheck根本不编译测试文件;vitest.config.ts没开typecheck.enabled,vitest 走 esbuild 剥类型,不做类型检查。sabotage 验证:把
export const SessionSchema加回identity.zod.ts再跑tsc --noEmit,pin 零报错。附带第二个坑:
keyof typeof import(...)只枚举 value 导出,type-only 导出对它不可见,所以对裸类型名写这类断言即便被编译也是空转(已用最小复现验证)。所以本 PR 没有跟着写一个「看起来像门禁、实际不会红」的 pin,改用运行时模块命名空间断言,并在注释里写明取舍;裸类型交给
check:dual-source-exports(读构建产物.d.ts,type 和 const 都枚举)。同样的 sabotage 下新断言立刻红:范围外发现已另立 #4642(unassigned)记录,影响 #4581 / #4638 已落地的 pin —— 本 PR 不修。
连带产物
json-schema.manifest.json:去掉identity/Sessionauthorable-surface.json:去掉该 schema 的 10 个 key(整形状移除,同 feat(spec)!: 双源 C3 收敛 — 通知语汇归 ./api,./ui 与 ./system 侧死删 (#4610) #4638 先例;非可作者化 metadata key,不需要 tombstone / ADR-0087)api-surface.json:重新生成Session之前因名字碰撞被生成到references/api/identity页,而真正声明它的references/api/auth页反而缺了它。现在Session出现在api/auth,碰撞页api/identity随之消失(api/meta.json同步)identity/粗粒度行 34 → 33,并写明掉站点的原因(该行在粗粒度表,不受 gate 解析,但地图不能漂)docs-import-surface.baseline.json(fix(#4570): reference 页的 import 示例改由真实导出面生成,并加只减不增的棘轮兜底 #4595 ratchet):未触发,未改动content/docs/releases/:未触碰changeset
.changeset/session-dual-source-c4.md——@objectstack/specmajor,含 FROM → TO 表与迁移指引(sessionToken→token,expires→expiresAt;createdAt/updatedAt/activeOrganizationId/fingerprint不在线上形状,改读sys_session对象)。验证(全绿)
在
packages/spec下,全部前台跑、共享 flock 串行、--max-old-space-size=4096:pnpm --filter @objectstack/spec buildpnpm check:dual-source-exports4313 names across 16 entry points — 160 re-exported, 22 accepted dual-source (baseline)pnpm check:generatedAll 8 generated artifacts are up to date.pnpm test291 passed (291) / 7266 passed (7266)check:livenesscheck:strictness-ledger67 file(s) across 5 triaged director(ies) — 站点数与章节总计均衡check:empty-stateall classified (1 closed, 2 open, 4 output, 9 scope)check:variant-docs20 discriminated union(s) — 8 governed, 12 exemptcheck:exported-any1884 types + 1638 schemas,无anycheck:skill-examples202 prose examples type-checkpnpm typecheck122 successful, 122 total收尾前
git fetch origin main && git merge origin/main→Already up to date(main 仍在0a936ea62),无需重跑。🤖 Generated with Claude Code
https://claude.ai/code/session_01M9uWvoEp9CoLzYjNExj9sL
Generated by Claude Code