Skip to content

34 backfilled contracts carry a legal_owner, and is_backfilled says on the same row that no review ever happened #53

Closed

Description

@zhuangjianguo

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. ⚠️ This is a semantics question, not a defect report: one of the two readings below is right and the fixture currently asserts the other without saying so.

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.ts says the same at length: the backfilled population "claims nothing they do not have … no submitted_at, no review_started_at, no approved_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_owner means "the lawyer who accepted the review". Then a backfilled contract must not carry one, and hasLegalOwner gains && !backfilled. ⚠️ Cost, and it is real: DESIGN.md §10 draws the expiring corpus from the backfilled contracts, so those 34 rows are precisely F12 renewal_notice and F13 expiration_sweep's population. Both copy legal_owner beside owner_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 no legal_owner, so four of six scheduled jobs notify nobody #47.
  • B. legal_owner means "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

  1. zhuangjianguo commented on Sep 10, 2026

    @zhuangjianguo
    CollaboratorAuthor

    维护者速读

    分诊:收进决策箱并补上入箱必带的四棱块与速读。⛔ 本席不裁 —— 见下面「为什么不能自裁」。

    事情:三分之一的种子合同(34 份补录合同)上写着一位「法务负责人」,而同一行的另一列说这份合同从来没有被审查过。两句话不可能都对。

    为什么会这样:legal_owner 的字段说明写的是**「接受了审查的那位律师」。而补录合同(is_backfilled)按设计是已经签好、事后录入、跳过审查与审批**的 —— 它们没有 review_started_at、没有审查记录、没有签署轮次。种子在分配 legal_owner 时没有看 backfilled 这一列,于是两个声明落到了同一行上。

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

    做法 客户感知到的后果
    A 认定 legal_owner 就是「接受审查的律师」⇒ 补录合同不该有,种子加一条 && !backfilled 数据变诚实。⚠️ 代价是真的:设计方案 §10 的「即将到期」语料正是从补录合同里抽的,而 F12 续签通知与 F13 到期扫描都会连带抄送 legal_owner ⇒ 每一条续签与到期通知从两个收件人掉到一个 —— 与 #47 刚做的事方向相反
    B 认定 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、§10


    Generated by Claude Code

  2. zhuangjianguo commented on Oct 25, 2026

    @zhuangjianguo
    CollaboratorAuthor

    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(席位四棱)。复测(main 134707e):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 席位)

    1. 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)。
    2. 种子的 hasLegalOwner 规则不改(数据是对的)。
    3. F16 补录流程今天不赋 legal_owner;是否在补录表单加一个可选的归口人字段,由 lane 在同一张卡里判(不改受管面)。

    ⛔ 本席不动标签;lane 席位派发时转标签并带 Ruling-ref:。


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 25, 2026

    @objectstack-fleet
    Contributor

    Out of the decision box — Ruling-ref: comment 6396326020 (batch #3 · letter B · maintainer 「同意」).

    • State: needs-user-decision → pm:queue. The fix is ruled and small (the legal_owner description to custodian semantics, in contract.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

  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-53-legal-owner-custodian
    Worktree: hotclm-issue-53
    Domain: repo:hotclm (single-lane sister repo — no domain:*)
    Seat: repo:hotclm#1
    File surface: src/objects/contract.object.ts (legal_owner.description only) · its en and zh-CN translation values — 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 legal_owner help text (says "the lawyer who accepted the review", false on 34 backfilled contracts) | none known | everyone reading a backfilled contract
    Thread-read: 6397140950
    Ruling-ref: 6396326020 (batch #3 item 4 · letter B)
    Premise: re-measured by the dev on origin/main f5ad133 (18.1.0)
    Serial constraints cleared: PR #114 (contract.object.ts) merged f5ad133. Next on contract.object.ts after 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_upload answers 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

  6. objectstack-fleet commented on Oct 25, 2026

    @objectstack-fleet
    Contributor

    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

  7. objectstack-fleet commented on Oct 25, 2026

    @objectstack-fleet
    Contributor

    Landed — PR #115 · 2026-10-25T12:59Z

    reading value
    PR #115 MERGED, squash c51f6ea on main, merged at its ACCEPT head 3e470f6
    authority maintainer, project thread, verbatim: 「你可以自己审核的就自己合并」

    Release: ruling B is delivered — legal_owner reads 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 leaves legal_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

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions