# 任务卡前端字段变更与路由说明（开发共识版）

> 对比基线：`任务卡前端展示字段表 3.0.xlsx`  
> 当前规则基线：2026-07-10 17:50（Asia/Shanghai）  
> 业务共识基线：2026-07-10（房型/Rate Code、Parent split、Note/Trace、Rooming List/TA Recorder、人工复核）  
> `booking-desk-event.skill` SHA-256：`b5f33e17c642fefc11b41934cdd657ff2df25ea50b544c7a44424c5d3d977192`

> 文档定位：本文是信息系统 adapter/任务卡开发的确定性交接契约。如项目 reference 中仍存在旧的含混表述，本文中已标记为“已确认业务决策”的规则优先供开发实现，后续再回写上游 skill。

## 1. 开发先看结论

1. 当前 Agent **没有**为业务结果直接输出 `result_type`、`task_type`、`task_subtype`。普通业务的真实路由键是 `message_events[].event_type`；只有 S10/S99 原生输出 `result_type=source_message_review_notification`。
2. 三元组应由信息系统 adapter **按每个 `message_events[i]` 派生**，不能在邮件根对象只算一次。一封邮件可以同时生成多张候选卡。
3. `message_events[]` 是候选事件，不是真实 TaskCard；信息系统仍需做 Case 匹配、Preflight、去重、锁和状态检查后再建卡。
4. S10、S99 与 `unhandled_current_intents[]` 都是展示路径，不创建业务 TaskCard。
5. 业务 `Need Manual Review` 与 S99 不是同一种复核：前者是 `business_event_review`，后者是 `main_agent_entry_review`。
6. 房型或 Rate Code 无法唯一映射时，默认转业务人工复核；唯一例外是 Parent split 中某个 child 的房型原文仅为 `SUITE`，该 child 可保留为 New Booking 候选事件，但不得成为可执行任务。
7. Parent split 固定产生 **1 个 Parent Group Cancel Booking 候选 + N 个 Child Group New Booking 候选**，不经过 `Allotment Maintenance`。
8. 每个 Rooming List 目标必须派生一个与该 group/预订明确绑定的 TA Recorder。
9. 修改已有结构化订单字段/备注走 Update；新增补充事实、要求或安排走 Trace；`Note` 不再开放新任务卡。
10. 业务人工复核不按原业务类型细分卡型。进入 `manual_review` 后，卡内业务字段全量可编辑；原邮件、证据、系统 ID 和审计字段仍只读。

### 1.1 已确认业务决策

| 主题 | 最终规则 | 开发不得做的事 |
| --- | --- | --- |
| 房型/Rate Code 歧义 | 默认 `Need Manual Review` | 不得猜 PMS 房型或 Rate Code |
| Parent split + raw `SUITE` | 只对该 child 保留 `room_type_raw=SUITE`，`pms_room_type_code=null`，`requires_downstream_hard_validation=true` | 不得填 `SU1/SU3/SU6`；不得创建可执行任务或写 PMS |
| Parent split | Parent 是 Group Block 取消；children 是 Group Block New Booking | 不得路由到 `Cancel Allotment` 或 `Allotment Maintenance` |
| Rooming List | 每个已确认目标必须同时产生 1 个 Rooming List + 1 个 TA Recorder | 不得按邮件/workbook/sheet 只产生 1 个 TA Recorder |
| 修改备注 vs 新增补充 | 修改已有结构化值走 Update；新增信息走 Trace | 不得用 `Note` 承接普通补充信息 |
| 业务人工复核 | 统一人工复核卡，业务字段可编辑 | 不得依赖 `intended_event_type` 拆分复核卡 |

当前正式契约以以下文件组合为准：

- `prompts/main_agent_prompt.md`
- `skills/booking-desk-event/SKILL.md`
- `skills/booking-desk-event/references/00-output-contract.md`
- `skills/booking-desk-event/references/01-current-history-boundary.md`
- `skills/booking-desk-event/references/03-current-content-completeness.md`
- `skills/booking-desk-event/references/02-event-routing-map.md`
- 各业务 reference

`task_plan.md`、`findings.md`、`progress.md` 是累积工作日志，不是接口契约。`.skill` 包也不包含 Main Agent prompt，部署时两者必须同步。

## 2. 信息系统标准三元组

