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/specmajor,含 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-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:liveness✅ 全部 governed-type 属性已分类
check: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 exempt
check:exported-any1884 types + 1638 schemas,无 any
check:skill-examples202 prose examples type-check
全仓 pnpm typecheck122 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

vercelBot commented Aug 2, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
objectstackIgnoredIgnoredAug 2, 2026 1:36pm

Request Review

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

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.

@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 queueAug 2, 2026
Merged via the queue into main with commit 21676ebAug 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.
Fixesobjectstack-ai#4650
Claude-Session: https://claude.ai/code/session_0176qgxgCXTJCUv4YFLtusP9
Co-authored-by: Claude <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

2 participants

@os-zhuang@claude