# Changelog 0.10

## v0.10.3 — 2026-08-08

### ✨ Added

- `drivers.<element>[.<facet>]` config can now pin just the environment keys that differ from the real default, merged per-key instead of replacing the whole map — `{ store: { sql: { local: "pglite" } } }` resolves to `{ local: "pglite", docker: "postgres", test: "memory", prod: "postgres" }`, every other driver map (`kv`, `files`, `signal`, `clock`, `journal`, `vault`, `channel.email/sms`) staying fully at its real, untouched default. The real defaults now live in one place, `src/config/driver-defaults.ts`, and merge through a new `mergeEnvDriverMap` helper scoped strictly to the `{ local?, docker?, test?, prod? }` shape (never a generic whole-config deep merge). Fixes a leak in `resolveDriverId`'s old `docker → prod → local → test` cascade, where pinning only `local` could bleed that value into `docker` / `prod` instead of falling back to the real default.

- `oke doctor` now prints the fully-resolved `drivers.*` config — every real default plus every override, for all four env keys at once — so the complete picture stays visible even when `oke.config.ts` only pins one key. New `--env local|docker|test|prod` flag picks which env's active driver id is highlighted (default: `docker` when `OKE_DOCKER=1`, else `local`); also included under a `drivers` key in `--json` output.

### 💥 Breaking Changes

- `images` config is now nested the same way as `drivers` (pre-1.0; no compat shim) — `{ store: { sql, kv, files, index }, channel: { email }, vault, ai, pgdog, proxy }` — instead of a flat `Record<string, string>` keyed by dotted role strings. The old flat form no longer type-checks and is not read back; every project pinning `images` must migrate. Closes a real type-safety gap: the old shape accepted any string key with zero compile-time or boot-time catch (`"strore.sql"` silently did nothing). Internal compose/Dockerfile derivation is unchanged — a new `flattenImagesConfig()` in `okengine/config` flattens the nested shape back to the dotted-role map `deriveInfrastructure()` / `buildSpecs()` / credentials already worked with. `create-oke`'s scaffolder and both starter templates now generate the nested form; `oke ai setup` and the customize wizard's `images` codegen were rewritten to parse/emit it correctly (brace-balanced, not the old first-`}`-closes-it regex, which broke on nested sub-objects).

### 🔥 Removed

- Removed the Console section from the documentation site, including its navigation entry and links from related documentation pages.

## v0.10.2 — 2026-08-07

### ✨ Added

- `store.sql` / `store.files`, `vault.secret`, `signal()`, and channel medium binders' `.template()` (`channel.email/sms/whatsapp/push().template()`) now register into a shared boot-time registry — `oke({...})` auto-populates `stores` / `secrets` / `signals` / `channel.templates` with zero explicit arrays, draining under the same `registry: "consume" | "keep" | "ignore"` switch that already governs `on()`'s trigger registry. Explicit arrays keep working unchanged — additive, deduped by reference, never silently ignored. `store.kv` / `store.index`, `vault.config`, and the medium-agnostic `channel.template()` are intentionally not auto-registered.

- `.adopt()` route wiring can now be generated instead of hand-listed: `oke build` / `oke dev` regenerate `src/flows/generated.ts` from every `src/flows/<unit>/index.ts` folder (`export * as <unit> from "./<unit>/index.ts"`), so `app.ts` becomes `import * as routes from "./flows/generated"; oke({ name }).adopt(routes)`. A real file on disk, not a virtual module — a virtual-module Bun plugin was investigated and rejected: `Bun.plugin()`'s `onResolve` is never invoked for an unresolvable specifier during a plain runtime `import()`, so `oke dev` and `oke build` would have resolved it differently. Both templates ship a committed `src/flows/generated.ts` stub so a fresh clone type-checks before the first `oke dev` / `oke build` ever runs.

### 🔒 Security

- `docker` / `prod` boots now fail loud (`OKE1009`) when a `src/flows/<unit>` folder exists on disk but adopted zero flows — a stale or hand-edited `.adopt()` barrel, instead of a silently-incomplete route table. Opt-in via `rootDir` / `OKE_ROOT_DIR` (same gate as the `OKE1008` Manifest fallback); `local` / `test` warn once instead of failing, so the dev loop stays unbroken.

### 💥 Breaking Changes

