# scope 消息模型实现设计

> 规范路径：`specs/message/scope-implementation-design.md`
> 依赖规范：
> - [`specs/message/scope-model.md`](scope-model.md)
> - [`specs/message/delivery-ledger-optimization.md`](delivery-ledger-optimization.md)
> - [`specs/message/delivery-runtime.md`](delivery-runtime.md)
> - [`specs/message/reaction.md`](reaction.md)
> - [`specs/message/rewind.md`](rewind.md)
> - [`specs/message/search.md`](search.md)
> - [`specs/collaboration/collaboration.md`](../collaboration/collaboration.md)
> - [`specs/storage/storage.md`](../storage/storage.md)

规范状态：实现设计。

本文定义把 scope 消息模型落地到代码和 schema 的实现方式。实现采用停机 cutover，不保留历史兼容业务路径，不保留无用代码。

## 1. 实现原则

- 目标代码只保留 scope 模型，不保留并行 `space_*` 消息事实源。
- 这是完整 cutover 重构，不是在旧模型上接功能。
- fresh bootstrap schema 与迁移后的 schema 一致。
- 停机窗口内完成 schema migration、数据搬迁、校验和服务切换。
- schema bootstrap、migration、schema verify 必须统一维护，任何表/索引/约束变更必须三者同步。
- 不为了滚动升级引入长期双写、双读、兼容 helper 或 legacy routing 分支。
- 不允许为了功能保全继续调用旧 routing / store / helper。
- 不允许通过双读、双写、shadow read、胶水 adapter 或 fallback chain 维持旧模型。
- 所有发送入口生成同一种 `MessagePlan`。
- ordinary message fanout、membership、notification，以及 message-plan-owned activity 写入通过 `ApplyMessagePlanTx` / message plan transaction boundary；direct/no-outbox allowlist 例外必须由对应子域 spec/guardrail 显式约束。
- private agent 安全边界由 policy 和 visibility filter 统一执行。
- 所有专业子域继续遵守各自 canonical spec：reaction、rewind、search、delivery ledger、delivery runtime。
- 文档同步属于实现范围；canonical spec、guide、API 文档、公共文档不能留下旧模型口径。
- 功能保全是硬门槛；除非 `scope-model.md` 明确删除某能力，否则不得回退现有功能。
- 功能保全必须通过新 scope schema、Policy、Planner、`ApplyMessagePlanTx` transaction boundary、VisibilityFilter、outbox 等新路径实现；新路径表达不了的能力先补模型和设计，不接回旧代码。
- UI 不是本次 cutover 的独立目标；除非 schema/语义切换、private 展示或功能保全必须，不能做交互和视觉改版。

### 1.1 非目标

- 不做无关 UI 视觉刷新、交互重设计或组件体系调整。
- 不借 scope cutover 删除现有产品能力。
- 不保留过时文档来描述旧行为。
- 不用兼容层、双读双写或旧 helper 掩盖未迁完的代码路径。
- 不用旧 routing/store/helper、shadow read、fallback chain 或胶水 adapter 继续承载业务功能。
- 不在 API、UI、daemon 或临时 helper 中拼接消息域 SQL；核心 SQL 由 repository/query builder 统一拥有。

## 2. 功能保全清单

实现不得丢失已有产品能力。每个 checkpoint 都要用测试或 smoke 覆盖下列能力：

- 文档同步：canonical specs、guide、API 文档、公共文档无过时口径；
- channel、DM、reply thread 普通发消息和读历史；
- reply thread root context 展示；
- channel/thread 成员列表、private member 展示、owner 语义展示；
- human、public agent、private agent 的消息发送；
- public agent auto-wake/no-wake；
- private agent owner authority、grant authority、拒绝未授权 actor；
- direct mention、controlled broadcast、canonical inert parser；
- 单条转发、多条/合并转发、explicit context；
- app/service 可见消息和显式收件人；
- reaction 添加/删除/agent-facing reaction activity；
- rewind recovery point 和 rewind 校验；
- TiDB message search readiness gate 和 visibility filter；
- attachments；
- channel-share message action state；
- read state、unread、archive cursor；
- agent daemon delivery、automatic continuation、history/search/read-check、runtime wake；
- owner_quote source refs / execution context ingress；
- agent outbox status；
- diagnostics / explain；
- idempotency 和重试；
- realtime / WebSocket fanout；
- schema verify、health check、ready check。

任何能力无法在目标模型中表达时，必须先回到 `scope-model.md` 修改模型；不能在实现中用临时旧路径补功能。

## 3. 实现阶段

