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,57 +0,0 @@
1
- # frozen_string_literal: true
2
- require_relative "protected_submission"
3
- require_relative "../../authority/campaign_round_transfer"
4
-
5
- module Ace
6
- module Assign
7
- module CLI
8
- module Commands
9
- class CampaignRecordRound < Ace::Support::Cli::Command
10
- include Ace::Support::Cli::Base
11
- include ProtectedSubmission
12
- desc "Consume settled protected campaign children into the original R1 store"
13
- option :mapping, desc: "Original installed parent mapping"
14
- option :assignment, desc: "Original parent assignment"
15
- option :attempt, desc: "Original parent attempt"
16
- option :head, desc: "Exact parent candidate head"
17
- option :candidate_generation, desc: "Exact parent candidate counter"
18
- option :mutation, desc: "Stable R1 round attempt ID"
19
- option :input, desc: "Closed bounded round envelope"
20
- option :artifact, type: :array, desc: "Ordered report byte files"
21
-
22
- def call(**options)
23
- usage = "campaign-record-round with original parent selectors, --mutation ID and --input FILE"
24
- context = protected_context(options)
25
- raise AttemptErrors::EvidenceUnavailable, "Campaign consumption requires installed authority" unless context
26
- params = {"assignment_id" => require_option(options, :assignment, usage),
27
- "attempt_id" => require_option(options, :attempt, usage), "head" => require_option(options, :head, usage),
28
- "candidate_generation" => integer_option(options, :candidate_generation, usage, positive: true)}
29
- unless params["head"].match?(/\A[0-9a-f]{40}\z/)
30
- raise Ace::Support::Cli::Error, "Campaign head must be an exact SHA-1"
31
- end
32
- context.verify_attempt_hints!(assignment_id: params.fetch("assignment_id"), attempt_id: params.fetch("attempt_id"))
33
- mutation = require_option(options, :mutation, usage)
34
- bytes = input_bytes(require_option(options, :input, usage), limit: 16 * 1024)
35
- files = options.fetch(:artifact, [])
36
- raise Ace::Support::Cli::Error, "Campaign artifacts require at most 16 files" unless files.is_a?(Array) && files.size <= 16
37
- parts = [bytes, *files.map { |path| input_bytes(path, limit: 64 * 1024, nonempty: false) }]
38
- params["input_sha256"] = Digest::SHA256.hexdigest(bytes)
39
- input = Struct.new(:parts) do
40
- def count = parts.length
41
- def bytes(index:) = parts.fetch(index)
42
- end.new(parts)
43
- admitted = Ace::Assign::Authority::CampaignRoundTransfer.decode(input: input, sha256: params.fetch("input_sha256"))
44
- unless admitted.fetch(:round).values_at("attempt_id", "head") == [mutation, params.fetch("head")]
45
- raise Ace::Support::Cli::Error, "Campaign input request ID or head differs"
46
- end
47
- emit_json(upload_call(context.client(options: options), "campaign_record_round", params, mutation, parts, :receipt_artifacts))
48
- rescue SecurityError, Ace::Runtime::RuntimeUnavailableError
49
- protected_transport_unavailable!
50
- rescue ArgumentError => error
51
- raise Ace::Support::Cli::Error, error.message
52
- end
53
- end
54
- end
55
- end
56
- end
57
- end
@@ -1,107 +0,0 @@
1
- # frozen_string_literal: true
2
- require "json"
3
- require_relative "../../authority/protected_assignment_context"
4
- require_relative "../../organisms/protected_delivery_coordinator"
5
-
6
- module Ace
7
- module Assign
8
- module CLI
9
- module Commands
10
- class Delivery < Ace::Support::Cli::Command
11
- desc "Execute or reconcile attempt-bound forge delivery"
12
- option :assignment, required: true, desc: "Managed assignment ID"
13
- option :attempt, required: true, desc: "Active delivery attempt ID"
14
- option :operation, required: true, desc: "create, update, ready, review, merge, status"
15
- option :parameters, desc: "JSON delivery parameters (otherwise assignment source delivery mapping)"
16
- option :title, desc: "PR title"
17
- option :body_file, desc: "PR description file"
18
- option :tests, desc: "JSON reference to coordinator-accepted test attempt and receipt digest"
19
- option :review, desc: "JSON reference to coordinator-accepted independent review attempt and receipt digest"
20
- option :service_request, desc: "Original authorized PR service request ID"
21
- option :mapping, desc: "Installed original protected mapping selector"
22
- option :scope, desc: "Original prepared worker scope"
23
- option :candidate_head, desc: "Exact original protected candidate SHA"
24
- option :candidate_generation, type: :integer, desc: "Exact original protected candidate generation"
25
- option :input_digest, desc: "Exact original accepted service input SHA256"
26
- option :target, desc: "Exact original target repository or PR URL"
27
- option :artifact_digest, desc: "Original target artifact SHA256, if present"
28
-
29
- def initialize(protected_context: nil)
30
- @protected_assignment_context = protected_context
31
- end
32
-
33
- def call(**options)
34
- context = @protected_assignment_context ||= Ace::Assign::Authority::ProtectedAssignmentContext.load
35
- protected = context.protected_participant? || options[:mapping] || context.mapping_hint?
36
- if protected
37
- unless context.protected_worker?
38
- raise AttemptErrors::EvidenceUnavailable, "protected delivery is worker-only"
39
- end
40
- unless %w[create update ready merge status].include?(options[:operation]) && %i[title body_file tests review].all? { |key| options[key].nil? }
41
- raise ArgumentError, "protected delivery accepts canonical PR results/status only; local input/evidence flags are unavailable"
42
- end
43
- input = context.resolve(options: options, assignment_id: options[:assignment], scope: options[:scope])
44
- raise AttemptErrors::EvidenceUnavailable, "original protected prepared input is unavailable" unless input
45
- result = Organisms::ProtectedDeliveryCoordinator.new(client: context.client(options: options),
46
- project_id: input.work.manifest.fetch("project_id")).perform(assignment_id: options[:assignment],
47
- attempt_id: options[:attempt], operation: options[:operation], service_request_id: options[:service_request],
48
- candidate_head: options[:candidate_head], candidate_generation: options[:candidate_generation],
49
- input_digest: options[:input_digest], target: {"resource" => options[:target], "artifact_digest" => options[:artifact_digest]},
50
- parameters: read_json(options[:parameters]))
51
- puts JSON.generate(json_value(result))
52
- return
53
- end
54
- if %i[mapping scope candidate_head candidate_generation input_digest target artifact_digest].any? { |key| options[key] }
55
- raise ArgumentError, "protected delivery selectors cannot select ordinary local delivery"
56
- end
57
- result = build_delivery.perform(assignment_id: options[:assignment], attempt_id: options[:attempt],
58
- operation: options[:operation], parameters: read_json(options[:parameters]),
59
- title: options[:title], body: options[:body_file] && File.read(options[:body_file]),
60
- tests: read_json(options[:tests]), review: read_json(options[:review]),
61
- service_request_id: options[:service_request])
62
- puts JSON.generate(json_value(result))
63
- rescue ArgumentError, Ace::Git::Error, Ace::Assign::Error, Ace::Runtime::RuntimeUnavailableError,
64
- JSON::ParserError, SecurityError, SystemCallError => e
65
- message = e.is_a?(JSON::ParserError) ? "protected installed selection unavailable" : e.message
66
- raise Ace::Support::Cli::Error, message
67
- end
68
-
69
- private
70
-
71
- def build_delivery
72
- Organisms::DeliveryCoordinator.new(repo_root: Ace::Support::Fs::Molecules::ProjectRootFinder.find_or_current)
73
- end
74
-
75
- def read_json(path)
76
- return nil unless path
77
- bytes = File.open(path, File::RDONLY | File::NOFOLLOW | File::NONBLOCK) do |file|
78
- before = file.stat
79
- raise ArgumentError, "Delivery input must be a bounded regular file" unless before.file? && before.size <= 65_536
80
- value = file.read(65_537)
81
- after = file.stat
82
- unless value && value.bytesize <= 65_536 &&
83
- [before.dev, before.ino, before.size, before.mtime, before.ctime] == [after.dev, after.ino, after.size, after.mtime, after.ctime]
84
- raise ArgumentError, "Delivery input changed or exceeds its bound"
85
- end
86
- value
87
- end
88
- data = JSON.parse(bytes, max_nesting: 16, create_additions: false,
89
- allow_duplicate_key: false, allow_comments: false, allow_nan: false)
90
- raise ArgumentError, "Delivery input must be a JSON object" unless data.is_a?(Hash)
91
- data
92
- rescue JSON::ParserError
93
- raise ArgumentError, "Delivery input must be valid JSON"
94
- end
95
-
96
- def json_value(value)
97
- case value
98
- when Hash then value.transform_values { |item| json_value(item) }
99
- when Array then value.map { |item| json_value(item) }
100
- else value.respond_to?(:to_h) ? json_value(value.to_h) : value
101
- end
102
- end
103
- end
104
- end
105
- end
106
- end
107
- end
@@ -1,34 +0,0 @@
1
- # frozen_string_literal: true
2
- require_relative "attempt/base"
3
-
4
- module Ace
5
- module Assign
6
- module CLI
7
- module Commands
8
- class InboxBind < Ace::Support::Cli::Command
9
- include Ace::Support::Cli::Base
10
- include Attempt::Base
11
-
12
- desc "Bind an original protected Inbox registration before scope closure"
13
- option :mapping, desc: "Exact installed mapping ID"
14
- option :assignment, desc: "Exact original assignment ID"
15
- option :attempt, desc: "Exact original attempt ID"
16
- option :event, desc: "Original Inbox event ID"
17
- option :inbox_context, desc: "Installed Inbox context ID"
18
- option :mutation, desc: "Stable original authority mutation ID"
19
- option :expected_generation, desc: "Original authority mutation generation"
20
-
21
- def call(**options)
22
- context = protected_context(options)
23
- raise AttemptErrors::EvidenceUnavailable, "Inbox binding requires installed protected authority" unless context
24
- usage = "inbox-bind --mapping MAP --assignment ID --attempt ID --event ID --inbox-context ID --mutation ID --expected-generation N"
25
- client, params, mutation = protected_attempt_request(context, options, usage)
26
- params.merge!("event_id" => require_option(options, :event, usage),
27
- "inbox_context_id" => require_option(options, :inbox_context, usage))
28
- emit_json(protected_call(client, "bind_inbox", params, mutation))
29
- end
30
- end
31
- end
32
- end
33
- end
34
- end
@@ -1,61 +0,0 @@
1
- # frozen_string_literal: true
2
- require_relative "attempt/base"
3
- require "digest"
4
-
5
- module Ace
6
- module Assign
7
- module CLI
8
- module Commands
9
- # Local files provide bounded bytes; only the existing authority grants admission.
10
- module ProtectedSubmission
11
- include Attempt::Base
12
- private
13
-
14
- def require_option(options, key, usage)
15
- value = options[key].to_s.strip
16
- raise Ace::Support::Cli::Error, "Missing --#{key}: usage: ace-assign #{usage}" if value.empty?
17
- value
18
- end
19
-
20
- def submission_request(options, usage)
21
- context = protected_context(options)
22
- raise AttemptErrors::EvidenceUnavailable, "Submission requires installed protected authority" unless context
23
- protected_attempt_request(context, options, usage)
24
- end
25
-
26
- def input_bytes(path, limit:, nonempty: true)
27
- File.open(path, File::RDONLY | File::NOFOLLOW | File::NONBLOCK) do |file|
28
- before = file.stat
29
- raise Ace::Support::Cli::Error, "Input must be a bounded regular file" unless before.file? && before.size.between?(nonempty ? 1 : 0, limit)
30
- bytes = file.read(limit + 1)
31
- after = file.stat
32
- selected = File.lstat(path)
33
- identity = ->(stat) { [stat.dev, stat.ino, stat.size, stat.mtime, stat.ctime] }
34
- unless bytes.bytesize == before.size && identity.call(before) == identity.call(after) &&
35
- identity.call(after) == identity.call(selected) && !selected.symlink?
36
- raise Ace::Support::Cli::Error, "Input changed during bounded read"
37
- end
38
- bytes.freeze
39
- end
40
- rescue SystemCallError, IOError
41
- raise Ace::Support::Cli::Error, "Input file is unavailable"
42
- end
43
-
44
- def input_json(bytes)
45
- text = bytes.dup.force_encoding(Encoding::UTF_8)
46
- raise Ace::Support::Cli::Error, "Input JSON is malformed" unless text.valid_encoding?
47
- JSON.parse(text, create_additions: false, max_nesting: 32, allow_duplicate_key: false, allow_comments: false)
48
- rescue JSON::ParserError
49
- raise Ace::Support::Cli::Error, "Input JSON is malformed"
50
- end
51
-
52
- def upload_call(client, operation, params, mutation, parts, purpose)
53
- client.call(operation, params, mutation_id: mutation, upload_parts: parts, purpose: purpose, timeout: 30).data
54
- rescue SecurityError, Ace::Runtime::RuntimeUnavailableError, SystemCallError, IOError
55
- protected_transport_unavailable!
56
- end
57
- end
58
- end
59
- end
60
- end
61
- end
@@ -1,38 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "json"
4
-
5
- module Ace
6
- module Assign
7
- module CLI
8
- module Commands
9
- class Resume < Ace::Support::Cli::Command
10
- include Ace::Support::Cli::Base
11
- include AssignmentTarget
12
- desc "Recover accepted assignment state without launching another writer"
13
- option :assignment, desc: "Assignment ID"
14
- option :mapping, desc: "Installed protected launch mapping ID"
15
- option :attempt, desc: "Original protected attempt ID"
16
- option :dry_run, type: :boolean, default: false, desc: "Inspect recovery without writing observations"
17
-
18
- def call(**options)
19
- target = resolve_assignment_target(options)
20
- if target.prepared_input
21
- raise Ace::Support::Cli::Error, "protected resume only supports --dry-run" unless options[:dry_run]
22
- result = build_executor_for_target(target).status
23
- descriptor = target.prepared_input.descriptor
24
- puts JSON.generate(descriptor.slice("assignment_id", "mapping_id", "attempt_id", "scope", "definition_digest", "selection_sha256").merge(
25
- "state" => result.fetch(:state).assignment_state.to_s, "dry_run" => true))
26
- return
27
- end
28
- id = options[:assignment].to_s.strip
29
- raise Ace::Support::Cli::Error, "--assignment ID is required" if id.empty?
30
-
31
- puts JSON.generate(Organisms::AttemptCoordinator.new.resume(
32
- assignment_id: id, dry_run: options[:dry_run]))
33
- end
34
- end
35
- end
36
- end
37
- end
38
- end
@@ -1,34 +0,0 @@
1
- # frozen_string_literal: true
2
- require_relative "protected_submission"
3
- module Ace
4
- module Assign
5
- module CLI
6
- module Commands
7
- class SubmitCandidate < Ace::Support::Cli::Command
8
- include Ace::Support::Cli::Base
9
- include ProtectedSubmission
10
- desc "Submit original protected worker bundle bytes"
11
- option :mapping, desc: "Installed mapping"
12
- option :assignment, desc: "Original assignment"
13
- option :attempt, desc: "Original attempt"
14
- option :head, desc: "Exact candidate head"
15
- option :candidate_generation, desc: "Expected current candidate generation (zero before first submission)"
16
- option :expected_generation, desc: "Persisted authority generation"
17
- option :mutation, desc: "Stable original mutation"
18
- option :bundle, desc: "Bounded local byte file"
19
-
20
- def call(**options)
21
- usage = "submit-candidate with original selectors, generation, mutation and --bundle FILE"
22
- client, params, mutation = submission_request(options, usage)
23
- head = require_option(options, :head, usage)
24
- raise Ace::Support::Cli::Error, "Head must be an exact SHA-1" unless head.match?(/\A[0-9a-f]{40}\z/)
25
- params.merge!("head" => head, "candidate_generation" => integer_option(options, :candidate_generation, usage))
26
- bytes = input_bytes(require_option(options, :bundle, usage), limit: Ace::Assign::Authority::CandidateTransfer::MAX_BYTES)
27
- parts = [bytes]
28
- emit_json(upload_call(client, "submit_candidate", params, mutation, parts, :candidate))
29
- end
30
- end
31
- end
32
- end
33
- end
34
- end
@@ -1,61 +0,0 @@
1
- # frozen_string_literal: true
2
- require_relative "protected_submission"
3
- require_relative "../../authority/receipt_transfer"
4
- module Ace
5
- module Assign
6
- module CLI
7
- module Commands
8
- class SubmitResult < Ace::Support::Cli::Command
9
- include Ace::Support::Cli::Base
10
- include ProtectedSubmission
11
- desc "Submit original protected worker receipt bytes"
12
- option :mapping, desc: "Installed mapping"
13
- option :assignment, desc: "Original assignment"
14
- option :attempt, desc: "Original attempt"
15
- option :head, desc: "Exact candidate head"
16
- option :candidate_generation, desc: "Positive candidate generation"
17
- option :expected_generation, desc: "Persisted authority generation"
18
- option :mutation, desc: "Stable original mutation"
19
- option :receipt, desc: "Bounded local byte file"
20
- option :artifact, type: :array, desc: "Ordered artifact byte files"
21
-
22
- def call(**options)
23
- usage = "submit-result with original selectors, generation, mutation and --receipt FILE"
24
- client, params, mutation = submission_request(options, usage)
25
- head = require_option(options, :head, usage)
26
- raise Ace::Support::Cli::Error, "Head must be an exact SHA-1" unless head.match?(/\A[0-9a-f]{40}\z/)
27
- params.merge!("head" => head, "candidate_generation" => integer_option(options, :candidate_generation, usage, positive: true))
28
- bytes = input_bytes(require_option(options, :receipt, usage), limit: 16 * 1024)
29
- receipt = input_json(bytes)
30
- raise Ace::Support::Cli::Error, "Receipt must be an object" unless receipt.is_a?(Hash)
31
- allowed = Models::ExecutionReceipt.from_h(receipt).to_h.keys
32
- if (receipt.keys - allowed).any? || Models::ExecutionReceipt.forbidden_field?(receipt)
33
- raise Ace::Support::Cli::Error, "Receipt contains unsupported fields"
34
- end
35
- unless receipt.values_at("assignment_id", "attempt_id", "head") == params.values_at("assignment_id", "attempt_id", "head") &&
36
- Models::ExecutionReceipt::VERDICTS.include?(receipt["verdict"])
37
- raise Ace::Support::Cli::Error, "Receipt identity or verdict differs"
38
- end
39
- files = options.fetch(:artifact, [])
40
- unless files.is_a?(Array) && files.size <= 16 && !(receipt["verdict"] == "succeeded" && files.empty?)
41
- raise Ace::Support::Cli::Error, "Receipt artifact declarations are invalid"
42
- end
43
- artifacts = files.map { |path| input_bytes(path, limit: 64 * 1024, nonempty: false) }
44
- parts = [bytes, *artifacts]
45
- digest = Digest::SHA256.hexdigest(bytes)
46
- input = Struct.new(:parts) do
47
- def count = parts.length
48
- def bytes(index: 0) = parts.fetch(index)
49
- end.new(parts)
50
- # Reuse the maintained ordered artifact validator. This is input
51
- # checking only; canonical result/producer admission remains Endcap's.
52
- Ace::Assign::Authority::ReceiptTransfer.decode(input: input, receipt_sha256: digest,
53
- artifact_field: "artifacts", reference_key: "path")
54
- params["receipt_sha256"] = digest
55
- emit_json(upload_call(client, "submit_result", params, mutation, parts, :receipt_artifacts))
56
- end
57
- end
58
- end
59
- end
60
- end
61
- end
@@ -1,178 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "../atoms/attempt_state_machine"
4
- require_relative "attempt_binding"
5
-
6
- module Ace
7
- module Assign
8
- module Models
9
- # Mutable derived state of an assignment attempt.
10
- #
11
- # The binding is immutable; an Attempt carries the derived execution
12
- # state: the current state-machine state, the pinned candidate head,
13
- # the latest evidence journal commit, accepted receipts (by digest),
14
- # recorded effects, and the append-only local event trail for
15
- # taskless attempts. `base_head` (binding) and `candidate_head`
16
- # (deliverable) are always tracked separately.
17
- class Attempt
18
- attr_reader :binding, :state, :candidate_head, :journal_commit,
19
- :accepted_receipts, :effects, :events, :updated_at
20
-
21
- # @param binding [Models::AttemptBinding] Immutable start-time binding
22
- # @param state [String] State-machine state
23
- # @param candidate_head [String, nil] Pinned deliverable head
24
- # @param journal_commit [String, nil] Latest evidence journal commit SHA
25
- # @param accepted_receipts [Array<Hash>] Accepted receipt payloads (digests included)
26
- # @param effects [Array<Hash>] Recorded effects with intent/receipt linkage
27
- # @param events [Array<Hash>] Local event trail (taskless attempts only)
28
- # @param updated_at [Time] Last state change
29
- def initialize(binding:, state: "reserved", candidate_head: nil, journal_commit: nil,
30
- accepted_receipts: [], effects: [], events: [], updated_at: nil)
31
- @binding = binding
32
- @state = state.to_s
33
- @candidate_head = candidate_head
34
- @journal_commit = journal_commit
35
- @accepted_receipts = accepted_receipts.dup.freeze
36
- @effects = effects.dup.freeze
37
- @events = events.dup.freeze
38
- @updated_at = updated_at || binding.created_at
39
- end
40
-
41
- # @return [String] Immutable attempt ID
42
- def attempt_id
43
- binding.attempt_id
44
- end
45
-
46
- # @return [Boolean] True if no further transitions or effects are allowed
47
- def terminal?
48
- Atoms::AttemptStateMachine.terminal?(state)
49
- end
50
-
51
- # @return [Boolean] True if the attempt awaits reconciliation
52
- def uncertain?
53
- state == "uncertain"
54
- end
55
-
56
- # @return [Boolean] True if the attempt owns an active subtree slot
57
- def active?
58
- %w[reserved running uncertain].include?(state)
59
- end
60
-
61
- # @return [String] "git" or "local_only" recovery mode
62
- def recovery_mode
63
- binding.recovery_mode
64
- end
65
-
66
- # @return [Boolean] True when managed evidence-ref journaling applies
67
- def managed?
68
- binding.managed?
69
- end
70
-
71
- # Effects whose intent was recorded but whose accepted receipt is missing.
72
- #
73
- # @return [Array<Hash>] Unresolved effect records
74
- def unresolved_effects
75
- effects.reject { |effect| effect["receipt_digest"] }
76
- end
77
-
78
- # Return a new Attempt with the state transitioned.
79
- #
80
- # @param to [String] Target state (validated against the state machine)
81
- # @param at [Time] Transition time
82
- # @return [Attempt] New attempt instance
83
- # @raise [Ace::Assign::AttemptErrors::InvalidTransition] if illegal
84
- def transition(to, at: Time.now.utc)
85
- Atoms::AttemptStateMachine.transition!(state, to)
86
- with(state: to.to_s, updated_at: at)
87
- end
88
-
89
- # Return a new Attempt with updated derived fields. The binding is
90
- # never replaced.
91
- #
92
- # @param attrs [Hash] Fields to override (state, candidate_head,
93
- # journal_commit, accepted_receipts, effects, events, updated_at)
94
- # @return [Attempt] New attempt instance
95
- def with(**attrs)
96
- Attempt.new(
97
- binding: binding,
98
- state: attrs.fetch(:state, state),
99
- candidate_head: attrs.fetch(:candidate_head, candidate_head),
100
- journal_commit: attrs.fetch(:journal_commit, journal_commit),
101
- accepted_receipts: attrs.fetch(:accepted_receipts, accepted_receipts),
102
- effects: attrs.fetch(:effects, effects),
103
- events: attrs.fetch(:events, events),
104
- updated_at: attrs.fetch(:updated_at, Time.now.utc)
105
- )
106
- end
107
-
108
- # Convert to a hash for canonical serialization.
109
- # @return [Hash] Attempt data with string keys
110
- def to_h
111
- {
112
- "binding" => binding.to_h,
113
- "state" => state,
114
- "candidate_head" => candidate_head,
115
- "journal_commit" => journal_commit,
116
- "accepted_receipts" => accepted_receipts,
117
- "effects" => effects,
118
- "events" => events,
119
- "updated_at" => updated_at.iso8601
120
- }
121
- end
122
-
123
- # JSON projection exposed by public commands. Contains attempt
124
- # ID, state, binding facts, references and digests only — never
125
- # credentials, receipt bytes, or terminal output.
126
- #
127
- # @return [Hash] Redacted projection
128
- def projection
129
- {
130
- "attempt_id" => attempt_id,
131
- "state" => state,
132
- "assignment_id" => binding.assignment_id,
133
- "scope" => binding.scope,
134
- "project_id" => binding.project_id,
135
- "task_id" => binding.task_id,
136
- "actor" => binding.actor,
137
- "role" => binding.role,
138
- "runtime" => binding.runtime,
139
- "base_head" => binding.base_head,
140
- "candidate_head" => candidate_head,
141
- "evidence_git_ref" => binding.evidence_git_ref,
142
- "journal_commit" => journal_commit,
143
- "recovery_mode" => recovery_mode,
144
- "accepted_receipt_digests" => accepted_receipts.map { |receipt| receipt["digest"] },
145
- "unresolved_effects" => unresolved_effects.map { |effect| effect["operation"] },
146
- "created_at" => binding.created_at.iso8601,
147
- "updated_at" => updated_at.iso8601
148
- }
149
- end
150
-
151
- # Rebuild an attempt from serialized data.
152
- #
153
- # @param data [Hash] Serialized attempt (string keys)
154
- # @return [Attempt] Rebuilt attempt
155
- def self.from_h(data)
156
- new(
157
- binding: AttemptBinding.from_h(data["binding"]),
158
- state: data["state"],
159
- candidate_head: data["candidate_head"],
160
- journal_commit: data["journal_commit"],
161
- accepted_receipts: data["accepted_receipts"] || [],
162
- effects: data["effects"] || [],
163
- events: data["events"] || [],
164
- updated_at: parse_time(data["updated_at"])
165
- )
166
- end
167
-
168
- def self.parse_time(value)
169
- return nil if value.nil?
170
- return value if value.is_a?(Time)
171
- require "time"
172
- Time.parse(value)
173
- end
174
- private_class_method :parse_time
175
- end
176
- end
177
- end
178
- end