Skip to content

Track A · crm_contact: the buying-centre map (role, attitude, relationship strength) — REQ-0004 #1913

Description

@os-elon-musk

Part of #1904 — Track A 实现批,REQ-0002 前 14 步的销售侧增强。⛔ 不依赖上游发版,与 #1907 的拆包线并行。

权威规格

docs/requirements/0004-contact-buying-centre-map.md(已在 origin/main,PR #1912 合入 6ff28e7)。

⚠️ 该记录是本卡的唯一规格来源。⛔ 本卡不重抄它的内容、⛔ 不重新裁定它的 B/C 划分。动手前把它整篇读完,包括 Standard product analysis、Disposition & rationale、Product response 与 Acceptance 四节。

其中 C(客户定制层) 项一律 ⛔ 不做 —— 它们不属于标准产品,记录里逐条写明了理由。

串行约束 ⚠️

Track A 的四张实现卡文件面相交:都要动 src/sales/objects/ 与四个语言包 src/sales/translations/{en,zh-CN,es-ES,ja-JP}.ts。

⇒ 硬串行,一次只有一张在飞。 本卡落地后 PM 才派下一张。⛔ 不要并行开工、⛔ 不要顺手改别的对象。

文件面

  • src/sales/objects/contact.object.ts —— 三个 select,通用选项集,各自成组
  • src/sales/translations/{en,zh-CN,es-ES,ja-JP}.ts —— 四个语言包同笔
  • content/docs/ —— 一页面向销售用户解释 buying-centre 概念的用户文档

⚠️ 记录明写:无 hook、无 flow、无 validation。这三个字段由人和报表读,不由机器判。⛔ 不要自行加校验。

⛔ 不碰其它销售对象、⛔ 不碰 src/service/、src/revenue/、src/marketing/、⛔ 不碰 content/docs/releases/。

验收

以 docs/requirements/0004-contact-buying-centre-map.md 的 Acceptance 节为准,⛔ 本卡不另立标准。另加两条通用项:

  1. pnpm verify 绿(package.json 是权威,⛔ 不自行窄化)。退出码在任何管道之前写进文件再读。
  2. 一条 changeset。本卡有用户可见的元数据变化,⛔ 不是 docs-only,级别自判并在 PR 正文说明理由。

为什么先派这张

四张里最小的一张:纯字段 + 翻译 + 文档,无流程无校验。先用它把「加字段 → 四语言包 → 用户文档 → changeset」这条链路验通,后面三张按同一形状推进。


Generated by Claude Code

Activity

  1. added
    enhancementNew feature or request
    pm:epicParent delegated to a dedicated epic PM — other PMs never dispatch into its subtree
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    on Sep 15, 2026
  2. self-assigned this
    on Sep 15, 2026
  3. os-elon-musk commented on Sep 15, 2026

    @os-elon-musk
    CollaboratorAuthor

    Claim: PM loop round 6
    Session: session_01T3YsvpK1PvYf9n1YUhYP6W
    Branch: claude/issue-1913-contact-buying-centre-map
    Worktree: hotcrm-issue-1913
    Domain: single-lane repo (hotcrm) — no domain:*
    File surface: src/sales/objects/contact.object.ts, src/sales/translations/{en,zh-CN,es-ES,ja-JP}.ts, one page under content/docs/ (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: 默认判断档
    Clause-②: no
    Thread-read: none (card opened by this seat at 2026-09-15T14:5xZ, no comments yet)
    Serial constraints cleared: NOT cleared — deliberately serialised. Track A 的四张实现卡(#1913 #1914 #1915 #1916)文件面相交:都动 src/sales/objects/ 与同四个语言包。⇒ 硬串行,本卡是链首,#1914 / #1915 / #1916 依次在前一张 MERGED 后才派。同仓在飞:#1907 是 pm:blocked 的打包卡,其分支 claude/issue-1907-sales-app-service-module 移动整个 src/,但它在等上游发版、未在飞,⛔ 本卡落地后它重启时须先合 main。

    授权

    维护者 2026-09-15:「开始 Track A」(方向性授权,覆盖整条执行链)。

    派发排序与理由

    四张按复杂度递增:contact(纯字段)→ lead(字段 + 审批)→ account(能力闸 + 审批)→ opportunity(多字段组 + 状态变更审批)。链首取最小的一张,先把「加字段 → 四语言包 → 用户文档 → changeset」这条链路验通。


    Generated by Claude Code

  4. os-elon-musk commented on Sep 15, 2026

    @os-elon-musk
    CollaboratorAuthor

    os-dev-report

    {
    "issue": 1913,
    "status": "done",
    "branch": "claude/issue-1913-contact-buying-centre-map",
    "pr": "#1917",
    "premise_still_valid": true,
    "summary": "REQ-0004 implemented on crm_contact only. New buying_centre fieldGroup holding three optional, ordered/generic selects — buying_function (6 values), attitude (5, champion->blocker), relationship_strength (5, distant->trusted_advisor) — plus a group-REFERENCE form section ({ group: 'buying_centre' }, AGENTS.md ladder rung 2, no per-field enumeration anywhere) and the three as columns on all_contacts, which already groups by account. All four locale packs in the same commit (en/zh-CN/es-ES/ja-JP: 3 field labels + 16 option labels + the _sections row each). New user page content/docs/sales/buying-centre.{mdx,zh-Hans,zh-Hant} registered in all three meta.json faces and linked from the sales index and contacts page; contacts.* section table 6->7 rows in all three locales. One changeset, 'hotcrm': minor (additive user-visible metadata, no breaking change) naming REQ-0004. No hook, no flow, no validation, no default, nothing required — proven by grep, see tests. TWO DECISIONS MADE AND DECLARED: (1) buying_function is SINGLE-valued, argued in the PR body from GroupingConfigSchema/GroupingFieldSchema in the installed @objectstack/spec/ui — grouping is one server-side aggregate whose header 'carries its raw stored value', so a multi-valued column groups by the COMBINATION and loses REQ-0004 acceptance 2. (2) The option sets are authored INLINE on the object, not in src/sales/objects/_picklists.ts: REQ-0004 names that file parenthetically as 'where the canonical sets already live', and that file's own header admits only vocabularies carried by MORE THAN ONE declaration; these three are crm_contact-only, exactly like department, which is inline. Flagging it because it is the one place this PR reads the record's Product response narrowly. PREMISE NUANCE (premise still valid): REQ-0004 calls the first attribute 角色 / 'role', and the platform refuses that word — security-role-word (ADR-0090 D3) rejected both buying_role as a field NAME and 'Buying Role' as a LABEL, two successive pnpm validate exit-1 runs quoted in the PR body. Name and English label take the remedy the rule itself prescribes (function); the rule matches /\brole(s)?\b/i on the English declaration surface and does not read locale packs, so zh-CN 采购角色 / es-ES Rol de Compra / ja-JP 購買役割 keep the natural term, each with a note beside it. SCOPE: crm_contact only — no lead/account/opportunity, nothing under src/service, src/revenue, src/marketing, nothing under content/docs/releases. docs/STATUS.md was edited because test/docs-declared-versions.test.ts went red on it (see tests). ASSIGNEE: the field was already set to os-elon-musk on arrival; ⛔ not written by me.",
    "tests": "pnpm verify — the full chain package.json declares (validate && typecheck && lint && lint:i18n-gate && hygiene && hygiene:tokens && build && test), ⛔ not narrowed. Exit code written to a file BEFORE any pipe: pnpm verify > verify2.log 2>&1; echo \"VERIFY_EXIT=$?\" >> verify2.log -> VERIFY_EXIT=0. Run under the container's shared heavy-verify lock (scripts/pm/os-verify-lock.sh, OS_VERIFY_LOCK_SLOT=hotcrm-1913): 'VERDICT batch-last-exit 0 ... held the lock 167s (2m47s) waited 0s'. Per gate, from that run: validate '✓ Validation passed' (0 errors; the remaining warnings are pre-existing carrier-only fields on crm_article_feedback/crm_forecast/crm_quote_line_item — none names crm_contact); typecheck exit 0; lint exit 0; lint:i18n-gate '✓ i18n lint gate: 0 i18n/missing-* issues'; hygiene '✓ source hygiene clean'; hygiene:tokens '✓ source token ratchet clean' — business semantics ~50,338/53,000, interaction layer ~29,397/31,000, authored total ~92,324/97,000, no ceiling moved; build exit 0; test 'Test Files 166 passed (166) / Tests 3479 passed | 1 skipped (3480)'. RED->GREEN on the way: the first full run failed one test — test/docs-declared-versions.test.ts, 'Fields: page says 331, the stack registers 334'. Fixed at the source of the drift by re-transcribing docs/STATUS.md's pnpm validate summary from this branch's own run (331 -> 334 fields), not by touching the guard. ARTIFACT READ-BACK (dist/objectstack.json, not an assertion about the source): fieldGroups keys 'identity account_info buying_centre contact_info mailing_address additional preferences'; buying_function=select/6opts/required=false/multiple=false, attitude=select/5opts/required=false/multiple=false, relationship_strength=select/5opts/required=false/multiple=false; form sections ["identity","group:buying_centre","contact_details","mailing_address","comm_preferences"] — the buying-centre section carries group and NO fields; grid columns include buying_function, attitude, relationship_strength; and all four packs resolve section + 3 labels + 6/5/5 option labels (en 'Buying Centre', zh-CN 采购决策圈, ja-JP バイイングセンター, es-ES 'Centro de Compra'). ACCEPTANCE 4 PROBE (no write is refused): grep -rn 'buying_function|relationship_strength|\\battitude\\b' src/ --include=*.ts minus the object, the view and the four packs returns NOTHING — no hook, no flow, no validation, no action, no skill reads them; combined with required=false and no option default, a contact with all three blank saves as before. NOT MEASURED: the browser. Nothing here is a renderer change, and no ablation was authored — this card adds no gate, so there is no new guard whose failure needed proving.",
    "mcp_calls": "0 — no MCP GitHub tool was called at any point, read or write. Every GitHub touch went through the REST proxy with curl + $GITHUB_TOKEN.",
    "api_writes": "5 writes, all of them inside the dispatched budget's three kinds: git push x3 (1: empty branch as the mandated write-route probe; 2: the implementation commit 9212d29; 3: the docs/STATUS.md commit 230b11c) — final head 230b11c confirmed present on the remote with git ls-remote BEFORE this report; POST /repos/objectstack-ai/hotcrm/pulls x1 (draft PR #1917, base main, draft=true confirmed on read-back); POST /repos//issues/1913/comments x1 (this report). ⛔ No labels touched, ⛔ no PATCH of any body, ⛔ no ready flip, ⛔ no enqueue, ⛔ no merge. Reads (not writes): GET issues/1913, GET pulls/1917 for the full body read-back — the stored PR body came back byte-identical to what was sent, footer included.",
    "open_questions": [],
    "out_of_scope_findings": [
    "to file (class a — a documented instruction that fails when followed; dedupe words: src/flows path, ADR-0130 package move, contact-welcome.flow.ts, docs stale directory, content/docs admin tip): the product docs still tell readers to edit files under src/flows/, a directory that stopped existing in the ADR-0130 layout move (#1910). Named sites: content/docs/sales/contacts.mdx:95 and .zh-Hans.mdx:95 (src/flows/contact-welcome.flow.ts, really src/sales/flows/), sales/quotes.mdx:155 and :161, revenue/approvals.{mdx,zh-Hans,zh-Hant}:~140, revenue/billing-handoff.{mdx,zh-Hans,zh-Hant}:~136. An admin following the instruction finds nothing there. Not fixed here: different defect class from this card and it spans three packages' docs across three locale faces.",
    "to file (class a — a doc states machine facts that are false; dedupe words: crm_contact.department picklist, contacts.mdx department list, docs hand-copied roster, IT Legal Other department, AGENTS.md documentation rule 5): content/docs/sales/contacts.mdx:94 (and the two zh faces) says the department list is 'Sales, Marketing, Engineering, Finance, IT, Operations, HR, Legal, Executive, Other'. crm_contact.department declares executive, sales, marketing, engineering, support, finance, hr, operations — so IT, Legal and Other are named and do not exist, and Support is missing. Exactly the hand-copied machine roster AGENTS.md documentation rule 5 forbids, already drifted. Untouched by this PR.",
    "noted, not filed: the 'No org chart — and what to use instead' section of contacts.mdx now reads thin — it answers 'who matters here' with job title + department, and the buying centre is the better answer as of this PR. The section is not FALSE, so it is a wording call, not a defect. Carrier named: whoever next edits contacts.mdx, most likely the REQ-0002 Track B work on deal-side stakeholder coverage; the new buying-centre page already carries the better framing and is linked from that page's section table."
    ]
    }


    Generated by Claude Code

  5. os-elon-musk commented on Sep 15, 2026

    @os-elon-musk
    CollaboratorAuthor

    复核裁定:ACCEPT

    卡 #1913 · PR #1917 · 头 230b11cb68 · 底 main


    一、边界

    查项 结论
    销售对象 只动 src/sales/objects/contact.object.ts,其余销售对象零改动 ✅
    禁区 src/service/、src/revenue/、src/marketing/ 零命中 ✅
    禁区 content/docs/releases/ 零命中 ✅
    四个语言包 四个都在同一笔里 ✅
    hook / flow / validation 零新增 —— 记录明写「由人和报表读,不由机器判」,守住了 ✅
    C(客户定制层)项 零实现 ✅

    二、两处看似超范围,实为规格内

    复核时我先把这两处当超范围挂起,逐条回查规格后撤销:

    1. 视图文件改动 —— 不是顺手改。docs/requirements/0004-contact-buying-centre-map.md 的 Acceptance 第 2 条要求「三个字段在联系人列表视图里可筛可分组」。不动视图这条验收就不成立。在范围内。

    2. 三语言文档 —— 不是自行扩面。content/docs/sales/ 本来就是 .mdx / .zh-Hans.mdx / .zh-Hant.mdx 三份加三个 meta*.json 的固有形状,只补一份反而破了目录的既有约定。是本仓的既定形,不是新开面。

    三、docs/STATUS.md 的改动

    test/docs-declared-versions.test.ts 因字段数 331 → 334 变红。开发没有去动那道守卫,而是在源头重抄了自己分支的 validate 摘要 —— 方向正确。守卫仍然在守,读数跟着事实走。✅

    四、role 词禁令的前提冲突 —— 我独立复验过

    卡面规格写的是「采购角色(buying role)」,但平台 lint 会拒。我没有采信 PR 正文的说法,自己读了 node_modules/@objectstack/lint/dist/index.js:

    function identifierHasRoleToken(name) { ... }
    function labelHasRoleWord(label2) {
      if (typeof label2 !== 'string') return false;
      return /\brole(s)?\b/i.test(label2);
    }

    规则只测英文声明面(标识符 + 英文 label),⛔ 不读语言包。

    开发的化解办法成立:字段名取 buying_function、英文 label 取 Buying Function,四个语言包里保留读者真正在说的词(采购角色 / 購買役割 / Rol de Compra),每处都带一条本地语言的注释说明为什么英文面用了另一个词。这是在规则的真实边界内保住了业务语义,不是绕过规则。 ACCEPT。

    五、形式检查

    • 正文首行 Fixes #1913 ✅
    • 全文恰好一个关闭关键词 ✅
    • draft ✅ / base main ✅ / mergeable_state: clean ✅
    • CI 10/10 success,0 红 0 挂起 ✅
    • changeset:'hotcrm': minor ✅ —— 有用户可见元数据变化,不是 docs-only,级别对

    六、复核中浮出的三条发现(不阻断本卡,PM 另立卡)

    开发按 (a)(c) 分类报了两条,我自己又量出第三条:

    • (a) 文档仍在教读者去改 src/flows/ —— 该目录在 ADR-0130 的搬迁里已移除。已点名的站点:content/docs/sales/contacts.mdx:95 与 .zh-Hans.mdx:95、sales/quotes.mdx:155,161、revenue/approvals.*:~140、revenue/billing-handoff.*:~136。
    • (a) crm_contact.department 花名册失真 —— contacts.mdx:94 列的 IT / Legal / Other 三项在声明里不存在,而实际存在的 Support 没列。
    • (c) 元数据陷阱(我量的) —— 同一文件 第 19 行的 fieldGroup label 'Account & Role' 带着 role 词却能过,因为 labelHasRoleWord 只测字段 label,⛔ 不测字段组 label。同一个词在同一个文件里一处被拒一处放行,这是规则面本身的洞,不是本 PR 的债。

    这三条都不在本卡的文件面里,⛔ 不塞进本 PR 扩面。PM 立卡走各自的队列。


    裁定:ACCEPT。 PR #1917 转 ready,走普通队列 squash 落地。


    Generated by Claude Code

  6. removed their assignment
    on Sep 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestpm:epicParent delegated to a dedicated epic PM — other PMs never dispatch into its subtree

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions