Leia

Errors And Diagnostics

Leia distinguishes runtime errors from recoverable structured failures. A runtime error aborts the current expression or statement and unwinds the active call stack until it reaches a protected-call boundary or the top-level execution boundary. Deferred calls run according to the statement semantics for defer; errors raised while unwinding replace or report through the same runtime error path as other deferred-call failures.

Error Values

An error value is any Leia value carried by an error path. Script code can raise strings, numbers, booleans, nil, tables, functions, and other values. Leia does not normalize a script-raised table into a standard diagnostic shape and does not stringify script-raised non-strings before handing them to pcall or an xpcall handler.

The v1.0 contract for script-raised error values is identity-preserving for identity-bearing values. A table raised with error(t) is the same table received by pcall or by an xpcall handler. If the table already has fields such as kind, message, code, source, or retryable, those fields are ordinary user data unless a specific library documents that table as a structured result.

```leia run all payload := {kind: “demo”, message: “boom”} ok, err := pcall(func() { error(payload) }) assert(!ok) assert(err == payload) assert(err.kind == “demo”)


```leia run all
ok, err := pcall(error, nil)
assert(!ok)
assert(err == nil)

ok, err = pcall(error, false)
assert(!ok)
assert(err == false)

Runtime errors raised by invalid operations, host callbacks, parser/compiler operations called from script, resource budgets, and capability checks are represented inside pcall and xpcall as diagnostic strings.

```leia run all ok, err := pcall(func() { return “x” + 3 }) assert(!ok) assert(type(err) == “string”)


## `error`

`error(value)` raises exactly `value`. `error()` raises a string error value.
Extra arguments to `error` are evaluated by the call expression but ignored by
the v1.0 `error` contract; the first argument is the only raised value.

```leia run all
ok, err := pcall(func() {
    error()
})
assert(!ok)
assert(type(err) == "string")

ok, err = pcall(error, "first", "ignored")
assert(!ok)
assert(err == "first")

assert(false, value) and assert(nil, value) raise value when a second argument is present. If no message argument is present, the failure is a runtime diagnostic string.

```leia run all payload := {kind: “assertion”} ok, err := pcall(func() { assert(false, payload) }) assert(!ok) assert(err == payload)

ok, err = pcall(assert, false) assert(!ok) assert(type(err) == “string”)


## Protected Calls

`pcall(fn, ...)` calls `fn(...)` in protected mode. On success it returns
`true` followed by all results from the call. On failure it returns `false` and
one error object. If the failure came from `error(value)` or `assert(false,
value)`, the object is `value`; if it came from a runtime, host, parser,
budget, or capability failure, the protected error object is the current
diagnostic string.

```leia run all
ok, a, b := pcall(func() {
    return "a", "b"
})

assert(ok)
assert(a == "a")
assert(b == "b")

The first argument to pcall is evaluated before the protected call begins. The protected region includes the attempt to call that value, so a non-callable first argument is returned as false, err. With no first argument, pcall() raises an unprotected argument error at the call site.

Arguments after the function are evaluated before entering the callee, then passed normally. Once the callable is entered, both script-raised errors and runtime errors are converted to the protected result form.

```leia run all ok, err := pcall(17) assert(!ok) assert(type(err) == “string”)


`xpcall(fn, handler, ...)` calls `fn(...)` in protected mode. On success it
returns `true` followed by all results from `fn`. On failure it calls
`handler(err)` and returns `false` plus the handler's first result. Extra
handler results are discarded. If the handler returns no values, the second
`xpcall` result is `nil`. If the handler itself fails, `xpcall` returns `false`
plus the handler failure's error object.

```leia run all
ok, value := pcall(func(a, b) {
    return a + b, "kept"
}, 2, 5)
assert(ok && value == 7)

ok, handled := xpcall(error, func(err) {
    return "handled:" .. tostring(err)
}, "boom")
assert(!ok && handled == "handled:boom")

```leia run all ok, a, b := xpcall(func() { error(“bad”) }, func(err) { return “handled:” .. err, “discarded” }) assert(!ok) assert(a == “handled:bad”) assert(b == nil)


```leia run all
ok, value := xpcall(func() {
    error("bad")
}, func(err) {
})
assert(!ok)
assert(value == nil)

Like pcall, xpcall() and xpcall(fn) raise unprotected argument errors because the protected function and handler arguments are missing.

```leia fail all pcall()


```leia fail all
xpcall(print)

Protected calls are recovery boundaries for ordinary script failures. They do not make process termination, cancellation, host shutdown, or bugs in the host process safe to continue unless the embedding API explicitly reports those conditions as ordinary errors.

Runtime Errors

The following conditions must raise runtime errors when they occur during execution:

