Skip to content

fix(spec)!: tenancy.tenantField 不再默认 'tenant_id' —— 未声明如实为 undefined,driver 回落为唯一真相 (#5315) - #5766

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-5315-tenantfield-default-removal
Aug 6, 2026
Merged

fix(spec)!: tenancy.tenantField 不再默认 'tenant_id' —— 未声明如实为 undefined,driver 回落为唯一真相 (#5315)#5766
os-zhuang merged 1 commit into
mainfrom
claude/issue-5315-tenantfield-default-removal

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Fixes #5315

#5315 评论区 2026-08-05 22:31Z 排程注记预记的方向 2(否决窗口未被行使):去掉 TenancyConfigSchema.tenantField 的默认值,让「未声明」如实表达为 undefined,driver-sql computeTenantField 的既有回落作为唯一真相。依据 ADR-0078(不留没人读的声明)+ ADR-0120 §Terminology(可授权词汇定死 organization,tenant / org 拒收)。

一处实现细节:不是单删 .default()

单删 .default('tenant_id') 是错的 —— Zod 里 z.string()必填,单删会让 tenancy: { enabled: true } 直接 parse 失败,那是个真正的破坏性变更。裁定要的语义(未声明 = undefined)对应的是 .optional()。三种写法实测:

写法 parse({ enabled: true })
z.string().default('tenant_id')(改前) {"enabled":true,"tenantField":"tenant_id"}
z.string()(单删 default) FAILinvalid_type:tenantField
z.string().optional()(本 PR) {"enabled":true}

前提复核:消费方清单 + 各自 undefined 处理实测

全仓扫描 tenantField(objectstack / objectui / cloud,排除 node_modulesdist、CHANGELOG)。绝大多数命中是 driver-sql 内部 string | null局部参数/变量(schema-drift.ts 等),与 spec 声明面无关。真正读取 spec 声明值的只有两处:

# 消费方 位置 读法 undefined 处理 会看到物化默认值吗
1 computeTenantField driver-sql/src/sql-driver.ts:3531 tenancyDecl?.tenantField truthy 守卫 → 跳过声明分支,回落 organization_id 会(parse 后元数据)
2 authoredTenantColumn lint/src/data-model-rules.ts:88 obj?.tenancy?.tenantField typeof === 'string' && trim(),否则 'organization_id' 不会 —— 读的是作者原文,未经 parse
3 tenantPolicy() spec/src/security/rls.zod.ts:601 函数默认参数 = 'organization_id' 与 schema 无关 不适用
4 isTenancyDisabled ×2 object.zod.ts:419lint/validate-org-axis-red-lines.ts:187 只读 .enabled 不适用 不适用
5 TenancyConfig 类型 object.zod.ts:2095 z.infer 导出 三仓零消费方 不适用

第 2 条值得单独点名:lint 的注释原文就是 "The organization column, as an AUTHOR would have spelled it",其测试喂的是 tenancy: { tenantField: 'tenant_ref' }(没有 enabled,这个输入根本过不了 TenancyConfigSchema.parse)—— 证明该路径读的是未经 parse 的作者原文,物化默认值从来不在它的视野里。

补充核实:

  • SqliteWasmDriver / TursoDriverextends SqlDriver,共用 Add metamodel interfaces for ObjectQL/ObjectUI contract #1computeTenantField,无独立实现。
  • driver-mongodb 的 tenancy guard(mongodb-tenancy-guard.ts:96)只读 .enabled
  • 运行时另有一个同名但不同tenancy(tenancy.posture / tenancy.defaultOrgId(),ADR-0105 的租户服务),与对象 schema 的 tenancy 块无关,不受影响。
  • objectui / cloud 两仓对 tenantFieldTenancyConfig 均零代码引用(仅 objectui 一处 CHANGELOG 历史文本)。

结论:不存在依赖「parse 后 tenantField 恒有值」的消费方(运行时或类型),硬前置 1、2 通过,无 #4666 家族停手条件。

类型面:TenancyConfig['tenantField']string 变为 string | undefined。因零消费方,无一处需要改逻辑,未使用任何 ! 或 cast

顺带一提,全仓唯一声明了 tenancy 块的对象是 sys_sso_provider({ enabled: false }),走 isTenancyDisabled 直接返回 null,压根不读 tenantField。物化的 'tenant_id' 在本仓真的是零效果。

行为等价性:先证红

先按错误预期写断言,在未改动的 origin/main 上跑红:

FAIL  src/sql-driver-tenant-scope.test.ts > #5315 undeclared tenantField resolves to organization_id (parse → driver)
AssertionError: expected 'organization_id' to be 'tenant_id' // Object.is equality
Expected: "tenant_id"
Received: "organization_id"

 Test Files  1 failed | 62 passed | 4 skipped (67)
      Tests  1 failed | 860 passed | 46 skipped (907)

Received: "organization_id" 一行同时给出两个事实:(a) 改动前的基线实测值就是 organization_id(硬前置 3 要的那条);(b) 物化的 'tenant_id' 从未抵达 driver —— 它确实是死声明。翻正断言后前后同绿。

关于验证方向,按实情报告:这条 pin 按设计两侧皆绿,不是「改前绿改后红」那种反向验证。它证明的是等价性,这正是本单要的东西;它的长期职责是防止有人再塞回一个指向不存在列的默认值。真正的红证据是上面那次故意写错预期的运行。

测试与门禁

合并 origin/main(前移 9 个 commit,无一触及本 PR 文件面)后整体复验:

packages/spec        Test Files  317 passed (317)      Tests  8106 passed (8106)
packages/lint        Test Files   60 passed (60)       Tests  1383 passed | 4 skipped (1387)
driver-sql           Test Files   63 passed | 4 skipped (67)   Tests  861 passed | 46 skipped (907)

typecheck:spec / driver-sql / lint 三包全绿。(lint 首轮报 Cannot find module '@objectstack/formula' 等,是未构建依赖的老陷阱,补 pnpm --filter '@objectstack/lint^...' build 后清零,与本改动无关。)

生成物走 os-regen 四步:check:generated 只报 content/docs/references/** 一项 stale → gen:docs 重生成,tenantField✅ required 变为 optional 并带上新 describe(顺带修正了该页原有的不一致:上方内联类型早已写 tenantField?: string,下方属性表却标 required)。合并后复跑 ✓ All 10 generated artifacts are up to date.

authorable-surface.base.json 在每次 spec build 时被重锚,带进 api/Discovery:scopingui/ActionSession:*他人在飞的无关键,已按 #5358 纪律 git checkout 还原,不进本 PR(还原后 check:authorable-surface 仍 ✓)。check-nul-bytes OK,并对本 PR 全部改动文件做了超出该门禁扫描范围的控制字符自查,干净。

范围外发现

本 PR 内 content/docs/data-modeling/objects.mdx直接记录这个键的授权指引,其示例正是 issue 点名的「AI 会照抄」的面,故随 schema 一并更正为「省略即回落 organization_id」,并把自定义列的例子换成 workspace_id


Generated by Claude Code

… undefined (#5315)

`TenancyConfigSchema.tenantField` defaulted to 'tenant_id', so parsing
`tenancy: { enabled: true }` materialized a tenant column the platform does
not use. The real column is `organization_id` — kernel-injected, assumed by
`tenantPolicy()` in security/rls.zod.ts and by the RLS predicates.

The default had no consumer it could reach. driver-sql's `computeTenantField`
honours a declared tenantField only when the object actually has that field,
so the materialized 'tenant_id' merely sent it looking for a column that did
not exist before falling back to `organization_id` anyway — the right answer
by accident. A declaration nobody reads is what ADR-0078 prohibits, spelling
a word ADR-0120 refuses for the authorable vocabulary.

`.optional()`, not a bare removal: `z.string()` alone is REQUIRED, which
would reject `tenancy: { enabled: true }` outright. `.optional()` is what
expresses "undeclared stays undefined".

The effective tenant column does not move, pinned end to end (parse through
ObjectSchema, then resolve through the driver) by the new
`#5315 undeclared tenantField resolves to organization_id` case.

Consumers verified against undefined across objectstack/objectui/cloud:
`computeTenantField` truthiness-guards it; lint's `authoredTenantColumn`
falls back to 'organization_id' and reads unparsed author text anyway; the
`TenancyConfig` z.infer export has zero consumers, so widening it to
`string | undefined` breaks nothing.

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

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

Request Review

@github-actions github-actions Bot added the size/m label Aug 6, 2026
@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

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

109 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 @objectstack/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/tenancy-modes.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/http-protocol.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/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/apps.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 documentation Improvements or additions to documentation protocol:data tests tooling labels Aug 6, 2026
@os-zhuang
os-zhuang marked this pull request as ready for review August 6, 2026 04:59
@os-zhuang
os-zhuang added this pull request to the merge queue Aug 6, 2026
Merged via the queue into main with commit fc5f536 Aug 6, 2026
25 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-5315-tenantfield-default-removal branch August 6, 2026 05:12
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 protocol:data size/m tests tooling

Projects

None yet

2 participants