Repository navigation
Every member holds clm_requester by default; the app declares its eleven capability tokens - #122
Merged
Merged
Conversation
DESIGN.md §04 makes clm_requester every employee's default set. Until now the app distributed it by hand: each of the seven positions named it next to its own set, and an employee with no position needed a Setup grant. The platform mechanism for a default set, `isDefault: true`, was refused because the set grants system-permission tokens, which count as high-privilege unless the stack declares them (ADR-0090 D5/D9, rule security-anchor-high-privilege). 18.1 excuses declared tokens, so the stack now declares the eleven authorization tokens its five sets grant under `capabilities:` (scope org), clm_requester is marked `isDefault: true`, and the seven position entries that named it are removed. Every position persona's effective sets are unchanged; a member with no position now holds clm_requester through the everyone anchor, and so does the dev admin. README drops the per-person grant, the "dev admin sees no My Contracts" passage and the id-typed permission_set_id claim (the column is `permission_set`, a name). 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, Part of #11, no closing keyword; assignee zhuangjianguo |
| CI on the head | both Validate check-runs success on d96e62a = PR head.sha; mergeable clean |
| scope | 4 files (+105/−65): objectstack.config.ts gains only the capabilities: block; clm_requester gets isDefault: true; positions.ts drops the seven RequesterSet.name entries; README rewritten to match. Nothing governed |
| the ruling | 6398381229 step 3, letter A — delivered. The eleven declared tokens are exactly the union of the five sets' systemPermissions (seat cross-check of all five profiles on the head), each scope: 'org' |
| measured (dev, 18.1.0) | every persona's resolved sets unchanged before/after (only the attribution moves to "additive baseline"); a member with no position goes from 403 / navigation: [] to 我的合同 + 分析 and can create a contract; dev admin now sees 我的合同 + 分析; boot diagnostics byte-identical to main |
| not in this PR | src/apps/clm.app.ts still names the retired bind-position-sets.ts twice → the #95 remainder; three declared tokens (archive_contract, manage_clauses, manage_approval_rules) have no requiredPermissions consumer yet — observation only |
Landing: under the maintainer's merge authorization (project thread: 「你可以自己审核的就自己合并」).
Generated by Claude Code
zhuangjianguo
marked this pull request as ready for review
October 25, 2026 14:02
This was referenced Oct 25, 2026
Merged
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.
Part of #11 — ruling step 3 (A): every member holds clm_requester by default; the app declares its eleven capability tokens
#11 stays open: the §04 gate × holder table (ruling step 1) rides the seat's governed PR #120. Ruling-ref: issue comment 6398381229 (batch #6 item 1, letter A), the retraction pre-written in 5604446794.
What changed
objectstack.config.tscapabilities:block (only this block): the eleven authorization tokens the five permission sets grant insystemPermissions, eachscope: 'org'with a label and a description — the fiveclm_*.accessgroup gates andapprove_contract·execute_contract·archive_contract·terminate_contract·manage_clauses·manage_approval_rules. Norequires:, plugin or service is added.src/profiles/requester.profile.tsisDefault: true; the header's "how the default is distributed" section is rewritten as "how the default reaches every member" (theeveryoneanchor, why the declaration is the other half of the flag).src/sharing/positions.tsRequesterSet.nameentries are removed (the executive and GM positions now name no set, so theirpermissionSetskey is omitted); theRequesterSetimport goes; the header comment is rewritten.README.mdadmin.profile.tsis untouched (ruling step 6: its own update rules from PR #117 stay, since the administrator still holdsclm_requester).README
clm_requesterby default through theeveryoneanchor; creating the account is the whole of it. The named-accounts table's "Give it" cell for the three requesters now reads "nothing — every member already holdsclm_requester".clm_*set and is servednavigation: []) → the dev admin holdsclm_requesterlike every member and is served 我的合同 and 分析, and no other group; the "rows parked on the dev admin" warning is restated for what is still true.permission_set_iddrift (Assigning a position from a script): the sentence claimingsys_user_permission_set.permission_set_idis id-typed is replaced — on 18.1 the column ispermission_set,text, carrying the set's name (read fromGET /api/v1/meta/object/sys_user_permission_seton this branch's boot:permission_set text,user_id lookup sys_user;POST /api/v1/data/sys_user_permission_set {"user_id": …, "permission_set": "clm_requester"}→ 201).clm_requester, because every position names that set too" (it no longer does — membership bringsclm_requester, and explain attributes it to the "additive baseline"), and "an id in thepositionfield lands the account in the dev admin's state: noclm_*set,navigation: []" (it now lands as a plain member:clm_requesteralone, 我的合同 and 分析 only).bind-position-sets.tsname: already gone fromREADME.mdon4460d31— PR Upgrade the@objectstack/*dependencies from 17.7.0 to 18.1.0, walking the official upgrade checklists of 18.0 and 18.1 #110 (7b736a0) replaced it withsrc/sharing/positions.ts(git show 7b736a0 -- README.md), so README needs no change there. The two remaining mentions are insrc/apps/clm.app.tscomments, outside this PR's surface (Acceptance notes).Measurements — 18.1.0,
pnpm demo --port 3411, one throwaway local databaseBefore =
origin/main4460d31; after = this branchd96e62a; same database for both. Persona accounts were created the README way on that database as the dev admin (POST /api/v1/auth/admin/create-user×9 → 200;POST /api/v1/data/sys_user_position×7 andPOST /api/v1/data/sys_user_permission_set×1 withpermission_set: "clm_requester"→ 201; each persona's password then changed throughPOST /api/v1/auth/change-password→ 200). No permission prompt refused it.Effective permission sets —
POST /api/v1/security/explainprincipal,clm_contractread and createclm_adminpositionclm_admin, clm_requester, member_defaultclm_legal_counsel·clm_legal_headclm_legal, clm_requester, member_defaultclm_finance_controllerclm_finance, clm_requester, member_defaultclm_records_managerclm_records, clm_requester, member_defaultclm_executive·clm_general_managerclm_requester, member_defaultsys_user_permission_setrow)clm_requester, member_defaultPersona Blank)member_default— readfalse, createfalseclm_requester, member_default— readtrue, createtrueadmin_full_access, member_default, organization_admin_no_bypassadmin_full_access, clm_requester, member_default, organization_admin_no_bypassEvery position persona's set list is identical before and after. The only difference for them is attribution: explain names
clm_requester"via direct grant" before and "via additive baseline (ADR-0090 D5)" after.What the personas are served (
GET /api/v1/meta/app/clmnavigation groups,GET /api/v1/data/clm_contract?count=truetotal)403on the list;navigation: []navigation: []A brand-new member creates a contract: as
Persona Blank,POST /api/v1/data/clm_contract {title, contract_type, party, our_entity}→ before403 PERMISSION_DENIED("You do not have permission to perform this action…", objectclm_contract); after201, and the row then appears in that member's list (total 1).Browser — Chromium at
/opt/pw-browsers/chromium, Playwright, signed in asadmin@objectos.aiOpened
/_console/, signed in, went to/_console/apps/clm.4460d31): the app's sidebar carries none of My Contracts, Launched by Me, Waiting on Me, My Obligations, Launch a Contract, Analytics, Executive Overview.d96e62a): the app opens on/_console/apps/clm/clm_contract/view/my_contracts; the sidebar shows My Contracts (Launch a Contract, Launched by Me, Waiting on Me, My Obligations) and Analytics (Executive Overview) and no Legal, Finance, Records or Administration group. Clicking Launched by Me lands on themy_contractsview ofclm_contractwith the dev admin's rows grouped by status. (我的合同 is that group's zh-CN label.)401onGET /api/v1/auth/get-session(before sign-in) and one404resource load. Nothing new.Boot
The boot diagnostics block (timestamps stripped) and every
WARN/ERRORline of the server log are byte-identical between a re-run ofpnpm demoon main and on this branch against the same database:Plugin superseded: 'com.objectstack.metadata', and the seeder's deferred-reference summary for the persona names this database does not have (Business Requester 1…, the README's documented empty columns). No refusal line, nohigh-privilege/security-anchorline.objectstack dev --compile(both boots ofpnpm demo) compiled.The refusal leg —
isDefault: truewithout thecapabilities:block failspnpm lint,pnpm validateand the dev compile withsecurity-anchor-high-privilege— was measured on 18.1.0 in PR #117 (Leg A) and is not re-run here.Gates — head
d96e62a, exit codes captured to files before any pipevalidate's seven warnings are the pre-existing ones (hierarchy-securityedition note and the approval-slateapproval-approvers-may-resolve-emptynotes); none names a capability,clm_requesterorisDefault.Acceptance notes
src/apps/clm.app.tsstill names the retiredbind-position-sets.tstwice in comments (the header's "Every position also holdsclm_requester(bind-position-sets.ts)" and the leadership-rungs note in the Analytics group). Both are now doubly stale — the binding is neither that file nor the positions any more, it is the default set. Outside this PR's surface; the seat's Implementation notes are shown to users as help text — field and dashboard-widget descriptions cite hook files, card numbers, DESIGN.md sections and PRs #95 remainder takes them.requiredPermissionsonarchive_contract,manage_clausesormanage_approval_rules(approve_contractis consumed by the approval centre, ascontract-lifecycle.actions.tsexplains). The descriptions in thecapabilities:block therefore call them "DESIGN.md §04 action gate for …" and do not claim a button they guard. Observation only.sys_user_permission_setrow is now redundant (it grants what membership already gives); harmless, and a deployment that grantedclm_requesterby hand can leave those rows or delete them.security/explainattributes position-defined sets "via direct grant" (filed upstream by the seat after PR clm_admin edits contracts past submitted: the administrator carries its own permissive update policies #117); after this change it no longer touchesclm_requester, which reads "additive baseline".🤖 Generated with Claude Code
https://claude.ai/code/session_017nzWFRUo2U7evMkPdVniAn