170 lines
14 KiB
Markdown
170 lines
14 KiB
Markdown
# 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。
|