ace-assign 0.71.0 → 0.73.1
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 +5 -2116
- 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/molecules/runtime_control_surface_runner.rb +6 -2
- data/lib/ace/assign/molecules/skill_assign_source_resolver.rb +9 -0
- 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 +22 -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,115 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "tmpdir"
|
|
4
|
-
require "fileutils"
|
|
5
|
-
require_relative "prepared_work"
|
|
6
|
-
require_relative "private_directory"
|
|
7
|
-
require_relative "../organisms/assignment_executor"
|
|
8
|
-
|
|
9
|
-
module Ace
|
|
10
|
-
module Assign
|
|
11
|
-
module Authority
|
|
12
|
-
# A disposable projection for the existing queue engine. Only bytes read
|
|
13
|
-
# from (or about to be committed to) the canonical execution ref enter it.
|
|
14
|
-
# No files left by an earlier coordinator are recovery input.
|
|
15
|
-
class OrdinaryQueue
|
|
16
|
-
attr_reader :bytes
|
|
17
|
-
|
|
18
|
-
def initialize(work:, root:, bytes: nil)
|
|
19
|
-
raise ArgumentError, "ordinary queue requires prepared work" unless work.is_a?(PreparedWork)
|
|
20
|
-
@work, @root = work, PrivateDirectory.verify!(root)
|
|
21
|
-
value = if bytes
|
|
22
|
-
invalid! unless bytes.is_a?(String) && bytes.bytesize.between?(1, PreparedWork::MAX_TOTAL)
|
|
23
|
-
JSON.parse(bytes)
|
|
24
|
-
else
|
|
25
|
-
{"version" => 1, "steps" => work.manifest.fetch("steps").to_h { |step|
|
|
26
|
-
name = step.fetch("filename"); [name, work.files.fetch("steps/#{name}")]
|
|
27
|
-
}, "reports" => {}}
|
|
28
|
-
end
|
|
29
|
-
validate!(value, initial: bytes.nil?)
|
|
30
|
-
@bytes = Atoms::EvidenceDigest.canonical_json(value).freeze
|
|
31
|
-
rescue JSON::ParserError
|
|
32
|
-
invalid!
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
def status
|
|
36
|
-
value = JSON.parse(@bytes)
|
|
37
|
-
assignment = Models::Assignment.from_h(@work.definition, cache_dir: File.join(@root, @work.manifest.fetch("assignment_id")))
|
|
38
|
-
Molecules::QueueScanner.new.scan_captured(steps: value.fetch("steps"), reports: value.fetch("reports"), assignment: assignment)
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
# This returns proposed bytes only. The lifecycle owns authorization,
|
|
42
|
-
# generation, replay and publication together with its attempt events.
|
|
43
|
-
def transition(operation:, scope: @work.manifest.fetch("scope"), step: nil, report: nil, message: nil)
|
|
44
|
-
invalid! unless @work.manifest.fetch("steps").any? { |entry| entry.fetch("number") == scope }
|
|
45
|
-
value = JSON.parse(@bytes)
|
|
46
|
-
Dir.mktmpdir("ordinary-queue-", @root) do |scratch|
|
|
47
|
-
File.chmod(0o700, scratch)
|
|
48
|
-
directory = File.join(scratch, @work.manifest.fetch("assignment_id"))
|
|
49
|
-
%w[steps reports].each { |name| FileUtils.mkdir_p(File.join(directory, name), mode: 0700) }
|
|
50
|
-
assignment = Models::Assignment.from_h(@work.definition, cache_dir: directory)
|
|
51
|
-
write(File.join(directory, "assignment.yaml"), assignment.to_h.to_yaml)
|
|
52
|
-
write(File.join(directory, "job.yaml"), @work.files.fetch("job.yaml"))
|
|
53
|
-
%w[steps reports].each do |group|
|
|
54
|
-
value.fetch(group).each { |filename, content| write(File.join(directory, group, filename), content) }
|
|
55
|
-
end
|
|
56
|
-
executor = Organisms::AssignmentExecutor.new(cache_base: scratch, selected_assignment: assignment)
|
|
57
|
-
case operation
|
|
58
|
-
when :activate
|
|
59
|
-
state = executor.status.fetch(:state)
|
|
60
|
-
invalid! unless state.find_by_number(scope)&.status == :pending
|
|
61
|
-
executor.step_writer.mark_active(state.find_by_number(scope).file_path)
|
|
62
|
-
when :start
|
|
63
|
-
executor.start_step(step_number: step, fork_root: scope)
|
|
64
|
-
when :finish
|
|
65
|
-
executor.finish_step(report_content: report, step_number: step, fork_root: scope)
|
|
66
|
-
when :fail
|
|
67
|
-
executor.fail(message, fork_root: scope)
|
|
68
|
-
else
|
|
69
|
-
raise ArgumentError, "unknown ordinary queue transition"
|
|
70
|
-
end
|
|
71
|
-
result = {"version" => 1}
|
|
72
|
-
%w[steps reports].each do |group|
|
|
73
|
-
result[group] = Dir.children(File.join(directory, group)).to_h do |filename|
|
|
74
|
-
[filename, File.binread(File.join(directory, group, filename)).force_encoding(Encoding::UTF_8)]
|
|
75
|
-
end
|
|
76
|
-
end
|
|
77
|
-
self.class.new(work: @work, root: @root, bytes: Atoms::EvidenceDigest.canonical_json(result))
|
|
78
|
-
end
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
private
|
|
82
|
-
|
|
83
|
-
def validate!(value, initial:)
|
|
84
|
-
invalid! unless value.is_a?(Hash) && value.keys.sort == %w[reports steps version] && value["version"].is_a?(Integer) && value["version"] == 1 &&
|
|
85
|
-
value["steps"].is_a?(Hash) && value["reports"].is_a?(Hash)
|
|
86
|
-
expected = @work.manifest.fetch("steps").map { |step| step.fetch("filename") }
|
|
87
|
-
invalid! unless value.fetch("steps").keys.sort == expected.sort
|
|
88
|
-
reports = []
|
|
89
|
-
value.fetch("steps").each do |filename, content|
|
|
90
|
-
parsed = @work.parse_queue_step!(content)
|
|
91
|
-
progress = parsed.fetch(:frontmatter).keys & PreparedWork::PROGRESS
|
|
92
|
-
allowed = initial ? %w[status] : %w[status started_at completed_at error stall_reason]
|
|
93
|
-
invalid! unless (progress - allowed).empty?
|
|
94
|
-
original = @work.files.fetch("steps/#{filename}")
|
|
95
|
-
invalid! unless PreparedWork.work_projection(content) == PreparedWork.work_projection(original) &&
|
|
96
|
-
%w[pending active done failed].include?(parsed.fetch(:frontmatter).fetch("status", "pending"))
|
|
97
|
-
info = Atoms::StepFileParser.parse_filename(filename)
|
|
98
|
-
reports << Atoms::StepFileParser.generate_report_filename(info.fetch(:number), info.fetch(:name))
|
|
99
|
-
end
|
|
100
|
-
invalid! unless (value.fetch("reports").keys - reports).empty?
|
|
101
|
-
value.fetch("reports").each_value { |content| @work.parse_queue_step!(content) }
|
|
102
|
-
invalid! if Atoms::EvidenceDigest.canonical_json(value).bytesize > PreparedWork::MAX_TOTAL
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
def write(path, content)
|
|
106
|
-
File.open(path, File::WRONLY | File::CREAT | File::EXCL | File::NOFOLLOW, 0600) { |file| file.write(content) }
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
def invalid!
|
|
110
|
-
raise AttemptErrors::EvidenceUnavailable, "ordinary canonical queue differs from its accepted work"
|
|
111
|
-
end
|
|
112
|
-
end
|
|
113
|
-
end
|
|
114
|
-
end
|
|
115
|
-
end
|
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative "release_artifact_manifest"
|
|
4
|
-
require_relative "candidate_transfer"
|
|
5
|
-
|
|
6
|
-
module Ace
|
|
7
|
-
module Assign
|
|
8
|
-
module Authority
|
|
9
|
-
# Retention is a canonical input, never a publication permit or result.
|
|
10
|
-
# Native/candidate/review and release policy are rechecked at every CAS.
|
|
11
|
-
module OrdinaryReleaseArtifacts
|
|
12
|
-
RELEASE_OPERATIONS = %w[retain_release_artifact].freeze
|
|
13
|
-
RELEASE_TRANSFERS = {"retain_release_artifact" =>
|
|
14
|
-
{direction: :upload, purpose: :release_artifact, roles: %i[worker supervisor]}}.freeze
|
|
15
|
-
RELEASE_PARAMETERS = %w[assignment_id attempt_id head candidate_generation pane expected_generation
|
|
16
|
-
authorization manifest transfer].freeze
|
|
17
|
-
|
|
18
|
-
# Read an accepted import at the current immutable canonical prefix only.
|
|
19
|
-
# Historical commit selection is not supported by the native origin reader.
|
|
20
|
-
# The later effect consumer must independently admit its fresh scope,
|
|
21
|
-
# candidate, review, authorization and fixed executor before dispatch.
|
|
22
|
-
def release_artifact_at!(events:, commit:, artifact_id:)
|
|
23
|
-
token!(artifact_id)
|
|
24
|
-
unless commit && Models::EvidenceEvent.chain_valid?(events)
|
|
25
|
-
raise AttemptErrors::EvidenceUnavailable, "release artifact history is unavailable"
|
|
26
|
-
end
|
|
27
|
-
accepted = events.select { |event| event["type"] == "authority_mutation" &&
|
|
28
|
-
event.dig("payload", "operation") == "retain_release_artifact" && event.dig("payload", "mutation_id") == artifact_id }
|
|
29
|
-
data = accepted.one? && accepted.first.dig("payload", "data")
|
|
30
|
-
unless data && data["artifact_id"] == artifact_id && data["input_digest"] == Atoms::EvidenceDigest.digest(data.fetch("input"))
|
|
31
|
-
raise AttemptErrors::EvidenceUnavailable, "release artifact acceptance is unavailable"
|
|
32
|
-
end
|
|
33
|
-
origin = data.fetch("producer_origin")
|
|
34
|
-
retained = @journal.read_events(origin.fetch("assignment_id"), commit: commit)
|
|
35
|
-
.select { |event| event["attempt_id"] == origin.fetch("attempt_id") }
|
|
36
|
-
unless retained == events && ordinary_origin_at!(events, commit, running: false) == origin
|
|
37
|
-
raise AttemptErrors::EvidenceUnavailable, "release artifact original scope differs"
|
|
38
|
-
end
|
|
39
|
-
context = release_evidence_context(data.fetch("input"), origin, data.fetch("authorization"), artifact_id)
|
|
40
|
-
bytes = Molecules::CanonicalEvidence.new(journal: @journal).read(data.fetch("artifact"), **context, commit: commit)
|
|
41
|
-
unless bytes.bytesize == data.dig("input", "publication", "artifact_bytes") &&
|
|
42
|
-
Digest::SHA256.hexdigest(bytes) == data.dig("input", "target", "artifact_digest")
|
|
43
|
-
raise AttemptErrors::EvidenceUnavailable, "release artifact retained bytes differ"
|
|
44
|
-
end
|
|
45
|
-
data.merge("bytes" => bytes)
|
|
46
|
-
rescue KeyError, TypeError
|
|
47
|
-
raise AttemptErrors::EvidenceUnavailable, "release artifact acceptance is incomplete"
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
private
|
|
51
|
-
|
|
52
|
-
def validate_ordinary_release!(request)
|
|
53
|
-
params = request.fetch("params")
|
|
54
|
-
strict!(params, RELEASE_PARAMETERS)
|
|
55
|
-
%w[assignment_id attempt_id authorization].each { |key| token!(params.fetch(key)) }
|
|
56
|
-
token!(request.fetch("mutation_id"))
|
|
57
|
-
unless params["head"].is_a?(String) && CandidateTransfer::SHA.match?(params["head"]) &&
|
|
58
|
-
%w[candidate_generation expected_generation].all? { |key| params[key].is_a?(Integer) && params[key].positive? }
|
|
59
|
-
raise ArgumentError, "invalid release artifact candidate selection"
|
|
60
|
-
end
|
|
61
|
-
ReleaseArtifactManifest.validate!(params.fetch("manifest"))
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
def authorize_ordinary_release!(request, peer, role, deadline)
|
|
65
|
-
validate_ordinary_release!(request)
|
|
66
|
-
params = request.fetch("params")
|
|
67
|
-
with_scope(assignment: params.fetch("assignment_id"), attempt: params.fetch("attempt_id"), deadline: deadline) do
|
|
68
|
-
prefix = @journal.ref_value
|
|
69
|
-
release_context_at!(request, peer, role, deadline, result_events(params, prefix), prefix)
|
|
70
|
-
end
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
# Caller holds lifecycle exclusion before entering the journal writer.
|
|
74
|
-
# This method never acquires another lifecycle lock or trusts a path.
|
|
75
|
-
def release_context_at!(request, peer, role, deadline, events, commit)
|
|
76
|
-
authorize!(request, peer, role, deadline)
|
|
77
|
-
unless %i[worker supervisor].include?(role) && @release_authorizer
|
|
78
|
-
raise AttemptErrors::UnauthorizedIdentity, "installed release artifact authorization is unavailable"
|
|
79
|
-
end
|
|
80
|
-
params = request.fetch("params")
|
|
81
|
-
origin = origin_at!(events: events, commit: commit)
|
|
82
|
-
current = candidate_at!(events: events, commit: commit, head: params.fetch("head"), generation: params.fetch("candidate_generation"))
|
|
83
|
-
reference = candidate_reference_at!(events: events, commit: commit, head: params.fetch("head"), generation: params.fetch("candidate_generation"))
|
|
84
|
-
observed = @admission.execution!(peer: peer, pane: params.fetch("pane"), head: current.fetch("head"), deadline: deadline)
|
|
85
|
-
unless observed.fetch(:identity).runtime_binding == origin.fetch("runtime_binding")
|
|
86
|
-
raise AttemptErrors::UnauthorizedIdentity, "release artifact producer native identity changed"
|
|
87
|
-
end
|
|
88
|
-
review_at!(events: events, commit: commit, head: current.fetch("head"), generation: current.fetch("candidate_generation"))
|
|
89
|
-
input = ReleaseArtifactManifest.publication_input(manifest: params.fetch("manifest"), candidate: current, reference: reference)
|
|
90
|
-
binding = params.slice("assignment_id", "attempt_id", "authorization").merge(
|
|
91
|
-
"operation" => "publish", "project_id" => @project, "candidate_head" => current.fetch("head"),
|
|
92
|
-
"caller_uid" => origin.dig("runtime_binding", "process_identity", "uid"),
|
|
93
|
-
"input_digest" => Atoms::EvidenceDigest.digest(input), "target" => input.fetch("target"))
|
|
94
|
-
unless @release_authorizer.call(binding)
|
|
95
|
-
raise AttemptErrors::UnauthorizedIdentity, "release artifact manifest is not authorized"
|
|
96
|
-
end
|
|
97
|
-
{input: input, binding: binding, origin: origin}
|
|
98
|
-
rescue SecurityError
|
|
99
|
-
raise AttemptErrors::UnauthorizedIdentity, "release artifact manifest authorization is unavailable"
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
def retain_release_artifact(request, peer, role, deadline, transfer)
|
|
103
|
-
validate_ordinary_release!(request)
|
|
104
|
-
unless transfer && transfer.count == 1
|
|
105
|
-
raise AttemptErrors::MalformedTransfer, "release artifact requires its one bounded binary transfer"
|
|
106
|
-
end
|
|
107
|
-
params = request.fetch("params")
|
|
108
|
-
with_scope(assignment: params.fetch("assignment_id"), attempt: params.fetch("attempt_id"), deadline: deadline) do
|
|
109
|
-
prefix = @journal.ref_value
|
|
110
|
-
release_context_at!(request, peer, role, deadline, result_events(params, prefix), prefix)
|
|
111
|
-
bytes = transfer.bytes.dup.freeze
|
|
112
|
-
ReleaseArtifactManifest.verify!(bytes, params.fetch("manifest"))
|
|
113
|
-
digest = Atoms::EvidenceDigest.digest("project_id" => @project, "params" => params)
|
|
114
|
-
recheck = lambda do
|
|
115
|
-
current = @journal.ref_value
|
|
116
|
-
release_context_at!(request, peer, role, deadline, result_events(params, current), current)
|
|
117
|
-
end
|
|
118
|
-
@journal.mutate(assignment_id: params.fetch("assignment_id"), attempt_id: params.fetch("attempt_id"),
|
|
119
|
-
mutation_id: request.fetch("mutation_id"), operation: "retain_release_artifact", parameters_digest: digest,
|
|
120
|
-
expected_generation: params.fetch("expected_generation"), with_replay: true, deadline: deadline,
|
|
121
|
-
replay_authorizer: recheck) do |events, commit, _generation|
|
|
122
|
-
selected = release_context_at!(request, peer, role, deadline, events, commit)
|
|
123
|
-
artifact_id = request.fetch("mutation_id")
|
|
124
|
-
context = release_evidence_context(selected.fetch(:input), selected.fetch(:origin), params.fetch("authorization"), artifact_id)
|
|
125
|
-
plan = Molecules::CanonicalEvidence.new(journal: @journal).import_plan(**context,
|
|
126
|
-
artifacts: [bytes], admitted_after_event_digest: events.last.fetch("digest"))
|
|
127
|
-
plan.merge(data: {"artifact_id" => artifact_id, "input" => selected.fetch(:input),
|
|
128
|
-
"input_digest" => selected.fetch(:binding).fetch("input_digest"), "artifact" => plan.fetch(:references).first,
|
|
129
|
-
"authorization" => params.fetch("authorization"), "producer_origin" => selected.fetch(:origin)})
|
|
130
|
-
end
|
|
131
|
-
end
|
|
132
|
-
end
|
|
133
|
-
|
|
134
|
-
def release_evidence_context(input, origin, authorization, artifact_id)
|
|
135
|
-
{kind: "release-artifact", project_id: @project, assignment_id: origin.fetch("assignment_id"),
|
|
136
|
-
attempt_id: origin.fetch("attempt_id"), peer_uid: origin.dig("runtime_binding", "process_identity", "uid"),
|
|
137
|
-
binding: {"input" => input, "producer_origin" => origin, "authorization" => authorization},
|
|
138
|
-
request_id_or_event_id: artifact_id, generation: input.fetch("publication").fetch("candidate_reference").fetch("candidate_generation")}
|
|
139
|
-
end
|
|
140
|
-
end
|
|
141
|
-
end
|
|
142
|
-
end
|
|
143
|
-
end
|
|
@@ -1,206 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative "receipt_transfer"
|
|
4
|
-
require_relative "../molecules/canonical_evidence"
|
|
5
|
-
|
|
6
|
-
module Ace
|
|
7
|
-
module Assign
|
|
8
|
-
module Authority
|
|
9
|
-
# A worker submits an attributable result. This never accepts a terminal
|
|
10
|
-
# outcome, an independent review, or an external delivery effect.
|
|
11
|
-
module OrdinaryResults
|
|
12
|
-
RESULT_OPERATIONS = %w[submit_result result_status result_snapshot].freeze
|
|
13
|
-
RESULT_TRANSFERS = {
|
|
14
|
-
"submit_result" => {direction: :upload, purpose: :receipt_artifacts, roles: %i[worker supervisor]},
|
|
15
|
-
"result_snapshot" => {direction: :download, purpose: :receipt_artifacts, roles: %i[worker supervisor]}
|
|
16
|
-
}.freeze
|
|
17
|
-
RESULT_KEYS = %w[assignment_id attempt_id expected_generation pane head receipt_sha256 transfer].freeze
|
|
18
|
-
|
|
19
|
-
private
|
|
20
|
-
|
|
21
|
-
def validate_result!(request)
|
|
22
|
-
params = request.fetch("params")
|
|
23
|
-
if request.fetch("operation") != "submit_result"
|
|
24
|
-
snapshot = request.fetch("operation") == "result_snapshot"
|
|
25
|
-
strict!(params, %w[assignment_id attempt_id] + (snapshot ? ["result_id"] : []))
|
|
26
|
-
token!(params.fetch("result_id")) if snapshot
|
|
27
|
-
raise ArgumentError, "ordinary result status mutation must be null" unless request.fetch("mutation_id").nil?
|
|
28
|
-
else
|
|
29
|
-
strict!(params, RESULT_KEYS)
|
|
30
|
-
token!(request.fetch("mutation_id"))
|
|
31
|
-
unless params.fetch("expected_generation").is_a?(Integer) && params.fetch("expected_generation").positive? &&
|
|
32
|
-
params.fetch("head").is_a?(String) && params.fetch("head").match?(/\A[0-9a-f]{40}\z/) &&
|
|
33
|
-
params.fetch("receipt_sha256").is_a?(String) && params.fetch("receipt_sha256").match?(/\A[0-9a-f]{64}\z/)
|
|
34
|
-
raise ArgumentError, "ordinary result selection is invalid"
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
%w[assignment_id attempt_id].each { |key| token!(params.fetch(key)) }
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
def ordinary_result(request, peer, role, deadline, transfer)
|
|
41
|
-
validate_result!(request)
|
|
42
|
-
params = request.fetch("params")
|
|
43
|
-
commit = @journal.ref_value
|
|
44
|
-
registered = registration(params.fetch("assignment_id"), commit: commit)
|
|
45
|
-
raise AttemptErrors::NotFound, "ordinary work is not registered" unless registered
|
|
46
|
-
if request.fetch("operation") != "submit_result"
|
|
47
|
-
if request.fetch("operation") == "result_status" && @journal.read_events(params.fetch("assignment_id"), commit: commit)
|
|
48
|
-
.none? { |event| event.fetch("attempt_id") == params.fetch("attempt_id") }
|
|
49
|
-
authorize!(request, peer, role, deadline)
|
|
50
|
-
return {data: params.slice("assignment_id", "attempt_id").merge("project_id" => @project,
|
|
51
|
-
"state" => "not_started", "generation" => 0, "submitted_result" => nil), replayed: false}
|
|
52
|
-
end
|
|
53
|
-
events = result_events(params, commit)
|
|
54
|
-
result = retained_ordinary_result(params, events, commit)
|
|
55
|
-
if request.fetch("operation") == "result_snapshot"
|
|
56
|
-
unless result && result.fetch("result_id") == params.fetch("result_id")
|
|
57
|
-
raise AttemptErrors::NotFound, "ordinary exact result is unavailable"
|
|
58
|
-
end
|
|
59
|
-
receipt = result.fetch("receipt")
|
|
60
|
-
canonical = Molecules::CanonicalEvidence.new(journal: @journal)
|
|
61
|
-
parts = [JSON.generate(receipt)] + receipt.fetch("artifacts").map { |artifact|
|
|
62
|
-
canonical.read({"ref" => artifact.fetch("path"), "sha256" => artifact.fetch("sha256")},
|
|
63
|
-
**ordinary_result_context(result.fetch("binding")), commit: commit)
|
|
64
|
-
}
|
|
65
|
-
authorize!(request, peer, role, deadline)
|
|
66
|
-
return {data: ordinary_result_projection(result).merge("journal_commit" => commit), transfer_parts: parts, replayed: false}
|
|
67
|
-
end
|
|
68
|
-
authorize!(request, peer, role, deadline)
|
|
69
|
-
started = events.find { |event| event["type"] == "process_start" }.fetch("payload")
|
|
70
|
-
scope = events.find { |event| event["type"] == "intent" }.fetch("payload").fetch("scope")
|
|
71
|
-
return {data: {"project_id" => @project, "assignment_id" => params.fetch("assignment_id"), "attempt_id" => params.fetch("attempt_id"),
|
|
72
|
-
"scope" => scope, "producer" => started.slice("actor", "role", "runtime"),
|
|
73
|
-
"runtime_binding" => started.fetch("runtime_binding"),
|
|
74
|
-
"journal_commit" => commit, "generation" => @journal.authority_generation(events),
|
|
75
|
-
"state" => Molecules::CanonicalAttemptState.derive(events), "submitted_result" => result && ordinary_result_projection(result)}, replayed: false}
|
|
76
|
-
end
|
|
77
|
-
unless %i[worker supervisor].include?(role)
|
|
78
|
-
raise AttemptErrors::UnauthorizedIdentity, "ordinary result requires its original worker"
|
|
79
|
-
end
|
|
80
|
-
authorize_start_replay!(request, peer, role, deadline)
|
|
81
|
-
admitted = ReceiptTransfer.decode(input: transfer, receipt_sha256: params.fetch("receipt_sha256"),
|
|
82
|
-
artifact_field: "artifacts", reference_key: "path")
|
|
83
|
-
digest = Atoms::EvidenceDigest.digest({"params" => params, "project_id" => @project, "peer_uid" => peer.fetch("uid")})
|
|
84
|
-
keys = [@exclusion.task_key(registered.fetch("task_id")), @exclusion.assignment_key(params.fetch("assignment_id"))]
|
|
85
|
-
@exclusion.with_shared_multi(keys, deadline: deadline) do
|
|
86
|
-
@journal.mutate(assignment_id: params.fetch("assignment_id"), attempt_id: params.fetch("attempt_id"),
|
|
87
|
-
mutation_id: request.fetch("mutation_id"), operation: "submit_result", parameters_digest: digest,
|
|
88
|
-
expected_generation: params.fetch("expected_generation"), with_replay: true, deadline: deadline,
|
|
89
|
-
replay_authorizer: -> { authorize_start_replay!(request, peer, role, deadline) }) do |events, prefix, generation|
|
|
90
|
-
authorize_start_replay!(request, peer, role, deadline)
|
|
91
|
-
unless Molecules::CanonicalAttemptState.derive(events) == "running"
|
|
92
|
-
raise AttemptErrors::Conflict, "ordinary result requires the original running attempt"
|
|
93
|
-
end
|
|
94
|
-
raise AttemptErrors::Conflict, "ordinary attempt already submitted its result" if retained_ordinary_result(params, events, prefix)
|
|
95
|
-
observed = @admission.execution!(peer: peer, pane: params.fetch("pane"), head: params.fetch("head"), deadline: deadline)
|
|
96
|
-
start_replay_owner!(params, observed.fetch(:identity))
|
|
97
|
-
ordinary_result_plan(params, peer, admitted, observed, events, prefix, generation + 1)
|
|
98
|
-
end
|
|
99
|
-
end
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
def result_events(params, commit)
|
|
103
|
-
events = @journal.read_events(params.fetch("assignment_id"), commit: commit)
|
|
104
|
-
.select { |event| event.fetch("attempt_id") == params.fetch("attempt_id") }
|
|
105
|
-
unless Models::EvidenceEvent.chain_valid?(events) && events.one? { |event| event["type"] == "process_start" }
|
|
106
|
-
raise AttemptErrors::EvidenceUnavailable, "ordinary result attempt evidence is unavailable"
|
|
107
|
-
end
|
|
108
|
-
events
|
|
109
|
-
end
|
|
110
|
-
|
|
111
|
-
def ordinary_result_plan(params, peer, admitted, observed, events, commit, generation)
|
|
112
|
-
original = admitted.fetch(:receipt)
|
|
113
|
-
original_receipt = Models::ExecutionReceipt.from_h(original)
|
|
114
|
-
unless original_receipt.digest == Atoms::EvidenceDigest.digest(original_receipt.digest_payload)
|
|
115
|
-
raise AttemptErrors::ReceiptRejected, "ordinary original result digest differs"
|
|
116
|
-
end
|
|
117
|
-
identity = observed.fetch(:identity)
|
|
118
|
-
expected_producer = {"actor" => identity.actor, "role" => identity.role, "runtime" => identity.runtime}
|
|
119
|
-
validate_ordinary_result_receipt!(original, expected_producer)
|
|
120
|
-
id = SecureRandom.hex(16)
|
|
121
|
-
binding = params.slice("assignment_id", "attempt_id", "head").merge("authority" => "ordinary", "project_id" => @project,
|
|
122
|
-
"result_id" => id, "worker_uid" => peer.fetch("uid"), "producer" => expected_producer,
|
|
123
|
-
"runtime_binding" => identity.runtime_binding, "checkout" => observed.fetch(:checkout),
|
|
124
|
-
"generation" => generation, "uploaded_receipt_sha256" => admitted.fetch(:receipt_sha256),
|
|
125
|
-
"original_receipt_digest" => original_receipt.digest)
|
|
126
|
-
canonical = Molecules::CanonicalEvidence.new(journal: @journal)
|
|
127
|
-
plan = admitted.fetch(:artifacts).empty? ? {events: [], blobs: {}, references: []} :
|
|
128
|
-
canonical.import_plan(**ordinary_result_context(binding), artifacts: admitted.fetch(:artifacts),
|
|
129
|
-
admitted_after_event_digest: events.last.fetch("digest"))
|
|
130
|
-
receipt = original.except("digest", "recorded_at").merge("artifacts" => plan.fetch(:references).map { |reference|
|
|
131
|
-
{"path" => reference.fetch("ref"), "sha256" => reference.fetch("sha256")}
|
|
132
|
-
})
|
|
133
|
-
pending = @journal.send(:chain_mutation_events, params.fetch("attempt_id"), events.last.fetch("digest"), plan.fetch(:events))
|
|
134
|
-
reader = ->(_data, artifact) { canonical.read_pending({"ref" => artifact.fetch("path"), "sha256" => artifact.fetch("sha256")},
|
|
135
|
-
**ordinary_result_context(binding), current_events: events, pending_events: pending, blobs: plan.fetch(:blobs), commit: commit) }
|
|
136
|
-
verified = verify_ordinary_result_receipt(params, receipt, binding, commit, reader)
|
|
137
|
-
if JSON.generate(verified.to_h).bytesize > 16_384
|
|
138
|
-
raise AttemptErrors::ReceiptRejected, "canonical ordinary receipt exceeds its transfer limit"
|
|
139
|
-
end
|
|
140
|
-
payload = {"authority" => "ordinary", "result_id" => id, "binding" => binding, "receipt" => verified.to_h}
|
|
141
|
-
plan.merge(events: plan.fetch(:events) + [{type: "result_submitted", payload: payload}],
|
|
142
|
-
data: ordinary_result_projection(payload))
|
|
143
|
-
end
|
|
144
|
-
|
|
145
|
-
def ordinary_result_projection(payload)
|
|
146
|
-
payload.slice("result_id").merge(payload.fetch("binding").slice("head", "generation"),
|
|
147
|
-
"receipt_digest" => payload.fetch("receipt").fetch("digest"), "verdict" => payload.fetch("receipt").fetch("verdict"),
|
|
148
|
-
"state" => "running", "authority" => "ordinary")
|
|
149
|
-
end
|
|
150
|
-
|
|
151
|
-
def ordinary_result_context(binding)
|
|
152
|
-
{kind: "result", project_id: @project, assignment_id: binding.fetch("assignment_id"), attempt_id: binding.fetch("attempt_id"),
|
|
153
|
-
peer_uid: binding.fetch("worker_uid"), binding: binding, request_id_or_event_id: binding.fetch("result_id"), generation: binding.fetch("generation")}
|
|
154
|
-
end
|
|
155
|
-
|
|
156
|
-
def validate_ordinary_result_receipt!(receipt, producer)
|
|
157
|
-
unless receipt["producer"] == producer && (receipt.keys - Models::ExecutionReceipt.from_h(receipt).to_h.keys).empty? &&
|
|
158
|
-
!receipt["campaign"] && !receipt["review"] && receipt["operation"] != "review" &&
|
|
159
|
-
!Molecules::ReceiptVerifier::EXTERNAL_EFFECT_OPERATIONS.include?(receipt["operation"])
|
|
160
|
-
raise AttemptErrors::ReceiptRejected, "ordinary result producer or operation requires another evidence owner"
|
|
161
|
-
end
|
|
162
|
-
end
|
|
163
|
-
|
|
164
|
-
def verify_ordinary_result_receipt(params, receipt, binding, commit, reader)
|
|
165
|
-
validate_ordinary_result_receipt!(receipt, binding.fetch("producer"))
|
|
166
|
-
attempt = @journal.derived_attempts(params.fetch("assignment_id"), commit: commit).find { |item| item.attempt_id == params.fetch("attempt_id") }
|
|
167
|
-
raise AttemptErrors::EvidenceUnavailable, "ordinary result attempt is unavailable" unless attempt
|
|
168
|
-
Array(receipt["artifacts"]).each { |artifact| reader.call(receipt, artifact) }
|
|
169
|
-
Molecules::ReceiptVerifier.new(artifact_reader: reader).verify_result!(receipt,
|
|
170
|
-
attempt: attempt, live_head: binding.fetch("head"), repo_root: @journal.repo_root)
|
|
171
|
-
end
|
|
172
|
-
|
|
173
|
-
def retained_ordinary_result(params, events, commit)
|
|
174
|
-
raise AttemptErrors::EvidenceUnavailable, "ordinary result chain is corrupt" unless Models::EvidenceEvent.chain_valid?(events)
|
|
175
|
-
records = events.select { |event| event["type"] == "result_submitted" }
|
|
176
|
-
replies = events.select { |event| event["type"] == "authority_mutation" && event.dig("payload", "operation") == "submit_result" }
|
|
177
|
-
return nil if records.empty? && replies.empty?
|
|
178
|
-
unless records.one? && replies.one?
|
|
179
|
-
raise AttemptErrors::EvidenceUnavailable, "ordinary result provenance is missing or duplicated"
|
|
180
|
-
end
|
|
181
|
-
payload = records.first.fetch("payload")
|
|
182
|
-
binding = payload.fetch("binding")
|
|
183
|
-
start = events.find { |event| event["type"] == "process_start" }.fetch("payload")
|
|
184
|
-
unless payload.keys.sort == %w[authority binding receipt result_id] && payload["authority"] == "ordinary" &&
|
|
185
|
-
payload["result_id"].is_a?(String) && payload["result_id"].match?(/\A[0-9a-f]{32}\z/) &&
|
|
186
|
-
binding.keys.sort == %w[assignment_id attempt_id authority checkout generation head original_receipt_digest producer project_id result_id runtime_binding uploaded_receipt_sha256 worker_uid] &&
|
|
187
|
-
binding["authority"] == "ordinary" && binding["result_id"] == payload["result_id"] &&
|
|
188
|
-
binding["head"] == binding.dig("checkout", "head") &&
|
|
189
|
-
binding.values_at("project_id", "assignment_id", "attempt_id", "runtime_binding") ==
|
|
190
|
-
[@project, params.fetch("assignment_id"), params.fetch("attempt_id"), start.fetch("runtime_binding")] &&
|
|
191
|
-
binding.fetch("producer") == start.slice("actor", "role", "runtime") &&
|
|
192
|
-
binding.fetch("worker_uid") == start.dig("runtime_binding", "process_identity", "uid") &&
|
|
193
|
-
payload.fetch("receipt").fetch("producer") == binding.fetch("producer") &&
|
|
194
|
-
replies.first.dig("payload", "data").slice(*ordinary_result_projection(payload).keys) == ordinary_result_projection(payload)
|
|
195
|
-
raise AttemptErrors::EvidenceUnavailable, "ordinary result owner differs"
|
|
196
|
-
end
|
|
197
|
-
canonical = Molecules::CanonicalEvidence.new(journal: @journal)
|
|
198
|
-
reader = ->(_data, artifact) { canonical.read({"ref" => artifact.fetch("path"), "sha256" => artifact.fetch("sha256")},
|
|
199
|
-
**ordinary_result_context(binding), commit: commit) }
|
|
200
|
-
verify_ordinary_result_receipt(params, payload.fetch("receipt"), binding, commit, reader)
|
|
201
|
-
payload
|
|
202
|
-
end
|
|
203
|
-
end
|
|
204
|
-
end
|
|
205
|
-
end
|
|
206
|
-
end
|