Skip to content

feat(spec): ADR-0117 scoped 接受 —— owning_business_unit_id 规范名进入登记处 (#4611) - #5675

Merged
os-zhuang merged 2 commits into
mainfrom
claude/issue-4611-ownership-business-unit
Aug 6, 2026
Merged

feat(spec): ADR-0117 scoped 接受 —— owning_business_unit_id 规范名进入登记处 (#4611)#5675
os-zhuang merged 2 commits into
mainfrom
claude/issue-4611-ownership-business-unit

Conversation

@os-zhuang

@os-zhuangos-zhuang commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Fixes#4611

ADR-0105 D13 把 promotion 的第三步写成「按子树的 scoping field 回填 organization_id」,却没有任何元数据声明「哪个字段承载 BU 归属」。维护者 2026-08-05 裁定选 1 —— 加速 ADR-0117;PM 复审进一步裁定本轮取 Q1=D / Q2=(b)。本 PR 落地协议决定的名字面,不含运行时注入。

⚠️ 合并语义:接受的是 scoped 范围,不是全文

维护者合并本 PR 即为接受 ADR-0117 的 Accepted (D1/D3 scoped) 状态,即:

  • D1 —— ownership 新增 business_unit 一档、owning_business_unit_id 记录戳的命名与语义;
  • D3 —— 不变量 record.organization_id == sys_business_unit(owning_business_unit_id).organization_id

以下四项仍为 Proposed,不随本次合并静默通过,需各自单独评审:

遗留决策内容
D5法人归属做成解析规则而非物化列(ADR 正文自标「⚠️ 偏离父 ADR,需评审」)
D2 默认值pinned 是否适合作默认盖章策略(平台既有对象以 CRM 形态居多,直觉相反)
D8 粒度启用门按对象启用还是按部署一次性启用
D4 权限位复用 allowTransfer 还是新增 allowChangeOwningUnit

另:D6 是破坏性契约变更(resolveOwnerIdsresolveScope,packages/spec/src/contracts/sharing-service.ts:415),不在本 PR 范围内,也不因本次 scoped 接受而获批。

状态行的限定式写法对齐仓内既有先例(ADR-0022 / ADR-0025 / ADR-0038 都在 Status 行标注了范围)。

本轮落地了什么

  1. ADR-0117 定稿Accepted (D1/D3 scoped),新增「落地状态」表(协议已接受 / 执行待实现),未决问题列表保留并标明已裁定的那一条。
  2. SystemFieldName.OWNING_BUSINESS_UNIT_ID(owning_business_unit_id)—— 明确标注 open-core 暂不注入。该表是名字登记处而非注入集合,tenant_id / user_id / deleted_at 是既有三条同类先例。早登记是为了阻断消费方各造一个 business_unit_id / bu_id / dept_id,即 cloud#982 用 tenant_id/org_id/space 付过学费的漂移形态。
  3. PUBLIC_FORM_SERVER_MANAGED_FIELDS 收该名 —— 它是与 owner_id / organization_id 同类的归属锚点;一旦盖章,匿名面上被伪造的值会把记录推到别的部门墙后。在列存在之前拒收是零成本且 fail-closed 的,等列上线后再补名单,中间那个版本就是一个带着发布号的洞。
  4. scripts/adr-anchors.json 锚点 —— 按 AGENTS.md [WIP] Add Chinese version of the documentation #13,一个「保留但不注入」的条目单独读会像死重量(而本仓正在主动清理死面),锚点把它站在哪个决策上写清楚。

刻意不动 ownership 枚举 —— 附实测

packages/objectql/src/registry.ts:359-363wantOwner排除式判定:

constwantOwner=ownership!=='org'&&ownership!=='none'&&!(schemaasany).managedBy&&!schema.name.startsWith('sys_');

只排除 org / none。因此只加枚举值而不动 registry,ownership: 'business_unit'照常注入 owner_id —— 与 ADR-0117 D1 表格(该档 owner_id ❌、owning_business_unit_id ✅)恰好相反。一次性探针实测(跑完即删,未入库),断言按 D1 写:

AssertionError: ADR-0117 D1: business_unit tier must NOT get owner_id:
expected { type: 'lookup', …(6) } to be undefined
+ Received: { type: "lookup", reference: "sys_user", label: "Owner",
system: true, readonly: false, … }
Test Files 1 failed | 119 passed (120)

即:那不是「惰性新增」,而是把今天的响亮 Zod 拒绝换成明天的静默错误 —— ADR-0049「spec 不得声明运行时不执行的东西」所禁止的形态,也是「让 AI 写的元数据难以出错」这条轴上最差的一档。故本轮不加枚举值,并加 pin 钉住当前的拒绝(且断言错误信息仍列出 user / org / none 三个合法值),注释指向 ADR-0117 与后续实施链,防止后人「顺手补全」。

pin 的方向说明(不套模板):business_unit 在本 PR 之前就已被拒绝,所以这条 pin 不改变行为,它固化的是一个既有的正确拒绝。它不是「先红后绿」型的回归证明 —— 真正的先证红在下一节。

先证红

闸门本身证红,证明这次登记是被门禁看守的、而非装饰性的。预测方向:把名字加进拒收名单、但在 conformance 测试里归类,应当以「stray entry — classify it」失败。

FAIL src/system-managed-fields-conformance.test.ts : the denylist is exactly
(actively-injected ∪ documented-reserved) — no stray or missing entries
AssertionError: 'owning_business_unit_id' is on PUBLIC_FORM_SERVER_MANAGED_FIELDS
but is neither injected by open-core nor listed as defense-in-depth reserved
in this test — classify it: expected false to be true
Test Files 1 failed (1) | Tests 1 failed | 2 passed (3)

归类到 documented-reserved 后转绿。

验证

命令结果
pnpm --filter @objectstack/spec test316 files / 8062 tests passed
pnpm --filter @objectstack/objectql test119 files / 1923 tests passed
pnpm --filter @objectstack/rest test49 files / 737 tests passed
pnpm --filter @objectstack/plugin-security test34 files / 731 tests passed
pnpm --filter @objectstack/cli exec vitest run test/commands.test.ts14 passed(枚举 pin,未受影响)
pnpm --filter @objectstack/spec typecheckOK
check:api-surface / check:authorable-surface / check:liveness / check:dual-source-exports全绿
check:adr-anchors / check:nul-bytesOK(30 anchored files;5582 files 无控制字节)

消费半径已扫:PUBLIC_FORM_SERVER_MANAGED_FIELDS 的消费方在 packages/rest(表单路由白名单)与 packages/plugins/plugin-security(grant strip)—— 都不在被改动的包内,已单独跑绿。ownership 枚举的消费方 packages/cli/test/commands.test.ts:83 同理已验(本轮枚举未动,故仍绿)。

生成物

零生成物变动。check:api-surface 报 "public API surface + factory signatures unchanged" —— SystemFieldName 本就是导出符号,新增成员值不进签名基线;authorable-surface.base.json 未动,因为本 PR 没有碰 object.zod.ts(authorable 面只在枚举真正落地那一 PR 才会合法移动)。spec-changes.jsonownership 条目,不变。

changeset:判为 minor(非 patch),理由

PM 建议 patch,但按仓内既有判例,@objectstack/spec新增导出成员走 minor(见 .changeset/action-alias-conflict-warning.md 等)。本次除新增 SystemFieldName 成员外,还收紧了匿名公开表单面的行为(该名自此不再接受客户端提交值),两者都是用户可见的。patch 会让下游在补丁区间内静默收到一次安全面收紧,方向不对,故取 minor 并在 changeset 内写明行为变更提示。若维护者仍偏好 patch,改动仅一行。

后续实施链(本 PR 不实现)

已立两单,均 Blocked-by 本 PR:

D2 盖章策略、D3 不变量的运行时校验、D4 守卫、D8 迁移、D6 契约变更、D13 工具(cloud#874)均不在此链本轮范围;其中四项尚未裁定,实现时若无法回避应回报 needs_decision。

…4611)
ADR-0105 D13 的 promotion 工具要求「按子树的 scoping field 回填 organization_id」,
但没有任何元数据声明「哪个字段承载 BU 归属」。维护者裁定加速 ADR-0117 补上这一层。
本轮落地协议决定的名字面,不含运行时注入。
- ADR-0117 定稿为 `Accepted (D1/D3 scoped)`;D5/D2 默认值/D8/D4 仍为 Proposed
- 新增 SystemFieldName.OWNING_BUSINESS_UNIT_ID,标注 open-core 暂不注入
- PUBLIC_FORM_SERVER_MANAGED_FIELDS 收该名(fail-closed,先于列存在)
- 刻意不动 ownership 枚举:registry 的 wantOwner 是排除式判定,加值会让
business_unit 档照常注入 owner_id,与 D1 相反(ADR-0049 禁止的形态);
加 pin 钉住当前的响亮拒绝,防止后人顺手补全
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018fxLGQdatPbBUvCgiVxg6D
@vercel

vercelBot commented Aug 5, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
objectstackIgnoredIgnoredAug 5, 2026 11:50pm

Request Review

@github-actions

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.

#5677(engine-core:wantOwner 翻正面清单 + 列注入)、#5678(spec:枚举扩展),
并把 D2/D3/D4/D8 单列一行,标明其中四项尚未裁定。
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018fxLGQdatPbBUvCgiVxg6D
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationprotocol:dataprotocol:systemsize/mteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ADR-0105 D13's "scoping field" has no metadata home — the 0105↔0117 linkage gap

2 participants

@os-zhuang@claude