# Booking 邮件 contracts-v2 | 项目 | 内容 | | --- | --- | | 文档状态 | 权威契约;离线实现已验收;真实运行与发布 `HOLD` | | 契约版本 | `booking-contracts-v2` | | 业务规则 | [Booking Business Agent v1.0](booking-business-agent-rules-v1.md) | | 对应架构 | [Booking 邮件处理架构 v0.5](booking-email-architecture-v0.5.md) | | 决策依据 | ADR-012(修订 ADR-011) | | 适用终点 | Layer 5 Candidate、Layer 6 Validation、Layer 7 用户确认;不执行 PMS/Opera | ## 1. 单一权威与共同规则 新 Layer 5 运行只使用 `CandidateDecisionV2`。CandidateDecision v1 从未上线,不提供双写、回退、迁移或兼容 分支。冻结 Layer 3/CP4 若仍编译依赖旧 Java 类型,只能视为隔离的迁移债务,不能产生新 Layer 5 结果。 所有对象使用 snake_case JSON、稳定 ID/枚举、显式 issue code 和 evidence reference。未知字段一律拒绝。来源缺失 不得由 Agent、Prompt、历史原文或数据库暗中补造。 ## 2. Layer3ResultV2 Layer 3 只表达当前来源事实和材料关系: - `source_facts[]` 保存物理来源单元、来源动作原文/规范值、目标、住期、房型/房量等中立事实; - `structured_mentions[]` 保存当前明确或中立的服务事项及参数; - `target_bindings[]/neutral_relationships[]` 保存材料归属、source→actual 等关系; - `material_observations[]/display_information[]/material_dispositions[]/parse_issues[]` 保存材料状态和证据。 `SOURCE_ACTION_KIND` 是来源事实,不是 `business_type/action_kind`。Layer 3 不输出 Candidate、任务卡、部门、 General、Risk 或 Allotment 派生动作。 ## 3. 内部输入 BookingDecisionInputV2 内部 canonical input schema 为 [`booking-decision-input-v2.schema.json`](schemas/booking-decision-input-v2.schema.json)。它供 Layer 4、Layer 6、 存储和审计使用,包含: - 当前邮件、完整有序的邮件线程 History 正文与附件安全描述; - Layer3ResultV2; - `decision_context.order_contexts[]`:物理来源单元、canonical Room Type、结构性覆盖 FIT/GROUP 的 `rate_options[]`;每个 option 包含原子 `{Rate Code+早餐+餐厅}` 解析结果; - `rooming_list_resolutions[]`:Layer 4 对当前附件给出的 Excel、Rooming List 标签、唯一 Tour Code 及状态; - 完整 evidence registry 和 canonical `input_hash`。 当前文件名型 resolver 只检查 `current_material=true` 且完整文件名匹配批准的 `LLT...xlsx` 规则的附件,并使用 既有材料 attributes 快照和 Evidence 形成该数组。OOXML 安全结构检查通过时为 `RESOLVED`;文件名命中但 bytes、身份或结构检查失败时保留中立候选,输出 `tour_code=null / target_status=UNRESOLVED`。旧 artifact 没有 识别快照时数组自然为空。Layer 5 不得自行打开 Excel 补做,也不得把正文关键词或普通 QBD/LianTai 更新表提升为 Rooming List。 Layer 4 不查询数据库当前订单或任务历史,也不使用历史值补齐来源字段。来源必要值缺失时,对应固定槽位保持 null/`UNRESOLVED` 并转人工。Layer 4 的 Room/Rate、GRPA1、早餐和名单判定规则见各自契约,不复制进 Booking Agent。 ## 4. Agent 精简输入与决定 Agent wire schema 为 [`booking-business-agent-compact-input-v1.schema.json`](schemas/booking-business-agent-compact-input-v1.schema.json)。所有白名单入站 邮件只要进入预订流程,都必须投影为此对象并调用 Booking Agent 一次。 允许字段: - `contract_meta/source_input_hash`; - 当前 `subject/body` 业务材料; - 完整有序的邮件线程 `email_history[]` 正文; - 当前附件说明; - 按物理来源单元组织的必要Layer3事实和Layer4 Room Type/Rate解析状态; - 语义材料、目标关系、Rooming List options、方向、输入 issue 与当次短引用。 物理禁止进入 Agent:History 附件、数据库当前订单快照、数据库历史任务、生命周期准入结论、附件 bytes/Base64、 完整Layer3技术包装、evidence registry、下载 URL、Secret、数据库/PMS 凭据。`source_input_hash` 回指未裁剪的内部 canonical input;短引用只在本次调用内有效。 Agent只返回 [`booking-business-agent-compact-decision-v1.schema.json`](schemas/booking-business-agent-compact-decision-v1.schema.json): 业务类型、目标/单元/Rooming短引用、Booking Type、Trace语义内容、linked/derived选择、Risk和未归类原文。 它不返回`source_input_hash`、日期、房型、房量、价格、Rate、早餐、evidence ID、payload、稳定ID或根级disposition。 Layer 5B使用信息系统内部保存的canonical input hash关联本次结果。 组装器不是“把两个对象随便塞在一起”:它校验同一 run/message/revision/version、保持 Layer3ResultV2 不变, 按 `source_unit_ref/room_item_ref` 绑定来源槽位,以稳定顺序生成 `order_contexts[]`,验证 evidence closure,并只 生成一个 canonical `BookingDecisionInputV2`/hash。Layer 5A从该对象投影一次并调用一次;Layer 5B按返回短引用从同一 canonical输入复制固定字段,展开为一份完整`CandidateDecisionV2`。 ## 5. CandidateDecisionV2 根结构 完整Candidate schema仍为 [`booking-candidate-decision-v2.schema.json`](schemas/booking-candidate-decision-v2.schema.json)。它由Layer 5B根据Agent精简决定组装, `decision_origin=AGENT`;本地错误方向兜底可为 `DETERMINISTIC`,调用失败为 `FAIL_CLOSED`。 根集合: - `target_decisions[]`:New、Update、Cancel、Trace、Rooming List; - `derived_actions[]`:Allotment source deduction 或 source whole cancel; - `risk_items[]`:只隔离无法识别类型/必要目标或异常范围的事项; - `unclassified_source_texts[]`:未被任何任务承接的当前原文,按 `sequence=0..n` 保序; - `agent_trace_ref/issues/evidence_refs`。 `result_disposition` 是确定性汇总,不是另一个任务: | 内容 | disposition | | --- | --- | | 至少一个 target 或 derived action,可同时有局部 Risk/未归类原文 | `IN_SCOPE_TASKS` | | 无任务但至少一个 Risk | `RISK_NOTIFICATION` | | 无任务、无 Risk、至少一项未归类原文 | `GENERAL_NOTIFICATION` | | 受控错误方向,且其他集合全空 | `IGNORED_DIRECTION` | `FAIL_CLOSED` 只能是 `RISK_NOTIFICATION`。General 原文不是任务、不是通知卡、无需业务确认;Risk 也不创建 可确认任务。 ## 6. Target typed payload `business_type/action_kind/payload_type` 必须匹配: | business_type | action_kind | payload_type | | --- | --- | --- | | `NEW_BOOKING` | `CREATE_BOOKING` | `NEW_BOOKING` | | `UPDATE_BOOKING` | `UPDATE_BOOKING` | `UPDATE_BOOKING` | | `CANCEL_BOOKING` | `CANCEL_BOOKING` | `CANCEL_BOOKING` | | `TRACE_RESERVATION_NOTES` | `CREATE_TRACE` | `TRACE` | | `ROOMING_LIST` | `CREATE_ROOMING_LIST_NOTICE` | `ROOMING_LIST` | 不存在 `PAYMENT` business/action/payload。付款原文只进入 `unclassified_source_texts[]`。 ### 6.1 New / Update 两者 wire shape 相同但类型互斥,固定包含 `booking_type/source_unit_ref/arrival_date/departure_date/room_items/ rate_code/breakfast_included/breakfast_restaurant_code`。第二道门槛缺失时字段仍必须结构性存在,值可为 null/空数组, 交 Layer 6 review;不得改成 Risk。 每个 `CandidateRoomItem` 保存 `room_item_ref/source_room_raw/source_room_name/room_type/quantity/ source_price_display/evidence_refs`。不同物理来源行不合并;同一来源内映射到相同 Room Type 的房型也不聚合。 房量 wire 允许保留 null/0/负数/小数供人工修正,Layer 6 只接受精确正整数。价格只是可选原文展示,不是必填, 不存在 people/pax/total_guests。 ### 6.2 Cancel `CancelBookingTaskPayload` 只含 `payload_type=CANCEL_BOOKING`。目标在 `target_identity` 表达;不得附带住期、 房型房量、Rate、早餐、价格或 Booking Type。 ### 6.3 Trace `TraceTaskPayload` 包含非空 `service_items[]` 和整项唯一 `department_code=FO/FO+HSK/null`。每个 service item 保存 ID、`GENERAL_SERVICE/EXTRA_BED`、完整 `service_text`、可选 Room Type/数量和 evidence,不存在 `parameters`。 两种 service type 都必须有 `service_text`;Extra Bed 数量未写时 Agent 默认 1,Room Type 或合法数量缺失时保留 Trace,由 Layer 6 review。不存在单独 HSK。 ### 6.4 Rooming List 成功 payload 只包含一个 `attachment_id` 和证据;一个 target 对应一个当前 Excel。多附件数组不属于成功 shape。 该 target 必须精确对应一项 `RESOLVED` 的 Layer 4 resolution,并保持相同 attachment、Tour Code 与材料 Evidence; 同一 Tour Code 有多份有效附件时不得接受成功 target。 ### 6.5 Linked action target 内只保留有真实业务含义的 `GROUP_CODE_REPLACEMENT`。Trace/Rooming 与 Booking 同属 Tour Code 不需要为了 展示制造 linked action。 ## 7. Allotment derived action `derived_action_type` 只允许: - `ALLOTMENT_SOURCE_DEDUCTION`:一项只引用一个物理目标 `actual_target_decision_id`,保存 source Tour Code 与 `room_items[]` 的 Room Type/房量/evidence;同 source 的两个 actual 行输出两项,不聚合; - `ALLOTMENT_SOURCE_WHOLE_CANCEL`:一个唯一 source Tour Code 一项,`actual_target_decision_id=null` 且房型列表为空。 两种 shape 均不包含 source old/remaining、余额、住期、Rate、早餐、价格或 contributions。Layer 5 不查库存、不做 差额运算;before→after 的 Update 行不派生 deduction。 ## 8. Layer 6 ValidationResultV2 Layer 6 只返回目标/派生项的 `VALID/REVIEW_REQUIRED/RISK/REJECTED`、blocker/review,不返回替换后的业务类型或 payload。校验前后 `candidate_hash` 必须相同。 第二道门槛稳定 code 包括: - 身份:`GROUP_TOUR_CODE_REQUIRED`、`FIT_TARGET_TOUR_CODE_OR_NAME_REQUIRED`、 `CANCEL_TARGET_TOUR_CODE_OR_NAME_REQUIRED`; - New/Update:Booking Type、source unit、成对住离日期、Room Type/正整数房量、Rate Code、早餐/餐厅; - Tour Code 一致性:`TOUR_CODE_RATE_CODE_CONFLICT/TOUR_CODE_BREAKFAST_CONFLICT/ TOUR_CODE_BOOKING_TYPE_CONFLICT`; - Trace:部门、Extra Bed Room Type/数量; - Allotment:source、实际目标引用、Room Type/正整数扣减量; - Layer 4 等值:候选已知值不得与其唯一来源单元的住期、房型、数量、价格、Rate/早餐冲突。 - Rooming List 等值:成功 target 必须精确匹配唯一 resolved resolution 的 attachment、Tour Code 与材料 Evidence; 同 Tour Code 多份有效附件、伪造 attachment/Tour Code/Evidence 均进入本地拒绝或人工边界。 本期 Layer 6 不查询数据库生命周期,不生成 `NEW_BOOKING_ALREADY_EXISTS`、 `BOOKING_LIFECYCLE_*` 或 `PRIOR_TASK_REQUIRES_COMPLETION`。邮件 History 只作为 Layer 5 的语义上下文,不能被 Layer 6 当成订单存在性或任务先后证据。 ## 9. 投影、持久化与发布边界 - Layer 7 只为 target/derived 建立可确认项;Risk 是不可确认通知,未归类原文只保存在 Candidate artifact; - 当前无 V2→V4 authoritative writer;本契约不授权创建 TaskCard; - `SHADOW/CAPTURE_ONLY` 不得写业务任务;`AUTHORITATIVE`、真实数据库、真实 SuperAgent/Profile/Secret、PMS/Opera 均需另行批准; - Main Prompt 与 Skill 是两个独立交付物,Main Prompt 不得进入 `.skill` archive。