jazari 0.4.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: b3e1d3f594584f56c5ea72c8aacd01ce1aa9fc6ddd12b2b1f9c160072dfd9c63
4
- data.tar.gz: f5d622adb6252f7465adb0aeab696c9876598bef6a368bf1845dd8a2c896b252
3
+ metadata.gz: f9cbfc9b54c5489c8f751da47fb21b875022595d50b41b144e3aab021e46d9cd
4
+ data.tar.gz: a477a52e534f3683e07d05cae377cdbaaf114c28c8ddb738e532f43bc9ab2cdb
5
5
  SHA512:
6
- metadata.gz: dfc06b9155a1f706488fa36ee9661c92fb419a27c76cfe6f1e912be31a365ceb6249e24805a92f7e1b9d025511e0559332d6f391f604dbe24e569f46ab886cc1
7
- data.tar.gz: f5f37bdc359b9626221055c908a5a0ab59f713ab2238ff5df99a82da9c787e403966ef591e65d25fab45ebd2d9dadb78e2a1fd8af860627c4d8661b589356668
6
+ metadata.gz: 79803d6a78f7a6bfc032b3319ca341d2f327e9839492d5512f7ddc42d05d5994cc0163cfb9864a230bfd04dee0d9934728a21948b0ebbb49e05ac1dd251a88e1
7
+ data.tar.gz: 6609743e4e095d8e37b8aaf4a753300e67596faef66cf97ceb364cb7d974b3bb24da76bd3d16ba6ddf57afc5db5a16004a8bc44de929ff430f627dbe0b31efd7
data/CHANGELOG.md CHANGED
@@ -8,6 +8,27 @@ 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
+
11
32
  ## [0.4.0] - 2026-08-11
12
33
 
13
34
  ### 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
 
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.4.0"
4
+ VERSION = "0.5.0"
5
5
  end
data/lib/jazari.rb CHANGED
@@ -44,7 +44,9 @@ module Jazari
44
44
  # renaming live tables in the same deploy as a cut-over is exactly what
45
45
  # the adoption plan forbids. Any key omitted falls back to the prefix.
46
46
  self.table_names ||= {}
47
- 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?
48
50
  end
49
51
 
50
52
  # Fail at boot, not at first call.
@@ -52,6 +54,10 @@ module Jazari
52
54
  anchor_scopes.each_key do |scope|
53
55
  raise ArgumentError, "anchor scope #{scope.inspect} must be a String" unless scope.is_a?(String)
54
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
55
61
  true
56
62
  end
57
63
  end
@@ -82,18 +88,18 @@ module Jazari
82
88
  # The documented public interface (spec 02 section 3). `Runs` is the
83
89
  # implementation; these are the names hosts and the MCP handler call.
84
90
  class << self
85
- def open_run(target:, actor_ref:, now: Time.now.utc)
91
+ def open_run(target:, actor_ref: nil, now: Time.now.utc)
86
92
  Runs.open(target: target, actor_ref: actor_ref, now: now)
87
93
  end
88
94
 
89
- 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)
90
96
  Runs.tick(run: run, expected_revision: expected_revision, item_id: item_id,
91
97
  done: done, actor_ref: actor_ref, note: note)
92
98
  end
93
99
 
94
- def attach_evidence(run:, expected_revision:, item_id:, kind:, value:)
100
+ def attach_evidence(run:, expected_revision:, item_id:, kind:, value:, actor_ref: nil)
95
101
  Runs.attach_evidence(run: run, expected_revision: expected_revision,
96
- item_id: item_id, kind: kind, value: value)
102
+ item_id: item_id, kind: kind, value: value, actor_ref: actor_ref)
97
103
  end
98
104
 
99
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.4.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nauman Tariq