ace-assign 0.71.0 → 0.73.1

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 (197) 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 +5 -2116
  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 -4
  16. data/handbook/workflow-instructions/assign/drive.wf.md +47 -195
  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 +6 -2
  35. data/lib/ace/assign/molecules/skill_assign_source_resolver.rb +9 -0
  36. data/lib/ace/assign/organisms/assignment_executor.rb +0 -6
  37. data/lib/ace/assign/organisms/task_assignment_creator.rb +3 -8
  38. data/lib/ace/assign/version.rb +1 -1
  39. data/lib/ace/assign.rb +0 -6
  40. metadata +22 -176
  41. data/.ace-defaults/assign/presets/close-delivery.yml +0 -57
  42. data/.ace-defaults/assign/presets/deliver-release.yml +0 -60
  43. data/.ace-defaults/assign/presets/integrate-batch.yml +0 -72
  44. data/.ace-defaults/assign/presets/supervise-delivery.yml +0 -69
  45. data/docs/protected-authority.md +0 -282
  46. data/exe/ace-scope-ready +0 -55
  47. data/lib/ace/assign/atoms/assignment_scope.rb +0 -58
  48. data/lib/ace/assign/atoms/attempt_state_machine.rb +0 -84
  49. data/lib/ace/assign/atoms/candidate_limits.rb +0 -20
  50. data/lib/ace/assign/atoms/delivery_parameters.rb +0 -19
  51. data/lib/ace/assign/atoms/evidence_digest.rb +0 -78
  52. data/lib/ace/assign/atoms/fixed_merge_schemas.rb +0 -398
  53. data/lib/ace/assign/atoms/git_blob_batch.rb +0 -59
  54. data/lib/ace/assign/authority/assignment_definition.rb +0 -35
  55. data/lib/ace/assign/authority/assignment_inventory.rb +0 -290
  56. data/lib/ace/assign/authority/campaign_consumer_policy.rb +0 -100
  57. data/lib/ace/assign/authority/campaign_execution.rb +0 -119
  58. data/lib/ace/assign/authority/campaign_round_transfer.rb +0 -82
  59. data/lib/ace/assign/authority/candidate_blob_batch.rb +0 -55
  60. data/lib/ace/assign/authority/candidate_head_comparison.rb +0 -139
  61. data/lib/ace/assign/authority/candidate_transfer.rb +0 -357
  62. data/lib/ace/assign/authority/cleanup_result_protection.rb +0 -53
  63. data/lib/ace/assign/authority/client.rb +0 -257
  64. data/lib/ace/assign/authority/command_execution.rb +0 -180
  65. data/lib/ace/assign/authority/deployment.rb +0 -627
  66. data/lib/ace/assign/authority/deployment_history.rb +0 -146
  67. data/lib/ace/assign/authority/endcap.rb +0 -457
  68. data/lib/ace/assign/authority/endcap_attempts.rb +0 -274
  69. data/lib/ace/assign/authority/endcap_campaign_results.rb +0 -232
  70. data/lib/ace/assign/authority/endcap_campaign_rounds.rb +0 -266
  71. data/lib/ace/assign/authority/endcap_contexts.rb +0 -23
  72. data/lib/ace/assign/authority/endcap_inboxes.rb +0 -37
  73. data/lib/ace/assign/authority/endcap_prepared_work.rb +0 -80
  74. data/lib/ace/assign/authority/endcap_publication.rb +0 -41
  75. data/lib/ace/assign/authority/endcap_results.rb +0 -398
  76. data/lib/ace/assign/authority/endcap_review_requests.rb +0 -183
  77. data/lib/ace/assign/authority/endcap_reviews.rb +0 -130
  78. data/lib/ace/assign/authority/endcap_service_context.rb +0 -38
  79. data/lib/ace/assign/authority/endcap_service_settlement.rb +0 -152
  80. data/lib/ace/assign/authority/evidence_transfer.rb +0 -67
  81. data/lib/ace/assign/authority/execution_scope_observation.rb +0 -903
  82. data/lib/ace/assign/authority/fixed_publication_lifecycle.rb +0 -176
  83. data/lib/ace/assign/authority/fixed_service_lifecycle.rb +0 -594
  84. data/lib/ace/assign/authority/inbox_context_original.rb +0 -113
  85. data/lib/ace/assign/authority/launch_attempt_consumers.rb +0 -51
  86. data/lib/ace/assign/authority/launch_campaign_registration.rb +0 -214
  87. data/lib/ace/assign/authority/launch_control_channel.rb +0 -313
  88. data/lib/ace/assign/authority/launch_control_exclusions.rb +0 -127
  89. data/lib/ace/assign/authority/launch_driver.rb +0 -393
  90. data/lib/ace/assign/authority/launch_input_inhibition.rb +0 -132
  91. data/lib/ace/assign/authority/launch_lifecycle.rb +0 -997
  92. data/lib/ace/assign/authority/launch_prepared_work.rb +0 -122
  93. data/lib/ace/assign/authority/launch_review.rb +0 -91
  94. data/lib/ace/assign/authority/launch_scope_abort.rb +0 -81
  95. data/lib/ace/assign/authority/launch_scope_admission.rb +0 -215
  96. data/lib/ace/assign/authority/launch_scope_close.rb +0 -146
  97. data/lib/ace/assign/authority/launch_scope_parent.rb +0 -44
  98. data/lib/ace/assign/authority/launch_scope_release.rb +0 -859
  99. data/lib/ace/assign/authority/launch_steering.rb +0 -353
  100. data/lib/ace/assign/authority/launch_stop.rb +0 -254
  101. data/lib/ace/assign/authority/maintenance_workspace_retirement.rb +0 -229
  102. data/lib/ace/assign/authority/ordinary_attempt_start.rb +0 -137
  103. data/lib/ace/assign/authority/ordinary_client.rb +0 -70
  104. data/lib/ace/assign/authority/ordinary_completion.rb +0 -104
  105. data/lib/ace/assign/authority/ordinary_effect_settlement.rb +0 -118
  106. data/lib/ace/assign/authority/ordinary_lifecycle.rb +0 -227
  107. data/lib/ace/assign/authority/ordinary_merge_evidence.rb +0 -81
  108. data/lib/ace/assign/authority/ordinary_peer.rb +0 -54
  109. data/lib/ace/assign/authority/ordinary_progress.rb +0 -162
  110. data/lib/ace/assign/authority/ordinary_queue.rb +0 -115
  111. data/lib/ace/assign/authority/ordinary_release_artifacts.rb +0 -143
  112. data/lib/ace/assign/authority/ordinary_results.rb +0 -206
  113. data/lib/ace/assign/authority/ordinary_review_evidence.rb +0 -180
  114. data/lib/ace/assign/authority/ordinary_reviews.rb +0 -214
  115. data/lib/ace/assign/authority/ordinary_server.rb +0 -75
  116. data/lib/ace/assign/authority/ordinary_supervisor.rb +0 -99
  117. data/lib/ace/assign/authority/original_inbox_selection.rb +0 -41
  118. data/lib/ace/assign/authority/posix_acl.rb +0 -59
  119. data/lib/ace/assign/authority/prepared_input.rb +0 -130
  120. data/lib/ace/assign/authority/prepared_queue.rb +0 -185
  121. data/lib/ace/assign/authority/prepared_task_context.rb +0 -116
  122. data/lib/ace/assign/authority/prepared_work.rb +0 -309
  123. data/lib/ace/assign/authority/prepared_worker.rb +0 -69
  124. data/lib/ace/assign/authority/private_directory.rb +0 -33
  125. data/lib/ace/assign/authority/protected_assignment_context.rb +0 -226
  126. data/lib/ace/assign/authority/readiness_hook.rb +0 -120
  127. data/lib/ace/assign/authority/receipt_transfer.rb +0 -48
  128. data/lib/ace/assign/authority/release_artifact_manifest.rb +0 -91
  129. data/lib/ace/assign/authority/retained_input.rb +0 -53
  130. data/lib/ace/assign/authority/router.rb +0 -81
  131. data/lib/ace/assign/authority/server.rb +0 -131
  132. data/lib/ace/assign/authority/service_cleanup_dispatch.rb +0 -88
  133. data/lib/ace/assign/authority/service_cleanup_evidence.rb +0 -180
  134. data/lib/ace/assign/authority/service_delivery_evidence.rb +0 -101
  135. data/lib/ace/assign/authority/service_evidence.rb +0 -286
  136. data/lib/ace/assign/authority/service_publication_evidence.rb +0 -110
  137. data/lib/ace/assign/authority/socket_server.rb +0 -255
  138. data/lib/ace/assign/authority/task_context_entry.rb +0 -32
  139. data/lib/ace/assign/authority/transfer_codec.rb +0 -189
  140. data/lib/ace/assign/cli/commands/attempt/base.rb +0 -84
  141. data/lib/ace/assign/cli/commands/attempt/evidence.rb +0 -28
  142. data/lib/ace/assign/cli/commands/attempt/finish.rb +0 -51
  143. data/lib/ace/assign/cli/commands/attempt/reconcile.rb +0 -49
  144. data/lib/ace/assign/cli/commands/attempt/start.rb +0 -45
  145. data/lib/ace/assign/cli/commands/attempt/status.rb +0 -54
  146. data/lib/ace/assign/cli/commands/authority/inbox_context_principal.rb +0 -25
  147. data/lib/ace/assign/cli/commands/authority/inbox_context_selection.rb +0 -28
  148. data/lib/ace/assign/cli/commands/authority/launch.rb +0 -97
  149. data/lib/ace/assign/cli/commands/authority/serve.rb +0 -31
  150. data/lib/ace/assign/cli/commands/authority/status.rb +0 -26
  151. data/lib/ace/assign/cli/commands/authority/task_context.rb +0 -31
  152. data/lib/ace/assign/cli/commands/authority/task_context_principal.rb +0 -25
  153. data/lib/ace/assign/cli/commands/authority/task_context_selection.rb +0 -30
  154. data/lib/ace/assign/cli/commands/authority/terminate.rb +0 -29
  155. data/lib/ace/assign/cli/commands/authority/worker.rb +0 -23
  156. data/lib/ace/assign/cli/commands/campaign_export_result.rb +0 -55
  157. data/lib/ace/assign/cli/commands/campaign_record_round.rb +0 -57
  158. data/lib/ace/assign/cli/commands/delivery.rb +0 -107
  159. data/lib/ace/assign/cli/commands/inbox_bind.rb +0 -34
  160. data/lib/ace/assign/cli/commands/protected_submission.rb +0 -61
  161. data/lib/ace/assign/cli/commands/resume.rb +0 -38
  162. data/lib/ace/assign/cli/commands/submit_candidate.rb +0 -34
  163. data/lib/ace/assign/cli/commands/submit_result.rb +0 -61
  164. data/lib/ace/assign/models/attempt.rb +0 -178
  165. data/lib/ace/assign/models/attempt_binding.rb +0 -116
  166. data/lib/ace/assign/models/evidence_event.rb +0 -86
  167. data/lib/ace/assign/models/execution_receipt.rb +0 -139
  168. data/lib/ace/assign/molecules/attempt_reconciler.rb +0 -127
  169. data/lib/ace/assign/molecules/attempt_store.rb +0 -201
  170. data/lib/ace/assign/molecules/canonical_attempt_state.rb +0 -151
  171. data/lib/ace/assign/molecules/canonical_evidence.rb +0 -170
  172. data/lib/ace/assign/molecules/canonical_read_commands.rb +0 -84
  173. data/lib/ace/assign/molecules/canonical_read_snapshot.rb +0 -313
  174. data/lib/ace/assign/molecules/evidence_calculator.rb +0 -237
  175. data/lib/ace/assign/molecules/evidence_journal.rb +0 -1899
  176. data/lib/ace/assign/molecules/execution_identity_resolver.rb +0 -137
  177. data/lib/ace/assign/molecules/execution_scope_lineage.rb +0 -345
  178. data/lib/ace/assign/molecules/fixed_effect_reference.rb +0 -31
  179. data/lib/ace/assign/molecules/installed_journal_read.rb +0 -143
  180. data/lib/ace/assign/molecules/journal_input_inhibition.rb +0 -78
  181. data/lib/ace/assign/molecules/journal_mutation.rb +0 -274
  182. data/lib/ace/assign/molecules/journal_prompt_mutation.rb +0 -241
  183. data/lib/ace/assign/molecules/ordinary_merge_cli.rb +0 -68
  184. data/lib/ace/assign/molecules/ordinary_merge_contract.rb +0 -410
  185. data/lib/ace/assign/molecules/original_launch_binding.rb +0 -37
  186. data/lib/ace/assign/molecules/proposal_journal.rb +0 -179
  187. data/lib/ace/assign/molecules/protected_control_exclusion.rb +0 -303
  188. data/lib/ace/assign/molecules/protected_workspace_exclusion.rb +0 -330
  189. data/lib/ace/assign/molecules/protected_workspace_provisioner.rb +0 -164
  190. data/lib/ace/assign/molecules/receipt_verifier.rb +0 -320
  191. data/lib/ace/assign/molecules/terminal_scope_receipt.rb +0 -88
  192. data/lib/ace/assign/organisms/attempt_coordinator.rb +0 -1374
  193. data/lib/ace/assign/organisms/delivery_coordinator.rb +0 -342
  194. data/lib/ace/assign/organisms/ordinary_driver.rb +0 -310
  195. data/lib/ace/assign/organisms/ordinary_merge_consumer.rb +0 -365
  196. data/lib/ace/assign/organisms/prepared_work_builder.rb +0 -194
  197. 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