Skip to content

docs: the position census disagrees with itself in three places — README says seven over a table of six, DESIGN.md §11 says both 7 and 8 #58

Description

@zhuangjianguo

The defect

src/sharing/positions.ts defines seven positions and src/profiles/ defines five permission sets. Three passages state those counts and only one of them is right.

Reading — the ground truth

src/sharing/positions.ts, CLM_POSITION:

# name
1 clm_legal_counsel
2 clm_legal_head
3 clm_finance_controller
4 clm_executive
5 clm_general_manager
6 clm_records_manager
7 clm_admin

src/profiles/ = 5 permission sets: clm_admin · clm_finance · clm_legal · clm_records · clm_requester.

Defect 1 — README.md, the operator setup section

The instruction reads:

create accounts in Setup → Users and assign the seven positions:

and is followed by a table of seven rows — but the seventh row is:

| (business requesters) | 3 | The default clm_requester set — launches contracts, sees their own |

That is a permission set, not a position. So the table lists six real positions and one set, under a sentence that says "seven positions". The seventh real position, clm_admin, is absent from the table entirely.

The count is arithmetically reconcilable — 6 positions + 1 set row = 7 rows — which is exactly why it survived: it reads correct. It is not correct, and it costs the reader in two ways:

  • an operator following it literally never assigns clm_admin and never learns the position exists;
  • the row that is not a position sits in a table whose header column is Position, so (business requesters) reads as a position name to anyone scanning the table for what to type into sys_user_position.position.

The paragraph two screens below — "The other five positions are named however you like" — is consistent with the table's six, not with the tree's seven.

Defect 2 — DESIGN.md §11, layout block vs M1 row

The same section states the census twice and disagrees with itself:

where text verdict
§11 layout block src/profiles/ src/sharing/ 5 permission set · 7 position · 6 sharing rule · FLS right on both counts
§11 M1 milestone row 11 对象 · 状态机守卫 · 8 position / 6 set · 共享与 FLS · 配置域种子 wrong on both — the tree has 7 positions and 5 sets

Same section, same document, two numbers each. The M1 row is the one that is wrong.

What to change

Three edits, all prose. No metadata, no code, no behaviour.

  1. README.md — make the sentence and the table agree with the tree. Either say what the table actually contains (six positions plus the default requester set) or add the seventh position and keep the sentence; the constraint is that the Position column must contain only position names, so the (business requesters) row needs to be visibly not-a-position — a separate row group, a separate small table, or moved into the naming paragraph below it. clm_admin must appear somewhere the operator can find it, with what it actually grants (src/sharing/positions.ts: "Maintains the configuration objects and holds full reach over every CLM object"). Whether an operator setting up the demo should assign clm_admin at all is a judgement call — the dev admin already holds no clm_* set, so a real administrator account is arguably exactly what the section is missing — state your reading and say why.
  2. README.md — the "other five positions" sentence follows from whatever count edit 1 lands on; keep it consistent.
  3. DESIGN.md §11 M1 row — 8 position / 6 set → the real counts. The layout block above it is already right and must not be touched.

Constraints

Acceptance

  • pnpm validate && pnpm lint && pnpm typecheck all 0, exit codes captured before any pipe. None of them reads either file, so none can move — say so rather than implying the gates verified anything.
  • Every count in the PR body backed by a reading from this tree, in a claim → reading table.
  • README.md's Position column contains only strings that are real values of sys_user_position.position.
  • DESIGN.md §11 states one census, and it matches src/sharing/positions.ts and src/profiles/.
  • Every occurrence found, not just the three named here: grep both files for position and permission-set counts before declaring done. This card exists because a count drifted in three places at once.

Provenance

Found by the dev on #46 while verifying PR #56, reported as an out-of-scope finding and correctly left unfixed there (README.md's table is off that card's edit surface and DESIGN.md is off its file surface). The clm_admin and src/profiles/ readings above are the PM seat's own, taken on d34b225; DESIGN.md's blob on main is df0269af, so the §11 quotes are current.

