Skip to content

fix: four dead ends from the 18.2.0 browser pass (#2338 items 1-4) - #2346

Merged
objectstack-fleet[bot] merged 7 commits into
mainfrom
claude/project-thread-6amvua
Oct 27, 2026
Merged

objectstack-fleet[bot] merged 7 commits into
mainfrom
claude/project-thread-6amvua

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Requested by Irene · project thread
Part of #2338 (items 1–4)

Clause-②: no
Metadata-protocol: none

Items 5–13 of the list card stay open for later orders. Dispatch order 6450948994, claim 6451041709. Implemented by session session_01J8VVAk32RuJpqjMZ6Lqc7k.

维护者速读

  • 改了什么:四个浏览器回归里的死路。没有工单读权限的人不再看到 Service Overview;编辑工单选「已关闭」时出现解决方案字段;市场经理、市场总监两个岗位绑定 Marketing User 权限集,可以编辑活动、登记成员;商机列表表头的「更新阶段」去掉,改为每个商机表格视图的勾选栏都提供它。
  • 为什么改:QA: complete browser pass of HotCRM on @objectstack/* 18.2.0 — all 161 feature-inventory rows, Setup and Studio, in six tracks #2320 浏览器测试在 18.2.0 上实测,这四处要么整页 403,要么保存不了、点了失败。
  • 风险与回滚:都是元数据改动,回滚即还原本 PR。注意一个副作用:五个商机列表视图声明了勾选栏动作后,平台默认的批量「删除」不再出现(Open Deals 本来就是这样),详见 Acceptance notes。
  • 先看:src/sales/sharing/positions.ts(两个岗位新增权限集,权限面最大);src/sales/views/opportunity.view.ts(五个视图的勾选栏与默认删除的取舍);src/service/views/case.view.ts(解决方案字段的 visibleWhen)。
  • 两分钟验证:以服务坐席打开一张处理中的工单,点 Edit,把状态改为 Closed,应立刻出现 Resolution 字段,填写后 Update 保存成功;New 新建工单对话框里没有 Resolution。
  • 需要你做的:无,席位落地。

What changes

Item 1: dashboards offered to personas who cannot read them

  • nav_service_dashboard now carries CASE_READ_AUDIENCE (case read, and not a sales rep), the gate the three case reports already carried, renamed from CASE_REPORT_AUDIENCE (src/service/index.ts). The console prunes object entries by read access but not dashboard entries, so a marketing user, who reads no cases, was offered Service Overview beside no Cases and no My Cases.
  • Measured before (main, 18.2.0, a user holding only the marketing_user position): Service Overview drew 14 tiles reading "You don't have permission to view the data behind this widget", one 403 POST /api/v1/analytics/dataset/query each. After: the entry is gone from that user's sidebar; Knowledge stays (screenshots 03-after-mkt-sidebar.png).
  • Sales Activity: no gate added. Every member reads their own events and tasks through crm_user, so all 13 tiles answer 200 for the marketing user; the zeros and "No data yet" are their own activity, not a refusal. A read gate would hide it from no one. This is the order's mechanism assumption measured false for that entry, reported rather than worked around.
  • Sales Performance: kept open. It refuses that user 1 of its 15 tiles, Quota Attainment by Rep (forecast_metrics; the set grants no crm_forecast), not the 2 the card estimated. Hiding the whole dashboard for one tile would repeat neither house precedent (Sales Activity and Customer Churn Signals are both kept open for a single refused panel). No tile-level read gate exists on 18.2.0: a widget's requiresObject is filtered against the served object list (DashboardView in the console), and /api/v1/meta/object serves all 99 objects, crm_forecast included, to the marketing user; a widget takes no visible. That is a platform finding, in the report. The reasoning is recorded beside the audiences in src/sales/apps/crm.app.ts.

Item 2: Edit Case offers Closed but no Resolution

  • The case form (src/service/views/case.view.ts) offers resolution right under Status, gated by has(record.status) && (record.status == "resolved" || record.status == "closed") (TOTAL, per the house rule). It is not required on the form: resolution_required_for_closed asks for it on closed only.
  • The same form is the create dialog; a new case opens at new, so the field stays off it.
  • Browser, as a service agent: Edit on an In Progress case shows no Resolution; picking Closed shows it; typing one and pressing Update saved status: closed with the resolution stored; the New Case dialog shows no Resolution (04–07).

Item 3: Marketing Manager and Director cannot edit live campaigns

  • Both positions now declare permissionSets: ['marketing_user'] (src/sales/sharing/positions.ts). An existing set, not a new one: a rung that leads the marketers does their work, enrolling members and tracking responses included.
  • Grants that change for a holder of either position (from crm_user alone to crm_user + marketing_user): crm_campaign create + edit + view all; crm_campaign_member create + read + edit; crm_lead create + edit + view all + export; crm_contact create + edit + view all + export; crm_account and crm_opportunity view all + export (private deals held back by the set's row filter); crm_article_feedback create/read/edit, crm_knowledge_article read; sys_attachment, sys_comment, sys_activity as the set declares; field rules crm_opportunity.amount and crm_account.health_score read-only; the set's three row-level policies (private-deal filter, campaign and member update widening).
  • Measured before: both rungs held crm_user + member_default only, campaign allowEdit: false, and PATCH /api/v1/data/crm_campaign/ID answered 403 PERMISSION_DENIED on all seven seeded campaigns. After: both resolve marketing_user through the position alone, PATCH answers 200, and the record header shows Enroll Members and Edit (08).
  • Measured, and worth knowing: on 18.2.0 the marketing_user set alone writes every campaign, completed ones included (the write scope is org-wide), so for these rungs the campaign_leadership_* share grants nothing beyond the set. The note in src/marketing/sharing/campaign.sharing.ts that the sharing layer's write-owned baseline still refuses a specialist's campaign is a 17.0.0-rc.2 measurement and no longer holds; left untouched here (outside the claimed surface) and listed in the report.

Item 4: "Update Stage" in the list toolbar fails on every selection

  • Measured: the console's server-action dispatch from the list header never injects _selectedIds (the record-id resolver in the console framework bundle refuses more than one ticked row with "select exactly one row", and with none opens the dialog over no record). So the toolbar copy cannot work on a selection on 18.2.0; it is removed (locations: [], the spec's headless spelling; the selection bar places an action by name).
  • Card premise corrected: only the list view (Open Deals) carried the aggregate bulkActionDefs; named list views do not inherit it (All Opportunities' bar offered only Delete). Surface extended at the seat's word, 19:2xZ: the five other grid lists (All Opportunities, My Open Deals, Stale, Tender This Quarter, Closing This Quarter) now declare the same entry through one GRID_BAR constant (Update Stage plus an explicit bulk Delete; see below).
  • Browser, as the admin: no Update Stage in the header; two rows ticked on Open Deals and on All Opportunities each moved 2/2 ("Succeeded 2 / 2") (09–11).

Tests

Acceptance notes

  • Bulk Delete kept on every grid list (seat ruling B, round 1): an authored bulk entry hides the console's default bulk Delete. Open Deals had already lost it before this PR. So GRID_BAR also carries an explicit operation: 'delete' def modelled on all_accounts, and all six grid lists (Open Deals included) use it. The labels are in the zh-CN, es-ES and ja-JP packs, one OPPORTUNITY_BULK_DELETE per pack under each view's bulkActions. test/bulk-action-dispatch.test.ts requires the delete def on every grid list, and an ablation of it turns the test red. bulkActions: ['delete'] alone is refused by pnpm lint (action-name-undefined, measured).
  • Platform finding (report): no read-access gate exists for a single dashboard widget.
  • Stale comment (report): campaign.sharing.ts's 17.0.0-rc.2 write-boundary note.
  • One early commit on this branch carries a model-named co-author trailer from the harness; history is not rewritten.

Verification

🤖 Generated with Claude Code

https://claude.ai/code/session_01J8VVAk32RuJpqjMZ6Lqc7k

@vercel

vercel Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated
hotcrm Ignored Ignored Oct 27, 2026 11:24pm UTC

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation ci/cd CI plumbing and the verification pipeline metadata Declarative metadata — schema, security posture, UI surfaces backend Server-side behaviour — hooks, flows, actions labels Oct 27, 2026
…em 4)

Any authored bulk entry hides the console's default bulk Delete, so the
Update Stage defs left the six opportunity grid lists without it (Open Deals
already lacked it). The shared constant becomes GRID_BAR and carries an
explicit operation: 'delete' def, modelled on all_accounts, with its labels
in the zh-CN, es-ES and ja-JP packs. The selection-bar pin now requires the
delete def too; ablating it turned the pin red. The mass_update_stage
comment no longer claims that list views inherit the def.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J8VVAk32RuJpqjMZ6Lqc7k
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对

Verdict: ACCEPT · repo:hotcrm seat 1 · huangyiirene · session session_01J8VVAnPkgK6CWrJa6t7Fk5 · 2026-10-27T23:25Z · head 9568b7c
Implemented-by: card thread session session_01J8VVAk32RuJpqjMZ6Lqc7k (report 6455824863 on #2338; round 1 in the seat thread) · Reviewed-by: the seat, against the 24-file diff and hotcrm origin/main a31d91a

  • Shape:
  • Item 1 (marketing user sees dashboards it cannot read):
    • nav_service_dashboard is gated on case read plus not a rep (CASE_READ_AUDIENCE), the same gate the case reports use. This was measured as 14 refused tiles before, and the entry is gone after.
    • Premise corrections are accepted. Sales Activity answers every tile for that user, so it stays ungated. Sales Performance refuses 1 of 15 tiles and stays open, like Customer Churn Signals.
  • Item 2 (closing a case offers no Resolution): the case form shows resolution under Status, with a TOTAL visibleWhen on resolved/closed. It is not required on the form, and the New dialog does not show it.
  • Item 3 (marketing manager cannot edit a campaign):
    • marketing_manager and marketing_director declare permissionSets: ['marketing_user'], the existing set.
    • Measured 403 before; after, 200 with Edit and Enroll Members.
    • Every grant change is listed in the PR.
  • Item 4 (Update Stage fails from the list header):
    • mass_update_stage has locations: []. Measured on 18.2.0, the list-header dispatch injects no _selectedIds.
    • GRID_BAR puts the aggregate Update Stage and an explicit bulk Delete (modelled on all_accounts) on all six grid lists. Ruling B keeps Delete wherever it was.
    • The action comment now says list views do not inherit the def.
  • Tests:
    • New and changed pins: the navigation audience, the case create form, marketing leadership campaign edit, demo staffing, and bulk-action dispatch, which now requires both defs on every grid list.
    • Ablations of the grid-list def and of the delete entry each turn the pin red. The restore was proven by hash.
  • Gates:
    • pnpm verify exit 0 at 9568b7c: 232 files, 4495 passed, 1 skipped.
    • CI on 9568b7c: 5 of 9 check runs are success. Build and Test, Typecheck and Build, Playwright and CodeQL are still running, and auto-merge waits for them.
  • Out of scope, carried by the seat:
    • A dashboard tile has no read gate on 18.2.0. requiresObject checks install presence, not read access. This becomes a platform card.
    • The stale write-scope comment in campaign.sharing.ts is noted only.
  • Deviation noted: the report lists none beyond the forbidden MCP reviewer request.
  • Landing: non-governed. The seat patches the body, flips the PR to ready and arms auto-merge in this stroke.

Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend Server-side behaviour — hooks, flows, actions ci/cd CI plumbing and the verification pipeline documentation Improvements or additions to documentation metadata Declarative metadata — schema, security posture, UI surfaces

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants