> For AI agents: the complete documentation index is available at /llms.txt, the full documentation bundle is available at /llms-full.txt.

# Task V3 Product / Domain Contract

> Product, Domain, AI Runtime, Backend Contract & UX Projection

| Metadata                               | Value                                                                                                                 |
| -------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
| Status                                 | **Selected Target contract**：产品与领域方向已选定；本文不宣告 deployment、UAT 或 PROD release                                           |
| Scope                                  | Task V3 稳定产品目标、领域 contract、Target Architecture 与安全边界                                                                  |
| Owner                                  | Product + Engineering                                                                                                 |
| Last contract/status-separation review | 2026-08-04                                                                                                            |
| Contract covers                        | Task、Display Type、Next Action、Activity、Business Progress、Outcome、Task Policy、AI authority、Workbench/Funnel projection |
| Dynamic rollout source                 | [Task V3 Rollout Status](/product-design/v3/tasks-feature/task-v3-rollout-status.md)                                  |
| Supersedes at rollout                  | `Case + Task`、`Task → rigid Step → Activity`、global `closeResult`、假想 provider/Milestone truth 等旧方案                    |
| Not a replacement for                  | Figma visual spec、migration DDL、具体 API OpenAPI 文件、production code                                                     |

## 0. 怎么读这份文档

这份文档只锁定 Task V3 的稳定产品目标、领域词汇和安全边界。PR 是否 merge、某环境是否 deployed/enabled、真实模型质量、员工 UAT 和 PROD release 都会变化，统一维护在 [Task V3 Rollout Status](/product-design/v3/tasks-feature/task-v3-rollout-status.md)。本文不再复制这些动态数字和待办清单。

`contract selected`、`code merged`、`deployed to TEST`、`live-model evaluated`、`employee UAT passed` 和 `released to PROD` 始终是不同的证据等级。任何读者都不得用前一项推导后一项。

Store 的出厂 automation tier 是 `full_auto`。`cautious` 和 `standard` 保留为 Store override 与故障降级能力。`full_auto` 也不等于 AI 任意写库：模型只产生 typed proposal/command；DNC、Store isolation、dedupe、evidence、Policy、idempotency 和 atomic writer 仍由代码决定。

第一次接触 Retaintive 的产品经理、设计师或工程师，只读这一份就应该能回答：

- 产品解决什么问题。系统真实能看到什么，又看不到什么。
- `Task`、`Display Type`、`Next Action`、`Activity`、`Business Progress`、`Outcome` 分别是什么。
- 店员和 AI 分别可以做什么。
- 数据库、API、AI loop 和 Workbench 分别负责什么，以及它们怎样配合。
- Lead Funnel 为什么不是 Task status。
- 新增一种业务 Task 时应该改哪里，避免再复制一套 hardcoded mapping。

文中会反复出现以下标签。它们表示一条事实或 proposal 目前处在哪个阶段：

- **Current source**：最后一次核验时 source code/schema 已经存在的行为。它不自动表示某个 migration 已在 production 执行、某个 event source 已启用，或某家 Store 已经 cutover。
- **Enabled / deployed**：必须由目标环境的 config、migration state 和 runtime evidence 单独证明。checked-in default 不能替代 production verification。
- **Proposed**：本文中仍未定稿的 future extension 或产品细节，不能当成 current implementation。
- **Target**：已经选定的系统终态；不表示相应代码已经 merge、部署、验证或向 PROD 开放。
- **Open Decision**：还没有拍板。实现者不能把它当成已经接受的结论。

如果后文保留的历史 rollout phase、lead-only MVP 或 Store-by-Store canary 与当前状态冲突，**当前状态**以 [Task V3 Rollout Status](/product-design/v3/tasks-feature/task-v3-rollout-status.md) 为准，**领域语义**以本文为准。禁止偷偷改写历史数据；UI、prompt 和 backend 也不能各自发明另一套 vocabulary。

### 0.1 Contract 与 rollout status 的边界

| 问题                                                                                                                     | Source of truth                                                                      |
| ---------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
| Task / Next Action / Activity / Business Progress / Outcome 的含义，Policy、Guard、AI authority 和 Workbench projection 的不变规则 | 本文                                                                                   |
| PR merge state、checked-in environment gates、Store authority、eval 结果、deployment、UAT、PROD release 与剩余待办                  | [Task V3 Rollout Status](/product-design/v3/tasks-feature/task-v3-rollout-status.md) |
| 某个具体环境实际运行了什么                                                                                                          | 该环境的 config、migration state、runtime evidence 和 UAT 记录；任何文档都不能代替                      |

> **2026-08-04 status-separation baseline（仅记录本文为什么移除旧 snapshot）：** #1908 的 approval backend 和 #2024 的 owner automation / proposal UI 已 merge；#1913 已移除 Contacts Analyzer 的 AI Task decision authority，但 `applyTaskAction` compatibility facade、deterministic lead backstop 和 legacy Timeline projection 仍存在；#1925 已开始 human-ratified/live eval，quality gate 未通过；#1982 使 `MANAGED_TASK_KIND` 中全部七类 objective 具备 Target/Shadow authority capability，#1993 把新 Store 的初始 authority 改为 `target/target`。checked-in `main` 中 TEST 的 Task V3 source gates 为 `true`，`pre/prod` 为 `false`。这些 source/merge 事实不证明员工 UAT 已通过，也不证明 PROD 已发布。分离日之后的变化只更新 rollout status 文档，不再复制到本节。

### 0.2 Unified Call AI chain contract

Unified Call path 复用同一次 Call analysis 产生的 `task_semantics`，而不是让两个 AI 重读同一份 transcript：

```text
deterministic pre-triage
  → triage AI（未被 pre-triage 短路时）
  → classify AI：一次生成 Call 分类/总结 + Task-specific claims
  → Task Reasoner Turn 1
  → 如果缺少会改变决定的资料：server 执行 allowlisted read
  → Task Reasoner Turn 2（可选且必须是 final）
  → deterministic Guard + writer
```

因此，统一路径不再有独立的 “Task semantic extraction” AI call。对于本来就会做 Call analysis 的合格 Call，Task V3 的增量模型开销通常是一个 Reasoner turn，material context 不足时才是两个。`triage`、`classify`、可选 `verify/coaching` 属于现有 Call analysis 成本，仍须在 end-to-end 账单里展示，不能因为不是 Task 增量就隐藏。

Default `two_turn` contract 最多允许一个 read round、两个 approved read-only tool requests；同一 read request 在一个 run 中最多执行一次。首轮已经装入的 source text、bounded Task list 和 Policy catalog 会被 read ledger 标为已加载，重复请求只返回确定性的 rejection，不再次查库。第二轮之后必须输出 final proposal、`requestStaffInput` 或 `no_op`。Adapter 也包含需显式 opt in 的 bounded native-tool mode；它的启用、资源预算和质量/成本证据必须单独审批，不能由本文推导为已在任何环境运行。

历史 Call artifact 或缺少统一 `task_semantics` 的来源暂时仍走 `standalone semantic model` fallback，以便兼容 rollout；它是明确的剩余质量风险，不是统一路径的目标形态。

PROD physical legacy columns 只有在 backfill 完成并证明零读者、零写者后才能 drop。暂时保留无人读写的旧列，不等于继续运行两套 active system。

存储边界也必须说清楚：**Task V3 的 state、authority、source proof、receipts 和 audit 落在 Neon/PostgreSQL。** S3 继续保存录音、transcript 或 analysis 等大文件/不可变 artifact；部分现有 ingestion/control-plane 仍可能使用 DynamoDB。不能为了宣称“全部迁到 Neon”而删除仍有明确职责的 S3 或现有 infrastructure。

***

## 1. 两分钟理解整个系统

### 1.1 产品一句话

Retaintive 是一个 **AI Revenue Workbench**。它使用真实电话、短信、Email Lead 和员工掌握的线下信息来帮助门店工作：

> 发现值得处理的客户机会或问题，告诉员工下一步做什么，自动记录可观察的沟通，持续跟踪到有证据的结果，并让门店老板看到这些工作带来的业务价值。

产品闭环是：

```text
发现机会
  → 建立 Task
  → 明确 Next Action
  → 员工采取行动
  → 记录 Activity
  → AI 或员工决定下一步
  → 必要时记录非终态 Business Progress
  → 得到有证据的 Outcome
  → 更新 Workbench、Funnel 与 Revenue reporting
```

### 1.2 最终只保留五个核心业务概念

| 概念                    | 用人话解释                                  | 例子                         |
| --------------------- | -------------------------------------- | -------------------------- |
| **Task**              | 一件需要持续处理，直到得到明确结果的客户机会、问题或目标           | 推进王女士从 Lead 到明确销售结果；处理取消请求 |
| **Next Action**       | 这个 Task 当前已经采用的下一步计划（员工或获授权的 AI 都可以采用） | 现在打电话；周五下午回电               |
| **Activity**          | 实际发生过、可审计的一次工作或互动                      | 电话无人接；SMS 已发送；店员记录线下沟通     |
| **Business Progress** | Task 处理中已经发生、但还不是最终 Outcome 的业务进展      | Intro 已预约；取消请求进入保留流程       |
| **Outcome**           | Task 最后怎么样、为什么结束、证据是什么                 | Converted；客户明确拒绝；无法联系；员工确认 |

辅助概念只有四个，不再增加：

| 概念                             | 作用                                                                               |
| ------------------------------ | -------------------------------------------------------------------------------- |
| **Suggestion / AI Suggestion** | 一个还没有被采用的建议。`sourceType=ai` 时，UI 把它叫作 AI Suggestion。它还不是 Next Action             |
| **Display Type**               | 员工现在在队列里看到的工作分类，例如 Lead Outreach。它不是另一个 Task，也不是另一个 lifecycle                    |
| **Task Policy**                | 每一种 Managed Task 都要遵守的 contract。它规定如何创建和关闭、需要什么证据、SLA 怎么算、UI 怎么显示、reporting 怎么统计 |
| **Funnel Projection**          | 系统根据现有 evidence 算出的客户进展视图。它不是 Task lifecycle                                     |

### 1.3 MVP 明确不引入什么

MVP 不引入：

- `Case`：Task 自己就代表完整的客户事项，不再多造一层意思相近的对象。
- 固定 `Step`：销售和客服是人与人的沟通，不是标准化 SOP。系统只保留一个可以随情况变化的 `Next Action`。
- 第一等 `Action` 表：第一阶段把当前 Next Action 直接存在 Task current snapshot 中。每次历史变化写入 Timeline。
- 复杂业务 `Milestone` / external outcome provider system：当前没有 booking、attendance、membership、billing、CRM/POS 的权威 integration，不能假装这些事实已经存在。RingCentral source-proof 仍然必须保存，因为它证明 Call/SMS/VoiceMail evidence identity；它不等于权威 membership/billing truth。
- 永远运行的 autonomous agent：每次 event 到来时，AI 只运行一个有预算、有终点的 bounded loop。
- AI 任意写数据库：AI 只能提出 typed domain command。Code/Policy Guard 决定是否执行。

`Current Action` 不是一个独立概念。文档、API 和 UI 统一叫：

> **Next Action / 下一步**

***

## 2. 为什么这样设计

### 2.1 Task 已经足够表达“客户这件事”

以前 `Case + Task` 的解释是：

```text
Case = 客户事项
Task = 员工具体工作
```

这样一来，产品和用户会同时面对两个都很像“任务”的对象。当前业务不需要这层重复结构。

在本模型中，同一件客户事项只用一个 Task 表达：

```text
Task：推进王女士这个 Lead 到明确销售结果
  displayType：lead_outreach
  当前 Next Action：5 分钟内首次电话联系
  Activity 1：电话无人接
  displayType：lead_follow_up
  新 Next Action：发送 SMS，明天再次联系
  Activity 2：客户在 SMS 中确认周二 Intro 已预约
  displayType：booked_not_converted
  新 Next Action：确认到店计划，并根据客户需求讨论 Membership
  Activity 3：客户确认已经正式加入
  Outcome：converted
```

Task 从头到尾管理整件事：为什么开始、现在准备做什么、过程中发生了什么，以及最终得到什么 Outcome。Task 的 result 不需要再变成另一个 Case。它就是这个 Task 自己的 Outcome。

### 2.2 为什么不是 Task + 标准 Steps

一个 Task 可以发生很多次 Activity，但这些 Activity 不需要预先排成一套写死的 Steps。

- 首次电话可能直接预约成功。
- 无人接后可能发 SMS，也可能店员第二天线下遇到客户。
- 客户可能主动回电，改变计划。
- 一通沟通可能同时推进 `lead_conversion` 和 `referral` 两个 Tasks。

因此，无论沟通怎么变化，系统都只需要持续回答四个问题：

1. 这件事为什么存在？
2. 现在最合理的 Next Action 是什么？
3. 实际发生了什么？
4. 是否已有足够 evidence 关闭，Outcome 是什么？

### 2.3 一个 Contact 可以同时有多个 Tasks

```text
Contact at Store A
  ├── Task: lead_conversion
  ├── Task: referral
  └── Task: payment_recovery
```

这三个目标互不依赖。它们可以分别分配，也可以分别关闭。

同一种 `taskKind` 能不能同时有多个 open Task，由对应 Task Policy 的 `dedupeRule` 决定。

查询 Contact、Task、Activity 或 Evidence 时，都必须用 `store_id` 隔离。不能只看 phone，然后把不同门店的数据合在一起。

### 2.4 Contact contract：identity anchor，不是 decision truth

在产品 UI 里，Contact 可以显示成“一个客户”。但在 MVP 的技术 contract 中，它只代表下面这个店内身份锚点：

```text
Contact identity = store_id + normalized_phone
```

这个 identity 用来把某家 Store 内的 Calls、SMS、Email Leads、Tasks、Activities 和 Notes 串成 conversation history。

它不能证明现实中“这个号码永远只属于这一个人”。共享号码、换号、wrong number 和跨店到访都可能发生，所以必须允许员工纠正。系统不能只按 phone 做全局 merge。

关系与 cardinality：

```text
Contact 1 ── * Tasks
Task    1 ── 0..1 current adopted Next Action
Task    1 ── * historical Next Action changes (Timeline)
Task    1 ── * Activities
Task    1 ── * Suggestions (可以多个，但不是多个 adopted Actions)
```

在 MVP 中，一个 open Task 同一时间最多只有一个已经采用的 `Next Action`。

如果未来真的出现多项工作需要同时执行，而且每项都要分别分配、分别设置 deadline，再评估是否引入 first-class `Action` entity。在此之前，多个 Suggestions 仍然只是多个建议，不能当成多个 current Actions。

不同 Contact fields 的可信程度不同，不能一视同仁：

| Contact data                                                       | Role in Task decision                                       |
| ------------------------------------------------------------------ | ----------------------------------------------------------- |
| `storeId + normalizedPhone`、source linkage                         | 用于关联和隔离数据；不能当作现实身份的绝对证明                                     |
| DNC/restriction、authenticated staff correction/note                | 有 provenance 的 hard guard 或员工提供的 context                    |
| Name 等 mixed-source profile field                                  | 按每个 field 自己的 provenance 展示。员工确认优先，不能全部称为 AI-derived        |
| AI summary、`lead_status`、lifecycle、purchase intent、action needed 等 | 只作为 background/projection。不能仅凭这些字段 create、close 或 skip Task |
| 原始 Call/SMS/Lead、Task Activity、`staff_assertion`                   | 当前做判断时最主要、而且可以回查的 evidence                                  |

Target reasoning 使用四类输入：fresh evidence、Task/Activity history、Policy，以及 Contact guards/background。

任何 derived Contact field 都不能反过来被当成 terminal Outcome truth。未来迁到稳定的 `contactId` 时，也必须保留 store isolation 和 identity-link audit，不能静默合并历史记录。

***

## 3. 系统真实拥有的数据

### 3.1 Current：可以看到

当前产品可以可靠使用以下输入：

- RingCentral call metadata、录音/转写以及已生成的分析结果。
- RingCentral SMS message 与 thread。
- 通过 Email ingestion 进入系统的 Lead form 信息。
- 员工在 Retaintive 中记录的 Activity、Note、Next Action 和 Outcome。
- 系统已有的 Contact snapshot、Tasks、Suggestions 和 Timeline。

员工输入不是“自动化做不到时才用的 fallback”，而是一等数据源。店员知道很多系统看不到的线下 context，例如客户已经到店、客户当面做了承诺、员工在后台完成了操作，或者员工从其他系统看到了结果。

### 3.2 Current：看不到

当前不能假定系统知道以下事实：

- booking system 的 authoritative state。
- class attendance / intro attendance。
- membership activation/cancellation。
- billing/payment settlement。
- 门店 CRM 或 POS 的完整客户历史。

因此，Contact Profile 只是根据现有 evidence 组装、再由 AI enrich 的 operational snapshot。它不是完整的 CRM truth。

姓名、DNC、Notes 等字段也可能来自 Lead、RingCentral 或员工，不能全部说成 AI 生成。更不能因为 AI 听起来像是“已经预约”，就把它写成“外部 booking system 已确认”。

### 3.3 Evidence 与 Verification vocabulary

| Value                    | 含义                          | 典型来源                                               | Allowed system effect                                                                              |
| ------------------------ | --------------------------- | -------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
| `system_observed`        | 系统直接看到了一个技术事实               | Email Lead 到达、RingCentral call/SMS metadata        | 可以创建 Task 或记录 Activity；但单独使用时通常不能证明业务 Outcome                                                      |
| `communication_explicit` | 电话或 SMS 中有清楚、可以引用的客户或员工表述   | “I booked her for Tuesday at 6”                    | 可以按 Policy 记录非终态 booking progress，并重算 `displayType`。只有命中 terminal Outcome allowlist 时才能 auto-close |
| `staff_confirmed`        | 有权限的员工明确确认了线下或后台事实          | 员工确认 “Intro booked” 或 “Converted” 并写 evidence/note | 可以记录 progress 或关闭 Task。记录 progress 本身不会关闭 Task，而且这类确认优先于后续 AI inference                            |
| `form_asserted`          | Lead form 或其他表单中填写的信息       | Lead 的 preferred time、历史 `booked_date`             | 可以用于创建 Task、生成建议，或作为带 provenance 的 Funnel input；不能单独证明 terminal Outcome                            |
| `ai_inferred`            | AI 根据上下文推断出的结论，但原始沟通没有明确这样说 | “客户似乎有兴趣”                                          | 不能自动关闭 Task；只能形成 Suggestion 或进入 human review                                                       |
| `external_verified`      | 未来外部权威系统确认                  | future CRM/booking/billing integration             | 未来保留；MVP 不得生成                                                                                      |
| `imported_legacy`        | 从旧系统迁移而来，来源强度信息不完整          | legacy Task close result                           | 不能自动升级成更强的 evidence                                                                                |

Reporting 的措辞必须与 evidence 强度相符：

- `AI-inferred`：AI 推断。
- `communication-confirmed`：沟通中明确表达。
- `staff-confirmed`：员工确认。
- `externally verified`：只有未来真的接入权威系统后才能使用。

外部客户端或 model 只能提交不带 scope 的 pointer。server 完成解析和鉴权后，才生成可以持久化的 ref。这样可以避免调用方自行填写并声称某个 `storeId` 可信：

```ts
type EvidencePointer = {
  type:
    | 'ringcentral_call'
    | 'ringcentral_message'
    | 'email_lead'
    | 'task_activity'
    | 'staff_assertion'
    | 'legacy_import'
    | 'external_record'; // future only
  id: string;
};

type ResolvedEvidenceRef = EvidencePointer & {
  storeId: string;       // server 注入/验证，AI 和客户端不能自报可信 store
  occurredAt?: string;
};
```

Task、Activity、Business Progress、Outcome snapshot 和 Timeline 保存 `ResolvedEvidenceRef`。public API 和 model tool input 接受 `EvidencePointer`，再由 server 检查它是否属于同一家店。

系统不能只保存一段自然语言，因为那样无法回查原始来源。

不同用途的 evidence refs 要分别命名为 `creationEvidenceRefs / activityEvidenceRefs / progressEvidenceRefs / outcomeEvidenceRefs`。

`sourceType/sourceEntityType/sourceEntityId` 只保留为 legacy creation provenance shortcut，不能替代 typed evidence。`verificationType` 必须由 server evidence resolver 和 Policy Guard 根据已经解析的 evidence 决定。客户端和模型不能自己声明可信等级。

`staff_assertion` 是员工把线下信息正式写入系统时使用的 evidence。它不是把普通 note 包装成外部事实。

员工以 `staff_confirmed` 执行 `recordBusinessProgress` 或 `closeTask` 时，server 必须在同一个 transaction 内完成四件事：

1. 要求员工填写一条简短、具体的 confirmation note。
2. 生成 append-only `staff_assertion` Timeline event 和稳定 ID。
3. 让 progress 或 Outcome 的 `evidenceRefs` 指向这个 event 的 `ResolvedEvidenceRef`。
4. 记录 authenticated actor。客户端不能提交或伪造 actor identity。

如果员工还说明了自己具体做过什么，例如“我在线下帮她完成预约”，同一次提交也可以生成一条 typed Activity。

如果员工只确认 progress 或 Outcome，没有声明自己做过实际工作，就不能因为他点了一次 Update/Close 而编造 Activity，也不能给他增加 staff revenue credit。

从 Current 迁到 Target 时，evidence adapter 必须按下面的规则显式转换：

| Current provenance/source | Target handling                                                                                                          |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| `call`                    | 如果能解析到同店 RingCentral Call row，就转成 `ringcentral_call`；否则转成 `legacy_import`                                                |
| `message`                 | 如果能解析到同店 RingCentral message row，就转成 `ringcentral_message`；否则转成 `legacy_import`                                          |
| `lead`                    | 确认来源是 Email ingestion lead row 时，转成 `email_lead`。phone/SMS lead signal 应引用对应的 Call/Message，不能冒充 Email Lead               |
| `manual`                  | 新的 authenticated write 创建 `staff_assertion` 或 `task_activity`。历史 manual 数据如果没有 typed actor/evidence，只能转成 `legacy_import` |
| `contact_analysis`        | 只代表 AI decision provenance，也就是某次 run/assessment；它不是 source fact。系统必须继续解析 underlying source refs，否则只能算 `ai_inferred`      |

Current verification 不能只按旧名字机械升级。

- 如果 `provider_confirmed` 只说明 RingCentral source 存在，Target 最多只能映射为 `system_observed`。
- 只有 evidence excerpt 明确说出了结果，Policy 才能把它判断为 `communication_explicit`。
- 旧值 `provider_confirmed` 永远不能映射成 `external_verified`：它最多升到 `system_observed`；只有 evidence 原话明确表达了结果，才能经 Policy 判为 `communication_explicit`。
- `staff_asserted` 只有在 actor 和 note 都可审计时，才能映射成 `staff_confirmed`；其他情况保持 `imported_legacy`。

### 3.4 Current store-scope boundary 与 historical-data discipline

本节之前记录的两个具体 query 漏洞已经在当前 source 中修复：

- per-call `queryCustomerHistory()` 要求 server-resolved `storeId`，calls/leads 同时按 Store + phone 查询；缺 `store_id` 的 legacy rows fail closed，并单独计数供 repair。
- Contact Profile 要求已授权的 `storeId`，外层 Contact 和 Timeline counts 都使用 exact Store + account + phone scope。

这并不把 Contact Profile 变成完整 CRM truth，也不允许把历史 AI fields 升级为 evidence。当前仍然遵守下面的边界：

- Task Reasoner 的每个 read tool 都由 server 注入 `storeId + contactPhone`，逐表验证 scope、time window 和 field allowlist；模型不能自报 Store。
- 历史 row 缺 `store_id`、缺 exact provider identity 或无法证明事件发生时 Store 时，保持 unresolved/imported legacy。禁止用“当前 phone 属于哪家 Store”回填历史 Store。
- `leads.booked_date` 只保留为带 `form_asserted` provenance 的 Funnel/cohort input；它不自动写 completed booking progress，也不证明 membership Outcome。
- `customer_type / topic / summary / contacts.lead_status` 等历史 AI fields 仍然只是 background。它们不能 create/close/skip Target Task，也不能证明 terminal Outcome。

因此，修复 query isolation 只解决“不能读取另一家店”的正确性问题，不会凭空补齐历史 CRM、booking、membership 或 billing truth。

***

## 4. End-to-end conceptual model

```mermaid
flowchart LR
  I["RingCentral Call/SMS<br/>Email Lead<br/>Staff Input"] --> R["Event Router"]
  R --> C["Store-scoped Context Builder"]
  C --> A["Bounded Task Reasoner"]
  A --> P["Typed Proposal"]
  P --> G["Policy + Authority Guard"]
  G --> O["Task Orchestrator"]
  O --> T["Task current snapshot"]
  O --> L["Append-only Timeline / Activity"]
  O --> S["Suggestion + AI audit"]
  T --> W["Employee Workbench"]
  L --> W
  S --> W
  T --> F["Funnel / Revenue Projection"]
  L --> F
```

图中的业务对象之间有以下关系：

```text
Contact  1 ── * Task
Task     1 ── * Activity
Task     1 ── 0..* business progress events (non-terminal, append-only)
Task     1 ── 0..1 active Next Action snapshot
Task     1 ── 0..* AI Suggestions
Task     1 ── 0..1 current terminal Outcome
```

Outcome correction、reopen 和所有历史值都保存在 append-only Timeline 中。Task row 只保存 current snapshot。

***

## 5. Lifecycle 与 UI projection

### 5.1 数据库只保存简单的 lifecycle

**Selected Target lifecycle：**

```text
open → closed
closed → open  (reopen，必须追加 audit event)
```

`status` 只回答一个问题：“这件客户事项还需不需要继续处理？”

它不表示员工是否已经尝试过，也不表示员工应该在什么时候处理。

关闭 Task 时，必须同时记录以下信息：

- 一个 business `resultCode`，或者一个 administrative result。
- 一个 `closureReasonCode`。
- verification 与 evidence。
- 谁关闭了 Task、什么时候关闭、当时使用哪一版 Policy。

### 5.2 下面这些是计算出来的视图，不是 Task status

