Files
th-hotel-simple/docs/project/requirements/booking-email-contracts-v1.md
T

170 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Booking 邮件 contracts-v1
| 项目 | 内容 |
| --- | --- |
| 文档状态 | 权威契约 |
| 契约版本 | `booking-contracts-v1` |
| 对应架构 | [Booking 邮件处理架构 v0.4](booking-email-architecture-v0.4.md) |
| 适用终点 | 用户确认;PMS/Opera 不在本契约执行范围 |
## 1. 共同信封与版本规则
七层之间传递的每个顶层对象必须包含 `contract_meta` 和 `evidence_refs[]`。禁止靠调用方上下文、数据库隐式默认值或 Agent prompt 隐式补齐版本。
| 字段 | 必填 | 说明 |
| --- | --- | --- |
| `contract_version` | 是 | 固定 `booking-contracts-v1`。不兼容变更必须新版本。 |
| `catalog_version` | 是 | 本次使用的业务目录版本;尚未执行目录处理时填 `catalog-not-applied`。 |
| `parser_version` | 是 | 本次 Parser 版本;未执行时填 `parser-not-run`。 |
| `agent_profile_version` | 是 | Booking Agent profile 版本;未调用时填 `agent-not-invoked`。 |
| `processing_run_id` | 是 | 一次处理运行的稳定 ID,重试使用同一 run、不同 attempt。 |
| `source_message_id` | 是 | 平台 SourceMessage 的稳定 ID;不可使用邮件主题代替。 |
| `source_revision` | 是 | 同一来源消息在材料重取或重放后的可追溯版本。 |
| `evidence_refs` | 是 | 最小化证据引用;不嵌入原始邮件、完整附件、Secret 或外链 URL。 |
`evidence_ref` 至少包含 `evidence_id`、`kind`、`source_message_id`、`locator`、`content_hash`、`is_current_material`。`kind` 允许 `CURRENT_BODY`、`QUOTED_HISTORY`、`ATTACHMENT`、`WORKBOOK_SHEET`、`WORKBOOK_ROW`、`IMAGE`、`OCR_TEXT`、`CONTEXT_RECORD`、`USER_CORRECTION`。`locator` 只能表达安全定位(例如 Sheet/行号、附件逻辑 ID、上下文记录 ID),不能含签名 URL 或原文全文。
所有 issue 使用同一结构:`code`、`severity`(`INFO`/`WARNING`/`BLOCKER`)、`scope`(`MESSAGE`/`MATERIAL`/`TARGET`/`FIELD`)、`message_key`、`evidence_refs[]`、可选 `retryable`。用户可见文本由 `message_key` 和前端文案生成,避免把原文写进 API。
## 2. SourceMessageEnvelope
**层 1 输出。** 统一 AgentBus 与手工 EML,不携带业务判断。
| 字段 | 说明 |
| --- | --- |
| `message_origin` | `AGENTBUS` 或 `MANUAL_EML`。 |
| `external_message_id`、`conversation_id`、`in_reply_to` | 邮件身份与会话链路;缺失时保留 null,不用主题猜。 |
| `idempotency_key` | 基于可信消息身份/内容摘要形成;只处理外部重复投递,不合并两封真实邮件。 |
| `sender`、`recipients`、`subject` | 最小化安全摘要或受控引用;不作为唯一业务事实。 |
| `received_at` | 收件时间点(UTC)。 |
| `current_body_ref`、`quoted_history_ref` | 对正文 current/history 分离后的证据引用。 |
| `attachment_descriptors[]` | 逻辑附件 ID、文件名摘要、媒体类型、大小、内容哈希、受控原件引用。 |
不可接受:入口直接调用 Parser/Agent 后跳过 SourceMessage、入口把 AgentBus 的原始 JSON 当领域对象、或根据邮件主题创建订单。
## 3. MaterialPackage
**层 2 输出。** 描述可供解析/理解的材料,不表达最终业务类型。
| 字段 | 说明 |
| --- | --- |
| `message_envelope` | `SourceMessageEnvelope` 的最小必要摘要与 ID。 |
| `current_materials[]` | 本次可触发业务的 current body、当前附件、当前图片、明确版本指令。 |
| `quoted_history_materials[]` | 仅用于解释、继承身份、重复检查的历史材料。 |
| `workbook_inspections[]` | 文件哈希、Sheet 名称、表头签名、候选行/列、底色证据、Profile 候选与检测结果。 |
| `image_inspections[]` | 图片证据与可选 OCR 安全摘要;不把 OCR 当唯一事实。 |
| `material_selection_status` | `READY`、`NO_ATTACHMENT`、`UNSUPPORTED`、`AMBIGUOUS`、`FAILED`。 |
| `issues[]` | 材料层问题,例如超限、文件损坏、多个 Profile 平分、非法 Sheet。 |
规则:current 与 quoted 必须可区分;没有附件时输出 `NO_ATTACHMENT` 但仍为有效 package;固定渠道只把严格候选行送入确定性 Parser,其他行只能作为复核证据。
## 4. ParsedFactSet
**层 3 输出。** Parser 的标准事实集合;它不等于最终业务决定。
| 字段 | 说明 |
| --- | --- |
| `parser_applicability` | `APPLICABLE`、`NOT_APPLICABLE`、`FAILED`、`AMBIGUOUS`。 |
| `channel_profile_code` | 成功解析时的固定渠道 Profile;不能唯一确定时为 null。 |
| `facts[]` | 可追溯的标准事实,必须逐项带 `fact_id`、`confidence_kind=DETERMINISTIC`、`evidence_refs[]`。 |
| `unresolved_fields[]` | 未能确定的字段及原因;未知房量、Rate Code 映射等必须保留。 |
| `fallback_reasons[]` | 需要 Booking Agent 或 Risk 的明确原因代码。 |
| `issues[]` | Profile、底色、字段格式、解析失败等问题。 |
| `agent_input_material` | 可选、受控的 Agent current-message 文本;只在本地 policy 脱敏、限长后出现。 |
`facts[]` 的稳定字段包括:
- `action_hint`:`NEW_BOOKING`、`UPDATE_BOOKING`、`CANCEL_BOOKING` 或 null;`booking_type` 只在房量足以确定时为 `FIT`/`GROUP`,否则为 null。
- `target_identity`:`group_code` 是团号统一字段;`Name of Group`、`Group Name`、`Tour Code`、`Group Code` 进入该字段。`group_name` 只用于邮件明确给出的独立展示名称;`tour_code` 是历史兼容镜像,不能与 `group_code` 形成两个目标。`name_values` 只保存真正的客人/联系人姓名;字段未知必须为 null。
- `stay`:`arrival_date`、`departure_date`、`nights`、`room_items[]`、价格原始证据。
- `room_items[]`:`room_type_code_hint`、`room_count`、`rate_hint`、`evidence_refs[]`;Extra Bed 不能写入房型。
- `related_hints[]`:Trace、Rooming List、Payment、Allotment source/actual、明确 `GROUP_CODE_REPLACEMENT` old→new 关系等候选线索。
`agent_input_material` 是为了让 Parser fallback 能理解无固定渠道的正文,而不是第二份原始邮件:它只能绑定一个 `CURRENT_BODY` evidence,内容仅来自 current subject/body,按 `booking-agent-current-message-v1` 脱敏并限制为最多 12,000 个 code point。它必须排除 quoted history、原始附件、附件 URL、sender 原文和 Secret;该受控摘要可随 `PARSED_FACT_SET` 在项目 schema 保留至 `retention_until`,以支持同一 run 的异步 Agent/retry,不得用它还原原始邮件。
`GROUP_CODE_REPLACEMENT` 只接受 current body 中明确标注的 old→new 指令。单封普通文本中出现两个团号、主题猜测或 quoted history 都不能生成关系。该 relation 在 Parser fallback 中是无动作 Context/Agent clue;固定渠道仅能绑定到同一 new group code 的 `UPDATE_BOOKING` fact。
Parser 不得:将 `room_count=0` 解释为 FIT、在 Profile 平分/未知/非法 Sheet 时产生确定事实、用多行互补制造 anchor 事实、或因字段缺失把已知动作改写成 General/Risk。
## 5. ContextPackage
**层 4 输出。** 面向一封当前邮件与已识别目标的最小上下文,而不是历史全量 dump。
| 字段 | 说明 |
| --- | --- |
| `context_scope` | `MESSAGE` 或一个/多个 `TARGET`;每个 target 有独立查询边界。 |
| `target_resolutions[]` | 输入线索、零/一/多候选、解析状态和证据;多候选不自动选择。 |
| `current_state` | 已确认事实、有效未完成任务、计划完成态;带数据来源与更新时间。 |
| `conversation_context` | 当前邮件的必要历史摘要、revision 关系与重复线索;历史内容不重新触发动作。 |
| `lifecycle_context` | New/Update/Cancel 前置链、old→new Group Code、Cancel 后冲突、Trace 顺序。 |
| `allotment_context` | source 团与 actual 团候选关系、库存/扣减可用性;不是最终扣减命令。 |
| `issues[]` | 上下文缺失、多目标冲突、历史证据冲突等。 |
读取规则:优先 Group Code、明确订单 ID、已确认消息关联;无可靠目标时返回未识别,而不是全文/全库模糊扫描。
当 `GROUP_CODE_REPLACEMENT` 存在时,Context 必须只读取 old/new 两个明确 Group Code:old 有效已确认链且 new 不存在才是 `RESOLVED`;old 已取消/不存在为 `UNRESOLVED`,new 已存在为 `CONFLICT`。`group_code_replacements[]` 必须保留 relation ID、old/new、状态、候选 ID 和 issue。Trace 历史按 Group Code 和时间顺序只提供安全摘要(任务 ID、状态、FO/HSK、服务类型);跨封 Trace 永远是新卡,历史自由文本、附件定位和 quoted 内容不能进入 Context。
## 6. CandidateDecision
**层 5 输出。** 可以来自纯确定性合成或 Booking Agent;两者使用同一结构。
| 字段 | 说明 |
| --- | --- |
| `decision_origin` | `DETERMINISTIC`、`AGENT`、`MIXED`。 |
| `result_disposition` | `IN_SCOPE_TASKS`、`GENERAL_NOTIFICATION`、`RISK_NOTIFICATION`、`IGNORED_DIRECTION`。 |
| `target_decisions[]` | 每个目标独立的候选动作、目标身份、字段、linked/derived actions、证据和 issues。 |
| `message_notifications[]` | General/Risk/ignored 的消息级结果;Risk 每封最多一条。 |
| `agent_trace_ref` | Agent 调用 ID/版本/安全摘要;纯 Parser 时为空。 |
| `issues[]` | 对整封消息的无法分配问题。 |
`target_decision` 的 `business_type` 为:`NEW_BOOKING`、`UPDATE_BOOKING`、`CANCEL_BOOKING`、`TRACE_RESERVATION_NOTES`、`ROOMING_LIST`、`PAYMENT`、`ALLOTMENT`;`action_kind` 由业务类型和当前态决定。Allotment 必须显式输出 actual 与 source 的关联,不能把多个 actual 或 source 扣减隐匿在一张普通 New 卡里。
Booking Agent 的受控输入只允许 `ParsedFactSet + ContextPackage`;`ParsedFactSet.agent_input_material` 是唯一可见的正文材料,且受上文 policy 约束。若 Parser `NOT_APPLICABLE` 或 `FAILED`,也必须先创建最小的 `ParsedFactSet`,明确未解析原因,而不是把原始附件或 quoted history 直接交给 Agent。
## 7. ValidationResult
**层 6 的校验输出。** 只有 `validation_status=VALID` 的目标才可产生确认投影。
| 字段 | 说明 |
| --- | --- |
| `validation_status` | `VALID`、`REVIEW_REQUIRED`、`RISK`、`REJECTED`。 |
| `validated_targets[]` | 目标级通过/阻断结果,不让一个目标阻断同封其他清晰目标。 |
| `schema_checks[]` | contracts-v1 结构和版本检查。 |
| `business_checks[]` | Catalog、必填、目标、生命周期、前置任务、linked action、重复/revision 检查。 |
| `blockers[]` | 必须由用户补齐或修复才可确认的条目。 |
| `risk_notification` | 仅隔离无法安全决定的部分;每封至多一张。 |
Validator 必须校验:版本一致性、证据存在、目标解析、字段必填、Catalog 映射、当前生命周期、已确认/未完成任务、外部重复投递、真实新邮件、revision、历史底色遗留和业务内容相同。Agent 输出无效时,目标级进入 Risk,不能绕过 Validator 建卡。
## 8. ConfirmationProjection
**层 6 面向前端的安全输出。** 它不是原始 CandidateDecision 的直通 JSON。
| 字段 | 说明 |
| --- | --- |
| `projection_status` | `AWAITING_CONFIRMATION`、`REVIEW_REQUIRED` 或不可生成。 |
| `confirmation_items[]` | 用户可见任务/通知,按目标拆分。 |
| `display_fields[]` | 已归一化的关键字段、展示值、来源证据、可编辑性与校验规则。 |
| `blocked_fields[]` | 缺失/冲突字段及需用户选择的候选;不暴露内部 payload。 |
| `linked_actions[]` | 如 Allotment source/actual、Trace 合并关系;只显示本期可确认参数。 |
| `safe_evidence[]` | 文件/Sheet/行/图片等安全引用与脱敏摘要。 |
| `processing_run` | run ID、当前状态、重试次数、可重试状态与安全错误摘要。 |
确认 API 只冻结用户确认的参数和审计,必须携带 `processing_run_id` 与并发版本。确认成功不调用 PMS/Opera,也不执行付款、库存扣减或部门流转。
## 9. 编排、持久化与 API 约束
1. AgentBus 与手工 EML 都调用同一个 `BookingMessageOrchestrator`,并产生一致的 `SourceMessageEnvelope` 幂等语义。
2. 同步完成且无需异步 Agent 时返回 `201`;进入 Agent 或异步处理时返回 `202` 与 `processing_run_id`。
3. 状态查询为 `GET /api/reservation/booking-processing-runs/{runId}`;已有 `POST /api/reservation/booking-email-intakes` 逐步适配为统一入口。仅 `FAILED` run 可经 `POST /api/reservation/booking-processing-runs/{runId}/retry` 以 `202` 重放;它只能重用已持久化的脱敏 contracts artifact,新增 processing attempt,不重新下载邮件/附件、不创建 revision、更不调用 PMS/Opera。
4. PostgreSQL `th_hotel_booking` 保存 run、attempt、版本、证据引用、候选、校验和确认投影;`PARSED_FACT_SET` 中只允许保存受 policy 约束的 Agent 摘要,所有这些项目 schema 数据按 `retention_until` 三个月清理。旧 MySQL 只能被 Context 兼容读取,不能参与新主线写入或跨库事务。
5. 所有持久化/接口代码必须使用 `contract_version` 做版本门禁;未知未来版本 fail closed 并形成安全 Risk/技术错误记录。
6. 仅拥有 `SYSTEM_ADMIN_CONSOLE_ACCESS` 的用户可读取 `GET /api/reservation/booking-processing-metrics`。该接口固定返回最近 24 小时的总 run、失败率、Agent fallback 比率、Risk 比率、积压、已重试 run 与 retry attempt 聚合,不返回任何邮件、附件、团号或用户信息。
## 10. 兼容与测试要求
- Parser 与 Booking Agent 对相同事实必须输出可比较的 `CandidateDecision`;差异只能通过显式 `decision_origin` 和 issue 表达。
- 每个 Contract 都需 JSON/Java fixture、schema validation、正向/错误案例及 evidence reference 检查。
- 真实邮件只作为 opt-in 外部验收;仓库只保存去隐私 fixture 与样本哈希/Profile 断言。
- 后续对字段、枚举、状态机或业务动作做破坏性变更时,必须新增 contracts-v2,而不是静默改 v1。