Skip to content

docs(service): write the cases and state-machine pages' remaining claims to source (#912, #920, #925, #926) - #939

Merged
yinlianghui merged 1 commit into
mainfrom
claude/issue-912-920-925-926-service-family
Aug 6, 2026
Merged

yinlianghui merged 1 commit into
mainfrom
claude/issue-912-920-925-926-service-family

Conversation

@yinlianghui

Copy link
Copy Markdown
Collaborator

Fixes #912
Fixes #920
Fixes #925
Fixes #926

四单并单,收口 service/cases 与 administration/state-machines 两页余下的失实说法,外加 whats-new / guides/integrations 两个卫星点。三语同步,src/ 零改动。

零、stale-premise 复核(先证后改)

四单的行号基线早于 PR #919 / #922 / #924,全部在 fresh origin/main(4855d50b)重定位;每处改动前逐条回源码复核。

#920 的引擎侧机制(一次性探针,跑完即删,未留在仓库里)。探针直接调 evaluateValidationRules,即 @objectstack/objectql/dist/core.mjs:2425-2443 那条路径:violation 只有 severity === 'error' 才进 errors 数组并 throw,其余走 logger.warn。

crm_case.status — rule 'case_status_progression'
  declared severity : warning
  declares initialStates? : no
  transitions[new] : [in_progress, waiting_customer, escalated, closed]
  probe (update): new -> resolved  (outside the table? true)
  ILLEGAL verdict   : NOT BLOCKED (save proceeds)
  engine log lines  :
      Validation rule 'case_status_progression' (warning): Invalid status transition

crm_lead.status — rule 'lead_status_progression'
  probe (update): converted -> contacted  (outside the table? true)
  ILLEGAL verdict   : NOT BLOCKED (save proceeds)
  engine log lines  :
      Validation rule 'lead_status_progression' (warning): Invalid lead status transition

crm_case.status — probe (insert): closed
  ILLEGAL verdict   : NOT BLOCKED (save proceeds)
  engine log lines  : (none)

crm_case.status — probe (update): new -> in_progress  (outside the table? false)   [对照组]
  ILLEGAL verdict   : NOT BLOCKED (save proceeds)
  engine log lines  : (none)

与 PR #919 的实录一致。三点值得单独记下:

  1. 第一条探针跑出的正是 cases:78 那句原话的反例(New 直接到 Resolved)。
  2. 串扰要隔离:探针第一版里 lead 那条抛了 ValidationError: Email is required、case 的 insert 那条抛了 Resolution is required when closing a case —— 都不是状态机。把邻近规则的必填项补齐后,两条都变成 NOT BLOCKED,只剩一条 WARN。把这类串扰当成「状态机拦下了」是最容易犯的误判。
  3. 对照组不是多余的:表内的移动一条日志都不记,说明规则不是空转,它确实在判别 —— 只是判别的结果是记日志而不是拦截。

其余逐条复核:grep -rn "initialStates" src/ 零命中(故 insert 路径直接 return null);grep -rniE "bypass" src/ 无任何权限项;legalNextStates 在 node_modules/@objectstack/ 里唯一的消费者是 runtime/dist/index.js:5834 的 GET /objects/{name}/state/{field} 端点,本仓零调用;五条规则 severity 全部 warning(lead:513 / opportunity:414 / case:347 / contract:260 / quote:264)。⛔ 按 #575 B4,没有把任何一条提到 error。

一、#920 —— state-machines 页的「护栏」说法(:3 :8 :86-94 :106 :110-112 :133 :134)

沿用 PR #919 已在本页 :22 落地的口径(advice, not a gate / 保存照样通过 / 引擎记 WARN),不新造措辞。