| Projection                  | 计算含义                                   | 是否写回 `tasks.status`  |
| --------------------------- | -------------------------------------- | -------------------- |
| `not_started`               | 当前 open period 还没有 qualifying Activity | No                   |
| `in_progress` / `attempted` | 当前 open period 已经有 Activity            | No                   |
| `needs_attention`           | 现在应该处理，而且没有安排未来的 Next Action           | No                   |
| `scheduled`                 | `nextActionAt` 在未来                     | No                   |
| `overdue`                   | `deadlineAt` 已过，但 Task 仍然 open         | No；它只是 boolean/label |
| `first_response_met`        | 第一次 qualifying Activity 发生在 SLA 要求的时间内 | No                   |

`no_answer`、`voicemail_left`、`sent / delivered` 描述某次 Activity 的 outcome。`message_sent` 描述某次 Activity 的 action。它们都不是 Task status，也不是 Task Outcome。

### 5.3 三种时间各自回答不同的问题

| Field                | 回答的问题                            | Lead 例子                |
| -------------------- | -------------------------------- | ---------------------- |
| `firstResponseDueAt` | 最晚多久之内必须第一次采取 qualifying action？ | Lead 到达后 5 分钟          |
| `nextActionAt`       | 员工下一次应该什么时候处理？                   | 明天 10:00 再打            |
| `deadlineAt`         | 整件 Task 最晚什么时候必须解决或升级处理？         | 7 天内完成 outreach policy |

修改 Next Action 时，不能顺便移动 `deadlineAt`。记录了一次 Activity，也不等于 Task 已经 closed。

### 5.4 Reopen 在 MVP 中是什么意思

MVP 不增加 `WorkCycle` 表。不过，Task reopen 后，不能因为过去有 Activity 就立刻显示 attempted。具体规则如下：

- Target 在 Task snapshot 中保存 `currentOpenedAt`。
- Task 初次创建时，`currentOpenedAt` 等于 `createdAt`。reopen 时，把它更新为 reopen 时间。
- 计算 work state 时，只看 `occurredAt >= currentOpenedAt` 的 Activity。
- reopen 时，从 current snapshot 清除旧 Outcome。但旧 Outcome 和 reopen reason 继续保留在 Timeline 中。
- 如果以后同一个 Task 确实需要复杂的多-cycle 报表，再把 cycle 升级为 first-class entity。

### 5.5 Current implementation 已经支持什么

Current source 同时包含 Task V2 projection 与 Target projection，必须分开描述。

Target engine/API 已经支持：

- `tasks.status = open | closed`，并以 `currentOpenedAt` 区分 reopen 后的新 open period。
- `nextActionText + nextActionAt` 是 canonical adopted plan；`deadlineAt` 和 `firstResponseDueAt` 分别回答整件事期限和首次响应 SLA。`dueAt` 只保留 legacy compatibility。
- `recordActivityAndPlan`、Next Action、deadline、priority、assignee、note、`intro_booked` progress、converted confirmation、correction 和 reopen typed commands。
- atomic Task snapshot + append-only Timeline；Activity 数量不会隐式关闭 Task，terminal closure 必须经过显式 Outcome command。
- Target work state 只读取 `occurredAt >= currentOpenedAt` 的 qualifying Activity；`nextActionAt > now` 投影为 Scheduled。

Task V2 queue/read path 仍保留现有 scheduling/work-state 语义。它可能把“门店本地时间明天 00:00 之前”的未来动作留在 Needs Attention，也可能按 Task 整个 lifetime 的 Activity 显示 attempted。

因此，部分 Target 语义已经存在于 executable code，但不能写成“所有 production 用户已经在用”，也不能由此推断应该 cutover。任何获批切换前必须运行 Task V2 / Task V3 dual projection，按 Store 对比 queue counts、SLA、Timeline、Funnel 和 Speed，再由 per-objective authority 执行可回滚 cutover。

MVP 不增加 `waiting / blocked` queue state。需要等待客户或内部信息时，员工或 AI 必须设置下一次 review 时间，所以 Task 仍会进入 Scheduled。这样可以避免 Task 无期限消失。

只有未来确认存在“无法安排日期的 hold”需求时，才增加 typed `holdType / holdReason / holdUntil` 和对应 commands。不能从 Next Action 文本中用 NLP 猜测 hold 状态。

***

## 6. Target fields 与 values

下面的字段名以 Target logical contract 为准。表里的 `Current mapping` 说明当前 source schema/runtime 的承载方式；`Expand column present` 表示 nullable compatibility column 已经在 migration/schema 中定义，但不表示 migration 已在 production 执行或 read authority 已切换。

### 6.1 Task fields

| Field                    | Type / values                           |             Required | Meaning                                                                  | Authority                   | Current mapping                                              |
| ------------------------ | --------------------------------------- | -------------------: | ------------------------------------------------------------------------ | --------------------------- | ------------------------------------------------------------ |
| `taskId`                 | UUID                                    |                  Yes | Task identity                                                            | System                      | `tasks.task_id`                                              |
| `storeId`                | UUID                                    |                  Yes | 数据属于哪家店，也是隔离数据的边界                                                        | Code only                   | `tasks.store_id`                                             |
| `contactPhone`           | normalized E.164 text                   |                  Yes | 当前 store 内客户的 MVP identity；它始终和 `storeId` 一起组成 scope                     | Code only                   | `tasks.contact_phone`                                        |
| `taskMode`               | `managed / flexible`                    |                  Yes | 这个 Task 是否由 Policy 管理                                                    | Staff/Policy at create      | Expand column present                                        |
| `taskKind`               | Managed kind 或 `custom`                 |                  Yes | 这个 Task 长期要解决哪类目标                                                        | Staff or evidence-gated AI  | Expand column present；`type_category` 仍作 compatibility input |
| `displayType`            | Policy-defined text nullable            |             Lead yes | 员工当前在队列里看到的分类。可以 materialize，但必须能根据有效 evidence/Activity 重算               | Code/Policy only；员工通过记录事实纠正 | Expand column present；`type_category` 仍作 compatibility input |
| `title`                  | text                                    |                  Yes | 员工可读标题                                                                   | Staff/Policy; AI 可建议        | Expand column present                                        |
| `objective`              | text / policy label                     |                  Yes | 这件事最终要达成什么                                                               | Policy; flexible 由 staff    | Target projection                                            |
| `taskReason`             | text                                    |                  Yes | 为什么创建这个 Task，以及它现在为什么重要                                                  | AI/staff, with evidence     | `task_reason`                                                |
| `status`                 | `open / closed`                         |                  Yes | Task 是否仍需处理，或者已经结束                                                       | Domain command only         | live                                                         |
| `priority`               | `high / medium / low`                   |                  Yes | 当前队列优先级                                                                  | Policy/staff; AI 可建议        | live                                                         |
| `assignedToStaffId`      | UUID nullable                           |                   No | 当前负责人                                                                    | Authorized staff/system     | live                                                         |
| `sourceType`             | `lead / contact_analysis / manual`，以后可扩 |                  Yes | 创建来源                                                                     | Code                        | live                                                         |
| `sourceEntityType`       | text nullable                           |                   No | 主要 evidence 的类型                                                          | Code                        | live                                                         |
| `sourceEntityId`         | text nullable                           |                   No | 主要 evidence 的 identity                                                   | Code                        | live                                                         |
| `sourceRunId`            | text nullable                           |                   No | AI/job/run provenance                                                    | Code                        | live                                                         |
| `creationEvidenceRefs`   | typed refs                              |          Managed yes | 支撑创建的完整同店 evidence。source fields 只是 legacy shortcut                      | Server resolver/Policy      | Expand column present                                        |
| `creationIdempotencyKey` | text                                    |                  Yes | 防止同一个 create request 重试时重复创建                                             | Code                        | live                                                         |
| `dedupeKey`              | text nullable                           | Managed normally yes | 当前 open business objective 的 identity。它与 request replay idempotency 是两件事 | Server/Policy only          | Expand column present                                        |
| `creationPolicyVersion`  | text                                    |          Managed yes | 创建时使用的 effective Policy 版本                                               | Code                        | Expand column present                                        |
| `firstResponseDueAt`     | timestamptz nullable                    |                   No | 首次响应 SLA                                                                 | Policy/staff override       | Expand column present                                        |
| `firstResponseAt`        | timestamptz nullable                    |                   No | Task 整个 lifetime 内第一次 qualifying staff Activity 的时间；reopen 不重置           | Code-derived, materialized  | Expand column present + Timeline projection                  |
| `deadlineAt`             | timestamptz nullable                    |                   No | 整件 Task 的最终期限                                                            | Policy/staff command        | live                                                         |
| `currentOpenedAt`        | timestamptz                             |                  Yes | 当前这一段 open period 从什么时候开始                                                | Domain transition           | Expand column present                                        |
| `aggregateVersion`       | integer                                 |                  Yes | optimistic concurrency                                                   | Code                        | Expand column present                                        |
| `createdAt / updatedAt`  | timestamptz                             |                  Yes | 系统时间                                                                     | Code                        | live                                                         |

Target 不把 `taskKind` 做成数据库 global enum。它应该是 text key，再由 versioned Task Policy Catalog 负责 application/domain validation。否则，每增加一种业务类型都要做一次 DB enum migration。

`taskKind` 和 `displayType` 回答的是两个不同问题，不能混用：

- `taskKind` 回答“这件事长期要解决什么”。它在同一个 Task 存续期间保持稳定，例如 `lead_conversion`。
- `displayType` 回答“员工现在应该把它当成哪类工作”。它可以随着有效 evidence 变化，例如 `lead_outreach → lead_follow_up → booked_not_converted`。
- `displayType` 不是 `status`，不是 Funnel stage，也不是另一个 Task。客户端不能直接提交任意值。员工在 UI 中记录 Call、SMS 或线下事实后，由 Policy Guard 重算。
- 为了支持队列筛选，Target 可以把当前值 materialize 到 `tasks.display_type`。Timeline/evidence 仍然是审计和重算的来源。后台 reconciliation 必须能发现 drift。

`creationIdempotencyKey` 和 `dedupeKey` 也解决不同问题：

- `creationIdempotencyKey` 防止同一个 create request retry 时产生两条记录。
- `dedupeKey` 防止不同 event 为同一个 open business objective 重复创建 Task。

Managed Task 的 `dedupeKey` 默认由 server 生成为 `contact:{normalizedPhone}:kind:{taskKind}`。如果 Task 还需要绑定一个具体业务对象，再加入 Policy-defined `subjectKey`。`lead_conversion` 的 `displayType` 发生变化时，key 不变。

Flexible Task 的 `dedupeKey=null`，所以同一个 Contact 可以同时拥有多个 open Flexible Tasks。不过，同一个 create request 仍然受 `creationIdempotencyKey` 保护。AI 和客户端都不能自行提交 `dedupeKey`。

MVP 的物理 identity 继续使用 `store_id + contact_phone`。现有 ingestion normalization 会把 phone 写成 canonical E.164。

如果号码无法规范化，就不能拿 raw display string 做 dedupe。系统应生成 `requestHumanReview`/Suggestion，让员工纠正。MVP 不另外建设 identity-review subsystem。

未来迁到稳定的 `contact_id` 是一项独立 migration。在迁移真正完成前，不能在本设计中用抽象 `contactRef` 假装它已经存在。

### 6.2 Next Action fields

Next Action 只表示“已经采用的当前计划”。它不是所有可能建议的集合。

| Field                  | Type / values                                                                                                     |               Required | Meaning                                     | Authority           |
| ---------------------- | ----------------------------------------------------------------------------------------------------------------- | ---------------------: | ------------------------------------------- | ------------------- |
| `nextActionText`       | text nullable                                                                                                     | Open Task normally yes | 下一步具体要做什么                                   | Staff；Policy 允许时 AI |
| `nextActionAt`         | timestamptz nullable                                                                                              |                     No | 什么时候做。按 V2 语义，NULL 表示 ready now/unscheduled | Staff；Policy 允许时 AI |
| `nextActionReasonCode` | `customer_requested / activity_follow_up / staff_planned / new_inbound_evidence / reprioritized / manual / other` |           When changed | 为什么改变                                       | Command             |
| `nextActionSource`     | `staff / ai / policy / system`                                                                                    |               When set | 计划来源                                        | Code                |
| `nextActionSetById`    | text nullable                                                                                                     |                     No | 具体 actor                                    | Code                |
| `nextActionSetAt`      | timestamptz                                                                                                       |               When set | 采用时间                                        | Code                |
| `sourceSuggestionId`   | UUID nullable                                                                                                     |                     No | 如果这个计划来自 AI Suggestion，就指回原 proposal        | Code                |

MVP 不建 `actions` table。Task 只保存当前 snapshot。每次 replace、cancel 或 reschedule Next Action 时，都追加一条 `task.next_action_changed` Timeline event。

**Current implementation：** Target Expand schema 已有 `nextActionText / nextActionSource / nextActionSetById / nextActionSetAt / sourceSuggestionId`，Neon adapter 与 Staff commands 会原子更新 snapshot，并追加 `task.next_action_changed`。Task V2 rows 可能仍只有 `nextActionAt` 或 active `task_suggestions.content.action`；migration 不能把 Suggestion 猜成已经采用的 Next Action，未知时保持 null。

### 6.3 AI Suggestion fields

AI Suggestion 是先展示给员工看的建议，不是需要先批准才能看到的内容。员工可以直接参考，也可以什么都不做。

只有当员工把建议变成 Task 的实际 mutation 时，系统才需要记录它是否被采用。例如员工执行建议中的 `changeNextAction` 或 `closeTask`。thumbs up/down 只评价建议质量，不代表建议已经被采用或驳回。

| Field                                                   | Type / values                                                   | Meaning                                                             |
| ------------------------------------------------------- | --------------------------------------------------------------- | ------------------------------------------------------------------- |
| `suggestionId`                                          | UUID                                                            | proposal identity                                                   |
| `taskId / storeId`                                      | UUID                                                            | 归属与隔离                                                               |
| `sourceType`                                            | `ai / staff`                                                    | Current `ai / human` 的 Target 命名；说明建议来源                             |
| `suggestedActionText`                                   | text                                                            | 建议做什么                                                               |
| `reason`                                                | text                                                            | 为什么建议                                                               |
| `priority`                                              | high/medium/low                                                 | 建议优先级                                                               |
| `evidenceRefs`                                          | typed refs                                                      | 依据哪些 Call/SMS/Lead/Activity                                         |
| `status`                                                | `active / accepted / edited / dismissed / superseded / expired` | proposal 的 lifecycle。`active` 表示当前可展示；`expired` 在 `expiresAt` 到期后产生 |
| `proposedCommand + payload`                             | typed command nullable                                          | mutation 类提案必填（§11.2）。员工采纳时，以员工身份原样执行，不需要重新手工填写                     |
| `generatedAgainstTaskVersion`                           | integer nullable                                                | mutation 类提案必填；生成时的 `aggregateVersion`，用于展示层 stale 标记与执行层 Guard 拒绝  |
| `expiresAt`                                             | timestamptz nullable                                            | 建议的有效期。过期后自动退出 UI，不再渲染                                              |
| `promptVersion / model / runId / decisionPolicyVersion` | text                                                            | 完整 provenance                                                       |
| `createdAt / decidedAt / decidedBy`                     | timestamps/actor                                                | 记录用户决定并用于审计                                                         |

Current `task_suggestions` 会保留历史，不会破坏旧 content。新建议产生时，旧 row 会 UPDATE 为 `superseded`，同时插入新的 `active` row。表中已经有 promptVersion 和 runId。

Target 的 `accepted / edited / dismissed` 是供 audit 和 projection 使用的 proposal state。系统不能靠猜测产生这些状态，而要把员工的明确操作映射到 `accept/edit/dismissSuggestion` command，并写入 `task.suggestion_decided` event。UI 不平铺通用的 Accept/Edit/Dismiss 三个按钮（这是 §11.2 的硬规则，不是可选项）；员工执行具体建议时，就等于 accept。这里的 `dismissed` 表示员工主动把建议移出当前工作区。员工只是看过后暂时不采用，可以直接忽略，不必先点 reject。

Current `ai_feedback` 不能直接冒充 adoption state，因为两者回答的问题不同：

- `helpful / not_relevant` 是质量评价，也就是 thumbs up/down。它不表示 adopted 或 dismissed。
- `followed` 是员工自己报告“已经照做”。只有同时存在关联 mutation command（`changeNextAction / recordActivity / recordActivityAndPlan / recordBusinessProgress / closeTask`）和 `sourceSuggestionId`，系统才能把它投影为 accepted/adopted。
- `corrected` 保存员工给出的替代内容。只有这段内容通过 command 成为 Next Action 后，系统才能把它投影为 edited + accepted。

Target logical status 根据 suggestion row 和 decision event 一起计算。Current feedback 必须原样保留，migration 时不能把旧值一对一改名成新的 adoption status。

### 6.4 Activity fields 与 live values

Activity 记录已经发生过的事实，因此只能 append，不能覆盖旧记录。第一阶段继续把它写入 `contact_timeline` 的 `task.activity_recorded`，不同时维护第二套 Activity source of truth。

| Field                        | Type / values                                                                                                                                                    |              Required | Meaning                                                                                                                                 |
| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------: | --------------------------------------------------------------------------------------------------------------------------------------- |
| `activityId`                 | stable UUID                                                                                                                                                      |                   Yes | Activity payload 的 identity。它不同于 `contact_timeline.id` 的 bigserial event-row ID                                                         |
| `taskId / storeId`           | UUID                                                                                                                                                             |                   Yes | Task 归属和隔离                                                                                                                              |
| `occurredAt / recordedAt`    | timestamptz                                                                                                                                                      |                   Yes | 这件事实际发生的时间 / 系统写入的时间                                                                                                                    |
| `channel`                    | `phone / sms / email / in_person / other`                                                                                                                        |                   Yes | 通道                                                                                                                                      |
| `direction`                  | `inbound / outbound` nullable                                                                                                                                    |               Depends | 方向                                                                                                                                      |
| `action`                     | `call_completed / message_sent / conversation_logged / work_completed`                                                                                           |                   Yes | 做了什么                                                                                                                                    |
| `outcome`                    | `connected / no_answer / voicemail_left / sent / delivered / failed / wrong_number / reply_received / bounced / not_available / completed / no_response / other` |                   Yes | 这一次动作怎么样                                                                                                                                |
| `conversationDisposition`    | `follow_up_agreed / considering / not_interested / resolved / appointment_booked / other` nullable                                                               |                    No | 这次沟通明确表达了什么                                                                                                                             |
| `actorType / actorSubjectId` | `staff / ai / system / import` + stable identity                                                                                                                 |                   Yes | Target domain 中是谁执行或记录。`contact_analysis/call_analysis/lead_webhook` 属于 source/run provenance，persist 时再映射到 Current Timeline vocabulary |
| `creditedStaffId`            | UUID nullable                                                                                                                                                    |                    No | 业务 credit 归给谁。它与 recorder 是两个不同角色                                                                                                       |
| `verificationType`           | canonical evidence vocabulary                                                                                                                                    |                   Yes | 事实如何确认                                                                                                                                  |
| `activityEvidenceRefs`       | typed refs                                                                                                                                                       |        When available | 原通话、短信、人工说明                                                                                                                             |
| `sourceInteractionKey`       | text nullable                                                                                                                                                    | For observed Call/SMS | 用于识别并去重同一个真实 interaction 的稳定 key                                                                                                        |
| `sourceSuggestionId`         | UUID nullable                                                                                                                                                    |                    No | 如果动作直接从 suggestion 发起，就携带这个引用。执行动作即表示采纳，是 adopted 投影（§6.3）的依据之一                                                                         |
| `note`                       | text nullable                                                                                                                                                    |                    No | 必要上下文                                                                                                                                   |
| `idempotencyKey`             | text                                                                                                                                                             |       Yes for command | 重放不重复记录                                                                                                                                 |

Legacy Timeline/API 仍可能出现 `provider_confirmed / staff_asserted / imported_legacy`。Target engine 已使用 §3.3 的 canonical vocabulary，并由 server evidence resolver + Policy Guard 生成 verification；migration 必须按 evidence strength 显式转换，不能机械改名。

Channel、action、outcome 不能任意组合。shared contract/Policy Guard 强制执行下面的组合表——不在表里的组合一律拒绝（与 Current `TASK_ACTIVITY_CONTRACT` 对齐）：

| Channel     | Action                | Allowed outcomes                                                         |
| ----------- | --------------------- | ------------------------------------------------------------------------ |
| `phone`     | `call_completed`      | `connected / no_answer / voicemail_left / wrong_number / failed / other` |
| `phone`     | `conversation_logged` | `connected / other`                                                      |
| `sms`       | `message_sent`        | `sent / delivered / failed / other`                                      |
| `sms`       | `conversation_logged` | `reply_received / other`                                                 |
| `email`     | `message_sent`        | `sent / delivered / bounced / failed / other`                            |
| `email`     | `conversation_logged` | `reply_received / other`                                                 |
| `in_person` | `conversation_logged` | `connected / other`                                                      |
| `other`     | `work_completed`      | `completed / not_available / failed / no_response / other`               |

例如，`message_sent + no_answer` 不是有效组合，backend 必须拒绝。UI options 和 AI tool schema 都要从同一份 contract 生成。

### 6.5 Business Progress fields

Business Progress 表示 Task 中已经有 evidence 支撑、但还不是终态的业务进展。它写成 append-only Timeline event。current value 取 latest non-invalidated projection，不新增 `Milestone` table。

| Field                       | Type / values                                            |          Required | Meaning                                                                                               |
| --------------------------- | -------------------------------------------------------- | ----------------: | ----------------------------------------------------------------------------------------------------- |
| `progressEventId`           | stable UUID                                              |               Yes | 这次 progress fact 的 identity                                                                           |
| `taskId / storeId`          | UUID                                                     |               Yes | Task 归属和隔离                                                                                            |
| `progressCode`              | Task-specific；Lead MVP 为 `intro_booked / intro_attended` |               Yes | 已发生的中间业务进展                                                                                            |
| `progressOccurredAt`        | timestamptz nullable                                     | Attribution 时 Yes | 进展实际发生的时间。不能拿 recorded/closed time 代替                                                                 |
| `recordedAt`                | timestamptz                                              |               Yes | 系统写入时间                                                                                                |
| `verificationType`          | evidence vocabulary                                      |               Yes | 这项进展是怎样确认的。由 server resolver 决定                                                                       |
| `progressEvidenceRefs`      | typed refs                                               |               Yes | 支撑 progress 的同店 evidence                                                                              |
| `decisionPolicyVersion`     | text                                                     |       Managed yes | 哪版 Policy 接受/纠正本次 progress                                                                            |
| `reportingMetricCode`       | text nullable                                            |                No | 非终态 reporting projection；Lead booking 为 `intro_booking`                                               |
| `supersedesProgressEventId` | UUID nullable                                            |   Correction only | 指明要纠正或作废哪次旧 progress。原 event 不删除                                                                      |
| `sourceSuggestionId`        | UUID nullable                                            |                No | 采纳并执行 suggestion 时携带。server 在同一个 transaction 中写 `task.suggestion_decided`，并把该 suggestion 投影为 accepted |

### 6.6 Outcome fields

Outcome 是 Task 结束时的 terminal snapshot。它不是另一个 Case，也不等于其中某一次 Activity。

| Field                  | Type / values                  |                Required on close | Meaning                                                                                                      |
| ---------------------- | ------------------------------ | -------------------------------: | ------------------------------------------------------------------------------------------------------------ |
| `resultCode`           | Task-specific code             |                              Yes | 业务上最终发生了什么                                                                                                   |
| `closureReasonCode`    | global stop reason             |                              Yes | 为什么现在不再继续处理                                                                                                  |
| `verificationType`     | evidence vocabulary            |                              Yes | 这个结果通过什么来源确认                                                                                                 |
| `outcomeEvidenceRefs`  | typed refs                     | Yes；legacy import 可指向 legacy row | 支撑结果的证据；offline close 使用系统生成的 `staff_assertion`                                                              |
| `outcomeOccurredAt`    | timestamptz nullable           |                Attribution 时 Yes | 结果实际发生的时间。未知时可以为空，但不能用 `closedAt` 代替                                                                         |
| `closedByType`         | `staff / ai / system / import` |                              Yes | 谁执行了关闭操作                                                                                                     |
| `closedBySubjectId`    | text nullable                  |                          Depends | generic actor identity                                                                                       |
| `closedAt`             | timestamptz                    |                              Yes | 系统写入关闭操作的时间                                                                                                  |
| `outcomePolicyVersion` | text                           |                      Managed yes | 关闭/纠正时哪版 effective Policy 接受该组合                                                                              |
| `revenueMappingCode`   | text nullable                  |                               No | reporting projection key                                                                                     |
| `note`                 | text nullable                  |                 Policy dependent | 补充解释或员工掌握的 context                                                                                           |
| `sourceSuggestionId`   | UUID nullable                  |                               No | 采纳 suggestion 并执行 close 时携带。server 在同一个 transaction 中写 `task.suggestion_decided`，并把该 suggestion 投影为 accepted |

所有 Task 共用以下 `closureReasonCode`：

```text
goal_achieved
customer_declined
contact_exhausted
no_longer_needed
compliance_stop
invalid_identity
duplicate
superseded
other
```

`positive / negative / neutral / unknown` 只能根据 `taskKind + resultCode + outcomePolicyVersion` 派生，用于 UI/reporting。用户不能把这些值当成 canonical fact 直接选择。

当 `contributionLevel=attributed` 时，必须有 `outcomeOccurredAt`。

- `communication_explicit` 默认使用 qualifying Call/SMS 的 `occurredAt`。
- `staff_confirmed` 可以由员工填写真实发生时间。
- 如果不知道真实时间，就保持 null。此时最高只能投影为 `discovered/assisted`。

同样，任何 non-terminal progress attribution 都必须有 `progressOccurredAt`，不能拿 Task 最终的 `closedAt` 代替。

### 6.7 Derived projection fields

下面这些字段由 API projection 计算。客户端只能读取，不能写回：

- `workState`：`not_started / in_progress / closed`。
- `queueState`：`needs_attention / scheduled / closed`。
- `overdue`：boolean。
- `firstResponseSlaState`：`not_due / met / missed / not_applicable`。
- `outcomeValence`：`positive / negative / neutral / unknown`。
- `contributionLevel`：`discovered / assisted / attributed`。按 deterministic eligibility 计算。
- `businessProgress`：Policy-defined、evidence-backed 的非终态业务进展；例如 `intro_booked / intro_attended`。它不是 Task Outcome。
- `displayType`：Policy 驱动的当前 UI/queue classification；可以 materialize 方便筛选，但不得由客户端任意写。

