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.
Files changed (195) 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 +3 -2119
  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/organisms/assignment_executor.rb +0 -6
  35. data/lib/ace/assign/organisms/task_assignment_creator.rb +3 -8
  36. data/lib/ace/assign/version.rb +1 -1
  37. data/lib/ace/assign.rb +0 -6
  38. metadata +4 -176
  39. data/.ace-defaults/assign/presets/close-delivery.yml +0 -57
  40. data/.ace-defaults/assign/presets/deliver-release.yml +0 -60
  41. data/.ace-defaults/assign/presets/integrate-batch.yml +0 -72
  42. data/.ace-defaults/assign/presets/supervise-delivery.yml +0 -69
  43. data/docs/protected-authority.md +0 -282
  44. data/exe/ace-scope-ready +0 -55
  45. data/lib/ace/assign/atoms/assignment_scope.rb +0 -58
  46. data/lib/ace/assign/atoms/attempt_state_machine.rb +0 -84
  47. data/lib/ace/assign/atoms/candidate_limits.rb +0 -20
  48. data/lib/ace/assign/atoms/delivery_parameters.rb +0 -19
  49. data/lib/ace/assign/atoms/evidence_digest.rb +0 -78
  50. data/lib/ace/assign/atoms/fixed_merge_schemas.rb +0 -398
  51. data/lib/ace/assign/atoms/git_blob_batch.rb +0 -59
  52. data/lib/ace/assign/authority/assignment_definition.rb +0 -35
  53. data/lib/ace/assign/authority/assignment_inventory.rb +0 -290
  54. data/lib/ace/assign/authority/campaign_consumer_policy.rb +0 -100
  55. data/lib/ace/assign/authority/campaign_execution.rb +0 -119
  56. data/lib/ace/assign/authority/campaign_round_transfer.rb +0 -82
  57. data/lib/ace/assign/authority/candidate_blob_batch.rb +0 -55
  58. data/lib/ace/assign/authority/candidate_head_comparison.rb +0 -139
  59. data/lib/ace/assign/authority/candidate_transfer.rb +0 -357
  60. data/lib/ace/assign/authority/cleanup_result_protection.rb +0 -53
  61. data/lib/ace/assign/authority/client.rb +0 -257
  62. data/lib/ace/assign/authority/command_execution.rb +0 -180
  63. data/lib/ace/assign/authority/deployment.rb +0 -627
  64. data/lib/ace/assign/authority/deployment_history.rb +0 -146
  65. data/lib/ace/assign/authority/endcap.rb +0 -457
  66. data/lib/ace/assign/authority/endcap_attempts.rb +0 -274
  67. data/lib/ace/assign/authority/endcap_campaign_results.rb +0 -232
  68. data/lib/ace/assign/authority/endcap_campaign_rounds.rb +0 -266
  69. data/lib/ace/assign/authority/endcap_contexts.rb +0 -23
  70. data/lib/ace/assign/authority/endcap_inboxes.rb +0 -37
  71. data/lib/ace/assign/authority/endcap_prepared_work.rb +0 -80
  72. data/lib/ace/assign/authority/endcap_publication.rb +0 -41
  73. data/lib/ace/assign/authority/endcap_results.rb +0 -398
  74. data/lib/ace/assign/authority/endcap_review_requests.rb +0 -183
  75. data/lib/ace/assign/authority/endcap_reviews.rb +0 -130
  76. data/lib/ace/assign/authority/endcap_service_context.rb +0 -38
  77. data/lib/ace/assign/authority/endcap_service_settlement.rb +0 -152
  78. data/lib/ace/assign/authority/evidence_transfer.rb +0 -67
  79. data/lib/ace/assign/authority/execution_scope_observation.rb +0 -903
  80. data/lib/ace/assign/authority/fixed_publication_lifecycle.rb +0 -176
  81. data/lib/ace/assign/authority/fixed_service_lifecycle.rb +0 -594
  82. data/lib/ace/assign/authority/inbox_context_original.rb +0 -113
  83. data/lib/ace/assign/authority/launch_attempt_consumers.rb +0 -51
  84. data/lib/ace/assign/authority/launch_campaign_registration.rb +0 -214
  85. data/lib/ace/assign/authority/launch_control_channel.rb +0 -313
  86. data/lib/ace/assign/authority/launch_control_exclusions.rb +0 -127
  87. data/lib/ace/assign/authority/launch_driver.rb +0 -393
  88. data/lib/ace/assign/authority/launch_input_inhibition.rb +0 -132
  89. data/lib/ace/assign/authority/launch_lifecycle.rb +0 -997
  90. data/lib/ace/assign/authority/launch_prepared_work.rb +0 -122
  91. data/lib/ace/assign/authority/launch_review.rb +0 -91
  92. data/lib/ace/assign/authority/launch_scope_abort.rb +0 -81
  93. data/lib/ace/assign/authority/launch_scope_admission.rb +0 -215
  94. data/lib/ace/assign/authority/launch_scope_close.rb +0 -146
  95. data/lib/ace/assign/authority/launch_scope_parent.rb +0 -44
  96. data/lib/ace/assign/authority/launch_scope_release.rb +0 -859
  97. data/lib/ace/assign/authority/launch_steering.rb +0 -353
  98. data/lib/ace/assign/authority/launch_stop.rb +0 -254
  99. data/lib/ace/assign/authority/maintenance_workspace_retirement.rb +0 -229
  100. data/lib/ace/assign/authority/ordinary_attempt_start.rb +0 -137
  101. data/lib/ace/assign/authority/ordinary_client.rb +0 -70
  102. data/lib/ace/assign/authority/ordinary_completion.rb +0 -104
  103. data/lib/ace/assign/authority/ordinary_effect_settlement.rb +0 -118
  104. data/lib/ace/assign/authority/ordinary_lifecycle.rb +0 -227
  105. data/lib/ace/assign/authority/ordinary_merge_evidence.rb +0 -81
  106. data/lib/ace/assign/authority/ordinary_peer.rb +0 -54
  107. data/lib/ace/assign/authority/ordinary_progress.rb +0 -162
  108. data/lib/ace/assign/authority/ordinary_queue.rb +0 -115
  109. data/lib/ace/assign/authority/ordinary_release_artifacts.rb +0 -143
  110. data/lib/ace/assign/authority/ordinary_results.rb +0 -206
  111. data/lib/ace/assign/authority/ordinary_review_evidence.rb +0 -180
  112. data/lib/ace/assign/authority/ordinary_reviews.rb +0 -214
  113. data/lib/ace/assign/authority/ordinary_server.rb +0 -75
  114. data/lib/ace/assign/authority/ordinary_supervisor.rb +0 -99
  115. data/lib/ace/assign/authority/original_inbox_selection.rb +0 -41
  116. data/lib/ace/assign/authority/posix_acl.rb +0 -59
  117. data/lib/ace/assign/authority/prepared_input.rb +0 -130
  118. data/lib/ace/assign/authority/prepared_queue.rb +0 -185
  119. data/lib/ace/assign/authority/prepared_task_context.rb +0 -116
  120. data/lib/ace/assign/authority/prepared_work.rb +0 -309
  121. data/lib/ace/assign/authority/prepared_worker.rb +0 -69
  122. data/lib/ace/assign/authority/private_directory.rb +0 -33
  123. data/lib/ace/assign/authority/protected_assignment_context.rb +0 -226
  124. data/lib/ace/assign/authority/readiness_hook.rb +0 -120
  125. data/lib/ace/assign/authority/receipt_transfer.rb +0 -48
  126. data/lib/ace/assign/authority/release_artifact_manifest.rb +0 -91
  127. data/lib/ace/assign/authority/retained_input.rb +0 -53
  128. data/lib/ace/assign/authority/router.rb +0 -81
  129. data/lib/ace/assign/authority/server.rb +0 -131
  130. data/lib/ace/assign/authority/service_cleanup_dispatch.rb +0 -88
  131. data/lib/ace/assign/authority/service_cleanup_evidence.rb +0 -180
  132. data/lib/ace/assign/authority/service_delivery_evidence.rb +0 -101
  133. data/lib/ace/assign/authority/service_evidence.rb +0 -286
  134. data/lib/ace/assign/authority/service_publication_evidence.rb +0 -110
  135. data/lib/ace/assign/authority/socket_server.rb +0 -255
  136. data/lib/ace/assign/authority/task_context_entry.rb +0 -32
  137. data/lib/ace/assign/authority/transfer_codec.rb +0 -189
  138. data/lib/ace/assign/cli/commands/attempt/base.rb +0 -84
  139. data/lib/ace/assign/cli/commands/attempt/evidence.rb +0 -28
  140. data/lib/ace/assign/cli/commands/attempt/finish.rb +0 -51
  141. data/lib/ace/assign/cli/commands/attempt/reconcile.rb +0 -49
  142. data/lib/ace/assign/cli/commands/attempt/start.rb +0 -45
  143. data/lib/ace/assign/cli/commands/attempt/status.rb +0 -54
  144. data/lib/ace/assign/cli/commands/authority/inbox_context_principal.rb +0 -25
  145. data/lib/ace/assign/cli/commands/authority/inbox_context_selection.rb +0 -28
  146. data/lib/ace/assign/cli/commands/authority/launch.rb +0 -97
  147. data/lib/ace/assign/cli/commands/authority/serve.rb +0 -31
  148. data/lib/ace/assign/cli/commands/authority/status.rb +0 -26
  149. data/lib/ace/assign/cli/commands/authority/task_context.rb +0 -31
  150. data/lib/ace/assign/cli/commands/authority/task_context_principal.rb +0 -25
  151. data/lib/ace/assign/cli/commands/authority/task_context_selection.rb +0 -30
  152. data/lib/ace/assign/cli/commands/authority/terminate.rb +0 -29
  153. data/lib/ace/assign/cli/commands/authority/worker.rb +0 -23
  154. data/lib/ace/assign/cli/commands/campaign_export_result.rb +0 -55
  155. data/lib/ace/assign/cli/commands/campaign_record_round.rb +0 -57
  156. data/lib/ace/assign/cli/commands/delivery.rb +0 -107
  157. data/lib/ace/assign/cli/commands/inbox_bind.rb +0 -34
  158. data/lib/ace/assign/cli/commands/protected_submission.rb +0 -61
  159. data/lib/ace/assign/cli/commands/resume.rb +0 -38
  160. data/lib/ace/assign/cli/commands/submit_candidate.rb +0 -34
  161. data/lib/ace/assign/cli/commands/submit_result.rb +0 -61
  162. data/lib/ace/assign/models/attempt.rb +0 -178
  163. data/lib/ace/assign/models/attempt_binding.rb +0 -116
  164. data/lib/ace/assign/models/evidence_event.rb +0 -86
  165. data/lib/ace/assign/models/execution_receipt.rb +0 -139
  166. data/lib/ace/assign/molecules/attempt_reconciler.rb +0 -127
  167. data/lib/ace/assign/molecules/attempt_store.rb +0 -201
  168. data/lib/ace/assign/molecules/canonical_attempt_state.rb +0 -151
  169. data/lib/ace/assign/molecules/canonical_evidence.rb +0 -170
  170. data/lib/ace/assign/molecules/canonical_read_commands.rb +0 -84
  171. data/lib/ace/assign/molecules/canonical_read_snapshot.rb +0 -313
  172. data/lib/ace/assign/molecules/evidence_calculator.rb +0 -237
  173. data/lib/ace/assign/molecules/evidence_journal.rb +0 -1899
  174. data/lib/ace/assign/molecules/execution_identity_resolver.rb +0 -137
  175. data/lib/ace/assign/molecules/execution_scope_lineage.rb +0 -345
  176. data/lib/ace/assign/molecules/fixed_effect_reference.rb +0 -31
  177. data/lib/ace/assign/molecules/installed_journal_read.rb +0 -143
  178. data/lib/ace/assign/molecules/journal_input_inhibition.rb +0 -78
  179. data/lib/ace/assign/molecules/journal_mutation.rb +0 -274
  180. data/lib/ace/assign/molecules/journal_prompt_mutation.rb +0 -241
  181. data/lib/ace/assign/molecules/ordinary_merge_cli.rb +0 -68
  182. data/lib/ace/assign/molecules/ordinary_merge_contract.rb +0 -410
  183. data/lib/ace/assign/molecules/original_launch_binding.rb +0 -37
  184. data/lib/ace/assign/molecules/proposal_journal.rb +0 -179
  185. data/lib/ace/assign/molecules/protected_control_exclusion.rb +0 -303
  186. data/lib/ace/assign/molecules/protected_workspace_exclusion.rb +0 -330
  187. data/lib/ace/assign/molecules/protected_workspace_provisioner.rb +0 -164
  188. data/lib/ace/assign/molecules/receipt_verifier.rb +0 -320
  189. data/lib/ace/assign/molecules/terminal_scope_receipt.rb +0 -88
  190. data/lib/ace/assign/organisms/attempt_coordinator.rb +0 -1374
  191. data/lib/ace/assign/organisms/delivery_coordinator.rb +0 -342
  192. data/lib/ace/assign/organisms/ordinary_driver.rb +0 -310
  193. data/lib/ace/assign/organisms/ordinary_merge_consumer.rb +0 -365
  194. data/lib/ace/assign/organisms/prepared_work_builder.rb +0 -194
  195. 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