ace-assign 0.70.0 → 0.73.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 (181) hide show
  1. checksums.yaml +4 -4
  2. data/.ace-defaults/assign/catalog/steps/auto-merge.step.yml +8 -8
  3. data/.ace-defaults/assign/catalog/steps/create-pr.step.yml +1 -1
  4. data/.ace-defaults/assign/catalog/steps/mark-pr-ready.step.yml +1 -1
  5. data/.ace-defaults/assign/config.yml +0 -13
  6. data/.ace-defaults/assign/presets/fix-bug.yml +1 -1
  7. data/.ace-defaults/assign/presets/work-on-task-ace-development.yml +1 -1
  8. data/.ace-defaults/assign/presets/work-on-task-auto-merge.yml +1 -1
  9. data/.ace-defaults/assign/presets/work-on-task.yml +1 -1
  10. data/CHANGELOG.md +3 -2099
  11. data/README.md +58 -31
  12. data/docs/exit-codes.md +36 -44
  13. data/docs/getting-started.md +9 -4
  14. data/docs/usage.md +129 -522
  15. data/handbook/skills/as-assign-drive/SKILL.md +1 -1
  16. data/handbook/workflow-instructions/assign/drive.wf.md +47 -118
  17. data/handbook/workflow-instructions/assign/prepare.wf.md +1 -1
  18. data/handbook/workflow-instructions/assign/verify-test-suite.wf.md +8 -2
  19. data/lib/ace/assign/cli/commands/assignment_target.rb +2 -15
  20. data/lib/ace/assign/cli/commands/create.rb +1 -8
  21. data/lib/ace/assign/cli/commands/fail.rb +0 -6
  22. data/lib/ace/assign/cli/commands/finish.rb +2 -23
  23. data/lib/ace/assign/cli/commands/fork_session.rb +0 -1
  24. data/lib/ace/assign/cli/commands/select.rb +0 -1
  25. data/lib/ace/assign/cli/commands/start.rb +0 -2
  26. data/lib/ace/assign/cli/commands/status.rb +1 -31
  27. data/lib/ace/assign/cli/commands/step.rb +1 -3
  28. data/lib/ace/assign/cli.rb +1 -85
  29. data/lib/ace/assign/errors.rb +8 -80
  30. data/lib/ace/assign/models/assignment.rb +5 -28
  31. data/lib/ace/assign/molecules/assignment_manager.rb +0 -26
  32. data/lib/ace/assign/molecules/fork_session_launcher.rb +4 -12
  33. data/lib/ace/assign/molecules/lifecycle_exclusion.rb +15 -112
  34. data/lib/ace/assign/molecules/runtime_control_surface_runner.rb +2 -1
  35. data/lib/ace/assign/organisms/assignment_executor.rb +0 -6
  36. data/lib/ace/assign/organisms/task_assignment_creator.rb +3 -8
  37. data/lib/ace/assign/version.rb +1 -1
  38. data/lib/ace/assign.rb +0 -6
  39. metadata +4 -161
  40. data/.ace-defaults/assign/presets/close-delivery.yml +0 -57
  41. data/.ace-defaults/assign/presets/deliver-release.yml +0 -60
  42. data/.ace-defaults/assign/presets/integrate-batch.yml +0 -72
  43. data/.ace-defaults/assign/presets/supervise-delivery.yml +0 -69
  44. data/docs/protected-authority.md +0 -282
  45. data/exe/ace-scope-ready +0 -55
  46. data/lib/ace/assign/atoms/assignment_scope.rb +0 -58
  47. data/lib/ace/assign/atoms/attempt_state_machine.rb +0 -84
  48. data/lib/ace/assign/atoms/delivery_parameters.rb +0 -19
  49. data/lib/ace/assign/atoms/evidence_digest.rb +0 -78
  50. data/lib/ace/assign/atoms/fixed_merge_schemas.rb +0 -398
  51. data/lib/ace/assign/authority/assignment_definition.rb +0 -35
  52. data/lib/ace/assign/authority/assignment_inventory.rb +0 -290
  53. data/lib/ace/assign/authority/campaign_consumer_policy.rb +0 -100
  54. data/lib/ace/assign/authority/campaign_execution.rb +0 -119
  55. data/lib/ace/assign/authority/campaign_round_transfer.rb +0 -82
  56. data/lib/ace/assign/authority/candidate_transfer.rb +0 -327
  57. data/lib/ace/assign/authority/cleanup_result_protection.rb +0 -53
  58. data/lib/ace/assign/authority/client.rb +0 -257
  59. data/lib/ace/assign/authority/deployment.rb +0 -627
  60. data/lib/ace/assign/authority/deployment_history.rb +0 -146
  61. data/lib/ace/assign/authority/endcap.rb +0 -455
  62. data/lib/ace/assign/authority/endcap_attempts.rb +0 -274
  63. data/lib/ace/assign/authority/endcap_campaign_results.rb +0 -232
  64. data/lib/ace/assign/authority/endcap_campaign_rounds.rb +0 -266
  65. data/lib/ace/assign/authority/endcap_contexts.rb +0 -23
  66. data/lib/ace/assign/authority/endcap_inboxes.rb +0 -37
  67. data/lib/ace/assign/authority/endcap_prepared_work.rb +0 -80
  68. data/lib/ace/assign/authority/endcap_publication.rb +0 -207
  69. data/lib/ace/assign/authority/endcap_results.rb +0 -398
  70. data/lib/ace/assign/authority/endcap_review_requests.rb +0 -183
  71. data/lib/ace/assign/authority/endcap_reviews.rb +0 -130
  72. data/lib/ace/assign/authority/endcap_service_settlement.rb +0 -152
  73. data/lib/ace/assign/authority/endcap_services.rb +0 -598
  74. data/lib/ace/assign/authority/evidence_transfer.rb +0 -67
  75. data/lib/ace/assign/authority/execution_scope_observation.rb +0 -903
  76. data/lib/ace/assign/authority/inbox_context_original.rb +0 -113
  77. data/lib/ace/assign/authority/launch_attempt_consumers.rb +0 -51
  78. data/lib/ace/assign/authority/launch_campaign_registration.rb +0 -214
  79. data/lib/ace/assign/authority/launch_control_channel.rb +0 -313
  80. data/lib/ace/assign/authority/launch_control_exclusions.rb +0 -127
  81. data/lib/ace/assign/authority/launch_driver.rb +0 -393
  82. data/lib/ace/assign/authority/launch_input_inhibition.rb +0 -132
  83. data/lib/ace/assign/authority/launch_lifecycle.rb +0 -997
  84. data/lib/ace/assign/authority/launch_prepared_work.rb +0 -122
  85. data/lib/ace/assign/authority/launch_review.rb +0 -91
  86. data/lib/ace/assign/authority/launch_scope_abort.rb +0 -81
  87. data/lib/ace/assign/authority/launch_scope_admission.rb +0 -215
  88. data/lib/ace/assign/authority/launch_scope_close.rb +0 -146
  89. data/lib/ace/assign/authority/launch_scope_parent.rb +0 -44
  90. data/lib/ace/assign/authority/launch_scope_release.rb +0 -859
  91. data/lib/ace/assign/authority/launch_steering.rb +0 -353
  92. data/lib/ace/assign/authority/launch_stop.rb +0 -254
  93. data/lib/ace/assign/authority/maintenance_workspace_retirement.rb +0 -229
  94. data/lib/ace/assign/authority/ordinary_attempt_start.rb +0 -137
  95. data/lib/ace/assign/authority/ordinary_client.rb +0 -70
  96. data/lib/ace/assign/authority/ordinary_completion.rb +0 -94
  97. data/lib/ace/assign/authority/ordinary_effect_settlement.rb +0 -118
  98. data/lib/ace/assign/authority/ordinary_lifecycle.rb +0 -191
  99. data/lib/ace/assign/authority/ordinary_peer.rb +0 -54
  100. data/lib/ace/assign/authority/ordinary_progress.rb +0 -162
  101. data/lib/ace/assign/authority/ordinary_queue.rb +0 -115
  102. data/lib/ace/assign/authority/ordinary_results.rb +0 -196
  103. data/lib/ace/assign/authority/ordinary_server.rb +0 -75
  104. data/lib/ace/assign/authority/original_inbox_selection.rb +0 -41
  105. data/lib/ace/assign/authority/posix_acl.rb +0 -59
  106. data/lib/ace/assign/authority/prepared_input.rb +0 -130
  107. data/lib/ace/assign/authority/prepared_queue.rb +0 -185
  108. data/lib/ace/assign/authority/prepared_task_context.rb +0 -116
  109. data/lib/ace/assign/authority/prepared_work.rb +0 -304
  110. data/lib/ace/assign/authority/prepared_worker.rb +0 -69
  111. data/lib/ace/assign/authority/private_directory.rb +0 -33
  112. data/lib/ace/assign/authority/protected_assignment_context.rb +0 -226
  113. data/lib/ace/assign/authority/readiness_hook.rb +0 -120
  114. data/lib/ace/assign/authority/receipt_transfer.rb +0 -48
  115. data/lib/ace/assign/authority/router.rb +0 -81
  116. data/lib/ace/assign/authority/server.rb +0 -131
  117. data/lib/ace/assign/authority/service_cleanup_dispatch.rb +0 -88
  118. data/lib/ace/assign/authority/service_cleanup_evidence.rb +0 -180
  119. data/lib/ace/assign/authority/service_delivery_evidence.rb +0 -101
  120. data/lib/ace/assign/authority/service_evidence.rb +0 -286
  121. data/lib/ace/assign/authority/service_publication_evidence.rb +0 -110
  122. data/lib/ace/assign/authority/socket_server.rb +0 -255
  123. data/lib/ace/assign/authority/task_context_entry.rb +0 -32
  124. data/lib/ace/assign/authority/transfer_codec.rb +0 -187
  125. data/lib/ace/assign/cli/commands/attempt/base.rb +0 -84
  126. data/lib/ace/assign/cli/commands/attempt/evidence.rb +0 -28
  127. data/lib/ace/assign/cli/commands/attempt/finish.rb +0 -51
  128. data/lib/ace/assign/cli/commands/attempt/reconcile.rb +0 -49
  129. data/lib/ace/assign/cli/commands/attempt/start.rb +0 -45
  130. data/lib/ace/assign/cli/commands/attempt/status.rb +0 -54
  131. data/lib/ace/assign/cli/commands/authority/inbox_context_principal.rb +0 -25
  132. data/lib/ace/assign/cli/commands/authority/inbox_context_selection.rb +0 -28
  133. data/lib/ace/assign/cli/commands/authority/launch.rb +0 -97
  134. data/lib/ace/assign/cli/commands/authority/serve.rb +0 -31
  135. data/lib/ace/assign/cli/commands/authority/status.rb +0 -26
  136. data/lib/ace/assign/cli/commands/authority/task_context.rb +0 -31
  137. data/lib/ace/assign/cli/commands/authority/task_context_principal.rb +0 -25
  138. data/lib/ace/assign/cli/commands/authority/task_context_selection.rb +0 -30
  139. data/lib/ace/assign/cli/commands/authority/terminate.rb +0 -29
  140. data/lib/ace/assign/cli/commands/authority/worker.rb +0 -23
  141. data/lib/ace/assign/cli/commands/campaign_export_result.rb +0 -55
  142. data/lib/ace/assign/cli/commands/campaign_record_round.rb +0 -57
  143. data/lib/ace/assign/cli/commands/delivery.rb +0 -107
  144. data/lib/ace/assign/cli/commands/inbox_bind.rb +0 -34
  145. data/lib/ace/assign/cli/commands/protected_submission.rb +0 -61
  146. data/lib/ace/assign/cli/commands/resume.rb +0 -38
  147. data/lib/ace/assign/cli/commands/submit_candidate.rb +0 -34
  148. data/lib/ace/assign/cli/commands/submit_result.rb +0 -61
  149. data/lib/ace/assign/models/attempt.rb +0 -178
  150. data/lib/ace/assign/models/attempt_binding.rb +0 -116
  151. data/lib/ace/assign/models/evidence_event.rb +0 -86
  152. data/lib/ace/assign/models/execution_receipt.rb +0 -139
  153. data/lib/ace/assign/molecules/attempt_reconciler.rb +0 -127
  154. data/lib/ace/assign/molecules/attempt_store.rb +0 -201
  155. data/lib/ace/assign/molecules/canonical_attempt_state.rb +0 -151
  156. data/lib/ace/assign/molecules/canonical_evidence.rb +0 -153
  157. data/lib/ace/assign/molecules/canonical_read_commands.rb +0 -84
  158. data/lib/ace/assign/molecules/canonical_read_snapshot.rb +0 -313
  159. data/lib/ace/assign/molecules/evidence_calculator.rb +0 -237
  160. data/lib/ace/assign/molecules/evidence_journal.rb +0 -1912
  161. data/lib/ace/assign/molecules/execution_identity_resolver.rb +0 -137
  162. data/lib/ace/assign/molecules/execution_scope_lineage.rb +0 -345
  163. data/lib/ace/assign/molecules/fixed_effect_reference.rb +0 -31
  164. data/lib/ace/assign/molecules/installed_journal_read.rb +0 -143
  165. data/lib/ace/assign/molecules/journal_input_inhibition.rb +0 -78
  166. data/lib/ace/assign/molecules/journal_mutation.rb +0 -273
  167. data/lib/ace/assign/molecules/journal_prompt_mutation.rb +0 -241
  168. data/lib/ace/assign/molecules/ordinary_merge_cli.rb +0 -68
  169. data/lib/ace/assign/molecules/ordinary_merge_contract.rb +0 -410
  170. data/lib/ace/assign/molecules/original_launch_binding.rb +0 -37
  171. data/lib/ace/assign/molecules/proposal_journal.rb +0 -179
  172. data/lib/ace/assign/molecules/protected_control_exclusion.rb +0 -303
  173. data/lib/ace/assign/molecules/protected_workspace_exclusion.rb +0 -330
  174. data/lib/ace/assign/molecules/protected_workspace_provisioner.rb +0 -164
  175. data/lib/ace/assign/molecules/receipt_verifier.rb +0 -314
  176. data/lib/ace/assign/molecules/terminal_scope_receipt.rb +0 -88
  177. data/lib/ace/assign/organisms/attempt_coordinator.rb +0 -1337
  178. data/lib/ace/assign/organisms/delivery_coordinator.rb +0 -342
  179. data/lib/ace/assign/organisms/ordinary_merge_consumer.rb +0 -365
  180. data/lib/ace/assign/organisms/prepared_work_builder.rb +0 -194
  181. data/lib/ace/assign/organisms/protected_delivery_coordinator.rb +0 -100