`lead_conversion.displayType` 的 MVP 计算规则是 deterministic。系统按下面的 precedence 从上到下判断：

```text
open + 有仍然有效的 completed booking/attendance evidence，且尚未 Converted
  → booked_not_converted

open + 当前 evidence chain 尚无 `initialContactCompletedEvidence`
  → lead_outreach

open + 已有 `initialContactCompletedEvidence`
  → lead_follow_up
```

`initialContactCompletedEvidence` 和 first-response SLA 的 outbound-only qualifying rule 不是同一个概念。

一次 outbound connected/no-answer/voicemail/sent 可以完成首次 outreach。如果 Task 本来就是由 meaningful inbound Call/SMS 创建，说明有效沟通已经发生，应直接进入 `lead_follow_up`，不能再要求员工做一次“首次联系”。

Completed booking/attendance evidence 可以来自 Call/SMS 中的明确表述，也可以来自员工记录的线下 `staff_assertion`。

“Book me Tuesday at 6”只是一条 booking request，还不足以进入 `booked_not_converted`。如果 booking 后来被取消、被员工纠正或 evidence 失效，而且没有更晚的 valid attendance evidence，open Task 就回到 `lead_follow_up`。

`booked_not_converted` 只表示“已经有有效的 Intro progress，但还没有 conversion evidence”。它不是 negative Outcome。`converted` 可以直接从三种 `displayType` 中的任意一种发生，不要求先有 Intro。

为了兼容现有用户语言，暂时保留 `booked_not_converted` 这个 code。但不能根据这个 code 反推客户究竟只是 booked，还是已经 attended。详情页和 Funnel 必须读取独立的 `businessProgress` 和 provenance。

如果未来用户研究证明这个 label 容易误解，只修改 Policy/UI label，不修改 Task identity。

Closed Task 不再参与 current queue 的 `displayType` 重算。历史 projection 保留 `displayTypeAtClose`，或者最后一个有效的 materialized value，用来解释关闭时的工作上下文。

Task reopen 后，再根据仍然有效的 current evidence 重算。不能因为 Outcome 被清除，就机械地回到 `lead_outreach`。

这样，Workbench 仍然可以显示并筛选 “Lead Outreach”“Lead Follow-up”“Intro Booked · Conversion Pending”。但在 backend 中，这些工作始终属于同一个长期 `lead_conversion` Task。

关闭/reopen、Outcome 和 Funnel 都不能根据前端 label 反向推断。

***

## 7. Managed Task 与 Flexible Task

| Capability      | Managed Task                                  | Flexible Task                 |
| --------------- | --------------------------------------------- | ----------------------------- |
| 目的              | 系统可以自动发现、跟踪和统计的核心 revenue/service opportunity | 员工临时创建或某家店特有的工作               |
| `taskMode`      | `managed`                                     | `flexible`                    |
| `taskKind`      | Policy Catalog 中的 kind                        | `custom`                      |
| 谁可创建            | AI、system 或员工；无论谁创建，都必须有 evidence/provenance  | **员工创建**；AI 可以建议创建            |
| Next Action     | AI 可以按 Policy 设置或建议；员工始终可以修改                  | 员工设置；AI 可以建议                  |
| Activity        | RingCentral 可以自动关联；员工可以补充线下 Activity          | 员工记录；也可以关联相关沟通                |
| 谁可关闭            | 通过 evidence gate 的 AI，或员工                     | 员工；AI 默认只能 propose close      |
| Revenue mapping | Policy 定义、带 verification strength             | 默认不进入自动 revenue attribution   |
| Outcome options | 根据 `taskKind` 动态生成                            | 通用 completed/cancelled + note |

员工也可以创建 Managed Task，因为员工掌握的线下 context 是一等输入。引入 AI 自动化时，不能因此删掉人工入口。

Flexible Task 至少使用下面这组 terminal vocabulary：

- `resultCode = completed / cancelled / outcome_unknown`。
- 继续使用 common `closureReasonCode`，例如 `goal_achieved / no_longer_needed / duplicate / other`。
- `other / outcome_unknown` 需要 note。
- 默认 `revenueMappingCode = null`。

第一版 Managed Task catalog：

```text
lead_conversion
cancellation_request
payment_recovery
renewal
upgrade
win_back
referral
```

***

## 8. Task Policy Catalog

### 8.1 Policy 解决什么

先讲人话：每一种 Managed Task 都需要一本统一的“规则说明书”。这本说明书就是 `Task Policy`。它要同时告诉 AI、backend、UI 和 reporting：什么情况下能建 Task、接下来做什么、什么证据足以更新或关闭，以及最后怎样统计。

当前的问题不只是少了一张 `typeCategory → closeResult` 对照表。真正的问题是，同一条业务规则分散在 schema、prompt、UI、API 和 reporting 中：

```text
taskKind
  → 什么 evidence 可以创建
  → 如何 dedupe
  → 初始 priority / SLA / Next Action
  → 哪些 Activity 有意义
  → 哪些 resultCode 合法
  → 哪些 closureReason 合法
  → 每个 Outcome 需要什么 evidence
  → AI 可以自动做什么
  → UI 展示哪些 options
  → Funnel / Revenue 如何投影
```

因此，这整条链必须由一个 versioned、code-owned `Task Policy Catalog` 统一定义。其他层不再各写一份规则，而是由 Catalog 生成或驱动：

- AI prompt vocabulary 和 tool schema。
- Backend validation 与 Policy Guard。
- UI labels、help、create/close options。
- Reporting valence、attribution wording。
- Contract tests 和 golden eval fixtures。

### 8.2 Machine-readable policy shape

下面的 TypeScript type 是这本“规则说明书”的机器可读版本。读它时不必先理解所有 type；按业务问题看即可：

- `creation`：什么证据允许创建 Task。
- `dedupe`：怎样避免重复建同一件事。
- `initialPlan`：Task 刚建立时，员工先做什么、多久内要响应。
- `display` 和 `progressSignals`：事实变化后，员工看到什么分类和业务进展。
- `outcomes`：哪些最终结果合法，各自需要什么证据。
- `aiAuthority`：AI 能做到哪一步；`dncBehavior`：客户 DNC 时这个 Task 怎么处置（close / block\_outbound / keep\_open\_for\_review，对员工和 AI 都生效）。
- `reporting` 和 `uiMetadata`：报表和界面怎样解释同一套规则。

```ts
type ManagedTaskPolicy = {
  taskKind: ManagedTaskKind;
  version: string;
  label: string;
  objective: string;

  creation: {
    allowedEvidenceTypes: EvidenceType[];
    requiresExplicitSignal: boolean;
    rules: CreationRule[];
  };

  dedupe: {
    keyParts: Array<'contactPhone' | 'taskKind' | 'subjectKey'>; // storeId is always DB scope
    reuseOpenTask: boolean;
    sourceRefIdempotency: boolean;
  };

  initialPlan: {
    nextActionTemplate: string;
    firstResponsePolicy?: {
      duration: DurationPolicy;
      clock: 'elapsed' | 'store_business_hours';
      qualifyingActivities: Array<{
        channel: ActivityChannel;
        direction?: 'outbound';
        actions: ActivityAction[];
        allowedOutcomes: ActivityOutcome[];
      }>;
    };
    defaultPriority: 'high' | 'medium' | 'low';
  };

  display: {
    defaultType: string;
    allowedTypes: string[];
    initialContactCompletedEvidence?: EvidenceRule[];
    deriveFromCurrentEvidence: DisplayTypeRule[];
  };

  progressSignals?: Record<ProgressCode, {
    allowedVerificationTypes: VerificationType[];
    requiresCompletedFact: boolean;
    invalidationRules: ProgressInvalidationRule[];
    displayType?: string;
    funnelStage?: string;
    reportingMetricCode?: string;
  }>;

  outcomes: Record<ResultCode, {
    allowedClosureReasons: ClosureReasonCode[];
    allowedVerificationTypes: VerificationType[];
    aiAutoCloseEvidence: VerificationType[];
    requiresCompletedFact: boolean;
    revenueMappingCode: string | null;
    reportingLabel: string;
  }>;

  contactExhaustionPolicy?: {
    attemptCount: number;
    elapsedWindow: DurationPolicy;
    qualifyingActivities: ActivityOutcome[];
  };

  aiAuthority: {
    canCreate: boolean;
    canAutoClose: boolean;
    canCloseFromInferenceOnly: false;
  };

  dncBehavior: 'close' | 'block_outbound' | 'keep_open_for_review';

  reporting: {
    attributionWindow?: DurationPolicy;
    qualifyingStaffActivities: ActivityRule[];
    revenueCreditKeyStrategy?: RevenueCreditKeyStrategy;
    estimateDefinitionVersion?: string;
  };

  uiMetadata: {
    workstream: 'lead_funnel' | 'member_care' | 'revenue_growth';
    icon: string;
    helpText: string;
    displayLabels: Record<string, string>;
  };
};
```

`Policy registry` 是这套规则的 source of truth。版本和 label 可以同步到数据库，方便查询；但可变的数据库配置不能在没有 review/test 的情况下改变历史语义。

落到代码里，一个 `taskKind` 对应一个声明式、版本化的 policy 文件，所有文件组成 TypeScript registry。增加第 N 种 Managed Task 时，只增加一个 policy 文件；不修改 prompt 主体、不动 schema、不动 Orchestrator。§8.6 会说明为什么。

Policy 会升级，但历史必须可复现——给定记录下来的版本号，要能重新加载那一版规则、重演当时的判定。因此每一次业务决定都要记住自己使用的是哪一版规则：

- `creationPolicyVersion` 固定记录 Task 创建时的 effective Policy。
- `outcomePolicyVersion` 记录 Close/Correction 当时实际执行 validation 的 effective Policy；长时间 open 的 Task 可以由 v1 创建、按 v2 关闭。
- 每个 AI assessment、Suggestion 和 Timeline decision event 记录自己使用的 `decisionPolicyVersion`。
- store override 与 base Policy 合并后生成不可变 effective version/fingerprint；已经被历史记录引用的版本必须继续可加载。
- Policy 升级不静默重算历史 Outcome 或 attribution；若要重算，必须发布新的 projection/definition version。

`firstResponseAt` 回答一个简单问题：员工第一次真正响应这个 Task 是什么时候。它由最早一条 qualifying Activity 确定性计算，并 materialize 到 Task-lifetime 字段，方便队列和报表读取；Timeline 仍是审计来源。

默认可以算作首次响应的员工动作包括：outbound connected/no-answer/voicemail Call，以及成功 sent/delivered 的 outbound SMS。以下内容不算 staff first response：failed Call、inbound message、Note、AI suggestion。

补录过去发生的 offline Activity 时，只有同时满足 authenticated staff、`occurredAt >= createdAt` 和 typed assertion，才可以计入首次响应。具体允许哪些 channel/outcome，以及 SLA 按 elapsed time 还是 business hours 计算，都由 Task Policy 定义。

### 8.3 所有 Managed Task 的共同硬规则

不管 `taskKind` 是什么，下面这些底线都一样：不能重复建单、不能拿推断当最终事实、不能用一次无人接就结束 Task，也不能让 AI 覆盖更新的人工决定。

- Managed Task 默认由 server 生成 `dedupeKey=contactPhone + taskKind`；Policy 需要时加入 `subjectKey`。Flexible Task 不做 business-objective dedupe，依赖 creation idempotency 防 retry。
- 相同 source evidence 必须通过 idempotency 避免重复创建。
- 新 evidence 优先更新现有 open Task，不重复开同类 Task。
- AI 创建的 Task 必须带 `reason + evidenceRefs + creationPolicyVersion`。
- `ai_inferred` 可以发现低风险机会，但不能单独证明 terminal Outcome。
- 一次 `no_answer` 不能成为 `contact_exhausted`；必须满足该 Policy 的 exhaustion threshold 或员工确认。
- AI auto-close 必须满足 Task-specific allowlist、同店 evidence、无更新人工冲突、可纠正/reopen，并写完整 provenance。
- Staff confirmation 优先于后续 AI inference；AI 不得静默覆盖人工 Outcome、note、assignee 或 credit。

### 8.4 第一版 Managed Task policy matrix

下表把第一批 Managed Task 放在一起比较。阅读时要分清三种确定程度：

- Task 的 objective、creation boundary 和 Outcome 语义是 **Selected semantic boundaries**；实现与 rollout 必须保持这些语义并用真实用户行为验证可用性。
- 时间与 exhaustion 是 **Proposed policy defaults**。
- AI auto-close 是否在某家店启用，属于 **Open rollout authority**。

在 shadow evaluation 完成前，表里的示例时间和 eligibility 只是待验证的默认建议，不代表 production feature flag 已经批准。

| `taskKind`             | Objective 与 creation evidence                                                             | Initial Next Action / timing                                                    | Valid `resultCode`                                                             | AI authority                                                                                                                                                | Revenue wording                                                                                                 |
| ---------------------- | ----------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
| `lead_conversion`      | 推进一个 Lead 到明确销售结果。Email Lead、明确 Call/SMS prospect signal 或员工线下确认可以创建；Intro 是一种可能路径，不是强制目标 | 新 Email Lead：立即联系，first-response SLA 建议 5m；后续按客户需求预约 Intro、回答问题或直接讨论 Membership | `converted / not_converted / outcome_unknown / not_applicable`                 | 可 create；completed booking 只更新 progress/displayType，不关 Task；明确 membership 已完成或 staff confirmation 才可关 `converted`；明确最终拒绝可关 `not_converted`；一次 no-answer 不可关 | 分开显示 “Intro booking progress attributed” 与 terminal “Membership conversion attributed”；都不是 CRM-verified revenue |
| `cancellation_request` | 处理明确取消请求并争取 retention。普通抱怨、负面情绪或 AI churn guess 不足以自动创建                                   | 高优先级 callback，建议 same business day；不规定固定 save SOP                               | `cancel_saved / cancellation_completed / outcome_unknown / not_applicable`     | 明确取消信号可 create；客户明确决定保留可关 `cancel_saved`；“我要取消”只是 creation evidence；只有明确已处理完成或 staff confirmation 才关 `cancellation_completed`                               | `cancel_saved` = estimated revenue protected；不得称 membership-system verified                                     |
| `payment_recovery`     | 处理明确 declined/past-due/payment update 问题。泛化价格问题不够                                         | 安全联系客户并安排更新；建议 same business day；不得存完整 card data                                | `payment_restored / payment_not_restored / outcome_unknown / not_applicable`   | 只在 payment issue 明确时 create；“我会更新卡”不够；明确已成功或 staff confirmation 才关 `payment_restored`                                                                       | “Payment recovery attributed”；金额未知时只报 count/opportunity                                                         |
| `renewal`              | 对话明确 renewal/到期/续约决定，或员工知道真实 renewal window。不能靠 contact age 猜到期                           | 有可靠 renewal date 时在此前处理；无日期时进入 Needs Attention                                  | `renewed / not_renewed / outcome_unknown / not_applicable`                     | 明确 renewal signal 可 create；“愿意续”可能只是 commitment；明确续约已完成或 staff confirmation 才关 `renewed`                                                                    | “Renewal attributed / estimated retained revenue”                                                               |
| `upgrade`              | 明确询问更高 tier、更多 classes、套餐变更，或员工线下发现机会。普通满意度不够                                             | 回应需求并确认 option；强 inbound request 立即处理，否则建议 1 business day                       | `upgraded / not_upgraded / outcome_unknown / not_applicable`                   | 可按明确需求 create；“听起来不错”不等于生效；明确已处理/生效或 staff confirmation 才关 `upgraded`                                                                                       | “Upgrade attributed / estimated expansion revenue”                                                              |
| `win_back`             | 同时存在 former-member 背景和明确 return signal，或员工确认。仅长时间未联系不够                                    | 确认 return goal 和 next move；建议 signal 后 1 business day                           | `won_back / not_won_back / outcome_unknown / not_applicable`                   | former + return signal 明确时可 create；“可能回来”不够；明确已重新加入或 staff confirmation 才关 `won_back`                                                                       | “Reactivation attributed / estimated reactivated revenue”                                                       |
| `referral`             | 客户提到具体朋友/家人有兴趣、已有可用 contact info，或员工线下确认。仅满意/“以后会介绍”不够                                    | 有信息时确认 consent 并处理；只有意向时请求可分享信息                                                 | `referral_captured / referral_not_captured / outcome_unknown / not_applicable` | 明确 referral signal 可 create；实际可执行 referral 信息和 consent 或 staff confirmation 才关 `referral_captured`                                                          | “Qualified referral captured”；本身不算 revenue                                                                      |

`resultCode` 说明“最后发生了什么”，`closureReasonCode` 说明“为什么现在停止处理”。两者不能随意组合；合法组合只能来自下面的 Outcome Decision Matrix。

### 8.5 Outcome Decision Matrix

这张表回答三个问题：这个 Task 可以用什么结果关闭、为什么可以关闭、需要什么证据。

`allowedVerificationTypes` 是“允许哪些 evidence 类型”的集合，不是把所有 evidence 硬排成一个线性分数。表中的 “AI eligible” 也不等于 AI 一定会自动关闭；它只表示语义上允许进入 auto-close guard。真正执行时，还必须同时满足该 Policy 版本的 feature flag、同店 evidence、无人工冲突和 rollout allowlist。

| `taskKind`             | `resultCode`             | Allowed `closureReasonCode` | Allowed verification                                           |            Completed fact required |                       AI eligible | `revenueMappingCode`    |
| ---------------------- | ------------------------ | --------------------------- | -------------------------------------------------------------- | ---------------------------------: | --------------------------------: | ----------------------- |
| `lead_conversion`      | `converted`              | `goal_achieved`             | `communication_explicit / staff_confirmed / external_verified` | Yes；必须明确 Membership conversion 已发生 |      explicit completed fact only | `membership_conversion` |
| `lead_conversion`      | `not_converted`          | `customer_declined`         | `communication_explicit / staff_confirmed / imported_legacy`   |              No；需明确 final decision |                     explicit only | null                    |
| `cancellation_request` | `cancel_saved`           | `goal_achieved`             | `communication_explicit / staff_confirmed / external_verified` |                  Yes；必须明确撤回取消/决定保留 |                     explicit only | `cancellation_save`     |
| `cancellation_request` | `cancellation_completed` | `goal_achieved`             | `communication_explicit / staff_confirmed / external_verified` |                       Yes；不能只凭取消请求 | explicit completed statement only | null                    |
| `payment_recovery`     | `payment_restored`       | `goal_achieved`             | `communication_explicit / staff_confirmed / external_verified` |                                Yes | explicit completed statement only | `payment_recovery`      |
| `payment_recovery`     | `payment_not_restored`   | `customer_declined`         | `communication_explicit / staff_confirmed / imported_legacy`   |              No；需明确 final decision |                     explicit only | null                    |
| `renewal`              | `renewed`                | `goal_achieved`             | `communication_explicit / staff_confirmed / external_verified` |                                Yes | explicit completed statement only | `renewal_retention`     |
| `renewal`              | `not_renewed`            | `customer_declined`         | `communication_explicit / staff_confirmed / imported_legacy`   |              No；需明确 final decision |                     explicit only | null                    |
| `upgrade`              | `upgraded`               | `goal_achieved`             | `communication_explicit / staff_confirmed / external_verified` |                                Yes | explicit completed statement only | `membership_upgrade`    |
| `upgrade`              | `not_upgraded`           | `customer_declined`         | `communication_explicit / staff_confirmed / imported_legacy`   |              No；需明确 final decision |                     explicit only | null                    |
| `win_back`             | `won_back`               | `goal_achieved`             | `communication_explicit / staff_confirmed / external_verified` |                                Yes | explicit completed statement only | `member_reactivation`   |
| `win_back`             | `not_won_back`           | `customer_declined`         | `communication_explicit / staff_confirmed / imported_legacy`   |              No；需明确 final decision |                     explicit only | null                    |
| `referral`             | `referral_captured`      | `goal_achieved`             | `communication_explicit / staff_confirmed / external_verified` |    Yes；需 usable referral + consent |      explicit completed fact only | `qualified_referral`    |
| `referral`             | `referral_not_captured`  | `customer_declined`         | `communication_explicit / staff_confirmed / imported_legacy`   |              No；需明确 final decision |                     explicit only | null                    |

除了每种 Task 自己的业务结果，所有 Managed Task 还共享两个 administrative results。它们表示“结果未知”或“这件事已经不适用”，不是员工做失败了；reason mapping 仍然必须严格校验：

| `resultCode`      | Allowed `closureReasonCode`                                                      | Allowed verification                                                                           | AI eligibility                                      | Reporting meaning                |
| ----------------- | -------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | --------------------------------------------------- | -------------------------------- |
| `outcome_unknown` | `contact_exhausted / other`                                                      | `system_observed / staff_confirmed / imported_legacy`                                          | 仅 deterministic exhaustion policy；`other` 必须人工 note | 目标结果未知，不是 negative business fact |
| `not_applicable`  | `no_longer_needed / compliance_stop / invalid_identity / duplicate / superseded` | reason-specific `system_observed / communication_explicit / staff_confirmed / imported_legacy` | 仅 deterministic duplicate、DNC 或同等 allowlist         | 不适用/停止，不算员工失败                    |

Flexible Task 没有 Managed Task 的业务结果矩阵，因此只允许下面这组最小结果：

| `resultCode`      | Allowed `closureReasonCode`                         | Required evidence                             |
| ----------------- | --------------------------------------------------- | --------------------------------------------- |
| `completed`       | `goal_achieved`                                     | authenticated staff assertion；note 可选         |
| `cancelled`       | `no_longer_needed / duplicate / superseded / other` | authenticated staff assertion；`other` 必须 note |
| `outcome_unknown` | `other`                                             | authenticated staff assertion + note          |

Lead Intro 是过程中的业务进展，不是 Task 的最终结果，所以不放进 Outcome Decision Matrix：

| `taskKind`        | `progressCode`   | Required evidence                                                                                                  | `displayType`          | Funnel projection                          | `reportingMetricCode` |
| ----------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------ | ---------------------- | ------------------------------------------ | --------------------- |
| `lead_conversion` | `intro_booked`   | completed booking statement with `communication_explicit / staff_confirmed / external_verified`；booking request 不够 | `booked_not_converted` | `Booked` + verification provenance         | `intro_booking`       |
| `lead_conversion` | `intro_attended` | completed attendance statement with `communication_explicit / staff_confirmed / external_verified`；时间已过不够          | `booked_not_converted` | `Showed/Trialed`，仅在对应 Funnel definition 启用 | `intro_attendance`    |

如果 Booking evidence 后来被取消或纠正，而且没有 valid attendance evidence，系统要追加 correction event，再把 `displayType` 重算为 `lead_follow_up`。原来的 evidence 不能删除。

员工在 UI 里选择 “Intro booked/attended” 时，不是在关闭 Task。Backend 必须通过一个 typed command，原子地写入 `staff_assertion`、progress evidence 和新的 Next Action。

Lead display/progress decision table：

| Current evidence / event                                 | Task effect                                   | Resulting `displayType`             |
| -------------------------------------------------------- | --------------------------------------------- | ----------------------------------- |
| 新 Email Lead，尚无有效沟通                                      | create/reuse open `lead_conversion`           | `lead_outreach`                     |
| meaningful inbound Call/SMS 创建 Task                      | create/reuse；initial contact 已发生              | `lead_follow_up`                    |
| qualifying outbound attempt（包含 no-answer/voicemail/sent） | record Activity；Task open                     | `lead_follow_up`                    |
| “Book me Tuesday at 6”                                   | 记录 request + Next Action；不是 completed booking | `lead_follow_up`                    |
| 明确 booking completed evidence                            | `progressCode=intro_booked`；Task open         | `booked_not_converted`              |
| 明确 attendance completed evidence                         | `progressCode=intro_attended`；Task open       | `booked_not_converted`              |
| booking cancelled/corrected/invalidated                  | correct progress；Task open                    | `lead_follow_up`                    |
| 任一状态明确 membership conversion completed                   | terminal `resultCode=converted`               | Task closed；保留 `displayTypeAtClose` |

第一版 `revenueMappingCode` allowed values 只有：

```text
membership_conversion
cancellation_save
payment_recovery
renewal_retention
membership_upgrade
member_reactivation
qualified_referral
null
```

`intro_booking / intro_attendance` 是 non-terminal `reportingMetricCode`，不是 Outcome 的 `revenueMappingCode`。这两类 code 只负责选择 calculation/wording policy，不表示金额已经被权威系统验证，也不把 verification 或 contribution 编码进 code 本身。

`cancellation_completed` 等没有正向 revenue mapping 的结果，仍然可以进入业务 Outcome/Funnel 报表。

### 8.6 为什么新增 Task 类型不应重写数据库、页面和 prompt

Policy Catalog 的价值，是把“新增一种业务规则”和“新增一种系统能力”分开。

例如，以后增加 `freeze_return`，目标是让 freeze 到期客户恢复服务。

如果它仍然满足现有通用能力：

- 一个 Contact/store 下的 Task。
- 一个当前 Next Action。
- 多个 Activity。
- 一个 evidence-backed Outcome。
- 同样的 ownership、SLA、close、audit。

如果它仍然只需要上面这些通用能力，就只需：

1. 在 Policy Catalog 增加 `freeze_return.v1`。
2. 定义 creation evidence、dedupe、outcomes、authority、reporting。
3. 增加 policy/guard/eval tests。

UI 从 Policy 读 label、help、create/close options；AI 从同一 Policy 读 vocabulary；backend 从同一 Policy 验证；reporting 从同一 Policy 做 mapping。无需：

- 新增数据库 enum/column/table。
- 复制一个新的 Task 页面。
- 在 prompt、Zod、UI、API、reporting 五处手写五份 mapping。

只有需求超出这些通用能力时，才需要 architecture change。例如：

- 一个 Task 同时绑定多个 Contacts。
- 多负责人并行且各自有独立 completion。
- 真实 payment/refund transaction。
- 外部系统写操作或 approval。
- 需要多个同时 active、可独立分配的 Actions。