下表是本次确认冻结的 **adapter 输出**。其中 `normal_task`、`manual_review` 延续 3.0 的信息系统语义；`source_message_review_notification` 来自当前上游；`unhandled_current_intent` 是为非任务展示新增的下游枚举。

`result_type` 是本文和 3.0 表使用的标准名。如信息系统实体内部字段名为 `task_result`，必须做同义归一：`task_result := result_type`；不得当成第四个独立路由字段。信息系统的人工复核判别组合固定为 `task_type=Fallback + task_result=manual_review`，`task_subtype=business_event_review`。

| result_type | task_type | task_subtype | 上游判别 | 信息系统具体去向 | 是否建业务卡 |
| --- | --- | --- | --- | --- | --- |
| `source_message_review_notification` | `Message Notification` | `S10` | 根 `route_code=S10` | 源邮件查看通知组件 | 否 |
| `source_message_review_notification` | `Message Notification` | `S99` | 根 `route_code=S99` | 源邮件查看通知组件 + 入口复核信息 | 否 |
| `normal_task` | `New Booking` | `new_fit_reservation` | `event_type=New Booking` 且 `extracted_fields.booking_object_type=FIT Reservation` | New Booking 卡（FIT） | 字段校验通过后为候选卡 |
| `normal_task` | `New Booking` | `new_group_block` | `event_type=New Booking` 且 `extracted_fields.booking_object_type=Group Block` | New Booking 卡（Group Block） | 字段校验通过后为候选卡 |
| `normal_task` | `New Booking` | `new_allotment_control_block` | `event_type=New Booking` 且 `extracted_fields.booking_object_type=Allotment / Control Block` | New Booking 卡（Allotment / Control Block） | 候选卡，Preflight 后 |
| `normal_task` | `Update Booking` | `update_booking_amendment` | `event_type=Update Booking / Amendment` | Update Booking 卡；修改项另外展示 | 候选卡，Preflight 后 |
| `normal_task` | `Cancel Booking` | `cancel_fit_reservation` | `event_type=Cancel Booking` 且 `extracted_fields.cancel_object_type=fit_reservation` | Cancel Booking 卡（FIT） | 字段校验通过后为候选卡 |
| `normal_task` | `Cancel Booking` | `cancel_group_block` | `event_type=Cancel Booking` 且 `extracted_fields.cancel_object_type=group_block` | Cancel Booking 卡（Group Block） | 字段校验通过后为候选卡 |
| `normal_task` | `Cancel Booking` | `linked_parent_release_after_child_split` | `event_type=Cancel Booking`；`cancel_object_type=group_block`；`relationship_type` 同名 | Cancel Booking 卡（Parent Group） | 候选卡，必须下游硬校验 |
| `normal_task` | `Cancel Allotment` | `cancel_allotment_control_block` | `event_type=Cancel Allotment` | Cancel Allotment 卡 | 候选卡，Preflight 后 |
| `normal_task` | `Voucher Received` | `lian_tai_credit_voucher` | `event_type=Voucher Received` | Voucher Received 卡 | 候选卡，Preflight 后 |
| `normal_task` | `Payment Evidence` | `payment_evidence` | `event_type=Payment Evidence` | Payment Evidence 卡 | 候选卡，Preflight 后 |
| `normal_task` | `Rooming List` | `rooming_list` | `event_type=Rooming List` | Rooming List 卡 | 候选卡，Preflight 后 |
| `normal_task` | `Amend Group Code` | `amend_group_code` | `event_type=AMEND GROUP CODE` | Amend Group Code 卡 | 候选卡，Preflight 后 |
| `normal_task` | `Trace / Reservation Notes` | `extra_bed` | `event_type=Trace` 且 `extracted_fields.trace_subtype=extra_bed` | Trace 卡（Extra Bed） | 候选卡，Preflight 后 |
| `normal_task` | `Trace / Reservation Notes` | `general_request` | `event_type=Trace` 且 `extracted_fields.trace_subtype=general_request` | Trace 卡（General Request） | 候选卡，Preflight 后 |
| `normal_task` | `TA Recorder` | `maintain_ta_recorder` | `event_type=TA RECORDER`；与同目标 Rooming List 关联 | TA Recorder 卡（从对应 Rooming List 派生） | 每目标必产生 1 张；Preflight 后 |
| `normal_task` | `Invoice Generation` | `invoice_generation` | `event_type=Invoice Generation` | Invoice Generation 卡 | 候选卡；业务字段待冻结 |
| `normal_task` | `Invoice Received` | `invoice_received` | `event_type=Invoice Received` | Invoice Received 卡 | 候选卡；业务字段待冻结 |
| `normal_task` | `Payment Notice` | `payment_notice` | `event_type=Payment Notice` | Payment Notice 卡 | 候选卡；业务字段待冻结 |
| `normal_task` | `Manual RateCode` | `manual_rate_code` | `event_type=Manual RateCode` | Manual RateCode 卡 | 候选卡；业务字段待冻结 |
| `manual_review` | `Fallback` | `business_event_review` | `event_type=Need Manual Review` 且 `manual_review.review_record_type=business_event_review` | 统一业务人工复核卡；业务字段全量可编辑 | 是，但不得执行业务写入 |
| `unhandled_current_intent` | `Unhandled Current Intent` | `requires_business_approval_or_unsupported_task_card` | 每个 `unhandled_current_intents[i]` | 邮件详情内未覆盖意图展示块 | 否 |