@@ -1,313 +0,0 @@
1
- # frozen_string_literal: true
2
- require "securerandom"
3
- require_relative "transfer_codec"
4
-
5
- module Ace
6
- module Assign
7
- module Authority
8
- # A rendezvous for one already authenticated original launcher stream.
9
- # Canonical issue permits belong to LaunchLifecycle/JournalMutation.
10
- # This queue owns no replay, authorization, terminal state or effects.
11
- class LaunchControlChannel
12
- Pending = Struct.new(:frame, :bytes, :deadline, :result, :error, :finished, keyword_init: true)
13
- PROMPT_FIELDS = %w[attempt_id intent_event_id journal_commit mutation_id original_binding_digest text_descriptor transfer_id type version].freeze
14
- OUTCOME_FIELDS = %w[guarded_evidence intent_event_id mutation_id original_binding_digest type version].freeze
15
- RECORDED_FIELDS = %w[intent_event_id journal_commit mutation_id original_binding_digest outcome type version].freeze
16
- REVIEW_FIELDS = %w[attempt_id journal_commit mutation_id original_binding_digest request_event_id type version].freeze
17
- REVIEW_ASSIGNED_FIELDS = %w[assignment_event_id journal_commit mutation_id original_binding_digest request_event_id type version].freeze
18
- INHIBIT_FIELDS = %w[attempt_id journal_commit original_binding_digest seal_event_id type version].freeze
19
- INHIBIT_OUTCOME_FIELDS = %w[guarded_evidence original_binding_digest seal_event_id type version].freeze
20
- INHIBIT_RECORDED_FIELDS = %w[journal_commit original_binding_digest seal_event_id type version].freeze
21
-
22
- def initialize(socket:, codec:, outcome:, input_inhibition: nil)
23
- @socket, @codec, @outcome = socket, codec, outcome
24
- @input_inhibition = input_inhibition
25
- @mutex, @changed = Mutex.new, ConditionVariable.new
26
- @pending = nil
27
- @closed = false
28
- end
29
-
30
- def reserve_dispatch!
31
- @mutex.synchronize do
32
- raise AttemptErrors::EvidenceUnavailable, "Original launcher control channel is unavailable" if @closed
33
- raise AttemptErrors::Conflict, "Original launcher input is inhibited" if @inhibiting
34
- raise AttemptErrors::Conflict, "Original launcher control channel is busy" if @reservation || @pending
35
- @reservation = Object.new.freeze
36
- end
37
- end
38
-
39
- def inhibit_input(frame:, deadline:)
40
- self.class.validate_inhibit!(frame)
41
- pending = Pending.new(frame: frame, deadline: deadline, finished: false)
42
- @mutex.synchronize do
43
- @inhibiting = true
44
- if @inhibition_pending
45
- unless @inhibition_pending.frame.slice("attempt_id", "original_binding_digest", "seal_event_id") == frame.slice("attempt_id", "original_binding_digest", "seal_event_id")
46
- raise AttemptErrors::Conflict, "Original input inhibition names another seal"
47
- end
48
- pending = @inhibition_pending
49
- else
50
- @inhibition_pending = pending
51
- while @reservation || @pending
52
- raise AttemptErrors::EvidenceUnavailable, "Original launcher control channel is unavailable" if @closed
53
- remaining = deadline - Process.clock_gettime(Process::CLOCK_MONOTONIC)
54
- raise AttemptErrors::EvidenceUnavailable, "Original input inhibition is uncertain" unless remaining.positive?
55
- @changed.wait(@mutex, remaining)
56
- end
57
- raise AttemptErrors::EvidenceUnavailable, "Original launcher control channel is unavailable" if @closed
58
- @pending = pending
59
- @changed.broadcast
60
- end
61
- until pending.finished
62
- remaining = deadline - Process.clock_gettime(Process::CLOCK_MONOTONIC)
63
- raise AttemptErrors::EvidenceUnavailable, "Original input inhibition is uncertain" unless remaining.positive?
64
- @changed.wait(@mutex, remaining)
65
- end
66
- end
67
- raise pending.error if pending.error
68
- pending.result
69
- rescue AttemptErrors::EvidenceUnavailable
70
- close
71
- raise
72
- end
73
-
74
- def dispatch_reserved?(reservation)
75
- @mutex.synchronize { !@closed && !reservation.nil? && @reservation.equal?(reservation) }
76
- end
77
-
78
- def release_dispatch!(reservation)
79
- @mutex.synchronize do
80
- @reservation = nil if @reservation.equal?(reservation) && !@pending
81
- @changed.broadcast if @inhibiting
82
- end
83
- end
84
-
85
- def dispatch_prompt(frame:, bytes:, deadline:, reservation:)
86
- validate_prompt!(frame)
87
- dispatch_pending(Pending.new(frame: frame, bytes: bytes, deadline: deadline, finished: false), reservation)
88
- end
89
-
90
- def dispatch_review(frame:, deadline:, reservation:)
91
- self.class.validate_review!(frame)
92
- dispatch_pending(Pending.new(frame: frame, deadline: deadline, finished: false), reservation)
93
- end
94
-
95
- def dispatch_pending(pending, reservation)
96
- @mutex.synchronize do
97
- raise AttemptErrors::EvidenceUnavailable, "Original launcher control channel is unavailable" if @closed
98
- raise AttemptErrors::EvidenceUnavailable, "Original launcher dispatch capacity is not held" unless reservation && @reservation.equal?(reservation)
99
- raise AttemptErrors::Conflict, "Original launcher control channel is busy" if @pending
100
- @pending = pending
101
- @changed.broadcast
102
- until pending.finished
103
- remaining = pending.deadline - Process.clock_gettime(Process::CLOCK_MONOTONIC)
104
- raise AttemptErrors::EvidenceUnavailable, "Original launcher dispatch outcome is uncertain" unless remaining.positive?
105
- @changed.wait(@mutex, remaining)
106
- end
107
- end
108
- raise pending.error if pending.error
109
- pending.result
110
- end
111
-
112
- private :dispatch_pending
113
-
114
- # Only the existing Server connection handler calls serve. No slot,
115
- # assignment, journal or channel mutex is held over a socket wait or
116
- # the original owner's canonical outcome ingestion callback.
117
- def serve
118
- loop do
119
- pending = @mutex.synchronize do
120
- @changed.wait(@mutex, 1) unless @closed || @pending
121
- break if @closed
122
- @pending
123
- end
124
- break if closed?
125
- unless pending
126
- ping
127
- next
128
- end
129
- begin
130
- wire.write(@socket, pending.frame, deadline: pending.deadline, limit: 16_384)
131
- if pending.frame.fetch("type") == "launch_review_delegate"
132
- result = wire.read(@socket, deadline: pending.deadline, limit: 16_384)
133
- self.class.validate_review_assigned!(result, pending.frame)
134
- # This transport correlation is not assignment acceptance.
135
- # The canonical request owner must authenticate the returned
136
- # event/commit before recording the public response.
137
- complete(pending, result: result)
138
- next
139
- end
140
- if pending.frame.fetch("type") == "launch_input_inhibit"
141
- result = wire.read(@socket, deadline: pending.deadline, limit: 16_384)
142
- self.class.validate_inhibit_outcome!(result, pending.frame)
143
- raise AttemptErrors::EvidenceUnavailable, "Canonical input inhibition owner unavailable" unless @input_inhibition
144
- accepted = @input_inhibition.call(result)
145
- unless accepted.is_a?(Hash) && accepted.keys.sort == %w[journal_commit original_binding_digest seal_event_id] &&
146
- %w[original_binding_digest seal_event_id].all? { |key| accepted[key] == pending.frame.fetch(key) }
147
- raise AttemptErrors::EvidenceUnavailable, "Canonical input inhibition acceptance differs"
148
- end
149
- recorded = pending.frame.slice("original_binding_digest", "seal_event_id").merge("version" => 1,
150
- "type" => "launch_input_inhibit_recorded", "journal_commit" => accepted.fetch("journal_commit"))
151
- self.class.validate_inhibit_recorded!(recorded, pending.frame)
152
- wire.write(@socket, recorded, deadline: pending.deadline, limit: 16_384)
153
- complete(pending, result: result)
154
- next
155
- end
156
- @codec.send_launch_prompt(@socket, bytes: pending.bytes, descriptor: pending.frame.fetch("text_descriptor"),
157
- transfer_id: pending.frame.fetch("transfer_id"), deadline: pending.deadline)
158
- pending.bytes = nil
159
- result = wire.read(@socket, deadline: pending.deadline, limit: 16_384)
160
- validate_outcome!(result, pending.frame)
161
- accepted = @outcome.call(result)
162
- unless accepted.is_a?(Hash) && accepted.keys.sort == %w[journal_commit outcome] &&
163
- accepted["outcome"] == result.dig("guarded_evidence", "outcome") &&
164
- accepted["journal_commit"].is_a?(String) && accepted["journal_commit"].match?(/\A(?:[0-9a-f]{40}|[0-9a-f]{64})\z/)
165
- raise AttemptErrors::EvidenceUnavailable, "Canonical prompt completion acknowledgement is unavailable"
166
- end
167
- recorded = pending.frame.slice("mutation_id", "intent_event_id", "original_binding_digest").merge(accepted).merge(
168
- "version" => 1, "type" => "prompt_outcome_recorded")
169
- wire.write(@socket, recorded, deadline: pending.deadline, limit: 16_384)
170
- complete(pending, result: result)
171
- rescue StandardError => error
172
- complete(pending, error: error)
173
- break
174
- end
175
- end
176
- rescue IOError, SystemCallError, Ace::Runtime::RuntimeUnavailableError
177
- nil
178
- ensure
179
- close
180
- end
181
-
182
- def close
183
- @mutex.synchronize do
184
- @closed = true
185
- @reservation = nil
186
- if @pending && !@pending.finished
187
- @pending.error = AttemptErrors::EvidenceUnavailable.new("Original launcher control channel closed with uncertain outcome")
188
- @pending.finished = true
189
- @pending.bytes = nil
190
- end
191
- if @inhibition_pending && !@inhibition_pending.finished
192
- @inhibition_pending.error = AttemptErrors::EvidenceUnavailable.new("Original input inhibition is uncertain")
193
- @inhibition_pending.finished = true
194
- end
195
- @changed.broadcast
196
- end
197
- end
198
-
199
- def closed?
200
- @mutex.synchronize { @closed }
201
- end
202
-
203
- def self.validate_review!(frame)
204
- unless frame.is_a?(Hash) && frame.keys.sort == REVIEW_FIELDS &&
205
- frame["version"].is_a?(Integer) && frame["version"] == 1 && frame["type"] == "launch_review_delegate" &&
206
- %w[attempt_id mutation_id].all? { |key| frame[key].is_a?(String) && frame[key].match?(Molecules::JournalMutation::ID) } &&
207
- %w[request_event_id original_binding_digest].all? { |key| frame[key].is_a?(String) && frame[key].match?(/\A[0-9a-f]{64}\z/) } &&
208
- frame["journal_commit"].is_a?(String) && frame["journal_commit"].match?(/\A(?:[0-9a-f]{40}|[0-9a-f]{64})\z/)
209
- raise AttemptErrors::EvidenceUnavailable, "Private review delegation differs"
210
- end
211
- true
212
- end
213
-
214
- def self.validate_review_assigned!(result, frame)
215
- unless result.is_a?(Hash) && result.keys.sort == REVIEW_ASSIGNED_FIELDS &&
216
- result["version"].is_a?(Integer) && result["version"] == 1 && result["type"] == "launch_review_assigned" &&
217
- %w[mutation_id request_event_id original_binding_digest].all? { |key| result[key] == frame.fetch(key) } &&
218
- result["assignment_event_id"].is_a?(String) && result["assignment_event_id"].match?(/\A[0-9a-f]{64}\z/) &&
219
- result["journal_commit"].is_a?(String) && result["journal_commit"].match?(/\A(?:[0-9a-f]{40}|[0-9a-f]{64})\z/)
220
- raise AttemptErrors::EvidenceUnavailable, "Private review assignment does not join original request"
221
- end
222
- true
223
- end
224
-
225
- def self.validate_prompt!(frame)
226
- unless frame.is_a?(Hash) && frame.keys.all? { |key| key.is_a?(String) } && frame.keys.sort == PROMPT_FIELDS &&
227
- frame["version"].is_a?(Integer) && frame["version"] == 1 && frame["type"] == "prompt_dispatch" &&
228
- %w[intent_event_id original_binding_digest].all? { |key| frame[key].is_a?(String) && frame[key].match?(/\A[0-9a-f]{64}\z/) } &&
229
- frame["journal_commit"].is_a?(String) && frame["journal_commit"].match?(/\A(?:[0-9a-f]{40}|[0-9a-f]{64})\z/) &&
230
- %w[attempt_id mutation_id].all? { |key| frame[key].is_a?(String) && frame[key].match?(/\A[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}\z/) } &&
231
- frame["transfer_id"].is_a?(String) && frame["transfer_id"].match?(/\A[0-9a-f]{32}\z/)
232
- raise AttemptErrors::EvidenceUnavailable, "Private prompt dispatch frame is malformed"
233
- end
234
- TransferCodec.new.validate!(frame.fetch("text_descriptor"), :prompt_text)
235
- true
236
- end
237
-
238
- def self.validate_recorded!(recorded, frame, evidence)
239
- unless recorded.is_a?(Hash) && recorded.keys.sort == RECORDED_FIELDS && recorded["version"].is_a?(Integer) && recorded["version"] == 1 &&
240
- recorded["type"] == "prompt_outcome_recorded" && recorded["outcome"] == evidence.fetch("outcome") &&
241
- %w[mutation_id intent_event_id original_binding_digest].all? { |key| recorded[key] == frame.fetch(key) } &&
242
- recorded["journal_commit"].is_a?(String) && recorded["journal_commit"].match?(/\A(?:[0-9a-f]{40}|[0-9a-f]{64})\z/)
243
- raise AttemptErrors::EvidenceUnavailable, "Canonical prompt completion acknowledgement differs"
244
- end
245
- true
246
- end
247
-
248
- def self.validate_inhibit!(frame)
249
- unless frame.is_a?(Hash) && frame.keys.sort == INHIBIT_FIELDS && frame["version"].is_a?(Integer) && frame["version"] == 1 &&
250
- frame["type"] == "launch_input_inhibit" && frame["attempt_id"].is_a?(String) && frame["attempt_id"].match?(/\A[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}\z/) &&
251
- %w[original_binding_digest seal_event_id].all? { |key| frame[key].is_a?(String) && frame[key].match?(/\A[0-9a-f]{64}\z/) } &&
252
- frame["journal_commit"].is_a?(String) && frame["journal_commit"].match?(/\A(?:[0-9a-f]{40}|[0-9a-f]{64})\z/)
253
- raise AttemptErrors::EvidenceUnavailable, "Private input inhibition frame is malformed"
254
- end
255
- true
256
- end
257
-
258
- def self.validate_inhibit_outcome!(result, frame)
259
- unless result.is_a?(Hash) && result.keys.sort == INHIBIT_OUTCOME_FIELDS && result["version"].is_a?(Integer) && result["version"] == 1 &&
260
- result["type"] == "launch_input_inhibit_outcome" && result["guarded_evidence"].is_a?(Hash) &&
261
- %w[original_binding_digest seal_event_id].all? { |key| result[key] == frame.fetch(key) }
262
- raise AttemptErrors::EvidenceUnavailable, "Private input inhibition outcome differs"
263
- end
264
- true
265
- end
266
-
267
- def self.validate_inhibit_recorded!(result, frame)
268
- unless result.is_a?(Hash) && result.keys.sort == INHIBIT_RECORDED_FIELDS && result["version"].is_a?(Integer) && result["version"] == 1 &&
269
- result["type"] == "launch_input_inhibit_recorded" && %w[original_binding_digest seal_event_id].all? { |key| result[key] == frame.fetch(key) } &&
270
- result["journal_commit"].is_a?(String) && result["journal_commit"].match?(/\A(?:[0-9a-f]{40}|[0-9a-f]{64})\z/)
271
- raise AttemptErrors::EvidenceUnavailable, "Canonical input inhibition acknowledgement differs"
272
- end
273
- true
274
- end
275
-
276
- private
277
-
278
- def validate_prompt!(frame) = self.class.validate_prompt!(frame)
279
-
280
- def validate_outcome!(result, frame)
281
- unless result.is_a?(Hash) && result.keys.all? { |key| key.is_a?(String) } && result.keys.sort == OUTCOME_FIELDS &&
282
- result["version"].is_a?(Integer) && result["version"] == 1 && result["type"] == "prompt_dispatch_outcome" &&
283
- %w[mutation_id intent_event_id original_binding_digest].all? { |key| result[key] == frame.fetch(key) } &&
284
- result["guarded_evidence"].is_a?(Hash)
285
- raise AttemptErrors::EvidenceUnavailable, "Private prompt outcome does not join original dispatch"
286
- end
287
- end
288
-
289
- def complete(pending, result: nil, error: nil)
290
- @mutex.synchronize do
291
- @closed = true if error
292
- pending.result, pending.error, pending.finished = result, error, true
293
- pending.bytes = nil
294
- @pending = nil if @pending.equal?(pending)
295
- @reservation = nil
296
- @changed.broadcast
297
- end
298
- end
299
-
300
- def ping
301
- nonce, deadline = SecureRandom.hex(16), wire.deadline(5)
302
- wire.write(@socket, {"version" => 1, "type" => "launch_control_idle", "nonce" => nonce}, deadline: deadline, limit: 16_384)
303
- result = wire.read(@socket, deadline: deadline, limit: 16_384)
304
- unless result.is_a?(Hash) && result["version"].is_a?(Integer) && result == {"version" => 1, "type" => "launch_control_idle_ack", "nonce" => nonce}
305
- raise AttemptErrors::EvidenceUnavailable, "Private launcher idle response differs"
306
- end
307
- end
308
-
309
- def wire = Ace::Runtime::Molecules::ProtectedSocket
310
- end
311
- end
312
- end
313
- end
@@ -1,127 +0,0 @@
1
- # frozen_string_literal: true
2
- require_relative "../molecules/protected_control_exclusion"
3
- require_relative "campaign_execution"
4
- require_relative "assignment_definition"
5
-
6
- module Ace
7
- module Assign
8
- module Authority
9
- class LaunchLifecycle
10
- private
11
-
12
- def control_registration_context!(params, map, journal, commit: journal.ref_value)
13
- assignment = params.fetch("assignment_id")
14
- mapping = params.fetch("mapping_id")
15
- inventory = if commit.nil?
16
- unless params.key?("definition_bytes") && journal.ref_value.nil?
17
- raise AttemptErrors::EvidenceUnavailable, "Original control canonical ref is unavailable"
18
- end
19
- # Existing first-registration bootstrap. The lower CAS initializes
20
- # and authenticates its seed prefix before accepting any event.
21
- {"commit" => nil, "events" => {}, "introductions" => {}}
22
- else
23
- journal.canonical_event_inventory!(commit: commit)
24
- end
25
- unless inventory.fetch("commit") == commit
26
- raise AttemptErrors::EvidenceUnavailable, "Control canonical snapshot differs"
27
- end
28
- events = inventory.fetch("events").fetch(assignment, [])
29
- introductions = inventory.fetch("introductions").fetch(assignment, {})
30
- registrations = events.select { |event| event["type"] == "authority_mutation" &&
31
- event.dig("payload", "operation") == "register_assignment" }
32
- first = registrations.first&.dig("payload", "data")
33
- registration = registrations.last&.dig("payload", "data")
34
- if params["attempt_id"]
35
- chain = events.select { |event| event["attempt_id"] == params.fetch("attempt_id") }
36
- reserves = chain.select { |event| event.dig("payload", "operation") == "reserve_attempt" }
37
- unless reserves.one? && reserves.first.dig("payload", "data", "mapping_id") == mapping
38
- raise AttemptErrors::EvidenceUnavailable, "Original control reservation is unavailable"
39
- end
40
- prefix = introductions.fetch(reserves.first.fetch("digest"))
41
- registration = definition(journal, assignment, commit: prefix)
42
- end
43
- unless registration || params.key?("definition_bytes")
44
- raise AttemptErrors::NotFound, "canonical assignment registration is missing"
45
- end
46
- selection = first&.fetch("lifecycle_control")
47
- selected = selection ? control_original_descriptor!(selection) : @deployment
48
- original_map = selected.mapping(mapping)
49
- project = selected.project(original_map.fetch("project_id"))
50
- unless original_map.fetch("project_id") == map.fetch("project_id") &&
51
- project.values_at("journal_repository", "evidence_git_ref", "evidence_checkout_root") ==
52
- [journal.repo_root, journal.ref, journal.checkout_root]
53
- raise AttemptErrors::EvidenceUnavailable, "Original control journal association differs"
54
- end
55
- control_owner_unchanged!(selected, original_map, map)
56
- if first
57
- authenticate_inventory_registration!(journal, assignment, events, first, mapping, original_map,
58
- commit, introductions)
59
- authenticate_inventory_registration!(journal, assignment, events, registration, mapping, original_map,
60
- commit, introductions) unless registration == first
61
- unless registrations.all? { |event| event.dig("payload", "data", "lifecycle_control") == selection }
62
- raise AttemptErrors::EvidenceUnavailable, "Assignment control owner was replaced"
63
- end
64
- end
65
- control = exclusion_for(original_map, journal, deployment: selected, selection: selection)
66
- actual_selection = control.selection!
67
- if actual_selection.fetch("descriptor_sha256") != selected.artifact_reference.fetch("sha256") ||
68
- selection && actual_selection != selection
69
- raise AttemptErrors::EvidenceUnavailable, "Original control root differs"
70
- end
71
- proposed = params.key?("definition_bytes") ? validated_registration_definition!(params, map) : nil
72
- tasks = [registration&.fetch("task_id"), proposed&.task_id].compact.uniq.sort
73
- tasks.each { |task| token!(task) }
74
- keys = tasks.map { |task| control.task_key(task) } + [control.assignment_key(assignment)]
75
- {commit: commit, registration: registration, descriptor: selected, map: original_map,
76
- selection: actual_selection, exclusion: control, keys: keys.freeze}.freeze
77
- rescue KeyError, TypeError, JSON::ParserError
78
- raise AttemptErrors::EvidenceUnavailable, "Original control registration is unavailable"
79
- end
80
-
81
- def control_original_descriptor!(selection)
82
- unless selection.is_a?(Hash) && selection.keys.sort == %w[descriptor_sha256 root_identity] &&
83
- selection["descriptor_sha256"].is_a?(String) && selection["descriptor_sha256"].match?(/\A[0-9a-f]{64}\z/)
84
- raise AttemptErrors::EvidenceUnavailable, "Original control selection is malformed"
85
- end
86
- sha = selection.fetch("descriptor_sha256")
87
- return @deployment if sha == protected_descriptor_sha256!
88
- unless @deployment_history
89
- raise AttemptErrors::EvidenceUnavailable, "Original control descriptor is unavailable"
90
- end
91
- @deployment_history.descriptor!(sha256: sha)
92
- end
93
-
94
- def control_owner_unchanged!(selected, original_map, current_map)
95
- old_authority = selected.authority(original_map.fetch("authority_id"))
96
- current_authority = @deployment.authority(current_map.fetch("authority_id"))
97
- slot_fields = %w[slot_id slice_unit service_unit root_directory runtime_directory network_namespace_path]
98
- unless original_map.fetch("authority_id") == current_map.fetch("authority_id") &&
99
- old_authority.values_at("state_root", "uid", "gid") == current_authority.values_at("state_root", "uid", "gid") &&
100
- original_map.fetch("execution_scope").slice(*slot_fields) == current_map.fetch("execution_scope").slice(*slot_fields)
101
- raise AttemptErrors::Conflict, "Control ownership changes require a fresh assignment"
102
- end
103
- end
104
-
105
- def recheck_control_registration!(context, params, map, journal, commit: journal.ref_value, held_exclusion: context.fetch(:exclusion))
106
- fresh = if context.fetch(:commit) == commit
107
- context
108
- else
109
- control_registration_context!(params, map, journal, commit: commit)
110
- end
111
- unless fresh.fetch(:registration) == context.fetch(:registration) &&
112
- fresh.fetch(:selection) == context.fetch(:selection) && fresh.fetch(:keys) == context.fetch(:keys) &&
113
- journal.ref_value == commit && context.fetch(:exclusion).selection! == context.fetch(:selection)
114
- raise AttemptErrors::Conflict, "Control registration changed while entering exclusion"
115
- end
116
- held_exclusion.verify_unchanged!
117
- true
118
- end
119
-
120
- def validated_registration_definition!(params, map)
121
- AssignmentDefinition.parse(bytes: params.fetch("definition_bytes"), digest: params["definition_digest"],
122
- assignment_id: params["assignment_id"], project_id: map["project_id"])
123
- end
124
- end
125
- end
126
- end
127
- end