Skip to content

feat(spec)!: 双源 C4 收敛 — Session 归 ./api,./identity 侧死删 (#4641) - #4643

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-4641-session-dual-source
Aug 2, 2026
Merged

feat(spec)!: 双源 C4 收敛 — Session 归 ./api,./identity 侧死删 (#4641)#4643
os-zhuang merged 1 commit into
mainfrom
claude/issue-4641-session-dual-source

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Fixes #4641

#4535 C 组第四簇(C4)。Session / SessionSchema 各有两处声明,消费者拿到哪个形状只取决于 import 路径(#4411 陷阱)—— 而两侧连字段名都不一致,所以写错的表现是运行时 undefined,不是类型错误。

判真源:三仓 import 语句级扫描

形状 消费方 / runtime 读取点
./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? } 零消费方 —— framework / cloud / objectui 三仓除自身单测外无任何 importer,未接进任何父 schema

判定 ./identity 侧为死侧的决定性证据不只是「没人 import」,还有它已经偏离了自己声称描述的那张表:被强制执行的会话记录是 packages/platform-objects/src/identity/sys-session.object.tssys_session 对象,列名是 token / expires_at(与 ./api 一致,而非 ./identitysessionToken / expires),且根本没有 fingerprint。cloud 侧确实读 activeOrganizationId,但走的是 better-auth 自己的类型(active-org-session-hook.ts 只 import 了 @objectstack/spec/contracts),不经 spec 的 Session

也就是说 ./identitySession 是一份没人执行、且已经漂移的 better-auth 词汇复述 —— 正是 ADR-0049「declared but unenforced」要清掉的东西。

处置

路线一:死删无消费方一侧(v17 major 窗口)。./identitySessionSchema / Session 移除,./api 成为裸名唯一所有者。未做收敛+re-export:两者是「存储行」与「线上投影」两个概念,收敛要么把 sessionToken / fingerprint 泄进 REST 响应体,要么收窄记录 —— 都不对。也未改名:改名会把一个零消费方的死声明换个名字留下来。

dual-source-exports.baseline.json 恰好删掉 issue 指名的 2 行,24 → 22,只减不增:

- Session — [./api (type)] ≠ [./identity (type)]
- SessionSchema — [./api (const)] ≠ [./identity (const)]

回归 pin —— 顺带发现手册推荐的 pin 是空转的

手册(C1 #4581 / C3 #4638 先例)推荐用编译期条件类型做 pin。落之前我实测了一下,它在 packages/spec 里不可能失败:

  1. packages/spec/tsconfig.jsonexclude**/*.test.ts —— pnpm typecheck 根本不编译测试文件;
  2. 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 下新断言立刻红:

Tests  1 failed | 19 passed (20)

范围外发现已另立 #4642(unassigned)记录,影响 #4581 / #4638 已落地的 pin —— 本 PR 不修。

连带产物

changeset

.changeset/session-dual-source-c4.md —— @objectstack/spec major,含 FROM → TO 表与迁移指引(sessionTokentoken,expiresexpiresAt;createdAt / updatedAt / activeOrganizationId / fingerprint 不在线上形状,改读 sys_session 对象)。

验证(全绿)

packages/spec 下,全部前台跑、共享 flock 串行、--max-old-space-size=4096:

门禁 结果
pnpm --filter @objectstack/spec build
pnpm check:dual-source-exports 4313 names across 16 entry points — 160 re-exported, 22 accepted dual-source (baseline)
pnpm check:generated All 8 generated artifacts are up to date.
pnpm test 291 passed (291) / 7266 passed (7266)
check:liveness ✅ 全部 governed-type 属性已分类
check:strictness-ledger 67 file(s) across 5 triaged director(ies) — 站点数与章节总计均衡
check:empty-state all classified (1 closed, 2 open, 4 output, 9 scope)
check:variant-docs 20 discriminated union(s) — 8 governed, 12 exempt
check:exported-any 1884 types + 1638 schemas,无 any
check:skill-examples 202 prose examples type-check
全仓 pnpm typecheck 122 successful, 122 total

收尾前 git fetch origin main && git merge origin/mainAlready up to date(main 仍在 0a936ea62),无需重跑。

🤖 Generated with Claude Code

https://claude.ai/code/session_01M9uWvoEp9CoLzYjNExj9sL


Generated by Claude Code

`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
@vercel

vercel Bot commented Aug 2, 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 2, 2026 1:36pm

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling labels Aug 2, 2026
@github-actions

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec.

107 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/agents.mdx (via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx (via @objectstack/spec)
  • content/docs/ai/skills.mdx (via @objectstack/spec)
  • content/docs/api/client-sdk.mdx (via @objectstack/spec)
  • content/docs/api/environment-routing.mdx (via @objectstack/spec)
  • content/docs/api/error-catalog.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-client.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx (via @objectstack/spec)
  • content/docs/api/index.mdx (via @objectstack/spec)
  • content/docs/automation/approvals.mdx (via @objectstack/spec)
  • content/docs/automation/connectors.mdx (via @objectstack/spec)
  • content/docs/automation/flows.mdx (via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx (via packages/spec)
  • content/docs/automation/hooks.mdx (via @objectstack/spec)
  • content/docs/automation/index.mdx (via @objectstack/spec)
  • content/docs/automation/webhooks.mdx (via @objectstack/spec)
  • content/docs/automation/workflows.mdx (via @objectstack/spec)
  • content/docs/concepts/architecture.mdx (via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx (via packages/spec)
  • content/docs/concepts/index.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx (via packages/spec)
  • content/docs/concepts/north-star.mdx (via packages/spec)
  • content/docs/data-modeling/analytics.mdx (via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx (via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx (via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx (via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx (via @objectstack/spec)
  • content/docs/data-modeling/index.mdx (via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx (via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx (via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx (via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx (via @objectstack/spec)
  • content/docs/deployment/cli.mdx (via @objectstack/spec)
  • content/docs/deployment/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx (via @objectstack/spec)
  • content/docs/getting-started/build-with-claude-code.mdx (via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx (via @objectstack/spec)
  • content/docs/getting-started/examples.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx (via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/spec)
  • content/docs/kernel/cluster.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx (via packages/spec)
  • content/docs/kernel/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx (via packages/spec)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/spec)
  • content/docs/kernel/services.mdx (via @objectstack/spec)
  • content/docs/permissions/authorization.mdx (via @objectstack/spec)
  • content/docs/permissions/permission-sets.mdx (via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx (via @objectstack/spec)
  • content/docs/permissions/positions.mdx (via @objectstack/spec)
  • content/docs/permissions/rls.mdx (via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx (via @objectstack/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx (via @objectstack/spec)
  • content/docs/plugins/development.mdx (via @objectstack/spec)
  • content/docs/plugins/index.mdx (via @objectstack/spec)
  • content/docs/plugins/packages.mdx (via @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx (via @objectstack/spec)
  • content/docs/protocol/diagram.mdx (via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/runtime-capabilities.mdx (via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/query-syntax.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx (via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx (via @objectstack/spec)
  • content/docs/releases/implementation-status.mdx (via @objectstack/spec)
  • content/docs/releases/index.mdx (via @objectstack/spec)
  • content/docs/releases/v12.mdx (via @objectstack/spec)
  • content/docs/releases/v13.mdx (via @objectstack/spec)
  • content/docs/releases/v16.mdx (via @objectstack/spec)
  • content/docs/releases/v17.mdx (via @objectstack/spec)
  • content/docs/releases/v9.mdx (via @objectstack/spec)
  • content/docs/ui/actions.mdx (via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx (via @objectstack/spec)
  • content/docs/ui/dashboards.mdx (via @objectstack/spec)
  • content/docs/ui/forms.mdx (via @objectstack/spec)
  • content/docs/ui/index.mdx (via @objectstack/spec)
  • content/docs/ui/public-data-collection.mdx (via @objectstack/spec)
  • content/docs/ui/setup-app.mdx (via @objectstack/spec)
  • content/docs/ui/translations.mdx (via @objectstack/spec)
  • content/docs/ui/views.mdx (via @objectstack/spec)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@github-actions github-actions Bot added the size/m label Aug 2, 2026
@os-zhuang
os-zhuang marked this pull request as ready for review August 2, 2026 13:37
@os-zhuang
os-zhuang enabled auto-merge August 2, 2026 13:37
@os-zhuang
os-zhuang added this pull request to the merge queue Aug 2, 2026
Merged via the queue into main with commit 21676eb Aug 2, 2026
22 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-4641-session-dual-source branch August 2, 2026 13:58
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>
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/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

spec 双源清账 C4:Session / SessionSchema(./api ≠ ./identity)—— 2 条

2 participants