完成 V4 目录校验和字段白名单

This commit is contained in:
andy
2026-07-19 12:25:06 +07:00
parent b4f1b3c856
commit 39518b3cf2
17 changed files with 1124 additions and 53 deletions

View File

@@ -444,6 +444,7 @@ V3 P0.1 不做以下事项:
- M002 V4 CP4 已补充新模型写入:普通 V4 业务包会额外创建 V4 订单任务、来源邮件展示卡、Basic Information 卡和业务卡;只有契约错误、没有合法业务 event 的包不会创建 V4 订单任务。
- M002 V4 CP4 后V4 `route_code=S10/S99` 写入 `workflow_reservation_v4_source_notification`不再创建隐藏技术订单或旧任务V3 S10/S99 和旧 S000/S999 仍保留历史兼容链路。
- M002 V4 CP5 已开放 V4 查询接口:`GET /api/reservation/workbench-items``GET /api/reservation/order-tasks``GET /api/reservation/order-tasks/{orderTaskId}``GET /api/reservation/source-notifications/{notificationId}`
- M002 V4 CP6/CP7/CP8 已开放 V4 普通卡片确认、S10/S99 来源通知 ack、`REVIEW_REQUIRED` 卡复核解阻、复核场景订单归属确认、固定种子目录校验和 V4 卡片 `fields[]` 字段白名单。
- V4 包级契约错误在 `source_message.source_message_id` 可定位时只写 `adapter_contract_error` transition不创建订单、任务或用户可处理卡`source_message_id` 缺失或 SourceMessage 不存在时仍返回明确错误。
- V4 `PAYMENT.attachment_ids[]` 必须匹配 `source_message.attachments[].id`V4 `UPDATE_BOOKING` 不接受 `rate_code``after.rate_code`;这类契约错误只落 `adapter_contract_error` transition不创建用户可处理业务任务。
- 40 条 P0.1 路由枚举 / 稳定配置。
@@ -477,12 +478,12 @@ M002 V4 CP2 设计文档已落地:
- 文档路径:`docs/project/requirements/M002-v4-order-task-card-domain-model-cp2.md`
- 设计内容SourceMessage 邮件展示卡、S10/S99 来源通知、`source_message_id + order_ref` 订单任务、Basic Information 独立卡、每个 V4 event 的业务卡、卡片确认 / 复核 / 锁定、同订单阻塞、表结构草案和后续接口草案。
- 已确认V4 工作台统一列表新开 `/api/reservation/workbench-items`,业务订单任务新开 `/api/reservation/order-tasks/**`S10/S99 来源通知使用 `/api/reservation/source-notifications/**`S10/S99 采用来源通知模型;`FIT + BOOKING_CODE` 不建 ACTIVE 唯一约束匹配多条进人工复核Basic Information 必须先确认其它业务卡第一版不强制逐张确认Account / Market / Source 目录第一版使用后端固定种子数据。
- 当前状态CP3 表结构 / Repository、CP4 入站写入新模型、CP5 查询接口CP6 普通卡片确认 / S10/S99 ack 已完成V4 复核接口和前端页面仍未实现。
- 当前状态CP3 表结构 / Repository、CP4 入站写入新模型、CP5 查询接口CP6 普通卡片确认 / S10/S99 ack、CP7 复核解阻和 CP8 固定目录 / 字段白名单已完成V4 前端页面仍未实现。
仍需后续 checkpoint 实现:
- V4 表结构和 Repository 落地已完成第一版:新增 V4 订单任务表、V4 任务卡表和 V4 来源通知表,并提供 Entity、Mapper、Repository、幂等创建、`order_context_index` 稳定排序、非 event 卡 `source_event_index=0` 和 version 乐观锁更新基础方法。
- V4 入站写入新模型已完成第一版:真正创建 SourceMessage 展示卡、Basic Information 卡、业务卡和 S10/S99 来源通知。
- V4 复核解阻、复核场景订单归属确认和前端页面模型切换。
- V4 前端页面模型、任务详情字段矩阵和目录校验完全切换
- V4 前端页面模型切换。
- 真实 PMS 目录、Rate Code 配置中心或通用 lookup API 接入;当前 CP8 只是后端固定种子目录第一版
- 真实 OPERA / OHIP、普通任务任意切换订单、字段矩阵从当前扁平结构整体迁移到 0711 P0 新结构、历史旧 Parent Cancel Booking payload 批量迁移。

View File

