Skip to content

The launch wizard offers every business requester a "new counterparty" step that always fails — create_party is refused for clm_requester - #154

Merged
zhuangjianguo merged 2 commits into
mainfrom
claude/issue-133-requester-creates-party
Oct 26, 2026
Merged

zhuangjianguo merged 2 commits into
mainfrom
claude/issue-133-requester-creates-party

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #133

What changed

Ruling A (comment 6422701531), with the file surface the seat widened after the first measurement (claim amendment 6423291471).

  • clm_requester may create a counterparty, not edit one. On clm_party, allowCreate is now true and allowEdit stays false (src/profiles/requester.profile.ts).
  • One list of assessed party fields, in src/profiles/_grants.ts: PARTY_ASSESSED_FIELDS = [risk_flag, risk_note, screening_status, is_active, bank_name].
  • clm_finance opens bank_name and bank_account. This is §04 finance's "RU(银行信息)".
  • The intake flow's header comment no longer says a requester is refused on clm_party. Comment only: the create_party node writes name, party_kind, registration_no, contact_name and contact_email, none of which is locked.

Why legal, admin and finance had to change too

On 18.1.0, a field rule only counts from the permission sets that name that field (getFieldPermissions, plugin-security). A set that says nothing about a field does not override a set that locks it. Every member holds clm_requester through the everyone anchor.

So the requester's read-only lines alone also locked those fields for every other set. Measured on the first commit (029d8c6), which changed only the requester set:

  • Legal Counsel 1 was refused risk_flag, risk_note, screening_status, is_active and bank_name on a party she owns. Each answered 403 "Field write denied"; on main the same write answered 200.
  • The same mechanism already masked bank_account from finance on main. Explain for Finance Controller 1 read "masked from responses: [bank_account]".

The open lines follow the discipline _grants.ts and admin.profile.ts already document: a set that must edit what another co-held set locks says so explicitly.

Anchor safety

allowCreate is not a high-privilege bit (describeHighPrivilegeBits flags only undeclared systemPermissions, view/modify all, delete/transfer and export).

  • lint and both validate runs have 0 security-anchor-high-privilege lines.
  • The boot logs no refusal.
  • Explain still lists clm_requester "via additive baseline (ADR-0090 D5)".

Measurements: 18.1.0, pnpm demo:zh --port 3133, throwaway local databases

Before = main eace613; after = this branch d21ffc9 (and 029d8c6 for the requester-only step).

Personas, made the README way as the dev admin:

  • POST /api/v1/auth/admin/create-user → 200 for Business Requester 1, Legal Counsel 1, Finance Controller 1 and CLM Admin 1, each with mustChangePassword: true.
  • POST /api/v1/data/sys_user_position → 201 for clm_legal_counsel, clm_finance_controller and clm_admin.
  • POST /api/v1/auth/change-password → 200 for each persona.
  • Then a second pnpm demo:zh on the same database.

Every reading below was taken as the persona. Browser: Playwright with Chromium at /opt/pw-browsers/chromium, locale zh-CN, sidebar 发起合同.

Business Requester 1: launch with a new counterparty

Steps: pick 双向保密协议 and 阿卡迪亚欧洲有限公司, leave 相对方 empty, press 确认. Then fill 合同信息 and press 提交, then fill 新建相对方 and press 提交.

  • Before: resume answered 400. The dialog read "Node 'create_party' failed: create_record(clm_party) failed: You do not have permission to perform this action." No party and no contract were written.

  • After: resume answered 200 → 首个版本 → 200 → the 上传首个版本 form. It created:

    • party 二轮相对方戊: owner Business Requester 1, risk_flag none, screening_status not_screened, is_active true;
    • contract NDA-2026-0021: draft, linked to that party.

    The first after-run, on 029d8c6, created 后测相对方丙 and NDA-2026-0022 the same way. The page made no request that answered 400 or higher, other than the static-resource 404 also present before.

