Skip to content

feat(spec): extend the ownership enum with ADR-0117 D1's business_unit tier (#5678) - #7260

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-5678-ownership-business-unit
Aug 10, 2026
Merged

feat(spec): extend the ownership enum with ADR-0117 D1's business_unit tier (#5678)#7260
os-zhuang merged 1 commit into
mainfrom
claude/issue-5678-ownership-business-unit

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

What

ObjectSchema.ownership becomes 'user' | 'business_unit' | 'org' | 'none'. The fourth tier means owned by an org UNIT, not by a person: owning_business_unit_id is injected, owner_id deliberately is not.

ObjectSchema.create({
  name: 'inventory_item',
  ownership: 'business_unit',   // owner_id ❌ · owning_business_unit_id ✅
  fields: { sku: { type: 'text' } },
});

This is the D1 declaration surface only. Per ADR-0117 D1's table:

ownership owner_id owning_business_unit_id
'user' / omitted
'business_unit' (new)
'org' / 'none'

Authorization for this scope: the 2026-08-09 unlock sweep — both blockers resolved, the 「严格后置于 #5677」 ordering satisfied on main, and "the work plan stands as written".

Premise re-measurement (on fresh origin/main @ f40c5b4)

Every premise the card rests on was re-measured before the first edit, not assumed:

