0.9

Every published OKModel release, newest first.

all series ยท 0.8

v0.9.1

๐Ÿ’ฅ Breaking Changes

  • Breaking-ish: now refused โ€” a backslash in fragment / raw template text is OKM1125. Pass the value as an interpolation (B-01, D287).
  • Breaking-ish: now refused โ€” an unknown key on insert, update, upsert / onConflict, or first-or-create is OKM1120 and names the key and the table (B-39, D291).
  • Breaking-ish: now refused โ€” a finished statement with more than 65,535 parameters is OKM1126 (count and limit in the message) instead of a retryable unavailable (B-40, D292).
  • Breaking-ish: now refused โ€” an unknown CLI flag is an error; okm <command> --help lists that command's flags (B-26, D289).
  • Breaking-ish: now refused โ€” an update that would set a tree parent to the row itself or to a descendant is OKM1120 when where is the primary key (B-10, D295).

๐Ÿ”’ Security

  • A backslash in a composed fragment can no longer desync the literal guard and escape a tenant filter (B-01, D287).
  • A unique, foreign-key, or check failure on a table with a sensitive column no longer puts the value in error.cause (detail, parameters, where, and internalQuery stay off). Tables without sensitive columns keep today's cause. includeValues remains the explicit opt-in (B-33, D291).
  • With defineConfig({ roles }), managed functions revoke EXECUTE from PUBLIC and grant it only to the app role, so the reporting role cannot execute them (B-31, D293). Provider-specific role checks (RDS, Cloud SQL, Supabase) follow in M-4.

๐Ÿ› Fixed

dialects

  • filters() OKM1123 uses the calling table's own columns for allow/sort, and replaces per-table hidden marks when the same SQL name is redefined, so a prior .hidden() no longer poisons later suites that reuse tasks.title (and the same pattern for relation targets).

runtime

  • A statement with more than 65,535 parameters fails with OKM1126 (count and limit in the message) instead of a retryable unavailable. The limit is for the whole statement, including filters and inList (B-40, D292). Split from the write-safety work on #339 when the combined change grew the featureless app by more than 100 B.
  • An update that would set a tree parent to the row itself or to a descendant is OKM1120 when where is the primary key (B-10, D295).
  • A throw from a custom routing.select keeps an OkmError's kind; a plain throw becomes kind invalid instead of driver (B-35, D295).

adapters

  • Bun.sql no longer parks a reserved handle for the next checkout, so concurrent scoped units after a backend kill keep separate sessions and tenant settings (B-03, D288).
  • A reset or release failure after a statement or COMMIT completed destroys the connection and does not fail the call on Bun.sql, node-postgres, or Neon, so a committed write is reported as committed and afterCommit still runs (B-04, D288).
  • node-postgres maps Client has encountered a connection error and is not queryable to a lost connection (CONNECTION_CLOSED), matching Bun.sql's message-only closed texts (D288).
  • Bun.sql reserve() stamps code-less closed-socket texts (Connection closed, connection must be a PostgresSQLConnection) with SQLSTATE 08006, so a checkout that fails before the reserved-handle guard classifies as unavailable instead of driver (B-03, D288).
  • postgres.js caps reconnect backoff under 1 s with jitter, defaults timeouts.acquire to 10 s (OKM1846), swallows nextWrite on a null socket (Bun and Node), and rebuilds the pool so a kill storm's first OK read arrives within 3 s (B-02, B-43, D294). Other uncaught exceptions still print and leave a non-zero exit so okm migrate check / apply refusals are not reported as success on Bun.
  • node-postgres defaults timeouts.acquire to 10 s and keeps an idle/checked-out client error listener that logs on the notice path without throwing, so a backend kill of an idle client does not crash Node (B-44, D294).
  • A node-postgres call with timeout or signal removes its checkout error listener, so repeated timed calls do not stack listeners on a pooled client. A call that reaches its deadline while waiting for a connection is refused and does not run. A cancel that does not settle destroys that client, so the pool can serve the next call after a kill storm.

