Repository navigation
34 backfilled contracts carry a legal_owner, and is_backfilled says on the same row that no review ever happened #53
Description
Ruled: 6396326020 · letter B · 2026-10-25T10:07Z
Unblocked: the 17.7.0 → 18.1.0 upgrade landed as PR #110 (7b736a0).
Found while measuring #47.
Measured
claude/issue-47-seed-legal-owner, from the compiled artifact:
contracts with a legal_owner 68
of those, is_backfilled = true 34
of those 34, review_started_at null (all 34)
So a third of the corpus carries a named lawyer on a row that also says, in its own columns, that nobody ever reviewed it.
The contradiction, in the declarations' own words
clm_contract.legal_owner,src/objects/contract.object.ts: description "The lawyer who accepted the review. Required before a submitted contract enters review."clm_contract.is_backfilled, DESIGN.md §13 Q8: 补录的已签合同 — an already-executed contract entered after the fact, which starts active and skipped review and approval.src/data/plan-contracts.tssays the same at length: the backfilled population "claims nothing they do not have … nosubmitted_at, noreview_started_at, noapproved_at, no review, no deviation and no signature round".- F16 (
executed_upload) is the action that produces such a contract in the running app, and it assigns no legal owner.
The fixture's rule is hasLegalOwner = pastSubmission && type.requiresLegalReview && status !== 'submitted', which does not consult backfilled. That is why the two claims sit on the same row.
The two readings, and what each costs
- A.
legal_ownermeans "the lawyer who accepted the review". Then a backfilled contract must not carry one, andhasLegalOwnergains&& !backfilled.⚠️ Cost, and it is real: DESIGN.md §10 draws the expiring corpus from the backfilled contracts, so those 34 rows are precisely F12renewal_noticeand F13expiration_sweep's population. Both copylegal_ownerbesideowner_id, so every renewal and expiry notice would drop from two recipients to one — the opposite direction from The demo makes M3's reminder layer look broken: 54 of 120 contracts have nolegal_owner, so four of six scheduled jobs notify nobody #47. - B.
legal_ownermeans "the lawyer who owns this file in legal". Then the data is right and the field description is what is wrong, and it should say so — custodianship, with the review-acceptance sentence demoted to how the column is normally first written.
B is the cheaper answer and, for a records-keeping product, arguably the truer one: a backfilled contract still has somebody in legal responsible for it. But that is a product decision about what a column means, not a developer's, and either way something has to change so the row stops asserting both.
Not urgent, and ⛔ not a rider
Nothing measured is broken: no gate, no flow and no screen misbehaves. It is on the "places the product lies" list from #45 — a column that says one thing while the column next to it says the opposite — which is why it is filed rather than left in a PR body.
Related
#47 (where this was measured; it deals this column but deliberately does not change WHICH rows carry it) · #39 / PR #42 (F12/F13, and F16 which creates backfilled contracts) · #45 (the dogfood pass and its "places the product lies" category) · DESIGN.md §13 Q8
Activity
zhuangjianguo commentedon Sep 10, 2026
维护者速读
事情:三分之一的种子合同(34 份补录合同)上写着一位「法务负责人」,而同一行的另一列说这份合同从来没有被审查过。两句话不可能都对。
为什么会这样:
legal_owner的字段说明写的是**「接受了审查的那位律师」。而补录合同(is_backfilled)按设计是已经签好、事后录入、跳过审查与审批**的 —— 它们没有review_started_at、没有审查记录、没有签署轮次。种子在分配legal_owner时没有看backfilled这一列,于是两个声明落到了同一行上。你要做的:回一个字母,A 或 B。
legal_owner就是「接受审查的律师」⇒ 补录合同不该有,种子加一条&& !backfilledlegal_owner⇒ 每一条续签与到期通知从两个收件人掉到一个 —— 与 #47 刚做的事方向相反legal_owner是「法务这边归口负责这份档案的人」⇒ 数据是对的,错的是字段说明,改说明业务含义直译
A = 「律师」这个栏位专指审过这份合同的人,没审过就该空着;B = 那是这份档案在法务的归口人,合同怎么进来的不影响谁负责它。
四棱分析
os-decision-facets
① 项目长远合理性 — 偏 B。对一个档案管理型产品来说,一份合同在它整个生命周期里都该有法务归口人,而不只是在审查那一刻。选 A 之后,补录合同在法务侧无人负责 —— 而那恰恰是会到期、会需要续签决策的那一批。那是个架构上的洞,不只是少个收件人。
② 实际业务拉动 — 偏 B,且这一棱有实测。F12/F13 今天就在抄送
legal_owner,#47 刚测过收件人从 5 人变 7 人。选 A 会让续签与到期通知倒退回单收件人,方向与刚落地的改进相反。③ 防 AI 犯错 —⚠️ 这一棱内部打架,必须说破。仓规是「协议为基准:spec 与代码不一致默认改代码对齐」,照字面指向 A(改数据去迁就已声明的说明)。但那条规矩预设了声明是对的;这张卡问的正是那句说明是不是从一开始就写窄了。往深一层看:B 之后
legal_owner在 F12/F13 里被正确使用;A 之后那两个流程会去抄一个经常为空的列 —— 让下一个作者以为「抄了就有收件人」。所以字面规矩指 A,防错的实质指 B。⛔ 我不替你在这两者之间选。④ 创业阶段不扩散 — 偏 B。B 改一句话。A 改种子逻辑、丢收件人,还要重新论证 §10 的到期语料从哪来。
推荐:B,并要求同批把字段说明改成「法务归口人」并把「审查时首次写入」降格为一句用法说明。回退:选 A 就要一并接受 F12/F13 退回单收件人,并说明 §10 的到期语料改从哪里抽。
⛔ 本分析看不见什么:你们真实法务流程里,补录进来的旧合同有没有人接手。如果现实里就是没人管,A 才是诚实的;如果有归口人,B 是。这一个事实定这张卡,而它只在你那里。
为什么这张不能自裁
「说明书脱节」那条自裁通道的前提是实现没被裁错。这里恰恰相反:实现是对是错,正是待裁的那个问题 —— B 说数据对、说明错;A 说说明对、数据错。⛔ 通道够不到。而且它动的是已发布的字段说明,属公开契约语义。
不阻塞任何在飞卡。
关联
#47(发现处,已合并
d34b225;它刻意只分配这一列、不改哪些行携带它)· #39 / PR #42(F12/F13 与创建补录合同的 F16)· #45(「产品说谎的地方」那一类)·DESIGN.md§13 Q8、§10Generated by Claude Code
zhuangjianguo commentedon Oct 25, 2026
Ruling: batch #3 item 4 · letter B · maintainer 「同意」 2026-10-25T10:07:34Z
裁定:B ——
legal_owner是「法务归口人」;改字段说明,数据不动维护者在第 3 批决策箱回了「同意」(= 推荐项 B)。裁决文档:https://claude.ai/artifact/N1GCqHkEVMy3kneFmVEM8w 。文档明写「若现实里补录的旧合同就是没人管,选 A」,维护者仍选 B,即确认补录合同在法务侧有归口人。
鲜度门:正文 2026-09-10T07:26Z;评论一条 2026-09-10T07:31Z(席位四棱)。复测(
main134707e):contract.object.ts:213的说明仍是「The lawyer who accepted the review. Required before a submitted contract enters review.」;F13(expiration-sweep.flow.ts:79)与 F12(renewal-notice.flow.ts:177)把legal_owner作为also收件人;法务 SLA 巡检把它作为主收件人(补录合同不进审查,不受影响)。录裁前重读,无变化。裁后执行(给 lane 席位)
contract.object.ts的legal_owner.description改为归口人语义,例如:「The lawyer in legal who owns this contract's file. Normally first written when legal accepts the review; a backfilled contract names the lawyer records assigns.」两套翻译同步;§03 字段表若有说明则同步(受管面,随设计文档 PR,Refs #53)。hasLegalOwner规则不改(数据是对的)。legal_owner;是否在补录表单加一个可选的归口人字段,由 lane 在同一张卡里判(不改受管面)。⛔ 本席不动标签;lane 席位派发时转标签并带
Ruling-ref:。Generated by Claude Code
objectstack-fleet commentedon Oct 25, 2026
Out of the decision box — Ruling-ref: comment 6396326020 (batch #3 · letter B · maintainer 「同意」).
needs-user-decision→pm:queue. The fix is ruled and small (thelegal_ownerdescription to custodian semantics, incontract.object.ts, both translation bundles, and §03 if it carries one; the seed rule unchanged), so it is dispatchable, serial after the 18.1 upgrade PR (body line 3).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-53-legal-owner-custodianWorktree:
hotclm-issue-53Domain:
repo:hotclm(single-lane sister repo — nodomain:*)Seat:
repo:hotclm#1File surface:
src/objects/contract.object.ts(legal_owner.descriptiononly) · its en and zh-CN translation values — 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 legal_owner help text (says "the lawyer who accepted the review", false on 34 backfilled contracts) | none known | everyone reading a backfilled contractThread-read: 6397140950
Ruling-ref: 6396326020 (batch #3 item 4 · letter B)
Premise: re-measured by the dev on
origin/mainf5ad133(18.1.0)Serial constraints cleared: PR #114 (
contract.object.ts) mergedf5ad133. Next oncontract.object.tsafter this lands: #20.Scope: ruling step 1 (code half). Step 2 is no change. Step 3 (an optional custodian field on the F16 backfill form) is the lane's call and is deferred to #43: F16 cannot run on 18.1 until the release carrying objectstack-ai/objectstack#25643 (its
executed_uploadanswers 403), so a form change there could not be measured; #43 carries the note. The §03 field-table sync, if §03 has a description, is governed — the seat's design-doc PR.Generated by Claude Code
objectstack-fleet commentedon Oct 25, 2026
os-dev-report
{ "issue": 53, "status": "done", "branch": "claude/issue-53-legal-owner-custodian", "pr": "https://github.com/objectstack-ai/hotclm/pull/115", "session": "session_017nzWFRUo2U7evMkPdVniAn", "premise_still_valid": true, "summary": "Ruling B step 1 is done (commit 3e470f6). clm_contract.legal_owner's description and en help now read 'The lawyer in legal who owns this contract's file. Usually assigned when the contract enters legal review; a backfilled contract skips review, so legal names its lawyer here directly.' The zh-CN help reads '在法务归口负责这份合同档案的律师。通常在合同进入法务审查时分配;补录的合同不经审查,由法务直接在这里指定。' A code comment beside the field carries the mechanics. I checked the ruling's example clause 'records assigns' and did not use it because it is false. F16 executed_upload has no legal_owner param and writes none. clm_records cannot write the column: editableOnly(Contract, ['status','archive_no']) locks it. clm_legal (FLS opens the parties group; its row policy covers every status) and clm_admin can. So the text says legal names the lawyer. Premise re-measured on origin/main f5ad133: the old description sits at contract.object.ts:216 and en objects.contract.ts:120, unchanged. DESIGN.md §03 lists 'legal_owner lookup user' with no description (line 106), so no governed sync is needed. The PR body offers a replacement line in case §03 later gains descriptions. Step 2 is no change. Step 3 stays with #43, which remains open; the body says so and closes #53 only. The file surface was held: 3 files, 1 description string, 2 help strings, 1 comment. Worktree and node_modules removed after the push.", "tests": "No test suite in this repo covers description text, and nothing here is a tested behaviour, so there is no ablation. Browser check: pnpm demo --port 3153 on a clean .objectstack/data, Chromium /opt/pw-browsers/chromium via Playwright, signed in as admin@objectos.ai. Record: backfilled NDA-2026-0006 (4V-BF26k2ggfqaR7, is_backfilled true, review_started_at null; GET /api/v1/data/clm_contract is_backfilled+active answered 200 total 41). The read-only record page draws no field help. The Edit form (?form=id) shows the help as the field's description paragraph. en-US context: label 'Legal Owner' with the new en text. zh-CN context: label '法务经办人' with the new zh text. Both were read from the DOM verbatim. Screenshots: /tmp/claude-0/-home-claude/bc53be1b-6c43-5271-bc20-52035f64763b/scratchpad/issue-53/branch-legal-owner-en-US.png and branch-legal-owner-zh-CN.png (plus -full.png variants). Page console: one 401 and one 404 resource load and two object-ui predicate fallbacks (submit_contract, start_renewal header actions); none relates to this text-only change. Boot warnings vs main: a second pnpm demo --port 3154 from a detached f5ad133 worktree on a clean db. Both boots logged 430 raw WARN/ERROR lines. After normalising ids and timestamps, the class/count sets are identical (diff exit 0): 68 legal_owner, 120 owner_id, 17 decided_by and 199 obligation.owner first-boot deferred refs, 6 approver-slate warnings, the plugin-superseded, seed-budget and hierarchy-security notices. No new boot warning. Because the counsel accounts do not exist on a first boot, every legal_owner is NULL in the demo, so the field is empty on the record; the help renders regardless.", "gates": "Local, on the tree committed unchanged as 3e470f6, each exit captured before any pipe: pnpm install 0; pnpm validate 0 ('✓ Validation passed'); pnpm lint 0 ('6 suggestion(s)', the pre-existing contract_approval approver-slate set); pnpm typecheck 0 (tsc --noEmit); pnpm lint:i18n-gate 0 ('✓ i18n gate', 'COVERAGE : 0 missing keys across 2 locale(s)'). dispatch-gates.mjs --ran was not run: the script is objectstack-only, and hotclm's gate list comes by hand from package.json plus .github/workflows/ci.yml (one job, Validate, which runs those same four). CI on head 3e470f6: Validate (push run 40194574384) completed success. Validate (pull_request run 40195282675) was in_progress at report time. Not waited on, per the os-dev contract.", "dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm; hotclm has no dogfood package", "mcp_calls": "0", "api_writes": "2 — fleet-write relay pr_create (POST /repos/objectstack-ai/hotclm/pulls draft + POST /repos/objectstack-ai/hotclm/issues/115/assignees zhuangjianguo; read-back: 7559 bytes sent, 7559 stored, identical; assignees zhuangjianguo) · post-stamped comment on #53 (this report). git push (empty-branch probe, then 3e470f6) is not REST.", "open_questions": [], "out_of_scope_findings": [ "Observation, not filed (read from the code, not measured; carrier: 承接者:无): clm_finance locks CONTRACT_LEGAL_FIELDS (legal group + risk_level, liability_cap) and status. legal_owner is in the parties group, so the finance controller, who edits approved-onward contracts through contract_finance_post_approval, appears able to reassign the custodian. Whether legal_owner belongs in finance's locked set is a §04 question. Recorded in PR #115 Acceptance notes. Dedupe words: finance legal_owner FLS, CONTRACT_LEGAL_FIELDS parties, custodian reassign.", "Polish, not filed (carrier: 承接者:无): the zh-CN label 法务经办人 (field and contract_cycle_time dataset dimension) reads narrower than the 归口 semantics the help now gives. Relabelling is outside this card's description-only surface. Recorded in PR #115 Acceptance notes." ] }Generated by Claude Code
objectstack-fleet commentedon Oct 25, 2026
Landed — PR #115 · 2026-10-25T12:59Z
MERGED, squashc51f6eaonmain, merged at its ACCEPT head3e470f6Release: ruling B is delivered —
legal_ownerreads as legal's custodian lawyer (en and zh-CN); seed data unchanged (step 2). Step 3 (an optional custodian field on the F16 backfill form) is carried by #43, where F16's own unblock lives. The card closes; labels cleared. Seat notes from the dev report, not filed: finance's FLS leaveslegal_owner(parties group) writable on approved-onward contracts — a §04 question for a later design pass; the zh-CN label 法务经办人 reads narrower than 归口.Generated by Claude Code