- `flow()` now takes its name as the first positional argument — `flow(name, options)` instead of `flow({ name, ...options })` — matching the name-first convention already used by `signal(name, options)` and `vault.secret(name, options?)` (pre-1.0; no compat shim, no dual-form overload).

- `flow()` no longer takes a `unit` option — `unit` is now derived from `name`'s first dot segment (`"auth.refresh"` → unit `"auth"`), matching every real call site, which always set both to the same value. Flows with no dot in their name have no unit, same as before.

### ♻️ Changed

- `create-oke`'s `advanced` / `standard` templates now scaffold `oke({ name: "notes" })` with no explicit `stores` / `secrets` / `signals` / `channel.templates` arrays and no hand-written `.adopt({ main, notes })` — both come from the auto-registries and the generated route barrel above. Plugins are unaffected: `.plug()` stays fully explicit everywhere.

### 🐛 Fixed

- `oke dev`'s live keyboard controls dropped the `l` (list docker services) key and its whole `1`-`9` select-then-`u`/`x`/`r` per-service subsystem — `l` was the only way to discover which number mapped to which service, and the persistent status board above Logs already lists every service with a live status dot on every refresh, so the panel was a redundant duplicate. Refresh is now bound to `r` (was `c`) — freed up by dropping per-service restart, which was the only other thing bound to `r`.

- `oke ai setup` now also updates `docker/.env.docker` directly when it already exists. `OKE_AI_MODEL` is only ever seeded from `.env.local` into the docker stack on its _first_ boot (later values there are treated as durable, possibly hand-edited pins), so a second `oke ai setup` run — picking a different model after the stack already exists — previously wrote `.env.local` correctly but silently left the running docker profile on the old model forever.

- `oke dev --docker`'s llama.cpp entrypoint now passes an explicit `--ctx-size` (default `4096`, override via `OKE_AI_CTX_SIZE`) to `llama-server`. Left unset, `llama-server` defaults to the model's full native training context (32K-256K+ for many current models) and allocates the KV cache for that up front — often many times the model file's own size — OOM-killing the container on start regardless of how much memory Docker is given, even for small models.

- `bootApplication` no longer lets the ambient `OKE_DOCKER=1` process flag override an explicit `env` passed to `boot()` — only the `env`-unset default path now falls back to `docker`. Previously any sub-app that explicitly booted with `env: "local"` (e.g. the Console's own internal app under `oke dev -d`) got silently promoted to `docker`, tripping the `OKE1008` strict capability check on its own zero-effect flows.

- `extractManifest` now always records a flow's `effects` (including an empty `{}`), instead of omitting the key when a flow has no effects. Previously a pure, zero-effect flow (e.g. a template's `main.root`) was indistinguishable from "effects unknown," so `docker` / `prod` boots kept refusing an open capability token even after a correct manifest extraction confirmed there was nothing to declare.

- `oke db seed` now passes `rootDir` when booting the app entry, so `docker` / `prod` seeding can lazily derive a Manifest instead of hard-failing `OKE1008` on a fresh scaffold that hasn't run `oke build` yet.

## v0.10.1 — 2026-08-07

### ✨ Added

- Boot-time effects stamping: a flow with no hand-declared `effects` can now derive them from a compiled Manifest at boot — pass one explicitly (`oke({ manifest })`) or point `oke({ rootDir })` / `OKE_ROOT_DIR` at a source tree for a lazy `extractManifest` (never bundled into the edge graph — same `new URL(...)` trick as the element binders). `oke dev` / `oke start` set `OKE_ROOT_DIR` automatically; no template changes needed.

### 🔒 Security

- `docker` / `prod` boots now fail loud (`OKE1008`) when a flow has no declared effects and no Manifest to derive them from, instead of silently minting an open capability token (every access allowed, no gate at all). Previously true in every environment, for every flow that omitted `effects` — confirmed via real boot + `fetch()`, not assumption. `local` / `test` keep the open-token fallback (once-per-process `oke boot:` warning) so the existing dev loop and test suite are unaffected.

- The SQL capability gate (`fx.store(db).insert(table)` / `.select().from(table)` / …) now resolves the exact table touched (`sql:<table>`) instead of only the store-level ref (`sql:<store-name>`) — matching the AoT compiler's own `sql:<table>` inference, which the boot-time stamping above now makes live for the first time. Backward compatible: a flow that still declares the older `effects: { writes: ["sql:<store-name>"] }` convention (every existing template) keeps working unchanged — the gate tries the precise table ref first, falls back to the store-level ref.