行 原文 改法
:3 frontmatter 「how HotCRM enforces valid status transitions」 declares + 「what the tables do and do not do」。这是搜索结果与导航里第一眼看到的句子
:8 「A state machine enforces these transitions so users can't put records into nonsensical states」 只改第二句:声明路线、不强制,五条全 warning、保存照样通过,并指向本页 :22
:86-94 「Why state machines matter」—— 以「没有状态机就会出现 X」立论 改题为「What a state machine buys you」:X 现在照样出现,各记一条警告后落库;漏斗报表不会因为存在一张表就「反映现实」。随后正面写清转换表真正给的三件事
:106 「Configure at Setup → Object → Status → State Machine」 该配置面不存在(#920 留的待核项)。写实为:每张表是对象上的 validations[] 条目,改它是代码改动加重新部署
:110 下拉「只显示合法的下一个状态」 零实现:写清 legalNextStates 存在于平台、唯一消费者是那个 HTTP 端点、本应用无人调用
:111 「shows an error: Cannot move from Converted back to Working」 不抛错不弹窗;唯一痕迹是一行服务端日志,界面里看不到。顺带:那句假的错误文案里带着 #921 的状态词,改写后整句消失,R30 在 :111 无需再动(#921 与 #920 原文即写明「可以分别改也可以一轮改完」)
:133 「下拉缺选项 = 状态机在拦它」 :110 的孪生句,同一零实现。不改会让同页 :110 与 :133 自相矛盾,故一并收口
:134 「bypass state machine 权限」 grep 零命中,且不需要 —— 没有东西要绕。新建路径根本不查表

:112(新状态的必填字段)保留:那是 requiredWhen 谓词,是真的会拒绝保存的另一套机制(opportunity.object.ts:303 / :327),只补一句点明它不是状态机。

⚠️ 一处刻意的克制(复核中改掉了我自己的初稿)

「What a state machine buys you」的初稿里我写了一条「Copilot 只建议转换表允许的那些转换」。写完去核,grep -rniE "state.?machine|transition|legalNext" src/skills/ src/actions/ 零命中 —— 本仓没有任何技能或动作提到转换表。这句话我证不了,于是删掉重写成不对当前消费面做断言的说法(「流程条件、报表或技能可以按声明出来的路线来写」)。本页 :114-122 那一节本身(「Copilot 理解状态机…这可以防止 Copilot 建议非法的转换」)同属存疑,但它是平台侧 agent 行为、本仓证据不足以断言真伪,故不在本 PR 顺手改,另行归档为越界发现。

二、#925 —— cases 页的三处 SLA 说法(:59 :70 :71 :111)

口径抄平 #886 / PR #885 与 #903 / PR #918 已在 sla-and-escalation 页落地的写法。

三、#926 —— cases 页的视图清单与经理提示(:114-121 / :171-173,fresh main 上为 :116-124 / :174-176)

四、#912 —— bare workflows 残留 4 处

  • whats-new:89 ×3:contract_renewal 是流程不是 workflow(src/flows/contract-renewal.flow.ts:25,label: 'Contract Renewal Reminder')。改成 flow 并与 administration/automation:77 内置流程表的标签逐字一致;中文两页沿用该表已有的「合同续约提醒」/「合約續約提醒」。这一项的实质(按每份合同自己的 renewal_notice_days 触发)复核属实(流程的 check_notice_window 决策节点按记录逐条判窗口),只纠叫法。
  • state-machines:133(fresh main 上为 :140)×3:Related 链接的「由转换触发的 workflows」改为「转换可以触发的流程与对象钩子」。
  • cases:80 ×3:小节标题 ## Workflow automation 改为 ## Flow and hook automation(中文「流程与对象钩子自动化」/「流程與物件鉤子自動化」),与该节四条正文(流程的 notify 节点、case_status_side_effects 钩子)对齐,且不与同页 ## Case escalation 等相邻标题撞名。
  • guides/integrations:130 ×3(bare workflows 残留族在 #899 枚举之外还有 4 处:whats-new 把真实的 contract_renewal 流程叫成 workflow,state-machines:133 在 PR #894 扫过该文件后仍留着 #912 判为最弱一处,交接单人定夺):改。理由:该段虽是「设计意图(尚未落地)」,但它并列的两个代码面是现在时的事实陈述,而 workflow 命名的是平台 7.7 移除的元数据类型 —— 留着等于暗示除 flow / hook 之外还有第三种代码面。改为 flow / hook,一词之差,不动该段「尚未落地」的定性。

五、PM 验收 #928 时追加的两行(出处:#928 报告的边界节)

以下两行不在四单原始面内,是 PM 在 #928 的 dev 验收中发现、判归本单一次收口的(同页、同口径,且紧邻 :174-176,hunk 自然连续)。照常先复核后改:

  • :166「New cases should not sit for more than the first-response SLA」—— 本仓不存在首次响应目标:first_response_date 的唯一写入方是 global.actions.ts:386-401 的 logActivityAction,写完之后没有任何东西读它去比对 —— case_metrics 无首次响应度量(case.dataset.ts:23-27 三个 measure 均与它无关),grep -rn "first_response" src/flows/ 零命中,无报表、无磁贴、无告警。按 docs: write the three business-hours claims outside the SLA page to source (#928) #933 已落地的「服务承诺而非应用跑着的时钟」口径降格。
  • :167「it stops the SLA clock in some configurations」—— 与 docs(service): write the sla page's views, report dimensions and business-hours claims to source (#917) #924 已在 sla 页 :138 杀掉的是同一处虚构:sla_due_date 只写一次、从不重算,case_sla_monitor 的 $nin 只排除 resolved / closed,waiting_customer 不在排除名单上。同口径写实。

六、边界(未触碰的东西,逐条自证)

七、验证

六道门全部在 flock -w 7200 /tmp/os-heavy-verify.lock 内串行,NODE_OPTIONS=--max-old-space-size=4096:

门 退出码 关键行
pnpm validate 0 Data: 17 Objects 344 Fields;5 条 author-time 警告与 main 同(approval / campaign_member group)
pnpm typecheck 0 tsc --noEmit,无输出
pnpm lint 0 13 warning(s), 14 suggestion(s),与 main 同
pnpm hygiene 0 ✓ no raw control bytes in first-party files;扫描面含 content 与 .changeset(425 个文件)
pnpm build 0 ✓ Build complete;dist/objectstack.json (1921.4 KB)
pnpm test -- --maxWorkers=2 0 Test Files 70 passed (70);Tests 1602 passed, 1 skipped (1603)

单跑 #919 新增的守卫(本页唯一读 content/docs 的钉子):test/status-state-machines.test.ts 得到 Test Files 1 passed (1) / Tests 39 passed (39)。

守卫覆盖面的诚实交代 —— predicted GREEN, stayed GREEN。 该守卫只读本页首个 ## 小节的列表项(:12-18 的对象清单),而本 PR 一个字都没动那一节,所以它预测就应该保持绿,跑出来也确实是绿 —— 这证明我没碰坏清单,不证明新写的散文是真的。本 PR 改的其余全部行都在守卫盲区里:grep -rn "content/docs" test/ 命中的几个文件分别钉 automation 页的流程表、contacts 页的邮箱唯一性、dashboards 页的磁贴名、三语 callout 计数与「转化率」用词,没有一个读 cases / state-machines / whats-new / integrations 的散文。撑住这些散文的是上面第零节的探针实录与逐条源码引用,都写进了正文,下一个读者可以照着复核。

push 前另做控制字节自扫(覆盖 check:nul-bytes 之外的整段范围):改动的 12 个 mdx 加 changeset 零命中。未起 dev server;探针为一次性文件,跑完已删,git status 干净。


Generated by Claude Code

…ims to source (#912, #920, #925, #926)

The state-machines page claimed enforcement it does not have: all five
`state_machine` rules are `warning` severity, so an illegal move is logged and
saved; the create path is not checked at all; nothing filters a status dropdown
by the transition table; and no "bypass state machine" permission exists. The
cases page carried the same claim (":78") plus three SLA statements that named a
field that does not exist and a mechanism that was replaced, and a list-view
roster where six of seven names were not views.

Also corrects `workflow` where it named a metadata type removed in platform 7.7:
the Contract Renewal Reminder automation is a flow.

English, Simplified Chinese and Traditional Chinese. Docs only; `src/` untouched.
@vercel

vercel Bot commented Aug 6, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
hotcrm Ignored Ignored Aug 6, 2026 10:27am

Request Review

@yinlianghui
yinlianghui marked this pull request as ready for review August 6, 2026 12:35
@yinlianghui
yinlianghui added this pull request to the merge queue Aug 6, 2026
Merged via the queue into main with commit 11f6242 Aug 6, 2026
9 checks passed
This was referenced Aug 6, 2026
yinlianghui added a commit to yinlianghui/hotcrm that referenced this pull request Aug 10, 2026
… source (objectstack-ai#948) (objectstack-ai#955)

All four bullets of `content/docs/service/index*.mdx` "Standard dashboards &
reports" were wrong in all three locales, in two independent ways.

The dashboard bullet advertised a `top agents` tile and an `oldest open cases`
tile. `src/dashboards/service.dashboard.ts` ships ten widgets and neither is
among them — and neither is a widget nobody built yet: `case_metrics`
(`src/datasets/case.dataset.ts`) declares Status, Priority, Origin, Type and
Created as its only dimensions, so nothing in analytics can rank agents, and
every widget on the dashboard binds that dataset, i.e. aggregates it, so no tile
lists individual cases by age. `content/docs/service/cases.mdx:188` (objectstack-ai#912 / PR
objectstack-ai#939) had already written the agent half to source, so the two service pages
contradicted each other; this page was the one that was lying. The bullet now
names the ten real tiles and states why the other two cannot be built.

The three report bullets named labels that do not exist in
`src/reports/case.report.ts`: `Cases Opened by Day × Priority` inverts the two
dimensions of the real `Cases Opened by Priority × Day` (priority in `rows`, the
day in `columns` — `sla-and-escalation.mdx` already had the order right after
objectstack-ai#917 / PR objectstack-ai#924), `Cases by Status × Priority` spells `and` as `×`, and
`SLA Performance` drops the `Report` its label ends with. The SLA bullet also
still carried the "% of cases resolved within SLA target" claim PR objectstack-ai#924 removed
from the SLA page: no such measure exists — the report gives case count,
SLA Violation Rate and average resolution time by priority, over closed cases
only.

`test/docs-service-index-analytics.test.ts` pins both directions: every bolded
Latin name in the section must resolve to a real widget title, report label or
dataset label (phantom names stay in the *italics* this page already uses for a
name the product lacks, objectstack-ai#927 / PR objectstack-ai#932); every widget title must appear, so a
new tile cannot land while the summary goes stale; and the source side of both
negative claims is pinned too, so adding an agent dimension or an agent-ranking
tile fails here rather than silently making the prose wrong the other way.
Reverse-verified: restoring the four old lines turns 13 of the 20 assertions
red.

PR objectstack-ai#947's `Service Overview` reference and its first-mention `Customer Service`
annotation are untouched, as are the objectstack-ai#913 / objectstack-ai#922 / objectstack-ai#932 lines elsewhere on the
page. No metadata changed.

Claude-Session: https://claude.ai/code/session_01VHrPAGEgFDoHjphqYG4BMa

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment