Chapter 6. Errors and Observability

The error model and observability are two cross-cutting wires across Fayger's three layers. They let external callers understand failures, locate failures, and monitor execution with one set of semantics.

6.1 Unified error model

struct FaygerError {
  code: ErrorCode
  source_layer: Layer       // loader | runtime | adapter | host
  message: String
  context: Map<String, Value>
  cause: Option<Box<FaygerError>>
}

Field semantics:

  • code. A stable, documentable identifier. Used for caller branching; semantics never change across versions.
  • source_layer. Tags the layer that originally produced the error. Enables routing to the right module's diagnostics.
  • message. Human-readable description. Short, does not duplicate code information.
  • context. Tool-facing structured key-value pairs. At minimum carries the key fields that triggered the error (offset, missing_fields, current_state, expected_range, …).
  • cause. The causal chain across layers. Wrapping errors place the lower-layer error in cause to avoid swallowing information.

6.2 Error production and propagation rules

  1. Construct close to the source. Each layer constructs FaygerError within its own boundary and tags source_layer.
  2. Wrap layer by layer. When an upper layer adds semantics, it must preserve the lower-layer error in cause and must not rewrite source_layer.
  3. Don't lose context. context carries the key fields that triggered the error.
  4. Failure preserved. A BuF_Instance entering Failed must persist the last Universal_Instruction and the corresponding error in failure_info until the instance is explicitly destroyed.

6.3 Error chain example

A typical three-layer propagation:

[top]  RT_INSTANCE_FAILED      (source_layer = runtime)
         └── cause:
[mid]    ADP_HOST_CALL_FAILED  (source_layer = adapter)
           └── cause:
[low]      <native host error> (source_layer = host)

Walking cause yields the layer sequence [runtime, adapter, host], matching the inside-out order in which the errors actually arose. The property is continuously verified by property-based testing.

6.4 Key error code overview

SourceError codeTrigger
LoaderLDR_PARSE_FAILBuF byte stream invalid
LoaderLDR_DIGEST_MISMATCHSection digest mismatch (Eager)
LoaderLDR_LAZY_DIGEST_MISMATCHSection digest mismatch on first Lazy access
LoaderLDR_SIGNATURE_FAILSignature missing in enforce mode / invalid signature
LoaderLDR_SCHEMA_UNSUPPORTEDschema version outside supported range
LoaderLDR_RUNTIME_VERSION_TOO_HIGHRuntime_Interface min higher than provided
LoaderLDR_MISSING_REQUIRED_FIELDManifest missing required fields during serialization
LoaderLDR_PROFILE_REQUIRED_SECTION_MISSINGRequired section unloadable under the current profile
LoaderLDR_LAZY_SOURCE_UNAVAILABLEBuF_Source unavailable on subsequent Lazy access
LoaderLDR_SOURCE_READ_FAILEDBuF_Source read error (Eager or Lazy)
RuntimeRT_ILLEGAL_TRANSITIONIllegal lifecycle transition
RuntimeRT_IMPL_NOT_FOUNDRouting did not find a matching implementation
RuntimeRT_IMPL_MISSING_METHODSRegistered implementation missing required methods
RuntimeRT_QUOTA_EXCEEDEDResource usage exceeds quota
RuntimeRT_INSTANCE_FAILEDInstance entered Failed
AdapterADP_NO_MATCHING_PLATFORMNo adapter matches the current host
AdapterADP_UNSUPPORTED_INSTRUCTIONThe current adapter does not support the instruction
AdapterADP_CAPABILITY_DENIEDCapabilities insufficient after trimming
AdapterADP_HOST_CALL_FAILEDTranslation to system call failed on host side

Loader-layer errors tag context.phase as eager or lazy so callers can route by phase: startup-time failure may trigger fallback to a different source / profile or a retry; runtime-time Lazy failure usually only affects access to that section, and the caller decides whether to degrade.

6.5 Observability event bus

interface EventBus {
  publish(event: FaygerEvent)
  subscribe(filter: EventFilter, handler: EventHandler) -> SubscriptionId
  set_debug_enabled(enabled: bool)
}

Event categories:

  • Lifecycle Event. BuF_Instance state transition, carrying from, to, timestamp, instance_id.
  • Quota Event. Resource sample, exceed, suspend.
  • Loader Event. Phase enter / complete / fail, carrying phase name.
  • Adapter Event. Instruction dispatch, capability negotiation outcome, unsupported instruction record.
  • Debug Event. Emitted only when set_debug_enabled(true). For deep diagnosis.

Lifecycle event sequence consistency

For any legal transition sequence [(s₀, s₁), (s₁, s₂), …], the emitted event sequence must satisfy:

  • Same length.
  • Each eᵢ.from == sᵢ₋₁, eᵢ.to == sᵢ.
  • eᵢ.timestamp is monotonically non-decreasing.

6.6 Integration with host log / trace channels

When the host provides log or trace channels (systemd-journal, OS Logger, browser console, In-App SDK channel, …), the adapter layer maps Fayger internal events to that channel:

  • The mapping is self-described by the Platform_Adapter; the runtime layer is unaware of channel differences.
  • Event levels (debug / info / warn / error) map to corresponding host levels.
  • Lifecycle Event and Loader Event are info; Quota / Adapter errors are warn / error.

If the host has no log channel, events stay in the EventBus for subscribers.

6.7 Debug event switch

Debug-level event output is off by default. Turn it on via:

event_bus.set_debug_enabled(true)

Debug events include:

  • Per-instruction dispatch details.
  • Per-trim input and output.
  • Per-sample resource readings.

Because of volume, debug subscribers should filter, rate-limit, or persist actively.

6.8 State query interface

Fayger must expose the following queries for ops and diagnostics:

QueryReturns
state(instance_id)Current Lifecycle_State
failure_info(instance_id)Last Universal_Instruction and error object that triggered failure
list_implementations()Registered Runtime_Implementation descriptors
current_adapter()Currently selected Platform_Adapter descriptor
granted_capabilities(instance_id)The instance's actual capability set after trimming

These interfaces are read-only, idempotent, and callable in any state.

6.9 Default safety stance

  • Enforce-signature mode is off by default (developer convenience), but release builds should default to on at the configuration layer.
  • Debug events are off by default; explicitly enable to opt in.
  • Undeclared capabilities are denied by default for BuF.
  • Unrecognized Universal_Instruction categories are denied by default; "silent pass-through" is not allowed.