采用单一集成分支和 checkpoint review。checkpoint 内部可以有短暂未完成子步骤；进入 review 的 checkpoint 必须已经可构建、可验证。

### 3.1 checkpoint A：schema cutover

目标：建立最终 scope schema，删除重复表和旧事实源，并完成最低限度 repository 编译适配。

工作项：

1. 新建最终 bootstrap schema。每张表必须同时定义字段语义、PK、FK、unique、check 和必要索引：
   - `scopes`
   - `scope_channel_profiles`
   - `scope_memberships`
   - `scope_notification_settings`
   - `scope_private_grants`
   - `scope_messages`
   - `scope_message_sources`
   - `scope_activity_events`
   - `scope_fanout_outbox`
   - `attachments`
   - reaction / rewind / search 表按对应 spec 改名或保留 canonical 名称并统一 `scope_id`
   - `scope_share_cards`，包含 channel-share message action state、creator、revoke 生命周期字段和对应索引 / FK
2. 写停机 migration：
   - 搬迁 channel / DM / reply thread 拓扑到 `scopes`；
   - 搬迁 channel 展示信息到 `scope_channel_profiles`；
   - 搬迁消息到 `scope_messages`；
   - 搬迁 source refs 到 `scope_message_sources`；
   - 搬迁 membership / read state 到 `scope_memberships`；
   - 搬迁 notification state 到 `scope_notification_settings`；
   - 搬迁 non-message activity rows 到 `scope_activity_events`；
   - 搬迁 private visibility membership / grant / author interval；
   - 搬迁附件、reaction、recovery、search projection、timeline/share projection；
   - 搬迁 outbox status，并建立到 `scope_activity_events` / message / event ref 的关联。
3. 删除旧表、旧列、旧索引、旧 constraint。
4. 增加 schema verify：fresh schema 与 migrated schema 一致。
5. 同步完成 store/repository 编译适配，保证 checkpoint review 点可构建。

验收：

- fresh DB 和 migrated DB 的表清单、字段、PK/FK/unique/check、索引一致；
- schema verify 能验证关键约束和索引存在；
- 无旧消息事实源表；
- 所有 scope 都有正确 type、parent、root；
- 每个消息都有唯一 `(scope_id, seq)` 和 `(workspace_id, workspace_global_seq)`；
- 非空 `client_message_id` 满足幂等唯一约束；
- non-message activity event 不包含 default/broad reason；
- private visibility interval / grant 有授权来源；
- fanout outbox 表存在且 `ApplyMessagePlanTx` 可写；
- outbox status 表存在且可关联 non-message activity event、message 或 event ref；
- checkpoint review 点 `go test` 编译通过。

### 3.2 checkpoint B：repository 和 store 重构

目标：代码只通过 scope repository 访问消息事实源。

模块边界：

- `ScopeStore`：scope 拓扑、scope profile、scope lookup。
- `MembershipStore`：scope membership、read state、visibility membership 查询。
- `NotificationStore`：notification setting。
- `MessageStore`：message 写入、history 读取、idempotency；负责消息核心 SQL 和索引使用说明。
- `SourceStore`：source refs。
- `ActivityEventStore`：non-message activity。
- `PrivateVisibilityStore`：private agent 可读性的 membership / grant / visibility interval 输入。
- `OutboxStore`：事务后 fanout outbox。
- `TaskContextIngressStore`：task / execution context ingress 查询，保持 task-native 事实源。

删除项：

- API、UI、daemon、runtime 中散落的消息域 SQL 拼接；
- 直接读写旧 message facts 的 helper；
- 以历史 non-message activity event 反推 membership 的 helper；
- 基于 channel roster 直接补 private recipients 的 helper；
- 独立 thread read state / subscription 事实源；
- 默认投递 per-recipient ledger 写入路径。

验收：

- ordinary / outbox-backed producer 必须在事务内写 durable outbox，并显式声明 commit 后的 first-delivery owner/replay policy；inline/store-owned first attempt 必须消费该 committed delivery 并与 outbox completion 收敛，不得存在“只写 outbox、没有 first owner”的 ordinary producer；direct/no-outbox allowlist 例外必须由 named spec/guardrail 显式允许，并具备 durable fact + refetch / daemon-delivery recovery；
- history/search/read access 使用同一 visibility filter；
- history/search/read-check 只读取可见 message/source/context facts；当前事件 delivery 只消费 fanout outbox payload/status，不恢复 server receive cursor；
- 每个新增或迁移查询都有 repository owner、目标索引和放大风险说明；
- 核心查询能通过 explain 或等价验证确认不依赖全表扫描。

