ace-assign 0.70.0 → 0.73.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 +8 -8
- data/.ace-defaults/assign/catalog/steps/create-pr.step.yml +1 -1
- data/.ace-defaults/assign/catalog/steps/mark-pr-ready.step.yml +1 -1
- data/.ace-defaults/assign/config.yml +0 -13
- data/.ace-defaults/assign/presets/fix-bug.yml +1 -1
- data/.ace-defaults/assign/presets/work-on-task-ace-development.yml +1 -1
- data/.ace-defaults/assign/presets/work-on-task-auto-merge.yml +1 -1
- data/.ace-defaults/assign/presets/work-on-task.yml +1 -1
- data/CHANGELOG.md +3 -2099
- data/README.md +58 -31
- data/docs/exit-codes.md +36 -44
- data/docs/getting-started.md +9 -4
- data/docs/usage.md +129 -522
- data/handbook/skills/as-assign-drive/SKILL.md +1 -1
- data/handbook/workflow-instructions/assign/drive.wf.md +47 -118
- data/handbook/workflow-instructions/assign/prepare.wf.md +1 -1
- data/handbook/workflow-instructions/assign/verify-test-suite.wf.md +8 -2
- data/lib/ace/assign/cli/commands/assignment_target.rb +2 -15
- data/lib/ace/assign/cli/commands/create.rb +1 -8
- data/lib/ace/assign/cli/commands/fail.rb +0 -6
- data/lib/ace/assign/cli/commands/finish.rb +2 -23
- data/lib/ace/assign/cli/commands/fork_session.rb +0 -1
- data/lib/ace/assign/cli/commands/select.rb +0 -1
- data/lib/ace/assign/cli/commands/start.rb +0 -2
- data/lib/ace/assign/cli/commands/status.rb +1 -31
- data/lib/ace/assign/cli/commands/step.rb +1 -3
- data/lib/ace/assign/cli.rb +1 -85
- data/lib/ace/assign/errors.rb +8 -80
- data/lib/ace/assign/models/assignment.rb +5 -28
- data/lib/ace/assign/molecules/assignment_manager.rb +0 -26
- data/lib/ace/assign/molecules/fork_session_launcher.rb +4 -12
- data/lib/ace/assign/molecules/lifecycle_exclusion.rb +15 -112
- data/lib/ace/assign/molecules/runtime_control_surface_runner.rb +2 -1
- data/lib/ace/assign/organisms/assignment_executor.rb +0 -6
- data/lib/ace/assign/organisms/task_assignment_creator.rb +3 -8
- data/lib/ace/assign/version.rb +1 -1
- data/lib/ace/assign.rb +0 -6
- metadata +4 -161
- data/.ace-defaults/assign/presets/close-delivery.yml +0 -57
- data/.ace-defaults/assign/presets/deliver-release.yml +0 -60
- data/.ace-defaults/assign/presets/integrate-batch.yml +0 -72
- data/.ace-defaults/assign/presets/supervise-delivery.yml +0 -69
- data/docs/protected-authority.md +0 -282
- data/exe/ace-scope-ready +0 -55
- data/lib/ace/assign/atoms/assignment_scope.rb +0 -58
- data/lib/ace/assign/atoms/attempt_state_machine.rb +0 -84
- data/lib/ace/assign/atoms/delivery_parameters.rb +0 -19
- data/lib/ace/assign/atoms/evidence_digest.rb +0 -78
- data/lib/ace/assign/atoms/fixed_merge_schemas.rb +0 -398
- data/lib/ace/assign/authority/assignment_definition.rb +0 -35
- data/lib/ace/assign/authority/assignment_inventory.rb +0 -290
- data/lib/ace/assign/authority/campaign_consumer_policy.rb +0 -100
- data/lib/ace/assign/authority/campaign_execution.rb +0 -119
- data/lib/ace/assign/authority/campaign_round_transfer.rb +0 -82
- data/lib/ace/assign/authority/candidate_transfer.rb +0 -327
- data/lib/ace/assign/authority/cleanup_result_protection.rb +0 -53
- data/lib/ace/assign/authority/client.rb +0 -257
- data/lib/ace/assign/authority/deployment.rb +0 -627
- data/lib/ace/assign/authority/deployment_history.rb +0 -146
- data/lib/ace/assign/authority/endcap.rb +0 -455
- data/lib/ace/assign/authority/endcap_attempts.rb +0 -274
- data/lib/ace/assign/authority/endcap_campaign_results.rb +0 -232
- data/lib/ace/assign/authority/endcap_campaign_rounds.rb +0 -266
- data/lib/ace/assign/authority/endcap_contexts.rb +0 -23
- data/lib/ace/assign/authority/endcap_inboxes.rb +0 -37
- data/lib/ace/assign/authority/endcap_prepared_work.rb +0 -80
- data/lib/ace/assign/authority/endcap_publication.rb +0 -207
- data/lib/ace/assign/authority/endcap_results.rb +0 -398
- data/lib/ace/assign/authority/endcap_review_requests.rb +0 -183
- data/lib/ace/assign/authority/endcap_reviews.rb +0 -130
- data/lib/ace/assign/authority/endcap_service_settlement.rb +0 -152
- data/lib/ace/assign/authority/endcap_services.rb +0 -598
- data/lib/ace/assign/authority/evidence_transfer.rb +0 -67
- data/lib/ace/assign/authority/execution_scope_observation.rb +0 -903
- data/lib/ace/assign/authority/inbox_context_original.rb +0 -113
- data/lib/ace/assign/authority/launch_attempt_consumers.rb +0 -51
- data/lib/ace/assign/authority/launch_campaign_registration.rb +0 -214
- data/lib/ace/assign/authority/launch_control_channel.rb +0 -313
- data/lib/ace/assign/authority/launch_control_exclusions.rb +0 -127
- data/lib/ace/assign/authority/launch_driver.rb +0 -393
- data/lib/ace/assign/authority/launch_input_inhibition.rb +0 -132
- data/lib/ace/assign/authority/launch_lifecycle.rb +0 -997
- data/lib/ace/assign/authority/launch_prepared_work.rb +0 -122
- data/lib/ace/assign/authority/launch_review.rb +0 -91
- data/lib/ace/assign/authority/launch_scope_abort.rb +0 -81
- data/lib/ace/assign/authority/launch_scope_admission.rb +0 -215
- data/lib/ace/assign/authority/launch_scope_close.rb +0 -146
- data/lib/ace/assign/authority/launch_scope_parent.rb +0 -44
- data/lib/ace/assign/authority/launch_scope_release.rb +0 -859
- data/lib/ace/assign/authority/launch_steering.rb +0 -353
- data/lib/ace/assign/authority/launch_stop.rb +0 -254
- data/lib/ace/assign/authority/maintenance_workspace_retirement.rb +0 -229
- data/lib/ace/assign/authority/ordinary_attempt_start.rb +0 -137
- data/lib/ace/assign/authority/ordinary_client.rb +0 -70
- data/lib/ace/assign/authority/ordinary_completion.rb +0 -94
- data/lib/ace/assign/authority/ordinary_effect_settlement.rb +0 -118
- data/lib/ace/assign/authority/ordinary_lifecycle.rb +0 -191
- data/lib/ace/assign/authority/ordinary_peer.rb +0 -54
- data/lib/ace/assign/authority/ordinary_progress.rb +0 -162
- data/lib/ace/assign/authority/ordinary_queue.rb +0 -115
- data/lib/ace/assign/authority/ordinary_results.rb +0 -196
- data/lib/ace/assign/authority/ordinary_server.rb +0 -75
- data/lib/ace/assign/authority/original_inbox_selection.rb +0 -41
- data/lib/ace/assign/authority/posix_acl.rb +0 -59
- data/lib/ace/assign/authority/prepared_input.rb +0 -130
- data/lib/ace/assign/authority/prepared_queue.rb +0 -185
- data/lib/ace/assign/authority/prepared_task_context.rb +0 -116
- data/lib/ace/assign/authority/prepared_work.rb +0 -304
- data/lib/ace/assign/authority/prepared_worker.rb +0 -69
- data/lib/ace/assign/authority/private_directory.rb +0 -33
- data/lib/ace/assign/authority/protected_assignment_context.rb +0 -226
- data/lib/ace/assign/authority/readiness_hook.rb +0 -120
- data/lib/ace/assign/authority/receipt_transfer.rb +0 -48
- data/lib/ace/assign/authority/router.rb +0 -81
- data/lib/ace/assign/authority/server.rb +0 -131
- data/lib/ace/assign/authority/service_cleanup_dispatch.rb +0 -88
- data/lib/ace/assign/authority/service_cleanup_evidence.rb +0 -180
- data/lib/ace/assign/authority/service_delivery_evidence.rb +0 -101
- data/lib/ace/assign/authority/service_evidence.rb +0 -286
- data/lib/ace/assign/authority/service_publication_evidence.rb +0 -110
- data/lib/ace/assign/authority/socket_server.rb +0 -255
- data/lib/ace/assign/authority/task_context_entry.rb +0 -32
- data/lib/ace/assign/authority/transfer_codec.rb +0 -187
- data/lib/ace/assign/cli/commands/attempt/base.rb +0 -84
- data/lib/ace/assign/cli/commands/attempt/evidence.rb +0 -28
- data/lib/ace/assign/cli/commands/attempt/finish.rb +0 -51
- data/lib/ace/assign/cli/commands/attempt/reconcile.rb +0 -49
- data/lib/ace/assign/cli/commands/attempt/start.rb +0 -45
- data/lib/ace/assign/cli/commands/attempt/status.rb +0 -54
- data/lib/ace/assign/cli/commands/authority/inbox_context_principal.rb +0 -25
- data/lib/ace/assign/cli/commands/authority/inbox_context_selection.rb +0 -28
- data/lib/ace/assign/cli/commands/authority/launch.rb +0 -97
- data/lib/ace/assign/cli/commands/authority/serve.rb +0 -31
- data/lib/ace/assign/cli/commands/authority/status.rb +0 -26
- data/lib/ace/assign/cli/commands/authority/task_context.rb +0 -31
- data/lib/ace/assign/cli/commands/authority/task_context_principal.rb +0 -25
- data/lib/ace/assign/cli/commands/authority/task_context_selection.rb +0 -30
- data/lib/ace/assign/cli/commands/authority/terminate.rb +0 -29
- data/lib/ace/assign/cli/commands/authority/worker.rb +0 -23
- data/lib/ace/assign/cli/commands/campaign_export_result.rb +0 -55
- data/lib/ace/assign/cli/commands/campaign_record_round.rb +0 -57
- data/lib/ace/assign/cli/commands/delivery.rb +0 -107
- data/lib/ace/assign/cli/commands/inbox_bind.rb +0 -34
- data/lib/ace/assign/cli/commands/protected_submission.rb +0 -61
- data/lib/ace/assign/cli/commands/resume.rb +0 -38
- data/lib/ace/assign/cli/commands/submit_candidate.rb +0 -34
- data/lib/ace/assign/cli/commands/submit_result.rb +0 -61
- data/lib/ace/assign/models/attempt.rb +0 -178
- data/lib/ace/assign/models/attempt_binding.rb +0 -116
- data/lib/ace/assign/models/evidence_event.rb +0 -86
- data/lib/ace/assign/models/execution_receipt.rb +0 -139
- data/lib/ace/assign/molecules/attempt_reconciler.rb +0 -127
- data/lib/ace/assign/molecules/attempt_store.rb +0 -201
- data/lib/ace/assign/molecules/canonical_attempt_state.rb +0 -151
- data/lib/ace/assign/molecules/canonical_evidence.rb +0 -153
- data/lib/ace/assign/molecules/canonical_read_commands.rb +0 -84
- data/lib/ace/assign/molecules/canonical_read_snapshot.rb +0 -313
- data/lib/ace/assign/molecules/evidence_calculator.rb +0 -237
- data/lib/ace/assign/molecules/evidence_journal.rb +0 -1912
- data/lib/ace/assign/molecules/execution_identity_resolver.rb +0 -137
- data/lib/ace/assign/molecules/execution_scope_lineage.rb +0 -345
- data/lib/ace/assign/molecules/fixed_effect_reference.rb +0 -31
- data/lib/ace/assign/molecules/installed_journal_read.rb +0 -143
- data/lib/ace/assign/molecules/journal_input_inhibition.rb +0 -78
- data/lib/ace/assign/molecules/journal_mutation.rb +0 -273
- data/lib/ace/assign/molecules/journal_prompt_mutation.rb +0 -241
- data/lib/ace/assign/molecules/ordinary_merge_cli.rb +0 -68
- data/lib/ace/assign/molecules/ordinary_merge_contract.rb +0 -410
- data/lib/ace/assign/molecules/original_launch_binding.rb +0 -37
- data/lib/ace/assign/molecules/proposal_journal.rb +0 -179
- data/lib/ace/assign/molecules/protected_control_exclusion.rb +0 -303
- data/lib/ace/assign/molecules/protected_workspace_exclusion.rb +0 -330
- data/lib/ace/assign/molecules/protected_workspace_provisioner.rb +0 -164
- data/lib/ace/assign/molecules/receipt_verifier.rb +0 -314
- data/lib/ace/assign/molecules/terminal_scope_receipt.rb +0 -88
- data/lib/ace/assign/organisms/attempt_coordinator.rb +0 -1337
- data/lib/ace/assign/organisms/delivery_coordinator.rb +0 -342
- data/lib/ace/assign/organisms/ordinary_merge_consumer.rb +0 -365
- data/lib/ace/assign/organisms/prepared_work_builder.rb +0 -194
- data/lib/ace/assign/organisms/protected_delivery_coordinator.rb +0 -100
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Ace
|
|
4
|
-
module Assign
|
|
5
|
-
module Authority
|
|
6
|
-
# Structural admission of source-owned pins. Artifact authentication belongs
|
|
7
|
-
# to the selected entry owner; these references are never an execution grant.
|
|
8
|
-
module TaskContextEntry
|
|
9
|
-
LIMITS = {"manifest" => 65_536, "wrapper" => 33_554_432}.freeze
|
|
10
|
-
|
|
11
|
-
def self.validate!(value)
|
|
12
|
-
unless value.is_a?(Hash) && value.keys.sort == LIMITS.keys.sort
|
|
13
|
-
raise ArgumentError, "task context entry fields differ"
|
|
14
|
-
end
|
|
15
|
-
LIMITS.each do |key, limit|
|
|
16
|
-
reference = value[key]
|
|
17
|
-
unless reference.is_a?(Hash) && reference.keys.sort == %w[bytes path sha256] &&
|
|
18
|
-
reference["bytes"].is_a?(Integer) && reference["bytes"].between?(1, limit) &&
|
|
19
|
-
reference["sha256"].is_a?(String) && reference["sha256"].match?(/\A[0-9a-f]{64}\z/) &&
|
|
20
|
-
reference["path"].is_a?(String) && reference["path"].encoding == Encoding::UTF_8 &&
|
|
21
|
-
reference["path"].valid_encoding? && reference["path"].bytesize.between?(2, 4096) &&
|
|
22
|
-
!reference["path"].include?("\0") && reference["path"].start_with?("/") &&
|
|
23
|
-
File.expand_path(reference["path"]) == reference["path"]
|
|
24
|
-
raise ArgumentError, "task context entry reference differs"
|
|
25
|
-
end
|
|
26
|
-
end
|
|
27
|
-
value
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
end
|
|
31
|
-
end
|
|
32
|
-
end
|
|
@@ -1,187 +0,0 @@
|
|
|
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], assignment_snapshot: [64 * 1024 * 1024, 1, 64 * 1024 * 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 assignment_snapshot].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,84 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "json"
|
|
4
|
-
require_relative "../../../authority/protected_assignment_context"
|
|
5
|
-
|
|
6
|
-
module Ace
|
|
7
|
-
module Assign
|
|
8
|
-
module CLI
|
|
9
|
-
module Commands
|
|
10
|
-
# Public `ace-assign attempt` command namespace (interface contract:
|
|
11
|
-
# start, status, finish, reconcile).
|
|
12
|
-
module Attempt
|
|
13
|
-
# Shared coordinator construction, option validation, and JSON output.
|
|
14
|
-
module Base
|
|
15
|
-
private
|
|
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
|
-
|
|
65
|
-
def build_coordinator
|
|
66
|
-
Organisms::AttemptCoordinator.new
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
def require_option(options, key, usage)
|
|
70
|
-
value = options[key].to_s.strip
|
|
71
|
-
raise Ace::Support::Cli::Error, "Missing --#{key}: usage: ace-assign attempt #{usage}" if value.empty?
|
|
72
|
-
|
|
73
|
-
value
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
def emit_json(payload)
|
|
77
|
-
puts JSON.generate(payload)
|
|
78
|
-
end
|
|
79
|
-
end
|
|
80
|
-
end
|
|
81
|
-
end
|
|
82
|
-
end
|
|
83
|
-
end
|
|
84
|
-
end
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
require_relative "base"
|
|
3
|
-
module Ace
|
|
4
|
-
module Assign
|
|
5
|
-
module CLI
|
|
6
|
-
module Commands
|
|
7
|
-
module Attempt
|
|
8
|
-
class Evidence < Ace::Support::Cli::Command
|
|
9
|
-
include Attempt::Base
|
|
10
|
-
desc "Read coordinator-accepted live execution or historical execution authority"
|
|
11
|
-
option :attempt, required: true, desc: "Accepted execution attempt ID"
|
|
12
|
-
option :receipt_digest, required: true, desc: "Accepted receipt digest"
|
|
13
|
-
option :kind, default: "check", desc: "Evidence purpose: check, review-collection or review-approval"
|
|
14
|
-
option :check_name, default: "tests", desc: "Required check name (tests binds operation test)"
|
|
15
|
-
option :historical_head, desc: "Exact recorded head for historical authority only (never current-head evidence)"
|
|
16
|
-
option :format, default: "json", desc: "JSON evidence projection"
|
|
17
|
-
|
|
18
|
-
def call(**options)
|
|
19
|
-
raise Ace::Support::Cli::Error, "evidence supports --format json" unless options[:format] == "json"
|
|
20
|
-
emit_json(build_coordinator.evidence(attempt_id: options[:attempt], receipt_digest: options[:receipt_digest],
|
|
21
|
-
kind: options[:kind], check_name: options[:check_name], historical_head: options[:historical_head]))
|
|
22
|
-
end
|
|
23
|
-
end
|
|
24
|
-
end
|
|
25
|
-
end
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
|
-
end
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "json"
|
|
4
|
-
require_relative "base"
|
|
5
|
-
|
|
6
|
-
module Ace
|
|
7
|
-
module Assign
|
|
8
|
-
module CLI
|
|
9
|
-
module Commands
|
|
10
|
-
module Attempt
|
|
11
|
-
# Protected completion selects an already submitted canonical result.
|
|
12
|
-
# Ordinary local completion accepts its structured receipt file.
|
|
13
|
-
class Finish < Ace::Support::Cli::Command
|
|
14
|
-
include Ace::Support::Cli::Base
|
|
15
|
-
include Attempt::Base
|
|
16
|
-
|
|
17
|
-
desc "Finish a protected canonical result or an ordinary local receipt"
|
|
18
|
-
|
|
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"
|
|
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"
|
|
28
|
-
|
|
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
|
|
39
|
-
attempt_id = require_option(options, :attempt, "finish --attempt ID --receipt FILE")
|
|
40
|
-
receipt = require_option(options, :receipt, "finish --attempt ID --receipt FILE")
|
|
41
|
-
|
|
42
|
-
result = build_coordinator.finish(attempt_id: attempt_id, receipt_path: receipt)
|
|
43
|
-
|
|
44
|
-
emit_json(result.projection)
|
|
45
|
-
end
|
|
46
|
-
end
|
|
47
|
-
end
|
|
48
|
-
end
|
|
49
|
-
end
|
|
50
|
-
end
|
|
51
|
-
end
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "json"
|
|
4
|
-
require_relative "base"
|
|
5
|
-
|
|
6
|
-
module Ace
|
|
7
|
-
module Assign
|
|
8
|
-
module CLI
|
|
9
|
-
module Commands
|
|
10
|
-
module Attempt
|
|
11
|
-
# Classify interruptions and resolve uncertainty against verified,
|
|
12
|
-
# boundary-attributed receipts. Never replays external effects.
|
|
13
|
-
class Reconcile < Ace::Support::Cli::Command
|
|
14
|
-
include Ace::Support::Cli::Base
|
|
15
|
-
include Attempt::Base
|
|
16
|
-
|
|
17
|
-
desc "Reconcile an interrupted or uncertain attempt"
|
|
18
|
-
|
|
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"
|
|
24
|
-
option :receipt, desc: "Path to the receipt JSON file (required to resolve uncertainty)"
|
|
25
|
-
|
|
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
|
|
34
|
-
attempt_id = require_option(options, :attempt, "reconcile --attempt ID [--receipt FILE]")
|
|
35
|
-
receipt = options[:receipt].to_s.strip
|
|
36
|
-
|
|
37
|
-
result = build_coordinator.reconcile(
|
|
38
|
-
attempt_id: attempt_id,
|
|
39
|
-
receipt_path: receipt.empty? ? nil : receipt
|
|
40
|
-
)
|
|
41
|
-
|
|
42
|
-
emit_json(result.projection)
|
|
43
|
-
end
|
|
44
|
-
end
|
|
45
|
-
end
|
|
46
|
-
end
|
|
47
|
-
end
|
|
48
|
-
end
|
|
49
|
-
end
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "json"
|
|
4
|
-
require_relative "base"
|
|
5
|
-
|
|
6
|
-
module Ace
|
|
7
|
-
module Assign
|
|
8
|
-
module CLI
|
|
9
|
-
module Commands
|
|
10
|
-
module Attempt
|
|
11
|
-
# Bind an immutable attempt to an assignment step/subtree.
|
|
12
|
-
#
|
|
13
|
-
# Identity is derived from the execution boundary; a repeated
|
|
14
|
-
# identical start returns the active attempt, and a conflicting
|
|
15
|
-
# binding fails without launching a second writer.
|
|
16
|
-
class Start < Ace::Support::Cli::Command
|
|
17
|
-
include Ace::Support::Cli::Base
|
|
18
|
-
include Attempt::Base
|
|
19
|
-
|
|
20
|
-
desc "Start a scoped attempt for an assignment step"
|
|
21
|
-
|
|
22
|
-
option :assignment, desc: "Assignment ID"
|
|
23
|
-
option :step, desc: "Step or subtree scope (e.g. 010 or 010.01)"
|
|
24
|
-
option :project, desc: "Project ID the attempt executes in"
|
|
25
|
-
|
|
26
|
-
def call(**options)
|
|
27
|
-
usage = "start --assignment ID --step STEP --project ID"
|
|
28
|
-
assignment = require_option(options, :assignment, usage)
|
|
29
|
-
step = require_option(options, :step, usage)
|
|
30
|
-
project = require_option(options, :project, usage)
|
|
31
|
-
|
|
32
|
-
attempt = build_coordinator.start(
|
|
33
|
-
assignment_id: assignment,
|
|
34
|
-
step: step,
|
|
35
|
-
project_id: project
|
|
36
|
-
)
|
|
37
|
-
|
|
38
|
-
emit_json(attempt.projection)
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
|
-
end
|
|
44
|
-
end
|
|
45
|
-
end
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "json"
|
|
4
|
-
require_relative "base"
|
|
5
|
-
|
|
6
|
-
module Ace
|
|
7
|
-
module Assign
|
|
8
|
-
module CLI
|
|
9
|
-
module Commands
|
|
10
|
-
module Attempt
|
|
11
|
-
# Show attempt state, binding facts, and evidence references.
|
|
12
|
-
# JSON output carries only state, binding, references, and digests
|
|
13
|
-
# — never credentials or receipt bytes.
|
|
14
|
-
class Status < Ace::Support::Cli::Command
|
|
15
|
-
include Ace::Support::Cli::Base
|
|
16
|
-
include Attempt::Base
|
|
17
|
-
|
|
18
|
-
desc "Show attempt status for an assignment"
|
|
19
|
-
|
|
20
|
-
option :assignment, desc: "Assignment ID"
|
|
21
|
-
option :format, desc: "Output format (json, text)", default: "json"
|
|
22
|
-
|
|
23
|
-
def call(**options)
|
|
24
|
-
assignment = require_option(options, :assignment, "status --assignment ID --format json")
|
|
25
|
-
projection = build_coordinator.status(assignment)
|
|
26
|
-
|
|
27
|
-
if options[:format] == "text"
|
|
28
|
-
print_text(assignment, projection)
|
|
29
|
-
else
|
|
30
|
-
emit_json(projection || {"attempt" => nil})
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
private
|
|
35
|
-
|
|
36
|
-
def print_text(assignment, projection)
|
|
37
|
-
return puts "No attempts recorded for #{assignment}" if projection.nil?
|
|
38
|
-
|
|
39
|
-
puts "Attempt: #{projection['attempt_id']} #{projection['state']} (#{projection['scope']})"
|
|
40
|
-
puts "Recovery: #{projection['recovery_mode']}"
|
|
41
|
-
puts "Base head: #{projection['base_head']}"
|
|
42
|
-
puts "Candidate head: #{projection['candidate_head'] || 'unpinned'}"
|
|
43
|
-
puts "Evidence ref: #{projection['evidence_git_ref'] || 'local-only'}"
|
|
44
|
-
puts "Journal commit: #{projection['journal_commit'] || 'none'}"
|
|
45
|
-
return unless projection["unresolved_effects"].any?
|
|
46
|
-
|
|
47
|
-
puts "Unresolved effects: #{projection['unresolved_effects'].join(', ')}"
|
|
48
|
-
end
|
|
49
|
-
end
|
|
50
|
-
end
|
|
51
|
-
end
|
|
52
|
-
end
|
|
53
|
-
end
|
|
54
|
-
end
|
|
@@ -1,25 +0,0 @@
|
|
|
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
|
|
@@ -1,28 +0,0 @@
|
|
|
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
|