You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Decision] Legal and finance cannot edit a counterparty they do not own — §04 gives them update on clm_party, but its writes are owner-scoped #152
Ruled: 6607872904 · letter D · 2026-11-03T15:28:05Z
Filing class: ① product defect, needs a ruling (touches §04's clm_party row and sharing table) — reach: public door, measured on 18.1.0 by the #133 dev (os-dev-report on #133, maineace613). Reader: the maintainer through the decision box, then the hotclm lane.
Symptom
DESIGN.md §04 gives legal RCU on clm_party and finance RU on its bank information, and README calls finance "the only position that may edit bank details". At runtime neither can edit a counterparty they do not own:
Legal Counsel 1 PATCH /api/v1/data/clm_party/<seeded 峰顶翻译> → 403 You do not have access to this record;
Finance Controller 1 writing bank_name on a party it does not own → refused at the row layer.
Since #133 (ruling 6422701531) every requester can create a counterparty, so every new party is owned by a business requester, and the screen promises 「之后由法务维护它的风险标记和筛查结果」 — which legal cannot do on that party.
Why
clm_party's org-wide default is public_read: everyone reads, but writes are owner-scoped. No sharing rule on clm_party and no modifyAllRecords on any CLM set covers legal or finance.
What needs deciding
A — a §04 sharing rule on clm_party granting edit to the legal positions (and to the finance controller, whose field locks keep it to the bank fields). Same shape as the contract sharing rules; governed §04 table row added.
B — modifyAllRecords on clm_party for clm_legal and clm_admin (and finance), field locks unchanged. Broader: it also lifts row scope for delete if delete is ever granted.
C — accept: only a party's owner edits it; legal maintains risk only on parties it created.
The seat's reading: A, because it is the mechanism §04 already uses for contracts.
Dedupe: hotclm issues listed and grepped for clm_party, counterparty, 相对方, owner-scoped — #133 (create), #64 (different object), none this.
Filed by the repo:hotclm PM seat from the #133 dev report.
Ruling: batch #10 item 1 · letter D · maintainer 「同意」 2026-11-03T15:28:05Z
D — clm_party becomes public_read_write; the permission sets stay as they are. Not A (sharing rules on an object that has no stages), not B (super-user bypass), not C.
What the director checked before recording (hotclm main 76e766a at presentation, objectstack main):
clm_party declares sharingModel: 'public_read' (everyone reads, the owner writes) and no sharing rule names it; the only modifyAllRecords on it is the administrator's.
The platform defines public_read_write as "everyone can see and edit" and enforces it (plugin-security platform-ownership-policies.ts, #8023): it opens the row scope for update only, deletes stay owner-only, and object permissions plus field locks still gate. So after the change: legal edits every field of every party, finance edits only bank_name / bank_account, requesters and records cannot edit any party, their own included (allowEdit: false).
A counterparty is shared master data with no lifecycle; the staged sharing rules exist for the private contract, not for this object.
Execution after the ruling (lane):
src/objects/party.object.ts: sharingModel: 'public_read_write'. Nothing in src/profiles changes.
Measure with demo staff: legal counsel sets risk_flag on a party it did not create (200); finance writes bank_name on the same party (200) and name (403, field lock); a requester PATCHes the party it created (403, object permission); a non-owner DELETE stays refused.
§03 object map (clm_party [public_read] → [public_read_write]) and the §04 sentence on the public_read objects, on the governed DESIGN.md PR.
Rider: check clm_clause for the same shape (§04 gives legal clause RCU, the object is public_read, seeded rows are not legal's). Same finding, same fix, under this ruling.
{
"issue": 152,
"status": "done",
"branch": "claude/issue-152-party-public-rw",
"pr": "https://github.com/objectstack-ai/hotclm/pull/193",
"session": "session_017nzWFRUo2U7evMkPdVniAn",
"premise_still_valid": true,
"summary": "Ruling D is carried out. clm_party is now sharingModel public_read_write; nothing in src/profiles changed. The clm_clause rider had the same shape on main (counsel1's PATCH of a seeded clause was refused by row-level security), so it gets the same one-line change. Measured with zh-CN demo staff on 18.2.0, over the API and in the browser: legal now edits parties and clauses it does not own. Finance edits only the banking pair; its write to name is refused by the field lock. Requesters and records stay refused by object permission, their own party included. Every non-owner DELETE is still refused. The diff is two lines plus comments, in src/objects/party.object.ts and src/objects/clause.object.ts. The PR is a draft because the relay forces pr_create to draft; the actions file carried no draft key. No Claim comment was on #152 when this report was written; the file surface follows the round roster (party.object.ts sharingModel + clause rider).",
"tests": "Before (origin/main 7e7c2dc) and after (cf33339) on fresh .objectstack DBs, pnpm demo:zh --port 3162, each persona signed in as itself, every write read back as the same persona. Seeded rows were owned by the dev admin at reading time. API, main → branch: counsel1 PATCH risk_flag on requester1's party 403 (row-level: 您无权访问这条记录) → 200, read-back watch. Same on seeded 峰顶翻译: 403 → 200. finance PATCH bank_name on both parties 403 → 200, read-back holds. finance PATCH name 403 row-level → 403 'Field write denied: not permitted to edit [name]'. requester1 PATCH own party 403 object permission (您没有执行此操作的权限) → 403 same. records PATCH 403 → 403. counsel1, finance and requester1 DELETE 403 → 403, row still present. Clause rider: counsel1 PATCH position_note on a seeded clause 403 row-level → 200, read-back holds; finance, requester1 and records 403 → 403; counsel1 DELETE 403 → 403. Browser (Chromium /opt/pw-browsers/chromium, zh-CN): on main no persona gets 编辑 or 双击编辑 on a party or clause. On the branch, counsel1 edits seeded 北风物流's 风险标记 to 关注 via 编辑/更新: PATCH 200, toast 相对方“北风物流”已更新, header reads 关注 after reload. finance's edit form leaves only bank_name and bank_account open (13 locked) and the bank_name save is PATCH 200. counsel1 edits seeded clause 服务质量保证: PATCH 200, toast 条款“服务质量保证”已更新. requester1 and records get no 编辑 on the party, and finance gets none on the clause. Server log on main names 'row-level security' for the legal and finance refusals; on the branch only object-permission and field-lock refusals remain. Console errors are the same on both sides (401 pre-session probe on the login page, one 404 on counsel1's first page). Boot banner is the same on both sides (6 author-time suggestions, seed continues in background). No ablation: metadata-only change, no tested behaviour pinned in a test file.",
"gates": "Run under os-verify-lock (slot dev-152-gates) on the tree committed as cf33339, with no edit afterwards. Each exit was captured before any pipe: pnpm install=0; pnpm validate=0 (✓ Validation passed); pnpm lint=0 (0 warnings, 6 suggestions, same as main); pnpm typecheck=0; pnpm lint:i18n-gate=0 (0 missing keys across 2 locales); CLM_DEMO_SEED=1 pnpm validate=0 (✓ Validation passed). CI on PR #193 head cf33339: Validate completed success.",
"dogfood_battery": "n/a: hotclm, no ! in the PR title and no (narrowing) arm",
"mcp_calls": "0",
"api_writes": "2 relay writes: (1) fleet-write dispatch POST carrying pr_create, which created objectstack-ai/hotclm#193 and set the assignee zhuangjianguo; the read-back says 5314 bytes sent and stored, identical. (2) post-stamped comment on #152 (this report). git push of the branch is not a REST write.",
"open_questions": [],
"governed_text_proposals": {
"DESIGN.md §03 object map (code block)": "配置域 合同域 签后域\nclm_contract_type [public_read] clm_contract [private] clm_obligation [MD]\nclm_clause [public_read_write] clm_contract_version [MD] clm_payment_plan [MD]\nclm_approval_rule [public_read] clm_review [MD]\nclm_party [public_read_write] clm_deviation [MD]\n clm_signature [MD]",
"DESIGN.md §03 字段清单, the same fact in two row labels": "| `clm_clause` `public_read` | → | `clm_clause` `public_read_write` | ; | `clm_party` `public_read` | → | `clm_party` `public_read_write` |",
"DESIGN.md §04 OWD 与岗位, first sentence": "`clm_contract` `private`;五个子对象 `controlled_by_parent`;`clm_contract_type` 与 `clm_approval_rule` `public_read`,写权限仅管理岗;`clm_party` 与 `clm_clause` `public_read_write`(#152 裁决 D):行级对所有人开放修改,谁能改哪些字段仍由权限集与字段级安全决定(相对方:法务全部字段、财务仅银行信息;条款:仅法务),删除仍仅限所有者。"
},
"out_of_scope_findings": [
"carrier: 承接者:无 · noted, not filed: src/flows/contract-intake.flow.ts (clm_party note) says '(on a party legal does not own, writes are owner-scoped today, see #152)'. This PR makes that false. The file is outside this card's surface. Comment drift only, so it goes in PR #193 Acceptance notes and is not filed.",
"carrier: 承接者:无 · noted, not filed: src/profiles/requester.profile.ts says 'Configuration objects are public_read: everyone reads every row'. clm_clause is now public_read_write (the read half still holds). The ruling says src/profiles does not change. Comment drift only, so it goes in PR #193 Acceptance notes.",
"carrier: 承接者:无 · noted, not filed: no CLM staff persona holds allowDelete on clm_party or clm_clause (only clm_admin, which has modifyAllRecords), so every non-owner DELETE is refused at the object-permission layer. The platform's owner-only delete floor on public_read_write is confirmed from source (owdOpenWritesCoversOperation returns true only for update), but no persona can reach it.",
"carrier: 承接者:无 · observation, not filed: on a fresh demo DB the seeded config rows (party, clause, contract_type, approval_rule) carry owner_id null for about 3 minutes after boot, until a background 'Owner' update sets the dev admin. That happened on both sides. The before and the second after API pass ran with owner = dev admin."
],
"deviations": "The README Rung 1 line-manager step (set-user-manager for the requesters) was not run. No reading on this card touches the approval ladder, and common.md names no manager to set."
}
Claim: PM loop round 18
Ruling-ref: 6607872904 (batch #10 item 1 · letter D)
Session: session_017nzWFRUo2U7evMkPdVniAn
Account: zhuangjianguo (the seat's linked user as GET /user answers it; the card's assignee)
Branch: claude/issue-152-party-public-rw
Worktree: hotclm-issue-152
Domain: repo:hotclm (single-lane sister repo — no domain:*)
Seat: repo:hotclm#1
File surface: src/objects/party.object.ts (sharingModel) · src/objects/clause.object.ts (rider, only if it has the same shape) · README only if it states the old rule — 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 counterparty and clause master data (legal and finance edit what their grants allow, on rows they did not create) | none (platform #8023 already enforces public_read_write) | legal counsel and finance controllers
Thread-read: 6607872904
Premise: re-measured by the dev on origin/main7e7c2dc (@objectstack 18.2.0) with the declared demo staff
Serial constraints cleared: round 17 ended with an empty queue; #190 is dispatched in parallel on disjoint files (contract action, finance grant, demo reviews).
Release: legal can now edit any counterparty and any clause, not only the ones it created; finance can fill in a counterparty's bank details; requesters and records still cannot edit counterparties, and deletes stay with the owner. State: pm:dispatched removed, assignee cleared.
Activity
zhuangjianguo commentedon Nov 3, 2026
Ruling: batch #10 item 1 · letter D · maintainer 「同意」 2026-11-03T15:28:05Z
D —
clm_partybecomespublic_read_write; the permission sets stay as they are. Not A (sharing rules on an object that has no stages), not B (super-user bypass), not C.What the director checked before recording (hotclm main
76e766aat presentation, objectstack main):clm_partydeclaressharingModel: 'public_read'(everyone reads, the owner writes) and no sharing rule names it; the onlymodifyAllRecordson it is the administrator's.public_read_writeas "everyone can see and edit" and enforces it (plugin-securityplatform-ownership-policies.ts, #8023): it opens the row scope forupdateonly, deletes stay owner-only, and object permissions plus field locks still gate. So after the change: legal edits every field of every party, finance edits onlybank_name/bank_account, requesters and records cannot edit any party, their own included (allowEdit: false).Execution after the ruling (lane):
src/objects/party.object.ts:sharingModel: 'public_read_write'. Nothing insrc/profileschanges.risk_flagon a party it did not create (200); finance writesbank_nameon the same party (200) andname(403, field lock); a requester PATCHes the party it created (403, object permission); a non-owner DELETE stays refused.clm_party [public_read]→[public_read_write]) and the §04 sentence on the public_read objects, on the governed DESIGN.md PR.clm_clausefor the same shape (§04 gives legal clause RCU, the object ispublic_read, seeded rows are not legal's). Same finding, same fix, under this ruling.No platform card. Doc: https://claude.ai/artifact/QT2nrniJjuZMbwiT5v26Xq
Generated by Claude Code
objectstack-fleet commentedon Nov 3, 2026
os-dev-report
{ "issue": 152, "status": "done", "branch": "claude/issue-152-party-public-rw", "pr": "https://github.com/objectstack-ai/hotclm/pull/193", "session": "session_017nzWFRUo2U7evMkPdVniAn", "premise_still_valid": true, "summary": "Ruling D is carried out. clm_party is now sharingModel public_read_write; nothing in src/profiles changed. The clm_clause rider had the same shape on main (counsel1's PATCH of a seeded clause was refused by row-level security), so it gets the same one-line change. Measured with zh-CN demo staff on 18.2.0, over the API and in the browser: legal now edits parties and clauses it does not own. Finance edits only the banking pair; its write to name is refused by the field lock. Requesters and records stay refused by object permission, their own party included. Every non-owner DELETE is still refused. The diff is two lines plus comments, in src/objects/party.object.ts and src/objects/clause.object.ts. The PR is a draft because the relay forces pr_create to draft; the actions file carried no draft key. No Claim comment was on #152 when this report was written; the file surface follows the round roster (party.object.ts sharingModel + clause rider).", "tests": "Before (origin/main 7e7c2dc) and after (cf33339) on fresh .objectstack DBs, pnpm demo:zh --port 3162, each persona signed in as itself, every write read back as the same persona. Seeded rows were owned by the dev admin at reading time. API, main → branch: counsel1 PATCH risk_flag on requester1's party 403 (row-level: 您无权访问这条记录) → 200, read-back watch. Same on seeded 峰顶翻译: 403 → 200. finance PATCH bank_name on both parties 403 → 200, read-back holds. finance PATCH name 403 row-level → 403 'Field write denied: not permitted to edit [name]'. requester1 PATCH own party 403 object permission (您没有执行此操作的权限) → 403 same. records PATCH 403 → 403. counsel1, finance and requester1 DELETE 403 → 403, row still present. Clause rider: counsel1 PATCH position_note on a seeded clause 403 row-level → 200, read-back holds; finance, requester1 and records 403 → 403; counsel1 DELETE 403 → 403. Browser (Chromium /opt/pw-browsers/chromium, zh-CN): on main no persona gets 编辑 or 双击编辑 on a party or clause. On the branch, counsel1 edits seeded 北风物流's 风险标记 to 关注 via 编辑/更新: PATCH 200, toast 相对方“北风物流”已更新, header reads 关注 after reload. finance's edit form leaves only bank_name and bank_account open (13 locked) and the bank_name save is PATCH 200. counsel1 edits seeded clause 服务质量保证: PATCH 200, toast 条款“服务质量保证”已更新. requester1 and records get no 编辑 on the party, and finance gets none on the clause. Server log on main names 'row-level security' for the legal and finance refusals; on the branch only object-permission and field-lock refusals remain. Console errors are the same on both sides (401 pre-session probe on the login page, one 404 on counsel1's first page). Boot banner is the same on both sides (6 author-time suggestions, seed continues in background). No ablation: metadata-only change, no tested behaviour pinned in a test file.", "gates": "Run under os-verify-lock (slot dev-152-gates) on the tree committed as cf33339, with no edit afterwards. Each exit was captured before any pipe: pnpm install=0; pnpm validate=0 (✓ Validation passed); pnpm lint=0 (0 warnings, 6 suggestions, same as main); pnpm typecheck=0; pnpm lint:i18n-gate=0 (0 missing keys across 2 locales); CLM_DEMO_SEED=1 pnpm validate=0 (✓ Validation passed). CI on PR #193 head cf33339: Validate completed success.", "dogfood_battery": "n/a: hotclm, no ! in the PR title and no (narrowing) arm", "mcp_calls": "0", "api_writes": "2 relay writes: (1) fleet-write dispatch POST carrying pr_create, which created objectstack-ai/hotclm#193 and set the assignee zhuangjianguo; the read-back says 5314 bytes sent and stored, identical. (2) post-stamped comment on #152 (this report). git push of the branch is not a REST write.", "open_questions": [], "governed_text_proposals": { "DESIGN.md §03 object map (code block)": "配置域 合同域 签后域\nclm_contract_type [public_read] clm_contract [private] clm_obligation [MD]\nclm_clause [public_read_write] clm_contract_version [MD] clm_payment_plan [MD]\nclm_approval_rule [public_read] clm_review [MD]\nclm_party [public_read_write] clm_deviation [MD]\n clm_signature [MD]", "DESIGN.md §03 字段清单, the same fact in two row labels": "| `clm_clause` `public_read` | → | `clm_clause` `public_read_write` | ; | `clm_party` `public_read` | → | `clm_party` `public_read_write` |", "DESIGN.md §04 OWD 与岗位, first sentence": "`clm_contract` `private`;五个子对象 `controlled_by_parent`;`clm_contract_type` 与 `clm_approval_rule` `public_read`,写权限仅管理岗;`clm_party` 与 `clm_clause` `public_read_write`(#152 裁决 D):行级对所有人开放修改,谁能改哪些字段仍由权限集与字段级安全决定(相对方:法务全部字段、财务仅银行信息;条款:仅法务),删除仍仅限所有者。" }, "out_of_scope_findings": [ "carrier: 承接者:无 · noted, not filed: src/flows/contract-intake.flow.ts (clm_party note) says '(on a party legal does not own, writes are owner-scoped today, see #152)'. This PR makes that false. The file is outside this card's surface. Comment drift only, so it goes in PR #193 Acceptance notes and is not filed.", "carrier: 承接者:无 · noted, not filed: src/profiles/requester.profile.ts says 'Configuration objects are public_read: everyone reads every row'. clm_clause is now public_read_write (the read half still holds). The ruling says src/profiles does not change. Comment drift only, so it goes in PR #193 Acceptance notes.", "carrier: 承接者:无 · noted, not filed: no CLM staff persona holds allowDelete on clm_party or clm_clause (only clm_admin, which has modifyAllRecords), so every non-owner DELETE is refused at the object-permission layer. The platform's owner-only delete floor on public_read_write is confirmed from source (owdOpenWritesCoversOperation returns true only for update), but no persona can reach it.", "carrier: 承接者:无 · observation, not filed: on a fresh demo DB the seeded config rows (party, clause, contract_type, approval_rule) carry owner_id null for about 3 minutes after boot, until a background 'Owner' update sets the dev admin. That happened on both sides. The before and the second after API pass ran with owner = dev admin." ], "deviations": "The README Rung 1 line-manager step (set-user-manager for the requesters) was not run. No reading on this card touches the approval ladder, and common.md names no manager to set." }objectstack-fleet commentedon Nov 3, 2026
Claim: PM loop round 18
Ruling-ref: 6607872904 (batch #10 item 1 · letter D)
Session:
session_017nzWFRUo2U7evMkPdVniAnAccount:
zhuangjianguo(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-152-party-public-rwWorktree:
hotclm-issue-152Domain:
repo:hotclm(single-lane sister repo — nodomain:*)Seat:
repo:hotclm#1File surface:
src/objects/party.object.ts(sharingModel) ·src/objects/clause.object.ts(rider, only if it has the same shape) · README only if it states the old rule — 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 counterparty and clause master data (legal and finance edit what their grants allow, on rows they did not create) | none (platform #8023 already enforces public_read_write) | legal counsel and finance controllersThread-read: 6607872904
Premise: re-measured by the dev on
origin/main7e7c2dc(@objectstack 18.2.0) with the declared demo staffSerial constraints cleared: round 17 ended with an empty queue; #190 is dispatched in parallel on disjoint files (contract action, finance grant, demo reviews).
Generated by Claude Code
Comments: party and clause rows are public_read_write (#152)
DESIGN.md §03/§04: clm_party and clm_clause are public_read_write (#152)
objectstack-fleet commentedon Nov 3, 2026
Ruling-ref: 6607872904 (batch #10 item 1 · letter D)
Landed — PR #193 · 2026-11-03
MERGED(squash onmain), merged at its ACCEPT head8ab6300;Fixes #152closes this cardRelease: legal can now edit any counterparty and any clause, not only the ones it created; finance can fill in a counterparty's bank details; requesters and records still cannot edit counterparties, and deletes stay with the owner. State:
pm:dispatchedremoved, assignee cleared.Generated by Claude Code