### 3.3 checkpoint C：Policy / Planner / transaction boundary

目标：所有发送入口使用统一 MessagePlan。

实现模块：

- `PolicyEngine`
  - scope write policy；
  - mention policy；
  - private authority；
  - grant inheritance；
  - app/service routing boundary；
  - forward mention mode。
- `MessagePlanner`
  - 读取 scope context；
  - 调用 canonical routing-token parser；
  - 计算 membership mutations；
  - 计算 notification mutations；
  - 引用 membership、grant、author interval、source refs、message visibility from membership/grants plus non-message activity visibility、task-native ingress 等 private visibility facts；
  - 计算 non-message activity events；
  - 关联 task / execution ingress refs；
  - 计算 outbox events；
  - 生成 explain reason。
- `ApplyMessagePlanTx` / message plan transaction boundary
  - 作为生产持久化边界，单事务写 message、sources、membership、notification、non-message activity event、outbox；
  - 不恢复独立 DB wrapper；“applier” 只表示事务内应用 `MessagePlan` 的职责边界；
  - production applier callsite 不是按文件放行，而是由 AST guard 以 exact function + count inventory 封闭；`privateagent` ordinary conversation writer 只能沿 `WriteConversationMessageResult` → `writeConversationMessageResult` → `writeScopeMessageToConversationResult` 链路下沉，并且只有最下层函数可以恰好一次调用 `ApplyMessagePlanTxWithSidecarWriter`、保留完整 apply/send result；
  - `internal/store/message` 内部的 lowercase `applyMessagePlanTx` 也是 canonical closed seam：只能由 `ApplyMessagePlanTx` 与 `ApplyMessagePlanTxWithSidecarWriter` 各调用一次；同包 producer 不能绕过 exported profile validation / sidecar-writer policy 直接调用它；
  - ordinary message / outbox-backed fanout 在事务提交后由显式 policy 指定的 API/service caller、app Store committed-message lifecycle 或 outbox worker 执行；
  - direct/no-outbox allowlist 例外不写普通 fanout outbox，必须记录 durable fact 和 refetch / daemon-delivery recovery；
  - outbox-backed fanout 失败由 outbox 重试。

`MessagePlan` 结构：

```text
MessagePlan
  MessageWrite
  SourceWrites[]
  MembershipMutations[]
  NotificationMutations[]
  ActivityEvents[]
  TaskContextIngressRefs[]
  OutboxEvents[]
  Explain[]
```

验收：

- human send、agent send、thread send、DM send、forward、app/service message、explicit context 都形成 `MessagePlan` 并通过 `ApplyMessagePlanTx` 持久化；
- `ApplyMessagePlanTx` 事务内不直接唤醒 runtime、不直接发 WebSocket；
- ordinary message creation 的 runtime wake、daemon handoff、WebSocket 只在提交后执行；direct-first 使用事务产生的 committed delivery，worker-first/replay 使用同一 durable outbox payload；direct/no-outbox allowlist 例外如有 direct projection 也只能在提交后执行，并必须保留 durable recovery；
- policy reason 可用于 explain。

### 3.3.1 outbox worker 边界

`scope_fanout_outbox` worker 负责 worker-first 交付和未完成 direct-first delivery 的 durable replay，包括 policy 允许的 runtime wake、daemon handoff、WebSocket/realtime 和通知 fanout。Direct-first producer 在 commit 后可以使用同一 committed payload 立即做 first attempt，成功后完成 outbox；这不是绕过 durable outbox。显式 direct/no-outbox allowlist 例外不由该 worker 表达。

约束：

- 每个 outbox row 必须有 `fanout_type` 和 `dedupe_key`；
- worker 通过 lease 领取，lease 过期后可被其他 worker 重试；
- retry 使用 `attempts/max_attempts/next_attempt_at`；
- 执行前二次检查 non-message activity event 或 visibility grant 是否已 suppress/revoke；
- direct/no-outbox allowlist 不被误纳入普通 outbox worker；新增 direct/no-outbox 例外必须先进入对应 spec/guardrail；
- 成功写 `delivered_at`，失败写 `last_error` 和下一次重试时间，永久失败写 `failed`；
- outbox 执行结果不改变消息可读性，只改变副作用执行状态。

### 3.3.2 outbox status 边界

ordinary message fanout、visible reply、runtime error 等 outbox-backed 副作用必须写 durable outbox status。Reaction direct fanout 是 direct/no-outbox allowlist 中的显式例外：它可以写 durable `scope_activity_events` 供 daemon-delivery diagnostics / refetch 修复，但不声明 reaction fanout outbox status。

