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 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/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-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/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-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/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-core/lib/yes/core/command_handling/event_publisher.rb b/yes-core/lib/yes/core/command_handling/event_publisher.rb index 11db9b4d..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 @@ -175,7 +179,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..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( { @@ -277,7 +282,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/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 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/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 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/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 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