ace-assign 0.71.0 → 0.73.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (197) hide show
  1. checksums.yaml +4 -4
  2. data/.ace-defaults/assign/catalog/steps/auto-merge.step.yml +8 -8
  3. data/.ace-defaults/assign/catalog/steps/create-pr.step.yml +1 -1
  4. data/.ace-defaults/assign/catalog/steps/mark-pr-ready.step.yml +1 -1
  5. data/.ace-defaults/assign/config.yml +0 -13
  6. data/.ace-defaults/assign/presets/fix-bug.yml +1 -1
  7. data/.ace-defaults/assign/presets/work-on-task-ace-development.yml +1 -1
  8. data/.ace-defaults/assign/presets/work-on-task-auto-merge.yml +1 -1
  9. data/.ace-defaults/assign/presets/work-on-task.yml +1 -1
  10. data/CHANGELOG.md +5 -2116
  11. data/README.md +58 -31
  12. data/docs/exit-codes.md +36 -44
  13. data/docs/getting-started.md +9 -4
  14. data/docs/usage.md +129 -522
  15. data/handbook/skills/as-assign-drive/SKILL.md +1 -4
  16. data/handbook/workflow-instructions/assign/drive.wf.md +47 -195
  17. data/handbook/workflow-instructions/assign/prepare.wf.md +1 -1
  18. data/handbook/workflow-instructions/assign/verify-test-suite.wf.md +8 -2
  19. data/lib/ace/assign/cli/commands/assignment_target.rb +2 -15
  20. data/lib/ace/assign/cli/commands/create.rb +1 -8
  21. data/lib/ace/assign/cli/commands/fail.rb +0 -6
  22. data/lib/ace/assign/cli/commands/finish.rb +2 -23
  23. data/lib/ace/assign/cli/commands/fork_session.rb +0 -1
  24. data/lib/ace/assign/cli/commands/select.rb +0 -1
  25. data/lib/ace/assign/cli/commands/start.rb +0 -2
  26. data/lib/ace/assign/cli/commands/status.rb +1 -31
  27. data/lib/ace/assign/cli/commands/step.rb +1 -3
  28. data/lib/ace/assign/cli.rb +1 -85
  29. data/lib/ace/assign/errors.rb +8 -80
  30. data/lib/ace/assign/models/assignment.rb +5 -28
  31. data/lib/ace/assign/molecules/assignment_manager.rb +0 -26
  32. data/lib/ace/assign/molecules/fork_session_launcher.rb +4 -12
  33. data/lib/ace/assign/molecules/lifecycle_exclusion.rb +15 -112
  34. data/lib/ace/assign/molecules/runtime_control_surface_runner.rb +6 -2
  35. data/lib/ace/assign/molecules/skill_assign_source_resolver.rb +9 -0
  36. data/lib/ace/assign/organisms/assignment_executor.rb +0 -6
  37. data/lib/ace/assign/organisms/task_assignment_creator.rb +3 -8
  38. data/lib/ace/assign/version.rb +1 -1
  39. data/lib/ace/assign.rb +0 -6
  40. metadata +22 -176
  41. data/.ace-defaults/assign/presets/close-delivery.yml +0 -57
  42. data/.ace-defaults/assign/presets/deliver-release.yml +0 -60
  43. data/.ace-defaults/assign/presets/integrate-batch.yml +0 -72
  44. data/.ace-defaults/assign/presets/supervise-delivery.yml +0 -69
  45. data/docs/protected-authority.md +0 -282
  46. data/exe/ace-scope-ready +0 -55
  47. data/lib/ace/assign/atoms/assignment_scope.rb +0 -58
  48. data/lib/ace/assign/atoms/attempt_state_machine.rb +0 -84
  49. data/lib/ace/assign/atoms/candidate_limits.rb +0 -20
  50. data/lib/ace/assign/atoms/delivery_parameters.rb +0 -19
  51. data/lib/ace/assign/atoms/evidence_digest.rb +0 -78
  52. data/lib/ace/assign/atoms/fixed_merge_schemas.rb +0 -398
  53. data/lib/ace/assign/atoms/git_blob_batch.rb +0 -59
  54. data/lib/ace/assign/authority/assignment_definition.rb +0 -35
  55. data/lib/ace/assign/authority/assignment_inventory.rb +0 -290
  56. data/lib/ace/assign/authority/campaign_consumer_policy.rb +0 -100
  57. data/lib/ace/assign/authority/campaign_execution.rb +0 -119
  58. data/lib/ace/assign/authority/campaign_round_transfer.rb +0 -82
  59. data/lib/ace/assign/authority/candidate_blob_batch.rb +0 -55
  60. data/lib/ace/assign/authority/candidate_head_comparison.rb +0 -139
  61. data/lib/ace/assign/authority/candidate_transfer.rb +0 -357
  62. data/lib/ace/assign/authority/cleanup_result_protection.rb +0 -53
  63. data/lib/ace/assign/authority/client.rb +0 -257
  64. data/lib/ace/assign/authority/command_execution.rb +0 -180
  65. data/lib/ace/assign/authority/deployment.rb +0 -627
  66. data/lib/ace/assign/authority/deployment_history.rb +0 -146
  67. data/lib/ace/assign/authority/endcap.rb +0 -457
  68. data/lib/ace/assign/authority/endcap_attempts.rb +0 -274
  69. data/lib/ace/assign/authority/endcap_campaign_results.rb +0 -232
  70. data/lib/ace/assign/authority/endcap_campaign_rounds.rb +0 -266
  71. data/lib/ace/assign/authority/endcap_contexts.rb +0 -23
  72. data/lib/ace/assign/authority/endcap_inboxes.rb +0 -37
  73. data/lib/ace/assign/authority/endcap_prepared_work.rb +0 -80
  74. data/lib/ace/assign/authority/endcap_publication.rb +0 -41
  75. data/lib/ace/assign/authority/endcap_results.rb +0 -398
  76. data/lib/ace/assign/authority/endcap_review_requests.rb +0 -183
  77. data/lib/ace/assign/authority/endcap_reviews.rb +0 -130
  78. data/lib/ace/assign/authority/endcap_service_context.rb +0 -38
  79. data/lib/ace/assign/authority/endcap_service_settlement.rb +0 -152
  80. data/lib/ace/assign/authority/evidence_transfer.rb +0 -67
  81. data/lib/ace/assign/authority/execution_scope_observation.rb +0 -903
  82. data/lib/ace/assign/authority/fixed_publication_lifecycle.rb +0 -176
  83. data/lib/ace/assign/authority/fixed_service_lifecycle.rb +0 -594
  84. data/lib/ace/assign/authority/inbox_context_original.rb +0 -113
  85. data/lib/ace/assign/authority/launch_attempt_consumers.rb +0 -51
  86. data/lib/ace/assign/authority/launch_campaign_registration.rb +0 -214
  87. data/lib/ace/assign/authority/launch_control_channel.rb +0 -313
  88. data/lib/ace/assign/authority/launch_control_exclusions.rb +0 -127
  89. data/lib/ace/assign/authority/launch_driver.rb +0 -393
  90. data/lib/ace/assign/authority/launch_input_inhibition.rb +0 -132
  91. data/lib/ace/assign/authority/launch_lifecycle.rb +0 -997
  92. data/lib/ace/assign/authority/launch_prepared_work.rb +0 -122
  93. data/lib/ace/assign/authority/launch_review.rb +0 -91
  94. data/lib/ace/assign/authority/launch_scope_abort.rb +0 -81
  95. data/lib/ace/assign/authority/launch_scope_admission.rb +0 -215
  96. data/lib/ace/assign/authority/launch_scope_close.rb +0 -146
  97. data/lib/ace/assign/authority/launch_scope_parent.rb +0 -44
  98. data/lib/ace/assign/authority/launch_scope_release.rb +0 -859
  99. data/lib/ace/assign/authority/launch_steering.rb +0 -353
  100. data/lib/ace/assign/authority/launch_stop.rb +0 -254
  101. data/lib/ace/assign/authority/maintenance_workspace_retirement.rb +0 -229
  102. data/lib/ace/assign/authority/ordinary_attempt_start.rb +0 -137
  103. data/lib/ace/assign/authority/ordinary_client.rb +0 -70
  104. data/lib/ace/assign/authority/ordinary_completion.rb +0 -104
  105. data/lib/ace/assign/authority/ordinary_effect_settlement.rb +0 -118
  106. data/lib/ace/assign/authority/ordinary_lifecycle.rb +0 -227
  107. data/lib/ace/assign/authority/ordinary_merge_evidence.rb +0 -81
  108. data/lib/ace/assign/authority/ordinary_peer.rb +0 -54
  109. data/lib/ace/assign/authority/ordinary_progress.rb +0 -162
  110. data/lib/ace/assign/authority/ordinary_queue.rb +0 -115
  111. data/lib/ace/assign/authority/ordinary_release_artifacts.rb +0 -143
  112. data/lib/ace/assign/authority/ordinary_results.rb +0 -206
  113. data/lib/ace/assign/authority/ordinary_review_evidence.rb +0 -180
  114. data/lib/ace/assign/authority/ordinary_reviews.rb +0 -214
  115. data/lib/ace/assign/authority/ordinary_server.rb +0 -75
  116. data/lib/ace/assign/authority/ordinary_supervisor.rb +0 -99
  117. data/lib/ace/assign/authority/original_inbox_selection.rb +0 -41
  118. data/lib/ace/assign/authority/posix_acl.rb +0 -59
  119. data/lib/ace/assign/authority/prepared_input.rb +0 -130
  120. data/lib/ace/assign/authority/prepared_queue.rb +0 -185
  121. data/lib/ace/assign/authority/prepared_task_context.rb +0 -116
  122. data/lib/ace/assign/authority/prepared_work.rb +0 -309
  123. data/lib/ace/assign/authority/prepared_worker.rb +0 -69
  124. data/lib/ace/assign/authority/private_directory.rb +0 -33
  125. data/lib/ace/assign/authority/protected_assignment_context.rb +0 -226
  126. data/lib/ace/assign/authority/readiness_hook.rb +0 -120
  127. data/lib/ace/assign/authority/receipt_transfer.rb +0 -48
  128. data/lib/ace/assign/authority/release_artifact_manifest.rb +0 -91
  129. data/lib/ace/assign/authority/retained_input.rb +0 -53
  130. data/lib/ace/assign/authority/router.rb +0 -81
  131. data/lib/ace/assign/authority/server.rb +0 -131
  132. data/lib/ace/assign/authority/service_cleanup_dispatch.rb +0 -88
  133. data/lib/ace/assign/authority/service_cleanup_evidence.rb +0 -180
  134. data/lib/ace/assign/authority/service_delivery_evidence.rb +0 -101
  135. data/lib/ace/assign/authority/service_evidence.rb +0 -286
  136. data/lib/ace/assign/authority/service_publication_evidence.rb +0 -110
  137. data/lib/ace/assign/authority/socket_server.rb +0 -255
  138. data/lib/ace/assign/authority/task_context_entry.rb +0 -32
  139. data/lib/ace/assign/authority/transfer_codec.rb +0 -189
  140. data/lib/ace/assign/cli/commands/attempt/base.rb +0 -84
  141. data/lib/ace/assign/cli/commands/attempt/evidence.rb +0 -28
  142. data/lib/ace/assign/cli/commands/attempt/finish.rb +0 -51
  143. data/lib/ace/assign/cli/commands/attempt/reconcile.rb +0 -49
  144. data/lib/ace/assign/cli/commands/attempt/start.rb +0 -45
  145. data/lib/ace/assign/cli/commands/attempt/status.rb +0 -54
  146. data/lib/ace/assign/cli/commands/authority/inbox_context_principal.rb +0 -25
  147. data/lib/ace/assign/cli/commands/authority/inbox_context_selection.rb +0 -28
  148. data/lib/ace/assign/cli/commands/authority/launch.rb +0 -97
  149. data/lib/ace/assign/cli/commands/authority/serve.rb +0 -31
  150. data/lib/ace/assign/cli/commands/authority/status.rb +0 -26
  151. data/lib/ace/assign/cli/commands/authority/task_context.rb +0 -31
  152. data/lib/ace/assign/cli/commands/authority/task_context_principal.rb +0 -25
  153. data/lib/ace/assign/cli/commands/authority/task_context_selection.rb +0 -30
  154. data/lib/ace/assign/cli/commands/authority/terminate.rb +0 -29
  155. data/lib/ace/assign/cli/commands/authority/worker.rb +0 -23
  156. data/lib/ace/assign/cli/commands/campaign_export_result.rb +0 -55
  157. data/lib/ace/assign/cli/commands/campaign_record_round.rb +0 -57
  158. data/lib/ace/assign/cli/commands/delivery.rb +0 -107
  159. data/lib/ace/assign/cli/commands/inbox_bind.rb +0 -34
  160. data/lib/ace/assign/cli/commands/protected_submission.rb +0 -61
  161. data/lib/ace/assign/cli/commands/resume.rb +0 -38
  162. data/lib/ace/assign/cli/commands/submit_candidate.rb +0 -34
  163. data/lib/ace/assign/cli/commands/submit_result.rb +0 -61
  164. data/lib/ace/assign/models/attempt.rb +0 -178
  165. data/lib/ace/assign/models/attempt_binding.rb +0 -116
  166. data/lib/ace/assign/models/evidence_event.rb +0 -86
  167. data/lib/ace/assign/models/execution_receipt.rb +0 -139
  168. data/lib/ace/assign/molecules/attempt_reconciler.rb +0 -127
  169. data/lib/ace/assign/molecules/attempt_store.rb +0 -201
  170. data/lib/ace/assign/molecules/canonical_attempt_state.rb +0 -151
  171. data/lib/ace/assign/molecules/canonical_evidence.rb +0 -170
  172. data/lib/ace/assign/molecules/canonical_read_commands.rb +0 -84
  173. data/lib/ace/assign/molecules/canonical_read_snapshot.rb +0 -313
  174. data/lib/ace/assign/molecules/evidence_calculator.rb +0 -237
  175. data/lib/ace/assign/molecules/evidence_journal.rb +0 -1899
  176. data/lib/ace/assign/molecules/execution_identity_resolver.rb +0 -137
  177. data/lib/ace/assign/molecules/execution_scope_lineage.rb +0 -345
  178. data/lib/ace/assign/molecules/fixed_effect_reference.rb +0 -31
  179. data/lib/ace/assign/molecules/installed_journal_read.rb +0 -143
  180. data/lib/ace/assign/molecules/journal_input_inhibition.rb +0 -78
  181. data/lib/ace/assign/molecules/journal_mutation.rb +0 -274
  182. data/lib/ace/assign/molecules/journal_prompt_mutation.rb +0 -241
  183. data/lib/ace/assign/molecules/ordinary_merge_cli.rb +0 -68
  184. data/lib/ace/assign/molecules/ordinary_merge_contract.rb +0 -410
  185. data/lib/ace/assign/molecules/original_launch_binding.rb +0 -37
  186. data/lib/ace/assign/molecules/proposal_journal.rb +0 -179
  187. data/lib/ace/assign/molecules/protected_control_exclusion.rb +0 -303
  188. data/lib/ace/assign/molecules/protected_workspace_exclusion.rb +0 -330
  189. data/lib/ace/assign/molecules/protected_workspace_provisioner.rb +0 -164
  190. data/lib/ace/assign/molecules/receipt_verifier.rb +0 -320
  191. data/lib/ace/assign/molecules/terminal_scope_receipt.rb +0 -88
  192. data/lib/ace/assign/organisms/attempt_coordinator.rb +0 -1374
  193. data/lib/ace/assign/organisms/delivery_coordinator.rb +0 -342
  194. data/lib/ace/assign/organisms/ordinary_driver.rb +0 -310
  195. data/lib/ace/assign/organisms/ordinary_merge_consumer.rb +0 -365
  196. data/lib/ace/assign/organisms/prepared_work_builder.rb +0 -194
  197. 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