# P0 → P0.1 增量修订说明（给已基于旧 P0 包开发的团队与 Codex）

> 日期：2026-07-11（Asia/Shanghai）
> 旧开发基线：`开发交付_P0冻结基线_2026-07-11.zip`
> 旧 ZIP SHA-256：`f08337fab17a1b016d0d6c0b6e667b4d8d9ffa9741bc2067916bae73dbe472c7`
> 最新完整增量基线：`开发交付_P0.1_ParentGroup语义修订_2026-07-11.zip`
> 新 ZIP SHA-256：`d2bbdfbfb786ce06fcf948f3665a654007154beb942ad054bc34017ff38e0eee`

## 1. 可以直接交给开发 Codex 的任务指令

```text
你现在维护的是已经按照“开发交付_P0冻结基线_2026-07-11”开发中的代码。

请在现有实现上做 P0 → P0.1 增量修改，不要推倒重做全部 P0，也不要实施 P1/P2。

本次只有一个领域语义调整：
Allotment / Control Block = Parent Group。

完整 Parent split 必须从：
N 个 Child New Booking + 1 个 Parent Cancel Booking

改为：
N 个 Child New Booking + 1 个 Parent Cancel Allotment。

Parent 使用既有 task subtype：cancel_allotment_control_block。
relationship_type=linked_parent_release_after_child_split 只用于关联和 Preflight，不再选择独立任务卡 subtype。

请同步修改：
1. 当前事件 producer/AI 输出消费校验；
2. Adapter 路由；
3. Parent key 归一；
4. 同 Parent 去重；
5. Legacy 只读兼容；
6. 前端卡型映射；
7. 测试和路由计数。

删除当前合法路由中的两条旧三元组：
- normal_task + Cancel Booking + linked_parent_release_after_child_split
- manual_review + Cancel Booking + linked_parent_release_after_child_split

Parent split 改为复用：
- normal_task + Cancel Allotment + cancel_allotment_control_block
- manual_review + Cancel Allotment + cancel_allotment_control_block

总路由由 42 条改为 40 条。

历史已经保存的旧 linked Parent Cancel Booking payload 不迁移、不改写；只在 reader/adapter 展示层归一为现有 Cancel Allotment 卡。
当前 producer 如果继续产生该旧组合，必须 adapter_contract_error/fail closed。

完成后按本文第 10 节逐项验收，并报告修改文件、测试结果和仍未覆盖的风险。
```

## 2. 本次修改边界

本次只取代旧 P0 的以下两项：

1. Parent split 父事件从独立 linked `Cancel Booking` 改为复用 `Cancel Allotment / cancel_allotment_control_block`。
2. 因删除独立 Parent Cancel Booking subtype，Adapter 从 19 个业务 subtype、42 条总路由收敛为18个业务 subtype、40条总路由。

以下旧 P0 规则继续有效，不得借本次增量重写：

- S10/S99 两个独立根契约。
- Main 只做粗分类、Skill 做最终业务判型。
- type-known 人工复核保留原业务卡型。
- 房型不唯一时同卡复核。
- 同卡 `review_status/review_resolution` 解阻。
- 缺少 `source_message_id` 返回基础设施输入错误。
- 行级隔离和明确 derived events。
- 部分 Allotment 维护不受支持。
- `Note`、`Allotment Maintenance` 当前禁止新生成。

P1/P2 仍未实施。本次不得依据 P1/P2 报告自行新增 Schema、路由或卡型。

## 3. 业务语义修改

### 3.1 统一业务对象

```text
Allotment / Control Block = Parent Group
Allotment code = Parent Group code
group_code = 业务主键
block_code = Parent Group 场景的兼容别名
```

`Parent` 是拆分关系里的角色，不是另一种独立业务对象。

### 3.2 完整 Parent split

1 个 Parent 拆成 N 个 Child 时，固定输出：

- 每个 Child：`New Booking + booking_object_type=Group Block`。
- Parent：恰好一个 `Cancel Allotment + cancel_scope=entire_allotment_control_block`。
- Parent 保留 `relationship_type=linked_parent_release_after_child_split`。
- Parent 必须 `requires_downstream_hard_validation=true`。

禁止把 Parent split 建模为：

- `Update Booking / Amendment`
- `Allotment Maintenance`
- 当前 `Cancel Booking`
- 一个同时包含 Child New 和 Parent Cancel 的复合单事件

## 4. Agent 事件 before / after

### 4.1 旧 Parent 事件：当前生产禁止

```json
{
  "event_type": "Cancel Booking",
  "event_role": "travel_agent_request",
  "current_or_history": "current",
  "source_event_index": "E_PARENT_CANCEL",
  "case_keys": {
    "group_code": "PARENT-2608",
    "confirmation_number": null,
    "reservation_number": null,
    "block_code": null
  },
  "extracted_fields": {
    "cancel_object_type": "group_block",
    "parent_release_or_cancel_candidate": true,
    "release_reason": "parent_to_child_allocation_split",
    "parent_group_code": "PARENT-2608",
    "child_group_codes": [
      "CHILD-2608-A",
      "CHILD-2608-B"
    ],
    "allocation_split_from_parent": true
  },
  "related_source_event_indices": [
    "E_CHILD_1",
    "E_CHILD_2"
  ],
  "related_event_type": "New Booking",
  "relationship_type": "linked_parent_release_after_child_split",
  "requires_downstream_hard_validation": true,
  "manual_review": null
}
```

