Module: Jazari::Operations

Defined in:
lib/jazari/operations.rb

Overview

The runbook half: resolving a subject's operating truth and customizing it.

Reading a default writes NOTHING — no row, no anchor, no audit noise. The first customization materializes a record; reset destroys it and reveals the current canon again, so a recipe correction reaches every subject that never overrode it.

Constant Summary collapse

MAX_TOPIC =
120
MAX_DESCRIPTION =
20_000
KEEP_ORIGIN =

origin distinguishes REWRITING the procedure from PERFORMING it, so the sentinel is not cosmetic. Rewriting it (customize) restates why the row exists, and passing nil there is an operator claiming it as their own. Ticking an item is doing the work the row already describes, and must leave that claim alone — otherwise the first person to check a box silently converts a migration artifact into a deliberate divergence.

:keep

Class Method Summary collapse

Class Method Details

.add_item(target:, expected_revision:, text:, required: true) ⇒ Object



38
39
40
41
42
43
44
45
46
47
# File 'lib/jazari/operations.rb', line 38

def add_item(target:, expected_revision:, text:, required: true)
  writable!(target)
  write(target, expected_revision) do |current|
    items = current[:checklist] + [
      { id: Checklist.generate_id, text: text.to_s, done: false, required: required == true }
    ]
    Checklist.validate!(items)
    current.merge(checklist: items)
  end
end

.check_item(target:, expected_revision:, item_id:, done:) ⇒ Object



59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/jazari/operations.rb', line 59

def check_item(target:, expected_revision:, item_id:, done:)
  writable!(target)
  write(target, expected_revision) do |current|
    found = false
    items = current[:checklist].map do |item|
      next item unless item[:id] == item_id.to_s

      found = true
      item.merge(done: done == true)
    end
    raise ItemNotFound, "unknown checklist item #{item_id}" unless found

    current.merge(checklist: items)
  end
end

.customize(target:, expected_revision:, topic:, description:, checklist:, origin: nil) ⇒ Object

origin records WHY this row exists, for hosts that materialize runbooks themselves — a backfill, an import, a template. Leave it nil for an operator edit, which is the default because that is the ordinary case.



29
30
31
32
33
34
35
36
# File 'lib/jazari/operations.rb', line 29

def customize(target:, expected_revision:, topic:, description:, checklist:, origin: nil)
  writable!(target)
  validate_document!(topic, description)
  items = Checklist.normalize(checklist)
  write(target, expected_revision, origin: origin) do |current|
    current.merge(topic: topic, description: description, checklist: items)
  end
end

.forget_subject(subject) ⇒ Object

A host calls this from its own after-commit when a subject is destroyed.

Jazari cannot hook the host's model itself: the subject may live in a different logical database, so no cross-database foreign key is claimed and no cascade exists. The host calls in; the gem cleans up.

Runs are deliberately PRESERVED. A run is an audit record of something that actually happened, and deleting the subject does not un-happen it. Their subject columns keep pointing at the departed record — which is sound precisely because no FK was ever claimed.



101
102
103
104
105
106
107
# File 'lib/jazari/operations.rb', line 101

def forget_subject(subject)
  runbook = Runbook.find_by(runbookable: subject)
  runbook&.destroy!
  subject.destroy! if subject.is_a?(Anchor) && subject.persisted?
  Jazari.config.on_subject_destroyed&.call(subject)
  true
end

.remove_item(target:, expected_revision:, item_id:) ⇒ Object



49
50
51
52
53
54
55
56
57
# File 'lib/jazari/operations.rb', line 49

def remove_item(target:, expected_revision:, item_id:)
  writable!(target)
  write(target, expected_revision) do |current|
    remaining = current[:checklist].reject { |item| item[:id] == item_id.to_s }
    raise ItemNotFound, "unknown checklist item #{item_id}" if remaining.length == current[:checklist].length

    current.merge(checklist: remaining)
  end
end

.reset(target:, expected_revision:) ⇒ Object

Destroys the customization and reveals the current canon. Idempotent on an already-default target only while the supplied default revision matches.



77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'lib/jazari/operations.rb', line 77

def reset(target:, expected_revision:)
  writable!(target)
  record = find_runbook(target)
  if record
    record.with_lock do
      verify_custom_revision!(record, expected_revision)
      destroy_with_anchor(record, target)
    end
  else
    verify_default_revision!(target, expected_revision)
  end
  resolve(target: target)
end

.resolve(target:) ⇒ Object



16
17
18
19
20
21
22
23
24
# File 'lib/jazari/operations.rb', line 16

def resolve(target:)
  recipe = RecipeRegistry.fetch(target.recipe_id)
  record = find_runbook(target)
  last = Runs.last(target: target)

  return custom_value(record, target, recipe, last) if record

  default_value(recipe, target, last)
end