Skip to content

[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

Closed

Description

@objectstack-fleet

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, main eace613). 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.

Activity

  1. zhuangjianguo commented on Nov 3, 2026

    @zhuangjianguo
    Collaborator

    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):

    1. src/objects/party.object.ts: sharingModel: 'public_read_write'. Nothing in src/profiles changes.
    2. 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.
    3. §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.
    4. 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.

    No platform card. Doc: https://claude.ai/artifact/QT2nrniJjuZMbwiT5v26Xq


    Generated by Claude Code

  2. objectstack-fleet commented on Nov 3, 2026

    @objectstack-fleet
    ContributorAuthor

    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."
    }
  3. objectstack-fleet commented on Nov 3, 2026

    @objectstack-fleet
    ContributorAuthor

    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/main 7e7c2dc (@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).


    Generated by Claude Code

  4. objectstack-fleet commented on Nov 3, 2026

    @objectstack-fleet
    ContributorAuthor

    Ruling-ref: 6607872904 (batch #10 item 1 · letter D)

    Landed — PR #193 · 2026-11-03

    reading value
    PR #193 MERGED (squash on main), merged at its ACCEPT head 8ab6300; Fixes #152 closes this card
    authority maintainer, project thread, verbatim: 「你可以自己审核的就自己合并」

    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.


    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