Skip to content

docs(automation): 按 flow 源码写实内置流程表两行,并修 opportunity_won_alert 的 description (#851) - #870

Merged
yinlianghui merged 1 commit into
mainfrom
claude/issue-851-flow-table-write-real
Aug 6, 2026
Merged

yinlianghui merged 1 commit into
mainfrom
claude/issue-851-flow-table-write-real

Conversation

@yinlianghui

Copy link
Copy Markdown
Collaborator

Fixes #851

按 PM 裁定的 #840/#841 口径「写实当前行为」修 automation 页 flow 表两行 ×3 语言 + opportunity-won-alert.flow.ts 的 description 串。flow 行为零改动。

前提复核:一半成立,一半被证伪

派单要求逐节点重读两条 flow 源码为据。结果如下 —— issue 的三项指控里两项成立,第三项(「不建任务」)不成立,这直接改变了 Case Escalation 那一行该怎么写。

成立 ①:赢单提醒不发给经理

src/flows/opportunity-won-alert.flow.ts 全流程 start → notify_management → end,唯一 notify 节点:

项 值
recipients ['{record.owner_id}']
channels ['inbox', 'email']

无 manager_id、无 position/team 目标。该节点 73–75 行注释自陈原因:{record.owner_id.manager} 在原始触发快照上无法穿透 lookup,会插值成字面量 undefined,消息发给幽灵用户。

编译产物复核(dist/objectstack.json):

notify recipients :: ["{record.owner_id}"]

flow 自身 description 原文 'On closed_won opportunities over $100K: notify owner + manager.' 同错 —— 表行不是抄错,是忠实抄了 src 里同样不实的描述,两处一并写实。

成立 ②:案例升级不改派

src/flows/case-escalation.flow.ts 节点链 start → get_record → update_record → notify → end。产物复核:

escalation node types :: start -> get_record -> update_record -> notify -> end
update fields :: ["is_escalated","escalation_reason","escalated_date","status"]

update_record 不含 owner_id。第 93–96 行注释开头即 No owner reassignment:;notify 正文自己就写 "It remains assigned to you."。src/profiles/service-agent.profile.ts:33-35 独立佐证:"the agent holds no transfer grant on crm_case … escalation still cannot move a case off its owner (#548; case reassignment remains the deferred half of that decision)"。

⚠️ 不成立 ③:「不建任务」—— 跟进任务是真的会建

issue 的事实节从 dist/objectstack.json 的节点表读出「无 create_record 节点 → 不建任务」。节点表看不见 hook,这一步是误归因。

flow 确实没有 create_record 节点,而且是刻意删掉的 —— src/flows/case-escalation.flow.ts:100-104 写明:

No create_task node here: the escalation write above flips status to escalated, which fires the case_status_side_effects hook — the single owner of escalation follow-up tasks … A second task node here produced duplicate, disagreeing tasks per escalation.

src/objects/case.hook.ts:136-152 的 case_status_side_effects(afterUpdate)在 status 转入 escalated 且案例挂了客户时,插入一条 crm_task:priority: 'urgent'、type: 'follow_up'、due_date = 次日、owner_id = 该客户的负责人。

test/hooks-runtime-service.test.ts:92 有一条现成的通过用例把这条链路钉死了:

✓ case_status_side_effects > creates an urgent task for the account owner on escalation

断言 task.priority === 'urgent'、task.owner_id === 'rep1'(= account.owner_id)、task.related_to_case、task.due_date === daysFromNow(1)。src/profiles/service-agent.profile.ts:26-29 与 content/docs/administration/sharing-and-security.mdx:164("tasks only (so an escalation can hand work to the account owner)")再次独立佐证。

结论:若照 issue 与派单细则的字面写「不建任务」,会把一句真话改成假话 —— 反而比原文更不准。所以本行的写法是:保留跟进任务,改正它归谁(客户负责人,不是资深客服),并明说不改派。这是「写实当前行为」的直接结果,不是对裁定的偏离。

另外:case-escalation.flow.ts 的 description 无需改

派单说「其 flow 文件 description 若同错一并写实」—— 逐条核过,没同错:

flow description 判定
case_escalation Automatically escalate high-priority cases 准确
case_escalation_on_create Escalate cases created critical (insert-time twin of case_escalation) 准确

故 src/flows/case-escalation.flow.ts 零改动。本 PR 的 src/** 改动面只有一行 description。

改文

表行(en / zh-Hans / zh-Hant 同步)

Large Deal Won Alert / 大额商机赢单提醒 / 大額商機贏單提醒

  • 前:When an opportunity over $100K turns *Closed Won*, notify the owner and their manager
  • 后:When an opportunity over $100K turns *Closed Won*, notify the owner — the owner alone, not their manager
  • zh-Hans:…时,通知负责人 —— 只通知负责人本人,不通知其经理
  • zh-Hant:…時,通知負責人 —— 只通知負責人本人,不通知其經理

Case Escalation Process / 案例升级流程 / 案例升級流程

  • 前:When a case turns *Critical*, reassign to a senior agent, notify, create a follow-up task
  • 后:When a case turns *Critical*, flag it escalated and notify its owner — the case is not reassigned to a senior agent, it stays with its owner; escalating also opens an urgent follow-up task for the account owner
  • zh-Hans:当案例变为 *Critical* 时,标记为已升级并通知其负责人 —— 不会重新分配给资深客服,案例仍归原负责人;升级同时会给客户负责人创建一条紧急跟进任务
  • zh-Hant:當案例變為 *Critical* 時,標記為已升級並通知其負責人 —— 不會重新指派給資深客服,案例仍歸原負責人;升級同時會給客戶負責人建立一條緊急跟進任務

术语沿用站内既有译法(客户负责人 / 客戶負責人 = account owner,见 content/docs/administration/sharing-and-security.zh-Hans.mdx:150);破折号沿用本表既有的 —— 写法。

description 串

src/flows/opportunity-won-alert.flow.ts

  • 前:'On closed_won opportunities over $100K: notify owner + manager.'
  • 后:'On closed_won opportunities over $100K: notify the owner — the owner alone, not their manager.'

产物落位复核(pnpm build 后读 dist/objectstack.json):

opportunity_won_alert :: "On closed_won opportunities over $100K: notify the owner — the owner alone, not their manager."
case_escalation :: "Automatically escalate high-priority cases"
case_escalation_on_create :: "Escalate cases created critical (insert-time twin of case_escalation)"

不静默删名 & #595 边界

「经理」「改派(资深客服)」「跟进任务」都是读者可能带着找来的词,三个都留在文本里,写成当前不做 / 当前归谁,而不是删掉让读者以为页面漏写了。

文本只描述今天的行为,不预判 #595(SLA policy matrix: … escalation that reassigns,尚开放的产品提案)的结论 —— 既不写「将来会改派」,也不写「永远不会」。让升级真的改派是行为变更,属 #595 的独立实现单,届时文档随之再更新。

守卫盲区(如实报,含反向验证)

test/automation-docs-coverage.test.ts 派生校验的是行集 / 触发面 / 两个数词,「它做什么」这一列是纯散文,不在派生范围内。预期改前改后全绿 —— 但比起断言,直接测了:

方向 预期 实测
改后(新散文) 20/20 绿 Test Files 1 passed (1) / Tests 20 passed (20)
把三个页面 stash 回原状(已知为假的旧散文),同一套规则重跑 仍 20/20 绿 —— 这就是盲区本身 Test Files 1 passed (1) / Tests 20 passed (20)

同一套规则在真值相反的两份散文上都给绿,盲区是测出来的而不是推出来的。本 PR 不加守卫(派单明确禁止);这条散文列目前无自动化守卫,是已知且被接受的状态(#854 已证同一盲区)。

test/metadata-references.test.ts 未钉 flow description 原文(grep 确认全仓只有两个 flow 文件自身含这些串),故 description 改动未触红任何测试,无需同批同步。

验证

全量套件,共享锁 flock -w 7200 /tmp/os-heavy-verify.lock + NODE_OPTIONS=--max-old-space-size=4096:

步骤 退出码 关键行
pnpm validate 0 仅既有 ⚠(approval 空审批人、campaign_member 字段组),与本改动无关
pnpm typecheck 0 —
pnpm build 0 产物含新 description(见上)
pnpm test --maxWorkers=2 0 Test Files 66 passed (66) / Tests 1587 passed | 1 skipped (1588)
pnpm lint 0 13 warning(s), 14 suggestion(s),均为既有
pnpm hygiene 0 ✓ no raw control bytes in first-party files / ✓ source hygiene clean

控制字节自扫(grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f]')覆盖全部 5 个改动文件:零命中。

边界遵守

顺带发现(已另立单,未在本 PR 修)

#869 —— 两条 flow 的节点 label 仍写着未发生的行为:'Notify Management'(opportunity-won-alert.flow.ts:71,实际只发负责人)、'Assign to Senior Agent'(case-escalation.flow.ts:87,实际不写 owner_id);外加 opportunity-won-alert.flow.ts:10-12 的文件头 JSDoc 仍留着本 PR 刚从 description 改掉的那句「notify the owner and their manager」。三处都在派单边界(「除 description 外 src/** 零改动」)之外,故未动。节点 label 是随产物发布的 authored metadata,与本 PR 修的表行属同族问题;节点 id 承重(edges[] 按 id 引用),不可顺手改 —— 已在 #869 里写明。


Generated by Claude Code

…lows

The built-in flow table billed `opportunity_won_alert` as notifying "the owner
and their manager" and `case_escalation` as "reassign to a senior agent, notify,
create a follow-up task". Read node by node against the flows:

- opportunity_won_alert has one notify node, recipients ['{record.owner_id}'] —
  the owner alone, no manager addressing anywhere. Its own `description` carried
  the same claim, which is what the table had copied; both are corrected.
- case_escalation's update_record writes is_escalated / escalation_reason /
  escalated_date / status and never owner_id — no reassignment. The escalation
  notice already said so: "It remains assigned to you."
- The follow-up task IS real, but it belongs to the `case_status_side_effects`
  hook (case.hook.ts), not to the flow, and it lands on the ACCOUNT owner, not a
  senior agent. The row keeps the task and corrects who receives it.

All three locales. The words readers arrive looking for — manager, senior agent,
follow-up task — are stated as not-done rather than deleted. No flow behaviour,
node, condition or recipient changed.

Fixes #851

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VHrPAGEgFDoHjphqYG4BMa
@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 1:05am

Request Review

@github-actions github-actions Bot added the backend Server-side behaviour — hooks, flows, actions label Aug 6, 2026
@yinlianghui
yinlianghui marked this pull request as ready for review August 6, 2026 01:07
@yinlianghui
yinlianghui added this pull request to the merge queue Aug 6, 2026
Merged via the queue into main with commit 5868728 Aug 6, 2026
10 checks passed
This was referenced Aug 6, 2026
yinlianghui added a commit to yinlianghui/hotcrm that referenced this pull request Aug 10, 2026
…the real navigation (objectstack-ai#927) (objectstack-ai#932)

The section that tells a new user where to click named four items; three of
them did not survive a read of `src/apps/crm.app.ts`, and the one real item
the group does carry was missing.

Measured against `src/apps/crm.app.ts:131-140`, `group_service` has exactly
three children: `nav_case` (Cases), `nav_knowledge` (Knowledge) and
`nav_service_dashboard`, whose label is **Service Overview** — not "Service
Dashboard". `crm_task` has no entry in this group at all: its two nav items
are `nav_my_tasks` (My Tasks) and `nav_all_tasks` (All Tasks), both under
`group_work` / **My Work** (`:84` / `:93`). And no metadata anywhere carries
the name *Service Board*: the kanban is the view `case_workflow` with
`label: 'Service Workflow'` (`src/views/case.view.ts:72-75`), reached from
the **Workflow** tab in the case list's view switcher (`:59`) and never from
the sidebar.

So the list now names the three real items with their real labels, adds the
Knowledge entry it had been dropping, and keeps the two names readers will
arrive with — Tasks and Service Board — pointing at where those things
actually live, per the objectstack-ai#870 / objectstack-ai#877 / objectstack-ai#885 / objectstack-ai#894 / objectstack-ai#913 / objectstack-ai#924 convention of
naming what does not exist rather than deleting it silently.

Product questions are left open on purpose (objectstack-ai#595 / objectstack-ai#596): whether the kanban
should become its own nav item, and whether Tasks belongs under Service, are
not decided here — only the current shape is recorded.

All three locales, same lines. `src/` untouched.


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

Co-authored-by: Claude <noreply@anthropic.com>
yinlianghui added a commit to yinlianghui/hotcrm that referenced this pull request Aug 10, 2026
…to the real navigation (objectstack-ai#943) (objectstack-ai#953)

The section that tells a reader where to click named four things; three of them
do not survive a read of `src/apps/crm.app.ts`, and the one that does was
labelled with a name the app never shows.

Measured against `src/apps/crm.app.ts` on `origin/main` (b7791ca, platform
17.0.0-rc.3), the app has seven groups — Sales, My Work, Activity, Marketing,
Service, Insights, Approvals — and none of them is **Products**. The catalog's
only sidebar entry anywhere is `nav_product`, labelled **Products**, under
`group_marketing` (`:126`); `grep -rn "crm_product" src/apps/` returns that one
line. So the page now sends a reader arriving with the name "Products group" to
Marketing instead of to a place that does not exist, and links the Marketing
overview page that objectstack-ai#938 / PR objectstack-ai#942 just brought in line.

`group_approvals` (`:159-171`) has exactly one child: `nav_approval_requests`,
whose label is **Inbox** (`:165`) — not *Approval Requests*, which no metadata
in this repo carries. The other two listed items are re-judged individually
rather than deleted silently, per the objectstack-ai#870 / objectstack-ai#877 / objectstack-ai#885 / objectstack-ai#894 / objectstack-ai#913 / objectstack-ai#924 /
objectstack-ai#932 / objectstack-ai#942 convention:

- **Action History** — no nav item of that name exists. The data behind it does:
  @objectstack/plugin-approvals registers `sys_approval_action` (enumerated from
  the installed package, alongside `sys_approval`, `sys_approval_approver`,
  `sys_approval_delegation`, `sys_approval_request`, `sys_approval_token`), and
  nothing in this app's navigation opens it. The page says exactly that.
- **Processes** — deleted on purpose, and the source records why at `:166-169`:
  no `sys_approval_process` object exists in any installed plugin, so the old
  item's `requiresObject` guard hid it on every install, forever. The enumeration
  above confirms the absence, so the reason is written into the page.

The one true claim survives unchanged: **Contracts** is under **Sales**
(`nav_contract`, `:60`), and it is the app's only sidebar route to a contract.
Two further facts from source are recorded with it: `group_marketing` and
`group_approvals` declare no `expanded` key while Sales, My Work, Activity and
Service set `expanded: true`, and `GroupNavItemSchema.expanded` defaults to
`false` — so both groups are collapsed on load, which is the failure mode that
sends a reader looking for the catalog away empty-handed. And the zh-Hans page
names the label a simplified-Chinese user actually sees, 待我审批
(`src/translations/zh-CN.ts:1219`), next to the source label.

Product questions stay open on purpose (objectstack-ai#595 / objectstack-ai#596): whether Products deserves
its own group and whether Approvals should gain an audit-trail item are product
decisions, not documentation ones. Only the current shape is recorded.

All three locales, same lines; zh internal links carry no anchor. `src/`
untouched.

Fixes objectstack-ai#943


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

Co-authored-by: Claude <noreply@anthropic.com>
yinlianghui added a commit to yinlianghui/hotcrm that referenced this pull request Aug 10, 2026
… real app (objectstack-ai#960) (objectstack-ai#968)

The first table a new user reads, whose entire job is "here is what the sidebar
holds", had drifted in every one of its eight rows.

Measured against `src/apps/crm.app.ts` on `origin/main` (4705aed, platform
17.0.0-rc.3), the app's `navigation` is one pinned top-level entry plus seven
groups: `nav_home` (Home, :34), `group_sales` (:42), `group_work` (My Work,
:69), `group_activity` (Activity, :103), `group_marketing` (:120),
`group_service` (:131), `group_insights` (Insights, :144) and `group_approvals`
(:160). Row by row, the old table said:

- **Sales** — the group is real, but it carries nine children, not six. The
  table dropped Account Workbench (:52), Pipeline (:55) and Sales Performance
  (:61).
- **Service** — real; the entry the table called *Knowledge Base* is labelled
  **Knowledge** (:138), and **Service Overview** (:139) was missing. Both
  names were already written to source on `service/index` by objectstack-ai#927 / PR objectstack-ai#932
  and objectstack-ai#937 / PR objectstack-ai#947, so the two pages contradicted each other.
- **Marketing** — real, but its children are Campaigns (:125) and Products
  (:126). *Campaign Members* is not a navigation entry at all:
  `grep -rn "crm_campaign_member" src/apps/` returns nothing.
- **Products** — no such group. The catalog's only sidebar entry is
  `nav_product` under `group_marketing`, the same finding objectstack-ai#938 / PR objectstack-ai#942 and
  objectstack-ai#943 / PR objectstack-ai#953 already wrote to two other pages.
- **Activities** — no such group; the real one is **Activity**, and `crm_task`
  is not in it. Its two entries are My Tasks (:84) and All Tasks (:93), both
  under **My Work** — which the table never mentioned at all, though it is the
  group a rep uses every day.
- **Analytics** — no such group; the real one is **Insights**, and no entry
  anywhere is labelled *Dashboards* or *Reports*.
- **AI** — no such group anywhere in the repo, and no entry labelled *Copilot*
  or *Knowledge Bases*. `src/apps/` contains one file; neither name appears in
  it.
- **Approvals** — real, with exactly one child, labelled **Inbox** (:165).
  *Approval Requests* and *Action History* carry no metadata in this repo;
  objectstack-ai#943 / PR objectstack-ai#953 recorded the same two names on the revenue page.

So the table now lists the pinned Home entry and all seven groups with their
real children in source order, and every retired name is re-pointed rather than
deleted silently, per the objectstack-ai#870 / objectstack-ai#877 / objectstack-ai#885 / objectstack-ai#894 / objectstack-ai#913 / objectstack-ai#924 / objectstack-ai#932 / objectstack-ai#942
/ objectstack-ai#953 convention. Two further facts from source ride along: `group_marketing`,
`group_insights` and `group_approvals` declare no `expanded` key while Sales,
My Work, Activity and Service set `expanded: true`, and
`GroupNavItemSchema.expanded` defaults to `false`, so those three are collapsed
on load — the failure mode that makes a reader conclude something is absent.
And the zh pages name the labels a simplified-Chinese user actually sees
(待我审批, 知识库, 我的工作 — `src/translations/zh-CN.ts:1195-1219`), which is
also why the old English *Knowledge Base* read plausibly for so long.

Nothing checked any of it: `os validate` and `pnpm lint` walk authored metadata
and never open `content/docs`. `test/docs-quick-tour-navigation.test.ts` now
compares the table to `CrmApp.navigation` group-for-group and child-for-child in
all three locales, pins the bold-is-real / italic-is-phantom typography the
sibling pages already use, and pins the source facts the prose rests on.
Restoring the old table turns 15 of its 21 assertions red in the predicted
direction.

Product questions stay open on purpose (objectstack-ai#595 / objectstack-ai#596): whether Products or AI
deserve their own groups is a product decision, not a documentation one. Only
the current shape is recorded.

All three locales, same section; zh internal links carry no anchor. `src/`
untouched.

Fixes objectstack-ai#960

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

Labels

backend Server-side behaviour — hooks, flows, actions

Projects

None yet

2 participants