说明：

- `task_type` 保留 3.0 已存在的信息系统卡名；上游 `event_type` 的大小写和空格必须按当前契约原值匹配。
- New Booking 的 FIT/Group 对象枚举和普通 Cancel 的 `extracted_fields.cancel_object_type` 按本文第 5 节直接冻结；不能靠前端猜测。
- Update 的多种修改可以同时存在，因此统一使用一个卡 subtype；不得再用单个 action token 决定卡型。
- 人工复核统一使用 `manual_review + Fallback + business_event_review`。前端只需通过 `result_type=manual_review`（或内部别名 `task_result=manual_review`）切换到全量业务字段可编辑模式，不再需要 `manual_review.intended_event_type`。
- Invoice、Payment Notice、Manual RateCode 已进入支持事件目录，但详细 payload 尚未完整冻结。首版只能使用通用事件头、目标、证据和人工复核字段。

### 2.1 Parent split 的确定性事件集

Parent split 不是 `Allotment Maintenance`，也不是一张复合卡。必须在同一封邮件的 `message_events[]` 中产生：

1. 每个 child group 一个 `New Booking`：
   - `booking_object_type=Group Block`
   - adapter 三元组为 `normal_task + New Booking + new_group_block`
   - `case_keys.group_code=<child_group_code>`
2. 一个 parent group `Cancel Booking` 候选：
   - `cancel_object_type=group_block`
   - adapter 三元组为 `normal_task + Cancel Booking + linked_parent_release_after_child_split`
   - `case_keys.group_code=<parent_group_code>`
   - `child_group_codes[]` 包含全部 children，`related_source_event_indices[]` 指向全部 child New Booking 事件
   - `requires_downstream_hard_validation=true`

Parent 取消事件只是候选。下游确认 parent group、全部 child 关系和 PMS 当前状态前，不得创建可执行取消任务，不得写入 PMS。

### 2.2 Parent split 的 generic `SUITE` 唯一例外

只有当一个 child 的唯一不确定项是房型原文 `SUITE` 时，该 child 才可继续输出 New Booking 候选事件：

```json
{
  "extracted_fields": {
    "room_type_raw": "SUITE",
    "pms_room_type_code": null
  },
  "requires_downstream_hard_validation": true
}
```

- 不得猜测或填写 `SU1`、`SU3`、`SU6`。
- 下游确认具体 PMS 房型代码前，该 child 只是不可执行候选，不得写 PMS。
- 校验失败时，只将该 child 转业务人工复核；不得阻塞或丢弃同邮件中其他已合格的 child 事件。
- Rate Code 不唯一不在此例外内；仍必须转业务人工复核。

## 3. Adapter 路由顺序

```text
1. 如果根 result_type == source_message_review_notification：
   - route_code 必须是 S10 或 S99；映射通知组件；不遍历业务卡。

2. 否则按业务根处理：
   - 校验五个根字段存在；
   - 遍历 message_events[]，每个 event 独立计算一组三元组；
   - 遍历 unhandled_current_intents[]，每个 item 生成一个展示块；
   - case_candidates[] 和 extraction_warnings[] 不参与任务卡路由。

3. 任意稳定判别字段缺失或出现未知 event_type：
   - 对该 event fail closed，记录接口契约错误并展示源邮件；
   - 不得按房量、关键词、附件名等在前端重新执行 AI 业务判断；
   - 不得为该无效 event 自动创建 TaskCard；根结构仍合法时，其他合法 sibling events 继续独立处理。
```

