Errors
The machine's failure model. The governing incident: a solve failing with
the string "unwrap on None" and no location, no subject, no demand chain.
[SETTLED] Machine fallibility is one typed error enum (MachineError).
Result<_, String> is forbidden everywhere in the machine.
[SETTLED] Every MachineError carries: the operation, the subject's
identity (schema + content hash) where one exists, the current vix source
span where applicable, and the demand chain (the breadcrumb of demands that
led here). MachineError is the immediate machine-error plane and may carry
current context directly. A stored language Failure is a value on a
separate plane: its identity does not include byte offsets, the live source
map, or the live demand chain; those are resolved when the failure is
reported.
An error that cannot name its subject when a published subject exists is a bug in error construction.
[SETTLED] Option is not an error channel. Fallible operations return
Result; absence-as-failure erases the failure's address by construction.
[DESIGN] MachineError implements From for its component errors so ?
propagates without stringification (thiserror-style; snark-dsl is the
in-house precedent).
[SETTLED] A Vix-level unwrap of None produces a Failure with typed
UnwrapOnNone payload and the unwrap source site
(machine.error.failure-source-site-identity). Reporting resolves that site
through the current lowering attribution to obtain the current source span
and reconstructs the current demand chain. This is a language outcome, not a
machine invariant error; bare strings and site-less unwrap requests are
banned.
[SETTLED] A structural impossibility — a state the types claim cannot happen
(comparator index out of bounds, post-force pending, malformed verified input)
— is a typed MachineError or execution fault with attribution. It is never
folded into a legitimate-miss or Ok(false) path. A debug assertion may
accompany an already-typed error path, but panic is not an externally visible
or production execution outcome. (Twin of machine.obs.loud-fallbacks.)
[SETTLED] A dense-array read outside 0..len is a typed IndexOutOfBounds
demand failure carrying the demanded index, the array's length, and the
indexing source site (machine.error.failure-source-site-identity). The
machine's checked array-read vocabulary reports access through a closed typed
status that distinguishes success, out-of-range, invalid handle, malformed
payload, uninitialized element, width mismatch, schema mismatch, arithmetic
overflow, and allocation failure. Only the out-of-range status becomes the
language IndexOutOfBounds failure; malformed, invalid, schema/width,
overflow, allocation, uninitialized, and impossible machine conditions remain
typed MachineErrors or execution faults with attribution. A lowering or
substrate path that folds the miss into a zero element, a wrapped index, an
Option, a single present witness, or any other default/absence collapse
has erased the failure's address. Unlike
machine.error.structural-impossibility, an out-of-bounds
index is a legitimate program outcome, not an invariant break.
A malformed array payload is a typed machine invariant error, never an
IndexOutOfBounds language failure. If the array is a task-private molten
value that has not crossed a publication boundary, there is no subject value
identity to attach; the failure identity uses no subject slot, and the
payload's index and length are the stable subject detail.
[SETTLED] Map.get(key) is an addressed read whose success type is the map's
value type. When the key is absent, the demand completes with a typed
MissingKey { key } language failure carrying the get operation's stable
source-site identity. The map's value identity is the subject when it has
crossed a publication boundary. A miss is never None, a default value, an
UnwrapOnNone, a machine invariant, or a process panic. map.has(key) is the
separate membership query and does not demand the stored value.
Postfix ? on the get expression observes the outcome edge as
Result<V, Failure>. It catches any failure of that projection, not only
MissingKey; it does not modify or replace the producer's memoized outcome.
[SETTLED] Extending a map with + fails with DuplicateKey { key } when the
left map already contains the key. Combining maps with ++ fails when their
key sets overlap. The failure carries the operator's stable source-site
identity and chooses conflicting keys deterministically in structural key
order. Authored duplicate keys within one map literal remain a compile-time
source diagnostic. map.with (key, value) is the distinct overwrite
operation and never reports DuplicateKey merely because the key existed.
[DESIGN, round 11] A failure is a value. It has a schema and a content hash
like anything else (machine.identity.value-identity-pair), so it can be stored,
memoized, put in a record, and returned. "The demand has no answer" is rhetoric;
the demand's answer is a Failure.
A demand's memo entry therefore stores an outcome, not a result:
Outcome<T> = Ok(T) | Failed(Failure). fail e has type ! and typechecks
anywhere.
Propagation is a rule of the machine, not a property of the value. Demanding a
value whose outcome is Failed(f) makes your outcome Failed(f) too, unless you
catch it with ?. Poison is per-demand: of two hundred sibling compiles, one
failing poisons only what demanded it.
The stored Failure is span-free and chain-free by construction. Its value
bytes may contain a stable source-site identity
(machine.error.failure-source-site-identity), never a raw byte range, live
source map, or live caller chain.
[SETTLED] A Failure has an intrinsic source-site identity so two distinct
failing operations with identical payloads remain distinct values. The
canonical source-site identity is the producing island/closure RecipeId
plus a stable closure-local node or trace-site identifier. The RecipeId is
the semantic producer component; the closure-local site id identifies the
raising operation within that producer.
Source-site identity excludes byte offsets, rendered source spans, the live
source map, the whole live demand key, and the live demand chain. Reporting
resolves the stored site through the current LoweringAttribution /
source-map chain to obtain the current span.
A Failure value identity is framed in a dedicated failure-identity domain
over (typed payload, optional subject value identity, stable source-site identity). The subject identity is the value identity
(machine.identity.value-identity-pair) of the subject where one has been
published. Subject absence is explicit and means no published semantic subject
exists, not that the subject is unknown after being available.
[SETTLED, round 11 — forced by failure-is-a-value] The demand chain is not part
of a failure's content identity. A failure's identity is intrinsic:
typed payload, optional subject value identity, and stable source-site
identity (machine.error.failure-source-site-identity). The chain is
context — it names who asked — and it differs per caller.
Were the chain in the identity, the same failing computation demanded from two
places would be two different values, and the memo would never hit. The chain is
instead reconstructed at the moment of observation, by reading the live demand
map (round-5 verdict: "error demand-chain = read of live demand map at failure
time; no retention"). machine.error.carries-context is satisfied at the point of
report, not at the point of construction.
Raw byte spans follow the same rule: they are per-compilation observation
context, not stable identity. A whitespace-only or span-only edit preserves the
failure value identity and the cached Failed(Failure) outcome while the
reported span is reconstructed from the current lowering/source-map state.
[DESIGN, round 11] Because a failure is a value and an outcome is memoized, a failing demand is an ordinary memo entry — with its read-set.
Consequences, and the second one is a product property nobody else has:
- A build that failed yesterday fails instantly today, with the identical failure value and a diagnostic re-rendered through the current attribution, without rerunning the compiler.
- Early cutoff applies to failures. A failed compile depended on exactly the files its read-set names. Change anything outside that set — the README, an unrelated crate — and the failure is still valid, proven, and reported without recomputation. Change something inside it, and only then does the compiler run.
Observing a cached failure reconstructs the current span and demand chain without re-executing the failed demand. A failure is not a special case of the memo. It is the memo, working.