ace-assign 0.64.0 → 0.66.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.
- checksums.yaml +4 -4
- data/.ace-defaults/assign/catalog/steps/auto-merge.step.yml +11 -0
- data/CHANGELOG.md +25 -0
- data/docs/protected-authority.md +23 -7
- data/docs/usage.md +117 -4
- data/exe/ace-scope-ready +55 -0
- data/handbook/workflow-instructions/assign/drive.wf.md +26 -1
- data/lib/ace/assign/atoms/attempt_state_machine.rb +8 -0
- data/lib/ace/assign/atoms/delivery_parameters.rb +4 -39
- data/lib/ace/assign/authority/assignment_inventory.rb +290 -0
- data/lib/ace/assign/authority/campaign_consumer_policy.rb +100 -0
- data/lib/ace/assign/authority/campaign_execution.rb +119 -0
- data/lib/ace/assign/authority/campaign_round_transfer.rb +82 -0
- data/lib/ace/assign/authority/candidate_transfer.rb +169 -2
- data/lib/ace/assign/authority/cleanup_result_protection.rb +53 -0
- data/lib/ace/assign/authority/client.rb +156 -6
- data/lib/ace/assign/authority/deployment.rb +201 -39
- data/lib/ace/assign/authority/deployment_history.rb +146 -0
- data/lib/ace/assign/authority/endcap.rb +131 -18
- data/lib/ace/assign/authority/endcap_attempts.rb +274 -0
- data/lib/ace/assign/authority/endcap_campaign_results.rb +232 -0
- data/lib/ace/assign/authority/endcap_campaign_rounds.rb +266 -0
- data/lib/ace/assign/authority/endcap_contexts.rb +23 -0
- data/lib/ace/assign/authority/endcap_inboxes.rb +3 -280
- data/lib/ace/assign/authority/endcap_prepared_work.rb +80 -0
- data/lib/ace/assign/authority/endcap_publication.rb +207 -0
- data/lib/ace/assign/authority/endcap_results.rb +36 -18
- data/lib/ace/assign/authority/endcap_review_requests.rb +183 -0
- data/lib/ace/assign/authority/endcap_reviews.rb +130 -0
- data/lib/ace/assign/authority/endcap_service_settlement.rb +152 -0
- data/lib/ace/assign/authority/endcap_services.rb +221 -18
- data/lib/ace/assign/authority/execution_scope_observation.rb +468 -41
- data/lib/ace/assign/authority/inbox_context_original.rb +113 -0
- data/lib/ace/assign/authority/launch_attempt_consumers.rb +51 -0
- data/lib/ace/assign/authority/launch_campaign_registration.rb +214 -0
- data/lib/ace/assign/authority/launch_control_channel.rb +313 -0
- data/lib/ace/assign/authority/launch_control_exclusions.rb +141 -0
- data/lib/ace/assign/authority/launch_driver.rb +298 -19
- data/lib/ace/assign/authority/launch_input_inhibition.rb +132 -0
- data/lib/ace/assign/authority/launch_lifecycle.rb +275 -100
- data/lib/ace/assign/authority/launch_prepared_work.rb +122 -0
- data/lib/ace/assign/authority/launch_review.rb +91 -0
- data/lib/ace/assign/authority/launch_scope_abort.rb +0 -7
- data/lib/ace/assign/authority/launch_scope_admission.rb +136 -5
- data/lib/ace/assign/authority/launch_scope_close.rb +68 -37
- data/lib/ace/assign/authority/launch_scope_release.rb +696 -17
- data/lib/ace/assign/authority/launch_steering.rb +353 -0
- data/lib/ace/assign/authority/launch_stop.rb +254 -0
- data/lib/ace/assign/authority/maintenance_workspace_retirement.rb +229 -0
- data/lib/ace/assign/authority/original_inbox_selection.rb +41 -0
- data/lib/ace/assign/authority/posix_acl.rb +5 -2
- data/lib/ace/assign/authority/prepared_input.rb +130 -0
- data/lib/ace/assign/authority/prepared_queue.rb +185 -0
- data/lib/ace/assign/authority/prepared_task_context.rb +116 -0
- data/lib/ace/assign/authority/prepared_work.rb +304 -0
- data/lib/ace/assign/authority/prepared_worker.rb +69 -0
- data/lib/ace/assign/authority/protected_assignment_context.rb +226 -0
- data/lib/ace/assign/authority/readiness_hook.rb +120 -0
- data/lib/ace/assign/authority/router.rb +24 -2
- data/lib/ace/assign/authority/server.rb +103 -33
- data/lib/ace/assign/authority/service_cleanup_dispatch.rb +88 -0
- data/lib/ace/assign/authority/service_cleanup_evidence.rb +180 -0
- data/lib/ace/assign/authority/service_delivery_evidence.rb +101 -0
- data/lib/ace/assign/authority/service_evidence.rb +243 -12
- data/lib/ace/assign/authority/service_publication_evidence.rb +110 -0
- data/lib/ace/assign/authority/task_context_entry.rb +32 -0
- data/lib/ace/assign/authority/transfer_codec.rb +44 -12
- data/lib/ace/assign/cli/commands/assignment_target.rb +15 -2
- data/lib/ace/assign/cli/commands/attempt/base.rb +49 -0
- data/lib/ace/assign/cli/commands/attempt/finish.rb +20 -4
- data/lib/ace/assign/cli/commands/attempt/reconcile.rb +11 -0
- data/lib/ace/assign/cli/commands/authority/inbox_context_principal.rb +25 -0
- data/lib/ace/assign/cli/commands/authority/inbox_context_selection.rb +28 -0
- data/lib/ace/assign/cli/commands/authority/launch.rb +58 -6
- data/lib/ace/assign/cli/commands/authority/serve.rb +2 -1
- data/lib/ace/assign/cli/commands/authority/status.rb +1 -1
- data/lib/ace/assign/cli/commands/authority/task_context.rb +31 -0
- data/lib/ace/assign/cli/commands/authority/task_context_principal.rb +25 -0
- data/lib/ace/assign/cli/commands/authority/task_context_selection.rb +30 -0
- data/lib/ace/assign/cli/commands/authority/worker.rb +23 -0
- data/lib/ace/assign/cli/commands/campaign_export_result.rb +55 -0
- data/lib/ace/assign/cli/commands/campaign_record_round.rb +57 -0
- data/lib/ace/assign/cli/commands/create.rb +2 -0
- data/lib/ace/assign/cli/commands/delivery.rb +54 -4
- data/lib/ace/assign/cli/commands/fail.rb +6 -0
- data/lib/ace/assign/cli/commands/finish.rb +23 -2
- data/lib/ace/assign/cli/commands/fork_session.rb +6 -2
- data/lib/ace/assign/cli/commands/inbox_bind.rb +34 -0
- data/lib/ace/assign/cli/commands/protected_submission.rb +61 -0
- data/lib/ace/assign/cli/commands/resume.rb +12 -0
- data/lib/ace/assign/cli/commands/select.rb +2 -0
- data/lib/ace/assign/cli/commands/start.rb +2 -0
- data/lib/ace/assign/cli/commands/status.rb +4 -2
- data/lib/ace/assign/cli/commands/step.rb +3 -1
- data/lib/ace/assign/cli/commands/submit_candidate.rb +34 -0
- data/lib/ace/assign/cli/commands/submit_result.rb +61 -0
- data/lib/ace/assign/cli.rb +36 -5
- data/lib/ace/assign/errors.rb +139 -0
- data/lib/ace/assign/models/assignment.rb +26 -4
- data/lib/ace/assign/models/evidence_event.rb +3 -3
- data/lib/ace/assign/molecules/assignment_manager.rb +12 -2
- data/lib/ace/assign/molecules/attempt_reconciler.rb +20 -0
- data/lib/ace/assign/molecules/canonical_attempt_state.rb +151 -0
- data/lib/ace/assign/molecules/canonical_evidence.rb +5 -2
- data/lib/ace/assign/molecules/canonical_read_snapshot.rb +374 -0
- data/lib/ace/assign/molecules/evidence_journal.rb +720 -44
- data/lib/ace/assign/molecules/execution_scope_lineage.rb +27 -9
- data/lib/ace/assign/molecules/fork_session_launcher.rb +16 -6
- data/lib/ace/assign/molecules/journal_input_inhibition.rb +78 -0
- data/lib/ace/assign/molecules/journal_mutation.rb +82 -13
- data/lib/ace/assign/molecules/journal_prompt_mutation.rb +241 -0
- data/lib/ace/assign/molecules/lifecycle_exclusion.rb +178 -26
- data/lib/ace/assign/molecules/original_launch_binding.rb +37 -0
- data/lib/ace/assign/molecules/protected_control_exclusion.rb +303 -0
- data/lib/ace/assign/molecules/protected_workspace_exclusion.rb +330 -0
- data/lib/ace/assign/molecules/protected_workspace_provisioner.rb +164 -0
- data/lib/ace/assign/molecules/queue_scanner.rb +26 -11
- data/lib/ace/assign/molecules/receipt_verifier.rb +21 -15
- data/lib/ace/assign/molecules/step_writer.rb +27 -5
- data/lib/ace/assign/molecules/terminal_scope_receipt.rb +88 -0
- data/lib/ace/assign/organisms/assignment_executor.rb +4 -4
- data/lib/ace/assign/organisms/attempt_coordinator.rb +66 -76
- data/lib/ace/assign/organisms/prepared_work_builder.rb +194 -0
- data/lib/ace/assign/organisms/protected_delivery_coordinator.rb +100 -0
- data/lib/ace/assign/organisms/task_assignment_creator.rb +4 -2
- data/lib/ace/assign/version.rb +1 -1
- data/lib/ace/assign.rb +1 -112
- metadata +90 -11
- data/lib/ace/assign/cli/commands/inbox_reconcile.rb +0 -36
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "json"
|
|
4
|
+
require_relative "../../../authority/protected_assignment_context"
|
|
4
5
|
|
|
5
6
|
module Ace
|
|
6
7
|
module Assign
|
|
@@ -13,6 +14,54 @@ module Ace
|
|
|
13
14
|
module Base
|
|
14
15
|
private
|
|
15
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
|
+
|
|
16
65
|
def build_coordinator
|
|
17
66
|
Organisms::AttemptCoordinator.new
|
|
18
67
|
end
|
|
@@ -8,18 +8,34 @@ module Ace
|
|
|
8
8
|
module CLI
|
|
9
9
|
module Commands
|
|
10
10
|
module Attempt
|
|
11
|
-
#
|
|
12
|
-
#
|
|
11
|
+
# Protected completion selects an already submitted canonical result.
|
|
12
|
+
# Ordinary local completion accepts its structured receipt file.
|
|
13
13
|
class Finish < Ace::Support::Cli::Command
|
|
14
14
|
include Ace::Support::Cli::Base
|
|
15
15
|
include Attempt::Base
|
|
16
16
|
|
|
17
|
-
desc "Finish
|
|
17
|
+
desc "Finish a protected canonical result or an ordinary local receipt"
|
|
18
18
|
|
|
19
19
|
option :attempt, desc: "Attempt ID"
|
|
20
|
-
option :
|
|
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"
|
|
21
28
|
|
|
22
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
|
|
23
39
|
attempt_id = require_option(options, :attempt, "finish --attempt ID --receipt FILE")
|
|
24
40
|
receipt = require_option(options, :receipt, "finish --attempt ID --receipt FILE")
|
|
25
41
|
|
|
@@ -17,9 +17,20 @@ module Ace
|
|
|
17
17
|
desc "Reconcile an interrupted or uncertain attempt"
|
|
18
18
|
|
|
19
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"
|
|
20
24
|
option :receipt, desc: "Path to the receipt JSON file (required to resolve uncertainty)"
|
|
21
25
|
|
|
22
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
|
|
23
34
|
attempt_id = require_option(options, :attempt, "reconcile --attempt ID [--receipt FILE]")
|
|
24
35
|
receipt = options[:receipt].to_s.strip
|
|
25
36
|
|
|
@@ -0,0 +1,25 @@
|
|
|
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
|
|
@@ -0,0 +1,28 @@
|
|
|
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
|
|
@@ -7,10 +7,13 @@ module Ace
|
|
|
7
7
|
module Commands
|
|
8
8
|
module Authority
|
|
9
9
|
class Launch < Ace::Support::Cli::Command
|
|
10
|
-
desc "Launch one mapped
|
|
10
|
+
desc "Launch one mapped worker and retain its original foreground control channel"
|
|
11
|
+
READY_FIELDS = %w[assignment_id attempt_id generation journal_commit mapping_id original_binding_digest type version].freeze
|
|
12
|
+
READY_LIMIT = 16_384
|
|
11
13
|
option :mapping, required: true, desc: "Installed launch mapping ID"
|
|
12
14
|
option :assignment, desc: "Managed assignment ID"
|
|
13
|
-
option :definition, desc: "
|
|
15
|
+
option :definition, desc: "Retained final prepared assignment definition JSON file"
|
|
16
|
+
option :prepared_bundle, desc: "Retained original prepared input Git bundle"
|
|
14
17
|
option :step, desc: "Assignment subtree scope"
|
|
15
18
|
option :base_head, desc: "Exact base commit SHA"
|
|
16
19
|
option :mutation, desc: "Stable invocation ID for safe replay"
|
|
@@ -20,20 +23,69 @@ module Ace
|
|
|
20
23
|
result = if options[:dry_run]
|
|
21
24
|
driver.preflight
|
|
22
25
|
else
|
|
23
|
-
%i[assignment definition step base_head].each do |key|
|
|
26
|
+
%i[assignment definition prepared_bundle step base_head].each do |key|
|
|
24
27
|
raise ArgumentError, "Missing --#{key.to_s.tr('_', '-')}" unless options[key].is_a?(String) && !options[key].empty?
|
|
25
28
|
end
|
|
26
|
-
|
|
27
|
-
|
|
29
|
+
definition = retained_input(options[:definition], 32_768).force_encoding(Encoding::UTF_8)
|
|
30
|
+
raise ArgumentError, "assignment definition is not UTF-8" unless definition.valid_encoding?
|
|
31
|
+
bundle = retained_input(options[:prepared_bundle], Ace::Assign::Authority::CandidateTransfer::MAX_BYTES)
|
|
32
|
+
args = {prepared_bundle: bundle, assignment_id: options[:assignment], definition_bytes: definition,
|
|
28
33
|
scope: options[:step], base_head: options[:base_head]}
|
|
29
34
|
args[:mutation_id] = options[:mutation] if options[:mutation]
|
|
30
35
|
driver.launch(**args)
|
|
31
36
|
end
|
|
32
|
-
|
|
37
|
+
if !options[:dry_run] && result["phase"] == "issued"
|
|
38
|
+
retaining_control = true
|
|
39
|
+
announced = false
|
|
40
|
+
driver.serve_control!(state: result) do |ready|
|
|
41
|
+
raise ArgumentError, "original launch readiness was already announced" if announced
|
|
42
|
+
line = readiness_line(ready, result, options)
|
|
43
|
+
$stdout.write(line)
|
|
44
|
+
$stdout.flush
|
|
45
|
+
announced = true
|
|
46
|
+
end
|
|
47
|
+
raise ArgumentError, "original control ended before authenticated readiness" unless announced
|
|
48
|
+
else
|
|
49
|
+
puts JSON.generate(result)
|
|
50
|
+
end
|
|
33
51
|
rescue ArgumentError, SystemCallError, Ace::Runtime::Error, Ace::Assign::Error => error
|
|
34
52
|
raise Ace::Support::Cli::Error, error.message
|
|
53
|
+
ensure
|
|
54
|
+
# Local foreground-loop cancellation is not a stopped/settled proof.
|
|
55
|
+
driver.request_control_cancel if retaining_control
|
|
35
56
|
end
|
|
36
57
|
private
|
|
58
|
+
def retained_input(path, limit)
|
|
59
|
+
File.open(path, File::RDONLY | File::NOFOLLOW | File::NONBLOCK) do |file|
|
|
60
|
+
before = file.stat
|
|
61
|
+
unless before.file? && before.size.between?(1, limit)
|
|
62
|
+
raise ArgumentError, "retained launch input is not a bounded regular file"
|
|
63
|
+
end
|
|
64
|
+
bytes = (file.read(limit + 1) || "").b
|
|
65
|
+
after = file.stat
|
|
66
|
+
identity = ->(stat) { [stat.dev, stat.ino, stat.size, stat.mtime, stat.ctime] }
|
|
67
|
+
unless bytes.bytesize == before.size && bytes.bytesize <= limit && identity.call(before) == identity.call(after)
|
|
68
|
+
raise ArgumentError, "retained launch input changed while reading"
|
|
69
|
+
end
|
|
70
|
+
bytes
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def readiness_line(ready, result, options)
|
|
75
|
+
unless ready.is_a?(Hash) && ready.keys.all? { |key| key.is_a?(String) } && ready.keys.sort == READY_FIELDS &&
|
|
76
|
+
ready["version"].is_a?(Integer) && ready["version"] == 1 && ready["type"] == "launch_ready" &&
|
|
77
|
+
ready.values_at("mapping_id", "assignment_id", "attempt_id") ==
|
|
78
|
+
[options.fetch(:mapping), options.fetch(:assignment), result.fetch("attempt_id")] &&
|
|
79
|
+
ready["generation"].is_a?(Integer) && ready["generation"].positive? &&
|
|
80
|
+
ready["journal_commit"].is_a?(String) && ready["journal_commit"].match?(/\A(?:[0-9a-f]{40}|[0-9a-f]{64})\z/) &&
|
|
81
|
+
ready["original_binding_digest"].is_a?(String) && ready["original_binding_digest"].match?(/\A[0-9a-f]{64}\z/)
|
|
82
|
+
raise ArgumentError, "original launch readiness is malformed or mismatched"
|
|
83
|
+
end
|
|
84
|
+
line = JSON.generate(ready) + "\n"
|
|
85
|
+
raise ArgumentError, "original launch readiness is oversized" if line.bytesize > READY_LIMIT
|
|
86
|
+
line
|
|
87
|
+
end
|
|
88
|
+
|
|
37
89
|
def build_driver(mapping)
|
|
38
90
|
Ace::Assign::Authority::LaunchDriver.new(mapping_id: mapping)
|
|
39
91
|
end
|
|
@@ -12,7 +12,8 @@ module Ace
|
|
|
12
12
|
option :authority, required: true, desc: "Installed authority ID"
|
|
13
13
|
def call(**options)
|
|
14
14
|
deployment = Ace::Assign::Authority::Deployment.load
|
|
15
|
-
|
|
15
|
+
history = Ace::Assign::Authority::DeploymentHistory.load
|
|
16
|
+
launch = Ace::Assign::Authority::LaunchLifecycle.new(deployment: deployment, deployment_history: history)
|
|
16
17
|
router = Ace::Assign::Authority::Router.new(launch: launch)
|
|
17
18
|
server = Ace::Assign::Authority::Server.new(authority_id: options.fetch(:authority), deployment: deployment, lifecycle: router)
|
|
18
19
|
signals = %w[INT TERM].to_h { |signal| [signal, Signal.trap(signal) { server.request_stop }] }
|
|
@@ -13,7 +13,7 @@ module Ace
|
|
|
13
13
|
option :attempt, required: true, desc: "Attempt ID"
|
|
14
14
|
def call(**options)
|
|
15
15
|
reply = Ace::Assign::Authority::Client.new(mapping_id: options.fetch(:mapping)).call("attempt_status",
|
|
16
|
-
{"assignment_id" => options.fetch(:assignment), "attempt_id" => options.fetch(:attempt)})
|
|
16
|
+
{"assignment_id" => options.fetch(:assignment), "attempt_id" => options.fetch(:attempt), "result_candidate_generation" => nil})
|
|
17
17
|
puts JSON.generate(reply.data)
|
|
18
18
|
rescue ArgumentError, Ace::Runtime::Error, Ace::Assign::Error => error
|
|
19
19
|
raise Ace::Support::Cli::Error, error.message
|
|
@@ -0,0 +1,31 @@
|
|
|
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 TaskContext < Ace::Support::Cli::Command
|
|
10
|
+
desc "Return the captured original task context through the fixed selected entry"
|
|
11
|
+
option :mapping, required: true, desc: "Installed original mapping ID"
|
|
12
|
+
option :assignment, required: true, desc: "Original assignment ID@SCOPE"
|
|
13
|
+
option :attempt, required: true, desc: "Original protected attempt ID"
|
|
14
|
+
option :task, required: true, desc: "Captured canonical task ID"
|
|
15
|
+
|
|
16
|
+
def call(**options)
|
|
17
|
+
unless options.keys.sort == %i[assignment attempt mapping task]
|
|
18
|
+
raise ArgumentError, "fixed task-context accepts only its four selectors"
|
|
19
|
+
end
|
|
20
|
+
consumer = Ace::Assign::Authority::PreparedTaskContext.new(context: @protected_assignment_context)
|
|
21
|
+
$stdout.write(consumer.call(**options))
|
|
22
|
+
0
|
|
23
|
+
rescue ArgumentError, Ace::Runtime::Error, Ace::Assign::Error => error
|
|
24
|
+
raise Ace::Support::Cli::Error, error.message
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
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 TaskContextPrincipal < Ace::Support::Cli::Command
|
|
10
|
+
desc "Classify this actual caller through the installed and retained protected owners"
|
|
11
|
+
|
|
12
|
+
def call(**options)
|
|
13
|
+
raise ArgumentError, "fixed task-context-principal accepts no options" unless options.empty?
|
|
14
|
+
consumer = Ace::Assign::Authority::PreparedTaskContext.new(context: @protected_assignment_context)
|
|
15
|
+
$stdout.write(consumer.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
|
|
@@ -0,0 +1,30 @@
|
|
|
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 TaskContextSelection < Ace::Support::Cli::Command
|
|
10
|
+
desc "Return the authenticated original task-context entry without captured text"
|
|
11
|
+
option :mapping, required: true, desc: "Installed original mapping ID"
|
|
12
|
+
option :assignment, required: true, desc: "Original assignment ID@SCOPE"
|
|
13
|
+
option :attempt, required: true, desc: "Original protected attempt ID"
|
|
14
|
+
|
|
15
|
+
def call(**options)
|
|
16
|
+
unless options.keys.sort == %i[assignment attempt mapping]
|
|
17
|
+
raise ArgumentError, "fixed task-context-selection accepts only its three selectors"
|
|
18
|
+
end
|
|
19
|
+
consumer = Ace::Assign::Authority::PreparedTaskContext.new(context: @protected_assignment_context)
|
|
20
|
+
$stdout.write(consumer.selection(**options))
|
|
21
|
+
0
|
|
22
|
+
rescue ArgumentError, Ace::Runtime::Error, Ace::Assign::Error => error
|
|
23
|
+
raise Ace::Support::Cli::Error, error.message
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require_relative "../../../authority/prepared_worker"
|
|
3
|
+
|
|
4
|
+
module Ace
|
|
5
|
+
module Assign
|
|
6
|
+
module CLI
|
|
7
|
+
module Commands
|
|
8
|
+
module Authority
|
|
9
|
+
class Worker < Ace::Support::Cli::Command
|
|
10
|
+
desc "Consume the original authenticated prepared subtree as its fixed worker"
|
|
11
|
+
|
|
12
|
+
def call(**options)
|
|
13
|
+
raise ArgumentError, "fixed worker accepts no options" unless options.empty?
|
|
14
|
+
Ace::Assign::Authority::PreparedWorker.new.run
|
|
15
|
+
rescue ArgumentError, Ace::Runtime::Error, Ace::Assign::Error => error
|
|
16
|
+
raise Ace::Support::Cli::Error, error.message
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require_relative "protected_submission"
|
|
3
|
+
|
|
4
|
+
module Ace
|
|
5
|
+
module Assign
|
|
6
|
+
module CLI
|
|
7
|
+
module Commands
|
|
8
|
+
class CampaignExportResult < Ace::Support::Cli::Command
|
|
9
|
+
include Ace::Support::Cli::Base
|
|
10
|
+
include ProtectedSubmission
|
|
11
|
+
desc "Download a verified accepted campaign result through its original authority"
|
|
12
|
+
option :mapping, desc: "Original installed parent mapping"
|
|
13
|
+
option :assignment, desc: "Original parent assignment"
|
|
14
|
+
option :attempt, desc: "Original parent attempt"
|
|
15
|
+
option :head, desc: "Exact parent candidate head"
|
|
16
|
+
option :candidate_generation, desc: "Exact parent candidate counter"
|
|
17
|
+
option :output, desc: "Result byte file (created once, exact retries allowed)"
|
|
18
|
+
|
|
19
|
+
def call(**options)
|
|
20
|
+
usage = "campaign-export-result with original parent selectors and --output FILE"
|
|
21
|
+
context = protected_context(options)
|
|
22
|
+
raise AttemptErrors::EvidenceUnavailable, "Campaign export requires installed authority" unless context
|
|
23
|
+
params = {"assignment_id" => require_option(options, :assignment, usage),
|
|
24
|
+
"attempt_id" => require_option(options, :attempt, usage), "head" => require_option(options, :head, usage),
|
|
25
|
+
"candidate_generation" => integer_option(options, :candidate_generation, usage, positive: true)}
|
|
26
|
+
raise Ace::Support::Cli::Error, "Campaign head must be an exact SHA-1" unless params["head"].match?(/\A[0-9a-f]{40}\z/)
|
|
27
|
+
context.verify_attempt_hints!(assignment_id: params.fetch("assignment_id"), attempt_id: params.fetch("attempt_id"))
|
|
28
|
+
path = require_option(options, :output, usage)
|
|
29
|
+
reply = context.client(options: options).call("campaign_export_result", params, download: true, purpose: :artifacts, timeout: 30)
|
|
30
|
+
bytes = reply.parts&.first
|
|
31
|
+
unless reply.parts&.size == 1 && bytes.is_a?(String) && bytes.bytesize <= 64 * 1024 &&
|
|
32
|
+
reply.data.values_at("head", "candidate_generation", "bytes", "sha256") ==
|
|
33
|
+
[params.fetch("head"), params.fetch("candidate_generation"), bytes.bytesize, Digest::SHA256.hexdigest(bytes)]
|
|
34
|
+
raise AttemptErrors::EvidenceUnavailable, "Campaign result download differs"
|
|
35
|
+
end
|
|
36
|
+
begin
|
|
37
|
+
File.open(path, File::WRONLY | File::CREAT | File::EXCL | File::NOFOLLOW, 0o600) do |file|
|
|
38
|
+
file.write(bytes)
|
|
39
|
+
file.flush
|
|
40
|
+
file.fsync
|
|
41
|
+
end
|
|
42
|
+
rescue Errno::EEXIST
|
|
43
|
+
raise Ace::Support::Cli::Error, "Existing campaign result file differs" unless input_bytes(path, limit: 64 * 1024) == bytes
|
|
44
|
+
end
|
|
45
|
+
emit_json(reply.data.except("transfer").merge("path" => path))
|
|
46
|
+
rescue SecurityError, Ace::Runtime::RuntimeUnavailableError
|
|
47
|
+
protected_transport_unavailable!
|
|
48
|
+
rescue SystemCallError, IOError
|
|
49
|
+
raise Ace::Support::Cli::Error, "Campaign result file or transport is unavailable"
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
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
|
|
@@ -8,6 +8,7 @@ module Ace
|
|
|
8
8
|
# Create a new workflow assignment from YAML or preset-backed task refs
|
|
9
9
|
class Create < Ace::Support::Cli::Command
|
|
10
10
|
include Ace::Support::Cli::Base
|
|
11
|
+
include AssignmentTarget
|
|
11
12
|
|
|
12
13
|
desc "Create a new workflow assignment"
|
|
13
14
|
|
|
@@ -20,6 +21,7 @@ module Ace
|
|
|
20
21
|
option :debug, aliases: ["-d"], type: :boolean, default: false, desc: "Show debug output"
|
|
21
22
|
|
|
22
23
|
def call(yaml: nil, task: nil, preset: nil, **options)
|
|
24
|
+
refuse_protected_graph_mutation!(options)
|
|
23
25
|
validate_modes!(yaml, task, preset)
|
|
24
26
|
raise Ace::Support::Cli::Error, "--delivery-parameters requires --task" if yaml && options[:delivery_parameters]
|
|
25
27
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
require "json"
|
|
3
|
+
require_relative "../../authority/protected_assignment_context"
|
|
4
|
+
require_relative "../../organisms/protected_delivery_coordinator"
|
|
3
5
|
|
|
4
6
|
module Ace
|
|
5
7
|
module Assign
|
|
@@ -15,17 +17,53 @@ module Ace
|
|
|
15
17
|
option :body_file, desc: "PR description file"
|
|
16
18
|
option :tests, desc: "JSON reference to coordinator-accepted test attempt and receipt digest"
|
|
17
19
|
option :review, desc: "JSON reference to coordinator-accepted independent review attempt and receipt digest"
|
|
18
|
-
option :service_request, desc: "
|
|
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
|
|
19
32
|
|
|
20
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
|
|
21
57
|
result = build_delivery.perform(assignment_id: options[:assignment], attempt_id: options[:attempt],
|
|
22
58
|
operation: options[:operation], parameters: read_json(options[:parameters]),
|
|
23
59
|
title: options[:title], body: options[:body_file] && File.read(options[:body_file]),
|
|
24
60
|
tests: read_json(options[:tests]), review: read_json(options[:review]),
|
|
25
61
|
service_request_id: options[:service_request])
|
|
26
62
|
puts JSON.generate(json_value(result))
|
|
27
|
-
rescue ArgumentError, Ace::Git::Error, Ace::Assign::Error,
|
|
28
|
-
|
|
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
|
|
29
67
|
end
|
|
30
68
|
|
|
31
69
|
private
|
|
@@ -36,7 +74,19 @@ module Ace
|
|
|
36
74
|
|
|
37
75
|
def read_json(path)
|
|
38
76
|
return nil unless path
|
|
39
|
-
|
|
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)
|
|
40
90
|
raise ArgumentError, "Delivery input must be a JSON object" unless data.is_a?(Hash)
|
|
41
91
|
data
|
|
42
92
|
rescue JSON::ParserError
|
|
@@ -12,6 +12,8 @@ module Ace
|
|
|
12
12
|
desc "Mark current step as failed"
|
|
13
13
|
|
|
14
14
|
option :message, aliases: ["-m"], required: true, desc: "Error message"
|
|
15
|
+
option :mapping, desc: "Installed protected launch mapping ID"
|
|
16
|
+
option :attempt, desc: "Original protected attempt ID"
|
|
15
17
|
option :assignment, desc: "Target specific assignment ID"
|
|
16
18
|
option :quiet, aliases: ["-q"], type: :boolean, default: false, desc: "Suppress non-essential output"
|
|
17
19
|
option :debug, aliases: ["-d"], type: :boolean, default: false, desc: "Show debug output"
|
|
@@ -28,6 +30,10 @@ module Ace
|
|
|
28
30
|
puts "Step #{failed.number} (#{failed.name}) marked as failed"
|
|
29
31
|
puts "Updated: #{File.basename(failed.file_path)}"
|
|
30
32
|
puts "Error: #{message}"
|
|
33
|
+
if target.prepared_input
|
|
34
|
+
puts "Further work requires a reviewed new prepared version and attempt."
|
|
35
|
+
return
|
|
36
|
+
end
|
|
31
37
|
puts
|
|
32
38
|
puts "Options:"
|
|
33
39
|
puts "- ace-assign add \"fix-step\" to add a fix step"
|