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 +4 -4
- data/CHANGELOG.md +21 -0
- data/README.md +40 -5
- data/lib/jazari/mcp/actions.rb +2 -1
- data/lib/jazari/mcp/handler.rb +3 -2
- data/lib/jazari/runs.rb +19 -5
- data/lib/jazari/version.rb +1 -1
- data/lib/jazari.rb +11 -5
- 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: f9cbfc9b54c5489c8f751da47fb21b875022595d50b41b144e3aab021e46d9cd
|
|
4
|
+
data.tar.gz: a477a52e534f3683e07d05cae377cdbaaf114c28c8ddb738e532f43bc9ab2cdb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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 [
|
|
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,
|
|
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" },
|
data/lib/jazari/mcp/actions.rb
CHANGED
|
@@ -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,
|
data/lib/jazari/mcp/handler.rb
CHANGED
|
@@ -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
|
|
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),
|
|
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
|
|
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
|
|
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
|
|
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:,
|
|
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
|
data/lib/jazari/version.rb
CHANGED
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
|
-
|
|
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
|
|
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
|
|
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:)
|