关键判别：

- New Booking：必须读取 `extracted_fields.booking_object_type`。
- Update：固定 subtype=`update_booking_amendment`；具体修改用 action 数组或 `before_after[]` 展示。
- Cancel：先判断 parent split 的 `relationship_type`；命中时 `cancel_object_type` 必须是 `group_block`，再处理其他普通 cancel object。
- Trace：只允许 `extracted_fields.trace_subtype=extra_bed|general_request`。
- Need Manual Review：必须同时检查 `manual_review.review_record_type=business_event_review`。
- S99 的 `main_agent_entry_review` 只展示在 S99 通知中，不能创建 Fallback 业务卡。
- Unhandled Current Intent：`text_raw` 和 `visible_message` 都必须展示；前者是证据原文，后者是给用户的中文提示。

### 3.1 “契约错误”和“人工复核”不再混用

| 情况 | 系统处理 | 该项自动业务卡数 |
| --- | --- | --- |
| 未知/禁用 `event_type`，或已声明某路由但必填判别字段缺失 | 记录 `adapter_contract_error`，保留并展示源邮件；该 event 不转业务复核 | 0 |
| 业务类型已识别，但房型/Rate Code/目标/证据存在业务歧义 | 生成统一 `manual_review + Fallback + business_event_review` 卡 | 1 张人工复核卡，0 张可执行卡 |
| Parent split 的业务意图已确认，但 parent group 或 parent-child 绑定无法确认 | 该 parent 取消转统一业务人工复核；合法 child events 仍独立处理 | parent 可执行卡 0；人工复核 1 |
| event 声明 `linked_parent_release_after_child_split`，但 `parent_group_code/child_group_codes/related_source_event_indices` 等契约字段缺失 | 这是 payload 契约错误，不是业务歧义；记录错误并拒绝该 event | 0 |

因此，开发文档中不再使用“0 卡/复核”这类二选一表述。契约错误和业务不确定必须按上表分开。

## 4. 3.0 之后已发生的字段变化

### 4.1 已新增，开发必须消费

| 字段/结构 | 变化 | 开发动作 |
| --- | --- | --- |
| `source_message` | 所有合规结果新增源邮件身份 | 根级保存并用于原邮件跳转 |
| `source_message.source_message_id` | 升级为非空必填，不得猜测 | 缺失视为上游/基础设施错误 |
| `message_events[]` | 邮件级多事件数组 | 每个 item 独立路由；不能一封邮件只建一张卡 |
| `case_candidates[]` | 新根字段 | 保留；item schema 未冻结，暂不消费 |
| `extraction_warnings[]` | 新根字段/语义收窄 | 只显示解析/OCR/抽取告警，不建卡 |
| `unhandled_current_intents[]` | 新增第五个业务根字段 | 每项同时展示 `visible_message` 和 `text_raw`，永不建卡 |
| `message_events[].event_role` | 新增事件角色 | 审计展示；当前常见值 `travel_agent_request` |
| `message_events[].current_or_history` | 新增证据边界 | 正常新事件必须为 `current` |
| `message_events[].source_event_index` | 新增批次内关系索引 | 仅用于事件关联，不是 TaskCard ID |
| `case_keys.reservation_number` / `block_code` | 通用目标 key 扩展为四键 | 与 group/confirmation 一起固定保留 |
| `file_references[]` | 通用文件引用 | 与 `attachments[]` 分开归一，缺失补 `[]` |
| `context_used` | 新增历史/系统证据审计对象 | 只做审计和硬校验，不做卡型推断 |
| `related_source_event_index` | 新增单来源关系 | linked Trace 等场景使用 |
| `related_source_event_indices[]` | 新增多来源关系 | parent release 关联多个 child 时使用 |
| `related_event_type` | 新增关联事件类型 | 关系展示 |
| `relationship_type` | 新增关系类型 | `linked_trace`、`linked_parent_release_after_child_split` 或 `derived_ta_recorder_from_rooming_list` |
| `requires_downstream_hard_validation` | 新增下游硬校验开关 | 为 true 时禁止自动执行 |
| `extracted_fields.room_type_raw` | 将房型原文与 normalized/PMS code 分开 | 始终保留原文；不再用单一 `room_type` 同时表示原文和标准值 |
| `extracted_fields.pms_room_type_code` | 从默认必填改为“唯一映射后才可填” | 歧义时保持 `null`；Parent split raw `SUITE` 例外中禁止填猜测值 |
| `extracted_fields.nights` | 住期新增晚数 | 与 arrival/departure 一起显示/校验 |
| `extracted_fields.date_evidence` | 新增酒店/行程/动作/sheet 原始日期证据 | 复核区展示；action date 不是入住日期 |
| `extracted_fields.trace_items[]` | Trace 新增结构化明细 | 同一目标仍是一张 Trace，不按 item 拆卡 |
| `trace_items[].category/text_raw/service_date/service_period_raw/pax/notify_departments` | Trace 明细字段新增 | 按原顺序展示 |
| `extracted_fields.requires_rate_update` | Extra Bed 新增费率更新意图 | 只表示意图，不计算价格 |
| parent release/cancel 一组字段 | 新增 parent/child、release reason、候选标记 | 固定映射 Parent Group Cancel Booking 特殊候选卡，使用硬校验 |
| TA Recorder 派生关系 | 每个 Rooming List 目标必须派生 | `related_source_event_index=<Rooming List index>`；`related_event_type=Rooming List`；`relationship_type=derived_ta_recorder_from_rooming_list`；两者 `case_keys` 必须一致 |
| QBD/LianTai row evidence | 新增 sheet/row/highlight/status/raw date/room/price 等证据 | 复核区展示；一个有效行一个事件 |
| `rate_code_result.manual_price_reason_code` | 手工价原因新增 | 手工价提示 |
| `rate_code_result.price_evidence[]` | 价格证据新增 | 审计/复核展示 |
| Fix Charge item 扩展字段 | 新增 amount/currency/unit/quantity/total/raw/evidence/follow-up | 保留但不自动调用 TBD 工具 |
| `additional_operations[]` | 新增后续操作意图 | 只显示 pending，不直接执行 |
| S10/S99 notification 结构 | 新增 route/assessment/notification/user-decision | 统一源邮件查看组件 |