这区分了“新增一种业务类型”和“新增一种系统能力”。

***

## 9. Database design

### 9.1 Current：可直接复用的基础

先讲结论：Target 没有新建第二套 Task source of truth。当前 source 已在现有 Neon/PostgreSQL storage 上完成 nullable Expand schema、typed engine 与 receipts；剩余工作主要是 migration execution、historical reconciliation、read/authority cutover 和最终 Contract cleanup。

Live code 里，几块存储各自负责：

| Store                                                    | Current responsibility                                                                                                               |
| -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `tasks`                                                  | Task current snapshot、ownership、canonical Next Action、SLA/deadline、current progress projection、Outcome 和 aggregate version           |
| `contact_timeline`                                       | append-only Activity、Business Progress、Outcome/correction、status/reopen、assignee、note、Next Action、deadline 与 staff assertion history |
| `task_suggestions`                                       | AI/人工建议；content/history non-destructive，旧 row status 会 supersede                                                                     |
| `ai_task_decision_assessments` / `ai_task_reasoner_runs` | AI 写入前 assessment、bounded run provenance、proposal/no-op/failure audit                                                                |
| provider source-proof / receipts / cursors / outbox      | exact raw source envelope、source binding、idempotent custody、SMS window high-water 和 Call/VoiceMail semantic handoff                  |
| Target Task Engine + Neon adapter                        | typed command、Policy Guard、authority/version/idempotency、atomic snapshot + Timeline mutation                                         |

换句话说，Current Target schema/runtime 已经能保存这些事实：

- `status = open / closed`。
- legacy `typeCategory` compatibility，以及 stable `taskKind + displayType` Target contract。
- `sourceType = lead / contact_analysis / manual`。
- `priority = high / medium / low`。
- canonical `nextActionText + nextActionAt`、`firstResponseDueAt/firstResponseAt`、`deadlineAt`、assignee、source provenance。
- Managed objective `dedupeKey`、creation idempotency、aggregate version 和 existing-row writer ownership。
- canonical Activity、Business Progress、Outcome、correction、reopen 与 Staff command Timeline events。

仍未完成的不是“再建一套 Task 表”，而是下面这些 rollout/data contract：

- Production database migration state、runtime gates 和每个 Store 的 authority 必须外部核验。Checked-in source 已不是一个全局 `legacy/disabled` default：新 Store authority 会 seed 为 `target/target`，TEST Task V3 source gates 为 `true`，而 `pre/prod` 为 `false`。
- open Task V2 Lead Tasks 与 ambiguous historical rows 需要 evidence-aware reconciliation，不能按当前 phone assignment 回填历史 Store。
- Task V2 / Task V3 Workbench、Timeline、Funnel、Speed 和 reporting 需要 dual projection reconciliation。
- human-ratified/live eval 已经开始，但 Gate A 质量闸门未通过；shadow、UAT 和各环境状态见 [Task V3 Rollout Status](/product-design/v3/tasks-feature/task-v3-rollout-status.md)。
- membership/billing amount 与 causal revenue attribution 尚无 authoritative integration。

以下 current source 是本节所有 implementation claim 的 source of truth：

- `callytics-infrastructure/packages/common/src/db/schema/tasks.ts`
- `callytics-infrastructure/packages/common/src/db/schema/task-progress.ts`
- `callytics-infrastructure/packages/common/src/db/schema/contact-timeline.ts`
- `callytics-infrastructure/packages/common/src/db/schema/task-suggestions.ts`
- `callytics-infrastructure/packages/common/src/db/schema/ai-task-decision-assessments.ts`
- `callytics-infrastructure/packages/common/src/db/schema/ai-task-reasoner-runs.ts`
- `callytics-infrastructure/packages/common/src/db/schema/task-objective-authority.ts`
- `callytics-infrastructure/packages/common/src/db/schema/provider-interaction-source-proof.ts`
- `callytics-infrastructure/packages/common/src/domain/task-engine/`
- `callytics-infrastructure/packages/common/src/domain/task-policy/`

### 9.2 Target MVP logical ERD

Target 继续把 `tasks` 当 current snapshot，把 `contact_timeline` 当历史账本。Suggestion、AI assessment、pre-task proposal 和 reasoner run 分别保存自己的 lifecycle；它们不会被塞进 Task status。

```mermaid
erDiagram
  CONTACT ||--o{ TASK : "store-scoped"
  TASK ||--o{ CONTACT_TIMELINE : "events and activities"
  TASK ||--o{ TASK_SUGGESTION : "proposals"
  TASK ||--o{ AI_TASK_ASSESSMENT : "pre-write audit"
  CONTACT ||--o{ TASK_PROPOSAL : "pre-task review"
  CONTACT ||--o{ AI_TASK_REASONER_RUN : "bounded run audit"

  TASK {
    uuid task_id PK
    uuid store_id
    text contact_phone
    text task_mode
    text task_kind
    text display_type
    text dedupe_key
    text status
    jsonb creation_evidence_refs
    text next_action_text
    timestamptz next_action_at
    timestamptz deadline_at
    text result_code
    text closure_reason_code
    text outcome_verification_type
    text creation_policy_version
    text outcome_policy_version
    int aggregate_version
  }
```

所以，第一阶段明确不创建下面这些新 source of truth：

- `cases`。
- `task_steps`。
- `actions`。
- `milestones`。
- 第二份 `task_activities` source of truth。

### 9.3 `tasks` Expand schema

`expand` 的意思是：先在现有 `tasks` 表上增加 Target 字段，同时保留 legacy 字段做兼容；不是一次性替换线上读写。下列 columns 已在当前 source schema/migration 中定义为 nullable Expand fields；表中的 `Present` 不证明 production migration 已执行，也不表示 legacy columns 已可删除。

| Target column               | Action                   | Notes                                                                                                               |
| --------------------------- | ------------------------ | ------------------------------------------------------------------------------------------------------------------- |
| `task_mode`                 | Present, nullable Expand | `managed / flexible`                                                                                                |
| `task_kind`                 | Present, nullable Expand | Policy key；先与 `type_category` dual-write                                                                            |
| `display_type`              | Present, nullable Expand | Lead current UI classification；仅 projector/Policy Guard 写，可从 Timeline/evidence 重算；close 时冻结为历史 `displayTypeAtClose` |
| `dedupe_key`                | Present, nullable Expand | Managed open objective identity；Flexible null；server-derived                                                        |
| `title`                     | Present, nullable Expand | 可先由 Policy label/projector 生成                                                                                       |
| `creation_evidence_refs`    | Present, nullable Expand | 完整 typed creation evidence；legacy source fields 继续作 shortcut                                                        |
| `creation_policy_version`   | Present, nullable Expand | 创建时 pin；legacy rows 为 `legacy-v1`                                                                                   |
| `next_action_text`          | Present, nullable Expand | 与 `next_action_at` 组成 adopted plan                                                                                  |
| `next_action_source`        | Present, nullable Expand | staff/ai/policy/system                                                                                              |
| `next_action_set_by_id`     | Present, nullable Expand | actor provenance                                                                                                    |
| `next_action_set_at`        | Present, nullable Expand | plan adoption time                                                                                                  |
| `source_suggestion_id`      | Present, nullable Expand | 采纳 suggestion 时写                                                                                                    |
| `first_response_due_at`     | Present, nullable Expand | 与 deadline 分开                                                                                                       |
| `first_response_at`         | Present, nullable Expand | 首个 qualifying Activity                                                                                              |
| `current_opened_at`         | Present, nullable Expand | create/reopen period                                                                                                |
| `result_code`               | Present, nullable Expand | Task-specific                                                                                                       |
| `closure_reason_code`       | Present, nullable Expand | common reason                                                                                                       |
| `outcome_verification_type` | Present, nullable Expand | evidence strength                                                                                                   |
| `outcome_evidence_refs`     | Present, nullable Expand | typed refs array                                                                                                    |
| `outcome_occurred_at`       | Present, nullable Expand | 与 closedAt 分开                                                                                                       |
| `closed_by_type`            | Present, nullable Expand | staff/ai/system/import                                                                                              |
| `closed_by_subject_id`      | Present, nullable Expand | generic actor identity；Current `closed_by_staff_id` 仅覆盖 staff                                                       |
| `outcome_policy_version`    | Present, nullable Expand | Close/Correction 当时 validation 版本                                                                                   |
| `revenue_mapping_code`      | Present, nullable Expand | 只保存 terminal Outcome mapping；booking 使用 progress projection                                                         |
| `aggregate_version`         | Present, nullable Expand | optimistic concurrency                                                                                              |

第一阶段把 current Outcome 直接存在 `tasks` snapshot 中，原因很简单：

- 每个 Task 同时只需要一个 current terminal Outcome。
- reopen/correction history 已由 Timeline 保存。
- 避免为尚未出现的查询需求增加 `task_resolutions` join 和双写复杂度。

只有未来真的出现“多个并存 resolution、法律级不可变 resolution entity、复杂 supersession query”，才把 Outcome 抽成独立 table。

`task_suggestions` 的 Expand fields 也已在 current source schema 中定义。纯文本建议仍可只存内容；如果一条 Suggestion 代表可执行的 mutation，就必须额外保存具体 command、生成时的 Task version 和有效期：

| Target column / source                       | Action                                                         | Notes                                                               |
| -------------------------------------------- | -------------------------------------------------------------- | ------------------------------------------------------------------- |
| `evidence_refs`                              | Present, nullable Expand                                       | resolved typed refs；legacy 无来源保持 null/legacy                        |
| `proposed_command` + `payload`               | Present, nullable Expand                                       | mutation 类提案的 typed command（§6.3/§11.2）；Next Action 纯文本建议保持 null    |
| `generated_against_task_version`             | Present, nullable Expand                                       | 生成时的 `aggregateVersion`；执行层 Guard 拒绝 stale 提案，展示层据此计算 stale 标记      |
| `expires_at`                                 | Present, nullable Expand                                       | 到期投影为 `expired`，退出 UI                                               |
| `ai_model`                                   | Present, nullable Expand                                       | 与 prompt/run provenance 同行                                          |
| `decision_policy_version`                    | Present, nullable Expand                                       | 生成 Suggestion 时使用的 effective Policy                                 |
| accept/edit/dismiss                          | 写 `task.suggestion_decided` Timeline/feedback-compatible event | 不覆盖原 suggestion content；记录 actor/time/decision                      |
| logical `status` / `decidedAt` / `decidedBy` | API projection                                                 | suggestion row + latest valid decision event；不是把 `ai_feedback` 生硬改名 |

这里要把员工看到的 Suggestion 和 backend lifecycle 分开理解：

- `stale` 不写成数据库 status。API 用 `generated_against_task_version` 与 Task 当前 `aggregateVersion` 比较后得出；展示层必须隐藏或明确标记（二选一），执行层 Guard 必须拒绝。
- staff 对同一 Task 做 mutation、close 或 reopen 时，server 在同一 transaction 内把相关 active suggestion rows 标记为 `superseded`，并记录 reason。
- reopen 不会复活上一个 open period 的旧建议。

这三条只规定后台怎样防止旧提案被误执行；员工界面怎样表达，见 §11.2 和 §13。

### 9.4 Timeline / Activity

历史继续写进 `contact_timeline`。Task row 只保留“现在是什么”，下面这些 event 保留“之前发生过什么”：

```text
task.created
task.status_changed
task.activity_recorded
task.business_progress_recorded  (Target writer present；非终态，如 intro_booked)
task.business_progress_corrected (Target writer present；supersede/invalidates，不删历史)
task.next_action_changed
task.deadline_changed
task.assignee_changed
task.note_updated
task.outcome_corrected   (Target writer present)
task.suggestion_decided  (Target writer present；不得由 quality feedback 自动冒充)
task.staff_asserted      (Target writer present；线下 confirmation evidence)
task.reopened            (Target writer present；保留旧 Outcome history)
```

每条新写入都必须有 `store_id`。`occurred_at` 表示事实实际发生的时间，`created_at` 表示系统把它记下来的时间；两者不能混用。Activity credit 使用 `staff_id`，也不能把“谁录入”自动当成“谁完成了工作”。

一个 command 可能在同一个 transaction 里产生多条 Timeline events。为了既防止整个请求重复执行，又允许同一请求安全地写多条 event，幂等 identity 分成三层：

```text
commandId             = 整个 request/receipt 的稳定 ID，payload hash 必须一致
eventIdempotencyKey   = commandId + eventType + ordinal
sourceInteractionKey  = 同一 RingCentral Call/SMS 的稳定事实 key
```

`recordActivityAndPlan` 就是一个例子：一次保存同时写 `task.activity_recorded` 与 `task.next_action_changed`。这两行不能竞争同一个唯一 key。Target 在 Timeline typed columns 或 metadata 中保存 `commandId + commandPayloadHash`；每一行非空的 `idempotency_key` 使用自己的 event-level key。

Target runtime 已经使用 canonical `sourceInteractionKey`：provider Activity 把它写进 Timeline metadata，并在 `task_engine_receipts` 保存 source receipt。当前没有单独的 `contact_timeline.source_interaction_key` column；不能因为不是独立 column 就说 contract 不存在，也不能让 reporting 从自然语言重新推断它。Legacy Timeline rows 如果只有 `sourceRefs[] / providerRef`，必须经 server-side adapter 解析；无法证明时保持 unknown。

Target 允许一通 Call/SMS 同时推进多个 Tasks，但底层 RingCentral interaction 仍然只是一条真实事实，不会被复制。Server 从已验证的 source row 确定性生成：

```text
sourceInteractionKey = v1:{storeId}:{evidenceType}:{immutableSourcePrimaryKey}
```

这个 key 只能由 server 写入 Activity typed field/Timeline metadata，AI 和客户端不能自行指定。每个 task-specific Activity event 都引用同一个 `sourceInteractionKey + ResolvedEvidenceRef`。

这样，Call volume 和 staff interaction credit 必须按 `storeId + sourceInteractionKey` 去重——同一次 interaction 推进两个 Tasks 也只计一次；不同 Task 的 progress/outcome contribution 仍可分别计算。

### 9.5 Index 与 integrity

下面这些 index 和 integrity rule 不是为了“把 schema 做复杂”，而是为了守住队列性能、同店隔离、去重、并发和 Outcome/Progress 的边界。Target 至少保留或增加：

- open queue：`store_id, status, next_action_at`。
- overdue：`store_id, status, deadline_at`。
- ownership queue：`store_id, assigned_to_staff_id, status, next_action_at`。
- Task history：`store_id, entity_type, entity_id, occurred_at desc`。
- Activity aggregation：`store_id, entity_id, occurred_at desc` where event type is Task Activity。
- dedupe：partial unique `(store_id, dedupe_key)` where `status='open' AND dedupe_key IS NOT NULL`。phone 是 canonical E.164；Managed key 由 Policy 生成，Flexible null。
- optimistic write：所有 mutation 携带 `expectedVersion`，成功后 increment `aggregateVersion`。
- closed integrity：closed 必须有 Outcome/admin closure；open 的 current Outcome fields 必须为空。
- lead progress integrity：`intro_booked / intro_attended` 不能写入 terminal `result_code`；open `lead_conversion` 的 materialized `display_type` 必须与 latest valid progress/contact evidence 一致。

`store_id` 必须来自 authenticated server context，或由 server 验证。AI 和客户端提交的任意 `store_id` 都不能直接信任。

非终态 Business Progress 写进 Timeline 时，最少需要下面这些 typed fields：

```ts
type TaskBusinessProgressEvent = {
  progressEventId: string;
  taskId: string;
  progressCode: 'intro_booked' | 'intro_attended';
  progressOccurredAt?: string; // attribution 需要真实时间；未知时不得拿 recordedAt 代替
  verificationType: VerificationType;
  evidenceRefs: ResolvedEvidenceRef[];
  decisionPolicyVersion: string;
  reportingMetricCode?: 'intro_booking' | 'intro_attendance';
  supersedesProgressEventId?: string;
};
```

这份 payload 写入 Timeline，不新增 `Milestone` table。当前有效的 progress 由 latest non-invalidated event 投影得出；`display_type` 只是为了查询方便而 materialize 的当前值。

### 9.6 Pre-task proposal、Reasoner run 与 SMS cursor

这里先区分两种对象：

- Task 已经存在时，AI 可以针对这条 Task 产生 Suggestion。
- Task 还不存在、且 AI 没有该类型的 auto-create 权限时，AI 只能提出“要不要建 Task”的 `TaskProposal`；有权限时按 §11.2 直接创建（Guard 照常把关）。

AI 发现潜在 Task 但没有 auto-create authority 时，不能写 `task_suggestions`，因为 `task_suggestions.task_id` 必填，而此时还没有 Task。Current Target schema/runtime 已把 `ai_task_decision_assessments` 的 pre-write audit 扩展成 persisted `TaskProposal` lifecycle。`ReviewItem` 只是 Workbench 对它的展示，不再创建一个同义 domain object：

```text
proposalId / storeId / contactPhone
proposedTaskMode / proposedTaskKind / proposedTitle / proposedNextAction
reason / evidenceRefs / creationPolicyVersion / reasonerRunId
status = pending_review / approved / dismissed / executed / expired
proposalVersion / createdAt / decidedAt / decidedBy / executedTaskId
```

员工批准 proposal 时，系统仍要重新验证最新 DNC、dedupe、evidence、Policy version 和 Task state。旧 assessment 曾经建议 create，不代表可以绕过 current guard。

**Implementation boundary：** 原 infrastructure PR #1900 已关闭，合并后的 #1908 在 `main` 中提供下列 backend contract：

- `GET /v3/tasks/task-proposals`：按 authenticated Store scope 读取 review queue；
- `POST /v3/tasks/task-proposals/:proposalId/approve`：以 server-owned OWNER actor/time 重新运行 Guard，再进入 unified canonical writer；
- `POST /v3/tasks/task-proposals/:proposalId/dismiss`：store-scoped、version-fenced、idempotent dismiss；
- approve 使用一个 PostgreSQL transaction 锁定 pending proposal、执行 Task mutation、验证 exact command receipt，再写 `status=executed + executedTaskId`；
- deterministic command identity 使 retry、并发双击和 commit-unknown recovery 不会产生第二个 Task；
- real PostgreSQL tests 已覆盖 atomic approve/replay、concurrent double-click 和 transaction-time objective authority 变化时两边一起 rollback。

当前 routes 保守要求 `OWNER`；这不是完整 RBAC 的最终结论。#2024 已 merge owner automation settings 与 `/tasks/proposals` approval queue UI，但 source/UI merge 仍不能证明目标环境已部署，也不能证明 owner/employee browser UAT 已通过。

Legacy text-only rows 仍可能只包含已有 Task 的 Next Action 建议；§9.3 的 typed mutation fields 与 Target persistence path 已存在，新的 progress/close mutation proposal 可以按 §11.2 进入同一 Suggestion lifecycle。

Bounded loop 和 2-minute SMS batching 还需要两份可恢复的 logical state：一份记录每次 Reasoner run，一份记录每个 SMS thread 已经处理到哪里。

| Store                       | Minimum fields / constraint                                                                                                                                                                                                                                                                                      |
| --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ai_task_reasoner_runs`     | `runId, storeId, contactPhone, triggerType, triggerSourceKey, reasonerVersion, decisionPolicyVersion, status, startedAt/completedAt, turn/tool/token/cost counters, finalDisposition, proposal/assessment refs, executedCommandId, errorCode`；unique `(storeId, triggerType, triggerSourceKey, reasonerVersion)` |
| `sms_task_reasoner_cursors` | PK `(storeId, threadId)`；`contactPhone, lastProcessedCursor, pendingHighWaterCursor, quietUntil, maxWaitAt, leaseRunId, leaseExpiresAt, version, updatedAt`                                                                                                                                                      |

这两类 store、SMS Neon consumer 和 bounded model adapter 已在 current source 中实现，并有 fake-model/unit 与 PostgreSQL production-seam coverage。Call/VoiceMail 使用 provider source-proof、custody 和 outbox stores，完成 exact source re-read、fresh Guard 与 atomic audit/review/receipt commit。Checked-in `main` 中 TEST 的 SMS 与 provider source gates 为 `true`，`pre/prod` 对应 gates 为 `false`。这只是 source config，不是 deployment/runtime evidence；fake-model tests 也只证明 orchestration、atomicity、failure/replay 和 Guard contract，不证明真实模型 semantic accuracy。当前环境状态见 [Task V3 Rollout Status](/product-design/v3/tasks-feature/task-v3-rollout-status.md)。

SMS worker 先用 CAS/lease claim 固定这次要处理的 high-water。只有 run audit/proposal 安全落库后，cursor 才能向前推进。

运行期间到达的新消息留给下一个窗口。失败时不推进 cursor；retry 继续命中同一个 run key。DNC 等 immediate Fast Lane 也要记录 source idempotency，避免后面的 quiet-window run 把同一事实再执行一次。

***

## 10. API 与 domain commands

这一节只回答两个问题：页面怎样读取已经整理好的 Task 信息，以及用户/AI 怎样请求系统改变 Task。前者用 typed read projection，后者用明确的 domain command。前端和模型都不直接拼数据库状态。

### 10.1 Read projection

前端和 AI 需要的是“这张卡现在该显示什么”，而不是一堆要自己 join 的表和 Timeline JSON。Backend 因此提供下面这些 typed projections：

| Read                 | Returns                                                                                                         |
| -------------------- | --------------------------------------------------------------------------------------------------------------- |
| `listWorkbenchTasks` | 卡片所需 Task、Contact、`displayType/businessProgress`、work/queue/timing、Next Action、latest Activity、suggestion count |
| `getTaskDetail`      | Task snapshot、current progress + provenance、Outcome、Policy label/options、ownership、reason                       |
| `getTaskTimeline`    | typed Activity/progress/status/plan/assignment/outcome events                                                   |
| `getTaskPolicy`      | 当前合法 result、closure reasons、evidence requirements、UI metadata                                                   |
| `getEvidence`        | store-scoped excerpt、source type/id、occurredAt、verification                                                     |
| `listTaskProposals`  | 尚未创建 Task 的 AI proposal/review items；不能混入 `task_suggestions`                                                    |

### 10.2 Commands

Command 是系统认可的业务动作，例如“改下一步”“记录进展”“关闭 Task”。UI 可以使用更自然的按钮文案，但真正改变状态时，必须落到下面某个 command；不能让页面直接改字段。

| Command                                        | Purpose                                                 |      Human |                                             AI |
| ---------------------------------------------- | ------------------------------------------------------- | ---------: | ---------------------------------------------: |
| `createManagedTask`                            | 建立 Policy-controlled Task                               |        Yes |                                   Policy-gated |
| `createFlexibleTask`                           | 员工自由 Task                                               |        Yes |                                No，only suggest |
| `changeNextAction`                             | 采用/修改当前计划                                               |        Yes |                                   Policy-gated |
| `recordActivity`                               | 记录实际工作                                                  |        Yes | 仅 observed communication/authorized automation |
| `recordActivityAndPlan`                        | 一次保存 Activity + Next Action                             |        Yes |                                   Policy-gated |
| `recordBusinessProgress`                       | 记录非终态业务进展，例如 `intro_booked`，并重算 display/Funnel          |        Yes |               explicit evidence + Policy-gated |
| `correctBusinessProgress`                      | 纠正/失效 progress，保留旧 evidence                             |        Yes |                                   propose only |
| `changeDeadline / changePriority / updateNote` | 修改独立字段，不借 Update Task 写平行状态                             |        Yes |              Policy-gated/propose；人工 note 不可覆盖 |
| `editFlexibleTaskTitle`                        | 修改员工自由 Task 的标题                                         |        Yes |                                             No |
| `assign / unassign`                            | 负责人管理                                                   |        Yes |                             默认 propose/guarded |
| `closeTask`                                    | 写 Outcome 并关闭                                           |        Yes |                       evidence-gated allowlist |
| `reopenTask`                                   | 纠正/继续处理                                                 |        Yes |                                          默认不自动 |
| `correctOutcome`                               | 修正结果但保留旧历史                                              |        Yes |                                   propose only |
| `accept/edit/dismissSuggestion`                | 人工处理建议                                                  |        Yes |                                             No |
| `recordSuggestionFeedback`                     | 只记录 helpful/not relevant 等质量评价；不等于 adoption             |        Yes |                                             No |
| `approve/dismissTaskProposal`                  | 处理尚未创建 Task 的 proposal；approve 后重新过 create guard        |        Yes |                                             No |
| `setContactDoNotContact`                       | 写 Contact restriction 并按每种 Policy 执行 close/block/review |        Yes |               explicit evidence Fast Lane only |
| `clearContactDoNotContact`                     | 特权纠正；不自动 reopen 旧 Task                                  | Privileged |                                          Never |

表里有两个容易混淆的动作：

- `accept/edit/dismissSuggestion` 处理“员工是否采用或隐藏这条建议”。
- `recordSuggestionFeedback` 只评价建议是否 helpful/not relevant，不改变 Task，也不等于 adoption。

Public API request 不能自行声明一个可信的 `actor` 或任意 `storeId`。Actor、tenant 和 allowed stores 必须由 authenticated server context 注入。

下面的 JSON 是 server normalization 后交给 Orchestrator 的 **internal mutation command**，不是客户端可以照抄的 public body：

```json
{
  "commandId": "stable-idempotency-key",
  "taskId": "uuid",
  "expectedVersion": 7,
  "actorFromAuthContext": {
    "type": "staff",
    "id": "staff-uuid"
  },
  "evidenceRefs": [
    {
      "type": "ringcentral_message",
      "id": "store-scoped-id",
      "storeId": "server-validated-store-uuid"
    }
  ]
}
```

Create 和 mutation 面对的并发问题不同：Create 要防止同一个目标被重复建立；mutation 要防止在旧 Task version 上覆盖新修改。因此它们的 concurrency/idempotency contract 也不同：

```text
createManagedTask
  public: source/evidence + desired kind/plan
  internal: commandId + dedupeKey + expectedAbsent=true + actor/auth scope

changeNextAction / recordActivity / closeTask / reopenTask
  public: taskId + expectedVersion + command payload
  internal: commandId + taskId + expectedVersion + actor/auth scope
