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
"新建" on the contract list and on the contract page's Renewals list opens the raw contract form, not the launch wizard — status, legal fields and six child grids for a requester #136
Filing class: ① user-visible defect — reach: public door, measured in a browser on mainf534eef (18.1.0, pnpm demo:zh). Reader: the hotclm lane (object userActions.create, possibly a ruling on whether legal/admin keep a raw create).
Symptom
As Business Requester 1, on NDA-2026-0023 (active) the 续签合同 list (PR #123) shows a 新建 button although the component declares actions: [] (src/pages/contract_detail.page.ts, contract_renewals). It opens ?form=new&formObject=clm_contract&formLink=renewed_from:… — the platform's full object form:
状态 is a select offering all eleven statuses (草稿 … 生效中 … 已作废);
legal-owned fields are on it (风险等级, 法务经办人, 档案号, 当前轮次);
six inline child grids (Versions, Deviations, Obligations, Payment Plan, Reviews, Signatures) — their headings in English, platform card i18n: a master-detail field's inlineTitle has no translation slot — the inline child grids on a zh-CN create form read "Versions / Deviations / Payment Plan" objectstack#26142.
The server guards hold (measured: a requester's create with status: active is refused "A contract is created as a draft…", with risk_level is refused by FLS), so nothing wrong is stored — but the requester is handed a form that is mostly refusals, and a draft made this way has no first version and no routing, i.e. it skips everything F1 does. The contract list's header also shows a generic 新建 beside 发起合同 (the wizard); it is the same create affordance (not opened separately in this pass).
Why
actions: [] removes the list's toolbar actions; the related list's New is a separate affordance the host draws unless userActions.create.visibleWhen is false for the parent (objectui RelatedList.tsx, #4646). clm_contract does not declare userActions.create.
Fix
Declare userActions.create on clm_contract so the generic New is not offered (contracts are launched through F1 and backfilled through F16, §06). If legal or the CLM admin should keep a raw create, gate it with visibleWhen on their permission instead — that is the one question for the lane.
Dedupe: hotclm issues (all states) grepped for New button, 新建, raw form, create form, bypass … intake — 0 hits.
Filed from the browser regression pass on main (2026-10-26).
Claim: PM loop round 15
Session: session_017nzWFRUo2U7evMkPdVniAn
Account: zhuangjianguo (the seat's linked user as GET /user answers it; the card's assignee)
Branch: claude/issue-136-no-raw-create
Worktree: hotclm-issue-136
Domain: repo:hotclm (single-lane sister repo — no domain:*)
Seat: repo:hotclm#1
File surface: src/objects/contract.object.ts (userActions.create) · src/pages/contract_detail.page.ts only if the Renewals list needs it · both translation bundles only if a string changes — 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 contract create doors (F1 and F16 are the only ones) | none known | every member who presses New on a contract list
Thread-read: none
Premise: measured in the 2026-10-26 browser regression pass on mainf534eef with real personas; re-measured by the dev on origin/main first
Serial constraints cleared: nothing in flight on these files; #135, #136, #137 and #138 are dispatched together on disjoint surfaces (#136 and #137 share contract_detail.page.ts only if #136 needs the Renewals list — different lines).
{
"issue": 136,
"status": "done",
"branch": "claude/issue-136-no-raw-create",
"pr": "https://github.com/objectstack-ai/hotclm/pull/146",
"session": "session_017nzWFRUo2U7evMkPdVniAn",
"premise_still_valid": true,
"summary": "Re-measured the premise on origin/main f534eef first: the contract list header showed 新建 · 导入 · 发起合同 and the contract page's 续签合同 related list carried a 新建 for all three personas, and GET /api/v1/meta/objects/clm_contract answered userActions = null. The fix is one block in src/objects/contract.object.ts: userActions: { create: false }. That is 18.1.0's declared mechanism and it was measured: resolveCrudAffordances narrows the managedBy bucket default, and both the console's listNew and a related list's relatedNew read that same resolved row through resolveAffordance in @object-ui/core, so one declaration closes both doors that actions: [] (PR #123) could not. No role keeps a raw create — legal and the CLM admin launch through F1 too and F16 is the backfill door — and the per-role form would not have worked anyway: a toolbar visibleWhen binds to the record in scope, which on a standalone list is nothing, so a record.* predicate hides it for everyone by the fail-closed rule. No platform gap. The change is UI-only on 18.1.0 (plugin-security's affordance write guard covers engine-owned and append-only buckets only), and the data API answers are unchanged, which is reported, not altered.",
"tests": "Metadata-only app: no unit suite exists (package.json has no test script). Evidence is the browser and the API, one throwaway DB on port 3136, pnpm demo, Chromium /opt/pw-browsers/chromium at 1440x1000, zh-CN, before = f534eef and after = 30cfe08 on the same database. Personas made the README way with no refusal: admin create-user 200 for Business Requester 1 and Legal Counsel 1, sys_user_position clm_legal_counsel 201 for the lawyer, change-password 200 each (mustChangePassword was stamped), plus the dev admin. Boots: '✓ Server is ready', no 'degraded capabilities', no 'no such table'; the six author-time warnings are the pre-existing approval-slate notes on contract_approval. Served metadata: userActions = null before, {\"create\":false} after. Contract list header: 新建·导入·发起合同 before → 导入·发起合同 after, all three personas. 概要 tab 续签合同 list: one New before → zero after, all three. Other related lists unchanged in both directions: 版本 (all three), 审查/偏离 (偏离 only for the requester; both for legal and admin), 履约义务 + 收付款计划 and 签署记录 (legal and admin) each keep their New. Record headers unchanged (发起续签 for the requester and admin; 终止·发起续签·编辑 for legal). F1 after the change, as Business Requester 1: the dialog opens (合同类型 9 / 我方签约主体 3 / 相对方 38 options), POST /api/v1/actions/clm_contract/launch_contract → 200, the 合同信息 screen opens, submitting created SUP-2026-0026 (status draft, read back over the data API) and went on to the 上传首个版本 screen; the scripted run stopped there for want of a file ('文件不能为空'), which is user input, not a refusal. F12 after the change, same persona on SUP-2026-0003: 续签合同 0 · 暂无相关记录 with no New → POST …/start_renewal → 200 → the list refetched to 续签合同 1 showing SUP-2026-0025 · Renewal — Supplier Agreement — Vantage Staffing · 草稿, still with no New. GET /api/v1/meta/actions still serves all ten clm_contract actions to all three personas, launch_contract@list_toolbar and executed_upload@list_toolbar among them. Direct POST /api/v1/data/clm_contract, same body: 201 draft for all three personas before (SUP-2026-0019/0020/0021) and after (SUP-2026-0022/0023/0024) — unchanged. NOT MEASURED: the F16 button in the browser — executed_upload is gated on execute_contract, which none of the three dispatched personas holds (the dev admin deliberately holds no position, README), so it was absent from the list header before and after; its declaration is untouched and it is still served. No ablation: the diff changes no behaviour a test pins, and this repo has no test suite to pin one.",
"gates": "Local, each exit code captured before any pipe into its own log file: pnpm install EXIT=0 ('Done in 1.2s using pnpm v10.28.0'); pnpm validate EXIT=0 ('✓ Validation passed (1212ms)'); pnpm lint EXIT=0 ('6 suggestion(s) (1183ms)' — the six pre-existing approval-approvers-may-resolve-empty notes, unchanged by this diff); pnpm typecheck EXIT=0 (tsc --noEmit, no output); pnpm lint:i18n-gate EXIT=0 ('COVERAGE : 0 missing keys across 2 locale(s)'); CLM_DEMO_SEED=1 pnpm validate EXIT=0 ('✓ Validation passed (1263ms)'). All six run on the final commit 30cfe08. hotclm has no scripts/pm/dispatch-gates.mjs and no .changeset directory, so there is no derived-family reconciliation and no changeset to write; CI's own conclusions on PR #146 are not read here — the report is delivered at the end of local verification.",
"dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm",
"mcp_calls": "0 — no MCP GitHub tool was called",
"api_writes": "2 — both through the relay from /home/claude/objectstack, as objectstack-fleet[bot]: POST /repos/objectstack-ai/hotclm/pulls (plus its POST /repos/objectstack-ai/hotclm/issues/146/assignees leg, one action) and POST /repos/objectstack-ai/hotclm/issues/136/comments (this report). git push -u origin claude/issue-136-no-raw-create is not a REST write. The pr_create read-back: 8191 bytes sent, 8191 stored, identical; assignees as the pull lists them: zhuangjianguo.",
"open_questions": [],
"out_of_scope_findings": [
"carrier: none — noted in the PR's Acceptance notes, not filed. The contract list header still offers 导入 (CSV import), a second generic write door into clm_contract that bypasses F1 exactly as the raw New did. It is a separate flag (userActions.import), the regression pass did not drive the wizard, and whether legal or the CLM admin should keep a bulk import is a product question. Observation only: not reproduced as a defect in this pass."
]
}
Release: no generic New on the contract list or the Renewals list for anyone; contracts are created through F1, F12 and F16. The remaining bulk door (导入) is filed for the decision box (#148). State: pm:dispatched removed, assignee cleared.
Platform card filed: objectstack-ai/objectstack#26182 — record:related_list with actions: [] still draws New. The app-side fix here (object-level userActions.create) stays valid on its own; the platform card is what lets a page drop New from one list only.
Activity
objectstack-fleet commentedon Oct 26, 2026
Claim: PM loop round 15
Session:
session_017nzWFRUo2U7evMkPdVniAnAccount:
zhuangjianguo(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-136-no-raw-createWorktree:
hotclm-issue-136Domain:
repo:hotclm(single-lane sister repo — nodomain:*)Seat:
repo:hotclm#1File surface:
src/objects/contract.object.ts(userActions.create) ·src/pages/contract_detail.page.tsonly if the Renewals list needs it · both translation bundles only if a string changes — 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 contract create doors (F1 and F16 are the only ones) | none known | every member who presses New on a contract listThread-read: none
Premise: measured in the 2026-10-26 browser regression pass on
mainf534eefwith real personas; re-measured by the dev onorigin/mainfirstSerial constraints cleared: nothing in flight on these files; #135, #136, #137 and #138 are dispatched together on disjoint surfaces (#136 and #137 share
contract_detail.page.tsonly if #136 needs the Renewals list — different lines).Generated by Claude Code
objectstack-fleet commentedon Oct 26, 2026
os-dev-report
{ "issue": 136, "status": "done", "branch": "claude/issue-136-no-raw-create", "pr": "https://github.com/objectstack-ai/hotclm/pull/146", "session": "session_017nzWFRUo2U7evMkPdVniAn", "premise_still_valid": true, "summary": "Re-measured the premise on origin/main f534eef first: the contract list header showed 新建 · 导入 · 发起合同 and the contract page's 续签合同 related list carried a 新建 for all three personas, and GET /api/v1/meta/objects/clm_contract answered userActions = null. The fix is one block in src/objects/contract.object.ts: userActions: { create: false }. That is 18.1.0's declared mechanism and it was measured: resolveCrudAffordances narrows the managedBy bucket default, and both the console's listNew and a related list's relatedNew read that same resolved row through resolveAffordance in @object-ui/core, so one declaration closes both doors that actions: [] (PR #123) could not. No role keeps a raw create — legal and the CLM admin launch through F1 too and F16 is the backfill door — and the per-role form would not have worked anyway: a toolbar visibleWhen binds to the record in scope, which on a standalone list is nothing, so a record.* predicate hides it for everyone by the fail-closed rule. No platform gap. The change is UI-only on 18.1.0 (plugin-security's affordance write guard covers engine-owned and append-only buckets only), and the data API answers are unchanged, which is reported, not altered.", "tests": "Metadata-only app: no unit suite exists (package.json has no test script). Evidence is the browser and the API, one throwaway DB on port 3136, pnpm demo, Chromium /opt/pw-browsers/chromium at 1440x1000, zh-CN, before = f534eef and after = 30cfe08 on the same database. Personas made the README way with no refusal: admin create-user 200 for Business Requester 1 and Legal Counsel 1, sys_user_position clm_legal_counsel 201 for the lawyer, change-password 200 each (mustChangePassword was stamped), plus the dev admin. Boots: '✓ Server is ready', no 'degraded capabilities', no 'no such table'; the six author-time warnings are the pre-existing approval-slate notes on contract_approval. Served metadata: userActions = null before, {\"create\":false} after. Contract list header: 新建·导入·发起合同 before → 导入·发起合同 after, all three personas. 概要 tab 续签合同 list: one New before → zero after, all three. Other related lists unchanged in both directions: 版本 (all three), 审查/偏离 (偏离 only for the requester; both for legal and admin), 履约义务 + 收付款计划 and 签署记录 (legal and admin) each keep their New. Record headers unchanged (发起续签 for the requester and admin; 终止·发起续签·编辑 for legal). F1 after the change, as Business Requester 1: the dialog opens (合同类型 9 / 我方签约主体 3 / 相对方 38 options), POST /api/v1/actions/clm_contract/launch_contract → 200, the 合同信息 screen opens, submitting created SUP-2026-0026 (status draft, read back over the data API) and went on to the 上传首个版本 screen; the scripted run stopped there for want of a file ('文件不能为空'), which is user input, not a refusal. F12 after the change, same persona on SUP-2026-0003: 续签合同 0 · 暂无相关记录 with no New → POST …/start_renewal → 200 → the list refetched to 续签合同 1 showing SUP-2026-0025 · Renewal — Supplier Agreement — Vantage Staffing · 草稿, still with no New. GET /api/v1/meta/actions still serves all ten clm_contract actions to all three personas, launch_contract@list_toolbar and executed_upload@list_toolbar among them. Direct POST /api/v1/data/clm_contract, same body: 201 draft for all three personas before (SUP-2026-0019/0020/0021) and after (SUP-2026-0022/0023/0024) — unchanged. NOT MEASURED: the F16 button in the browser — executed_upload is gated on execute_contract, which none of the three dispatched personas holds (the dev admin deliberately holds no position, README), so it was absent from the list header before and after; its declaration is untouched and it is still served. No ablation: the diff changes no behaviour a test pins, and this repo has no test suite to pin one.", "gates": "Local, each exit code captured before any pipe into its own log file: pnpm install EXIT=0 ('Done in 1.2s using pnpm v10.28.0'); pnpm validate EXIT=0 ('✓ Validation passed (1212ms)'); pnpm lint EXIT=0 ('6 suggestion(s) (1183ms)' — the six pre-existing approval-approvers-may-resolve-empty notes, unchanged by this diff); pnpm typecheck EXIT=0 (tsc --noEmit, no output); pnpm lint:i18n-gate EXIT=0 ('COVERAGE : 0 missing keys across 2 locale(s)'); CLM_DEMO_SEED=1 pnpm validate EXIT=0 ('✓ Validation passed (1263ms)'). All six run on the final commit 30cfe08. hotclm has no scripts/pm/dispatch-gates.mjs and no .changeset directory, so there is no derived-family reconciliation and no changeset to write; CI's own conclusions on PR #146 are not read here — the report is delivered at the end of local verification.", "dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm", "mcp_calls": "0 — no MCP GitHub tool was called", "api_writes": "2 — both through the relay from /home/claude/objectstack, as objectstack-fleet[bot]: POST /repos/objectstack-ai/hotclm/pulls (plus its POST /repos/objectstack-ai/hotclm/issues/146/assignees leg, one action) and POST /repos/objectstack-ai/hotclm/issues/136/comments (this report). git push -u origin claude/issue-136-no-raw-create is not a REST write. The pr_create read-back: 8191 bytes sent, 8191 stored, identical; assignees as the pull lists them: zhuangjianguo.", "open_questions": [], "out_of_scope_findings": [ "carrier: none — noted in the PR's Acceptance notes, not filed. The contract list header still offers 导入 (CSV import), a second generic write door into clm_contract that bypasses F1 exactly as the raw New did. It is a separate flag (userActions.import), the regression pass did not drive the wizard, and whether legal or the CLM admin should keep a bulk import is a product question. Observation only: not reproduced as a defect in this pass." ] }objectstack-fleet commentedon Oct 26, 2026
Landed — PR #146 · 2026-10-26
MERGED(squash onmain), merged at its ACCEPT head30cfe08;Fixes #136closes this cardRelease: no generic New on the contract list or the Renewals list for anyone; contracts are created through F1, F12 and F16. The remaining bulk door (导入) is filed for the decision box (#148). State:
pm:dispatchedremoved, assignee cleared.Generated by Claude Code
objectstack-fleet commentedon Oct 26, 2026
Platform card filed: objectstack-ai/objectstack#26182 —
record:related_listwithactions: []still draws New. The app-side fix here (object-leveluserActions.create) stays valid on its own; the platform card is what lets a page drop New from one list only.