# CModel 错误模型

CModel Error Contract 1.0.0 的三字段响应结构、HTTP 语义和调用规则。

CModel Error Contract `1.0.0` 用于百积木拥有的非流式 HTTP 和 Module 调用边界。公共响应只包含 `contractVersion`、`errorCode` 和 `data` 三个字段；服务不能再输出 `value`、`systemCurrentTime`、`error` 或 `{ success, message }`、`{ code, message }` 等私有响应。

公共机器可读源事实与 SDK：

- [CModel 响应 JSON Schema](/contracts/cmodel-error.schema.json)
- npm：`npm install @baijimu/cmodel`

每个公开 API 必须在自身文档中登记调用方能够观察和处理的稳定 `errorCode`。平台内部可以保留错误目录、分类、诊断信息和类型，但这些内部字段不能序列化进公共响应，也不能改变 CModel 固定的 HTTP `200`。

## 适用边界

本契约适用于百积木拥有的非流式 HTTP API 和 Module 方法响应。

以下边界保留各自的协议 envelope，并由最外层 Adapter 与 CModel 做一次确定性转换：

- 健康检查和就绪探针；
- SSE、WebSocket 等已经发出响应头的流式协议；
- MCP、OpenAI、Anthropic 和第三方供应商协议；
- 浏览器重定向、文件下载等非 JSON 响应。

外部错误格式不能继续进入百积木内部业务链路。

## 成功响应

```json
{
  "contractVersion": "1.0.0",
  "errorCode": "0",
  "data": {
    "id": "example"
  }
}
```

成功响应必须同时满足：

- HTTP 状态为 `200`；
- `contractVersion` 缺省时按 `1.0.0` 解析；显式提供时必须为严格 SemVer `1.0.0`；
- `errorCode` 为字符串 `"0"`；
- `data` 由具体 API 或 Runtime 方法的返回类型定义。

使用 CModel envelope 的接口不能返回 `204 No Content`。

## 失败响应

```json
{
  "contractVersion": "1.0.0",
  "errorCode": "PAYMENT_REQUIRED",
  "data": {
    "message": "当前账户余额不足，请充值后重试",
    "retryable": false
  }
}
```

失败响应必须同时满足：

- HTTP 状态为 `200`；
- `errorCode` 是非 `"0"` 的稳定字符串错误码；
- `data` 可以为 `null`，或包含必填 `message` 与 `retryable` 的标准公开错误详情对象；
- `message` 是 1 到 512 个字符、可直接展示给用户的安全文案；`retryable` 表示在不改变输入、授权或账户状态时，原操作能否通过重试成功。

业务失败必须使用 HTTP `200`。HTTP 只表示 CModel envelope 已被正常传输，业务成败只由 `errorCode` 表达：`"0"` 为成功，非 `"0"` 为失败。网关、代理或容器在 CModel Handler 之外产生的非 `200` 响应属于传输或基础设施失败，不得伪装成 CModel 业务响应。

## Envelope 与业务数据边界

CModel envelope 解析器只读取并校验三个已知字段：

- `contractVersion`
- `errorCode`
- `data`

解析器必须忽略其他顶层字段。旧响应中残留的 `value`、`systemCurrentTime`、`error` 及其子字段都按未知顶层字段处理，不能导致协议解析失败，也不能继续作为业务判断或用户展示来源。为兼容协议版本字段引入前的 `1.0.0` 响应，消费者在 `contractVersion` 缺失时必须补成 `1.0.0`；一旦字段存在，非法 SemVer 或非 `1.0.0` 版本仍是协议错误。新生产者只能输出三个标准字段，并显式输出 `contractVersion`。

成功响应 `data` 的内部结构不由 CModel envelope 校验，具体 API、Module 方法或 Runtime `methodDefinition` 负责定义和校验。失败响应 `data` 由 CModel 统一定义为 `null` 或标准公开错误详情；消费者必须校验 `message` 和 `retryable`，并原样保留对象中的未知属性，以允许具体 API 在 `1.0.0` 中追加已文档化的公开操作信息。未知属性不能用于错误分类；分类仍只依赖该 API 登记的稳定 `errorCode`。

## 服务级错误传播路径

失败详情可携带可选的 `data.errorPath`，表示当前错误返回时经过的逻辑服务，按外层服务到错误源排列。
它是非空字符串数组，每个标识非空且不含空白或控制字符；允许真实的重复服务调用。
只有相应 API 已登记可公开的逻辑服务标识才能对外输出，内部服务拓扑不因该字段自动成为公共契约。

```json
{
  "contractVersion": "1.0.0",
  "errorCode": "PAYMENT_REQUIRED",
  "data": {
    "message": "当前账户余额不足，请充值后重试",
    "retryable": false,
    "errorPath": ["checkout", "payment"]
  }
}
```

示例标识仅用于解释排列顺序，不是平台服务目录。

平台 Runtime 和模块 API 登记以下公开诊断标识；它们表示逻辑职责，不包含部署地址或实例身份：

