Skip to content

A requester cannot launch a contract — clm_requester has no read on sys_business_unit, and our_entity is required - #126

Merged
zhuangjianguo merged 1 commit into
mainfrom
claude/issue-124-requester-business-unit-read
Oct 25, 2026
Merged

zhuangjianguo merged 1 commit into
mainfrom
claude/issue-124-requester-business-unit-read

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #124

What changed

One line in src/profiles/requester.profile.ts: clm_requester grants object-level read on the platform's sys_business_unit (allowCreate/allowEdit/allowDelete: false), with a comment saying why it sits there.

  • clm_contract.our_entity has been a required lookup to sys_business_unit since our_entity is a lookup to the platform's sys_business_unit (kind company), seeded as data #118, and no CLM set granted read on it. member_default grants none either. So every member who is not an administrator was refused the launch dialog's "Our signing entity" picker, and the record page showed the entity's raw id.
  • The grant goes in clm_requester because every member holds that set through the everyone anchor (PR Every member holds clm_requester by default; the app declares its eleven capability tokens #122). The legal, finance and records sets inherit it that way. Measured below: explain attributes the legal and finance personas' read to clm_requester. So the other four sets are left unchanged.
  • 18.1.0 lets an app set grant read on a platform object. This set already does it for sys_comment, sys_activity, sys_attachment and sys_approval_request. The new line passes validate, lint and the boot without a diagnostic.
  • The grant does not make the set high-privilege. describeHighPrivilegeBits (@objectstack/spec 18.1.0, security/high-privilege.ts) flags only undeclared systemPermissions, viewAllRecords/modifyAllRecords, allowDelete/allowTransfer and allowExport. A read grant has none of these. Measured below: no security-anchor-high-privilege finding in lint or validate, no refusal in the boot log, and explain still reports clm_requester as the additive baseline.
  • The object's sharing baseline is unset, which means the system default public: rows are shared org-wide, as explain says. The read therefore reaches the organization's own units.

README needs no change: no paragraph describes the launch dialog's entity picker or a grant for it.

Measurements: 18.1.0, pnpm demo --port 3424, one throwaway local database

Before = origin/main 1264e02, after = this branch 19e8dac, same database for both. I created the personas the README way, as the dev admin:

  • POST /api/v1/auth/admin/create-user ×3 → 200: Business Requester 1 (no position), Legal Counsel 1, and Finance Controller 1.
  • POST /api/v1/data/sys_user_position → 201 for clm_legal_counsel and for clm_finance_controller.
  • POST /api/v1/auth/change-password ×3 → 200.
  • pnpm demo again, which handed the 43 / 34 contracts to the requester and the counsel.

Every reading below was taken signed in as the persona, never as the dev admin. Browser: Playwright with Chromium at /opt/pw-browsers/chromium.

GET /api/v1/data/sys_business_unit

Persona Before After
Business Requester 1 403 PERMISSION_DENIED (object sys_business_unit) 200, total 3: ARC-US Arcadia Holdings Inc. · ARC-EU Arcadia Europe GmbH · ARC-APAC Arcadia Asia Pacific Pte. Ltd., all kind: company, active: true
Legal Counsel 1 (clm_legal_counsel) 403 200, the same 3
Finance Controller 1 (clm_finance_controller) 403 200, the same 3

GET /api/v1/data/clm_contract?populate=our_entity answered the raw id before ("our_entity":"G8yvrH008o2ERCyS") for all three personas. After, it answers the expanded unit ({"code":"ARC-US","name":"Arcadia Holdings Inc.",…}).

Read only, measured as Business Requester 1 after the change:

  • POST /api/v1/data/sys_business_unit → 403 PERMISSION_DENIED.
  • PATCH /api/v1/data/sys_business_unit/{ARC-US} → 403.
  • security/explain on sys_business_unit: read allowed ("read on 'sys_business_unit' is granted by [clm_requester]"). create, update and delete are denied ("No resolved permission set grants …").

Launch dialog: "Our signing entity" picker (sidebar Launch a Contract)

  • Before, all three personas: the picker requested GET /api/v1/data/sys_business_unit?populate=parent_business_unit_id,manager_user_id&top=50&filter=["and",["kind","=","company"],["active","=",true]] → 403. The popover read "You do not have permission to perform this action. Contact your administrator if you need access." with a Retry button. The browser console logged one new 403.
  • After, all three personas: the same request → 200. The popover offered exactly the 3 company units (Arcadia Asia Pacific Pte. Ltd., Arcadia Holdings Inc., Arcadia Europe GmbH, each shown with "Kind: Company" and its parent). No new console error appeared.

Launch with an entity picked, then the record page (after)

Business Requester 1

  1. I picked Mutual Non-Disclosure Agreement, Aurora Systems and Arcadia Europe GmbH, then Confirm → POST /api/v1/actions/clm_contract/launch_contract 200.
  2. Contract details (title, governing law Germany, confidentiality 24): Submit → resume 200.
  3. First version: Submit → resume 200. That created NDA-2026-0021 (draft, owner Business Requester 1, our_entity → ARC-EU), and the dialog moved on to the version upload screen.
  4. Its record page reads "OUR SIGNING ENTITY | Arcadia Europe GmbH". The page made no sys_business_unit request that failed.

Legal Counsel 1

The same path with Arcadia Asia Pacific Pte. Ltd. created NDA-2026-0022 (draft, our_entity → ARC-APAC). Its record page reads "OUR SIGNING ENTITY | Arcadia Asia Pacific Pte. Ltd.".

Finance Controller 1

  • The picker answered 200 and the dialog took the pick.
  • Launching did not complete. On the First version screen the flow's create_contract node was refused: resume → 400, "Node 'create_contract' failed: create_record(clm_contract) failed: [Security] Field write denied: not permitted to edit [confidentiality_term_months, governing_law, status, summary] on 'clm_contract'".
  • That refusal is clm_finance's legal-field FLS, not this grant. See Acceptance notes. Per the persona rule: finance launch NOT MEASURED, refused by a permission check, no alternate route taken.

Record page of a seeded contract (AMD-2026-0001), read by all three personas

  • Before: the entity read G8yvrH008o2ERCyS (the raw id), GET /api/v1/data/sys_business_unit/G8yvrH008o2ERCyS → 403, and the console logged HTTP request failed … 403 [PERMISSION_DENIED]. The server logged Access denied: operation 'find' on object 'sys_business_unit' is not permitted for positions [org_member, everyone] and Failed to expand relationship field; retaining foreign key IDs {"field":"our_entity","reference":"sys_business_unit"}.
  • After: the entity reads Arcadia Holdings Inc. for all three personas. The page made no sys_business_unit 403 request, and the console has nothing new: the same pre-login 401, the same resource 404, and the same three pre-existing record.status predicate warnings as before.

security/explain and the boot

  • Explain for Business Requester 1, after: permission sets clm_requester, member_default, and clm_requester is "via additive baseline (ADR-0090 D5)", identical to before. Legal Counsel 1: clm_legal, clm_requester, member_default. Finance Controller 1: clm_finance, clm_requester, member_default. Both lists are the same as before.
  • Boot log: I compared the de-duplicated WARN/ERROR signatures (timestamps and ids stripped) of the second main boot and the branch boot on the same database. They are identical except the seeder's counters (updated 150 on the hand-over run, skipped 823 on the re-run) and the one Inline seed exceeded … budget timing line. In all three boot logs, high-privilege, security-anchor, refused and degraded each match 0 lines.
  • After the change, the server log names sys_business_unit in exactly two Access denied lines: the insert and the update of my two write probes above. It has no find denial and no Failed to expand line.

Gates: head 19e8dac, exit codes captured to files before any pipe

pnpm install         exit 0
pnpm validate        exit 0   ✓ Validation passed (867ms)   7 warnings, the pre-existing hierarchy-security note and six approval-approvers-may-resolve-empty notes
pnpm lint            exit 0   6 suggestion(s)
pnpm typecheck       exit 0   tsc --noEmit
pnpm lint:i18n-gate  exit 0   COVERAGE : 0 missing keys across 2 locale(s)

Acceptance notes

  • Finance cannot launch a contract (found here, not fixed). As Finance Controller 1, the intake's create_contract node is refused with Field write denied … [confidentiality_term_months, governing_law, status, summary].
    • Cause: clm_finance declares the legal fields and status read-only (finance.profile.ts, readOnly(Contract, [...CONTRACT_LEGAL_FIELDS, 'status', …])). clm_requester declares nothing on those fields, and only the sets that declare a field vote (see _grants.ts). So the finance lock wins on the create too, although the flow writes a brand-new draft.
    • On main this was hidden, because the picker's 403 stopped every non-admin member before that node. It is outside this card's file surface and is reported to the seat for a card.
  • Personas not measured: records and executive. The dispatch named the requester, one legal and one finance holder. The executive holds clm_requester alone, which is the requester's shape. The grant reaches the records set through the same baseline.
  • The picker's request also populates manager_user_id (a sys_user lookup). It answered 200 for all three personas, so this card needed no sys_user grant.
  • Two runs of the launch dialog were abandoned while I was learning its screens (one as the requester, one as the finance persona's refused run). Neither created a contract: the row is written only at the First version step.

🤖 Generated with Claude Code

https://claude.ai/code/session_017nzWFRUo2U7evMkPdVniAn

clm_contract.our_entity is a required lookup to sys_business_unit since
#118, and no CLM set granted read on that platform object, so a member who
is not an administrator was refused the launch dialog's "Our signing
entity" picker (403) and saw the entity's raw id on the record page.

clm_requester, which every member holds through the everyone anchor, now
grants object-level read on sys_business_unit — read only, no create,
edit or delete. A read grant is not a high-privilege bit, so the anchor
binding stays valid.

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

Copy link
Copy Markdown
Contributor Author

席位核对 — ACCEPT · head 19e8dac · 2026-10-25T14:44Z

check reading
shape draft → main, Fixes #124; assignee zhuangjianguo; mergeable clean
CI on the head both Validate check-runs success on 19e8dac = PR head.sha
scope 1 file (+11): a read-only sys_business_unit object grant in clm_requester, beside its other platform-object reads. Within the claim's surface. Nothing governed
the card measured as real personas (requester, legal counsel, finance controller), not the dev admin: GET sys_business_unit 403 → 200 (the 3 companies); picker refused → 3 options; requester and counsel launched with an entity picked and the record page names it; write probes stay 403
anchor clm_requester is still bound as the additive baseline (explain, before and after); no high-privilege, anchor or refusal line in the boot log; pnpm lint raises no security-anchor-high-privilege
not in this PR a finance controller still cannot launch: clm_finance's field locks on the legal fields and status (§13 Q1) also apply to the draft they create, so create_contract is refused (Field write denied). Unmasked by this fix, not caused by it; it is a design question, so the seat files it as its own card for the decision box

Landing: under the maintainer's 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

Development

Successfully merging this pull request may close these issues.

A requester cannot launch a contract — clm_requester has no read on sys_business_unit, and our_entity is required

2 participants