Premise Method Result
The enum still lacks 'business_unit' packages/spec/src/data/object.zod.ts (the site had drifted from the card's :1152 to :1393) z.enum(['user', 'org', 'none'])
#5677 present on main (the ordering premise) packages/objectql/src/registry.ts:377-394 reads plan.owner off a POSITIVE list; resolveInjectedSystemColumns (packages/spec/src/data/injected-system-columns.ts:171-172) implements owner = undefined || 'user' and owningBusinessUnit = owner || 'business_unit' ✅ D1's row implemented engine-side
#5675 present on main SystemFieldName.OWNING_BUSINESS_UNIT_ID registered; ADR-0117 on disk as Accepted (D1/D3 scoped)
The #4611 pin still asserts rejection packages/spec/src/data/object.test.ts:1020it('rejects business_unit until ADR-0117 D1 injection lands, naming the three legal values') ✅ RED-able target confirmed

So the ordering this card exists to respect held: the engine honours the tier, and only the acceptance surface was missing.

Changes

File Change
packages/spec/src/data/object.zod.ts The enum → four values; the error string and describe() (both of which spell the legal values out verbatim); the ownership JSDoc; the systemFields JSDoc's owner_id / owning_business_unit_id bullets; the systemFields.owner guidance string (a third value now skips owner_id, for a different reason than the other two)
packages/spec/src/data/object.test.ts The #4611 pin, rewritten — see below
packages/cli/src/commands/explain.ts os explain object's hand-maintained catalog — type string + description
packages/cli/test/commands.test.ts The exact-token-set assertion (see below)
packages/spec/src/system/constants/system-names.ts OWNING_BUSINESS_UNIT_ID JSDoc — the "not authorable yet" paragraph, named as a co-update target by the pin itself; and OWNER_ID's one-line withheld-under list
packages/spec/src/data/injected-system-columns.ts The ⚠️ "ahead of the acceptance surface" note; the string-widening rationale now stands on its real reason (the function takes unknown, incl. pre-parse input) rather than on the closed gap
packages/objectql/src/registry.ts Injection-site comments; the positive-list argument is restated so it applies to a fifth tier too, rather than reading as "safe now that the enum is complete"
packages/objectql/src/registry.test.ts Comment + the as any casts the old comment predicted would become droppable — dropped for ServiceObject / satisfies ServiceObject, no assertion changed
packages/spec/src/data/object-strictness-batch20.test.ts Its loop's premise was literally "across the whole AUTHORABLE enum the two anchors move together … the fourth tier is deliberately unauthorable — #5678". Now: three tiers move together, business_unit splits them, asserted against the authority
packages/spec/src/system/constants/system-names.test.ts The "fact 2 of the pair" comment, which asserted the split state
packages/metadata-protocol/src/protocol.injected-system-columns.test.ts Added the business_unit row to the #6562 served-columns table — the read surface must report owner_id as absent on a unit-owned object
content/docs/references/data/object.mdx Generated (gen:docs) — exactly one line, from the describe()
content/docs/data-modeling/objects.mdx, skills/objectstack-data/SKILL.md, packages/spec/liveness/object.json Hand-written prose that enumerated the three values
.changeset/ownership-business-unit-enum-member.md @objectstack/spec minor (author-visible enum member) + @objectstack/cli patch

The #4611 pin rewrite — reverse verification, direction predicted first

The old pin named its own expiry: "when #5678 arrives, this test failing is the intended signal to REWRITE it (not to delete the guard) — assert the fourth value is accepted and that a fifth is still rejected naming four legal values." Done exactly that, and the flipped pin was measured in both directions after stating the prediction:

# Predicted Method Observed
A The two rewritten pins are RED against the pre-change enum git checkout origin/main -- packages/spec/src/data/object.zod.ts, re-run 2 failedcreate throws on business_unit; the rejection message enumerates only ["user","org","none"], so toContain('business_unit') fails
A′ GREEN with the enum restored restore, re-run ✅ 2 passed
B The packages/cli token-set pin is RED on the CLI revert alone — i.e. the spec change does not carry it git checkout origin/main -- packages/cli/src/commands/explain.ts, re-run 1 failedSet{none,org,user}Set{business_unit,none,org,user}
B′ GREEN restored restore, re-run ✅ 1 passed

B is the card's "a spec-only scan would miss it" claim, measured rather than asserted: explain.ts is hand-maintained and does not derive from the enum, so nothing in packages/spec moves it. Its comment now records that, so the next enum change finds the reason instead of rediscovering it.

The rewritten pin also asserts the tier resolves to D1's row against resolveInjectedSystemColumns — the authority, not prose — so the enum member cannot drift away from what the engine does with it.

Gates

Gate Result
pnpm --filter @objectstack/spec build (first, per the stale-dist trap #7122)
check:generated — all 11 artifacts ✅ green (check:docs was the only stale one; gen:docs1 line)
@objectstack/spec tests ✅ 9398 passed / 360 files
@objectstack/objectql tests ✅ 2906 passed / 167 files
@objectstack/cli tests ✅ 1139 passed / 105 files
@objectstack/metadata-protocol tests ✅ 862 passed / 67 files
typecheck (spec + objectql + cli) ✅ 57/57
check:empty-changeset, check:changeset-no-major, check:adr-0087-registration

Special-inspection items

  1. No export-surface change → no dual snapshot. check:api-surface and check:export-origins are green unchanged: the enum gains a member, not an export. Dual-snapshot was scoped but is not owed.
  2. authorable-surface.base.json did not move, and that is correct. The card flagged it as possibly legitimately moving. 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.
  3. packages/spec/json-schema/** regenerated but is gitignored (.gitignore:61), so it is correctly absent from the diff despite carrying the new description.
  4. No i18n regeneration owed. Checked as instructed: no platform-objects select carries the record-ownership vocabulary (every ownership hit there is transfer_ownership / provider-ownership prose).
  5. Consumers of the enum VALUE are two, both already correct. Repo-wide sweep for ownership === / .ownership: resolveInjectedSystemColumns (handles the tier since ADR-0117 D1 执行面:applySystemFields 的 wantOwner 由排除式翻为正面清单 + 注入 owning_business_unit_id #5677) and packages/cli/src/commands/info.ts:79 (display-only, obj.ownership || 'user' — prints the new value as-is).
  6. The positive-list comment was rewritten forward, not just updated. It read as "safe only while the enum has exactly three members"; left alone it would invite a future reader to "complete" the list to the current four. It now states the argument applies to any fifth tier.

D2/D4/D5/D8 guard

No contact. Adding the enum member touches none of the four undecided items, and the hard stop was not reached:

  • D2 (stamping policy + default) — no owningBusinessUnit.policy key added; nothing stamps a value. The column stays provisioned-but-inert, so declaring the tier yields the column and the withheld owner_id, and nothing more.
  • D4 (transfer guard / permission bit) — untouched; no allowTransfer reuse, no write-path change.
  • D5 (legal-entity resolution) — no resolver, no materialized column.
  • D8 (enablement gate + granularity) — no gate, no flag.

The declaration/derivation boundary held cleanly: everything this PR changes is either the enum, prose describing it, or a test asserting the derivation that #5677 already landed. docs/adr/** and content/docs/releases/ are untouched.

Closes #5678


Generated by Claude Code

…it` tier (#5678)

`ObjectSchema.ownership` becomes `'user' | 'business_unit' | 'org' | 'none'`. The
fourth tier means "owned by an org UNIT, not by a person": `owning_business_unit_id`
is injected, `owner_id` deliberately is not — D1's table, end to end.

This is the DECLARATION surface only. #5677 already landed the execution surface:
`applySystemFields`' owner decision became an allow-list and the shared derivation
`resolveInjectedSystemColumns` already implements D1's row. The ordering was the
point — under the previous deny-list a fourth value would have fallen through and
been stamped `owner_id`, the exact inverse of the tier's meaning — so this change
is strictly after it.

The #4611 pin is REWRITTEN, not deleted, exactly as it asked to be: it now asserts
the fourth value is ACCEPTED and resolves to D1's row against the injection
authority, plus a second pin that a FIFTH value is still rejected and the rejection
enumerates all four legal values.

Consumption radius a spec-only scan misses: `os explain object`'s schema catalog is
hand-maintained (`packages/cli/src/commands/explain.ts`) and its token-set assertion
pins the enum EXACTLY, so it is co-updated here.

D2 (stamping policy), D4 (transfer guard), D5 (legal-entity resolution) and D8
(enablement gate) remain undecided in ADR-0117 and are untouched: the column stays
provisioned-but-inert, so the tier is declarable and nothing stamps it yet.

Closes #5678

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

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

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 3 package(s): @objectstack/cli, @objectstack/objectql, @objectstack/spec.

113 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 packages/cli, @objectstack/spec)
  • content/docs/ai/skills.mdx (via @objectstack/spec)
  • content/docs/api/client-sdk.mdx (via @objectstack/cli, @objectstack/spec)
  • content/docs/api/data-flow.mdx (via @objectstack/cli)
  • content/docs/api/environment-routing.mdx (via @objectstack/cli, @objectstack/spec)
  • content/docs/api/error-catalog.mdx (via @objectstack/cli, @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/cli, 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 @objectstack/objectql, 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 packages/objectql, @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/backup-restore.mdx (via @objectstack/cli)
  • content/docs/deployment/cli.mdx (via @objectstack/cli, @objectstack/spec)
  • content/docs/deployment/migration-from-objectql.mdx (via @objectstack/objectql)
  • content/docs/deployment/self-hosting.mdx (via @objectstack/cli)
  • content/docs/deployment/tenancy-modes.mdx (via @objectstack/spec)
  • content/docs/deployment/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx (via packages/cli, @objectstack/spec)
  • content/docs/deployment/vercel.mdx (via @objectstack/objectql)
  • 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/cli, @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/objectql, @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 @objectstack/spec)
  • content/docs/kernel/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/data-service.mdx (via @objectstack/cli, @objectstack/spec)
  • content/docs/kernel/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/examples.mdx (via packages/objectql, @objectstack/spec)
  • content/docs/kernel/runtime-services/index.mdx (via packages/cli, packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx (via @objectstack/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx (via @objectstack/spec)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/objectql, @objectstack/spec)
  • content/docs/kernel/services.mdx (via @objectstack/objectql, @objectstack/spec)
  • content/docs/permissions/authentication.mdx (via @objectstack/cli, @objectstack/objectql)
  • 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/permissions/system-context.mdx (via packages/objectql, packages/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/cli, @objectstack/objectql, @objectstack/spec)
  • content/docs/plugins/packages.mdx (via @objectstack/cli, @objectstack/objectql, @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/objectql, @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/cli, @objectstack/spec)
  • content/docs/protocol/kernel/realtime-protocol.mdx (via @objectstack/cli)
  • 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 packages/objectql, @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/objectql, @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/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/field-grouping-and-order.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)

7 release-owned page(s) also reference the affected code. These are read-only:

  • content/docs/releases/implementation-status.mdx (via @objectstack/cli, @objectstack/objectql, @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/cli, @objectstack/spec)
  • content/docs/releases/v17.mdx (via @objectstack/cli, @objectstack/spec)
  • content/docs/releases/v9.mdx (via @objectstack/spec)

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

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.

@os-zhuang
os-zhuang marked this pull request as ready for review August 10, 2026 04:55
@os-zhuang
os-zhuang added this pull request to the merge queue Aug 10, 2026
Merged via the queue into main with commit 1a15893 Aug 10, 2026
27 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-5678-ownership-business-unit branch August 10, 2026 05:27
os-zhuang pushed a commit that referenced this pull request Aug 10, 2026
一处冲突:`scripts/adr-anchors.json` —— main 的 #7301 把 ADR 锚点注册表
按文件分片(`scripts/adr-anchors/*.json`)并删除了整块 JSON,本分支同时
改了其中两条锚点的内容。按**并集**处置,不是二选一:采用 main 的分片布局,
把两条锚点的新内容移植进对应分片。

- `packages__objectql__src__registry.ts.json` —— invariant 由「D9 是
  DESIGN ONLY,不要实现」改写为已落地的模型
- `packages__metadata-protocol__src__protocol.ts.json` —— 依 D9 §8 加上
  ADR-0029;移植前逐字节校验了 main 侧 ADR-0119 那句未变,确保是并集而非覆盖

生成物 `content/docs/references/**` 取 main 一侧后用 `gen:schema` +
`gen:docs` 重新生成,未手工合并。

其余全部自动合并。main 的 #7260 把**记录**所有权枚举扩成
`user | business_unit | org | none`,与 D9 的**贡献**种类
(`own | extend | overlay`)是两个枚举,两侧改动均完整保留。
main 对 `registry.ts` 的改动是 `applySystemFields` 内的纯注释,与 D9 模型
无交集;`registerObject` / `resolveObject` / `getArtifactItem` / 两个
hydration 缝 / delete heal 均未被 main 触碰。任何钉子都未移动。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W6bLax4KMrSfnE1ydFU8Dw
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-0117 D1 声明面:ownership 枚举扩展 'business_unit'(须与列注入同 PR 或严格后置)

2 participants