# Changelog 0.8

## v0.8.0 — 2026-08-04

### ✨ Added

- Postgres Clock CronStore (`drivers.clock: "postgres"`) — multi-host leader election via the same `FOR UPDATE SKIP LOCKED` + lazy lease reclaim pattern as Signal `once` delivery. Table `oke_crons` holds schedule rows with `locked_by` / `lease_expires_at`. Chaos-proven across OS processes against a real Postgres (`OKE_TEST_POSTGRES_URL`). `file` remains for local / single-host shared volumes.

- `defineSeed({ essential, dev, prod })` + `oke db seed` — explicit seed runs (never at boot). `essential` every env; `dev` on `local`/`docker`; `prod` on `prod` only. Docker/prod print the DB target and require typing the env name (`--force` skips). create-oke standard + advanced ship real `src/seed/` (welcome note + sample notes).

- `fx.store(db).upsert(table, matchOn, values, options?)` — insert when no match; existing rows untouched unless `{ onExisting: "update" }`.

- Store docs **Seeding** section (categories, upsert, confirm, migration boundary) and CLI reference for `oke db seed`.

- `<StoreSeeding />` teaching figure — env→block lighting + upsert outcomes under Store docs Seeding.

- Phone-number OTP delivery via Taqnyat Verify — `fx.sendOtp` / `fx.verifyOtp` on Channel; when `drivers.channel.sms` is `taqnyat`, `phoneNumber()` uses Taqnyat's provider-managed Verify API (no self-generated code). Non-Taqnyat SMS drivers fail loudly; no SMS driver keeps the local hashed path + `exposeDevOtp`.

- `taqnyat-mail` Channel email driver (additive) — magic-link and every `channel.email` template can deliver through Taqnyat Mail (`TAQNYAT_MAIL_TOKEN` + `TAQNYAT_CAMPAIGN`); SMTP/Mailpit stays the docker default.

- Opt-in Taqnyat live test suite — real `sendOtp` (success code `5`) and real Taqnyat Mail magic-link sends, double-gated behind the global per-medium flags `OKE_SMS_LIVE=1` / `OKE_EMAIL_LIVE=1` **plus** the real credentials; credential presence alone never sends. Skips are always visible.

- Postgres durable journal (`drivers.journal: "postgres"`) — durable-flow runs persist in `oke_journal_runs` with `locked_by` / `lease_expires_at`, reusing the Signal/Clock `FOR UPDATE SKIP LOCKED` + lazy lease reclaim pattern. `memory` (default) and `file` stay for local / single-host.

- Boot-time orphan discovery — a booted app scans the shared journal for `running` / due `sleeping` runs without a live lease and resumes them, no caller-supplied `runId`; the scheduler tick also claims due sleeps and sweeps expired-lease orphans from the shared store.

- Run-level lease coordination — every start / resume / wake acquires the run lease first (renewed on each journal persist, released on park / complete / fail), so two instances never execute the same run under a live lease; an in-process in-flight guard stops overlapping sessions (orphan scan vs. scheduler tick) inside one instance.

- Durable-journal chaos proofs across OS processes — SIGKILL mid-run with survivor takeover (completed `fx.step` never re-runs) and a two-process race on one due sleep (exactly one executes), gated on `OKE_TEST_POSTGRES_URL`.

### ♻️ Changed

- Booted apps bind `drivers.journal` from config instead of a hardcoded in-memory store. `postgres` without `DATABASE_URL` / `OKE_STORE_SQL_URL` fails loud at boot; the postgres journal pulls the `store.sql` docker image like clock does.

- Template / create-oke: standard + advanced gain `drivers.journal` — `memory` for local/test, `postgres` for docker/prod.

- `JournalStore` gains an optional lease surface — `JournalLeaseStore` (`acquireLease` / `releaseLease` / `claimDueSleep` / `listOrphans`), `hasJournalLease`, `JournalLeaseBusy`, and `JOURNAL_DEFAULT_LEASE_MS` (30s) are exported; memory and file stores implement it too.

- Flow / Clock docs: durable crash-recovery claims are now scoped to a shared journal — `postgres` resumes after restarts and across instances; the `memory` journal remains same-process only. At-least-once for in-flight steps and the client-retry boundary are documented.

- Template / create-oke docker+prod clock default: `file` → `postgres` now that the real CronStore exists (`file` still valid for local/single-host).

- `sently` 1.0.0 → 1.2.0 — Taqnyat OTP helpers (`sendOtp` / `verifyOtp`) unchanged in shape; 1.1/1.2 add vendor extras + the opt-in live-suite pattern mirrored by OKE's new live tests.

- Split `JournalSuspend` / `isJournalSuspend` into `journal-suspend.ts` so the kernel edge profile (retry filter) no longer pulls Node journal persistence into the browser bundle (~15.01 kB → ~13.4 kB gzip).

- `fx.store(sqlDecl)` typed as `SqlStoreHandle` (facet overloads) so `.upsert` / `.insert` / `.select` typecheck; SQL decls without a store runtime throw instead of a stub missing those methods.

- Store Seeding docs: full env matrix, simple vs complex file forms, upsert outcome table, CLI flags, seed-vs-migrate Callout.

- Store docs: short **Multiple environments** note under SQL schema sync — local `push` vs staging/prod versioned `drizzle/` + `migrate` catch-up.

- `oke db` adversarial test: multi-file `generate` + re-`migrate` skips applied migrations; simulated env lag applies only the pending file (drizzle-kit `__drizzle_migrations` history through the CLI wrapper).

### 🐛 Fixed

- Durable three-layer gap closed: with a shared journal, restarts no longer lose every durable run, crashed runs resume via the boot orphan scan, and the same run can no longer execute on two instances concurrently under a live lease.

- `fx.store(db).upsert` through real `oke()` → `createTestApp` → `app.fetch()` (regression test); SQL `StoreDecl` without a store runtime no longer silently returns a stub that made `.upsert` look like “not a function”.
