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

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) FAIL — invalid_type:tenantField
z.string().optional()(本 PR) {"enabled":true}

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

全仓扫描 tenantField(objectstack / objectui / cloud,排除 node_modules、dist、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:419、lint/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 / TursoDriver 均 extends SqlDriver,共用 Add metamodel interfaces for ObjectQL/ObjectUI contract #1 的 computeTenantField,无独立实现。
  • driver-mongodb 的 tenancy guard(mongodb-tenancy-guard.ts:96)只读 .enabled。
  • 运行时另有一个同名但不同的 tenancy(tenancy.posture / tenancy.defaultOrgId(),ADR-0105 的租户服务),与对象 schema 的 tenancy 块无关,不受影响。
  • objectui / cloud 两仓对 tenantField 与 TenancyConfig 均零代码引用(仅 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:scoping、ui/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.

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

2 participants