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

  1. zhuangjianguo commented on Sep 10, 2026

    @zhuangjianguo
    Collaborator

    PM triage — needs-user-decision, and the decision box goes to 9

    Labelled, ⛔ not dispatched. Every one of the three options either edits a governed surface (DESIGN.md §04/§05) or widens a row-level grant to satisfy a check it was not written for. That is not a developer's call, and the card is right to say so rather than picking one.

    Both load-bearing readings independently confirmed

    claim reading
    clm_obligation derives access from its parent src/objects/obligation.object.ts:34 — sharingModel: 'controlled_by_parent'
    a requester may edit a contract only in draft / submitted src/sharing/_lifecycle.ts:44 — REQUESTER_EDITABLE_STATUSES = subset(CONTRACT_STATUSES, ['draft', 'submitted'], …)

    So the disjointness is structural, exactly as the card argues: an obligation exists only under a post-signature contract, a requester holds edit only before signature, and controlled_by_parent routes the child's update through the parent's edit. There is no state in which the two overlap. §04's U(本人负责) on clm_obligation is unreachable for 100% of rows.

    Why this one is worth the maintainer's attention rather than a shrug

    The card names the cost precisely and I want it preserved for the decision batch:

    A read-only to-do list is a worse demo than an empty one, because it looks like it works.

    That is the distinguishing feature. This is not a missing capability — it is a declared capability that renders, gets reminded about by F10, and refuses the one action the reminder asks for. explain even answers allowed: true, because the refusal happens at the master-record check below it, so the platform's own introspection endpoint agrees with §04 right up until the write.

    And it has just become reachable in practice: until #52 (in flight), every obligation was owned by the dev admin, which holds no clm_* set and never opened 我负责的履约 at all. #52 does not cause this — it is the first change that makes a requester-owned obligation exist for anyone to be refused on.

    What I am NOT doing here

    ⛔ No adjudication and no four-prism analysis on this card yet. The tier fuse (CONTRACT_REVIEW_TIER = 'claude-fable-5-1', compared exactly) is not met by this seat, so the director's four duties stay parked — 不裁不清标. This card joins the box and gets its full analysis when the maintainer calls the next decision batch.

    One note for whoever writes that analysis: the card's own recommendation splits on a question the maintainer has effectively already answered elsewhere — is 我负责的履约 a status board or a work queue? §05 orders that grid by due_date ascending and filters to unfinished, and F10's message is "Record progress, or mark it done once it is performed." Both read as a work queue. That points at option B, and option B is the one that keeps §04 and §05 as written — but B widens a row-level grant, which is the kind of thing that wants an explicit ruling rather than an inference.

    Attribution

    Found and written by the dev on #52 while measuring, not by this seat. It measured a REST refusal it did not need to measure to finish its own card, chased it to two declarations that are each individually correct, and stopped at the point where a person has to choose. That is the right shape for a card an agent should not resolve.


    Generated by Claude Code

  2. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    Contributor

    Evidence from the 17.7.0 browser pass · 2026-10-10T02:46Z — #87, finding 5 (screenshots 092–095 under qa/browser-test-17-7/screenshots/ on branch claude/issue-87-browser-test-17-7).

    Business Requester 1, My Obligations → "Provide the annual security attestation" → mark done:
    PATCH /api/v1/data/clm_obligation/-YpaMcuIEzJyRhnt {"status":"done"} → 403 "requires edit access to its master record (master 'clm_contract' not editable by this user (row-level security))"; the form says "You don't have permission to save this record".

    So this card's measurement holds on 17.7.0 in the browser, for an obligation the requester owns on an active contract: every requester obligation on an in-force contract is unreachable, as the card argued.


    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

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions