kioku-migrations 0.11.0.0 → 0.12.0.0
raw patch · 20 files changed
+852/−24 lines, 20 filesdep ~keiro-migrationsdep ~keiro-test-supportdep ~kioku-migrationsPVP ok
version bump matches the API change (PVP)
Dependency ranges changed: keiro-migrations, keiro-test-support, kioku-migrations, kiroku-store-migrations
API changes (from Hackage documentation)
Files
- CHANGELOG.md +16/−0
- kioku-migrations.cabal +11/−8
- test/Main.hs +131/−16
- test/fixtures/kiroku-0.7.1.0/0001-kiroku-bootstrap.sql +229/−0
- test/fixtures/kiroku-0.7.1.0/0002-add-subscription-dead-letters.sql +31/−0
- test/fixtures/kiroku-0.7.1.0/0003-notify-trigger-append-guard.sql +26/−0
- test/fixtures/kiroku-0.7.1.0/0004-dead-letters-event-id-index.sql +10/−0
- test/fixtures/kiroku-0.7.1.0/0005-index-hygiene-and-streams-fillfactor.sql +33/−0
- test/fixtures/kiroku-0.7.1.0/0006-stream-name-length-check.sql +9/−0
- test/fixtures/kiroku-0.7.1.0/0007-stream-truncate-before.sql +8/−0
- test/fixtures/kiroku-0.7.1.0/0008-schema-management-comment.sql +2/−0
- test/fixtures/kiroku-0.7.1.0/0009.sql +35/−0
- test/fixtures/kiroku-0.7.1.0/0010.sql +146/−0
- test/fixtures/kiroku-0.7.1.0/0011.sql +51/−0
- test/fixtures/kiroku-0.7.1.0/0012.sql +51/−0
- test/fixtures/kiroku-0.7.1.0/0013.sql +13/−0
- test/fixtures/kiroku-0.7.1.0/0014.sql +11/−0
- test/fixtures/kiroku-0.7.1.0/0015.sql +16/−0
- test/fixtures/kiroku-0.7.1.0/README.md +8/−0
- test/fixtures/kiroku-0.7.1.0/manifest +15/−0
CHANGELOG.md view
@@ -2,6 +2,22 @@ ## [Unreleased] +## 0.12.0.0 — 2026-10-27++### Added++- Test the complete previous 62-entry cohort using frozen Kiroku migrations+ 0.7.1.0 fixtures, proving that only `kiroku/0016` applies and repeat runs+ are no-ops.++### Changed++- Require `mori://shinzui/keiro` migrations and test support 0.22.0.0 and+ `mori://shinzui/kiroku/packages/kiroku-store-migrations` 0.7.2.0.+- Compose 63 migrations (16 Kiroku, 32 Keiro, 15 Kioku). The additive upstream+ migration changes subscription constraints and the Kiroku schema comment;+ all historical Kioku SQL and checksums are preserved. No ledger fixup is needed.+ ## 0.11.0.0 — 2026-10-19 ### Added
kioku-migrations.cabal view
@@ -1,6 +1,6 @@ cabal-version: 3.0 name: kioku-migrations-version: 0.11.0.0+version: 0.12.0.0 synopsis: Schema migrations for kioku description: Owns kioku schema evolution as a native pg-migrate component and composes@@ -31,6 +31,9 @@ test/fixtures/kiroku-0.6.0.0/README.md test/fixtures/kiroku-0.6.0.0/SHA256SUMS test/fixtures/kiroku-0.6.0.0/manifest+ test/fixtures/kiroku-0.7.1.0/*.sql+ test/fixtures/kiroku-0.7.1.0/README.md+ test/fixtures/kiroku-0.7.1.0/manifest source-repository head type: git@@ -75,8 +78,8 @@ containers >=0.6 && <0.8, hasql >=1.10 && <1.11, hasql-transaction >=1.2 && <1.3,- keiro-migrations ^>=0.21.0.0,- kiroku-store-migrations ^>=0.7.1.0,+ keiro-migrations ^>=0.22.0.0,+ kiroku-store-migrations ^>=0.7.2.0, pg-migrate ^>=1.2.0.0, pg-migrate-embed ^>=1.2.0.0, pg-migrate-import-codd ^>=1.2.0.0,@@ -92,8 +95,8 @@ base >=4.18 && <5, directory >=1.3 && <1.4, ephemeral-pg >=0.3.1 && <0.4,- keiro-test-support ^>=0.21.0.0,- kioku-migrations ^>=0.11.0.0,+ keiro-test-support ^>=0.22.0.0,+ kioku-migrations ^>=0.12.0.0, text >=2.0 && <2.2, unix >=2.8 && <2.9, @@ -115,10 +118,10 @@ bytestring >=0.11 && <0.13, containers >=0.6 && <0.8, hasql >=1.6,- keiro-migrations ^>=0.21.0.0,- kioku-migrations ^>=0.11.0.0,+ keiro-migrations ^>=0.22.0.0,+ kioku-migrations ^>=0.12.0.0, kioku-migrations:test-support,- kiroku-store-migrations ^>=0.7.1.0,+ kiroku-store-migrations ^>=0.7.2.0, pg-migrate ^>=1.2.0.0, pg-migrate-embed ^>=1.2.0.0, pg-migrate-import-codd ^>=1.2.0.0,
test/Main.hs view
@@ -89,7 +89,9 @@ ], testCase "the full migration chain applies to a fresh database" testFreshDatabase, testCase "0.10.0.0 upgrades with one recency index and unchanged rows" testRecencyUpgrade,- testCase "the released previous cohort preserves data and checkpoints through 0013–0015" testPreviousCohortUpgrade,+ testCase "the released previous cohort preserves data and checkpoints through 0013–0016" testPreviousCohortUpgrade,+ testCase "the 62-entry Keiro 0.21 cohort adds only Kiroku 0016" testKeiro021Upgrade,+ testCase "a changed previous checksum refuses before migration writes" testKeiro021ChecksumRefusal, testGroup "migration-session isolation" [testCase "restores the host search path before later components" testHostSearchPathRestored],@@ -473,7 +475,7 @@ checkpointsBefore <- query connStr previousCheckpointSnapshot query connStr previousCohortIsLegacy >>= (@?= True) migrated <- runMigrationPlan defaultRunOptions settings current >>= either (assertFailure . show) pure- appliedNow migrated @?= [expectRight (migrationId "kiroku" n) | n <- ["0013", "0014", "0015"]]+ appliedNow migrated @?= [expectRight (migrationId "kiroku" n) | n <- ["0013", "0014", "0015", "0016"]] query connStr previousCohortSnapshot >>= (@?= before) query connStr previousCohortLedger >>= (@?= ledgerBefore) query connStr previousCheckpointSnapshot >>= (@?= checkpointsBefore)@@ -481,12 +483,12 @@ verification <- verifyMigrationPlan defaultRunOptions settings current >>= either (assertFailure . show) pure let VerificationReport {issues, appliedMigrations, pendingMigrations, unknownMigrations} = verification issues @?= []- length appliedMigrations @?= 62+ length appliedMigrations @?= 63 pendingMigrations @?= [] unknownMigrations @?= [] repeated <- runMigrationPlan defaultRunOptions settings current >>= either (assertFailure . show) pure let MigrationReport {results} = repeated- length [() | MigrationResult {outcome = AlreadyApplied} <- toList results] @?= 62+ length [() | MigrationResult {outcome = AlreadyApplied} <- toList results] @?= 63 appliedNow repeated @?= [] query connStr previousCohortSnapshot >>= (@?= before) query connStr previousCheckpointSnapshot >>= (@?= checkpointsBefore)@@ -545,7 +547,7 @@ """ SELECT jsonb_agg(to_jsonb(t) ORDER BY component, migration)::text FROM pgmigrate.migrations t- WHERE NOT (component = 'kiroku' AND migration IN ('0013', '0014', '0015'))+ WHERE NOT (component = 'kiroku' AND migration IN ('0013', '0014', '0015', '0016')) """ E.noParams (D.singleRow (D.column (D.nonNullable D.text)))@@ -578,6 +580,118 @@ E.noParams (D.singleRow (D.column (D.nonNullable D.bool))) +-- | Frozen released bytes, rather than private pg-migrate representation access.+previous021Plan :: IO MigrationPlan+previous021Plan = do+ entries <- checkMigrationManifest "test/fixtures/kiroku-0.7.1.0/manifest" >>= either (assertFailure . show) pure+ [(Text.dropEnd 4 (Text.pack name), checksumHex bytes) | (name, bytes) <- toList entries] @?= previous021Checksums+ oldKiroku <- either (assertFailure . show) pure (migrationComponentFromEmbeddedSql "kiroku" Set.empty entries)+ keiro <- either (assertFailure . show) pure KeiroMigrations.keiroMigrations+ kioku <- either (assertFailure . show) pure kiokuMigrations+ either (assertFailure . show) pure (migrationPlan (oldKiroku :| [keiro, kioku]))++previous021Checksums :: [(Text, Text)]+previous021Checksums =+ [ ("0001-kiroku-bootstrap", "af7c777f8702d9b5bce6b3ea16540ab7542bc78725cb45e945c6d5b6025a4d66"),+ ("0002-add-subscription-dead-letters", "2771cdd2605a8808ea469c0bcba175c8ba4c5d2465f43eb05c56edb188fa8141"),+ ("0003-notify-trigger-append-guard", "432796c59389d45478ba773b62e9f38d0a562807061eb49280d295ec83248351"),+ ("0004-dead-letters-event-id-index", "f698db092ebf34beeddecc949bbd506afc0b9c63d3e9983ad5d14f203f2ee1cd"),+ ("0005-index-hygiene-and-streams-fillfactor", "80c28c7743f1d9967eadf921c0509fc8477db16500466bf7a49c5f59aadb0fa2"),+ ("0006-stream-name-length-check", "a0e4338a2feadf8f01499a3809d3de78966966689b095ea46195c4d07743e4aa"),+ ("0007-stream-truncate-before", "a4fe42ad8702b43c3d6f72edf263d6b301e783493d49f40a35dd14d2a7928fa7"),+ ("0008-schema-management-comment", "3fb3dd5a7172da3cd14edecb0877cc9963fc9a6dceaff9ed41705a2365070f70"),+ ("0009", "62c80d627dc8f17393d26ff7dbc00cabde11ed38ee7bf3cb1ae42ea89f484c74"),+ ("0010", "debc19187d79cd263be66c6c85cd789c1176e508d562a27390095aaa70f650c4"),+ ("0011", "bf8b76a712a31cb602aee793a11691283ccd1eafdc16224e45e14484512b5af7"),+ ("0012", "32a0a0c877ff1aa6c1f74de8db62c74884e96a9c7500b2b65725b9e654101a7a"),+ ("0013", "49e519280a578e5b0f02603173b492075b072d0ae52d0e5848fea8ae1d6f7d53"),+ ("0014", "b93580ef072e85a6a598724fa8746aaa079346d7dd4a9c859412cc07113ff4d1"),+ ("0015", "821b12e410c1cece52aee018f1916a8cb01b2e900784817ba9b33d37b78d3be5")+ ]++-- The event and application fixture is shared, while modern bindings cover all+-- three pre-namespace target kinds and nonzero checkpoint positions.+prepare021 :: Text -> IO ()+prepare021 connStr = do+ old <- previous021Plan+ installed <- runMigrationPlan defaultRunOptions (Settings.connectionString connStr) old >>= either (assertFailure . show) pure+ length (appliedNow installed) @?= 62+ withConnection connStr $ \conn -> do+ run conn (Session.script (fst (Text.breakOn "INSERT INTO kiroku.subscriptions" previousCohortSeed)))+ run conn (Session.script (Text.replace "kiroku.kioku_" "kioku." relocationSeedRows))+ run+ conn+ ( Session.script+ "INSERT INTO kiroku.subscriptions (subscription_name,last_seen,consumer_group_member,consumer_group_size,target_kind,target_category) VALUES ('upgrade-exact',1,0,1,'category','kioku_memory'),('upgrade-all',1,0,1,'all',NULL),('upgrade-unbound',0,0,1,'unbound',NULL)"+ )++ledgerWithout0016 :: Statement () Text+ledgerWithout0016 =+ preparable+ "SELECT jsonb_agg(to_jsonb(t) ORDER BY component,migration)::text FROM pgmigrate.migrations t WHERE NOT (component='kiroku' AND migration='0016')"+ E.noParams+ (D.singleRow (D.column (D.nonNullable D.text)))++bindingsSnapshot :: Statement () Text+bindingsSnapshot =+ preparable+ "SELECT jsonb_agg(to_jsonb(t) ORDER BY subscription_name,consumer_group_member)::text FROM kiroku.subscriptions t"+ E.noParams+ (D.singleRow (D.column (D.nonNullable D.text)))++testKeiro021Upgrade :: Assertion+testKeiro021Upgrade = withBareDatabase $ \connStr -> do+ prepare021 connStr+ rows <- query connStr previousCohortSnapshot+ ledger <- query connStr ledgerWithout0016+ bindings <- query connStr bindingsSnapshot+ checkpoints <- query connStr previousCheckpointSnapshot+ current <- either (assertFailure . show) pure kiokuMigrationPlan+ migrated <- runMigrationPlan defaultRunOptions (Settings.connectionString connStr) current >>= either (assertFailure . show) pure+ appliedNow migrated @?= [expectRight (migrationId "kiroku" "0016")]+ query connStr previousCohortSnapshot >>= (@?= rows)+ query connStr ledgerWithout0016 >>= (@?= ledger)+ query connStr bindingsSnapshot >>= (@?= bindings)+ query connStr previousCheckpointSnapshot >>= (@?= checkpoints)+ verification <- verifyMigrationPlan defaultRunOptions (Settings.connectionString connStr) current >>= either (assertFailure . show) pure+ let VerificationReport {issues, appliedMigrations, pendingMigrations, unknownMigrations} = verification+ issues @?= []+ length appliedMigrations @?= 63+ pendingMigrations @?= []+ unknownMigrations @?= []+ beforeRepeat <- query connStr fullLedgerSnapshot+ repeated <- runMigrationPlan defaultRunOptions (Settings.connectionString connStr) current >>= either (assertFailure . show) pure+ appliedNow repeated @?= []+ let MigrationReport {results} = repeated+ length [() | MigrationResult {outcome = AlreadyApplied} <- toList results] @?= 63+ query connStr fullLedgerSnapshot >>= (@?= beforeRepeat)+ query connStr previousCohortSnapshot >>= (@?= rows)+ query connStr bindingsSnapshot >>= (@?= bindings)+ query connStr previousCheckpointSnapshot >>= (@?= checkpoints)++testKeiro021ChecksumRefusal :: Assertion+testKeiro021ChecksumRefusal = withBareDatabase $ \connStr -> do+ prepare021 connStr+ withConnection connStr $ \conn ->+ run+ conn+ ( Session.script+ "UPDATE pgmigrate.migrations SET checksum=decode(repeat('00',32),'hex') WHERE component='kiroku' AND migration='0015'"+ )+ ledger <- query connStr fullLedgerSnapshot+ schema <- query connStr cohortSchemaSnapshotStatement+ rows <- query connStr previousCohortSnapshot+ bindings <- query connStr bindingsSnapshot+ current <- either (assertFailure . show) pure kiokuMigrationPlan+ result <- runMigrationPlan defaultRunOptions (Settings.connectionString connStr) current+ case result of+ Left _ -> pure ()+ Right report -> assertFailure ("changed checksum unexpectedly accepted: " <> show report)+ query connStr fullLedgerSnapshot >>= (@?= ledger)+ query connStr cohortSchemaSnapshotStatement >>= (@?= schema)+ query connStr previousCohortSnapshot >>= (@?= rows)+ query connStr bindingsSnapshot >>= (@?= bindings)+ -- * The fresh-database test -- | The rewritten body still applies inside the real pg-migrate chain.@@ -592,7 +706,7 @@ runMigrationPlan defaultRunOptions (Settings.connectionString connStr) plan >>= either (assertFailure . show) pure let MigrationReport {results = repeatedResults} = repeated- length [() | MigrationResult {outcome = AlreadyApplied} <- toList repeatedResults] @?= 62+ length [() | MigrationResult {outcome = AlreadyApplied} <- toList repeatedResults] @?= 63 length [() | MigrationResult {outcome = AppliedNow} <- toList repeatedResults] @?= 0 verification <- verifyMigrationPlan defaultRunOptions (Settings.connectionString connStr) plan@@ -711,10 +825,10 @@ plan <- either (fail . show) pure kiokuMigrationPlan let settings = Settings.connectionString connStr initial <- runMigrationPlan defaultRunOptions settings plan >>= either (assertFailure . show) pure- length (appliedNow initial) @?= 62+ length (appliedNow initial) @?= 63 baselineLedger <- query connStr fullLedgerSnapshot- fst baselineLedger @?= 62+ fst baselineLedger @?= 63 baselineSchema <- query connStr cohortSchemaSnapshotStatement fixup <- Text.IO.readFile ledgerFixupPath @@ -740,7 +854,7 @@ repeated <- runMigrationPlan defaultRunOptions settings plan >>= either (assertFailure . show) pure let MigrationReport {results = repeatedResults} = repeated- length [() | MigrationResult {outcome = AlreadyApplied} <- toList repeatedResults] @?= 62+ length [() | MigrationResult {outcome = AlreadyApplied} <- toList repeatedResults] @?= 63 length [() | MigrationResult {outcome = AppliedNow} <- toList repeatedResults] @?= 0 withConnection connStr \conn -> run conn (Session.script fixup)@@ -1205,12 +1319,12 @@ full <- either (fail . show) pure kiokuMigrationPlan kirokuReport <- runMigrationPlan defaultRunOptions settings kirokuOnly >>= either (assertFailure . show) pure- length (appliedNow kirokuReport) @?= 15+ length (appliedNow kirokuReport) @?= 16 ledgerBefore <- query connStr kirokuLedgerSnapshot adoption <- runMigrationPlan defaultRunOptions settings full >>= either (assertFailure . show) pure let MigrationReport {results = adoptionResults} = adoption- length [() | MigrationResult {outcome = AlreadyApplied} <- toList adoptionResults] @?= 15+ length [() | MigrationResult {outcome = AlreadyApplied} <- toList adoptionResults] @?= 16 length [() | MigrationResult {outcome = AppliedNow} <- toList adoptionResults] @?= 47 -- Verified and skipped, never re-executed: the stored rows keep their checksums and their@@ -1224,7 +1338,7 @@ verification <- verifyMigrationPlan defaultRunOptions settings full >>= either (assertFailure . show) pure let VerificationReport {issues = adoptionIssues, appliedMigrations, pendingMigrations, unknownMigrations} = verification adoptionIssues @?= []- length appliedMigrations @?= 62+ length appliedMigrations @?= 63 pendingMigrations @?= [] unknownMigrations @?= [] @@ -1575,13 +1689,13 @@ verification <- verifyMigrationPlan defaultRunOptions settings plan >>= either (assertFailure . show) pure let VerificationReport {issues = verificationIssues, appliedMigrations, pendingMigrations, unknownMigrations} = verification verificationIssues @?= []- length appliedMigrations @?= 62+ length appliedMigrations @?= 63 pendingMigrations @?= [] unknownMigrations @?= [] repeated <- runMigrationPlan defaultRunOptions settings plan >>= either (assertFailure . show) pure let MigrationReport {results = repeatedResults} = repeated- length [() | MigrationResult {outcome = AlreadyApplied} <- toList repeatedResults] @?= 62+ length [() | MigrationResult {outcome = AlreadyApplied} <- toList repeatedResults] @?= 63 length [() | MigrationResult {outcome = AppliedNow} <- toList repeatedResults] @?= 0 testSchemaConvergence :: Assertion@@ -1750,6 +1864,7 @@ migrationId "kiroku" "0013", migrationId "kiroku" "0014", migrationId "kiroku" "0015",+ migrationId "kiroku" "0016", migrationId "keiro" "0015-keiro-outbox-claim-order-index", migrationId "keiro" "0016-keiro-inbox-drop-received-idx", migrationId "keiro" "0017-schema-management-comment",@@ -1817,7 +1932,7 @@ (EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema = 'kiroku' AND table_name = 'streams' AND column_name = 'truncate_before'))::int + (coalesce(obj_description(to_regnamespace('kiroku'), 'pg_namespace'), '') =- 'Managed by pg-migrate component kiroku through 0015')::int+ 'Managed by pg-migrate component kiroku through 0016')::int + (to_regclass('keiro.keiro_outbox_claim_order_idx') IS NOT NULL)::int + (to_regclass('keiro.keiro_inbox_received_idx') IS NULL)::int + (coalesce(obj_description(to_regnamespace('keiro'), 'pg_namespace'), '') =@@ -1864,7 +1979,7 @@ old <- either (assertFailure . show) pure (migrationPlan (kiroku :| [keiro, oldKioku])) current <- either (assertFailure . show) pure kiokuMigrationPlan initial <- runMigrationPlan defaultRunOptions settings old >>= either (assertFailure . show) pure- length (appliedNow initial) @?= 61+ length (appliedNow initial) @?= 62 withConnection connStr $ \conn -> run conn
+ test/fixtures/kiroku-0.7.1.0/0001-kiroku-bootstrap.sql view
@@ -0,0 +1,229 @@+-- Kiroku Store bootstrap migration (codd)+-- Supports PostgreSQL 17+.+--+-- kiroku-store-migrations is the schema owner. codd applies this file+-- verbatim, records that the timestamped migration ran, and skips it on later+-- runs. Future schema changes add new timestamped SQL files in this directory+-- instead of changing kiroku-store.+--+-- All Kiroku-owned objects live in the dedicated `kiroku` schema, leaving+-- `public` free for application objects. Creating the schema and setting+-- search_path first means every unqualified object name in the rest of this+-- file resolves into the Kiroku schema.+CREATE SCHEMA IF NOT EXISTS kiroku;+SET search_path TO kiroku, pg_catalog;++-- PostgreSQL 18 provides pg_catalog.uuidv7(); PostgreSQL 17 needs this+-- Kiroku-schema fallback before events.event_id DEFAULT uuidv7() is parsed.+-- With search_path set above, the unqualified CREATE FUNCTION lands in the+-- Kiroku schema, and to_regprocedure('uuidv7()') resolves through search_path+-- (pg_catalog first for the built-in, then the Kiroku schema for the fallback).+DO $$+BEGIN+ IF to_regprocedure('pg_catalog.uuidv7()') IS NULL+ AND to_regprocedure('uuidv7()') IS NULL THEN+ EXECUTE $fn$+ CREATE FUNCTION uuidv7()+ RETURNS uuid+ AS $body$+ DECLARE+ unix_ts_ms bytea;+ uuid_bytes bytea;+ BEGIN+ unix_ts_ms = substring(int8send(floor(extract(epoch from clock_timestamp()) * 1000)::bigint) from 3);+ uuid_bytes = uuid_send(gen_random_uuid());+ uuid_bytes = overlay(uuid_bytes placing unix_ts_ms from 1 for 6);+ uuid_bytes = set_byte(uuid_bytes, 6, (b'0111' || get_byte(uuid_bytes, 6)::bit(4))::bit(8)::int);+ RETURN encode(uuid_bytes, 'hex')::uuid;+ END+ $body$+ LANGUAGE plpgsql+ VOLATILE+ $fn$;+ END IF;+END+$$;++-- Streams (including $all as stream_id = 0)+CREATE TABLE IF NOT EXISTS streams (+ stream_id BIGSERIAL PRIMARY KEY,+ stream_name TEXT NOT NULL,+ category TEXT GENERATED ALWAYS AS (split_part(stream_name, '-', 1)) STORED,+ stream_version BIGINT NOT NULL DEFAULT 0,+ created_at TIMESTAMPTZ NOT NULL DEFAULT now(),+ deleted_at TIMESTAMPTZ,+ CONSTRAINT ix_streams_stream_name UNIQUE (stream_name)+);++-- Seed the $all stream+INSERT INTO streams (stream_id, stream_name, stream_version)+VALUES (0, '$all', 0)+ON CONFLICT DO NOTHING;++-- Reset sequence past the reserved stream_id=0+SELECT setval('streams_stream_id_seq', GREATEST((SELECT MAX(stream_id) FROM streams), 1));++-- Events (flat table — stream membership tracked in stream_events)+CREATE TABLE IF NOT EXISTS events (+ event_id UUID PRIMARY KEY DEFAULT uuidv7(),+ event_type TEXT NOT NULL,+ causation_id UUID,+ correlation_id UUID,+ data JSONB NOT NULL,+ metadata JSONB,+ created_at TIMESTAMPTZ NOT NULL DEFAULT now()+);++-- Stream-event junction (each event gets 2+ rows: source stream + $all + any links)+CREATE TABLE IF NOT EXISTS stream_events (+ event_id UUID NOT NULL REFERENCES events(event_id),+ stream_id BIGINT NOT NULL REFERENCES streams(stream_id),+ stream_version BIGINT NOT NULL,+ original_stream_id BIGINT NOT NULL,+ original_stream_version BIGINT NOT NULL,+ PRIMARY KEY (event_id, stream_id)+);++-- Indexes++-- Primary read path: fetch events from a stream in order+CREATE INDEX IF NOT EXISTS ix_stream_events_stream_version+ ON stream_events (stream_id, stream_version);++-- Event type filtering (for server-side subscription filtering)+CREATE INDEX IF NOT EXISTS ix_events_event_type+ ON events (event_type);++-- Correlation tracing+CREATE INDEX IF NOT EXISTS ix_events_correlation_id+ ON events (correlation_id) WHERE correlation_id IS NOT NULL;++-- Causation tracing+CREATE INDEX IF NOT EXISTS ix_events_causation_id+ ON events (causation_id) WHERE causation_id IS NOT NULL;++-- Category filtering (for readCategory — uses generated column, not LIKE)+CREATE INDEX IF NOT EXISTS ix_streams_category+ ON streams (category);++-- Category read path: find $all entries by originating stream, ordered by global position+-- Enables efficient category reads by allowing the planner to: look up category stream_ids →+-- index scan $all for each → merge ordered by stream_version+CREATE INDEX IF NOT EXISTS ix_stream_events_all_by_origin+ ON stream_events (original_stream_id, stream_version)+ WHERE stream_id = 0;++-- Subscriptions (checkpoint persistence for subscription positions).+-- consumer_group_member / consumer_group_size carry static consumer-group+-- topology (ExecPlan 28 / EP-1). Non-group subscriptions are member 0, size 1.+-- The unique key is composite (subscription_name, consumer_group_member) so each+-- group member persists its own checkpoint under one shared subscription name.+CREATE TABLE IF NOT EXISTS subscriptions (+ subscription_id BIGSERIAL PRIMARY KEY,+ subscription_name TEXT NOT NULL,+ stream_name TEXT NOT NULL DEFAULT '$all',+ last_seen BIGINT NOT NULL DEFAULT 0,+ consumer_group_member INT NOT NULL DEFAULT 0,+ consumer_group_size INT NOT NULL DEFAULT 1,+ created_at TIMESTAMPTZ NOT NULL DEFAULT now(),+ updated_at TIMESTAMPTZ NOT NULL DEFAULT now()+);++-- Idempotent convergence for databases created before EP-1: add the columns if+-- missing, drop the old auto-named single-column unique constraint if present,+-- and install the composite unique index. All guarded so the bootstrap body is+-- still safe in disposable local databases; codd records the timestamped+-- migration and does not reapply it after a successful run.+ALTER TABLE subscriptions ADD COLUMN IF NOT EXISTS consumer_group_member INT NOT NULL DEFAULT 0;+ALTER TABLE subscriptions ADD COLUMN IF NOT EXISTS consumer_group_size INT NOT NULL DEFAULT 1;+ALTER TABLE subscriptions DROP CONSTRAINT IF EXISTS subscriptions_subscription_name_key;+CREATE UNIQUE INDEX IF NOT EXISTS ix_subscriptions_name_member+ ON subscriptions (subscription_name, consumer_group_member);++-- Triggers++-- NOTIFY on stream changes (fires once per append, not per event)+CREATE OR REPLACE FUNCTION notify_events() RETURNS TRIGGER AS $$+BEGIN+ PERFORM pg_notify(+ TG_TABLE_SCHEMA || '.events',+ NEW.stream_name || ',' || NEW.stream_id || ',' || NEW.stream_version+ );+ RETURN NEW;+END;+$$ LANGUAGE plpgsql;++DROP TRIGGER IF EXISTS stream_events_notify ON streams;+CREATE TRIGGER stream_events_notify+ AFTER INSERT OR UPDATE ON streams+ FOR EACH ROW EXECUTE FUNCTION notify_events();++-- Immutability: prevent event mutation+CREATE OR REPLACE FUNCTION prevent_mutation() RETURNS TRIGGER AS $$+BEGIN+ RAISE EXCEPTION 'Immutable table: % cannot be updated', TG_TABLE_NAME;+END;+$$ LANGUAGE plpgsql;++DROP TRIGGER IF EXISTS no_update_events ON events;+CREATE TRIGGER no_update_events+ BEFORE UPDATE ON events+ FOR EACH ROW EXECUTE FUNCTION prevent_mutation();++DROP TRIGGER IF EXISTS no_update_stream_events ON stream_events;+CREATE TRIGGER no_update_stream_events+ BEFORE UPDATE ON stream_events+ FOR EACH ROW EXECUTE FUNCTION prevent_mutation();++-- Gated hard deletes (for maintenance/GDPR only)+CREATE OR REPLACE FUNCTION protect_deletion() RETURNS TRIGGER AS $$+BEGIN+ IF current_setting('kiroku.enable_hard_deletes', true) = 'on' THEN+ RETURN OLD;+ END IF;+ RAISE EXCEPTION 'Hard deletes require: SET LOCAL kiroku.enable_hard_deletes = ''on''';+END;+$$ LANGUAGE plpgsql;++DROP TRIGGER IF EXISTS no_delete_events ON events;+CREATE TRIGGER no_delete_events+ BEFORE DELETE ON events+ FOR EACH ROW EXECUTE FUNCTION protect_deletion();++DROP TRIGGER IF EXISTS no_delete_stream_events ON stream_events;+CREATE TRIGGER no_delete_stream_events+ BEFORE DELETE ON stream_events+ FOR EACH ROW EXECUTE FUNCTION protect_deletion();++DROP TRIGGER IF EXISTS no_delete_streams ON streams;+CREATE TRIGGER no_delete_streams+ BEFORE DELETE ON streams+ FOR EACH ROW EXECUTE FUNCTION protect_deletion();++-- TRUNCATE bypasses row-level triggers, so the BEFORE DELETE triggers above+-- do not protect against an operator running TRUNCATE on these tables. Add+-- statement-level BEFORE TRUNCATE triggers gated by the same GUC so the+-- protection is symmetric. See EP-1 F6.+CREATE OR REPLACE FUNCTION protect_truncation() RETURNS TRIGGER AS $$+BEGIN+ IF current_setting('kiroku.enable_hard_deletes', true) = 'on' THEN+ RETURN NULL;+ END IF;+ RAISE EXCEPTION 'TRUNCATE requires: SET LOCAL kiroku.enable_hard_deletes = ''on''';+END;+$$ LANGUAGE plpgsql;++DROP TRIGGER IF EXISTS no_truncate_events ON events;+CREATE TRIGGER no_truncate_events+ BEFORE TRUNCATE ON events+ FOR EACH STATEMENT EXECUTE FUNCTION protect_truncation();++DROP TRIGGER IF EXISTS no_truncate_stream_events ON stream_events;+CREATE TRIGGER no_truncate_stream_events+ BEFORE TRUNCATE ON stream_events+ FOR EACH STATEMENT EXECUTE FUNCTION protect_truncation();++DROP TRIGGER IF EXISTS no_truncate_streams ON streams;+CREATE TRIGGER no_truncate_streams+ BEFORE TRUNCATE ON streams+ FOR EACH STATEMENT EXECUTE FUNCTION protect_truncation();
+ test/fixtures/kiroku-0.7.1.0/0002-add-subscription-dead-letters.sql view
@@ -0,0 +1,31 @@+-- Add the kiroku.dead_letters table (MasterPlan 6 / EP-2, docs/plans/40-...).+--+-- Forward, additive migration: codd applies this file once, records it, and+-- skips it on later runs. It does not edit the bootstrap migration and does not+-- mutate existing event data.+--+-- A dead-letter row records one event that a subscription handler asked to+-- "dead-letter" (return DeadLetter, or exhaust its bounded retry budget). The+-- event itself stays immutable in kiroku.events; this table references it by+-- event_id and global_position rather than copying the payload. The+-- consumer_group_member column (default 0, matching kiroku.subscriptions)+-- attributes the row to the member that produced it, so a consumer group's dead+-- letters are per-member. The worker writes a row and advances the member's+-- checkpoint in one atomic statement (see SQL.insertDeadLetterAndCheckpointStmt).++CREATE TABLE IF NOT EXISTS kiroku.dead_letters (+ dead_letter_id BIGSERIAL PRIMARY KEY,+ subscription_name TEXT NOT NULL,+ consumer_group_member INT NOT NULL DEFAULT 0,+ global_position BIGINT NOT NULL,+ event_id UUID NOT NULL REFERENCES kiroku.events(event_id),+ reason JSONB NOT NULL,+ reason_summary TEXT NOT NULL,+ attempt_count INT NOT NULL,+ created_at TIMESTAMPTZ NOT NULL DEFAULT now(),+ UNIQUE (subscription_name, consumer_group_member, global_position, event_id)+);++-- Operator read path: list a subscription member's dead letters by recency.+CREATE INDEX IF NOT EXISTS ix_dead_letters_subscription_created_at+ ON kiroku.dead_letters (subscription_name, consumer_group_member, created_at);
+ test/fixtures/kiroku-0.7.1.0/0003-notify-trigger-append-guard.sql view
@@ -0,0 +1,26 @@+-- Guard append notifications at the trigger level.+--+-- The trigger function and payload format stay unchanged:+-- stream_name,stream_id,stream_version+--+-- INSERT covers newly-created streams. UPDATE covers later appends to an+-- existing stream. Both exclude the internal $all row (stream_id = 0), and the+-- UPDATE trigger only fires when an append advances stream_version, not for+-- lifecycle updates such as soft-delete or undelete.++DROP TRIGGER IF EXISTS stream_events_notify ON kiroku.streams;++DROP TRIGGER IF EXISTS stream_events_notify_insert ON kiroku.streams;+CREATE TRIGGER stream_events_notify_insert+ AFTER INSERT ON kiroku.streams+ FOR EACH ROW+ WHEN (NEW.stream_id <> 0)+ EXECUTE FUNCTION kiroku.notify_events();++DROP TRIGGER IF EXISTS stream_events_notify_update ON kiroku.streams;+CREATE TRIGGER stream_events_notify_update+ AFTER UPDATE ON kiroku.streams+ FOR EACH ROW+ WHEN (NEW.stream_id <> 0+ AND NEW.stream_version IS DISTINCT FROM OLD.stream_version)+ EXECUTE FUNCTION kiroku.notify_events();
+ test/fixtures/kiroku-0.7.1.0/0004-dead-letters-event-id-index.sql view
@@ -0,0 +1,10 @@+-- Index dead_letters by event_id (MasterPlan 9 / EP-5, docs/plans/60-...).+--+-- dead_letters.event_id has a FK to kiroku.events. The UNIQUE key leads with+-- subscription_name, so every referential-integrity check triggered by a+-- DELETE on kiroku.events (the hard-delete path) was a sequential scan of+-- dead_letters, and the hard-delete transaction's own dead-letter pre-delete+-- (Kiroku.Store.SQL.deleteDeadLettersForOrphanedEventsStmt) needs the same+-- access path.+CREATE INDEX IF NOT EXISTS ix_dead_letters_event_id+ ON kiroku.dead_letters (event_id);
+ test/fixtures/kiroku-0.7.1.0/0005-index-hygiene-and-streams-fillfactor.sql view
@@ -0,0 +1,33 @@+-- Index hygiene and $all hot-row tuning (MasterPlan 9 / EP-5, docs/plans/60-...).++-- 1. ix_events_event_type is referenced by no statement in kiroku-store; it is+-- pure write amplification on every append. Server-side event-type pushdown+-- (see the EventTypeFilter haddock in kiroku-store) should re-add a+-- fit-for-purpose index when it ships.+DROP INDEX IF EXISTS kiroku.ix_events_event_type;++-- 2. Stream versions are unique per stream by construction (assigned under the+-- stream row lock; $all versions are the global position). Enforce it so a+-- version-assignment bug surfaces as a loud 23505 instead of silent+-- duplicates. Built as a new unique index, then the old non-unique index is+-- dropped (an index cannot be altered to unique in place).+CREATE UNIQUE INDEX IF NOT EXISTS ux_stream_events_stream_version+ ON kiroku.stream_events (stream_id, stream_version);+DROP INDEX IF EXISTS kiroku.ix_stream_events_stream_version;++-- 3. readDeadLetters orders by (global_position DESC, dead_letter_id DESC) --+-- the store's canonical, deterministic "newest first". Re-key the read+-- index to match so the read is index-ordered instead of sorting each time.+CREATE INDEX IF NOT EXISTS ix_dead_letters_subscription_position+ ON kiroku.dead_letters+ (subscription_name, consumer_group_member,+ global_position DESC, dead_letter_id DESC);+DROP INDEX IF EXISTS kiroku.ix_dead_letters_subscription_created_at;++-- 4. The $all row (stream_id 0) is updated by every append in the database.+-- Its updated column (stream_version) is not indexed, so updates are+-- HOT-eligible when the page has free space; fillfactor 50 reserves that+-- space on newly written pages. Existing pages converge through normal+-- update/prune activity (VACUUM cannot run inside this migration's+-- transaction). Autovacuum tuning for this table is left to operators.+ALTER TABLE kiroku.streams SET (fillfactor = 50);
+ test/fixtures/kiroku-0.7.1.0/0006-stream-name-length-check.sql view
@@ -0,0 +1,9 @@+-- Defense-in-depth bound on stream-name length (MasterPlan 9 / EP-5,+-- docs/plans/60-...). The Haskell store validates this before any SQL+-- (StoreError StreamNameTooLong, maxStreamNameBytes = 512); the constraint+-- catches writers that bypass the library (raw SQL via runTransaction,+-- psql sessions). 512 bytes is far below pg_notify's 8,000-byte payload+-- limit, so the append-notification trigger can never abort on payload size.+ALTER TABLE kiroku.streams+ ADD CONSTRAINT chk_streams_stream_name_length+ CHECK (octet_length(stream_name) <= 512);
+ test/fixtures/kiroku-0.7.1.0/0007-stream-truncate-before.sql view
@@ -0,0 +1,8 @@+-- Logical truncate-before marker for close-the-book compaction+-- (ExecPlan docs/plans/65). Per-stream cursor: ordered stream reads return+-- only events whose stream_version >= truncate_before. Default 0 keeps all+-- events (per-stream versions are 1-based). Reversible; the global $all log+-- is never affected. UPDATE on streams is already permitted (soft-delete+-- uses it), so no trigger/GUC changes are needed.+ALTER TABLE kiroku.streams+ ADD COLUMN IF NOT EXISTS truncate_before BIGINT NOT NULL DEFAULT 0;
+ test/fixtures/kiroku-0.7.1.0/0008-schema-management-comment.sql view
@@ -0,0 +1,2 @@+COMMENT ON SCHEMA kiroku IS+ 'Managed by pg-migrate component kiroku through 0008-schema-management-comment';
+ test/fixtures/kiroku-0.7.1.0/0009.sql view
@@ -0,0 +1,35 @@+-- Publish the frozen, owner-rights, structurally read-only v1 checkpoint relation.+CREATE VIEW kiroku.subscription_checkpoints_v1+ (subscription_name,+ consumer_group_member,+ checkpoint_position,+ checkpoint_updated_at)+WITH (security_invoker = false)+AS+WITH checkpoint_rows AS NOT MATERIALIZED (+ SELECT subscription_name,+ consumer_group_member,+ last_seen AS checkpoint_position,+ updated_at AS checkpoint_updated_at+ FROM kiroku.subscriptions+)+SELECT subscription_name,+ consumer_group_member,+ checkpoint_position,+ checkpoint_updated_at+FROM checkpoint_rows;++COMMENT ON VIEW kiroku.subscription_checkpoints_v1 IS+ 'Stable read-only v1 relation of exact persisted subscription-member checkpoints; its columns and order are frozen, and its rows are unordered unless the caller supplies ORDER BY.';++COMMENT ON COLUMN kiroku.subscription_checkpoints_v1.subscription_name IS+ 'Persisted subscription name; member zero does not distinguish a non-group subscription from member zero of a consumer group.';++COMMENT ON COLUMN kiroku.subscription_checkpoints_v1.consumer_group_member IS+ 'Persisted consumer-group member key; member zero carries no topology classification.';++COMMENT ON COLUMN kiroku.subscription_checkpoints_v1.checkpoint_position IS+ 'Exact persisted global position for this subscription member; an explicit reset may move it backward or forward.';++COMMENT ON COLUMN kiroku.subscription_checkpoints_v1.checkpoint_updated_at IS+ 'Time of the latest checkpoint-row upsert; it does not imply position advancement or worker liveness.';
+ test/fixtures/kiroku-0.7.1.0/0010.sql view
@@ -0,0 +1,146 @@+-- add replay history retention++-- Publish kiroku.uuidv7() as the component's version-independent, always+-- schema-qualified UUIDv7 generator.+--+-- PostgreSQL 18 provides pg_catalog.uuidv7(), so 0001 installed no fallback+-- and the name exists only in pg_catalog. PostgreSQL 17 has no builtin, so+-- 0001 installed the fallback into the Kiroku schema. Naming uuidv7()+-- unqualified therefore resolves only through search_path, which no migration+-- after 0001 may depend on. Establishing kiroku.uuidv7() on both versions lets+-- this migration -- and every migration after it -- name one generator that+-- resolves without session state on every PostgreSQL version the component+-- supports.+DO $$+BEGIN+ IF to_regprocedure('kiroku.uuidv7()') IS NOT NULL THEN+ -- PostgreSQL 17: 0001's fallback already occupies the name.+ RETURN;+ END IF;++ IF to_regprocedure('pg_catalog.uuidv7()') IS NULL THEN+ RAISE EXCEPTION+ 'no uuidv7() generator: neither pg_catalog.uuidv7() nor kiroku.uuidv7() exists';+ END IF;++ -- PostgreSQL 18+: alias the builtin so the qualified name is available.+ EXECUTE $fn$+ CREATE FUNCTION kiroku.uuidv7()+ RETURNS uuid+ LANGUAGE sql+ VOLATILE+ AS 'SELECT pg_catalog.uuidv7()'+ $fn$;+END+$$;++-- A schema-local singleton row serializes lease lifecycle changes with every+-- destructive statement without adding work to append or ordinary read paths.+CREATE TABLE kiroku.history_retention_coordinator (+ singleton BOOLEAN PRIMARY KEY DEFAULT TRUE,+ CONSTRAINT chk_history_retention_coordinator_singleton CHECK (singleton)+);++INSERT INTO kiroku.history_retention_coordinator (singleton)+VALUES (TRUE)+ON CONFLICT DO NOTHING;++-- Durable operational evidence for rebuilds that require the retained event+-- set to remain stable. Active state is derived from released_at and database+-- time; expiry needs no worker or cleanup mutation.+CREATE TABLE kiroku.history_retention_leases (+ lease_id UUID PRIMARY KEY DEFAULT kiroku.uuidv7(),+ owner TEXT NOT NULL,+ reason TEXT NOT NULL,+ protected_through BIGINT NOT NULL,+ created_at TIMESTAMPTZ NOT NULL,+ renewed_at TIMESTAMPTZ NOT NULL,+ expires_at TIMESTAMPTZ NOT NULL,+ released_at TIMESTAMPTZ,+ CONSTRAINT chk_history_retention_lease_owner_bytes+ CHECK (octet_length(owner) BETWEEN 1 AND 512),+ CONSTRAINT chk_history_retention_lease_reason_bytes+ CHECK (octet_length(reason) BETWEEN 1 AND 2048),+ CONSTRAINT chk_history_retention_lease_frontier+ CHECK (protected_through >= 0),+ CONSTRAINT chk_history_retention_lease_renewal_time+ CHECK (renewed_at >= created_at),+ CONSTRAINT chk_history_retention_lease_expiry+ CHECK (expires_at > created_at),+ CONSTRAINT chk_history_retention_lease_release_time+ CHECK (released_at IS NULL OR released_at >= created_at)+);++CREATE INDEX ix_history_retention_leases_unreleased_expiry+ ON kiroku.history_retention_leases (expires_at)+ WHERE released_at IS NULL;++-- This function is attached only to DELETE and TRUNCATE. TG_TABLE_SCHEMA is+-- quoted into each dynamic statement so a schema-qualified maintenance command+-- cannot be redirected to a coordinator selected through the caller's+-- search_path.+CREATE FUNCTION kiroku.protect_replay_history_from_destruction()+RETURNS TRIGGER+LANGUAGE plpgsql+AS $$+DECLARE+ active_count BIGINT;+BEGIN+ EXECUTE format(+ 'SELECT singleton FROM %I.history_retention_coordinator WHERE singleton FOR UPDATE',+ TG_TABLE_SCHEMA+ );++ EXECUTE format(+ 'SELECT count(*) FROM %I.history_retention_leases WHERE released_at IS NULL AND expires_at > clock_timestamp()',+ TG_TABLE_SCHEMA+ ) INTO active_count;++ IF active_count > 0 THEN+ RAISE EXCEPTION USING+ ERRCODE = 'KR001',+ MESSAGE = format(+ 'replay history is protected by %s active retention lease(s)',+ active_count+ );+ END IF;++ RETURN NULL;+END;+$$;++CREATE TRIGGER protect_replay_history_delete+ BEFORE DELETE ON kiroku.events+ FOR EACH STATEMENT+ EXECUTE FUNCTION kiroku.protect_replay_history_from_destruction();++CREATE TRIGGER protect_replay_history_truncate+ BEFORE TRUNCATE ON kiroku.events+ FOR EACH STATEMENT+ EXECUTE FUNCTION kiroku.protect_replay_history_from_destruction();++CREATE TRIGGER protect_replay_history_delete+ BEFORE DELETE ON kiroku.stream_events+ FOR EACH STATEMENT+ EXECUTE FUNCTION kiroku.protect_replay_history_from_destruction();++CREATE TRIGGER protect_replay_history_truncate+ BEFORE TRUNCATE ON kiroku.stream_events+ FOR EACH STATEMENT+ EXECUTE FUNCTION kiroku.protect_replay_history_from_destruction();++CREATE TRIGGER protect_replay_history_delete+ BEFORE DELETE ON kiroku.streams+ FOR EACH STATEMENT+ EXECUTE FUNCTION kiroku.protect_replay_history_from_destruction();++CREATE TRIGGER protect_replay_history_truncate+ BEFORE TRUNCATE ON kiroku.streams+ FOR EACH STATEMENT+ EXECUTE FUNCTION kiroku.protect_replay_history_from_destruction();++COMMENT ON TABLE kiroku.history_retention_leases IS+ 'Durable, expiring replay-history retention leases; rows are operational evidence and remain until explicitly pruned.';++COMMENT ON SCHEMA kiroku IS+ 'Managed by pg-migrate component kiroku through 0010';
+ test/fixtures/kiroku-0.7.1.0/0011.sql view
@@ -0,0 +1,51 @@+-- converge databases that applied the withdrawn 0.3.2.x payload of 0010++-- kiroku-store-migrations 0.3.2.0 and 0.3.2.1 shipped a payload of 0010 whose+-- lease_id default named uuidv7() unqualified. That payload could not parse on+-- PostgreSQL 17 outside the bootstrap session (BUG-1), and where it did apply+-- -- PostgreSQL 18, or a fresh PostgreSQL 17 install -- it left the schema in a+-- state the corrected 0010 does not produce: no kiroku.uuidv7(), and a lease_id+-- default bound to whichever uuidv7() the applying session happened to resolve.+--+-- The corrected 0010 cannot reach those databases: its ledger row is already+-- present, and re-baselining that row (see ledger-fixups/) restores the+-- checksum without replaying the SQL. This migration is what makes both+-- populations converge, and it is a no-op on any database that applied the+-- corrected 0010.++-- 1. kiroku.uuidv7() exists on every supported major version. Identical to the+-- header of the corrected 0010, and skipped there because 0010 ran first.+DO $$+BEGIN+ IF to_regprocedure('kiroku.uuidv7()') IS NOT NULL THEN+ -- PostgreSQL 17, or a database that applied the corrected 0010.+ RETURN;+ END IF;++ IF to_regprocedure('pg_catalog.uuidv7()') IS NULL THEN+ RAISE EXCEPTION+ 'no uuidv7() generator: neither pg_catalog.uuidv7() nor kiroku.uuidv7() exists';+ END IF;++ EXECUTE $fn$+ CREATE FUNCTION kiroku.uuidv7()+ RETURNS uuid+ LANGUAGE sql+ VOLATILE+ AS 'SELECT pg_catalog.uuidv7()'+ $fn$;+END+$$;++-- 2. Bind lease_id's default to the qualified generator. A column default is+-- stored as a resolved function OID, so a database that applied the+-- withdrawn payload on PostgreSQL 18 holds pg_catalog.uuidv7() here. Both+-- generators produce the same values; this only makes the stored default+-- match what the corrected 0010 installs, so a dumped schema is identical+-- whichever payload the database applied. Existing lease rows are untouched+-- and no table is rewritten.+ALTER TABLE kiroku.history_retention_leases+ ALTER COLUMN lease_id SET DEFAULT kiroku.uuidv7();++COMMENT ON SCHEMA kiroku IS+ 'Managed by pg-migrate component kiroku through 0011';
+ test/fixtures/kiroku-0.7.1.0/0012.sql view
@@ -0,0 +1,51 @@+-- denormalize category onto $all junction rows for category reads++-- Category reads (readCategoryForwardSQL and its consumer-group variant in+-- kiroku-store) used to start from every stream of the category and probe+-- stream_events once per stream, so a caught-up poll cost work proportional to+-- the number of streams ever written in the category (BUG-2). Carrying the+-- originating stream's category on each $all junction row lets both reads run+-- as one index range scan from (category, checkpoint) that stops at the limit.+--+-- The column is populated only on $all rows (stream_id = 0). Home rows and link+-- rows keep it NULL; nothing reads them by category. The CHECK below makes any+-- inserter that forgets the column fail loudly instead of writing rows that a+-- category read cannot see.+--+-- The whole file runs in one transaction. The backfill rewrites every $all row+-- and the index build blocks writes, so appends wait for the duration; apply it+-- in a maintenance window on a large store and VACUUM (ANALYZE)+-- kiroku.stream_events afterwards.++ALTER TABLE kiroku.stream_events+ ADD COLUMN category TEXT;++COMMENT ON COLUMN kiroku.stream_events.category IS+ 'Originating stream''s category, present on $all rows (stream_id = 0) only; equals streams.category of original_stream_id.';++-- Backfill every existing $all row from its originating stream. The immutability+-- trigger rejects every UPDATE on this table, so it is suspended for this one+-- statement and re-enabled before the transaction ends. Runs as the table owner.+ALTER TABLE kiroku.stream_events DISABLE TRIGGER no_update_stream_events;++UPDATE kiroku.stream_events AS se+SET category = s.category+FROM kiroku.streams AS s+WHERE se.stream_id = 0+ AND s.stream_id = se.original_stream_id;++ALTER TABLE kiroku.stream_events ENABLE TRIGGER no_update_stream_events;++ALTER TABLE kiroku.stream_events+ ADD CONSTRAINT ck_stream_events_all_category+ CHECK (stream_id <> 0 OR category IS NOT NULL);++-- Category read path: rows of one category in global-position order. The+-- INCLUDE column lets the consumer-group hash predicate run on index tuples.+CREATE INDEX ix_stream_events_all_by_category+ ON kiroku.stream_events (category, stream_version)+ INCLUDE (original_stream_id)+ WHERE stream_id = 0;++COMMENT ON SCHEMA kiroku IS+ 'Managed by pg-migrate component kiroku through 0012';
+ test/fixtures/kiroku-0.7.1.0/0013.sql view
@@ -0,0 +1,13 @@+-- Derive consumer-group topology from existing member checkpoints.+-- Stop all subscription workers before applying this migration.+-- An incomplete legacy row set derives an underestimate: startup refuses the+-- configured topology until the operator explicitly equalizes it with resize.+UPDATE kiroku.subscriptions AS s+SET consumer_group_size = derived.size+FROM (+ SELECT subscription_name, max(consumer_group_member) + 1 AS size+ FROM kiroku.subscriptions+ GROUP BY subscription_name+) AS derived+WHERE s.subscription_name = derived.subscription_name+ AND s.consumer_group_size <> derived.size;
+ test/fixtures/kiroku-0.7.1.0/0014.sql view
@@ -0,0 +1,11 @@+-- bind subscription checkpoints to their targets+++-- Stop subscription workers before applying this catalog change and CHECK scan.+-- Constant defaults preserve legacy rows without a heap rewrite.+ALTER TABLE kiroku.subscriptions+ ADD COLUMN target_kind TEXT NOT NULL DEFAULT 'unbound'+ CHECK (target_kind IN ('unbound', 'all', 'category')),+ ADD COLUMN target_category TEXT+ CHECK ((target_kind = 'category') = (target_category IS NOT NULL)),+ DROP COLUMN stream_name;
+ test/fixtures/kiroku-0.7.1.0/0015.sql view
@@ -0,0 +1,16 @@+-- Stable byte-ordered stream catalog browsing (MasterPlan 13 / plan 88).+-- The original unique-name constraint and category index remain unchanged.+-- This one browse index also serves literal prefixes and exact-category ranges;+-- plan 54's bounded global-event windows require no separate prefix index.+-- PostgreSQL applies this transactional index build once. It blocks writes for+-- the build duration; schedule the upgrade in a maintenance window on large+-- stores. New-stream inserts and non-HOT application-stream updates pay its+-- maintenance cost. The reserved global stream is outside this partial index.+CREATE INDEX ix_streams_browse_name+ ON kiroku.streams (stream_name COLLATE "C")+ WHERE stream_id <> 0;++COMMENT ON INDEX kiroku.ix_streams_browse_name IS+ 'Byte-ordered stream browsing and literal-prefix seeks; does not change unique-name or subscription event ordering.';+COMMENT ON SCHEMA kiroku IS+ 'Managed by pg-migrate component kiroku through 0015';
+ test/fixtures/kiroku-0.7.1.0/README.md view
@@ -0,0 +1,8 @@+# Released Kiroku 0.7.1.0 migration payloads++Frozen from the Hackage `kiroku-store-migrations-0.7.1.0.tar.gz` archive,+owned by mori://shinzui/kiroku/packages/kiroku-store-migrations. All fifteen+SQL files match the retained prefix at release commit+`22d103e4f424caab06c6a2b76ad92b408c262bc0`. Tests freeze their SHA-256s and+require the current composed plan to verify the installed checksums before+applying only 0016. Never regenerate these from current SQL.
+ test/fixtures/kiroku-0.7.1.0/manifest view
@@ -0,0 +1,15 @@+0001-kiroku-bootstrap.sql+0002-add-subscription-dead-letters.sql+0003-notify-trigger-append-guard.sql+0004-dead-letters-event-id-index.sql+0005-index-hygiene-and-streams-fillfactor.sql+0006-stream-name-length-check.sql+0007-stream-truncate-before.sql+0008-schema-management-comment.sql+0009.sql+0010.sql+0011.sql+0012.sql+0013.sql+0014.sql+0015.sql