0.18

Every published okengine release, newest first.

all series · 0.19 · 0.17

v0.18.5

♻️ Changed

Docs

  • Formalized the Canonical Site Knowledge Model (concept-graph.ts) defining the 8 closed core primitives, their irreducible physics, and architectural composition formulas.
  • Restructured documentation information architecture into 5 numbered stages (01 Understand, 02 Build, 03 Operate, 04 Extend, 05 Reference) across meta.json and the handbook index.
  • Refactored Elements and AI handbook overviews to emphasize the closed primitive set, inbound MCP vs outbound AI runtime separation, and composition patterns (Realtime, Security, Agents, Operations).
  • Standardized site positioning statements and identity constants (SITE_CATEGORY, SITE_PHILOSOPHY, SITE_INSPECTABILITY, SITE_TAXONOMY_EQUATION, SITE_MANIFEST_DISTINCTION) in site-identity.ts.

v0.18.4

♻️ Changed

Docs

  • Reorganized documentation information architecture into Layer 1 (Understand OKE) and Layer 2 (Build with OKE) per the positioning map.
  • Updated landing page hero and narrative arc to the 10-step argument with dedicated architectural proof stories (Realtime, Security, Agents, Observability).
  • Refined positioning vocabulary across package metadata, introduction, and why thesis: retired "10 exports" / "entire public vocabulary" in favor of "core programming vocabulary", and clarified MCP client vs Gate provider roles.

v0.18.3

🐛 Fixed

Runtime

  • Manual live-query HTTP e2e types use a minimal SSE reader shape so tsc no longer fails on missing ReadableStreamReadResult / readMany.

v0.18.2

🐛 Fixed

Runtime

  • Realtime bridge rebinds when boot hands a new primary SQL connection (fresh PGLite per test) instead of classifying CDC against a prior instance; createTestApp().close() calls app.stop() so the process-wide sink tears down with the harness.

v0.18.1

🐛 Fixed

Runtime

  • Manual live-query HTTP e2e no longer waits for two SSE chunks after a single tasks insert (one classified upsert is enough) and raises the Bun test timeout so PGLite boot + CDC fan-out fit under CI load.

v0.18.0

✨ Added