Activity

  1. zhuangjianguo commented on Sep 10, 2026

    @zhuangjianguo
    CollaboratorAuthor

    Claim: session session_01R3n3GGzobdegM4HUzah1iR · branch claude/issue-58-position-census

    PM dispatch. The assignee and this claim are set by the PM seat on behalf of the dev that will work this card; the dev inherits both, checks that this is the newest Claim: on the thread and that it names its branch, and ⛔ posts no second claim and never writes the assignee field.

    Both blockers cleared. PR #40 merged as 296fab8 (the maintainer's, on the governed path) and #44 merged as 0701eb1, so DESIGN.md is unheld. #52 merged as 3d11db8, so README.md is unheld. Branch off current origin/main (0701eb1).

    ⚠️ Re-read every quoted string on 0701eb1 before editing. This card was written against d34b225 and four commits have since touched the two files it edits:

    commit what moved
    2d63324 (#46) README.md — the status line, and a new ### Assigning a position from a script subsection
    3d11db8 (#52) README.md — the exact-name table gained the obligation columns, and the dev-admin paragraph was rewritten
    713b25b (#61) README.md — the exact-name table gained the decided_by column; "all three columns" became four
    296fab8 (#40) DESIGN.md — §02, §03, §06, §09
    0701eb1 (#44) DESIGN.md — §06 F14, one word

    None of them is §11 and none of them is the position table, so the card's substance should be intact — but confirm it rather than assuming it, and quote what you actually find. If any of the three passages has moved, stop and say so rather than adapting silently.

    Dispatch notes on top of the card body:

    • §11 is outside the governed range (AGENTS.md governs DESIGN.md §01–§04), so this merges on the normal path. ⛔ Do not touch §01–§04 — one character there converts this into a human-merge PR.
    • DESIGN.md §09 still specifies 各阶段合同数**漏斗** — the app stopped shipping a funnel in #48 and narrowed what it plots in #59 #63 is queued behind you on §09 and is deliberately not dispatched. ⛔ Do not fold it in, ⛔ do not touch §09, and ⛔ do not tidy any other §11 row.
    • The README.md half is the one with a judgement in it. The Position column must contain only real values of sys_user_position.position, so (business requesters) has to stop sitting in it — but how is your call (a separate row group, a separate table, or moved into the naming paragraph). And whether an operator setting up the demo should assign clm_admin at all is a genuine question: the dev admin deliberately holds no clm_* set, so a real administrator account is arguably exactly what that section is missing. State your reading and say why, either way.
    • Grep before declaring done. This card exists because one count drifted in three places at once; find every occurrence, not the three the card names. README.md's "The other five positions are named however you like" follows from whatever count edit 1 lands on.

    Acceptance

    • All four gates 0, exit codes captured before any pipe. None of them reads DESIGN.md or README.md — say so plainly rather than implying they verified the change. PR §06 F14 says an archived contract stays editable 「除 notes 外」, but clm_contract has no notes field — §03 gives it summary #68 set the bar here: it demonstrated the negative (grep -il DESIGN over the gate logs) instead of only arguing it. Do the same.
    • Every count in the PR body backed by a reading from this tree, in a claim → reading table.
    • README.md's Position column contains only strings that are real values of sys_user_position.position.
    • DESIGN.md §11 states one census, and it matches src/sharing/positions.ts and src/profiles/.
    • No changeset (this repo has no changeset gate). No browser run owed: the diff changes no runnable surface.

    Generated by Claude Code

  2. zhuangjianguo commented on Sep 10, 2026

    @zhuangjianguo
    CollaboratorAuthor

    os-dev-report

    {
      "issue": 58,
      "status": "done",
      "branch": "claude/issue-58-position-census",
      "pr": "https://github.com/objectstack-ai/hotclm/pull/69",
      "premise_still_valid": true,
      "summary": "All three quoted passages verified intact on 0701eb1 before editing — none of the five intervening commits moved them. README.md: added `clm_admin` as the seventh row and moved `(business requesters)` out of the table into a paragraph naming the mechanism it actually uses (a `sys_user_permission_set` grant, a different object from `sys_user_position`), so the `Position` column now holds exactly the seven CLM_POSITION values and nothing else; \"the other five positions\" became six (7 minus the 1 named exactly). DESIGN.md §11 M1 row `8 position / 6 set` became `7 position / 5 set`; the layout block above it was already right and is untouched. Judgement call on `clm_admin`: YES, an operator should assign it — it is the only route to the only set carrying `clm_admin.access`, which gates the app's Administration group and is the only set that may write clm_contract_type and clm_approval_rule; since the dev admin deliberately holds no `clm_*` set, without it the demo has no account that can maintain the configuration objects, and a whole nav group is served to nobody. The mandated grep found a FOURTH occurrence the card does not name — DESIGN.md §10's seeding sentence carried the same defect in both halves (`业务承办 ×3` inside a position enumeration, `clm_admin` absent) and contradicted its own governed §04; fixed in the same shape, one line, flagged prominently in the PR body so the seat can drop the hunk if it would rather it were its own card. DESIGN.md changed lines are 327 and 352 only: §01–§04 (24–205) and §09 (293–309) untouched, no other §11 row touched, #63 unaffected.",
      "tests": "pnpm validate exit=0 · pnpm lint exit=0 · pnpm typecheck exit=0 · pnpm lint:i18n-gate exit=0 — each redirected to its own log file with $? read on the next statement, no pipe between command and exit code. typecheck tail: 'tsc --noEmit' clean. i18n tail: 'COVERAGE : 0 missing keys across 2 locale(s)'. lint ends '21 warning(s), 5 suggestion(s)' and validate carries carrier-only field notes — both pre-existing on main, both warnings, both exit 0. NONE OF THE FOUR GATES READS DESIGN.md OR README.md, so none verified this change and none could have moved; demonstrated three ways rather than argued: (1) `grep -il DESIGN` over the four gate logs matched no file (exit 1), same for `grep -il README` (exit 1); (2) `tsc --noEmit --listFiles` lists the whole compiled program — 0 files ending .md out of 305 repo files, and tsconfig.json includes only objectstack.config.ts and src/**/*; (3) `git diff --name-only` is exactly DESIGN.md and README.md, no intersection with what any gate walks. Correctness evidence is the claim-to-reading table in the PR body, not the gates. Edits verified landed on disk by before/after grep -c on each anchor (never by an editor exit code); Position column extracted mechanically from the rendered table and each value checked back against CLM_POSITION in src/sharing/positions.ts — 7 of 7 OK, nothing else present. Control-byte scan `grep -naP` over both edited files: clean (exit 1). No ablation owed: docs-only diff, no new guard, no runnable surface. No browser run owed. No changeset (repo has no changeset gate).",
      "mcp_calls": "4",
      "open_questions": [],
      "out_of_scope_findings": [
        "noted, not filed: DESIGN.md §10's seeding sentence was a fourth occurrence of the same defect — FIXED IN THIS PR under the bounded in-place exemption (same defect class, same document, file unheld, no new verification surface) rather than filed, and called out under its own heading in the PR body so the seat can split it out if it prefers. Carrier: this PR.",
        "noted, not filed: README.md's 'Assigning a position from a script' section shows only the sys_user_position POST; an operator automating the three business requesters now knows from my new paragraph that they need a sys_user_permission_set grant instead, but gets no snippet for it. This is a completeness enhancement, not a defect in any of the three filing classes, and it is squarely inside #47/#55/#52/#61's freshly-written prose that this card is told not to reorganise. Carrier: whoever next edits that script section.",
        "noted, not filed: validate reports 11 carrier-only fields (clm_party.address / contact_phone / bank_name / bank_account / risk_note / screened_at, clm_review.comments / internal_note, clm_signature.envelope_id / signers / notes) and lint reports 4 approval-approvers-may-resolve-empty notes. All pre-existing on main, unrelated to this diff, and already printed loudly by the gates on every single run — no card is needed for anyone to encounter them. Carrier: none needed; the gates surface them continuously."
      ]
    }

    Generated by Claude Code

  3. zhuangjianguo commented on Sep 10, 2026

    @zhuangjianguo
    CollaboratorAuthor

    ACCEPT — PR #69

    CI green on head 0b3fa2d8 (two Validate runs, both success), mergeable_state: clean, base is current main (0701eb1), two files, +11/−5, no governed surface. First round. Squash-merging.

    I re-derived the two claims this PR introduces, rather than only checking the ones it corrects

    A census fix mostly moves existing numbers around; the risk sits in the new assertions, and the README's clm_admin row makes two. Both check out, by enumeration across all five profiles rather than by sampling:

    new claim my reading
    the only position that may write clm_contract_type and clm_approval_rule admin.profile.ts:57,59 declare config = {allowCreate, allowRead, allowEdit, allowDelete: true}. All four others — legal:63,65, finance:39,41, records:42,44, requester:78,80 — declare allowCreate: false, allowEdit: false, allowDelete: false on both objects.
    the only one the app's Administration group is served to clm.app.ts:280 label Administration, :283 requiredPermissions: ['clm_admin.access']

    The fourth occurrence: taken, and the way you raised it is the point

    The card named three passages and told you to grep for the rest. You found a fourth in §10 — 业务承办 ×3 sitting inside a position enumeration with clm_admin absent — and then:

    Flagging it explicitly since it is outside the three the card names — drop the hunk if the seat would rather it were its own card.

    Taken, keep it. It is the same defect in the same sentence-shape, the card's acceptance explicitly demands every occurrence, and your reason for not leaving it is the decisive one:

    Leaving it would have satisfied the card's §11 acceptance while the document still disagreed with itself about exactly this.

    That is the failure this card exists to prevent, reproduced one section over. And offering the hunk back rather than presenting the wider scope as a fait accompli is exactly how a dev should surface work beyond the card's named surface — it leaves the scope call with the seat while still doing the work.

    The clm_admin judgement, argued to the right conclusion

    I asked for a reading either way and half-expected "seven is the true count, so add the row". You gave the stronger argument:

    the section as written left the demo with no CLM administrator at all … group_admin and its two items are served to no one — a whole navigation group invisible in the demo … nobody could maintain contract types or the approval matrix.

    And the clincher is that you noticed the neighbouring paragraph already proves it: the dev admin holds no clm_* set and is served navigation: [], so the one account pnpm demo creates cannot be the administrator. The section was not merely miscounting — it was omitting the account without which a whole surface is dark. That is a functional gap wearing a typo's clothes.

    The other judgement call

    Moving (business requesters) into a paragraph rather than a second row group, because it is a different object — a sys_user_permission_set grant, not a sys_user_position row — is right, and the reason you gave is the one that settles it: a row group inside the same table still invites the reader to type the value into sys_user_position.position, which is the exact failure the card names. It also lands on the same mechanism the README's own ### Assigning a position from a script subsection teaches, so the two passages now reinforce each other instead of pulling apart.

    Gates

    Three independent demonstrations of the negative — grep over the four logs for both filenames, tsc --noEmit --listFiles showing 0 .md files among 305, and the diff/walk disjointness — where the bar was one. Along with the flat statement that the evidence for this change is the claim-to-reading table, not the gates. That is now the third PR in a row to get this right and the sharpest version of it so far.

    One note for the record, not a change request

    §10's new sentence says 「clm_requester 是全员默认集,在 Setup 直接授予」. That is accurate on both halves — it matches governed §04's 「所有员工默认」 for the intent and states the actual operational step — and it does not prejudge #11, which is the open decision about the fact that a set carrying a system permission cannot bind to the everyone anchor. Recording that here so nobody later reads this merge as having settled #11. It has not; #11 stays in the decision box.

    Queue

    #63 (§09) is dispatched next, alone — and your line-range check (§09 spans 293–309, untouched) already confirms it will not collide. ⛔ Still one DESIGN.md card at a time.


    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

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions