<!-- Sema documentation — Error Handling
     Sema is a new AI-native, neurosymbolic programming language most models
     have not been trained on. Full corpus: https://sema.49.12.246.95.sslip.io/llms-full.txt
     Install language support: https://sema.49.12.246.95.sslip.io/install-skill/ -->

# Error Handling

> Sema has no exceptions — failures are typed values propagated with ?, handled with flat expect/except arms, and combinators. No cascade tax.

Sema has **no unwinding exceptions and no `raise`.** Every failure is a **typed
value** that flows through the program's types. You propagate it with `?`, handle
it with flat `expect … / except E as e:` arms, and transform it with prelude
combinators. Because failures are values, the effect system sees them, the debugger
can replay them, and blame is preserved end to end.

## Typed failures

Every failure the language names is a `struct` conforming to the prelude `Error`
trait (carrying a blame label, source span, evidence, and journal ref). The ones
you will meet most:

| Error | Raised by |
|---|---|
| `ContractViolation` | a failed `require`/`ensure`/`invariant` |
| `SemanticsViolation` | a failed `semantics(...)` guard |
| `OverflowError` | checked integer overflow |
| `DivisionByZero` | division/modulo by zero |
| `ValueError` | math-domain errors, bad conversions |
| `BudgetExceeded` | a `with budget(...)` cap being crossed |
| `DecodeError` | a failed typed decode / schema parse |
| `ForeignError` | a failure crossing an FFI / port boundary |
| `SimulationFailed` | a `simulate` body that could not satisfy its contract |

A fallible expression types as `Result[T, E]` (or the sum `T | E₁ | E₂` that
`expect` scrutinizes). There is no `null`; absence is `Option[T]`. See
[Types](/language/types/).

## `?` — propagate the failure upward

`?` unwraps the success value, or **returns the failure from the enclosing
function** — which must declare a compatible failure type. Propagation is **blame-
and trust-preserving**: a forwarded error keeps its original blame party and the
carried value's trust labels, so escalation cannot launder either.

```sema
def import_statement(path: Path) -> Result[Statement, IngestError] !{fs.read}:
    raw  = fs.read_text(path)?          # ? propagates the typed failure upward
    stmt = parse_statement(raw)?
    return Ok(stmt)
```

## `expect` / `except` — flat, ordered handling

`expect <expr>:` scrutinizes a fallible expression; `except E as e:` arms handle
each typed failure. The arms are **siblings, ordered, and exhaustiveness-checkable**
— there is no nesting and no unwinding:

```sema
expect rows = load_rows(path):
    reconcile(rows)
except ContractViolation as v:
    quarantine(path, evidence=v)
except ForeignError as e:
    escalate(e)
```

The handler value carries full evidence — for a `SemanticsViolation`, that means
`v.predicate`, `v.judge`, `v.score`, `v.threshold`, `v.excerpts`, and `v.blame`.

The `expect semantics(...): / except SemanticsViolation as v:` block (see
[Verification](/neurosymbolic/verification/)) is this exact construct applied to a
semantic predicate — the guarded expression types `T | SemanticsViolation` and the
`except` arm is the handling branch.

## `unwrap` and `expect`-message aborts

`unwrap()` is a **checked-region abort with blame** — it converts a failure into a
replayable `UnwrapFailed` fault. It is for cases you have already proven cannot
fail, or for scripts. `@assure(gold)` functions **reject reachable `unwrap`** the
way release builds reject a reachable `todo`:

```sema
value = maybe_value.unwrap()        # aborts with blame if it's None/Err
```

## Contracts as failures

Contract clauses raise typed `ContractViolation` values on failure — and crucially,
the raw result **cannot flow onward** (this is the core Sema principle: a value that
fails its contract is *typed as failed*):

```sema
def normalize(scores: list[f32]) -> list[f32]:
    require len(scores) > 0
    ensure  all(0.0 <= s <= 1.0 for s in result)     # a sound, fatal check
    ...

struct Account:
    balance: Money sem "Current settled account balance"
    invariant balance.minor_units >= 0
```

