Skip to content

DESIGN.md §04 gives clm_requester update on its own obligations, and controlled_by_parent refuses it for all 200 — 我负责的履约 is read-only for the audience it is built for #64

Description

@claude

Found while measuring #52, which deals clm_obligation.owner to real accounts for the first time. ⛔ Not introduced by that card — it applies to any requester-owned obligation, and #52 is simply the first change that makes one exist.

The contract

DESIGN.md §04, permission matrix, verbatim:

对象 clm_requester clm_legal clm_finance clm_records clm_admin
clm_obligation RU(本人负责) RCU R R RCUD

U(本人负责) — a requester updates the obligations they are accountable for. §05 puts that list in the 我的合同 group every employee reaches (我负责的履约), and F10 reminds the owner to "Record progress, or mark it done once it is performed."

Measured: the U never arrives, for every row that exists

Clean database, pnpm demo on port 3152, README operator setup performed plus the clm_requester grant #11 is about, re-seeded so the upserts hand the rows over. Signed in as Business Requester 3 over REST:

obligation : Submit the quarterly service report | status pending | owner == me: true
its parent : NDA-2026-0009                       | status active  | owner_id == me: true

POST /api/v1/security/explain {object: clm_obligation, operation: update}  ->  allowed: true

PATCH /api/v1/data/clm_obligation/OBLIGATION_ID {"status":"in_progress"}  ->  403
  {"error":"[Security] Access denied: update on 'clm_obligation' requires edit access to
    its master record (master 'clm_contract' not editable by this user (row-level security))",
   "code":"PERMISSION_DENIED","object":"clm_obligation"}

The account owns the obligation and owns its parent contract, and the object-level explain says yes. The write is still refused.

Why it can never succeed

Two declarations that are each correct alone:

  • clm_obligation is sharingModel: 'controlled_by_parent' (§04 "五个子对象 controlled_by_parent", ADR-0055) — so update on a child requires edit on the master clm_contract row, whatever the child's own row scope says;
  • clm_requester's contract edit window is REQUESTER_EDITABLE_STATUSES = ['draft', 'submitted'] (§04 "本人发起,draft/submitted 可改", src/sharing/_lifecycle.ts).

An obligation is a post-signature commitment: plan-children.ts hangs every one of the 200 off a contract in active / expired / terminated, and the product itself only creates them at F9 (contract_activate). So the two windows are disjoint by construction — there is no state in which a requester holds edit on the parent of an obligation. §04's U(本人负责) on clm_obligation is unreachable for 100% of rows, today and by design.

clm_legal is unaffected and was measured working: Legal Counsel 2 PATCHed one of its own obligations to in_progress and got 200, because contract_legal_all grants edit on every contract.

What this costs

我负责的履约 renders for a requester (measured: 18 open rows for Business Requester 3, navigation groups My Contracts and Analytics), F10's reminder reaches them, and every action the reminder asks for is refused. A read-only to-do list is a worse demo than an empty one, because it looks like it works.

What it is not

⛔ Not a platform bug. controlled_by_parent is doing exactly what ADR-0055 says. The disagreement is between two things this repository declares about itself.

Options, none of which a developer agent should pick

  • A. Change §04: clm_obligation for clm_requester becomes R(本人负责), and the reminder wording, my_obligations and F10's message stop implying the owner can act. Cheapest; concedes that only legal, finance and records ever close an obligation, which contradicts §05's placement of 我负责的履约 in the 我的合同 group.
  • B. Give clm_requester a narrow contract-edit grant on executed contracts they own, scoped by FLS to nothing — enough to satisfy the master check. Keeps §04 and §05 as written; widens a row-level grant for a reason that reads as a workaround, and needs the FLS surface listed field by field.
  • C. Change clm_obligation off controlled_by_parent to private with its own sharing rules. Honours both §04 rows literally; contradicts §04's own OWD sentence and ADR-0055's reason for deriving child access from the parent, and every other child object would ask why not too.

Recommendation: A if 我负责的履约 is a status board, B if it is a work queue. §05's 履约 grid (due_date 升序, unfinished only) and F10's "mark it done once it is performed" both read as a work queue, so B is what the rest of the design already assumes — but the wording that would have to change is §04/§05, which is a governed surface.

Related

#52 (where this was measured) · #11 (the other half of why a requester holds nothing by default) · #28 · #39 / PR #42 (F10 itself, working as built)


Generated by Claude Code

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions