⏱️ 本卡所有读数取自同一动作:2026-09-17T17:04Z。逐条本席第一手实测,file:line 全部当场打印过。
一句话
@objectstack/spec/identity 有 三个已发布 schema 把 updatedAt 声明为必填,而 @objectstack/client 对着真实服务器、真实 SQL driver 量过的线上载荷一条都不带它;OrganizationSchema.metadata 同理声明成对象,而四条读路由上它是存库的 JSON 文本、未设时为 null。⭐ 关键在于:client 侧已经把这三处逐条写进注释并声明「not relayed」,spec 侧一个字没动,而两边都在已发布面上。
实测
⏱️ 本块全部读数取于 2026-09-17T17:04Z
spec 侧声明(必填 `updatedAt`)
packages/spec/src/identity/organization.zod.ts:57 Organization.updatedAt z.string().datetime() ← 必填
packages/spec/src/identity/organization.zod.ts:105 Member.updatedAt z.string().datetime() ← 必填
packages/spec/src/identity/organization.zod.ts:183 Invitation.updatedAt z.string().datetime() ← 必填
packages/spec/src/identity/identity.zod.ts:55 User.updatedAt z.string().datetime() ← 必填
packages/spec/src/identity/identity.zod.ts:142 Account.updatedAt z.string().datetime() ← 必填
client 侧实测线形(packages/client/src/index.ts)
:1216 OrganizationWire —— 无 updatedAt;metadata?: string | null
:1251 OrganizationMemberWire —— 无 updatedAt
:1342 OrganizationInvitationWire—— 无 updatedAt
:1265 OrganizationMemberUserWire—— 四列投影,无 updatedAt
⭐ client 自己写下的三处「not relayed」,逐字:
:1213 「`@objectstack/spec/identity`'s `Organization` is NOT relayed: it declares
`updatedAt` required and `metadata` as an object, and neither is what this wire carries.」
:1249 「`Member` is not relayed: it declares `updatedAt` required and the wire never carries it.」
:1335 「No `updatedAt`, so `@objectstack/spec/identity`'s `Invitation` is not relayed」
⭐ 而它的出处不是推断,是实测 —— :1196 逐字:
「`sys_organization`'s `updated_at` and every other ObjectStack column stay off
the wire — **measured against a real server on a real SQL driver**」
metadata 的第二半(⛔ 不只是 null)
spec organization.zod.ts:47 metadata: z.record(z.string(), z.unknown()).optional() ← 对象,且拒 null
client :1205 逐字「**`metadata` arrives as the stored JSON TEXT, not an object**, on every
route that reads the row back (`setActive`, `get`, `delete`, `list`)」
client return-type-precision.test.ts:1050 把它钉死:
delete('o').metadata → string | null | undefined
⇒ 四条读路由上,spec 的声明对 **类型** 和 **null** 两头都不成立;
只有 create/update 两条写路由回声是解码过的对象(:1235 OrganizationEchoWire)。
⭐ 为什么这不是「反正没人用」
⏱️ 读于 2026-09-17T17:04Z
已发布面(packages/spec/api-surface/identity.json)
:32 "InvitationSchema (const)"
:40 "MemberSchema (const)"
:45 "OrganizationSchema (const)"
⇒ 三个都在**已声明公开面**上。
仓内非测试消费者(排除 api-surface / 自身文件 / migrations 登记串)
OrganizationSchema → 0
MemberSchema → 0
InvitationSchema → 0
⇒ 两头合起来才是本卡的要害:受众全在仓外,而仓内没有任何消费者会把它撞红。所以它不会自己暴露,只会一直错着。
与在飞卡 #18509 的关系(⭐ 本卡不抢它的活)
#18509 / PR #18718 正在把同一对文件里的 UserSchema.image 与 OrganizationSchema.logo 由 .optional() 改成 .nullish(),理由正是「线上服务 null」。本卡指出的是:同一张 schema 上,同一句 client 注释里并列点名的 metadata 与 updatedAt 没有一起被看。
⚠️ ⛔ 本席不主张把它们塞进 #18509 —— 那会把一张已进复核的卡扩面。本卡单独立,由维护者定先后。
真正要裁的是一个二选一(⛔ 非裁定,这是维护者的字)
这几张 schema 到底描述什么,仓里目前没有一句话说。两条路互斥:
- A —— 它们就是 better-auth 线上实体的契约:那么
updatedAt 必填在 5 处是错的,metadata 的对象声明在 4 条路由上是错的,该按 client 的实测逐条对齐(updatedAt 转可选、metadata 认 string | null)。
- B —— 它们是 ObjectStack 自己的领域模型,不承诺描述线上载荷:那么 client 那三处「not relayed」就是正确且最终的,本卡不改代码,改的是在 schema 上写一句范围声明,免得下一个人再照着它读线。
⭐ 本席倾向 B 更可能是真相(client 三处注释都是主动划清,不是抱怨),但 A 是 #18509 正在走的方向 —— .nullish() 那一笔恰恰是在按「它描述线上载荷」办事。⚠️ 两条路现在同时开着,这才是本卡最该被看见的一点:不裁定,就会一边按 A 打补丁、一边按 B 写注释。
查重(MCP search_issues,含 closed)
三轮检索(OrganizationSchema metadata null / organization create updatedAt / auth/organization/create)无孪生。最近邻是 #18509 本身(open,同文件同类,但面是 image/logo),已在上一节划清。
⚠️ 本席第一次用 curl 打 /search/issues 读到三个 None —— 那是 403(本会话的 token 不许走 search 端点),⛔ 不是「没查到」。改用 MCP 工具重查才是上面这个读数。记在这里是因为:非 200 是「未测量」,不是判据。
出处
domain:spec seat 2(座位贴 #18549)复核 PR #18718 / 卡 #17235-族 时,dev 在 out_of_scope_findings 里交出来的。
⭐ dev 交的是两条,本席合成一条 —— dev 的两条是「OrganizationSchema.metadata 服务端present-and-null」与「/auth/organization/create 漏了必填 updatedAt」。本席重测后改了框:
updatedAt 不是 create 一条路由的事,是三张 schema、五个字段、所有路由都不带 ⇒ dev 的说法说小了;
metadata 不只是 null,四条读路由上它是 JSON 文本而非对象 ⇒ dev 的说法漏了更重的那半;
- 两条同根(同一对 schema 对不上同一份实测线形),分开立会把根藏掉。
Generated by Claude Code
⏱️ 本卡所有读数取自同一动作:2026-09-17T17:04Z。逐条本席第一手实测,file:line 全部当场打印过。
一句话
@objectstack/spec/identity有 三个已发布 schema 把updatedAt声明为必填,而@objectstack/client对着真实服务器、真实 SQL driver 量过的线上载荷一条都不带它;OrganizationSchema.metadata同理声明成对象,而四条读路由上它是存库的 JSON 文本、未设时为null。⭐ 关键在于:client 侧已经把这三处逐条写进注释并声明「not relayed」,spec 侧一个字没动,而两边都在已发布面上。实测
⭐ 为什么这不是「反正没人用」
⇒ 两头合起来才是本卡的要害:受众全在仓外,而仓内没有任何消费者会把它撞红。所以它不会自己暴露,只会一直错着。
与在飞卡 #18509 的关系(⭐ 本卡不抢它的活)
#18509 / PR #18718 正在把同一对文件里的
UserSchema.image与OrganizationSchema.logo由.optional()改成.nullish(),理由正是「线上服务 null」。本卡指出的是:同一张 schema 上,同一句 client 注释里并列点名的metadata与updatedAt没有一起被看。真正要裁的是一个二选一(⛔ 非裁定,这是维护者的字)
这几张 schema 到底描述什么,仓里目前没有一句话说。两条路互斥:
updatedAt必填在 5 处是错的,metadata的对象声明在 4 条路由上是错的,该按 client 的实测逐条对齐(updatedAt转可选、metadata认string | null)。⭐ 本席倾向 B 更可能是真相(client 三处注释都是主动划清,不是抱怨),但 A 是 #18509 正在走的方向 ——⚠️ 两条路现在同时开着,这才是本卡最该被看见的一点:不裁定,就会一边按 A 打补丁、一边按 B 写注释。
.nullish()那一笔恰恰是在按「它描述线上载荷」办事。查重(MCP
search_issues,含 closed)三轮检索(
OrganizationSchema metadata null/organization create updatedAt/auth/organization/create)无孪生。最近邻是 #18509 本身(open,同文件同类,但面是image/logo),已在上一节划清。curl打/search/issues读到三个None—— 那是 403(本会话的 token 不许走 search 端点),⛔ 不是「没查到」。改用 MCP 工具重查才是上面这个读数。记在这里是因为:非 200 是「未测量」,不是判据。出处
domain:specseat 2(座位贴 #18549)复核 PR #18718 / 卡 #17235-族 时,dev 在out_of_scope_findings里交出来的。⭐ dev 交的是两条,本席合成一条 —— dev 的两条是「
OrganizationSchema.metadata服务端present-and-null」与「/auth/organization/create漏了必填updatedAt」。本席重测后改了框:updatedAt不是 create 一条路由的事,是三张 schema、五个字段、所有路由都不带 ⇒ dev 的说法说小了;metadata不只是 null,四条读路由上它是 JSON 文本而非对象 ⇒ dev 的说法漏了更重的那半;Generated by Claude Code