Error class Required triggers
Missing or invalid arguments Calling a builtin without a required argument; passing a value of the wrong type; passing an invalid option, index, range, mode, pattern, or table shape required by that API.
Invalid calls Calling a non-function value with no __call metamethod; calling pcall or xpcall without their required protected-call arguments.
Invalid operators Arithmetic, bitwise, concatenation, length, or ordering over unsupported values when no matching metamethod applies.
Invalid assignment and table protocol Assigning through an invalid target; indexing or assigning through a table/metatable protocol that exceeds supported __index or __newindex chains; using an invalid metamethod result such as a non-string __tostring; attempting to change a protected metatable.
Invalid control flow and coroutine use Yielding outside a yieldable coroutine boundary; resuming an invalid coroutine state; invalid channel send, receive, close, or select operations.
Module and script loading failures Module not found; module loading disabled; module path escaping the configured filesystem root; malformed source passed to a script compile/load API.
Resource and capability failures Step, native-call, call-depth, goroutine, channel-capacity, host-result, module-byte, or module-depth budget exhaustion; filesystem, network, process, debug, testkit, dynamic-eval, or module-loading capability denial.
Host failures surfaced as runtime errors Registered callback errors, callback panics recovered by the embedding API, provider errors, filesystem errors, network errors, and sandbox denials when the API is not designed to return a recoverable structured result.

Runtime error diagnostic strings are for humans. The stable script-level contract is their type (string), recovery boundary, and association with the failing operation, not exact wording. Tests and portable programs must not parse runtime diagnostic prose unless a specific CLI JSON field or library result table documents that text as machine-readable.

```leia run all func fails(f) { ok, err := pcall(f) assert(!ok) assert(type(err) == “string”) }

fails(func() { return {}() }) fails(func() { return “a” .. false }) fails(func() { return math.sin() }) fails(func() { coroutine.yield() }) ```

Structured Recoverable Failures

Recoverable host and provider failures should return nil, err or a structured result when the API is designed for recovery. Error result tables use stable, lowercase string fields when structured recovery is intended:

Field Meaning
kind Machine-readable category such as validation, provider, network, rate_limit, budget, capability, or an API-specific kind.
message Human-readable diagnostic string safe to expose under the active capability policy.
code Optional machine-readable API/provider code.
source Optional subsystem or provider name.
retryable Optional boolean hint for transient failures.

Host and provider APIs should use stable kind and code values when callers need branching behavior:

Kind Typical codes
host API-specific codes for callback, provider, process, filesystem, network, or environment failures. Public Go callback failures surface as *leia.HostCallbackError; callback panics surface as *leia.HostCallbackPanicError.
capability A denied capability name or API-specific denial code, such as disabled filesystem read/write, network, process execution, debug, testkit, dynamic eval, or module loading. Capability denials are currently runtime diagnostics unless a library intentionally returns a structured error table.
budget One of the current public budget resources: steps, native_calls, call_depth, goroutines, channel_capacity, host_result_bytes, module_bytes, or module_depth. Public execution APIs expose these through *leia.BudgetError.Resource and Limit.
provider Provider-specific API codes, model errors, rate limits, authentication failures, or validation failures.

LLM provider failures, filesystem failures, network failures, and sandbox denials must not leak data forbidden by the active capability policy.

Inside Leia code, typed diagnostics are not automatically reified as tables. The current boundary is:

Boundary Error representation
Public Go APIs Typed Go errors such as *leia.Error, *leia.BudgetError, *leia.HostCallbackError, *leia.HostCallbackPanicError, and *leia.ExitError.
pcall / xpcall for error(value) The original Leia value.
pcall / xpcall for runtime, parser, host, budget, or capability failures A diagnostic string.
Recoverable library/provider APIs API-specific results, commonly nil, err or a table with fields such as kind, message, code, source, and retryable.
CLI diagnostics Command-specific text, JSON, or SARIF. JSON field names documented for a command are the stable interface; prose messages may change.

Public API Diagnostics

Public Go execution APIs expose typed errors before text formatting. *leia.Error has stable fields Kind, Message, Line, Col, File, Err, and Value. Kind is lex, parse, runtime, or script; script is used for error(value) and carries the converted original value in Value when conversion succeeds. Runtime, parser, and lexer errors use Message as a human diagnostic. Err unwraps to the underlying cause, including *leia.BudgetError, *leia.ExitError, host callback errors, and host callback panic errors when available.

Diagnostics should include source location when available. Stable source fields are source name or file path, line, and column. Public Go APIs map those to File, Line, and Col. Script-side debug APIs may expose sourceName, line, and column fields for frames and functions when debug access is enabled. String diagnostics may include the same coordinates in text, but callers that need stability should use typed Go errors, documented debug table fields, or CLI JSON.

Stack and traceback text is diagnostic output, not a stable machine interface. Frame ordering, formatting, function names, native/script labels, and stack depth are not stable unless a specific command or API documents them as structured fields.