# Changelog 0.21

## v0.21.0 — 2026-09-17

### ✨ Added

#### Runtime

- Built-in `fx.fail` helpers (`notFound`, `forbidden`, `conflict`, `foreignKey`, `unauthorized`, `rateLimited`, `serviceUnavailable`, `database`, `internal`) are always on — no `errors: { NotFound }` bag required. Domain codes (`OutOfStock`, `FlightFull`, keel `Duplicate`) still go on `errors:`.

- `okengine/client/explain` — `match` / `matchError` / `explain` collapse unknown flow and transport errors into a UX kind (`auth`, `missing`, `invalid`, …). Dispatch: named code → kind arm → required `_`. `match` takes the whole `{ data, error }` envelope (`ok` + those arms). `ValidationError` issues type as `{ message, path }[]` (`explain.fields` for forms).

- `fx.store` SQL unique / FK / NOT NULL / CHECK / invalid / too_long / out_of_range / connection failures become typed `fail` values (same family as `ValidationError`). Serialization / deadlock / lock-busy (`40001` / `40P01` / `55P03`, SQLite BUSY) stay thrown so `flow.retry` can run, then `ServiceUnavailable`. Redis connection / `CLUSTERDOWN` / `READONLY` map to `ServiceUnavailable`; `BUSY` / `TRYAGAIN` follow the same retry-then-503 path. `WRONGTYPE` stays `InternalError`. Files `fs` / `s3` `AccessDenied` and path-escape keys map to `Forbidden`; `SlowDown` / `EBUSY` follow retry-then-503; `NoSuchBucket` / `ENOSPC` are `ServiceUnavailable`.

- Boot fatals print cause + fix (`formatFatalError`). `VaultBootError` is **OKE1510**. No TTY stack unless `OKE_DEBUG=1`. Request-line chips include `error.code` (`409 Conflict`).

#### Docs

- Site header adds a Discord square before npm (blurple tile, invite `https://discord.gg/j2hkZZbnp`), matching npm’s flush square cell and Clyde glyph height to the npm “n”, including the mobile menu.

- Flow / HTTP / Store / Errors / Vault / Try It document always-on helpers, honest 404/409/503 tables, SQL / KV / files auto-fail, and the OKE1510 TTY dump.

- Errors reference catalogs `fx.fail` helpers, always-on vs domain `errors:`, HTTP statuses, and SQL / KV / files store auto-map.

- Calling documents `match` / `matchError` / `explain` (kind arms, required `_`, dispatch order) and JSON `NotFound` vs a plain-text router 404. Errors smallest example uses `match`.

### 💥 Breaking Changes

#### Runtime

- Built-in HTTP statuses: `NotFound` **404** (was 400), `Conflict` / `ForeignKey` **409**, `AuthRateLimited` **429** (was 400), `ServiceUnavailable` **503**. Domain codes stay **400**. Router miss remains plain-text `404`.

- SQL unique violations are `Conflict` **409**, not `500 InternalError`.

- `InternalError` uses the catalog message only — it no longer copies the thrown `Error.message`.

- Typed client `ContractErrors` always includes built-in codes; a declared key still wins (shorter `{ code }` tightening).

- `store.resource` no longer stamps Manifest `errors: { NotFound }` by default.

### ♻️ Changed

#### Runtime

- Export gzip regression baselines refreshed in `budgets.json` after always-on `fx.fail` helpers, store SQL/KV/files auto-map, and i18n catalog strings (kernel edge 16.99 kB and client 4.99 kB still under the 17 kB / 5 kB absolute caps).

#### Docs

- Try It is a first-app walkthrough: scaffold file map, `GET` / `POST /users` in one routes file, persist, then Signal + Channel, then split files / `http.resource` and short Gate · Clock · Vault · AI explainers.

- Try It Create explains why Bun ≥ 1.4.2 and Docker Compose (host app, production-shaped data plane) and links the install pages.

- Try It puts **The files** before First HTTP — roles (Flow, Store, root, boot), not the blank tree as the only layout.

- Try It HTTP contracts can live in the router file or in `shapes.ts`.

- Try It First HTTP explains the `{ data, error }` envelope (success vs `ValidationError` / `fx.fail`).

- Try It First HTTP explains `.public()` — required posture, open with no session.

- Try It First Signal declares in `src/signals/`, binds a worker in `src/flows/workers/`, and emits from create (`{ key }` for `once`). Code samples on the page focus the lines that change; earlier context dims.

#### Dev, Keel & create-oke

- Compose default RustFS pin: `1.0.0-rc.5` → `1.0.0` (stable, no `v` prefix).

- create-oke shorter and examples/keel use `fail.notFound` / `fail.forbidden` / `fail.conflict` helpers. Keel drops redundant `errors: { NotFound }` bags and keeps domain `Duplicate` / `Unavailable`. Shorter keeps `{ code }` tightening on NotFound / Forbidden / Conflict.

### 🐛 Fixed

#### Runtime

- Untriggered RPC flows (`POST /_oke/...`) returning unhandled throws now serialize to 500 `InternalError` (or store auto-mapped failure) instead of empty `204 No Content`.

- `fx.call()` and `app.call()` rethrow unhandled exceptions across flow boundaries instead of returning `undefined`.

#### Client

- `TransportError` runtime instances now consistently populate `error.message` matching `error.data.message`. The type requires `message` (same string as `data.message`).

- SSE handshake failure decoding prioritizes top-level `error.message` while preserving fallback to `error.data.message` and `error.code`.

- Typed client `ContractErrors` treats a missing `errors` bag as the built-in map — a phantom `Record<string, unknown>` no longer wipes `NotFound.id`.

- `match` success `ok` data is the success type, not `O | null`.

#### Docs

- Calling / Errors document `TransportError.message === error.data.message`. Flow / fx note that `fx.call` / `app.call` rethrow unhandled throws (not silent `undefined`).
