Repository navigation
The launch wizard offers every business requester a "new counterparty" step that always fails — create_party is refused for clm_requester - #154
Merged
Conversation
…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
Contributor
Author
席位核对 — ACCEPT · head
|
| 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
marked this pull request as ready for review
October 26, 2026 18:01
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #133
What changed
Ruling A (comment 6422701531), with the file surface the seat widened after the first measurement (claim amendment 6423291471).
clm_requestermay create a counterparty, not edit one. Onclm_party,allowCreateis nowtrueandallowEditstaysfalse(src/profiles/requester.profile.ts).src/profiles/_grants.ts:PARTY_ASSESSED_FIELDS = [risk_flag, risk_note, screening_status, is_active, bank_name].clm_requesterholds themreadOnly, so a party a requester creates is born unflagged and unscreened.clm_legalandclm_adminopenthem.verified_at. That field no longer exists: commit 75fa5a7 replaced it withscreening_status+screened_at, and [Decision] 17.4 的新规则指出 21 个字段声明了却没人读——展品该给它们消费者,还是删掉声明 #38 removedscreened_at. So it maps toscreening_status.clm_financeopensbank_nameandbank_account. This is §04 finance's "RU(银行信息)".clm_party. Comment only: thecreate_partynode writesname,party_kind,registration_no,contact_nameandcontact_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 holdsclm_requesterthrough theeveryoneanchor.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:
risk_flag,risk_note,screening_status,is_activeandbank_nameon a party she owns. Each answered 403 "Field write denied"; onmainthe same write answered 200.bank_accountfrom finance onmain. Explain for Finance Controller 1 read "masked from responses: [bank_account]".The
openlines follow the discipline_grants.tsandadmin.profile.tsalready document: a set that must edit what another co-held set locks says so explicitly.Anchor safety
allowCreateis not a high-privilege bit (describeHighPrivilegeBitsflags only undeclaredsystemPermissions, view/modify all, delete/transfer and export).security-anchor-high-privilegelines.clm_requester"via additive baseline (ADR-0090 D5)".Measurements: 18.1.0,
pnpm demo:zh --port 3133, throwaway local databasesBefore =
maineace613; after = this branchd21ffc9(and029d8c6for 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 withmustChangePassword: true.POST /api/v1/data/sys_user_position→ 201 forclm_legal_counsel,clm_finance_controllerandclm_admin.POST /api/v1/auth/change-password→ 200 for each persona.pnpm demo:zhon 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:risk_flagnone,screening_statusnot_screened,is_activetrue;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_partywith name, kind, registration no., contact name and contact email → 201. The party is bornnone/not_screened/ active.POSTnaming one locked field at a time → 403 "Field write denied: not permitted to edit [F]" for F =risk_flag,risk_note,screening_status,is_activeandbank_name. The hiddenbank_accountandcontact_phoneanswer the same way.PATCHcontact_nameon a party BR1 owns, on legal's party and on a seeded party → 403PERMISSION_DENIED, from the object layer (no edit).registration_no→ 409UNIQUE_VIOLATION.Legal Counsel 1
PATCHon her own party, each of the five fields alone and all five together → 200. On 029d8c6 each was 403.POSTof a new party withrisk_flag: watchandscreening_status: hit→ 201.CLM Admin 1 (
clm_adminposition)PATCHof all five fields on a party it owns → 200.blocked, and the intake guard then refused it (below).Finance Controller 1
clm_party: FLSnot_applicable, "No field-level masking applies". Before it read "masked from responses: [bank_account]".bank_account(GB29 NWBK …) andbank_name.bank_nameandbank_account. That is [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, recorded here and not routed around. So finance's field-layer edit is NOT MEASURED.editableOnlylocksnameandparty_kind, andPOSTanswers 403. That keeps §04 finance at RU.Blocked-counterparty guard, as Business Requester 1
Headless
POST /api/v1/actions/clm_contract/launch_contractnaming 德尔塔岭控股 (seededblocked):400 FLOW_FAILEDatrefuse_blocked;The party CLM Admin 1 flagged
blockedis 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_bypassandmember_default. It holds noclm_admin, so no set it holds opens the field. This is the same mechanism that already masksbank_accountandcontact_phonefrom it onmain.A CLM administrator in the README's sense holds the
clm_adminposition, and that account is measured at 200 above. See Acceptance notes.security/explain and the boot
clm_partyis allowed, "granted by [clm_requester]"; update is denied.clm_requesterandmember_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.tswas checked out ateace613, then restored fromHEAD:git diff HEADwas empty and the blob hash matched.The signatures are identical except:
clm_partyupdate denials, which are my own probes above.high-privilege,security-anchor,refus,degradedandno such tableeach match 0 lines in both logs.Gates: head
d21ffc9, exit codes captured to files before any pipeFor the seat: proposed DESIGN.md §04 wording (governed, rides PR #120)
clm_partyrow,clm_requestercell:R(不含银行与电话)→RC(不含银行账号与电话;风险标记、风险说明、筛查结果、启用状态与开户行只读)| clm_party.risk_flag · risk_note · screening_status · is_active · bank_name | clm_requester 只读 | 由法务评定或由筛查连接器写入;发起人新建的相对方生来未标记、未筛查 |RC(不含银行与电话;风险与核验字段只读). 核验 maps toscreening_status(筛查结果), andbank_name(开户行) is read-only, not hidden, so the cell names it.verified_atin theclm_partyrow. It should readscreening_status.Acceptance notes
clm_admincan edit only theclm_partyrows they own:public_readwrites are owner-scoped and no sharing rule covers the object. This already happens onmain(Legal Counsel 1PATCHof a seeded party → 403).clm_admin. That is inferred from the two measurements above; I did not measure such an account.modifyAllRecords.clm_partybullet.clm_admin. The dev admin, or any platform admin who does not hold theclm_adminposition, cannot write the five assessed fields. Onmainit already cannot readbank_accountorcontact_phone. Opening them for platform-only admins has no app-level spelling: their sets are the platform's. The README's administrator holdsclm_admin.everyone-bound set gives create onclm_partyto every position. Finance's own field locks stop it in practice (measured: 403). Records can create, inferred from its grants and not measured.PATCHed a party id that was never created (404). They wrote nothing.🤖 Generated with Claude Code
https://claude.ai/code/session_017nzWFRUo2U7evMkPdVniAn