You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
18.3 lint: clm_admin's modifyAllRecords on clm_contract makes its 17 field edit locks inert — the locks are declared but bind no admin #196
Filing gate: ② declared≠enforced, surfaced by a new @objectstack 18.3.0 lint rule (security-superuser-field-mask-inert) during the hotclm 18.3 upgrade; pnpm lint exits 0 (warnings), so it did not block the upgrade.
Reading.pnpm validate / pnpm lint on 18.3.0 report 17 warnings of the form: permission set "clm_admin": modifyAllRecords on objects.clm_contract votes on every field of "clm_contract", so the edit lock on 'clm_contract.' binds none of this set's holders. Fields: submitted_at, review_started_at, approved_at, signed_at, executed_at, activated_at, closed_at, archived_at, approval_status, route_legal_head, route_finance, route_executive, route_gm, ai_summary, ai_risk_score, ai_risk_rationale, ai_reviewed_at. On 18.2.0 the same tree linted with 0 warnings.
Why it matters. The clm_admin permission set (src/profiles/, built with the _grants.ts helpers) locks those fields with editable: false (the lifecycle stamps and routing flags are written only by flows and hooks, DESIGN.md §03/§04). The platform says the lock does nothing for a set holding modifyAllRecords, so a CLM administrator can overwrite approved_at or route_finance through the API. Either the locks are meant (then modifyAllRecords has to give way to a write scope or row policy) or they are not (then they should be deleted so the declaration matches what runs).
Needs a ruling on which side wins (DESIGN.md §04 clm_admin row) — the seat's call whether it goes to the decision box.
Platform version: 18.3.0. Rule: os explain security-superuser-field-mask-inert.
The seat's call: this is a §04 question (the clm_admin row), so it goes to the maintainer. On 18.3.0 the platform says clm_admin's modifyAllRecords on clm_contract makes 17 field locks inert, so an administrator can overwrite lifecycle stamps and routing flags over the API. This was already true on 18.2; 18.3 only reports it. Either the locks are meant, and the admin's modifyAllRecords gives way, or they are not, and the locks go. State: → needs-user-decision.
Claim: PM loop round 19
Ruling-ref: 6617227919
Session: session_017nzWFRUo2U7evMkPdVniAn
Account: zhuangjianguo (the seat's linked user as GET /user answers it; the card's assignee)
Branch: claude/issue-196-admin-field-locks
Worktree: hotclm-issue-196
Domain: repo:hotclm (single-lane sister repo — no domain:*)
Seat: repo:hotclm#1
File surface: src/profiles/admin.profile.ts (the clm_contract entry and the header comment) — stop on breach; explain in the report
Container & model: S, mode:subagent, model: opus (TIER_DEFAULT; hand-derived for a sister repo), effort: medium
Clause-②: no
Metadata-protocol: none
Responsibility: this app's admin field locks on clm_contract (stamped fields read-only for every position) | objectstack-ai/objectstack#29106 ruling A: a super-user bit votes edit on every field | the CLM admin
Thread-read: none
Premise: re-measured by the dev on origin/maindfa5d67 (@objectstack 18.3.0) with the declared demo staff
Serial constraints cleared: batch 12 ruled 2026-11-03T22:34:37Z. Dispatched together with #64, which edits the clm_obligation entries of the same profile; the second PR to land rebases.
{
"issue": 196,
"status": "done",
"branch": "claude/issue-196-admin-field-locks",
"pr": "https://github.com/objectstack-ai/hotclm/pull/204",
"session": "session_017nzWFRUo2U7evMkPdVniAn",
"premise_still_valid": true,
"summary": "Implemented ruling 6617227919 (A) in src/profiles/admin.profile.ts only. The clm_contract entry is now { allowCreate, allowRead, allowEdit, allowDelete: true, viewAllRecords: true, writeScope: 'org', allowTransfer: true } with no modifyAllRecords; readOnly(Contract, CONTRACT_STAMPED_FIELDS) is kept and the six child entries are unchanged. The header comment is rewritten: a lock binds only without modifyAll (ADR-0066 2026-11-02 note), viewAllRecords never votes an edit, whole-row write comes from writeScope 'org', and owner reassign comes from allowTransfer. writeScope semantics were confirmed in permission.zod.ts at @objectstack/spec@18.3.0: 'writeScope beside viewAllRecords: true — READ AND HONOURED'. No gate refused the pair, so the readScope+writeScope fallback was not used. Premise note: before the change, PATCH approved_at/route_finance as clm.admin answered 200 but the stored value did not move (silently dropped on 18.3). After the change it is a 403 'Field write denied'. main has not moved since b38df5a and #64 has not landed, so no rebase was needed.",
"tests": "18.3.0, pnpm demo:zh on a throwaway DB, port 3169, as clm.admin@example.com, on another user's active contract. BEFORE (origin/main b38df5a): GET 200; PATCH jurisdiction 200; PATCH approved_at 200 with the value unchanged (2026-07-24 stays); PATCH route_finance 200 with the value unchanged; PATCH owner_id 200 and read back; PATCH child clm_review.comments 200; requester1 creates a throwaway draft 201, admin DELETE 200, then GET 404. AFTER (c50654e): GET 200; PATCH jurisdiction 200; PATCH approved_at 403 \"[Security] Field write denied: not permitted to edit [approved_at] on 'clm_contract'\" PERMISSION_DENIED; PATCH route_finance 403 \"[Security] Field write denied: not permitted to edit [route_finance] on 'clm_contract'\" PERMISSION_DENIED; PATCH owner_id 200 and read back as the new owner; PATCH clm_review.comments 200; throwaway DELETE 200, then GET 404. /auth/me/permissions after: clm_contract modifyAllRecords false, writeScope org, allowTransfer true; fields approved_at {readable true, editable false}, owner_id editable true. Browser (Chromium, zh-CN, clm.admin): on another user's active contract ORD-2026-0015, 编辑 → change 管辖 → 更新 → toast 合同“订单 — 黑貂保险”已更新, the new value on the page, API read-back with updated_by = clm.admin and owner unchanged. The edit form shows no stamped fields and no 业务承办人: objectui plugin-form autoLayout drops owner_id for every user, independent of permissions, so this is not a regression. Nothing broke. Ablation: n/a, a metadata-only permission change with no test file.",
"gates": "Head c50654e, each exit code captured before any pipe: pnpm install exit 0; pnpm validate exit 0 (1 warning, obligation_requester_own = #64); pnpm lint exit 0 '1 warning(s), 6 suggestion(s)', security-superuser-field-mask-inert count 0 (baseline on b38df5a: '18 warning(s), 6 suggestion(s)', 17 of the rule); 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. hotclm has no dispatch-gates derivation; the list is the repo's own (AGENTS.md / common rules). PR check-runs: not awaited (in_progress at report time).",
"dogfood_battery": "n/a — hotclm sister repo, no `!` in the PR title and no (narrowing) arm",
"mcp_calls": "0",
"api_writes": "2 relay strokes: (1) fleet-write dispatch pr_create → POST /repos/objectstack-ai/hotclm/pulls + POST /repos/objectstack-ai/hotclm/issues/204/assignees (run 41736361873 success, body read back identical, 6188 bytes); (2) post-stamped comment POST /repos/objectstack-ai/hotclm/issues/196/comments (this report). Plus git push of the branch (not REST).",
"open_questions": [],
"out_of_scope_findings": [
"class: observation (not filed) · reach: PATCH /api/v1/data/clm_contract/ID as clm.admin on b38df5a · a write naming approved_at or route_finance answered 200 and left the stored value unchanged, i.e. silently dropped. With the lock binding it is now a 403. Noted in the PR's readings; carrier: none",
"class: observation (not filed) · src/profiles/admin.profile.ts rowLevelSecurity contract_admin_edit_any_status inline comment ('Modify All Data does not skip them on this object') is outside this card's surface. It is still true, but the admin no longer holds Modify All on clm_contract. Suggested wording: 'policies OR-merge across the sets a person holds, and neither writeScope nor Modify All Data skips them on this object.' carrier: none (Acceptance notes)",
"class: observation (not filed) · openAllExcept(Signature, []) and open(Review, ['internal_note']) in admin.profile.ts repeat what the children's modifyAllRecords already votes since ADR-0066's 2026-11-02 note. Harmless; carrier: none",
"class: NOT MEASURED · GET /api/v1/data/clm_payment_plan as clm.admin answered total 0 about 2-3 min into the baseline demo boot. Seeding was still in the background and its result line never printed, while src/data/plan-children.ts and post-signature.seed.ts declare payment instalments. Not investigated (out of scope). Dedupe words: payment_plan seed, demo instalments, clm_payment_plan empty"
],
"governed_text_proposal": "none. DESIGN.md §04 stays as worded (ruling: 一字不改), and no AGENTS.md/CLAUDE.md change is needed."
}
MERGED (squash on main), merged at its ACCEPT head c50654e; Fixes #196 closes this card
authority
batch 12 item 2, letter A, maintainer 「同意」
Release: an administrator still reads, edits, reassigns and deletes every contract, but the stamped fields (routing flags, stage timestamps, AI fields) now refuse a hand edit with 403 instead of silently dropping it. Lint on main goes from 18 warnings to 1 (#64's, in flight). State: pm:dispatched removed, assignee cleared.
Activity
objectstack-fleet commentedon Nov 3, 2026
To the decision box — 2026-11-03
The seat's call: this is a §04 question (the
clm_adminrow), so it goes to the maintainer. On 18.3.0 the platform saysclm_admin'smodifyAllRecordsonclm_contractmakes 17 field locks inert, so an administrator can overwrite lifecycle stamps and routing flags over the API. This was already true on 18.2; 18.3 only reports it. Either the locks are meant, and the admin'smodifyAllRecordsgives way, or they are not, and the locks go. State: →needs-user-decision.Generated by Claude Code
zhuangjianguo commentedon Nov 3, 2026
Ruling: batch #12 item 2 · letter A · maintainer 「同意」 2026-11-03T22:34:37Z
裁定:A —— 字段锁赢,
clm_admin在clm_contract上放弃modifyAllRecords决策箱文档:https://claude.ai/artifact/VTMgyRUKZu1gQChSijSnXD 。前提:objectstack#29106 裁 A(ADR-0066 2026-11-02 注)——持超级用户位的权限集对该对象每个字段投「可编辑」票,「要藏字段就别给那两位」;
permission.zod.ts明确writeScope放在viewAllRecords旁边会被读取并生效,'org'是原生深度。§04「route_*·approval_status· 阶段时间戳 ·ai_*:所有岗位只读」一字不改。裁后执行(lane 一个 PR,
src/profiles/admin.profile.ts)clm_contract条目改为{ allowCreate, allowRead, allowEdit, allowDelete: true, viewAllRecords: true, writeScope: 'org', allowTransfer: true },去掉modifyAllRecords;readOnly(Contract, CONTRACT_STAMPED_FIELDS)保留。六个子对象条目不动(它们没有锁)。clm_admin的管理员):读他人合同 200、PATCH 普通字段 200、PATCHapproved_at/route_finance403「Field write denied」、改派owner_id200、删除 200、子记录写入 200;pnpm lint的security-superuser-field-mask-inert从 17 条归零。viewAllRecords+writeScope的组合被某道门拒绝,退而用readScope: 'org'+writeScope: 'org'(两者等价于本裁),在 PR 里说明读数。⛔ 不取 B(删锁、§04 加例外)、C(向平台要否决层)。⛔ 本席不动标签。
Generated by Claude Code
objectstack-fleet commentedon Nov 3, 2026
Claim: PM loop round 19
Ruling-ref: 6617227919
Session:
session_017nzWFRUo2U7evMkPdVniAnAccount:
zhuangjianguo(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-196-admin-field-locksWorktree:
hotclm-issue-196Domain:
repo:hotclm(single-lane sister repo — nodomain:*)Seat:
repo:hotclm#1File surface:
src/profiles/admin.profile.ts(theclm_contractentry and the header comment) — stop on breach; explain in the reportContainer & model:
S,mode:subagent,model: opus(TIER_DEFAULT; hand-derived for a sister repo),effort: mediumClause-②: no
Metadata-protocol: none
Responsibility:
this app's admin field locks on clm_contract (stamped fields read-only for every position) | objectstack-ai/objectstack#29106 ruling A: a super-user bit votes edit on every field | the CLM adminThread-read: none
Premise: re-measured by the dev on
origin/maindfa5d67(@objectstack 18.3.0) with the declared demo staffSerial constraints cleared: batch 12 ruled 2026-11-03T22:34:37Z. Dispatched together with #64, which edits the
clm_obligationentries of the same profile; the second PR to land rebases.Generated by Claude Code
objectstack-fleet commentedon Nov 3, 2026
os-dev-report
{ "issue": 196, "status": "done", "branch": "claude/issue-196-admin-field-locks", "pr": "https://github.com/objectstack-ai/hotclm/pull/204", "session": "session_017nzWFRUo2U7evMkPdVniAn", "premise_still_valid": true, "summary": "Implemented ruling 6617227919 (A) in src/profiles/admin.profile.ts only. The clm_contract entry is now { allowCreate, allowRead, allowEdit, allowDelete: true, viewAllRecords: true, writeScope: 'org', allowTransfer: true } with no modifyAllRecords; readOnly(Contract, CONTRACT_STAMPED_FIELDS) is kept and the six child entries are unchanged. The header comment is rewritten: a lock binds only without modifyAll (ADR-0066 2026-11-02 note), viewAllRecords never votes an edit, whole-row write comes from writeScope 'org', and owner reassign comes from allowTransfer. writeScope semantics were confirmed in permission.zod.ts at @objectstack/spec@18.3.0: 'writeScope beside viewAllRecords: true — READ AND HONOURED'. No gate refused the pair, so the readScope+writeScope fallback was not used. Premise note: before the change, PATCH approved_at/route_finance as clm.admin answered 200 but the stored value did not move (silently dropped on 18.3). After the change it is a 403 'Field write denied'. main has not moved since b38df5a and #64 has not landed, so no rebase was needed.", "tests": "18.3.0, pnpm demo:zh on a throwaway DB, port 3169, as clm.admin@example.com, on another user's active contract. BEFORE (origin/main b38df5a): GET 200; PATCH jurisdiction 200; PATCH approved_at 200 with the value unchanged (2026-07-24 stays); PATCH route_finance 200 with the value unchanged; PATCH owner_id 200 and read back; PATCH child clm_review.comments 200; requester1 creates a throwaway draft 201, admin DELETE 200, then GET 404. AFTER (c50654e): GET 200; PATCH jurisdiction 200; PATCH approved_at 403 \"[Security] Field write denied: not permitted to edit [approved_at] on 'clm_contract'\" PERMISSION_DENIED; PATCH route_finance 403 \"[Security] Field write denied: not permitted to edit [route_finance] on 'clm_contract'\" PERMISSION_DENIED; PATCH owner_id 200 and read back as the new owner; PATCH clm_review.comments 200; throwaway DELETE 200, then GET 404. /auth/me/permissions after: clm_contract modifyAllRecords false, writeScope org, allowTransfer true; fields approved_at {readable true, editable false}, owner_id editable true. Browser (Chromium, zh-CN, clm.admin): on another user's active contract ORD-2026-0015, 编辑 → change 管辖 → 更新 → toast 合同“订单 — 黑貂保险”已更新, the new value on the page, API read-back with updated_by = clm.admin and owner unchanged. The edit form shows no stamped fields and no 业务承办人: objectui plugin-form autoLayout drops owner_id for every user, independent of permissions, so this is not a regression. Nothing broke. Ablation: n/a, a metadata-only permission change with no test file.", "gates": "Head c50654e, each exit code captured before any pipe: pnpm install exit 0; pnpm validate exit 0 (1 warning, obligation_requester_own = #64); pnpm lint exit 0 '1 warning(s), 6 suggestion(s)', security-superuser-field-mask-inert count 0 (baseline on b38df5a: '18 warning(s), 6 suggestion(s)', 17 of the rule); 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. hotclm has no dispatch-gates derivation; the list is the repo's own (AGENTS.md / common rules). PR check-runs: not awaited (in_progress at report time).", "dogfood_battery": "n/a — hotclm sister repo, no `!` in the PR title and no (narrowing) arm", "mcp_calls": "0", "api_writes": "2 relay strokes: (1) fleet-write dispatch pr_create → POST /repos/objectstack-ai/hotclm/pulls + POST /repos/objectstack-ai/hotclm/issues/204/assignees (run 41736361873 success, body read back identical, 6188 bytes); (2) post-stamped comment POST /repos/objectstack-ai/hotclm/issues/196/comments (this report). Plus git push of the branch (not REST).", "open_questions": [], "out_of_scope_findings": [ "class: observation (not filed) · reach: PATCH /api/v1/data/clm_contract/ID as clm.admin on b38df5a · a write naming approved_at or route_finance answered 200 and left the stored value unchanged, i.e. silently dropped. With the lock binding it is now a 403. Noted in the PR's readings; carrier: none", "class: observation (not filed) · src/profiles/admin.profile.ts rowLevelSecurity contract_admin_edit_any_status inline comment ('Modify All Data does not skip them on this object') is outside this card's surface. It is still true, but the admin no longer holds Modify All on clm_contract. Suggested wording: 'policies OR-merge across the sets a person holds, and neither writeScope nor Modify All Data skips them on this object.' carrier: none (Acceptance notes)", "class: observation (not filed) · openAllExcept(Signature, []) and open(Review, ['internal_note']) in admin.profile.ts repeat what the children's modifyAllRecords already votes since ADR-0066's 2026-11-02 note. Harmless; carrier: none", "class: NOT MEASURED · GET /api/v1/data/clm_payment_plan as clm.admin answered total 0 about 2-3 min into the baseline demo boot. Seeding was still in the background and its result line never printed, while src/data/plan-children.ts and post-signature.seed.ts declare payment instalments. Not investigated (out of scope). Dedupe words: payment_plan seed, demo instalments, clm_payment_plan empty" ], "governed_text_proposal": "none. DESIGN.md §04 stays as worded (ruling: 一字不改), and no AGENTS.md/CLAUDE.md change is needed." }Generated by Claude Code
objectstack-fleet commentedon Nov 3, 2026
Ruling-ref: 6617227919
Landed — PR #204 · 2026-11-03
MERGED(squash onmain), merged at its ACCEPT headc50654e;Fixes #196closes this cardRelease: an administrator still reads, edits, reassigns and deletes every contract, but the stamped fields (routing flags, stage timestamps, AI fields) now refuse a hand edit with 403 instead of silently dropping it. Lint on main goes from 18 warnings to 1 (#64's, in flight). State:
pm:dispatchedremoved, assignee cleared.Generated by Claude Code