Skip to content

Decision: §10 wants a multi-entity group, but clm_contract.our_entity ships exactly one option and a seed cannot add one #20

Open

Description

@hotlong

Ruled: 6396457129 · letter C · 2026-10-25T10:22:34Z

Unblocked: the 17.7.0 → 18.1.0 upgrade landed as PR #110 (7b736a0).

Raised by card 08 (#18, seed data), by Claude Code session session_01KcrVDXSptwDukFsHPHPR1V, rather than editing the governed surface.

The conflict

  • §03 declares the field as our_entity select(签约主体,种子) — the parenthetical says the value list is seed data.
  • §10 asks the demo to be "一家虚构的跨国集团(美国母公司,欧洲与亚太子公司)" — a US parent with European and Asia-Pacific subsidiaries, which only means something if a contract can say which entity signed it.
  • src/objects/contract.object.ts implements it as a select with exactly one option, head_office, and a comment saying a group with several legal entities replaces the list with its own: "The shipped list is a single placeholder — a group with several legal entities replaces it with its own (DESIGN.md §01: signing entities are configuration, not schema)."

A select's options are schema. A seed writes values, and the value vocabulary is closed.

Measured (@objectstack/* 17.3.0), both write paths

Through the REST API, as the platform admin:

POST /api/v1/data/clm_contract  {"our_entity":"meridian_holdings_us", ...}
  -> HTTP 400
  {"error":"Our Signing Entity must be one of: head_office","code":"VALIDATION_FAILED",
   "fields":[{"field":"our_entity","code":"invalid_option","options":["head_office"]}]}

Through the seed loader, which is a system write and bypasses RBAC:

✗ Failed to write clm_contract record #0 (title=PROBE entity vocab):
  Our Signing Entity must be one of: head_office

Positive control: the other 14 rows of the same seed load inserted without complaint, so the refusal is about the value and not about the seed path. isSystem bypasses RBAC; it does not widen a select's vocabulary.

What card 08 did instead

#18 seeds our_entity: 'head_office' on all 120 contracts and expresses the multi-entity group through the axes that ARE data: governing law (US-NY 37 / Germany 38 / England and Wales 35), currency (USD 42 / EUR 41 / GBP 37), jurisdiction, and the counterparty base. That satisfies §10's observable requirements — three currencies, three governing laws — but the group's own signing entities are invisible, and "which of our entities signed this" is not answerable from the demo data.

Options

A. Leave it. The demo shows a multi-jurisdiction book signed by one entity. Honest, and the placeholder comment already says a customer replaces the list. Cost: §03's "(种子)" stays untrue for this field, and the §10 story is thinner than the table promises.

B. Ship three options (head_office, plus an EU and an APAC entity) in contract.object.ts and let the seed spread contracts across them. Cheapest thing that makes §10 true. Cost: an edit to a card-02 file and, arguably, to §01's rule that signing entities are configuration rather than schema — three placeholder entities in the shipped product is still schema, just three of them.

C. Make the signing entity a lookup to a real object (a clm_entity, or the platform's sys_business_unit), so the list genuinely is data and a seed can extend it. This is what "(种子)" describes. Cost: a §03 change, a new object or a platform-object dependency, and a migration for anyone already on our_entity.

Recommendation: B if the demo matters more, C if §03's word matters more. B is one field edit and makes the seed table honest today. C is the shape §03 actually describes and the one a group with a real entity list needs, but it is a governed-surface decision with a migration behind it. Either way, this is not card 08's to take.

Activity

  1. zhuangjianguo commented on Sep 9, 2026

    @zhuangjianguo
    Collaborator

    维护者速读

    ⚠️ 拟稿,不是裁决。 总监席本场服役档 claude-opus-5 ≠ CONTRACT_REVIEW_TIER(claude-fable-5-1,dispatch-gates.mjs:10446,精确比较),四职停放,⛔ 不裁不清标。本评论补这张卡欠的四棱块、速读,以及一个推荐(卡片正文给的是二选一的分叉,不符合一卡一推荐的格式)。标签一动未动。

    事情:设计方案第 10 章要演示「一家虚构的跨国集团(美国母公司,欧洲与亚太子公司)」,而第 3 章把签约主体标注为「(种子)」—— 意思是主体清单属于数据。但实现是一个只有一个选项的 select(head_office),而 select 的选项是 schema,种子写不进新选项。

    两条写入路径都实测过(17.3.0):REST 写一个新主体 400 VALIDATION_FAILED / invalid_option;种子加载器(系统写入、绕过 RBAC)同样被拒 —— isSystem 绕的是权限,不加宽 select 的词表。同一次种子加载的另外 14 行正常插入,所以拒绝是关于值本身,不是种子路径。

    卡 08 于是用真正是数据的那些轴表达跨国集团:适用法律(美国-纽约 37 / 德国 38 / 英格兰与威尔士 35)、币种(USD 42 / EUR 41 / GBP 37)、司法辖区、相对方基底。§10 可观测的要求(三币种、三法律)满足了,但「我们哪个主体签的这份合同」在 demo 数据里答不出来。

    你要做的:回一个字母,A、B 或 C。

    四棱分析

    卡片正文(英文,卡 08 所写)没有给单一推荐,给的是「demo 更重要就 B,§03 的字眼更重要就 C」。四棱算下来偏 C,理由在第二、三棱。

    实际业务需求 — 偏 C。本车道收卡第一问恒为「这张卡展现哪项平台能力」。B(往 schema 里塞三个虚构主体)展现不了任何平台能力,它只是让表格好看;C(主体改成指向真实对象的 lookup)展现的正是 §01 主张的配置即数据。而「我们哪个主体签的」对多主体集团是真问题 —— 集团内部结算、税务与授权链都挂在它上面。

    项目长远合理性 — 明确偏 C,这一棱差距最大。§03 已经写了「(种子)」,C 就是那个词描述的形状;A 让那个词长期不成立,B 让它以「三个也是 schema,只是三个」的方式继续不成立。更硬的是 §01 自己说签约主体是配置而非 schema —— B 直接违背它,只是违背得比现状少两个数量级。

    防 AI 写错 — 偏 C。今天那个单选项占位符的注释写着「有多个法律主体的集团用自己的清单替换它」,也就是教下一个作者去改我们发布的 schema。展品撒谎等于教每个照抄的 AI 撒谎 —— 这条注释就是在教一件错事。C 把主体变成数据,客户加主体是加一行记录,不是改 schema。B 把「往发布的 select 里加占位选项」立成惯例,下一个 AI 会在别的字段照抄。

    创业阶段不扩散 — 偏 B。B 是一次字段编辑;C 要新增一个 clm_entity 对象(或依赖平台的 sys_business_unit),加一次 §03 编辑,还要给已经用 our_entity 的人一条迁移。这是 C 唯一的实质代价,也是唯一让 B 有理由的一棱。

    结论:三棱同向 C,一棱(不扩散)偏 B。按「长远合理性权重恒 ≥50%,四棱冲突时其余各棱合起来投不翻它」,并按分歧推荐序「有实测拉动 ⇒ 荐长远形态一次付清」(§10 是已声明的展品要求,即拉动),拟推荐 C。

    ⚠️ 但 C 触受管面(§03 编辑)且带一条迁移,所以无论如何要你点头 —— 这也是这张卡不该由循环自裁的原因。若你选 B,请一并要求删掉那条「客户替换清单」的注释并改写成「主体清单是 schema,扩展需改 schema」,否则注释会继续教错事;若你选 A,请把 §03 的「(种子)」改掉,别让文档留一个不成立的词。

    ⚠️ 前提新鲜度

    两次实测取自 @objectstack/* 17.3.0。核心前提「select 的选项是 schema,isSystem 不加宽词表」是一句平台能力断言。#35 正在把应用推到 17.4.0(396 changeset / 35 breaking)。

    ⇒ 建议:这张排在 #35 落地之后。若 17.4 让选项词表可由数据扩展,C 的成本会大幅下降,甚至变成 A 的自然演进。


    Generated by Claude Code

  2. zhuangjianguo commented on Oct 25, 2026

    @zhuangjianguo
    Collaborator

    Ruling: batch #4 item 1 · letter C · maintainer 「同意」 2026-10-25T10:22:34Z

    C, with the platform object as the target: our_entity becomes a lookup to sys_business_unit, not a new clm_entity. Signing entities are data, as DESIGN §01 and §03's 「(种子)」 already say.

    Premise re-check (objectstack main, 2026-10-25) behind the choice of target:

    • sys_business_unit (packages/platform-objects/src/identity/sys-business-unit.object.ts) already exists, with kind: company | division | department | office | cost_center, a self-referencing parent_business_unit_id, code, active, effective_from/to and an org-chart tree view. The platform's own system-field docs define it as "which department / legal entity does this row belong to".
    • An app seed can write it: examples/app-showcase/src/data/seed/index.ts upserts sys_business_unit rows (a kind: 'company' parent plus children); the identity write guard does not judge managedBy: 'platform' tables.
    • Field.lookup accepts lookupFilters: [{ field, operator, value }], so the picker can be limited to kind = company.
    • The picklist metadata kind (objectstack#18164 / #19518 / #19519) extends options only at package level, so it does not change this card's premise.
    • No objectstack card is needed: the capability is already on the platform.

    Lane follow-through (not the director's):

    1. our_entity → Field.lookup('sys_business_unit', { lookupFilters: [{ field: 'kind', operator: 'equals', value: 'company' }], required: true, … }). Keep the field name our_entity (business_unit is a platform reserved word, DESIGN §01).
    2. Seed: add a sys_business_unit upsert (US parent kind: company; EU and APAC subsidiaries kind: company with parent_business_unit_id), spread the 120 contracts across them, and delete the "a group with several legal entities replaces it with its own" comment.
    3. DESIGN §03: our_entity select(签约主体,种子) → our_entity lookup sys_business_unit(签约主体,种子). §10 unchanged.
    4. Re-check the six consumers of our_entity: contract-backfill.actions, contract.actions, contract.view, translations, expiration-sweep.flow, executed-upload.flow.
    5. department select(种子) (contract.object.ts:183) has the same shape. Out of this card's scope: the lane files a separate card citing this ruling.

    Rec source: director thread (Fable), four-axis block in the batch-4 doc; the earlier draft (comment 5603651791) reached the same letter but left the target open.


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 25, 2026

    @objectstack-fleet
    Contributor

    Out of the decision box — Ruling-ref: comment 6396457129 (batch #4 · letter C · maintainer 「同意」).


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 25, 2026

    @objectstack-fleet
    Contributor

    The serial predecessor is gone: PR #110 (the 18.1 upgrade) merged as 7b736a0. State stays pm:queue.


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 25, 2026

    @objectstack-fleet
    Contributor

    Claim: PM loop round 14
    Session: session_017nzWFRUo2U7evMkPdVniAn
    Account: zhuangjianguo (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-20-our-entity-business-unit
    Worktree: hotclm-issue-20
    Domain: repo:hotclm (single-lane sister repo — no domain:*)
    Seat: repo:hotclm#1
    File surface: src/objects/contract.object.ts (our_entity) · src/data/** (a sys_business_unit seed, contract.seed.ts) · the our_entity consumers: src/actions/contract-backfill.actions.ts · src/actions/contract.actions.ts · src/apps/clm.app.ts · src/flows/{contract-intake,executed-upload,expiration-sweep,renewal-notice}.flow.ts · src/pages/contract_detail.page.ts · src/views/contract.view.ts · src/translations/{en,zh-CN}/{objects.contract,flows}.ts — stop on breach; explain in the report
    Container & model: S, mode:subagent, model: opus (TIER_DEFAULT; hand-derived for a sister repo), effort: medium
    Clause-②: no
    Metadata-protocol: none
    Responsibility: this app's our_entity (organisation structure written as a fixed option list) | none known | every contract author choosing the signing entity
    Thread-read: 6397147795
    Ruling-ref: 6396457129 (batch #4 item 1 · letter C)
    Premise: re-measured by the dev on origin/main c51f6ea (18.1.0): sys_business_unit with kind: company, an app seed can write it, lookupFilters on Field.lookup
    Serial constraints cleared: PRs #114 and #115 (contract.object.ts) merged c51f6ea. In flight: #38 (party / contract-type / signature / version objects and their translations, party.seed.ts), #11 (src/profiles/**, positions.ts) — disjoint. After this lands: #109 (department, same file and seed). The DESIGN.md §03 line is governed — the seat's design-doc PR.


    Generated by Claude Code

  6. objectstack-fleet commented on Oct 25, 2026

    @objectstack-fleet
    Contributor

    os-dev-report

    {
      "issue": 20,
      "status": "done",
      "branch": "claude/issue-20-our-entity-business-unit",
      "pr": "https://github.com/objectstack-ai/hotclm/pull/118",
      "session": "session_017nzWFRUo2U7evMkPdVniAn",
      "premise_still_valid": true,
      "summary": "Ruling C steps 1, 2 and 4 are implemented. clm_contract.our_entity is now Field.lookup('sys_business_unit'), required + storage.notNull. Its lookupFilters are kind eq company and active eq true; 18.1 has no 'equals' operator, so the filter uses 'eq'. A new business-unit.seed.ts upserts the group's three company units on code: ARC-US is the root, ARC-EU and ARC-APAC sit under it via parent_business_unit_id, and both locales carry names. The 120 contracts are spread by governing law: 42 US / 41 EU / 37 APAC, all resolving to company units. Every consumer is re-pointed. Intake and backfill collect the entity with the action dialog's field-backed picker, which on 18.1 honours lookupFilters (measured); no 'head_office' default remains. Translations, the register column and the clm.app note are updated, and the 'replaces it with its own' comment is deleted. The §03 line is verbatim under Acceptance notes, and the PR body says 'Part of #20'. Note: on reading #20 (4 comments, last 6397147795) no 'Claim: PM loop round 14' comment was present yet; I posted none, per the dispatch.",
      "tests": "Real boots on 18.1.0, clean .objectstack/data. pnpm demo (en): 'Seed loading completed … {\"inserted\":823,…,\"errored\":404}' against main 4efc391's inserted 820 / errored 404 on the same command. The +3 are the business units, and the 404 are the same unresolved user-name families on both sides. The sorted, de-duplicated warning-signature diff against main differs only in indentation: no new warning. API readback: 3 company units with organization_id stamped; contracts 120 = ARC-US 42 / ARC-EU 41 / ARC-APAC 37, non-company 0, unresolved 0. pnpm demo:zh, clean: same counts with zh names. Re-running demo:zh on the same DB gave inserted 0 / skipped 823 and still 3 units (idempotent). Browser as admin (Chromium /opt/pw-browsers/chromium), after POSTing a department-kind unit and an inactive company unit (both 201): the Create Contract form's quick-select and table picker, en and zh-CN (我方签约主体), and the Launch Contract dialog all send filter [\"and\",[\"kind\",\"=\",\"company\"],[\"active\",\"=\",true]] and offer exactly the 3 Arcadia companies; the department and the inactive company are not offered. Contract Register and the detail page show the entity name in en and zh. Intake run (interactive): launch_contract 200, then resume x3, then version POST 201, giving NDA-2026-0021 with our_entity = ARC-APAC 'Arcadia Asia Pacific Pte. Ltd.'. F12 Start Renewal on LSE-2026-0005 created a draft carrying the same entity (Arcadia Europe GmbH), and its detail shows the name. Headless: launch_contract without our_entity gives 400 'Action param \"our_entity\" is required'; data POST without it gives 400 'Our Signing Entity is required'. NOT MEASURED: F13 sweep / notice text, because the manual trigger answers 403 'This caller may not start this flow' and the flow is cron-only (no notice text names our_entity, per grep). NOT MEASURED: backfill end to end, because the admin lacks execute_contract (403) and a records persona was not created. Ablation: n/a (metadata-only diff, no tested behaviour).",
      "gates": "Local, head 5dc8738, each exit captured to its own file before any pipe: pnpm validate exit=0 ('✓ Validation passed', 11 Objects 168 Fields); pnpm lint exit=0 (6 suggestions, unchanged from main); pnpm typecheck exit=0; pnpm lint:i18n-gate exit=0 ('0 missing keys across 2 locale(s)'); plus CLM_DEMO_SEED=1 pnpm validate exit=0 and OS_SEED_LOCALE=zh-CN CLM_DEMO_SEED=1 pnpm validate exit=0. The objectstack dispatch-gates.mjs derivation does not apply to hotclm; its gate list is the four AGENTS.md gates. PR check-runs on head 5dc8738: Validate completed/success (actions run 40198080003) and Validate completed/success (actions run 40196751191).",
      "dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm",
      "mcp_calls": "0",
      "api_writes": "2 — fleet-write relay: pr_create (POST /repos/objectstack-ai/hotclm/pulls draft + POST /issues/118/assignees) as one dispatch; post-stamped comment POST /repos/objectstack-ai/hotclm/issues/20/comments (this report). git push of the branch is not a REST write.",
      "open_questions": [],
      "out_of_scope_findings": [
        "class: a · reach: PATCH /api/v1/data/clm_contract/{id} {our_entity: DEPT_UNIT_ID} → 200 (measured on 18.1.0 and restored); lookupFilters only restrict the picker, and nothing at the write layer refuses a non-company (or inactive) business unit as signing entity. clm_party has a submission hook for its equivalent rule; this field has none. The fix site is contract.hook.ts, outside this card's surface. Dedupe words: our_entity kind company enforce, signing entity department, lookupFilters write-side, contract hook entity · for the seat to file (also recorded in PR #118 Acceptance notes)",
        "observation · fresh non-demo install has no company-kind sys_business_unit, so no contract can be created until an admin adds one in Setup → Business Units (by design: entities are data, the same as contract types) · carrier: none — PR #118 Acceptance notes only",
        "observation · pre-existing databases hold our_entity = 'head_office' strings that resolve to no unit; no data migration ships (demo is the only populated deployment) · carrier: none — PR #118 Acceptance notes only",
        "observation · the 签约主体与部门 (管理) sidebar row now has half an object to point at (sys_business_unit org chart); the clm.app note defers the row to #109 · carrier: #109"
      ]
    }
  7. objectstack-fleet commented on Oct 25, 2026

    @objectstack-fleet
    Contributor

    Landed — PR #118 · 2026-10-25T13:39Z

    reading value
    PR #118 MERGED, squash 4460d31 on main, merged at its ACCEPT head 5dc8738
    authority maintainer, project thread, verbatim: 「你可以自己审核的就自己合并」

    Release (ruling 6396457129): steps 1, 2, 4 delivered — our_entity is a required lookup to the platform's sys_business_unit (active, kind company), the demo seeds the group's three entities, every consumer is re-pointed. Still open here: step 3, the §03 line (governed — the seat's design-doc PR). Step 5 is #109, dispatched next. A follow-up card is filed for the write-side check (the picker filter does not stop a PATCH naming a department unit). State: pm:dispatched → pm:queue, assignee cleared.


    Generated by Claude Code

  8. objectstack-fleet commented on Oct 25, 2026

    @objectstack-fleet
    Contributor

    Maintainer-action: merge PR #120 — the governed design-doc PR carrying this card's remaining ruled line (code already landed: PR #118). It closes this card.

    State: pm:queue → pm:awaiting-maintainer (no dev work left).


    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

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions