jazari 0.5.2 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +23 -0
- data/lib/jazari/errors.rb +7 -0
- data/lib/jazari/runs.rb +59 -4
- data/lib/jazari/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0c3e5b0a2641a7939f7353e3bec9cb18336272f6e0a9f333155cc36a09e888cd
|
|
4
|
+
data.tar.gz: cf778b406814acf997a9716bb5128dce9070d934d5fb61912906f6f5534f5eb0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4985319ede9e2196979829901c3184e1f1ddba0ba2191b64ac9e4e3b2d498bd5cd568bf8d43664a3a2c6440fc3327ae7e8e940c3fa4319fad94529a542bb1eb9
|
|
7
|
+
data.tar.gz: f90b41f05765d28295d8ba704eba10ba92f730bcc246b30b879ed3fd4db2dee9476e2c08febda3137948b11986579ef0daa6c0e0fcd25501937f8ca3e3295b89
|
data/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,29 @@ codes, the resolved-value shape, how revisions are computed, and the schema the
|
|
|
8
8
|
generator emits — changes to any of those are breaking even when the method
|
|
9
9
|
signatures do not move.
|
|
10
10
|
|
|
11
|
+
## [0.6.0] - 2026-08-15
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- **A step discovered mid-run is legal.** `tick` now accepts an item that was
|
|
16
|
+
added to the subject's own runbook after the run opened: it widens the run's
|
|
17
|
+
`checklist_snapshot` to carry the item and marks both the snapshot entry and
|
|
18
|
+
the tick with `post_snapshot: true`. Reported from a production deploy where
|
|
19
|
+
a host's canonical `check_item` had already committed when `tick` raised —
|
|
20
|
+
the caller was told a write failed while it stood, and retrying a
|
|
21
|
+
half-applied two-phase write is how a double record happens.
|
|
22
|
+
|
|
23
|
+
A **recipe** change mid-run is still refused. Those are different events: the
|
|
24
|
+
canon moving underneath an in-flight run is someone else's edit arriving
|
|
25
|
+
uninvited, and that is what the snapshot exists to hold out. A queue run has
|
|
26
|
+
no subject, so its checklist is the recipe and it never widens.
|
|
27
|
+
|
|
28
|
+
- `Jazari::ItemNotInSnapshot` (code `item_not_in_snapshot`), raised when the
|
|
29
|
+
item exists in the canon but post-dates the run's snapshot. It subclasses
|
|
30
|
+
`ItemNotFound`, so hosts rescuing the old class keep working, and hosts that
|
|
31
|
+
need to choose between failing, retrying and proceeding stop matching on the
|
|
32
|
+
message text.
|
|
33
|
+
|
|
11
34
|
## [0.5.2] - 2026-08-12
|
|
12
35
|
|
|
13
36
|
### Fixed
|
data/lib/jazari/errors.rb
CHANGED
|
@@ -12,6 +12,13 @@ module Jazari
|
|
|
12
12
|
class InvalidRunbook < Error; end
|
|
13
13
|
class RevisionConflict < Error; end
|
|
14
14
|
class ItemNotFound < Error; end
|
|
15
|
+
|
|
16
|
+
# The item exists — it just post-dates the snapshot this run froze at open.
|
|
17
|
+
# A subclass, so a host that already rescues ItemNotFound keeps working, and
|
|
18
|
+
# one that wants to tell "no such step" from "not this run's step" can.
|
|
19
|
+
# Without the distinction a caller cannot choose between failing, retrying,
|
|
20
|
+
# and proceeding, and ends up matching on prose.
|
|
21
|
+
class ItemNotInSnapshot < ItemNotFound; end
|
|
15
22
|
class ReadOnlyTarget < Error; end
|
|
16
23
|
class RunClosed < Error; end
|
|
17
24
|
end
|
data/lib/jazari/runs.rb
CHANGED
|
@@ -85,12 +85,15 @@ module Jazari
|
|
|
85
85
|
end
|
|
86
86
|
|
|
87
87
|
known = snapshot.map { |item| item["id"] }
|
|
88
|
-
|
|
88
|
+
late = admit_late_item!(record, snapshot, item_id) unless known.include?(item_id.to_s)
|
|
89
|
+
record.checklist_snapshot = snapshot + [ late ] if late
|
|
89
90
|
|
|
90
91
|
ticks = stored(record.ticks).reject { |t| t["id"] == item_id.to_s }
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
92
|
+
tick = { "id" => item_id.to_s, "done" => done == true, "at" => now.utc.iso8601,
|
|
93
|
+
"actor_ref" => resolve_actor_ref(actor_ref, fallback: record.actor_ref),
|
|
94
|
+
"note" => note&.to_s }
|
|
95
|
+
tick["post_snapshot"] = true if late
|
|
96
|
+
ticks << tick
|
|
94
97
|
record.ticks = ticks
|
|
95
98
|
end
|
|
96
99
|
end
|
|
@@ -158,6 +161,58 @@ module Jazari
|
|
|
158
161
|
end
|
|
159
162
|
private_class_method :mutate
|
|
160
163
|
|
|
164
|
+
# A step discovered mid-run is the ordinary case, not an anomaly: a deploy
|
|
165
|
+
# is exactly when a missing step is found. Refusing the tick pushes that
|
|
166
|
+
# work outside the record, which is the one thing the record exists to
|
|
167
|
+
# provide — so the snapshot WIDENS, and says that it did.
|
|
168
|
+
#
|
|
169
|
+
# It widens from the SUBJECT'S OWN runbook only, never from the recipe.
|
|
170
|
+
# Those are different events wearing the same shape:
|
|
171
|
+
#
|
|
172
|
+
# * someone performing this run added a step to this subject — legal, and
|
|
173
|
+
# the run should carry it;
|
|
174
|
+
# * the canon moved underneath an in-flight run — not legal, and the
|
|
175
|
+
# snapshot exists precisely to hold that line (see `open`).
|
|
176
|
+
#
|
|
177
|
+
# A queue run has no subject, so its checklist IS the recipe and it can
|
|
178
|
+
# never widen. That is the correct answer there, not a limitation.
|
|
179
|
+
def admit_late_item!(record, snapshot, item_id)
|
|
180
|
+
id = item_id.to_s
|
|
181
|
+
item = subject_checklist(record).find { |i| i["id"] == id }
|
|
182
|
+
unless item
|
|
183
|
+
# Distinguish "no such step anywhere" from "exists in the canon, but
|
|
184
|
+
# this run froze before it did" — the caller's response differs.
|
|
185
|
+
raise ItemNotInSnapshot, "checklist item #{item_id} post-dates run #{record.id}'s snapshot" if
|
|
186
|
+
recipe_item?(record, id)
|
|
187
|
+
|
|
188
|
+
raise ItemNotFound, "unknown checklist item #{item_id}"
|
|
189
|
+
end
|
|
190
|
+
raise InvalidRunbook, "run #{record.id} snapshot exceeds #{Checklist::MAX_ITEMS} items" if
|
|
191
|
+
snapshot.length >= Checklist::MAX_ITEMS
|
|
192
|
+
|
|
193
|
+
# Marked, so a reader can tell what the run opened against from what it
|
|
194
|
+
# picked up along the way. Widening silently would make the snapshot a
|
|
195
|
+
# record of the present, which is the opposite of its job.
|
|
196
|
+
item.merge("post_snapshot" => true)
|
|
197
|
+
end
|
|
198
|
+
private_class_method :admit_late_item!
|
|
199
|
+
|
|
200
|
+
def subject_checklist(record)
|
|
201
|
+
return [] unless record.subject_type && record.subject_id
|
|
202
|
+
|
|
203
|
+
runbook = Runbook.find_by(runbookable_type: record.subject_type,
|
|
204
|
+
runbookable_id: record.subject_id)
|
|
205
|
+
stored(runbook&.checklist)
|
|
206
|
+
end
|
|
207
|
+
private_class_method :subject_checklist
|
|
208
|
+
|
|
209
|
+
def recipe_item?(record, id)
|
|
210
|
+
RecipeRegistry.fetch(record.recipe_id).checklist.any? { |i| i[:id].to_s == id }
|
|
211
|
+
rescue Error
|
|
212
|
+
false
|
|
213
|
+
end
|
|
214
|
+
private_class_method :recipe_item?
|
|
215
|
+
|
|
161
216
|
def find_days_run(attributes)
|
|
162
217
|
Run.where(
|
|
163
218
|
recipe_id: attributes[:recipe_id],
|
data/lib/jazari/version.rb
CHANGED