```

每次执行 command，Server 都必须按同一顺序把关：

1. 从 auth context 解析允许的 stores。
2. 验证 Task/Contact/Evidence 均属于同一 `store_id`。
3. 验证 expected version、status transition、Policy version 和 field-level authority。
4. 用 idempotency key 防重。
5. 在一个 transaction 内更新 snapshot、追加 Timeline；AI run 才写 AI audit，只有用户明确评价时才写 suggestion feedback。
6. 返回 typed projection 与 deterministic error code。

校验失败时返回稳定、可判断的 error code，而不是一段需要猜测的自然语言。核心错误示例：

```text
TASK_NOT_FOUND
STORE_SCOPE_MISMATCH
STALE_TASK_VERSION
INVALID_TASK_TRANSITION
OUTCOME_NOT_ALLOWED_FOR_TASK_KIND
INSUFFICIENT_OUTCOME_EVIDENCE
PROGRESS_NOT_ALLOWED_FOR_TASK_KIND
INSUFFICIENT_PROGRESS_EVIDENCE
HUMAN_AUTHORITY_CONFLICT
IDEMPOTENCY_PAYLOAD_MISMATCH
```

### 10.3 高频 atomic command

员工最常见的流程是：刚完成一次联系，马上记录结果，再安排下一步。UI 应让这两件事一次保存；backend 则在一个 transaction 中执行对应的 Activity 和 Next Action changes：

```json
{
  "activity": {
    "channel": "phone",
    "direction": "outbound",
    "action": "call_completed",
    "outcome": "no_answer",
    "occurredAt": "2026-07-15T14:05:00Z"
  },
  "nextAction": {
    "text": "Send SMS, then call tomorrow morning",
    "at": "2026-07-16T14:00:00Z",
    "reasonCode": "activity_follow_up"
  }
}
```

对员工来说，这就是一次“保存结果和下一步”，而不是先 Log Activity、再 Update Task、再 Schedule 的三次后台操作。

员工确认线下 booking 时也是一样：一次提交 progress + plan。Public request 不允许自报 `verificationType`；server 根据 auth context 和同一 transaction 内生成的 `staff_assertion` 解析 verification：

```json
{
  "progress": {
    "code": "intro_booked",
    "occurredAt": "2026-07-15T18:30:00Z",
    "staffConfirmationNote": "Customer booked in person for Tuesday 6 PM"
  },
  "nextAction": {
    "text": "Confirm attendance plan and discuss membership options",
    "at": "2026-07-16T14:00:00Z",
    "reasonCode": "staff_planned"
  }
}
```

***

## 11. AI authority

这一节必须分三层读：

1. **Authority**：AI 有没有资格提出或执行这个动作。
2. **Suggestion lifecycle**：暂时不能执行的 proposal 怎样安全保存、失效和审计。
3. **Employee interaction**：员工看到什么文案和按钮。

三层使用同一套 domain command，但回答的问题不同。Backend 的 version/TTL 不能直接变成员工界面；员工点了什么，也不能绕过 backend Guard。

Current implementation 在 Store 维度下按 objective 拆开两个 rollout decisions：`createLane` 决定新 Task 由 legacy、Target 还是 paused lane 创建；`semanticAutomationMode` 决定 legacy/shadow/target/paused。Existing row 的 writer ownership 固定在 Task row 上，修改 authority 不能偷换旧 row 的 owner。每次 advance、rollback、kill switch 或 resume legacy 都使用 revision/CAS 并写 append-only audit。#1982 已使全部七类 canonical Managed objective 具备 Target/Shadow authority capability；#1993 之后，新 Store 会 seed 为 `target/target`，而真正缺少 authority row 时的兼容解析仍 fail safe 到 `legacy/legacy`。任何已存在 Store 的 persisted authority 与环境启用状态都必须从 rollout status 和 runtime evidence 核验，不能从 control-plane capability 反推。

### 11.1 核心原则

“这个 field AI 能不能改”这个问题太粗。系统每次都要结合谁在操作、做什么、依据什么规则和证据、Task 是否已经变化，以及员工是否刚做过决定：

```text
actor
  + operation
  + task policy
  + evidence type
  + current task version
  + human lock/last edit
  = authority decision
```

### 11.2 Authority matrix

#### 11.2.1 谁能做什么

下表先只回答 authority：AI 可以直接做、只能 propose，还是完全不能做；员工是否可以做；Guard 需要检查什么。

| Object / operation          | AI                                                                  | Staff                  | Guard                                              |
| --------------------------- | ------------------------------------------------------------------- | ---------------------- | -------------------------------------------------- |
| Create Managed Task         | 可按 creation policy 自动创建                                             | 可基于线上/线下 context 创建    | evidence、dedupe、store、idempotency                  |
| Create Flexible Task        | 只能建议                                                                | 可以                     | staff identity                                     |
| Set Next Action             | 可在 allowlist 内设置，其他只建议                                              | 可以修改/清空                | stale version、human-wins                           |
| Record RingCentral Activity | 可把系统观察到的沟通结构化关联                                                     | 可补充/纠正                 | source exists、idempotency                          |
| Record offline Activity     | 不得伪造                                                                | 可以                     | actor/staff attribution                            |
| Record business progress    | 仅 explicit observed evidence + progress Policy allowlist；否则 propose | 可以确认线下 progress        | progress code/evidence/version；不自动 close           |
| Correct business progress   | 只能 propose human review                                             | 可以                     | append-only supersession，重算 display/Funnel         |
| Close Managed Task          | 仅 Policy allowlist + sufficient evidence                            | 可以                     | result/reason/evidence contract                    |
| Close Flexible Task         | 只能 propose                                                          | 可以                     | staff confirmation                                 |
| Correct Outcome / reopen    | 默认 propose human review                                             | 可以                     | append-only audit                                  |
| DNC                         | 明确沟通可走 dedicated Fast Lane 设置；永远不能清除                                | 可以按权限设置；清除需 privileged | 不可被普通 Task mutation删除；Policy 决定 close/block/review |
| Wrong number                | 可记录 observed Activity 并 propose identity review                     | 可以纠正 identity          | 不可跨店自动 merge                                       |
| Assignee / staff credit     | 默认建议，不静默改变                                                          | 可以按权限改                 | same-store FK、audit                                |
| Raw transcript/message      | 永远不能修改                                                              | 也只能通过原系统更正流程           | immutable source evidence                          |

本文只定义 domain capability，不定义门店的完整 RBAC。具体 `front_desk / sales / manager / owner → capability` 由 auth/RBAC spec 管理。

因此，表里的 “Staff” 不表示每一个员工账号都拥有全部权限。至少 DNC removal、Outcome correction、reopen、跨员工 credit 修改和 revenue-value override 应属于 privileged capability。

#### 11.2.2 员工决定优先

Human-wins rules：

- AI run 启动后发生的 staff edit，使旧 AI proposal stale。
- Staff-confirmed progress/Outcome 或人工 lock 不能被后续 AI inference 覆盖。
- AI auto-close 必须可见 evidence，员工可以 correct/reopen。
- correction 产生新 event，不删除旧 AI decision。

这些规则的共同含义是：AI 可以根据新 evidence 继续判断，但不能静默覆盖更新的人工事实或决定。

#### 11.2.3 Guard 怎样处理 AI proposal

Guard 对每个 AI proposal 只做三种裁决，而且每一种都写 audit：

| 裁决                         | 条件                                        | 去向                                                                                  |
| -------------------------- | ----------------------------------------- | ----------------------------------------------------------------------------------- |
| `auto_execute`             | policy、evidence、authority 全部满足            | 直接执行                                                                                |
| `needs_staff_confirmation` | 建议本身合理，但按当前规则 AI 不能直接执行                   | Task 已存在：保存为这条 Task 的 Suggestion；员工点击具体动作后，系统以员工身份执行。Task 不存在：走 §9.6 的 TaskProposal |
| `rejected`                 | 类型 / 词表 / evidence / scope / version 校验失败 | 留 reason + evidence 痕迹，绝不静默丢弃                                                       |

这里不再另造第三种“AI 审批单”。如果 Task 已经存在，而 AI 只能建议、不能直接执行，系统就把建议保存到这条 Task 下。员工看到的是具体动作，不是一张抽象的审批单。如果 Task 还不存在，系统才使用 §9.6 的 TaskProposal。

#### 11.2.4 Backend 怎样保存和淘汰 mutation Suggestion

这一段只讲 backend lifecycle，不规定 UI 长什么样。

要让 `needs_staff_confirmation` 真正可执行，Suggestion 不能只有一句自然语言。Legacy row 可能只有 `suggestedActionText`；Current Target schema/persistence 已为 mutation 类降级提案补齐下面四项，新的 writer 缺一不可：

- `proposedCommand + payload`：Guard 已验证的 typed command（如 `closeTask(resultCode, closureReasonCode, evidenceRefs)`）；员工采纳时原样以员工身份执行，不重新手工填写。
- `generatedAgainstTaskVersion`：记录生成时的 `aggregateVersion`。如果 Task 后来被人工修改，API 把提案投影为 stale，执行层 Guard 必须拒绝。
- `expiresAt`：记录有效期。到期后投影为 `expired` 并退出 active UI。TaskProposal 已有 `expired`（§9.6）；Suggestion 也需要，否则无人处理的建议会无限期停留。
- 失效规则：staff 对同一 Task 做 mutation、close 或 reopen 时，相关 active suggestions 必须在同一 transaction 内进入 `superseded`，并记录 reason。Reopen 不得让上一个 open period 的旧建议复活。

#### 11.2.5 员工看到什么

这一段只讲 interaction semantics。`generatedAgainstTaskVersion`、`expiresAt`、`superseded` 等 backend 字段不直接展示给员工。

UI 按 effect 使用业务语言，不平铺通用 Accept/Edit/Dismiss 三按钮：

- 低风险、可逆的 mutation 显示一个具体的一键动作，例如「设下一步：明早 10 点」或「Confirm booking」。
- Close 等 terminal 变更显示「Review outcome」。它打开预填好的标准关单流程，让员工检查后再确认；不能一键关闭。
- 能直接执行的建议显示具体动作按钮。员工点击动作本身，就代表采纳这条 Suggestion。

UI 不显示通用 Accept 按钮，但 domain 命令层仍保留 `accept/edit/dismissSuggestion` 语义。

“执行本身即采纳”适用于完整 mutation contract，不只适用于 Next Action 和 Activity。所有 mutation command（`changeNextAction / recordActivity / recordActivityAndPlan / recordBusinessProgress / closeTask`，以及 TaskProposal 的 approve）都接受可选 `sourceSuggestionId`。

Server 必须在同一个 transaction 中完成三件事：执行 mutation、追加 `task.suggestion_decided` event、把对应 Suggestion 投影为 accepted。任何 mutation 类型都不允许出现“动作已经执行，但建议仍挂在 active”的状态。

***

## 12. 从 one-shot inference 到 bounded reasoning loop

### 12.1 Current

Current source 中只保留一个 AI semantic Task decider：Router / Reasoner → Guard → writer。#1913 已移除 Contacts Analyzer 的 Task prompt/output/writer authority；Contacts Analyzer 现在做 profile-only one-shot analysis，可以读 Task context 作为 background，但 output contract 明确禁止 `taskDecisions`、`visibleTaskAssessments` 和 `newObjectiveAssessments`，不再生成 AI-driven Task mutation。

旧 Task V2 semantic path 保留在下面仅用于理解 retirement 对比，不是 current decider：

```text
prefetch recent calls + messages + leads + Task snapshots + suggestions
  → build one prompt
  → one structured generateObject/invokeAI call
  → typed proposal
  → Orchestrator writes
```

从业务推理的角度看，历史 pipeline 只有一次 logical inference；retry 或 schema repair 只是重复同一次生成，不会让模型根据缺失信息主动再查一次，因此不等于 reasoning loop。当时的能力边界是：

- 看的是近期 call summary/evidence snippet，不是完整 raw transcript。
- 看 Task snapshot、active suggestion 和有限 closed history。
- 没有读取 `contact_timeline` / Task Activity history 的 tool。
- 不能在推理中按需反复读取 Call、SMS、Lead 或 Task Timeline。

AI decider 已退役不等于 legacy contract 全部删除。`applyTaskAction` 稳定 compatibility facade、Contacts Analyzer cron 中的 deterministic engaged-lead backstop、pure-legacy Task readers/blockers，以及 API/Web 的 legacy Timeline projection 仍在 source 中。它们用于兼容、安全不变量和历史展示，不是第二个 AI Task 大脑。

Target path 已实现本章的 bounded runtime，而不是只存在于设计中：

- default `two_turn` adapter 最多两次 logical turns，read tools 只读且由 server 固定 scope；opt-in native-tool mode 也必须保持明确轮次/读取预算。Tests 可使用 fake model，production adapter 才调用真实 model。
- SMS consumer 完成 `claim → exact Neon source re-read → semantic evidence → model → fresh Task/DNC/authority Guard → atomic audit/review/auto-execution/cursor commit`。
- Call/VoiceMail runtime 通过 source-proof + content-addressed artifact pointer + transactional outbox 绑定 exact provider event；#1908 中的新 Call analysis 会把同一次 `classify` 产生的 Task claims 连同 model/prompt/schema provenance 保存为 `task_semantics`，consumer 重新读取 exact artifact 并校验后直接复用，不再重读 transcript 做独立 semantic inference。
- 没有统一 `task_semantics` 的历史 Call artifact 和其他兼容来源暂时回退到 standalone semantic resolver；这条 fallback 尚有 terminal Outcome 误判风险，不能当成目标主路径。
- Email Lead create/reuse、canonical Call/SMS Activity 和 exact STOP/DNC 走 deterministic Fast Lane，不为了“用 AI”而调用模型。
- Auto-execution 必须同时通过 Task Policy、resolved evidence、automation tier、per-objective authority 和 fresh Guard。`full_auto` 的 terminal/correction/reopen execution seam 已在 source 中，但 capability 存在不表示任何环境已启用或通过质量验收。

Checked-in `main` 中 TEST Task V3 source gates 为 `true`，`pre/prod` 为 `false`。这不是 deployment、UAT 或 PROD release evidence；人工标注/live eval 已开始，但 quality gate 未通过。当前状态和剩余退役面见 [Task V3 Rollout Status](/product-design/v3/tasks-feature/task-v3-rollout-status.md)。

### 12.2 Target：它是一个有终点的 loop

Target 不把 Reasoner 变成一个长期运行的自主 agent。它只把一次 one-shot inference 扩展成一个有明确入口、有限读取次数和明确出口的 bounded loop：先判断现有 context 是否足够；只有缺少会影响决定的关键 evidence 时，才允许补读一次，然后必须给出 final proposal、转人工或明确 no-op。

```mermaid
flowchart TD
  E["Meaningful event"] --> D{"Deterministic router"}
  D -->|"Known mechanical fact"| F["Fast lane: no LLM"]
  D -->|"Needs semantic judgment"| B["Build compact store-scoped context"]
  B --> M["Reasoning turn"]
  M --> Q{"Missing material evidence?"}
  Q -->|"No"| P["Final typed proposal"]
  Q -->|"Yes, within budget"| T["Call approved read-only tool"]
  T --> M
  Q -->|"Unclear/high risk"| H["requestHumanReview"]
  F --> G["Policy + Authority Guard"]
  P --> G
  H --> G
  G --> O["Existing Task Orchestrator"]
  O --> A["Write snapshot + timeline + audit"]
```

图中的每一次 run 都必须先固定以下边界：

- 明确 trigger。
- 固定 store/contact scope。
- 最大 model turns、read tool calls、tokens、timeout 和 cost budget。
- 明确 final proposal，或者明确返回 `requestHumanReview / no_op`。
- 失败时绝不错误关闭 Task。

因此，模型不能自行决定什么时候再次运行。Trigger 必须由 deterministic 规则产生，尤其要防止 Orchestrator 刚写出的 Task event 又反过来唤醒 Reasoner，形成自触发循环：

| Trigger                          | Default lane                                                                          | Dedupe / debounce                             | Self-trigger rule                             |
| -------------------------------- | ------------------------------------------------------------------------------------- | --------------------------------------------- | --------------------------------------------- |
| Email Lead ingested              | Fast Lane 建立确定性 `lead_conversion`；初始 `displayType=lead_outreach`，必要时 Reasoner 生成个性化建议 | 按 lead source ID                              | 同一 lead/task create event 不重跑                 |
| Call analysis finalized          | Reasoner                                                                              | 每个 `storeId + callId + analysisVersion` 一次    | 本 run 产生的 Task events 只更新 projection          |
| SMS thread/burst settled         | Reasoner                                                                              | 按 thread cursor；短 debounce window 由 config 定义 | 新 external message 才推进 cursor；Task writes 不触发 |
| Staff Activity recorded          | Fast Lane 更新 projection；只有明确请求建议或新语义 evidence 才 Reasoner                              | `commandId/sourceInteractionKey`              | AI 建议/plan event 不反向触发 Activity analysis      |
| Staff Outcome corrected/reopened | Audit/eval + 可选 human-requested Reasoner                                              | correction event ID                           | 默认不允许 AI 立即重新关闭                               |
| Scheduled review timer           | Fast Lane eligibility check；material uncertainty 才 Reasoner                           | `taskId + expectedVersion + reviewAt`         | 有更新 Activity/Outcome 后旧 timer no-op           |
| `task.*` event from Orchestrator | Projection only                                                                       | `commandId + event ordinal`                   | **永不直接作为新 Reasoner trigger**                  |

每次 run 都保存 `triggerType + triggerSourceKey + reasonerVersion` 唯一键。同一 payload 被 replay 时返回既有 decision，不再重复推理或 mutation。只有新的外部 Call/SMS/Lead，或人工明确提出的新请求，才可以开始新的 run。

SMS 也不能采用“一条消息调用一次 LLM”的模型。每条 message 仍然立即、deterministic 地持久化并更新 UI；Reasoner 调用则按 `storeId + threadId` 聚合成 conversation window：

1. 保存 `lastProcessedMessageCursor` 与当前 high-water mark。
2. 新消息重置 quiet-window timer；首版默认 **2 分钟**，具体值进 runtime config。
3. quiet window 到期后，一次 run 只读取 cursor 之后的新 messages + compact Task context。
4. 持续聊天首版设 **5 分钟 max-wait**，达到上限时对当前窗口运行，避免无限等待。
5. run 成功/audit 落库后原子推进 cursor；失败按同一 trigger key retry，不重复 mutation。
6. run 期间又到达的新消息留给下一个 window，不重读完整 thread。

普通 message logging、delivery status 和 UI Timeline 都不需要 LLM。只有 settled conversation window 才可能进入 Reasoner；DNC、威胁、安全/合规等少数信号直接走 immediate Fast Lane，不等待 2 分钟。这样，成本随“有意义的 conversation turns”增长，而不是随 SMS row 数量线性增长。

quiet/max-wait 的最终数值属于 rollout config，不能写死在数据库或 prompt。上线后必须根据真实 thread 分布以及延迟/成本指标校准。

每个 run 还要有初始资源预算。下面是 Recommended initial budget，属于 **Target rollout default，不是永久常数**：

- routine case：一次 structured inference。
- 缺关键 context：最多一次 read-tool round、最多两个只读 tool calls。
- 高风险或冲突：不继续烧 token，转 human review。
- timeout/tool failure：保留现有 state，可创建 review suggestion，不执行 terminal mutation。

为了让这个上限由程序保证，Current Target adapter 使用两段式 structured generation，而不是依赖 vendor tool-calling loop：

- 第 1 次调用的 output schema 允许两种顶层结果：final proposals，或 `need_more`（请求只能从已批准的 read tools 中选）。
- 返回 `need_more` 时，runner 确定性取数（不调模型）拼进 context；第 2 次调用的 schema 只允许 final，必须收敛。
- 「最多两次」指 logical reasoning turns，由 schema 结构保证，不是只写在 prompt 里的约定。schema validation repair、provider retry、transport retry 产生的 physical attempts 属于另一个维度（§12.1 已区分 Current 的两种计数）。run metrics 必须分别记录 `logicalTurnCount` 与 `physicalAttemptCount / schemaRepairCount / retryCount`。这样实现不依赖 provider，每一步都可 mock，也能分别记录 cost。

Target Reasoner 只负责 Task 决策，不负责 Contact profile enrichment。这里的“统一”发生在 transcript understanding 层：现有 Call `classify` 同一次输出 Call taxonomy/summary 与 evidence-backed Task claims；Reasoner 消费这些已确认事实，但不重新生成 customer profile，也不重新解释 raw transcript。这样省掉重复 semantic call，同时保持 profile/Call analysis 与 Task mutation authority 的边界。

Event Router 在 Reasoner 之前先完成 deterministic narrowing。一条新 evidence 到达后，纯代码先算出它属于哪个 Contact、可能影响哪些 open Tasks、需要检查哪些候选 Policies。每次 run 只预载候选 Policy，以及相关 open Task 的 Policy，从源头控制 context 大小。

如果通信与 Task 的关系没有歧义，例如员工外呼命中该 Contact 唯一的 open Task，Router 直接把它机械地记录成 Activity，不调用模型。

### 12.3 Read tools

Reasoner 发现关键 evidence 不足时，也不能自由查询系统。它只能调用以下 server-bound、typed、read-only tools：

```text
readContactSnapshot
listRecentCalls
readCallEvidence
listRecentMessages
readMessageEvidence
listLeadRecords
listTasks(status, since, taskKind)
readTaskTimeline
readTaskPolicy
```

每个 read tool 都受同一组安全边界约束：

- `storeId` 和 Contact identity 由 server run context 注入，不让模型任意指定。
- tool 内部强制 store scope、row limit、time window 和 field allowlist。
- 默认先返回 summary/index，只有需要时读 evidence excerpt。
- 一个 run 内以 `tool + ref` 形成 read ledger key，同一请求最多执行一次。
- 已在 Turn 1 装入的 exact source text、bounded Task list 与 Policy catalog 不重复读取；server 返回 typed rejection，并把 ledger 和可用的 E-ref/T-ref 清单交给 final turn。
- 不提供 raw SQL、不提供任意 database write tool、不把 secrets 放入 model context。
- 每个 tool call 记录 tool name、validated args、latency、rows/bytes、result hash 和 cost trace。
- prompt 和 proposal 使用两类短引用：`E{n}` 表示本次 run 已装入的 evidence，`T{n}` 表示本次 run 已装入的 Task；server 保存它们与真实 ID 的映射。模型不抄写 UUID，也不能引用清单外的 E-ref/T-ref；未知引用在 model-output boundary 被拒绝或降级，不会进入 writer。

读取完成后，AI 只能提出下面这些 model-facing commands：

```text
proposeTask
createAllowedTask
proposeNextAction
setAllowedNextAction
recordObservedActivity
proposeBusinessProgress
recordAllowedBusinessProgress
proposeClose
closeAllowedTask
requestHumanReview
noOp
```

这些名称只描述模型可以提出什么。最终 mutation 仍然必须由 Policy Guard + Task Orchestrator 执行，模型本身没有 write authority。

model-facing tool names 也不能发展成第二套 domain vocabulary。每个 proposal 必须固定映射到现有 domain command 或审计结果：

| Model tool/proposal             | Domain command or result                                                                    |
| ------------------------------- | ------------------------------------------------------------------------------------------- |
| `proposeTask`                   | 写 persisted `TaskProposal`/assessment；Task 尚不存在，不能写 `task_suggestions`                      |
| `createAllowedTask`             | `createManagedTask`；Flexible 永不由 AI 调用                                                      |
| `proposeNextAction`             | 写 Suggestion                                                                                |
| `setAllowedNextAction`          | `changeNextAction`                                                                          |
| `recordObservedActivity`        | `recordActivity`；source 必须由 server 验证                                                       |
| `proposeBusinessProgress`       | 写 assessment/review proposal；不改 Task current progress                                       |
| `recordAllowedBusinessProgress` | `recordBusinessProgress`；仅 explicit evidence + Progress Decision Matrix + rollout allowlist |
| `proposeClose`                  | 写 assessment/Suggestion；不关 Task                                                             |
| `closeAllowedTask`              | `closeTask`；只接受 Outcome Decision Matrix + rollout allowlist                                 |
| `requestHumanReview`            | 创建 review projection/Suggestion，不做 terminal mutation                                        |
| `noOp`                          | 只写 run audit                                                                                |

因此，Task Reasoner 的 “skill” 只包装这些 read tools、proposal schemas 和 decision instructions。它不能绕过 Orchestrator，也不能获得 raw database、shell 或任意 network access。

### 12.4 Streaming、reasoning 与 harness 的关系

这里的 bounded reasoning 与输出是否 streaming 没有直接关系：

- **Streaming** 只影响用户是否逐字看到模型输出，不决定 AI 能不能调用 tools 或循环推理。
- 背景 Task Reasoner 不需要 streaming；未来的 interactive copilot 可以为了交互体感使用。
- 实现上可以借鉴 Grok Build/coding harness 的 typed tools、permission boundary、trace、bounded loop 和 recovery 思路。
- 不需要把 coding harness 整套搬进产品，也不能让模型获得 shell/SQL 能力。这里需要的是一个范围很窄、边界清楚的 Task runtime。

### 12.5 Cost control

Reasoner 的成本由 event volume、model turns、input context、tool result 和 output tokens 共同决定。应按下面的顺序控制，而不是先换更便宜的模型、再让所有 event 都调用它：

1. Deterministic Fast Lane 不调用 LLM。
2. 事件去重、debounce 同一 call/SMS burst。
3. 先传 compact context，不把完整 customer history 每次全塞进去。
4. 只有 material uncertainty 才调用 read tool。
5. 高风险冲突转人工，不靠无限推理“猜准”。
6. 按 Task kind 监控每次 run cost、auto-action rate、correction rate 和 incremental outcome value。

最终目标不是让每个 event 都运行一个更贵的大 agent，而是只在额外 reasoning 可能改变决策时支付这部分成本。

***

## 13. Workbench / UI projection contract

这一节只规定员工必须看懂什么、能够做什么，不规定具体像素或 visual layout。Domain 负责保存完整事实和审计信息；Workbench 只投影员工当前需要的部分。Figma 应引用这份 contract，不能反过来用某个 mock 定义 domain。

员工处理一张 Task 的日常路径可以按五步检查：

1. 扫描队列，选出现在该处理的 Task。
2. 看懂这件事为什么存在、下一步是什么、什么时候要做。
3. 执行工作，例如 Call、Text 或线下处理。
4. 记录结果，并在同一次操作里安排新的 Next Action。
5. 只有发生业务进展或最终结果时，才确认 progress、close、correct 或 reopen。

UI 的信息顺序和按钮数量都应该服务这五步。Backend 有一个 field 或 lifecycle，并不代表前端必须为它单独画一个 section。

### 13.1 一张 Task card 必须回答

Task card 服务前两步：让员工不用打开 detail 就知道“这是谁、为什么现在要处理、下一步做什么、是否紧急”。例如：

```text
Sarah Liddle · Lead

