kiroku-store-migrations 0.4.0.0 → 0.5.0.0
raw patch · 4 files changed
+106/−30 lines, 4 filesdep ~ephemeral-pgdep ~pg-migratedep ~pg-migrate-cli
Dependency ranges changed: ephemeral-pg, pg-migrate, pg-migrate-cli, pg-migrate-embed, pg-migrate-import-codd, pg-migrate-test-support
Files
- CHANGELOG.md +24/−0
- README.md +31/−3
- kiroku-store-migrations.cabal +10/−10
- test/Main.hs +41/−17
CHANGELOG.md view
@@ -1,5 +1,29 @@ # Changelog +## 0.5.0.0 — 2026-09-18++### Breaking Changes++* Requires the `pg-migrate` 1.2 series (`pg-migrate`, `pg-migrate-embed`,+ `pg-migrate-import-codd`, `pg-migrate-cli` `^>=1.2.0.0`, was `^>=1.1.0.0`).+ `Kiroku.Store.Migrations` re-exports `MigrationComponent`, `MigrationPlan`,+ `DefinitionError` and `PlanError` from `pg-migrate`, so this major bump+ follows the dependency's. pg-migrate 1.2 has no API or behavior changes+ relative to 1.1: consumers only need to move their own `pg-migrate` bounds+ to `^>=1.2`. The migration payloads, their checksums and the+ `kiroku-store-migrate` executable are unchanged.++### Other Changes++* The test-suite requires `ephemeral-pg >=0.3.1 && <0.4` and+ `pg-migrate-test-support ^>=1.2.0.0`, and builds on its+ `defaultEphemeralConfig` so orphaned clusters from killed runs are reaped.+* The UUIDv7 test now requires the PostgreSQL 18 builtin to exist exactly when+ the server major is 18, and reports which route supplied `kiroku.uuidv7()`.+ A run can no longer pass while exercising a different major than intended.+* The README documents running the suites against both PostgreSQL 17 and 18+ (`just test-matrix`, `just test-pg <major>`).+ ## 0.4.0.0 — 2026-08-16 ### Breaking Changes
README.md view
@@ -105,9 +105,37 @@ source-ledger preservation. The relation contract suite also proves the frozen `kiroku.subscription_checkpoints_v1` catalog, non-null value semantics, owner-rights privilege isolation, structural read-only behavior, downstream-view survival, and indexed query plan. `cabal test kiroku-store:kiroku-store-test`-consumes the same native plan through `kiroku-test-support` and proves the-complete store behavior, including append and read scenarios.+view survival, and indexed query plan. It also applies the plan's pending tail+in a session that never ran `0001` and cannot reach the Kiroku schema through+`search_path`, which is the upgrade shape BUG-1 lived in.+`cabal test kiroku-store:kiroku-store-test` consumes the same native plan+through `kiroku-test-support` and proves the complete store behavior, including+append and read scenarios.++### Both PostgreSQL majors++The suites reach PostgreSQL through `ephemeral-pg`, which runs whichever server+is on `PATH`, and the default dev shell carries only PostgreSQL 18. Migration+behavior differs by major -- `uuidv7()` is a builtin on 18 and comes from+`0001`'s fallback on 17 -- so a single-major run cannot cover this package.+Run both:++```bash+just test-matrix # every suite, on PostgreSQL 17 and then 18+just test-pg 17 # one major+```++Each recipe enters the matching `nix develop .#postgresql<major>` shell and+refuses to run if that major is not the one on `PATH`. The suite reports the+route it exercised, so a run states which half of the matrix it covered:++```+PostgreSQL 17 UUIDv7 generator: kiroku.uuidv7() via 0001's fallback+PostgreSQL 18 UUIDv7 generator: kiroku.uuidv7() via 0010's alias for the builtin+```++Run the matrix before releasing this package, and after any migration change+that touches version-dependent behavior. Migration `0010` adds replay-history retention leases, the per-schema coordinator, an indexed active-lease predicate, and statement-level
kiroku-store-migrations.cabal view
@@ -1,6 +1,6 @@ cabal-version: 3.0 name: kiroku-store-migrations-version: 0.4.0.0+version: 0.5.0.0 synopsis: Schema migrations for kiroku-store description: Native pg-migrate component, Codd history mapping, and migration executable@@ -52,9 +52,9 @@ , bytestring >=0.11 && <0.13 , containers >=0.6 && <0.8 , filepath >=1.4 && <1.6- , pg-migrate ^>=1.1.0.0- , pg-migrate-embed ^>=1.1.0.0- , pg-migrate-import-codd ^>=1.1.0.0+ , pg-migrate ^>=1.2.0.0+ , pg-migrate-embed ^>=1.2.0.0+ , pg-migrate-import-codd ^>=1.2.0.0 , template-haskell >=2.20 && <2.24 , text >=2.0 && <2.2 @@ -70,8 +70,8 @@ , hasql >=1.10 && <1.11 , kiroku-store-migrations , optparse-applicative >=0.17 && <0.20- , pg-migrate ^>=1.1.0.0- , pg-migrate-cli ^>=1.1.0.0+ , pg-migrate ^>=1.2.0.0+ , pg-migrate-cli ^>=1.2.0.0 , text >=2.0 && <2.2 test-suite kiroku-store-migrations-test@@ -85,14 +85,14 @@ , base >=4.18 && <5 , bytestring >=0.11 && <0.13 , directory >=1.3- , ephemeral-pg >=0.2 && <0.3+ , ephemeral-pg >=0.3.1 && <0.4 , filepath >=1.4 && <1.6 , hasql >=1.10 && <1.11 , hspec >=2.10 && <2.12 , kiroku-store-migrations- , pg-migrate ^>=1.1.0.0- , pg-migrate-import-codd ^>=1.1.0.0- , pg-migrate-test-support ^>=1.1.0.0+ , pg-migrate ^>=1.2.0.0+ , pg-migrate-import-codd ^>=1.2.0.0+ , pg-migrate-test-support ^>=1.2.0.0 , temporary >=1.3 && <1.4 , text >=2.0 && <2.2 , time >=1.12 && <1.15
test/Main.hs view
@@ -21,7 +21,7 @@ import Database.PostgreSQL.Migrate import Database.PostgreSQL.Migrate.History.Codd import Database.PostgreSQL.Migrate.Internal (migrationChecksumBytes)-import Database.PostgreSQL.Migrate.Test (withMigratedDatabase)+import Database.PostgreSQL.Migrate.Test (defaultEphemeralConfig, withMigratedDatabase) import EphemeralPg qualified as Pg import EphemeralPg.Config qualified as PgConfig import Hasql.Connection qualified as Connection@@ -315,16 +315,27 @@ length applied `shouldBe` length nativeMigrationFiles withConnection settings assertSchema - it "resolves the lease default through the qualified component generator" $ do+ -- kiroku.uuidv7() is the component's version-independent generator, but+ -- it arrives by a different route on each major: 0001's fallback on+ -- PostgreSQL 17, 0010's alias for the builtin on PostgreSQL 18. This+ -- case pins the route to the running server, so an acceptance run that+ -- believes it exercised one major and actually ran the other fails here+ -- instead of passing quietly. Both routes need a run to be covered --+ -- see `just test-matrix`.+ it "publishes the generator by the route its PostgreSQL major requires" $ do plan <- requirePlan result <- withMigratedDatabase plan $ \connection -> do- -- kiroku.uuidv7() is the component's version-independent- -- generator: PostgreSQL 17 gets 0001's fallback, PostgreSQL 18- -- gets 0010's alias for the builtin. Either way the stored- -- default names it, and no later migration needs search_path to- -- reach a UUIDv7 generator.- generator <- useSession connection (Session.statement () leaseDefaultStatement)- generator `shouldBe` (True, Just "kiroku.uuidv7()")+ (major, builtinExists, generatorExists, leaseDefault) <-+ useSession connection (Session.statement () leaseDefaultStatement)+ Text.IO.putStrLn+ ( "PostgreSQL "+ <> Text.pack (show major)+ <> " UUIDv7 generator: kiroku.uuidv7() via "+ <> (if builtinExists then "0010's alias for the builtin" else "0001's fallback")+ )+ generatorExists `shouldBe` True+ builtinExists `shouldBe` (major >= 18)+ leaseDefault `shouldBe` Just "kiroku.uuidv7()" either (expectationFailure . show) pure result it "acquires a lease from a session whose search_path excludes kiroku" $ do@@ -512,12 +523,18 @@ importOutcomes :: HistoryImportReport -> [HistoryImportOutcome] importOutcomes HistoryImportReport{importResults} = importOutcome <$> toList importResults -kirokuPgConfig :: Pg.Config-kirokuPgConfig = Pg.defaultConfig{PgConfig.user = "kiroku"}+{- | Built on pg-migrate's stable per-user temporary root, which 'withMigratedDatabase'+also uses, so ephemeral-pg's startup sweep reclaims clusters left by killed runs.+-}+kirokuPgConfig :: IO Pg.Config+kirokuPgConfig = do+ base <- defaultEphemeralConfig+ pure base{PgConfig.user = "kiroku"} withKirokuPg :: (Pg.Database -> IO ()) -> IO () withKirokuPg action = do- started <- Pg.startCached kirokuPgConfig Pg.defaultCacheConfig+ config <- kirokuPgConfig+ started <- Pg.startCached config Pg.defaultCacheConfig case started of Left startError -> expectationFailure (show startError) Right database -> action database `finally` Pg.stop database@@ -559,12 +576,17 @@ Encoders.noParams (Decoders.singleRow (Decoders.column (Decoders.nonNullable Decoders.bool))) --- | Whether @kiroku.uuidv7()@ exists, and the stored @lease_id@ default.-leaseDefaultStatement :: Statement () (Bool, Maybe Text)+{- | The server major, whether each UUIDv7 generator exists, and the stored+@lease_id@ default. Together these pin which route published+@kiroku.uuidv7()@ on the running server.+-}+leaseDefaultStatement :: Statement () (Int32, Bool, Bool, Maybe Text) leaseDefaultStatement = Statement.preparable """- SELECT to_regprocedure('kiroku.uuidv7()') IS NOT NULL,+ SELECT pg_catalog.current_setting('server_version_num')::int / 10000,+ to_regprocedure('pg_catalog.uuidv7()') IS NOT NULL,+ to_regprocedure('kiroku.uuidv7()') IS NOT NULL, (SELECT pg_catalog.pg_get_expr(stored.adbin, stored.adrelid) FROM pg_catalog.pg_attrdef AS stored JOIN pg_catalog.pg_attribute AS attribute@@ -575,8 +597,10 @@ """ Encoders.noParams ( Decoders.singleRow- ( (,)- <$> Decoders.column (Decoders.nonNullable Decoders.bool)+ ( (,,,)+ <$> Decoders.column (Decoders.nonNullable Decoders.int4)+ <*> Decoders.column (Decoders.nonNullable Decoders.bool)+ <*> Decoders.column (Decoders.nonNullable Decoders.bool) <*> Decoders.column (Decoders.nullable Decoders.text) ) )