ace-assign 0.63.1 → 0.66.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.
- checksums.yaml +4 -4
- data/.ace-defaults/assign/catalog/steps/auto-merge.step.yml +11 -0
- data/.ace-defaults/assign/catalog/steps/create-pr.step.yml +2 -2
- data/.ace-defaults/assign/catalog/steps/mark-pr-ready.step.yml +1 -1
- data/.ace-defaults/assign/catalog/steps/release-minor.step.yml +4 -2
- data/.ace-defaults/assign/catalog/steps/release.step.yml +4 -2
- data/.ace-defaults/assign/catalog/steps/update-pr-desc.step.yml +1 -1
- data/.ace-defaults/assign/presets/fix-bug.yml +15 -3
- data/.ace-defaults/assign/presets/work-on-task-ace-development.yml +16 -4
- data/.ace-defaults/assign/presets/work-on-task-auto-merge.yml +15 -3
- data/.ace-defaults/assign/presets/work-on-task.yml +16 -4
- data/CHANGELOG.md +62 -0
- data/docs/demo/canonical-skill-source.tape.yml +4 -4
- data/docs/protected-authority.md +282 -0
- data/docs/usage.md +205 -3
- data/exe/ace-scope-ready +55 -0
- data/handbook/workflow-instructions/assign/drive.wf.md +28 -3
- data/handbook/workflow-instructions/assign/prepare.wf.md +2 -2
- data/lib/ace/assign/atoms/attempt_state_machine.rb +8 -0
- data/lib/ace/assign/atoms/delivery_parameters.rb +19 -0
- 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 +327 -0
- data/lib/ace/assign/authority/cleanup_result_protection.rb +53 -0
- data/lib/ace/assign/authority/client.rb +257 -0
- data/lib/ace/assign/authority/deployment.rb +627 -0
- data/lib/ace/assign/authority/deployment_history.rb +146 -0
- data/lib/ace/assign/authority/endcap.rb +455 -0
- 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 +37 -0
- 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 +398 -0
- 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 +598 -0
- data/lib/ace/assign/authority/evidence_transfer.rb +67 -0
- data/lib/ace/assign/authority/execution_scope_observation.rb +903 -0
- 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 +393 -0
- data/lib/ace/assign/authority/launch_input_inhibition.rb +132 -0
- data/lib/ace/assign/authority/launch_lifecycle.rb +997 -0
- 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 +81 -0
- data/lib/ace/assign/authority/launch_scope_admission.rb +215 -0
- data/lib/ace/assign/authority/launch_scope_close.rb +146 -0
- data/lib/ace/assign/authority/launch_scope_parent.rb +44 -0
- data/lib/ace/assign/authority/launch_scope_release.rb +859 -0
- 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 +59 -0
- 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/private_directory.rb +33 -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/receipt_transfer.rb +48 -0
- data/lib/ace/assign/authority/router.rb +76 -0
- data/lib/ace/assign/authority/server.rb +300 -0
- 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 +286 -0
- 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 +187 -0
- 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 +97 -0
- data/lib/ace/assign/cli/commands/authority/serve.rb +31 -0
- data/lib/ace/assign/cli/commands/authority/status.rb +26 -0
- 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/terminate.rb +29 -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 +11 -1
- data/lib/ace/assign/cli/commands/delivery.rb +107 -0
- 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 +38 -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 +56 -4
- 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 +4 -2
- data/lib/ace/assign/molecules/assignment_manager.rb +12 -2
- data/lib/ace/assign/molecules/attempt_reconciler.rb +51 -12
- data/lib/ace/assign/molecules/canonical_attempt_state.rb +151 -0
- data/lib/ace/assign/molecules/canonical_evidence.rb +153 -0
- data/lib/ace/assign/molecules/canonical_read_snapshot.rb +374 -0
- data/lib/ace/assign/molecules/evidence_journal.rb +922 -91
- data/lib/ace/assign/molecules/execution_identity_resolver.rb +49 -9
- data/lib/ace/assign/molecules/execution_scope_lineage.rb +345 -0
- 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 +271 -0
- data/lib/ace/assign/molecules/journal_prompt_mutation.rb +241 -0
- data/lib/ace/assign/molecules/lifecycle_exclusion.rb +182 -26
- data/lib/ace/assign/molecules/original_launch_binding.rb +37 -0
- data/lib/ace/assign/molecules/proposal_journal.rb +151 -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 +68 -32
- 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 +10 -4
- data/lib/ace/assign/organisms/attempt_coordinator.rb +302 -13
- data/lib/ace/assign/organisms/delivery_coordinator.rb +342 -0
- 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 +11 -4
- data/lib/ace/assign/version.rb +1 -1
- data/lib/ace/assign.rb +4 -102
- metadata +186 -8
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require "json"
|
|
3
|
+
require_relative "../../../authority/launch_driver"
|
|
4
|
+
module Ace
|
|
5
|
+
module Assign
|
|
6
|
+
module CLI
|
|
7
|
+
module Commands
|
|
8
|
+
module Authority
|
|
9
|
+
class Terminate < Ace::Support::Cli::Command
|
|
10
|
+
desc "Close the original gated native child and request proven abort"
|
|
11
|
+
option :mapping, required: true, desc: "Installed launch mapping ID"
|
|
12
|
+
option :assignment, required: true, desc: "Assignment ID"
|
|
13
|
+
option :attempt, required: true, desc: "Attempt ID"
|
|
14
|
+
def call(**options)
|
|
15
|
+
client = Ace::Assign::Authority::Client.new(mapping_id: options.fetch(:mapping))
|
|
16
|
+
state = client.call("inspect_launch", {"assignment_id" => options.fetch(:assignment), "attempt_id" => options.fetch(:attempt)}).data
|
|
17
|
+
binding = state.fetch("process_binding") { raise ArgumentError, "no positively recorded original child; supervisor inspection required" }
|
|
18
|
+
result = Ace::Assign::Authority::LaunchDriver.new(mapping_id: options.fetch(:mapping)).terminate(
|
|
19
|
+
state: state, binding: binding, evidence: "protected exact-child termination requested")
|
|
20
|
+
puts JSON.generate(result)
|
|
21
|
+
rescue ArgumentError, Ace::Runtime::Error, Ace::Assign::Error => error
|
|
22
|
+
raise Ace::Support::Cli::Error, error.message
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
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
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
+
require "json"
|
|
2
3
|
|
|
3
4
|
module Ace
|
|
4
5
|
module Assign
|
|
@@ -7,9 +8,11 @@ module Ace
|
|
|
7
8
|
# Create a new workflow assignment from YAML or preset-backed task refs
|
|
8
9
|
class Create < Ace::Support::Cli::Command
|
|
9
10
|
include Ace::Support::Cli::Base
|
|
11
|
+
include AssignmentTarget
|
|
10
12
|
|
|
11
13
|
desc "Create a new workflow assignment"
|
|
12
14
|
|
|
15
|
+
option :delivery_parameters, desc: "JSON file with forge selection and explicit PR provenance (task mode)"
|
|
13
16
|
option :yaml, desc: "Path to job.yaml config file"
|
|
14
17
|
option :task, aliases: ["-t"], type: :array,
|
|
15
18
|
desc: "Task reference(s), repeatable and comma-separated"
|
|
@@ -18,14 +21,17 @@ module Ace
|
|
|
18
21
|
option :debug, aliases: ["-d"], type: :boolean, default: false, desc: "Show debug output"
|
|
19
22
|
|
|
20
23
|
def call(yaml: nil, task: nil, preset: nil, **options)
|
|
24
|
+
refuse_protected_graph_mutation!(options)
|
|
21
25
|
validate_modes!(yaml, task, preset)
|
|
26
|
+
raise Ace::Support::Cli::Error, "--delivery-parameters requires --task" if yaml && options[:delivery_parameters]
|
|
22
27
|
|
|
23
28
|
result = if yaml
|
|
24
29
|
Organisms::AssignmentExecutor.new.start(yaml)
|
|
25
30
|
else
|
|
26
31
|
Organisms::TaskAssignmentCreator.new.call(
|
|
27
32
|
task_refs: task,
|
|
28
|
-
preset_name: preset || Organisms::TaskAssignmentCreator::DEFAULT_PRESET
|
|
33
|
+
preset_name: preset || Organisms::TaskAssignmentCreator::DEFAULT_PRESET,
|
|
34
|
+
delivery_parameters: options[:delivery_parameters] && JSON.parse(File.read(options[:delivery_parameters]))
|
|
29
35
|
)
|
|
30
36
|
end
|
|
31
37
|
|
|
@@ -34,6 +40,10 @@ module Ace
|
|
|
34
40
|
print_assignment_header(result[:assignment])
|
|
35
41
|
print_step_instructions(result[:current] || result[:state]&.next_workable)
|
|
36
42
|
end
|
|
43
|
+
rescue JSON::ParserError, Errno::ENOENT
|
|
44
|
+
raise Ace::Support::Cli::Error, "Delivery parameters file unavailable or invalid JSON"
|
|
45
|
+
rescue ArgumentError => e
|
|
46
|
+
raise Ace::Support::Cli::Error, e.message
|
|
37
47
|
end
|
|
38
48
|
|
|
39
49
|
private
|
|
@@ -0,0 +1,107 @@
|
|
|
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
|
|
@@ -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"
|
|
@@ -13,6 +13,8 @@ module Ace
|
|
|
13
13
|
|
|
14
14
|
argument :step, required: false, desc: "Step number to finish (active assignment only)"
|
|
15
15
|
option :message, aliases: ["-m"], desc: "Report content: string, file path, or pipe stdin"
|
|
16
|
+
option :mapping, desc: "Installed protected launch mapping ID"
|
|
17
|
+
option :attempt, desc: "Original protected attempt ID"
|
|
16
18
|
option :assignment, desc: "Target specific assignment ID"
|
|
17
19
|
option :quiet, aliases: ["-q"], type: :boolean, default: false, desc: "Suppress non-essential output"
|
|
18
20
|
option :debug, aliases: ["-d"], type: :boolean, default: false, desc: "Show debug output"
|
|
@@ -24,7 +26,7 @@ module Ace
|
|
|
24
26
|
|
|
25
27
|
target = resolve_assignment_target(options)
|
|
26
28
|
executor = build_executor_for_target(target)
|
|
27
|
-
report_content = resolve_report_content(options)
|
|
29
|
+
report_content = resolve_report_content(options, protected: !target.prepared_input.nil?)
|
|
28
30
|
result = executor.finish_step(
|
|
29
31
|
report_content: report_content,
|
|
30
32
|
step_number: step,
|
|
@@ -72,8 +74,27 @@ module Ace
|
|
|
72
74
|
|
|
73
75
|
private
|
|
74
76
|
|
|
75
|
-
def resolve_report_content(options)
|
|
77
|
+
def resolve_report_content(options, protected: false)
|
|
76
78
|
message = options[:message]&.strip
|
|
79
|
+
if protected
|
|
80
|
+
limit = Ace::Assign::Authority::PreparedWork::MAX_TEXT
|
|
81
|
+
if message && !message.empty? && File.exist?(message)
|
|
82
|
+
return File.open(message, File::RDONLY | File::NOFOLLOW | File::NONBLOCK) do |file|
|
|
83
|
+
before = file.stat
|
|
84
|
+
raise Error, "Report input must be a bounded regular file" unless before.file? && before.size.between?(1, limit)
|
|
85
|
+
bytes = file.read(limit + 1)
|
|
86
|
+
after = file.stat
|
|
87
|
+
unless bytes.bytesize == before.size && [before.dev, before.ino, before.size, before.mtime, before.ctime] ==
|
|
88
|
+
[after.dev, after.ino, after.size, after.mtime, after.ctime]
|
|
89
|
+
raise Error, "Report input changed"
|
|
90
|
+
end
|
|
91
|
+
bytes
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
bytes = message && !message.empty? ? message : $stdin.read(limit + 1)
|
|
95
|
+
raise Error, "Report input exceeds captured text bound" unless bytes && bytes.bytesize.between?(1, limit)
|
|
96
|
+
return bytes
|
|
97
|
+
end
|
|
77
98
|
return File.read(message) if message && !message.empty? && File.exist?(message)
|
|
78
99
|
return message if message && !message.empty?
|
|
79
100
|
|
|
@@ -7,6 +7,7 @@ module Ace
|
|
|
7
7
|
# Internal command used by tmux-backed fork panes to launch the provider session once.
|
|
8
8
|
class ForkSession < Ace::Support::Cli::Command
|
|
9
9
|
include Ace::Support::Cli::Base
|
|
10
|
+
include AssignmentTarget
|
|
10
11
|
|
|
11
12
|
desc "Run one provider-backed fork session for a subtree"
|
|
12
13
|
|
|
@@ -21,10 +22,11 @@ module Ace
|
|
|
21
22
|
|
|
22
23
|
def initialize(launcher: nil)
|
|
23
24
|
super()
|
|
24
|
-
@launcher = launcher
|
|
25
|
+
@launcher = launcher
|
|
25
26
|
end
|
|
26
27
|
|
|
27
28
|
def call(**options)
|
|
29
|
+
refuse_protected_graph_mutation!(options)
|
|
28
30
|
launcher.launch_provider_session(
|
|
29
31
|
assignment_id: options[:assignment],
|
|
30
32
|
fork_root: options[:root],
|
|
@@ -44,7 +46,9 @@ module Ace
|
|
|
44
46
|
|
|
45
47
|
private
|
|
46
48
|
|
|
47
|
-
|
|
49
|
+
def launcher
|
|
50
|
+
@launcher ||= Molecules::ForkSessionLauncher.new
|
|
51
|
+
end
|
|
48
52
|
end
|
|
49
53
|
end
|
|
50
54
|
end
|
|
@@ -0,0 +1,34 @@
|
|
|
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
|
|
@@ -0,0 +1,61 @@
|
|
|
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
|
|
@@ -0,0 +1,38 @@
|
|
|
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
|
|
@@ -13,6 +13,7 @@ module Ace
|
|
|
13
13
|
# ace-assign select --clear
|
|
14
14
|
class Select < Ace::Support::Cli::Command
|
|
15
15
|
include Ace::Support::Cli::Base
|
|
16
|
+
include AssignmentTarget
|
|
16
17
|
|
|
17
18
|
desc "Select an assignment as the current active assignment"
|
|
18
19
|
|
|
@@ -22,6 +23,7 @@ module Ace
|
|
|
22
23
|
option :debug, aliases: ["-d"], type: :boolean, default: false, desc: "Show debug output"
|
|
23
24
|
|
|
24
25
|
def call(id: nil, **options)
|
|
26
|
+
refuse_protected_graph_mutation!(options)
|
|
25
27
|
manager = Molecules::AssignmentManager.new
|
|
26
28
|
|
|
27
29
|
if options[:clear]
|
|
@@ -12,6 +12,8 @@ module Ace
|
|
|
12
12
|
desc "Start next workable pending step"
|
|
13
13
|
|
|
14
14
|
argument :step, required: false, desc: "Step number to start (active assignment only)"
|
|
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"
|
|
@@ -41,6 +41,8 @@ module Ace
|
|
|
41
41
|
option :format, desc: "Output format (table, json)", default: "table"
|
|
42
42
|
option :quiet, aliases: ["-q"], type: :boolean, default: false, desc: "Suppress non-essential output"
|
|
43
43
|
option :debug, aliases: ["-d"], type: :boolean, default: false, desc: "Show debug output"
|
|
44
|
+
option :mapping, desc: "Installed protected launch mapping ID"
|
|
45
|
+
option :attempt, desc: "Original protected attempt ID"
|
|
44
46
|
option :assignment, desc: "Show status for specific assignment ID"
|
|
45
47
|
option :all, aliases: ["-a"], type: :boolean, default: false, desc: "Include completed assignments in other assignments section"
|
|
46
48
|
|
|
@@ -85,7 +87,7 @@ module Ace
|
|
|
85
87
|
def status_to_h(assignment, state, active_steps, next_step, target:, scope_root:)
|
|
86
88
|
# Extract auto_merge config if present in assignment settings or preset
|
|
87
89
|
auto_merge = false
|
|
88
|
-
preset_name = Ace::Assign::Molecules::PresetInferrer.infer_from_assignment(assignment)
|
|
90
|
+
preset_name = Ace::Assign::Molecules::PresetInferrer.infer_from_assignment(assignment) unless target.prepared_input
|
|
89
91
|
auto_merge = true if preset_name && preset_name.start_with?("work-on-task-auto-merge")
|
|
90
92
|
|
|
91
93
|
evidence = Ace::Assign::Molecules::EvidenceCalculator.calculate(
|
|
@@ -135,7 +137,7 @@ module Ace
|
|
|
135
137
|
lines = []
|
|
136
138
|
lines.concat(compact_summary_lines(view.assignment, view.scoped_state, view.active_steps, view.next_step))
|
|
137
139
|
|
|
138
|
-
if (attempt_line = compact_attempt_line(view.assignment.id))
|
|
140
|
+
if !target.prepared_input && (attempt_line = compact_attempt_line(view.assignment.id))
|
|
139
141
|
lines.insert(1, attempt_line)
|
|
140
142
|
end
|
|
141
143
|
|
|
@@ -12,6 +12,8 @@ module Ace
|
|
|
12
12
|
desc "Show instructions for the active, next, or explicit step"
|
|
13
13
|
|
|
14
14
|
argument :step, required: false, desc: "Exact step number to inspect"
|
|
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"
|
|
@@ -24,7 +26,7 @@ module Ace
|
|
|
24
26
|
return if options[:quiet]
|
|
25
27
|
|
|
26
28
|
if inspected
|
|
27
|
-
puts inspected.instructions
|
|
29
|
+
puts(target.prepared_input ? target.prepared_input.render(inspected.instructions) : inspected.instructions)
|
|
28
30
|
else
|
|
29
31
|
puts no_work_message(view)
|
|
30
32
|
end
|