### 4.2 已修改或迁移

| 3.0 字段/枚举 | 当前处理 | 开发动作 |
| --- | --- | --- |
| 根级/卡级旧字段路径 | 业务字段现在位于 `message_events[i]` | normalizer 加事件数组前缀 |
| `result_type/task_type/task_subtype` | 业务 Agent 不再原生输出 | adapter 逐 event 派生 |
| 信息系统内部 `task_result` | 如存在，仅是 `result_type` 的别名 | normalizer 统一为 `task_result := result_type` |
| `Update Booking` 多个 action 作为 subtype | 一个 Update 可多 action | subtype 固定 `update_booking_amendment`，动作另存数组/明细 |
| 修改已有备注/结构化字段 | `Update Booking / Amendment` | 使用 `update_actions[]` 和 `before_after[]` 展示改前/改后 |
| 新增补充事实、要求、安排 | `Trace` | 使用 `trace_items[]`，不转 `Note` |
| `cancel_allotment_control_block` 属于 Cancel Booking | 当前有独立 `Cancel Allotment` | 迁移到新卡 |
| `bank_transfer_slip` 属于 Voucher Received | 当前银行/现金/交易凭证是 `Payment Evidence` | 迁移到新卡 |
| Rooming List 四个 subtype | 当前是同一名单事件的证据同义词 | 统一 subtype=`rooming_list`，原文留 evidence |
| Rooming List → TA Recorder | 从“可能派生”收口为“每目标必须派生” | 每个 target 产生一对关联事件，保留同一目标键和 Rooming List 关系 |
| Parent split 路由 | 旧文档使用 release/cancel 混合描述 | 固定 child Group New Booking + parent Group Cancel Booking；不进入 Cancel Allotment/Allotment Maintenance |
| 房型/Rate Code 不唯一 | 旧文档写成“人工复核或 downstream validation” | 默认人工复核；只保留 Parent split child raw `SUITE` 这一个硬校验例外 |
| 旧 `extracted_fields.room_type` | 拆分为房型原文、normalized 值和 PMS code | 原文迁移到 `room_type_raw`；PMS code 仅在唯一映射时填写 |
| `Trace / Reservation Notes` 上游 task name | 当前 `event_type=Trace` | adapter 映回旧信息系统卡名 |
| `linked task` Trace subtype | 当前是 `relationship_type=linked_trace` | subtype 仍必须是 extra_bed/general_request |
| `parent_source_event_index` | 当前改用通用 related 字段 | 迁移到 `related_source_event_index/indices` |
| `post_confirm_action` | 当前改为 `post_confirmation_intent` | 字段改名；仍是 pending intent |
| Rate Code 固定下拉全集 | 当前 reference 仅为 Typical/已知规则 | 不得硬编码文档示例为封闭枚举；使用配置中心或可编辑文本 |
| Fix Charge `charge_type=additional_charge` | 当前为 `fixed_charge` | 更新枚举 |
| Fix Charge `pricing_mode=unit_price` | 当前为 `unit` | 更新枚举 |
| 业务人工复核只展示 reason/known fields | 当前固定九字段结构 | 完整显示 missing/blocking/conflict/action/evidence/known fields；业务字段全量可编辑 |
| S10 状态名 `no_booking_action_detected` | 值兼容保留，但语义改为“未匹配支持事件” | 必须展示原邮件并要求用户决定，不能自动关闭 |

