jazari 0.3.0 → 0.5.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c1e44727af4c1cfd7ca851c84618d9015f39aec562c3133301772f6d97b21076
4
- data.tar.gz: 8c87563ee6acfbe91b8544274c981dbb98fb24ac05e77cb170f7e33033067f2d
3
+ metadata.gz: f9cbfc9b54c5489c8f751da47fb21b875022595d50b41b144e3aab021e46d9cd
4
+ data.tar.gz: a477a52e534f3683e07d05cae377cdbaaf114c28c8ddb738e532f43bc9ab2cdb
5
5
  SHA512:
6
- metadata.gz: dec279a4cedbcf0377a512298300487316e04a6ac3dfa9059e37415edcfba07ba0015b11d1d8d2b08d6bf4fd33ec557d87a507a216a095d66a52bdc5a4441f6e
7
- data.tar.gz: 148962f5b5f9762f35cc04ba285485c4e76e5de41ef086ea0dafcf4861cd2e9b1110c299ee39ddd45abe491e13a473c0477b66bac914306c7cc9540b6142e382
6
+ metadata.gz: 79803d6a78f7a6bfc032b3319ca341d2f327e9839492d5512f7ddc42d05d5994cc0163cfb9864a230bfd04dee0d9934728a21948b0ebbb49e05ac1dd251a88e1
7
+ data.tar.gz: 6609743e4e095d8e37b8aaf4a753300e67596faef66cf97ceb364cb7d974b3bb24da76bd3d16ba6ddf57afc5db5a16004a8bc44de929ff430f627dbe0b31efd7
data/CHANGELOG.md CHANGED
@@ -8,6 +8,74 @@ 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.5.0] - 2026-08-12
12
+
13
+ ### Added
14
+
15
+ - **Execution-scoped actor identity.** Runs now require an opaque `actor_ref`,
16
+ with explicit values taking precedence over a configured trusted-system
17
+ default. Ticks and evidence inherit the run actor unless explicitly
18
+ attributed otherwise.
19
+ - Evidence records now retain `actor_ref`, making it possible to answer who or
20
+ what performed each procedure step without giving Jazari access to actor
21
+ objects.
22
+ - MCP evidence declarations and public examples now expose the actor contract.
23
+ - Public guides and the landing page now document actor attribution and
24
+ RecipeFiles load, seed, dump, and drift workflows.
25
+
26
+ ### Compatibility
27
+
28
+ - Existing explicit `actor_ref` calls continue to work unchanged.
29
+ - MCP run start continues to require an explicit actor identity; subsequent
30
+ tick and evidence calls may inherit the actor from the open run.
31
+
32
+ ## [0.4.0] - 2026-08-11
33
+
34
+ ### Added
35
+
36
+ - **`Jazari::RecipeFiles` — recipes as YAML or JSON, without files silently
37
+ overwriting operators.**
38
+
39
+ Nothing ever forbade files: `RecipeRegistry.seed!` takes plain hashes, so
40
+ `seed!(YAML.load_file(…))` already worked. What was missing was everything
41
+ that makes it *safe*.
42
+
43
+ - **`load(path)`** — a file or a directory of `.yml` / `.yaml` / `.json`.
44
+ Accepts one recipe, a list, or a list under a `recipes:` key. It validates
45
+ at LOAD, which is the whole reason it exists rather than "just call
46
+ `YAML.load_file`": an unknown key is a typo, and a typo that loads silently
47
+ becomes a recipe resolving to something nobody wrote. Unknown keys, missing
48
+ topics, bad `run_policy`, duplicate ids and malformed YAML all raise, naming
49
+ the file.
50
+ - **`dump(dir)`** — writes what is actually stored back out, one file per
51
+ recipe. Without this the loop never closes: an operator's runtime fix could
52
+ not be reviewed or committed, so runtime editing would quietly become the
53
+ thing you avoid rather than the thing the design is built around.
54
+ - **`drift(entries)`** — which stored recipes disagree with their file, and in
55
+ which fields. **Reported, never applied.**
56
+
57
+ **Files seed; they do not sync.** `seed!` stays create-if-missing, so a file
58
+ never overwrites a row an operator edited. That is the same rule the runbook
59
+ layer already follows — a customisation diverges rather than rebasing, because
60
+ silently overwriting a deliberate edit with a change nobody saw is the worst
61
+ available outcome. Applying files on every deploy would do exactly that, one
62
+ layer up. The cost is drift, so drift is made visible instead of resolved.
63
+
64
+ One place the loader is deliberately **stricter than the API**: a malformed
65
+ checklist id is an error rather than a fixup. `Checklist.normalize` replaces an
66
+ unusable id with a generated one, which is right when an id is absent and
67
+ opaque — but in a file someone wrote it, MCP addresses the step by it, and
68
+ documentation quotes it. Swapping it for a random token would create exactly
69
+ the file-versus-row disagreement this loader exists to prevent.
70
+
71
+ ### Fixed
72
+
73
+ - **The boundary check read heredoc bodies as code.** It stripped `"…"` and
74
+ `'…'` on the stated principle that a capitalised word inside a string is data,
75
+ but a fixture written as `<<~YML` had its own prose reported as constant
76
+ references. Heredoc bodies are now skipped. Verified still catching a planted
77
+ breach.
78
+
11
79
  ## [0.3.0] - 2026-08-11
