From 94aadffb5e39a9aa90f3d71c2b936d70c5c6b8d1 Mon Sep 17 00:00:00 2001 From: Arek Swidrak Date: Tue, 28 Jul 2026 11:05:36 +0200 Subject: [PATCH 1/5] feat!: require pg_eventstore v3 (2.0.0) yes-core moves to pg_eventstore ~> 3.0, whose schema is incompatible with v1, so this is a major bump for all four gems. 2.0.0 rather than 1.5.0 is deliberate. Consumers declare these gems at `~> 1.3` from the git source and main is already at 1.4.0 -- so 1.5.0 would satisfy the existing constraint and could be pulled in by an unrelated `bundle update`, putting v3 code against a still-v1 store. 2.0.0 makes that structurally impossible; adoption now requires an explicit Gemfile edit, which is what it should require. The v3 code change itself is two OpenTelemetry span attributes. pg_eventstore migration 13 drops events.link_id in favour of the bigint link_global_position, so Event#link_id raises NoMethodError: * Commands::Stateless::Handler * CommandHandling::EventPublisher Dashboards or trace queries keyed on `event.link_id` need updating to `event.link_global_position`. yes-auth pinned `yes-core ~> 1.0`, which would have made the monorepo unresolvable the moment yes-core went to 2.0.0 -- bumped to ~> 2.0. Verified by resolving the full set: pg_eventstore 3.0.0, all four gems at 2.0.0, no conflicts. NOT verified locally: rubocop and rspec both need Ruby 3.4.5 per the Gemfile and the dev machine had 3.4.1. CI runs both on 3.4.5 with Postgres 16 + Redis, and its "Set up EventStore DB" step exercises pg_eventstore 3.0's migrations for the first time in this repo -- watch that step in particular. Co-Authored-By: Claude Opus 5 --- CHANGELOG.md | 28 +++++++++++++++++++ Gemfile.lock | 28 +++++++++---------- lib/yes/version.rb | 2 +- yes-auth/lib/yes/auth/version.rb | 2 +- yes-auth/yes-auth.gemspec | 2 +- .../lib/yes/command/api/version.rb | 2 +- .../core/command_handling/event_publisher.rb | 2 +- .../yes/core/commands/stateless/handler.rb | 2 +- yes-core/lib/yes/core/version.rb | 2 +- .../core/commands/stateless/handler_spec.rb | 2 +- yes-core/yes-core.gemspec | 2 +- yes-read-api/lib/yes/read/api/version.rb | 2 +- 12 files changed, 52 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 84d4387e..b3046e21 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,34 @@ All notable changes to this project will be documented in this file. +## [2.0.0] - 2026-07-28 + +Major bump because `yes-core` now requires `pg_eventstore` v3, whose schema is +incompatible with v1. Released as 2.0.0 rather than 1.5.0 deliberately: consumers +constrain these gems at `~> 1.3`, which 1.5.0 would satisfy, so a minor bump could +be pulled in by an unrelated `bundle update` and put v3 code against a v1 store. +2.0.0 makes that impossible. + +⚠️ **Do not adopt until your event store has been migrated to v3.** Migrating is a +one-way, downtime-requiring operation — see the `pg_eventstore` upgrade notes. + +### yes-core + +#### Changed +- **Breaking change**: `pg_eventstore` dependency `~> 1.0` → `~> 3.0`. +- **Breaking change**: OpenTelemetry span attribute `event.link_id` is now + `event.link_global_position`, in `Commands::Stateless::Handler` and + `CommandHandling::EventPublisher`. `pg_eventstore` v3 drops `events.link_id` + (migration 13) in favour of the bigint `link_global_position`, so `Event#link_id` + raises `NoMethodError`. Mirrors the same change in `yousty-eventsourcing` 16.0.0. + **Update any dashboards or trace queries keyed on `event.link_id`.** + +### yes-auth + +#### Changed +- **Breaking change**: `yes-core` dependency `~> 1.0` → `~> 2.0`, required to stay + resolvable alongside yes-core 2.0.0. + ## [1.4.0] - 2026-06-24 ### yes-command-api diff --git a/Gemfile.lock b/Gemfile.lock index 18c85dab..7d416d9e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,14 +1,14 @@ PATH remote: yes-auth specs: - yes-auth (1.4.0) + yes-auth (2.0.0) rails (>= 7.1) - yes-core (~> 1.0) + yes-core (~> 2.0) PATH remote: yes-command-api specs: - yes-command-api (1.4.0) + yes-command-api (2.0.0) message_bus (~> 4.0) rails (>= 7.1) yes-core @@ -16,7 +16,7 @@ PATH PATH remote: yes-core specs: - yes-core (1.4.0) + yes-core (2.0.0) cerbos (~> 0.8) dry-inflector (~> 1.2) dry-schema (~> 1.13) @@ -25,14 +25,14 @@ PATH has_scope (~> 0.8) jsonapi-serializer (~> 2.2) opentelemetry-sdk (~> 1.0) - pg_eventstore (~> 1.0) + pg_eventstore (~> 3.0) rails (>= 7.1) zeitwerk (~> 2.6) PATH remote: yes-read-api specs: - yes-read-api (1.4.0) + yes-read-api (2.0.0) api-pagination (~> 5.0) pagy (~> 6.0) rails (>= 7.1) @@ -299,9 +299,9 @@ GEM pg (1.6.3-x86_64-darwin) pg (1.6.3-x86_64-linux) pg (1.6.3-x86_64-linux-musl) - pg_eventstore (1.13.4) - connection_pool (~> 2.4) - pg (~> 1.5) + pg_eventstore (3.0.0) + connection_pool (>= 2.5.4, < 4) + pg (~> 1) sinatra (>= 3, < 5) pp (0.6.3) prettyprint @@ -531,7 +531,7 @@ CHECKSUMS pg (1.6.3-x86_64-darwin) sha256=ee2e04a17c0627225054ffeb43e31a95be9d7e93abda2737ea3ce4a62f2729d6 pg (1.6.3-x86_64-linux) sha256=5d9e188c8f7a0295d162b7b88a768d8452a899977d44f3274d1946d67920ae8d pg (1.6.3-x86_64-linux-musl) sha256=9c9c90d98c72f78eb04c0f55e9618fe55d1512128e411035fe229ff427864009 - pg_eventstore (1.13.4) sha256=45ff50db33fc86b1dc72e515e30c33bda0334fc255362a81600c93a6799ee413 + pg_eventstore (3.0.0) sha256=7cbed46560952efd36b8f02510d99a55604a3771a4698a697dcdab9930bc08ea pp (0.6.3) sha256=2951d514450b93ccfeb1df7d021cae0da16e0a7f95ee1e2273719669d0ab9df6 prettyprint (0.2.0) sha256=2bc9e15581a94742064a3cc8b0fb9d45aae3d03a1baa6ef80922627a0766f193 prism (1.9.0) sha256=7b530c6a9f92c24300014919c9dcbc055bf4cdf51ec30aed099b06cd6674ef85 @@ -571,10 +571,10 @@ CHECKSUMS useragent (0.16.11) sha256=700e6413ad4bb954bb63547fa098dddf7b0ebe75b40cc6f93b8d54255b173844 websocket-driver (0.8.0) sha256=ed0dba4b943c22f17f9a734817e808bc84cdce6a7e22045f5315aa57676d4962 websocket-extensions (0.1.5) sha256=1c6ba63092cda343eb53fc657110c71c754c56484aad42578495227d717a8241 - yes-auth (1.4.0) - yes-command-api (1.4.0) - yes-core (1.4.0) - yes-read-api (1.4.0) + yes-auth (2.0.0) + yes-command-api (2.0.0) + yes-core (2.0.0) + yes-read-api (2.0.0) zeitwerk (2.7.5) sha256=d8da92128c09ea6ec62c949011b00ed4a20242b255293dd66bf41545398f73dd RUBY VERSION diff --git a/lib/yes/version.rb b/lib/yes/version.rb index c6d63678..694ce29a 100644 --- a/lib/yes/version.rb +++ b/lib/yes/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module Yes - VERSION = '1.4.0' + VERSION = '2.0.0' end diff --git a/yes-auth/lib/yes/auth/version.rb b/yes-auth/lib/yes/auth/version.rb index ab886de8..2ac1b9f9 100644 --- a/yes-auth/lib/yes/auth/version.rb +++ b/yes-auth/lib/yes/auth/version.rb @@ -2,6 +2,6 @@ module Yes module Auth - VERSION = '1.4.0' + VERSION = '2.0.0' end end diff --git a/yes-auth/yes-auth.gemspec b/yes-auth/yes-auth.gemspec index 87f0aea1..26bc10cf 100644 --- a/yes-auth/yes-auth.gemspec +++ b/yes-auth/yes-auth.gemspec @@ -23,5 +23,5 @@ Gem::Specification.new do |spec| spec.require_paths = ['lib'] spec.add_dependency 'rails', '>= 7.1' - spec.add_dependency 'yes-core', '~> 1.0' + spec.add_dependency 'yes-core', '~> 2.0' end diff --git a/yes-command-api/lib/yes/command/api/version.rb b/yes-command-api/lib/yes/command/api/version.rb index 79a88f2c..5c219da0 100644 --- a/yes-command-api/lib/yes/command/api/version.rb +++ b/yes-command-api/lib/yes/command/api/version.rb @@ -3,7 +3,7 @@ module Yes module Command module Api - VERSION = '1.4.0' + VERSION = '2.0.0' end end end diff --git a/yes-core/lib/yes/core/command_handling/event_publisher.rb b/yes-core/lib/yes/core/command_handling/event_publisher.rb index 11db9b4d..eaa55af3 100644 --- a/yes-core/lib/yes/core/command_handling/event_publisher.rb +++ b/yes-core/lib/yes/core/command_handling/event_publisher.rb @@ -175,7 +175,7 @@ def otl_record_response(result) timestamp: result.created_at, attributes: { 'event.type' => result.type, - 'event.link_id' => result.link_id || '', + 'event.link_global_position' => result.link_global_position || '', 'global_position' => result.global_position, 'stream' => result.stream.to_json, 'stream.revision' => result.stream_revision, diff --git a/yes-core/lib/yes/core/commands/stateless/handler.rb b/yes-core/lib/yes/core/commands/stateless/handler.rb index e5ea8812..4c522180 100644 --- a/yes-core/lib/yes/core/commands/stateless/handler.rb +++ b/yes-core/lib/yes/core/commands/stateless/handler.rb @@ -277,7 +277,7 @@ def otl_record_response(result) timestamp: result.created_at, attributes: { 'event.type' => result.type, - 'event.link_id' => result.link_id || '', + 'event.link_global_position' => result.link_global_position || '', 'global_position' => result.global_position, 'stream' => result.stream.to_json, 'stream.revision' => result.stream_revision, diff --git a/yes-core/lib/yes/core/version.rb b/yes-core/lib/yes/core/version.rb index 9f443af0..238d5349 100644 --- a/yes-core/lib/yes/core/version.rb +++ b/yes-core/lib/yes/core/version.rb @@ -2,6 +2,6 @@ module Yes module Core - VERSION = '1.4.0' + VERSION = '2.0.0' end end diff --git a/yes-core/spec/lib/yes/core/commands/stateless/handler_spec.rb b/yes-core/spec/lib/yes/core/commands/stateless/handler_spec.rb index a45d5dcf..8d5c6e3e 100644 --- a/yes-core/spec/lib/yes/core/commands/stateless/handler_spec.rb +++ b/yes-core/spec/lib/yes/core/commands/stateless/handler_spec.rb @@ -214,7 +214,7 @@ expect(published_event.attributes).to eq( 'event.type' => event.type, - 'event.link_id' => '', + 'event.link_global_position' => '', 'global_position' => event.global_position, 'stream' => event.stream.to_json, 'stream.revision' => event.stream_revision, diff --git a/yes-core/yes-core.gemspec b/yes-core/yes-core.gemspec index 824a3504..c62c7fc6 100644 --- a/yes-core/yes-core.gemspec +++ b/yes-core/yes-core.gemspec @@ -30,7 +30,7 @@ Gem::Specification.new do |spec| spec.add_dependency 'has_scope', '~> 0.8' spec.add_dependency 'jsonapi-serializer', '~> 2.2' spec.add_dependency 'opentelemetry-sdk', '~> 1.0' - spec.add_dependency 'pg_eventstore', '~> 1.0' + spec.add_dependency 'pg_eventstore', '~> 3.0' spec.add_dependency 'rails', '>= 7.1' spec.add_dependency 'zeitwerk', '~> 2.6' end diff --git a/yes-read-api/lib/yes/read/api/version.rb b/yes-read-api/lib/yes/read/api/version.rb index fa3b126e..258ab49c 100644 --- a/yes-read-api/lib/yes/read/api/version.rb +++ b/yes-read-api/lib/yes/read/api/version.rb @@ -3,7 +3,7 @@ module Yes module Read module Api - VERSION = '1.4.0' + VERSION = '2.0.0' end end end From 05dbd387133e70c618a3d2bfefad7dcc18e80932 Mon Sep 17 00:00:00 2001 From: Arek Swidrak Date: Tue, 28 Jul 2026 11:10:41 +0200 Subject: [PATCH 2/5] fix: update the per-gem lockfiles for 2.0.0 CI runs each gem's suite from its own directory with bundler in frozen mode, so the four per-gem Gemfile.lock files must record the new versions too -- only the root lockfile had been regenerated, and the jobs failed with "The gemspecs for path gems changed, but the lockfile can't be updated because frozen mode is set". All four now resolve yes-core 2.0.0 and pg_eventstore 3.0.0. Co-Authored-By: Claude Opus 5 --- yes-auth/Gemfile.lock | 20 ++++++++++---------- yes-command-api/Gemfile.lock | 18 +++++++++--------- yes-core/Gemfile.lock | 22 +++++++++++----------- yes-read-api/Gemfile.lock | 18 +++++++++--------- 4 files changed, 39 insertions(+), 39 deletions(-) diff --git a/yes-auth/Gemfile.lock b/yes-auth/Gemfile.lock index 7a0c5ec2..65280516 100644 --- a/yes-auth/Gemfile.lock +++ b/yes-auth/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: ../yes-core specs: - yes-core (1.4.0) + yes-core (2.0.0) cerbos (~> 0.8) dry-inflector (~> 1.2) dry-schema (~> 1.13) @@ -10,16 +10,16 @@ PATH has_scope (~> 0.8) jsonapi-serializer (~> 2.2) opentelemetry-sdk (~> 1.0) - pg_eventstore (~> 1.0) + pg_eventstore (~> 3.0) rails (>= 7.1) zeitwerk (~> 2.6) PATH remote: . specs: - yes-auth (1.4.0) + yes-auth (2.0.0) rails (>= 7.1) - yes-core (~> 1.0) + yes-core (~> 2.0) GEM remote: https://rubygems.org/ @@ -273,9 +273,9 @@ GEM pg (1.6.3-x86_64-darwin) pg (1.6.3-x86_64-linux) pg (1.6.3-x86_64-linux-musl) - pg_eventstore (1.13.4) - connection_pool (~> 2.4) - pg (~> 1.5) + pg_eventstore (3.0.0) + connection_pool (>= 2.5.4, < 4) + pg (~> 1) sinatra (>= 3, < 5) pp (0.6.3) prettyprint @@ -497,7 +497,7 @@ CHECKSUMS pg (1.6.3-x86_64-darwin) sha256=ee2e04a17c0627225054ffeb43e31a95be9d7e93abda2737ea3ce4a62f2729d6 pg (1.6.3-x86_64-linux) sha256=5d9e188c8f7a0295d162b7b88a768d8452a899977d44f3274d1946d67920ae8d pg (1.6.3-x86_64-linux-musl) sha256=9c9c90d98c72f78eb04c0f55e9618fe55d1512128e411035fe229ff427864009 - pg_eventstore (1.13.4) sha256=45ff50db33fc86b1dc72e515e30c33bda0334fc255362a81600c93a6799ee413 + pg_eventstore (3.0.0) sha256=7cbed46560952efd36b8f02510d99a55604a3771a4698a697dcdab9930bc08ea pp (0.6.3) sha256=2951d514450b93ccfeb1df7d021cae0da16e0a7f95ee1e2273719669d0ab9df6 prettyprint (0.2.0) sha256=2bc9e15581a94742064a3cc8b0fb9d45aae3d03a1baa6ef80922627a0766f193 prism (1.9.0) sha256=7b530c6a9f92c24300014919c9dcbc055bf4cdf51ec30aed099b06cd6674ef85 @@ -542,8 +542,8 @@ CHECKSUMS useragent (0.16.11) sha256=700e6413ad4bb954bb63547fa098dddf7b0ebe75b40cc6f93b8d54255b173844 websocket-driver (0.8.0) sha256=ed0dba4b943c22f17f9a734817e808bc84cdce6a7e22045f5315aa57676d4962 websocket-extensions (0.1.5) sha256=1c6ba63092cda343eb53fc657110c71c754c56484aad42578495227d717a8241 - yes-auth (1.4.0) - yes-core (1.4.0) + yes-auth (2.0.0) + yes-core (2.0.0) zeitwerk (2.7.5) sha256=d8da92128c09ea6ec62c949011b00ed4a20242b255293dd66bf41545398f73dd RUBY VERSION diff --git a/yes-command-api/Gemfile.lock b/yes-command-api/Gemfile.lock index 08655b8b..c9357eaa 100644 --- a/yes-command-api/Gemfile.lock +++ b/yes-command-api/Gemfile.lock @@ -8,7 +8,7 @@ GIT PATH remote: ../yes-core specs: - yes-core (1.4.0) + yes-core (2.0.0) cerbos (~> 0.8) dry-inflector (~> 1.2) dry-schema (~> 1.13) @@ -17,14 +17,14 @@ PATH has_scope (~> 0.8) jsonapi-serializer (~> 2.2) opentelemetry-sdk (~> 1.0) - pg_eventstore (~> 1.0) + pg_eventstore (~> 3.0) rails (>= 7.1) zeitwerk (~> 2.6) PATH remote: . specs: - yes-command-api (1.4.0) + yes-command-api (2.0.0) message_bus (~> 4.0) rails (>= 7.1) yes-core @@ -297,9 +297,9 @@ GEM pg (1.6.3-x86_64-darwin) pg (1.6.3-x86_64-linux) pg (1.6.3-x86_64-linux-musl) - pg_eventstore (1.13.4) - connection_pool (~> 2.4) - pg (~> 1.5) + pg_eventstore (3.0.0) + connection_pool (>= 2.5.4, < 4) + pg (~> 1) sinatra (>= 3, < 5) pp (0.6.3) prettyprint @@ -548,7 +548,7 @@ CHECKSUMS pg (1.6.3-x86_64-darwin) sha256=ee2e04a17c0627225054ffeb43e31a95be9d7e93abda2737ea3ce4a62f2729d6 pg (1.6.3-x86_64-linux) sha256=5d9e188c8f7a0295d162b7b88a768d8452a899977d44f3274d1946d67920ae8d pg (1.6.3-x86_64-linux-musl) sha256=9c9c90d98c72f78eb04c0f55e9618fe55d1512128e411035fe229ff427864009 - pg_eventstore (1.13.4) sha256=45ff50db33fc86b1dc72e515e30c33bda0334fc255362a81600c93a6799ee413 + pg_eventstore (3.0.0) sha256=7cbed46560952efd36b8f02510d99a55604a3771a4698a697dcdab9930bc08ea pp (0.6.3) sha256=2951d514450b93ccfeb1df7d021cae0da16e0a7f95ee1e2273719669d0ab9df6 prettyprint (0.2.0) sha256=2bc9e15581a94742064a3cc8b0fb9d45aae3d03a1baa6ef80922627a0766f193 prism (1.9.0) sha256=7b530c6a9f92c24300014919c9dcbc055bf4cdf51ec30aed099b06cd6674ef85 @@ -588,8 +588,8 @@ CHECKSUMS useragent (0.16.11) sha256=700e6413ad4bb954bb63547fa098dddf7b0ebe75b40cc6f93b8d54255b173844 websocket-driver (0.8.0) sha256=ed0dba4b943c22f17f9a734817e808bc84cdce6a7e22045f5315aa57676d4962 websocket-extensions (0.1.5) sha256=1c6ba63092cda343eb53fc657110c71c754c56484aad42578495227d717a8241 - yes-command-api (1.4.0) - yes-core (1.4.0) + yes-command-api (2.0.0) + yes-core (2.0.0) zeitwerk (2.7.5) sha256=d8da92128c09ea6ec62c949011b00ed4a20242b255293dd66bf41545398f73dd BUNDLED WITH diff --git a/yes-core/Gemfile.lock b/yes-core/Gemfile.lock index 58cb76dd..29b5ef74 100644 --- a/yes-core/Gemfile.lock +++ b/yes-core/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: ../yes-command-api specs: - yes-command-api (1.4.0) + yes-command-api (2.0.0) message_bus (~> 4.0) rails (>= 7.1) yes-core @@ -9,7 +9,7 @@ PATH PATH remote: ../yes-read-api specs: - yes-read-api (1.4.0) + yes-read-api (2.0.0) api-pagination (~> 5.0) pagy (~> 6.0) rails (>= 7.1) @@ -19,7 +19,7 @@ PATH PATH remote: . specs: - yes-core (1.4.0) + yes-core (2.0.0) cerbos (~> 0.8) dry-inflector (~> 1.2) dry-schema (~> 1.13) @@ -28,7 +28,7 @@ PATH has_scope (~> 0.8) jsonapi-serializer (~> 2.2) opentelemetry-sdk (~> 1.0) - pg_eventstore (~> 1.0) + pg_eventstore (~> 3.0) rails (>= 7.1) zeitwerk (~> 2.6) @@ -313,9 +313,9 @@ GEM pg (1.6.3-x86_64-darwin) pg (1.6.3-x86_64-linux) pg (1.6.3-x86_64-linux-musl) - pg_eventstore (1.13.4) - connection_pool (~> 2.4) - pg (~> 1.5) + pg_eventstore (3.0.0) + connection_pool (>= 2.5.4, < 4) + pg (~> 1) sinatra (>= 3, < 5) pp (0.6.3) prettyprint @@ -583,7 +583,7 @@ CHECKSUMS pg (1.6.3-x86_64-darwin) sha256=ee2e04a17c0627225054ffeb43e31a95be9d7e93abda2737ea3ce4a62f2729d6 pg (1.6.3-x86_64-linux) sha256=5d9e188c8f7a0295d162b7b88a768d8452a899977d44f3274d1946d67920ae8d pg (1.6.3-x86_64-linux-musl) sha256=9c9c90d98c72f78eb04c0f55e9618fe55d1512128e411035fe229ff427864009 - pg_eventstore (1.13.4) sha256=45ff50db33fc86b1dc72e515e30c33bda0334fc255362a81600c93a6799ee413 + pg_eventstore (3.0.0) sha256=7cbed46560952efd36b8f02510d99a55604a3771a4698a697dcdab9930bc08ea pp (0.6.3) sha256=2951d514450b93ccfeb1df7d021cae0da16e0a7f95ee1e2273719669d0ab9df6 prettyprint (0.2.0) sha256=2bc9e15581a94742064a3cc8b0fb9d45aae3d03a1baa6ef80922627a0766f193 prism (1.9.0) sha256=7b530c6a9f92c24300014919c9dcbc055bf4cdf51ec30aed099b06cd6674ef85 @@ -631,9 +631,9 @@ CHECKSUMS webmock (3.26.2) sha256=774556f2ea6371846cca68c01769b2eac0d134492d21f6d0ab5dd643965a4c90 websocket-driver (0.8.0) sha256=ed0dba4b943c22f17f9a734817e808bc84cdce6a7e22045f5315aa57676d4962 websocket-extensions (0.1.5) sha256=1c6ba63092cda343eb53fc657110c71c754c56484aad42578495227d717a8241 - yes-command-api (1.4.0) - yes-core (1.4.0) - yes-read-api (1.4.0) + yes-command-api (2.0.0) + yes-core (2.0.0) + yes-read-api (2.0.0) zeitwerk (2.7.5) sha256=d8da92128c09ea6ec62c949011b00ed4a20242b255293dd66bf41545398f73dd RUBY VERSION diff --git a/yes-read-api/Gemfile.lock b/yes-read-api/Gemfile.lock index ff2a06dd..6ee69342 100644 --- a/yes-read-api/Gemfile.lock +++ b/yes-read-api/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: ../yes-core specs: - yes-core (1.4.0) + yes-core (2.0.0) cerbos (~> 0.8) dry-inflector (~> 1.2) dry-schema (~> 1.13) @@ -10,14 +10,14 @@ PATH has_scope (~> 0.8) jsonapi-serializer (~> 2.2) opentelemetry-sdk (~> 1.0) - pg_eventstore (~> 1.0) + pg_eventstore (~> 3.0) rails (>= 7.1) zeitwerk (~> 2.6) PATH remote: . specs: - yes-read-api (1.4.0) + yes-read-api (2.0.0) api-pagination (~> 5.0) pagy (~> 6.0) rails (>= 7.1) @@ -299,9 +299,9 @@ GEM pg (1.6.3-x86_64-darwin) pg (1.6.3-x86_64-linux) pg (1.6.3-x86_64-linux-musl) - pg_eventstore (1.13.4) - connection_pool (~> 2.4) - pg (~> 1.5) + pg_eventstore (3.0.0) + connection_pool (>= 2.5.4, < 4) + pg (~> 1) sinatra (>= 3, < 5) pp (0.6.3) prettyprint @@ -545,7 +545,7 @@ CHECKSUMS pg (1.6.3-x86_64-darwin) sha256=ee2e04a17c0627225054ffeb43e31a95be9d7e93abda2737ea3ce4a62f2729d6 pg (1.6.3-x86_64-linux) sha256=5d9e188c8f7a0295d162b7b88a768d8452a899977d44f3274d1946d67920ae8d pg (1.6.3-x86_64-linux-musl) sha256=9c9c90d98c72f78eb04c0f55e9618fe55d1512128e411035fe229ff427864009 - pg_eventstore (1.13.4) sha256=45ff50db33fc86b1dc72e515e30c33bda0334fc255362a81600c93a6799ee413 + pg_eventstore (3.0.0) sha256=7cbed46560952efd36b8f02510d99a55604a3771a4698a697dcdab9930bc08ea pp (0.6.3) sha256=2951d514450b93ccfeb1df7d021cae0da16e0a7f95ee1e2273719669d0ab9df6 prettyprint (0.2.0) sha256=2bc9e15581a94742064a3cc8b0fb9d45aae3d03a1baa6ef80922627a0766f193 prism (1.9.0) sha256=7b530c6a9f92c24300014919c9dcbc055bf4cdf51ec30aed099b06cd6674ef85 @@ -583,8 +583,8 @@ CHECKSUMS useragent (0.16.11) sha256=700e6413ad4bb954bb63547fa098dddf7b0ebe75b40cc6f93b8d54255b173844 websocket-driver (0.8.0) sha256=ed0dba4b943c22f17f9a734817e808bc84cdce6a7e22045f5315aa57676d4962 websocket-extensions (0.1.5) sha256=1c6ba63092cda343eb53fc657110c71c754c56484aad42578495227d717a8241 - yes-core (1.4.0) - yes-read-api (1.4.0) + yes-core (2.0.0) + yes-read-api (2.0.0) zeitwerk (2.7.5) sha256=d8da92128c09ea6ec62c949011b00ed4a20242b255293dd66bf41545398f73dd BUNDLED WITH From c23e11110adc6c152df846900b6562da9f3e1eb7 Mon Sep 17 00:00:00 2001 From: Arek Swidrak Date: Tue, 28 Jul 2026 11:13:32 +0200 Subject: [PATCH 3/5] ci: set PG_EVENTSTORE_URI on every rake step pg_eventstore 3.0's setup.rake resolves the URI through ENV.fetch at LOAD time, inside a top-level `configure` block -- not lazily inside a task. Since the Rakefile loads that file unconditionally, EVERY rake invocation now needs the variable, not just the pg_eventstore ones. So `db:create db:migrate` and `app:db:migrate:message_bus` aborted with `KeyError: key not found: "PG_EVENTSTORE_URI"` even though neither touches the event store. yes-auth passed only because it skips both steps. Worth knowing beyond this repo: any app whose Rakefile loads setup.rake inherits the same requirement, so deploy-time `rake db:migrate` needs PG_EVENTSTORE_URI present in the environment after upgrading to v3. Co-Authored-By: Claude Opus 5 --- .github/workflows/main.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 64db0c2d..1cd574bd 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -54,12 +54,18 @@ jobs: RAILS_ENV: test PG_EVENTSTORE_URI: postgresql://postgres:postgres@localhost:5532/eventstore_test + # PG_EVENTSTORE_URI is required on every rake invocation, not just the + # eventstore ones: pg_eventstore 3.0's setup.rake resolves the URI via + # ENV.fetch at LOAD time (inside a top-level `configure` block), and the + # Rakefile loads that file unconditionally. Without it, unrelated tasks such + # as db:create abort with `KeyError: key not found: "PG_EVENTSTORE_URI"`. - name: Set up test database if: matrix.gem != 'yes-auth' run: bundle exec rake db:create db:migrate working-directory: ${{ matrix.gem }} env: RAILS_ENV: test + PG_EVENTSTORE_URI: postgresql://postgres:postgres@localhost:5532/eventstore_test - name: Set up message bus database if: matrix.gem == 'yes-command-api' @@ -67,6 +73,7 @@ jobs: working-directory: ${{ matrix.gem }} env: RAILS_ENV: test + PG_EVENTSTORE_URI: postgresql://postgres:postgres@localhost:5532/eventstore_test - name: Run specs run: bundle exec rspec From 7d9980056c3c4ec9ec7b2d78742012aa4807ed72 Mon Sep 17 00:00:00 2001 From: Arek Swidrak Date: Tue, 28 Jul 2026 11:21:32 +0200 Subject: [PATCH 4/5] fix: pass the required verdict: to WrongExpectedRevisionError pg_eventstore 3.0 changed the signature to `initialize(revision:, expected_revision:, stream:, verdict:)`. `verdict` is a Symbol selecting the user-facing message, and it is mandatory -- so all 7 construction sites raised `ArgumentError: missing keyword: :verdict` instead of the error they were meant to produce, failing 7 specs. Verdicts follow the gem's own rules in Commands::RevisionCheck::StreamRevisionComparison: * two differing Integer revisions -> :unmatched_stream_revision * Integer revision vs :no_stream -> :expected_not_to_have_stream Applied per site rather than blanket-set, so the rendered message matches the condition actually detected: lib event_publisher.rb, stateless/handler.rb -> :unmatched_stream_revision (both only reach the raise when held and stored revisions differ) spec command_executor_spec (revision 1 vs 2) -> :unmatched_stream_revision command_group_executor_spec (n vs :no_stream) -> :expected_not_to_have_stream This is a second v3 API break beyond the link_global_position rename, and it is not in the migration notes -- worth checking other consumers that construct this error directly. Co-Authored-By: Claude Opus 5 --- .../lib/yes/core/command_handling/event_publisher.rb | 6 +++++- yes-core/lib/yes/core/commands/stateless/handler.rb | 7 ++++++- .../core/command_handling/command_executor_spec.rb | 12 ++++++++++-- .../command_handling/command_group_executor_spec.rb | 9 ++++++--- 4 files changed, 27 insertions(+), 7 deletions(-) diff --git a/yes-core/lib/yes/core/command_handling/event_publisher.rb b/yes-core/lib/yes/core/command_handling/event_publisher.rb index eaa55af3..d292d64f 100644 --- a/yes-core/lib/yes/core/command_handling/event_publisher.rb +++ b/yes-core/lib/yes/core/command_handling/event_publisher.rb @@ -111,10 +111,14 @@ def verify_external_revisions! next if normalized_revision == expected_revision + # pg_eventstore 3.0 requires `verdict:`, which selects the error's + # message. This branch is only reached when the revision we hold + # differs from the store's, which is exactly :unmatched_stream_revision. raise PgEventstore::WrongExpectedRevisionError.new( revision: aggregate_revision, expected_revision:, - stream: + stream:, + verdict: :unmatched_stream_revision ) end end diff --git a/yes-core/lib/yes/core/commands/stateless/handler.rb b/yes-core/lib/yes/core/commands/stateless/handler.rb index 4c522180..8884c4b5 100644 --- a/yes-core/lib/yes/core/commands/stateless/handler.rb +++ b/yes-core/lib/yes/core/commands/stateless/handler.rb @@ -198,7 +198,12 @@ def verify_revisions! # @param expected_revision [Integer] # @param stream [PgEventstore::Stream] def revision_error!(revision, expected_revision, stream) - PgEventstore::WrongExpectedRevisionError.new(revision:, expected_revision:, stream:).tap do |error| + # pg_eventstore 3.0 requires `verdict:`, which selects the error's + # message. verify_revisions! only calls this when the two revisions + # differ, which is exactly :unmatched_stream_revision. + PgEventstore::WrongExpectedRevisionError.new( + revision:, expected_revision:, stream:, verdict: :unmatched_stream_revision + ).tap do |error| self.class.current_span&.status = ::OpenTelemetry::Trace::Status.error('Wrong expected revision') self.class.current_span&.add_attributes( { diff --git a/yes-core/spec/lib/yes/core/command_handling/command_executor_spec.rb b/yes-core/spec/lib/yes/core/command_handling/command_executor_spec.rb index 472291fe..10472501 100644 --- a/yes-core/spec/lib/yes/core/command_handling/command_executor_spec.rb +++ b/yes-core/spec/lib/yes/core/command_handling/command_executor_spec.rb @@ -88,7 +88,11 @@ end context 'when event store has revision conflict' do - let(:revision_error) { PgEventstore::WrongExpectedRevisionError.new(revision: 1, expected_revision: 2, stream: {}) } + let(:revision_error) do + PgEventstore::WrongExpectedRevisionError.new( + revision: 1, expected_revision: 2, stream: {}, verdict: :unmatched_stream_revision + ) + end before do call_count = 0 @@ -117,7 +121,11 @@ end context 'when event store fails persistently' do - let(:revision_error) { PgEventstore::WrongExpectedRevisionError.new(revision: 1, expected_revision: 2, stream: {}) } + let(:revision_error) do + PgEventstore::WrongExpectedRevisionError.new( + revision: 1, expected_revision: 2, stream: {}, verdict: :unmatched_stream_revision + ) + end it 'raises error after MAX_RETRIES' do call_count = 0 diff --git a/yes-core/spec/lib/yes/core/command_handling/command_group_executor_spec.rb b/yes-core/spec/lib/yes/core/command_handling/command_group_executor_spec.rb index 18d361f5..2aafb3ac 100644 --- a/yes-core/spec/lib/yes/core/command_handling/command_group_executor_spec.rb +++ b/yes-core/spec/lib/yes/core/command_handling/command_group_executor_spec.rb @@ -66,7 +66,8 @@ expected_revision: :no_stream, stream: PgEventstore::Stream.new( context: 'Test', stream_name: 'PersonalInfo', stream_id: aggregate_id - ) + ), + verdict: :expected_not_to_have_stream ) ) end @@ -93,7 +94,8 @@ expected_revision: :no_stream, stream: PgEventstore::Stream.new( context: 'Test', stream_name: 'PersonalInfo', stream_id: aggregate_id - ) + ), + verdict: :expected_not_to_have_stream ) ) @@ -131,7 +133,8 @@ expected_revision: :no_stream, stream: PgEventstore::Stream.new( context: 'Test', stream_name: 'PersonalInfo', stream_id: aggregate_id - ) + ), + verdict: :expected_not_to_have_stream ) ) end From ce5e09a72b27276de8d8fe3fcae8578f13ec9673 Mon Sep 17 00:00:00 2001 From: Arek Swidrak Date: Tue, 28 Jul 2026 12:32:56 +0200 Subject: [PATCH 5/5] test(yes-core): cover the encryptor middleware end-to-end encryptor_spec.rb unit-tests #serialize/#deserialize by calling them directly, which cannot catch changes in WHEN pg_eventstore invokes them. That is exactly the gap v3 fell through: 3.0 began running #deserialize on append_to_stream as well as on reads, and nothing in this repo could see it, because nothing registers an encryptor in config.middlewares. The host applications gate their registration on `!Rails.env.test?`, so their suites cannot see it either -- yousty-eventsourcing was the only repo whose CI caught the change, and only because it registers a dummy encryptor in its own spec setup. Registers one here, mutating config.middlewares the same way the host apps do (appended last so :with_indifferent_access stays first) and removing it afterwards, then covers the three behaviours worth pinning: * the protected attribute is encrypted AT REST -- asserted by reading with the encryptor bypassed, which is how EncryptorAdapter-style read paths see events * reads decrypt * append_to_stream returns an already-decrypted event, the v3 behaviour change itself, so a future change to it fails here rather than silently in a consumer Co-Authored-By: Claude Opus 5 --- .../middlewares/encryptor_integration_spec.rb | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 yes-core/spec/lib/yes/core/middlewares/encryptor_integration_spec.rb diff --git a/yes-core/spec/lib/yes/core/middlewares/encryptor_integration_spec.rb b/yes-core/spec/lib/yes/core/middlewares/encryptor_integration_spec.rb new file mode 100644 index 00000000..e6ad0a70 --- /dev/null +++ b/yes-core/spec/lib/yes/core/middlewares/encryptor_integration_spec.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +# End-to-end coverage of the encryptor middleware as pg_eventstore actually applies it. +# +# encryptor_spec.rb unit-tests #serialize/#deserialize by calling them directly, which +# cannot catch changes in WHEN pg_eventstore invokes them. pg_eventstore 3.0 started +# running #deserialize on append_to_stream as well as on reads, and no spec in this repo +# could see that: nothing here registers an encryptor in config.middlewares, and the host +# applications gate their registration on `!Rails.env.test?`, so their suites cannot see +# it either. +RSpec.describe 'Encryptor middleware integration' do + let(:key_repository) { DummyRepository.new } + let(:user_id) { SecureRandom.uuid } + let(:stream) do + PgEventstore::Stream.new(context: 'EncryptionCtx', stream_name: 'MyStream', stream_id: SecureRandom.uuid) + end + let(:data) do + { 'user_id' => user_id, 'name' => 'Anakin Skywalker', 'secret_name' => 'Darth Vader' } + end + let(:append) { PgEventstore.client.append_to_stream(stream, EncryptedEvent.new(data: data.dup)) } + + around do |example| + # Assigned last so :with_indifferent_access stays first, matching how the host + # applications register it. + PgEventstore.config.middlewares[:encryptor] = Yes::Core::Middlewares::Encryptor.new(key_repository) + example.run + ensure + PgEventstore.config.middlewares.delete(:encryptor) + end + + it 'stores the protected attribute encrypted' do + append + at_rest = PgEventstore.client.read(stream, middlewares: Yes::Core::Middlewares.without(:encryptor)).last + + aggregate_failures do + expect(at_rest.data['secret_name']).to eq('es_encrypted') + expect(at_rest.data['es_encrypted']).to eq(DummyRepository.encrypt(data.slice('secret_name').to_json)) + expect(at_rest.data['name']).to eq('Anakin Skywalker') + end + end + + it 'decrypts on read' do + append + + expect(PgEventstore.client.read(stream).last.data['secret_name']).to eq('Darth Vader') + end + + # pg_eventstore 3.0 runs #deserialize on append too, so the returned event comes back + # already decrypted. Pinned here so a future change to that behaviour fails in this + # spec rather than silently in a consumer. + it 'returns a decrypted event from append_to_stream' do + expect(append.data['secret_name']).to eq('Darth Vader') + end +end