### 4.3 废弃、禁止或暂不开放

| 旧值/组合 | 状态 | 替代 |
| --- | --- | --- |
| `informational_message + Message Notification + thank_you/fyi/...` | 废弃业务卡路由 | 裸礼貌/裸 FYI → S10；具体预订信息 → Trace；输入不足 → S99 |
| `Fallback` 的旧 routing subtype | 废弃为路由主键 | S10、S99 或业务 Need Manual Review 三分流 |
| `manual_review + 各业务 task_type + manual_review` | 废弃 | 统一 `manual_review + Fallback + business_event_review`；不再新增 intended event type |
| `new_booking` | 废弃直接路由 | 必须归一成 FIT/Group/Allotment 三类之一 |
| `new_allotment` | 兼容 alias | 归一为 `new_allotment_control_block` |
| `update_trace_or_guest_request` | 废弃 | 独立 Trace |
| `update_group_block_linkage` | 废弃模糊值 | old→new code 用 Amend Group Code；parent split 用 child New + parent Cancel |
| Trace subtype=`linked task` | 废弃 | 使用 relationship 字段 |
| Trace subtype=`任务卡展示编辑矩阵` | 3.0 数据污染 | 删除 |
| `No Action`、`S000`、`S999` | 非法 | 只允许 S10/S99 |
| `Note` | 禁用；仅作为旧契约历史文字 | 普通补充信息统一 Trace；不开放新卡 |
| `Allotment Maintenance` | 禁用；无独立 producer/schema/任务卡 | 修改已有 Allotment/Control Block 走 `Update Booking / Amendment` + `update_allotment_control_block`；Parent split 走 child New + parent Cancel |
| Fix Charge 独立任务 | 非法 | 只作为 New/Update 内嵌字段和后续意图 |

## 5. 开发可直接收口的技术契约（无需再等产品确认）

### 5.1 路由和标识字段

| 项目 | 必须实现的固定规则 |
| --- | --- |
| New Booking 对象判别 | 强制 `extracted_fields.booking_object_type`，枚举固定为 `FIT Reservation / Group Block / Allotment / Control Block` |
| Cancel Booking 对象判别 | 强制 `extracted_fields.cancel_object_type=fit_reservation\|group_block`；普通 Allotment 取消必须使用 `Cancel Allotment`；Parent split 的 parent 固定为 `group_block` |
| Update 修改项 | 冻结 `extracted_fields.update_actions[]`，允许多值；它只用于展示修改项，不用作卡 subtype |
| `result_type/task_result` | 对外契约使用 `result_type`；信息系统内部若使用 `task_result`，只做同义别名归一 |
| 业务人工复核 | 固定 `result_type/task_result=manual_review`、`task_type=Fallback`、`task_subtype=business_event_review`，不增加 `intended_event_type`；业务字段全量可编辑，证据/审计/系统字段只读 |
| 事件关系 ID | `source_event_index/related_*` 仅在本次 Agent 输出内建立关系，绝不是 TaskCard ID 或 PMS ID |

### 5.2 字段归一

