Skip to content

A finance controller cannot launch a contract — clm_finance's field locks also apply to the draft they create #127

Closed

Description

@objectstack-fleet

Ruled: 6406413107 · letter A · 2026-10-26T01:47:10Z

Blocked-by: objectstack-ai/objectstack#25866 (field permission entry with a row condition; filed 2026-10-26, unlocks when a published release carrying it is installed here)

Filing class: ① product defect, needs a ruling (it touches §04 / §13 Q1) — reach: public door, measured on 18.1.0 by the #124 dev (os-dev-report on #124, PR #126 head 19e8dac). Reader: the maintainer, through the decision box.

Symptom

Every member holds clm_requester (PR #122), so every member is meant to be able to launch a contract (DESIGN.md §04 「所有员工默认」). A finance controller cannot: the launch wizard's last step answers

400 Node 'create_contract' failed: create_record(clm_contract) failed: [Security] Field write denied: not permitted to edit [confidentiality_term_months, governing_law, status, summary] on 'clm_contract'

The records manager is very likely in the same place (its set locks contract fields with editableOnly); not measured.

Why

clm_finance locks the legal fields and status on clm_contract (src/profiles/finance.profile.ts:48, §04 「FLS 锁法律字段」 + §13 Q1 「与 status」). Field-level security in this platform is per set and per object, not per row: it cannot say "locked on the contracts finance reaches through the approval sharing rule, open on the draft finance launches itself". And only sets that declare a field vote (src/profiles/_grants.ts), so clm_requester, which says nothing about those fields, does not lift the lock. The lock therefore also lands on the brand-new draft a finance controller creates. Until PR #126 this was hidden behind the business-unit 403 one step earlier.

What needs deciding

Which of §04's two promises gives way, or whether the platform should be asked for row-scoped field locks. The seat's reading of the options, for the director to set out:

No code moves until the maintainer rules.

Dedupe: hotclm issues listed and grepped for finance, Field write denied, launch, FLS — #64 (finance detail rows, ruled, different), none of them this.


Filed by the repo:hotclm PM seat from the #124 dev report.

Activity

  1. zhuangjianguo commented on Oct 26, 2026

    @zhuangjianguo
    Collaborator

    Ruling: batch #8 item 1 · letter A · maintainer 「A」 2026-10-26T01:47:10Z

    A — ask the platform. Field-level security gets a per-row condition; hotclm keeps §04 and §13 Q1 as written and waits.

    What the director checked before recording (objectstack main, hotclm main ae13f57):

    • FieldPermissionSchema carries only readable / editable (packages/spec/src/security/permission.zod.ts); no row predicate exists, and RLS cannot name a field. The gap is real, not a usage error.
    • getFieldPermissions (plugin-security permission-evaluator.ts) merges most-permissively, but only sets that declare a field vote. Opening the four fields on clm_requester would also lift finance's post-approval lock, so there is no app-side fix that keeps §13 Q1.
    • B would run the whole intake wizard as the system (the flow header records why it runs as the user), would still refuse a finance controller editing their own draft, and waits for a release carrying objectstack#25643. Not taken.

    Execution after the ruling:

    1. Director files the platform card at objectstack (field permission entry with an optional row condition, same expression language as RLS using); this issue gets a Blocked-by: line pointing at it.
    2. PM seat labels pm:blocked; no hotclm code moves until the platform ships, is released, and hotclm upgrades.
    3. Then the lane adds the condition to clm_finance (lock from approved onward) and clm_records (lock from signing onward), measures finance AND records launching a contract, and §04 stays unchanged.

    Doc: https://claude.ai/artifact/W7sEAgCjQ1XKQQ8UbebbPT


    Generated by Claude Code

  2. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    ContributorAuthor

    Ruling-ref: 6406413107

    State: needs-user-decision → pm:blocked on objectstack-ai/objectstack#25866 (letter A: ask the platform). No hotclm code until a release carrying it is installed here; then the lane adds the row condition to clm_finance (approved onward) and clm_records (signing onward) and measures a finance and a records persona launching a contract.


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 27, 2026

    @objectstack-fleet
    ContributorAuthor

    Unblocked — 2026-10-27

    upstream state installable here
    objectstack-ai/objectstack#25866 closed fix commit c8cb510 is an ancestor of tag @objectstack/spec@18.2.0

    main now pins @objectstack/* 18.2.0 (PR #155, merge 21f2319, push CI Validate success), so the fix is installable in this repo. The ruling recorded in the body stands; this card returns to the queue for dispatch. The dev re-measures the premise on main before writing code.

    State: pm:blocked → pm:queue.


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 27, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 17
    Ruling-ref: 6406413107
    Session: session_017nzWFRUo2U7evMkPdVniAn
    Account: zhuangjianguo (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-127-finance-launch-fls-when
    Worktree: hotclm-issue-127
    Domain: repo:hotclm (single-lane sister repo — no domain:*)
    Seat: repo:hotclm#1
    File surface: src/profiles/finance.profile.ts · src/profiles/records.profile.ts · src/profiles/_grants.ts (helper only, if needed) — the ruling's row condition on the finance lock (from approved) and the records lock (from signing) — 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 field permissions (a draft its own launcher can fill) | objectstack-ai/objectstack#25866 shipped in 18.2.0 | every finance controller and records manager who launches a contract
    Thread-read: 6437373910
    Premise: re-measured by the dev on origin/main 21f2319 (@objectstack 18.2.0) with README personas first
    Serial constraints cleared: Unblocked 2026-10-27 (18.2.0 on main); round 17 dispatches #134, #127, #64 and #159 together on disjoint files.


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 27, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
    "issue": 127,
    "status": "done",
    "branch": "claude/issue-127-finance-launch-fls-when",
    "pr": "#166",
    "session": "session_017nzWFRUo2U7evMkPdVniAn",
    "premise_still_valid": true,
    "summary": "Premise re-measured on origin/main 21f2319 (@objectstack 18.2.0): Finance Controller 1 and Records Manager 1 both failed 发起合同 at create_contract with 400 FLOW_FAILED 'Field write denied'. Per ruling A, the platform's field permission row condition (when, objectstack#25866, c8cb510, in 18.2.0) now scopes clm_finance's legal-field+status+termination_reason lock to status in POST_APPROVAL_STATUSES and clm_records' editableOnly lock to status in EXECUTION_STATUSES, through a new _grants.ts helper onRowsWhere (it refuses an unreadable entry, as the spec does). On every other row a conditioned entry is absent, so clm_requester alone decides on the launcher's own draft. Its locks still hold there (stamps, risk_level, liability_cap, termination_reason). Finance or records can update a non-window row only through the requester window (draft/submitted, own_and_reports), so nothing opens for other roles or other rows. After the change both personas launch (submitted run lands in_review, unsubmitted run stays draft). §13 Q1 holds unchanged, and requester and legal readings match main. One platform residual remains: the Console record form's field projection is row-blind by the 18.2.0 spec, so finance and records see their own draft's legal fields disabled in the 编辑 dialog, although the wizard and API writes are admitted.",
    "tests": "pnpm demo:zh, port 3142, fresh .objectstack each run: before 21f2319, after feec816. README personas: admin create-user, change-password, sys_user_position by name, set-user-manager to Executive 1, contract sharing rules re-evaluated. Browser: Playwright /opt/pw-browsers/chromium, zh-CN, signed in as each persona. LAUNCH (双向保密协议/极光系统/阿卡迪亚欧洲有限公司, filled title, 摘要, 适用法律, 保密期限, uploaded v1): before fc1 'POST …/contract_intake/runs/…/resume -> 400 FLOW_FAILED … Field write denied … [confidentiality_term_months, governing_law, status, summary]'; rm1 400, same with a long list (amount, auto_renew, contract_type …); br1 合同已发起。. After: fc1, rm1 and br1 all 合同已发起。; submitted runs land in_review, unsubmitted runs stay draft, with governing_law=德国法 and conf=24 stored. FILL (API as persona, current value written back): after, fc1 and rm1 PATCH own draft legal fields -> 200 and commercial fields -> 200 (before: no draft existed); fc1 own draft {status:draft} -> 200; {status:approved} -> 422 state machine; risk_level and submitted_at -> 403 for fc1, rm1 and br1 (requester locks); fc1 POST insert {status:approved, governing_law} -> 403 Field write denied [governing_law, status]; rm1 POST insert {status:signing} -> 403 [contract_type, governing_law, our_entity, party, title]. §13 Q1, identical before and after: fc1 approved governing_law, summary, confidentiality_term_months, status, termination_reason and jurisdiction -> 403 FLS x6; payment_terms+amount -> 200; signing governing_law+status -> 403, payment_terms -> 200; in_approval (finance-routed) -> 403 RLS. rm1 signing title, governing_law, amount and risk_level -> 403 FLS x4; signing status (same value) -> 200; expired archive_no -> 200; expired title -> 403; approved title -> 403 RLS. lc1 in_review governing_law, summary, risk_level and liability_cap -> 200; approved governing_law -> 200. br1 another owner's active -> 404. EXPLAIN (after, update): finance 10 and records 56 conditioned entries named 'locked when status in [...]'. FORM (browser, after): fc1 own draft 编辑 disables 摘要, 适用法律, 保密期限, 管辖, 风险等级 and 状态; saving 标题 -> POST /api/v1/batch 200, toast 已更新. rm1 own draft: only 档案号 enabled. br1 own draft (control): only 风险等级 disabled. Boot-phase WARN lines diff empty before vs after; 0 'row condition … applied as its lock' WARN lines after. REVERSE CHECK: when field changed to statuz -> pnpm validate exit 1 'field permission when field "statuz" is not a field on object "clm_contract"'; restored with git checkout HEAD --, git diff HEAD empty, hash matches HEAD. No ablation beyond that: metadata only, and no suite pins this behaviour.",
    "gates": "On feec816, each exit captured before any pipe: pnpm install 0; pnpm validate 0 (8 ⚠, line-identical to origin/main 21f2319, measured on a detached main worktree); pnpm lint 0 ('1 warning(s), 6 suggestion(s)', the one warning #64's security-controlled-by-parent-owner-policy-without-opt-in, identical to main); pnpm typecheck 0; pnpm lint:i18n-gate 0 (0 missing keys across 2 locales); CLM_DEMO_SEED=1 pnpm validate 0. CI on PR #166: not awaited (in_progress at report time).",
    "dogfood_battery": "n/a — hotclm, no ! in the title and no (narrowing) arm",
    "mcp_calls": "0",
    "api_writes": "2 — relay fleet-write pr_create (#166, read-back identical, assignee zhuangjianguo); post-stamped os-dev-report comment on #127. Plus git push of the branch (not REST).",
    "open_questions": [
    {
    "question": "After this PR, a finance controller or records manager launches a contract, but the Console 编辑 dialog on their own draft disables the row-conditioned fields. The 18.2.0 field projection is row-blind by declared contract ('Readers that hold no row … report a row-conditioned entry as its lock'). For finance that is the legal fields and status; for records it is every field but 档案号. The write gate admits those writes (wizard create, API PATCH 200). Raise a capability request upstream?",
    "options": [
    "A — ask objectstack for a row-aware field projection on the record form (the form judges when against the record it edits); hotclm waits, no app change",
    "B — accept: the launch wizard is the door for these fields on a launcher's draft, and later edits go through legal"
    ],
    "recommendation": "A, because the ruling's intent ('a draft its own launcher can fill') is met at launch but not in the form, and the gap is the platform's declared row-blind reader, which hotclm must not work around (AGENTS.md platform gaps)."
    }
    ],
    "out_of_scope_findings": [
    "class: none (declared platform behaviour, capability request) · reach: Console 编辑 dialog as Finance Controller 1 on own draft UfYFt_Hz3epInWiq, 18.2.0 feec816: 摘要/适用法律/保密期限/管辖/状态 disabled while PATCH of the same fields answers 200 · evidence: PR #166 Acceptance notes, FieldPermissionSchema.when describe · dedupe words: row-conditioned field permission form projection, when editable form disabled, row-blind field readers · carrier: none, noted in PR Acceptance notes",
    "carrier: #64 (requester.profile.ts, same round) · finance and records launchers' drafts now rely on clm_requester's contract locks (stamps, risk_level, liability_cap, termination_reason); #64's diff should keep them"
    ],
    "governed_text_proposal": "none — ruling A keeps DESIGN.md §04 and §13 Q1 unchanged"
    }

  6. objectstack-fleet commented on Oct 27, 2026

    @objectstack-fleet
    ContributorAuthor

    Ruling-ref: 6406413107

    Landed — PR #166 · 2026-10-27

    reading value
    PR #166 MERGED (squash on main), merged at its ACCEPT head feec816; Fixes #127 closes this card
    authority maintainer, project thread, verbatim: 「你可以自己审核的就自己合并」

    Release: a finance controller or records manager can launch a contract. Their field locks now apply only from approved (finance) or signing (records) onward, so §13 Q1 holds.

    Residual, platform: the console's 编辑 dialog still shows those fields disabled on their own draft, although saving them is allowed. This is filed in objectstack and linked below. State: pm:dispatched removed, assignee cleared.


    Generated by Claude Code

  7. objectstack-fleet commented on Oct 27, 2026

    @objectstack-fleet
    ContributorAuthor

    Residual filed upstream: objectstack-ai/objectstack#26594 (the record form disables row-conditioned fields on every row).


    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

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions