# Main Agent 到 Skill 的内部输入契约

## 用途

定义 Main Agent 交给 `booking-desk-event` 的素材包边界、粗候选结构、调用门槛和内部回传。这个接口只存在于 Main Agent 与 Skill 之间；`candidate_events` 和 `internal_route` 都不是最终对外输出。

Main Agent 负责入口校验、取得当前素材、OCR/表格原始抽取、current/history 分离、粗候选和未覆盖当前意图。Skill 对最终 event type、subtype、目标拆分、事件合并以及 linked/derived events 拥有唯一裁决权。

## 目录

- Step 0：Source Message Identity
- 事件集合
- Candidate Event Item
- 调用门槛
- Skill 最终裁决与内部回传
- 行级与目标级边界

## Step 0：Source Message Identity

在读取正文、附件、OCR、历史或系统上下文前，先校验 `source_message.source_message_id`。缺失、`null`、空字符串或只包含空白时，不得建立素材包或调用 Skill，固定返回输出契约定义的：

```json
{
  "result_type": "infrastructure_input_error",
  "error_code": "missing_source_message_id",
  "retryable": true,
  "missing_fields": ["source_message.source_message_id"]
}
```

该结果不是 S10、S99、业务事件或人工复核，不展示源邮件通知，不创建 TaskCard。不得猜测、生成、修剪后替换或从 confirmation/group/reservation number 推导 `source_message_id`。

## 事件集合

事件集合的唯一权威定义在 `00-output-contract.md`：

- `active_emittable_event_types`：当前可以由新邮件生成的普通业务 event type。
- `legacy_accepted_event_types`：只用于读取历史兼容数据；不得进入 `candidate_events`，不得由当前邮件新生成。
- `business_review_outcomes`：业务复核 outcome；不得进入 `candidate_events`，也不能独立满足 Skill 调用门槛。

`possible_event_types` 只能使用 `active_emittable_event_types` 中的精确值。S10、S99、`Need Manual Review`、`Note`、`Allotment Maintenance` 或其他 legacy/review/入口值都不是合法粗候选类型。

## Candidate Event Item

每个相互独立、由 current evidence 支持的业务信号形成一个粗候选：

```json
{
  "candidate_id": "C1",
  "classification_status": "coarse_supported_signal",
  "possible_event_types": ["Voucher Received", "Payment Evidence"],
  "evidence_refs": [
    {
      "source": "current_attachments",
      "index": 0
    }
  ],
  "target_hints": [
    {
      "case_keys": {
        "group_code": null,
        "confirmation_number": null,
        "reservation_number": null,
        "block_code": null
      },
      "target_locator_raw": null
    }
  ],
  "uncertainty_codes": ["document_subtype_unresolved"]
}
```

固定规则：

- `candidate_id` 必须匹配 `^C[1-9][0-9]*$`，并在本素材包中唯一。
- `classification_status` 固定为 `coarse_supported_signal`，表示 Main Agent 没有作最终业务裁决。
- `possible_event_types` 必须非空、去重，只能使用 active 集合精确值；可以保留多个候选类型。
- `evidence_refs` 必须非空，只能引用 current 素材。`source` 只能是 `body_current`、`current_attachments`、`current_tables`、`current_ocr` 或 `parent_child_split_evidence`；`index` 必须是存在于对应素材中的非负整数索引，`body_current` 固定使用 `0`。
- `target_hints` 是非权威提示，可以为空或包含多个 item。每个 item 固定包含四键 `case_keys` 和原始 locator；不唯一时保持 `null`，不得猜测。
- `uncertainty_codes` 可以为空；有不确定性时使用稳定 reason code，不用自然语言结论替代。
- history-only 内容不得创建粗候选。历史只能在 current 粗候选已经存在后绑定目标、旧值或上下文。
- 每个相互独立的 current 信号都必须有自己的粗候选；同一行或同一目标中的主业务信号和补充 Trace 信号也分别建 candidate。Skill 再按最终目标和业务规则合并、拆分或建立 linked relationship。
- 一个粗候选不等于一个最终事件。Skill 可以拆成多个目标事件、合并同目标信号，或生成契约明确要求的 linked/derived events。
- 最终事件在 `context_used.main_candidate_ids` 中记录所使用的 `candidate_id`；一个事件可关联多个 candidate，一个 candidate 也可关联多个最终事件。最终输出只允许保留这些 ID 作为 provenance，不得复制 `candidate_events` item、`possible_event_types`、`uncertainty_codes` 或任何 `internal_route`。

