错误系统设计
SecurityDept 将内部诊断与公开错误展示分离。目标是在保留可运维日志的同时,避免向 browser 和 API consumer 泄露 provider、token、storage 或 configuration 细节。
Structured diagnosis 是与 public error presentation 分离的另一层契约。面向 operator 的、secret-safe machine-readable auth-flow diagnosis 属于 securitydept-utils::observability,应独立保留,而不是被折叠成 adopter-facing error copy。
当前模型
当前模型有三层彼此独立的语义:
- 内部错误枚举,通常由
snafu实现。 - 通过
ToHttpStatus或 route/service status helpers 映射 HTTP status。 - 通过
ToErrorPresentation生成安全的 public presentation。
TS SDK 对应采用四层边界:
ClientError是公开异步流程的 canonical runtime exception;kind、namespacedcode、recovery、source与cause是 machine contract。ResourceSnapshot可以保留原始 error object,供调用方诊断和重试控制使用。- lifecycle events 与 tracing 只投影 secret-safe
ErrorSummary:errorName、可选errorKind、errorCode与recovery,不复制 runtime message。 readErrorPresentationDescriptor()只读取显式安全presentation、domain code mapping 或 foundation generic kind copy,绝不把Error.message当成用户文案。
在 TypeScript API 中,ServerErrorPresentation 表示服务端提供的安全载荷,ErrorCodePresentation 配置稳定 error code 对应的 host 文案,ErrorPresentationDescriptor 是最终 UI 投影。最终投影只包含 code、展示文案、recovery、tone 与可选 recovery action;machine policy 所需的 kind、source、retryable 应从原始 ClientError 读取。
TS SDK 的 ClientErrorKind 固定为 authorization、transport、server、protocol、storage、configuration、unauthenticated、unauthorized、cancelled、timeout 与 internal。Schema/options 输入错误属于 configuration;remote 或 persisted payload 错误属于 protocol;未知错误在公开 client operation boundary 被包装为 internal 并保留 cause。
共享类型位于 securitydept-utils:
ErrorPresentationUserRecoveryServerErrorKindServerErrorDescriptorServerErrorEnvelopeToErrorPresentation
相关 diagnosis vocabulary 独立位于 securitydept-utils::observability,包括 AuthFlowDiagnosis、DiagnosedResult、共享 AuthFlowOperation 名称(例如 projection.config_fetch、oidc.callback、oidc.token_refresh、propagation.forward、forward_auth.check),以及较新的 server operation,如 dashboard_auth.check 与 creds_manage.group.* / creds_manage.entry.*。
通过 shared envelope 渲染的 server response 使用以下形状:
{
"success": false,
"status": 401,
"error": {
"kind": "unauthenticated",
"code": "backend_oidc_mode.bearer_token_required",
"message": "A bearer access token is required for this endpoint.",
"recovery": "reauthenticate",
"presentation": {
"code": "backend_oidc_mode.bearer_token_required",
"message": "A bearer access token is required for this endpoint.",
"recovery": "reauthenticate"
}
}
}顶层 code / message / recovery 是稳定 consumer convenience fields。嵌套的 presentation 保留原始 presentation object,供已经消费它的客户端使用。
Public Response 规则
- 不要从
Display、source.to_string()、provider response body、transport error 或任意 lower-layer text 派生 public message。 - 如果 lower-layer condition 对用户有意义,先提升成 typed variant 或 typed reason enum,再公开。
- 只有当 condition 安全且可行动时,public message 才能具体。
- 内部日志应包含完整 error chain;public response 只包含 sanitized code、message 与 recovery。
- Structured diagnosis fields 应保持 machine-readable 且 secret-safe。它们用于日志与 operator tooling,不应被当作生成 public browser/API copy 的捷径。
Recovery Vocabulary
UserRecovery 是公开 action hint:
none:不暗示用户恢复动作。retry:可以合理重试同一操作。restart_flow:重新开始当前 auth flow。reauthenticate:重新登录或重新获取凭证。contact_support:用户无法自行解决。
它优于 retryable、reauth_required 等多个布尔字段,因为枚举能避免含义模糊的组合。
Disclosure Policy
| 分类 | Public message 风格 | 示例 |
|---|---|---|
| Safe and specific | 解释可恢复问题 | invalid redirect URL、expired login request、duplicate callback state |
| Safe but generic | 保持上下文宽泛 | session expired、authentication required、access denied |
| Internal only | 永不公开 raw detail | metadata fetch errors、introspection transport failures、storage errors、crypto / sealing failures |
Route Response Exceptions
大多数普通 server failures 使用 ServerErrorEnvelope,但部分 route families 有意不使用:
- Basic Auth challenge routes 必须保留
WWW-Authenticate。 - Basic Auth 不存在 credential-revocation route;本地投影清理不得伪装成 server protocol response。
- ForwardAuth challenge routes 必须保留 proxy protocol semantics。
- Backend-mode
metadata/redeemnot-found 是业务404,不是 shared server failure envelope。 - Propagation forwarding 保留 underlying upstream status / presentation,而不是改写成 route-local generic error。
- Static web UI fallback errors 来自底层 static-file service。
apps/server/src/routes/policy.rs 中的 mounted-route policy table 记录这些边界,防止新 route 静默回退 response shape。
Implementation Owners
packages/utils/src/error.rs:shared presentation 与 envelope types。packages/oidc-client/src/error.rs:OIDC public presentation。packages/oauth-resource-server/src/error.rs:resource-server presentation。packages/creds/src/error.rs:credential verification presentation。packages/creds-manage/src/error.rs:credential-management presentation。packages/session-context/src/lib.rs与packages/session-context/src/service.rs:session presentation。packages/basic-auth-context/src/service.rs:Basic Auth context presentation。packages/token-set-context/src/**/error.rs与 service modules:token-set、substrate、propagation、forwarder presentation。apps/server/src/error.rs:shared server envelope rendering。apps/server/src/routes/policy.rs:mounted-route response-shape policy。apps/webui:host-side presentation components 与 recovery links。
维护规则
- 新 public route 必须有明确 response-shape classification。
- 能到达 adopter-facing response 的新 error variant 必须实现 safe presentation。
- 如果会破坏 browser 或 proxy semantics,protocol-specific routes 不得被“统一”进 shared JSON envelope。
- Tests 应断言
kind、code与recovery;message text 可以验证,但不能成为唯一 machine contract。 - TS SDK public operation 不得让未知裸
Error逃逸;使用ClientError.fromUnknown(...)建立稳定 boundary,已有ClientError不重复包装。 - Error code 必须由 domain owner 以
const + type集中定义,并使用 domain namespace。 - Presentation mapping 由 domain owner 提供;foundation 不包含 popup、OIDC 等应用层文案。