ace-assign 0.64.0 → 0.66.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 +11 -0
- data/CHANGELOG.md +25 -0
- data/docs/protected-authority.md +23 -7
- data/docs/usage.md +117 -4
- data/exe/ace-scope-ready +55 -0
- data/handbook/workflow-instructions/assign/drive.wf.md +26 -1
- data/lib/ace/assign/atoms/attempt_state_machine.rb +8 -0
- data/lib/ace/assign/atoms/delivery_parameters.rb +4 -39
- data/lib/ace/assign/authority/assignment_inventory.rb +290 -0
- data/lib/ace/assign/authority/campaign_consumer_policy.rb +100 -0
- data/lib/ace/assign/authority/campaign_execution.rb +119 -0
- data/lib/ace/assign/authority/campaign_round_transfer.rb +82 -0
- data/lib/ace/assign/authority/candidate_transfer.rb +169 -2
- data/lib/ace/assign/authority/cleanup_result_protection.rb +53 -0
- data/lib/ace/assign/authority/client.rb +156 -6
- data/lib/ace/assign/authority/deployment.rb +201 -39
- data/lib/ace/assign/authority/deployment_history.rb +146 -0
- data/lib/ace/assign/authority/endcap.rb +131 -18
- data/lib/ace/assign/authority/endcap_attempts.rb +274 -0
- data/lib/ace/assign/authority/endcap_campaign_results.rb +232 -0
- data/lib/ace/assign/authority/endcap_campaign_rounds.rb +266 -0
- data/lib/ace/assign/authority/endcap_contexts.rb +23 -0
- data/lib/ace/assign/authority/endcap_inboxes.rb +3 -280
- data/lib/ace/assign/authority/endcap_prepared_work.rb +80 -0
- data/lib/ace/assign/authority/endcap_publication.rb +207 -0
- data/lib/ace/assign/authority/endcap_results.rb +36 -18
- data/lib/ace/assign/authority/endcap_review_requests.rb +183 -0
- data/lib/ace/assign/authority/endcap_reviews.rb +130 -0
- data/lib/ace/assign/authority/endcap_service_settlement.rb +152 -0
- data/lib/ace/assign/authority/endcap_services.rb +221 -18
- data/lib/ace/assign/authority/execution_scope_observation.rb +468 -41
- data/lib/ace/assign/authority/inbox_context_original.rb +113 -0
- data/lib/ace/assign/authority/launch_attempt_consumers.rb +51 -0
- data/lib/ace/assign/authority/launch_campaign_registration.rb +214 -0
- data/lib/ace/assign/authority/launch_control_channel.rb +313 -0
- data/lib/ace/assign/authority/launch_control_exclusions.rb +141 -0
- data/lib/ace/assign/authority/launch_driver.rb +298 -19
- data/lib/ace/assign/authority/launch_input_inhibition.rb +132 -0
- data/lib/ace/assign/authority/launch_lifecycle.rb +275 -100
- data/lib/ace/assign/authority/launch_prepared_work.rb +122 -0
- data/lib/ace/assign/authority/launch_review.rb +91 -0
- data/lib/ace/assign/authority/launch_scope_abort.rb +0 -7
- data/lib/ace/assign/authority/launch_scope_admission.rb +136 -5
- data/lib/ace/assign/authority/launch_scope_close.rb +68 -37
- data/lib/ace/assign/authority/launch_scope_release.rb +696 -17
- data/lib/ace/assign/authority/launch_steering.rb +353 -0
- data/lib/ace/assign/authority/launch_stop.rb +254 -0
- data/lib/ace/assign/authority/maintenance_workspace_retirement.rb +229 -0
- data/lib/ace/assign/authority/original_inbox_selection.rb +41 -0
- data/lib/ace/assign/authority/posix_acl.rb +5 -2
- data/lib/ace/assign/authority/prepared_input.rb +130 -0
- data/lib/ace/assign/authority/prepared_queue.rb +185 -0
- data/lib/ace/assign/authority/prepared_task_context.rb +116 -0
- data/lib/ace/assign/authority/prepared_work.rb +304 -0
- data/lib/ace/assign/authority/prepared_worker.rb +69 -0
- data/lib/ace/assign/authority/protected_assignment_context.rb +226 -0
- data/lib/ace/assign/authority/readiness_hook.rb +120 -0
- data/lib/ace/assign/authority/router.rb +24 -2
- data/lib/ace/assign/authority/server.rb +103 -33
- data/lib/ace/assign/authority/service_cleanup_dispatch.rb +88 -0
- data/lib/ace/assign/authority/service_cleanup_evidence.rb +180 -0
- data/lib/ace/assign/authority/service_delivery_evidence.rb +101 -0
- data/lib/ace/assign/authority/service_evidence.rb +243 -12
- data/lib/ace/assign/authority/service_publication_evidence.rb +110 -0
- data/lib/ace/assign/authority/task_context_entry.rb +32 -0
- data/lib/ace/assign/authority/transfer_codec.rb +44 -12
- data/lib/ace/assign/cli/commands/assignment_target.rb +15 -2
- data/lib/ace/assign/cli/commands/attempt/base.rb +49 -0
- data/lib/ace/assign/cli/commands/attempt/finish.rb +20 -4
- data/lib/ace/assign/cli/commands/attempt/reconcile.rb +11 -0
- data/lib/ace/assign/cli/commands/authority/inbox_context_principal.rb +25 -0
- data/lib/ace/assign/cli/commands/authority/inbox_context_selection.rb +28 -0
- data/lib/ace/assign/cli/commands/authority/launch.rb +58 -6
- data/lib/ace/assign/cli/commands/authority/serve.rb +2 -1
- data/lib/ace/assign/cli/commands/authority/status.rb +1 -1
- data/lib/ace/assign/cli/commands/authority/task_context.rb +31 -0
- data/lib/ace/assign/cli/commands/authority/task_context_principal.rb +25 -0
- data/lib/ace/assign/cli/commands/authority/task_context_selection.rb +30 -0
- data/lib/ace/assign/cli/commands/authority/worker.rb +23 -0
- data/lib/ace/assign/cli/commands/campaign_export_result.rb +55 -0
- data/lib/ace/assign/cli/commands/campaign_record_round.rb +57 -0
- data/lib/ace/assign/cli/commands/create.rb +2 -0
- data/lib/ace/assign/cli/commands/delivery.rb +54 -4
- data/lib/ace/assign/cli/commands/fail.rb +6 -0
- data/lib/ace/assign/cli/commands/finish.rb +23 -2
- data/lib/ace/assign/cli/commands/fork_session.rb +6 -2
- data/lib/ace/assign/cli/commands/inbox_bind.rb +34 -0
- data/lib/ace/assign/cli/commands/protected_submission.rb +61 -0
- data/lib/ace/assign/cli/commands/resume.rb +12 -0
- data/lib/ace/assign/cli/commands/select.rb +2 -0
- data/lib/ace/assign/cli/commands/start.rb +2 -0
- data/lib/ace/assign/cli/commands/status.rb +4 -2
- data/lib/ace/assign/cli/commands/step.rb +3 -1
- data/lib/ace/assign/cli/commands/submit_candidate.rb +34 -0
- data/lib/ace/assign/cli/commands/submit_result.rb +61 -0
- data/lib/ace/assign/cli.rb +36 -5
- data/lib/ace/assign/errors.rb +139 -0
- data/lib/ace/assign/models/assignment.rb +26 -4
- data/lib/ace/assign/models/evidence_event.rb +3 -3
- data/lib/ace/assign/molecules/assignment_manager.rb +12 -2
- data/lib/ace/assign/molecules/attempt_reconciler.rb +20 -0
- data/lib/ace/assign/molecules/canonical_attempt_state.rb +151 -0
- data/lib/ace/assign/molecules/canonical_evidence.rb +5 -2
- data/lib/ace/assign/molecules/canonical_read_snapshot.rb +374 -0
- data/lib/ace/assign/molecules/evidence_journal.rb +720 -44
- data/lib/ace/assign/molecules/execution_scope_lineage.rb +27 -9
- data/lib/ace/assign/molecules/fork_session_launcher.rb +16 -6
- data/lib/ace/assign/molecules/journal_input_inhibition.rb +78 -0
- data/lib/ace/assign/molecules/journal_mutation.rb +82 -13
- data/lib/ace/assign/molecules/journal_prompt_mutation.rb +241 -0
- data/lib/ace/assign/molecules/lifecycle_exclusion.rb +178 -26
- data/lib/ace/assign/molecules/original_launch_binding.rb +37 -0
- data/lib/ace/assign/molecules/protected_control_exclusion.rb +303 -0
- data/lib/ace/assign/molecules/protected_workspace_exclusion.rb +330 -0
- data/lib/ace/assign/molecules/protected_workspace_provisioner.rb +164 -0
- data/lib/ace/assign/molecules/queue_scanner.rb +26 -11
- data/lib/ace/assign/molecules/receipt_verifier.rb +21 -15
- data/lib/ace/assign/molecules/step_writer.rb +27 -5
- data/lib/ace/assign/molecules/terminal_scope_receipt.rb +88 -0
- data/lib/ace/assign/organisms/assignment_executor.rb +4 -4
- data/lib/ace/assign/organisms/attempt_coordinator.rb +66 -76
- data/lib/ace/assign/organisms/prepared_work_builder.rb +194 -0
- data/lib/ace/assign/organisms/protected_delivery_coordinator.rb +100 -0
- data/lib/ace/assign/organisms/task_assignment_creator.rb +4 -2
- data/lib/ace/assign/version.rb +1 -1
- data/lib/ace/assign.rb +1 -112
- metadata +90 -11
- data/lib/ace/assign/cli/commands/inbox_reconcile.rb +0 -36
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ace
|
|
4
|
+
module Assign
|
|
5
|
+
module Authority
|
|
6
|
+
class Endcap
|
|
7
|
+
# Internal R1 dependency. The fixed authority consumer supplies the
|
|
8
|
+
# original expected linkage; no metadata document can attest execution.
|
|
9
|
+
# Raw imported artifacts are read at this same immutable prefix.
|
|
10
|
+
def with_campaign_execution_evidence!(journal:, commit:, params:, map:, receipt_digest:, execution_binding:)
|
|
11
|
+
raise ArgumentError, "campaign evidence requires a block" unless block_given?
|
|
12
|
+
begin
|
|
13
|
+
row = @launch.completion_terminal!(journal: journal, commit: commit, params: params, map: map)
|
|
14
|
+
unless row["canonical_state"] == "succeeded" && row["reservation_release_event_id"]
|
|
15
|
+
raise AttemptErrors::EvidenceUnavailable, "campaign execution is not succeeded and released"
|
|
16
|
+
end
|
|
17
|
+
definition = @launch.preview_attempt_definition!(journal: journal, commit: commit, params: params, map: map)
|
|
18
|
+
execution = definition.campaign_execution
|
|
19
|
+
raise AttemptErrors::EvidenceUnavailable, "campaign execution linkage is missing" unless execution
|
|
20
|
+
binding = execution.merge(params.slice("assignment_id", "attempt_id"),
|
|
21
|
+
"definition_digest" => row.fetch("definition_digest"), "binding_digest" => row.fetch("original_binding_digest"))
|
|
22
|
+
unless binding == execution_binding && binding["binding_digest"].is_a?(String)
|
|
23
|
+
raise AttemptErrors::EvidenceUnavailable, "campaign execution original linkage differs"
|
|
24
|
+
end
|
|
25
|
+
inventory = journal.canonical_event_inventory!(commit: commit)
|
|
26
|
+
accepted_commit = inventory.fetch("introductions").fetch(params.fetch("assignment_id"))
|
|
27
|
+
.fetch(row.fetch("terminal_event_id"))
|
|
28
|
+
events = @launch.preview_attempt_events!(journal: journal, commit: accepted_commit, params: params, map: map)
|
|
29
|
+
results = events.select { |event| event["type"] == "result_submitted" &&
|
|
30
|
+
event.dig("payload", "receipt_digest") == receipt_digest }
|
|
31
|
+
raise AttemptErrors::EvidenceUnavailable, "campaign execution result is ambiguous" unless results.one?
|
|
32
|
+
submitted = results.first.fetch("payload")
|
|
33
|
+
selected = params.merge(submitted.fetch("binding").slice("head", "candidate_generation"),
|
|
34
|
+
"result_id" => submitted.fetch("result_id"))
|
|
35
|
+
current = exact_candidate!(retained_candidate(events, selected.fetch("candidate_generation")), selected)
|
|
36
|
+
result = verified_result(journal, events, selected, map, retained_origin(events, selected), current, accepted_commit,
|
|
37
|
+
result_id: selected.fetch("result_id"))
|
|
38
|
+
unless result && result["receipt_digest"] == receipt_digest && result.dig("receipt", "verdict") == "succeeded"
|
|
39
|
+
raise AttemptErrors::EvidenceUnavailable, "campaign execution accepted receipt differs"
|
|
40
|
+
end
|
|
41
|
+
terminal = events.find { |event| event["digest"] == row.fetch("terminal_event_id") }
|
|
42
|
+
unless terminal && terminal["type"] == "receipt_accepted" && terminal.dig("payload", "receipt") == result.fetch("receipt")
|
|
43
|
+
raise AttemptErrors::EvidenceUnavailable, "campaign execution terminal receipt differs"
|
|
44
|
+
end
|
|
45
|
+
accepted = approved_review!(journal, events, selected, map, current, commit: accepted_commit)
|
|
46
|
+
campaign_finished_result!(journal: journal, commit: accepted_commit, events: events, params: selected, map: map,
|
|
47
|
+
result: result, accepted: accepted)
|
|
48
|
+
proof = result.fetch("receipt").merge("attempt_id" => params.fetch("attempt_id"),
|
|
49
|
+
"receipt_digest" => receipt_digest, "execution_binding" => binding, "journal_commit" => accepted_commit)
|
|
50
|
+
freeze_campaign_evidence!(proof)
|
|
51
|
+
rescue KeyError, TypeError, ArgumentError, AttemptErrors::ReceiptRejected
|
|
52
|
+
raise AttemptErrors::EvidenceUnavailable, "campaign execution evidence is unavailable"
|
|
53
|
+
end
|
|
54
|
+
active = true
|
|
55
|
+
thread = Thread.current
|
|
56
|
+
canonical = Molecules::CanonicalEvidence.new(journal: journal)
|
|
57
|
+
reader = ->(artifact) {
|
|
58
|
+
unless active && Thread.current.equal?(thread) && result.fetch("artifacts").include?(artifact)
|
|
59
|
+
raise AttemptErrors::EvidenceUnavailable, "campaign artifact read escaped its original evidence scope"
|
|
60
|
+
end
|
|
61
|
+
canonical.read({"ref" => artifact.fetch("path"), "sha256" => artifact.fetch("sha256")},
|
|
62
|
+
**result_context(result.fetch("binding")), commit: accepted_commit)
|
|
63
|
+
}
|
|
64
|
+
yield proof, reader
|
|
65
|
+
ensure
|
|
66
|
+
active = false
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
private
|
|
70
|
+
|
|
71
|
+
def campaign_execution_dependencies!(journal:, commit:, parent_params:, parent_map:)
|
|
72
|
+
# This closure belongs to one held original exclusion and immutable
|
|
73
|
+
# journal selection. Never retained across RPCs. Live/original source
|
|
74
|
+
# parent checks below still run on every callback; only fully verified raw
|
|
75
|
+
# canonical artifacts and their introduced-prefix proof are reused.
|
|
76
|
+
verified = {}
|
|
77
|
+
selected_rows = {}
|
|
78
|
+
inventory = nil
|
|
79
|
+
read = lambda do |reference, phase:, head:, artifacts: nil, name: nil, producer: nil, reviewer: nil, historical: false|
|
|
80
|
+
unless reference.is_a?(Hash) && reference.keys.sort == %w[attempt_id digest] &&
|
|
81
|
+
reference["digest"].is_a?(String) && reference["digest"].match?(DIGEST)
|
|
82
|
+
raise AttemptErrors::EvidenceUnavailable, "campaign child receipt reference differs"
|
|
83
|
+
end
|
|
84
|
+
selection_key = Atoms::EvidenceDigest.digest("commit" => commit, "reference" => reference,
|
|
85
|
+
"phase" => phase, "head" => head, "name" => name)
|
|
86
|
+
params, map, execution, row = selected_rows[selection_key] ||= begin
|
|
87
|
+
inventory ||= journal.canonical_event_inventory!(commit: commit)
|
|
88
|
+
matches = inventory.fetch("events").filter_map do |assignment, events|
|
|
89
|
+
next unless events.any? { |event| event["attempt_id"] == reference["attempt_id"] }
|
|
90
|
+
registration = events.reverse.find { |event| event.dig("payload", "operation") == "register_assignment" }&.dig("payload", "data")
|
|
91
|
+
next unless registration
|
|
92
|
+
descriptor = @launch.send(:control_original_descriptor!, registration.fetch("lifecycle_control"))
|
|
93
|
+
child_map = descriptor.mapping(registration.fetch("mapping_id"))
|
|
94
|
+
params = {"assignment_id" => assignment, "attempt_id" => reference.fetch("attempt_id"),
|
|
95
|
+
"mapping_id" => registration.fetch("mapping_id")}
|
|
96
|
+
definition = @launch.preview_attempt_definition!(journal: journal, commit: commit, params: params, map: child_map)
|
|
97
|
+
execution = definition.campaign_execution
|
|
98
|
+
next unless execution && execution.values_at("parent_assignment_id", "parent_attempt_id", "phase", "head") ==
|
|
99
|
+
parent_params.values_at("assignment_id", "attempt_id") + [phase, head]
|
|
100
|
+
next unless child_map["project_id"] == parent_map.fetch("project_id")
|
|
101
|
+
next if name && execution["check_name"] != name
|
|
102
|
+
[params, child_map, execution]
|
|
103
|
+
end
|
|
104
|
+
raise AttemptErrors::EvidenceUnavailable, "campaign child receipt linkage is ambiguous" unless matches.one?
|
|
105
|
+
params, map, execution = matches.first
|
|
106
|
+
row = @launch.completion_terminal!(journal: journal, commit: commit, params: params, map: map)
|
|
107
|
+
[params.freeze, map, freeze_campaign_evidence!(JSON.parse(JSON.generate(execution))),
|
|
108
|
+
freeze_campaign_evidence!(JSON.parse(JSON.generate(row)))]
|
|
109
|
+
end
|
|
110
|
+
binding = execution.merge(params.slice("assignment_id", "attempt_id"),
|
|
111
|
+
"definition_digest" => row.fetch("definition_digest"), "binding_digest" => row.fetch("original_binding_digest"))
|
|
112
|
+
unless historical
|
|
113
|
+
campaign_parent_candidate!(journal: journal, commit: commit, params: parent_params.merge(
|
|
114
|
+
"scope" => execution.fetch("parent_scope"), "head" => head, "base" => execution.fetch("base"),
|
|
115
|
+
"candidate_generation" => execution.fetch("parent_candidate_generation")), map: parent_map)
|
|
116
|
+
end
|
|
117
|
+
key = Atoms::EvidenceDigest.digest("journal" => journal.object_id, "commit" => commit,
|
|
118
|
+
"binding" => binding, "receipt" => reference, "phase" => phase, "head" => head,
|
|
119
|
+
"name" => name, "producer" => producer, "reviewer" => reviewer, "artifacts" => artifacts)
|
|
120
|
+
proof = verified[key] ||= with_campaign_execution_evidence!(journal: journal, commit: commit, params: params, map: map,
|
|
121
|
+
receipt_digest: reference.fetch("digest"), execution_binding: binding) do |canonical, reader|
|
|
122
|
+
canonical.fetch("artifacts").each do |artifact|
|
|
123
|
+
unless Digest::SHA256.hexdigest(reader.call(artifact)) == artifact.fetch("sha256")
|
|
124
|
+
raise AttemptErrors::EvidenceUnavailable, "canonical campaign artifact bytes differ"
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
freeze_campaign_evidence!(JSON.parse(JSON.generate(canonical)))
|
|
128
|
+
end
|
|
129
|
+
if producer && (proof.dig("producer", "actor") != producer || proof.dig("review", "reviewer", "actor") != reviewer)
|
|
130
|
+
raise AttemptErrors::EvidenceUnavailable, "campaign approval original actors differ"
|
|
131
|
+
end
|
|
132
|
+
if artifacts && artifacts.any? { |artifact| !proof.fetch("artifacts").any? { |ref| ref["sha256"] == artifact.fetch("sha256") } }
|
|
133
|
+
raise AttemptErrors::EvidenceUnavailable, "campaign artifact differs from canonical child bytes"
|
|
134
|
+
end
|
|
135
|
+
proof.merge("artifacts" => artifacts || proof.fetch("artifacts"), "historical" => historical)
|
|
136
|
+
end
|
|
137
|
+
{check_evidence: ->(reference, head:, name:, historical: false) {
|
|
138
|
+
read.call(reference, phase: "check", head: head, name: name, historical: historical)
|
|
139
|
+
}, review_evidence: ->(reference, head:, artifacts:, historical: false) {
|
|
140
|
+
read.call(reference, phase: "collection", head: head, artifacts: artifacts, historical: historical)
|
|
141
|
+
}, approval_evidence: ->(reference, head:, artifacts:, producer:, reviewer:, historical: false) {
|
|
142
|
+
read.call(reference, phase: "approval", head: head, artifacts: artifacts, producer: producer, reviewer: reviewer, historical: historical)
|
|
143
|
+
}}.freeze
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def freeze_campaign_evidence!(value)
|
|
147
|
+
case value
|
|
148
|
+
when Hash then value.each { |key, item| freeze_campaign_evidence!(key); freeze_campaign_evidence!(item) }
|
|
149
|
+
when Array then value.each { |item| freeze_campaign_evidence!(item) }
|
|
150
|
+
end
|
|
151
|
+
value.freeze
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# The ordinary result is still campaign-free. Its phase comes only
|
|
155
|
+
# from the authenticated original registered child definition.
|
|
156
|
+
def campaign_child_result!(journal:, commit:, events:, params:, map:, receipt:)
|
|
157
|
+
definition = @launch.preview_attempt_definition!(journal: journal, commit: commit,
|
|
158
|
+
params: params, map: map)
|
|
159
|
+
execution = definition.campaign_execution
|
|
160
|
+
return unless execution
|
|
161
|
+
CampaignExecution.validate!(execution, parent: definition.parent,
|
|
162
|
+
policy: campaign_original_parent_policy!(journal, commit, execution, map))
|
|
163
|
+
intent = events.find { |event| event["type"] == "intent" }&.fetch("payload")
|
|
164
|
+
CampaignExecution.validate_result!(execution, receipt: receipt, base: intent&.fetch("base_head"))
|
|
165
|
+
execution
|
|
166
|
+
rescue KeyError, TypeError, ArgumentError
|
|
167
|
+
raise AttemptErrors::ReceiptRejected, "campaign child result binding is incomplete"
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def campaign_finished_result!(journal:, commit:, events:, params:, map:, result:, accepted:)
|
|
171
|
+
execution = campaign_child_result!(journal: journal, commit: commit, events: events, params: params, map: map,
|
|
172
|
+
receipt: result.fetch("receipt"))
|
|
173
|
+
return unless execution
|
|
174
|
+
CampaignExecution.validate_finished_review!(execution, receipt: result.fetch("receipt"),
|
|
175
|
+
accepted_review: accepted.fetch("review_receipt"))
|
|
176
|
+
rescue KeyError, ArgumentError, TypeError
|
|
177
|
+
raise AttemptErrors::ReceiptRejected, "campaign independent review differs from executed result"
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def campaign_original_parent_policy!(journal, commit, execution, map)
|
|
181
|
+
campaign_original_parent_context!(journal, commit, execution, map).fetch(:selected).fetch("policy")
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def campaign_original_parent_context!(journal, commit, execution, map)
|
|
185
|
+
parent = {"assignment_id" => execution.fetch("parent_assignment_id"),
|
|
186
|
+
"attempt_id" => execution.fetch("parent_attempt_id")}
|
|
187
|
+
inventory = journal.canonical_event_inventory!(commit: commit)
|
|
188
|
+
events = inventory.fetch("events").fetch(parent.fetch("assignment_id"))
|
|
189
|
+
reservations = events.select { |event| event["attempt_id"] == parent.fetch("attempt_id") &&
|
|
190
|
+
event.dig("payload", "operation") == "reserve_attempt" }
|
|
191
|
+
raise AttemptErrors::EvidenceUnavailable, "campaign parent original reservation differs" unless reservations.one?
|
|
192
|
+
parent["mapping_id"] = reservations.first.dig("payload", "data", "mapping_id")
|
|
193
|
+
prefix = inventory.fetch("introductions").fetch(parent.fetch("assignment_id"))
|
|
194
|
+
.fetch(reservations.first.fetch("digest"))
|
|
195
|
+
registration = @launch.send(:definition, journal, parent.fetch("assignment_id"), commit: prefix)
|
|
196
|
+
descriptor = @launch.send(:control_original_descriptor!, registration.fetch("lifecycle_control"))
|
|
197
|
+
original = @launch.send(:control_registration_context!, parent,
|
|
198
|
+
descriptor.mapping(parent.fetch("mapping_id")), journal, commit: commit)
|
|
199
|
+
unless original.fetch(:map).fetch("project_id") == map.fetch("project_id")
|
|
200
|
+
raise AttemptErrors::EvidenceUnavailable, "campaign child original project differs"
|
|
201
|
+
end
|
|
202
|
+
definition = @launch.preview_attempt_definition!(journal: journal, commit: commit,
|
|
203
|
+
params: parent, map: original.fetch(:map))
|
|
204
|
+
selected = CampaignExecution.validate_parent!(definition.review_campaign)
|
|
205
|
+
unless selected.values_at("campaign_id", "subject", "contract_identity") ==
|
|
206
|
+
execution.values_at("campaign_id", "subject", "contract_identity")
|
|
207
|
+
raise AttemptErrors::EvidenceUnavailable, "campaign child original parent differs"
|
|
208
|
+
end
|
|
209
|
+
{params: parent, map: original.fetch(:map), definition: definition, selected: selected}.freeze
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def campaign_prepared_candidate!(journal:, commit:, execution:, map:)
|
|
213
|
+
original = campaign_original_parent_context!(journal, commit, execution, map)
|
|
214
|
+
CampaignExecution.validate!(execution, parent: original.fetch(:definition).id,
|
|
215
|
+
policy: original.fetch(:selected).fetch("policy"))
|
|
216
|
+
params = original.fetch(:params).merge("scope" => execution.fetch("parent_scope"),
|
|
217
|
+
"base" => execution.fetch("base"), "head" => execution.fetch("head"),
|
|
218
|
+
"candidate_generation" => execution.fetch("parent_candidate_generation"))
|
|
219
|
+
campaign_parent_candidate!(journal: journal, commit: commit, params: params, map: original.fetch(:map))
|
|
220
|
+
events = @launch.preview_attempt_events!(journal: journal, commit: commit, params: params, map: original.fetch(:map))
|
|
221
|
+
selected = exact_candidate!(candidate(events), params)
|
|
222
|
+
bytes = journal.bounded_blob(selected.fetch("bundle_ref"), commit: commit, max_bytes: CandidateTransfer::MAX_BYTES)
|
|
223
|
+
unless bytes.is_a?(String) && bytes.bytesize == selected.fetch("bytes") &&
|
|
224
|
+
Digest::SHA256.hexdigest(bytes) == selected.fetch("sha256")
|
|
225
|
+
raise AttemptErrors::EvidenceUnavailable, "campaign original candidate bundle differs"
|
|
226
|
+
end
|
|
227
|
+
[selected.slice("head", "tree", "bytes", "sha256", "candidate_generation").merge("campaign_execution" => execution), bytes]
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
end
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require_relative "campaign_round_transfer"
|
|
3
|
+
|
|
4
|
+
module Ace
|
|
5
|
+
module Assign
|
|
6
|
+
module Authority
|
|
7
|
+
class Endcap
|
|
8
|
+
private
|
|
9
|
+
|
|
10
|
+
def campaign_round_request!(request)
|
|
11
|
+
params = request.fetch("params")
|
|
12
|
+
unless params.is_a?(Hash) && params.keys.sort == PARAMETERS.fetch("campaign_record_round").sort &&
|
|
13
|
+
params["head"].is_a?(String) && params["head"].match?(CandidateTransfer::SHA) &&
|
|
14
|
+
params["candidate_generation"].is_a?(Integer) && params["candidate_generation"].positive? &&
|
|
15
|
+
params["input_sha256"].is_a?(String) && params["input_sha256"].match?(DIGEST)
|
|
16
|
+
raise ArgumentError, "campaign round request differs"
|
|
17
|
+
end
|
|
18
|
+
%w[mapping_id assignment_id attempt_id].each { |key| result_id!(params.fetch(key)) }
|
|
19
|
+
result_id!(request.fetch("mutation_id"))
|
|
20
|
+
map = @deployment.mapping(params.fetch("mapping_id"))
|
|
21
|
+
raise AttemptErrors::UnauthorizedIdentity, "campaign project differs" unless map["project_id"] == request.fetch("project_id")
|
|
22
|
+
[params, map]
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def authorize_campaign_round!(request:, peer:, role:)
|
|
26
|
+
params, map = campaign_round_request!(request)
|
|
27
|
+
@launch.with_assignment(params: params, map: map) do |journal, registration|
|
|
28
|
+
protected_journal!(journal)
|
|
29
|
+
descriptor = @launch.send(:control_original_descriptor!, registration.fetch("lifecycle_control"))
|
|
30
|
+
original_map = descriptor.mapping(params.fetch("mapping_id"))
|
|
31
|
+
events = @launch.preview_attempt_events!(journal: journal, commit: journal.ref_value, params: params, map: original_map)
|
|
32
|
+
finish_identity!(events, params, original_map, peer, role)
|
|
33
|
+
definition = @launch.preview_attempt_definition!(journal: journal, commit: journal.ref_value, params: params, map: original_map)
|
|
34
|
+
CampaignExecution.validate_parent!(definition.review_campaign)
|
|
35
|
+
end
|
|
36
|
+
true
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def with_campaign_receipt!(journal:, commit:, events:, params:, map:, receipt:, result:, historical:)
|
|
40
|
+
unless result.is_a?(Hash) && result["schema"] == "ace.review.accepted-result/v1" &&
|
|
41
|
+
receipt["campaign"].is_a?(Hash) && receipt["campaign"].keys.sort == %w[id result] &&
|
|
42
|
+
receipt["operation"] == "review" && receipt["verdict"] == "succeeded"
|
|
43
|
+
raise AttemptErrors::ReceiptRejected, "parent campaign receipt differs"
|
|
44
|
+
end
|
|
45
|
+
context = @launch.send(:control_registration_context!, params, map, journal, commit: commit)
|
|
46
|
+
original_map = context.fetch(:map)
|
|
47
|
+
descriptor = context.fetch(:descriptor)
|
|
48
|
+
selected = CampaignExecution.validate_parent!(@launch.preview_attempt_definition!(journal: journal,
|
|
49
|
+
commit: commit, params: params, map: original_map).review_campaign)
|
|
50
|
+
unless receipt.dig("campaign", "id") == selected.fetch("campaign_id") && result["campaign_id"] == selected.fetch("campaign_id")
|
|
51
|
+
raise AttemptErrors::ReceiptRejected, "parent campaign identity differs"
|
|
52
|
+
end
|
|
53
|
+
origin = retained_origin(events, params)
|
|
54
|
+
parent = params.merge("scope" => origin.fetch("scope"), "base" => origin.fetch("base_head"), "head" => receipt.fetch("head"))
|
|
55
|
+
current = exact_candidate!(retained_candidate(events, params.fetch("candidate_generation")), parent)
|
|
56
|
+
project = descriptor.project(original_map.fetch("project_id"))
|
|
57
|
+
root = File.join(project.fetch("campaign_repository"), ".ace-local", "review", "candidate-views")
|
|
58
|
+
FileUtils.mkdir_p(root, mode: 0o700)
|
|
59
|
+
bundle = journal.bounded_blob(current.fetch("bundle_ref"), commit: commit, max_bytes: CandidateTransfer::MAX_BYTES)
|
|
60
|
+
CandidateTransfer.new(root: root).with_campaign_repository(bytes: bundle, sha256: current.fetch("sha256"),
|
|
61
|
+
size: current.fetch("bytes"), head: current.fetch("head"), tree: current.fetch("tree"), base: parent.fetch("base")) do |candidate_reader|
|
|
62
|
+
manager = @launch.send(:campaign_manager_for!, context,
|
|
63
|
+
**campaign_execution_dependencies!(journal: journal, commit: commit, parent_params: parent, parent_map: original_map),
|
|
64
|
+
candidate_reader: candidate_reader)
|
|
65
|
+
binding = selected.slice("subject", "contract_identity", "policy").transform_keys(&:to_sym).merge(
|
|
66
|
+
result: result, head: receipt.fetch("head"), base: parent.fetch("base"),
|
|
67
|
+
producer: receipt.fetch("producer").fetch("actor"), reviewer: receipt.fetch("review").fetch("reviewer").fetch("actor"))
|
|
68
|
+
if historical
|
|
69
|
+
manager.verify_retained_result!(**binding)
|
|
70
|
+
yield
|
|
71
|
+
else
|
|
72
|
+
deployment_sha = @launch.send(:protected_descriptor_sha256!)
|
|
73
|
+
CampaignConsumerPolicy.new.with(@deployment.project(original_map.fetch("project_id")).fetch("campaign_policy")) do |profiles, held|
|
|
74
|
+
manager.with_verified_result!(**binding, consumer_profiles: profiles) do |_verified, snapshot|
|
|
75
|
+
unless result == snapshot
|
|
76
|
+
raise AttemptErrors::ReceiptRejected, "exported campaign result bytes differ from verified owner"
|
|
77
|
+
end
|
|
78
|
+
held.verify_unchanged!
|
|
79
|
+
campaign_parent_candidate!(journal: journal, commit: commit, params: parent, map: original_map)
|
|
80
|
+
unless @launch.send(:protected_descriptor_sha256!) == deployment_sha
|
|
81
|
+
raise AttemptErrors::Conflict, "campaign consumer policy descriptor advanced"
|
|
82
|
+
end
|
|
83
|
+
value = yield
|
|
84
|
+
held.verify_unchanged!
|
|
85
|
+
unless @launch.send(:protected_descriptor_sha256!) == deployment_sha
|
|
86
|
+
raise AttemptErrors::Conflict, "campaign consumer policy descriptor advanced"
|
|
87
|
+
end
|
|
88
|
+
value
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
rescue Ace::Review::Atoms::CampaignContract::Invalid, KeyError, TypeError, ArgumentError
|
|
94
|
+
raise AttemptErrors::ReceiptRejected, "canonical parent campaign evidence is unavailable"
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def with_campaign_submission!(journal:, commit:, events:, params:, map:, admitted:, replay:)
|
|
98
|
+
receipt = admitted.fetch(:receipt)
|
|
99
|
+
return yield unless receipt["campaign"]
|
|
100
|
+
definition = @launch.preview_attempt_definition!(journal: journal, commit: commit, params: params, map: map)
|
|
101
|
+
unless definition.review_campaign
|
|
102
|
+
raise AttemptErrors::EvidenceUnavailable, "protected campaigns require their canonical owner"
|
|
103
|
+
end
|
|
104
|
+
reference = receipt.fetch("campaign").fetch("result")
|
|
105
|
+
index = receipt.fetch("artifacts").index(reference)
|
|
106
|
+
raise AttemptErrors::ReceiptRejected, "campaign result is not an uploaded artifact" unless index
|
|
107
|
+
result = JSON.parse(admitted.fetch(:artifacts).fetch(index), create_additions: false,
|
|
108
|
+
max_nesting: 64, allow_duplicate_key: false, allow_comments: false)
|
|
109
|
+
with_campaign_receipt!(journal: journal, commit: commit, events: events, params: params, map: map,
|
|
110
|
+
receipt: receipt, result: result, historical: !replay.nil?) { yield }
|
|
111
|
+
rescue JSON::ParserError
|
|
112
|
+
raise AttemptErrors::ReceiptRejected, "campaign result JSON is malformed"
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Service callers already retain with_assignment's original control SH
|
|
116
|
+
# and authority mutex through CAS. Round writers take the same keys EX
|
|
117
|
+
# before R1, so this read cannot invert with an admitted round writer.
|
|
118
|
+
def campaign_service_checks!(journal:, commit:, events:, params:, map:, current:)
|
|
119
|
+
definition = @launch.preview_attempt_definition!(journal: journal, commit: commit, params: params, map: map)
|
|
120
|
+
return true unless definition.review_campaign
|
|
121
|
+
parent = params.merge("head" => current.fetch("head"), "candidate_generation" => current.fetch("candidate_generation"))
|
|
122
|
+
result = verified_result(journal, events, parent, map, retained_origin(events, parent), current, commit)
|
|
123
|
+
unless result&.dig("receipt", "campaign") && result.dig("receipt", "verdict") == "succeeded"
|
|
124
|
+
raise AttemptErrors::ReceiptRejected, "ready or merge requires its canonical parent campaign result and executed checks"
|
|
125
|
+
end
|
|
126
|
+
with_campaign_finish!(journal: journal, commit: commit, events: events,
|
|
127
|
+
params: parent.merge("result_id" => result.fetch("result_id")), map: map) { true }
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def with_campaign_finish!(journal:, commit:, events:, params:, map:)
|
|
131
|
+
record = events.find { |event| event["type"] == "result_submitted" && event.dig("payload", "result_id") == params["result_id"] }
|
|
132
|
+
return yield unless record&.dig("payload", "receipt", "campaign")
|
|
133
|
+
current = exact_candidate!(candidate(events), params)
|
|
134
|
+
payload = verified_result(journal, events, params, map, retained_origin(events, params), current, commit,
|
|
135
|
+
result_id: params.fetch("result_id"))
|
|
136
|
+
reference = payload.fetch("receipt").fetch("campaign").fetch("result")
|
|
137
|
+
canonical = Molecules::CanonicalEvidence.new(journal: journal)
|
|
138
|
+
bytes = canonical.read({"ref" => reference.fetch("path"), "sha256" => reference.fetch("sha256")},
|
|
139
|
+
**result_context(payload.fetch("binding")), commit: commit)
|
|
140
|
+
result = JSON.parse(bytes, create_additions: false, max_nesting: 64, allow_duplicate_key: false, allow_comments: false)
|
|
141
|
+
with_campaign_receipt!(journal: journal, commit: commit, events: events, params: params, map: map,
|
|
142
|
+
receipt: payload.fetch("receipt"), result: result, historical: false) { yield }
|
|
143
|
+
rescue JSON::ParserError
|
|
144
|
+
raise AttemptErrors::ReceiptRejected, "canonical campaign result JSON is malformed"
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def dispatch_campaign_export(request:, peer:, role:, body: true)
|
|
148
|
+
params = request.fetch("params")
|
|
149
|
+
unless params.is_a?(Hash) && params.keys.sort == PARAMETERS.fetch("campaign_export_result").sort &&
|
|
150
|
+
params["head"].is_a?(String) && params["head"].match?(CandidateTransfer::SHA) &&
|
|
151
|
+
params["candidate_generation"].is_a?(Integer) && params["candidate_generation"].positive? && request["mutation_id"].nil?
|
|
152
|
+
raise ArgumentError, "campaign result selectors differ"
|
|
153
|
+
end
|
|
154
|
+
%w[mapping_id assignment_id attempt_id].each { |key| result_id!(params.fetch(key)) }
|
|
155
|
+
map = @deployment.mapping(params.fetch("mapping_id"))
|
|
156
|
+
raise AttemptErrors::UnauthorizedIdentity, "campaign project differs" unless map["project_id"] == request.fetch("project_id")
|
|
157
|
+
@launch.with_assignment(params: params, map: map, exclusive: true) do |journal, registration|
|
|
158
|
+
protected_journal!(journal)
|
|
159
|
+
commit = journal.ref_value
|
|
160
|
+
descriptor = @launch.send(:control_original_descriptor!, registration.fetch("lifecycle_control"))
|
|
161
|
+
original_map = descriptor.mapping(params.fetch("mapping_id"))
|
|
162
|
+
context = @launch.send(:control_registration_context!, params, original_map, journal, commit: commit)
|
|
163
|
+
events = @launch.preview_attempt_events!(journal: journal, commit: commit, params: params, map: original_map)
|
|
164
|
+
origin = active_origin(events, params)
|
|
165
|
+
role == :worker ? worker_or_launcher!(peer, role, original_map, origin) : finish_identity!(events, params, original_map, peer, role)
|
|
166
|
+
selected = CampaignExecution.validate_parent!(@launch.preview_attempt_definition!(journal: journal,
|
|
167
|
+
commit: commit, params: params, map: original_map).review_campaign)
|
|
168
|
+
return true unless body
|
|
169
|
+
parent = params.merge("scope" => origin.fetch("scope"), "base" => origin.fetch("base_head"))
|
|
170
|
+
campaign_parent_candidate!(journal: journal, commit: commit, params: parent, map: original_map)
|
|
171
|
+
current = exact_candidate!(candidate(events), params)
|
|
172
|
+
bundle = journal.bounded_blob(current.fetch("bundle_ref"), commit: commit, max_bytes: CandidateTransfer::MAX_BYTES)
|
|
173
|
+
project = descriptor.project(original_map.fetch("project_id"))
|
|
174
|
+
root = File.join(project.fetch("campaign_repository"), ".ace-local", "review", "candidate-views")
|
|
175
|
+
FileUtils.mkdir_p(root, mode: 0o700)
|
|
176
|
+
CandidateTransfer.new(root: root).with_campaign_repository(bytes: bundle, sha256: current.fetch("sha256"),
|
|
177
|
+
size: current.fetch("bytes"), head: current.fetch("head"), tree: current.fetch("tree"), base: parent.fetch("base")) do |candidate_reader|
|
|
178
|
+
manager = @launch.send(:campaign_manager_for!, context,
|
|
179
|
+
**campaign_execution_dependencies!(journal: journal, commit: commit, parent_params: parent, parent_map: original_map),
|
|
180
|
+
candidate_reader: candidate_reader)
|
|
181
|
+
result = manager.accepted_result_snapshot(selected.fetch("campaign_id"))
|
|
182
|
+
deployment_sha = @launch.send(:protected_descriptor_sha256!)
|
|
183
|
+
CampaignConsumerPolicy.new.with(@deployment.project(original_map.fetch("project_id")).fetch("campaign_policy")) do |profiles, held|
|
|
184
|
+
manager.with_verified_result!(result: result, **selected.slice("subject", "contract_identity", "policy").transform_keys(&:to_sym),
|
|
185
|
+
head: params.fetch("head"), base: parent.fetch("base"), producer: result.fetch("producer"), reviewer: result.fetch("reviewer"),
|
|
186
|
+
consumer_profiles: profiles) do
|
|
187
|
+
held.verify_unchanged!
|
|
188
|
+
unless @launch.send(:protected_descriptor_sha256!) == deployment_sha
|
|
189
|
+
raise AttemptErrors::Conflict, "campaign export policy descriptor advanced"
|
|
190
|
+
end
|
|
191
|
+
campaign_parent_candidate!(journal: journal, commit: commit, params: parent, map: original_map)
|
|
192
|
+
bytes = JSON.generate(result).b
|
|
193
|
+
raise AttemptErrors::EvidenceUnavailable, "campaign result exceeds artifact bound" if bytes.bytesize > 64 * 1024
|
|
194
|
+
{data: {"campaign_id" => selected.fetch("campaign_id"), "result_identity" => result.fetch("result_identity"),
|
|
195
|
+
"sha256" => Digest::SHA256.hexdigest(bytes), "bytes" => bytes.bytesize, "head" => params.fetch("head"),
|
|
196
|
+
"candidate_generation" => params.fetch("candidate_generation"), "journal_commit" => commit},
|
|
197
|
+
replayed: false, transfer_parts: [bytes]}
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
rescue Ace::Review::Atoms::CampaignContract::Invalid, KeyError, TypeError
|
|
203
|
+
raise AttemptErrors::EvidenceUnavailable, "canonical campaign result is unavailable"
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def dispatch_campaign_round(request:, peer:, role:, transfer:)
|
|
207
|
+
params, map = campaign_round_request!(request)
|
|
208
|
+
admitted = CampaignRoundTransfer.decode(input: transfer, sha256: params.fetch("input_sha256"))
|
|
209
|
+
input = admitted.fetch(:round)
|
|
210
|
+
unless input["attempt_id"] == request.fetch("mutation_id") && input["head"] == params.fetch("head")
|
|
211
|
+
raise AttemptErrors::Conflict, "campaign round stable request or head differs"
|
|
212
|
+
end
|
|
213
|
+
@launch.with_assignment(params: params, map: map, exclusive: true) do |journal, registration|
|
|
214
|
+
protected_journal!(journal)
|
|
215
|
+
commit = journal.ref_value
|
|
216
|
+
descriptor = @launch.send(:control_original_descriptor!, registration.fetch("lifecycle_control"))
|
|
217
|
+
original_map = descriptor.mapping(params.fetch("mapping_id"))
|
|
218
|
+
context = @launch.send(:control_registration_context!, params, original_map, journal, commit: commit)
|
|
219
|
+
events = @launch.preview_attempt_events!(journal: journal, commit: commit, params: params, map: original_map)
|
|
220
|
+
origin = finish_identity!(events, params, original_map, peer, role)
|
|
221
|
+
selected = CampaignExecution.validate_parent!(@launch.preview_attempt_definition!(journal: journal,
|
|
222
|
+
commit: commit, params: params, map: original_map).review_campaign)
|
|
223
|
+
parent_params = params.merge("scope" => origin.fetch("scope"), "base" => input.fetch("base"))
|
|
224
|
+
project = descriptor.project(original_map.fetch("project_id"))
|
|
225
|
+
paths = CampaignRoundTransfer.materialize(admitted: admitted, repository: project.fetch("campaign_repository"),
|
|
226
|
+
request_id: request.fetch("mutation_id"))
|
|
227
|
+
dependencies = campaign_execution_dependencies!(journal: journal, commit: commit,
|
|
228
|
+
parent_params: parent_params, parent_map: original_map)
|
|
229
|
+
current = exact_candidate!(retained_candidate(events, params.fetch("candidate_generation")), params)
|
|
230
|
+
bundle = journal.bounded_blob(current.fetch("bundle_ref"), commit: commit, max_bytes: CandidateTransfer::MAX_BYTES)
|
|
231
|
+
quarantine = File.join(project.fetch("campaign_repository"), ".ace-local", "review", "candidate-views")
|
|
232
|
+
FileUtils.mkdir_p(quarantine, mode: 0o700)
|
|
233
|
+
transfer_owner = CandidateTransfer.new(root: quarantine)
|
|
234
|
+
transfer_owner.with_campaign_repository(bytes: bundle, sha256: current.fetch("sha256"), size: current.fetch("bytes"),
|
|
235
|
+
head: current.fetch("head"), tree: current.fetch("tree"), base: input.fetch("base")) do |candidate_reader|
|
|
236
|
+
manager = @launch.send(:campaign_manager_for!, context, **dependencies, artifact_paths: paths, candidate_reader: candidate_reader)
|
|
237
|
+
deployment_sha = @launch.send(:protected_descriptor_sha256!)
|
|
238
|
+
CampaignConsumerPolicy.new.with(@deployment.project(original_map.fetch("project_id")).fetch("campaign_policy")) do |profiles, held|
|
|
239
|
+
# The existing R1 store checks exact replay before fresh policy,
|
|
240
|
+
# and binds the complete supplied frame in its input_digest.
|
|
241
|
+
data = manager.record_round(selected.fetch("campaign_id"), input, source_digest: admitted.fetch(:sha256),
|
|
242
|
+
expected_campaign: selected.reject { |key, _| key == "version" }, consumer_profiles: profiles,
|
|
243
|
+
before_record: -> {
|
|
244
|
+
held.verify_unchanged!
|
|
245
|
+
campaign_parent_candidate!(journal: journal, commit: commit, params: parent_params, map: original_map)
|
|
246
|
+
unless @launch.send(:protected_descriptor_sha256!) == deployment_sha
|
|
247
|
+
raise AttemptErrors::Conflict, "campaign policy descriptor advanced"
|
|
248
|
+
end
|
|
249
|
+
})
|
|
250
|
+
held.verify_unchanged!
|
|
251
|
+
unless journal.ref_value == commit && @launch.send(:protected_descriptor_sha256!) == deployment_sha
|
|
252
|
+
raise AttemptErrors::Conflict, "campaign round canonical selection advanced"
|
|
253
|
+
end
|
|
254
|
+
reply = data.slice("campaign_id", "recorded_complete", "completed_rounds", "accepted", "result_identity", "replayed")
|
|
255
|
+
.merge("request_id" => request.fetch("mutation_id"), "round_id" => input.fetch("round_id"))
|
|
256
|
+
{data: reply, replayed: data["replayed"] == true}
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
rescue Ace::Review::Atoms::CampaignContract::Invalid, KeyError, TypeError
|
|
261
|
+
raise AttemptErrors::EvidenceUnavailable, "canonical campaign round is unavailable"
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require "ace/herdr/molecules/inbox_context_client"
|
|
3
|
+
|
|
4
|
+
module Ace
|
|
5
|
+
module Assign
|
|
6
|
+
module Authority
|
|
7
|
+
class Endcap
|
|
8
|
+
private
|
|
9
|
+
|
|
10
|
+
def with_inbox_context(params, map, mutation_id:)
|
|
11
|
+
context = @deployment.inbox_context(params.fetch("mapping_id"), params.fetch("inbox_context_id"))
|
|
12
|
+
@deployment.verify_inbox_context!(params.fetch("mapping_id"), params.fetch("inbox_context_id"))
|
|
13
|
+
client = @inbox_context_clients&.fetch(params.fetch("inbox_context_id"))
|
|
14
|
+
client ||= Ace::Herdr::Molecules::InboxContextClient.selected(context_id: params.fetch("inbox_context_id"),
|
|
15
|
+
socket_path: context.fetch("control_socket_path"), owner_credentials: context.fetch("owner_credentials"), kernel: @kernel)
|
|
16
|
+
yield client
|
|
17
|
+
rescue Ace::Runtime::RuntimeUnavailableError, Ace::Herdr::Error
|
|
18
|
+
raise AttemptErrors::EvidenceUnavailable, "protected inbox context is unavailable"
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|