Runtime

  • Manual live queries — hand-written flows declare live query surfaces with http.get("/tasks/live").gate(member).live(tasks) plus liveQuery(fx, tasks, input, { filter, search, order }) in the flow body. Compiles to the same internal oke/live/sql:<table> signal, guardrails, and per-subscriber classified CDC stream as store.resource({ live: true }); the PostgREST list grammar is extracted into a standalone shared module (parseListQuery / resolveListScope) so resource lists, live windows, and hand-written flows parse identically.
  • Realtime live queries — store.resource({ live: true }) synthesizes GET <path>/live SSE with per-subscriber RLS classification (upsert / revoked / delete). Application-level CDC via the sql-session write hook + oke_cdc_outbox SKIP LOCKED poller; fan-out through LiveQueryRuntime (bounded worker pool). oke.row_passes_policies parity-gated against native Postgres RLS.
  • useLiveQuery (okengine/client-react) — snapshot + SSE race protocol, three-event reducer, optimistic mutate() wrapping existing ClientCall envelopes. Pure helpers (reduceLiveQueryRows, applyOptimisticPatch) also on okengine/client.
  • store.schema.policy.scope(PolicyGateDecl | string) — DRY gate refs in RLS policies.
  • G16 live-query fan-out bench (src/bench/g16-live-query-fanout.bench.ts).
  • CDC outbox retention defaults (24h max-age / 50k-row count cap, configurable via retention) — the durable event table now prunes itself like every other Signal/Journal surface.
  • X-Oke-Mutation-Id end-to-end: mutation requests carry a client UUID header; writes echo it onto their CDC events (in-process and outbox-round-tripped) so useLiveQuery dedupes its own late echoes — the realtime optimistic-race contract.
  • useLiveQuery gains enabled: false idle mode, manual refetch(), isReconnecting (distinct from first-load isLoading), reconnect seq-guard replay dedup (isReplayedEvent), and identity refresh via onAuthRefresh (full subscribe-protocol re-run on auth change).
  • Compiler guardrails for store.resource({ live: true }) — missing primary key fails extract loud; missing updatedAt/updated_at column and missing RLS policies warn with remediation intent.
  • Realtime operations surface: realtimeMetrics() snapshot (subscribers, fan-out queue depth, shed/check counters, outbox backlog gauges) plus four new oke doctor findings — cdc_outbox_backlog (warn >10k / error >100k), cdc_outbox_retention, live_subscriber_pressure (>150 subs), and live_fanout_queue_saturated.
  • Project-wide live-by-default: oke({ store: { live: true } }) flips the default posture for NEW store.schema.table() declarations to live — the automatic CDC + RLS-per-event stream store.resource({ live: true }) already provides — unless a table opts out with store.schema.live(false) (or store.resource(…, { live: false })). Declaration ergonomics only; the runtime/fan-out cost model is identical to explicit live: true (per-subscriber stamped RLS checks, documented in store docs). Flag defaults false — existing explicit-only behavior unchanged, zero cost when off. Same DX Pack A guardrails fire on the default-flip path.
  • field.* widens from text | integer to the full Drizzle Postgres column surface — varchar / char (with length + enum literals), boolean, smallint, bigint, the serial family, numeric / decimal (precision/scale/mode), real / doublePrecision, json / jsonb (generic payload), uuid, time / timestamp / date / interval, point / line, bytea, and the network family (inet / cidr / macaddr / macaddr8). Options bags carry per-type knobs; temporals infer Date by default with { mode: "string" } opting into ISO strings; the serial family is NOT NULL by SQL physics.
  • .type<T>() builder override (mirrors Drizzle $type<T>) and typed .default(value) that rejects values outside the column's JS type.
  • Prepared default shorthands — .okid() (fresh OKID on insert) and .now() sugar over defaultFn(id) / the now family; .now() resolves by the column — epoch-ms on numbers, Date on default/date-mode temporals, ISO-8601 on string-mode temporals.
  • Drizzle emitter emits drizzle-exact factory calls per type (options, pinned temporal modes) and collects the drizzle-orm/pg-core import list dynamically.
  • Manifest columns record the widened types plus enumValues, length, precision, scale, withTimezone, mode, and fields.
  • okengine/okid — native id generator okid() (21 chars, 126 bits, URL-safe a-zA-Z0-9-_), opt-in { sortable: true } time prefix and alphabet control (numbers / lowercase / uppercase / symbols / lookAlikes). Zero dependencies, crypto.getRandomValues() only, rejection-sampled for bias-free character selection.
  • oauth() plugin — Authorization Code + PKCE social sign-in for Apple, Discord, Facebook, Figma, GitHub, Google, Microsoft, and X. Hand-rolled drivers (OIDC JWKS verify or OAuth2 profile fetch), Vault-declared client secrets, optional sealed provider-token storage, and identity only through linkOrProvision.

Dev, Keel & create-oke

  • Keel inbox mounts GET /inbox/live (live query stack); bindCrud({ live: true }) wires the same surface for any CRUD unit.
  • oke doctor file_descriptor_limit check — estimates peak FDs from the manifest (live signals + SSE routes) and compares against ulimit -n.
  • bun run bench:load + src/bench/ — system load harness (15 groups) for regression trend runs against live Keel infra (OKE_BENCH=1).

Docs

  • Store + Client docs cover live: true and useLiveQuery.
  • Plugins → **OAuth** category: overview plus per-provider pages (Apple, Discord, Facebook, Figma, GitHub, Google, Microsoft, X).

