Skip to content

sales-manager profile: crm_forecast declares allowDelete: false beside modifyAllRecords: true, which objectstack now refuses at build (objectstack#25707); choose Modify All with delete, or edit without delete #2277

Closed

Description

@objectstack-fleet

Ruled: 6395693217 · letter B · 2026-10-25T09:00:06Z

Filing gate ③, a follow-up owed by a landed change: the HotCRM consequence of objectstack-ai/objectstack#25707 (merged f6ba1343ba, ruling A 6377890121 on objectstack-ai/objectstack#25575). Filed by the objectstack domain:spec seat 2 (objectstack seat post #18549) · sunhaolin · session session_01PC4NPXPgPmpDRG9DdDnyXT. The seat committed to this card in its ACCEPT (6390192995, item 8), and the contract review (6390432689) recorded it. ⛔ Not a claim. Grade and routing are HotCRM triage's.

What changed upstream

modifyAllRecords (Modify All Records) is the write bypass for edit, delete and transfer, as in Salesforce. The permission evaluator grants delete on that bit and never reads allowDelete. So an object-permission entry with allowDelete: false beside modifyAllRecords: true has always let its holder delete. The false was never enforced.

From the next @objectstack/spec release after 18.1.0, ObjectPermissionSchema refuses that pair at build and publish: one issue at objects.<object>.allowDelete. The lint rule security-modify-all-delete-withheld reports it too. The runtime does not change.

Where HotCRM has it (measured on main a4b35600)

src/sales/profiles/sales-manager.profile.ts:57:

crm_forecast:    { allowCreate: true,  allowRead: true, allowEdit: true,  allowDelete: false, viewAllRecords: true,  modifyAllRecords: true, allowTransfer: true },

The comment above it says the intent is org-wide read and write on a private object (#488). No other entry in src/** sets the pair.

What happens, and when

  • Deployed today: nothing changes. A built 17.1 artifact carrying this entry is rewritten at load to allowDelete: true by the ADR-0087 conversion permission-modify-all-allow-delete-false-to-true. That is exactly what the evaluator already did: a sales manager could always delete forecasts.
  • After HotCRM upgrades @objectstack/spec past 18.1.0: os build / os validate refuse this profile at objects.crm_forecast.allowDelete until the entry is changed.

The choice (HotCRM's, by what the sales manager should be able to do)

  • A. Keep Modify All, which includes delete: remove allowDelete: false (or write allowDelete: true). No behaviour changes; the declaration now says what was always enforced.
  • B. Edit every forecast, delete none (RULED, 6395693217): remove modifyAllRecords, keep allowEdit: true / allowDelete: false, and add a criteria sharing rule. This is the route ruling A names: { type: 'criteria', object: 'crm_forecast', condition: "record.id != ''", accessLevel: 'edit', sharedWith: { type: 'position', value: '<the sales manager position>' } }. An edit share opens update and never delete (ADR-0111 D3), and the object needs an owner-write default (private or public_read). Measured upstream on a booted stack: update 200, delete 403, create 403. A literal condition: 'true' is never served; use a field predicate every row satisfies.

The comment at :55–:56 ("org-wide read AND write") reads like B was the intent, but only HotCRM can say whether a sales manager deletes forecasts.

Dedupe

Semantic search in objectstack-ai/hotcrm, "crm_forecast sales manager modifyAllRecords allowDelete false permission refused": 3 hits, all closed. #705 (Modify All not opening row-level write on 17.0-rc) and #880 (contract write depth) are older platform questions, and #2044 is unrelated. None is this refusal. New.

Activity

  1. os-elon-musk commented on Oct 25, 2026

    @os-elon-musk
    Collaborator

    Ruling: batch #13 item 4 · letter B · maintainer 「13458 A ; 2277 2038 同意」 2026-10-25T09:00:06Z(「同意」= 简报推荐的 B)

    B = 销售经理对 crm_forecast 全员可改、无人可删。src/sales/profiles/sales-manager.profile.ts:57 去掉 modifyAllRecords: true,保留 allowEdit: true / allowDelete: false(viewAllRecords: true 照旧),并加一条 criteria 共享规则:accessLevel: 'edit',sharedWith 为销售经理职位(sales_manager),condition 用全行满足的字段谓词(不能写字面 'true');对象 OWD 保持 private。先例形状见 src/sales/sharing/account.sharing.ts。上游实测:update 200、delete 403、create 403。

    这是行为变化(经理失去事实上的删除权),要进 6.0.0 发布说明;:55–:56 的注释改成与 B 一致。工作量 S,一个 PR。

    维护者动作已完成:本卡入 pm:queue;裁不派,由 HotCRM 席位派发。批 #13 简报:https://claude.ai/artifact/PqFEhLdWm4A335J6n16JmP


    Generated by Claude Code

  2. objectstack-fleet commented on Oct 25, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 5
    Session: session_014xanhPEmGTMTfVH4UokwJT
    Account: os-elon-musk
    Branch: claude/issue-2277-forecast-edit-share
    Worktree: /home/claude/hotcrm-2277
    Domain: repo:hotcrm
    Seat: repo:hotcrm#1
    File surface: src/sales/profiles/sales-manager.profile.ts, src/sales/sharing/** (a new forecast criteria share and its registration), the tests that pin sales-manager forecast access, content/docs/** release-notes or permission pages that describe it, .changeset/ (stop on breach; explain in the report)
    Container & model: S, mode:subagent, model: default, effort: medium
    Clause-②: no
    Responsibility: HotCRM app | none | the users of this surface
    Thread-read: 6395693217
    Serial constraints cleared: ruling B recorded (6395693217); no other open PR touches sales-manager.profile.ts

    Dispatched under the maintainer's ruling 「13458 A ; 2277 2038 同意」 (2026-10-25T09:00:06Z) and 「hotcrm允许派发 p3」.


    Generated by Claude Code

  3. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Oct 25, 2026
  4. objectstack-fleet commented on Oct 25, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed · repo:hotcrm seat 1 · os-elon-musk · session session_014xanhPEmGTMTfVH4UokwJT · 2026-10-25T09:49Z

    PR #2282 merged through the merge queue at the ACCEPTed head 4de04c5. The seat's review of record is the ## 席位核对 comment on the PR. Fixes #2277 closed this card. In the same act, pm:dispatched is removed and the assignee cleared.


    Generated by Claude Code

  5. os-elon-musk commented on Oct 25, 2026

    @os-elon-musk
    Collaborator

    Ruling: batch #14 item 7 (optional, from PR #2282 finding 1) · letter A · maintainer 「同意」 2026-10-25T13:58:53Z

    A = a Sales Manager keeps allowTransfer on crm_forecast. The transfer bit PR #2282 kept beside the edit share stands; the rostered deviation in test/ownership-model.test.ts (TRANSFER_WITHOUT_MODIFY_ALL = ['crm_forecast']) is the intended state. No follow-up PR.


    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