| 项目 | 必须实现的固定规则 |
| --- | --- |
| `attachments[]` / `file_references[]` | 两个字段分开保留；缺失时 normalizer 补 `[]`，不得合并成一个字段 |
| `manual_review` | 普通事件缺失时补 `null`；`Need Manual Review` 必须包含完整九字段对象，不完整则契约错误 |
| `case_keys` | 固定保留 `group_code/confirmation_number/reservation_number/block_code` 四键，无值用 `null`，不得猜测 |
| `unhandled_current_intents[]` | 每项必须保留并展示 `text_raw`、`visible_message`、`requires_user_decision=true`；不建卡 |
| `source_message.source_message_id` | 非空必填；缺失时整个根结果按上游/基础设施错误拒绝，不得创建卡或 S10/S99 通知 |
| `case_candidates[]` / `extraction_warnings[]` | item schema 冻结前只保留/展示，不参与任务创建 |

### 5.3 执行安全

| 项目 | 必须实现的固定规则 |
| --- | --- |
| `requires_downstream_hard_validation=true` | 该 event 只能停留在候选/待确认状态；禁止变成可执行 TaskCard，禁止写 PMS |
| 房型/Rate Code 歧义 | 默认转业务人工复核；不得由前端、adapter 或配置默认值猜测 |
| Parent split raw `SUITE` | 仅保留 raw + `null` PMS code + hard validation；失败后仅转该 child 人工复核 |
| Parent split parent cancel | 必须检查 parent group、全部 child relation、去重/锁/当前 PMS 状态；候选事件不等于已取消 |
| 未知/禁用 event | 逐 event fail closed，记录 `adapter_contract_error`，不自动转业务人工复核，不影响其他合法 sibling events |
| 最终输出 | 直接解析结构化 JSON 对象；不依赖 JSON 文件、包装层、下载链接或 output mode |

### 5.4 尚未冻结的专属编辑区

Invoice Generation、Invoice Received、Payment Notice、Manual RateCode 的专属 `extracted_fields` 仍不完整。Cancel、Rooming List、TA Recorder 也没有完整覆盖旧 3.0 的全部专属字段路径。在新 schema 冻结前：

- 允许开发通用候选卡头、目标、证据和人工复核区。
- 不得自行发明专属字段、枚举或必填条件。
- 建议新增 `schema_version`；当前先以本文日期和 skill SHA-256 锁定联调基线。

## 6. 明确的非法组合与 fail-closed 规则

以下情况必须拒绝自动建卡：

- 在邮件根只生成一个三元组，忽略多个 `message_events`。
- S10/S99 创建业务 TaskCard。
- 已匹配支持事件后仍输出或转换成 S99。
- `unhandled_current_intents` 创建 TaskCard。
- 清楚但不支持的业务意图被转换成 Need Manual Review。
- history-only 内容创建事件、Trace 或未覆盖意图。
- 多个 group code 塞进一个 event。
- bank slip 继续进入 Voucher Received。
- 控房/配额取消继续进入普通 Cancel Booking。
- Parent split 被当成普通 Update、`Allotment Maintenance` 或 `Cancel Allotment`。
- Parent split 的 child 不使用 `New Booking + Group Block`，或 parent 不使用 `Cancel Booking + group_block`。
- Parent split 的 parent cancel candidate 被视为已取消成功。
- 普通房型或 Rate Code 不唯一时仍创建 normal task；Parent split raw `SUITE` 例外除外。
- Parent split raw `SUITE` 例外中填写 `SU1/SU3/SU6`，或在硬校验前创建可执行任务/写 PMS。
- Extra Bed 被当成 Update 或房量。
- Trace subtype 不是 `extra_bed/general_request`。
- `trace_items[].category` 被拆成多张任务卡。
- 修改已有结构化备注被路由到 Trace，或新增补充信息被路由到 Update/Note。
- 每个 Rooming List 目标未派生对应 TA Recorder，或两者 target/关系字段不一致。
- `Note` 或 `Allotment Maintenance` 在没有新 schema 的情况下建卡。
- 缺 `source_message_id` 时仍接受任何“合规”结果、创建业务卡或生成 S10/S99 通知；该情况必须按上游/基础设施错误处理。
- 把 `source_event_index/related_*` 当真实 TaskCard ID。
- 根据 Fix Charge 的 TBD 工具名或 payment intent 自动执行外部写入。

## 7. 最低验收场景