### 4.2 新 Parent 事件：当前生产唯一合法形式

```json
{
  "event_type": "Cancel Allotment",
  "event_role": "travel_agent_request",
  "current_or_history": "current",
  "source_event_index": "E_PARENT_CANCEL",
  "case_keys": {
    "group_code": "PARENT-2608",
    "confirmation_number": null,
    "reservation_number": null,
    "block_code": "PARENT-2608"
  },
  "extracted_fields": {
    "cancel_scope": "entire_allotment_control_block",
    "parent_release_or_cancel_candidate": true,
    "release_reason": "parent_to_child_allocation_split",
    "parent_group_code": "PARENT-2608",
    "child_group_codes": [
      "CHILD-2608-A",
      "CHILD-2608-B"
    ],
    "allocation_split_from_parent": true
  },
  "related_source_event_indices": [
    "E_CHILD_1",
    "E_CHILD_2"
  ],
  "related_event_type": "New Booking",
  "relationship_type": "linked_parent_release_after_child_split",
  "requires_downstream_hard_validation": true,
  "manual_review": null
}
```

Child 事件保持 `New Booking + new_group_block`；Child 和普通 Group Block 仍然只填 `group_code`，`block_code=null`。

## 5. Adapter 路由修改

### 5.1 删除两条当前路由

以下三元组从当前合法路由表删除，合法数量均为 0：

| 旧三元组 | 当前处理 |
| --- | --- |
| `normal_task + Cancel Booking + linked_parent_release_after_child_split` | 当前 producer 命中时 `adapter_contract_error`；仅 legacy reader 可识别 |
| `manual_review + Cancel Booking + linked_parent_release_after_child_split` | 当前 producer 命中时 `adapter_contract_error`；仅 legacy reader 可识别 |

### 5.2 复用已有 Cancel Allotment 卡

| Agent event | Adapter 三元组 |
| --- | --- |
| `Cancel Allotment` 且 `manual_review=null` | `normal_task + Cancel Allotment + cancel_allotment_control_block` |
| `Cancel Allotment` 且 `manual_review!=null` | `manual_review + Cancel Allotment + cancel_allotment_control_block` |

`relationship_type=linked_parent_release_after_child_split` 只决定关联校验和 Preflight，不得再决定 `task_subtype`。

### 5.3 最新统计

```text
18 个业务 subtype × normal/manual = 36 条
type/subtype 未知 Fallback = 1 条
S10、S99、unhandled display = 3 条
总计 = 40 条
```

因此所有写死的 `42`、`19`、旧 linked subtype 枚举、路由快照和断言都要同步更新。

## 6. Parent key 归一与人工复核

当前 producer 对所有 Parent Group 事件都执行：

| 输入情况 | 输出 |
| --- | --- |
| 同时取得相同 `group_code`、`block_code` | 两边保留相同值 |
| 只取得 `group_code` | 将其复制到 `block_code` |
| 只取得 `block_code` | 将其复制到 `group_code` |
| 两边都存在但归一后不同 | 两边都置为 `null`，保留原始候选证据，进入已知业务卡人工复核 |

双 key 冲突时：

- 事件仍是 `Cancel Allotment`，不能改成 Fallback。
- Adapter 仍派生 `manual_review + Cancel Allotment + cancel_allotment_control_block`。
- `manual_review.reason_code=target_object_unclear`。
- `context_used.parent_identity_candidates[]` 保留两边原始候选和证据来源。
- `manual_review` 仍必须满足旧 P0 已冻结的完整对象契约，不能只写一个 reason code。

## 7. 去重和关系校验

### 7.1 同 Parent 合并

如果同一封当前邮件同时包含：

- 完整 split 派生的 Parent release；以及
- 字面明确的同一 Parent `Cancel Allotment`

则只输出一个 Parent `Cancel Allotment`，合并两份证据。不能生成两张 Parent 取消卡。

如果显式取消的是另一个 Parent，则分别输出两个 Parent `Cancel Allotment`。

### 7.2 关系不变量

- 一个 split cluster 恰好一个 Parent 事件。
- `child_group_codes[]` 与 `related_source_event_indices[]` 数量相等、顺序一一对应。
- 每个 index 都必须指向同根 `message_events[]` 中对应的 Child `New Booking + Group Block`。
- Parent 的 `related_event_type` 固定为 `New Booking`。
- Parent 的 `requires_downstream_hard_validation` 必须为 `true`。
- 关系不完整时拒绝 Parent event，不猜测关系，不因 Parent 失败而删除合法 sibling Child。

## 8. Legacy 兼容和数据迁移

历史已经存储的旧组合：

```text
Cancel Booking
+ cancel_object_type=group_block
+ relationship_type=linked_parent_release_after_child_split
```

处理要求：