LEAD OUTREACH
Task goal: Reach a clear sales outcome
Why: New email lead requested information at 10:02

Next action: Call now
[Needs attention] [Not started] [High priority]
First-response SLA: 3 min remaining
Assignee: Unassigned

Latest activity: None
AI suggestion（话术类）: Start by asking what brought her in

[Call] [Text] [Record activity] [Update next action] [Close]
```

员工不需要手动维护 Task status、work state、queue state、Funnel stage 四套平行状态。页面必须根据 Task、Timeline 和 Policy 自动计算这些 projection。

Lead Workbench 仍要让员工第一眼区分当前工作，不能只显示 backend 的长期 `lead_conversion` kind：

```text
Lead
├── Lead outreach (12)
├── Lead follow-up (31)
└── Intro booked · conversion pending (8)
```

同一个 Task 的 card label 会随事实变化。下面三个 label 都是 `displayType`，不是三个 Tasks，也不会启动三套 lifecycle：

```text
Sarah Liddle · New Lead
LEAD OUTREACH
Call now · First-response SLA 还剩 3 分钟

第一次 no-answer Activity 后：

Sarah Liddle · Lead
LEAD FOLLOW-UP
Text tomorrow 10:00 · 1 attempt

确认 Intro 已预约后：

Sarah Liddle · Lead
INTRO BOOKED · CONVERSION PENDING
Confirm attendance plan and discuss the right membership option
```

左侧 `Lead / Member Care / Revenue Growth` workstream、queue tabs 和 filters 可以保留。`Lead Outreach / Lead Follow-up / Intro Booked · Conversion Pending` 是可筛选的 derived/materialized `displayType`。它们解决员工扫描、优先级和 reporting 问题，但不会重新创建三个 backend lifecycles。

排序也要跟当前工作一致：`lead_outreach` 可以由 first-response SLA 排到更前；另外两类仍按 `nextActionAt / deadlineAt / priority` 排序。

### 13.2 Detail 必须回答

Detail pane 服务后三步：员工在这里执行工作、记录结果，并在必要时确认进展或关单。它必须回答：

- 这是什么 Task，为什么现在存在。
- 当前已经采用的 Next Action 是什么，什么时候处理。
- AI Suggestion 是什么；如果需要员工动作，入口必须按 effect 使用业务语言，例如采用计划、确认事实或隐藏，不能平铺通用 Accept/Edit/Dismiss 三按钮。
- Activity Timeline 中是谁、何时、通过什么 channel、发生了什么。
- Customer summary 与 Contact Profile 依据哪些 evidence/provenance；必须明确它们是 evidence-assembled、AI-enriched operational snapshot，不能冒充 CRM truth。
- 员工在哪里补充 Note 与线下 context。
- 当前 assignee、first-response SLA 和 deadline。
- 当前 `taskKind` Policy 允许哪些 Close options。
- Outcome 的 verification、evidence、AI auto-close reason，以及怎样 correction/reopen。

#### 13.2.1 Suggestion 的交互按 effect 决定

Suggestion 在展示前就已经存在，不需要员工先批准才能看。员工可以直接参考，也可以什么都不做。UI 先判断建议会产生什么 effect，再决定是否需要按钮：

| Suggestion effect                   | 员工看到什么                                               | 员工操作的含义                              | Domain 结果                                                                |
| ----------------------------------- | ---------------------------------------------------- | ------------------------------------ | ------------------------------------------------------------------------ |
| 只提供 coaching、话术或判断参考，不执行系统 mutation | 直接展示建议内容；可以忽略、隐藏，也可以给 suggestion 级 thumbs up/down    | 忽略不产生任何决定；隐藏表示员工主动移出工作面；thumbs 只评价质量 | 无 Task state 变化；隐藏走 `dismissSuggestion`，质量反馈走 `recordSuggestionFeedback` |
| 可以立即执行的真实动作或低风险、可逆 mutation         | 一个具体按钮，例如「Review & send」或「设下一步：明早 10 点」              | 点击动作本身就代表采纳，不再先点一次 Accept            | 执行动作或 mutation，并用 `sourceSuggestionId` 把 Suggestion 投影为 accepted         |
| 需要员工补录系统看不到的线下事实                    | 业务语言按钮，例如「Record booking」或「Confirm booking」          | 员工是在确认 evidence，不是在审批 AI             | 写入 typed Business Progress/Activity；是否改变 Task 由 Policy 决定                |
| Close 等 terminal 变更                 | 「Review outcome」，打开已预填的标准关单流程                        | 员工检查 result、reason 和 evidence 后确认    | 确认后执行 `closeTask`；取消则不改变 Task                                            |
| AI 已按 authority 自动执行                | 已发生的结果、「set by AI」标识、evidence 和 correction/reopen 入口 | 不需要事后 Accept；员工只在事实不对时纠正             | 保留原 AI decision audit，纠正时追加新 event                                       |

Domain 命令仍保留 `accept/edit/dismissSuggestion`，但它们是 adoption lifecycle，不是必须逐个展示的通用按钮。`recordSuggestionFeedback` 回答的是建议是否 helpful/not relevant，也不等于 accepted、edited 或 dismissed。完整 backend lifecycle 见 §6.3 和 §11.2。

#### 13.2.2 Detail 的信息层级

这份 contract 不固定 pane 必须画成几个 box，但默认工作面应优先放高频动作：

1. **行动区**：当前 Next Action、处理时间、关键 evidence，以及 Call/Text 和当前可执行的具体 Suggestion action。
2. **记录区**：一次完成 `Activity + Next Action`，并提供必要的 typed progress 入口。
3. **Task Timeline**：默认留在主工作面，让员工一眼扫到之前做过什么。每条记录保持紧凑，并按时间连续显示；很久以前的记录可以用 `Load older` 继续加载，但不要把整个 Timeline 折叠起来或藏到次级 tab。（这是用户研究拍板的决定：门店反馈 Task Timeline 和 History 是高使用率功能——把信息整理好、方向给明确，员工自己会用，不需要替他们藏信息。）

Task reason、Customer summary、完整 evidence/provenance、Policy version、历史建议和教学说明仍然可访问，但不需要各自常驻成一个大 section。日常工作面不应重复展示同一个事实，也不应把 schema 中的每个概念直接变成 UI box。

### 13.3 对当前 UI 的审查结果

下面把 Current Workbench 中已经有效的部分，与需要接入新 domain contract 的部分分开：

| Decision     | Current element                                                   | Target handling                                                                                             |
| ------------ | ----------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| **Preserve** | split Workbench、queue tabs、search/filter/sort、Contact grouping    | 骨架适合高频处理，无需推倒                                                                                               |
| **Preserve** | Call / Text、assignee、History/Timeline、Notes、evidence containers   | 接到新的 command/projection contract                                                                            |
| **Refactor** | `Lead Outreach / Lead Follow-up / Booked Not Converted` card type | backend 合并为 long-lived `lead_conversion` Task；前端保留三个 `displayType`，变化的是 current work context 与 Next Action  |
| **Refactor** | `Action Guidance`                                                 | 上半明确 adopted **Next Action**；AI guidance 单独作为 Suggestion                                                    |
| **Refactor** | `Attempted / Not attempted` badge                                 | 作为 derived work state，不是 lifecycle                                                                          |
| **Refactor** | deadline/timing                                                   | 分开 first-response SLA、Next Action time、Task deadline                                                        |
| **Refactor** | `Update task`                                                     | 聚焦 assignee、priority、Next Action、deadline、note，并允许记录 typed progress（如 Intro booked/cancelled）；不让用户直接改抽象平行状态 |
| **Refactor** | `Close task` dialog                                               | 按 Policy 展示 result + reason + verification/evidence                                                         |
| **Refactor** | Funnel/Profile labels                                             | 展示 provenance，例如 AI-inferred / communication-confirmed / staff-confirmed                                    |
| **Remove**   | 全局 Positive/Negative/Neutral close picker                         | valence 由 Policy 派生                                                                                         |
| **Remove**   | 固定编号 SOP / rigid Steps                                            | AI 给下一步建议，不强迫用户维护流程                                                                                         |
| **Prevent**  | 任何 provider/CRM truth 暗示                                          | 保留并强化 Current guidance：Profile 是 evidence-assembled/AI-enriched，Outcome 显示真实 verification                   |
| **Add**      | `Create task`                                                     | 员工可创建 Managed 或 Flexible Task                                                                               |
| **Add**      | `Record lead progress`                                            | 员工可确认/纠正 Intro booked 等线下事实；不必 Close Task                                                                   |
| **Add**      | `Record activity + plan next action`                              | 一次完成高频操作                                                                                                    |
| **Add**      | Suggestion 决策入口（按 effect 命名）                                      | 让 AI 帮忙但不混淆 proposal 与 state；能直接执行的建议渲染成具体动作按钮，执行即采纳                                                        |
| **Add**      | Outcome evidence/correction                                       | 可看 AI 为什么关闭，也能人工纠正/reopen                                                                                   |

Current Target detail 已接入 `Record booking`（Task 保持 open）、AI booking correction、`Confirm converted` 和 reopen，并通过 Target commands 写 snapshot + Timeline。Task V2 仍走现有 compatibility interaction；页面必须根据 Target contract availability 显式选择 lane，不能把 partial Target row 当成完整 Target truth。

Current 前端已经有 `task-create.vue`，但 active Workbench chain 没有引用它。Target Staff create API/domain command 已有基础；正式 UI entry 仍需接线和 cutover 验证，不能继续把组件存在误写成用户已经可以创建。

### 13.4 员工体验底线

无论最后采用哪一种 visual layout，以下底线都不能退让：

- RingCentral 中已经发生的 Call/SMS 应尽量自动记录，不能要求员工重复录入。
- 系统看不到的线下动作必须有快速人工入口。
- 高频流程必须能在一次常用操作中完成 `Activity + Next Action`。
- AI 自动执行的事情必须可见、可解释、可纠正。
- flexible work 不应要求员工先配置一个新的系统 type。
- UI 应使用 “Lead outreach / Call now / Intro booked / Converted” 这样的业务语言，不能把 `aggregateVersion`、`verificationType` 等内部字段直接扔给员工。
- 同一个事实在主工作面只表达一次；provenance、Policy details 和教学说明应按需展开，不能挤占高频操作空间。

***

## 14. Funnel 与 Revenue projection

> 本节描述 **Target 最终能力**：怎样从证据计算 Funnel，以及怎样诚实说明业务贡献。首版不会一次实现完整 attribution 报表；**MVP 首发范围**见 §16.5。

### 14.1 Funnel 和 Task status 回答的是两件事

Funnel 回答“客户走到了哪一步”，Task lifecycle 回答“员工还要不要继续处理这件事”。不要把两者压进同一个 status。

| Dimension           | 它回答的问题                        |
| ------------------- | ----------------------------- |
| Task lifecycle      | 这件工作还要不要继续处理？                 |
| Next Action / queue | 员工现在或以后要做什么？                  |
| Activity            | 谁已经做过什么？                      |
| Outcome             | 这件 Task 最终怎么样？                |
| Funnel Projection   | 现有 evidence 能证明这个 Lead 走到了哪里？ |

一条 evidence 可以同时影响 Task 和 Funnel。例如，一条明确的 booking SMS 可以更新 Funnel，也可以改变 Task 的 `displayType` 和 Next Action；但 Task 与 Funnel 必须各自计算，不能共用一套 status。

Current 的 `contacts.lead_status` 也不能继续充当“万能状态”。它目前是 AI 写入的 12-value profile snapshot：

```text
new / attempted / connected / booked / showed / trialed / converted /
unreachable / lost_contact / neglected / bad_timing / not_interested
```

这个字段把三类不同信息混在了一起：客户进展、员工工作状态，以及停止处理的原因。Current prompt 已经把它放在 `CONTACT BACKGROUND` 中，并明确它不是决定 Task 是否创建或关闭的输入。Target 按下面的规则处理：

- `lead_status` 不得用于创建、关闭或跳过 Task。
- Target Funnel 只根据 typed evidence、Activities、Business Progress 和 Outcomes 计算。旧值最多显示为 `AI-inferred profile summary`。
- `new / attempted / connected` 要根据 intake 和 Activity evidence 重新计算。
- `booked / showed / trialed / converted` 只有在各自所需的 verification evidence 存在时，才进入 Funnel。
- `unreachable / lost_contact / neglected / bad_timing / not_interested` 要分别归到 work state、Outcome 或 reason，不能继续假装是 customer journey stage。
- 等 Target Funnel 的 dual projection 稳定后，新功能应停止读取 `lead_status`。是否停写旧值要单独做 migration 决策；历史值绝不能一对一回填成 Task Outcome。

Current 的 `contacts.action_needed` 和 `contacts.suggested_actions` 也只保留兼容用途：前者由“同店是否有 open Task”确定性回写，后者目前已清空。它们不是第二套 Task 或 Suggestion source of truth。Target Workbench 只读取 Task 和 Task Suggestion projection；到了 Contract phase，再停止写入或移除这些 contact-level compatibility fields。

### 14.2 MVP 不建 Milestone table，也能算出可信 Funnel

**MVP 可用的 Funnel 输入**只有系统确实拥有的事实：

- Email Lead received fact。
- RingCentral Call/SMS observed facts 和 communication evidence。
- Task Activities。
- Task Business Progress。
- Task Outcomes。
- Staff confirmation。

用这些输入可以得到下面的 projection。它只是可能的客户路径，不要求每个 Lead 逐级经过：

```text
Received
  → Outreach attempted
  → Connected
  → Booked
  → Converted

也允许：Received / Attempted / Connected → Converted
```

每一层的含义如下：

- **Received**：Email Lead intake，或者 Call/SMS 中出现明确 lead signal 后建立的 `lead_conversion` evidence chain。Current Funnel 的两条 intake lane 都必须保留；cutover 后不能只剩 Email。
- **Attempted**：已经发生 qualifying outbound Activity。
- **Connected**：出现 connected Call 或 inbound reply。
- **Booked**：`lead_conversion` 上存在仍然有效、并且有 completed booking evidence 支撑的 progress。Task 继续保持 open，同时投影 `displayType=booked_not_converted`。
- **Converted**：`lead_conversion.resultCode=converted` 的 terminal Outcome，并同时显示 verification。客户不需要先走 Intro/Booked，也可以直接 Converted。

系统如果不知道 attendance 或 membership 的真实状态，就不能因为预约时间已经过去而自动把 Booked 改成 Attended 或 Converted。员工确认或明确 communication 可以形成 attribution evidence，但仍不能被说成 external verification。

Current Funnel 还会把 Lead row 的 `booked_date` 当作 Booked evidence。Target 继续保留这批历史 cohort，但必须显示 `form_asserted` provenance。它可以让 Funnel 显示 “Booked (form-asserted)”，但不能自动写 completed booking progress，也不能自动把 `displayType` 改成 `booked_not_converted`。Task 仍然 open，Next Action 应是确认 booking。只有 `communication_explicit` 或 `staff_confirmed` 的 completed fact，才可以写 current booking progress。这样既不会丢掉历史 Funnel 数据，也不会把表单字段误当成权威 booking truth。

Current Funnel 的 stage vocabulary 还没有 `Converted`。这里的 Converted 是 Target projection。上线时必须给 dashboard definition 加 version，并对 Current 与 Target cohort 做 reconciliation，不能静默改口径。

### 14.3 报业务贡献时，要分开说明“谁促成”和“证据有多强”

**Target 最终报表**使用两个互相独立的维度。`contributionLevel` 说明 Retaintive/员工与结果之间的关系；`verificationType` 说明结果是怎样被确认的。不能用一个维度冒充另一个维度。

| `contributionLevel` | 它回答的问题                                                                 | 可以使用的措辞                                 |
| ------------------- | ---------------------------------------------------------------------- | --------------------------------------- |
| `discovered`        | Retaintive 发现了机会或结果，但在该 business result 之前没有 qualifying staff Activity | opportunity/outcome discovered          |
| `assisted`          | 员工在结果前采用或操作过 Task/Suggestion，但没有形成满足 attribution 的完整 Activity chain    | AI-assisted opportunity/outcome         |
| `attributed`        | 结果满足下面的 deterministic eligibility，可以与员工使用 Workbench 后的行动关联             | attributed booking/conversion/retention |

| `verificationType`              | 它回答的问题                    | 可以使用的措辞                      |
| ------------------------------- | ------------------------- | ---------------------------- |
| `communication_explicit`        | Call/SMS 是否明确说出了结果        | communication-confirmed      |
| `staff_confirmed`               | 有权限的员工是否确认了线下或后台事实        | staff-confirmed              |
| `external_verified`             | 未来的权威 integration 是否确认了结果 | externally verified；MVP 不得生成 |
| `ai_inferred / imported_legacy` | 结果是否只是 AI 推断或旧记录          | inferred / legacy；不得升级措辞     |

Terminal Outcome 只有同时满足以下六条，才可以标为 `attributed`：

1. Task 在 Outcome 发生前已经存在，而且带有同店 creation evidence。
2. Outcome 之前存在该 Task Policy 定义的 qualifying staff Activity。只有 AI 发现结果，或员工只点了 Close，都不够。
3. Activity 与 Outcome 位于同一个 `storeId + contactPhone + taskId` evidence chain。
4. Activity 发生在 Policy 的 `attributionWindow` 内，并且早于 `outcomeOccurredAt`。使用的 window 和 Policy version 必须一起记录。
5. Outcome 没有被 correction 或 reopen 推翻，并且 `revenueMappingCode` 不是 null。
6. 如果只有 AI 观察到结果、员工没有采取行动，最多只能标为 `discovered`，不能标为 `attributed`。

Non-terminal Intro progress 也遵守同一套 attribution 纪律，但使用自己的字段：`progressCode=intro_booked / intro_attended`、`progressOccurredAt` 和对应的 `reportingMetricCode`。记录 progress 不需要关闭 Task，也不能把它写进 terminal `resultCode`。只有在 progress 之前存在同一 evidence chain 的 qualifying staff Activity，才可以说 “Booking/Attendance attributed”。如果 AI 只是从沟通中发现客户已经自行完成，措辞只能是 “discovered”。Progress mapping 和 terminal Outcome mapping 要分别记录 version 和 `revenueCreditKey`，防止同一个经济结果被重复计数。

员工在线下完成工作时，一个 UI submission 可以同时记录 Activity、`staff_assertion` 和 Outcome。如果员工只确认了结果，没有说明自己做过实际动作，那么 verification 可以是 `staff_confirmed`，但 contribution 不能自动升级成 `attributed`。

去重要分两层做：

- Call/SMS volume 和 staff interaction credit 按 `storeId + sourceInteractionKey` 只计算一次。即使同一次 interaction 推进了两个 Tasks，也不能算两次。
- 如果两个 Tasks 映射到同一个经济结果，Policy 必须生成相同的 `revenueCreditKey`。Owner dashboard 对这笔金额只能算一次。不同业务 Outcome 可以分别展示，但不能重复声称同一笔美元价值。

每个带 revenue mapping 的 Policy 都必须明确定义 `attributionWindow`、qualifying Activities、`revenueCreditKey` 规则、estimate formula/version，以及 owner-facing wording。AI 不能临场发明这些规则。

Estimated influenced MRR 不能写成 accounting-recognized revenue。缺少可靠金额时，宁可只展示：

- Bookings attributed。
- Conversions staff-confirmed。
- Cancellations saved。
- Payment recoveries confirmed。
- Estimated value，并清楚标记估算模型和 Policy version。

`progressCode/resultCode`、verification、reporting/revenue mapping 和 calculation definition 都必须 versioned。发生 correction 时，系统更新 current projection，同时保留原 decision 和 audit；不能静默改写过去的 dashboard。

**MVP 首发报表**不要求一次实现上述完整模型。首版只做 §16.5 定义的诚实计数；完整的 `discovered / assisted / attributed` projection 按 rollout 阶段逐步启用。

***

## 15. End-to-end scenarios

> 这些场景用具体操作说明 **Target contract**。当 Target 允许 AI 自动执行、但 MVP 只做 shadow 或人工确认时，场景会明确写出两者的差别。

### 15.1 新 Lead：第一次电话无人接，后来通过 SMS 完成预约

| 阶段                  | 系统应记录什么                                                                                                                                                                                                                                              |
| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Input               | Email Lead 到达，verification 为 `system_observed`                                                                                                                                                                                                       |
| Task creation       | 创建 `lead_conversion`；`displayType=lead_outreach`；reason 指向该 Lead；first-response SLA 为 5m                                                                                                                                                             |
| Next Action         | “Call now”                                                                                                                                                                                                                                           |
| Activity            | outbound phone / `call_completed` / `no_answer`                                                                                                                                                                                                      |
| Task after Activity | Task 仍 open；`workState=in_progress`；`displayType=lead_follow_up`                                                                                                                                                                                     |
| New Next Action     | “Send SMS now, call tomorrow if no reply”                                                                                                                                                                                                            |
| Activity            | 客户回复 “Book me Tuesday at 6”。这是 booking request，还不是 completed booking fact                                                                                                                                                                            |
| Task after request  | Task 仍 open；Next Action 改为 “Complete/confirm Tuesday booking”                                                                                                                                                                                        |
| Later Activity      | 客户明确回复 “I booked myself for Tuesday at 6”，或员工发送 “You’re booked for Tuesday at 6”                                                                                                                                                                     |
| AI decision         | Target：只有 completed statement 通过 Task Policy、resolved evidence、automation tier、per-objective authority 和 fresh Guard 时，才可写 `progressCode=intro_booked`，而且 booking progress 本身不会 close Task。缺少任何 authority/evidence 就转员工确认；本场景不宣告该 authority 已在任何环境启用 |
| Task after booking  | Task 仍 open；`displayType=booked_not_converted`；Next Action 是确认到店计划，并按客户需要推进 Membership                                                                                                                                                               |
| Funnel              | Booked，并标为 communication-confirmed                                                                                                                                                                                                                   |
| Revenue             | booking attributed；不能写成 membership revenue                                                                                                                                                                                                           |
| Later result        | 客户明确已经正式加入，或员工确认 conversion 已完成                                                                                                                                                                                                                      |
| Outcome             | `resultCode=converted`，`closureReason=goal_achieved`，Task closed。客户不需要 Intro 时，也可以从前两种 `displayType` 直接到达这个 Outcome                                                                                                                                  |

### 15.2 客户在线下完成预约，但系统看不到

AI 不能因为后续没有消息就推断客户已经 booked。员工在 Workbench 用一次业务操作补录系统看不到的事实：

1. 选择 Update / Record result，再选择 “Intro booked”。
2. Backend 写入 `progressCode=intro_booked`。
3. Verification 写为 `verificationType=staff_confirmed`。
4. 员工填写一条简短 confirmation note；server 在同一个 transaction 中生成 `staff_assertion` EvidenceRef。
5. 如果员工同时声明自己完成了预约工作，同一个表单还可以记录一条 Activity；如果只确认结果，就不能凭这次 click 编造 Activity 或 attribution。

写完以后，Task 仍然 open，`displayType` 变成 `booked_not_converted`，Funnel projection 也随之更新；员工同时设置下一步。这个人工入口不是自动化失败后的 exception，而是当前 data boundary 下必须存在的正常入口。

### 15.3 AI 把 booking 判断错了，员工怎样纠正

如果 AI 把一句含糊的对话误标成 booked，处理过程如下：

1. 员工打开这条 progress 的 evidence。
2. 选择 “Booking incorrect/cancelled”。
3. 系统让当前 booking evidence 失效，并重新计算 `displayType=lead_follow_up`。Task 从始至终都保持 open。
4. 原来的 AI progress 和新的 correction 都留在 Timeline。
5. 这次 correction 进入 eval/monitoring；后续 AI 不能根据旧 proposal 把同一个错误再次写回。

### 15.4 一个 Contact 同时有 `lead_conversion` 和 `referral`

一次 connected Call 是一个真实 interaction，但它可以为两个 Task 提供证据。系统只记录一次底层 interaction，再让两个 Task 的 decision 分别引用它：

- `lead_conversion`：如果客户完成预约，记录 progress，但 Task 继续 open；只有 converted 或其他 terminal Outcome 才关闭。
- `referral`：如果客户只说以后可能介绍朋友，Task 继续 open，或只生成 Suggestion。

Timeline event 主要归属一个 Task；Current Target writer 在同一 communication 支撑多个 Tasks 时，让每个 Task event 引用同一个由 server 生成的 `sourceInteractionKey` 和 `evidenceRefs`。Call count 和 staff credit 按这个 key 去重，不能因为关联两个 Tasks 就算两次；各 Task 的 contribution 仍可分别计算。Legacy event 缺 key 时只能标 coverage unknown，不能当成 0 或用文字猜。

### 15.5 客户提出 Cancellation request

```text
Customer says: "I want to cancel."
  → create cancellation_request
  → Next Action: manager callback
  → Activity: manager connected
  → Customer says: "I'll stay if we switch the plan."
