ace-assign 0.70.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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +20 -0
  3. data/handbook/skills/as-assign-drive/SKILL.md +5 -2
  4. data/handbook/workflow-instructions/assign/drive.wf.md +77 -0
  5. data/lib/ace/assign/atoms/candidate_limits.rb +20 -0
  6. data/lib/ace/assign/atoms/git_blob_batch.rb +59 -0
  7. data/lib/ace/assign/authority/candidate_blob_batch.rb +55 -0
  8. data/lib/ace/assign/authority/candidate_head_comparison.rb +139 -0
  9. data/lib/ace/assign/authority/candidate_transfer.rb +55 -25
  10. data/lib/ace/assign/authority/command_execution.rb +180 -0
  11. data/lib/ace/assign/authority/endcap.rb +4 -2
  12. data/lib/ace/assign/authority/endcap_campaign_rounds.rb +1 -1
  13. data/lib/ace/assign/authority/endcap_publication.rb +0 -166
  14. data/lib/ace/assign/authority/endcap_service_context.rb +38 -0
  15. data/lib/ace/assign/authority/fixed_publication_lifecycle.rb +176 -0
  16. data/lib/ace/assign/authority/{endcap_services.rb → fixed_service_lifecycle.rb} +42 -46
  17. data/lib/ace/assign/authority/ordinary_completion.rb +13 -3
  18. data/lib/ace/assign/authority/ordinary_lifecycle.rb +40 -4
  19. data/lib/ace/assign/authority/ordinary_merge_evidence.rb +81 -0
  20. data/lib/ace/assign/authority/ordinary_release_artifacts.rb +143 -0
  21. data/lib/ace/assign/authority/ordinary_results.rb +11 -1
  22. data/lib/ace/assign/authority/ordinary_review_evidence.rb +180 -0
  23. data/lib/ace/assign/authority/ordinary_reviews.rb +214 -0
  24. data/lib/ace/assign/authority/ordinary_supervisor.rb +99 -0
  25. data/lib/ace/assign/authority/prepared_work.rb +7 -2
  26. data/lib/ace/assign/authority/release_artifact_manifest.rb +91 -0
  27. data/lib/ace/assign/authority/retained_input.rb +53 -0
  28. data/lib/ace/assign/authority/transfer_codec.rb +5 -3
  29. data/lib/ace/assign/cli/commands/submit_candidate.rb +1 -1
  30. data/lib/ace/assign/molecules/canonical_evidence.rb +25 -8
  31. data/lib/ace/assign/molecules/evidence_journal.rb +17 -30
  32. data/lib/ace/assign/molecules/journal_mutation.rb +2 -1
  33. data/lib/ace/assign/molecules/receipt_verifier.rb +8 -2
  34. data/lib/ace/assign/molecules/runtime_control_surface_runner.rb +2 -1
  35. data/lib/ace/assign/organisms/attempt_coordinator.rb +40 -3
  36. data/lib/ace/assign/organisms/ordinary_driver.rb +310 -0
  37. data/lib/ace/assign/version.rb +1 -1
  38. metadata +19 -4
@@ -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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Ace
4
4
  module Assign
5
- VERSION = "0.70.0"
5
+ VERSION = "0.71.0"
6
6
  end
7
7
  end
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.70.0
4
+ version: 0.71.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michal Czyz
@@ -175,14 +175,14 @@ dependencies:
175
175
  requirements:
176
176
  - - "~>"
177
177
  - !ruby/object:Gem::Version
178
- version: 0.60.3
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.60.3
185
+ version: 0.61.0
186
186
  - !ruby/object:Gem::Dependency
187
187
  name: ace-task
188
188
  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
@@ -423,9 +425,12 @@ files:
423
425
  - lib/ace/assign/authority/campaign_consumer_policy.rb
424
426
  - lib/ace/assign/authority/campaign_execution.rb
425
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
426
430
  - lib/ace/assign/authority/candidate_transfer.rb
427
431
  - lib/ace/assign/authority/cleanup_result_protection.rb
428
432
  - lib/ace/assign/authority/client.rb
433
+ - lib/ace/assign/authority/command_execution.rb
429
434
  - lib/ace/assign/authority/deployment.rb
430
435
  - lib/ace/assign/authority/deployment_history.rb
431
436
  - lib/ace/assign/authority/endcap.rb
@@ -439,10 +444,12 @@ files:
439
444
  - lib/ace/assign/authority/endcap_results.rb
440
445
  - lib/ace/assign/authority/endcap_review_requests.rb
441
446
  - lib/ace/assign/authority/endcap_reviews.rb
447
+ - lib/ace/assign/authority/endcap_service_context.rb
442
448
  - lib/ace/assign/authority/endcap_service_settlement.rb
443
- - lib/ace/assign/authority/endcap_services.rb
444
449
  - lib/ace/assign/authority/evidence_transfer.rb
445
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
446
453
  - lib/ace/assign/authority/inbox_context_original.rb
447
454
  - lib/ace/assign/authority/launch_attempt_consumers.rb
448
455
  - lib/ace/assign/authority/launch_campaign_registration.rb
@@ -466,11 +473,16 @@ files:
466
473
  - lib/ace/assign/authority/ordinary_completion.rb
467
474
  - lib/ace/assign/authority/ordinary_effect_settlement.rb
468
475
  - lib/ace/assign/authority/ordinary_lifecycle.rb
476
+ - lib/ace/assign/authority/ordinary_merge_evidence.rb
469
477
  - lib/ace/assign/authority/ordinary_peer.rb
470
478
  - lib/ace/assign/authority/ordinary_progress.rb
471
479
  - lib/ace/assign/authority/ordinary_queue.rb
480
+ - lib/ace/assign/authority/ordinary_release_artifacts.rb
472
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
473
484
  - lib/ace/assign/authority/ordinary_server.rb
485
+ - lib/ace/assign/authority/ordinary_supervisor.rb
474
486
  - lib/ace/assign/authority/original_inbox_selection.rb
475
487
  - lib/ace/assign/authority/posix_acl.rb
476
488
  - lib/ace/assign/authority/prepared_input.rb
@@ -482,6 +494,8 @@ files:
482
494
  - lib/ace/assign/authority/protected_assignment_context.rb
483
495
  - lib/ace/assign/authority/readiness_hook.rb
484
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
485
499
  - lib/ace/assign/authority/router.rb
486
500
  - lib/ace/assign/authority/server.rb
487
501
  - lib/ace/assign/authority/service_cleanup_dispatch.rb
@@ -576,6 +590,7 @@ files:
576
590
  - lib/ace/assign/organisms/assignment_executor.rb
577
591
  - lib/ace/assign/organisms/attempt_coordinator.rb
578
592
  - lib/ace/assign/organisms/delivery_coordinator.rb
593
+ - lib/ace/assign/organisms/ordinary_driver.rb
579
594
  - lib/ace/assign/organisms/ordinary_merge_consumer.rb
580
595
  - lib/ace/assign/organisms/prepared_work_builder.rb
581
596
  - lib/ace/assign/organisms/protected_delivery_coordinator.rb