Skip to content

docs(contacts): state the email rule as per-organization, not per-account (#648) - #666

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-648-contact-email-docs
Aug 4, 2026
Merged

docs(contacts): state the email rule as per-organization, not per-account (#648)#666
os-zhuang merged 1 commit into
mainfrom
claude/issue-648-contact-email-docs

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Fixes #648

问题

content/docs/sales/contacts.mdx:62(及两份中文翻译)把联系人邮箱的唯一性规则写成:

An email address must be unique within the same account. The same person can appear under multiple companies (a board member, for example) but not twice in one company.

两句话都是错的,而且第二句描述的正是产品明确拒绝的写入 —— 读者照着文档去建"同一个人挂在两家公司下"的两条联系人,只会拿到 Another contact (…) with email … already exists.

当前实际生效的规则(已在 origin/main @ 5a11631,17.0.0-rc.2 上重新核对)

两层互相独立的约束,作用域都是组织而不是客户:

  1. src/objects/contact.object.ts 在字段上声明 unique: true。自 framework#3696 起,字段级 unique 会落成租户复合唯一索引 (organization_id, email) —— 一个组织内全局唯一,跨越该组织下的所有客户。对象里刻意没有表级 indexes 上的 email 条目:表级声明会被逐字materialize成平台级索引,反而让复合约束不可达(framework#3991 unique/double-declaration)。
  2. src/objects/contact.hook.tscontact_integrity 先把邮箱转小写,再以不带客户作用域的查询查重并抛错,抢在数据库索引之前给出可读错误。test/hooks-runtime-sales.test.ts 里那条名字就叫 "rejects a duplicate email GLOBALLY, not just within one account" 的用例记录了原因:按客户作用域查重会让跨客户重复溜过友好检查,然后在转换过程中撞上数据库全局唯一索引。

改动

三份 contacts*.mdx 的规则行改成实际生效的表述,措辞对齐 #625 / PR #646 修正 crm_account 名称规则时用的模板:组织内唯一;同一平台上的其他组织可以有自己使用该地址的联系人;邮箱在存储与比较前统一转小写,所以 Ada@Example.comada@example.com 视为同一个地址;同一个人同时在两家公司与你往来时,两条联系人记录需要各自不同的邮箱。董事会成员那个例子被删除 —— 文档里不再有任何描述"会被拒绝的写入"的示例。

新增 test/docs-contact-email-uniqueness.test.ts 把两侧都钉住,防止措辞漂回去:

  • 按语言逐份检查规则行 —— 组织作用域的表述必须在,旧的"同一个客户内"说法和董事会成员示例必须不在,跨客户不可复用必须写明;
  • 同时钉住文档所描述的实现:Contact.fields.email.unique === trueindexes 里没有 email 条目、以及真实跑一遍 contact_integrityaccA 已有 ada@example.com,往 accBAda@Example.com 被拒)。

任意还原一份 docs 文件,该文件 15 条断言里有 4 条转红。

仅文档改动 —— 没有动任何 metadata、hook 或约束。"一个人、两家公司"到底应不应该被支持,是产品决策 + schema 变更(邮箱约束得挪位置),按 issue 的说明刻意不在本 PR 范围内。

验证

npx vitest run --maxWorkers=2
  Test Files  49 passed (49)
       Tests  1180 passed | 1 skipped (1181)

pnpm typecheck   # tsc --noEmit,无输出
pnpm hygiene     # ✓ source hygiene clean

反向验证(临时 stash 掉 contacts.mdx 后跑新测试):

× scopes the rule to the organization, not to one account
× does not resurrect the per-account claim
× does not describe the one-person-two-companies write the product rejects
× says the address cannot be reused across accounts
Tests  4 failed | 11 passed (15)

Changeset:.changeset/contact-email-unique-per-org-docs.md(patch)。未触碰 content/docs/releases/

🤖 Generated with Claude Code

https://claude.ai/code/session_01Rvtsew6XgsSjxVa59HRPRK


Generated by Claude Code

…ount

`content/docs/sales/contacts.mdx` and its two translations promised that "an
email address must be unique within the same account" and that "the same person
can appear under multiple companies (a board member, for example)". Both halves
were wrong, and the second described the exact write the product refuses.

What ships, on two independent layers:

- `crm_contact.email` declares FIELD-level `unique: true`, which since
  framework#3696 materializes as the tenant composite `(organization_id, email)`
  — one address per ORGANIZATION, spanning every account in it. The object
  deliberately declares no table-level index on `email`: a declared index is
  taken verbatim (platform-wide) and would leave the composite unreachable
  (framework#3991 `unique/double-declaration`).
- `contact_integrity` (`src/objects/contact.hook.ts`) lowercases the address and
  looks it up with NO account scope, throwing `Another contact (…) with email …
  already exists.` before the database index can. `test/hooks-runtime-sales.test.ts`
  already carries the behavioural proof ("rejects a duplicate email GLOBALLY,
  not just within one account").

So a reader following the board-member example got an error the documentation
said they would not get. The three rule lines now read like the `crm_account`
name rule corrected in #625 / PR #646: unique within your organization, another
organization on the same platform may hold its own contact at that address, the
address is normalised to lower case before it is stored and compared, and a
person you deal with at two companies needs a different address on each record.

`test/docs-contact-email-uniqueness.test.ts` pins both sides so the prose cannot
drift back: the rule line per language (organization scope present, per-account
claim and board-member example absent) plus the metadata and hook behaviour the
prose describes. Reverting any one of the three docs files turns 4 of its 15
assertions red.

Documentation only — no metadata, hook or constraint changed. Whether "one
person, two companies" SHOULD be supported is a product decision and a schema
change; it is deliberately out of scope here.

Fixes #648

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rvtsew6XgsSjxVa59HRPRK
@vercel

vercel Bot commented Aug 4, 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 4, 2026 12:32am

Request Review

@github-actions github-actions Bot added the ci/cd CI plumbing and the verification pipeline label Aug 4, 2026
@os-zhuang
os-zhuang marked this pull request as ready for review August 4, 2026 00:51
@os-zhuang
os-zhuang added this pull request to the merge queue Aug 4, 2026
Merged via the queue into main with commit b551f15 Aug 4, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cd CI plumbing and the verification pipeline

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Docs promise contact emails are unique "within the same account" — the enforced rule is per organization, and the documented use case is rejected

1 participant