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,32 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Ace
4
- module Assign
5
- module Authority
6
- # Structural admission of source-owned pins. Artifact authentication belongs
7
- # to the selected entry owner; these references are never an execution grant.
8
- module TaskContextEntry
9
- LIMITS = {"manifest" => 65_536, "wrapper" => 33_554_432}.freeze
10
-
11
- def self.validate!(value)
12
- unless value.is_a?(Hash) && value.keys.sort == LIMITS.keys.sort
13
- raise ArgumentError, "task context entry fields differ"
14
- end
15
- LIMITS.each do |key, limit|
16
- reference = value[key]
17
- unless reference.is_a?(Hash) && reference.keys.sort == %w[bytes path sha256] &&
18
- reference["bytes"].is_a?(Integer) && reference["bytes"].between?(1, limit) &&
19
- reference["sha256"].is_a?(String) && reference["sha256"].match?(/\A[0-9a-f]{64}\z/) &&
20
- reference["path"].is_a?(String) && reference["path"].encoding == Encoding::UTF_8 &&
21
- reference["path"].valid_encoding? && reference["path"].bytesize.between?(2, 4096) &&
22
- !reference["path"].include?("\0") && reference["path"].start_with?("/") &&
23
- File.expand_path(reference["path"]) == reference["path"]
24
- raise ArgumentError, "task context entry reference differs"
25
- end
26
- end
27
- value
28
- end
29
- end
30
- end
31
- end
32
- end
@@ -1,187 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "digest"
4
- require "tempfile"
5
- require "timeout"
6
- require "ace/runtime/molecules/protected_socket"
7
- require_relative "private_directory"
8
-
9
- module Ace
10
- module Assign
11
- module Authority
12
- # Binary framing after the existing authenticated control header. The
13
- # Server selects purpose from a source handler's fixed operation table,
14
- # admits a transfer slot before calling, and holds no journal lock here.
15
- class TransferCodec
16
- LIMITS = {prompt_text: [16_384, 1, 16_384], scope_boundary_observation: [65_536, 1, 65_536], candidate: [64 * 1024 * 1024, 1, 64 * 1024 * 1024],
17
- artifacts: [256 * 1024, 16, 64 * 1024],
18
- receipt_artifacts: [272 * 1024, 17, 64 * 1024],
19
- service_input: [64 * 1024, 1, 64 * 1024], assignment_snapshot: [64 * 1024 * 1024, 1, 64 * 1024 * 1024]}.freeze
20
- SHA256 = /\A[0-9a-f]{64}\z/
21
-
22
- class Input
23
- def initialize(file, descriptor)
24
- @file, @descriptor = file, descriptor
25
- end
26
-
27
- def bytes(index: 0)
28
- unless index.is_a?(Integer) && index.between?(0, count - 1)
29
- raise ArgumentError, "Invalid transferred part index"
30
- end
31
- part = @descriptor.fetch("parts").fetch(index)
32
- offset = @descriptor.fetch("parts").take(index).sum { |entry| entry.fetch("bytes") }
33
- @file.seek(offset)
34
- bytes = (@file.read(part.fetch("bytes")) || "").b
35
- unless bytes.bytesize == part["bytes"] && Digest::SHA256.hexdigest(bytes) == part["sha256"]
36
- raise AttemptErrors::ReceiptRejected, "Transferred bytes no longer match accepted binding"
37
- end
38
- bytes
39
- end
40
-
41
- def count
42
- @descriptor.fetch("parts").length
43
- end
44
- end
45
-
46
- def initialize(root: nil)
47
- @root = root && PrivateDirectory.verify!(root)
48
- end
49
-
50
- # Upload write-EOF is required before invoking the mutation consumer.
51
- # Persistent launch prompt streams use their fixed framed entry point below.
52
- def receive(socket, descriptor:, purpose:, deadline:)
53
- receive_parts(socket, descriptor: descriptor, purpose: purpose, deadline: deadline, eof: true) { |input| yield input }
54
- end
55
-
56
- # Fixed authenticated launch_control stream only. A declared exact
57
- # prompt body is followed by the next bounded control frame; unlike
58
- # public uploads, this private duplex connection remains writable.
59
- def receive_launch_prompt(socket, descriptor:, transfer_id:, deadline:)
60
- deadline = [deadline, Process.clock_gettime(Process::CLOCK_MONOTONIC) + 30].min
61
- marker = launch_prompt_marker(transfer_id)
62
- receive_parts(socket, descriptor: descriptor, purpose: :prompt_text, deadline: deadline, eof: false) do |input|
63
- boundary = Ace::Runtime::Molecules::ProtectedSocket.read(socket, deadline: deadline, limit: 16_384, with_size: true)
64
- unless boundary.fetch(:data) == marker && boundary.fetch(:bytesize) == (JSON.generate(marker) + "\n").bytesize
65
- reject!("Private prompt boundary differs")
66
- end
67
- yield input
68
- end
69
- end
70
-
71
- def send_launch_prompt(socket, bytes:, descriptor:, transfer_id:, deadline:)
72
- deadline = [deadline, Process.clock_gettime(Process::CLOCK_MONOTONIC) + 30].min
73
- marker = launch_prompt_marker(transfer_id)
74
- send(socket, parts: [bytes], descriptor: descriptor, purpose: :prompt_text, deadline: deadline)
75
- Ace::Runtime::Molecules::ProtectedSocket.write(socket, marker, deadline: deadline, limit: 16_384)
76
- end
77
-
78
- def launch_prompt_marker(transfer_id)
79
- unless transfer_id.is_a?(String) && transfer_id.match?(/\A[0-9a-f]{32}\z/)
80
- raise ArgumentError, "invalid private prompt transfer identity"
81
- end
82
- {"type" => "launch_prompt_end", "transfer_id" => transfer_id}
83
- end
84
- private :launch_prompt_marker
85
-
86
- def receive_parts(socket, descriptor:, purpose:, deadline:, eof:)
87
- validate!(descriptor, purpose)
88
- descriptor = descriptor.merge("parts" => descriptor.fetch("parts").map { |part| part.dup.freeze }.freeze).freeze
89
- deadline = [deadline, Process.clock_gettime(Process::CLOCK_MONOTONIC) + 30].min
90
- raise ArgumentError, "receive requires protected source scratch" unless @root
91
- PrivateDirectory.verify!(@root)
92
- Tempfile.create(["transfer-", ".bytes"], @root, binmode: true) do |file|
93
- digest = Digest::SHA256.new
94
- descriptor.fetch("parts").each do |part|
95
- part_digest = Digest::SHA256.new
96
- remaining = part.fetch("bytes")
97
- while remaining.positive?
98
- chunk = read(socket, [remaining, 65_536].min, deadline)
99
- reject!("Transferred bytes ended early") if chunk.nil?
100
- file.write(chunk)
101
- digest.update(chunk)
102
- part_digest.update(chunk)
103
- remaining -= chunk.bytesize
104
- end
105
- reject!("Transferred part digest differs") unless part_digest.hexdigest == part["sha256"]
106
- end
107
- reject!("Transferred aggregate digest differs") unless digest.hexdigest == descriptor["sha256"]
108
- reject!("Transferred bytes exceed declared size") if eof && !read(socket, 1, deadline).nil?
109
- file.flush
110
- file.rewind
111
- yield Input.new(file, descriptor)
112
- end
113
- end
114
- private :receive_parts
115
-
116
- def descriptor(parts, purpose:)
117
- reject!("Invalid transfer parts") unless parts.is_a?(Array) && parts.all? { |part| part.is_a?(String) }
118
- digest = Digest::SHA256.new
119
- parts.each { |part| digest.update(part.b) }
120
- result = {"version" => 1, "bytes" => parts.sum(&:bytesize), "sha256" => digest.hexdigest,
121
- "parts" => parts.map { |part| {"bytes" => part.bytesize, "sha256" => Digest::SHA256.hexdigest(part.b)} }}
122
- validate!(result, purpose)
123
- result
124
- end
125
-
126
- # The caller writes the bounded JSON descriptor first, then these raw
127
- # bytes. Both directions share the Server's original monotonic deadline.
128
- def send(socket, parts:, descriptor:, purpose:, deadline:)
129
- expected = self.descriptor(parts, purpose: purpose)
130
- reject!("Export descriptor differs from bytes") unless expected == descriptor
131
- deadline = [deadline, Process.clock_gettime(Process::CLOCK_MONOTONIC) + 30].min
132
- parts.each do |part|
133
- offset = 0
134
- while offset < part.bytesize
135
- wait(socket, deadline, write: true)
136
- written = socket.write_nonblock(part.byteslice(offset, [65_536, part.bytesize - offset].min), exception: false)
137
- offset += written unless written == :wait_writable
138
- end
139
- end
140
- end
141
-
142
- def validate!(descriptor, purpose)
143
- total, count, each = LIMITS.fetch(purpose) { reject!("Unknown source transfer purpose") }
144
- unless descriptor.is_a?(Hash) && descriptor.keys.sort == %w[bytes parts sha256 version] &&
145
- descriptor["version"] == 1 && descriptor["bytes"].is_a?(Integer) &&
146
- descriptor["bytes"].between?(0, total) && descriptor["sha256"].is_a?(String) && descriptor["sha256"].match?(SHA256) &&
147
- descriptor["parts"].is_a?(Array) && descriptor["parts"].length.between?(1, count) &&
148
- descriptor["parts"].all? { |part| part.is_a?(Hash) && part.keys.sort == %w[bytes sha256] &&
149
- part["bytes"].is_a?(Integer) && part["bytes"].between?(0, each) &&
150
- part["sha256"].is_a?(String) && part["sha256"].match?(SHA256) } &&
151
- descriptor["bytes"] == descriptor["parts"].sum { |part| part["bytes"] } &&
152
- (!%i[candidate service_input prompt_text assignment_snapshot].include?(purpose) || descriptor["bytes"].positive?)
153
- reject!("Invalid or oversized transfer descriptor")
154
- end
155
- if purpose == :receipt_artifacts
156
- receipt, *evidence = descriptor.fetch("parts")
157
- unless receipt.fetch("bytes").between?(1, 16 * 1024) && evidence.sum { |part| part.fetch("bytes") } <= 256 * 1024
158
- reject!("Receipt or evidence exceeds its fixed transfer limit")
159
- end
160
- end
161
- true
162
- end
163
-
164
- private
165
-
166
- def read(socket, count, deadline)
167
- loop do
168
- wait(socket, deadline)
169
- bytes = socket.read_nonblock(count, exception: false)
170
- return bytes unless bytes == :wait_readable
171
- end
172
- end
173
-
174
- def wait(socket, deadline, write: false)
175
- remaining = deadline - Process.clock_gettime(Process::CLOCK_MONOTONIC)
176
- raise Timeout::Error, "Transfer deadline exceeded" unless remaining.positive?
177
- ready = IO.select(write ? nil : [socket], write ? [socket] : nil, nil, remaining)
178
- raise Timeout::Error, "Transfer deadline exceeded" unless ready
179
- end
180
-
181
- def reject!(message)
182
- raise AttemptErrors::MalformedTransfer, message
183
- end
184
- end
185
- end
186
- end
187
- end
@@ -1,84 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "json"
4
- require_relative "../../../authority/protected_assignment_context"
5
-
6
- module Ace
7
- module Assign
8
- module CLI
9
- module Commands
10
- # Public `ace-assign attempt` command namespace (interface contract:
11
- # start, status, finish, reconcile).
12
- module Attempt
13
- # Shared coordinator construction, option validation, and JSON output.
14
- module Base
15
- private
16
-
17
- def protected_context(options)
18
- keys = %i[mapping assignment result head candidate_generation mutation expected_generation inbox_context registration signature]
19
- keys.each do |key|
20
- if options.key?(key) && options[key].to_s.strip.empty?
21
- raise Ace::Support::Cli::Error, "Empty protected --#{key.to_s.tr('_', '-')} selector"
22
- end
23
- end
24
- context = @protected_assignment_context ||= Ace::Assign::Authority::ProtectedAssignmentContext.load
25
- selected = keys.any? do |key|
26
- options.key?(key)
27
- end
28
- context if context.protected_participant? || selected || context.mapping_hint?
29
- rescue SecurityError, Ace::Runtime::RuntimeUnavailableError
30
- protected_transport_unavailable!
31
- end
32
-
33
- def protected_attempt_request(context, options, usage)
34
- params = {"assignment_id" => require_option(options, :assignment, usage),
35
- "attempt_id" => require_option(options, :attempt, usage),
36
- "expected_generation" => integer_option(options, :expected_generation, usage)}
37
- context.verify_attempt_hints!(assignment_id: params.fetch("assignment_id"), attempt_id: params.fetch("attempt_id"))
38
- [context.client(options: options), params, require_option(options, :mutation, usage)]
39
- rescue SecurityError, Ace::Runtime::RuntimeUnavailableError
40
- protected_transport_unavailable!
41
- end
42
-
43
- def protected_call(client, operation, params, mutation)
44
- client.call(operation, params, mutation_id: mutation, timeout: 30).data
45
- rescue SecurityError, Ace::Runtime::RuntimeUnavailableError
46
- protected_transport_unavailable!
47
- end
48
-
49
- def protected_transport_unavailable!
50
- raise AttemptErrors::EvidenceUnavailable, "Protected authority outcome unavailable; retain original selectors and mutation for reconciliation"
51
- end
52
-
53
- def integer_option(options, key, usage, positive: false)
54
- value = options[key]
55
- unless value.is_a?(Integer) || (value.is_a?(String) && value.match?(/\A(?:0|[1-9][0-9]*)\z/))
56
- raise Ace::Support::Cli::Error, "--#{key.to_s.tr('_', '-')} requires an exact integer: #{usage}"
57
- end
58
- number = value.is_a?(Integer) ? value : Integer(value, 10)
59
- unless positive ? number.positive? : number >= 0
60
- raise Ace::Support::Cli::Error, "--#{key.to_s.tr('_', '-')} is out of range"
61
- end
62
- number
63
- end
64
-
65
- def build_coordinator
66
- Organisms::AttemptCoordinator.new
67
- end
68
-
69
- def require_option(options, key, usage)
70
- value = options[key].to_s.strip
71
- raise Ace::Support::Cli::Error, "Missing --#{key}: usage: ace-assign attempt #{usage}" if value.empty?
72
-
73
- value
74
- end
75
-
76
- def emit_json(payload)
77
- puts JSON.generate(payload)
78
- end
79
- end
80
- end
81
- end
82
- end
83
- end
84
- end
@@ -1,28 +0,0 @@
1
- # frozen_string_literal: true
2
- require_relative "base"
3
- module Ace
4
- module Assign
5
- module CLI
6
- module Commands
7
- module Attempt
8
- class Evidence < Ace::Support::Cli::Command
9
- include Attempt::Base
10
- desc "Read coordinator-accepted live execution or historical execution authority"
11
- option :attempt, required: true, desc: "Accepted execution attempt ID"
12
- option :receipt_digest, required: true, desc: "Accepted receipt digest"
13
- option :kind, default: "check", desc: "Evidence purpose: check, review-collection or review-approval"
14
- option :check_name, default: "tests", desc: "Required check name (tests binds operation test)"
15
- option :historical_head, desc: "Exact recorded head for historical authority only (never current-head evidence)"
16
- option :format, default: "json", desc: "JSON evidence projection"
17
-
18
- def call(**options)
19
- raise Ace::Support::Cli::Error, "evidence supports --format json" unless options[:format] == "json"
20
- emit_json(build_coordinator.evidence(attempt_id: options[:attempt], receipt_digest: options[:receipt_digest],
21
- kind: options[:kind], check_name: options[:check_name], historical_head: options[:historical_head]))
22
- end
23
- end
24
- end
25
- end
26
- end
27
- end
28
- end
@@ -1,51 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "json"
4
- require_relative "base"
5
-
6
- module Ace
7
- module Assign
8
- module CLI
9
- module Commands
10
- module Attempt
11
- # Protected completion selects an already submitted canonical result.
12
- # Ordinary local completion accepts its structured receipt file.
13
- class Finish < Ace::Support::Cli::Command
14
- include Ace::Support::Cli::Base
15
- include Attempt::Base
16
-
17
- desc "Finish a protected canonical result or an ordinary local receipt"
18
-
19
- option :attempt, desc: "Attempt ID"
20
- option :mapping, desc: "Exact installed protected mapping ID"
21
- option :assignment, desc: "Exact protected assignment ID"
22
- option :mutation, desc: "Stable original authority mutation ID"
23
- option :expected_generation, desc: "Original authority mutation generation"
24
- option :receipt, desc: "Path to the ordinary local receipt JSON file"
25
- option :result, desc: "Already submitted canonical protected result ID"
26
- option :head, desc: "Exact canonical candidate head"
27
- option :candidate_generation, desc: "Original canonical candidate generation"
28
-
29
- def call(**options)
30
- if (context = protected_context(options))
31
- raise Ace::Support::Cli::Error, "Protected finish forbids --receipt" unless options[:receipt].to_s.empty?
32
- usage = "finish --mapping MAP --assignment ID --attempt ID --result ID --head SHA --candidate-generation N --mutation ID --expected-generation N"
33
- client, params, mutation = protected_attempt_request(context, options, usage)
34
- params.merge!("result_id" => require_option(options, :result, usage), "head" => require_option(options, :head, usage),
35
- "candidate_generation" => integer_option(options, :candidate_generation, usage, positive: true))
36
- emit_json(protected_call(client, "finish", params, mutation))
37
- return
38
- end
39
- attempt_id = require_option(options, :attempt, "finish --attempt ID --receipt FILE")
40
- receipt = require_option(options, :receipt, "finish --attempt ID --receipt FILE")
41
-
42
- result = build_coordinator.finish(attempt_id: attempt_id, receipt_path: receipt)
43
-
44
- emit_json(result.projection)
45
- end
46
- end
47
- end
48
- end
49
- end
50
- end
51
- end
@@ -1,49 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "json"
4
- require_relative "base"
5
-
6
- module Ace
7
- module Assign
8
- module CLI
9
- module Commands
10
- module Attempt
11
- # Classify interruptions and resolve uncertainty against verified,
12
- # boundary-attributed receipts. Never replays external effects.
13
- class Reconcile < Ace::Support::Cli::Command
14
- include Ace::Support::Cli::Base
15
- include Attempt::Base
16
-
17
- desc "Reconcile an interrupted or uncertain attempt"
18
-
19
- option :attempt, desc: "Attempt ID"
20
- option :mapping, desc: "Exact installed protected mapping ID"
21
- option :assignment, desc: "Exact protected assignment ID"
22
- option :mutation, desc: "Stable original authority mutation ID"
23
- option :expected_generation, desc: "Original authority mutation generation"
24
- option :receipt, desc: "Path to the receipt JSON file (required to resolve uncertainty)"
25
-
26
- def call(**options)
27
- if (context = protected_context(options))
28
- raise Ace::Support::Cli::Error, "Protected recovery forbids --receipt" unless options[:receipt].to_s.empty?
29
- usage = "reconcile --mapping MAP --assignment ID --attempt ID --mutation ID --expected-generation N"
30
- client, params, mutation = protected_attempt_request(context, options, usage)
31
- emit_json(protected_call(client, "recover", params, mutation))
32
- return
33
- end
34
- attempt_id = require_option(options, :attempt, "reconcile --attempt ID [--receipt FILE]")
35
- receipt = options[:receipt].to_s.strip
36
-
37
- result = build_coordinator.reconcile(
38
- attempt_id: attempt_id,
39
- receipt_path: receipt.empty? ? nil : receipt
40
- )
41
-
42
- emit_json(result.projection)
43
- end
44
- end
45
- end
46
- end
47
- end
48
- end
49
- end
@@ -1,45 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "json"
4
- require_relative "base"
5
-
6
- module Ace
7
- module Assign
8
- module CLI
9
- module Commands
10
- module Attempt
11
- # Bind an immutable attempt to an assignment step/subtree.
12
- #
13
- # Identity is derived from the execution boundary; a repeated
14
- # identical start returns the active attempt, and a conflicting
15
- # binding fails without launching a second writer.
16
- class Start < Ace::Support::Cli::Command
17
- include Ace::Support::Cli::Base
18
- include Attempt::Base
19
-
20
- desc "Start a scoped attempt for an assignment step"
21
-
22
- option :assignment, desc: "Assignment ID"
23
- option :step, desc: "Step or subtree scope (e.g. 010 or 010.01)"
24
- option :project, desc: "Project ID the attempt executes in"
25
-
26
- def call(**options)
27
- usage = "start --assignment ID --step STEP --project ID"
28
- assignment = require_option(options, :assignment, usage)
29
- step = require_option(options, :step, usage)
30
- project = require_option(options, :project, usage)
31
-
32
- attempt = build_coordinator.start(
33
- assignment_id: assignment,
34
- step: step,
35
- project_id: project
36
- )
37
-
38
- emit_json(attempt.projection)
39
- end
40
- end
41
- end
42
- end
43
- end
44
- end
45
- end
@@ -1,54 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "json"
4
- require_relative "base"
5
-
6
- module Ace
7
- module Assign
8
- module CLI
9
- module Commands
10
- module Attempt
11
- # Show attempt state, binding facts, and evidence references.
12
- # JSON output carries only state, binding, references, and digests
13
- # — never credentials or receipt bytes.
14
- class Status < Ace::Support::Cli::Command
15
- include Ace::Support::Cli::Base
16
- include Attempt::Base
17
-
18
- desc "Show attempt status for an assignment"
19
-
20
- option :assignment, desc: "Assignment ID"
21
- option :format, desc: "Output format (json, text)", default: "json"
22
-
23
- def call(**options)
24
- assignment = require_option(options, :assignment, "status --assignment ID --format json")
25
- projection = build_coordinator.status(assignment)
26
-
27
- if options[:format] == "text"
28
- print_text(assignment, projection)
29
- else
30
- emit_json(projection || {"attempt" => nil})
31
- end
32
- end
33
-
34
- private
35
-
36
- def print_text(assignment, projection)
37
- return puts "No attempts recorded for #{assignment}" if projection.nil?
38
-
39
- puts "Attempt: #{projection['attempt_id']} #{projection['state']} (#{projection['scope']})"
40
- puts "Recovery: #{projection['recovery_mode']}"
41
- puts "Base head: #{projection['base_head']}"
42
- puts "Candidate head: #{projection['candidate_head'] || 'unpinned'}"
43
- puts "Evidence ref: #{projection['evidence_git_ref'] || 'local-only'}"
44
- puts "Journal commit: #{projection['journal_commit'] || 'none'}"
45
- return unless projection["unresolved_effects"].any?
46
-
47
- puts "Unresolved effects: #{projection['unresolved_effects'].join(', ')}"
48
- end
49
- end
50
- end
51
- end
52
- end
53
- end
54
- end
@@ -1,25 +0,0 @@
1
- # frozen_string_literal: true
2
- require_relative "../../../authority/prepared_task_context"
3
-
4
- module Ace
5
- module Assign
6
- module CLI
7
- module Commands
8
- module Authority
9
- class InboxContextPrincipal < Ace::Support::Cli::Command
10
- desc "Classify the actual caller through every retained protected owner"
11
-
12
- def call(**options)
13
- raise ArgumentError, "fixed inbox context options differ" unless options.empty?
14
- consumer = Ace::Assign::Authority::PreparedTaskContext.new(context: @protected_assignment_context)
15
- $stdout.write(consumer.inbox_principal())
16
- 0
17
- rescue ArgumentError, Ace::Runtime::Error, Ace::Assign::Error => error
18
- raise Ace::Support::Cli::Error, error.message
19
- end
20
- end
21
- end
22
- end
23
- end
24
- end
25
- end
@@ -1,28 +0,0 @@
1
- # frozen_string_literal: true
2
- require_relative "../../../authority/prepared_task_context"
3
-
4
- module Ace
5
- module Assign
6
- module CLI
7
- module Commands
8
- module Authority
9
- class InboxContextSelection < Ace::Support::Cli::Command
10
- desc "Read the fixed installed inbox context selection"
11
- option :project, required: true, desc: "Installed project ID"
12
- option :mapping, required: true, desc: "Installed mapping ID"
13
- option :inbox_context, required: true, desc: "Installed inbox context ID"
14
-
15
- def call(**options)
16
- raise ArgumentError, "fixed inbox context options differ" unless options.keys.sort == %i[inbox_context mapping project]
17
- consumer = Ace::Assign::Authority::PreparedTaskContext.new(context: @protected_assignment_context)
18
- $stdout.write(consumer.inbox_selection(**options))
19
- 0
20
- rescue ArgumentError, Ace::Runtime::Error, Ace::Assign::Error => error
21
- raise Ace::Support::Cli::Error, error.message
22
- end
23
- end
24
- end
25
- end
26
- end
27
- end
28
- end