ruby_reactor 0.7.1 → 0.8.1
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 +4 -4
- data/.release-please-manifest.json +1 -1
- data/.specify/feature.json +1 -1
- data/.specify/memory/constitution.md +26 -16
- data/.specify/templates/plan-template.md +4 -0
- data/.specify/templates/tasks-template.md +1 -1
- data/CHANGELOG.md +150 -3
- data/CLAUDE.md +1 -1
- data/README.md +171 -63
- data/lib/ruby_reactor/adapters/active_job/router.rb +2 -2
- data/lib/ruby_reactor/adapters/sidekiq/router.rb +2 -2
- data/lib/ruby_reactor/context.rb +2 -2
- data/lib/ruby_reactor/dsl/interrupt_builder.rb +6 -0
- data/lib/ruby_reactor/dsl/map_builder.rb +31 -5
- data/lib/ruby_reactor/dsl/reactor.rb +36 -18
- data/lib/ruby_reactor/dsl/step_builder.rb +95 -2
- data/lib/ruby_reactor/dsl/template_helpers.rb +2 -2
- data/lib/ruby_reactor/dsl/validation_helpers.rb +17 -0
- data/lib/ruby_reactor/error/input_validation_error.rb +4 -0
- data/lib/ruby_reactor/error/step_failure_error.rb +10 -3
- data/lib/ruby_reactor/executor/result_handler.rb +9 -3
- data/lib/ruby_reactor/executor/retry_manager.rb +2 -1
- data/lib/ruby_reactor/executor/step_executor.rb +9 -2
- data/lib/ruby_reactor/executor.rb +3 -0
- data/lib/ruby_reactor/map/helpers.rb +9 -0
- data/lib/ruby_reactor/max_retries_exhausted_failure.rb +3 -2
- data/lib/ruby_reactor/reactor.rb +9 -12
- data/lib/ruby_reactor/rspec/matchers.rb +3 -6
- data/lib/ruby_reactor/step/async_reactor_step.rb +159 -162
- data/lib/ruby_reactor/step/compose_step.rb +56 -75
- data/lib/ruby_reactor/step/input_contract.rb +128 -0
- data/lib/ruby_reactor/step/map_step.rb +182 -218
- data/lib/ruby_reactor/step.rb +116 -21
- data/lib/ruby_reactor/step_signals.rb +6 -2
- data/lib/ruby_reactor/step_worker.rb +25 -10
- data/lib/ruby_reactor/template/result.rb +9 -2
- data/lib/ruby_reactor/utils/fetch_indifferent.rb +13 -0
- data/lib/ruby_reactor/version.rb +1 -1
- data/lib/ruby_reactor.rb +5 -2
- data/specs/002-step-input-contracts/checklists/requirements.md +49 -0
- data/specs/002-step-input-contracts/contracts/dsl-surface.md +193 -0
- data/specs/002-step-input-contracts/data-model.md +115 -0
- data/specs/002-step-input-contracts/plan.md +165 -0
- data/specs/002-step-input-contracts/quickstart.md +170 -0
- data/specs/002-step-input-contracts/research.md +233 -0
- data/specs/002-step-input-contracts/spec.md +359 -0
- data/specs/002-step-input-contracts/tasks.md +367 -0
- data/specs/004-inheritable-step-class/checklists/requirements.md +40 -0
- data/specs/004-inheritable-step-class/contracts/step-lifecycle.md +85 -0
- data/specs/004-inheritable-step-class/data-model.md +116 -0
- data/specs/004-inheritable-step-class/plan.md +174 -0
- data/specs/004-inheritable-step-class/quickstart.md +112 -0
- data/specs/004-inheritable-step-class/research.md +308 -0
- data/specs/004-inheritable-step-class/spec.md +316 -0
- data/specs/004-inheritable-step-class/tasks.md +258 -0
- data/specs/deferred-003-step-lock-declarations/checklists/requirements.md +51 -0
- data/specs/deferred-003-step-lock-declarations/contracts/dsl-surface.md +154 -0
- data/specs/deferred-003-step-lock-declarations/data-model.md +131 -0
- data/specs/deferred-003-step-lock-declarations/plan.md +166 -0
- data/specs/deferred-003-step-lock-declarations/quickstart.md +169 -0
- data/specs/deferred-003-step-lock-declarations/research.md +196 -0
- data/specs/deferred-003-step-lock-declarations/spec.md +447 -0
- data/specs/deferred-003-step-lock-declarations/tasks.md +572 -0
- data/specs/possible_feature.md +22 -0
- metadata +28 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0d5c03b684aeac0b3d682581e9746e59219622f07752a5b5e30b5381582c09c9
|
|
4
|
+
data.tar.gz: bccbbdf3eec2187481fba0c5d724b1b2b61d96a32c93139dcf5fcd20e42ff491
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ab06166bc3462aa2cf8f4569015575feb7c37ae025022ae84b886dcc171040c2a89a88dbc69208c8b1ec84be6dd19c08c418915856a510506402e7ab72f26044
|
|
7
|
+
data.tar.gz: cfb0973443827d2d50572f834b1d9d2760464eab4290097b38c73465fcb7268f9e381d211b4a2fe1815f6bb410ac56ffa64c4f61ed17f0a765966392c50494a4
|
data/.specify/feature.json
CHANGED
|
@@ -1,25 +1,27 @@
|
|
|
1
1
|
<!--
|
|
2
2
|
SYNC IMPACT REPORT
|
|
3
3
|
==================
|
|
4
|
-
Version change: 1.1
|
|
5
|
-
|
|
4
|
+
Version change: 1.2.1 → 1.3.0 (MINOR: Development Workflow now REQUIRES every generated
|
|
5
|
+
plan.md and tasks.md to carry an explicit documentation-update task/gate, with the
|
|
6
|
+
requirement propagated into both templates — a new enforcement mechanism, not just
|
|
7
|
+
wording)
|
|
6
8
|
|
|
7
|
-
Modified principles:
|
|
8
|
-
- Principle VI: Demo-App Proof of Feature — added requirement 4, "Docker acceptance
|
|
9
|
-
run" (docker-compose.yml MUST track demo_app's services; demo: rake tasks are the
|
|
10
|
-
project's acceptance suite, runnable via `docker compose run`)
|
|
9
|
+
Modified principles: none (Development Workflow section, not a Core Principle)
|
|
11
10
|
|
|
12
|
-
Added sections: none
|
|
11
|
+
Added sections: none
|
|
13
12
|
|
|
14
13
|
Removed sections: none
|
|
15
14
|
|
|
16
15
|
Templates checked:
|
|
17
|
-
- .specify/templates/plan-template.md
|
|
18
|
-
|
|
19
|
-
- .specify/templates/spec-template.md
|
|
20
|
-
- .specify/templates/tasks-template.md
|
|
16
|
+
- .specify/templates/plan-template.md ✅ Added Constitution Check checklist item:
|
|
17
|
+
identify README.md/./documentation impact
|
|
18
|
+
- .specify/templates/spec-template.md ✅ No principle-specific content — no edit required
|
|
19
|
+
- .specify/templates/tasks-template.md ✅ Polish-phase doc task now explicit and marked
|
|
20
|
+
REQUIRED, citing README.md + ./documentation
|
|
21
21
|
- .specify/templates/checklist-template.md ✅ Generic — no edit required
|
|
22
|
-
- .specify/extensions.yml
|
|
22
|
+
- .specify/extensions.yml ✅ No before/after_constitution hooks registered
|
|
23
|
+
- README.md ✅ No principle-specific claims to update
|
|
24
|
+
- documentation/*.md ✅ No principle-specific claims to update
|
|
23
25
|
|
|
24
26
|
Deferred TODOs: none
|
|
25
27
|
-->
|
|
@@ -164,9 +166,17 @@ gaps in the matcher library surface as work instead of as workarounds.
|
|
|
164
166
|
- Feature branches target `main`. PRs MUST pass CI (RSpec + RuboCop) before merge.
|
|
165
167
|
- Releases are managed by release-please. Version bump lives in
|
|
166
168
|
`lib/ruby_reactor/version.rb`. Do not manually edit the version in gemspec.
|
|
167
|
-
-
|
|
168
|
-
`
|
|
169
|
-
|
|
169
|
+
- Every task MUST update documentation as part of its own change, not as a
|
|
170
|
+
follow-up: `README.md` and any affected file under `./documentation` MUST be
|
|
171
|
+
kept current with the behavior the task introduces, changes, or removes. A
|
|
172
|
+
task that alters observable behavior without a matching documentation update
|
|
173
|
+
is incomplete. New features additionally MUST add entries to `CHANGELOG.md`
|
|
174
|
+
under the correct semantic heading (`Features`, `Bug Fixes`, `documentation`).
|
|
175
|
+
- Every `plan.md` produced by `/speckit-plan` and every `tasks.md` produced by
|
|
176
|
+
`/speckit-tasks` MUST include at least one explicit task or checklist item to
|
|
177
|
+
update `README.md` and the relevant file(s) under `./documentation` for the
|
|
178
|
+
feature's behavior. A plan or task list that omits a documentation-update
|
|
179
|
+
task is incomplete and MUST be rejected in review.
|
|
170
180
|
- Class-based step definitions are the preferred authoring style (not inline
|
|
171
181
|
lambdas). Documentation and examples MUST reflect this.
|
|
172
182
|
- The `demo_app/` directory serves as a living integration example. Changes to
|
|
@@ -198,4 +208,4 @@ justified in the `Complexity Tracking` table of the plan.
|
|
|
198
208
|
Compliance review: at each MINOR or MAJOR gem release, confirm this constitution
|
|
199
209
|
still accurately reflects the codebase and update as needed.
|
|
200
210
|
|
|
201
|
-
**Version**: 1.
|
|
211
|
+
**Version**: 1.3.0 | **Ratified**: 2025-10-02 | **Last Amended**: 2026-09-11
|
|
@@ -42,6 +42,10 @@
|
|
|
42
42
|
|
|
43
43
|
[Gates determined based on constitution file]
|
|
44
44
|
|
|
45
|
+
- [ ] Documentation impact identified: which `README.md` sections and which
|
|
46
|
+
file(s) under `./documentation` this feature will require updating
|
|
47
|
+
(Constitution Development Workflow — carried into tasks.md as a required task)
|
|
48
|
+
|
|
45
49
|
## Project Structure
|
|
46
50
|
|
|
47
51
|
### Documentation (this feature)
|
|
@@ -151,7 +151,7 @@ Examples of foundational tasks (adjust based on your project):
|
|
|
151
151
|
|
|
152
152
|
**Purpose**: Improvements that affect multiple user stories
|
|
153
153
|
|
|
154
|
-
- [ ] TXXX [P]
|
|
154
|
+
- [ ] TXXX [P] Update `README.md` and affected file(s) under `./documentation` for this feature (REQUIRED — Constitution Development Workflow)
|
|
155
155
|
- [ ] TXXX Code cleanup and refactoring
|
|
156
156
|
- [ ] TXXX Performance optimization across all stories
|
|
157
157
|
- [ ] TXXX [P] Additional unit tests (if requested) in tests/unit/
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,152 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
### ⚠ BREAKING CHANGES
|
|
6
|
+
|
|
7
|
+
* **`RubyReactor::Step` is now a base class, not a mixin.** `include RubyReactor::Step` on a
|
|
8
|
+
plain class with `def self.run(arguments, context)` is gone — no compatibility shim, no dual
|
|
9
|
+
authoring style. A step subclasses `RubyReactor::Step` and writes `run` (and optionally
|
|
10
|
+
`compensate`/`undo`) as **instance** methods reading the validated arguments and the context
|
|
11
|
+
through `inputs`/`context` accessors instead of parameters. Every instance is built fresh for
|
|
12
|
+
its one action (`run`, `undo`, or `compensate`) from the stored arguments/result/reason alone —
|
|
13
|
+
nothing an author sets in `run` is visible in a later `undo`/`compensate`, which is exactly what
|
|
14
|
+
makes rollback behave identically whether it lands in the same process as `run` or, as with an
|
|
15
|
+
`async_step`, in a separate later one. `inputs` holds the same values in all three actions: the
|
|
16
|
+
arguments with the contract's defaults applied. Only `run` enforces the contract; `undo` and
|
|
17
|
+
`compensate` never do, so rollback cannot fail on the inputs that caused the failure.
|
|
18
|
+
|
|
19
|
+
```ruby
|
|
20
|
+
# Before
|
|
21
|
+
class ChargeStep
|
|
22
|
+
include RubyReactor::Step
|
|
23
|
+
|
|
24
|
+
input :amount, :integer, gteq?: 1
|
|
25
|
+
|
|
26
|
+
def self.run(arguments, context)
|
|
27
|
+
Success(charge!(arguments[:amount]))
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.undo(result, arguments, context)
|
|
31
|
+
refund!(result[:charge_id])
|
|
32
|
+
Success()
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# After
|
|
37
|
+
class ChargeStep < RubyReactor::Step
|
|
38
|
+
input :amount, :integer, gteq?: 1
|
|
39
|
+
|
|
40
|
+
def run
|
|
41
|
+
Success(charge!(inputs[:amount]))
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def undo
|
|
45
|
+
refund!(result[:charge_id])
|
|
46
|
+
Success()
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**Migration:** for every class step, replace `include RubyReactor::Step` with
|
|
52
|
+
`< RubyReactor::Step`, turn `def self.run(args, ctx)` into `def run` reading `inputs`/`context`,
|
|
53
|
+
and likewise for `def self.undo(result, args, ctx)` / `def self.compensate(reason, args, ctx)` →
|
|
54
|
+
`def undo` / `def compensate` reading `result`/`reason`/`inputs`/`context`. The class-level
|
|
55
|
+
`MyStep.run(arguments, context)` / `.call` / `.undo(result, arguments, context)` /
|
|
56
|
+
`.compensate(reason, arguments, context)` entry points every caller (executor, worker, a direct
|
|
57
|
+
unit-test call) already used are unchanged. Inline `step { run { |args, ctx| ... } }` blocks are
|
|
58
|
+
untouched by this change.
|
|
59
|
+
|
|
60
|
+
* **A class step's signal helpers (`success!`/`fail!`/`skip!`/`halt!`) now translate correctly on
|
|
61
|
+
every execution path, including the `async_step`/`background` worker.** Previously the worker had
|
|
62
|
+
no `catch` of its own, so a signal thrown from a class step running there escaped as an
|
|
63
|
+
`UncaughtThrowError` instead of the intended `Success`/`Failure`/`Skipped`/`Halt` — the base
|
|
64
|
+
class's class-level `run`/`undo`/`compensate` now own that translation, so every caller gets it
|
|
65
|
+
for free with no worker change. Known remaining gap, unchanged by this release: an **inline**
|
|
66
|
+
`run`/`compensate`/`undo` block's signals are still uncaught on the worker path.
|
|
67
|
+
|
|
68
|
+
* **A step's own input-validation failure is now guaranteed non-retryable on every path.**
|
|
69
|
+
`result.retryable?` is `false` whether the violation happened synchronously, inside an
|
|
70
|
+
`async_step`/`background` worker, or inside a `compose`d child's own step — previously only the
|
|
71
|
+
async worker path got this right; the synchronous path and a validation failure surfacing
|
|
72
|
+
through `compose` both defaulted to `retryable? == true`.
|
|
73
|
+
|
|
74
|
+
* **`Failure(...)` takes the same arguments everywhere.** Inside a class step and inside an inline
|
|
75
|
+
`run`/`compensate`/`undo` block, `Failure` now forwards every argument to `RubyReactor.Failure`,
|
|
76
|
+
so options such as `Failure("declined", retryable: false)` work instead of raising
|
|
77
|
+
`ArgumentError`. A bare `Failure()` with no error is no longer accepted, matching
|
|
78
|
+
`RubyReactor.Failure`, and a hash error needs braces, `Failure({ code: 1 })`, because a braceless
|
|
79
|
+
`Failure(code: 1)` is now read as options.
|
|
80
|
+
|
|
81
|
+
### Features
|
|
82
|
+
|
|
83
|
+
* **Step input contracts.** A step class declares its own inputs with `input :name, :type, **predicates`
|
|
84
|
+
(plus `optional:`, `default:`, `redact:`, the `do |i| ... end` macro block and `validate:`) and
|
|
85
|
+
cross-field rules with `validate_inputs`. The contract is enforced before `run` on every path
|
|
86
|
+
(inline, retries, `async_step` and `background` workers, resume, `map`, and a direct
|
|
87
|
+
`Step.run(args, context)` call), and a violation fails with `validation_errors` and the step's
|
|
88
|
+
name after completed steps are rolled back. Subclasses inherit and extend the contract.
|
|
89
|
+
Introspection: `input_contract`, `declared_inputs`, `required_input_names`, `declares_inputs?`.
|
|
90
|
+
* **Inline step contracts.** `inputs do ... end` inside a `step` block takes the same `input` /
|
|
91
|
+
`validate_inputs` lines as a step class and is enforced the same way.
|
|
92
|
+
* **Wiring by name.** A declared input with no `argument` resolves from the reactor input of the
|
|
93
|
+
same name (never from a step result). A required input that is neither wired nor a reactor input
|
|
94
|
+
raises `Error::ValidationError` before any step runs. `Reactor.validate_definition!` runs that
|
|
95
|
+
check on demand, e.g. from an initializer or CI.
|
|
96
|
+
* For a step that owns a contract, a type or predicate on `argument`, `validate_args`, or an
|
|
97
|
+
`argument` for an undeclared input raises `Error::ValidationError` when the `step` line is
|
|
98
|
+
evaluated.
|
|
99
|
+
* `have_validation_error` now also matches validation failures raised at a step, not only
|
|
100
|
+
reactor-input failures.
|
|
101
|
+
* `Failure#to_h` includes `retryable`, so a non-retryable failure stays non-retryable after it
|
|
102
|
+
crosses a worker boundary.
|
|
103
|
+
* A step that returns another unit's validation failure (e.g. an `async_step` reader propagating
|
|
104
|
+
the worker's `Failure`) keeps its `validation_errors` on the reactor's final failure.
|
|
105
|
+
|
|
106
|
+
### Deprecations
|
|
107
|
+
|
|
108
|
+
* Rules on `argument` (`argument :x, src, :type, **predicates`) and `validate_args` keep working
|
|
109
|
+
for steps without a contract, and print one deprecation notice per declaration site. Move them
|
|
110
|
+
to `input` / `validate_inputs` on the step class, or into an `inputs do ... end` block for an
|
|
111
|
+
inline step, and keep `argument :x, src` for wiring. Removal is no earlier than the next major
|
|
112
|
+
version. See "Step Input Contracts" in the README for the migration.
|
|
113
|
+
|
|
114
|
+
### Bug Fixes
|
|
115
|
+
|
|
116
|
+
* A supplied `false` reactor input or step result no longer resolves to `nil`.
|
|
117
|
+
`Context#get_input`, `Context#get_result` and `Template::Result#fetch` now check whether the key
|
|
118
|
+
exists instead of whether the value is truthy. Code that relied on `false` arriving as `nil`
|
|
119
|
+
will now see `false`.
|
|
120
|
+
* An input-validation `Failure` stays non-retryable across serialization. `Failure`'s
|
|
121
|
+
hash extractor and the reactor's stored `failure_reason` both dropped a `retryable: false`
|
|
122
|
+
(`||` swallowed the `false`, and the reactor never stored the flag at all), so a failure
|
|
123
|
+
rebuilt from JSON or reloaded with `Reactor.find` reported `retryable? == true`.
|
|
124
|
+
* A step-contract violation inside `compose` now reaches the parent with its `validation_errors`
|
|
125
|
+
and retryability intact, instead of being rebuilt from the child's error message alone.
|
|
126
|
+
* A reactor reopened after its first run is re-checked: `validate_definition!` no longer memoizes,
|
|
127
|
+
so a step declared later can no longer reach execution with a required input unwired.
|
|
128
|
+
* A class step that calls `halt!` under `async_step` is recorded as a halt rather than an
|
|
129
|
+
ordinary `nil` success, and `result(:step)` hands the reader the `Halt` — the same way it
|
|
130
|
+
already hands over a `Failure`.
|
|
131
|
+
|
|
132
|
+
## [0.8.1](https://github.com/arturictus/ruby_reactor/compare/v0.8.0...v0.8.1) (2026-09-22)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
### Miscellaneous Chores
|
|
136
|
+
|
|
137
|
+
* Update documentation clarifying background and async concepts ([#55](https://github.com/arturictus/ruby_reactor/issues/55)) ([968e32b](https://github.com/arturictus/ruby_reactor/commit/968e32bbc695e45471d04b7630d8cd437ff41624))
|
|
138
|
+
|
|
139
|
+
## [0.8.0](https://github.com/arturictus/ruby_reactor/compare/v0.7.1...v0.8.0) (2026-09-21)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
### ⚠ BREAKING CHANGES
|
|
143
|
+
|
|
144
|
+
* Step Instance and input validations per step ([#51](https://github.com/arturictus/ruby_reactor/issues/51))
|
|
145
|
+
|
|
146
|
+
### Features
|
|
147
|
+
|
|
148
|
+
* Step Instance and input validations per step ([#51](https://github.com/arturictus/ruby_reactor/issues/51)) ([4a20583](https://github.com/arturictus/ruby_reactor/commit/4a2058399d73e9b3bf767659dd5ba080537dc291))
|
|
149
|
+
|
|
3
150
|
## [0.7.1](https://github.com/arturictus/ruby_reactor/compare/v0.7.0...v0.7.1) (2026-09-14)
|
|
4
151
|
|
|
5
152
|
|
|
@@ -58,9 +205,9 @@
|
|
|
58
205
|
point (`:x` stays in the calling process); the two coincide in a linear chain
|
|
59
206
|
but pin different steps in a DAG.
|
|
60
207
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
208
|
+
Also affected: the map-internal element dispatch option was renamed from
|
|
209
|
+
`async true` to `fan_out`, so `map :items do async true, batch_size: 2 end`
|
|
210
|
+
now raises and should become `map :items do fan_out batch_size: 2 end`.
|
|
64
211
|
|
|
65
212
|
One behavior change falls out of "exactly one hand-off point per reactor":
|
|
66
213
|
resuming a reactor past its hand-off point now finishes in the resuming
|
data/CLAUDE.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<!-- SPECKIT START -->
|
|
2
2
|
For additional context about technologies to be used, project structure,
|
|
3
3
|
shell commands, and other important information, read the current plan:
|
|
4
|
-
`specs/
|
|
4
|
+
`specs/004-inheritable-step-class/plan.md`
|
|
5
5
|
<!-- SPECKIT END -->
|