feat!: require pg_eventstore v3 (2.0.0) - #34
Conversation
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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
|
| migration | time | share |
|---|---|---|
| 12 back-fill links | 1h 02m | 48% |
| 16+17 streams index | 34m | 26% |
| 18 events index + CLUSTER | 16m | 12% |
| 19–32 (everything else) | 11m | 9% |
| total | ~2h 04m |
Migration 12 dominates and is not one the release notes flag as slow. Migrations 19 and 26 — which look like they should be the expensive serial ones — are nearly free, because 13/14/22/24 drop the PK and all but one index before 26 rewrites the table.
Link back-fill throughput scaled 5,000 → 11,900 → 15,700 rows/min at CONCURRENCY 5 → 16 → 48, so more workers keep helping in wall-clock terms even as per-core efficiency collapses from lock contention.
Releases all four gems as 2.0.0, moving
yes-coretopg_eventstore ~> 3.0.Why 2.0.0 and not 1.5.0
Consumers declare these gems at
~> 1.3from the git source, andmainis already at 1.4.0. So1.5.0would satisfy the existing constraint and could be pulled in by an unrelatedbundle update— putting v3 code against a still-v1 store with no deliberate decision anywhere.2.0.0makes that structurally impossible. Adoption now requires an explicit Gemfile edit, which is exactly what it should require.The actual v3 change: two OTel span attributes
pg_eventstoremigration 13 dropsevents.link_idin favour of the bigintlink_global_position, soEvent#link_idraisesNoMethodError:yes-core/lib/yes/core/commands/stateless/handler.rb'event.link_id' => result.link_id→'event.link_global_position' => result.link_global_positionyes-core/lib/yes/core/command_handling/event_publisher.rb📊 Dashboards / trace queries keyed on
event.link_idneed updating toevent.link_global_position.yes-auth had to move too
yes-auth.gemspecpinnedyes-core '~> 1.0', which would have made the monorepo unresolvable the momentyes-corehit 2.0.0. Bumped to~> 2.0.Verified by resolving the full set —
pg_eventstore 3.0.0, all four gems at2.0.0, no conflicts.Two v3 API breaks found by CI
Neither is in the gem's migration notes.
1.
WrongExpectedRevisionError.newnow requiresverdict:— signature is(revision:, expected_revision:, stream:, verdict:), where the Symbol selects the message. Omitting it raisesArgumentError: missing keyword: :verdictinstead of the intended error, so retry logic keyed onWrongExpectedRevisionErrorsilently stops working. 7 specs caught it.Verdicts assigned per the gem's own rules in
Commands::RevisionCheck::StreamRevisionComparison, rather than blanket-set, so each message matches the condition detected:event_publisher.rb,stateless/handler.rb:unmatched_stream_revisioncommand_executor_spec1vs2:unmatched_stream_revisioncommand_group_executor_spec:no_stream:expected_not_to_have_stream2.
setup.rakereadsPG_EVENTSTORE_URIat load time (top-levelconfigureblock), not lazily inside a task. Since the Rakefile loads it unconditionally, every rake invocation now needs the variable —db:create db:migrateaborted onKeyErrordespite never touching the event store. Fixed in CI config here; any app whose Rakefile loadssetup.rakeneedsPG_EVENTSTORE_URIpresent for deploy-timerake db:migrate.Also updated the four per-gem
Gemfile.locks — CI runs each gem from its own directory in frozen mode.Verification
✅ All green: RuboCop,
yes-core,yes-auth,yes-command-api,yes-read-api— 1184 examples in yes-core.