- `require` is a **boundary precondition**.
- `ensure` is a **postcondition** (`result` names the return value) — and also
  works mid-body as a checked assertion over locals.
- `invariant` guards a struct across every mutation and boundary crossing.

The **hard semantic assertion** `ensure semantics("...", x, alpha=0.02)` (or any
calibrated coercion in ensure position) is Sema's semantic assert — legal only
under a calibrated judge, failing as a `ContractViolation` carrying the judge's
evidence. The **soft** form, statement-position `check semantics(...)`, never blocks
— its `Sim` evidence is journaled. Full contract semantics live at
[/neurosymbolic/contracts/](/neurosymbolic/contracts/).

:::caution[`assert` is rejected]
The Python spelling `assert` is a **reserved, rejected token** with a
machine-applicable fix-it to `ensure`/`check`. Python's `assert` strips under
optimization and unwinds — semantics Sema will not let you accidentally rely on.
:::

## Combinators — fallbacks without new syntax

Expression-level recovery uses prelude methods on `Result`/`Option`:

| Combinator | Effect |
|---|---|
| `.or(default)` | substitute a fallback value (discarded failure is journaled as *handled-by-default*) |
| `.or_else(f)` | substitute via a fallback function |
| `.map_err(f)` | convert error types at a membrane so `?` can propagate through a differently-typed caller |
| `.context("...")` | append a human-meaningful frame to the propagation trace before `?` |

```sema
def snapshot(path: Path) -> Result[Report, ReportError] !{fs.read, model.embed}:
    raw    = fs.read_text(path).context("loading ledger snapshot")?
    ledger = parse[Ledger](raw).map_err(ReportError.malformed)?
    fx     = fetch_rates().or(cached_rates())        # fallback; discard journaled
    return Ok(render(ledger, fx))
```

## No cascade tax — the mainstream vocabulary, flat

Everything the try/catch world does maps 1:1 onto constructs that stay **flat**:

| Mainstream | Sema | Why it stays flat |
|---|---|---|
| `try` | `expect <expr>:` | one block, many typed arms |
| `catch E` | `except E as e:` | arms are ordered siblings, exhaustiveness-checkable |
| rethrow / delegate up | `?` (+ `.context("...")`) | one character; blame, trust, and origin ride along |
| `finally` | `with <resource> as x:` | release runs on success, failure, *and* cancellation |
| retry / repair | `supervise`/`heal`, decode-repair | recovery is scope- or boundary-owned |
| hand off to another party | `emit FailureEvent(...)` | delegation is an event with origin intact |

An `expect` nested inside another `expect` arm more than two levels deep is a
**style lint** pointing you at `?`/`map_err` — the cascade shape is treated as a
smell by the toolchain, not just by convention.

## Failure modes

- **`?` in a function whose failure type can't carry the error** → compile error
  listing the missing variant (fix with `.map_err`).
- **`except` arm order shadowing a later arm** → compile warning.
- **Catch and discard without journaling** (`except E: pass`) → the
  catch-and-swallow lint. Use `.or(...)` if defaulting is intended.
- **Reachable `unwrap` under `@assure(gold)`** → rejected.

## What Sema deliberately does not have

- **`try`/`raise`/`finally` unwinding** — invisible to effect rows, hostile to
  replay and blame.
- **Go-style `(value, err)` tuples** — handling is unenforced.
- **Silent `Option`-ization of failures** — evidence loss.

## See also

- [Types](/language/types/) — `Option[T]` and `Result[T, E]`.
- [Pattern Matching](/language/pattern-matching/) — matching `Ok`/`Err`, `Some`/`None`.
- [Contracts](/neurosymbolic/contracts/) — `require`/`ensure`/`check` in depth.
- [Control Flow](/language/control-flow/) — `with` as the deterministic `finally`.
