Repository navigation
docs(administration): write the state-machine status vocabulary and the last two behavioural claims to source (#921, #940) - #950
Conversation
…he last two behavioural claims to source (#921, #940) The state-machines page used lead statuses and opportunity stages that do not exist. `Working` and `Disqualified` are entries in the lead import alias table (src/mappings/lead_import.mapping.ts), not statuses; the real vocabulary is new/contacted/qualified/unqualified/converted, and Contacted and Unqualified had never appeared on the page. `Closed Without Decision` exists nowhere in src, and the real Needs Analysis stage was missing. Both diagrams, both transition tables and the lede are now copied from the state_machine rules. Two route claims were backwards: New -> Converted is in the lead table deliberately (Convert Lead is offered on any open lead and lead_conversion stamps status:'converted'), and Unqualified is not terminal (it re-opens to New). The opportunity section advertised free backwards movement and a reopen path the table declares nowhere. #940: close_date and amount are unconditionally required, not stage-gated; approval starts at $100K with $500K as the Sales Director tier. The won/lost reason claim was already correct and is untouched. The AI Copilot section is replaced with what src can show - no skill or action references a transition table - leaving the platform-side behaviour explicitly unclaimed. administration/setup carried the same phantom stage, and removing it exposed that the missing Needs Analysis had shifted every probability down one row. All three locales. Documentation only; src unchanged.
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
…claims Self-review of the previous commit caught two of my own sentences claiming more than src supports: - The disqualification-reason requirement is a separate error-severity validations[] rule named disqualification_reason_required, not a requiredWhen predicate on the field. (The enforcement itself is real - severity is 'error' - only the mechanism was mis-named.) - 'These four reject the save' was too broad: opportunity_approval is a record_change flow that runs after the update and locks the record, not a save-time check. Only the three required/requiredWhen claims reject a save. All three locales.
|
追加一个提交(
改后复跑: Generated by Claude Code |
…three locales
The Closed Lost line in the diagram was an English sentence ('any of the five
open stages'), which reads as untranslated text on the two Chinese pages while
CJK replacements would break monospace alignment. Replaced with the stage
labels themselves, which are already English in these diagrams by design and
carry the same fact.
…d the whole cubes page to source (objectstack-ai#962, objectstack-ai#965) (objectstack-ai#975) objectstack-ai#962: src/reports/ publishes ten reports; the Sales / Revenue / Marketing sections named thirteen that exist nowhere, while the six real ones - Accounts by Industry and Type, Customer Churn Signals and the four in opportunity.report.ts - had never appeared on the page. Two of the thirteen were real names on the wrong thing: Pipeline by Stage is the funnel tile shared by three dashboards and the chart title of the real Opportunities by Stage report, and Stale Opportunities is the Stale list view, which applies no 14-day cut (it ranks open deals by stage_entry_date; the 14 days belong to opportunity_stagnation). Each remaining name is judged against the semantic layer: Forecast vs Actual, Win/Loss Analysis, Big Deals Won and the renewal pipeline are a custom report away; Sales Cycle Length, Discount Approval Activity and the three contract reports are not, because no dataset reads crm_contract or the line items and days_in_stage is a post-query formula. Marketing has no campaign dataset at all. The subscription example, the permissions bullet and the manager tips no longer name absent reports. objectstack-ai#965: the page was built on four cubes that do not exist. This app declares no cube - every semantic definition is a defineDataset() under src/datasets/ and the analytics service compiles each into its cube (ADR-0021; objectstack.config.ts registers no analyticsCubes). The page now lists the nine real datasets with their real dimensions and measures, folds Sales and Pipeline into the single opportunity_metrics both described, and names per item what is unreachable and why - weighted pipeline (Expected Revenue is a record field, no measure), discount (a line-item percent), product/family, account tier and size, team and region (positions and Billing Country, i.e. sharing machinery), sub-month grains, snapshot dates. Marketing keeps no claim it cannot support: the campaign records carry the spend, counters and the ROI % / Response Rate % formulas, and nothing aggregates them. Platform-side sections are left explicitly unclaimed rather than declared false, per the objectstack-ai#950 precedent: the cube UI, the refresh cadence and the cube access log are runtime questions this repo cannot answer. What is stated is the app-side half - no skill names a dataset or cube, and Live Data Access answers through the platform's object tools, not a compiled cube. All three locales. Documentation only; nothing under src/ changed. Claude-Session: https://claude.ai/code/session_01VHrPAGEgFDoHjphqYG4BMa Co-authored-by: Claude <noreply@anthropic.com>
Fixes #921
Fixes #940
两单并单落地。文档 only,
src/零改动,content/docs/releases/未触碰。基线origin/main=11f62429(PR #939 之后),行号全部在 fresh main 上重定位。一、#921 状态词以
src/为准lead ——
Working/Disqualified不是状态真值是
lead.object.ts:191-197的new / contacted / qualified / unqualified / converted。页面用的Working、Disqualified只是src/mappings/lead_import.mapping.ts的导入别名('Working': 'contacted'、'Disqualified': 'unqualified',同表还有Open/In Progress/Nurturing/Closed - Not Converted),产品从不显示这两个词;而真实存在的Contacted/Unqualified在这份「状态机权威页」上一次都没出现过。替换清单(英文页行号,三语同址):
:8导语unqualified: ['new']就是一条回到 New 的边):14列表项New → Working → Qualified → Converted / DisqualifiedNew → Contacted → Qualified → Converted,并写明 Unqualified 从任何开放状态可进、唯一出路是重新打开回 New:28-47ASCII 图transitions重画(主路线 + 两条 Disqualify 边 + Unqualified 的 re-open 边),三语共用同一张对齐过的 ASCII:49-58转换表lead_status_progression;Trigger 列一并重写(原列名的 Begin Working / Mark Qualified / Disqualify 三个按钮在src/actions/lead.actions.ts里并不存在,那里只有convert_lead/schedule_followup/create_campaign):60Disallowed:118Copilot 举例:60的依据:lead.object.ts:516-519的注释明写 Convert 动作可见性放宽到 new/contacted/qualified,ConvertLeadAction.visible是record.is_converted == false && status != "unqualified" && status != "converted",而lead-conversion.flow.ts:242直接盖status: 'converted'—— 这条捷径必须在表里,否则每一次从未跟进过的线索转化都会记一条名不副实的警告。原文把一条设计上刻意允许的边写成了「禁止」的典型。opportunity —— 没有
Closed Without Decision,漏了Needs Analysis真值是
_picklists.ts:111-119的 7 个阶段。「7 个阶段」这个数字是对的(换掉幻影、补回 Needs Analysis 之后仍是 7),成员错了一个。:65-70的图与:72-77的规则块按opportunity_stage_progression重写。顺带被同一处失真拖累的两条(写在同一个 Rules 块里、且我新写的表会与它们直接矛盾,故一并改,非扩围):
:76「可以移动到三个已关闭阶段之一」—— 只改数字仍是假:closed_won只从proposal/negotiation可达,closed_lost才是五个开放阶段都可达。:75「可以自由向后移动」/:77「除非重新打开 —— 仅限管理员」—— 转换表里没有任何一条通往更早阶段的边,closed_won/closed_lost都是[](无 reopen 边)。:77的锁定描述同时过窄:opportunity.hook.ts:78-116的冻结拒绝的是用户对已关单记录的一切编辑,只放行description/next_step/notes,且是抛错而非警告。setup 三页
setup.mdx:88-94的阶段/赢率表命中Closed Without Decision。删掉这一行后暴露出:缺失的 Needs Analysis 把它下面每一行的赢率都错位了一格 —— 页面的 Proposal 40% / Negotiation 60% 实际是opportunity.hook.ts:29-37里needs_analysis: 40/proposal: 60的值,真值是 60% / 80%。整表改为照抄STAGE_PROBABILITY,7 行仍对应「7 阶段默认管道」。(
setup.mdx:96「请在 设置 → 商机 → 阶段 中调整」与 PR #939 在 state-machines:108处理掉的「设置面不存在」同型,但属行为性主张、不在本单裁定面 —— 未动,另行归档见文末。)二、#940 两条行为性主张
close_date/amount:opportunity.object.ts:122-124与:82-84都是无条件required: true+notNull存储,不存在「进入 Proposal 前」「进入 Negotiation 前」这个时点。改写为「一直要求」。opportunity-approval.flow.ts:88的 START 条件是record.amount > 100000—— $100K 就进审批,走 Sales Manager 一档;$500K是:235的 Sales Director 加签档。两档都写清,并点明「一笔 $200K 的单子是会进审批的」(按原文读者会得出相反结论)。win_reason:303/loss_reason:327的requiredWhen确实按 stage 生效。required/requiredWhen确实拒绝保存,不套用administration/state-machines通篇声称状态机会「拦下」非法转换,实测五条规则全是warning:保存照样通过;连「只显示合法下一状态」「bypass state machine 权限」都无实现 #920 的「advice, not a gate」口径。文末补一句把它与上方转换表的 warning 级明确区分开。三、Copilot 节(
:114-122)的取舍选了「重写为中性表述」而非删节。 理由:删掉整节会让「状态机和 Copilot 有没有关系」这个问题从页面上消失,而它恰恰是读者会问的;本仓能证的事实(零支撑)本身就有信息量,值得留在页面上。
写进去的只有本仓能证的:
grep -rniE "state.?machine|transition|legalNext" src/skills/ src/actions/零命中,六个*.skill.ts与全部*.actions.ts都没提转换表,技能提示词里也没有任何合法后继清单。没有写进去的:平台侧 agent 装载对象元数据时会不会读
validations[]里的state_machine并据此收窄建议 —— 本仓证不了,所以明确写成「这是运行时的问题,本页对它不作任何方向的断言」,并提示读者在自己的部署上验证。不硬判平台侧真伪、不虚构,与 #939 dev 的「证不了」结论一致。四、验证
#919 守卫单跑(
test/status-state-machines.test.ts)方向如实报:predicted GREEN → observed GREEN,且这是守卫的盲区而非确认。 该守卫(
describe('the admin docs roster matches the ledger'))只读首个##小节的-列表项,并在其中做对象名子串匹配(DOC_LABEL:Leads / 潜在客户 / 潛在客戶 …),外加一条「列表项数量 > 0」的前置条件。我改的是列表项内部的状态词,对象标签与-前缀原样保留,所以守卫在结构上不可能因这次改动变红 —— 它既不会红,也不构成「状态词写对了」的证据。真正的证据是正文里逐条标注的src/出处。六道门(
flock -w 7200 /tmp/os-heavy-verify.lock内串行,NODE_OPTIONS=--max-old-space-size=4096)pnpm validateapproval-approvers-may-resolve-empty/crm_campaign_member分组告警,与本 PR 无关pnpm typechecktsc --noEmit无输出pnpm lint13 warning(s), 14 suggestion(s) (1077ms),同为既有pnpm hygiene✓ no raw control bytes in first-party files/✓ source hygiene cleanpnpm buildArtifact: dist/objectstack.json (1921.4 KB),17 Objects 344 Fieldspnpm test -- --maxWorkers=2Test Files 70 passed (70)/Tests 1602 passed | 1 skipped (1603)控制字节 push 前自扫(超出 hygiene 门的扫描面):
grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f]'覆盖 6 个改动文件 + changeset,退出码 1(无命中)。未起 dev server。回退检查
PR #939 的落地行(
:3description、:8后半、:86-96「What a state machine buys you」、:108「没有设置面」、:110-114「用户看到什么」、:135-136「给用户的提示」、:142Related)逐条 grep 确认三语全部OK,零回退。git diff删除行清单核对:删掉的正好是目标行集,无一条 #939 文本被移除。三张 ASCII 图(英/简/繁)用脚本核过列位,三份完全一致;简繁图内标签保持 ASCII(
(terminal)而非全角),避免等宽下错位。zh 页未新增任何内链,故无锚点问题。五、范围声明
src/零改动;content/docs/releases/未触碰;@objectstack/*版本未动。:24段落原样保留。content/docs/analytics/reports,其:31/:33同样把Working当 lead 状态用 —— 卫星命中、不在两单裁定面,已单独归档(见下方 out-of-scope)。Generated by Claude Code