Skip to content

A requester updates the obligations they own: the accountable person is the platform owner owner_id - #205

Merged
zhuangjianguo merged 2 commits into
mainfrom
claude/issue-64-owner-id
Nov 3, 2026
Merged

zhuangjianguo merged 2 commits into
mainfrom
claude/issue-64-owner-id

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #64

Ruling-ref: 6617224478 (batch #12 item 1, letter A): the person accountable for an obligation is its platform owner owner_id, and clm_obligation.owner is retired. Platform side: objectstack-ai/objectstack#26591, answered through PR #26658 in 18.3.0 (only owner_id, one owner concept).

What changed

Ruling step Where Change
1 src/objects/obligation.object.ts owner: Field.user removed. owner_id is declared the way clm_contract.owner_id is (Field.lookup('sys_user'), system: true, label Owner / 负责人, group obligation). highlightFields and relatedListColumns use owner_id. ownerMayEdit: true stays. The comment that said the platform owner is not the accountable person is rewritten.
2 src/profiles/requester.profile.ts obligation_requester_own: owner_id == current_user.id.
2 src/views/obligation.view.ts my_obligations filters on owner_id. The all_obligations column is owner_id.
2 src/flows/obligation-due.flow.ts F10's three stages select owner_id and send to obligation.owner_id.
2 src/datasets/obligation.dataset.ts + src/translations/{en,zh-CN}/app.ts The obligation_metrics dimension is now owner_id (name and field).
2 src/data/post-signature.seed.ts, src/data/keys.ts The seed writes the accountable person into owner_id as a system write. obligationOwnerOf never returns null.
2 src/objects/contract.hook.ts F9's renewal obligation gets owner_id = the contract's owner_id. The hook already read that value.
3 src/profiles/legal.profile.ts, src/profiles/admin.profile.ts allowTransfer: true on clm_obligation, so legal and the CLM admin can reassign an obligation. In admin.profile.ts only the clm_obligation entry changed; the clm_contract entry and the header are #196's (PR #204). This branch is rebased on it.
3 src/profiles/requester.profile.ts readOnly(Obligation, ['contract', 'due_date']).
3 src/profiles/legal.profile.ts open(Obligation, ['contract', 'due_date']). Legal also holds clm_requester, and only sets that declare a field get a vote on it (_grants.ts). Without this line legal would pick up the requester's lock. The admin needs no such line: modifyAllRecords on clm_obligation votes on every field.
4 src/data/plan-children.ts The one deliberately unowned obligation, and the leaveUnassigned machinery that kept it, are removed. A comment says why: owner_id is never empty, so F10 has no "nobody to tell" row. The shared sweep's edge is still exercised by F3 (UNASSIGNED_IN_REVIEW).
carry src/translations/{en,zh-CN}/objects.lifecycle.ts The retired owner key moves to owner_id. The label is kept (Owner / 负责人). The help text drops "empty means unassigned" and now says legal or a CLM administrator can reassign.
carry README.md The "one obligation is deliberately left with no owner" paragraph is replaced. Persona table: requesters now hold 144 obligations, 50 / 54 / 40, because the formerly unowned row is now Business Requester 2's. A reassigned obligation owner is listed among the columns a re-run resets.

Evidence: 18.3.0, pnpm demo:zh on port 3168, a throwaway DB each run, declared personas (password admin123), REST signed in as each persona

BEFORE = origin/main b38df5a. AFTER = 2ceb663 (pre-rebase). FINAL = 56a96ec, rebased on 7e6017a, read again in full.

# Who / what BEFORE AFTER and FINAL
census owner column of the 200 obligations, admin read owner: BR1 50 · BR2 53 · BR3 40 · LC1 29 · LC2 27 · empty 1. owner_id: Dev Admin on all of them (199, plus one I had just reassigned) owner_id: BR1 50 · BR2 54 · BR3 40 · LC1 29 · LC2 27 · empty 0
我负责的履约 requester3, the view's filter 18 rows 18 rows
1 requester3 PATCH own obligation (提交用量报表, MSA-2026-0015 active) {status: in_progress} 403 requires edit access to its master record 200
1b / 1c same row, {notes} then {status: done} 403 / 403 200 / 200, completed_at stamped
1d same row, done → pending 403 (master gate) 422 INVALID_STATE: the state machine is now reached
2 requester3 PATCH an obligation Legal Counsel 2 owns, on requester3's own contract 403 403 You do not have access to this record
3a requester3 PATCH own {contract: another active contract of theirs} 403 403 requires edit access to its master record
3b requester3 PATCH own {due_date} 403 403 Field write denied: not permitted to edit [due_date]
3c requester3 PATCH own {owner_id: requester1} 403 403 changing record ownership requires the transfer grant
4 / 4b Legal Counsel 2: own {status} / {due_date} 200 / 200 200 / 200 (the open() line holds)
4c Legal Counsel 2 reassigns requester3's obligation to requester1 403 requires the transfer grant 200, re-read owner_id = Business Requester 1
5 / 5b CLM admin reassigns it back / PATCH {due_date} 200 / not read 200 / 200 (FINAL is after #204 removed modifyAllRecords from the admin's contract entry)
explain requester3 security/explain clm_obligation update allowed: true allowed: true

F10 recipients were read on AFTER, with an uncommitted cron: '* * * * *' override on obligation_due that is now reverted (git diff HEAD empty, override string count 0). Legal Counsel 1 re-dated two obligations into the today and arrears windows. Receipts were read signed in as each persona:

23:06/23:07/23:08 clm_obligation_due_soon "更新保险凭证" owner_id=Legal Counsel 2 -> ["Legal Counsel 2/inbox/delivered"] MATCH
23:06/23:07       clm_obligation_due_soon "提交用量报表" owner_id=Business Requester 3 -> ["Business Requester 3/inbox/delivered"] MATCH
23:08             clm_obligation_due_today "提交季度服务报告" owner_id=Business Requester 1 -> ["Business Requester 1/inbox/delivered"] MATCH
23:08             clm_obligation_overdue "提交实施方案" owner_id=Business Requester 2 -> ["Business Requester 2/inbox/delivered"] MATCH (status moved to overdue)
total=7 match=7 mismatch=0

The 23:08 week stage no longer selected 提交用量报表, because requester3 had marked it done at step 1c.

F9 on AFTER: Legal Counsel 1 completed NDA-2026-0005's signature round, F7 filed the final version, and Legal Counsel 1 PATCHed the contract {status: active} → 200. The renewal obligation "Renewal decision: 双向保密协议 — 子午线零售集团" has owner_id = Business Requester 2, the contract's owner_id, not the acting lawyer.

Browser (Chromium at /opt/pw-browsers/chromium, zh-CN, signed in as each persona):

  • BEFORE, requester3: in the 我负责的履约 drawer for 提交用量报表, 负责人 showed twice, as Business Requester 3 (the owner field) and as Dev Admin (owner_id). There was no 编辑, and the menu offered only 添加到收藏, 分享, 整页打开.
  • AFTER, requester3: the drawer for 提供年度安全声明 shows one 负责人, Business Requester 3, and has 编辑. In the form, 合同 and 到期日 render disabled. Changing 状态 to 进行中, adding a note and pressing 更新 sent PATCH /api/v1/data/clm_obligation/keqFO3ERadREDNnl {"status":"in_progress","notes":…} -> 200, and the toast read 履约义务“提供年度安全声明”已更新. On the full record page requester3 gets no edit control beside 负责人.
  • AFTER, Legal Counsel 2: in the form, 合同 and 到期日 are editable, and the status edit PATCHed 200. On the full record page, inline edit of 负责人 (picker → Business Requester 1 → 保存) sent PATCH … {"owner_id":"…"} -> 200, and the header then read 负责人 Business Requester 1.
  • Console: only the two login-page lines, the same on both runs ("Failed to load resource" 404 and 401). No boot banner on these runs reported degraded capabilities or no such table.

Gates (FINAL head 56a96ec, each exit code captured before any pipe)

pnpm install                      exit 0
pnpm validate                     exit 0
pnpm lint                         exit 0   6 suggestion(s), 0 warnings (the obligation_requester_own warning is gone; on b38df5a it was 18 → 17 before #204 took the other 17)
pnpm typecheck                    exit 0
pnpm lint:i18n-gate               exit 0   COVERAGE : 0 missing keys across 2 locale(s)
CLM_DEMO_SEED=1 pnpm validate     exit 0

Acceptance notes

  • src/flows/_daily-sweep.ts, in the doc comment on notifyRecipients (about line 242), still cites "an obligation's owner is explicitly allowed to be empty" as one reason hasRecipient exists. That file is outside this card's claimed surface, so I left it. The code is unaffected, because F3's legal_owner still needs the edge. Suggested wording: drop the obligation clause.
  • The F10 email template descriptions say "Sent to the obligation owner." That is still accurate (the owner is now owner_id), so they are unchanged.
  • F10 has no "nobody to tell" row now. On a fresh corpus its stages select whatever the draw puts in each window: the week stage selected 2 rows on this run, and today and arrears selected none until the measurement re-dated rows into them.
  • Governed text is not touched. DESIGN.md §03 (line 111 owner lookup user) and §04 ride the seat's design-doc PR Sync the governed design text with the rulings landed on 2026-10-25 (#96 #100 #11 #20 #38 #82) #120, as the ruling's step 6 says.

Generated by Claude Code

🤖 Generated with Claude Code

https://claude.ai/code/session_017nzWFRUo2U7evMkPdVniAn

clm_obligation.owner is retired (ruling 6617224478 on #64, letter A): the
person accountable for an obligation is its platform owner, owner_id, the
one column the controlled_by_parent ownerMayEdit opt-in admits an update on
without edit on the contract. obligation_requester_own, my_obligations,
F10's recipients, the obligation dataset, the seed and F9's renewal
obligation now all key on owner_id.

Legal and the CLM administrator hold allowTransfer on clm_obligation, so
they can hand an obligation to someone else; a requester cannot, and holds
contract and due_date read-only (legal opens both again in its own set).
The demo's one deliberately unowned obligation goes with the column.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017nzWFRUo2U7evMkPdVniAn
…quester

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017nzWFRUo2U7evMkPdVniAn
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对 — ACCEPT · head 56a96ec · 2026-11-03T23:20Z

check reading
ruling batch 12 item 1, letter A (6617224478): the accountable person is the platform owner owner_id; clm_obligation.owner retired
shape draft → main, Fixes #64; rebased on 7e6017a (PR #204), mergeable clean
CI on the head both Validate check-runs success on 56a96ec = PR head.sha
scope 16 files, all inside the claim: owner removed and owner_id declared like the contract's; obligation_requester_own, my_obligations, F10's three stages, the obligation dataset, the seeds and F9's renewal obligation all read owner_id; legal and admin hold allowTransfer; the requester holds readOnly(Obligation, ['contract','due_date']), and legal re-opens those two because it also holds the requester set. Lint on main goes 1 → 0 warnings
measured (18.3.0, declared personas) requester3 on own obligation: status / notes / done 200 (before: 403); someone else's 403; contract 403, due_date 403 「Field write denied」, owner_id 403 (transfer). Legal Counsel 2: status, due_date, reassign 200; admin reassign 200. F10: 7 of 7 notifications reach the obligation's owner_id. F9: the renewal obligation's owner_id = the contract owner. Browser: one 负责人 and an 编辑 button for the requester
residuals the deliberately unowned demo obligation is gone (owner_id is never empty), as ruled. A stale clause in _daily-sweep.ts's notifyRecipients comment still mentions an empty obligation owner; it is left for the next card touching that file. §03 / §04 text is carried on PR #120

Landing: under the maintainer's ruling (batch 12, 「同意」 2026-11-03T22:34Z) and merge authorization (project thread: 「你可以自己审核的就自己合并」).


Generated by Claude Code

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

Labels

None yet

Projects

None yet

2 participants