| 场景 | 预期 |
| --- | --- |
| 只有 Thanks / Noted | 1 个 S10 源邮件通知，0 张业务卡 |
| `see attached` 且附件不可取得，无法判断方向 | 1 个 S99 源邮件通知，0 张业务卡 |
| Payment Evidence + “余款能否入住时支付” | 1 张 Payment Evidence 候选卡 + 1 个未覆盖意图展示块 |
| 上述未覆盖意图展示块 | 同时显示 `visible_message` 和 `text_raw`；不创建 TaskCard |
| Update + Meeting + Extra Bed，同一目标 | 1 张 Update 卡 + 1 张合并 Trace 卡；Trace subtype=general_request |
| Rooming List 含两个目标 group | 每目标 1 张 Rooming List + 1 张 TA Recorder，共 4 张候选卡；每对 `case_keys` 一致且 TA Recorder 关联回对应 Rooming List |
| Parent Group 拆 2 个 Child Group | 2 张 `New Booking/new_group_block` + 1 张 Parent Group `Cancel Booking/linked_parent_release_after_child_split`；parent 必须 hard validation |
| Parent split 中 1 个 child 房型仅写 `SUITE` | 该 child 仍输出 New Booking 候选；保留 `room_type_raw=SUITE`，PMS code 为 `null`，hard validation=true；其他 child 不受阻塞 |
| 非 Parent split 的 generic `SUITE`，或任意场景 Rate Code 无法唯一映射 | 1 张统一业务人工复核卡，0 张可执行业务卡；不填猜测值 |
| 同一 Update 改日期、房型、价格 | 1 张 Update 卡，多个修改项；不能因单值 subtype 丢字段 |
| 将订单中已有备注 A 修改为 B | Update Booking，保留 before/after |
| 当前邮件新增“客人 20:00 到店” | Trace/general_request，不是 Update 或 Note |
| LianTai Credit Voucher | Voucher Received，不是 Payment Evidence |
| bank transfer / cash deposit receipt | Payment Evidence，不是 Voucher Received |
| 已匹配 Update 但附件不可读 | 业务 Fallback 人工复核卡，不是 S99 |
| 任意业务 `Need Manual Review` | adapter 输出 `manual_review + Fallback + business_event_review`（或内部 `task_result=manual_review`）；业务字段全量可编辑，证据/审计字段只读 |
| 已匹配支持事件但参数不安全，同时还有清楚但不支持的当前意图 | 1 张业务 Fallback 人工复核卡 + 1 个未覆盖意图展示块；不得因复核分支提前返回而丢失展示项 |
| 纯付款政策询问且无支持事件 | S10，不是 Trace、Need Manual Review 或未覆盖意图数组 |
| 已知但当前禁用的 `Note` / `Allotment Maintenance`，或真正未知/拼写错误的 event type | 该 event 记录 `adapter_contract_error`，0 张自动业务卡，保留源邮件；其他合法 sibling events 仍继续处理 |

## 8. 实施顺序

1. 先实现根结构分流和逐事件循环。
2. 实现本文件的三元组表和 fail-closed 校验。
3. 落地统一业务人工复核卡和全量业务字段可编辑模式。
4. 实现 Parent split 事件集、generic `SUITE` 例外和逐 child 隔离失败。
5. 实现 Rooming List → TA Recorder 强制派生关系。
6. 新增 S10/S99 源邮件通知与 `unhandled_current_intents` 展示组件，同时展示 `visible_message/text_raw`。
7. 迁移 Cancel Allotment、Payment Evidence、Invoice、Manual RateCode 等卡型，更新 Trace、日期证据、Fix Charge 和手工价字段。
8. 尚未冻结的专属 schema 只开发通用候选卡，不自行发明字段。
9. 用第 7 节场景做 adapter 单元测试和端到端回归。

## 9. 证据位置

- 当前输出与事件结构：`skills/booking-desk-event/references/00-output-contract.md`
- 完整覆盖与未覆盖意图：`skills/booking-desk-event/references/03-current-content-completeness.md`
- 路由顺序、冲突和多事件拆分：`skills/booking-desk-event/references/02-event-routing-map.md`
- 业务复核/S10/S99 边界：`skills/booking-desk-event/references/90-manual-review.md`
- Update 多动作：`skills/booking-desk-event/references/11-update-booking.md`
- Cancel / Cancel Allotment：`skills/booking-desk-event/references/12-cancel-booking.md`
- Voucher / Payment Evidence：`skills/booking-desk-event/references/13-voucher-payment.md`
- Trace schema：`skills/booking-desk-event/references/16-trace-notes.md`
- Parent split：`skills/booking-desk-event/references/31-allotment-control-block.md`
- 房型/Rate/Fix Charge/日期：`skills/booking-desk-event/references/50-room-type-mapping.md` 至 `54-stay-date-parsing.md`