@@ -193,6 +193,14 @@ Agent 给出非空 `account_code`,但信息系统运行时目录不存在该
不同 `order_ref` 可以对应不同 Account。
后端 CP8 第一版已落地固定种子目录:
- Account`QBD_TRAVEL``LIAN_TAI``HANATOUR_TD`
- Market / Source由 Account 派生,当前分别为 `LEISURE` / `TRAVEL_AGENT`
- SuperAgent 不需要输出 `market_code``source_code``account_name`,也不要输出显示名称替代 `account_code`
- 如果 SuperAgent 输出的 `account_code` 不在上述目录,后端会创建 `REVIEW_REQUIRED` Basic Information 卡,并在 `validation_errors_json` / 查询 `fields[].validation_errors` 中返回目录错误。
- 如果 SuperAgent 输出的业务卡 `room_items[].room_type_code``rate_code` 不在上述固定目录,后端会创建 `REVIEW_REQUIRED` 业务卡,并在 `validation_errors_json` / 查询 `fields[].validation_errors` 中返回目录错误;用户可通过 V4 复核解阻接口提交对应字段 pointer 修正。
## 8. message_events 公共字段
### 8.1 结构
@@ -732,11 +740,12 @@ AI 回调包
- CP5 查询接口强制 Bearer 登录、`RESERVATION_TASK_READ` 和酒店访问权;不返回邮件正文、附件 URL、`ai_payload_json` 或来源通知原始 payload。
- `POST /api/reservation/order-tasks/{orderTaskId}/cards/{cardId}/confirm` 确认 V4 卡片,强制 Bearer 登录、`RESERVATION_TASK_CONFIRM`、酒店访问权和 version 并发校验。
- `POST /api/reservation/source-notifications/{notificationId}/ack` 确认 V4 S10/S99 来源通知已读 / 已处理,强制 Bearer 登录、`RESERVATION_TASK_CONFIRM`、酒店访问权和 version 并发校验。
- CP7 已完成 `POST /api/reservation/order-tasks/{orderTaskId}/cards/{cardId}/review-resolution` 复核解阻和复核场景订单归属确认。
- CP8 已完成 Account 固定目录校验、Market / Source 派生、Room Type / Rate Code 第一版固定种子校验和 V4 任务卡 `fields[]` 白名单。
当前仍未完成:
- 普通 V4 业务包暂时仍保留旧 V3 任务状态、草稿和 OPERA 模拟骨架兼容便于前端过渡V4 写接口和前端页面完成后再逐步废弃旧链路。
- V4 复核解阻写接口。
- 尚未接入真实 PMS / OPERA / OHIP。
- 尚未改造前端 V4 页面模型。

View File