Business Requester 1: API, after

  • POST clm_party with name, kind, registration no., contact name and contact email → 201. The party is born none / not_screened / active.
  • POST naming one locked field at a time → 403 "Field write denied: not permitted to edit [F]" for F = risk_flag, risk_note, screening_status, is_active and bank_name. The hidden bank_account and contact_phone answer the same way.
  • PATCH contact_name on a party BR1 owns, on legal's party and on a seeded party → 403 PERMISSION_DENIED, from the object layer (no edit).
  • A duplicate registration_no → 409 UNIQUE_VIOLATION.

Legal Counsel 1

  • The launch path is unchanged: before NDA-2026-0021, after NDA-2026-0022, each with a new party and every resume 200.
  • PATCH on her own party, each of the five fields alone and all five together → 200. On 029d8c6 each was 403.
  • POST of a new party with risk_flag: watch and screening_status: hit → 201.

CLM Admin 1 (clm_admin position)

  • PATCH of all five fields on a party it owns → 200.
  • It flagged that party blocked, and the intake guard then refused it (below).

Finance Controller 1

Blocked-counterparty guard, as Business Requester 1

Headless POST /api/v1/actions/clm_contract/launch_contract naming 德尔塔岭控股 (seeded blocked):

  • before → 400 FLOW_FAILED at refuse_blocked;
  • after → the same.

The party CLM Admin 1 flagged blocked is refused the same way.

Dev admin: an expectation not met

The dev admin PATCH risk_flag → 403 "Field write denied", both before the widening and after it.

Explain lists its sets as clm_requester, admin_full_access, organization_admin_no_bypass and member_default. It holds no clm_admin, so no set it holds opens the field. This is the same mechanism that already masks bank_account and contact_phone from it on main.

A CLM administrator in the README's sense holds the clm_admin position, and that account is measured at 200 above. See Acceptance notes.

security/explain and the boot

  • Business Requester 1: create on clm_party is allowed, "granted by [clm_requester]"; update is denied.
  • The permission sets are clm_requester and member_default, both "via additive baseline (ADR-0090 D5)", the same as before.

Boot. I compared the de-duplicated WARN/ERROR signatures of a second baseline boot with this branch's second boot. For the baseline boot, requester.profile.ts was checked out at eace613, then restored from HEAD: git diff HEAD was empty and the blob hash matched.

The signatures are identical except:

  • the seeder's inline-budget timing line, in place of the inline "830 ok" summary (the seed finished in the background);
  • three clm_party update denials, which are my own probes above.

high-privilege, security-anchor, refus, degraded and no such table each match 0 lines in both logs.

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

pnpm install                  exit 0
pnpm validate                 exit 0   ✓ Validation passed   7 warnings (6 approval-approvers-may-resolve-empty + hierarchy-security note), the same set as main
pnpm lint                     exit 0   6 suggestion(s), 0 warnings
pnpm typecheck                exit 0
pnpm lint:i18n-gate           exit 0   COVERAGE : 0 missing keys across 2 locale(s)
CLM_DEMO_SEED=1 pnpm validate exit 0   ✓ Validation passed

