Skip to content

ADR-0117 D1 声明面:ownership 枚举扩展 'business_unit'(须与列注入同 PR 或严格后置) #5678

Closed

Description

@os-zhuang

Blocked-by: #5677(引擎侧 wantOwner 翻正面清单 + 列注入)
Blocked-by: #5675(ADR-0117 scoped 接受)

ADR-0117 已按 Accepted (D1/D3 scoped) 落地名字面(#4611 / PR #5675),owning_business_unit_id 的规范名已登记。本单补上 D1 的作者可见声明面:ownership 枚举的第四档 'business_unit'。

⛔ 顺序约束(本单存在的唯一理由)

本单不得先于 #5677 落地,也不建议与之分开。 packages/objectql/src/registry.ts 的 wantOwner 是排除式判定,在它翻成正面清单之前,新增的 'business_unit' 会被照常注入 owner_id —— 与 ADR-0117 D1 表格相反,属 ADR-0049 禁止的「声明而不执行」。PR #5675 正是因此刻意没有扩展枚举;#4611 上有实测证据。

最稳妥的落地方式是与 #5677 合并为同一个 PR;若确要分开,本单必须严格后置,且合并前需确认 #5677 已在 main 上。

工作面

  1. packages/spec/src/data/object.zod.ts:1152 —— z.enum(['user', 'org', 'none']) 扩展为四值,并同步其 error 文案与 describe()(两处都逐字列出了合法值)。
  2. 同文件 ownership 的 JSDoc 与 systemFields 段落 —— 补上新档位与 owning_business_unit_id 的注入规则。
  3. 改写 packages/spec/src/data/object.test.ts 中的 #4611 pin。该 pin 目前钉住「business_unit 被拒绝且错误信息列出三个合法值」,并在注释里写明:它失败正是本单到来的预期信号,应改写而非删除——改成断言新值被接受、且第五个非法值仍被拒绝并列出四个合法值。
  4. CLI 消费半径(易漏,PR fix(lint): 收敛 validate-expressions / validate-security-posture 的 spec 不声明键 ?? 别名读法 (#5017) #5046 同型教训):packages/cli/test/commands.test.ts:83 断言 os explain object 的类型串 token 集合恰好等于 {'user','org','none'}(explain: os explain object documents ownership as "own" | "extend" — real values are user | org | none #3244 的目录精度钉子)。枚举扩展必须同步该断言,否则 CI 在 packages/cli 红 —— 一个只扫 packages/spec 的改动看不见它。
  5. 生成物走生成器:ownership 是 authorable 键,packages/spec/authorable-surface.base.json:3590 有其条目,新增枚举值可能合法移动 authorable-surface / spec-changes。逐行解释每处生成物 diff 与本改动的对应关系(check:authorable-surface 在 --check 模式下也会重写 authorable-surface.base.json —— 一次纯核验会改工作区,且任何无关 PR 都能因此静默推进删除门的锚点 #5358 纪律)。
  6. changeset:@objectstack/spec minor(新增枚举成员,作者可见)。

不在本单范围

D2 盖章策略与其默认值、D3 运行时校验、D4 守卫、D8 迁移与启用门、D6 契约变更 —— 均为 ADR-0117 的未决或后续项,其中 D2 默认值 / D5 / D8 粒度 / D4 权限位这四项尚未裁定,不得在本单顺手选定。

Activity

  1. added
    domain:specDomain lane — seat card indexed by label:pm:seat
    on Aug 5, 2026
  2. added
    pm:blockedBlocked by another issue/PR — body carries Blocked-by: #N
    on Aug 5, 2026
  3. os-project-manager commented on Aug 9, 2026

    @os-project-manager
    Collaborator

    Unlock sweep (maintainer-requested, 2026-08-09): UNBLOCKED — pm:blocked → pm:queue. Both blockers are resolved: #5675 (ADR-0117 scoped accept) merged, and #5677 (the wantOwner positive-list flip + owning_business_unit_id injection) closed completed 2026-08-06 — so the ordering constraint this card exists to respect (「严格后置于 #5677」) is now satisfied on main. The work plan stands as written, including the #4611 pin rewrite (assert the fourth value is accepted) and the packages/cli token-set assertion that a spec-only scan would miss. D2/D4/D5/D8 remain undecided — a dev hitting any of them STOPs per the card's own rule.


    Generated by Claude Code

  4. added
    pm:queueReady for the PM dispatch loop
    and removed
    pm:blockedBlocked by another issue/PR — body carries Blocked-by: #N
    on Aug 9, 2026
  5. self-assigned this
    on Aug 10, 2026
  6. os-zhuang commented on Aug 10, 2026

    @os-zhuang
    ContributorAuthor

    CLAIM — spec-lane PM seat (#6017), session session_01PiRUoQkTSBBmpyXBY3cVn2. Branch: claude/issue-5678-ownership-business-unit. Dispatching a cloud dev session under the maintainer's 2026-08-10 acceleration instructions.

    Basis: the 2026-08-09 unlock sweep (comment 5230595112) — both blockers resolved (#5675 merged, #5677 closed completed), the 「严格后置于 #5677」 ordering is satisfied on main, pm:queue restored, and "the work plan stands as written". Scope is the card's own D1 declaration-surface plan, including the #4611 pin rewrite (assert the fourth value is accepted) and the packages/cli token-set assertion a spec-only scan would miss. Hard guard carried into the brief verbatim: D2/D4/D5/D8 remain undecided — the dev STOPs and reports on first contact with any of them, per the card's own rule.


    Generated by Claude Code

  7. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Aug 10, 2026
  8. os-zhuang commented on Aug 10, 2026

    @os-zhuang
    ContributorAuthor

    OS-DEV-REPORT

    {
      "issue": 5678,
      "outcome": "completed",
      "branch": "claude/issue-5678-ownership-business-unit",
      "pr_number": 7260,
      "premise_check": {
        "base": "origin/main @ f40c5b4",
        "enum_lacked_business_unit": true,
        "enum_site_drift": "card said packages/spec/src/data/object.zod.ts:1152; actual site :1393 — located via the ADR-0117 D1 / #4611 pins, not the line number",
        "5675_on_main": true,
        "5675_evidence": "SystemFieldName.OWNING_BUSINESS_UNIT_ID registered; ADR-0117 on disk as 'Accepted (D1/D3 scoped)'",
        "5677_on_main": true,
        "5677_evidence": "packages/objectql/src/registry.ts:377-394 consumes plan.owner from a POSITIVE list; resolveInjectedSystemColumns (packages/spec/src/data/injected-system-columns.ts:171-172) implements owner = (undefined|'user') and owningBusinessUnit = owner || 'business_unit'",
        "ordering_premise_holds": true,
        "4611_pin_still_asserted_rejection": true,
        "4611_pin_location_pre_change": "packages/spec/src/data/object.test.ts:1020",
        "value_already_landed": false
      },
      "d_guard_contacts": [],
      "d_guard_note": "No contact with D2/D4/D5/D8. No owningBusinessUnit.policy key, no allowTransfer/permission-bit reuse, no legal-entity resolver or materialized column, no enablement gate. The column stays provisioned-but-inert: declaring the tier yields the column plus the withheld owner_id and nothing writes a value. Every changed line is the enum, prose describing it, or a test asserting the derivation #5677 already landed.",
      "changes": {
        "declaration_surface": [
          "packages/spec/src/data/object.zod.ts — enum -> ['user','business_unit','org','none']; error string and describe() (both enumerate legal values verbatim); ownership JSDoc; systemFields JSDoc owner_id/owning_business_unit_id bullets; systemFields.owner guidance (a THIRD value now skips owner_id, for a different reason than the other two)"
        ],
        "pin_rewrite": [
          "packages/spec/src/data/object.test.ts — #4611 pin REWRITTEN (not deleted) into two: (1) business_unit ACCEPTED and resolving to D1's row asserted against resolveInjectedSystemColumns (the authority, not prose); (2) a FIFTH value still rejected with all four legal values enumerated. Also widened the existing acceptance loop to four values."
        ],
        "cli_consumption_radius": [
          "packages/cli/src/commands/explain.ts — hand-maintained catalog: type string + description",
          "packages/cli/test/commands.test.ts — exact token-set assertion -> {user,business_unit,org,none}, with a comment recording WHY this pin exists (the catalog does not derive from the enum)"
        ],
        "co_updates_named_by_the_pin": [
          "packages/spec/src/system/constants/system-names.ts — OWNING_BUSINESS_UNIT_ID 'not authorable yet' paragraph; OWNER_ID one-line withheld-under list",
          "packages/spec/src/data/injected-system-columns.ts — the ahead-of-acceptance note; string-widening rationale restated on its real reason (takes `unknown`, incl. pre-parse input)"
        ],
        "other_co_updates": [
          "packages/objectql/src/registry.ts — injection-site comments; positive-list argument restated so it covers a FIFTH tier rather than reading as 'safe now the enum is complete'",
          "packages/objectql/src/registry.test.ts — comment + dropped the `as any` casts the old comment predicted would become droppable (ServiceObject / satisfies ServiceObject); no assertion changed",
          "packages/spec/src/data/object-strictness-batch20.test.ts — its loop premise was literally 'across the whole AUTHORABLE enum the anchors move together ... deliberately unauthorable today — #5678'; now three tiers move together and business_unit SPLITS them",
          "packages/spec/src/system/constants/system-names.test.ts — the 'fact 2 of the pair' comment asserting the split state",
          "packages/metadata-protocol/src/protocol.injected-system-columns.test.ts — added the business_unit row to the #6562 served-columns table",
          "content/docs/data-modeling/objects.mdx, skills/objectstack-data/SKILL.md, packages/spec/liveness/object.json — hand-written prose enumerating the three values"
        ],
        "generated": [
          "content/docs/references/data/object.mdx — gen:docs, exactly ONE line, from describe()"
        ],
        "changeset": ".changeset/ownership-business-unit-enum-member.md — @objectstack/spec minor + @objectstack/cli patch (no major)"
      },
      "gates": {
        "spec_build_first": "PASS (run before any regen — stale-dist trap #7122)",
        "check_generated": "PASS 11/11 (check:docs was the only stale artifact)",
        "spec_tests": "PASS 9398 tests / 360 files",
        "objectql_tests": "PASS 2906 tests / 167 files",
        "cli_tests": "PASS 1139 tests / 105 files",
        "metadata_protocol_tests": "PASS 862 tests / 67 files",
        "typecheck": "PASS 57/57 (spec + objectql + cli)",
        "changeset_gates": "PASS (check:empty-changeset, check:changeset-no-major, check:adr-0087-registration)",
        "reverse_verification": {
          "method": "direction predicted BEFORE running; per-file revert to origin/main, never git stash",
          "A_flipped_pin_red_pre_change": "CONFIRMED — reverting only object.zod.ts: 2 failed (create throws on business_unit; rejection message enumerates only [user,org,none])",
          "A_green_post_change": "CONFIRMED — 2 passed",
          "B_cli_pin_red_on_cli_revert": "CONFIRMED — reverting only explain.ts: 1 failed, Set{none,org,user} != Set{business_unit,none,org,user}. This MEASURES the card's 'a spec-only scan would miss it' claim.",
          "B_green_post_change": "CONFIRMED — 1 passed"
        }
      },
      "deviations": [],
      "docs_adr_touched": false,
      "releases_dir_touched": false,
      "special_inspection_notes": [
        "No export-surface change -> dual snapshot scoped but NOT owed: check:api-surface and check:export-origins green unchanged (the enum gains a member, not an export).",
        "authorable-surface.base.json did NOT move, and that is correct — it is a flat list of KEY PATHS (data/Object:ownership at :3559); a new enum member adds no key. check:authorable-surface green with no diff. (The card flagged this as only POSSIBLY moving.)",
        "packages/spec/json-schema/** regenerated with the new description but is gitignored (.gitignore:61), so its absence from the diff is correct, not an omission.",
        "No i18n regen owed — checked as instructed: no platform-objects select carries the record-ownership vocabulary (every `ownership` hit there is transfer_ownership / provider-ownership prose).",
        "Repo-wide sweep for consumers of the enum VALUE found exactly two, both already correct: resolveInjectedSystemColumns (handles the tier since #5677) and packages/cli/src/commands/info.ts:79 (display-only, prints the new value as-is).",
        "OUT-OF-SCOPE FINDING, not fixed and not yet filed: packages/spec/config-schema.json declares object `ownership` as enum [\"own\",\"shared\"] — matching NEITHER the record-ownership enum nor the own/extend contribution kind. Pre-existing on main, unrelated to this change, and it is the exact #3244 confusion class in a hand-written artifact. Recommend PM file/dedupe a `finding` issue rather than riding it on a D1 PR."
      ]
    }

    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

domain:specDomain lane — seat card indexed by label:pm:seatpm:dispatchedDispatched to a dev agent by /pm-dispatch

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions