0.10

Every published okengine release, newest first.

all series · 0.11 · 0.9

v0.10.3

✨ 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

✨ 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

✨ 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

✨ Added

  • oke dev -d keyboard controls (TTY): ? help · c refresh (clear logs + latest ●) · q quit · l list services · u up / x stop stack · 19 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).