For the seat: proposed DESIGN.md §04 wording (governed, rides PR #120)

  • §04 matrix, clm_party row, clm_requester cell: R(不含银行与电话) → RC(不含银行账号与电话;风险标记、风险说明、筛查结果、启用状态与开户行只读)
  • §04 字段级安全, a new row: | clm_party.risk_flag · risk_note · screening_status · is_active · bank_name | clm_requester 只读 | 由法务评定或由筛查连接器写入;发起人新建的相对方生来未标记、未筛查 |
  • Why the wording differs from the ruling's: the ruling's own text was RC(不含银行与电话;风险与核验字段只读). 核验 maps to screening_status (筛查结果), and bank_name (开户行) is read-only, not hidden, so the cell names it.
  • §03: its field list still names verified_at in the clm_party row. It should read screening_status.

Acceptance notes

  • Who can edit a requester-created party ([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). Legal, finance and clm_admin can edit only the clm_party rows they own: public_read writes are owner-scoped and no sharing rule covers the object. This already happens on main (Legal Counsel 1 PATCH of a seeded party → 403).
  • Platform admin without clm_admin. The dev admin, or any platform admin who does not hold the clm_admin position, cannot write the five assessed fields. On main it already cannot read bank_account or contact_phone. Opening them for platform-only admins has no app-level spelling: their sets are the platform's. The README's administrator holds clm_admin.
  • Create reaches every position. Granting create in the everyone-bound set gives create on clm_party to every position. Finance's own field locks stop it in practice (measured: 403). Records can create, inferred from its grants and not measured.
  • Discarded probes. Four probes PATCHed a party id that was never created (404). They wrote nothing.

🤖 Generated with Claude Code

https://claude.ai/code/session_017nzWFRUo2U7evMkPdVniAn

…d-only

The intake flow offers every launcher the inline "new counterparty" step and
runs its writes as the launching user, but clm_requester had no create on
clm_party, so the step always failed for a plain member. Ruling A on #133:
clm_requester gets allowCreate on clm_party (allowEdit stays false), and the
risk, screening and bank-name fields are read-only for it. The ruling's
verified_at is screening_status since the global-first revision.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017nzWFRUo2U7evMkPdVniAn
…uester holds read-only

Only the permission sets that declare a field vote on it, and every member
holds clm_requester, so its read-only lock on the assessed party fields bound
legal and admin too (measured: Legal Counsel 1 refused risk_flag on her own
party). One exported list, PARTY_ASSESSED_FIELDS, is read-only for
clm_requester and opened by clm_legal and clm_admin; clm_finance opens the
banking pair, which also ends the bank_account mask finance carried by the
same mechanism. The intake flow's header no longer says a requester is
refused on clm_party.

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 d21ffc9 · 2026-10-26T18:01Z

check reading
shape draft → main, Fixes #133; assignee zhuangjianguo; mergeable clean
CI on the head both Validate check-runs success on d21ffc9 = PR head.sha
scope 6 files (+54/−10): requester.profile.ts (clm_party create on; the five assessed fields read-only), _grants.ts (PARTY_ASSESSED_FIELDS), legal / admin (open the five), finance (opens bank_name · bank_account), and the intake flow header (comment only). This is within the claim's surface as the seat widened it (comment 6423291471). Nothing governed; lint 0 warnings; boot shows 0 security-anchor / high-privilege lines
the ruling 6422701531 letter A — delivered, measured with README personas: Business Requester 1 adds a counterparty from 发起合同 (before: 400 create_party permission). Naming any locked field → 403, 7 of 7. Editing any party → 403, 3 of 3. Legal Counsel 1 and CLM Admin 1 write all five fields (200). The blocked-party guard still refuses
side effect, kept finance now reads and writes bank_account again. It was hidden from finance on main, because clm_requester hid it and finance's set never declared it. This matches §04 「RU(银行信息)」
seat call — dev admin A, accept. The dev admin without the clm_admin position is refused the five fields (403 FLS), the same way it is already refused bank_account on main. Platform sets carry no app field grants. The README's CLM administrator holds clm_admin and measures 200. No card
not in this PR legal, finance and clm_admin editing a party they do not own is the row layer, #152 (decision box). The §04 matrix cell and FLS row go to seat PR #120 (governed). §03 already reads screening_status there

Landing: under the maintainer's merge authorization (project thread: 「你可以自己审核的就自己合并」).


Generated by Claude Code

@zhuangjianguo
zhuangjianguo marked this pull request as ready for review October 26, 2026 18:01
@zhuangjianguo
zhuangjianguo merged commit 3b7364f into main Oct 26, 2026
2 checks passed
zhuangjianguo pushed a commit that referenced this pull request Oct 26, 2026
…d-only (#133, PR #154)

Ruling 6422701531 (A).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017nzWFRUo2U7evMkPdVniAn
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.

The launch wizard offers every business requester a "new counterparty" step that always fails — create_party is refused for clm_requester

2 participants