yes 0.0.1 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 5eff49dca0523282e364a82fb408cc06d00742f2fca7056b82384ac091c7e107
4
+ data.tar.gz: a2a87f022f74b86b515bc66f8862086a9c036c677e9a1202226361dc5ba48243
5
+ SHA512:
6
+ metadata.gz: b28a95c77f846978113afb0e33996670b693413d972f9b8b20b5734bcafdf783cdec7c5122865e691e37becd8934ae9b800b63fe0e8e74bd458b7015b52eff96
7
+ data.tar.gz: 89dd8e95c3ef2cedc6f796e3052886c5948c865f8f8e844a9d23e9ca6d2a00dab1aae61f6f1fc8802ba9f92177ad92d9fef9b54149fadbbdd5e13c1486f426dd
data/CHANGELOG.md ADDED
@@ -0,0 +1,235 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ ## [2.2.0] - 2026-09-01
6
+
7
+ ### yes-core
8
+
9
+ #### Added
10
+ - `Authorization::LookupCache` — scoped memoization for the read-only lookups an
11
+ authorization pass repeats.
12
+
13
+ Caching is opt-in per scope: outside `LookupCache.with_scope` its `fetch` just yields,
14
+ so nothing changes for callers that do not open a scope. The store lives in
15
+ `ActiveSupport::IsolatedExecutionState`, so it is per thread/fiber, and `with_scope`
16
+ clears it on the way out — including when the block raises — so nothing leaks into the
17
+ next request. Nested scopes reuse the outermost cache.
18
+
19
+ #### Changed
20
+ - `Authorization::CommandCerbosAuthorizer` resolves the principal data and the
21
+ authorized resource through `LookupCache`.
22
+
23
+ Authorizing a batch of commands used to repeat both lookups once per command. The
24
+ principal data is derived purely from the request's auth data, and the commands of a
25
+ batch commonly act on the same resource, so both were re-read from the database for
26
+ every command — for an N-command batch, N times the queries for N identical results.
27
+ In production traces the two lookups accounted for the large majority of the authorize
28
+ span, dwarfing the Cerbos call itself.
29
+
30
+ This is safe because a batch is authorized in full before any of its commands is
31
+ executed, so no write can invalidate either lookup while the pass is running. Cached
32
+ values are shared between the commands of a pass and must be treated as read-only;
33
+ the per-command Cerbos payload is still built, and checked, per command.
34
+
35
+ - The `Cerbos Authorize Command` span now tracks SQL, so the time it spends in-process
36
+ can be attributed to queries rather than guessed at.
37
+
38
+ ### yes-command-api
39
+
40
+ #### Changed
41
+ - `Commands::BatchAuthorizer` authorizes a batch inside a single
42
+ `Yes::Core::Authorization::LookupCache` scope, which is what lets the authorizers of
43
+ one batch share their principal and resource lookups.
44
+
45
+ ## [2.1.1] - 2026-08-22
46
+
47
+ ### yes-core
48
+
49
+ - `Types::UUID` now accepts any RFC 9562 UUID version (1-8), not only v4.
50
+
51
+ pg_eventstore 3.0.0 moved event-id generation off the database's `gen_random_uuid()`
52
+ (always v4) to `SecureRandom.uuid_v7`. Those ids reach the gem as `causation_id` /
53
+ `correlation_id`, and a v4-only pattern makes `TransactionDetails.new` raise
54
+ `Dry::Struct::Error` — which fails the event handler and kills the subscription once
55
+ its restarts are exhausted.
56
+
57
+ Still a real constraint: version must be 1-8 and the variant nibble 8/9/a/b, so
58
+ arbitrary hex in UUID shape is rejected as before.
59
+
60
+ **Required for anything running pg_eventstore 3.x.**
61
+
62
+ - Register a `failed_subscription_notifier` so a dead subscription reports to Sentry.
63
+
64
+ `config.failed_subscription_notifier` is pg_eventstore's only death signal — it is
65
+ called once, when a subscription exhausts its restarts and stays dead. Without it that
66
+ death is silent: per-failure errors are only recorded on the subscription row and are
67
+ never raised, so a subscription can stop processing indefinitely with no alert.
68
+
69
+ Registered only when the host application has loaded Sentry.
70
+
71
+ Shipped alongside the UUID fix deliberately: that fix addresses a bug which *kills*
72
+ subscriptions, and this is what tells you when one has died.
73
+
74
+ ## [2.1.0] - 2026-07-31
75
+
76
+ ### yes-core
77
+
78
+ #### Added
79
+ - `Middlewares::WriteEncryptor` — encrypts on `#serialize` exactly like `Middlewares::Encryptor`
80
+ (it subclasses it), but its `#deserialize` is a no-op.
81
+ - `Middlewares.register_encryptor(key_repository, config:)` — registers `:encryptor` and
82
+ `:write_encryptor` together. Host applications should call this instead of assigning
83
+ `config.middlewares[:encryptor]` by hand; registering the decrypting encryptor on its own doubles
84
+ the encryptor round trips of every encrypted append.
85
+ - `Middlewares.for_write` — the middleware keys to pass to `#append_to_stream`: every configured
86
+ middleware, with `:encryptor` swapped for `:write_encryptor`. Derived from the live config rather
87
+ than hard-coded, because `PgEventstore::Client` resolves a passed list with
88
+ `config.middlewares.slice(*list)`, which silently drops unregistered names — a literal list could
89
+ therefore resolve to one with no encryptor at all and write plaintext at rest. Falls back to the
90
+ full list when `:write_encryptor` is missing, and the railtie warns about that at boot.
91
+ - `Middlewares::ENCRYPTOR` / `Middlewares::WRITE_ENCRYPTOR` config-key constants, and
92
+ `DataEncryptor::CIPHERTEXT_KEY` for the `es_encrypted` data key.
93
+
94
+ #### Changed
95
+ - Every write site now appends with `middlewares: Middlewares.for_write`, so an append no longer
96
+ decrypts the event it returns: `CommandHandling::EventPublisher`,
97
+ `CommandHandling::CommandGroupExecutor`, `Commands::Stateless::Handler` and
98
+ `TestSupport::EventHelpers#append_event`. This covers both `PgEventstore#multiple` paths, whose
99
+ sub-events publish through those same call sites.
100
+
101
+ pg_eventstore 3.0 runs every registered middleware's `#deserialize` on the events returned by
102
+ `#append_to_stream`, not only on reads. Nothing in yes-core reads `data` off that returned event —
103
+ `otl_record_response` records only type, revision, stream and positions, and `ReadModelUpdater`
104
+ always receives the command payload on the write path — so each encrypted append was paying an
105
+ uncached key lookup plus a decrypt against the encryptor service for a payload it discarded. Those
106
+ calls also ran inside the SERIALIZABLE transaction opened by `#multiple`, widening the window for
107
+ `PG::TRSerializationFailure` and its retries.
108
+
109
+ ⚠️ Consumers that relied on the appended event coming back decrypted must read the event instead.
110
+
111
+ #### Fixed
112
+ - `Middlewares::Encryptor#serialize` is now idempotent: it returns the event untouched when the data
113
+ is already encrypted. Required because the default middleware list holds two serialize-capable
114
+ encryptors once `:write_encryptor` is registered, so an append that omits `middlewares:` would
115
+ otherwise encrypt twice — the second pass encrypting the first pass's sentinels and overwriting the
116
+ real ciphertext irrecoverably. It also makes re-appending an event that was read at rest
117
+ (`middlewares: Middlewares.without(:encryptor)`) safe, which it was not before.
118
+
119
+ ## [2.0.0] - 2026-07-28
120
+
121
+ Major bump because `yes-core` now requires `pg_eventstore` v3, whose schema is
122
+ incompatible with v1. Released as 2.0.0 rather than 1.5.0 deliberately: consumers
123
+ constrain these gems at `~> 1.3`, which 1.5.0 would satisfy, so a minor bump could
124
+ be pulled in by an unrelated `bundle update` and put v3 code against a v1 store.
125
+ 2.0.0 makes that impossible.
126
+
127
+ ⚠️ **Do not adopt until your event store has been migrated to v3.** Migrating is a
128
+ one-way, downtime-requiring operation — see the `pg_eventstore` upgrade notes.
129
+
130
+ ### yes-core
131
+
132
+ #### Changed
133
+ - **Breaking change**: `pg_eventstore` dependency `~> 1.0` → `~> 3.0`.
134
+ - **Breaking change**: OpenTelemetry span attribute `event.link_id` is now
135
+ `event.link_global_position`, in `Commands::Stateless::Handler` and
136
+ `CommandHandling::EventPublisher`. `pg_eventstore` v3 drops `events.link_id`
137
+ (migration 13) in favour of the bigint `link_global_position`, so `Event#link_id`
138
+ raises `NoMethodError`. Mirrors the same change in `yousty-eventsourcing` 16.0.0.
139
+ **Update any dashboards or trace queries keyed on `event.link_id`.**
140
+
141
+ ### yes-auth
142
+
143
+ #### Changed
144
+ - **Breaking change**: `yes-core` dependency `~> 1.0` → `~> 2.0`, required to stay
145
+ resolvable alongside yes-core 2.0.0.
146
+
147
+ ## [1.4.0] - 2026-06-24
148
+
149
+ ### yes-command-api
150
+
151
+ #### Added
152
+ - Dispatch aggregate-DSL command groups over the HTTP command API. The deserializer now resolves the `<Context>::<Subject>::CommandGroups::<Name>::Command` class-name convention (generated by the `command_group` macro) in addition to the legacy top-level group, V2, and V1 conventions. The controller expands a `Yes::Core::Commands::CommandGroup` into its sub-commands for batch authorization and validation, while still passing the wrapped group to the command bus so the Processor dispatches it as one atomic unit.
153
+
154
+ ### yes-core
155
+
156
+ #### Added
157
+ - `Yes::Core::Commands::CommandGroup#to_h` now returns the flat input payload merged with reserved keys, so a group round-trips cleanly through `Class.new(to_h)` (used by the command bus' `add_metadata` and by the ActiveJob serializer). Reserved keys (`origin`, `batch_id`, `command_id`, `metadata`, `transaction`) now propagate to the aggregate group method through that round-trip.
158
+ - `Yes::Core::ActiveJobSerializers::CommandGroupSerializer` now serializes aggregate-DSL `CommandGroup`s as well as the legacy stateless `Group`, so groups survive the async command queue.
159
+ - `Yes::Core::Configuration#command_group_guard_evaluator_class` resolves a command group's guard evaluator from the `:command_group_guard_evaluator` registry, and `Processor#guard_evaluator_exists?` uses it for `CommandGroup` instances. A group registers its guard evaluator under that dedicated registry type, so without this the existence check raised `UnregisteredCommand` when a group was dispatched.
160
+
161
+ #### Changed
162
+ - `Yes::Core::Commands::Processor#run_command` no longer special-cases `CommandGroup` payloads; the flat `#to_h` makes a single code path correct for both single commands and groups (drops the `reinstantiate_with_reserved_keys` helper).
163
+
164
+ ## [1.3.1] - 2026-06-16
165
+
166
+ ### yes-auth
167
+
168
+ #### Added
169
+ - Rebuild the principals mirror rows on resource-access `Restored` events, so a restored read/write resource access re-materializes the principal it grants.
170
+
171
+ ### yes-core
172
+
173
+ #### Fixed
174
+ - `Yes::Core::Utils::HashUtils.deep_flatten_hash` now applies the `prefix` to array-valued keys, consistently with scalar and nested-hash keys. Previously an array value was keyed by its bare name (e.g. `deep_flatten_hash({ tags: [...] }, 'span')` produced `"tags"` instead of `"span.tags"`), so callers passing a prefix got a mix of namespaced and un-namespaced keys. Callers that pass no prefix are unaffected.
175
+
176
+ ## [1.3.0] - 2026-05-18
177
+
178
+ ### yes-core
179
+
180
+ #### Added
181
+ - `command_group :name do … end` DSL macro on `Yes::Core::Aggregate` for declaring aggregate-scoped command groups that execute several existing aggregate commands as one atomic, transactionally-published unit. Inside the block: `command :sub_name` lists existing commands by symbol (declaration order = execution order; sub-command guards are bypassed), and `guard(:name) { … }` declares group-level guards using the same DSL as per-command guards. All sub-events publish inside a single `PgEventstore.client.multiple` block at serializable isolation; the first event uses optimistic locking against the read model revision + external-aggregate revision tracking, and subsequent events use `expected_revision: :any`. Read-model updates run after the eventstore commit, in declaration order, so each sub-command's state-updater sees the cumulative state of the previous ones. Per `command_group :foo` the DSL generates `Context::Aggregate::CommandGroups::Foo::Command`, `Context::Aggregate::CommandGroups::Foo::GuardEvaluator`, plus `Aggregate#foo(payload, guards:, metadata:)`, `Aggregate#can_foo?(payload)`, and `Aggregate#foo_error`. Initial scope is single-aggregate groups; the legacy stateless `Yes::Core::Commands::Group` / `GroupHandler` are unchanged and continue to serve cross-aggregate use cases.
182
+ - Test DSL extension: `command_group 'name' do … end` block in `Yes::Core::TestSupport::Aggregate::CommandTestDsl` with `success_group`, `invalid_group`, `no_change_group` helpers and three matching shared examples that mirror the per-command DSL.
183
+ - `Yes::Core::Commands::GroupPayloadNormalizer` — standalone module extracted from `Yes::Core::Commands::Group#normalized_payloads` that normalizes the three legacy payload shapes (flat / subject-nested / context-nested). `Group` now delegates to it; the new `CommandGroup` reuses it. Existing `Group`/`GroupHandler` behavior is preserved.
184
+
185
+ #### Fixed
186
+ - `Yes::Core::TestSupport::Aggregate::CommandTestDsl` now resolves the draft `expected_event_type` the same way the runtime does, so specs against a `draftable` aggregate that configures `changes_read_model:` no longer fail with `expected "Foo::AggregateDraftEvent" / got "Foo::CustomChangesReadModelEvent"`. The 1.1.0 fix to `CommandUtils#aggregate_name_with_draft_suffix` was not mirrored in the test DSL; this aligns the two and adds focused unit coverage via the new `CommandTestDsl.expected_event_prefix` helper.
187
+
188
+ ## [1.2.0] - 2026-04-30
189
+
190
+ ### yes-core
191
+
192
+ #### Added
193
+ - Auto-injected `:not_removed` guard on `removable` aggregates. Calling `removable` now blocks every non-`:remove` command on the aggregate while the removal attribute (default `removed_at`) is set, so consumers no longer need to hand-write `guard(:not_removed) { removed_at.blank? }` on every mutation. The check is implemented as a runtime pre-check in `Yes::Core::CommandHandling::GuardEvaluator#call`, so it is order-independent (works whether `removable` is declared before or after the other commands) and fires before any registered guard — including the auto-injected `:no_change`. Post-remove mutations consistently raise `GuardEvaluator::InvalidTransition` with the i18n message under `aggregates.<context>.<aggregate>.commands.<command>.guards.not_removed.error` (with the existing generic fallback). The `:remove` command itself is exempt and remains gated only by its existing `:no_change`.
194
+ - Aggregate-level opt-out: `removable not_removed_guards: false` disables the auto-block for the whole aggregate.
195
+ - Per-command opt-out: both `command` and `parent` accept a new `skip_default_guards: %i[not_removed]` keyword argument that exempts the affected command from the auto-block. The kwarg is stored on `Yes::Core::Aggregate::Dsl::CommandData#skip_default_guards` and respected by the pre-check.
196
+ - `Yes::Core::Aggregate.removable_config` reader exposing the `{ attr_name:, not_removed_guards: }` hash recorded by `removable`.
197
+
198
+ #### Fixed
199
+ - `AggregateShortcuts.display` (the `shortcuts` Rails console helper) now writes directly to STDOUT via `puts`. Previously it used `Rails.logger.debug`, which made the helper unusable in production where Rails apps configure structured / JSON loggers (e.g. semantic_logger) — each line came back wrapped in a JSON envelope.
200
+
201
+ ## [1.1.0] - 2026-04-28
202
+
203
+ ### yes-core
204
+
205
+ #### Added
206
+ - `Yes::Core::TestSupport::Aggregate` — aggregate test DSL with command matchers and shared examples for asserting state transitions and emitted events from aggregate commands.
207
+ - `Yes::Core::Middlewares.without` — helper that temporarily removes one or more middlewares for the duration of a block, useful in tests and one-off command runs.
208
+
209
+ #### Fixed
210
+ - Draft commands against a `draftable` aggregate that configures `changes_read_model:` now append events to the configured stream (camelized `changes_read_model_name`) instead of falling back to a hard-coded `<Aggregate>Draft` name. Previously this raised `PgEventstore::WrongExpectedRevisionError` for any aggregate whose existing draft history lived on the configured stream. Aggregates that don't pass `changes_read_model:` keep the legacy `<Aggregate>Draft` / `<Aggregate>EditTemplate` behavior.
211
+ - Zeitwerk no longer eager-loads `Yes::Core::TestSupport` in non-test contexts, preventing test-helper code paths from being mounted in production.
212
+ - `AggregateShortcuts.load!` no longer silently skips aggregates whose subject name has a single capital letter and is 4 chars or shorter (e.g. `Task`, `User`, `Star`). Previously the auto-generated abbreviation collided with the subject's own namespace module and the shortcut was dropped.
213
+
214
+ #### Changed
215
+ - For single-capital subject names, the auto-generated shortcut now uses the **full subject name** instead of the first 4 characters. Examples: `Board → Board` (was `Boar`), `Location → Location` (was `Loca`). Multi-capital names are unchanged (`ContactInfo → CI`).
216
+ - Shortcut context modules (e.g. `TF`) are now fresh `Module.new` instances rather than aliases of the real context module, so shortcut constants cannot collide with the aggregates' own namespace modules.
217
+
218
+ ### yes-auth
219
+
220
+ #### Added
221
+ - Migration generators for the auth principal models: `yes:auth:install`, `yes:auth:principals:user`, `yes:auth:principals:role`, `yes:auth:principals:user_role`, `yes:auth:principals:read_resource_access`, `yes:auth:principals:write_resource_access`. Each generates the corresponding migration so consuming apps can scaffold the auth tables without copying SQL by hand.
222
+
223
+ ## [1.0.0] - 2026-03-21
224
+
225
+ ### Added
226
+ - Initial release of the Yes framework for building event-sourced Ruby on Rails applications
227
+ - Core DSL for defining aggregates, commands, events, projections, and process managers
228
+ - Command API engine with built-in authorization and validation
229
+ - Read API engine with filterable, sortable, and paginatable query endpoints
230
+ - Auth gem with Cerbos-based authorization and pluggable auth adapters
231
+ - Encryption middleware for sensitive event data
232
+ - ActionCable and MessageBus notifiers for real-time updates
233
+ - Subscription management for event handlers
234
+ - Comprehensive test support utilities
235
+ - Full documentation and contributing guidelines
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2024 ncri
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.