Test harness

  • Public okengine/testing subpath (aliased okengine/test) exposing createTestApp — a dedicated dist/testing.js build keeps the harness out of core/client production bundles (kernel + client budget gates cover it).
  • Multi-tenancy testing: loginAs({ tenantId }) stamps fx.tenant.id, and { tenant } on API calls re-scopes a single call — verified with real PGlite RLS isolation (store.schema.policy.tenant).
  • Deterministic live signals: signals.subscribeLive / signals.waitForLive give test-first assertions over delivery: "live" event streams (retention-aware replay).
  • Testing guide at get-started/testing.mdx — harness surface, multi-tenant RLS recipes, live-signal patterns, time travel, AI mocks, and troubleshooting.

♻️ Changed

Runtime

  • Internal opaque ids (id() / fx.id() / run, job, event, instance, and file ids) now use OKID from okengine/okid instead of UUID strings — shorter and equally collision-resistant.
  • defaultFn(id) now emits native OKIDs, and defaultFn(okid) is recognized as an id default; the declarative schema surface accepts either.

Docs

  • Store docs promote Access policies (owner / tenant / scope) and Live queries (live: true and .live(table)) to dedicated H3s under SQL.

💥 Breaking Changes

Runtime

  • field.timestamp() and field.date() now infer Date objects by default (previously ISO strings) — opt into ISO strings with { mode: "string" }. Drizzle emission pins mode: "date", and .now() on default temporals yields Date ($defaultFn(nowDate)).
  • .default(v) is now typed against the column's JS type — call sites passing mismatched literals (e.g. field.integer().default("0")) fail typecheck.

🐛 Fixed

Runtime

  • connectPostgres({ pool: { max: 1 } }) opens a dedicated single-connection client instead of the shared pool — vault rotateMaster and other manual BEGIN workloads no longer hit ERR_POSTGRES_UNSAFE_TRANSACTION.
  • Vault rotate-lease claim uses plain FOR UPDATE (no SKIP LOCKED) so a row briefly locked by an audit append is not misread as “lease held by another instance”.
  • RLS helper install (CREATE OR REPLACE FUNCTION oke.*) is memoized per SQL connection, not per session handle — concurrent identity bags no longer race pg_proc with tuple concurrently updated.
  • Channel default email sender is oke@localhost.test (was oke@localhost) so out-of-the-box SMTP validation accepts the envelope.
  • Concurrent channel sends through one transport are serialized so shared SMTP sockets cannot interleave DATA frames.
  • oke doctor estimates peak file-descriptor need from live signals / SSE routes (G3b-calibrated ~1.5 fds/subscriber) and warns or errors when the process soft limit is too low.
  • Test harness channel runtime now inherits templates declared on the app itself (oke({ channel: { templates } })) — previously only boot.channel.templates overrides reached the harness, so app-declared templates silently dropped out of t.channels.sent().

Docs

  • Root SECURITY.md — coordinated disclosure via GitHub private vulnerability reporting (Security tab); README Documentation link.

Dev, Keel & create-oke

  • bun install no longer fails with failed to link package: okengine@ (link) — Keel links the monorepo root via prepare symlink instead of "okengine": "file:../..", which made Bun hardlink the whole tree and choke on Cursor plan files under .cursor/plans (com.apple.provenance).
  • bun run verify — same check jobs as .github/workflows/ci.yml (verify:lint, verify:typecheck, verify:test, verify:gate, verify:site). bun run ci still adds budgets and the Keel example.

🔒 Security

Runtime

  • Adversarial audit of oke.row_passes_policies fixed two replay divergences: policies scoped TO <role> were evaluated without checking role applicability (a non-member policy could widen visibility through the live-query replay path), and INSERT probes picked the USING clause instead of WITH CHECK (fail-closed divergence vs native RLS). Permanent regression gates added to rls-row-passes-policies.parity.test.ts (TO-role leak + mixed-clause INSERT); parity re-verified on PGlite and live Postgres 17.
  • OAuth mix-up defense (RFC 9700 / RFC 9207): per-provider callback routes, single-use hashed state, and issuer pinning on every assertion.
  • Unverified provider emails cannot take over existing accounts (GHSA-6g38-8j4p-j3pr class) — Facebook/X/Figma always unverified; Apple string "false" stays false.