约束：

- message-backed outbox status 关联 `scope_id/message_id`；
- non-message activity-backed outbox status 关联 `scope_activity_events.id`；
- non-message activity 使用 `event_ref_type/event_ref_id` 和可选 `activity_event_id`；
- outbox status 不参与 membership、visibility 或 private authority 判定，只用于诊断、去重和可靠性追踪。

### 3.4 checkpoint D：API / UI / agent delivery/read

目标：外部行为使用新事实源和新安全边界。

工作项：

- API target 解析输出 scope target；
- history API 使用 membership + visibility filter；
- search API 使用相同 visibility filter；
- member list 展示 private member，并标记 private 和 owner；
- private mention 只允许 parent scope 中已有 private member；
- thread root live mention 初始化 thread membership；
- single-forward mention live；multi-forward mention inert；
- app/service visible message 走普通 scope routing 和 explicit routing；
- 删除 server agent `/receive`；agent 当前事件只由 fanout outbox payload/status 经 daemon delivery 进入 runtime，history/search/read-check 只做可读上下文读取。

验收：

- private agent 在 channel/thread 中可见为 private；
- 无 authority 的 actor 不能让 private agent 读到消息、收到 wake 或被指挥；
- private agent 不能通过 history/search 读到未授权消息；
- public agent auto-wake 由 membership + notification + dense cursor 推导；
- direct mention / explicit context 只改变 message fanout reason；reaction 使用 non-message activity event。

### 3.5 checkpoint E：cleanup 和 guardrails

目标：删除旧代码，增加 CI 保护。

删除项：

- 所有旧命名业务事实源；
- 旧 routing helper；
- 旧兼容测试；
- 旧 schema verify 白名单；
- 旧 docs 中和 scope model 冲突的 canonical 描述。

新增 guardrail：

- 禁止 default/broad delivery reason 写入 non-message activity event；
- 禁止新增 message delivery ledger 类表；
- 禁止新增并行 `space_*` 消息事实源；
- 禁止 hidden auto-flow copy、private-context creation backfill、source-ref-as-dedupe、attachment copy；
- 禁止 eager inherited membership / notification rows 自动扩散到未参与 child scope；
- 禁止 human/reaction/task side ledger 复制 dense fanout；
- 检查普通消息只写自身 message row、必要 source/projection 和 outbox；message-backed mention / explicit / broadcast 不写 activity row；
- 检查发送入口只依赖 Planner / `ApplyMessagePlanTx` transaction boundary；
- 检查 canonical parser 被复用，不能重新写 mention parser。

## 4. 数据搬迁设计

### 4.1 scope 拓扑

迁移生成三类 scope：

- channel scope；
- DM scope；
- thread scope。

thread scope 记录：

- parent scope；
- root message；
- root message short id；
- latest seq / latest global seq；
- archive status。

### 4.2 membership、notification 和 read state

迁移规则：

- human / public agent 的 channel membership 搬到 `scope_memberships`；
- private agent 的 channel membership 搬到 `scope_memberships(visibility='private')`；
- thread membership 搬到 thread scope 的 `scope_memberships`；
- notification state 搬到 `scope_notification_settings`；
- channel / DM read state 以原 parent surface 的 participant read cursor 为准；
- reply thread read state 以 thread 专属 read state 为准；如果同时存在 thread scope participant read cursor，二者必须处于同一 thread seq namespace，迁移取 max；如果无法证明处于同一 namespace，migration verify 失败；
- `owner_context_dismissed_through_global_seq` 搬到 membership，并只在同一 workspace global seq 语义下取 max；
- private thread membership 必须能追溯到 parent scope private membership。

校验：

- 每个 notification setting 都有对应 membership；
- 每个 private thread membership 都有 parent scope private membership；
- 没有 orphan membership；
- 没有跨 workspace membership；
- thread read cursor 不被 parent scope seq 污染；
- read state 冲突必须被迁移报告解释。

### 4.3 message 和 seq

迁移规则：

- 每条消息写入 `scope_messages`；
- 保留原 scope 内 seq；
- 保留 workspace global seq；
- 保留 author、message type、content、metadata、client id；
- source refs 迁移到 `scope_message_sources`。

校验：

- `(scope_id, seq)` 唯一；
- `(workspace_id, workspace_global_seq)` 唯一；
- 非空 `client_message_id` 幂等唯一；
- 每条 thread root 指向存在的 parent message。

### 4.4 non-message activity event 和 private visibility

迁移规则：