### 🐛 Fixed

- `oke dev` unit tests that inject `composeHealth` no longer shell out to a real `docker` binary for the session-long health watch — `composeHealthRun` is passed through to `startComposeHealthWatch` / `readComposeHealth`, and injecting `composeHealth` without a run stubs live `ps` polls.

- AoT effects inference: `fx.store(files).put(...)` / `.putImage(...)` are now recognized as writes and `.list(...)` as a read (were silently dropped from the inferred `effects`, files-store methods weren't in the read/write method sets).

- AoT effects inference: `fx.send(tpl, …)` now resolves through a local channel medium binder (`const mail = channel.email(...); mail.template(...)`), not just the literal `channel.template(...)` form — previously the send effect recorded the local variable name instead of the template id.

- AoT effects inference: a table argument now resolves through its `store.schema.table(name, …)` declaration instead of the raw JS identifier, so `const notesTable = store.schema.table("notes", …)` still infers `sql:notes` rather than the (wrong) `sql:notesTable`.

## v0.10.0 — 2026-08-07

### ✨ Added

- `oke dev -d` keyboard controls (TTY): `?` help · `c` refresh (clear logs + latest ●) · `q` quit · `l` list services · `u` up / `x` stop stack · `1`–`9` then `u`/`x`/`r` for one service. Compose/AI status updates the board above Logs only — not a `redis stopped` / `starting` / `ready` spam. Elements and Docker are spaced; `l` / `?` refresh chrome first so the control panel is not interleaved with request logs.

- `oke dev -d` surfaces AI model state: hero + Docker summary show `OKE_AI_MODEL`, and a background poller prints phase changes (`waiting` / `starting` / `loading` / `ready` / `error`) for llama.cpp, vLLM, SGLang, and Ollama without blocking boot.

- `oke dev` hero elements and Docker rows show a colored status ● — green ready, yellow pending/loading, red error, dim idle (unbound). Docker colors come from `compose ps` health; AI uses model phase (not container healthy alone). Boot chrome prints immediately; compose / vault / health / AI work streams in an ephemeral progress pane that clears before the elements / Docker board. Compose health uses `ps -a` and keeps polling for the session so ● turns red when a container is stopped (Docker Desktop or crash); AI ● follows model phase while the AI container is up.

- `llama-cpp`, `vllm`, and `sglang` Docker image recipes for local / self-hosted inference — OpenAI-compatible endpoints, loopback-only host publish, pinned tags (never `latest`); docs decision matrix under Recipes → AI.

- `cockroach`, `yugabyte`, and `timescale` Docker image recipes for self-hosted `store.sql` (driver id stays `postgres`) — Cockroach on 26257 with `COCKROACH_*` + DB Console `:8080`, Yugabyte YSQL on 5433 with `YSQL_*`, Timescale with the same `POSTGRES_*` contract as Postgres; docs pages under Recipes → SQL.

- Docs **Recipes** pages for the five recipes that had zero coverage — Mailpit, Meilisearch, Ollama, OpenBao, RustFS — env names, volume paths, production notes, and real failure modes.

- create-oke templates ship `.github/workflows/ci.yml` (typecheck + `bun test` on push/PR), `tsconfig.json`, and a `typecheck` script — working CI on first push.

- `supabase` Docker image recipe — matches `supabase/postgres` ahead of the generic Postgres recipe (env + healthcheck + URL); Postgres-protocol and extension bundle only — not Auth / Storage / Realtime / Studio.

- Docs **Recipes** (`/docs/recipes`) — pin compose recipes already wired into `oke docker` (Postgres, PgDog, Supabase Postgres, Redis / Valkey / Dragonfly, Caddy, Traefik); vendor choice stays in `images[…]`.

- Docs **Providers** (`/docs/providers`) — managed SQL / Redis connection guides (Neon, Supabase, CockroachDB, YugabyteDB, Redis Cloud, ElastiCache, Memorystore, Azure Cache, Upstash, Dragonfly Cloud, DigitalOcean Caching); driver ids stay `postgres` / `redis`.

- Trace continuity — each execution allocates a stable run id (aligned with the durable journal when present); `fx.emit` stamps `parentRunId` on Signal messages; consumers and `fx.call` children record `WideEvent.parentId` so Console Traces join Flow → Signal → Flow chains.

- `oke replay --request-id <id>` — re-invoke a past Flow locally from a Runs WideEvent (defaults to dry-run when the ledger has `send`/`ask`).

- `WideEvent.input` — validated input snapshot persisted for replay (archived personal fields redacted to `[archived]`).

- `fx.runs` — capability-gated Runs read (`effects.reads: ["runs"]`) with `query` / `all` / `window` / `checkSlo` for native P95 + availability checkers over Clock + Channel (no `fx.metric`).

- Optional OTLP-shaped metric mapping (`wideEventToOtlpMetrics`) for teams with an existing observability stack — additive, never required.

- Console Runs — lookback window (`since`) + Error patterns table (error code counts in the selected window).

- Flow-level `compensate` on durable flows — runs after terminal failure under the same journal; undo work must use distinct `fx.step("undo:…")` names so completed forwards never re-run.

- Channel adversarial proofs — hard-bounce auto-suppression (transport never called on the next send), receipt ledger status progression, injectable suppression/consent/receipts escape hatch, Arabic `{{field}}` catalog round-trip, and WhatsApp template-vs-text characterization (24h session window documented as a Known gap — not enforced).

- `createSuppressionStore` re-exported from the `okengine` root (alongside consent / receipts) for the documented multi-instance injection recipe.

### ♻️ Changed

- Default local AI is **llama.cpp** (`ghcr.io/ggml-org/llama.cpp:server-b10290`, driver `openai-compatible`) — lightest footprint; create-oke Recommended and `oke ai setup --provider llama-cpp` follow. Ollama stays a fully supported alternative (`ollama/ollama:0.32.6`, never `latest`).

- `oke dev` request logs and ready line label the :6530 surface as **Backend** (was **App**); Console / MCP labels unchanged.

- `create-oke` customize: drop the **Enable store.index?** yes/no gate — walk `store.index` like other facets (`none` · memory · pgvector · libsql · meilisearch; docker recommends meilisearch). **AI setup** is Recommended (llama.cpp defaults) · Customize · Off (replacing unclear Configure AI? Yes/No). Email menu labels `taqnyat-mail` as `taqnyat` (driver id unchanged). Install shows `bun install` progress instead of a silent spinner. Wizard labels drop decorative icons. `oke ai setup` / create-oke AI: llama.cpp · Ollama · vLLM · SGLang · cloud; llama.cpp and Ollama share the banner → tier → recommended / manual shape (Docker Hub `ai/` catalog vs Ollama library + detect); llama.cpp catalog shows up to 20 curated `ai/` models per tier. **AI Provider — docker → Back** returns to the previous provider step.

- Ollama docker ensure: skip `/api/pull` when the container's `/api/tags` already lists the model (host `ollama` weights are a different server); stream pull progress instead of hanging on a silent `stream:false` body.

- Docs section path **Recipes** moves from `/docs/images` → `/docs/recipes` (folder, sidebar, and cross-links).

- Ollama recipe serves only; `oke dev -d` / `oke ai setup` pull `${OKE_AI_MODEL}` with `POST /api/pull` against the container's exposed `OKE_AI_URL` — never a host `ollama` CLI (which may hit a different local install).

- Recipes sidebar — Supabase recipe title drops “(Docker)”; Timescale, RustFS, Mailpit, OpenBao, Meilisearch, and Ollama use monochrome brand marks instead of Lucide placeholders.

- Docs **Recipes** + **Providers** rewritten to a concrete acceptance bar — exact env / dashboard click-paths, volume backup meaning, production hardening, and one real failure mode per page (no interchangeable prose).

- Docs sidebar — Recipes and Providers pages use monochrome brand marks (Postgres, Redis, Neon, Supabase, Caddy, Traefik, cloud vendors, PgDog paw from pgdog.dev, …) instead of generic Lucide placeholders.

- Homepage stack strip — Built with / Works with sit in a two-column grid under Stack; each column is a desktop marquee ticker (opposite directions, static wrap below `lg` / reduced motion); every listed name carries a brand mark.

- create-oke templates: fold `stores` into the initial `oke({ … })` call and drop the post-construction `Object.assign(app.$options, …)` block (including the leftover `env: "test"` pin). READMEs spell out scaffold vs what you still build.

- `SchemaColumnDecl` exposes a `getSQL()` type bridge so drizzle-orm operators (`eq`, `isNull`, …) typecheck against abstract schema columns — matching the documented `fx.store` query style.

- Overview SLO burn also evaluates Manifest `slo.latency.p95` (availability burn unchanged).

- Channel docs — suppression / hard bounce, shared-store injection, permanent-error no-failover, receipt statuses, Arabic catalog keys, and WhatsApp session/template Known gap.

### 🔒 Security

- Local AI recipes (llama.cpp + Ollama + vLLM + SGLang) publish inference ports on `127.0.0.1` only — never `0.0.0.0` — and pin patched floors (llama.cpp ≥ b8146, Ollama ≥ 0.17.1). Docs require curated model sources (Docker Hub `ai/`, Ollama library); warn against arbitrary untrusted GGUF.

### 🔥 Removed

- create-oke package-level integration suite (`packages/create-oke/tests`) and the `CREATE_OKE_INTEGRATION` / `test:integration` gate — unit tests under `packages/create-oke/src` remain.

### 🐛 Fixed

- llama.cpp recipe no longer uses router `--models-preset` / bare `--docker-repo` (b10290+ leaves `/v1/models` stuck on `loading`). It emits `docker/llama-entrypoint.py` that Hub-pulls the curated model (native `llama download`, then CNCF `model.weight` fallback for tags like gemma4) and serves single-model with `-m` + `--alias`.

- `oke ai setup` / create-oke AI wizard write `OKE_AI_*` stack keys as **comments** in `.env.local` (same opt-in pattern as other infra) so they no longer shadow compose `OKE_AI_URL` from `docker/.env.docker`. Uncomment only for a host-managed AI endpoint; API tokens still write active.

- `docker/.env.docker` no longer duplicates `OKE_AI_URL` / `OKE_PGDOG_URL` (role aliases were re-emitting keys already written as `${prefix}_URL`).

- `oke dev` TTY matches create-oke Clack chrome: Docker summary sits under the hero, compose stdout is quiet, db/seed lines use `│` status rows, boot honesty prints once as a Notice box (Backend child suppressed), and seed in docker mode hydrates `DATABASE_URL` from `docker/.env.docker` so the prompt no longer fails with a garbled `%E2%80%A2` password redact.

- `oke dev --docker` restores parent `process.env` on session stop (compose URLs / `OKE_DOCKER` / driver overrides) so a stopped session cannot pollute later boots or the test suite.

- create-oke templates (and the framework pin) use exact `drizzle-orm` / `drizzle-kit` `1.0.0-rc.4` (npm `rc` tag) — caret `^1.0.0-rc.4` was resolving mismatched channel builds (`…-fb12281` / `…-ca0f029`).

- Local / monorepo create-oke no longer `file:`-links the workspace root — it stages a publish-shaped package under `~/.oke/create-oke/okengine` (production deps only). Linking the root installed Console `devDependencies` (including `drizzle-zod`), and Bun warned that RC `drizzle-orm` failed `drizzle-zod`’s `>=0.36` peer.

- Local create-oke stage **copies** `files` (not symlinks), folds peers into stage `dependencies`, and runs `bun install` in `~/.oke/create-oke/okengine`. Bun’s `file:` install drops directory symlinks and keeps `package.json` pointed at the stage, so without this `okengine/config` / `zod` resolution failed after scaffold.

- Ollama docker model download no longer depends on a host `ollama` CLI — boot/`oke ai setup` POST `/api/pull` to the container URL so a native host daemon cannot silently receive the weights.

- Durable execute path: thrown errors now commit the journal as `failed` (and record on Runs) instead of incorrectly marking `completed`.

- Channel email/SMS `FallbackTransport` now reuses `shouldFallbackOtpMedium` so permanent client errors (invalid address) do not advance the chain — matching OTP cross-medium failover (sently’s default only checked HTTP 400/401/403 and SMTP auth).

- Docs Neon + Supabase Postgres recipe Accordion titles — quote `"vector"` with single-quoted attributes so MDX compiles (escaped `\"` broke the site build).