tooling

  • CI copies the milestone from Closes #N onto the pull request before lint / pull request, so agents that cannot PATCH milestones still pass D206.
  • The sticky migrate-plan comment matches the marker and the bot author, so another author's marked comment is not edited (B-22, D295).
  • okm driver test drops leftover okm_* tables after each wire and error case, refuses Bun below 1.4.0, and never reports an unrun case as pass (B-05, B-06, B-42, D295).
  • okmodel/testing factories and isolation() work under compositeTenancy(); concurrent creates no longer false-positive as a factory cycle; matviews are refreshed in isolation() and documented otherwise (B-32, B-28, B-34, D295).
  • The Bun.sql kill-storm test (B-03) retries post-storm scoped reads on unavailable/closed (bounded) so a dying reserved handle after the storm does not fail CI, without relaxing storm short/leaks/readOnly/nested asserts.
  • okm push plans from the live catalog and applies each change under a fresh push-<n> id, so a second push is no longer skipped (B-29, D289).
  • When the schema hash moves with no DDL (an external table the dependent already verified), okm push and okm migrate apply re-stamp okm_meta instead of leaving connect() on OKM1520 (B-21, D289).
  • okm migrate check keeps a trigger's function in the logical namespace, so schemas with timestamps({ enforce: "trigger" }) or hand-written triggers no longer fail OKM1020 (B-24, D289).
  • okm check and okm push reprint domain checks on the connected server before planning, so a non-canonical author check is not permanent live drift (B-36, D289).
  • The guide's roles block applies as a superuser and as a CREATEROLE schema owner: managed roles are created before SET ROLE, schema ownership is transferred when possible, and a failed role unit stays re-runnable with a clear privilege error. On Postgres 15, apply grants itself membership after CREATE ROLE (which leaves none); on Postgres 16+ it grants WITH SET TRUE (B-30, D293).
  • Migrate strips the pgbouncer query parameter before the URL becomes a startup parameter; other pooler hints are not interpreted (B-25, D293). Provider-specific role-admin checks (RDS, Cloud SQL, Supabase) follow in M-4.
  • The pooler pid probe uses eight reads on the apply session (OKM1854 when the backend pid changes). Warm-and-hold for LIFO PgBouncer is kept out of apply/seed so single-connection targets do not hang; URL heuristics still refuse typical pooler hosts and ports (B-41, D293).
  • The type-change recipe's temporary IS NOT NULL validate is no longer OKM1531 (B-37, D289).
  • An unknown CLI flag is an error; okm <command> --help lists that command's flags (B-26, D289).
  • Backfill progress prints a 1-based step index (B-27, D289).
  • okm pull and okm import drizzle write foreign keys with the exported table binding, keep file stems unique on case-insensitive volumes, and never write a Windows-reserved stem (con, aux, nul, com1, โ€ฆ). A column __proto__ is a computed key. Composite primary-key columns stay not null on Drizzle import. Missing oxfmt prints a notice and still writes the report. okm ext scaffold includes composite types (B-14 to B-19, B-38, D290).

docs

  • Tree, testing, driver-test, and Action docs cover cycle refusal, composite factories, minimum Bun, cleanup, and sticky-comment author matching (D295).
  • Known limits record that Bun.sql does not park reserved handles, that a post-commit reset failure does not fail the call, and that a send-without-result is OKM1401 with no afterCommit (D288).
  • okm push docs say it applies the live schema diff as push-<n>, and known-limits cover external re-stamp and domain-check sealing (D289).
  • Sensitive docs cover redaction in logs, errors, cause, and spans; writes docs cover unknown keys (D291).
  • Queries and the error reference document OKM1126 when a finished statement exceeds 65,535 parameters (D292).
  • Guide roles privileges, production.md --target / status / check behavior, and known-limits pooler notes (DM-2, DM-3, D293).
  • Known limits, README, guide, spec, and handbook pages match the code after the 0.9.1 fixes: Temporal polyfill on Node 22/24, okm dev, okmodel/safety, redaction, untyped raw and raw projection (B-07, B-08), minimum Bun, okmodel.config.ts, listen as a driver capability only, catalog export vs describe, backfill step numbering, and a Not tested section (DM-1 to DM-12, D296).

