# 协议契约

更新时间：2026-04-23

本文档描述 `orion-error` 当前已经落地的协议层设计。

这里说的“协议层”不是新的 runtime 传播模型，而是对外稳定消费接口。

## 1. 三层结构

当前协议层由三层组成：

1. 稳定身份：`ErrorIdentity`
2. exposure 决策：`ExposureDecision`
3. 出口投影：HTTP / CLI / log / RPC / user debug

推荐把它理解为：

- `StructError<R>` 负责运行时传播
- `ErrorIdentity` 负责稳定识别
- `DiagnosticReport` 负责人类诊断
- `ErrorProtocolSnapshot` 负责把 identity + decision + report 组装成统一消费输入

## 2. 稳定身份

稳定身份结构是 [`ErrorIdentity`](/Users/zuowenjian/devspace/wp-labs/dev/crates/orion-error/src/core/snapshot.rs:134)。

字段：

- `code`
- `category`
- `reason`
- `detail`
- `position`
- `want`
- `path`

语义：

- `code`：稳定机器主键
- `category`：稳定分类
- `reason`：稳定的人类摘要
- `detail`：可变补充说明，不是主键
- `want` / `path`：兼容投影字段；当前主语义应优先理解为 `action` / `locator`

入口：

- `StructError::identity_snapshot()`
- `assert_err_code(...)`
- `assert_err_category(...)`
- `assert_err_identity(...)`

注意：当前 `assert_err_code(...)` 断言的是 stable code 字符串，不是数值 `error_code()`。

## 3. Exposure

exposure 决策结构是 [`ExposureDecision`](/Users/zuowenjian/devspace/wp-labs/dev/crates/orion-error/src/core/report.rs:82)。

字段：

- `http_status`
- `visibility`
- `default_hints`
- `retryable`

默认 exposure 策略实现是 `DefaultExposurePolicy`。

当前默认规则：

- `Biz -> 400 + Public`
- `Conf / Logic / Sys -> 500 + Internal`
- `sys.network_error` / `sys.timeout` -> `retryable = true`
- 其他 stable code 默认 `retryable = false`

说明：

- 当前文档中的运行时主路径仍然是 `doing(...)`
- `want` 继续存在于 identity / report / snapshot 投影里，主要用于兼容旧消费方
- 不应在新文档或新示例里把 `want(...)` 当成主路径 API 介绍

主要入口：

- `ExposurePolicy::decide(...)`
- `StructError::exposure_snapshot(...)`
- `StructError::into_exposure_snapshot(...)`
- `ErrorProtocolSnapshot::from_report(report, identity, policy)`

## 4. `ErrorProtocolSnapshot`

`ErrorProtocolSnapshot` 是当前统一协议输入。

结构：

- `identity`
- `decision`
- `report`

入口：

- `StructError::exposure_snapshot(...)`
- `StructError::into_exposure_snapshot(...)`
- `ErrorProtocolSnapshot::from_report(report, identity, policy)`

适用场景：

- 测试快照
- 网关二次投影
- 协议统一出口
- 用户调试摘要

## 5. HTTP Projection

JSON 字段：

- `status`
- `code`
- `category`
- `message`
- `visibility`
- `hints`

规则：

- `Public` 时，`message` 优先使用 `detail`
- `Internal` 时，`message` 使用稳定 `reason`

入口（需要 `serde_json` feature）：

- `ErrorProtocolSnapshot::to_http_error_json()`

## 6. CLI Projection

JSON 字段：

- `code`
- `category`
- `summary`
- `detail`
- `visibility`
- `hints`

规则：

- `summary` 使用 compact render
- `detail` 使用 verbose render

入口（需要 `serde_json` feature）：

- `ErrorProtocolSnapshot::to_cli_error_json()`

## 7. Log Projection

JSON 字段：

- `code`
- `category`
- `reason`
- `detail`
- `operation`
- `path`
- `visibility`
- `hints`
- `root_metadata`
- `context`
- `source_frames`

规则：

- 保留完整 `context`
- 保留 `root_metadata`
- 保留 `source_frames`

入口（需要 `serde_json` feature）：

- `ErrorProtocolSnapshot::to_log_error_json()`

## 8. RPC Projection

JSON 字段：

- `status`
- `code`
- `category`
- `reason`
- `detail`
- `visibility`
- `hints`
- `retryable`

规则：

- `detail` 只在 `Public` 可见时保留
- `retryable` 完全来自 exposure decision

入口（需要 `serde_json` feature）：

- `ErrorProtocolSnapshot::to_rpc_error_json()`

## 9. User Debug Summary

`render_user_debug(...)` 是给人看的调试摘要，不是机器协议。

入口：

- `ErrorProtocolSnapshot::render_user_debug()`
- `ErrorProtocolSnapshot::render_user_debug_redacted(...)`

它的定位是：

- 本地调试
- 示例输出
- 人工排障

它不是：

- public HTTP message
- 稳定 JSON schema

## 10. `DiagnosticReport`

`DiagnosticReport` 是 report 层对象。

它不依赖 `ErrorIdentityProvider`，因此更适合：

- 文本渲染
- redaction
- 人类诊断

常用入口：

- `StructError::report()`
- `StructError::into_report()`
- `ErrorSnapshot::report()`
- `StableErrorSnapshot::report()`

如果启用了 `serde_json` feature，还可以使用：

- `DiagnosticReport::to_exposure_snapshot_json(...)`
- `DiagnosticReport::to_exposure_snapshot_json(...)`

## 11. 建议的消费路径

推荐顺序：

1. 运行时传播用 `StructError<R>`
2. 要稳定识别时取 `identity_snapshot()`
3. 要统一出口规则时取 `exposure_snapshot(...)`
4. 要协议出口时使用 projection API
5. 要人类摘要时使用 `render_user_debug(...)`

不建议：

- 直接把 `Display` 文本当协议主键
- 直接把 CLI 文本当机器协议
- 用 `detail` 全文本做唯一稳定断言
