ace-assign 0.69.0 → 0.71.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/.ace-defaults/assign/presets/deliver-release.yml +11 -8
- data/.ace-defaults/assign/presets/integrate-batch.yml +10 -5
- data/.ace-defaults/assign/presets/supervise-delivery.yml +6 -4
- data/CHANGELOG.md +27 -0
- data/handbook/skills/as-assign-drive/SKILL.md +5 -2
- data/handbook/workflow-instructions/assign/drive.wf.md +77 -0
- data/lib/ace/assign/atoms/assignment_scope.rb +5 -0
- data/lib/ace/assign/atoms/candidate_limits.rb +20 -0
- data/lib/ace/assign/atoms/git_blob_batch.rb +59 -0
- data/lib/ace/assign/authority/assignment_definition.rb +35 -0
- data/lib/ace/assign/authority/candidate_blob_batch.rb +55 -0
- data/lib/ace/assign/authority/candidate_head_comparison.rb +139 -0
- data/lib/ace/assign/authority/candidate_transfer.rb +57 -27
- data/lib/ace/assign/authority/command_execution.rb +180 -0
- data/lib/ace/assign/authority/endcap.rb +4 -2
- data/lib/ace/assign/authority/endcap_attempts.rb +1 -1
- data/lib/ace/assign/authority/endcap_campaign_rounds.rb +1 -1
- data/lib/ace/assign/authority/endcap_publication.rb +0 -166
- data/lib/ace/assign/authority/endcap_service_context.rb +38 -0
- data/lib/ace/assign/authority/fixed_publication_lifecycle.rb +176 -0
- data/lib/ace/assign/authority/{endcap_services.rb → fixed_service_lifecycle.rb} +42 -46
- data/lib/ace/assign/authority/launch_control_exclusions.rb +3 -17
- data/lib/ace/assign/authority/ordinary_attempt_start.rb +137 -0
- data/lib/ace/assign/authority/ordinary_client.rb +70 -0
- data/lib/ace/assign/authority/ordinary_completion.rb +104 -0
- data/lib/ace/assign/authority/ordinary_effect_settlement.rb +118 -0
- data/lib/ace/assign/authority/ordinary_lifecycle.rb +227 -0
- data/lib/ace/assign/authority/ordinary_merge_evidence.rb +81 -0
- data/lib/ace/assign/authority/ordinary_peer.rb +54 -0
- data/lib/ace/assign/authority/ordinary_progress.rb +162 -0
- data/lib/ace/assign/authority/ordinary_queue.rb +115 -0
- data/lib/ace/assign/authority/ordinary_release_artifacts.rb +143 -0
- data/lib/ace/assign/authority/ordinary_results.rb +206 -0
- data/lib/ace/assign/authority/ordinary_review_evidence.rb +180 -0
- data/lib/ace/assign/authority/ordinary_reviews.rb +214 -0
- data/lib/ace/assign/authority/ordinary_server.rb +75 -0
- data/lib/ace/assign/authority/ordinary_supervisor.rb +99 -0
- data/lib/ace/assign/authority/prepared_work.rb +9 -4
- data/lib/ace/assign/authority/protected_assignment_context.rb +3 -3
- data/lib/ace/assign/authority/release_artifact_manifest.rb +91 -0
- data/lib/ace/assign/authority/retained_input.rb +53 -0
- data/lib/ace/assign/authority/router.rb +11 -6
- data/lib/ace/assign/authority/server.rb +11 -180
- data/lib/ace/assign/authority/socket_server.rb +255 -0
- data/lib/ace/assign/authority/transfer_codec.rb +5 -3
- data/lib/ace/assign/cli/commands/attempt/evidence.rb +2 -2
- data/lib/ace/assign/cli/commands/submit_candidate.rb +1 -1
- data/lib/ace/assign/molecules/canonical_evidence.rb +25 -8
- data/lib/ace/assign/molecules/canonical_read_commands.rb +84 -0
- data/lib/ace/assign/molecules/canonical_read_snapshot.rb +2 -63
- data/lib/ace/assign/molecules/evidence_journal.rb +157 -63
- data/lib/ace/assign/molecules/installed_journal_read.rb +143 -0
- data/lib/ace/assign/molecules/journal_mutation.rb +7 -4
- data/lib/ace/assign/molecules/lifecycle_exclusion.rb +13 -2
- data/lib/ace/assign/molecules/proposal_journal.rb +32 -4
- data/lib/ace/assign/molecules/receipt_verifier.rb +8 -2
- data/lib/ace/assign/molecules/runtime_control_surface_runner.rb +2 -1
- data/lib/ace/assign/organisms/attempt_coordinator.rb +88 -36
- data/lib/ace/assign/organisms/ordinary_driver.rb +310 -0
- data/lib/ace/assign/organisms/ordinary_merge_consumer.rb +5 -5
- data/lib/ace/assign/version.rb +1 -1
- metadata +39 -10
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "securerandom"
|
|
4
|
+
require "tmpdir"
|
|
5
|
+
require "fileutils"
|
|
6
|
+
require_relative "../authority/retained_input"
|
|
7
|
+
require_relative "../authority/prepared_work"
|
|
8
|
+
require_relative "../authority/transfer_codec"
|
|
9
|
+
require_relative "../molecules/journal_mutation"
|
|
10
|
+
require_relative "../models/execution_receipt"
|
|
11
|
+
require_relative "../models/assignment"
|
|
12
|
+
require_relative "../molecules/queue_scanner"
|
|
13
|
+
|
|
14
|
+
module Ace
|
|
15
|
+
module Assign
|
|
16
|
+
module Organisms
|
|
17
|
+
# Operator protocol over the installed owner supplied by the composition.
|
|
18
|
+
# Files retain requests only; every observation comes from that owner.
|
|
19
|
+
class OrdinaryDriver
|
|
20
|
+
Files = Authority::RetainedInput
|
|
21
|
+
CONTEXT_KEYS = %w[version project_id assignment_id attempt_id scope pane supervisor_pane cwd head provider].freeze
|
|
22
|
+
attr_reader :context, :path
|
|
23
|
+
|
|
24
|
+
def initialize(path:, project:, transport:)
|
|
25
|
+
@path = File.expand_path(path)
|
|
26
|
+
@context = Files.json(@path)
|
|
27
|
+
unless @context.is_a?(Hash) && @context.keys.sort == CONTEXT_KEYS.sort && @context["version"] == 1 &&
|
|
28
|
+
@context["project_id"] == project && %w[project_id assignment_id attempt_id].all? { |key|
|
|
29
|
+
@context[key].is_a?(String) && Molecules::JournalMutation::ID.match?(@context[key]) } &&
|
|
30
|
+
@context["scope"].is_a?(String) && Authority::PreparedWork::SCOPE.match?(@context["scope"]) &&
|
|
31
|
+
%w[pane supervisor_pane].all? { |key| @context[key].is_a?(String) && @context[key].match?(/\A[\w.:-]{1,128}\z/) } &&
|
|
32
|
+
@context["cwd"].is_a?(String) && @context["cwd"].start_with?("/") &&
|
|
33
|
+
File.expand_path(@context["cwd"]) == @context["cwd"] && @context["head"].is_a?(String) &&
|
|
34
|
+
@context["head"].match?(/\A[0-9a-f]{40}\z/) && @context["provider"].is_a?(String) &&
|
|
35
|
+
@context["provider"].bytesize.between?(1, 512)
|
|
36
|
+
raise ArgumentError, "ordinary driver selection differs; retain the original context"
|
|
37
|
+
end
|
|
38
|
+
@root = Authority::PrivateDirectory.verify!(File.dirname(@path))
|
|
39
|
+
@transport = transport
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def status
|
|
43
|
+
reply = read("result_status", selection)
|
|
44
|
+
data = reply.fetch("data")
|
|
45
|
+
unless data.values_at("project_id", "assignment_id", "attempt_id") == context.values_at("project_id", "assignment_id", "attempt_id")
|
|
46
|
+
raise AttemptErrors::EvidenceUnavailable, "canonical status belongs to another execution"
|
|
47
|
+
end
|
|
48
|
+
data
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def start(pane:)
|
|
52
|
+
worker!(pane)
|
|
53
|
+
mutate("start", "start_attempt", selection.merge("expected_generation" => 0,
|
|
54
|
+
"pane" => pane, "scope" => context.fetch("scope"), "head" => context.fetch("head")))
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def supervisor
|
|
58
|
+
data = read("supervisor_status", context.slice("assignment_id")).fetch("data")
|
|
59
|
+
unless data.values_at("project_id", "assignment_id") == context.values_at("project_id", "assignment_id")
|
|
60
|
+
raise AttemptErrors::EvidenceUnavailable, "canonical supervisor belongs to another assignment"
|
|
61
|
+
end
|
|
62
|
+
data
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def adopt_supervisor(pane:, generation:, previous_binding_sha256:)
|
|
66
|
+
mutate("supervisor-#{generation}", "adopt_supervisor", context.slice("assignment_id").merge(
|
|
67
|
+
"pane" => pane, "expected_generation" => generation, "previous_binding_sha256" => previous_binding_sha256))
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def queue
|
|
71
|
+
reply, bytes = snapshot("assignment_snapshot", selection.merge("kind" => "queue"))
|
|
72
|
+
data = reply.fetch("data")
|
|
73
|
+
unless data.values_at("project_id", "assignment_id", "attempt_id", "scope") ==
|
|
74
|
+
context.values_at("project_id", "assignment_id", "attempt_id", "scope") &&
|
|
75
|
+
Digest::SHA256.hexdigest(bytes) == data.fetch("sha256")
|
|
76
|
+
raise AttemptErrors::EvidenceUnavailable, "canonical queue belongs to another execution"
|
|
77
|
+
end
|
|
78
|
+
value = JSON.parse(bytes)
|
|
79
|
+
assignment = Models::Assignment.new(id: context.fetch("assignment_id"), name: "ordinary",
|
|
80
|
+
created_at: Time.now.utc, source_config: "canonical", cache_dir: @root)
|
|
81
|
+
state = Molecules::QueueScanner.new.scan_captured(steps: value.fetch("steps"),
|
|
82
|
+
reports: value.fetch("reports"), assignment: assignment)
|
|
83
|
+
scope = context.fetch("scope")
|
|
84
|
+
current = state.current_in_subtree(scope)
|
|
85
|
+
next_step = state.next_workable_in_subtree(scope)
|
|
86
|
+
data.merge("complete" => state.subtree_complete?(scope), "failed" => state.subtree_failed?(scope),
|
|
87
|
+
"current" => step(current), "next" => step(next_step), "reports" => value.fetch("reports"))
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def bootstrap(output:)
|
|
91
|
+
# Accepted task/context bytes are recovered from the canonical owner,
|
|
92
|
+
# not the worker checkout's possibly older mutable task files.
|
|
93
|
+
reply, bytes = snapshot("assignment_snapshot", selection.merge("attempt_id" => nil, "kind" => "prepared_work"))
|
|
94
|
+
data = reply.fetch("data")
|
|
95
|
+
unless data.values_at("project_id", "assignment_id") == context.values_at("project_id", "assignment_id")
|
|
96
|
+
raise AttemptErrors::EvidenceUnavailable, "canonical preparation belongs to another assignment"
|
|
97
|
+
end
|
|
98
|
+
reference = data.fetch("prepared_work")
|
|
99
|
+
work = Authority::PreparedWork.admit(bytes: bytes, sha256: data.fetch("sha256"), size: data.fetch("bytes"),
|
|
100
|
+
head: reference.fetch("prepared_head"), tree: reference.fetch("prepared_tree"), root: @root,
|
|
101
|
+
deadline: Process.clock_gettime(Process::CLOCK_MONOTONIC) + 30)
|
|
102
|
+
unless work.reference(head: reference.fetch("prepared_head"), tree: reference.fetch("prepared_tree")) == reference &&
|
|
103
|
+
work.manifest.values_at("project_id", "assignment_id", "scope") == context.values_at("project_id", "assignment_id", "scope")
|
|
104
|
+
raise AttemptErrors::EvidenceUnavailable, "canonical prepared scope differs"
|
|
105
|
+
end
|
|
106
|
+
directory = Files.create_output(output)
|
|
107
|
+
inventory = work.manifest.fetch("context").each_with_index.flat_map do |entry, index|
|
|
108
|
+
selected = %w[spec text].map { |kind| [kind, entry.fetch(kind)] } +
|
|
109
|
+
entry.fetch("reports").each_with_index.map { |report, number| ["report-#{number}", report.fetch("file")] }
|
|
110
|
+
selected.map do |kind, source|
|
|
111
|
+
content = work.files.fetch(source.fetch("filename"))
|
|
112
|
+
filename = "context-#{index}-#{kind}.md"
|
|
113
|
+
Files.write_file(directory, filename, content)
|
|
114
|
+
{"task_id" => entry.fetch("task_id"), "kind" => kind, "path" => File.join(directory, filename),
|
|
115
|
+
"sha256" => Digest::SHA256.hexdigest(content), "bytes" => content.bytesize}
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
{"project_id" => context.fetch("project_id"), **selection, "scope" => context.fetch("scope"),
|
|
119
|
+
"manifest_sha256" => work.manifest_sha256, "files" => inventory, "journal_commit" => data.fetch("journal_commit")}
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def progress(operation:, pane:, generation:, step: nil, report: nil, message: nil)
|
|
123
|
+
worker!(pane)
|
|
124
|
+
params = selection.merge("expected_generation" => generation, "pane" => pane)
|
|
125
|
+
parts = []
|
|
126
|
+
case operation
|
|
127
|
+
when "queue_start" then params["step"] = step
|
|
128
|
+
when "queue_finish"
|
|
129
|
+
params["step"] = step
|
|
130
|
+
parts << Files.read_file(report, 65_536)
|
|
131
|
+
when "queue_fail" then params["message"] = message
|
|
132
|
+
else raise ArgumentError, "unknown ordinary queue operation"
|
|
133
|
+
end
|
|
134
|
+
mutate("progress-#{generation}", operation, params, parts)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def submit(pane:, generation:, receipt:, artifacts: [])
|
|
138
|
+
worker!(pane)
|
|
139
|
+
bytes = Files.read_file(receipt, 16_384)
|
|
140
|
+
value = JSON.parse(bytes)
|
|
141
|
+
unless value.values_at("project_id", "assignment_id", "attempt_id", "scope") ==
|
|
142
|
+
context.values_at("project_id", "assignment_id", "attempt_id", "scope") &&
|
|
143
|
+
value.fetch("artifacts").size == artifacts.size
|
|
144
|
+
raise ArgumentError, "result receipt differs from ordinary selection"
|
|
145
|
+
end
|
|
146
|
+
parts = artifacts.each_with_index.map do |file, index|
|
|
147
|
+
content = Files.read_file(file, Authority::TransferCodec::LIMITS.fetch(:receipt_artifacts).last)
|
|
148
|
+
unless Digest::SHA256.hexdigest(content) == value.fetch("artifacts").fetch(index).fetch("sha256")
|
|
149
|
+
raise ArgumentError, "result artifact differs from its receipt"
|
|
150
|
+
end
|
|
151
|
+
content
|
|
152
|
+
end
|
|
153
|
+
params = selection.merge("expected_generation" => generation,
|
|
154
|
+
"pane" => pane, "head" => value.fetch("head"), "receipt_sha256" => Digest::SHA256.hexdigest(bytes))
|
|
155
|
+
verify_result_intent!(params, [bytes] + parts)
|
|
156
|
+
observed = status
|
|
157
|
+
if (accepted = observed.fetch("submitted_result"))
|
|
158
|
+
# An accepted result may have lost its reply. Its generation selects
|
|
159
|
+
# the original input; never mint a replacement mutation to recover it.
|
|
160
|
+
original_generation = accepted.fetch("generation") - 1
|
|
161
|
+
input = File.join(@root, "requests", "result-#{original_generation}", "request.json")
|
|
162
|
+
original = Files.json(input)
|
|
163
|
+
expected = document("submit_result", params.merge("expected_generation" => original_generation), nil,
|
|
164
|
+
inventory([bytes] + parts))
|
|
165
|
+
unless original.except("mutation_id") == expected.except("mutation_id")
|
|
166
|
+
raise ArgumentError, "accepted result differs from retained original input"
|
|
167
|
+
end
|
|
168
|
+
return replay(input: input).merge("request" => input)
|
|
169
|
+
end
|
|
170
|
+
unless observed.fetch("state") == "running" && observed.fetch("generation") == generation
|
|
171
|
+
raise AttemptErrors::EvidenceUnavailable, "result requires the current canonical generation; retain original inputs"
|
|
172
|
+
end
|
|
173
|
+
reply = mutate("result-#{generation}", "submit_result", params, [bytes] + parts)
|
|
174
|
+
signal_result(reply.fetch("data"))
|
|
175
|
+
reply
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def adopt(pane:)
|
|
179
|
+
owner = supervisor
|
|
180
|
+
raise ArgumentError, "ordinary result owner pane differs" unless pane == owner.fetch("binding").fetch("pane")
|
|
181
|
+
observed = status
|
|
182
|
+
return {"status" => "ok", "data" => observed} if %w[succeeded failed].include?(observed.fetch("state"))
|
|
183
|
+
result = observed.fetch("submitted_result")
|
|
184
|
+
raise AttemptErrors::EvidenceUnavailable, "ordinary worker has not submitted a result" unless result
|
|
185
|
+
mutate("adopt-#{owner.fetch('generation')}-#{observed.fetch('generation')}", "finish", selection.merge("pane" => pane,
|
|
186
|
+
"expected_generation" => observed.fetch("generation"),
|
|
187
|
+
**result.slice("result_id", "receipt_digest", "head")))
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def replay(input:)
|
|
191
|
+
request = Files.json(input)
|
|
192
|
+
expected = request["operation"] == "adopt_supervisor" ? context.slice("assignment_id") : selection
|
|
193
|
+
unless request["project_id"] == context.fetch("project_id") &&
|
|
194
|
+
request.fetch("params").slice("assignment_id", "attempt_id") == expected &&
|
|
195
|
+
File.dirname(File.dirname(File.expand_path(input))) == File.join(@root, "requests")
|
|
196
|
+
raise ArgumentError, "recovery request differs from original execution"
|
|
197
|
+
end
|
|
198
|
+
reply = @transport.request(input: input)
|
|
199
|
+
signal_result(reply.fetch("data")) if request.fetch("operation") == "submit_result"
|
|
200
|
+
reply
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
private
|
|
204
|
+
|
|
205
|
+
def selection = context.slice("assignment_id", "attempt_id")
|
|
206
|
+
|
|
207
|
+
# A filesystem watch hint is neither execution state nor a receipt.
|
|
208
|
+
# Adoption never reads this file, even after restart or forged writes.
|
|
209
|
+
def signal_result(result)
|
|
210
|
+
name = "hint-#{SecureRandom.hex(8)}"
|
|
211
|
+
Files.write_file(@root, name, JSON.generate({"hint_only" => true,
|
|
212
|
+
"project_id" => context.fetch("project_id"), **selection,
|
|
213
|
+
"result_id" => result.fetch("result_id")}))
|
|
214
|
+
File.rename(File.join(@root, name), File.join(@root, "result-hint.json"))
|
|
215
|
+
File.open(@root) { |file| file.fsync }
|
|
216
|
+
ensure
|
|
217
|
+
File.unlink(File.join(@root, name)) if name && File.exist?(File.join(@root, name))
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def worker!(pane)
|
|
221
|
+
raise ArgumentError, "ordinary worker pane differs" unless pane == context.fetch("pane")
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def step(value)
|
|
225
|
+
value && {"number" => value.number, "name" => value.name, "status" => value.status.to_s,
|
|
226
|
+
"instructions" => value.instructions, "workflow" => value.workflow, "skill" => value.skill}
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def read(operation, params)
|
|
230
|
+
Dir.mktmpdir("ordinary-read-", @root) do |directory|
|
|
231
|
+
Files.write_file(directory, "request.json", JSON.generate(document(operation, params, nil, [])))
|
|
232
|
+
@transport.request(input: File.join(directory, "request.json"))
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def snapshot(operation, params)
|
|
237
|
+
Dir.mktmpdir("ordinary-read-", @root) do |directory|
|
|
238
|
+
Files.write_file(directory, "request.json", JSON.generate(document(operation, params, nil, [])))
|
|
239
|
+
reply = @transport.request(input: File.join(directory, "request.json"), output: File.join(directory, "snapshot"))
|
|
240
|
+
entries = reply.fetch("files")
|
|
241
|
+
raise AttemptErrors::EvidenceUnavailable, "canonical queue snapshot count differs" unless entries.one?
|
|
242
|
+
[reply, Files.read_file(entries.first.fetch("file"), Authority::PreparedWork::MAX_TOTAL)]
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def mutate(key, operation, params, parts = [])
|
|
247
|
+
request = retain(key, operation, params, parts)
|
|
248
|
+
@transport.request(input: request).merge("request" => request)
|
|
249
|
+
rescue StandardError => error
|
|
250
|
+
raise error.class, "#{error.message}; recover original request #{request || key} before new work"
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def retain(key, operation, params, parts)
|
|
254
|
+
directory = File.join(@root, "requests")
|
|
255
|
+
begin
|
|
256
|
+
Dir.mkdir(directory, 0o700)
|
|
257
|
+
rescue Errno::EEXIST
|
|
258
|
+
Authority::PrivateDirectory.verify!(directory)
|
|
259
|
+
end
|
|
260
|
+
raise ArgumentError, "ordinary retained operation key is invalid" unless key.match?(/\A[a-z0-9-]{1,128}\z/)
|
|
261
|
+
File.open(File.join(directory, ".lock"), File::RDWR | File::CREAT | File::NOFOLLOW, 0o600) do |lock|
|
|
262
|
+
lock.flock(File::LOCK_EX)
|
|
263
|
+
target = File.join(directory, key)
|
|
264
|
+
value = document(operation, params, nil, inventory(parts))
|
|
265
|
+
if File.exist?(target)
|
|
266
|
+
Authority::PrivateDirectory.verify!(target)
|
|
267
|
+
original = Files.json(File.join(target, "request.json"))
|
|
268
|
+
unless original.except("mutation_id") == value.except("mutation_id")
|
|
269
|
+
raise ArgumentError, "retained operation changed; replay its exact request or reconcile canonical state"
|
|
270
|
+
end
|
|
271
|
+
else
|
|
272
|
+
scratch = Dir.mktmpdir(".prepare-", directory)
|
|
273
|
+
begin
|
|
274
|
+
parts.each_with_index { |bytes, index| Files.write_file(scratch, "part-#{index}", bytes) }
|
|
275
|
+
value["mutation_id"] = SecureRandom.hex(16)
|
|
276
|
+
Files.write_file(scratch, "request.json", JSON.generate(value) + "\n")
|
|
277
|
+
File.rename(scratch, target)
|
|
278
|
+
File.open(directory) { |handle| handle.fsync }
|
|
279
|
+
ensure
|
|
280
|
+
FileUtils.remove_entry(scratch) if File.exist?(scratch)
|
|
281
|
+
end
|
|
282
|
+
end
|
|
283
|
+
File.join(target, "request.json")
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
def document(operation, params, mutation, parts)
|
|
288
|
+
{"version" => 1, "project_id" => context.fetch("project_id"), "operation" => operation,
|
|
289
|
+
"mutation_id" => mutation, "params" => params, "parts" => parts}
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
def inventory(parts)
|
|
293
|
+
parts.each_with_index.map { |bytes, index|
|
|
294
|
+
{"file" => "part-#{index}", "bytes" => bytes.bytesize, "sha256" => Digest::SHA256.hexdigest(bytes)} }
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
def verify_result_intent!(params, parts)
|
|
298
|
+
expected = document("submit_result", params.except("expected_generation"), nil, inventory(parts))
|
|
299
|
+
Dir.glob(File.join(@root, "requests", "result-*", "request.json")).each do |input|
|
|
300
|
+
original = Files.json(input)
|
|
301
|
+
original = original.merge("params" => original.fetch("params").except("expected_generation"))
|
|
302
|
+
unless original.except("mutation_id") == expected.except("mutation_id")
|
|
303
|
+
raise ArgumentError, "result retry changed the retained original intent"
|
|
304
|
+
end
|
|
305
|
+
end
|
|
306
|
+
end
|
|
307
|
+
end
|
|
308
|
+
end
|
|
309
|
+
end
|
|
310
|
+
end
|
|
@@ -212,7 +212,7 @@ module Ace
|
|
|
212
212
|
return {state: "accepted", reference: ack.fetch("reference"),
|
|
213
213
|
request_id: ack.fetch("request_id"), input_digest: ack.fetch("input_digest")}
|
|
214
214
|
end
|
|
215
|
-
handle_refusal(binding, identity, branch[:payload], expected_digest: digest)
|
|
215
|
+
handle_refusal(binding, identity, branch[:payload], expected_digest: digest, request_id: selectors.fetch(:request_id))
|
|
216
216
|
end
|
|
217
217
|
|
|
218
218
|
# Closed outer envelope for admission responses: exactly {status,
|
|
@@ -241,7 +241,7 @@ module Ace
|
|
|
241
241
|
end
|
|
242
242
|
end
|
|
243
243
|
|
|
244
|
-
def handle_refusal(binding, identity, error, expected_digest:)
|
|
244
|
+
def handle_refusal(binding, identity, error, expected_digest:, request_id:)
|
|
245
245
|
refusal = begin
|
|
246
246
|
Molecules::OrdinaryMergeContract.validate_refusal!(error)
|
|
247
247
|
rescue ArgumentError
|
|
@@ -249,9 +249,9 @@ module Ace
|
|
|
249
249
|
"ordinary merge refusal is not the maintained contract; admission stays unconfirmed"
|
|
250
250
|
end
|
|
251
251
|
echo = refusal.fetch("input_digest")
|
|
252
|
-
if echo && echo != expected_digest
|
|
252
|
+
if refusal.fetch("request_id") != request_id || (echo && echo != expected_digest)
|
|
253
253
|
raise AttemptErrors::EvidenceUnavailable,
|
|
254
|
-
"ordinary merge refusal echoes a different
|
|
254
|
+
"ordinary merge refusal echoes a different request; admission stays unconfirmed"
|
|
255
255
|
end
|
|
256
256
|
if refusal.fetch("code") == Molecules::OrdinaryMergeContract::UNAVAILABLE_CODE
|
|
257
257
|
raise AttemptErrors::EvidenceUnavailable,
|
|
@@ -259,7 +259,7 @@ module Ace
|
|
|
259
259
|
end
|
|
260
260
|
record_event(binding, identity, {"stage" => RESULT_STAGE, "outcome" => "refused_no_effect",
|
|
261
261
|
"code" => refusal.fetch("code"), "message" => refusal.fetch("message"),
|
|
262
|
-
"request_id" =>
|
|
262
|
+
"request_id" => request_id, "input_digest" => expected_digest})
|
|
263
263
|
{state: "refused", code: refusal.fetch("code"), message: refusal.fetch("message")}
|
|
264
264
|
end
|
|
265
265
|
|
data/lib/ace/assign/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ace-assign
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.71.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Michal Czyz
|
|
@@ -175,42 +175,42 @@ dependencies:
|
|
|
175
175
|
requirements:
|
|
176
176
|
- - "~>"
|
|
177
177
|
- !ruby/object:Gem::Version
|
|
178
|
-
version: 0.
|
|
178
|
+
version: 0.61.0
|
|
179
179
|
type: :runtime
|
|
180
180
|
prerelease: false
|
|
181
181
|
version_requirements: !ruby/object:Gem::Requirement
|
|
182
182
|
requirements:
|
|
183
183
|
- - "~>"
|
|
184
184
|
- !ruby/object:Gem::Version
|
|
185
|
-
version: 0.
|
|
185
|
+
version: 0.61.0
|
|
186
186
|
- !ruby/object:Gem::Dependency
|
|
187
187
|
name: ace-task
|
|
188
188
|
requirement: !ruby/object:Gem::Requirement
|
|
189
189
|
requirements:
|
|
190
190
|
- - "~>"
|
|
191
191
|
- !ruby/object:Gem::Version
|
|
192
|
-
version: '0.
|
|
192
|
+
version: '0.40'
|
|
193
193
|
type: :runtime
|
|
194
194
|
prerelease: false
|
|
195
195
|
version_requirements: !ruby/object:Gem::Requirement
|
|
196
196
|
requirements:
|
|
197
197
|
- - "~>"
|
|
198
198
|
- !ruby/object:Gem::Version
|
|
199
|
-
version: '0.
|
|
199
|
+
version: '0.40'
|
|
200
200
|
- !ruby/object:Gem::Dependency
|
|
201
201
|
name: ace-bundle
|
|
202
202
|
requirement: !ruby/object:Gem::Requirement
|
|
203
203
|
requirements:
|
|
204
204
|
- - "~>"
|
|
205
205
|
- !ruby/object:Gem::Version
|
|
206
|
-
version:
|
|
206
|
+
version: 0.45.1
|
|
207
207
|
type: :runtime
|
|
208
208
|
prerelease: false
|
|
209
209
|
version_requirements: !ruby/object:Gem::Requirement
|
|
210
210
|
requirements:
|
|
211
211
|
- - "~>"
|
|
212
212
|
- !ruby/object:Gem::Version
|
|
213
|
-
version:
|
|
213
|
+
version: 0.45.1
|
|
214
214
|
- !ruby/object:Gem::Dependency
|
|
215
215
|
name: ace-runtime
|
|
216
216
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -245,14 +245,14 @@ dependencies:
|
|
|
245
245
|
requirements:
|
|
246
246
|
- - "~>"
|
|
247
247
|
- !ruby/object:Gem::Version
|
|
248
|
-
version: 0.
|
|
248
|
+
version: '0.6'
|
|
249
249
|
type: :runtime
|
|
250
250
|
prerelease: false
|
|
251
251
|
version_requirements: !ruby/object:Gem::Requirement
|
|
252
252
|
requirements:
|
|
253
253
|
- - "~>"
|
|
254
254
|
- !ruby/object:Gem::Version
|
|
255
|
-
version: 0.
|
|
255
|
+
version: '0.6'
|
|
256
256
|
- !ruby/object:Gem::Dependency
|
|
257
257
|
name: ace-support-test-helpers
|
|
258
258
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -405,11 +405,13 @@ files:
|
|
|
405
405
|
- lib/ace/assign/atoms/assign_frontmatter_parser.rb
|
|
406
406
|
- lib/ace/assign/atoms/assignment_scope.rb
|
|
407
407
|
- lib/ace/assign/atoms/attempt_state_machine.rb
|
|
408
|
+
- lib/ace/assign/atoms/candidate_limits.rb
|
|
408
409
|
- lib/ace/assign/atoms/catalog_loader.rb
|
|
409
410
|
- lib/ace/assign/atoms/composition_rules.rb
|
|
410
411
|
- lib/ace/assign/atoms/delivery_parameters.rb
|
|
411
412
|
- lib/ace/assign/atoms/evidence_digest.rb
|
|
412
413
|
- lib/ace/assign/atoms/fixed_merge_schemas.rb
|
|
414
|
+
- lib/ace/assign/atoms/git_blob_batch.rb
|
|
413
415
|
- lib/ace/assign/atoms/number_generator.rb
|
|
414
416
|
- lib/ace/assign/atoms/preset_expander.rb
|
|
415
417
|
- lib/ace/assign/atoms/preset_loader.rb
|
|
@@ -418,13 +420,17 @@ files:
|
|
|
418
420
|
- lib/ace/assign/atoms/step_numbering.rb
|
|
419
421
|
- lib/ace/assign/atoms/step_sorter.rb
|
|
420
422
|
- lib/ace/assign/atoms/tree_formatter.rb
|
|
423
|
+
- lib/ace/assign/authority/assignment_definition.rb
|
|
421
424
|
- lib/ace/assign/authority/assignment_inventory.rb
|
|
422
425
|
- lib/ace/assign/authority/campaign_consumer_policy.rb
|
|
423
426
|
- lib/ace/assign/authority/campaign_execution.rb
|
|
424
427
|
- lib/ace/assign/authority/campaign_round_transfer.rb
|
|
428
|
+
- lib/ace/assign/authority/candidate_blob_batch.rb
|
|
429
|
+
- lib/ace/assign/authority/candidate_head_comparison.rb
|
|
425
430
|
- lib/ace/assign/authority/candidate_transfer.rb
|
|
426
431
|
- lib/ace/assign/authority/cleanup_result_protection.rb
|
|
427
432
|
- lib/ace/assign/authority/client.rb
|
|
433
|
+
- lib/ace/assign/authority/command_execution.rb
|
|
428
434
|
- lib/ace/assign/authority/deployment.rb
|
|
429
435
|
- lib/ace/assign/authority/deployment_history.rb
|
|
430
436
|
- lib/ace/assign/authority/endcap.rb
|
|
@@ -438,10 +444,12 @@ files:
|
|
|
438
444
|
- lib/ace/assign/authority/endcap_results.rb
|
|
439
445
|
- lib/ace/assign/authority/endcap_review_requests.rb
|
|
440
446
|
- lib/ace/assign/authority/endcap_reviews.rb
|
|
447
|
+
- lib/ace/assign/authority/endcap_service_context.rb
|
|
441
448
|
- lib/ace/assign/authority/endcap_service_settlement.rb
|
|
442
|
-
- lib/ace/assign/authority/endcap_services.rb
|
|
443
449
|
- lib/ace/assign/authority/evidence_transfer.rb
|
|
444
450
|
- lib/ace/assign/authority/execution_scope_observation.rb
|
|
451
|
+
- lib/ace/assign/authority/fixed_publication_lifecycle.rb
|
|
452
|
+
- lib/ace/assign/authority/fixed_service_lifecycle.rb
|
|
445
453
|
- lib/ace/assign/authority/inbox_context_original.rb
|
|
446
454
|
- lib/ace/assign/authority/launch_attempt_consumers.rb
|
|
447
455
|
- lib/ace/assign/authority/launch_campaign_registration.rb
|
|
@@ -460,6 +468,21 @@ files:
|
|
|
460
468
|
- lib/ace/assign/authority/launch_steering.rb
|
|
461
469
|
- lib/ace/assign/authority/launch_stop.rb
|
|
462
470
|
- lib/ace/assign/authority/maintenance_workspace_retirement.rb
|
|
471
|
+
- lib/ace/assign/authority/ordinary_attempt_start.rb
|
|
472
|
+
- lib/ace/assign/authority/ordinary_client.rb
|
|
473
|
+
- lib/ace/assign/authority/ordinary_completion.rb
|
|
474
|
+
- lib/ace/assign/authority/ordinary_effect_settlement.rb
|
|
475
|
+
- lib/ace/assign/authority/ordinary_lifecycle.rb
|
|
476
|
+
- lib/ace/assign/authority/ordinary_merge_evidence.rb
|
|
477
|
+
- lib/ace/assign/authority/ordinary_peer.rb
|
|
478
|
+
- lib/ace/assign/authority/ordinary_progress.rb
|
|
479
|
+
- lib/ace/assign/authority/ordinary_queue.rb
|
|
480
|
+
- lib/ace/assign/authority/ordinary_release_artifacts.rb
|
|
481
|
+
- lib/ace/assign/authority/ordinary_results.rb
|
|
482
|
+
- lib/ace/assign/authority/ordinary_review_evidence.rb
|
|
483
|
+
- lib/ace/assign/authority/ordinary_reviews.rb
|
|
484
|
+
- lib/ace/assign/authority/ordinary_server.rb
|
|
485
|
+
- lib/ace/assign/authority/ordinary_supervisor.rb
|
|
463
486
|
- lib/ace/assign/authority/original_inbox_selection.rb
|
|
464
487
|
- lib/ace/assign/authority/posix_acl.rb
|
|
465
488
|
- lib/ace/assign/authority/prepared_input.rb
|
|
@@ -471,6 +494,8 @@ files:
|
|
|
471
494
|
- lib/ace/assign/authority/protected_assignment_context.rb
|
|
472
495
|
- lib/ace/assign/authority/readiness_hook.rb
|
|
473
496
|
- lib/ace/assign/authority/receipt_transfer.rb
|
|
497
|
+
- lib/ace/assign/authority/release_artifact_manifest.rb
|
|
498
|
+
- lib/ace/assign/authority/retained_input.rb
|
|
474
499
|
- lib/ace/assign/authority/router.rb
|
|
475
500
|
- lib/ace/assign/authority/server.rb
|
|
476
501
|
- lib/ace/assign/authority/service_cleanup_dispatch.rb
|
|
@@ -478,6 +503,7 @@ files:
|
|
|
478
503
|
- lib/ace/assign/authority/service_delivery_evidence.rb
|
|
479
504
|
- lib/ace/assign/authority/service_evidence.rb
|
|
480
505
|
- lib/ace/assign/authority/service_publication_evidence.rb
|
|
506
|
+
- lib/ace/assign/authority/socket_server.rb
|
|
481
507
|
- lib/ace/assign/authority/task_context_entry.rb
|
|
482
508
|
- lib/ace/assign/authority/transfer_codec.rb
|
|
483
509
|
- lib/ace/assign/cli.rb
|
|
@@ -533,6 +559,7 @@ files:
|
|
|
533
559
|
- lib/ace/assign/molecules/attempt_store.rb
|
|
534
560
|
- lib/ace/assign/molecules/canonical_attempt_state.rb
|
|
535
561
|
- lib/ace/assign/molecules/canonical_evidence.rb
|
|
562
|
+
- lib/ace/assign/molecules/canonical_read_commands.rb
|
|
536
563
|
- lib/ace/assign/molecules/canonical_read_snapshot.rb
|
|
537
564
|
- lib/ace/assign/molecules/evidence_calculator.rb
|
|
538
565
|
- lib/ace/assign/molecules/evidence_journal.rb
|
|
@@ -540,6 +567,7 @@ files:
|
|
|
540
567
|
- lib/ace/assign/molecules/execution_scope_lineage.rb
|
|
541
568
|
- lib/ace/assign/molecules/fixed_effect_reference.rb
|
|
542
569
|
- lib/ace/assign/molecules/fork_session_launcher.rb
|
|
570
|
+
- lib/ace/assign/molecules/installed_journal_read.rb
|
|
543
571
|
- lib/ace/assign/molecules/journal_input_inhibition.rb
|
|
544
572
|
- lib/ace/assign/molecules/journal_mutation.rb
|
|
545
573
|
- lib/ace/assign/molecules/journal_prompt_mutation.rb
|
|
@@ -562,6 +590,7 @@ files:
|
|
|
562
590
|
- lib/ace/assign/organisms/assignment_executor.rb
|
|
563
591
|
- lib/ace/assign/organisms/attempt_coordinator.rb
|
|
564
592
|
- lib/ace/assign/organisms/delivery_coordinator.rb
|
|
593
|
+
- lib/ace/assign/organisms/ordinary_driver.rb
|
|
565
594
|
- lib/ace/assign/organisms/ordinary_merge_consumer.rb
|
|
566
595
|
- lib/ace/assign/organisms/prepared_work_builder.rb
|
|
567
596
|
- lib/ace/assign/organisms/protected_delivery_coordinator.rb
|