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-zhuang os-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排除式判定:

const wantOwner =
  ownership !== 'org' &&
  ownership !== 'none' &&
  !(schema as any).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 test 316 files / 8062 tests passed
pnpm --filter @objectstack/objectql test 119 files / 1923 tests passed
pnpm --filter @objectstack/rest test 49 files / 737 tests passed
pnpm --filter @objectstack/plugin-security test 34 files / 731 tests passed
pnpm --filter @objectstack/cli exec vitest run test/commands.test.ts 14 passed(枚举 pin,未受影响)
pnpm --filter @objectstack/spec typecheck OK
check:api-surface / check:authorable-surface / check:liveness / check:dual-source-exports 全绿
check:adr-anchors / check:nul-bytes OK(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

vercel Bot commented Aug 5, 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 5, 2026 11:50pm

Request Review

@github-actions

github-actions Bot commented Aug 5, 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.

#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 free to join this conversation on GitHub. Already have an account? Sign in to comment

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