v0.9.0

โœจ Added

dialects

  • okmodel/pg/unaccent, hstore, ltree, btree_gist, btree_gin, fuzzystrmatch, and pgcrypto are generated extension packs. A feature the declared version does not include fails with OKM1812 and names minPostgres. vector is not in this version (D281).

tooling

  • describe(schema) and buildCatalog(description) on okmodel/migrate return the catalog as JSON. The logical identity stays public when the catalog is placed. Reading the JSON back keeps the catalog hash (D284).
  • okm import drizzle <path> writes table files from a Drizzle schema using the project's own drizzle-orm and the same emitter as okm pull. It needs no database. An existing file is OKM1855. A missing drizzle-orm is OKM1856. Composite foreign keys, relations(), and custom types are listed as not imported (D280).
  • The GitHub Action .github/actions/migrate posts one sticky pull-request comment from okm migrate plan --json: the strictest class, each step's class and lock, row estimates, and lint codes. Preview applies a database the caller created and never creates or drops one (D279).
  • okm pull reads a database and writes one table file per table plus the schema file. It is read-only, including on a protected target, and an existing file is OKM1855. Functions, triggers, views, policies, roles, and grants are listed under not pulled yet (D275).
  • okm migrate check warns OKM1552 when a domain check or a LANGUAGE sql function body reprints, and OKM1538 warns only when the type change rewrites the heap (D276).
  • external(table) marks a table another catalog owns. okm catalog export writes a snapshot whose catalogHash is separate from its logical identity. defineConfig({ namespace, external }) places the catalog and verifies those snapshots. Two catalogs on one database keep separate history. A foreign key into another database is OKM1842 (D277).
  • okm pull writes functions, triggers, views, and materialized views. A view is SQL with its columns. Views and BEGIN ATOMIC bodies take dependsOn from pg_depend. A plpgsql or SQL-string body gets lexical dependsOn candidates. A scratch replay with check_function_bodies on marks each function verified or candidate-only (D278).
  • okm ext scaffold <name> prints a starting extension() definition from the connected catalog. --out writes that file and never overwrites. It is read-only, including on a protected target (D281).
  • okm ext test <file> checks declared types, operators, functions, and index methods against the server. A wrong signature exits non-zero with the mismatch listed. A version the server does not meet is OKM1812. It is read-only, including on a protected target (D281).

docs

  • A type change that rewrites the heap stays OKM1538, a warning with no automatic safe form. The documented recipe adds the new column, backfills it, dual-writes, switches reads, then drops the old column, each as its own migration (D285).

๐Ÿ› Fixed

  • btreeGin().gin uses the int4_ops operator class that btree_gin creates.
  • A BEGIN ATOMIC reprint is matched by the function name and its identity arguments, so two overloads do not share a body. A name that appears once is that row, so a typmod or an array is still reprinted. orderBy on a function call and computed fields stay after M2 (D282).
  • The release smoke keeps asking npm while a published version is still processing: 31 times, 30 seconds apart, with bun add --no-cache. The job timeout is 45 minutes.
  • connect() reads okm_meta and okm_history from the namespace defineConfig placed, so a second catalog's hash is checked at startup. public keeps the same query. A missing table still skips the check (D283).

โ™ป๏ธ Changed

  • The execution plan schedules the 0.9 Tooling train as P110 through P119.
  • 0.9.0 is the M2 gate. No gate or ceiling moved. vector, tool 5, orderBy on a function call, and computed fields stay deferred (D286).