Repository navigation
Browser pass #2320 on 18.2.0 — HotCRM-side defects (13 items: dashboards shown to personas with no access, a dead-end case close, campaign editors who cannot edit, a toolbar action that always fails, …) — list card, split per item at dispatch #2338
Description
Filing gate: ① product defects with reach measured. These are the HotCRM-side findings of the #2320 browser pass, run by six cloud testers on main with @objectstack/* 18.2.0. The repo:hotcrm seat re-read each landing site on origin/main ea567f2. Filed by the repo:hotcrm seat (huangyiirene, session session_01J8VVAnPkgK6CWrJa6t7Fk5).
Who acts: the repo:hotcrm seat. This is a list card, like #2060 and #2073. Each item is split out or dispatched on its own when it is picked up. The items are folded here because the fleet write budget is 40 writes per hour.
Evidence: every item names its track and finding id. The reports are comments on #2320:
| Track | Report comment |
|---|---|
| Sales | 6443461344 |
| Setup | 6442758420 |
| Studio | 6443071470 |
| Service | 6444034183 |
| Marketing | 6444160853 |
| Revenue | 6444188882 and 6450345451 |
The screenshots are in project files qa-2320/<track>/, and on each track's branch named in its claim.
Not here:
- Revenue D6, the rejected-deal approval bypass. It is fix(opportunity): a large deal whose approval was rejected can never be resubmitted, and closes won with no approval #2334, which also took Sales D5 (Recall resubmits at once).
- Marketing D1 and D2, the Add to Campaign gates and a member with both a Lead and a Contact. They are fix(marketing): Add to Campaign skips the gates Enroll Members enforces (completed campaign, email opt-out), and a member can hold both a Lead and a Contact #2336.
- Marketing D4, where an unsubscribe clears
has_responded. It waits on the maintainer's ruling. - The platform and console findings, which are filed in objectstack and listed on QA: complete browser pass of HotCRM on @objectstack/* 18.2.0 — all 161 feature-inventory rows, Setup and Studio, in six tracks #2320.
- The 115 UI/UX suggestions, which wait for the maintainer's discussion before any card is filed.
The items
p2: a user is stopped, or sees the wrong thing
- Dashboards are offered to personas who cannot read the data behind them (Setup D1, D2; Marketing D12).
- A user holding
marketing_usersees Service Overview. All 9 tiles say "You don't have permission to view the data behind this widget", andPOST /api/v1/analytics/dataset/queryreturns 403. The set's own description says it "see[s] no cases". - The same user's Sales Performance has 2 no-access tiles; these are likely the
crm_forecastones, since the set has no forecast read. - Sales Activity shows that user zeros and "No data yet" beside "Open Deals 10".
- Landing:
src/service/index.ts:209nav_service_dashboard(visible: NOT_A_SALES_REPonly);src/sales/apps/crm.app.ts:262nav_sales_dashboard(OPPORTUNITY_AUDIENCE), or the forecast tiles' audience;src/sales/apps/crm.app.ts:379nav_activity_dashboard(novisible).
- Gate them on read access, the way Cases and My Cases are already hidden for this user. Per the [Decision] A sales rep opens the Executive Overview by URL although the sidebar hides it: is the nav
visiblepredicate an access boundary (needs a server-side gate) or only tidying #2287 ruling, navvisibleis tidying, not a boundary, so this is about not offering a page that cannot work.
- A user holding
- Edit Case offers Status "Closed" but has no Resolution field, so the save is a dead end (Service D3).
- The save is refused with "Resolution is required when closing a case", and there is nothing to fill in. The Resolution section is hidden on the record while it is empty, so the only way out is Cancel, then Close Case.
- Landing:
src/service/views/case.view.tsformsections[0].fields. The same form is the create dialog, so make Resolution visible when the status is resolved or closed, rather than always on. - Evidence:
svc-45-sm-close-without-resolution.png.
- Marketing Manager and Marketing Director cannot edit live campaigns (Marketing D3).
- A user holding only
marketing_manageropens CPG-0003 (planning). There is no Edit or Enroll, and a PATCH returns 403. - Both positions (
src/sales/sharing/positions.ts, around lines 94–101) declare nopermissionSets. The object-level gate therefore refuses before thecampaign_leadership_*edit share can apply. - Their descriptions and inventory MKT-008 promise "edits every live campaign, whoever owns it".
- Evidence:
mkt-12-mktmgr-*.
- A user holding only
- "Update Stage" in the opportunity list toolbar fails on every selection (Sales D6).
- With 2 rows ticked it says "This action runs on a single record — select exactly one row". With none ticked, the dialog opens and targets nothing. Only the bulk-bar copy in Open Deals works.
- Landing:
src/sales/actions/opportunity.actions.ts:202locations: ['list_toolbar']onmass_update_stage. Measure whether dropping the toolbar location is enough, since the views'bulkActionDefsalready carry the action. - Evidence:
opp-13-mass-stage-two-open.png,opp-13-mass-stage-none.png.
p3: misleading, but not blocking
- Sales Home tells every user to open an assistant panel this install does not have (Sales D2 = Setup D9).
- The card says "Open the assistant panel from the right edge of the page…", but there is no such control.
GET /api/v1/ai/agentsreturns[], and skills return 501. - Landing:
src/sales/pages/home.page.ts:295ai_briefing. Reword it, or drop it until the platform can show a component only when theaicapability is present; if so, that is a platform card.
- The card says "Open the assistant panel from the right edge of the page…", but there is no such control.
- The contact record page offers neither Mark as Primary nor Send Email (Sales D3).
- Both are declared with
locations: ['record_header', 'list_item']insrc/sales/actions/contact.actions.ts, but they appear only in the row menu.crm_contacthas no authored page, and the synthesized header shows only the activity actions. - Measure first: whether the console's synthesized header is meant to take
record_headeractions (then it is a platform card), or whether the app needs a contact page. - Evidence:
con-05-nonprimary-header.png.
- Both are declared with
- Deal Timeline draws every deal from
created_at, so closed deals show backwards bars (Sales D4).- A deal closed in September reads "10/27/2026 → 9/7/2026".
- Landing:
src/sales/views/opportunity.view.ts:175. - Evidence:
opp-01-04-deal-timeline.png.
- The Event Attendees related list shows no attendee names (Marketing D5).
- The list has three columns (Attendee Type, Response, Organizer), so it cannot tell who is attending.
- The related list reads
highlightFields(src/sales/objects/event_attendee.object.ts:184), not the curated view. - Evidence:
act-13-event-record-tab-related2.
- A reopened task keeps its Completed Date (Marketing D6).
- Completed, then back to In Progress:
completed_datestays, and the field is read-only, so the user cannot clear it. - Landing:
src/sales/objects/task.hook.tstask_completion. Clear the stamp on leavingcompleted, as the member lifecycle hook does.
- Completed, then back to In Progress:
- Sidebar "Lead Inflow" opens a report about lead engagement (Marketing D8).
- It counts contacted leads by
last_contacted_date. Logging a call on a May lead moved that lead to October. - Landing:
src/sales/apps/crm.app.ts:451(the label) or the report's name and definition.
- It counts contacted leads by
- "Top Resolving Articles" lists a "—" row with 0 (Service D4 = Marketing D9).
- Closed cases with no article form a blank bucket.
- Landing:
src/service/dashboards/service.dashboard.ts:293. Exclude a nullresolved_articlein the widget filter.
- Refusal copy shows API names to users (Revenue D3).
- Examples:
- "…is accepted; only internal_notes may be edited. Attempted: discount." (
src/revenue/objects/quote.hook.ts:224) - "Cannot shrink end_date (…) after activation." (
src/sales/objects/contract.hook.ts:93) - The same shape on closed deals (
src/sales/objects/opportunity.hook.ts:305).
- "…is accepted; only internal_notes may be edited. Attempted: discount." (
- Use the field labels the user sees. This overlaps theme 5 of the UX summary, so merge them if the maintainer adopts that theme.
- Examples:
- Three docs rows are wrong (Setup D3, Revenue D5, Marketing D7).
- Inventory PRM-006 says 「十一条」 and
src/docs/crm_admin.mdlists eleven rules. Twelve ship;forecast_manager_sharing(fix: a sales manager edits every forecast and deletes none (#2277) #2282) is missing. - Inventory CON-001 says 私有共享, but
contract.object.ts:20iscontrolled_by_parent. - Inventory MKT-001 promises 父活动层级, but
campaign.object.tshas noparent_campaign.
- Inventory PRM-006 says 「十一条」 and
Dedupe
I searched hotcrm issues with these queries:
- "Sales Home AI assistant card panel does not exist"
- "marketing user dashboard tiles no access Service Overview navigation visible"
- "marketing manager director position permission set edit campaign"
- "case edit form status closed resolution field missing; update stage toolbar single record; contact record header send email mark primary"
- "event attendee related list names; reopened task completed date; deal timeline created_at; top resolving articles dash row; lead inflow report label"
All the hits are closed: #1002, #2295, #2250, #2287, #2244, #510, #2060, #970, #2127, #2073, #2288, #2291, #2238, #2126 and #2158. None reports these items as they stand on 18.2.0. The one open hit, #2296, is the KPI compare card, which is unrelated.
Grade: priority:p2, from the highest item. Items 5–13 are p3 when they are split out.
Generated by Claude Code
Activity
objectstack-fleet commentedon Oct 27, 2026
Dispatch order · #2338 items 1–4 (the p2 section) ·
repo:hotcrmseat 1 ·huangyiirene· sessionsession_01J8VVAnPkgK6CWrJa6t7Fk5· 2026-10-27T18:58ZThis order takes the slot that #2297 freed (PR #2339 is accepted and auto-merge is armed). Irene asked for dispatch to continue at concurrency 5 (「继续派发」, 「4小时之后可以继续」). This is a list card. This order dispatches only items 1–4; items 5–13 stay on the card for later orders.
Container & model: M ·
mode:thread· modelTIER_DEFAULT· efforthigh(the thread runs at the project default).Role file:
objectstack-ai/objectstack.claude/agents/os-dev.md(read it in full). hotcrmAGENTS.mdbinds. Re-measure every file:line againstorigin/main(ea567f2or later).Writes: the PR goes only through the relay op
pr_create(draft, basemain,assignees: [huangyiirene]). Comments go only throughscripts/pm/post-stamped.mjs. ⛔ No GitHub MCP write, and ⛔ no issue filing; out-of-scope findings go in the report. The write budget is tight, so make two writes: the PR and the report.Report: the
os-dev-reportJSON, as a comment on this card and as the final message to the seat thread. ⛔ Start editing only after the newestClaim:on this card names your branch. Push work in progress at each milestone, so a stop loses nothing.Ruling (not to be re-decided)
Part of #2338 (items 1–4). The PR body has one section per item, with its evidence.nav_service_dashboard,nav_activity_dashboardand the forecast-bound tiles (ornav_sales_dashboard) on read access, the way Cases and My Cases are already gated.visiblepredicate an access boundary (needs a server-side gate) or only tidying #2287 ruling, navvisibleis tidying, not a boundary. ⛔ Do not add a server-side gate.campaign_leadership_*edit share apply.PM mechanism assumptions (measure first, on the installed 18.2.0)
visiblepredicate can read object read access (current_user.can(...)or the repo's existing audience predicates) for a user who holds onlymarketing_user.visibleWhenonstatusworks on the Edit dialog and keeps the field off the New dialog.list_toolbaraction with a selection runs on the whole selection, or the console offers no such mode.A refusal is a platform card, not a workaround. Stop and report it with evidence.
Suggested route (optional)
marketing_usercreated in Setup for items 1 and 3, and the service agent for item 2.visibleWhen, and the positions' sets.Serial constraints:
compareTo: previousPeriodon the revenue, pipeline, win and case tiles so a manager sees whether the number is up or down #2296 edits dashboard KPI tiles, including on the service dashboard. Item 1 touches nav entries and tile audiences, notcompareTo. If both PRs editservice.dashboard.tsorsales.dashboard.ts, whichever lands second mergesorigin/main.opportunity.actions.ts, which fix(opportunity): a large deal whose approval was rejected can never be resubmitted, and closes won with no approval #2334 does not.Generated by Claude Code
objectstack-fleet commentedon Oct 27, 2026
Claim: PM loop round 1
Session:
session_01J8VVAnPkgK6CWrJa6t7Fk5Account:
huangyiireneBranch:
claude/project-thread-6amvuaWorktree:
hotcrm-issue-2338(card thread; branch is platform-designated, the measured platform exception toclaude/issue-N-slug; implementing sessionsession_01J8VVAk32RuJpqjMZ6Lqc7k)Domain:
repo:hotcrm(single-lane repo, nodomain:*)Seat:
repo:hotcrm#1File surface: items 1–4 only:
src/service/index.tsandsrc/sales/apps/crm.app.tsnav audiences, forecast tile audiences insrc/sales/dashboards/sales.dashboard.tsif that is the route,src/service/views/case.view.ts(Resolution visibleWhen),src/sales/sharing/positions.tsand a permission set undersrc/sales/profiles/(item 3),src/sales/actions/opportunity.actions.tsmass_update_stagelocations, the four locale packs if a label is added, docs naming these surfaces (en, zh-Hans, zh-Hant), tests undertest/,.changeset/(stop on breach; explain in the report)Container & model:
M,mode:thread,model: TIER_DEFAULT (sister repo, hand-derived),effort: the order asked high; the thread runs at the project default, which only the maintainer setsClause-②: no
Metadata-protocol: none
Responsibility: hotcrm app metadata (nav audiences, form visibleWhen, position sets, action locations); a refusal is a platform card
Thread-read: 6450948994
Premise: holds — re-read at claim on hotcrm
origin/mainea567f2(order comment 6450948994)Serial constraints cleared: none; #2296 edits KPI compareTo (whoever lands second merges origin/main), #2334 opportunity approval, #2336 enroll flows, #2276 registration
test, docs, changeset: pin the four #2338 fixes and say what changed …
objectstack-fleet commentedon Oct 27, 2026
Dispatch order · #2338 items 5–13 (the p3 section) ·
repo:hotcrmseat 1 ·huangyiirene· sessionsession_01J8VVAnPkgK6CWrJa6t7Fk5· 2026-10-27T19:27ZThis order takes the slot freed by #2296 (PR #2340). Items 1–4 are already dispatched under order 6450948994 (claim 6451041709, branch
claude/project-thread-6amvua). This order covers items 5–13 only.Container & model: M ·
mode:thread· modelTIER_DEFAULT· efforthigh(the thread runs at the project default).Role file:
objectstack-ai/objectstack.claude/agents/os-dev.md. Read it in full. hotcrmAGENTS.mdbinds. Re-measure every file:line againstorigin/main(60d745bor later).Writes:
pr_create: draft, basemain,assignees: [huangyiirene].scripts/pm/post-stamped.mjs.Report: the
os-dev-reportJSON, posted as a comment on this card and sent as the final message to the seat thread. ⛔ Start editing only after the newestClaim:on this card names your branch for items 5–13. Push work in progress at each milestone, so a stop loses nothing.Ruling (not to be re-decided)
Part of #2338 (items 5–13). The body has one section per item.ai_briefingcard so it names nothing the user cannot find, or drop it. Say which you chose. ⛔ Do not add a capability check the platform does not offer; if one is needed, name it as an out-of-scope finding.record_headeractions by design, add a minimal contact record page with Mark as Primary and Send Email, as the lead and opportunity pages do. If the console should show them and does not, stop on that item and report it as a platform finding.completed_dateon leavingcompleted.resolved_articlefilter on the "Top Resolving Articles" widget.RECORD_LOCKED/ 409 code. Update any test that pins the old text.crm_admin.mdrow, CON-001, MKT-001).PM mechanism assumptions (measure first, on the installed 18.2.0)
relatedListColumnson thecrm_eventlookup, orhighlightFieldsoncrm_event_attendee, changes the Event Attendees related list.record_headeractions (item 6).A refusal is a platform card, not a workaround. Stop on that item and report it with evidence.
Suggested route (optional)
Serial constraints:
6amvua) touch nav audiences, the case form, positions andmass_update_stage, plusopportunity.view.tsbulk defs. Item 7 also touchesopportunity.view.ts. Whichever lands second mergesorigin/main.opportunity.hook.tsandquote.hook.ts. Item 12 touches messages in both files, so whichever lands second merges.Generated by Claude Code
objectstack-fleet commentedon Oct 27, 2026
Claim: PM loop round 1
Session:
session_01J8VVAnPkgK6CWrJa6t7Fk5Account:
huangyiireneBranch:
claude/project-thread-skc6v8Worktree:
hotcrm-issue-2338(card thread; branch is platform-designated, the measured platform exception toclaude/issue-N-slug; implementing sessionsession_01J8VVAnNyYSM5oQ2csRGcvh)Domain:
repo:hotcrm(single-lane repo, nodomain:*)Seat:
repo:hotcrm#1File surface: items 5–13 only: the
ai_briefingcard, a contact record page (item 6), the Deal Timeline inopportunity.view.ts, event-attendee related-list columns, taskcompleted_datehook, the Lead Inflow nav/report, the Top Resolving Articles widget filter, refusal copy in the three hooks plus the tests that pin it, the three docs rows, one changeset (stop on breach; explain in the report)Container & model:
M,mode:thread,model: TIER_DEFAULT (sister repo, hand-derived),effort: the order asked high; the thread runs at the project default, which only the maintainer setsClause-②: no
Metadata-protocol: none
Responsibility: #2338 items 5–13 (the p3 section) as ruled in the order; items 1–4 stay with claim 6451041709
Thread-read: 6451534867
Premise: holds — re-read at claim on hotcrm
origin/mainbbd375b(order comment 6451534867)Serial constraints cleared: items 1–4 (branch
claude/project-thread-6amvua) shareopportunity.view.ts; #2334 sharesopportunity.hook.ts/quote.hook.ts. Whichever lands second mergesorigin/mainfix(opportunity): every grid list offers Update Stage on its selectio…
fix(opportunities): mark each deal on its close date in Deal Timeline…
fix(events): name each attendee in a meeting's Event Attendees panel
fix(tasks): clear Completed Date when a completed task is reopened
8 remaining items
objectstack-fleet commentedon Oct 27, 2026
Items added ·
repo:hotcrmseat 1 · sessionsession_01J8VVAnPkgK6CWrJa6t7Fk5· 2026-10-27T23:3xZPR #2347's report (6456063458) found two more refusals in item 12's family that show users an API value. They join this card as items 14 and 15, p3, for a later order. Each follows item 12's shape: keep the diagnostic
message, add a labeluserMessage, and keep the code.Cannot edit converted lead Ada Lovelace - Acme (attempted: company), and the console toast shows the API key.src/sales/objects/lead.hook.ts:447.test/converted-lead-guard.test.tspins the attempted list on purpose.Opportunity X is closed (closed_won), so its line items can no longer be added, changed or removed.test/opportunity-line-freeze.test.ts:76pins that sentence.Status of this card: items 1–4 are PR #2346 and items 5–13 are PR #2347. Both are ACCEPTed with auto-merge armed. The card stays open for items 14 and 15.
Generated by Claude Code
objectstack-fleet commentedon Oct 27, 2026
Dispatch order · #2338 items 14–15 ·
repo:hotcrmseat 1 ·huangyiirene· sessionsession_01J8VVAnPkgK6CWrJa6t7Fk5· 2026-10-27T23:34ZThis order takes a slot freed by #2338 items 1–13: PR #2346 has landed, and PR #2347 has landed too. It covers items 14 and 15 only, as added in comment 6456156202.
Container & model: M ·
mode:thread· modelTIER_DEFAULT· efforthigh(the thread runs at the project default).Role file:
objectstack-ai/objectstack.claude/agents/os-dev.md. Read it in full. hotcrmAGENTS.mdbinds. Re-measure every file:line againstorigin/main(52a6fb2or later).Writes:
pr_create: draft, basemain,assignees: [huangyiirene].scripts/pm/post-stamped.mjs.Report: the
os-dev-reportJSON, posted as a comment on this card and sent as the final message to the seat thread. ⛔ Start editing only after the newestClaim:on this card names your branch for items 14–15. Push work in progress at each milestone, so a stop loses nothing.Ruling (not to be re-decided)
Part of #2338 (items 14–15).message, which pinned tests may read, and passes a labeluserMessage. The code and status stay unchanged.lead.hook.ts, the converted-lead freeze. The user sentence names the attempted fields by their labels, and the diagnostic keeps the API keys thattest/converted-lead-guard.test.tspins.test/refusal-labels.test.tsandrefusal-envelope'suserWordingpins to cover both sites.PM mechanism assumptions
userMessagein the toast and the Edit dialog for both sites. Item 12 measured this for its sites; re-check one of these sites in the browser.Serial constraints: none known. #2349 edits approval flows, not these hooks.
Generated by Claude Code
objectstack-fleet commentedon Oct 27, 2026
Claim: PM loop round 1
Session:
session_01J8VVAnPkgK6CWrJa6t7Fk5Account:
huangyiireneBranch:
claude/project-thread-fv0k33Worktree:
hotcrm-issue-2338(card thread; branch is platform-designated, the measured platform exception toclaude/issue-N-slug; implementing sessionsession_01J8VVAjXujUz9ErJ5BBgVDx)Domain:
repo:hotcrm(single-lane repo, nodomain:*)Seat:
repo:hotcrm#1File surface: items 14–15 only:
src/sales/objects/lead.hook.ts(converted-lead freeze), the opportunity line-item freeze hook,test/refusal-labels.test.ts,test/refusal-envelope.test.ts,test/converted-lead-guard.test.tsandtest/opportunity-line-freeze.test.tsonly where a pin needs it, one changeset (stop on breach; explain in the report)Container & model:
M,mode:thread,model: TIER_DEFAULT (sister repo, hand-derived),effort: the order asked high; the thread runs at the project default, which only the maintainer setsClause-②: no
Metadata-protocol: none
Responsibility: #2338 items 14–15 (label userMessage on two refusals) as ruled in the order; items 1–13 landed in #2346/#2347
Thread-read: 6456235199
Premise: holds — re-read at claim on hotcrm
origin/mainb8332aa(order comment 6456235199)Serial constraints cleared: none known
objectstack-fleet commentedon Oct 28, 2026
Landed, items 1–13 ·
repo:hotcrmseat 1 · sessionsession_01J8VVAnPkgK6CWrJa6t7Fk5· 2026-10-28T00:0xZGET /pulls/2346b65c403, from head9568b7c3cb, which is the head the seat ACCEPTed (6456097776).GET /pulls/234752a6fb2, from head8d75a7314a, which is the head the seat ACCEPTed (6456144351).mainCIGET /commits/{b65c403,52a6fb2}/check-runssuccess.State: items 1–13 are on
main. Items 14 and 15 are in flight under claim 6456278441 (branchclaude/project-thread-fv0k33), so this card keepspm:dispatcheduntil they land. The platform finding from item 1, that a dashboard tile has no read gate, is objectstack#26971.Generated by Claude Code
test(leads): the duplicate guard's source scan skips the converted-le…
test(leads): strip exactly the one label table, and prove it was there
objectstack-fleet commentedon Oct 28, 2026
os-dev-report
{ "issue": 2338, "status": "done", "branch": "claude/project-thread-fv0k33", "pr": "https://github.com/objectstack-ai/hotcrm/pull/2354", "session": "session_01J8VVAjXujUz9ErJ5BBgVDx", "premise_still_valid": true, "summary": "Items 14-15 in one draft PR (Part of #2338 items 14-15), item 12's shape. Item 14: lead_automation's converted-lead lock keeps its diagnostic message (attempted API keys, pinned by converted-lead-guard) and passes a label userMessage, 'Cannot change Company on converted lead Ada Lovelace - Acme; only its Description and Notes can still be edited. Make changes on the converted records instead.'; the labels ride a FIELD_LABELS table with one entry per held field (35), held to crm_lead by refusal-labels (keys = fields outside ALLOWED, labels = object labels). Item 15: opportunity_line_item_freeze keeps '(closed_won)' in the diagnostic and gives the user '(Closed Won)' via STAGE_LABELS. RECORD_LOCKED/409 unchanged on both. refusal-envelope gains both sites on the shipped and in-process paths with userWording; opportunity-line-freeze pins userMessage on every refusal it checks, the REST DELETE envelope included. Surface extended by the seat (00:17Z): test/lead-duplicate-management.test.ts source scan strips exactly the one FIELD_LABELS const (no nested brace, asserted to match exactly once, positive control kept), and the _refusal.ts note (two sites -> four). Paused 00:19Z-04:06Z at Irene's word; merged origin/main 1f1ea12 on resume. Card assignee untouched.", "tests": "pnpm verify on 946467d (branch + origin/main 1f1ea12): validate, typecheck, lint --strict, hygiene, hygiene:tokens, build all green; pnpm test Test Files 236 passed (236), Tests 4548 passed | 1 skipped (4549), VERIFY_EXIT=0 captured by redirect-then-$?. Earlier full run on 38a41be: 1 failed (lead-duplicate-management source scan, 'not to contain disqualification_reason'), fixed in 9ef687f and tightened in 00fff87. Ablations (one-shot, trap restore via git checkout HEAD -- ABS path, git diff HEAD empty afterwards, hash-object equal to HEAD blob checked separately after the script's own HEADBLOB read came back empty from a set -u bug): A1 lead userMessage arg removed -> converted-lead-guard ABL_EXIT=1, 1 failed of 20; A2 line-item userMessage arg removed -> opportunity-line-freeze ABL_EXIT=1, closed-deal cases failed; A3 injected input.disqualification_reason read outside the table -> lead-duplicate-management ABL_EXIT=1 (strip does not widen). Browser: dev server booted from this branch's dist/objectstack.json (lowered bodies), admin, edited Quantity on a Globex Training Package (Closed Won) line, Update -> dialog banner and toast both show the '(Closed Won)' sentence (project files issue-2338-p4/15-line-item-refusal.png); REST PATCH on the same line -> 409 RECORD_LOCKED, error = diagnostic (closed_won), userMessage = (Closed Won) (issue-2338-p4/rest-line.txt). Converted-lead site NOT MEASURED in the browser: reason, the seed holds no converted lead and the conversion flow is approval-gated; covered on the shipped QuickJS path and in-process.", "gates": "hotcrm has no dispatch-gates.mjs; local set hand-derived from package.json verify chain, all in the single pnpm verify on 946467d, VERIFY_EXIT=0. PR #2354 check-runs on 946467d at report time: Vercel Preview Comments success, Label Pull Request success, link-check success, Check Changeset success; Build and Test (22.x), Quality Checks, Playwright, Analyze Code (javascript) in_progress.", "dogfood_battery": "n/a - no ! in the PR title and Clause-2: no", "mcp_calls": "2 - mcp__github__issue_read get_comments x1 on #2338 (before reading the order's no-MCP-write rule; read only), plus 0 MCP writes. Reads after that via gh api. Non-GitHub MCP: hearthbot message_thread to the seat, checklist updates.", "api_writes": "2 - fleet-write relay pr_create objectstack-ai/hotcrm#2354 (draft, assignee huangyiirene; read-back identical, 7569 bytes) and this os-dev-report comment on #2338 via post-stamped. git push not counted. Review request for huangyiirene not sent (relay pr_create carries assignees only; the order bars MCP writes) - the seat may add it.", "open_questions": [], "out_of_scope_findings": [ "class: a · reach: public door - adding, changing or removing a line item of an accepted or expired quote answers 'Quote QTE-NNNN - NAME is accepted, so its line items can no longer be added, changed or removed.', the status API value shown to users (same family as items 12, 14, 15) · evidence: src/revenue/objects/quote.hook.ts:105; test/quote-line-freeze.test.ts:103 pins that sentence · dedupe words: quote line freeze accepted status label, quote line item refusal userMessage, settled quote lines refusal copy · carrier: #2338 (same family; seat decides item 16 or a card)", "carrier: none · noted, not filed: the grep the order asked for read every refuse( call under src/; no other hook refusal shows an API value (the rest name labels, record titles, counts or dates)" ], "deviations": [ "Surface extended at the seat's word (00:17Z): test/lead-duplicate-management.test.ts and the src/sales/objects/_refusal.ts note; both declared in the PR.", "Commit 38a41be carries objectstack-fleet[bot] as git author through a local -c user.name/email override; later commits use the container default. No history rewrite; the squash merge decides the landed author.", "Worktree hotcrm-issue-2338-p14 kept until the seat's ACCEPT in case of a rework round; dev server stopped (process group killed)." ] }Generated by Claude Code
objectstack-fleet commentedon Oct 28, 2026
Item added ·
repo:hotcrmseat 1 · sessionsession_01J8VVAnPkgK6CWrJa6t7Fk5· 2026-10-28T04:3xZPR #2354's report (6460657794) found one more refusal in item 12's family. It joins this card as item 16, p3, for a later order.
acceptedorexpired.src/revenue/objects/quote.hook.ts:105;test/quote-line-freeze.test.ts:103pins the sentence.messageand add auserMessagethat names the status by its label.Card state: items 14–15 are PR #2354, ACCEPTed with auto-merge armed. Item 16 keeps the card open.
Generated by Claude Code
objectstack-fleet commentedon Oct 28, 2026
Dispatch order · #2338 item 16 ·
repo:hotcrmseat 1 ·huangyiirene· sessionsession_01J8VVAnPkgK6CWrJa6t7Fk5· 2026-10-28T04:51ZThis order takes the slot freed by #2352, which is now
pm:blockedon objectstack#27086 and #27087. Lane concurrency is 2, per Irene's 「后续并发降到2」. The other slot is #2327. The order covers item 16 only, as added in comment 6460686188.Container & model: S ·
mode:thread· modelTIER_DEFAULT· efforthigh(the thread runs at the project default).Role file:
objectstack-ai/objectstack.claude/agents/os-dev.md. Read it in full. hotcrmAGENTS.mdbinds. Re-measure every file:line againstorigin/main(08bbdd5or later).Writes:
pr_create: draft, basemain,assignees: [huangyiirene].scripts/pm/post-stamped.mjs.Report: the
os-dev-reportJSON, posted as a comment on this card and sent as the final message to the seat thread. ⛔ Start editing only after the newestClaim:on this card names your branch for item 16. Push work in progress at each milestone, so a stop loses nothing.Ruling (not to be re-decided)
Part of #2338 (item 16).src/revenue/objects/quote.hook.ts, the line-item freeze keeps its diagnosticmessage, whichtest/quote-line-freeze.test.tspins. It passes auserMessagethat names the status bycrm_quote.status's label (Accepted / Expired).RECORD_LOCKED/ 409 is unchanged.crm_quote's own option labels bytest/refusal-labels.test.ts, the waySTAGE_LABELSis.refusal-envelopegains this site'suserWordingpin on the shipped path and the in-process path. Update the_refusal.tssite count if it names one.PM mechanism assumptions
userMessagein the toast for a line-item edit on an accepted quote. Measure it once in the browser. A seeded accepted quote should exist; if none does, pin the site on both test paths and say so.Serial constraints: none known. #2327 touches docs and the
app.tspacks, not this hook.Generated by Claude Code
objectstack-fleet commentedon Oct 28, 2026
Landed (items 14–15) ·
repo:hotcrmseat 1 · sessionsession_01J8VVAnPkgK6CWrJa6t7Fk5· 2026-10-28T04:52ZGET /pulls/235408bbdd5from head946467d1ca, the head the seat ACCEPTed (6460672060).mainCI on08bbdd5GET /commits/08bbdd5/check-runssuccess.State: both refusals now use labels. Editing a converted lead names the fields ("Cannot change Company …; only its Description and Notes can still be edited."). Editing a closed deal's line item names the stage ("Closed Won"). The diagnostics and the
RECORD_LOCKED/ 409 code are unchanged.Items 1–15 have landed. Item 16 (the quote line-item freeze shows a raw status) is ordered next in the same stroke, so the card stays open and keeps
pm:dispatched.Generated by Claude Code
objectstack-fleet commentedon Oct 28, 2026
Claim: PM loop round 1
Session:
session_01J8VVAnPkgK6CWrJa6t7Fk5Account:
huangyiireneBranch:
claude/project-thread-crw4h8Worktree:
hotcrm-issue-2338(card thread; branch is platform-designated, the measured platform exception toclaude/issue-N-slug; implementing sessionsession_01J8VVAo57nVvt62WhSprraZ)Domain:
repo:hotcrm(single-lane repo, nodomain:*)Seat:
repo:hotcrm#1File surface: item 16 only:
src/revenue/objects/quote.hook.ts,src/_refusal.ts(site-count note only),test/quote-line-freeze.test.ts,test/refusal-labels.test.ts,test/refusal-envelope.test.ts, one changeset (stop on breach; explain in the report)Container & model:
M,mode:thread,model: TIER_DEFAULT (sister repo, hand-derived),effort: the order asked high; the thread runs at the project default, which only the maintainer setsClause-②: no
Metadata-protocol: none
Responsibility: #2338 item 16: the quote line-item freeze names the status by its label (userMessage), diagnostic and RECORD_LOCKED/409 unchanged
Thread-read: 6460980185
Premise: holds — re-read at claim on hotcrm
origin/main08bbdd5(order comment 6460960959)Serial constraints cleared: none — #2327 touches docs and app.ts packs only
objectstack-fleet commentedon Oct 28, 2026
os-dev-report
{ "issue": 2338, "status": "done", "branch": "claude/project-thread-crw4h8", "pr": "https://github.com/objectstack-ai/hotcrm/pull/2356", "session": "session_01J8VVAo57nVvt62WhSprraZ", "premise_still_valid": true, "summary": "Item 16 in one draft PR (Part of #2338 item 16). The settled-quote line freeze in quote_workflow keeps its diagnostic message ('is accepted') and now passes a userMessage naming the status by crm_quote.status's label ('Quote QTE-0002 - Acme Annual Renewal 2025 Quote is Accepted, so its line items can no longer be added, changed or removed.'); RECORD_LOCKED/409 unchanged. STATUS_LABELS moved to the top of the handler so the quote freeze (item 12) and the line freeze read one table; refusal-labels asserts exactly one copy and holds it to crm_quote's option labels. refusal-envelope gains this site's userWording pin on the shipped and in-process paths (site count stays 35: the site already existed); quote-line-freeze pins userMessage on every refusal; _refusal.ts note says five sites. The PM assumption held: measured in the browser on the shipped artifact, the console shows the label sentence in both the dialog banner and the toast.", "tests": "head 39e8270: pnpm exec vitest run (full suite) -> Test Files 237 passed (237), Tests 4571 passed | 1 skipped (4572). Touched files (quote-line-freeze, refusal-labels, refusal-envelope) -> Test Files 3 passed (3), Tests 49 passed (49). Ablation: removed the line freeze's userMessage argument (grep -c marker 1 -> 0 on disk), quote-line-freeze + refusal-envelope -> ABL_EXIT=1, 12 failed of 44 (every quote-line-freeze refusal case plus both new envelope cases); restored with git checkout HEAD --, git diff HEAD empty, marker back to 1. No dist ablation needed: hotcrm tests lower the hook from source.", "gates": "hotcrm has no dispatch-gates.mjs; hand-derived from package.json's verify chain, each run separately on 39e8270 with exit captured before any pipe: validate 0, typecheck 0, lint (os lint --strict) 0, hygiene 0, hygiene:tokens 0, build 0, test 0 (above). CI check-runs on PR #2356 at report time: Vercel Preview Comments success; Label Pull Request, Quality Checks, Analyze Code (javascript), Check Changeset, Build and Test (22.x), link-check, Playwright all in_progress.", "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm", "mcp_calls": "1 — pull_request_read get_check_runs (read only); no MCP write", "api_writes": "2 relay/REST writes besides git push — fleet-write pr_create (POST /repos/objectstack-ai/hotcrm/pulls, then POST /repos/objectstack-ai/hotcrm/issues/2356/assignees, one stroke, read back identical) and this report via post-stamped (POST /repos/objectstack-ai/hotcrm/issues/2338/comments)", "open_questions": [], "out_of_scope_findings": [ "Post-fix grep of every refuse( call under src/: no hook refusal shows users an option API value any more; the rest name labels, record titles or counts.", "Observation, not a class a/b/c finding: four refusals interpolate stored date values verbatim — task_completion's reminder refusal (reminder_date is a datetime, so ISO form; read from source, not measured), contract_validation's term and End Date refusals and campaign_validation's Start/End Date refusal (YYYY-MM-DD). Machine formats, not option API values. carrier: none · noted, not filed. dedupe words: refusal date format, reminder_date ISO, userMessage date" ] }Generated by Claude Code
objectstack-fleet commentedon Oct 28, 2026
Landed (item 16) · closing ·
repo:hotcrmseat 1 · sessionsession_01J8VVAnPkgK6CWrJa6t7Fk5· 2026-10-28T05:45ZGET /pulls/2356f660fd8from head39e8270fd2, the head the seat ACCEPTed (6461403109).mainCI onf660fd8GET /commits/f660fd8/check-runssuccess.State: all 16 items are landed:
Editing a line on an accepted or expired quote now names the status by its label ("is Accepted"). The diagnostic and
RECORD_LOCKED/ 409 are unchanged. The post-fix grep finds no refusal that still shows users an option API value. The seat closes this card ascompletedand removes its lane label in this stroke.Generated by Claude Code