- reaction 等没有 message row 的非消息 activity 搬到 `scope_activity_events`；
- direct mention、explicit context、task dispatch 等 message-backed reason 不搬到 `scope_activity_events`，只在 message fanout/outbox/outbox status 中表达；
- task / execution context ingress 保持 task-native 事实源，不强塞进 `scope_activity_events`；
- private agent 可读输入搬到 membership / grant / VisibilityFilter author interval；
- default/broad fanout 不搬成 per-recipient event；
- membership 和 notification 搬迁为带 effective seq 的状态区间；
- private delivery 和 private visibility grant 必须补充 authorization source；无法证明授权的历史行只能进入 migration audit 报告，不创建 grant、不扩大 membership、不参与未来 authority；
- 只有显式 allowlist 的 audit 行可以作为迁移通过条件，且 allowlist 不等于未来可读授权。

校验：

- non-message activity event reason 不包含 default/broad；
- non-message activity event 不包含 message-backed recipient rows；
- private visibility grant / membership interval recipient 是 private member；
- private visibility grant 有授权解释；
- migration audit 行不参与未来 private visibility；
- dense unread 可由 membership cursor + notification range 推导；
- diagnostics 不使用当前状态猜历史。

## 5. 权限和可见性设计

### 5.1 actor 分类

Policy 先把 actor 归类：

- human；
- public agent；
- private agent；
- app/service；
- system。

actor 分类只来自 identity/member 系统，不从消息文本或 UI 状态推导。

### 5.2 private authority

private authority 判断输入：

- actor；
- owner；
- target private agent；
- target scope；
- parent scope；
- grant；
- explicit recipient；
- membership visibility。

输出：

- allow / deny；
- authorization source；
- authorized by；
- grant id；
- resolved scope；
- deny reason。

deny 结果必须阻止：

- non-message activity event；
- private readable input；
- runtime wake；
- implicit thread admission。

### 5.3 VisibilityFilter contract

所有读取路径使用同一 `VisibilityFilter` port：

- history；
- search；
- receive dense cursor；
- root context prepend；
- diagnostics preview；
- source ref 展示；
- attachment 读取。

输入：

- viewer member / actor type；
- target scope；
- seek cursor / page size；
- candidate message refs 或 source refs；
- request kind：history、search、receive、diagnostics、root context。

输出：

- visible message refs；
- filtering reason；
- membership interval id / grant id / non-message activity event id；
- pagination cursor；
- continuation state：`has_more` / `candidate_budget_exhausted` 必须显式返回，预算耗尽时即使当前 page 为空也要求调用方用 cursor 继续拉取。

human 和 public agent：

- 在 message seq 上存在有效 scope membership 即可读普通 scope timeline；
- direct mention 是通知边界，不是可读边界；
- membership remove 后不能读取 remove 后的新消息。

private agent：

- membership 只表示 private participant 和 read-state 容器；
- 可读消息必须满足以下至少一项：
  - message author 是该 private agent，且该 message 在它的 membership interval 内；
  - message author / author interval 对该 private agent 可见；
  - request 是 source/root context，且对应 source/root message 通过 source refs / non-message activity / grant interval 对该 private agent 可见；
- 未授权消息不进入它的输入上下文；
- root context、source refs、search snippets 也必须过滤；
- grant revoke / expire 不 retroactive 改写历史可见性窗口；
- filter 必须在 SQL/store 层可复用，不能由 API 层对返回结果临时筛；
- 分页先按可见性过滤再返回 page，避免不可见消息占满 page；请求级 candidate budget 耗尽时返回 partial page 和 continuation state，不能让空 page 被误判为历史结束。

## 6. mention 和 forward 设计

### 6.1 canonical parser

所有 live mention 使用 collaboration canonical routing-token parser：

- fenced code inert；
- inline code inert；
- blockquote inert；
- structured command 在 inert 区域中不触发。

禁止新增独立 mention parser。

### 6.2 forward shape

`routingMentionMode`：

- `normal`：解析 live mention；
- `single_forward`：解析 live mention；
- `multi_forward`：不解析 live mention；
- `explicit_context`：不解析文本 mention，使用 explicit recipient。

## 7. agent delivery/read 和 diagnostics

### 7.1 agent delivery/read

agent 当前事件 delivery 与可读 history/search/read-check 分开：