```

最后一句可能只是 commitment，不一定表示后台变更已经完成。当前没有 external membership truth，因此 Target 按以下边界处理：

- 如果客户明确表示“决定留下”，Policy 可以关闭为 `cancel_saved` 并标 communication-confirmed；也可以要求 staff confirmation。具体采用哪一种，由 rollout allowlist 决定。
- “取消已经在后台处理完成”只有在出现明确 completed statement 或员工确认时，才能支持 `cancellation_completed`。
- 固定的 “call → form → provider confirmation” Steps 不属于 domain model。

### 15.6 Renewal / Upgrade / Payment：有意向不等于已经完成

| Communication                            | 系统应该怎样处理                                                                               |
| ---------------------------------------- | -------------------------------------------------------------------------------------- |
| “I am interested in upgrading”           | 创建或更新 Task，并设置 Next Action；不能关闭为 `upgraded`                                            |
| “I agree to renew”                       | 可以记录 commitment；没有 completed fact 时 Task 仍 open                                        |
| “I will update my card”                  | 设置 Next Action 和下一次 review 时间，进入 Scheduled；不能关闭为 `payment_restored`                    |
| “The upgrade was processed this morning” | Target Policy 可以按 `communication_explicit` close，并标 attributed；不能说 externally verified |
| Staff sees completion elsewhere          | 员工以 staff-confirmed close                                                              |

其中 AI close 是否在某个环境执行，由 automation tier、per-objective authority、runtime gate 和 fresh Guard 共同决定；本场景只规定语义，不宣告 UAT 或 PROD 已开放。当前状态见 [Task V3 Rollout Status](/product-design/v3/tasks-feature/task-v3-rollout-status.md)。

### 15.7 员工创建 Flexible Task

员工可以直接创建：

```text
Title: Ask Sarah about accessibility preference
Mode: flexible
Next Action: Talk to Sarah on Friday
```

AI 可以总结相关 Call/SMS，也可以建议处理时间，但不能自动创建或关闭这个 Task。员工把它关闭为 `completed` 或 `cancelled`，并可填写 note；默认不做 revenue mapping。

### 15.8 DNC、wrong number 和 duplicate

Current Target message runtime 对 exact STOP 先解析 server-owned Contact/Store party roles，再决定 inbound customer command；它不信任 raw `msg.direction`。Source-bound DNC write 同时受 idempotency 和 authority fence 保护。VoiceMail row 即使来自同一个 Unified Message Store，也不会被当成 SMS STOP 或员工 outreach。

| Evidence                  | Domain 应怎样处理                                                                        |
| ------------------------- | ----------------------------------------------------------------------------------- |
| 明确 DNC                    | 写 Contact restriction；根据 Policy 关闭相关 outreach Tasks，`closureReason=compliance_stop` |
| wrong number              | 记录 Activity outcome 并进入 identity review；Task 可用 `invalid_identity` 关闭；不能跨店自动 merge  |
| duplicate open Task       | 保留正确 Task；另一个用 `closureReason=duplicate` 关闭，不能伪装成 completed                         |
| 新 evidence 表明旧 Task 已不再需要 | 使用 `closureReason=superseded/no_longer_needed`，并保留 evidence chain                   |
| evidence 不足               | 生成 Suggestion 或 `requestHumanReview`；不能做 terminal mutation                          |

`setContactDoNotContact` 必须在一个 transaction 中完成所有必要动作：先验证 explicit evidence，再写 Contact restriction 和 `contact.dnc_changed` audit，清除所有不允许的 future outbound Next Action，最后根据每个 Task Policy 的 `dncBehavior = close / block_outbound / keep_open_for_review` 处理相关 open Tasks。

`closeTask(not_applicable, compliance_stop)` 只有两种情况下合法：Contact 已经处于 DNC，或者本次 command 在同一个 transaction 中同时写入 DNC。清除 DNC 必须使用 privileged command；AI 永远不能调用。清除后也不能自动 reopen 旧 Tasks。

***

## 16. Current → Target migration

> 本节把两件事分开：**Target end state** 说明最终要有什么；**rollout phases 和 §16.5** 说明第一版怎样安全到达那里。它不是 big-bang 重写计划。

先看整体改动范围：

| Change class                               | Scope                                                                                                                                                                                                                            |
| ------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Keep**                                   | `tasks` current snapshot、`open/closed`、assignee、`nextActionAt/deadlineAt`、`contact_timeline`、suggestion/assessment stores、Task Orchestrator、Workbench 主骨架                                                                        |
| **Implemented in source; rollout pending** | `taskMode/taskKind/displayType/dedupeKey/title/nextActionText/currentOpenedAt`、typed progress/Outcome/evidence、split Policy versions、Policy Catalog、bounded read tools、SMS batching、provider semantic runtime、Target projections |
| **Replace**                                | global `typeCategory + closeResult` validation 改为 Task Policy；三个 legacy Lead categories 改为一个 `lead_conversion` Task 加 derived/materialized `displayType`；one-shot-only 改为 bounded optional read loop                             |
| **Recalculate**                            | work state、queue semantics、Funnel 和 revenue contribution；先做 dual projection，不能静默改历史                                                                                                                                              |
| **Deprecate**                              | `contacts.lead_status` 作为 stage truth、contact-level suggestions/action-needed truth、global valence mappings、旧 Task design docs                                                                                                   |
| **Do not add in MVP**                      | `Case`、rigid `Step`、standalone `Action`、`Milestone/provider` system、第二份 Activity table                                                                                                                                           |

这不是推倒重来。nullable Expand schema、独立 TypeScript domain、Policy Catalog、Guard、InMemory/Neon adapters 和 production seams 已经在 current source 中形成。现在的主线不再是“先设计字段再写 DDL”，而是保留 safe defaults，完成外部 gates、历史 reconciliation、shadow、dual projection 和 Store cutover。

“Domain 先离线验证、再做 schema Expand”是已经执行过的历史 sequencing decision，不是当前仍未开始的步骤。Schema/migration 可被代码和 PostgreSQL tests 验证，但 production migration state 仍必须在明确的 auth/account context 下单独检查。

### 16.1 Legacy `typeCategory` 怎样迁移

| Current `typeCategory` | Target                                                 | Migration rule                                                                                                                                              |
| ---------------------- | ------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `lead_outreach`        | `lead_conversion` + `displayType=lead_outreach`        | 先对同 store/contact、同一 lead opportunity evidence chain 的 open sibling 做 reconciliation，再合并；不同历史 opportunity 不能机械合并                                            |
| `lead_follow_up`       | `lead_conversion` + `displayType=lead_follow_up`       | 变成同一个长期 Task 的 current work context、Next Action 和 history，不再 close 后 relay                                                                                  |
| `booked_not_converted` | `lead_conversion` + `displayType=booked_not_converted` | Current taxonomy 实际要求 Intro attended/completed；只有 supporting evidence 存在时才导入 `businessProgress=intro_attended`。只有 legacy label 时，不生成 progress 或 attribution |
| `cancellation_risk`    | `cancellation_request` 或 review                        | 只有明确取消请求才能映射；模糊 churn risk 不能自动升级                                                                                                                           |
| `retention`            | case-by-case                                           | 它可能表示 cancellation、member issue、payment recovery 或普通 follow-up，不能一对一迁移                                                                                      |
| `win_back`             | `win_back`                                             | category 一对一，但仍要分配 Policy version                                                                                                                           |
| `upgrade`              | `upgrade`                                              | category 一对一                                                                                                                                                |
| `renewal`              | `renewal`                                              | category 一对一，但不能只看旧 label 就假定真实 renewal window                                                                                                              |
| `referral`             | `referral`                                             | category 一对一；未来的 subject key 另行扩展                                                                                                                           |

Current code 中的这些语义互相矛盾，migration 必须处理：

- Production prompt 把 `lead_outreach` 当成 long-lived objective，所以第一次 successful contact 后不会 close 再 relay。
- UI Help 仍然写着：第一次 Call/Message 完成 Lead Outreach 后，转成 Lead Follow-up。
- Current taxonomy 对 `booked_not_converted` 的实际定义是“已经 attended/completed Intro，但尚未入会”，与字面上的“已经 booking”不同。因此 migration 必须看 evidence，不能按 category 名字猜。

Target 以本文为准：一个 Lead 只有一个 stable `lead_conversion` Task。随着事实变化，系统更新的是 `displayType`、Next Action、Activities 和 non-terminal business progress。Intro 不是必经步骤，三种 `displayType` 都可以直接到达 `converted`。

在 dual-write 期间，Target → Legacy type compatibility 必须集中由一个 adapter 完成：

| Target `taskKind`                         | `displayType`          | Legacy `type_category` | Fidelity / gate                                    |
| ----------------------------------------- | ---------------------- | ---------------------- | -------------------------------------------------- |
| `lead_conversion`                         | `lead_outreach`        | `lead_outreach`        | 只用于 UI compatibility；Task identity/dedupe 不变       |
| `lead_conversion`                         | `lead_follow_up`       | `lead_follow_up`       | 只用于 UI compatibility；Task identity/dedupe 不变       |
| `lead_conversion`                         | `booked_not_converted` | `booked_not_converted` | Task 仍 open；`close_result` 必须为 null                |
| `cancellation_request`                    | default                | `cancellation_risk`    | lossy；必须保留 Target kind/evidence                    |
| `renewal / upgrade / win_back / referral` | default                | 同名 legacy value        | category 层面 lossless                               |
| `payment_recovery`                        | default                | unsupported            | Target read projection 切换前，不能启用 mutation authority |
| Flexible `custom`                         | n/a                    | unsupported            | 不能伪装成 `retention`                                  |

`lead_conversion` 改变 `displayType` 时，只更新 compatibility `type_category`，不能改变 `taskId/taskKind/dedupeKey`。如果 legacy schema 已经有 open sibling，adapter 必须在同一个 lock/transaction 中先做 reconciliation。

### 16.2 Legacy `closeResult` 怎样迁移

先把 Current 值放回正确的 Target 概念中：

| Current value       | Target home                                                                                                                                                |
| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `converted`         | `lead_conversion.resultCode=converted`                                                                                                                     |
| `won_back`          | `win_back.resultCode=won_back`                                                                                                                             |
| `issue_resolved`    | 对应 Managed/Flexible Task 的 result；无法判断 kind 时进入 legacy review                                                                                              |
| `cancel_saved`      | `cancellation_request.resultCode=cancel_saved`                                                                                                             |
| `renewed`           | `renewal.resultCode=renewed`                                                                                                                               |
| `upgraded`          | `upgrade.resultCode=upgraded`                                                                                                                              |
| `referral_obtained` | `referral.resultCode=referral_captured`                                                                                                                    |
| `wrong_number`      | Activity outcome 加 identity correction；Task 使用 closure reason `invalid_identity`                                                                           |
| `do_not_contact`    | Contact restriction 加 Task closure reason `compliance_stop`                                                                                                |
| `other`             | Task-specific result，或 admin `outcome_unknown`；必须填写 note                                                                                                   |
| `not_interested`    | Task-specific negative result 加 `customer_declined`                                                                                                        |
| `already_member`    | qualification/identity fact；通常是 `not_applicable / no_longer_needed`，不是员工失败                                                                                 |
| `booked`            | 导入为 `businessProgress=intro_booked`，不是 Target terminal Outcome。历史 closed row 保留 legacy closure；是否还需要另建 open conversion work，由 review policy 决定，不能静默 reopen |
| `cancelled`         | ambiguous legacy：可能只是 “form sent”，也可能是 “cancellation completed”；必须看 evidence，不能自动当成已完成                                                                     |
| `unable_to_reach`   | `resultCode=outcome_unknown` 加 `closureReason=contact_exhausted`；必须验证尝试次数和时间窗口都符合 Policy，不能写成确定的 `not_*` 业务结果                                              |

Current UI 用 global positive/negative/neutral 分组，backend 和 reporting 又各自维护另一份集合；`referral_obtained` 的 valence 已经出现 drift。Target 删除这些手写的平行 mapping，全部由 Task Policy 生成。

Dual-write 期间，Target → Legacy compatibility mapping 也只能由同一个 adapter 负责：

| Target Outcome                                                                           | Legacy `close_result`                                        | Fidelity / rollout rule                                                      |
| ---------------------------------------------------------------------------------------- | ------------------------------------------------------------ | ---------------------------------------------------------------------------- |
| `converted`                                                                              | `converted`                                                  | lossless                                                                     |
| `cancel_saved`                                                                           | `cancel_saved`                                               | lossless                                                                     |
| `cancellation_completed`                                                                 | `cancelled`                                                  | Target 含义更窄；保留 Target evidence，legacy 只用于兼容显示                                |
| `renewed`                                                                                | `renewed`                                                    | lossless                                                                     |
| `upgraded`                                                                               | `upgraded`                                                   | lossless                                                                     |
| `won_back`                                                                               | `won_back`                                                   | lossless                                                                     |
| `referral_captured`                                                                      | `referral_obtained`                                          | lossless                                                                     |
| 任一明确的 `not_* + customer_declined`                                                        | `not_interested`                                             | lossy；Target fields 必须保留 Task kind                                           |
| `outcome_unknown + contact_exhausted`                                                    | `unable_to_reach`                                            | stop-reason 层面 lossless                                                      |
| `not_applicable + invalid_identity`                                                      | evidence 确实是 wrong number 时写 `wrong_number`，否则写 `other`      | reason-specific                                                              |
| `not_applicable + compliance_stop`                                                       | Contact restriction 已经写入 DNC 时才写 `do_not_contact`，否则禁止 close | compliance invariant                                                         |
| `not_applicable + no_longer_needed` 且明确 already member                                   | `already_member`                                             | reason-specific                                                              |
| `payment_restored / payment_not_restored / referral_not_captured` 等没有精确 legacy value 的结果 | `other`                                                      | lossy；只有已经上线 Target projection 的 store 才能启用 authority                        |
| Flexible `completed / cancelled / outcome_unknown`                                       | `other / other / other`                                      | lossy；不能用 `issue_resolved` 制造 positive reporting；Target note/evidence 才是精确语义 |

Adapter 每次写入时都要记录 `targetOutcome → legacyValue + fidelity(lossless/lossy) + outcomePolicyVersion`。Shadow dashboard 必须监控 mismatch count。如果某个 Task kind 的 Target close 无法在 legacy UI 中安全表达，那么该 store 在切换 Target read projection 前，不能开启这个 kind 的 AI close authority。否则会出现 Target closed、Legacy open 的 split-brain。

`progressCode=intro_booked / intro_attended` 使用独立的 progress compatibility：写 `type_category=booked_not_converted`，同时保持 `status=open` 和 `close_result=null`。绝不能借用旧的 `close_result=booked` 表达非终态 progress，否则 legacy writer 会再次把 booking 变成 terminal closure。

Legacy `close_type = auto_closed / manual_closed / create_closed` 只说明旧系统走了哪条关闭路径，不能直接当作 actor。迁移时按下面的顺序判断：

1. 先读取该 Task 最新一条 closing `task.status_changed` Timeline event，查看 `actor_type / actor_subject_id / staff_id / closeInitiator`。
2. 有可信 staff actor 时，写 `closedByType=staff`；contact-analysis/AI writer 写 `ai`；deterministic service 写 `system`。
3. `create_closed` 只表示“创建时就关闭”，不等于 AI；`auto_closed` 也可能来自 system rule，不能机械映射成 `ai`。
4. 找不到可核验的 closing event 时，写 `closedByType=import`、`verificationType=imported_legacy` 和 `closedBySubjectId=null`。

Expand 阶段可以先把新 Target columns 设为 nullable；进入 Contract 前，必须按下面的规则完成可审计 backfill：

| Field                            | Legacy backfill rule                                                                                                                                                                            |
| -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `taskMode`                       | 默认 `managed`。`source_type=manual` 不自动等于 Flexible，必须按实际语义 review                                                                                                                                 |
| `taskKind`                       | 按 §16.1 mapping。无法判断的 closed/history row 使用 migration-only `legacy_unclassified`；它不能被新建、不能接受 AI mutation，也不能做 revenue attribution                                                               |
| `displayType`                    | 三种 legacy Lead category 分别保留 `lead_outreach / lead_follow_up / booked_not_converted`，并标 compatibility provenance；closed row 保存 `displayTypeAtClose`                                             |
| `businessProgress`               | Legacy `booked_not_converted` 只有 supporting evidence 存在时才导入 `intro_attended`。只有 label 时，保留 compatibility display 和 `imported_legacy` provenance，不生成 attribution；legacy closed/booked 不自动 reopen |
| `dedupeKey`                      | open-row reconciliation 完成后由 Target Policy 生成；Flexible 为 null                                                                                                                                   |
| `creationEvidenceRefs`           | 能解析到同店 source 时才转换；否则指向 `legacy_import`，不能根据 AI summary 编造                                                                                                                                      |
| `title`                          | 使用 mapped Policy label；unclassified 显示 “Legacy task”                                                                                                                                            |
| `taskReason`                     | 保留现值。null 时写明确占位 “Legacy reason unavailable”，并标 legacy provenance；不能让 AI 补写一段假的历史                                                                                                               |
| `priority`                       | 保留现值。null 时使用 migration policy default，并记录 `legacy_backfill`；不能伪装成人工选择                                                                                                                          |
| `nextActionText`                 | 不能把 active Suggestion 猜成 adopted plan；不确定时保持 null，等待员工或新 command 采用                                                                                                                             |
| `creationPolicyVersion`          | 写 `legacy-v1`                                                                                                                                                                                   |
| `outcomePolicyVersion`           | Legacy closed row 写 `legacy-v1`                                                                                                                                                                 |
| `currentOpenedAt`                | 优先使用最后一次 `closed → open` Timeline 的 `occurredAt`；没有时使用 `createdAt`                                                                                                                              |
| `aggregateVersion`               | 初始为 `0`；Target command 第一次成功后递增                                                                                                                                                                 |
| `firstResponseAt`                | 能从 qualifying `task.activity_recorded` 确定时，取最早的 `occurredAt`；无法确定时保持 null，不能从 legacy note 猜                                                                                                     |
| `resultCode / closureReasonCode` | 只有 exact mapping 才写精确结果；ambiguous row 使用 `outcome_unknown / other + imported_legacy note`                                                                                                       |
| `outcomeEvidenceRefs`            | 指向可解析 source；无法解析时指向 legacy Task row                                                                                                                                                            |
| `outcomeOccurredAt`              | 只有真实 evidence time 才填写；不能用 `closedAt` 代替                                                                                                                                                        |
| `revenueMappingCode`             | ambiguous/imported legacy 默认 null                                                                                                                                                               |
| SMS cursor                       | rollout 时从已经捕获的 high-water bootstrap，并写 audit；不能从零重跑整个 thread                                                                                                                                   |
| Reasoner run                     | 不伪造历史 loop；旧 assessments 继续作为 legacy one-shot audit                                                                                                                                             |
| close actor fields               | 按上面的 Timeline-first rule 回填                                                                                                                                                                     |

只有当 backfill coverage、unclassified count、null count，以及 Current/Target projection mismatch 都达到 rollout threshold 后，才能增加 Target `NOT NULL/CHECK`，并进入 Contract phase。

### 16.3 按阶段 rollout，不让员工突然换一套系统

一项能力是否可以向下一 evidence level 推进，必须按 gate 判定，不能用“代码很多”或“PR 已 merge”代替：

| Phase                       | Stable evidence gate                                                                                       |
| --------------------------- | ---------------------------------------------------------------------------------------------------------- |
| Phase 0 — Contract/eval     | 人工 ratified corpus、冻结后的 fresh real-model comparison、预先固定的 quality/cost/safety 标准                           |
| Phase 1 — Expand            | 在明确环境验证 migration、schema drift、legacy compatibility 和 rollback preflight                                   |
| Phase 2 — Shadow            | 真实 event source 以 shadow authority 运行，并有 quality/cost/error/correction evidence；不改用户 state                 |
| Phase 3 — Dual projection   | 按 Store/cohort 对齐 Task/Timeline/Funnel/Speed/UI，每个 unknown/partial/mismatch 都可解释                           |
| Phase 4 — Authority cutover | legacy open Task reconciliation、read/semantic/reporting staged authority、kill switch 和 rollback drill 都有证据 |
| Phase 5 — Contract          | active runtime 稳定且 rollback window 结束后，才退役 legacy Task decision/write path；历史 Timeline 继续保留                |

这些 Phase 可以在工程上并行准备，但不能跳过 evidence gate。尤其是 unit/PostgreSQL tests 不能替代 real-model quality gate，checked-in gate 也不能替代目标环境 runtime evidence。各 Phase 的当前状态只在 [Task V3 Rollout Status](/product-design/v3/tasks-feature/task-v3-rollout-status.md) 更新。

#### Phase 0 — Contract and eval

这一阶段只验证 contract，不改 production state：

- 冻结本文 vocabulary 和 Policy schema。
- 用真实历史 Call/SMS/Lead/Task 构建 store-scoped eval fixtures。
- 对每个 Managed kind 验证 create、close、Next Action 和 evidence。
- 选择第一批 AI authority allowlist。Booking 只能 auto-progress，不能 auto-close。Terminal close 可以先对明确的 `lead_conversion/converted` 做 shadow；production 可以更保守，先一律要求 staff confirmation。

Phase 0 的可执行形态是一个**离线双跑 harness**：

- 把同一批真实历史 evidence 按时间顺序回放，同时驱动两条 lane。旧 lane 运行现有 Contacts Analyzer 的 prompt/schema，真实调用模型，但 mock writer；新 lane 运行 Router → Reasoner → Guard → Orchestrator，并使用 in-memory adapter。所有副作用都 mock，模型调用保持真实，因此成本来自实测而不是估算。
- Harness 提供两种模式：`live-model` 真实调用模型，用来测质量和成本；`replay` 回放已经录制的模型输出，用零模型成本迭代 Guard/Engine 逻辑。
- 成功标准必须在运行前写好，不能看完结果再改标准。例如：新 lane 在期望 case 上的 create/close 正确率不低于旧 lane；Guard 对无法解析的 hallucinated evidence 引用拦截率为 100%；中位数为 1 次 logical turn，physical attempts 单独报告；每个 mutation 都能回溯到 evidence、Policy version 和 Guard decision。
- 对比报告至少包含：按 §16.1 mapping 对齐后的 decision diff、正确率、每个 case 的 token/实测成本、Guard 干预清单，以及 read model 是否能重建 §13 所需的全部 UI projection。
- 每个 case 的成本报告必须同时给出两个口径：Task Reasoner 的 incremental Task-only cost，以及包含 source understanding（Call `triage/classify` 或 standalone semantic fallback）、retry 和 repair physical attempts 的 attributable end-to-end cost。只报增量会把总成本隐藏起来，不能通过 Phase 0 成本闸门。

#### Phase 1 — Expand

这一阶段只增加兼容能力，不切换用户读路径：

- 增加 Target nullable columns、Policy Catalog 和新 validation。
- 保持 legacy API 和 UI 正常工作。
- 新 command dual-write `type_category + task_kind/display_type`，并 dual-write `close_result + Outcome fields`。Progress 使用单独的 dual projection，绝不能写 `close_result=booked`。
- Timeline 只追加 versioned events，不删除旧记录。

#### Phase 2 — Shadow

这一阶段让新 Reasoner 观察真实流量，但不改变用户看到的 state：

- 新 Task Reasoner 只写 assessment/proposal。
- 对比 Task V2 与 Task V3 的 create、update 和 close 决策。
- 重点监控 false auto-close、missing Task、duplicate、跨店污染和 staff correction。
- Shadow 是 Phase 0 harness 的在线延伸：把 replay driver 换成真实 event consumer，把 in-memory adapter 换成真实存储，但仍然只写 assessment/proposal；复用 `lambda/shared/utils/ai` 模块已有的 `ai_usage` 结构化记账，在真实流量上复测成本。

#### Phase 3 — Dual projection

这一阶段同时计算两套读模型，继续对账：

- Backend 同时生成 legacy 和 Target Workbench/Funnel/Revenue projection。
- 按 store/cohort 对比 reconciliation counts、queue、SLA、booked、conversion 和 retention。
- Figma/前端基于 Target API 实现 feature-flagged UX。

#### Phase 4 — Authority cutover and reconciliation

任何 existing Store/objective 的 authority 变更都必须按可回滚顺序执行：

- 先切 read projection，再切 AI authority，最后切 reporting。
- Staff manual create/update/close 始终保留。
- 必须能 rollback 到 legacy read；已经写入的新 Timeline/Outcome 数据不能丢。

#### Phase 5 — Contract

满足以下全部条件后，才能移除 legacy `type_category / close_result / due_at` 写路径：

- 所有 active stores 都已完成 cutover。
- 历史 ambiguous rows 都有明确 handling。
- Prompt、UI、backend 和 reporting 都只从 Policy Catalog 读取 vocabulary。
- Rollback window 已结束。
- Dashboard definition version 已公开。

### 16.4 Migration 绝不能做的事

Current historical-row Store backfill scripts 已改成 audit-only：`--apply` 在创建 database client 之前就 fail closed；current phone assignment 只能作为 audit signal，不能执行 historical `UPDATE`。这条安全栏必须保留，不能为了提高 migration coverage 重新开放写路径。

- 不能把每个 legacy row 原地 rename 后，就宣称语义已经正确。
- 不能把 `lead_outreach + lead_follow_up` 机械地变成两个 Target Tasks。
- 不能从 `due_at` 猜 `nextActionAt`、first-response SLA 或 deadline。
- 不能把 legacy closed 自动解释为 goal achieved。
- 不能从 AI summary 反向编造不存在的 Activity 或 evidence。
- 不跨 `store_id` merge——按 phone 不行，加任何其他证据也不行；跨店归并只能人工处理（§15.8）。
- 不能静默重算历史 Funnel/Revenue。
- 不能 big-bang 替换 production Workbench。

### 16.5 Original MVP acceptance slice（不再是当前 rollout scope）

本节保留最初用于验收 UX 的最短、可回溯闭环，不再定义当前的 objective allowlist、environment enablement 或 PROD launch scope：

```text
AI 从 Call/SMS/Lead 发现事项并创建 Task
  → 员工看到清楚的 Next Action 和关键 evidence
  → 员工直接 Call/Text
  → 系统自动记录可观察沟通
  → 员工用一次表单记录线下 Activity + Next Action
  → 员工确认 Outcome
  → Owner 看到“机会 → 行动 → 结果”的 evidence chain
