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.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/.release-please-manifest.json +1 -1
  3. data/.specify/feature.json +1 -1
  4. data/CHANGELOG.md +196 -0
  5. data/CLAUDE.md +1 -1
  6. data/README.md +47 -11
  7. data/lib/ruby_reactor/dsl/async_macros.rb +30 -1
  8. data/lib/ruby_reactor/dsl/async_reactor_builder.rb +12 -6
  9. data/lib/ruby_reactor/dsl/compose_builder.rb +12 -6
  10. data/lib/ruby_reactor/dsl/interrupt_builder.rb +1 -3
  11. data/lib/ruby_reactor/dsl/map_builder.rb +0 -2
  12. data/lib/ruby_reactor/dsl/step_builder.rb +91 -19
  13. data/lib/ruby_reactor/error/argument_resolution_error.rb +19 -0
  14. data/lib/ruby_reactor/error/rescuable.rb +28 -0
  15. data/lib/ruby_reactor/executor/compensation_manager.rb +30 -26
  16. data/lib/ruby_reactor/executor/result_handler.rb +18 -16
  17. data/lib/ruby_reactor/executor/step_coordination.rb +11 -8
  18. data/lib/ruby_reactor/executor/step_executor.rb +59 -49
  19. data/lib/ruby_reactor/executor.rb +38 -4
  20. data/lib/ruby_reactor/map/collector.rb +21 -11
  21. data/lib/ruby_reactor/map/dispatcher.rb +29 -3
  22. data/lib/ruby_reactor/map/element_executor.rb +9 -3
  23. data/lib/ruby_reactor/map/helpers.rb +32 -2
  24. data/lib/ruby_reactor/map/result_enumerator.rb +18 -12
  25. data/lib/ruby_reactor/reactor.rb +24 -0
  26. data/lib/ruby_reactor/rspec/matchers.rb +19 -3
  27. data/lib/ruby_reactor/step/compose_step.rb +7 -1
  28. data/lib/ruby_reactor/step/map_step.rb +109 -4
  29. data/lib/ruby_reactor/step.rb +7 -0
  30. data/lib/ruby_reactor/step_worker.rb +46 -22
  31. data/lib/ruby_reactor/storage/adapter.rb +4 -0
  32. data/lib/ruby_reactor/storage/redis_adapter.rb +9 -0
  33. data/lib/ruby_reactor/storage/redis_reactor_scan.rb +1 -1
  34. data/lib/ruby_reactor/version.rb +1 -1
  35. data/lib/ruby_reactor/web/api.rb +1 -1
  36. data/lib/ruby_reactor/web/public/assets/{index-CeZU-ESu.js → index-CQbgHtd0.js} +10 -10
  37. data/lib/ruby_reactor/web/public/index.html +1 -1
  38. data/lib/ruby_reactor/worker.rb +3 -1
  39. data/lib/ruby_reactor.rb +17 -6
  40. data/specs/007-execution-flow-analysis/analysis/README.md +147 -0
  41. data/specs/007-execution-flow-analysis/analysis/execution-order.md +359 -0
  42. data/specs/007-execution-flow-analysis/analysis/findings-and-options.md +502 -0
  43. data/specs/007-execution-flow-analysis/analysis/invariants.md +109 -0
  44. data/specs/007-execution-flow-analysis/checklists/requirements.md +39 -0
  45. data/specs/007-execution-flow-analysis/contracts/report-structure.md +71 -0
  46. data/specs/007-execution-flow-analysis/data-model.md +83 -0
  47. data/specs/007-execution-flow-analysis/evidence/harness.rb +229 -0
  48. data/specs/007-execution-flow-analysis/evidence/output.txt +333 -0
  49. data/specs/007-execution-flow-analysis/evidence/probes/01_plain.rb +122 -0
  50. data/specs/007-execution-flow-analysis/evidence/probes/02_compose.rb +182 -0
  51. data/specs/007-execution-flow-analysis/evidence/probes/03_map.rb +232 -0
  52. data/specs/007-execution-flow-analysis/evidence/probes/04_async.rb +132 -0
  53. data/specs/007-execution-flow-analysis/evidence/probes/05_background.rb +58 -0
  54. data/specs/007-execution-flow-analysis/evidence/probes/06_coordination.rb +158 -0
  55. data/specs/007-execution-flow-analysis/evidence/probes/07_interrupts_manual.rb +185 -0
  56. data/specs/007-execution-flow-analysis/evidence/run.rb +15 -0
  57. data/specs/007-execution-flow-analysis/plan.md +127 -0
  58. data/specs/007-execution-flow-analysis/quickstart.md +51 -0
  59. data/specs/007-execution-flow-analysis/research.md +202 -0
  60. data/specs/007-execution-flow-analysis/spec.md +270 -0
  61. data/specs/007-execution-flow-analysis/tasks.md +257 -0
  62. data/specs/008-rollback-reliability/checklists/requirements.md +43 -0
  63. data/specs/008-rollback-reliability/contracts/api-surface.md +126 -0
  64. data/specs/008-rollback-reliability/contracts/rollback-semantics.md +76 -0
  65. data/specs/008-rollback-reliability/data-model.md +139 -0
  66. data/specs/008-rollback-reliability/plan.md +233 -0
  67. data/specs/008-rollback-reliability/quickstart.md +105 -0
  68. data/specs/008-rollback-reliability/research.md +653 -0
  69. data/specs/008-rollback-reliability/spec.md +561 -0
  70. data/specs/008-rollback-reliability/tasks.md +1110 -0
  71. data/specs/future_improvements.md +48 -0
  72. 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`