- current delivery：message write path 计算 durable/wake recipients，并把 payload/reason 写入 `scope_fanout_outbox`；事务提交后由 fanout worker 下发 `agent:start` / `agent:deliver`，daemon accepted 后进入 runtime pending input 或本地 continuation inbox 并自动递交；
- readable context：history/search/read-check 使用 scope membership、private visibility intervals、source refs、non-message activity source-message visibility 和 task-native ingress；
- no offline inbox：server 不提供 agent `/receive`，也不因历史消息可读或机器离线期间累积旧消息而在恢复在线后批量唤醒；
- no cursor advance：agent `read_history` / search 不推进 human read cursor，也不消费 delivery；
- task/execution ingress 不强行复制成 scope non-message activity event；需要审计时使用 task-native fact。

### 7.2 diagnostics

diagnostics 必须能解释：

- 消息是否属于 scope stream；
- recipient 是否在当时 membership interval 中可读，以及当前 notification state 是否允许默认 wake；
- recipient 在对应 seq 是否有有效 membership；
- 已写消息的实际 agent delivery 是否记录在 fanout payload/outbox status；
- non-message activity event 是否存在；
- private authority 是否允许；
- grant / parent grant / explicit recipient 是否参与授权；
- policy 变化来自哪个 membership interval / current notification state；
- private visibility 来自哪个 membership interval / grant / non-message activity event；
- task/execution ingress 来自哪个 task-native fact；
- outbox status 是否记录 wake/daemon handoff/reply/failure；
- fanout 是否已经进入 outbox、是否投递成功。

## 8. 测试矩阵

### 8.1 schema / migration

- fresh schema 和 migrated schema 一致；
- 旧消息事实源不存在；
- scope 拓扑正确；
- membership / notification 无 orphan；
- non-message activity events 无 default/broad reason；
- private visibility interval / grant 有授权来源；
- fanout outbox 可重试；
- outbox status 可关联 message、non-message activity event 或 event ref。

### 8.2 send / routing

- 普通 channel 消息 dense 可读；
- public auto-wake 通过 membership + notification + cursor 进入 receive；
- direct mention 只写 message fanout reason / outbox，不写 activity event；
- private mention 需要 authority；
- no authority 的 private mention 不进入 private input；
- thread root mention 初始化 thread membership；
- root context 不污染 thread read cursor；
- app/service visible message 不获得 private authority；
- public agent ordinary scope message 按 membership + notification + cursor 进入 receive/wake eligibility。

### 8.3 visibility

- private channel member 不读取未授权消息；
- private thread member 不读取未授权 root；
- search 与 history 可见性一致；
- receive 与 history 可见性一致；
- task/execution context ingress 能进入 receive，但不伪装成普通 scope non-message activity event；
- grant revoke 后不 retroactive 改写历史 visibility interval；
- diagnostics 不把缺少 non-message activity event 解释成未递交 dense 消息。

### 8.4 forward / quote

- fenced / inline code / blockquote mention inert；
- 单条转发 mention live；
- 多条转发 mention inert；
- explicit context 使用 explicit recipient。

### 8.5 outbox / fanout

- 事务回滚不产生 runtime wake；
- fanout 失败不会回滚消息；
- outbox 可重试；
- duplicate client id 不创建第二条消息。

## 9. 最终验收标准

实现完成后，必须同时满足以下验收标准，才可以进入最终 cutover PR：

0. **文档、功能和 UI 边界**
   - `SPECS.md`、相关 canonical specs、channel/collaboration guide、API/公共文档都已同步到 scope 目标语义；
   - 仓库中不存在描述旧 space/thread delivery 模型的过时权威文档；
   - 现有功能全部保全，任何功能删除或行为回退都必须先在 scope model 中明确定义；
   - 功能保全全部由新 scope 路径实现，不存在旧 routing/store/helper、双读双写、shadow read、fallback chain 或胶水 adapter；
   - UI 只做 scope/private 语义所需的展示和入口调整，不做无关视觉或交互变化。
1. **schema、索引和迁移**
   - fresh schema 与 migrated schema 完全一致；
   - 每张核心表都有字段语义、PK、FK、unique、check 和必要索引；
   - schema bootstrap、migration、schema verify 三者一致；
   - 旧消息事实源、旧 thread read/subscription 事实源、并行 `space_*` 消息事实源残留为 0；
   - schema verify 覆盖 scope 拓扑、membership、notification、message seq、source refs、non-message activity events、private visibility、outbox、search projection、核心索引。
2. **发送架构**
   - human send、agent send、thread send、DM send、forward、app/service、explicit context 全部只走 Policy / Planner / `ApplyMessagePlanTx` transaction boundary；
   - 业务代码不绕过 `ApplyMessagePlanTx` transaction boundary 写 message、membership、notification、non-message activity event、private visibility fact、outbox 或 outbox status；
   - 普通 dense/broad fanout 不写 per-recipient rows；
   - 所有现有功能都有对应新路径 owner，不能通过旧代码路径补洞；
   - 消息域 SQL 只存在于 repository/query builder，所有核心查询都有索引说明和放大风险评估。