```

该 acceptance slice 的约束如下：

- 历史 lead-only pilot 已被后续决定取代；`MANAGED_TASK_KIND` 中全部七类 objective 现在都具备 Target/Shadow authority capability。它们在具体 Store/环境是否实际启用，只从 rollout status 和 runtime evidence 判断。
- Auto-execution 只有在 Task Policy、evidence、automation tier、per-objective `semanticAutomationMode=target`、runtime gate 和 fresh Guard 全部授权时才执行。`full_auto` terminal seam 在 source 中存在，但 checked-in `pre/prod` Task V3 source gates 仍为 `false`；本文不声称 PROD auto-close 已发布。
- Suggestion 分三条 lane 处理，另有一类根本不是 Suggestion 而是补录事实；**页面上不出现通用 Accept 按钮**：
  - 有明确 evidence、风险低、且可以撤销的变更：系统 `auto_execute`，显示 “set by AI”，并提供一键撤销。
  - 可以马上执行的建议：显示成具体动作，例如“设为下一步”或 “Review & send”。员工执行这个动作，就等于采纳；command 携带 `sourceSuggestionId`。
  - 纯 coaching 或话术：直接展示，允许隐藏，并提供 suggestion 级质量反馈 `helpful/not_relevant`；不要求员工先 Accept。
  - 线下事实和 terminal 变更：使用业务语言确认，例如 “Record booking” 或 “Review close”。这是让员工补录系统看不到的 evidence，不是在审批 AI。
- 同一屏最多显示一条现在就能做的建议。执行时间晚于当前 Next Action 的建议不渲染按钮（最多显示为一行备忘）：MVP 只有一个已经采用的 Next Action（§2.4），装不下第二个未来计划。当前动作完成后，下一次 run 再根据最新事实重新提出。
- Workbench detail 首版只保留三块：行动区（Next Action、关键 evidence、Call/Text）、记录区（`Record activity + plan` 原子操作）、默认可见的 Task Timeline。Timeline 用紧凑记录让员工快速回看已经做过什么；较早历史按需继续加载。Provenance、Policy version 和教学文案不进入日常工作面（§13.4）。
- Owner 报表首版只做诚实计数：发现的机会数、员工 Call/SMS 数、带 provenance 的 `intro_booked`/Outcome 数，以及能沿 evidence → Task → Activity 回溯的结果数。Policy 可以映射 transparent potential value，但必须区分 AI-observed、communication-explicit 和 staff-confirmed；在 membership/billing integration 前，不得称为 recognized dollar revenue。完整 `discovered/assisted/attributed` 模型按 §14.3 分阶段上线。

判断一项能力是否进入 MVP，只看两个问题：它是否直接服务上面的最短闭环；或者它是否属于不能延期的安全正确性底线。Store isolation、DNC、idempotency、append-only audit 和 human-wins 属于底线，即使不直接出现在 UI，也必须首版具备。其他能力不进首版。

一句话原则：**AI 在后台可以复杂；员工面前，方向唯一明确——第一眼永远是“现在做什么”；信息完整但整理好——Task Timeline 和 History 常驻可见。人没有那么笨，不要替员工藏信息，把信息整理好、给出方向，他们自己会做。**

***

## 17. Validation、testing 与 observability

> 这里列的是 **Target contract 的完整验证面**。Current source 已有 contract/unit/fake-model/PostgreSQL production-seam coverage；这只能证明代码边界和 transaction semantics。Human-ratified/live eval 已经开始，但 Gate A 质量闸门未通过；shadow、dual projection reconciliation、UAT 和 release state 仍必须按 [Task V3 Rollout Status](/product-design/v3/tasks-feature/task-v3-rollout-status.md) 与目标环境 evidence 核验。

### 17.1 Contract tests：证明词汇和 Policy 没有漂移

- 每个 `taskKind` 都必须有合法的 result/closure/evidence matrix。
- Prompt vocabulary、Zod schema、backend Policy Guard、UI options 和 reporting mapping 必须从同一个 registry 生成，或者有一致性测试证明它们完全一致。
- 每个 Managed Task Policy 都必须有 create、no-create、auto-close、human-review 和 correction fixtures。
- 遇到 unknown `taskKind` 或 unknown Policy version 时必须 fail closed，不能退化成任意 global result。
- `lead_conversion` 测试必须覆盖三种 display transition、meaningful inbound 直接进入 follow-up、booking request 不等于 completed booking、booking 完成不关闭 Task、booking correction 回到 follow-up，以及三种 `displayType` 都能直接 converted。
- Outcome Guard 必须拒绝 `lead_conversion.resultCode=booked`。Progress reporting 和 Outcome reporting 要分别 versioned，并证明不会 double count。

### 17.2 Domain/API tests：证明写入安全且可回放

- 所有路径都要测试 `store_id` isolation，包括同一个手机号出现在多家 store。
- 测试 dedupe，以及同一个 idempotency key 携带不同 payload 时的 mismatch。
- 测试 optimistic concurrency、stale AI proposal 和 human-wins。
- 测试 Activity 与 Next Action 在同一个 transaction 中成功或失败。
- 测试 business progress、`staff_assertion` 和 Next Action 的 atomicity；员工没有声明实际工作时，不能生成 Activity credit。
- Reopen 后的 work state 只读取 current open period。
- 测试 closed integrity 和 Outcome correction audit。
- 测试 DNC hard stop、wrong number、duplicate 和 superseded。
- Recorder 与 credited staff 必须分别记录，不能重复或错归。

### 17.3 AI eval：把不同错误分开量

Harness 是执行 eval 的工程化 runtime，不是答案本身。Fixture 是有 trigger/evidence/as-of state/expected decision 的 test case；只有业务团队人工批准的 exact fixture 才能进入 Gate A denominator。#1925 已把首批人工签署的历史 journey/checkpoint 和 live-model 评估纳入 source，所以“formal eval 尚未开始”已不正确；但当时的 semantic diagnostic 远未达到 release 闸门，不能 replay 成 Gate A PASS。最新 denominator、score 和未通过项只在 rollout status 文档更新。AI 不能自行 ratify 自己的 expected answer，也不能通过删难题、改 denominator、case-ID/regex hardcode 或偷看未来 evidence 提高分数。

至少单独衡量以下指标，不能合成一个“AI accuracy”：

- Task discovery precision/recall。
- `taskKind` 是否正确。
- Next Action 是否有用，以及 suggestion 最终是 accepted、edited 还是 dismissed。这里记录的是 domain decision；UI 仍使用具体动作，不显示通用 Accept/Edit/Dismiss 三按钮。
- Outcome accuracy。
- Evidence citation accuracy。
- False auto-close rate。
- Staff correction/reopen rate。
- Unnecessary tool-call rate。
- Cross-store leakage 必须为 0。

无论某环境的 terminal authority 当前是 shadow、staff review 还是 `full_auto`，eval 都要单独计算 false auto-close rate；该指标不能反向证明 PROD auto-close 已开启。

### 17.4 Runtime metrics：每次 run 都能解释成本和结果

每个 run 都要记录以下字段：

```text
triggerType
storeId (internal only)
taskKind / displayType / progressCode
creationPolicyVersion / decisionPolicyVersion / outcomePolicyVersion
model / promptVersion / schemaVersion
logicalTurnCount / physicalAttemptCount / schemaRepairCount / retryCount / toolCallCount
inputTokens / outputTokens / estimatedCost（按 physical attempt 逐次归账后汇总）
costStatus / knownCost / missingCostAttemptCount
latency / timeout
proposal
guardDecision + rejectionReason
executedCommand
staffProgressCorrection / staffOutcomeCorrection / reopen
```

成本报告必须同时分开：

- existing Call analysis cost：`triage/classify` 与可选 `verify/coaching`；
- incremental Task cost：Reasoner logical turns、read tools、retry/repair；
- attributable end-to-end cost：从触发到 Guard 决定的全部 physical attempts；
- incomplete cost：provider 没有返回 usage/cost 的 attempt 数量与已知最低成本，不能把缺失值当成 `$0`。

**Historical smoke snapshot（2026-07-31，#1908 source）：** 下表只保留当时用于暴露 contract/成本问题的有上限 OpenRouter smoke，不是当前 score，也不属于 human-ratified Gate A：

| Run                                       |        结果 | Physical attempts | Token / cost evidence                                                  | 结论边界                                                                                                       |
| ----------------------------------------- | --------: | ----------------: | ---------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| Unified Call 10-case synthetic regression |     10/10 |                11 | input 57,392；output 24,981；known cost `$0.007619776`；1 attempt 缺 cost  | 七类 objective、三个 lead boundary 和 unrelated no-op 的主路径 smoke 通过；一次 schema repair，无 retry storm               |
| Lead boundary focused rerun               |       3/3 |                 3 | input 15,630；output 6,288；cost `$0.002133012`，complete                 | completed signup → terminal Outcome；intro booked → Business Progress；pricing question → lead Task creation |
| Formal Provider create                    |      pass |                 3 | input 10,533；output 2,301；cost `$0.0015889988`，complete                | semantic 1 attempt + Reasoner 2 attempts；exact evidence、checkpoint、create/close 与 decision contract 均通过    |
| Legacy fallback full-auto close           | fail safe |                 5 | input 15,191；output 3,167；known cost `$0.0012393124`；2 attempts 缺 cost | standalone resolver 把 completed signup 降级成 creation；Guard 返回 `needs_staff_confirmation`，没有执行错误 close       |

这些数字只能证明 bounded smoke 的当前表现。它们不能替代 20–30 个经过人工 ratify 的历史 customer journey、TEST runtime/shadow、员工 UAT 或 formal V3 accuracy gate。下一轮真实评估必须优先覆盖 terminal Outcome 与 legacy fallback，而不是继续增加无上限 synthetic sampling。

业务层还要同时观察：

- Opportunity discovered。
- Time to first Activity。
- Task Outcome rate。
- Evidence-backed progress rate，例如 Intro booked，以及后续 conversion rate。
- Staff adoption。
- AI suggestion acceptance/edit rate；这是 domain adoption projection，不要求 UI 显示通用 Accept/Edit。
- Outcome verification strength。
- Assisted/attributed value。

“AI 做了更多 mutation”不是成功。成功意味着错误更少、员工负担更低，并且有 evidence 支撑的业务 Outcome 更多。

***

## 18. Open Decisions

下面这些问题还没有拍板。实现者不能把 Current recommendation 当成 approved contract；必须用真实数据、shadow eval 和 business owner 的决定来关闭问题。

| 尚未决定的问题                              | 当前建议                                                                                                     | 它会阻塞什么                 |
| ------------------------------------ | -------------------------------------------------------------------------------------------------------- | ---------------------- |
| 每种 Task 的 exact SLA                  | 放在 Policy/config 中，不写死在 DB。先验证 `lead_conversion` 的 first-response 5m；不能把 outreach SLA 当成整个 Task deadline | queue/alerts           |
| `contactExhaustionPolicy` 的次数和窗口     | 按 taskKind/store policy 配置；一次 no-answer 绝不能结束 Task                                                       | auto-close exhaustion  |
| 第一批 AI auto-close allowlist          | 先对少量 explicit outcomes 启用（shadow 验证通过后），再根据 false-positive 扩大                                            | production authority   |
| Cancellation 中“决定留下”是否足以 close       | communication-confirmed 可以用于 attribution；是否仍需 staff confirmation，由 rollout policy 决定                     | cancellation policy    |
| Outcome 长期是否拆成独立 table               | MVP 使用 Task snapshot 加 Timeline；只有出现复杂 supersession 才拆                                                   | future schema          |
| Flexible Task 将来是否允许 AI create/close | 默认继续由员工拥有；等安全性和价值证据足够后再决定                                                                                | flexible automation    |
| Revenue estimate 使用什么口径              | 先展示 counts 和透明的 estimate；不能冒充 recognized revenue                                                         | owner dashboard        |
| Funnel current stage 冲突时怎样选          | 先明确使用 latest-supported 还是 furthest-ever，并决定 correction 是否回退、多个历史 Tasks 怎样选择；不能让 SQL 作者临场决定               | Funnel API/UI          |
| Funnel historical time 用哪个时间         | 业务 cohort 默认使用 `occurredAt`，audit latency 使用 `recordedAt`；正式 dashboard definition 仍待确认                   | historical reporting   |
| Funnel 是否重算历史                        | 必须发布新的 definition version，并显式 release；保留 old/new cohort reconciliation                                   | historical reporting   |
| `contact_id` 怎样迁移                    | MVP 继续使用 `store_id + normalized contact_phone`；稳定 Contact ID 另行设计和 backfill                              | identity/API evolution |
| 何时建立 first-class Action entity       | 只有业务真的需要多个 simultaneous assignable Actions 时才升级                                                          | future workflow        |
| 何时允许 `external_verified`             | 真正接入并验证 authoritative source 后                                                                           | integration roadmap    |

***

## 19. Selected Target decision log

下面这些是 selected Target contract 已采用的设计选择。它们解释结构理由，但不能用来推导某项代码已部署、某个环境已启用或 rollout gate 已通过。

| Selected design decision                              | 原因                                                                                                                                                                    |
| ----------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 不使用 `Case`                                            | 它与 Task 表达同一件事，只会增加用户和工程认知成本                                                                                                                                          |
| 不使用 rigid `Step`                                      | 销售和客服不是固定程序；Next Action 会随着沟通变化                                                                                                                                       |
| 统一使用 `Next Action`                                    | `Current Action` 只是“当前采用”的描述，不应再成为一个 entity                                                                                                                           |
| Activity append-only                                  | 系统必须知道谁实际做过什么，summary 不能覆盖历史事实                                                                                                                                        |
| Task owns Outcome                                     | Task result 不需要额外的 Case；Activity outcome 与 Task Outcome 必须分开                                                                                                          |
| Funnel 是独立 projection                                 | 客户进展跨越多个时间点，不能用 Task current status 代替                                                                                                                                |
| MVP 不建 Milestone/provider system                      | Current 没有 authoritative booking、membership 或 billing integration                                                                                                     |
| Target 允许 AI evidence-gated close Managed Task        | 保留自动化能力，但必须同时通过 Policy、evidence、automation tier、runtime gate、per-objective authority、fresh Guard、audit 和 human override；具体环境状态只从 rollout evidence 判断                  |
| 一个 stable `lead_conversion` 加三个 `displayType`         | 长期销售目标保持不变，同时让员工一眼区分 Outreach、Follow-up 和 Booked pending conversion                                                                                                   |
| `intro_booked` 是 business progress，不是 Outcome         | Booking 后仍要继续推进或确认 conversion；中间 Funnel 进展不能冒充 Task 已完成                                                                                                               |
| `dedupeKey` 与 creation idempotency 分开                 | Managed objective 需要业务去重，同时允许同一个 Contact 创建多个 open Flexible Tasks                                                                                                     |
| Flexible Task 由员工创建和关闭                                | 给线下工作和未知业务保留出口，也避免 AI 发明不可管理的类型                                                                                                                                       |
| Policy Catalog 是扩展点                                   | 同一个 contract 驱动 prompt、backend、UI 和 reporting，消除 mapping drift                                                                                                        |
| 使用 bounded loop，不使用 perpetual agent                   | 只有 material uncertainty 时才多读 context，控制 cost、latency 和 blast radius                                                                                                   |
| 用两段式 structured generation 实现 bounded loop            | max-2 由 output schema 保证，不依赖 prompt 约定；实现与 provider 无关、可 mock、成本可逐步记录                                                                                                 |
| Domain 先在独立 package 中离线双跑，再做 schema Expand            | 这是已执行的 implementation sequencing；当前 source 已有 domain + Expand schema，rollout 仍需独立 production migration verification                                                   |
| 统一 Call understanding，Reasoner 只做 Task decision       | `classify` 在一次 transcript understanding 中同时产出 Call analysis 与 Task claims，省掉重复 semantic call；Reasoner 消费已确认事实并保持独立 mutation authority，profile enrichment 不塞进 Reasoner |
| Guard 只有三种 decision，权限不足统一降级为 Suggestion/TaskProposal | 不新增第三种审批队列；所有拒绝都必须留痕，不能静默丢弃                                                                                                                                           |

***

## 20. Implementation source map

下表中的路径用来核验 **Current source 实现**，不是 Target 已经部署或启用的证明。

| Concern                                                             | Current source                                                                                                                                                                                                                                                                                                                                |
| ------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Task schema/enums/indexes                                           | `callytics-infrastructure/packages/common/src/db/schema/tasks.ts`                                                                                                                                                                                                                                                                             |
| Per-objective authority / rollback audit                            | `callytics-infrastructure/packages/common/src/db/schema/task-objective-authority.ts`、`callytics-infrastructure/packages/common/src/domain/task-authority-control.ts`                                                                                                                                                                          |
| Current Task taxonomy wording                                       | `callytics-infrastructure/packages/common/src/taxonomy/task/definitions.ts`                                                                                                                                                                                                                                                                   |
| Activity vocabulary                                                 | `callytics-infrastructure/packages/common/src/db/schema/task-progress.ts`                                                                                                                                                                                                                                                                     |
| Timeline ledger                                                     | `callytics-infrastructure/packages/common/src/db/schema/contact-timeline.ts`                                                                                                                                                                                                                                                                  |
| Suggestion store                                                    | `callytics-infrastructure/packages/common/src/db/schema/task-suggestions.ts`                                                                                                                                                                                                                                                                  |
| AI assessment audit                                                 | `callytics-infrastructure/packages/common/src/db/schema/ai-task-decision-assessments.ts`                                                                                                                                                                                                                                                      |
| Unified Call understanding contract                                 | `callytics-infrastructure/lambda/ai-analysis-processor/src/core/stages/classify.ts`、`callytics-infrastructure/lambda/ai-analysis-processor/src/core/stages/pipeline.ts`、`callytics-infrastructure/lambda/shared/task-reasoner/unified-call-understanding-contract.ts`                                                                         |
| Bounded Reasoner runs / SMS cursor                                  | `callytics-infrastructure/packages/common/src/db/schema/ai-task-reasoner-runs.ts`、`callytics-infrastructure/packages/common/src/db/schema/sms-task-reasoner-cursors.ts`、`callytics-infrastructure/packages/common/src/db/schema/sms-task-reasoner-receipts.ts`                                                                                |
| Provider source proof / outbox                                      | `callytics-infrastructure/packages/common/src/db/schema/provider-interaction-source-proof.ts`、`callytics-infrastructure/packages/common/src/db/schema/provider-reasoner-outbox.ts`                                                                                                                                                            |
| TaskProposal approval                                               | `callytics-infrastructure/apps/api/src/routes/tasks/task-proposals.ts`、`callytics-infrastructure/packages/common/src/domain/task-engine/task-proposal-decision-store.ts`                                                                                                                                                                      |
| Domain commands / Policy / Guard / writers                          | `callytics-infrastructure/packages/common/src/domain/task-engine/`、`callytics-infrastructure/packages/common/src/domain/task-policy/`                                                                                                                                                                                                         |
| Neon Target adapter                                                 | `callytics-infrastructure/packages/common/src/domain/task-engine/neon-store.ts`                                                                                                                                                                                                                                                               |
| Shared bounded model adapter                                        | `callytics-infrastructure/lambda/shared/task-reasoner/model-adapter.ts`                                                                                                                                                                                                                                                                       |
| Bounded OpenRouter regression                                       | `callytics-infrastructure/scripts/task-v3-harness/unified-call-live-regression.cli.ts`                                                                                                                                                                                                                                                        |
| SMS production seam                                                 | `callytics-infrastructure/lambda/message-processor/src/sms-reasoner-consumer.ts`、`callytics-infrastructure/lambda/message-processor/src/infrastructure/task-v3-sms-reasoner-consumer.ts`                                                                                                                                                      |
| Call/VoiceMail production seam                                      | `callytics-infrastructure/lambda/shared/task-v3-provider-reasoner-consumer.ts`、`callytics-infrastructure/lambda/shared/task-v3-provider-neon-consumer-store.ts`、`callytics-infrastructure/lambda/shared/task-v3-provider-reasoner-lambda.ts`                                                                                                  |
| Call mechanical runtime                                             | `callytics-infrastructure/lambda/transcribe-processor/src/infrastructure/task-v3-call-runtime.ts`                                                                                                                                                                                                                                             |
| Message mechanical/STOP runtime                                     | `callytics-infrastructure/lambda/message-processor/src/infrastructure/task-v3-message-runtime.ts`、`callytics-infrastructure/lambda/message-processor/src/infrastructure/task-v3-message-dnc-runtime.ts`                                                                                                                                       |
| Contacts Analyzer profile-only AI（无 Task decision authority）        | `callytics-infrastructure/lambda/contacts-analyzer/src/infrastructure/ai-client.ts`、`callytics-infrastructure/lambda/contacts-analyzer/src/core/prompts/output-contract.ts`                                                                                                                                                                   |
| AI context builder                                                  | `callytics-infrastructure/lambda/contacts-analyzer/src/core/prompt-builder.ts`                                                                                                                                                                                                                                                                |
| Legacy compatibility / deterministic backstop / Timeline projection | `callytics-infrastructure/packages/common/src/domain/task-orchestrator/index.ts`、`callytics-infrastructure/lambda/contacts-analyzer/src/core/lead-task-invariant.ts`、`callytics-infrastructure/apps/api/src/routes/tasks/target-projection-contract.ts`、`callytics-infrastructure/apps/web/src/pages/tasks/composables/task-timeline-view.ts` |
| RingCentral input                                                   | `callytics-infrastructure/apps/api/src/services/ringcentral/`                                                                                                                                                                                                                                                                                 |
| Email Lead input                                                    | `market-lead-tracking/src/`                                                                                                                                                                                                                                                                                                                   |
| Target Staff command API                                            | `callytics-infrastructure/apps/api/src/routes/tasks/commands.ts`、`callytics-infrastructure/apps/api/src/routes/tasks/target-staff-outcome.ts`                                                                                                                                                                                                 |
| Workbench / Task UI                                                 | `callytics-infrastructure/apps/web/src/pages/tasks/`                                                                                                                                                                                                                                                                                          |
| Checked-in rollout defaults                                         | `callytics-infrastructure/lib/config/environments.ts`、`callytics-infrastructure/lambda/shared/task-v3-provider-reasoner-lambda.ts`                                                                                                                                                                                                            |
| Historical Gate A design review（2026-07 snapshot）                   | `callytics-infrastructure/scripts/task-v3-harness/TASK_V3_GATE_A_DESIGN_REVIEW.md`                                                                                                                                                                                                                                                            |
| Current human-ratification corpus                                   | `callytics-infrastructure/scripts/task-v3-harness/ratification/LEDGER.json`；当前 score、gate 与 PR state 见 [Task V3 Rollout Status](/product-design/v3/tasks-feature/task-v3-rollout-status.md)                                                                                                                                                   |

产品语义仍以本文为准；machine-readable Policy、schema、Zod/API contracts 和 tests 是 executable source of truth。后续任何 PR 都必须同步更新二者。Source code 实现完成后，仍要用目标环境的 migration/config/runtime evidence 证明 deploy、shadow、cutover 和 business effect，不能让 merge status 代替 production truth。

***

## 21. Glossary

| English term           | 用人话解释                                                                   |
| ---------------------- | ----------------------------------------------------------------------- |
| Task                   | 一件要持续处理到有结果的客户机会、问题或目标                                                  |
| Managed Task           | 由 versioned Policy 管理、可以自动发现并进入统计的核心 Task                               |
| Flexible Task          | 员工自由创建、默认不做自动 attribution 的 Task                                        |
| Next Action            | 这个 Task 当前已经采用的下一步计划                                                    |
| AI Suggestion          | AI 提出的、尚未成为当前计划的 proposal                                               |
| Activity               | 已经真实发生、可以审计的一次工作或互动                                                     |
| Business Progress      | Task 过程中有 evidence 支撑、但还不是最终结果的进展，例如 `intro_booked`                     |
| Display Type           | 员工当前在 queue 中看到的 Task 分类；它由 Policy/evidence 计算，不是 lifecycle，也不是另一个 Task |
| Outcome                | Task 最终发生了什么、为什么停止，以及证据是什么                                              |
| Task Policy            | 某一种 Task 的完整可执行 contract                                                |
| Evidence               | 支撑 Task 创建、更新或关闭的来源事实                                                   |
| Verification           | Evidence 是怎样被确认的，以及能使用多强的措辞                                             |
| Queue State            | API 计算出的 Needs attention、Scheduled 或 Closed view                        |
| Funnel Projection      | 根据现有 evidence 计算出的 Lead/customer progress                               |
| Revenue Attribution    | 对 Retaintive 工作与业务结果之间关系的可解释估算                                          |
| Policy Guard           | 用确定性代码检查权限、状态、evidence、store isolation 和 mapping                        |
| Bounded Reasoning Loop | 一次有明确 trigger、tool/cost budget 和终止条件的 AI run                            |
| Task Proposal          | AI 发现潜在 Task、但尚未创建时保存的 pre-task proposal，供员工审核                          |

***

## 22. Contract review checklist

在继续实现、修改 contract 或评审 Figma 前，逐项回答下面的问题。任何一项为 No，都表示文档或实现已经偏离 selected Target 的边界：

- 全文和产品是否一致使用 Task / Next Action / Activity / Business Progress / Outcome 这五个核心业务概念？
- 是否明确说明 Current 没有 authoritative CRM/provider truth？
- 是否允许员工创建 Managed/Flexible Task、补录线下 Activity、关闭 Task，并纠正错误？
- 是否把 Suggestion 与已经采用的 Next Action 分开？
- 所有 suggestion decision 是否都使用业务语言，并且只显示现在能做的动作？页面是否避免平铺通用 Accept/Edit/Dismiss？
- 是否把 Activity outcome、Task result、closure reason 和 Funnel stage 分开？
- 是否保持 `taskKind=lead_conversion` 稳定，只让三种 Lead `displayType` 随事实变化，并且 booking 不关闭 Task？
- UI、prompt、backend 和 reporting vocabulary 是否都由 Task Policy 生成？
- 所有读写是否都强制 `store_id`？
- AI 是否只能使用 read tools 和 typed commands，而不能 raw DB write？
- AI auto-execution 是否同时通过 Task Policy、resolved evidence、automation tier、runtime gate、per-objective authority、fresh Guard、atomic audit 和 correction/reopen？
- Queue、SLA 和 deadline 是否分别计算，而不是互相冒充？
- 是否避免 fixed Steps、global `closeResult` 和 silent history rewrite？
- 是否能通过 shadow 和 dual projection rollout，而不打断现有客户？
- 是否把 source-ready、enabled、deployed、shadow-verified、cutover、uplift-proven 分开报告，而没有用 PR/CI 状态冒充 production truth？

Contract 文档验收不是“看起来完整”，而是一个第一次接触 Retaintive 的人读完后，能够：

> 准确解释产品价值，画出 Target data model，评估合法 API 与 AI authority，设计 Workbench，并且不会重新引入 Case、固定 Step、假想 provider truth 或 global closeResult。