12
80
 
13
81
  ### Added
data/README.md CHANGED
@@ -12,7 +12,8 @@ Requires Ruby 3.2+, Rails 7.1+, and PostgreSQL.
12
12
 
13
13
  ## Guides
14
14
 
15
- The README is the pitch; the [guides](guide/) are the working documents —
15
+ The README is the pitch; the public [Kuickr guide](https://kuickr.co/jazari/guide)
16
+ is the operating manual. Its source lives in [`guide/`](guide/), including
16
17
  [concepts](guide/01-concepts.md) (start here: one word means something
17
18
  different than you expect), [adoption](guide/02-adoption.md),
18
19
  [anchors](guide/03-anchors.md), [runs and evidence](guide/04-runs.md),
@@ -96,7 +97,8 @@ Jazari.tick(run: run, expected_revision: run.lock_version,
96
97
  item_id: "restore", done: true, actor_ref: "agent:nightly")
97
98
 
98
99
  Jazari.attach_evidence(run: run.reload, expected_revision: run.lock_version,
99
- item_id: "counts", kind: "count", value: "4211 rows")
100
+ item_id: "counts", kind: "count", value: "4211 rows",
101
+ actor_ref: "agent:nightly")
100
102
 
101
103
  Jazari.close_run(run: run.reload, expected_revision: run.lock_version,
102
104
  outcome: "completed")
@@ -156,6 +158,15 @@ operator-owned. Reseeding never overwrites an edit.
156
158
  That means fixing a ritual is a **write, not a deploy** — and a fresh install
157
159
  can ship with working procedures instead of an empty text box.
158
160
 
161
+ For version-controlled recipes, load YAML or JSON as a seed and report drift
162
+ without overwriting operator edits:
163
+
164
+ ```ruby
165
+ entries = Jazari::RecipeFiles.load("config/recipes")
166
+ Jazari::RecipeRegistry.seed!(entries)
167
+ Jazari::RecipeFiles.drift(entries)
168
+ ```
169
+
159
170
  ## Runs are bound to the canon they opened against
160
171
 
161
172
  A run snapshots its checklist when it opens. Edit the recipe mid-run and the
@@ -163,6 +174,29 @@ in-flight run still ticks its own steps, and refuses steps that did not exist
163
174
  when it started. Without this, an operator improving a procedure silently breaks
164
175
  every run in progress.
165
176
 
177
+ ## Actor identity is part of the evidence
178
+
179
+ Jazari stores the opaque identity attached to the run, every tick, and every
180
+ evidence entry. Pass a stable reference when a human, agent, or job acts:
181
+
182
+ ```ruby
183
+ Jazari.open_run(target: target, actor_ref: "user:42")
184
+ Jazari.tick(run: run, expected_revision: run.lock_version,
185
+ item_id: "restore", done: true, actor_ref: "user:42")
186
+ Jazari.attach_evidence(run: run.reload, expected_revision: run.lock_version,
187
+ item_id: "restore", kind: "note", value: "verified",
188
+ actor_ref: "user:42")
189
+ ```
190
+
191
+ For trusted system jobs, configure a zero-argument default. Explicit references
192
+ always win. When a tick or evidence entry omits its actor, it inherits the run's
193
+ actor; opening a run without an explicit actor requires this configured default.
194
+
195
+ ```ruby
196
+ Jazari.configure { |c| c.actor_ref = -> { "system:nightly-backup" } }
197
+ Jazari.open_run(target: target)
198
+ ```
199
+
166
200
  ## MCP
167
201
 
168
202
  `Jazari::Mcp::Handler` maps action names onto the domain and knows nothing about
