ace-assign 0.64.0 → 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/CHANGELOG.md +19 -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 +88 -9
- data/lib/ace/assign/cli/commands/inbox_reconcile.rb +0 -36
|
@@ -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
|
|
@@ -8,11 +8,23 @@ module Ace
|
|
|
8
8
|
module Commands
|
|
9
9
|
class Resume < Ace::Support::Cli::Command
|
|
10
10
|
include Ace::Support::Cli::Base
|
|
11
|
+
include AssignmentTarget
|
|
11
12
|
desc "Recover accepted assignment state without launching another writer"
|
|
12
13
|
option :assignment, desc: "Assignment ID"
|
|
14
|
+
option :mapping, desc: "Installed protected launch mapping ID"
|
|
15
|
+
option :attempt, desc: "Original protected attempt ID"
|
|
13
16
|
option :dry_run, type: :boolean, default: false, desc: "Inspect recovery without writing observations"
|
|
14
17
|
|
|
15
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
|
|
16
28
|
id = options[:assignment].to_s.strip
|
|
17
29
|
raise Ace::Support::Cli::Error, "--assignment ID is required" if id.empty?
|
|
18
30
|
|
|
@@ -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
|
|
@@ -0,0 +1,34 @@
|
|
|
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: 64 * 1024 * 1024)
|
|
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
|
|
@@ -0,0 +1,61 @@
|
|
|
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
|
data/lib/ace/assign/cli.rb
CHANGED
|
@@ -55,11 +55,15 @@ require_relative "organisms/attempt_coordinator"
|
|
|
55
55
|
|
|
56
56
|
# Commands
|
|
57
57
|
require_relative "cli/commands/delivery"
|
|
58
|
-
require_relative "cli/commands/create"
|
|
59
58
|
require_relative "cli/commands/assignment_target"
|
|
59
|
+
require_relative "cli/commands/create"
|
|
60
60
|
require_relative "cli/commands/status"
|
|
61
61
|
require_relative "cli/commands/resume"
|
|
62
|
-
require_relative "cli/commands/
|
|
62
|
+
require_relative "cli/commands/inbox_bind"
|
|
63
|
+
require_relative "cli/commands/submit_candidate"
|
|
64
|
+
require_relative "cli/commands/submit_result"
|
|
65
|
+
require_relative "cli/commands/campaign_record_round"
|
|
66
|
+
require_relative "cli/commands/campaign_export_result"
|
|
63
67
|
require_relative "cli/commands/step"
|
|
64
68
|
require_relative "cli/commands/start"
|
|
65
69
|
require_relative "cli/commands/finish"
|
|
@@ -74,6 +78,12 @@ require_relative "cli/commands/authority/serve"
|
|
|
74
78
|
require_relative "cli/commands/authority/launch"
|
|
75
79
|
require_relative "cli/commands/authority/status"
|
|
76
80
|
require_relative "cli/commands/authority/terminate"
|
|
81
|
+
require_relative "cli/commands/authority/worker"
|
|
82
|
+
require_relative "cli/commands/authority/task_context"
|
|
83
|
+
require_relative "cli/commands/authority/task_context_principal"
|
|
84
|
+
require_relative "cli/commands/authority/task_context_selection"
|
|
85
|
+
require_relative "cli/commands/authority/inbox_context_principal"
|
|
86
|
+
require_relative "cli/commands/authority/inbox_context_selection"
|
|
77
87
|
require_relative "cli/commands/attempt/base"
|
|
78
88
|
require_relative "cli/commands/attempt/start"
|
|
79
89
|
require_relative "cli/commands/attempt/status"
|
|
@@ -96,7 +106,17 @@ module Ace
|
|
|
96
106
|
["authority launch", "Launch one mapped native gated worker"],
|
|
97
107
|
["authority status", "Inspect protected launch ownership"],
|
|
98
108
|
["authority terminate", "Terminate the original native gated child"],
|
|
109
|
+
["authority worker", "Consume original authenticated prepared work"],
|
|
110
|
+
["authority task-context", "Read captured original task context through the fixed entry"],
|
|
111
|
+
["authority task-context-principal", "Classify the actual caller through retained protected owners"],
|
|
112
|
+
["authority task-context-selection", "Read the authenticated original context entry selection"],
|
|
113
|
+
["authority inbox-context-principal", "Classify the actual caller through every retained protected owner"],
|
|
114
|
+
["authority inbox-context-selection", "Read the fixed installed inbox context selection"],
|
|
99
115
|
["delivery", "Execute or reconcile attempt-bound forge delivery"],
|
|
116
|
+
["submit-candidate", "Submit original protected worker candidate bytes"],
|
|
117
|
+
["submit-result", "Submit original protected worker receipt and artifact bytes"],
|
|
118
|
+
["campaign-export-result", "Download the original parent campaign's verified accepted result"],
|
|
119
|
+
["campaign-record-round", "Consume canonical settled children into the original campaign store"],
|
|
100
120
|
["status", "Show assignment status"],
|
|
101
121
|
["step", "Show step instructions"],
|
|
102
122
|
["start", "Start next workable step"],
|
|
@@ -110,8 +130,9 @@ module Ace
|
|
|
110
130
|
["attempt start", "Start a scoped attempt for an assignment step"],
|
|
111
131
|
["attempt status", "Show attempt status for an assignment"],
|
|
112
132
|
["attempt evidence", "Read accepted current-head check evidence"],
|
|
113
|
-
["attempt finish", "Finish
|
|
114
|
-
["attempt reconcile", "
|
|
133
|
+
["attempt finish", "Finish a protected canonical result or an ordinary local receipt"],
|
|
134
|
+
["attempt reconcile", "Recover an exact protected attempt or reconcile an ordinary attempt"],
|
|
135
|
+
["inbox-bind", "Bind an original protected Inbox registration"],
|
|
115
136
|
].freeze
|
|
116
137
|
|
|
117
138
|
HELP_EXAMPLES = [
|
|
@@ -164,6 +185,12 @@ module Ace
|
|
|
164
185
|
register "authority launch", wrap_command(Commands::Authority::Launch)
|
|
165
186
|
register "authority status", wrap_command(Commands::Authority::Status)
|
|
166
187
|
register "authority terminate", wrap_command(Commands::Authority::Terminate)
|
|
188
|
+
register "authority worker", wrap_command(Commands::Authority::Worker)
|
|
189
|
+
register "authority task-context", wrap_command(Commands::Authority::TaskContext)
|
|
190
|
+
register "authority task-context-principal", wrap_command(Commands::Authority::TaskContextPrincipal)
|
|
191
|
+
register "authority task-context-selection", wrap_command(Commands::Authority::TaskContextSelection)
|
|
192
|
+
register "authority inbox-context-principal", wrap_command(Commands::Authority::InboxContextPrincipal)
|
|
193
|
+
register "authority inbox-context-selection", wrap_command(Commands::Authority::InboxContextSelection)
|
|
167
194
|
register "create", wrap_command(Commands::Create)
|
|
168
195
|
register "delivery", wrap_command(Commands::Delivery)
|
|
169
196
|
register "status", wrap_command(Commands::Status)
|
|
@@ -183,7 +210,11 @@ register "authority terminate", wrap_command(Commands::Authority::Terminate)
|
|
|
183
210
|
register "attempt finish", wrap_command(Commands::Attempt::Finish)
|
|
184
211
|
register "attempt reconcile", wrap_command(Commands::Attempt::Reconcile)
|
|
185
212
|
register "resume", wrap_command(Commands::Resume)
|
|
186
|
-
register "inbox-
|
|
213
|
+
register "inbox-bind", wrap_command(Commands::InboxBind)
|
|
214
|
+
register "submit-candidate", wrap_command(Commands::SubmitCandidate)
|
|
215
|
+
register "submit-result", wrap_command(Commands::SubmitResult)
|
|
216
|
+
register "campaign-export-result", wrap_command(Commands::CampaignExportResult)
|
|
217
|
+
register "campaign-record-round", wrap_command(Commands::CampaignRecordRound)
|
|
187
218
|
|
|
188
219
|
# Register version command
|
|
189
220
|
version_cmd = Ace::Support::Cli::VersionCommand.build(
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require "ace/support/cli/error"
|
|
3
|
+
|
|
4
|
+
module Ace
|
|
5
|
+
module Assign
|
|
6
|
+
# Base error class for all ace-assign exceptions.
|
|
7
|
+
# Inherits from Ace::Support::Cli::Error to support exception-based
|
|
8
|
+
# exit code pattern (per ADR-023).
|
|
9
|
+
#
|
|
10
|
+
# Subclasses should call super with appropriate exit_code.
|
|
11
|
+
class Error < Ace::Support::Cli::Error
|
|
12
|
+
def initialize(message, exit_code: 1)
|
|
13
|
+
super
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Assignment-related errors
|
|
18
|
+
module AssignmentErrors
|
|
19
|
+
class NotFound < Error
|
|
20
|
+
def initialize(message = "Assignment not found")
|
|
21
|
+
super(message, exit_code: 2)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
class NoActive < Error
|
|
26
|
+
def initialize(message = "No active assignment")
|
|
27
|
+
super(message, exit_code: 2)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Config-related errors
|
|
33
|
+
module ConfigErrors
|
|
34
|
+
class NotFound < Error
|
|
35
|
+
def initialize(message = "Configuration not found")
|
|
36
|
+
super(message, exit_code: 3)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Step-related errors
|
|
42
|
+
module StepErrors
|
|
43
|
+
class NotFound < Error
|
|
44
|
+
def initialize(message = "Step not found")
|
|
45
|
+
super(message, exit_code: 4)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
class InvalidState < Error; end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Attempt/evidence-related errors (exit code 5)
|
|
53
|
+
module AttemptErrors
|
|
54
|
+
class InvalidTransition < Error
|
|
55
|
+
def initialize(message = "Illegal attempt state transition")
|
|
56
|
+
super(message, exit_code: 5)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
class InvalidScope < Error
|
|
61
|
+
def initialize(message = "Invalid attempt scope")
|
|
62
|
+
super(message, exit_code: 5)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Duplicate start with a conflicting binding for an owned subtree
|
|
67
|
+
class Conflict < Error
|
|
68
|
+
def initialize(message = "Conflicting active attempt for scope")
|
|
69
|
+
super(message, exit_code: 5)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
class NotFound < Error
|
|
74
|
+
def initialize(message = "Attempt not found")
|
|
75
|
+
super(message, exit_code: 5)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Untrusted or unknown execution identity; authority fails closed
|
|
80
|
+
class UnauthorizedIdentity < Error
|
|
81
|
+
def initialize(message = "Unknown or unauthorized execution identity")
|
|
82
|
+
super(message, exit_code: 5)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Receipt failed verification (binding, head, digest, artifacts, review)
|
|
87
|
+
class ReceiptRejected < Error
|
|
88
|
+
def initialize(message = "Receipt rejected")
|
|
89
|
+
super(message, exit_code: 5)
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Byte framing failed before any business receipt could be admitted.
|
|
94
|
+
class MalformedTransfer < ReceiptRejected; end
|
|
95
|
+
|
|
96
|
+
# Recovery settled a prior effect, but did not perform this request.
|
|
97
|
+
class CurrentEffectRequired < ReceiptRejected
|
|
98
|
+
def initialize(operation:, recovered_operation:)
|
|
99
|
+
super("Recovered #{recovered_operation}; requested #{operation} still needs execution")
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Attempt state forbids the requested effect (terminal, uncertain, blocked)
|
|
104
|
+
class InvalidState < Error
|
|
105
|
+
def initialize(message = "Attempt state forbids the requested operation")
|
|
106
|
+
super(message, exit_code: 5)
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Evidence storage is missing or unwritable; external effects are blocked
|
|
111
|
+
class EvidenceUnavailable < Error
|
|
112
|
+
def initialize(message = "Evidence storage unavailable")
|
|
113
|
+
super(message, exit_code: 5)
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Complete maintenance exclusions are busy or their admission deadline expired.
|
|
118
|
+
class MaintenanceBusy < EvidenceUnavailable
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# One canonical inventory row cannot fit the fixed protocol frame.
|
|
122
|
+
class BoundedResultUnavailable < EvidenceUnavailable
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Exhaustively authenticated service inventory remains unsettled.
|
|
126
|
+
class ServiceSettlementPending < EvidenceUnavailable
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Exhaustively authenticated current Inbox claims remain queued.
|
|
130
|
+
class InboxSettlementPending < EvidenceUnavailable
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Positively admitted original Inbox work remains pending, not corrupt.
|
|
134
|
+
class InboxContextPending < EvidenceUnavailable
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
end
|
|
139
|
+
end
|
|
@@ -20,7 +20,7 @@ module Ace
|
|
|
20
20
|
# but tests would need to be updated to provide all required fields.
|
|
21
21
|
class Assignment
|
|
22
22
|
attr_reader :id, :name, :description, :created_at, :updated_at, :source_config, :cache_dir, :parent,
|
|
23
|
-
:task_id, :project_id
|
|
23
|
+
:task_id, :project_id, :campaign_execution, :review_campaign
|
|
24
24
|
|
|
25
25
|
# @param id [String] Unique assignment ID (6-char compact timestamp)
|
|
26
26
|
# @param name [String] Human-readable assignment name
|
|
@@ -33,7 +33,7 @@ module Ace
|
|
|
33
33
|
# @param task_id [String, nil] Source task ID attached to this assignment (nil = taskless)
|
|
34
34
|
# @param project_id [String, nil] Project ID this assignment executes in
|
|
35
35
|
def initialize(id:, name:, created_at:, source_config:, description: nil, updated_at: nil, cache_dir: nil,
|
|
36
|
-
parent: nil, task_id: nil, project_id: nil)
|
|
36
|
+
parent: nil, task_id: nil, project_id: nil, campaign_execution: nil, review_campaign: nil)
|
|
37
37
|
@id = id.freeze
|
|
38
38
|
@name = name.freeze
|
|
39
39
|
@description = description&.freeze
|
|
@@ -44,6 +44,24 @@ module Ace
|
|
|
44
44
|
@parent = parent&.freeze
|
|
45
45
|
@task_id = task_id&.freeze
|
|
46
46
|
@project_id = project_id&.freeze
|
|
47
|
+
if !campaign_execution.nil? && !review_campaign.nil?
|
|
48
|
+
raise ArgumentError, "assignment cannot own both parent campaign and child execution"
|
|
49
|
+
end
|
|
50
|
+
@campaign_execution = immutable_campaign_execution(campaign_execution) unless campaign_execution.nil?
|
|
51
|
+
@review_campaign = immutable_campaign_execution(review_campaign) unless review_campaign.nil?
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Keep registered definition bytes stable without retaining caller-owned
|
|
55
|
+
# mutable objects. Authority validates the closed linkage and provenance.
|
|
56
|
+
private def immutable_campaign_execution(value)
|
|
57
|
+
case value
|
|
58
|
+
when Hash
|
|
59
|
+
value.to_h { |key, item| [immutable_campaign_execution(key), immutable_campaign_execution(item)] }.freeze
|
|
60
|
+
when Array then value.map { |item| immutable_campaign_execution(item) }.freeze
|
|
61
|
+
when String then value.dup.freeze
|
|
62
|
+
when Integer, NilClass, TrueClass, FalseClass then value
|
|
63
|
+
else raise ArgumentError, "invalid campaign execution value"
|
|
64
|
+
end
|
|
47
65
|
end
|
|
48
66
|
|
|
49
67
|
# @return [Boolean] True when a source task is attached; taskless
|
|
@@ -65,7 +83,9 @@ module Ace
|
|
|
65
83
|
"source_config" => source_config,
|
|
66
84
|
"parent" => parent,
|
|
67
85
|
"task_id" => task_id,
|
|
68
|
-
"project_id" => project_id
|
|
86
|
+
"project_id" => project_id,
|
|
87
|
+
"campaign_execution" => campaign_execution,
|
|
88
|
+
"review_campaign" => review_campaign
|
|
69
89
|
}.compact
|
|
70
90
|
end
|
|
71
91
|
|
|
@@ -84,7 +104,9 @@ module Ace
|
|
|
84
104
|
cache_dir: cache_dir,
|
|
85
105
|
parent: data["parent"],
|
|
86
106
|
task_id: data["task_id"],
|
|
87
|
-
project_id: data["project_id"]
|
|
107
|
+
project_id: data["project_id"],
|
|
108
|
+
campaign_execution: data["campaign_execution"],
|
|
109
|
+
review_campaign: data["review_campaign"]
|
|
88
110
|
)
|
|
89
111
|
end
|
|
90
112
|
|