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,187 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "digest"
|
|
4
|
+
require "tempfile"
|
|
5
|
+
require "timeout"
|
|
6
|
+
require "ace/runtime/molecules/protected_socket"
|
|
7
|
+
require_relative "private_directory"
|
|
8
|
+
|
|
9
|
+
module Ace
|
|
10
|
+
module Assign
|
|
11
|
+
module Authority
|
|
12
|
+
# Binary framing after the existing authenticated control header. The
|
|
13
|
+
# Server selects purpose from a source handler's fixed operation table,
|
|
14
|
+
# admits a transfer slot before calling, and holds no journal lock here.
|
|
15
|
+
class TransferCodec
|
|
16
|
+
LIMITS = {prompt_text: [16_384, 1, 16_384], scope_boundary_observation: [65_536, 1, 65_536], candidate: [64 * 1024 * 1024, 1, 64 * 1024 * 1024],
|
|
17
|
+
artifacts: [256 * 1024, 16, 64 * 1024],
|
|
18
|
+
receipt_artifacts: [272 * 1024, 17, 64 * 1024],
|
|
19
|
+
service_input: [64 * 1024, 1, 64 * 1024]}.freeze
|
|
20
|
+
SHA256 = /\A[0-9a-f]{64}\z/
|
|
21
|
+
|
|
22
|
+
class Input
|
|
23
|
+
def initialize(file, descriptor)
|
|
24
|
+
@file, @descriptor = file, descriptor
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def bytes(index: 0)
|
|
28
|
+
unless index.is_a?(Integer) && index.between?(0, count - 1)
|
|
29
|
+
raise ArgumentError, "Invalid transferred part index"
|
|
30
|
+
end
|
|
31
|
+
part = @descriptor.fetch("parts").fetch(index)
|
|
32
|
+
offset = @descriptor.fetch("parts").take(index).sum { |entry| entry.fetch("bytes") }
|
|
33
|
+
@file.seek(offset)
|
|
34
|
+
bytes = (@file.read(part.fetch("bytes")) || "").b
|
|
35
|
+
unless bytes.bytesize == part["bytes"] && Digest::SHA256.hexdigest(bytes) == part["sha256"]
|
|
36
|
+
raise AttemptErrors::ReceiptRejected, "Transferred bytes no longer match accepted binding"
|
|
37
|
+
end
|
|
38
|
+
bytes
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def count
|
|
42
|
+
@descriptor.fetch("parts").length
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def initialize(root: nil)
|
|
47
|
+
@root = root && PrivateDirectory.verify!(root)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Upload write-EOF is required before invoking the mutation consumer.
|
|
51
|
+
# Persistent launch prompt streams use their fixed framed entry point below.
|
|
52
|
+
def receive(socket, descriptor:, purpose:, deadline:)
|
|
53
|
+
receive_parts(socket, descriptor: descriptor, purpose: purpose, deadline: deadline, eof: true) { |input| yield input }
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Fixed authenticated launch_control stream only. A declared exact
|
|
57
|
+
# prompt body is followed by the next bounded control frame; unlike
|
|
58
|
+
# public uploads, this private duplex connection remains writable.
|
|
59
|
+
def receive_launch_prompt(socket, descriptor:, transfer_id:, deadline:)
|
|
60
|
+
deadline = [deadline, Process.clock_gettime(Process::CLOCK_MONOTONIC) + 30].min
|
|
61
|
+
marker = launch_prompt_marker(transfer_id)
|
|
62
|
+
receive_parts(socket, descriptor: descriptor, purpose: :prompt_text, deadline: deadline, eof: false) do |input|
|
|
63
|
+
boundary = Ace::Runtime::Molecules::ProtectedSocket.read(socket, deadline: deadline, limit: 16_384, with_size: true)
|
|
64
|
+
unless boundary.fetch(:data) == marker && boundary.fetch(:bytesize) == (JSON.generate(marker) + "\n").bytesize
|
|
65
|
+
reject!("Private prompt boundary differs")
|
|
66
|
+
end
|
|
67
|
+
yield input
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def send_launch_prompt(socket, bytes:, descriptor:, transfer_id:, deadline:)
|
|
72
|
+
deadline = [deadline, Process.clock_gettime(Process::CLOCK_MONOTONIC) + 30].min
|
|
73
|
+
marker = launch_prompt_marker(transfer_id)
|
|
74
|
+
send(socket, parts: [bytes], descriptor: descriptor, purpose: :prompt_text, deadline: deadline)
|
|
75
|
+
Ace::Runtime::Molecules::ProtectedSocket.write(socket, marker, deadline: deadline, limit: 16_384)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def launch_prompt_marker(transfer_id)
|
|
79
|
+
unless transfer_id.is_a?(String) && transfer_id.match?(/\A[0-9a-f]{32}\z/)
|
|
80
|
+
raise ArgumentError, "invalid private prompt transfer identity"
|
|
81
|
+
end
|
|
82
|
+
{"type" => "launch_prompt_end", "transfer_id" => transfer_id}
|
|
83
|
+
end
|
|
84
|
+
private :launch_prompt_marker
|
|
85
|
+
|
|
86
|
+
def receive_parts(socket, descriptor:, purpose:, deadline:, eof:)
|
|
87
|
+
validate!(descriptor, purpose)
|
|
88
|
+
descriptor = descriptor.merge("parts" => descriptor.fetch("parts").map { |part| part.dup.freeze }.freeze).freeze
|
|
89
|
+
deadline = [deadline, Process.clock_gettime(Process::CLOCK_MONOTONIC) + 30].min
|
|
90
|
+
raise ArgumentError, "receive requires protected source scratch" unless @root
|
|
91
|
+
PrivateDirectory.verify!(@root)
|
|
92
|
+
Tempfile.create(["transfer-", ".bytes"], @root, binmode: true) do |file|
|
|
93
|
+
digest = Digest::SHA256.new
|
|
94
|
+
descriptor.fetch("parts").each do |part|
|
|
95
|
+
part_digest = Digest::SHA256.new
|
|
96
|
+
remaining = part.fetch("bytes")
|
|
97
|
+
while remaining.positive?
|
|
98
|
+
chunk = read(socket, [remaining, 65_536].min, deadline)
|
|
99
|
+
reject!("Transferred bytes ended early") if chunk.nil?
|
|
100
|
+
file.write(chunk)
|
|
101
|
+
digest.update(chunk)
|
|
102
|
+
part_digest.update(chunk)
|
|
103
|
+
remaining -= chunk.bytesize
|
|
104
|
+
end
|
|
105
|
+
reject!("Transferred part digest differs") unless part_digest.hexdigest == part["sha256"]
|
|
106
|
+
end
|
|
107
|
+
reject!("Transferred aggregate digest differs") unless digest.hexdigest == descriptor["sha256"]
|
|
108
|
+
reject!("Transferred bytes exceed declared size") if eof && !read(socket, 1, deadline).nil?
|
|
109
|
+
file.flush
|
|
110
|
+
file.rewind
|
|
111
|
+
yield Input.new(file, descriptor)
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
private :receive_parts
|
|
115
|
+
|
|
116
|
+
def descriptor(parts, purpose:)
|
|
117
|
+
reject!("Invalid transfer parts") unless parts.is_a?(Array) && parts.all? { |part| part.is_a?(String) }
|
|
118
|
+
digest = Digest::SHA256.new
|
|
119
|
+
parts.each { |part| digest.update(part.b) }
|
|
120
|
+
result = {"version" => 1, "bytes" => parts.sum(&:bytesize), "sha256" => digest.hexdigest,
|
|
121
|
+
"parts" => parts.map { |part| {"bytes" => part.bytesize, "sha256" => Digest::SHA256.hexdigest(part.b)} }}
|
|
122
|
+
validate!(result, purpose)
|
|
123
|
+
result
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# The caller writes the bounded JSON descriptor first, then these raw
|
|
127
|
+
# bytes. Both directions share the Server's original monotonic deadline.
|
|
128
|
+
def send(socket, parts:, descriptor:, purpose:, deadline:)
|
|
129
|
+
expected = self.descriptor(parts, purpose: purpose)
|
|
130
|
+
reject!("Export descriptor differs from bytes") unless expected == descriptor
|
|
131
|
+
deadline = [deadline, Process.clock_gettime(Process::CLOCK_MONOTONIC) + 30].min
|
|
132
|
+
parts.each do |part|
|
|
133
|
+
offset = 0
|
|
134
|
+
while offset < part.bytesize
|
|
135
|
+
wait(socket, deadline, write: true)
|
|
136
|
+
written = socket.write_nonblock(part.byteslice(offset, [65_536, part.bytesize - offset].min), exception: false)
|
|
137
|
+
offset += written unless written == :wait_writable
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def validate!(descriptor, purpose)
|
|
143
|
+
total, count, each = LIMITS.fetch(purpose) { reject!("Unknown source transfer purpose") }
|
|
144
|
+
unless descriptor.is_a?(Hash) && descriptor.keys.sort == %w[bytes parts sha256 version] &&
|
|
145
|
+
descriptor["version"] == 1 && descriptor["bytes"].is_a?(Integer) &&
|
|
146
|
+
descriptor["bytes"].between?(0, total) && descriptor["sha256"].is_a?(String) && descriptor["sha256"].match?(SHA256) &&
|
|
147
|
+
descriptor["parts"].is_a?(Array) && descriptor["parts"].length.between?(1, count) &&
|
|
148
|
+
descriptor["parts"].all? { |part| part.is_a?(Hash) && part.keys.sort == %w[bytes sha256] &&
|
|
149
|
+
part["bytes"].is_a?(Integer) && part["bytes"].between?(0, each) &&
|
|
150
|
+
part["sha256"].is_a?(String) && part["sha256"].match?(SHA256) } &&
|
|
151
|
+
descriptor["bytes"] == descriptor["parts"].sum { |part| part["bytes"] } &&
|
|
152
|
+
(!%i[candidate service_input prompt_text].include?(purpose) || descriptor["bytes"].positive?)
|
|
153
|
+
reject!("Invalid or oversized transfer descriptor")
|
|
154
|
+
end
|
|
155
|
+
if purpose == :receipt_artifacts
|
|
156
|
+
receipt, *evidence = descriptor.fetch("parts")
|
|
157
|
+
unless receipt.fetch("bytes").between?(1, 16 * 1024) && evidence.sum { |part| part.fetch("bytes") } <= 256 * 1024
|
|
158
|
+
reject!("Receipt or evidence exceeds its fixed transfer limit")
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
true
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
private
|
|
165
|
+
|
|
166
|
+
def read(socket, count, deadline)
|
|
167
|
+
loop do
|
|
168
|
+
wait(socket, deadline)
|
|
169
|
+
bytes = socket.read_nonblock(count, exception: false)
|
|
170
|
+
return bytes unless bytes == :wait_readable
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def wait(socket, deadline, write: false)
|
|
175
|
+
remaining = deadline - Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
176
|
+
raise Timeout::Error, "Transfer deadline exceeded" unless remaining.positive?
|
|
177
|
+
ready = IO.select(write ? nil : [socket], write ? [socket] : nil, nil, remaining)
|
|
178
|
+
raise Timeout::Error, "Transfer deadline exceeded" unless ready
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def reject!(message)
|
|
182
|
+
raise AttemptErrors::MalformedTransfer, message
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
+
require_relative "../../authority/protected_assignment_context"
|
|
2
3
|
|
|
3
4
|
module Ace
|
|
4
5
|
module Assign
|
|
@@ -11,11 +12,16 @@ module Ace
|
|
|
11
12
|
# - <assignment-id>@<step-number>
|
|
12
13
|
module AssignmentTarget
|
|
13
14
|
DEFAULT_TARGET_ENV = "ACE_ASSIGN_DEFAULT_TARGET"
|
|
14
|
-
Target = Struct.new(:assignment_id, :scope, keyword_init: true)
|
|
15
|
+
Target = Struct.new(:assignment_id, :scope, :prepared_input, keyword_init: true)
|
|
15
16
|
View = Struct.new(:assignment, :state, :scoped_state, :active_steps, :next_step, :focus_step, :scope_root, keyword_init: true)
|
|
16
17
|
|
|
17
18
|
private
|
|
18
19
|
|
|
20
|
+
def refuse_protected_graph_mutation!(options)
|
|
21
|
+
context = @protected_assignment_context ||= Ace::Assign::Authority::ProtectedAssignmentContext.load
|
|
22
|
+
context.refuse_graph_mutation!(options: options)
|
|
23
|
+
end
|
|
24
|
+
|
|
19
25
|
def resolve_assignment_target(options)
|
|
20
26
|
assignment_raw = options[:assignment]
|
|
21
27
|
explicit_target = unless assignment_raw.nil? || assignment_raw.to_s.strip.empty?
|
|
@@ -29,7 +35,13 @@ module Ace
|
|
|
29
35
|
"does not match #{DEFAULT_TARGET_ENV}=#{target_identity(env_target)}"
|
|
30
36
|
end
|
|
31
37
|
|
|
32
|
-
explicit_target || env_target || Target.new(assignment_id: nil, scope: nil)
|
|
38
|
+
target = explicit_target || env_target || Target.new(assignment_id: nil, scope: nil)
|
|
39
|
+
context = @protected_assignment_context ||= Ace::Assign::Authority::ProtectedAssignmentContext.load
|
|
40
|
+
target.prepared_input = context.resolve(options: options, assignment_id: target.assignment_id, scope: target.scope)
|
|
41
|
+
if target.prepared_input && ![Status, Step, Start, Finish, Fail, Resume].any? { |command| is_a?(command) }
|
|
42
|
+
raise AttemptErrors::EvidenceUnavailable, "protected queue command requires a reviewed new prepared version and attempt"
|
|
43
|
+
end
|
|
44
|
+
target
|
|
33
45
|
end
|
|
34
46
|
|
|
35
47
|
def parse_assignment_target(raw)
|
|
@@ -63,6 +75,7 @@ module Ace
|
|
|
63
75
|
end
|
|
64
76
|
|
|
65
77
|
def build_executor_for_target(target)
|
|
78
|
+
return Ace::Assign::Authority::PreparedExecutor.new(target.prepared_input) if target.prepared_input
|
|
66
79
|
return Organisms::AssignmentExecutor.new unless target.assignment_id
|
|
67
80
|
|
|
68
81
|
manager = Molecules::AssignmentManager.new
|
|
@@ -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
|
|
@@ -0,0 +1,97 @@
|
|
|
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 Launch < Ace::Support::Cli::Command
|
|
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
|
|
13
|
+
option :mapping, required: true, desc: "Installed launch mapping ID"
|
|
14
|
+
option :assignment, desc: "Managed assignment ID"
|
|
15
|
+
option :definition, desc: "Retained final prepared assignment definition JSON file"
|
|
16
|
+
option :prepared_bundle, desc: "Retained original prepared input Git bundle"
|
|
17
|
+
option :step, desc: "Assignment subtree scope"
|
|
18
|
+
option :base_head, desc: "Exact base commit SHA"
|
|
19
|
+
option :mutation, desc: "Stable invocation ID for safe replay"
|
|
20
|
+
option :dry_run, type: :boolean, default: false, desc: "Verify installed boundary without mutation or creation"
|
|
21
|
+
def call(**options)
|
|
22
|
+
driver = build_driver(options.fetch(:mapping))
|
|
23
|
+
result = if options[:dry_run]
|
|
24
|
+
driver.preflight
|
|
25
|
+
else
|
|
26
|
+
%i[assignment definition prepared_bundle step base_head].each do |key|
|
|
27
|
+
raise ArgumentError, "Missing --#{key.to_s.tr('_', '-')}" unless options[key].is_a?(String) && !options[key].empty?
|
|
28
|
+
end
|
|
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,
|
|
33
|
+
scope: options[:step], base_head: options[:base_head]}
|
|
34
|
+
args[:mutation_id] = options[:mutation] if options[:mutation]
|
|
35
|
+
driver.launch(**args)
|
|
36
|
+
end
|
|
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
|
|
51
|
+
rescue ArgumentError, SystemCallError, Ace::Runtime::Error, Ace::Assign::Error => error
|
|
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
|
|
56
|
+
end
|
|
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
|
+
|
|
89
|
+
def build_driver(mapping)
|
|
90
|
+
Ace::Assign::Authority::LaunchDriver.new(mapping_id: mapping)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require_relative "../../../authority/server"
|
|
3
|
+
require_relative "../../../authority/router"
|
|
4
|
+
require_relative "../../../authority/launch_lifecycle"
|
|
5
|
+
module Ace
|
|
6
|
+
module Assign
|
|
7
|
+
module CLI
|
|
8
|
+
module Commands
|
|
9
|
+
module Authority
|
|
10
|
+
class Serve < Ace::Support::Cli::Command
|
|
11
|
+
desc "Serve the installed protected assignment authority"
|
|
12
|
+
option :authority, required: true, desc: "Installed authority ID"
|
|
13
|
+
def call(**options)
|
|
14
|
+
deployment = Ace::Assign::Authority::Deployment.load
|
|
15
|
+
history = Ace::Assign::Authority::DeploymentHistory.load
|
|
16
|
+
launch = Ace::Assign::Authority::LaunchLifecycle.new(deployment: deployment, deployment_history: history)
|
|
17
|
+
router = Ace::Assign::Authority::Router.new(launch: launch)
|
|
18
|
+
server = Ace::Assign::Authority::Server.new(authority_id: options.fetch(:authority), deployment: deployment, lifecycle: router)
|
|
19
|
+
signals = %w[INT TERM].to_h { |signal| [signal, Signal.trap(signal) { server.request_stop }] }
|
|
20
|
+
server.serve
|
|
21
|
+
rescue ArgumentError, Ace::Runtime::Error, Ace::Assign::Error => error
|
|
22
|
+
raise Ace::Support::Cli::Error, error.message
|
|
23
|
+
ensure
|
|
24
|
+
signals&.each { |signal, handler| Signal.trap(signal, handler) }
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require "json"
|
|
3
|
+
require_relative "../../../authority/client"
|
|
4
|
+
module Ace
|
|
5
|
+
module Assign
|
|
6
|
+
module CLI
|
|
7
|
+
module Commands
|
|
8
|
+
module Authority
|
|
9
|
+
class Status < Ace::Support::Cli::Command
|
|
10
|
+
desc "Inspect canonical protected launch ownership"
|
|
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
|
+
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), "result_candidate_generation" => nil})
|
|
17
|
+
puts JSON.generate(reply.data)
|
|
18
|
+
rescue ArgumentError, Ace::Runtime::Error, Ace::Assign::Error => error
|
|
19
|
+
raise Ace::Support::Cli::Error, error.message
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -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
|