3. **普通 IM 与 public agent 行为**
   - 没有 private agent 参与时，channel、DM、thread 行为等同普通 IM；
   - public agent 普通消息可见性由 membership + cursor 决定；auto-wake/no-wake 由 notification setting + policy 决定；
   - public agent 普通消息不依赖 non-message activity event。
4. **private agent 安全边界**
   - private member 可在具体 scope 中展示为 private；
   - 没有 owner/grant/same-owner-private/explicit recipient authority 的 actor 不能让 private agent delivery/wake、read history、search 或执行指令；
   - private 可读输入只来自 membership interval 内自己的输出、membership/grant/author interval、source refs、non-message activity/task ingress；
   - owner 普通消息不为 private roster 批量写逐消息授权索引；grant revoke / expire 不 retroactive 改写历史 visibility interval。
5. **agent delivery/read**
   - server agent `/receive` 已删除；当前事件 delivery 只来自 fanout outbox payload/status + daemon accepted queue；
   - history/search/read-check 使用同一 private visibility/readable predicate，不推进 agent cursor，不变成 offline inbox；
   - task/execution ingress 保持 task-native fact，不被伪装成普通 scope non-message activity event。
6. **outbox 和 outbox status**
   - ordinary message creation 的 runtime wake、WebSocket/realtime、daemon handoff fanout 均有 durable outbox，并在 commit 后由显式 first-delivery owner 消费 committed payload 或由 worker 首次执行/replay；
   - outbox 具备 dedupe、lease、retry、backoff、suppression 二次检查；
   - direct/no-outbox allowlist 例外（如 reaction direct fanout、rewind invalidation/notice、channel member system projection、hidden Task-description attachment anchor）必须有 durable fact 和 refetch / daemon-delivery recovery，不能被推广成普通 message creation 许可；service-event/external App Task-target discussion 已并入 canonical scope-fanout outbox 生产者，不再是 direct/no-outbox 例外；
   - outbox-backed runtime wake、receive、reply、failure 均有 durable outbox status。
7. **专业子域保全**
   - reaction add/remove、reaction activity、rewind、search readiness、attachments、channel-share message action state 的现有能力不丢失；
   - share cards 的 message 关联、创建者、撤销时间和撤销者是结构化状态，不能退化到 payload 或 message metadata；
   - search 与 history 使用同一 VisibilityFilter；
   - attachment 继续支持 task discussion 和未认领上传。
8. **diagnostics 和历史解释**
   - dense/broad explain 能用 membership interval、current notification state、cursor 解释；
   - targeted explain 能定位 non-message activity event；
   - private explain 能定位 membership interval / grant / non-message activity event 和授权来源；
   - fanout explain 能定位 outbox/outbox status。
9. **guardrail 和测试**
   - CI guardrail 阻止 default/broad per-recipient ledgers、并行旧事实源、隐藏物化路径、绕过 Planner / `ApplyMessagePlanTx` transaction boundary 的发送入口；对 exported/private lowercase writer seams、Agent-create handoff seams 和 `ApplyMessagePlanTx` / `ApplyMessagePlanTxWithSidecarWriter` 使用 exact production caller/function/count inventory，不能靠把新调用放进已 allowlist 的文件绕过；
   - 测试矩阵覆盖 send/routing、visibility、receive、fanout、search、rewind、attachments、migration verify 和停机升级 smoke。
10. **停机升级**
   - 停机 migration 可从备份恢复；
   - migration verify、schema verify、health/ready、关键消息 smoke 全部通过；
   - 升级后不保留旧代码路径、旧表、旧 helper、旧测试或旧文档口径。

## 10. 专业子域 cutover 边界

### 10.1 rewind

rewind 使用 `scope_id` 作为锁定和 tombstone 边界：

- rewind operation 锁定目标 scope；
- reply thread rewind 只 tombstone 该 thread scope 的 suffix；需要影响 parent scope 的动作必须显式锁 parent scope；
- tombstone suffix 时同时清理或 suppress 对应 `scope_activity_events`、visibility grants/memberships、reaction 状态、search projection 关联；
- 重算 `scopes.latest_seq/latest_global_seq`；
- membership read cursor 超过新的 latest seq 时按可见 latest seq clamp；
- recovery point 的 capability、strategy、prepared point、GC 继续遵守 rewind spec。

