第 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 错误产生与传播规则

  1. 就近构造:每一层在自己的边界内构造 FaygerError 并标注 source_layer
  2. 逐级包装:上层若需要补充语义,必须在 cause 中保留下层错误,不得改写 source_layer
  3. 不丢失上下文context 至少包含触发错误的关键字段。
  4. 失败保留:进入 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 关键错误码总览

来源层错误码触发条件
LoaderLDR_PARSE_FAILBuF 字节流非法
LoaderLDR_DIGEST_MISMATCHSection 摘要不匹配(Eager)
LoaderLDR_LAZY_DIGEST_MISMATCHSection 摘要不匹配(Lazy 首次访问)
LoaderLDR_SIGNATURE_FAIL签名缺失(强制模式)/ 签名无效
LoaderLDR_SCHEMA_UNSUPPORTEDschema 版本不在支持范围
LoaderLDR_RUNTIME_VERSION_TOO_HIGHRuntime_Interface 最低版本高于当前
LoaderLDR_MISSING_REQUIRED_FIELD序列化时 Manifest 缺必填字段
LoaderLDR_PROFILE_REQUIRED_SECTION_MISSING当前 profile 下 Required Section 不可加载
LoaderLDR_LAZY_SOURCE_UNAVAILABLELazy 后续访问 BuF_Source 不可用
LoaderLDR_SOURCE_READ_FAILEDBuF_Source 读取错误(Eager 或 Lazy)
RuntimeRT_ILLEGAL_TRANSITION非法生命周期迁移
RuntimeRT_IMPL_NOT_FOUND路由策略未找到匹配实现
RuntimeRT_IMPL_MISSING_METHODS注册的实现缺必需方法
RuntimeRT_QUOTA_EXCEEDED资源使用超限
RuntimeRT_INSTANCE_FAILED实例进入 Failed
AdapterADP_NO_MATCHING_PLATFORM没有适配器匹配当前宿主
AdapterADP_UNSUPPORTED_INSTRUCTION当前适配器不支持该指令
AdapterADP_CAPABILITY_DENIED能力被裁剪后不足以执行该指令
AdapterADP_HOST_CALL_FAILED翻译为系统调用后宿主侧失败

加载层错误的 context.phase 字段标注 eagerlazy,便于调用方按阶段路由:启动期失败可触发回退到其他源、不同 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 状态迁移,携带 fromtotimestampinstance_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 类别 默认拒绝,不允许"沉默放行"。