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,180 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative "candidate_transfer"
|
|
4
|
-
require_relative "receipt_transfer"
|
|
5
|
-
require_relative "../molecules/canonical_evidence"
|
|
6
|
-
|
|
7
|
-
module Ace
|
|
8
|
-
module Assign
|
|
9
|
-
module Authority
|
|
10
|
-
# Source-owned views of the same ordinary journal used by lifecycle CAS.
|
|
11
|
-
# Caller labels, filesystem reports and protected launch tickets are not
|
|
12
|
-
# alternate evidence sources. Service and merge consumers share this owner.
|
|
13
|
-
module OrdinaryReviewEvidence
|
|
14
|
-
def with_scope(assignment:, attempt:, deadline:)
|
|
15
|
-
token!(assignment)
|
|
16
|
-
token!(attempt)
|
|
17
|
-
selected = registration(assignment, commit: @journal.ref_value)
|
|
18
|
-
raise AttemptErrors::NotFound, "ordinary assignment is not registered" unless selected
|
|
19
|
-
@exclusion.with_shared_multi([@exclusion.task_key(selected.fetch("task_id")),
|
|
20
|
-
@exclusion.assignment_key(assignment)], deadline: deadline) do
|
|
21
|
-
yield @journal, selected
|
|
22
|
-
end
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
def origin_at!(events:, commit:)
|
|
26
|
-
ordinary_origin_at!(events, commit, running: true)
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
def candidate_at!(events:, commit:, head:, generation:)
|
|
30
|
-
origin_at!(events: events, commit: commit)
|
|
31
|
-
selected = ordinary_candidate_event(events)&.dig("payload", "data")
|
|
32
|
-
unless selected && selected.values_at("head", "candidate_generation") == [head, generation]
|
|
33
|
-
raise AttemptErrors::Conflict, "ordinary candidate changed"
|
|
34
|
-
end
|
|
35
|
-
bytes = @journal.bounded_blob(selected.fetch("bundle_ref"), commit: commit, max_bytes: CandidateTransfer::MAX_BYTES)
|
|
36
|
-
unless bytes.bytesize == selected.fetch("bytes") && Digest::SHA256.hexdigest(bytes) == selected.fetch("sha256")
|
|
37
|
-
raise AttemptErrors::EvidenceUnavailable, "ordinary canonical candidate is corrupt"
|
|
38
|
-
end
|
|
39
|
-
selected.slice("head", "tree", "candidate_generation", "bundle_ref", "sha256", "bytes")
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
def candidate_reference_at!(events:, commit:, head:, generation:)
|
|
43
|
-
candidate_at!(events: events, commit: commit, head: head, generation: generation)
|
|
44
|
-
origin = origin_at!(events: events, commit: commit)
|
|
45
|
-
origin.slice("assignment_id", "attempt_id").merge("candidate_generation" => generation,
|
|
46
|
-
"acceptance_event_digest" => ordinary_candidate_event(events).fetch("digest"))
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
def review_at!(events:, commit:, head:, generation:)
|
|
50
|
-
current = candidate_at!(events: events, commit: commit, head: head, generation: generation)
|
|
51
|
-
assigned = ordinary_review_assignment!(events, current)
|
|
52
|
-
ordinary_review_origins!(events, commit, assigned)
|
|
53
|
-
accepted = events.reverse.find { |event| event["type"] == "authority_mutation" &&
|
|
54
|
-
event.dig("payload", "operation") == "accept_review" && event.dig("payload", "data", "review_id") == assigned.fetch("review_id") }
|
|
55
|
-
data = accepted && accepted.dig("payload", "data")
|
|
56
|
-
unless data && data.fetch("review_binding") == assigned.except("generation", "journal_commit")
|
|
57
|
-
raise AttemptErrors::ReceiptRejected, "ordinary candidate has no accepted independent review"
|
|
58
|
-
end
|
|
59
|
-
canonical = Molecules::CanonicalEvidence.new(journal: @journal)
|
|
60
|
-
reader = ->(_receipt, artifact) { canonical.read({"ref" => artifact.fetch("path"), "sha256" => artifact.fetch("sha256")},
|
|
61
|
-
**ordinary_review_context(data.fetch("review_binding")), commit: commit) }
|
|
62
|
-
receipt = data.fetch("review_receipt")
|
|
63
|
-
verified = ordinary_verify_review!(receipt, events, commit, assigned, reader)
|
|
64
|
-
original = canonical.read({"ref" => data.fetch("original_receipt_ref"), "sha256" => data.fetch("uploaded_receipt_sha256")},
|
|
65
|
-
**ordinary_review_context(data.fetch("review_binding")), commit: commit)
|
|
66
|
-
unless Digest::SHA256.hexdigest(original) == data.fetch("uploaded_receipt_sha256") &&
|
|
67
|
-
verified.digest == data.fetch("receipt_digest")
|
|
68
|
-
raise AttemptErrors::EvidenceUnavailable, "ordinary accepted review bytes differ"
|
|
69
|
-
end
|
|
70
|
-
original_data = JSON.parse(original)
|
|
71
|
-
original_receipt = Models::ExecutionReceipt.from_h(original_data)
|
|
72
|
-
unless original_receipt.digest == Atoms::EvidenceDigest.digest(original_receipt.digest_payload) &&
|
|
73
|
-
original_data.except("digest", "recorded_at", "artifacts") == receipt.except("digest", "recorded_at", "artifacts") &&
|
|
74
|
-
original_data.fetch("artifacts").map { |value| value.fetch("sha256") } == receipt.fetch("artifacts").map { |value| value.fetch("sha256") }
|
|
75
|
-
raise AttemptErrors::EvidenceUnavailable, "ordinary review original and canonical receipt differ"
|
|
76
|
-
end
|
|
77
|
-
data.merge("acceptance_event_digest" => accepted.fetch("digest"), "recorded_at" => accepted.fetch("recorded_at"))
|
|
78
|
-
rescue JSON::ParserError, KeyError, TypeError
|
|
79
|
-
raise AttemptErrors::EvidenceUnavailable, "ordinary accepted review is incomplete"
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
private
|
|
83
|
-
|
|
84
|
-
def ordinary_origin_at!(events, commit, running:)
|
|
85
|
-
raise AttemptErrors::Conflict, "ordinary evidence prefix changed" unless commit && @journal.ref_value == commit
|
|
86
|
-
intent = events.select { |event| event["type"] == "intent" }
|
|
87
|
-
starts = events.select { |event| event["type"] == "process_start" }
|
|
88
|
-
accepted = events.select { |event| event["type"] == "authority_mutation" && event.dig("payload", "operation") == "start_attempt" }
|
|
89
|
-
unless Models::EvidenceEvent.chain_valid?(events) && intent.one? && starts.one? && accepted.one?
|
|
90
|
-
raise AttemptErrors::EvidenceUnavailable, "ordinary native origin is unavailable"
|
|
91
|
-
end
|
|
92
|
-
assignment, attempt = intent.first.dig("payload", "assignment_id"), intent.first.fetch("attempt_id")
|
|
93
|
-
token!(assignment)
|
|
94
|
-
token!(attempt)
|
|
95
|
-
registered = registration(assignment, commit: commit)
|
|
96
|
-
retained = @journal.read_events(assignment, commit: commit).select { |event| event["attempt_id"] == attempt }
|
|
97
|
-
start, data = starts.first.fetch("payload"), accepted.first.fetch("payload").fetch("data")
|
|
98
|
-
unless registered && retained == events && data.values_at("authority", "project_id", "assignment_id", "attempt_id", "runtime_binding") ==
|
|
99
|
-
["ordinary", @project, assignment, attempt, start.fetch("runtime_binding")] &&
|
|
100
|
-
intent.first.dig("payload", "project_id") == @project
|
|
101
|
-
raise AttemptErrors::EvidenceUnavailable, "ordinary native origin differs from accepted start"
|
|
102
|
-
end
|
|
103
|
-
if running && Molecules::CanonicalAttemptState.derive(events) != "running"
|
|
104
|
-
raise AttemptErrors::Conflict, "ordinary delivery requires its original running attempt"
|
|
105
|
-
end
|
|
106
|
-
{"authority" => "ordinary", "project_id" => @project, "assignment_id" => assignment, "attempt_id" => attempt,
|
|
107
|
-
"intent_digest" => intent.first.fetch("digest"), "process_start_digest" => starts.first.fetch("digest"),
|
|
108
|
-
"runtime_binding" => start.fetch("runtime_binding"), "scope" => intent.first.dig("payload", "scope"),
|
|
109
|
-
"base_head" => intent.first.dig("payload", "base_head")}
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
def ordinary_candidate_event(events)
|
|
113
|
-
events.reverse.find { |event| event["type"] == "authority_mutation" && event.dig("payload", "operation") == "submit_candidate" }
|
|
114
|
-
end
|
|
115
|
-
|
|
116
|
-
def ordinary_review_assignment!(events, candidate)
|
|
117
|
-
selected = events.reverse.find { |event| event["type"] == "authority_mutation" &&
|
|
118
|
-
event.dig("payload", "operation") == "assign_review" &&
|
|
119
|
-
event.dig("payload", "data", "candidate_generation") == candidate.fetch("candidate_generation") }
|
|
120
|
-
data = selected && selected.dig("payload", "data")
|
|
121
|
-
cancelled = selected && events.any? { |event| event["type"] == "authority_mutation" &&
|
|
122
|
-
event.dig("payload", "operation") == "cancel_review" && event.dig("payload", "data", "review_id") == data.fetch("review_id") }
|
|
123
|
-
unless data && data.fetch("head") == candidate.fetch("head") && !cancelled
|
|
124
|
-
raise AttemptErrors::ReceiptRejected, "ordinary independent review assignment is absent or revoked"
|
|
125
|
-
end
|
|
126
|
-
data
|
|
127
|
-
end
|
|
128
|
-
|
|
129
|
-
def ordinary_review_origins!(events, commit, assigned, reviewer_running: false)
|
|
130
|
-
producer = origin_at!(events: events, commit: commit)
|
|
131
|
-
reviewer_events = @journal.read_events(assigned.fetch("reviewer_assignment_id"), commit: commit)
|
|
132
|
-
.select { |event| event["attempt_id"] == assigned.fetch("reviewer_attempt_id") }
|
|
133
|
-
reviewer = ordinary_origin_at!(reviewer_events, commit, running: reviewer_running)
|
|
134
|
-
unless producer == assigned.fetch("producer_origin") && reviewer == assigned.fetch("reviewer_origin")
|
|
135
|
-
raise AttemptErrors::EvidenceUnavailable, "ordinary review origins differ"
|
|
136
|
-
end
|
|
137
|
-
ordinary_independent_agents!(producer.fetch("runtime_binding"), reviewer.fetch("runtime_binding"))
|
|
138
|
-
[producer, reviewer]
|
|
139
|
-
end
|
|
140
|
-
|
|
141
|
-
def ordinary_independent_agents!(producer, reviewer)
|
|
142
|
-
sessions = [producer, reviewer].map { |binding|
|
|
143
|
-
Ace::Herdr::Atoms::NativeAgentSession.normalize(binding.fetch("agent_session"), agent: binding.fetch("agent")) }
|
|
144
|
-
identities = [producer, reviewer].map { |binding|
|
|
145
|
-
identity = binding.fetch("process_identity")
|
|
146
|
-
unless identity.is_a?(Hash) && %w[pid uid started_at host].all? { |key| identity.key?(key) }
|
|
147
|
-
raise AttemptErrors::EvidenceUnavailable, "ordinary agent process generation is missing"
|
|
148
|
-
end
|
|
149
|
-
identity.values_at("pid", "uid", "started_at", "host") }
|
|
150
|
-
unless sessions.all? && sessions[0].fetch("value").downcase != sessions[1].fetch("value").downcase && identities[0] != identities[1] && producer.fetch("pane") != reviewer.fetch("pane")
|
|
151
|
-
raise AttemptErrors::ReceiptRejected, "review requires an independently started native agent and session"
|
|
152
|
-
end
|
|
153
|
-
true
|
|
154
|
-
rescue KeyError
|
|
155
|
-
raise AttemptErrors::EvidenceUnavailable, "ordinary agent identity is incomplete"
|
|
156
|
-
end
|
|
157
|
-
|
|
158
|
-
def ordinary_review_context(binding)
|
|
159
|
-
{kind: "review", project_id: @project, assignment_id: binding.fetch("producer_origin").fetch("assignment_id"),
|
|
160
|
-
attempt_id: binding.fetch("producer_origin").fetch("attempt_id"), peer_uid: binding.fetch("reviewer_uid"),
|
|
161
|
-
binding: binding, request_id_or_event_id: binding.fetch("review_id"), generation: binding.fetch("candidate_generation")}
|
|
162
|
-
end
|
|
163
|
-
|
|
164
|
-
def ordinary_verify_review!(receipt, events, commit, assigned, reader)
|
|
165
|
-
origin = origin_at!(events: events, commit: commit)
|
|
166
|
-
producer = events.find { |event| event["type"] == "process_start" }.fetch("payload").slice("actor", "role", "runtime")
|
|
167
|
-
unless receipt["operation"] == "review" && receipt["verdict"] == "succeeded" && !receipt["campaign"] &&
|
|
168
|
-
receipt["producer"] == producer && receipt.dig("review", "reviewer", "actor") == assigned.fetch("reviewer_actor")
|
|
169
|
-
raise AttemptErrors::ReceiptRejected, "ordinary review receipt attribution differs"
|
|
170
|
-
end
|
|
171
|
-
attempt = @journal.derived_attempts(origin.fetch("assignment_id"), commit: commit)
|
|
172
|
-
.find { |value| value.attempt_id == origin.fetch("attempt_id") }
|
|
173
|
-
independence = ->(_data) { ordinary_review_origins!(events, commit, assigned); true }
|
|
174
|
-
Molecules::ReceiptVerifier.new(artifact_reader: reader, review_independence_verifier: independence)
|
|
175
|
-
.verify_result!(receipt, attempt: attempt, live_head: assigned.fetch("head"), repo_root: @journal.repo_root)
|
|
176
|
-
end
|
|
177
|
-
end
|
|
178
|
-
end
|
|
179
|
-
end
|
|
180
|
-
end
|
|
@@ -1,214 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative "ordinary_review_evidence"
|
|
4
|
-
|
|
5
|
-
module Ace
|
|
6
|
-
module Assign
|
|
7
|
-
module Authority
|
|
8
|
-
module OrdinaryReviews
|
|
9
|
-
include OrdinaryReviewEvidence
|
|
10
|
-
REVIEW_OPERATIONS = %w[submit_candidate assign_review cancel_review export_candidate accept_review review_status].freeze
|
|
11
|
-
REVIEW_TRANSFERS = {
|
|
12
|
-
"submit_candidate" => {direction: :upload, purpose: :candidate, roles: %i[worker supervisor]},
|
|
13
|
-
"export_candidate" => {direction: :download, purpose: :candidate, roles: %i[worker supervisor]},
|
|
14
|
-
"accept_review" => {direction: :upload, purpose: :receipt_artifacts, roles: %i[worker supervisor]}
|
|
15
|
-
}.freeze
|
|
16
|
-
REVIEW_COMMON = %w[assignment_id attempt_id head candidate_generation pane].freeze
|
|
17
|
-
REVIEW_PARAMETERS = {
|
|
18
|
-
"submit_candidate" => REVIEW_COMMON + %w[expected_generation transfer],
|
|
19
|
-
"assign_review" => REVIEW_COMMON + %w[expected_generation reviewer_assignment_id reviewer_attempt_id],
|
|
20
|
-
"cancel_review" => REVIEW_COMMON + %w[expected_generation review_id],
|
|
21
|
-
"export_candidate" => REVIEW_COMMON + %w[review_id],
|
|
22
|
-
"accept_review" => REVIEW_COMMON + %w[expected_generation review_id receipt_sha256 transfer],
|
|
23
|
-
"review_status" => %w[assignment_id attempt_id head candidate_generation]
|
|
24
|
-
}.freeze
|
|
25
|
-
|
|
26
|
-
private
|
|
27
|
-
|
|
28
|
-
def validate_ordinary_review!(request)
|
|
29
|
-
operation, params = request.values_at("operation", "params")
|
|
30
|
-
strict!(params, REVIEW_PARAMETERS.fetch(operation))
|
|
31
|
-
%w[assignment_id attempt_id reviewer_assignment_id reviewer_attempt_id review_id].each { |key| token!(params[key]) if params.key?(key) }
|
|
32
|
-
unless params["head"].is_a?(String) && CandidateTransfer::SHA.match?(params["head"]) &&
|
|
33
|
-
params["candidate_generation"].is_a?(Integer) && params["candidate_generation"] >= 0
|
|
34
|
-
raise ArgumentError, "ordinary candidate selection is invalid"
|
|
35
|
-
end
|
|
36
|
-
if %w[export_candidate review_status].include?(operation)
|
|
37
|
-
raise ArgumentError, "ordinary review read mutation must be null" unless request["mutation_id"].nil?
|
|
38
|
-
else
|
|
39
|
-
token!(request["mutation_id"])
|
|
40
|
-
unless params["expected_generation"].is_a?(Integer) && params["expected_generation"].positive?
|
|
41
|
-
raise ArgumentError, "ordinary review generation is invalid"
|
|
42
|
-
end
|
|
43
|
-
end
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
def authorize_ordinary_review!(request, peer, role, deadline, events: nil, commit: nil)
|
|
47
|
-
authorize!(request, peer, role, deadline)
|
|
48
|
-
validate_ordinary_review!(request)
|
|
49
|
-
params = request.fetch("params")
|
|
50
|
-
commit ||= @journal.ref_value
|
|
51
|
-
events ||= result_events(params, commit)
|
|
52
|
-
origin = origin_at!(events: events, commit: commit)
|
|
53
|
-
operation = request.fetch("operation")
|
|
54
|
-
return origin if operation == "review_status"
|
|
55
|
-
identity = @admission.identity!(peer: peer, pane: params.fetch("pane"), deadline: deadline)
|
|
56
|
-
case operation
|
|
57
|
-
when "submit_candidate"
|
|
58
|
-
unless identity.runtime_binding == origin.fetch("runtime_binding")
|
|
59
|
-
raise AttemptErrors::UnauthorizedIdentity, "candidate belongs to its original native producer"
|
|
60
|
-
end
|
|
61
|
-
when "assign_review", "cancel_review"
|
|
62
|
-
registered = registration(params.fetch("assignment_id"), commit: commit)
|
|
63
|
-
unless role == :supervisor && identity.role == "coordinator" && identity.runtime_binding == supervisor_selection(registered, commit).fetch("binding")
|
|
64
|
-
raise AttemptErrors::UnauthorizedIdentity, "review selection belongs to the current canonical supervisor"
|
|
65
|
-
end
|
|
66
|
-
when "export_candidate", "accept_review"
|
|
67
|
-
candidate = candidate_at!(events: events, commit: commit, head: params.fetch("head"), generation: params.fetch("candidate_generation"))
|
|
68
|
-
assigned = ordinary_review_assignment!(events, candidate)
|
|
69
|
-
_, reviewer = ordinary_review_origins!(events, commit, assigned, reviewer_running: true)
|
|
70
|
-
unless assigned.fetch("review_id") == params.fetch("review_id") && identity.runtime_binding == reviewer.fetch("runtime_binding") &&
|
|
71
|
-
identity.actor == assigned.fetch("reviewer_actor") && peer.fetch("uid") == assigned.fetch("reviewer_uid")
|
|
72
|
-
raise AttemptErrors::UnauthorizedIdentity, "review belongs to its assigned native agent"
|
|
73
|
-
end
|
|
74
|
-
end
|
|
75
|
-
origin
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
def ordinary_review_operation(request, peer, role, deadline, transfer)
|
|
79
|
-
params, operation = request.values_at("params", "operation")
|
|
80
|
-
authorize_ordinary_review!(request, peer, role, deadline)
|
|
81
|
-
admitted = if operation == "submit_candidate"
|
|
82
|
-
raise ArgumentError, "ordinary candidate requires one bundle" unless transfer && transfer.count == 1
|
|
83
|
-
descriptor = params.fetch("transfer")
|
|
84
|
-
CandidateTransfer.new(root: @input_root).admit(bytes: transfer.bytes, sha256: descriptor.fetch("sha256"),
|
|
85
|
-
size: descriptor.fetch("bytes"), head: params.fetch("head"), deadline: deadline)
|
|
86
|
-
elsif operation == "accept_review"
|
|
87
|
-
ReceiptTransfer.decode(input: transfer, receipt_sha256: params.fetch("receipt_sha256"), artifact_field: "artifacts", reference_key: "path")
|
|
88
|
-
elsif transfer
|
|
89
|
-
raise ArgumentError, "ordinary review operation forbids upload"
|
|
90
|
-
end
|
|
91
|
-
with_scope(assignment: params.fetch("assignment_id"), attempt: params.fetch("attempt_id"), deadline: deadline) do
|
|
92
|
-
commit = @journal.ref_value
|
|
93
|
-
events = result_events(params, commit)
|
|
94
|
-
authorize_ordinary_review!(request, peer, role, deadline, events: events, commit: commit)
|
|
95
|
-
if %w[export_candidate review_status].include?(operation)
|
|
96
|
-
current = candidate_at!(events: events, commit: commit, head: params.fetch("head"), generation: params.fetch("candidate_generation"))
|
|
97
|
-
assigned = ordinary_review_assignment!(events, current)
|
|
98
|
-
if operation == "export_candidate"
|
|
99
|
-
bytes = @journal.bounded_blob(current.fetch("bundle_ref"), commit: commit, max_bytes: CandidateTransfer::MAX_BYTES)
|
|
100
|
-
return {data: current.merge("review_id" => assigned.fetch("review_id")), transfer_parts: [bytes], replayed: false}
|
|
101
|
-
end
|
|
102
|
-
accepted = events.any? { |event| event["type"] == "authority_mutation" && event.dig("payload", "operation") == "accept_review" && event.dig("payload", "data", "review_id") == assigned.fetch("review_id") }
|
|
103
|
-
value = accepted ? review_at!(events: events, commit: commit, head: params.fetch("head"), generation: params.fetch("candidate_generation")) : assigned
|
|
104
|
-
return {data: value.merge("accepted" => accepted, "journal_commit" => commit), replayed: false}
|
|
105
|
-
end
|
|
106
|
-
digest = Atoms::EvidenceDigest.digest({"params" => params, "project_id" => @project, "peer_uid" => peer.fetch("uid")})
|
|
107
|
-
replay = lambda do
|
|
108
|
-
authorize_ordinary_review!(request, peer, role, deadline)
|
|
109
|
-
if operation == "submit_candidate"
|
|
110
|
-
ordinary_candidate_replay!(request, admitted)
|
|
111
|
-
elsif operation == "accept_review"
|
|
112
|
-
prefix = @journal.ref_value
|
|
113
|
-
review_at!(events: result_events(params, prefix), commit: prefix, head: params.fetch("head"), generation: params.fetch("candidate_generation"))
|
|
114
|
-
end
|
|
115
|
-
end
|
|
116
|
-
@journal.mutate(assignment_id: params.fetch("assignment_id"), attempt_id: params.fetch("attempt_id"),
|
|
117
|
-
mutation_id: request.fetch("mutation_id"), operation: operation, parameters_digest: digest,
|
|
118
|
-
expected_generation: params.fetch("expected_generation"), with_replay: true, deadline: deadline,
|
|
119
|
-
replay_authorizer: replay) do |current_events, prefix, _generation|
|
|
120
|
-
authorize_ordinary_review!(request, peer, role, deadline, events: current_events, commit: prefix)
|
|
121
|
-
ordinary_review_plan(operation, params, peer, deadline, current_events, prefix, admitted, transfer)
|
|
122
|
-
end
|
|
123
|
-
end
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
def ordinary_review_plan(operation, params, peer, deadline, events, commit, admitted, transfer)
|
|
127
|
-
if operation == "submit_candidate"
|
|
128
|
-
current = ordinary_candidate_event(events)&.dig("payload", "data")
|
|
129
|
-
unless (current && current.fetch("candidate_generation") || 0) == params.fetch("candidate_generation")
|
|
130
|
-
raise AttemptErrors::Conflict, "ordinary candidate generation changed"
|
|
131
|
-
end
|
|
132
|
-
observed = @admission.execution!(peer: peer, pane: params.fetch("pane"), head: params.fetch("head"), deadline: deadline)
|
|
133
|
-
unless observed.fetch(:identity).runtime_binding == origin_at!(events: events, commit: commit).fetch("runtime_binding")
|
|
134
|
-
raise AttemptErrors::UnauthorizedIdentity, "ordinary candidate worker changed"
|
|
135
|
-
end
|
|
136
|
-
path = "candidates/bundles/#{SecureRandom.hex(16)}"
|
|
137
|
-
return {events: [], blobs: {path => admitted.fetch("bundle")}, data: admitted.except("bundle").merge(
|
|
138
|
-
"bundle_ref" => path, "candidate_generation" => params.fetch("candidate_generation") + 1,
|
|
139
|
-
"producer_origin" => origin_at!(events: events, commit: commit))}
|
|
140
|
-
end
|
|
141
|
-
current = candidate_at!(events: events, commit: commit, head: params.fetch("head"), generation: params.fetch("candidate_generation"))
|
|
142
|
-
if operation == "assign_review"
|
|
143
|
-
prior = events.reverse.find { |event| event["type"] == "authority_mutation" && event.dig("payload", "operation") == "assign_review" &&
|
|
144
|
-
event.dig("payload", "data", "candidate_generation") == current.fetch("candidate_generation") }
|
|
145
|
-
if prior && !events.any? { |event| event.dig("payload", "operation") == "cancel_review" && event.dig("payload", "data", "review_id") == prior.dig("payload", "data", "review_id") }
|
|
146
|
-
raise AttemptErrors::Conflict, "ordinary review is already assigned"
|
|
147
|
-
end
|
|
148
|
-
producer = origin_at!(events: events, commit: commit)
|
|
149
|
-
reviewer_events = @journal.read_events(params.fetch("reviewer_assignment_id"), commit: commit)
|
|
150
|
-
.select { |event| event["attempt_id"] == params.fetch("reviewer_attempt_id") }
|
|
151
|
-
reviewer = ordinary_origin_at!(reviewer_events, commit, running: true)
|
|
152
|
-
ordinary_independent_agents!(producer.fetch("runtime_binding"), reviewer.fetch("runtime_binding"))
|
|
153
|
-
observed = @admission.recorded_binding!(binding: reviewer.fetch("runtime_binding"), deadline: deadline)
|
|
154
|
-
unless observed == reviewer.fetch("runtime_binding")
|
|
155
|
-
raise AttemptErrors::UnauthorizedIdentity, "ordinary reviewer native binding changed"
|
|
156
|
-
end
|
|
157
|
-
start = reviewer_events.find { |event| event["type"] == "process_start" }.fetch("payload")
|
|
158
|
-
return {events: [], data: current.slice("head", "candidate_generation").merge(
|
|
159
|
-
"review_id" => SecureRandom.hex(16), "reviewer_assignment_id" => params.fetch("reviewer_assignment_id"),
|
|
160
|
-
"reviewer_attempt_id" => params.fetch("reviewer_attempt_id"), "reviewer_actor" => start.fetch("actor"),
|
|
161
|
-
"reviewer_uid" => reviewer.dig("runtime_binding", "process_identity", "uid"),
|
|
162
|
-
"reviewer_runtime_binding" => reviewer.fetch("runtime_binding"), "producer_origin" => producer, "reviewer_origin" => reviewer)}
|
|
163
|
-
end
|
|
164
|
-
assigned = ordinary_review_assignment!(events, current)
|
|
165
|
-
raise AttemptErrors::Conflict, "ordinary review selection changed" unless assigned.fetch("review_id") == params.fetch("review_id")
|
|
166
|
-
return {events: [], data: current.slice("head", "candidate_generation").merge("review_id" => assigned.fetch("review_id"))} if operation == "cancel_review"
|
|
167
|
-
if events.any? { |event| event.dig("payload", "operation") == "accept_review" && event.dig("payload", "data", "review_id") == assigned.fetch("review_id") }
|
|
168
|
-
raise AttemptErrors::Conflict, "ordinary review already accepted; retain original request"
|
|
169
|
-
end
|
|
170
|
-
observed = @admission.execution!(peer: peer, pane: params.fetch("pane"), head: current.fetch("head"), deadline: deadline)
|
|
171
|
-
unless observed.fetch(:identity).runtime_binding == assigned.fetch("reviewer_runtime_binding") &&
|
|
172
|
-
observed.fetch(:identity).actor == assigned.fetch("reviewer_actor")
|
|
173
|
-
raise AttemptErrors::UnauthorizedIdentity, "ordinary reviewer changed before receipt acceptance"
|
|
174
|
-
end
|
|
175
|
-
ordinary_accept_review_plan(params, events, commit, assigned, admitted, transfer.bytes(index: 0))
|
|
176
|
-
end
|
|
177
|
-
|
|
178
|
-
def ordinary_candidate_replay!(request, admitted)
|
|
179
|
-
retained = @journal.mutation_result(request.fetch("mutation_id"))
|
|
180
|
-
data = retained.fetch("data")
|
|
181
|
-
bytes = @journal.bounded_blob(data.fetch("bundle_ref"), commit: retained.fetch("journal_commit"), max_bytes: CandidateTransfer::MAX_BYTES)
|
|
182
|
-
unless data.values_at("head", "tree", "sha256", "bytes") == admitted.values_at("head", "tree", "sha256", "bytes") &&
|
|
183
|
-
bytes.bytesize == data.fetch("bytes") && Digest::SHA256.hexdigest(bytes) == data.fetch("sha256")
|
|
184
|
-
raise AttemptErrors::EvidenceUnavailable, "ordinary retained candidate bytes differ"
|
|
185
|
-
end
|
|
186
|
-
end
|
|
187
|
-
|
|
188
|
-
def ordinary_accept_review_plan(params, events, commit, assigned, admitted, original_bytes)
|
|
189
|
-
original = admitted.fetch(:receipt)
|
|
190
|
-
original_model = Models::ExecutionReceipt.from_h(original)
|
|
191
|
-
unless (original.keys - original_model.to_h.keys).empty? &&
|
|
192
|
-
original_model.digest == Atoms::EvidenceDigest.digest(original_model.digest_payload)
|
|
193
|
-
raise AttemptErrors::ReceiptRejected, "ordinary uploaded review digest differs"
|
|
194
|
-
end
|
|
195
|
-
binding = assigned.except("generation", "journal_commit")
|
|
196
|
-
canonical = Molecules::CanonicalEvidence.new(journal: @journal)
|
|
197
|
-
plan = canonical.import_plan(**ordinary_review_context(binding), artifacts: [original_bytes] + admitted.fetch(:artifacts),
|
|
198
|
-
admitted_after_event_digest: events.last.fetch("digest"))
|
|
199
|
-
normalized = original.except("digest", "recorded_at").merge("artifacts" => plan.fetch(:references).drop(1).map { |ref| {"path" => ref.fetch("ref"), "sha256" => ref.fetch("sha256")} })
|
|
200
|
-
pending = @journal.send(:chain_mutation_events, params.fetch("attempt_id"), events.last.fetch("digest"), plan.fetch(:events))
|
|
201
|
-
reader = ->(_receipt, artifact) { canonical.read_pending({"ref" => artifact.fetch("path"), "sha256" => artifact.fetch("sha256")},
|
|
202
|
-
**ordinary_review_context(binding), current_events: events, pending_events: pending, blobs: plan.fetch(:blobs), commit: commit) }
|
|
203
|
-
verified = ordinary_verify_review!(normalized, events, commit, assigned, reader)
|
|
204
|
-
path = plan.fetch(:references).first.fetch("ref")
|
|
205
|
-
data = binding.slice("head", "candidate_generation", "review_id", "reviewer_uid", "reviewer_actor", "reviewer_runtime_binding", "producer_origin")
|
|
206
|
-
.merge("review_binding" => binding, "review_receipt" => verified.to_h, "receipt_digest" => verified.digest,
|
|
207
|
-
"original_receipt_ref" => path, "uploaded_receipt_sha256" => admitted.fetch(:receipt_sha256))
|
|
208
|
-
raise AttemptErrors::ReceiptRejected, "ordinary review reply is oversized" if JSON.generate(data).bytesize > 12_000
|
|
209
|
-
plan.merge(data: data)
|
|
210
|
-
end
|
|
211
|
-
end
|
|
212
|
-
end
|
|
213
|
-
end
|
|
214
|
-
end
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative "socket_server"
|
|
4
|
-
|
|
5
|
-
module Ace
|
|
6
|
-
module Assign
|
|
7
|
-
module Authority
|
|
8
|
-
# Explicit ordinary admission over Assign's existing wire/transfer owner.
|
|
9
|
-
# The Lab composition supplies a trusted installed admission object and
|
|
10
|
-
# closed handlers. No protected Deployment or kernel adapter is replaced.
|
|
11
|
-
class OrdinaryServer < SocketServer
|
|
12
|
-
def initialize(admission:, lifecycle:, services: [])
|
|
13
|
-
@admission = admission
|
|
14
|
-
super(service: admission.service, lifecycle: lifecycle, services: services)
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
private
|
|
18
|
-
|
|
19
|
-
def verify_listener!
|
|
20
|
-
@admission.verify_listener!
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def authorize_transfer!(request:, peer:, role:, deadline:)
|
|
24
|
-
@lifecycle.authorize_transfer!(request: request, peer: peer, role: role, deadline: deadline)
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
def dispatch_lifecycle(request:, peer:, role:, deadline:, transfer: nil)
|
|
28
|
-
@lifecycle.dispatch(request: request, peer: peer, role: role, deadline: deadline, transfer: transfer)
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
def receive(socket)
|
|
32
|
-
deadline = wire.deadline(30)
|
|
33
|
-
peer = @admission.peer(socket, deadline: deadline)
|
|
34
|
-
ingress = Ace::Runtime::Molecules::ProtectedSocket::Ingress.new(socket)
|
|
35
|
-
frame = wire.read(ingress, deadline: [deadline, wire.deadline(5)].min, limit: 16_384, with_size: true)
|
|
36
|
-
request = frame.fetch(:data)
|
|
37
|
-
unless request.is_a?(Hash) && request.keys.sort == %w[mutation_id operation params project_id version] &&
|
|
38
|
-
request["version"].is_a?(Integer) && request["version"] == 1 &&
|
|
39
|
-
request["operation"].is_a?(String) && request["params"].is_a?(Hash)
|
|
40
|
-
raise ArgumentError, "invalid ordinary authority envelope"
|
|
41
|
-
end
|
|
42
|
-
role = @admission.authorize!(request: request, peer: peer, deadline: deadline)
|
|
43
|
-
binding = @lifecycle.transfer_binding(request)
|
|
44
|
-
bodyless_read!(ingress, deadline) unless binding && binding.fetch(:direction) == :upload
|
|
45
|
-
# Both binary bodies and EOF use ancillary-rejecting reads. Replies
|
|
46
|
-
# use the original duplex socket through the wrapper below.
|
|
47
|
-
dispatch_request(Duplex.new(socket, ingress), request, peer, role, deadline, frame_bytes: frame.fetch(:bytesize))
|
|
48
|
-
rescue ArgumentError, KeyError, AttemptErrors::MalformedTransfer
|
|
49
|
-
refusal(socket, "invalid_input")
|
|
50
|
-
rescue AttemptErrors::UnauthorizedIdentity, SecurityError
|
|
51
|
-
refusal(socket, "unauthorized")
|
|
52
|
-
rescue AttemptErrors::Conflict
|
|
53
|
-
refusal(socket, "conflict")
|
|
54
|
-
rescue AttemptErrors::NotFound
|
|
55
|
-
refusal(socket, "missing")
|
|
56
|
-
rescue AttemptErrors::BoundedResultUnavailable
|
|
57
|
-
refusal(socket, "bounded_result")
|
|
58
|
-
rescue StandardError
|
|
59
|
-
refusal(socket, "evidence_unavailable")
|
|
60
|
-
ensure
|
|
61
|
-
socket.close unless socket.closed?
|
|
62
|
-
@mutex.synchronize { @connections.delete(socket) }
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
class Duplex
|
|
66
|
-
def initialize(socket, ingress) = (@socket, @ingress = socket, ingress)
|
|
67
|
-
def to_io = @socket
|
|
68
|
-
def read_nonblock(*args, **options) = @ingress.read_nonblock(*args, **options)
|
|
69
|
-
def write_nonblock(*args, **options) = @socket.write_nonblock(*args, **options)
|
|
70
|
-
end
|
|
71
|
-
private_constant :Duplex
|
|
72
|
-
end
|
|
73
|
-
end
|
|
74
|
-
end
|
|
75
|
-
end
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Ace
|
|
4
|
-
module Assign
|
|
5
|
-
module Authority
|
|
6
|
-
# Replacement transfers only result ownership, never worker execution.
|
|
7
|
-
# The existing definition chain serializes it with canonical generation.
|
|
8
|
-
module OrdinarySupervisor
|
|
9
|
-
SUPERVISOR_OPERATIONS = %w[supervisor_status adopt_supervisor].freeze
|
|
10
|
-
|
|
11
|
-
private
|
|
12
|
-
|
|
13
|
-
def supervisor_operation(request, peer, role, deadline)
|
|
14
|
-
params = request.fetch("params")
|
|
15
|
-
assignment = params.fetch("assignment_id")
|
|
16
|
-
token!(assignment)
|
|
17
|
-
registered = registration(assignment, commit: @journal.ref_value)
|
|
18
|
-
raise AttemptErrors::NotFound, "ordinary work is not registered" unless registered
|
|
19
|
-
if request.fetch("operation") == "supervisor_status"
|
|
20
|
-
strict!(params, %w[assignment_id])
|
|
21
|
-
raise ArgumentError, "supervisor status mutation must be null" unless request.fetch("mutation_id").nil?
|
|
22
|
-
commit = @journal.ref_value
|
|
23
|
-
selected = supervisor_selection(registered, commit)
|
|
24
|
-
authorize!(request, peer, role, deadline)
|
|
25
|
-
return {data: selected.merge("journal_commit" => commit), replayed: false}
|
|
26
|
-
end
|
|
27
|
-
strict!(params, %w[assignment_id expected_generation pane previous_binding_sha256])
|
|
28
|
-
token!(request.fetch("mutation_id"))
|
|
29
|
-
unless role == :supervisor && params.fetch("expected_generation").is_a?(Integer) &&
|
|
30
|
-
params.fetch("expected_generation").positive? && params.fetch("previous_binding_sha256").is_a?(String) &&
|
|
31
|
-
params.fetch("previous_binding_sha256").match?(/\A[0-9a-f]{64}\z/)
|
|
32
|
-
raise AttemptErrors::UnauthorizedIdentity, "supervisor adoption requires exact current selection"
|
|
33
|
-
end
|
|
34
|
-
observed = @admission.identity!(peer: peer, pane: params.fetch("pane"), deadline: deadline)
|
|
35
|
-
raise AttemptErrors::UnauthorizedIdentity, "replacement is not an installed coordinator" unless observed.role == "coordinator"
|
|
36
|
-
binding = observed.runtime_binding
|
|
37
|
-
digest = Atoms::EvidenceDigest.digest({"project_id" => @project, "params" => params, "binding" => binding})
|
|
38
|
-
@exclusion.with_shared_multi([@exclusion.task_key(registered.fetch("task_id")), @exclusion.assignment_key(assignment)], deadline: deadline) do
|
|
39
|
-
@journal.mutate(assignment_id: assignment, attempt_id: "definition-#{assignment}",
|
|
40
|
-
mutation_id: request.fetch("mutation_id"), operation: "adopt_supervisor", parameters_digest: digest,
|
|
41
|
-
expected_generation: params.fetch("expected_generation"), with_replay: true, deadline: deadline,
|
|
42
|
-
replay_authorizer: -> {
|
|
43
|
-
verify_new_supervisor!(request, peer, role, deadline, observed)
|
|
44
|
-
unless supervisor_selection(registered, @journal.ref_value).fetch("binding") == binding
|
|
45
|
-
raise AttemptErrors::UnauthorizedIdentity, "supervisor adoption was superseded"
|
|
46
|
-
end
|
|
47
|
-
}) do |_events, prefix, _generation|
|
|
48
|
-
verify_new_supervisor!(request, peer, role, deadline, observed)
|
|
49
|
-
previous = supervisor_selection(registered, prefix)
|
|
50
|
-
unless previous.fetch("binding_sha256") == params.fetch("previous_binding_sha256") && previous.fetch("binding") != binding
|
|
51
|
-
raise AttemptErrors::Conflict, "original supervisor selection changed"
|
|
52
|
-
end
|
|
53
|
-
ended = @admission.supervisor_ended!(binding: previous.fetch("binding"), deadline: deadline)
|
|
54
|
-
unless ended.is_a?(Hash) && ended["liveness"] == "ended"
|
|
55
|
-
raise AttemptErrors::EvidenceUnavailable, "original supervisor generation has not ended"
|
|
56
|
-
end
|
|
57
|
-
verify_new_supervisor!(request, peer, role, deadline, observed)
|
|
58
|
-
{events: [], data: {"authority" => "ordinary", "assignment_id" => assignment, "project_id" => @project,
|
|
59
|
-
"previous_binding" => previous.fetch("binding"), "binding" => binding,
|
|
60
|
-
"binding_sha256" => Atoms::EvidenceDigest.digest(binding), "ended_observation" => ended}}
|
|
61
|
-
end
|
|
62
|
-
end
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
def verify_new_supervisor!(request, peer, role, deadline, observed)
|
|
66
|
-
authorize!(request, peer, role, deadline)
|
|
67
|
-
unless @admission.identity!(peer: peer, pane: request.fetch("params").fetch("pane"), deadline: deadline) == observed
|
|
68
|
-
raise AttemptErrors::UnauthorizedIdentity, "replacement supervisor changed during adoption"
|
|
69
|
-
end
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
def supervisor_selection(registered, commit)
|
|
73
|
-
assignment = registered.fetch("assignment_id")
|
|
74
|
-
events = @journal.read_events(assignment, commit: commit).select { |event| event.fetch("attempt_id") == "definition-#{assignment}" }
|
|
75
|
-
unless Models::EvidenceEvent.chain_valid?(events)
|
|
76
|
-
raise AttemptErrors::EvidenceUnavailable, "ordinary supervisor history is corrupt"
|
|
77
|
-
end
|
|
78
|
-
selected = registered.fetch("supervisor_binding")
|
|
79
|
-
generation = 1
|
|
80
|
-
# Validate the entire old→new chain rather than trusting a local cache.
|
|
81
|
-
events.select { |event| event["type"] == "authority_mutation" }.each do |event|
|
|
82
|
-
payload = event.fetch("payload")
|
|
83
|
-
next if payload.fetch("operation") == "register_assignment"
|
|
84
|
-
data = payload.fetch("data")
|
|
85
|
-
unless payload.fetch("operation") == "adopt_supervisor" && data.fetch("previous_binding") == selected &&
|
|
86
|
-
data.values_at("authority", "assignment_id", "project_id", "generation") == ["ordinary", assignment, @project, generation + 1] &&
|
|
87
|
-
data.fetch("binding_sha256") == Atoms::EvidenceDigest.digest(data.fetch("binding")) &&
|
|
88
|
-
data.dig("ended_observation", "liveness") == "ended"
|
|
89
|
-
raise AttemptErrors::EvidenceUnavailable, "ordinary supervisor adoption chain differs"
|
|
90
|
-
end
|
|
91
|
-
selected, generation = data.fetch("binding"), data.fetch("generation")
|
|
92
|
-
end
|
|
93
|
-
{"project_id" => @project, "assignment_id" => assignment, "generation" => generation,
|
|
94
|
-
"binding" => selected, "binding_sha256" => Atoms::EvidenceDigest.digest(selected)}
|
|
95
|
-
end
|
|
96
|
-
end
|
|
97
|
-
end
|
|
98
|
-
end
|
|
99
|
-
end
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Ace
|
|
4
|
-
module Assign
|
|
5
|
-
module Authority
|
|
6
|
-
# Original descriptor selection shared by canonical Inbox consumers.
|
|
7
|
-
# The held historical manifest/key owner supplies the exact artifact.
|
|
8
|
-
class OriginalInboxSelection
|
|
9
|
-
def initialize(history:, authority_id:)
|
|
10
|
-
@history, @authority_id = history, authority_id
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
def load!(events:, params:, map:, journal:, commit:)
|
|
14
|
-
provisioning = events.select { |event| event["type"] == "scope_provisioning" }
|
|
15
|
-
reservations = events.select { |event| event["type"] == "authority_mutation" && event.dig("payload", "operation") == "reserve_attempt" }
|
|
16
|
-
unless provisioning.one? && reservations.one?
|
|
17
|
-
raise AttemptErrors::EvidenceUnavailable, "context completion original selection is ambiguous"
|
|
18
|
-
end
|
|
19
|
-
identity = provisioning.first.fetch("payload")
|
|
20
|
-
reserved = reservations.first.fetch("payload").fetch("data")
|
|
21
|
-
original = @history.descriptor!(sha256: identity.fetch("descriptor_sha256"))
|
|
22
|
-
selected = original.mapping(params.fetch("mapping_id"))
|
|
23
|
-
unless identity.keys.sort == %w[deployment_digest descriptor_sha256 reservation_generation slot_id] &&
|
|
24
|
-
reserved.values_at("project_id", "assignment_id", "attempt_id", "mapping_id") ==
|
|
25
|
-
[map.fetch("project_id"), params.fetch("assignment_id"), params.fetch("attempt_id"), params.fetch("mapping_id")] &&
|
|
26
|
-
identity.fetch("reservation_generation") == reserved.fetch("reservation_generation") &&
|
|
27
|
-
identity.fetch("deployment_digest") == original.mapping_digest(params.fetch("mapping_id")) &&
|
|
28
|
-
selected.fetch("project_id") == map.fetch("project_id") && selected.fetch("authority_id") == @authority_id &&
|
|
29
|
-
identity.fetch("slot_id") == selected.fetch("execution_scope").fetch("slot_id") &&
|
|
30
|
-
original.project(selected.fetch("project_id")).values_at("journal_repository", "evidence_git_ref", "evidence_checkout_root") ==
|
|
31
|
-
[journal.repo_root, journal.ref, journal.checkout_root]
|
|
32
|
-
raise AttemptErrors::EvidenceUnavailable, "context completion original association differs"
|
|
33
|
-
end
|
|
34
|
-
journal.event_commit!(assignment_id: params.fetch("assignment_id"), event_digest: provisioning.first.fetch("digest"), commit: commit)
|
|
35
|
-
[original, selected]
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
end
|