### 10.2 search

search cutover 保留 TiDB FULLTEXT、parser、readiness gate 和 hot-only 范围：

- projection 表使用 `scope_id` 和 `workspace_global_seq`；
- migration 可以搬迁既有 hot search documents，但 search state 必须 reset 为 not read-ready，不能继承 pre-scope `read_ready` 或 finalize 状态；
- backfill 写入缺失 projection rows；message transaction boundary 在 read-ready 后继续 live 写 projection，避免结果停留在历史快照；
- finalize 创建并验证 `MULTILINGUAL` TiDB FULLTEXT artifact；未满足 `tidb_enable_fulltext_index=ON` 和 TiFlash 前置条件时不得标记 finalize complete；
- promote-ready 只有在 migration/backfill/finalize 均 complete、FULLTEXT artifact 可用且无缺失 projection rows 时才设置 `read_ready=true`；
- API readiness gate 与 state contract 一致：migration/backfill/finalize 必须全部 complete 且 `read_ready=true`，否则表现为搜索不可用；
- `workspace-message-search-backfill`、`workspace-message-search-finalize`、`workspace-message-search-promote-ready`、`workspace-message-search-status` 和 `workspace-message-search-ensure-ready` 负责 workspace-global projection 运维；
- admin command 必须能重新 backfill/finalize/promote-ready，且 ready projection 重跑不降级；
- fresh schema verify 和 migrated schema verify 都检查 search projection / state；
- search 查询使用同一 `VisibilityFilter`。

### 10.3 attachments

附件表保持通用能力：

- 支持 scope message 附件；
- 支持 task discussion attachment；
- 支持 nullable message/task ref 的未认领上传；
- 保留 storage driver、object key、payload 等存储字段；
- message-side cutover 只迁移 message foreign key 和 scope foreign key，不删除非消息附件语义。

### 10.4 collaboration spec 同步

scope 模型正式覆盖旧 collaboration 普通消息 agent 投递语义。目标语义是：

- 普通 scope 消息不写 per-recipient non-message activity event；
- public agent 是否进入当前事件 daemon delivery / auto-wake，由 scope membership、notification setting 和 policy 决定；可读历史由 history/search visibility 决定；
- private agent 仍必须通过 private authority 和 visibility filter。

## 11. agent 分工建议

单一集成分支设置一个 owner agent。其他 agent 做独立 review 或明确写集合不同的 worker。

推荐分工：

- agent A：schema / migration / verify；
- agent B：store / repository；
- agent C：Policy / Planner / transaction boundary；
- agent D：API / UI / receive；
- agent E：tests / guardrails / cleanup；
- reviewer agents：每个 checkpoint 独立 review。

原则：

- worker 写集合不重叠；
- owner agent 负责最终 merge；
- checkpoint 之间保持 worktree 可构建；
- 每个 checkpoint 都更新 review context；
- 不把临时兼容层合入主线。

## 12. 停机升级流程

1. 冻结写入入口；
2. 备份数据库；
3. 停止旧服务；
4. 执行 schema migration 和数据搬迁；
5. 执行 schema verify；
6. 启动新服务；
7. 执行 health check；
8. 执行消息 smoke test：channel、thread、private mention、public auto-wake、agent daemon delivery、history/search；
9. 解除写入冻结。

失败处理：

- migration 未开始：可重启旧服务；
- migration 已开始：使用备份恢复或继续完成 cutover；
- 新服务启动失败：保留停机状态，修复后继续部署，不回到半迁移旧代码。

## 13. 主线合并策略

采用一个最终 cutover PR 合入主线。实现过程中可以在同一 integration branch 上按 checkpoint 做 review slice，但这些 slice 不单独合入 main。

原因：本设计采用停机 cutover，不保留双读、双写、兼容 helper 或旧模型业务入口；把 schema、store、planner、API、UI 拆成多个主线 PR 容易产生不可运行的中间态或隐藏兼容路径。

最终 PR 必须满足：

- 可构建；
- 可运行；
- fresh schema 与 migrated schema 一致；
- 不引入长期兼容路径；
- 不留下旧模型业务入口；
- 不包含旧 routing/store/helper 业务调用、shadow read、fallback chain 或胶水 adapter；
- 包含必要 tests / guardrails；
- 完成文档同步检查；
- 完成 schema bootstrap / migration / verify 一致性检查；
- 完成核心查询索引和 explain / 放大风险检查；
- 完成 UI diff 检查，确认没有无关视觉/交互改动；
- 完成停机升级 smoke test。
