Repository navigation
Decision: §10 wants a multi-entity group, but clm_contract.our_entity ships exactly one option and a seed cannot add one #20
Description
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.tsimplements it as aselectwith 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
Demo seed data: demo-en (default) and demo-zh (#21)
zhuangjianguo commentedon Sep 9, 2026
维护者速读
事情:设计方案第 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。
四棱分析
实际业务需求 — 偏 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。
两次实测取自
@objectstack/*17.3.0。核心前提「select的选项是 schema,isSystem不加宽词表」是一句平台能力断言。#35 正在把应用推到 17.4.0(396 changeset / 35 breaking)。⇒ 建议:这张排在 #35 落地之后。若 17.4 让选项词表可由数据扩展,C 的成本会大幅下降,甚至变成 A 的自然演进。
Generated by Claude Code
zhuangjianguo commentedon Oct 25, 2026
Ruling: batch #4 item 1 · letter C · maintainer 「同意」 2026-10-25T10:22:34Z
C, with the platform object as the target:
our_entitybecomes a lookup tosys_business_unit, not a newclm_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, withkind: company | division | department | office | cost_center, a self-referencingparent_business_unit_id,code,active,effective_from/toand an org-chart tree view. The platform's own system-field docs define it as "which department / legal entity does this row belong to".examples/app-showcase/src/data/seed/index.tsupsertssys_business_unitrows (akind: 'company'parent plus children); the identity write guard does not judgemanagedBy: 'platform'tables.Field.lookupacceptslookupFilters: [{ field, operator, value }], so the picker can be limited tokind = company.Lane follow-through (not the director's):
our_entity→Field.lookup('sys_business_unit', { lookupFilters: [{ field: 'kind', operator: 'equals', value: 'company' }], required: true, … }). Keep the field nameour_entity(business_unitis a platform reserved word, DESIGN §01).sys_business_unitupsert (US parentkind: company; EU and APAC subsidiarieskind: companywithparent_business_unit_id), spread the 120 contracts across them, and delete the "a group with several legal entities replaces it with its own" comment.our_entity select(签约主体,种子)→our_entity lookup sys_business_unit(签约主体,种子). §10 unchanged.our_entity: contract-backfill.actions, contract.actions, contract.view, translations, expiration-sweep.flow, executed-upload.flow.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
objectstack-fleet commentedon Oct 25, 2026
Out of the decision box — Ruling-ref: comment 6396457129 (batch #4 · letter C · maintainer 「同意」).
needs-user-decision→pm:queue, serial after the 18.1 upgrade PR (body line 3). The dispatch carries the ruling's lane steps 1–4 (lookup tosys_business_unitfiltered tokind: company, field name kept; thesys_business_unitseed and the spread of the 120 contracts; delete the "replaces it with its own" comment; re-check the six consumers). The §03 wording goes with the design-document PR (Refs #20).clm_contract.departmentis a fixed option list — make it a lookup to the platform'ssys_business_unit, as #20 ruled forour_entity#109 (department, same shape), serial after this card.Generated by Claude Code
objectstack-fleet commentedon Oct 25, 2026
The serial predecessor is gone: PR #110 (the 18.1 upgrade) merged as
7b736a0. State stayspm:queue.Generated by Claude Code
objectstack-fleet commentedon Oct 25, 2026
Claim: PM loop round 14
Session:
session_017nzWFRUo2U7evMkPdVniAnAccount:
zhuangjianguo(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-20-our-entity-business-unitWorktree:
hotclm-issue-20Domain:
repo:hotclm(single-lane sister repo — nodomain:*)Seat:
repo:hotclm#1File surface:
src/objects/contract.object.ts(our_entity) ·src/data/**(asys_business_unitseed,contract.seed.ts) · theour_entityconsumers: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 reportContainer & model:
S,mode:subagent,model: opus(TIER_DEFAULT; hand-derived for a sister repo),effort: mediumClause-②: 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 entityThread-read: 6397147795
Ruling-ref: 6396457129 (batch #4 item 1 · letter C)
Premise: re-measured by the dev on
origin/mainc51f6ea(18.1.0):sys_business_unitwithkind: company, an app seed can write it,lookupFiltersonField.lookupSerial constraints cleared: PRs #114 and #115 (
contract.object.ts) mergedc51f6ea. 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). TheDESIGN.md§03 line is governed — the seat's design-doc PR.Generated by Claude Code
objectstack-fleet commentedon Oct 25, 2026
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" ] }Our signing entity is a company-kind business unit of the platform, s…
objectstack-fleet commentedon Oct 25, 2026
Landed — PR #118 · 2026-10-25T13:39Z
MERGED, squash4460d31onmain, merged at its ACCEPT head5dc8738Release (ruling 6396457129): steps 1, 2, 4 delivered —
our_entityis a required lookup to the platform'ssys_business_unit(active, kindcompany), 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 aPATCHnaming a department unit). State:pm:dispatched→pm:queue, assignee cleared.Generated by Claude Code
objectstack-fleet commentedon Oct 25, 2026
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
Sync the governed design text with the rulings landed on 2026-10-25
Sync the governed design text with the rulings landed on 2026-10-25
Write §03's department as the business-unit lookup #109 makes it
A contract's department is a business unit, not a fixed option list (#…
Sync the governed design text with the rulings landed on 2026-10-25
Write §03's department as the business-unit lookup #109 makes it