@@ -183,10 +217,11 @@ could disclose a record or whether a target exists.
183
217
  `Handler.actions_for("read")` returns the read-only subset, so a read-scoped
184
218
  connection never advertises mutations.
185
219
 
186
- ## You authorize; Jazari never sees an actor
220
+ ## You authorize; Jazari never sees an actor object
187
221
 
188
- The domain accepts no raw IDs, no arbitrary records, and no actor. Your app
189
- authorizes first, then constructs exactly one immutable target:
222
+ The domain accepts no raw IDs, arbitrary records, or actor objects. Your app
223
+ authorizes first, then constructs exactly one immutable target. It passes only an
224
+ opaque `actor_ref` string for audit history:
190
225
 
191
226
  ```ruby
192
227
  Jazari::RecordTarget.new(runbookable: site, public_reference: { kind: "site" },
@@ -74,7 +74,8 @@ module Jazari
74
74
  summary: "Attach evidence to a run: output, url, sha, count, or note.",
75
75
  params: { run_id: RUN_ID, expected_revision: REVISION, item_id: ITEM_ID,
76
76
  kind: { type: "string", description: "One of: output, url, sha, count, note." },
77
- value: { type: "string", description: "The evidence itself." } }),
77
+ value: { type: "string", description: "The evidence itself." },
78
+ actor_ref: ACTOR }),
78
79
  Action.new(name: "finish", scope: :write, effect: :additive, confirm: false,
79
80
  summary: "Close a run with an outcome: completed, abandoned, or failed.",
80
81
  params: { run_id: RUN_ID, expected_revision: REVISION,
@@ -86,13 +86,14 @@ module Jazari
86
86
  def handle_tick(_target, args)
87
87
  run = Runs.tick(run: args.fetch(:run_id), expected_revision: args[:expected_revision],
88
88
  item_id: args[:item_id], done: args.fetch(:done, true),
89
- actor_ref: args.fetch(:actor_ref), note: args[:note])
89
+ actor_ref: args[:actor_ref], note: args[:note])
90
90
  run_view(run)
91
91
  end
92
92
 
93
93
  def handle_evidence(_target, args)
94
94
  run = Runs.attach_evidence(run: args.fetch(:run_id), expected_revision: args[:expected_revision],
95
- item_id: args[:item_id], kind: args.fetch(:kind), value: args.fetch(:value))
95
+ item_id: args[:item_id], kind: args.fetch(:kind),
96
+ value: args.fetch(:value), actor_ref: args[:actor_ref])
96
97
  run_view(run)
97
98
  end
98
99
 
@@ -0,0 +1,174 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "yaml"
4
+ require "json"
5
+
6
+ module Jazari
7
+ # Recipes as FILES — the version-controlled, reviewable form of the same data.
8
+ #
9
+ # This does not make files a second source of truth. `seed!` has always been
10
+ # create-if-missing, so a file is a SEED, not a sync: the row wins once it
11
+ # exists, because an operator editing a procedure at runtime is the whole
12
+ # reason recipes are data rather than code.
13
+ #
14
+ # That is deliberate and it is the same rule the runbook layer already follows —
15
+ # a customised runbook diverges permanently rather than rebasing, because
16
+ # silently overwriting a deliberate edit with a change nobody saw is the worst
17
+ # available outcome. Applying files on every deploy would do exactly that, one
18
+ # layer up.
19
+ #
20
+ # The cost of that choice is drift: a file and a row can disagree and nothing
21
+ # says so. So drift is REPORTED (`drift`) rather than resolved, and there is a
22
+ # way back out (`dump`) — edit at runtime, export, review the diff in a pull
23
+ # request, commit. The loop closes without anyone's work being overwritten.
24
+ module RecipeFiles
25
+ EXTENSIONS = %w[.yml .yaml .json].freeze
26
+
27
+ # Keys a recipe file may carry. Anything else is a typo, and a typo that
28
+ # loads silently becomes a recipe that resolves to something nobody wrote.
29
+ KEYS = %i[id version topic description checklist run_policy].freeze
30
+
31
+ module_function
32
+
33
+ # Reads one file or every recipe file in a directory. Returns plain hashes,
34
+ # ready for `RecipeRegistry.seed!` — which is why this is a loader and not a
35
+ # registry: producing the data and storing it are separate concerns.
36
+ def load(path)
37
+ entries = Array(paths_for(path)).flat_map { |file| parse(file) }
38
+ entries.each { |entry| validate!(entry) }
39
+ ids = entries.map { |entry| entry[:id] }
40
+ duplicated = ids.tally.select { |_, count| count > 1 }.keys
41
+ raise InvalidRunbook, "duplicate recipe ids: #{duplicated.join(', ')}" if duplicated.any?
42
+
43
+ entries
44
+ end
45
+
46
+ # Writes what is actually stored back out as YAML, one file per recipe.
47
+ # This is the half that makes runtime editing safe to allow: whatever an
48
+ # operator changed can be exported, diffed and committed.
49
+ def dump(directory, recipes: RecipeRecord.order(:recipe_id))
50
+ dir = File.expand_path(directory.to_s)
51
+ Dir.mkdir(dir) unless Dir.exist?(dir)
52
+ recipes.map do |record|
53
+ file = File.join(dir, "#{record.recipe_id}.yml")
54
+ File.write(file, YAML.dump(stringify(to_entry(record))))
55
+ file
56
+ end
57
+ end
58
+
59
+ # Which stored recipes disagree with their file definition, and how.
60
+ #
61
+ # Reported, never applied. A host decides what a difference means: on one
62
+ # fleet a file is the reviewed truth and a divergent row is an incident; on
63
+ # another the row is an operator's fix and the file is simply stale.
64
+ def drift(entries)
65
+ Array(entries).filter_map do |entry|
66
+ attributes = normalize(entry)
67
+ record = RecipeRecord.find_by(recipe_id: attributes[:id].to_s)
68
+ next { id: attributes[:id], state: :missing } if record.nil?
69
+
70
+ differing = KEYS.reject { |key| same?(key, attributes, record) }
71
+ next if differing.empty?
72
+
73
+ { id: attributes[:id], state: :differs, fields: differing }
74
+ end
75
+ end
76
+
77
+ # -- internals ---------------------------------------------------------
78
+
79
+ def paths_for(path)
80
+ expanded = File.expand_path(path.to_s)
81
+ return [ expanded ] if File.file?(expanded)
82
+ raise InvalidRunbook, "no such recipe path: #{path}" unless File.directory?(expanded)
83
+
84
+ Dir.children(expanded).sort
85
+ .select { |name| EXTENSIONS.include?(File.extname(name)) }
86
+ .map { |name| File.join(expanded, name) }
87
+ end
88
+
89
+ def parse(file)
90
+ raw = File.read(file)
91
+ data = if File.extname(file) == ".json"
92
+ JSON.parse(raw)
93
+ else
94
+ # safe_load: a recipe file is operational content, never a place to
95
+ # instantiate arbitrary objects.
96
+ YAML.safe_load(raw, permitted_classes: [], aliases: false)
97
+ end
98
+ # A file is either one recipe, a list of them, or a list under a `recipes:`
99
+ # key. `Array(hash)` would explode a single recipe into key/value pairs, so
100
+ # the Hash cases are named rather than coerced.
101
+ entries = if data.is_a?(Hash)
102
+ data.key?("recipes") ? Array(data["recipes"]) : [ data ]
103
+ else
104
+ Array(data)
105
+ end
106
+ entries.map { |entry| normalize(entry) }
107
+ rescue JSON::ParserError, Psych::SyntaxError => error
108
+ raise InvalidRunbook, "#{File.basename(file)}: #{error.message}"
109
+ end
110
+
111
+ def normalize(entry)
112
+ entry.to_h.transform_keys { |key| key.to_s.to_sym }
113
+ end
114
+
115
+ def validate!(entry)
116
+ unknown = entry.keys - KEYS
117
+ raise InvalidRunbook, "unknown recipe keys: #{unknown.join(', ')}" if unknown.any?
118
+ raise InvalidRunbook, "recipe id is required" if entry[:id].to_s.empty?
119
+ raise InvalidRunbook, "recipe #{entry[:id]} has no topic" if entry[:topic].to_s.empty?
120
+
121
+ policy = entry.fetch(:run_policy, RunPolicy::UNRESTRICTED).to_s
122
+ unless RunPolicy::ALL.include?(policy)
123
+ raise InvalidRunbook, "recipe #{entry[:id]} has unknown run_policy #{policy}"
124
+ end
125
+
126
+ # Reuse the one checklist validator rather than writing a second, laxer
127
+ # one here — a file must not be able to store an item the API would reject.
128
+ items = entry.fetch(:checklist, [])
129
+ Checklist.normalize(items)
130
+
131
+ # STRICTER than the API on one point, deliberately. `normalize` REPLACES an
132
+ # unusable id with a generated one, which is right when an id is absent and
133
+ # opaque. In a file it is neither: someone wrote it, MCP addresses the step
134
+ # by it, and documentation quotes it. Silently swapping it for a random
135
+ # token would put the file and the row into exactly the disagreement this
136
+ # loader exists to prevent — so a malformed id is an error, not a fixup.
137
+ Array(items).each do |item|
138
+ id = (item[:id] || item["id"]).to_s
139
+ next if id.empty? || id.match?(Checklist::ID_FORMAT)
140
+
141
+ raise InvalidRunbook, "recipe #{entry[:id]}: checklist id #{id.inspect} is not a valid token"
142
+ end
143
+
144
+ entry
145
+ end
146
+
147
+ def to_entry(record)
148
+ { id: record.recipe_id, version: record.version, topic: record.topic,
149
+ description: record.description, run_policy: record.run_policy,
150
+ checklist: Checklist.normalize(record.checklist) }
151
+ end
152
+
153
+ def stringify(value)
154
+ case value
155
+ when Hash then value.to_h { |key, inner| [ key.to_s, stringify(inner) ] }
156
+ when Array then value.map { |inner| stringify(inner) }
157
+ when Symbol then value.to_s
158
+ else value
159
+ end
160
+ end
161
+
162
+ def same?(key, attributes, record)
163
+ stored = case key
164
+ when :id then record.recipe_id
165
+ when :checklist then Checklist.normalize(record.checklist)
166
+ else record.public_send(key)
167
+ end
168
+ expected = key == :checklist ? Checklist.normalize(attributes.fetch(key, [])) : attributes[key]
169
+ return true if expected.nil? && key != :id
170
+
171
+ stringify(stored) == stringify(expected)
172
+ end
173
+ end
174
+ end
data/lib/jazari/runs.rb CHANGED
@@ -18,10 +18,11 @@ module Jazari
18
18
  # Insert FIRST, then rescue the unique violation and select the winner.
19
19
  # A find-then-insert races under concurrent writers — the same defect class
20
20
  # the revision guard exists to prevent.
21
- def open(target:, actor_ref:, now: Time.now.utc)
21
+ def open(target:, actor_ref: nil, now: Time.now.utc)
22
22
  recipe = RecipeRegistry.fetch(target.recipe_id)
23
23
  subject = subject_for(target)
24
24
  started_at = now.utc
25
+ actor_ref = resolve_actor_ref(actor_ref)
25
26
 
26
27
  attributes = {
27
28
  recipe_id: recipe.id,
@@ -67,7 +68,7 @@ module Jazari
67
68
  end
68
69
  end
69
70
 
70
- def tick(run:, expected_revision:, item_id:, done:, actor_ref:, note: nil, now: Time.now.utc)
71
+ def tick(run:, expected_revision:, item_id:, done:, actor_ref: nil, note: nil, now: Time.now.utc)
71
72
  mutate(run, expected_revision) do |record|
72
73
  raise RunClosed, "run #{record.id} is already closed" if record.closed?
73
74
 
@@ -84,12 +85,14 @@ module Jazari
84
85
 
85
86
  ticks = stored(record.ticks).reject { |t| t["id"] == item_id.to_s }
86
87
  ticks << { "id" => item_id.to_s, "done" => done == true, "at" => now.utc.iso8601,
87
- "actor_ref" => actor_ref.to_s, "note" => note&.to_s }
88
+ "actor_ref" => resolve_actor_ref(actor_ref, fallback: record.actor_ref),
89
+ "note" => note&.to_s }
88
90
  record.ticks = ticks
89
91
  end
90
92
  end
91
93
 
92
- def attach_evidence(run:, expected_revision:, item_id:, kind:, value:, now: Time.now.utc)
94
+ def attach_evidence(run:, expected_revision:, item_id:, kind:, value:, actor_ref: nil,
95
+ now: Time.now.utc)
93
96
  raise InvalidRunbook, "unknown evidence kind #{kind.inspect}" unless EVIDENCE_KINDS.include?(kind.to_s)
94
97
 
95
98
  mutate(run, expected_revision) do |record|
@@ -97,7 +100,8 @@ module Jazari
97
100
 
98
101
  record.evidence = stored(record.evidence) + [
99
102
  { "item_id" => item_id&.to_s, "kind" => kind.to_s,
100
- "value" => value.to_s[0, MAX_EVIDENCE], "at" => now.utc.iso8601 }
103
+ "value" => value.to_s[0, MAX_EVIDENCE], "at" => now.utc.iso8601,
104
+ "actor_ref" => resolve_actor_ref(actor_ref, fallback: record.actor_ref) }
101
105
  ]
102
106
  end
103
107
  end
@@ -175,5 +179,15 @@ module Jazari
175
179
 
176
180
  def stored(value) = Array(value).map { |h| h.to_h.transform_keys(&:to_s) }
177
181
  private_class_method :stored
182
+
183
+ def resolve_actor_ref(explicit, fallback: nil)
184
+ value = explicit || fallback || Jazari.config.actor_ref
185
+ value = value.call if value.respond_to?(:call)
186
+ value = value.to_s
187
+ raise InvalidRunbook, "actor_ref is required" if value.empty?
188
+
189
+ value
190
+ end
191
+ private_class_method :resolve_actor_ref
178
192
  end
179
193
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Jazari
4
- VERSION = "0.3.0"
4
+ VERSION = "0.5.0"
5
5
  end
data/lib/jazari.rb CHANGED
@@ -8,6 +8,7 @@ require "jazari/targets"
8
8
  require "jazari/anchors"
9
9
  require "jazari/resolved_runbook"
10
10
  require "jazari/recipe_registry"
11
+ require "jazari/recipe_files"
11
12
  require "jazari/runs"
12
13
  require "jazari/operations"
13
14
  # The MCP layer is OPTIONAL. Descriptors are cheap and a host may want them to
@@ -43,7 +44,9 @@ module Jazari
43
44
  # renaming live tables in the same deploy as a cut-over is exactly what
44
45
  # the adoption plan forbids. Any key omitted falls back to the prefix.
45
46
  self.table_names ||= {}
46
- self.actor_ref ||= ->(actor) { "actor:#{actor.object_id}" }
47
+ # Optional zero-argument provider for trusted system contexts. User and
48
+ # agent callers should pass an explicit opaque actor_ref.
49
+ self.actor_ref = nil if actor_ref.nil?
47
50
  end
48
51
 
49
52
  # Fail at boot, not at first call.
@@ -51,6 +54,10 @@ module Jazari
51
54
  anchor_scopes.each_key do |scope|
52
55
  raise ArgumentError, "anchor scope #{scope.inspect} must be a String" unless scope.is_a?(String)
53
56
  end
57
+ if actor_ref && !actor_ref.is_a?(String) &&
58
+ !(actor_ref.respond_to?(:call) && actor_ref.respond_to?(:arity) && actor_ref.arity.zero?)
59
+ raise ArgumentError, "actor_ref must be a String or zero-argument callable"
60
+ end
54
61
  true
55
62
  end
56
63
  end
@@ -81,18 +88,18 @@ module Jazari
81
88
  # The documented public interface (spec 02 section 3). `Runs` is the
82
89
  # implementation; these are the names hosts and the MCP handler call.
83
90
  class << self
84
- def open_run(target:, actor_ref:, now: Time.now.utc)
91
+ def open_run(target:, actor_ref: nil, now: Time.now.utc)
85
92
  Runs.open(target: target, actor_ref: actor_ref, now: now)
86
93
  end
87
94
 
88
- def tick(run:, expected_revision:, item_id:, done:, actor_ref:, note: nil)
95
+ def tick(run:, expected_revision:, item_id:, done:, actor_ref: nil, note: nil)
89
96
  Runs.tick(run: run, expected_revision: expected_revision, item_id: item_id,
90
97
  done: done, actor_ref: actor_ref, note: note)
91
98
  end
92
99
 
93
- def attach_evidence(run:, expected_revision:, item_id:, kind:, value:)
100
+ def attach_evidence(run:, expected_revision:, item_id:, kind:, value:, actor_ref: nil)
94
101
  Runs.attach_evidence(run: run, expected_revision: expected_revision,
95
- item_id: item_id, kind: kind, value: value)
102
+ item_id: item_id, kind: kind, value: value, actor_ref: actor_ref)
96
103
  end
97
104
 
98
105
  def close_run(run:, expected_revision:, outcome:)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jazari
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nauman Tariq
@@ -67,6 +67,7 @@ files:
67
67
  - lib/jazari/operations.rb
68
68
  - lib/jazari/railtie.rb
69
69
  - lib/jazari/recipe.rb
70
+ - lib/jazari/recipe_files.rb
70
71
  - lib/jazari/recipe_registry.rb
71
72
  - lib/jazari/resolved_runbook.rb
72
73
  - lib/jazari/runs.rb