@@ -16,7 +16,7 @@ M002 V4 CP1 已完成 SuperAgent V4 回调包入站解析、基础校验、路
本文是 CP2 设计文档,用于把 2026-07-18 V4 字段契约落成后续可开发的数据模型和接口草案。
截至 CP7,后端已实现本文第 10、11 节中的持久化基线,并已把 SuperAgent V4 入站结果写入新表:普通业务包创建 V4 订单任务、来源邮件展示卡、Basic Information 卡和业务卡V4 S10/S99 创建来源通知。当前已开放 V4 工作台、订单任务列表 / 详情、来源通知详情查询接口、V4 卡片确认接口、S10/S99 来源通知 ack 接口,以及 V4 `REVIEW_REQUIRED` 卡复核解阻接口。
截至 CP8,后端已实现本文第 10、11 节中的持久化基线,并已把 SuperAgent V4 入站结果写入新表:普通业务包创建 V4 订单任务、来源邮件展示卡、Basic Information 卡和业务卡V4 S10/S99 创建来源通知。当前已开放 V4 工作台、订单任务列表 / 详情、来源通知详情查询接口、V4 卡片确认接口、S10/S99 来源通知 ack 接口V4 `REVIEW_REQUIRED` 卡复核解阻接口,以及 Account / Room Type / Rate Code 固定种子目录第一版校验和卡片 `fields[]` 白名单
后续如本文与 `M002-v4-agent-callback-field-contract.md` 的字段契约冲突,以字段契约为准;如与安全边界冲突,以 `security-access-control-boundary.md` 为准。
@@ -26,7 +26,7 @@ M002 V4 CP1 已完成 SuperAgent V4 回调包入站解析、基础校验、路
| --- | --- | --- |
| 入站识别 | 已识别 `route_code``source_message``order_contexts[]``message_events[]` | CP4 已把有合法 event 的 `order_ref` 建成订单任务聚合CP5 已开放 V4 安全查询接口 |
| SourceMessage | 已按 `source_message.source_message_id` 反查 SourceMessage Inbox | CP4 已固定生成普通业务包内邮件展示卡;邮件正文完整读取仍走 SourceMessage 会话接口 |
| Basic Information | 已写入 V4 Basic Information 独立卡 | CP6 已支持确认并锁定;目录校验和复核写接口仍待后续 checkpoint |
| Basic Information | 已写入 V4 Basic Information 独立卡 | CP6 已支持确认并锁定;CP7 已支持复核解阻CP8 已支持 Account 固定目录校验、Market / Source 派生和 `fields[]` 白名单 |
| 业务 Event | 可映射 event 临时创建旧 `workflow_reservation_task`,并已额外创建 V4 业务卡 | 旧任务链路仍作前端过渡兼容,后续 V4 查询和写接口完成后再逐步废弃 |
| 技术错误 | 已落 `adapter_contract_error` transition | 已符合目标方向:不创建用户可处理卡 |
| 草稿 / READY / OPERA | 仍复用 V3 草稿、READY 和 OPERA 模拟骨架 | V4 新数据确认口径是不保存草稿、确认后锁定、当前不生成 OPERA |
@@ -171,9 +171,9 @@ V4 新数据不再提供后端草稿保存。前端可以在页面本地维护
- 不改写 AI 原始 payload。
- 用户修正写入 `review_resolution_json``confirmed_payload_json`
- CP7 第一版只校验 JSON Pointer 是否指向当前卡允许编辑字段;目录值必须来自信息系统受控目录的校验放到受控目录 checkpoint
- CP8 起确认和复核都会校验目录字段Basic Information 的 `account_code` 必须来自第一版 Account 固定目录,通过后后端派生 `market_code` / `source_code`
- 通过校验后卡片直接进入 `CONFIRMED`,不再进入 V3 `READY` 状态。
- `field_overrides[].field_pointer` 必须是当前卡 `display_payload_json` 中允许编辑的 RFC 6901 JSON Pointer如果当前卡展示 payload 中存在显式 `missing_fields[]`,只允许提交该清单内的 pointer如果没有显式清单第一版只允许 `basic_information.*``business_fields.*` 下已经存在且值为 `null` / 空字符串的未解决叶子字段,不允许替换对象或数组。
- `field_overrides[].field_pointer` 必须是当前卡 `display_payload_json` 中允许编辑的 RFC 6901 JSON Pointer如果当前卡展示 payload 中存在显式 `missing_fields[]`,只允许提交该清单内的 pointer如果没有显式清单第一版只允许 `basic_information.*``business_fields.*` 下已经存在且值为 `null` / 空字符串的未解决叶子字段,或后端 `validation_errors_json` 指向的目录错误字段,不允许替换对象或数组。
- 来源消息、路由、订单定位关系、诊断、缺失字段清单、`manual_review`、raw evidence 等只读字段不得提交。
- 如果订单任务归属未解决,复核请求必须提交 `confirmed_order_id`;后端按当前订单任务酒店校验该订单存在、非逻辑删除且不是系统隐藏订单。
- 如果订单任务已经有 `order_id``target_resolution_status=RESOLVED`,复核请求不能提交不同的 `confirmed_order_id`,否则返回 `V4_ORDER_REBIND_NOT_ALLOWED`;普通任务任意切换订单继续后置。
@@ -195,6 +195,18 @@ V4 新数据不再提供后端草稿保存。前端可以在页面本地维护
第一版 Account / Market / Source 目录使用后端固定种子数据,不依赖 SuperAgent 动态提供目录文件。后续如目录由管理后台维护或从外部系统同步,应以专项 checkpoint 设计目录版本、变更审计和回放影响。
CP8 第一版固定种子:
| 目录 | 第一版代码 |
| --- | --- |
| Account | `QBD_TRAVEL``LIAN_TAI``HANATOUR_TD` |
| Market | 由 Account 派生,当前固定为 `LEISURE` |
| Source | 由 Account 派生,当前固定为 `TRAVEL_AGENT` |
| Room Type | `TWN``KING``DBL``SGL``TRP``RM1``RM2``RM3` |
| Rate Code | `BAR``RACK``PACKAGE``GROUP``FIT` |
说明Room Type / Rate Code 当前只作为确认和字段控件的第一版校验 / 选项来源代码;尚未接真实 PMS 房型目录、Rate Code 配置中心或通用 lookup API。
## 8. 订单归属和 target_order
### 8.1 order_ref 和 target_order 分工
@@ -599,6 +611,8 @@ POST /api/reservation/order-tasks/{orderTaskId}/cards/{cardId}/confirm
- 请求 JSON 必须携带 `version``confirmed_payload` 可选,未传时后端使用当前展示 payload 作为确认快照。
- 只提交当前卡允许编辑的字段或当前卡确认快照。
- Basic Information 确认时 `basic_information.account_code` 必须是第一版 Account 目录值;后端确认前会派生 `account_name``market_code``source_code` 写入 `confirmed_payload_json`
- 业务卡确认时,第一版会校验已有 `rate_code``room_items[].room_type_code` 是否在固定目录中;失败返回 `V4_FIELD_VALIDATION_FAILED`
- 不提交草稿。
- 必须带 `version` 做并发校验。
- 后端确认后卡片 `CONFIRMED` 并锁定。
@@ -621,7 +635,8 @@ POST /api/reservation/order-tasks/{orderTaskId}/cards/{cardId}/review-resolution
- 请求 JSON 必须携带卡片 `version`;可选 `reason` 写入业务审计摘要;`confirmed_order_id` 表示复核场景确认后的本地订单 ID。
- 如果订单任务当前 `order_id=null``target_resolution_status!=RESOLVED``confirmed_order_id` 必填;后端会按订单任务所属酒店查询并校验真实可见订单。
- `field_overrides[]` 每项包含 `field_pointer``value``field_pointer` 只允许指向当前卡展示 payload 中的可编辑业务字段,不允许指向 `source_message``route_code``card_type``target_order``order_ref``missing_fields``manual_review``raw_evidence``validation_errors` 等只读诊断字段。
- 第一版允许的写入容器是 `basic_information``business_fields`;如果展示 payload 中存在显式 `missing_fields[]`,只允许提交清单中的 pointer否则只能改已存在且值为 `null` / 空字符串的叶子字段,不能修改已有有效值、替换整个对象 / 数组或新增未知字段。
- 第一版允许的写入容器是 `basic_information``business_fields`;如果展示 payload 中存在显式 `missing_fields[]`,只允许提交清单中的 pointer否则只能改已存在且值为 `null` / 空字符串的叶子字段,或后端 `validation_errors_json` 指向的目录错误字段,不能修改其它已有有效值、替换整个对象 / 数组或新增未知字段。
- 复核提交后同样执行目录校验Basic Information 复核成功后会在 `confirmed_payload_json.basic_information` 中写入派生的 `account_name``market_code``source_code`
- 如果订单任务已经有 `order_id``target_resolution_status=RESOLVED``confirmed_order_id` 只能为空或等于当前订单 ID提交其它订单 ID 会返回 `V4_ORDER_REBIND_NOT_ALLOWED`
- Basic Information 必须先确认;如果 Basic Information 自身是 `REVIEW_REQUIRED`,允许通过本接口先复核并确认 Basic。
- 解阻过程不改写 `ai_payload_json`;用户修正写入 `review_resolution_json`,最终确认快照写入 `confirmed_payload_json`
@@ -704,7 +719,7 @@ AI 原始 payload、邮件正文、附件 URL 和技术 trace 不应直接进入
| M002-V4-CP5 | V4 查询接口 | 已完成:工作台统一列表、订单任务列表、订单任务详情和来源通知详情查询接口;订单详情 V4 专属时间线后续再做 |
| M002-V4-CP6 | V4 卡片确认和 S10/S99 ack | 已完成不保存草稿支持普通卡片确认、确认后锁定、Basic Information 前置约束、同订单前置任务写侧阻塞、业务审计、version 并发校验和 S10/S99 来源通知确认 |
| M002-V4-CP7 | V4 复核解阻与订单归属确认 | 已完成:支持 `REVIEW_REQUIRED` 卡字段修正、复核说明、复核场景订单归属确认、version 并发校验、直接 `CONFIRMED`、审计和 availability.reviewable |
| M002-V4-CP8 | 受控目录第一版 | Account、RoomTypeRateCode、Department 固定目录或版本化快照校验 |
| M002-V4-CP8 | 受控目录第一版 | 已完成第一版Account 固定目录校验、Market / Source 派生、RoomType / RateCode 固定种子校验、V4 任务卡 `fields[]` 字段白名单 |
| M002-V4-CP9 | V4 前端契约收口 | 字段、控件、availability、错误展示和旧任务入口切换 |
| M002-V4-CP10 | 旧 V3 / V2 能力收口评估 | 明确哪些兼容入口可以关闭,哪些仍保留只读历史 |