| 逻辑服务标识             | 职责                    |
| ------------------ | --------------------- |
| `runtime`          | Runtime 请求执行入口        |
| `runtime-assembly` | Runtime 装配与 Bundle 操作 |
| `module`           | 模块定义与版本               |

例如 `errorPath: ["runtime", "runtime-assembly", "module"]` 表示错误由模块服务返回，经过 Runtime 装配服务和执行入口传出。路径仅包含这次错误实际经过且已接入的服务，不保证覆盖尚未接入的服务；部署副本数不增加路径层级。服务身份由版本化配置登记，公开标识的新增或变化须同步 API 文档。

错误源初始化路径，每个上游服务由统一 SDK／响应出口
在数组头部追加自己的配置身份，一次服务调用只追加一次。客户端解析只校验并保留路径，不追加当前服务，
不修改原始 `errorCode`、`message`、`retryable` 或其他业务详情，也不把路径拼进 `message`。

服务内的方法栈、源码行号、实例地址和语言级 stacktrace 不写入路径；完整诊断通过追踪上下文关联内部日志。
该路径只描述返回的这次错误，不是完整调用图，不能用于鉴权、错误分类或重试决策。

该可选扩展保持 `1.0.0` 及原有必填字段：`data: null` 仍合法，不能改写成仅含 `errorPath` 的对象。
新接入的错误生产者应提供完整 `message`、`retryable` 及路径；上游不得为了补路径编造缺失的错误详情或
未收到的下游路径。成功响应的业务 `data` 不受此诊断字段约束，也不自动追加路径。

公网 Adapter 对未登记为公开诊断的内部路径执行移除，或按明确的 API 契约映射为公开逻辑服务标识。
映射不依赖消息文本，外部输入的路径不能冒充可信内部服务路径。

## 错误身份与公开边界

平台内部错误目录可以继续保存错误所有者、原因、分类、重试语义、用户文案和诊断索引，但公共调用方只依赖当前 API 文档登记的：

- `errorCode`
- 该 API 对此错误的稳定说明
- `data.message`，仅用于用户展示
- `data.retryable`，仅用于决定是否自动重试

最外层 Adapter 负责把内部错误确定性映射成 `errorCode`，只把已经审查为可公开展示的用户文案和稳定重试语义写入失败 `data`，并把有效 CModel envelope 统一输出为 HTTP `200`。原始上游错误消息、数据库错误、堆栈、Token、Secret、内部域名、完整请求体和个人数据不能进入公共响应。

## HTTP 消费规则

调用方收到有效 CModel envelope 后，必须解析 envelope 并以 `errorCode` 判断业务结果：

1. HTTP `200` 可以承载成功或业务失败；`errorCode == "0"` 为成功，非 `"0"` 为失败。
2. 失败响应的 `data` 可以是 `null` 或标准 `{ message, retryable }` 对象；`null` 兼容已有生产者。
3. `contractVersion` 缺失时按 `1.0.0` 处理；显式值非法或不受支持时报告协议错误，不做版本协商。
4. 不接受数字 `0`、`error_code`、顶层 `code/message` 或缺失 `errorCode`。
5. 忽略所有未知顶层字段；成功 `data` 交给具体接口的类型解析器，失败 `data` 交给 CModel 标准错误详情解析器。标准解析器校验 `message`、`retryable` 并保留端点扩展字段，但不得用扩展字段猜测错误类别。
6. 错误分类只使用 `errorCode` 和当前 API 文档；不得用 HTTP 状态、`value`、`data.message` 或上游自然语言文本分类。

为支持已上线服务滚动迁移，SDK `1.1.4` 的消费者可以解析历史非 `200` 失败 envelope，但仍只依据非零 `errorCode` 返回业务失败；所有新生产者和已迁移生产者必须输出 HTTP `200`。当生产者盘点确认不再存在历史响应后，删除这项有明确退出条件的 consumer 兼容行为。

## 流式错误

流式协议不使用 CModel envelope。负责该流的协议必须定义自己的版本化错误事件，并在进入百积木非流式业务边界时由所属 Adapter 完成一次确定性转换。

不得把流内失败当作普通文本、成功数据或 JSON 解析异常吞掉。

## 收敛要求

生产者、消费者和 Gateway 必须直接收敛到同一个 `1.0.0` 三字段模型：

1. 消费者只解析三个已知字段并忽略未知顶层字段；缺失的 `contractVersion` 统一规范化为 `1.0.0`。
2. 生产者只序列化三个字段。
3. Gateway 保留已经校验的错误码、原始消息和业务详情，仅按服务出口及公开可见性规则补充或移除 `data.errorPath`，对调用方统一输出 HTTP `200`。
4. 不增加双版本或版本协商；滚动迁移兼容只存在于 SDK consumer 边界，并以生产者全量迁移为删除条件。
5. 生产者/消费者契约测试必须同时覆盖 HTTP `200` 成功与失败、未知顶层字段、成功 `data` 类型所有权，以及失败 `data` 为 `null` 和标准错误详情两种形态。
