ace-assign 0.71.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 -2119
- 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 -4
- data/handbook/workflow-instructions/assign/drive.wf.md +47 -195
- 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/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 -176
- 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/candidate_limits.rb +0 -20
- 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/atoms/git_blob_batch.rb +0 -59
- 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_blob_batch.rb +0 -55
- data/lib/ace/assign/authority/candidate_head_comparison.rb +0 -139
- data/lib/ace/assign/authority/candidate_transfer.rb +0 -357
- 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/command_execution.rb +0 -180
- 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 -457
- 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 -41
- 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_context.rb +0 -38
- data/lib/ace/assign/authority/endcap_service_settlement.rb +0 -152
- 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/fixed_publication_lifecycle.rb +0 -176
- data/lib/ace/assign/authority/fixed_service_lifecycle.rb +0 -594
- 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 -104
- data/lib/ace/assign/authority/ordinary_effect_settlement.rb +0 -118
- data/lib/ace/assign/authority/ordinary_lifecycle.rb +0 -227
- data/lib/ace/assign/authority/ordinary_merge_evidence.rb +0 -81
- 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_release_artifacts.rb +0 -143
- data/lib/ace/assign/authority/ordinary_results.rb +0 -206
- data/lib/ace/assign/authority/ordinary_review_evidence.rb +0 -180
- data/lib/ace/assign/authority/ordinary_reviews.rb +0 -214
- data/lib/ace/assign/authority/ordinary_server.rb +0 -75
- data/lib/ace/assign/authority/ordinary_supervisor.rb +0 -99
- 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 -309
- 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/release_artifact_manifest.rb +0 -91
- data/lib/ace/assign/authority/retained_input.rb +0 -53
- 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 -189
- 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 -170
- 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 -1899
- 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 -274
- 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 -320
- data/lib/ace/assign/molecules/terminal_scope_receipt.rb +0 -88
- data/lib/ace/assign/organisms/attempt_coordinator.rb +0 -1374
- data/lib/ace/assign/organisms/delivery_coordinator.rb +0 -342
- data/lib/ace/assign/organisms/ordinary_driver.rb +0 -310
- 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,229 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
require_relative "../molecules/execution_scope_lineage"
|
|
3
|
-
require "json"
|
|
4
|
-
|
|
5
|
-
module Ace
|
|
6
|
-
module Assign
|
|
7
|
-
module Authority
|
|
8
|
-
# Source capabilities only. A serialized descriptor is not admission.
|
|
9
|
-
module MaintenanceWorkspaceRetirement
|
|
10
|
-
SCHEMA = "ace.maintenance-workspace-retirement/v1"
|
|
11
|
-
TARGET_FIELDS = %w[project_id mapping_id assignment_id attempt_id resource journal_commit binding_event_digest release_event_digest descriptor_sha256].freeze
|
|
12
|
-
INVOCATION_FIELDS = %w[request_id input_digest operation_owner_binding_digest dispatch_event_digest].freeze
|
|
13
|
-
REFERENCES = {
|
|
14
|
-
"captured" => %w[intent captured preservation],
|
|
15
|
-
"removed" => %w[intent captured preservation removal fence],
|
|
16
|
-
"completed" => %w[intent captured preservation removal fence receipt]
|
|
17
|
-
}.freeze
|
|
18
|
-
|
|
19
|
-
def self.immutable(value)
|
|
20
|
-
case value
|
|
21
|
-
when Hash then value.to_h { |key, item| [key.dup.freeze, immutable(item)] }.freeze
|
|
22
|
-
when Array then value.map { |item| immutable(item) }.freeze
|
|
23
|
-
when String then value.dup.freeze
|
|
24
|
-
else value.freeze
|
|
25
|
-
end
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
def self.unavailable!(message)
|
|
29
|
-
raise AttemptErrors::EvidenceUnavailable, message
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
class Evidence
|
|
33
|
-
attr_reader :descriptor, :owner
|
|
34
|
-
|
|
35
|
-
# Only the installed adapter mints this after validating its complete
|
|
36
|
-
# collection. Consumers additionally bind the exact adapter instance,
|
|
37
|
-
# original canonical projection and held operation before using it.
|
|
38
|
-
def self.mint!(owner:, descriptor:)
|
|
39
|
-
new(owner: owner, descriptor: descriptor)
|
|
40
|
-
end
|
|
41
|
-
private_class_method :new
|
|
42
|
-
|
|
43
|
-
def initialize(owner:, descriptor:)
|
|
44
|
-
@owner = owner
|
|
45
|
-
MaintenanceWorkspaceRetirement.unavailable!("retirement evidence descriptor is not an object") unless descriptor.is_a?(Hash)
|
|
46
|
-
@descriptor = MaintenanceWorkspaceRetirement.immutable(descriptor)
|
|
47
|
-
@thread = Thread.current
|
|
48
|
-
@consumption = nil
|
|
49
|
-
@expired = false
|
|
50
|
-
validate_descriptor!
|
|
51
|
-
rescue KeyError, TypeError, ArgumentError
|
|
52
|
-
MaintenanceWorkspaceRetirement.unavailable!("retirement evidence descriptor differs")
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
def verify!(owner:, target:, projection:, phase:, deadline:)
|
|
56
|
-
unless !@expired && @thread.equal?(Thread.current) && @owner.equal?(owner) && descriptor.fetch("target") == target &&
|
|
57
|
-
descriptor.fetch("phase") == phase && descriptor.fetch("resource") == projection.fetch("workspace_resource")
|
|
58
|
-
MaintenanceWorkspaceRetirement.unavailable!("retirement evidence original owner or selection differs")
|
|
59
|
-
end
|
|
60
|
-
identities = projection.fetch("resource_identities")
|
|
61
|
-
root = projection.fetch("worker_cwd")
|
|
62
|
-
expected = projection.fetch("parent_resource_declarations").filter_map do |declaration|
|
|
63
|
-
path = declaration.fetch("host_path")
|
|
64
|
-
next unless declaration.fetch("stage") == "parent" && (path == root || path.start_with?(root + "/"))
|
|
65
|
-
matches = identities.select { |row| row.values_at("host_path", "view_path") == declaration.values_at("host_path", "view_path") }
|
|
66
|
-
MaintenanceWorkspaceRetirement.unavailable!("retirement declared row has no unique original identity") unless matches.one?
|
|
67
|
-
matches.first
|
|
68
|
-
end.sort_by { |row| row.values_at("host_path", "view_path") }
|
|
69
|
-
unless descriptor.fetch("covered_resources").map { |row| row.fetch("original") } == expected
|
|
70
|
-
MaintenanceWorkspaceRetirement.unavailable!("retirement evidence covered resources are not exhaustive")
|
|
71
|
-
end
|
|
72
|
-
@owner.verify_retirement_evidence!(evidence: self, projection: projection, deadline: deadline)
|
|
73
|
-
true
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
def expire! = @expired = true
|
|
77
|
-
|
|
78
|
-
def covered_resource(path)
|
|
79
|
-
descriptor.fetch("covered_resources").find { |entry| entry.fetch("original").fetch("host_path") == path }
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
def verify_consumption!
|
|
83
|
-
unless !@expired && @consumption && @thread.equal?(Thread.current)
|
|
84
|
-
MaintenanceWorkspaceRetirement.unavailable!("retirement evidence is outside its held observation")
|
|
85
|
-
end
|
|
86
|
-
true
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
def with_consumption(verification)
|
|
90
|
-
MaintenanceWorkspaceRetirement.unavailable!("retirement observation cannot be nested") if @consumption
|
|
91
|
-
installed = true
|
|
92
|
-
@consumption = verification
|
|
93
|
-
verify_consumption!
|
|
94
|
-
verification.call
|
|
95
|
-
result = yield
|
|
96
|
-
verify_consumption!
|
|
97
|
-
verification.call
|
|
98
|
-
result
|
|
99
|
-
ensure
|
|
100
|
-
@consumption = nil if installed
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
private
|
|
104
|
-
|
|
105
|
-
def validate_descriptor!
|
|
106
|
-
fields = %w[schema phase target resource covered_resources invocation refs]
|
|
107
|
-
target = descriptor.fetch("target")
|
|
108
|
-
invocation = descriptor.fetch("invocation")
|
|
109
|
-
phase = descriptor.fetch("phase")
|
|
110
|
-
unless descriptor.keys.sort == fields.sort && descriptor.fetch("schema") == SCHEMA && REFERENCES.key?(phase) &&
|
|
111
|
-
target.is_a?(Hash) && target.keys.sort == TARGET_FIELDS.sort &&
|
|
112
|
-
invocation.is_a?(Hash) && invocation.keys.sort == INVOCATION_FIELDS.sort
|
|
113
|
-
MaintenanceWorkspaceRetirement.unavailable!("retirement evidence is not a closed supported descriptor")
|
|
114
|
-
end
|
|
115
|
-
resource!(descriptor.fetch("resource"))
|
|
116
|
-
unless target.fetch("resource") == "workspace:#{target.fetch('project_id')}:#{target.fetch('mapping_id')}:#{target.fetch('assignment_id')}" &&
|
|
117
|
-
%w[project_id mapping_id assignment_id attempt_id].all? { |key| token?(target[key]) } &&
|
|
118
|
-
target.fetch("journal_commit").is_a?(String) && target.fetch("journal_commit").match?(/\A[0-9a-f]{40}\z/) &&
|
|
119
|
-
%w[binding_event_digest release_event_digest descriptor_sha256].all? { |key| digest?(target[key]) } &&
|
|
120
|
-
token?(invocation.fetch("request_id")) && (INVOCATION_FIELDS - ["request_id"]).all? { |key| digest?(invocation[key]) }
|
|
121
|
-
MaintenanceWorkspaceRetirement.unavailable!("retirement evidence selectors differ")
|
|
122
|
-
end
|
|
123
|
-
covered = descriptor.fetch("covered_resources")
|
|
124
|
-
unless covered.is_a?(Array) && covered.size.between?(1, 64)
|
|
125
|
-
MaintenanceWorkspaceRetirement.unavailable!("retirement evidence covered inventory differs")
|
|
126
|
-
end
|
|
127
|
-
covered.each do |entry|
|
|
128
|
-
unless entry.is_a?(Hash) && entry.keys.sort == %w[captured_host_path original] && path?(entry["captured_host_path"])
|
|
129
|
-
MaintenanceWorkspaceRetirement.unavailable!("retirement evidence covered row differs")
|
|
130
|
-
end
|
|
131
|
-
resource!(entry.fetch("original"))
|
|
132
|
-
end
|
|
133
|
-
unless covered.map { |entry| entry.fetch("original") }.uniq.size == covered.size &&
|
|
134
|
-
covered.map { |entry| entry.fetch("captured_host_path") }.uniq.size == covered.size
|
|
135
|
-
MaintenanceWorkspaceRetirement.unavailable!("retirement evidence covered rows repeat")
|
|
136
|
-
end
|
|
137
|
-
roots = covered.select { |entry| entry.fetch("original") == descriptor.fetch("resource") }
|
|
138
|
-
unless roots.one? && covered.all? { |entry|
|
|
139
|
-
original = entry.fetch("original").fetch("host_path")
|
|
140
|
-
root = descriptor.fetch("resource").fetch("host_path")
|
|
141
|
-
(original == root || original.start_with?(root + "/")) &&
|
|
142
|
-
entry.fetch("captured_host_path") == roots.first.fetch("captured_host_path") + original.delete_prefix(root) }
|
|
143
|
-
MaintenanceWorkspaceRetirement.unavailable!("retirement evidence captured paths do not match exact relative resources")
|
|
144
|
-
end
|
|
145
|
-
refs = descriptor.fetch("refs")
|
|
146
|
-
unless refs.is_a?(Hash) && refs.keys.sort == REFERENCES.fetch(phase).sort
|
|
147
|
-
MaintenanceWorkspaceRetirement.unavailable!("retirement evidence reference set differs")
|
|
148
|
-
end
|
|
149
|
-
refs.each_value do |ref|
|
|
150
|
-
unless ref.is_a?(Hash) && ref.keys.sort == %w[bytes path sha256] && path?(ref["path"]) && digest?(ref["sha256"]) &&
|
|
151
|
-
ref["bytes"].is_a?(Integer) && ref["bytes"].between?(1, 64 * 1024 * 1024)
|
|
152
|
-
MaintenanceWorkspaceRetirement.unavailable!("retirement evidence reference differs")
|
|
153
|
-
end
|
|
154
|
-
end
|
|
155
|
-
end
|
|
156
|
-
|
|
157
|
-
def token?(value) = value.is_a?(String) && value.match?(/\A[A-Za-z0-9][A-Za-z0-9._:-]{0,127}\z/)
|
|
158
|
-
def digest?(value) = value.is_a?(String) && value.match?(/\A[0-9a-f]{64}\z/)
|
|
159
|
-
def path?(value)
|
|
160
|
-
value.is_a?(String) && value.valid_encoding? && value.bytesize.between?(1, 4096) &&
|
|
161
|
-
value.start_with?("/") && !value.include?("\0") && File.expand_path(value) == value
|
|
162
|
-
end
|
|
163
|
-
def resource!(value)
|
|
164
|
-
unless value.is_a?(Hash) && value.keys.sort == Molecules::ExecutionScopeLineage::RESOURCE_FIELDS.sort &&
|
|
165
|
-
%w[host_path view_path].all? { |key| path?(value[key]) } &&
|
|
166
|
-
%w[mount_id device inode].all? { |key| value[key].is_a?(Integer) && value[key].positive? } &&
|
|
167
|
-
%w[uid gid].all? { |key| value[key].is_a?(Integer) && value[key] >= 0 } &&
|
|
168
|
-
%w[ext4 xfs btrfs tmpfs].include?(value["filesystem_type"])
|
|
169
|
-
MaintenanceWorkspaceRetirement.unavailable!("retirement evidence resource identity differs")
|
|
170
|
-
end
|
|
171
|
-
end
|
|
172
|
-
end
|
|
173
|
-
class Session
|
|
174
|
-
attr_reader :projection
|
|
175
|
-
def self.open!(**arguments) = new(**arguments)
|
|
176
|
-
private_class_method :new
|
|
177
|
-
|
|
178
|
-
def initialize(lifecycle:, mapping_id:, journal:, commit:, target:, projection:, evidence_owner:, deadline:, contexts:)
|
|
179
|
-
@lifecycle, @mapping_id, @journal, @commit = lifecycle, mapping_id, journal, commit
|
|
180
|
-
@target, @projection = MaintenanceWorkspaceRetirement.immutable(target), projection
|
|
181
|
-
@owner, @deadline, @contexts = evidence_owner, deadline, contexts
|
|
182
|
-
@thread, @active, @phase = Thread.current, true, nil
|
|
183
|
-
@evidences = []
|
|
184
|
-
@writer = @owner.retirement_workspace_writer!
|
|
185
|
-
unless @writer.is_a?(Molecules::LifecycleExclusion::WorkspaceWriter) && @writer.projection == projection.fetch("workspace_exclusion")
|
|
186
|
-
MaintenanceWorkspaceRetirement.unavailable!("retirement session requires the original maintained workspace writer")
|
|
187
|
-
end
|
|
188
|
-
verify_held!
|
|
189
|
-
end
|
|
190
|
-
|
|
191
|
-
def verify_captured!(evidence:) = verify_phase!("captured", evidence)
|
|
192
|
-
def verify_removed!(evidence:) = verify_phase!("removed", evidence)
|
|
193
|
-
def close!
|
|
194
|
-
@active = false
|
|
195
|
-
@evidences.each(&:expire!)
|
|
196
|
-
end
|
|
197
|
-
|
|
198
|
-
private
|
|
199
|
-
|
|
200
|
-
def verify_held!
|
|
201
|
-
unless @active && @thread.equal?(Thread.current) &&
|
|
202
|
-
Thread.current[:ace_assign_maintenance_contexts]&.[](@lifecycle.object_id).equal?(@contexts)
|
|
203
|
-
MaintenanceWorkspaceRetirement.unavailable!("retirement session no longer owns complete maintenance contexts")
|
|
204
|
-
end
|
|
205
|
-
@lifecycle.send(:maintenance_deadline!, @deadline)
|
|
206
|
-
@lifecycle.send(:require_maintenance_context!, @mapping_id, @journal, @commit)
|
|
207
|
-
@writer.verify_unchanged!
|
|
208
|
-
end
|
|
209
|
-
|
|
210
|
-
def verify_phase!(phase, evidence)
|
|
211
|
-
unless evidence.is_a?(Evidence) && (phase == "captured" && @phase.nil? || phase == "removed" && @phase == "captured")
|
|
212
|
-
MaintenanceWorkspaceRetirement.unavailable!("retirement session phase or evidence differs")
|
|
213
|
-
end
|
|
214
|
-
@evidences << evidence
|
|
215
|
-
verify = -> {
|
|
216
|
-
verify_held!
|
|
217
|
-
evidence.verify!(owner: @owner, target: @target, projection: @projection, phase: phase, deadline: @deadline)
|
|
218
|
-
}
|
|
219
|
-
evidence.with_consumption(verify) do
|
|
220
|
-
@lifecycle.send(:verify_retirement_contexts!, evidence: evidence, deadline: @deadline)
|
|
221
|
-
end
|
|
222
|
-
@phase = phase
|
|
223
|
-
true
|
|
224
|
-
end
|
|
225
|
-
end
|
|
226
|
-
end
|
|
227
|
-
end
|
|
228
|
-
end
|
|
229
|
-
end
|
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Ace
|
|
4
|
-
module Assign
|
|
5
|
-
module Authority
|
|
6
|
-
module OrdinaryAttemptStart
|
|
7
|
-
START_KEYS = %w[assignment_id attempt_id expected_generation head pane scope].freeze
|
|
8
|
-
|
|
9
|
-
private
|
|
10
|
-
|
|
11
|
-
def start_attempt(request, peer, role, deadline)
|
|
12
|
-
params = request.fetch("params")
|
|
13
|
-
strict!(params, START_KEYS)
|
|
14
|
-
%w[assignment_id attempt_id].each { |key| token!(params.fetch(key)) }
|
|
15
|
-
if params.fetch("attempt_id").start_with?("definition-")
|
|
16
|
-
raise ArgumentError, "ordinary attempt ID uses the registration namespace"
|
|
17
|
-
end
|
|
18
|
-
token!(request.fetch("mutation_id"))
|
|
19
|
-
unless params.fetch("head").is_a?(String) && params.fetch("head").match?(/\A[0-9a-f]{40}\z/)
|
|
20
|
-
raise ArgumentError, "ordinary start HEAD is invalid"
|
|
21
|
-
end
|
|
22
|
-
unless %i[supervisor worker].include?(role) && params.fetch("expected_generation").is_a?(Integer) && params.fetch("expected_generation") == 0
|
|
23
|
-
raise AttemptErrors::UnauthorizedIdentity, "ordinary start requires an installed worker and a new attempt"
|
|
24
|
-
end
|
|
25
|
-
commit = @journal.ref_value
|
|
26
|
-
registered = registration(params.fetch("assignment_id"), commit: commit)
|
|
27
|
-
raise AttemptErrors::NotFound, "ordinary work is not registered" unless registered
|
|
28
|
-
work = registered_work(registered, commit, deadline)
|
|
29
|
-
scope = params.fetch("scope")
|
|
30
|
-
unless work.manifest.fetch("steps").any? { |step| step.fetch("number") == scope }
|
|
31
|
-
raise AttemptErrors::InvalidScope, "ordinary scope is not in the accepted work"
|
|
32
|
-
end
|
|
33
|
-
keys = [@exclusion.task_key(registered.fetch("task_id")), @exclusion.assignment_key(params.fetch("assignment_id"))]
|
|
34
|
-
@exclusion.with_shared_multi(keys, deadline: deadline) do
|
|
35
|
-
if @journal.mutation_result(request.fetch("mutation_id"))
|
|
36
|
-
# Work may legitimately have advanced since an acknowledged or
|
|
37
|
-
# lost start reply. Recover its original head, never recapture it.
|
|
38
|
-
identity = @admission.identity!(peer: peer, pane: params.fetch("pane"), deadline: deadline)
|
|
39
|
-
return @journal.mutate(assignment_id: params.fetch("assignment_id"), attempt_id: params.fetch("attempt_id"),
|
|
40
|
-
mutation_id: request.fetch("mutation_id"), operation: "start_attempt", parameters_digest: start_digest(params, peer, identity),
|
|
41
|
-
expected_generation: 0, with_replay: true, deadline: deadline,
|
|
42
|
-
replay_authorizer: -> { authorize_start_replay!(request, peer, role, deadline) }) do
|
|
43
|
-
raise AttemptErrors::EvidenceUnavailable, "accepted ordinary start disappeared"
|
|
44
|
-
end
|
|
45
|
-
end
|
|
46
|
-
# Only the installed admission can observe the actual native owner
|
|
47
|
-
# and worker checkout. It performs no launch or delivery effect.
|
|
48
|
-
observed = @admission.execution!(peer: peer, pane: params.fetch("pane"), head: params.fetch("head"), deadline: deadline)
|
|
49
|
-
identity = observed.fetch(:identity)
|
|
50
|
-
principal = {"uid" => peer.fetch("uid"), "actor" => identity.actor}
|
|
51
|
-
digest = start_digest(params, peer, identity)
|
|
52
|
-
authorize!(request, peer, role, deadline)
|
|
53
|
-
@journal.mutate(assignment_id: params.fetch("assignment_id"), attempt_id: params.fetch("attempt_id"),
|
|
54
|
-
mutation_id: request.fetch("mutation_id"), operation: "start_attempt", parameters_digest: digest,
|
|
55
|
-
expected_generation: 0, with_replay: true, deadline: deadline,
|
|
56
|
-
replay_authorizer: -> { authorize_start_replay!(request, peer, role, deadline) }) do |events, prefix, _generation|
|
|
57
|
-
authorize!(request, peer, role, deadline)
|
|
58
|
-
unless @admission.identity!(peer: peer, pane: params.fetch("pane"), deadline: deadline) == identity
|
|
59
|
-
raise AttemptErrors::UnauthorizedIdentity, "ordinary native worker changed before start acceptance"
|
|
60
|
-
end
|
|
61
|
-
unless events.empty? && registration(params.fetch("assignment_id"), commit: prefix) == registered
|
|
62
|
-
raise AttemptErrors::Conflict, "ordinary start registration or attempt changed"
|
|
63
|
-
end
|
|
64
|
-
if @journal.assignment_ids(commit: prefix).any? { |assignment|
|
|
65
|
-
assignment != params.fetch("assignment_id") && @journal.read_events(assignment, commit: prefix).any? { |event|
|
|
66
|
-
event.fetch("attempt_id") == params.fetch("attempt_id") } }
|
|
67
|
-
raise AttemptErrors::Conflict, "ordinary attempt ID belongs to another assignment"
|
|
68
|
-
end
|
|
69
|
-
# Sibling scopes may run independently. A different request ID
|
|
70
|
-
# never repeats an already consumed scope or any part of it.
|
|
71
|
-
if @journal.derived_attempts(params.fetch("assignment_id"), commit: prefix).any? { |attempt|
|
|
72
|
-
Atoms::AssignmentScope.overlap?(attempt.binding.scope, scope) }
|
|
73
|
-
raise AttemptErrors::Conflict, "ordinary scope already has an attempt; reconcile its original execution"
|
|
74
|
-
end
|
|
75
|
-
record = current_queue_record(registered, prefix)
|
|
76
|
-
queue = OrdinaryQueue.new(work: work, root: @input_root, bytes: queue_bytes(record, prefix))
|
|
77
|
-
.transition(operation: :activate, scope: scope)
|
|
78
|
-
queue_sha = Digest::SHA256.hexdigest(queue.bytes)
|
|
79
|
-
queue_path = "execution/definitions/#{params.fetch('assignment_id')}-queue-#{queue_sha}.json"
|
|
80
|
-
{events: [
|
|
81
|
-
{type: "intent", payload: {"assignment_id" => params.fetch("assignment_id"), "scope" => scope,
|
|
82
|
-
"project_id" => @project, "task_id" => registered.fetch("task_id"), "base_head" => observed.fetch(:checkout).fetch("head")}},
|
|
83
|
-
{type: "process_start", payload: {"actor" => identity.actor, "role" => identity.role, "runtime" => identity.runtime,
|
|
84
|
-
"pid" => identity.process_pid, "process_identity" => identity.runtime_binding.fetch("process_identity"),
|
|
85
|
-
"runtime_binding" => identity.runtime_binding, "checkout" => observed.fetch(:checkout)}}
|
|
86
|
-
], blobs: {queue_path => queue.bytes}, data: {
|
|
87
|
-
"authority" => "ordinary", "assignment_id" => params.fetch("assignment_id"), "attempt_id" => params.fetch("attempt_id"),
|
|
88
|
-
"project_id" => @project, "scope" => scope, "state" => "running", "started_by" => principal,
|
|
89
|
-
"base_head" => observed.fetch(:checkout).fetch("head"), "runtime_binding" => identity.runtime_binding,
|
|
90
|
-
"queue_ref" => queue_path, "queue_sha256" => queue_sha}}
|
|
91
|
-
end
|
|
92
|
-
end
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
def start_digest(params, peer, identity)
|
|
96
|
-
Atoms::EvidenceDigest.digest({"params" => params, "project_id" => @project,
|
|
97
|
-
"principal" => {"uid" => peer.fetch("uid"), "actor" => identity.actor}})
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
def authorize_start_replay!(request, peer, role, deadline)
|
|
101
|
-
authorize!(request, peer, role, deadline)
|
|
102
|
-
params = request.fetch("params")
|
|
103
|
-
identity = @admission.identity!(peer: peer, pane: params.fetch("pane"), deadline: deadline)
|
|
104
|
-
start_replay_owner!(params, identity)
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
def registered_work(registered, commit, deadline)
|
|
108
|
-
reference = registered.fetch("prepared_work")
|
|
109
|
-
bytes = @journal.bounded_blob(registered.fetch("prepared_bundle_ref"), commit: commit,
|
|
110
|
-
max_bytes: registered.fetch("prepared_bundle_bytes"))
|
|
111
|
-
work = PreparedWork.admit(bytes: bytes, head: reference.fetch("prepared_head"), tree: reference.fetch("prepared_tree"),
|
|
112
|
-
sha256: registered.fetch("prepared_bundle_sha256"), size: registered.fetch("prepared_bundle_bytes"),
|
|
113
|
-
root: @input_root, deadline: deadline)
|
|
114
|
-
unless work.reference(head: reference.fetch("prepared_head"), tree: reference.fetch("prepared_tree")) == reference &&
|
|
115
|
-
work.manifest.values_at("assignment_id", "project_id", "task_id") == registered.values_at("assignment_id", "project_id", "task_id") &&
|
|
116
|
-
Digest::SHA256.hexdigest(work.definition_bytes(head: reference.fetch("prepared_head"), tree: reference.fetch("prepared_tree"))) == registered.fetch("definition_digest")
|
|
117
|
-
raise AttemptErrors::EvidenceUnavailable, "ordinary prepared registration bytes differ"
|
|
118
|
-
end
|
|
119
|
-
work
|
|
120
|
-
end
|
|
121
|
-
|
|
122
|
-
def start_replay_owner!(params, identity)
|
|
123
|
-
events = @journal.read_events(params.fetch("assignment_id"))
|
|
124
|
-
.select { |event| event.fetch("attempt_id") == params.fetch("attempt_id") }
|
|
125
|
-
unless Models::EvidenceEvent.chain_valid?(events) && events.count { |event| event["type"] == "process_start" } == 1
|
|
126
|
-
raise AttemptErrors::EvidenceUnavailable, "ordinary start evidence is unavailable"
|
|
127
|
-
end
|
|
128
|
-
owner = events.find { |event| event["type"] == "process_start" }.fetch("payload")
|
|
129
|
-
unless owner.values_at("actor", "role", "runtime", "runtime_binding") ==
|
|
130
|
-
[identity.actor, identity.role, identity.runtime, identity.runtime_binding]
|
|
131
|
-
raise AttemptErrors::UnauthorizedIdentity, "ordinary replay belongs to its original native worker"
|
|
132
|
-
end
|
|
133
|
-
end
|
|
134
|
-
end
|
|
135
|
-
end
|
|
136
|
-
end
|
|
137
|
-
end
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative "ordinary_peer"
|
|
4
|
-
require_relative "transfer_codec"
|
|
5
|
-
|
|
6
|
-
module Ace
|
|
7
|
-
module Assign
|
|
8
|
-
module Authority
|
|
9
|
-
# Installed selection is supplied by the caller's trusted composition.
|
|
10
|
-
# This client never selects a journal, local fallback or execution identity.
|
|
11
|
-
class OrdinaryClient
|
|
12
|
-
Reply = Struct.new(:data, :replayed, :parts, keyword_init: true)
|
|
13
|
-
|
|
14
|
-
def initialize(project_id:, service:, verify_selection:, peer: OrdinaryPeer.new, scratch_root: nil)
|
|
15
|
-
@project_id, @service, @verify_selection, @peer, @scratch_root = project_id, service, verify_selection, peer, scratch_root
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
def call(operation, params, mutation_id: nil, upload_parts: nil, download: false, purpose: nil, timeout: 30)
|
|
19
|
-
unless operation.is_a?(String) && params.is_a?(Hash) && !params.key?("transfer") &&
|
|
20
|
-
timeout.is_a?(Numeric) && timeout.finite? && timeout.positive?
|
|
21
|
-
raise ArgumentError, "invalid ordinary authority request"
|
|
22
|
-
end
|
|
23
|
-
raise ArgumentError, "ordinary download needs private scratch and forbids upload" if download && (upload_parts || !@scratch_root)
|
|
24
|
-
@verify_selection.call
|
|
25
|
-
wire = Ace::Runtime::Molecules::ProtectedSocket
|
|
26
|
-
path = @service.fetch("socket_path")
|
|
27
|
-
wire.root_path!(File.dirname(path), directory: true, owner: @service.fetch("uid"))
|
|
28
|
-
before = wire.socket_identity(path, mode: 0o660)
|
|
29
|
-
raise AttemptErrors::UnauthorizedIdentity, "ordinary endpoint owner differs" unless before.last == @service.fetch("uid")
|
|
30
|
-
codec = TransferCodec.new(root: download ? @scratch_root : nil)
|
|
31
|
-
descriptor = codec.descriptor(upload_parts, purpose: purpose) if upload_parts
|
|
32
|
-
parameters = descriptor ? params.merge("transfer" => descriptor) : params
|
|
33
|
-
deadline = wire.deadline([timeout, 30].min)
|
|
34
|
-
wire.connect(path, deadline: deadline) do |socket|
|
|
35
|
-
observed = @peer.capture(socket, deadline: deadline)
|
|
36
|
-
unless observed.values_at("uid", "gid") == @service.values_at("uid", "gid") && wire.socket_identity(path, mode: 0o660) == before
|
|
37
|
-
raise AttemptErrors::UnauthorizedIdentity, "ordinary endpoint peer changed"
|
|
38
|
-
end
|
|
39
|
-
@verify_selection.call
|
|
40
|
-
wire.write(socket, {"version" => 1, "operation" => operation, "mutation_id" => mutation_id,
|
|
41
|
-
"project_id" => @project_id, "params" => parameters}, deadline: deadline, limit: 16_384)
|
|
42
|
-
codec.send(socket, parts: upload_parts, descriptor: descriptor, purpose: purpose, deadline: deadline) if upload_parts
|
|
43
|
-
socket.shutdown(Socket::SHUT_WR)
|
|
44
|
-
ingress = wire::Ingress.new(socket)
|
|
45
|
-
result = wire.read(ingress, deadline: deadline, limit: 16_384)
|
|
46
|
-
unless result.is_a?(Hash) && result.keys.sort == %w[data status transport] && result["status"] == "ok" &&
|
|
47
|
-
result["data"].is_a?(Hash) && result["transport"].is_a?(Hash) &&
|
|
48
|
-
result["transport"].keys == ["replayed"] && [true, false].include?(result["transport"]["replayed"])
|
|
49
|
-
code = result.is_a?(Hash) && result["error"].is_a?(Hash) ? result["error"]["code"] : nil
|
|
50
|
-
raise AttemptErrors::EvidenceUnavailable, "ordinary authority refused (#{code || 'invalid_response'}); retain original mutation"
|
|
51
|
-
end
|
|
52
|
-
parts = if download
|
|
53
|
-
codec.receive(ingress, descriptor: result.fetch("data").fetch("transfer"), purpose: purpose, deadline: deadline) do |input|
|
|
54
|
-
Array.new(input.count) { |index| input.bytes(index: index) }
|
|
55
|
-
end
|
|
56
|
-
end
|
|
57
|
-
@verify_selection.call
|
|
58
|
-
@peer.live!(observed, deadline: deadline)
|
|
59
|
-
unless wire.socket_identity(path, mode: 0o660) == before
|
|
60
|
-
raise AttemptErrors::UnauthorizedIdentity, "ordinary endpoint changed before response acceptance"
|
|
61
|
-
end
|
|
62
|
-
Reply.new(data: result.fetch("data"), replayed: result.fetch("transport").fetch("replayed"), parts: parts)
|
|
63
|
-
end
|
|
64
|
-
rescue SystemCallError, IOError, Ace::Runtime::RuntimeUnavailableError
|
|
65
|
-
raise AttemptErrors::EvidenceUnavailable, "ordinary authority outcome unavailable; retain original mutation; no local fallback"
|
|
66
|
-
end
|
|
67
|
-
end
|
|
68
|
-
end
|
|
69
|
-
end
|
|
70
|
-
end
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative "ordinary_effect_settlement"
|
|
4
|
-
require_relative "../organisms/attempt_coordinator"
|
|
5
|
-
|
|
6
|
-
module Ace
|
|
7
|
-
module Assign
|
|
8
|
-
module Authority
|
|
9
|
-
# Terminal acceptance consumes the submitted canonical receipt. The
|
|
10
|
-
# supervisor cannot replace its bytes, worker, scope or candidate.
|
|
11
|
-
module OrdinaryCompletion
|
|
12
|
-
FINISH_KEYS = %w[assignment_id attempt_id expected_generation pane head result_id receipt_digest].freeze
|
|
13
|
-
|
|
14
|
-
private
|
|
15
|
-
|
|
16
|
-
def finish_result(request, peer, role, deadline)
|
|
17
|
-
params = request.fetch("params")
|
|
18
|
-
strict!(params, FINISH_KEYS)
|
|
19
|
-
%w[assignment_id attempt_id result_id].each { |key| token!(params.fetch(key)) }
|
|
20
|
-
token!(request.fetch("mutation_id"))
|
|
21
|
-
unless role == :supervisor
|
|
22
|
-
raise AttemptErrors::UnauthorizedIdentity, "ordinary completion requires the installed supervisor"
|
|
23
|
-
end
|
|
24
|
-
unless params.fetch("expected_generation").is_a?(Integer) && params.fetch("expected_generation").positive? &&
|
|
25
|
-
params.fetch("head").is_a?(String) && params.fetch("head").match?(/\A[0-9a-f]{40}\z/) &&
|
|
26
|
-
params.fetch("receipt_digest").is_a?(String) && params.fetch("receipt_digest").match?(/\A[0-9a-f]{64}\z/)
|
|
27
|
-
raise ArgumentError, "ordinary completion selection is invalid"
|
|
28
|
-
end
|
|
29
|
-
registered = registration(params.fetch("assignment_id"), commit: @journal.ref_value)
|
|
30
|
-
raise AttemptErrors::NotFound, "ordinary work is not registered" unless registered
|
|
31
|
-
verify_result_owner!(request, peer, role, deadline, registered)
|
|
32
|
-
digest = Atoms::EvidenceDigest.digest({"params" => params, "project_id" => @project, "peer_uid" => peer.fetch("uid")})
|
|
33
|
-
@exclusion.with_shared_multi([@exclusion.task_key(registered.fetch("task_id"))], deadline: deadline) do
|
|
34
|
-
@exclusion.with_exclusive(@exclusion.assignment_key(params.fetch("assignment_id")), deadline: deadline) do
|
|
35
|
-
authorize!(request, peer, role, deadline)
|
|
36
|
-
with_result_transport(request, deadline) do |transport|
|
|
37
|
-
@journal.mutate(assignment_id: params.fetch("assignment_id"), attempt_id: params.fetch("attempt_id"),
|
|
38
|
-
mutation_id: request.fetch("mutation_id"), operation: "finish", parameters_digest: digest,
|
|
39
|
-
expected_generation: params.fetch("expected_generation"), with_replay: true, deadline: deadline,
|
|
40
|
-
replay_authorizer: -> { verify_result_owner!(request, peer, role, deadline, registered) }) do |events, prefix, _generation|
|
|
41
|
-
verify_result_owner!(request, peer, role, deadline, registered)
|
|
42
|
-
unless Molecules::CanonicalAttemptState.derive(events) == "running"
|
|
43
|
-
raise AttemptErrors::Conflict, "ordinary completion requires a running attempt"
|
|
44
|
-
end
|
|
45
|
-
result = retained_ordinary_result(params, events, prefix)
|
|
46
|
-
unless result && result.fetch("result_id") == params.fetch("result_id") &&
|
|
47
|
-
result.dig("receipt", "digest") == params.fetch("receipt_digest") && result.dig("binding", "head") == params.fetch("head")
|
|
48
|
-
raise AttemptErrors::ReceiptRejected, "ordinary completion requires its exact submitted result"
|
|
49
|
-
end
|
|
50
|
-
binding = result.fetch("binding")
|
|
51
|
-
observed = @admission.recorded_execution!(binding: binding.fetch("runtime_binding"), head: binding.fetch("head"), deadline: deadline)
|
|
52
|
-
unless observed.fetch(:runtime_binding) == binding.fetch("runtime_binding") && observed.fetch(:checkout) == binding.fetch("checkout")
|
|
53
|
-
raise AttemptErrors::UnauthorizedIdentity, "ordinary result worker checkout changed"
|
|
54
|
-
end
|
|
55
|
-
references = events.select { |event| event["type"] == "inbox_binding" }.map { |event| event.fetch("payload") }
|
|
56
|
-
delivery = transport.call(references: references, attempt_id: params.fetch("attempt_id"), owner: binding.fetch("runtime_binding"))
|
|
57
|
-
scope = events.find { |event| event["type"] == "intent" }.fetch("payload").fetch("scope")
|
|
58
|
-
work = registered_work(registered, prefix, deadline)
|
|
59
|
-
queue = OrdinaryQueue.new(work: work, root: @input_root, bytes: queue_bytes(current_queue_record(registered, prefix), prefix))
|
|
60
|
-
verdict = result.dig("receipt", "verdict")
|
|
61
|
-
complete = verdict == "succeeded" ? queue.status.subtree_complete?(scope) : queue.status.subtree_failed?(scope)
|
|
62
|
-
raise AttemptErrors::Conflict, "ordinary result scope has unfinished progress" unless complete
|
|
63
|
-
settlement = OrdinaryEffectSettlement.new(journal: @journal).verify!(project: @project,
|
|
64
|
-
assignment: params.fetch("assignment_id"), attempt: params.fetch("attempt_id"), events: events, commit: prefix)
|
|
65
|
-
verify_result_owner!(request, peer, role, deadline, registered)
|
|
66
|
-
plan = Organisms::AttemptCoordinator.finished_transition_plan(events: events, receipt: result.fetch("receipt"), authority: :ordinary)
|
|
67
|
-
plan.merge(data: params.slice("assignment_id", "attempt_id", "result_id", "head", "receipt_digest").merge(
|
|
68
|
-
"authority" => "ordinary", "scope" => scope, "state" => verdict, "settlement" => settlement, "inbox_deliveries" => delivery))
|
|
69
|
-
end
|
|
70
|
-
end
|
|
71
|
-
end
|
|
72
|
-
end
|
|
73
|
-
rescue Ace::Herdr::Error => error
|
|
74
|
-
raise AttemptErrors::EvidenceUnavailable, "ordinary transport settlement is unavailable: #{error.message}"
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
def verify_result_owner!(request, peer, role, deadline, registered)
|
|
78
|
-
authorize!(request, peer, role, deadline)
|
|
79
|
-
supervisor = @admission.identity!(peer: peer, pane: request.fetch("params").fetch("pane"), deadline: deadline)
|
|
80
|
-
unless supervisor.role == "coordinator" &&
|
|
81
|
-
supervisor.runtime_binding == supervisor_selection(registered, @journal.ref_value).fetch("binding")
|
|
82
|
-
raise AttemptErrors::UnauthorizedIdentity, "ordinary completion belongs to its original supervisor"
|
|
83
|
-
end
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
def with_result_transport(request, deadline)
|
|
87
|
-
if @journal.mutation_result(request.fetch("mutation_id"), commit: @journal.ref_value)
|
|
88
|
-
# A terminal reply is immutable. Current peer authority is still
|
|
89
|
-
# checked by mutate, but retired transport files are not replay input.
|
|
90
|
-
return yield nil
|
|
91
|
-
end
|
|
92
|
-
if @inbox
|
|
93
|
-
@inbox.with_delivery_settlement(deadline: deadline) { |verify| yield verify }
|
|
94
|
-
else
|
|
95
|
-
yield lambda { |references:, **_|
|
|
96
|
-
raise AttemptErrors::EvidenceUnavailable, "ordinary Inbox owner is not installed" unless references.empty?
|
|
97
|
-
[]
|
|
98
|
-
}
|
|
99
|
-
end
|
|
100
|
-
end
|
|
101
|
-
end
|
|
102
|
-
end
|
|
103
|
-
end
|
|
104
|
-
end
|