1. 不批量迁移历史 JSON。
2. 不改写或回写原历史 payload。
3. Reader/adapter 只在展示层归一为已有 `Cancel Allotment + cancel_allotment_control_block` 卡。
4. 审计查看原始数据时仍应看到原 payload。
5. 当前 producer 输出同一旧组合时必须 fail closed，不能借 legacy 分支放行。

需要把“历史读取”和“当前生产”作为两个明确代码路径或模式参数，禁止仅按 payload 形状自动放行。

## 9. 对开发代码的建议修改位置

实际文件名以开发仓库为准，但至少检查以下模块：

| 模块 | 必须修改 |
| --- | --- |
| Event schema / validator | Parent split 当前只接受 `Cancel Allotment`；校验 `cancel_scope`、双 key、关系字段 |
| Adapter router | 删除旧 linked Cancel Booking subtype 两条路由；复用 Cancel Allotment 路由 |
| Route enum / registry | 删除当前合法 `linked_parent_release_after_child_split` task subtype |
| Parent normalizer | 单边 key 补齐；双边冲突置空并触发 type-known review |
| Deduplicator | 同 Parent 显式取消与 split-derived release 合并证据 |
| Legacy reader | 旧 linked Cancel Booking 只读映射到 Cancel Allotment 卡，不回写 |
| Preflight | 校验 Parent 双 key、Child code/index 顺序和硬校验标记 |
| Frontend card registry | Parent split 使用既有 Cancel Allotment 卡，不再注册单独 Parent Cancel Booking 卡 |
| Metrics / snapshots | 路由总数 42→40，业务 subtype 19→18 |
| Fixtures / tests | 使用第 10 节场景替换旧 Parent Cancel Booking 期望 |

如果部署 AI runtime，必须同时使用 P0.1 的：

- `main_agent_prompt.md`，SHA-256：`de87c0959b1143bc3bd2cdcd43d4c6c36ae6bdd46706e1949d0ff314f4b8b436`
- `booking-desk-event.skill`，SHA-256：`da14f305fd8c4d44ecd57a0a9409c4df462b314af2afac71b4e11584ff799ca5`

不能继续部署旧 Main Prompt/Skill，再只修改 Adapter。

## 10. 最低验收清单

| # | 场景 | 必须结果 |
| ---: | --- | --- |
| 1 | 1 Parent → 2 Children | 2 个 Child New Group + 1 个 Parent Cancel Allotment |
| 2 | 1 Parent → 7 Children | 7 个 Child New Group + 1 个 Parent Cancel Allotment |
| 3 | split 同时明确取消相同 Parent | 仍只有 1 个 Parent 事件，证据合并 |
| 4 | split 同时明确取消另一个 Parent | 两个 Parent 分别输出，不错误合并 |
| 5 | Parent 只提供一边 key | 自动补齐，最终 `group_code=block_code` |
| 6 | Parent 两边 key 冲突 | 两 key 为 null；同一 Cancel Allotment 卡进入 `target_object_unclear` 复核 |
| 7 | Child 与 index 数量或顺序不一致 | 拒绝 Parent event，不猜关系；合法 sibling Child 保留 |
| 8 | 普通 FIT/Group Block 取消 | 仍走普通 `Cancel Booking`，不得误改成 Cancel Allotment |
| 9 | 独立整块 Parent/Allotment 取消 | 正常走 `Cancel Allotment + cancel_allotment_control_block` |
| 10 | 历史旧 linked Parent Cancel Booking | 只读展示归一；原 payload 字节/字段不改 |
| 11 | 当前 producer 输出旧 linked Parent Cancel Booking | `adapter_contract_error`，该 event 0 卡，siblings 继续 |
| 12 | 路由注册表 | 40 个唯一三元组；18 个业务 subtype；旧 linked subtype 0 条 |
| 13 | `Allotment Maintenance` | 当前仍为 0 条合法路由，不得因本次修改重新开放 |
| 14 | PMS 写入 | Parent 始终经过 downstream hard validation 和 Preflight 后才允许继续 |

## 11. 完成定义

开发 Codex 只有在以下条件全部满足后才能报告完成：

- 已列出实际修改的代码文件。
- 已证明当前 producer 路径不再生成或接受旧 linked Parent Cancel Booking。
- 已证明 legacy reader 仍可展示旧数据且没有修改原 payload。
- 已证明 Parent 双 key、去重和关系顺序校验存在。
- 已证明普通 Cancel Booking 没被误改。
- 已证明路由数量为 40，旧 linked subtype 当前合法路由数为 0。
- 已运行相关单元测试/集成测试并提供结果。
- 未顺带实施或发明 P1/P2 规则。

## 12. 权威优先级

本增量说明用于指导旧 P0 实现升级。发生差异时按以下顺序处理：

1. `ADR-002-allotment-parent-group-unification.md`
2. P0.1 同版 `main_agent_prompt.md` 与 `booking-desk-event.skill`
3. P0.1 `任务卡前端字段变更与路由说明_3.0_to_current.md` 和 Excel
4. 本增量说明
5. 旧 P0 冻结包

旧 P0 包继续作为历史基线留存，但其中 Parent Cancel Booking/42 路由部分已经失效。
