Repository navigation
docs(administration): list all five objects that have a state machine (#896) - #919
Merged
Merged
Conversation
…#896) `content/docs/administration/state-machines` rostered two objects — Leads and Opportunities — and then named case, contract, campaign and quote as objects that "have status fields but use simpler status handling rather than full state machines, because their transitions are less strict". Three of the four were wrong: `crm_case`, `crm_contract` and `crm_quote` each carry a named `state_machine` validation rule with a complete transition table, added by #575 B4, and the roster never moved when they landed. Campaign was the one the sentence got right. All three locales now list five objects with their real routes, read off the transition tables edge by edge, and state the mechanism once: every machine is a named `state_machine` validation rule on the object over its lifecycle field — the same mechanism for all five, not a lighter one for the newer three. Severity was measured before the wording was chosen, and it changed it. All five rules are `severity: 'warning'`, and the engine only pushes an `error` into the thrown `ValidationError`; a warning is logged and the write proceeds. Driven on a real ObjectQL over the in-memory driver, one object at a time, every illegal transition SAVED and left the illegal value stored: lead qualified -> contacted saved, status=contacted opportunity prospecting -> negotiation saved, stage=negotiation case resolved -> waiting_customer saved, status=waiting_customer contract draft -> activated saved, status=activated quote draft -> accepted saved, status=accepted So the page says the declared route is advice, not a gate — matching the wording `contract_status_progression` already has on the contracts page — rather than the issue's "the validation rule stops you". No machine declares `initialStates`, so a record created directly in a late state is not checked at all; that is recorded too. `test/status-state-machines.test.ts` gains the direction it was missing. It already derived the governed set from the compiled stack; it now reads the bullet roster off all three pages and fails when a governed object is absent from it, or when a deliberately-descriptive status (campaign, task) appears in it. Bullets only — the paragraph explaining why campaigns have no machine is free to name them. Verified in reverse: reverting the three pages turns the new assertions red naming exactly crm_case, crm_contract and crm_quote in each locale. Documentation and one test; nothing under src/ changed. Refs #575 #832 #837
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
This was referenced Aug 6, 2026
yinlianghui
marked this pull request as ready for review
August 6, 2026 08:47
This was referenced Aug 6, 2026
Closed
Closed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #896
content/docs/administration/state-machines的开篇清单只列了 Leads 和 Opportunities 两个对象,随后又把 case / contract / campaign / quote 四个点名为「有状态字段但只做更简单的状态处理,而不是完整状态机」。四个里有三个说反了。一、stale-premise 复核:issue 的事实面成立
在 fresh
origin/main(7df29789,平台 17.0.0-rc.3)上重跑grep -rn "type: 'state_machine'" src/objects/,5 处命中逐条重定位(行号较 issue 各 +1,内容一致):crm_leadlead_status_progressionsrc/objects/lead.object.ts:512crm_opportunityopportunity_stage_progressionsrc/objects/opportunity.object.ts:413crm_casecase_status_progressionsrc/objects/case.object.ts:346crm_contractcontract_status_progressionsrc/objects/contract.object.ts:259crm_quotequote_status_progressionsrc/objects/quote.object.ts:263campaign 是四个里唯一说对的。issue 的核心事实成立。
二、severity 实测(⚠️ 这一步改掉了措辞)
按裁定不照抄 issue 的「会被校验规则拦下」。写了一次性探针(跑完即删),对 5 条规则逐条:真实
ObjectQL+InMemoryDriver,用各对象真实的 object schema 建记录,停在合法状态,然后尝试一次转换表之外的跳转,并同时做一次表内跳转作对照。引擎侧机制先看清(
@objectstack/objectql/dist/core.mjs:2433-2443):evaluateRule产生 violation 后,只有severity === 'error'才 push 进 errors 数组、最终throw new ValidationError(errors);其余 severity 走logger.warn,不进 errors。checkStateMachine在mode === 'insert'时若规则未声明initialStates直接return null—— 而grep -rn "initialStates" src/objects/零命中,故新建路径完全不检查。探针实测结果(5/5 一致,无分档):
lead_status_progressionwarningcontactedopportunity_stage_progressionwarningnegotiationcase_status_progressionwarningwaiting_customercontract_status_progressionwarningactivatedquote_status_progressionwarningaccepted探针原始输出节选:
lead / opportunity 与 case / contract / quote 没有分档 —— 五条同为
warning、同为「记日志后照样保存」。所以页面按实况写「声明出来的路线是建议,不是闸门」,沿用 #832 / PR #874 已在 contracts 页落地的口径(advice, not a gate/保存照样通过/儲存照樣通過),而不是 issue 里那句「会被校验规则拦下」。另附一句写实:新建路径不受检查。(对照探针第一版:opportunity 用 prospecting 到 closed_won 时确实抛了异常,但抛的是
win_reason的requiredWhen,不是状态机 —— 换成 prospecting 到 negotiation 隔离掉后即 SAVED。这类串扰不算状态机拦截。)三、
:12-17三语改法清单补 case / contract / quote 三条,路线按各自
transitions表逐条边核对后写(例如 case 的resolved只能从in_progress/escalated进入,所以没有写成Waiting on Customer直达Resolved的链)。随后一段讲清「五者同为对象上一条具名state_machine验证规则,作用于生命周期字段(商机是stage),7.7 顶层stateMachines键移除后统一迁到这里」。:17的取舍:改写,不删句。 删掉的话,「campaign 有状态字段却没有状态机」这个有意为之的事实会连带消失,而test/status-state-machines.test.ts的注释恰好点出:「deliberately absent」和「forgotten」在元数据里长得一模一样,只有一种应该保持原状 —— 这正是值得留在页面上的信息。所以改写为只讲 campaign(页面原句里唯一说对的那个),并补上同类的其余几个。这里也没有照抄「只讲 campaign」的字面:实测status字段无状态机的不止 campaign,还有 campaign member / event / knowledge article / task,所以句子点名 campaign 之后以「同理」带过其余,而不逐一枚举成一份会腐烂的清单。三语用词:
状态转换属 docs(i18n): spell the lead convert verb 转化 on every Chinese page (#844) #906 明确判定的另一个词,保持不变)。crm_campaign简体营销活动、crm_campaign_member简体营销活动成员(语言包里有专门注释说明不能简写成「活动成员」,因为「活动」已是crm_event的标签);繁体按现有 18 页既有用词行銷活動/行銷活動成員。验证规则(与同目录administration/automation.zh-Hans.mdx一致),繁体驗證規則。crm_case在中文文档里有三种叫法:语言包「服务案例」、25 个页面「工单」、14 个页面「案例」(两页同页混用) #837 单独跟踪,不在本单顺手动。四、守卫
test/status-state-machines.test.ts原本只有「元数据 to 元数据」一个方向:从编译后的 stack 推出必须被治理的对象集。#896 这类缺陷正好在它够不到的另一个方向 —— 源码长出了状态机,页面没跟上,而os validate/pnpm lint从不打开content/docs。新增 block 从同一份
GOVERNED/UNGOVERNEDledger 推导期望,读三语页面首个##小节的列表项:每个被治理的对象必须出现在清单里;每个状态是描述性的对象(campaign / task)不得出现在清单里。只读列表项、不读散文 —— 因为讲「campaign 为什么没有状态机」的那一段合法地点了 campaign 的名,整节做子串搜索会把它误判成清单条目。另有一条 precondition 断言清单非空,防止改成表格或散文后 UNGOVERNED 那条空转变绿。反向验证(方向为预测的 RED):把三个 mdx 还原到
origin/main后单跑该文件,新断言在三个 locale 全红,且点名的恰好是 crm_case / crm_contract / crm_quote:DOC_LABEL是手写的、刻意不取自src/translations/—— 文档与语言包是两个各自会漂移的面(#837 就是crm_case一个对象在三处三种叫法),拿语言包去钉页面会让这个守卫因为与「清单是否完整」无关的原因变红。另有一条断言保证 ledger 里每个对象都在DOC_LABEL里有词条,避免新对象因为缺词条而悄悄跳过检查。五、验证
全部在
flock -w 7200 /tmp/os-heavy-verify.lock内串行执行,NODE_OPTIONS=--max-old-space-size=4096:pnpm validate✓ Validation passed (2119ms)pnpm typechecktsc --noEmit,无输出pnpm lint13 warning(s), 14 suggestion(s),与 main 同(均为既有 approval / naming 提示)pnpm build✓ Build complete (1523ms)pnpm hygiene✓ source hygiene clean(含content/.changeset的控制字节扫描)pnpm test -- --maxWorkers=2Test Files 66 passed (66);Tests 1597 passed, 1 skipped (1598)单跑本次改动的守卫文件:
Test Files 1 passed (1)/Tests 39 passed (39)。push 前另做控制字节自扫(
grep -naP覆盖[\x00-\x08\x0b\x0c\x0e-\x1f],超出check:nul-bytes的扫描面):四个改动文件 + changeset 均零命中。未起 dev server;探针为一次性文件,跑完已删,仓库无残留。六、范围与边界
:12-17这一节 ×3 + 一个测试 + changeset。src/**零改动,content/docs/releases/未触碰,@objectstack/*版本未动。service/cases.mdx:78只引用未改 —— 但它与实测冲突(见越界发现)。:14-15两条既有列表项保持原样:它们用的状态词(Working/Disqualified/Closed Without Decision)在src/里查无此值,但同一套词贯穿本页的 ASCII 图、转换表、Copilot 小节,只改列表项会让页面与 5 行外的自家图自相矛盾 —— 作为越界发现另行归档,不在本 PR 顺手改。