ruby_reactor 0.8.4 → 0.8.5
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/CHANGELOG.md +196 -0
- data/CLAUDE.md +1 -1
- data/README.md +47 -11
- data/lib/ruby_reactor/dsl/async_macros.rb +30 -1
- data/lib/ruby_reactor/dsl/async_reactor_builder.rb +12 -6
- data/lib/ruby_reactor/dsl/compose_builder.rb +12 -6
- data/lib/ruby_reactor/dsl/interrupt_builder.rb +1 -3
- data/lib/ruby_reactor/dsl/map_builder.rb +0 -2
- data/lib/ruby_reactor/dsl/step_builder.rb +91 -19
- data/lib/ruby_reactor/error/argument_resolution_error.rb +19 -0
- data/lib/ruby_reactor/error/rescuable.rb +28 -0
- data/lib/ruby_reactor/executor/compensation_manager.rb +30 -26
- data/lib/ruby_reactor/executor/result_handler.rb +18 -16
- data/lib/ruby_reactor/executor/step_coordination.rb +11 -8
- data/lib/ruby_reactor/executor/step_executor.rb +59 -49
- data/lib/ruby_reactor/executor.rb +38 -4
- data/lib/ruby_reactor/map/collector.rb +21 -11
- data/lib/ruby_reactor/map/dispatcher.rb +29 -3
- data/lib/ruby_reactor/map/element_executor.rb +9 -3
- data/lib/ruby_reactor/map/helpers.rb +32 -2
- data/lib/ruby_reactor/map/result_enumerator.rb +18 -12
- data/lib/ruby_reactor/reactor.rb +24 -0
- data/lib/ruby_reactor/rspec/matchers.rb +19 -3
- data/lib/ruby_reactor/step/compose_step.rb +7 -1
- data/lib/ruby_reactor/step/map_step.rb +109 -4
- data/lib/ruby_reactor/step.rb +7 -0
- data/lib/ruby_reactor/step_worker.rb +46 -22
- data/lib/ruby_reactor/storage/adapter.rb +4 -0
- data/lib/ruby_reactor/storage/redis_adapter.rb +9 -0
- data/lib/ruby_reactor/storage/redis_reactor_scan.rb +1 -1
- data/lib/ruby_reactor/version.rb +1 -1
- data/lib/ruby_reactor/web/api.rb +1 -1
- data/lib/ruby_reactor/web/public/assets/{index-CeZU-ESu.js → index-CQbgHtd0.js} +10 -10
- data/lib/ruby_reactor/web/public/index.html +1 -1
- data/lib/ruby_reactor/worker.rb +3 -1
- data/lib/ruby_reactor.rb +17 -6
- data/specs/007-execution-flow-analysis/analysis/README.md +147 -0
- data/specs/007-execution-flow-analysis/analysis/execution-order.md +359 -0
- data/specs/007-execution-flow-analysis/analysis/findings-and-options.md +502 -0
- data/specs/007-execution-flow-analysis/analysis/invariants.md +109 -0
- data/specs/007-execution-flow-analysis/checklists/requirements.md +39 -0
- data/specs/007-execution-flow-analysis/contracts/report-structure.md +71 -0
- data/specs/007-execution-flow-analysis/data-model.md +83 -0
- data/specs/007-execution-flow-analysis/evidence/harness.rb +229 -0
- data/specs/007-execution-flow-analysis/evidence/output.txt +333 -0
- data/specs/007-execution-flow-analysis/evidence/probes/01_plain.rb +122 -0
- data/specs/007-execution-flow-analysis/evidence/probes/02_compose.rb +182 -0
- data/specs/007-execution-flow-analysis/evidence/probes/03_map.rb +232 -0
- data/specs/007-execution-flow-analysis/evidence/probes/04_async.rb +132 -0
- data/specs/007-execution-flow-analysis/evidence/probes/05_background.rb +58 -0
- data/specs/007-execution-flow-analysis/evidence/probes/06_coordination.rb +158 -0
- data/specs/007-execution-flow-analysis/evidence/probes/07_interrupts_manual.rb +185 -0
- data/specs/007-execution-flow-analysis/evidence/run.rb +15 -0
- data/specs/007-execution-flow-analysis/plan.md +127 -0
- data/specs/007-execution-flow-analysis/quickstart.md +51 -0
- data/specs/007-execution-flow-analysis/research.md +202 -0
- data/specs/007-execution-flow-analysis/spec.md +270 -0
- data/specs/007-execution-flow-analysis/tasks.md +257 -0
- data/specs/008-rollback-reliability/checklists/requirements.md +43 -0
- data/specs/008-rollback-reliability/contracts/api-surface.md +126 -0
- data/specs/008-rollback-reliability/contracts/rollback-semantics.md +76 -0
- data/specs/008-rollback-reliability/data-model.md +139 -0
- data/specs/008-rollback-reliability/plan.md +233 -0
- data/specs/008-rollback-reliability/quickstart.md +105 -0
- data/specs/008-rollback-reliability/research.md +653 -0
- data/specs/008-rollback-reliability/spec.md +561 -0
- data/specs/008-rollback-reliability/tasks.md +1110 -0
- data/specs/future_improvements.md +48 -0
- metadata +35 -2
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
# Feature Specification: Execution Flow & Compensation Analysis
|
|
2
|
+
|
|
3
|
+
**Feature Branch**: `execution_flow_analysis`
|
|
4
|
+
|
|
5
|
+
**Created**: 2026-09-26
|
|
6
|
+
|
|
7
|
+
**Status**: Draft
|
|
8
|
+
|
|
9
|
+
**Input**: User description: "We need to do a thorough analysis of the execution flow. That means
|
|
10
|
+
to figure out all the invariants and paths the execution flows in all conditions. We have to get:
|
|
11
|
+
order of execution of reactors and compensation in all the possible conditions.
|
|
12
|
+
|
|
13
|
+
We have to take into account:
|
|
14
|
+
- composed reactors
|
|
15
|
+
- maps
|
|
16
|
+
- async steps
|
|
17
|
+
- async reactors
|
|
18
|
+
|
|
19
|
+
In all the invariants:
|
|
20
|
+
- locks
|
|
21
|
+
- retries
|
|
22
|
+
- failures
|
|
23
|
+
|
|
24
|
+
This is important because we have to figure out if compensation is predictable and the DSL helps
|
|
25
|
+
clearly understand how everything is gonna be executed.
|
|
26
|
+
|
|
27
|
+
For example: I'm not very sure how compensation behaves in composed reactors and maps. In maps:
|
|
28
|
+
- we are not compensating individually all the maps already executed.
|
|
29
|
+
- when composed reactors fail do we compensate all previous composed reactors.
|
|
30
|
+
|
|
31
|
+
Should we add an entry point for maps to compensate all on failure? for example:
|
|
32
|
+
|
|
33
|
+
```ruby
|
|
34
|
+
map :many_things do
|
|
35
|
+
compensate_all do |all_elements|
|
|
36
|
+
all_elements.destroy
|
|
37
|
+
end
|
|
38
|
+
# or
|
|
39
|
+
compensate_each do |element|
|
|
40
|
+
element.destroy
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
This initiative is a research project to later figure out what solutions we could implement based
|
|
46
|
+
on data and evidence. We could suggest fixes at the end but they will be later analysed.
|
|
47
|
+
ONLY DOCUMENTATION."
|
|
48
|
+
|
|
49
|
+
## User Scenarios & Testing *(mandatory)*
|
|
50
|
+
|
|
51
|
+
The "users" of this research are the RubyReactor maintainers who must decide whether (and how) to
|
|
52
|
+
change rollback semantics, and reactor authors who need to predict what runs, in what order, when
|
|
53
|
+
something fails.
|
|
54
|
+
|
|
55
|
+
### User Story 1 - Look up the exact rollback order for any failure (Priority: P1)
|
|
56
|
+
|
|
57
|
+
A maintainer picks a reactor shape (plain steps, a composed reactor, a map, an async step, an async
|
|
58
|
+
reactor, or any nesting of these) and a point of failure, and finds the exact ordered sequence of
|
|
59
|
+
forward executions, compensations and undos that the library performs — including which already
|
|
60
|
+
completed work is **not** rolled back.
|
|
61
|
+
|
|
62
|
+
**Why this priority**: This is the core question of the initiative ("is compensation
|
|
63
|
+
predictable?"). Every later decision depends on knowing current behavior precisely.
|
|
64
|
+
|
|
65
|
+
**Independent Test**: Take any row of the delivered execution-order matrix, build that reactor
|
|
66
|
+
shape, trigger the failure at the stated point, and compare the observed sequence of step events
|
|
67
|
+
with the documented sequence. They match.
|
|
68
|
+
|
|
69
|
+
**Acceptance Scenarios**:
|
|
70
|
+
|
|
71
|
+
1. **Given** a reactor whose third of four plain steps fails, **When** the maintainer reads the
|
|
72
|
+
matrix, **Then** they find the failing step's compensation followed by undo of steps two and one
|
|
73
|
+
(in that order), and that step four never runs.
|
|
74
|
+
2. **Given** a map whose fifth element fails after four elements succeeded, **When** the maintainer
|
|
75
|
+
reads the map section, **Then** they find whether the four succeeded elements are individually
|
|
76
|
+
rolled back, rolled back as a whole, or left in place — for both synchronous and asynchronous
|
|
77
|
+
map execution and for each failure-tolerance setting.
|
|
78
|
+
3. **Given** a parent reactor with two composed child reactors where the second child fails,
|
|
79
|
+
**When** the maintainer reads the composition section, **Then** they find whether the first
|
|
80
|
+
child's completed steps are undone, in what order relative to the parent's own steps, and whether
|
|
81
|
+
the second child's own completed steps are undone before the parent learns of the failure.
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
### User Story 2 - Answer the open questions explicitly (Priority: P1)
|
|
86
|
+
|
|
87
|
+
The maintainer finds a dedicated answer, with evidence, to each question raised in the input:
|
|
88
|
+
(a) are already-executed map elements compensated individually; (b) when a composed reactor fails,
|
|
89
|
+
are previously completed composed reactors compensated; (c) would a map-level "compensate all" /
|
|
90
|
+
"compensate each" entry point close a real gap.
|
|
91
|
+
|
|
92
|
+
**Why this priority**: These are the concrete doubts that triggered the research; they must be
|
|
93
|
+
answered unambiguously, not left implicit in a large matrix.
|
|
94
|
+
|
|
95
|
+
**Independent Test**: Read the "Answers" section in isolation; each question has a yes/no/depends
|
|
96
|
+
answer, the conditions under which it holds, and the evidence that supports it.
|
|
97
|
+
|
|
98
|
+
**Acceptance Scenarios**:
|
|
99
|
+
|
|
100
|
+
1. **Given** question (a), **When** the maintainer reads its answer, **Then** it states what
|
|
101
|
+
happens to completed elements when a later element fails and when a step *after* the map fails,
|
|
102
|
+
in every map execution mode.
|
|
103
|
+
2. **Given** question (c), **When** the maintainer reads its answer, **Then** it states whether the
|
|
104
|
+
gap exists today and what problem each proposed entry point would and would not solve.
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
### User Story 3 - Catalogue of invariants under locks, retries and failures (Priority: P2)
|
|
109
|
+
|
|
110
|
+
The maintainer reads a list of invariants — statements that must always hold about execution and
|
|
111
|
+
rollback (e.g. "a step whose lock was never acquired is never compensated", "retries are exhausted
|
|
112
|
+
before compensation starts", "a lock taken by a step is held while that step is undone") — each
|
|
113
|
+
marked as holding, violated, holding only under conditions, or undetermined, with evidence.
|
|
114
|
+
|
|
115
|
+
**Why this priority**: Locks, retries and asynchrony multiply the paths through the executor. A
|
|
116
|
+
tested catalogue of invariants is what makes future changes safe to review.
|
|
117
|
+
|
|
118
|
+
**Independent Test**: Pick any invariant marked "holds" and reproduce the scenario it names; the
|
|
119
|
+
observed behavior agrees. Pick any marked "violated" and reproduce the counter-example.
|
|
120
|
+
|
|
121
|
+
**Acceptance Scenarios**:
|
|
122
|
+
|
|
123
|
+
1. **Given** a step that is retried and ultimately exhausts its attempts, **When** the maintainer
|
|
124
|
+
reads the retry invariants, **Then** they learn whether compensation runs once or per attempt,
|
|
125
|
+
and whether it runs synchronously or when the last retry fails in the background.
|
|
126
|
+
2. **Given** a step protected by a lock that fails, **When** the maintainer reads the lock
|
|
127
|
+
invariants, **Then** they learn when the lock is released relative to the step's compensation
|
|
128
|
+
and to the undo of earlier steps.
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
### User Story 4 - Predictability and DSL clarity assessment (Priority: P2)
|
|
133
|
+
|
|
134
|
+
The maintainer reads a ranked list of findings where behavior is surprising, inconsistent between
|
|
135
|
+
execution modes (inline vs background), not visible from the reactor definition, or different from
|
|
136
|
+
what README/documentation claims.
|
|
137
|
+
|
|
138
|
+
**Why this priority**: The initiative's stated goal is judging whether "the DSL helps clearly
|
|
139
|
+
understand how everything is going to be executed". Findings are the bridge from facts to decisions.
|
|
140
|
+
|
|
141
|
+
**Independent Test**: Each finding names the scenario, the expected-by-a-reader behavior, the
|
|
142
|
+
actual behavior, and a severity; a reviewer can verify it without reading the rest of the report.
|
|
143
|
+
|
|
144
|
+
**Acceptance Scenarios**:
|
|
145
|
+
|
|
146
|
+
1. **Given** a documented claim about compensation or ordering, **When** the actual behavior
|
|
147
|
+
differs, **Then** the finding quotes the claim and the location it appears in.
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
### User Story 5 - Evidence-based improvement options (Priority: P3)
|
|
152
|
+
|
|
153
|
+
For each significant finding, the maintainer reads candidate remedies (including the proposed map
|
|
154
|
+
`compensate_all` / `compensate_each` entry points), each with trade-offs, compatibility impact and
|
|
155
|
+
open questions — explicitly **not** a decision.
|
|
156
|
+
|
|
157
|
+
**Why this priority**: The user wants suggestions to analyse later; they are useful but depend on
|
|
158
|
+
the facts produced by stories 1–4.
|
|
159
|
+
|
|
160
|
+
**Independent Test**: Each option references the finding(s) it addresses and states at least one
|
|
161
|
+
downside.
|
|
162
|
+
|
|
163
|
+
**Acceptance Scenarios**:
|
|
164
|
+
|
|
165
|
+
1. **Given** the map-compensation gap (if confirmed), **When** the maintainer reads its options,
|
|
166
|
+
**Then** they see at least the two proposed shapes compared on failure semantics, async
|
|
167
|
+
behavior, and interaction with retries and failure tolerance.
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
### Edge Cases
|
|
172
|
+
|
|
173
|
+
- The compensation or undo of a step itself fails (returns failure or raises): does rollback of
|
|
174
|
+
earlier steps continue, and what does the final result report?
|
|
175
|
+
- A failure occurs while an async step or async reactor is still pending, and the parent is
|
|
176
|
+
resumed later in a different process.
|
|
177
|
+
- A map configured to tolerate element failures (collect results instead of failing fast) versus
|
|
178
|
+
one that fails fast; partially dispatched async maps when the failure happens.
|
|
179
|
+
- A composed reactor nested inside a map, and a map nested inside a composed reactor.
|
|
180
|
+
- A step that fails because its lock/semaphore could not be acquired (never started) versus a step
|
|
181
|
+
whose body started and then failed.
|
|
182
|
+
- A step that returns a halt/skip signal instead of success or failure.
|
|
183
|
+
- A reactor that is paused (interrupt) and later resumed, then fails after resume: are steps
|
|
184
|
+
completed before the pause undone?
|
|
185
|
+
- A worker crashes mid-execution and the sweeper re-drives it: can any step run or be compensated
|
|
186
|
+
twice?
|
|
187
|
+
- Input validation failures on the reactor or a step (before any body runs).
|
|
188
|
+
- Steps that define neither compensation nor undo.
|
|
189
|
+
|
|
190
|
+
## Requirements *(mandatory)*
|
|
191
|
+
|
|
192
|
+
### Functional Requirements
|
|
193
|
+
|
|
194
|
+
- **FR-001**: The deliverable MUST inventory every execution construct in scope — plain step,
|
|
195
|
+
composed reactor, map (synchronous and asynchronous, fail-fast and failure-tolerant), async step,
|
|
196
|
+
async/background reactor, interrupt step — and describe each one's lifecycle from scheduling to
|
|
197
|
+
terminal state.
|
|
198
|
+
- **FR-002**: The deliverable MUST provide an execution-order matrix: for each construct and each
|
|
199
|
+
failure location (before, inside, after the construct; in a nested child), the ordered sequence
|
|
200
|
+
of forward runs, compensations and undos, and the list of completed work that is left in place.
|
|
201
|
+
- **FR-003**: The matrix MUST distinguish inline (same process) execution from background
|
|
202
|
+
execution wherever their ordering or rollback coverage differ.
|
|
203
|
+
- **FR-004**: The deliverable MUST cover the cross-cutting conditions: locks (reactor-level,
|
|
204
|
+
step-level, ordered locks, semaphores), retries (in-attempt, re-enqueued, exhausted), and failure
|
|
205
|
+
kinds (returned failure, raised error, input validation failure, coordination contention,
|
|
206
|
+
compensation/undo failure, timeout, crash and re-drive).
|
|
207
|
+
- **FR-005**: The deliverable MUST answer the three questions of User Story 2 in a dedicated
|
|
208
|
+
section with explicit conditions and evidence.
|
|
209
|
+
- **FR-006**: The deliverable MUST state invariants as testable propositions, each with a status
|
|
210
|
+
(holds / violated / conditional / undetermined) and supporting evidence.
|
|
211
|
+
- **FR-007**: Every behavioral claim MUST cite evidence: a source location, an existing automated
|
|
212
|
+
test, and/or a reproducible observation. Claims MUST be labelled with the kind of evidence that
|
|
213
|
+
supports them, and claims backed only by reading MUST be distinguishable from observed ones.
|
|
214
|
+
- **FR-008**: The deliverable MUST map each invariant to the existing automated tests that cover it
|
|
215
|
+
(or state that none do), so coverage gaps are visible.
|
|
216
|
+
- **FR-009**: The deliverable MUST list predictability/DSL-clarity findings, each with scenario,
|
|
217
|
+
expected-by-reader behavior, actual behavior, severity, and any contradicting documentation.
|
|
218
|
+
- **FR-010**: The deliverable MUST propose improvement options for significant findings, including
|
|
219
|
+
an evaluation of map-level `compensate_all` and `compensate_each`, with trade-offs and
|
|
220
|
+
compatibility impact, and MUST mark all options as proposals pending later analysis.
|
|
221
|
+
- **FR-011**: The initiative MUST NOT change library runtime behavior, the test suite, or the demo
|
|
222
|
+
application; its outputs are documentation only.
|
|
223
|
+
- **FR-012**: Ordering descriptions MUST be presented so a reader can follow them without reading
|
|
224
|
+
source (numbered sequences and/or diagrams per scenario).
|
|
225
|
+
|
|
226
|
+
### Key Entities
|
|
227
|
+
|
|
228
|
+
- **Execution construct**: a unit the reactor schedules (step, compose, map, async step, async
|
|
229
|
+
reactor, interrupt), with its lifecycle states and rollback hooks.
|
|
230
|
+
- **Scenario**: a reactor shape plus a failure location plus cross-cutting conditions (lock,
|
|
231
|
+
retry, execution mode).
|
|
232
|
+
- **Execution trace**: the ordered list of forward, compensation and undo events for a scenario.
|
|
233
|
+
- **Invariant**: a proposition about ordering or rollback, with status, evidence and test coverage.
|
|
234
|
+
- **Finding**: a place where behavior is unpredictable, inconsistent, invisible in the DSL, or
|
|
235
|
+
contradicts documentation; carries severity.
|
|
236
|
+
- **Improvement option**: a candidate remedy linked to findings, with trade-offs; not a decision.
|
|
237
|
+
|
|
238
|
+
## Success Criteria *(mandatory)*
|
|
239
|
+
|
|
240
|
+
### Measurable Outcomes
|
|
241
|
+
|
|
242
|
+
- **SC-001**: 100% of construct × failure-location cells in the matrix are filled with a sequence
|
|
243
|
+
or explicitly marked "not reachable", with no blank cells.
|
|
244
|
+
- **SC-002**: A maintainer unfamiliar with the executor internals can answer each of the three
|
|
245
|
+
input questions from the deliverable in under 5 minutes.
|
|
246
|
+
- **SC-003**: 100% of behavioral claims carry an evidence label; at least the headline claims for
|
|
247
|
+
maps, composition, async steps and async reactors (the four constructs named by the user) are
|
|
248
|
+
backed by a reproducible observation, not only by reading.
|
|
249
|
+
- **SC-004**: Every finding rated high severity has at least two improvement options, each with at
|
|
250
|
+
least one stated downside.
|
|
251
|
+
- **SC-005**: Zero changes to library runtime code, automated tests or demo application result
|
|
252
|
+
from this initiative.
|
|
253
|
+
|
|
254
|
+
## Assumptions
|
|
255
|
+
|
|
256
|
+
- "Compensation" follows the library's existing vocabulary: *compensate* is the failing step's own
|
|
257
|
+
cleanup, *undo* is the reverse-order rollback of previously completed steps. The analysis covers
|
|
258
|
+
both and refers to them together as "rollback".
|
|
259
|
+
- Interrupts (pause/resume) are in scope only where they change execution or rollback order; they
|
|
260
|
+
were not named by the user but affect "all conditions".
|
|
261
|
+
- Reproducible observations may be produced by throwaway probe reactors run against the project's
|
|
262
|
+
real storage; the probes and their recorded outputs are kept alongside the research documents as
|
|
263
|
+
evidence, not added to the library, test suite or demo app.
|
|
264
|
+
- README.md and ./documentation are audited for claims that contradict findings, but not edited:
|
|
265
|
+
documenting current behavior as contract before the follow-up decision would lock it in.
|
|
266
|
+
Documentation changes ship with whichever remedy is later chosen.
|
|
267
|
+
- Rate limits and periods are treated as a kind of coordination contention (like locks) and not
|
|
268
|
+
analysed separately unless they produce a distinct rollback path.
|
|
269
|
+
- The analysis reflects the current `main`-based code at the time of writing (after step-scoped
|
|
270
|
+
retry declarations and inputs protection landed).
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
# Tasks: Execution Flow & Compensation Analysis
|
|
2
|
+
|
|
3
|
+
**Input**: Design documents from `specs/007-execution-flow-analysis/`
|
|
4
|
+
|
|
5
|
+
**Prerequisites**: plan.md, spec.md, research.md, data-model.md, contracts/report-structure.md,
|
|
6
|
+
quickstart.md
|
|
7
|
+
|
|
8
|
+
**Tests**: No test tasks. This is documentation-only (FR-011). Evidence probes are research
|
|
9
|
+
artifacts under `evidence/` and check themselves (expected vs observed).
|
|
10
|
+
|
|
11
|
+
**Organization**: Tasks are grouped by user story. `FD` below = `specs/007-execution-flow-analysis`.
|
|
12
|
+
|
|
13
|
+
## Format: `[ID] [P?] [Story] Description`
|
|
14
|
+
|
|
15
|
+
- **[P]**: Can run in parallel (different files, no dependencies)
|
|
16
|
+
- **[Story]**: User story the task belongs to (US1–US5)
|
|
17
|
+
|
|
18
|
+
## Rules for every probe task
|
|
19
|
+
|
|
20
|
+
- Each probe is a `scenario "S-<area>-<nn>", "<shape>", mode:, expected: [...] do … end` block
|
|
21
|
+
(harness from T002). Step bodies call `rec("run:x")` / `rec("compensate:x")` / `rec("undo:x")`.
|
|
22
|
+
- `expected` starts as the hypothesis from research.md. If the probe prints `MISMATCH`, the
|
|
23
|
+
**observation wins**. Update `expected` to the observed sequence and record the deviation in the
|
|
24
|
+
report (a refuted hypothesis is a result, not a failure).
|
|
25
|
+
- Use `base_delay: 0` for retries. Drain async work with `drain` (harness).
|
|
26
|
+
- Never edit `lib/`, `spec/`, `demo_app/`, `README.md`, `documentation/`.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Phase 1: Setup
|
|
31
|
+
|
|
32
|
+
**Purpose**: Evidence harness able to run a scenario end to end against real Redis.
|
|
33
|
+
|
|
34
|
+
- [X] T001 Create `FD/analysis/` and `FD/evidence/probes/`. Confirm test Redis answers `PING` at
|
|
35
|
+
`redis://localhost:6780` (or `RUBY_REACTOR_TEST_REDIS_URL`) and that
|
|
36
|
+
`bundle exec ruby -e 'require "ruby_reactor"'` loads from the repo root.
|
|
37
|
+
- [X] T002 Create `FD/evidence/harness.rb`. Configure RubyReactor storage (redis URL from env,
|
|
38
|
+
default 6780) and `async_router = Adapters::Sidekiq::Router`. Put `Sidekiq::Testing.fake!` on.
|
|
39
|
+
Define a global event recorder `rec(event)`. Define a `Recorder` middleware (subclass of
|
|
40
|
+
`RubyReactor::Middleware`) that logs `lock_acquired`, `lock_released`, `semaphore_acquired`,
|
|
41
|
+
`semaphore_released`, `retry_attempt`, `start_compensation`, `start_undo`, and register it
|
|
42
|
+
globally. Define `scenario(id, title, mode:, expected:, &block)`: it FLUSHDBs, clears Sidekiq
|
|
43
|
+
jobs and the recorder, runs the block (the block returns the final result), appends
|
|
44
|
+
`=> success|failure(<step>)|halt|paused`, then prints the block format from
|
|
45
|
+
contracts/report-structure.md and tallies MATCH/MISMATCH. Define `drain` wrapping
|
|
46
|
+
`RubyReactor::RSpec::SidekiqHelpers.drain_async_jobs`, and `outcome(result)`.
|
|
47
|
+
- [X] T003 Create `FD/evidence/run.rb`. Require the harness and every `probes/*.rb` in sorted
|
|
48
|
+
order, honour the `PROBE=<substring>` filter, and print a final
|
|
49
|
+
`N scenarios, X match, Y mismatch` line.
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Phase 2: Foundational (blocking)
|
|
54
|
+
|
|
55
|
+
**Purpose**: Establish the plain-step rollback baseline that every other construct is described
|
|
56
|
+
against. Refuting it would change every later expectation.
|
|
57
|
+
|
|
58
|
+
- [X] T004 Create `FD/evidence/probes/01_plain.rb` covering research H1–H6.
|
|
59
|
+
`S-plain-01` a→b(fails)→c. `S-plain-02` b raises vs returns Failure. `S-plain-03` b's
|
|
60
|
+
compensate fails (prior undos still run? rollback_failures?). `S-plain-04` an undo fails
|
|
61
|
+
(remaining undos run?). `S-plain-05` b returns Halt (no rollback). `S-plain-06` a Skipped step
|
|
62
|
+
before the failure (not undone). `S-plain-07` an argument `transform` raises a StandardError
|
|
63
|
+
after a completed step (H5: is `a` undone?). `S-plain-08` an output validation failure (H6).
|
|
64
|
+
`S-plain-09` a DAG with two independent branches (undo order = completion order reversed).
|
|
65
|
+
Run `PROBE=plain`, fix expectations until they MATCH, and save the transcript.
|
|
66
|
+
- [X] T005 Write the "Rollback algorithm" section of `FD/analysis/execution-order.md` (generic
|
|
67
|
+
compensate → reverse-undo, never-started exception, Halt/Skipped, error classes that skip
|
|
68
|
+
rollback) with a Mermaid flow and `[R]`/`[O: S-plain-*]` labels.
|
|
69
|
+
|
|
70
|
+
**Checkpoint**: Baseline confirmed. Construct probes can now be written in parallel.
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Phase 3: User Story 1 — Exact rollback order for any failure (P1) 🎯 MVP
|
|
75
|
+
|
|
76
|
+
**Goal**: A per-construct order matrix with no blank cells (FR-001/002/003/012, SC-001).
|
|
77
|
+
|
|
78
|
+
**Independent Test**: Pick any matrix row, run its probe id with `PROBE=<id>`, and see `MATCH`.
|
|
79
|
+
|
|
80
|
+
- [X] T006 [P] [US1] Create `FD/evidence/probes/02_compose.rb` (H10–H13). `S-compose-01` parent
|
|
81
|
+
a → compose(c1→c2 fails) → b. `S-compose-02` a → compose(c1→c2) → b fails (compose undone ⇒
|
|
82
|
+
c2, c1 undone). `S-compose-03` compose X(x1,x2) → compose Y(y1→y2 fails) (**Q2**: X's steps
|
|
83
|
+
undone?). `S-compose-04` compose nested two levels, innermost fails. `S-compose-05` compose with
|
|
84
|
+
`retries max_attempts: 2`, child c2 fails once then succeeds (H12: are undone c1 results reused
|
|
85
|
+
without re-running c1?). `S-compose-06` compose inside background worker (`background all:`),
|
|
86
|
+
failure after it.
|
|
87
|
+
- [X] T007 [P] [US1] Create `FD/evidence/probes/03_map.rb` (H14–H18). Element reactor has steps
|
|
88
|
+
e1→e2 with undo on both. `S-map-01` inline fail_fast, element 2 of 4 fails (**Q1**: are elements
|
|
89
|
+
0–1 undone?). `S-map-02` inline `fail_fast false`, one element fails. `S-map-03` inline map ok →
|
|
90
|
+
next parent step fails (map elements undone?). `S-map-04` fan-out fail_fast, one element fails
|
|
91
|
+
(drain; which elements ran, which were rolled back, parent rollback). `S-map-05` fan-out
|
|
92
|
+
`fail_fast false`. `S-map-06` fan-out ok → next parent step fails. `S-map-07` map inside a
|
|
93
|
+
composed child, element fails. `S-map-08` element reactor that composes a child, element fails.
|
|
94
|
+
- [X] T008 [P] [US1] Create `FD/evidence/probes/04_async.rb` (H19–H22). `S-async-01` async_step
|
|
95
|
+
fails, no reader (parent outcome? step's compensate?). `S-async-02` async_step fails, reader
|
|
96
|
+
`fail!`s (whose compensate runs? async_step's compensate/undo?). `S-async-03` async_step
|
|
97
|
+
succeeds, later parent step fails (async_step undone?). `S-async-04` async_reactor child fails
|
|
98
|
+
(child's own rollback, parent unaffected). `S-async-05` async_reactor child fails, reader
|
|
99
|
+
`fail!`s. `S-async-06` async_reactor child succeeds, parent fails later (child undone?).
|
|
100
|
+
`S-async-07` async_step with `retries max_attempts: 3` always failing (attempt count,
|
|
101
|
+
compensate calls).
|
|
102
|
+
- [X] T009 [P] [US1] Create `FD/evidence/probes/05_background.rb` (H8, H23). `S-bg-01`
|
|
103
|
+
`background all: true`, step 3 fails (same order as inline?). `S-bg-02` `background after: :a`,
|
|
104
|
+
worker step c fails (is caller-side a undone?). `S-bg-03` `background all:` with a retrying step
|
|
105
|
+
that exhausts (re-enqueue per attempt, single compensate at the end). `S-bg-04` `background all:`
|
|
106
|
+
with a retrying step that succeeds on attempt 2 (no compensate at all).
|
|
107
|
+
- [X] T010 [US1] Run `bundle exec ruby FD/evidence/run.rb | tee FD/evidence/output.txt`. Reconcile
|
|
108
|
+
every MISMATCH in T006–T009 (observation wins) and re-run until clean. Note refuted hypotheses
|
|
109
|
+
for the report.
|
|
110
|
+
- [X] T011 [US1] Write "Construct lifecycles" in `FD/analysis/execution-order.md`: step, compose,
|
|
111
|
+
map inline, map fan-out (dispatcher → element executor → collector → parent resume),
|
|
112
|
+
async_step (dispatch → StepWorker → record → reader), async_reactor, background reactor,
|
|
113
|
+
interrupt. Each is a numbered lifecycle saying where rollback hooks attach and where locks are
|
|
114
|
+
held, with `[R]` citations.
|
|
115
|
+
- [X] T012 [US1] Write the "Order matrix" tables in `FD/analysis/execution-order.md`, one table per
|
|
116
|
+
construct, with columns per contracts/report-structure.md. Fill every cell from `output.txt`
|
|
117
|
+
(`[O]`) or reading (`[R]`, labelled *by reading*). Mark unreachable combinations as
|
|
118
|
+
`not reachable: <reason>`.
|
|
119
|
+
|
|
120
|
+
**Checkpoint**: US1 complete. The matrix alone answers "what runs, in what order, what is left in
|
|
121
|
+
place".
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## Phase 4: User Story 2 — Explicit answers to the open questions (P1)
|
|
126
|
+
|
|
127
|
+
**Goal**: The three questions each answered in under 5 minutes of reading (FR-005, SC-002).
|
|
128
|
+
|
|
129
|
+
**Independent Test**: Read `FD/analysis/README.md` "Answers" alone. Each has a verdict line,
|
|
130
|
+
conditions, evidence and links.
|
|
131
|
+
|
|
132
|
+
- [X] T013 [US2] Write `FD/analysis/README.md`: scope & baseline, how to read (vocabulary,
|
|
133
|
+
labels, scales), **Answers** Q1 (map elements), Q2 (earlier composed reactors), Q3
|
|
134
|
+
(compensate_all/compensate_each gap: gap exists? what each shape would/would not fix, with a
|
|
135
|
+
forward link to options). Include per-mode conditions (inline vs fan-out, fail_fast on/off),
|
|
136
|
+
and a file index. Top-findings list is filled in T019.
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## Phase 5: User Story 3 — Invariants under locks, retries, failures (P2)
|
|
141
|
+
|
|
142
|
+
**Goal**: Testable invariant catalogue with status, evidence, coverage (FR-006, FR-008).
|
|
143
|
+
|
|
144
|
+
**Independent Test**: Re-run any `[O]` scenario an invariant cites. A `VIOLATED` one reproduces
|
|
145
|
+
its counter-example.
|
|
146
|
+
|
|
147
|
+
- [X] T014 [P] [US3] Create `FD/evidence/probes/06_coordination.rb` (H2, H7, H24, H25).
|
|
148
|
+
`S-lock-01` reactor `with_lock`, step fails (lock_released after last undo?). `S-lock-02`
|
|
149
|
+
step-class `with_lock` on b, c fails (b's lock released after run, re-acquired around undo:b).
|
|
150
|
+
`S-lock-03` step lock pre-held by another owner, sync run (b never started ⇒ no compensate:b,
|
|
151
|
+
a undone). `S-lock-04` step `with_semaphore limit: 1` same as 02. `S-retry-01` b retries 3× then
|
|
152
|
+
fails (3 run:b, then one compensate:b). `S-retry-02` `fail!(…, retry: false)` inside a retrying
|
|
153
|
+
step (no further attempts). `S-retry-03` a retrying step that succeeds on attempt 2 (no
|
|
154
|
+
compensate).
|
|
155
|
+
- [X] T015 [P] [US3] Create `FD/evidence/probes/07_interrupts_manual.rb` (H26–H28). `S-intr-01`
|
|
156
|
+
a → interrupt → c fails after `continue` (a undone?). `S-intr-02` interrupt payload validation
|
|
157
|
+
exhausting `max_attempts` (undo of a?). `S-intr-03` `Reactor.cancel` on a paused reactor (no
|
|
158
|
+
rollback). `S-intr-04` `Reactor.undo(id)` on a completed reactor (reverse undo, reactor lock
|
|
159
|
+
taken or not?).
|
|
160
|
+
- [X] T016 [US3] Re-run the full probe set, update `FD/evidence/output.txt`, reconcile mismatches.
|
|
161
|
+
- [X] T017 [US3] Map each invariant to existing specs: grep `spec/` (e.g. `compensation_*`,
|
|
162
|
+
`undo_spec`, `compose_spec`, `map/*`, `step_coordination/*`, `retry_*`, `interrupt_*`,
|
|
163
|
+
`async_*`) and record `[T: path:line]` or `none`.
|
|
164
|
+
- [X] T018 [US3] Write `FD/analysis/invariants.md`: `INV-nn` entries grouped by area (ordering,
|
|
165
|
+
rollback coverage, never-started, retries, locks, async isolation, interrupts/manual,
|
|
166
|
+
crash/re-drive), each with all data-model fields, ending with the coverage summary (counts by
|
|
167
|
+
status, list of `coverage: none`).
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## Phase 6: User Story 4 — Predictability & DSL-clarity findings (P2)
|
|
172
|
+
|
|
173
|
+
**Goal**: A ranked list of findings plus the documentation audit (FR-009). This is the
|
|
174
|
+
constitution's documentation task (plan.md Complexity Tracking).
|
|
175
|
+
|
|
176
|
+
**Independent Test**: Each finding stands alone: scenario, reader expectation, actual, severity,
|
|
177
|
+
doc conflict.
|
|
178
|
+
|
|
179
|
+
- [X] T019 [US4] Write the "Findings" section of `FD/analysis/findings-and-options.md`: `F-nn`
|
|
180
|
+
ranked High → Low with all data-model fields, derived from VIOLATED/CONDITIONAL invariants,
|
|
181
|
+
refuted hypotheses and DSL gaps (e.g. map has no compensate/undo/retries surface, compose has no
|
|
182
|
+
compensate/undo hook, async unit compensate blocks never run). Then fill the Top-findings list
|
|
183
|
+
in `FD/analysis/README.md`.
|
|
184
|
+
- [X] T020 [US4] Write the "Documentation audit" table in `FD/analysis/findings-and-options.md`:
|
|
185
|
+
every README.md / `documentation/*.md` claim about ordering or rollback that is contradicted,
|
|
186
|
+
incomplete or unstated. Cover README "Error Handling and Compensation", async_step/async_reactor
|
|
187
|
+
sections, `documentation/composition.md` compose-vs-async_reactor table,
|
|
188
|
+
`documentation/background_and_async.md` "Compensation is opt-in" and "Error Handling and
|
|
189
|
+
Compensation", `documentation/data_pipelines.md` fail_fast, `documentation/core_concepts.md`
|
|
190
|
+
"Compensation Order", `documentation/DAG.md`. Quote the claim and give `file:line`. **No edits to
|
|
191
|
+
those files.**
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## Phase 7: User Story 5 — Improvement options (P3)
|
|
196
|
+
|
|
197
|
+
**Goal**: Proposals only, linked to findings (FR-010, SC-004).
|
|
198
|
+
|
|
199
|
+
**Independent Test**: Every option names its finding(s) and at least one con. Every High finding
|
|
200
|
+
has ≥2 options.
|
|
201
|
+
|
|
202
|
+
- [X] T021 [US5] Write the "Options" section of `FD/analysis/findings-and-options.md`. Include the
|
|
203
|
+
required evaluation of map `compensate_all` vs `compensate_each` (and alternatives, e.g.
|
|
204
|
+
element-reactor undo replay / MapStep#undo over stored element contexts), compared on
|
|
205
|
+
fail_fast on/off, inline vs fan-out, element retries, a later-step failure, and data
|
|
206
|
+
availability. Give ≥2 options per High finding, each with compatibility impact and open
|
|
207
|
+
questions. Close with the "proposals pending later analysis" note.
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
## Phase 8: Polish & Cross-Cutting
|
|
212
|
+
|
|
213
|
+
- [X] T022 Run the quickstart.md validation. Check for no `TBD|TODO|???` in `FD/analysis/*.md`.
|
|
214
|
+
Check that every `[O: S-…]` label resolves to a block in `FD/evidence/output.txt`. Check that
|
|
215
|
+
every High finding has ≥2 options with cons. Fix gaps.
|
|
216
|
+
- [X] T023 Documentation task (REQUIRED, Constitution Development Workflow). Confirm T020's audit
|
|
217
|
+
covers README.md and each relevant `./documentation` file. Per plan.md Complexity Tracking,
|
|
218
|
+
README/documentation are **not edited**: behavior is unchanged, and doc updates ship with the
|
|
219
|
+
chosen remedy. Record that decision in `FD/analysis/README.md` scope.
|
|
220
|
+
- [X] T024 Verify zero product diff: `git status --porcelain` / `git diff --stat` show changes
|
|
221
|
+
only under `FD/`, `CLAUDE.md` (agent pointer) and `.specify/feature.json` (SC-005, FR-011).
|
|
222
|
+
- [X] T025 Mark all tasks complete in `FD/tasks.md`.
|
|
223
|
+
|
|
224
|
+
**Constitution Principle VI**: N/A. No public API or user-facing behavior change, so no demo
|
|
225
|
+
reactor, rake task or demo spec. Any remedy chosen later carries its own.
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
## Dependencies & Execution Order
|
|
230
|
+
|
|
231
|
+
- **Setup (T001–T003)** → **Foundational (T004–T005)** → user stories.
|
|
232
|
+
- **US1 (T006–T012)**: T006–T009 in parallel (separate probe files). T010 after all four.
|
|
233
|
+
T011–T012 after T010.
|
|
234
|
+
- **US2 (T013)**: after US1 (answers cite matrix rows).
|
|
235
|
+
- **US3 (T014–T018)**: T014/T015 can start right after Foundational, in parallel with US1 probes.
|
|
236
|
+
T016 after T014/T015 (and T010, since it re-runs everything). T017 is independent. T018 after
|
|
237
|
+
T016+T017.
|
|
238
|
+
- **US4 (T019–T020)**: after US1 and US3 (findings derive from matrix + invariants). T020 can
|
|
239
|
+
start any time (reading only).
|
|
240
|
+
- **US5 (T021)**: after T019.
|
|
241
|
+
- **Polish (T022–T025)**: last.
|
|
242
|
+
|
|
243
|
+
### Parallel Example
|
|
244
|
+
|
|
245
|
+
```text
|
|
246
|
+
# After T005:
|
|
247
|
+
T006 02_compose.rb T007 03_map.rb T008 04_async.rb T009 05_background.rb
|
|
248
|
+
T014 06_coordination.rb T015 07_interrupts_manual.rb T017 spec coverage grep T020 doc audit
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
## Implementation Strategy
|
|
252
|
+
|
|
253
|
+
1. **MVP = Setup + Foundational + US1 + US2**: the matrix plus the three direct answers already
|
|
254
|
+
settle the user's immediate doubts (maps, compose).
|
|
255
|
+
2. Add US3 (invariants) for review-safety of future changes.
|
|
256
|
+
3. Add US4 + US5 (findings, audit, options) as input to the follow-up decision.
|
|
257
|
+
4. Probes stay re-runnable, so any later remedy can re-run them and compare before/after.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Specification Quality Checklist: Reliable Rollback Across Constructs
|
|
2
|
+
|
|
3
|
+
**Purpose**: Validate specification completeness and quality before proceeding to planning
|
|
4
|
+
**Created**: 2026-09-26
|
|
5
|
+
**Revised**: 2026-09-27 (PR #65 review)
|
|
6
|
+
**Feature**: [spec.md](../spec.md)
|
|
7
|
+
|
|
8
|
+
## Content Quality
|
|
9
|
+
|
|
10
|
+
- [x] No implementation details (languages, frameworks, APIs)
|
|
11
|
+
- [x] Focused on user value and business needs
|
|
12
|
+
- [x] Written for non-technical stakeholders
|
|
13
|
+
- [x] All mandatory sections completed
|
|
14
|
+
|
|
15
|
+
## Requirement Completeness
|
|
16
|
+
|
|
17
|
+
- [x] No [NEEDS CLARIFICATION] markers remain (FR-029 defaulted to option A, 2026-09-27)
|
|
18
|
+
- [x] Requirements are testable and unambiguous
|
|
19
|
+
- [x] Success criteria are measurable
|
|
20
|
+
- [x] Success criteria are technology-agnostic (no implementation details)
|
|
21
|
+
- [x] All acceptance scenarios are defined
|
|
22
|
+
- [x] Edge cases are identified
|
|
23
|
+
- [x] Scope is clearly bounded
|
|
24
|
+
- [x] Dependencies and assumptions identified
|
|
25
|
+
|
|
26
|
+
## Feature Readiness
|
|
27
|
+
|
|
28
|
+
- [x] All functional requirements have clear acceptance criteria
|
|
29
|
+
- [x] User scenarios cover primary flows
|
|
30
|
+
- [x] Feature meets measurable outcomes defined in Success Criteria
|
|
31
|
+
- [x] No implementation details leak into specification
|
|
32
|
+
|
|
33
|
+
## Notes
|
|
34
|
+
|
|
35
|
+
- The product is a library, so its "stakeholders" are reactor authors and maintainers. The DSL
|
|
36
|
+
words used (`map`, `compose`, `async_reactor`, `async_step`, `retries`, `where`/`guard`,
|
|
37
|
+
`Skipped`, `compensate`/`undo`) are the public vocabulary, not implementation internals. No file
|
|
38
|
+
paths, classes or storage mechanisms appear in the requirements.
|
|
39
|
+
- Success criteria cite the 007 invariants and evidence set. They are the agreed, reproducible
|
|
40
|
+
baseline, not technology choices.
|
|
41
|
+
- 2026-09-27 revision: FR-009, FR-010, FR-014, FR-016, FR-018 revised; FR-028, FR-029 and US6 added.
|
|
42
|
+
FR numbers kept stable so plan, research and tasks references still resolve.
|
|
43
|
+
- Items marked incomplete require spec updates before `/speckit-clarify` or `/speckit-plan`
|