## 调用门槛

完成全部 current 内容盘点后：

1. 输入可理解，且没有任何 active 业务信号：Main Agent 输出 S10，不调用 Skill。
2. 输入不足，无法判断是否存在任何 active 业务信号：Main Agent 输出 S99，不调用 Skill。
3. `candidate_events` 至少有一个满足本契约的粗候选：Main Agent 调用 Skill，并同时传递 `unknowns`。

仅有 unsupported/legacy 内容、`unknowns` 或 review outcome 不能满足调用门槛。正文只有泛化 `see attached` 且附件无法取得、方向不可识别时走 S99；动作方向已明确为 active 事件但必要附件不可读时，仍形成粗候选，由 Skill 生成业务复核结果。

## Skill 最终裁决

- Skill 必须重新验证 current evidence，不得把 `possible_event_types` 当作最终结论。
- event type 和 subtype 已知但字段、目标、映射或证据不安全时，保留该 active `event_type`，并附非空 `manual_review`；`manual_review.missing_fields[]` 使用 RFC 6901 JSON Pointer。
- 只有 active 业务方向已确认，但最终 event type 或 subtype 仍无法确定时，才使用 `business_review_outcomes` 中的 `Need Manual Review`。
- 业务类型已知的 manual-review event 和 type-unknown `Need Manual Review` 都是业务输出，不得改成 S99。

如果 Skill 验证后确认所有粗候选都没有 active 事件，返回以下内部结果：

```json
{
  "internal_route": "no_supported_event",
  "reason_code": "skill_validation_found_no_active_event",
  "candidate_ids": ["C1"]
}
```

Main Agent 必须把该内部结果转换成正常 S10；不得把 `internal_route` 直接对外返回，也不得把它转换成 S99 或业务人工复核。

`no_supported_event.candidate_ids` 必须非空，列出本次已完成验证且均被否定的全部粗候选 ID。

如果素材包或 candidate item 违反本契约，Skill 返回内部 contract error：

```json
{
  "internal_route": "candidate_package_contract_error",
  "reason_code": "invalid_candidate_package",
  "candidate_ids": ["C1"],
  "contract_errors": [
    {
      "path": "/candidate_events/0/possible_event_types/0",
      "code": "not_active_emittable_event_type"
    }
  ]
}
```

`contract_errors[].path` 使用 RFC 6901 JSON Pointer。该内部错误报告给编排层并停止本次业务处理；不得直接对外，不得映射为 S10、S99 或 `Need Manual Review`。

`candidate_package_contract_error.contract_errors` 必须非空。`candidate_ids` 保留仍可安全读取的候选 ID；如果连 ID 都不合法，使用空数组。

## 行级与目标级边界

QBD/LianTai 当前有效行必须独立处理，不能把不同 current rows 合并成一个主事件。这里的“一行”是隔离边界，不是绝对事件数量：

- 普通情况下，一个 current row × 一个目标产生一个主事件。
- 同一行含多个目标时由 Skill 按目标拆分。
- Parent-to-child split、linked parent cancel、linked Trace、TA Recorder 和其他契约明确的 derived event 可以在同一行主事件之外额外产生。
- Main Agent 只保留行、highlight、目标提示和不确定点；最终拆分由 Skill 完成。
