BLUEPRINT
第 6 章 错误与可观测性
错误模型与可观测性是 Fayger 横切三层的两条公共导线。它们让外部调用方能用同一套语义理解失败、定位失败、监控运行。
6.1 统一错误模型
struct FaygerError {
code: ErrorCode
source_layer: Layer // loader | runtime | adapter | host
message: String
context: Map<String, Value>
cause: Option<Box<FaygerError>>
}
字段语义:
- code:稳定的、可文档化的标识。用于调用方分支决策;不在跨版本中变更语义。
- source_layer:标注最初产生错误的层。便于路由到对应模块的诊断手册。
- message:面向人类的描述。短句,不重复 code 信息。
- context:面向工具的结构化键值对。至少包含触发错误的关键字段(offset、missing_fields、current_state、expected_range 等)。
- cause:跨层错误的因果链。包装错误时把底层错误置入 cause,避免吞掉信息。
6.2 错误产生与传播规则
- 就近构造:每一层在自己的边界内构造
FaygerError并标注source_layer。 - 逐级包装:上层若需要补充语义,必须在
cause中保留下层错误,不得改写source_layer。 - 不丢失上下文:
context至少包含触发错误的关键字段。 - 失败保留:进入
Failed的 BuF_Instance 必须把最末一条 Universal_Instruction 与对应错误固化到failure_info中,直到实例被显式销毁。
6.3 错误链示例
一次跨三层的错误传播大致如下:
[top] RT_INSTANCE_FAILED (source_layer = runtime)
└── cause:
[mid] ADP_HOST_CALL_FAILED (source_layer = adapter)
└── cause:
[low] <Host 原生错误> (source_layer = host)
调用方沿 cause 遍历得到的层序列等于 [runtime, adapter, host],与实际错误产生顺序的"由内向外"层数对应一致。这条性质在测试中用属性测试持续验证。
6.4 关键错误码总览
| 来源层 | 错误码 | 触发条件 |
|---|---|---|
| Loader | LDR_PARSE_FAIL | BuF 字节流非法 |
| Loader | LDR_DIGEST_MISMATCH | Section 摘要不匹配(Eager) |
| Loader | LDR_LAZY_DIGEST_MISMATCH | Section 摘要不匹配(Lazy 首次访问) |
| Loader | LDR_SIGNATURE_FAIL | 签名缺失(强制模式)/ 签名无效 |
| Loader | LDR_SCHEMA_UNSUPPORTED | schema 版本不在支持范围 |
| Loader | LDR_RUNTIME_VERSION_TOO_HIGH | Runtime_Interface 最低版本高于当前 |
| Loader | LDR_MISSING_REQUIRED_FIELD | 序列化时 Manifest 缺必填字段 |
| Loader | LDR_PROFILE_REQUIRED_SECTION_MISSING | 当前 profile 下 Required Section 不可加载 |
| Loader | LDR_LAZY_SOURCE_UNAVAILABLE | Lazy 后续访问 BuF_Source 不可用 |
| Loader | LDR_SOURCE_READ_FAILED | BuF_Source 读取错误(Eager 或 Lazy) |
| Runtime | RT_ILLEGAL_TRANSITION | 非法生命周期迁移 |
| Runtime | RT_IMPL_NOT_FOUND | 路由策略未找到匹配实现 |
| Runtime | RT_IMPL_MISSING_METHODS | 注册的实现缺必需方法 |
| Runtime | RT_QUOTA_EXCEEDED | 资源使用超限 |
| Runtime | RT_INSTANCE_FAILED | 实例进入 Failed |
| Adapter | ADP_NO_MATCHING_PLATFORM | 没有适配器匹配当前宿主 |
| Adapter | ADP_UNSUPPORTED_INSTRUCTION | 当前适配器不支持该指令 |
| Adapter | ADP_CAPABILITY_DENIED | 能力被裁剪后不足以执行该指令 |
| Adapter | ADP_HOST_CALL_FAILED | 翻译为系统调用后宿主侧失败 |
加载层错误的 context.phase 字段标注 eager 或 lazy,便于调用方按阶段路由:启动期失败可触发回退到其他源、不同 profile 或重试;运行期 Lazy 失败通常仅影响该 Section 的访问,可由调用方决定是否降级。
6.5 可观测性事件总线
interface EventBus {
publish(event: FaygerEvent)
subscribe(filter: EventFilter, handler: EventHandler) -> SubscriptionId
set_debug_enabled(enabled: bool)
}
事件分类:
- Lifecycle Event:BuF_Instance 状态迁移,携带
from、to、timestamp、instance_id。 - Quota Event:资源采样、超限、暂停。
- Loader Event:加载阶段进入 / 完成 / 失败,携带阶段名。
- Adapter Event:指令派发、能力裁剪结果、不支持指令记录。
- Debug Event:仅在
set_debug_enabled(true)时输出,用于深度排查。
Lifecycle 事件序列一致性
对任意实例的合法迁移序列 [(s₀, s₁), (s₁, s₂), …],事件总线发出的事件序列必须满足:
- 长度相等。
- 每个事件
eᵢ.from == sᵢ₋₁、eᵢ.to == sᵢ。 eᵢ.timestamp单调非递减。
6.6 与宿主日志 / 追踪通道集成
当宿主提供日志或追踪通道(systemd-journal、OS Logger、浏览器 console、In-App SDK 通道等)时,适配层把 Fayger 内部事件映射到该通道:
- 映射规则由 Platform_Adapter 自描述,运行层无需感知通道差异。
- 事件级别(debug / info / warn / error)对应宿主通道的相应级别。
- Lifecycle Event 与 Loader Event 在 info 级别,Quota / Adapter 错误在 warn / error 级别。
如果宿主无日志通道,事件仅留在 EventBus 内供订阅者消费。
6.7 调试事件开关
调试级事件输出默认关闭。开启方式:
event_bus.set_debug_enabled(true)
调试事件包括:
- 每条 Universal_Instruction 的派发详情。
- 每次能力裁剪的输入与输出。
- 资源监测的每次采样值。
由于体量大,调试事件订阅者应主动过滤、限速或落盘。
6.8 状态查询接口
Fayger 必须暴露以下查询接口,用于运维与诊断:
| 查询 | 返回 |
|---|---|
state(instance_id) | 当前 Lifecycle_State |
failure_info(instance_id) | 触发失败的最末 Universal_Instruction 与错误对象 |
list_implementations() | 已注册 Runtime_Implementation 描述符列表 |
current_adapter() | 当前选中的 Platform_Adapter 描述符 |
granted_capabilities(instance_id) | 该实例被裁剪后实际拥有的能力集 |
这些接口都是只读、幂等、可在任意状态下调用的。
6.9 默认安全姿态
- 强制签名模式默认 关闭(出于开发便利),但发布版本建议在配置层默认 开启。
- 调试事件默认 关闭,需显式启用。
- 未声明的能力对 BuF 默认拒绝。
- 未识别的 Universal_Instruction 类别 默认拒绝,不允许"沉默放行"。
