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,130 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Ace
4
- module Assign
5
- module Authority
6
- class Endcap
7
- private
8
-
9
- # Historical assignment lookup stays separate: revocation removes
10
- # permission, never the original receipt or response.
11
- def active_review_reservation(events, current)
12
- reservation = events.reverse.find do |event|
13
- event["type"] == "authority_mutation" &&
14
- %w[request_review assign_review].include?(event.dig("payload", "operation")) &&
15
- !event.dig("payload", "mutation_id").to_s.start_with?("review-delegate.") &&
16
- event.dig("payload", "data", "head") == current.fetch("head") &&
17
- event.dig("payload", "data", "candidate_generation") == current.fetch("candidate_generation")
18
- end
19
- return unless reservation
20
- cancelled = events.any? do |event|
21
- event["type"] == "authority_mutation" && event.dig("payload", "operation") == "cancel_review" &&
22
- event.dig("payload", "data", "review_event_id") == reservation.fetch("digest")
23
- end
24
- reservation unless cancelled
25
- end
26
-
27
- def active_review_event(events, current)
28
- reservation = active_review_reservation(events, current)
29
- return unless reservation
30
- return reservation if reservation.dig("payload", "operation") == "assign_review"
31
- events.find do |event|
32
- event["type"] == "authority_mutation" && event.dig("payload", "operation") == "assign_review" &&
33
- event.dig("payload", "mutation_id") == "review-delegate.#{reservation.fetch('digest')}"
34
- end
35
- end
36
-
37
- # Reserved spelling is not authorization. Reconstruct the immutable
38
- # request through the original-launcher reader before historical replay,
39
- # then separately check the active reservation for a fresh mutation.
40
- def delegated_review_request!(journal, events, request, params, peer, role)
41
- mutation = request.fetch("mutation_id")
42
- return unless mutation.is_a?(String) && mutation.start_with?("review-delegate.")
43
- digest = mutation.delete_prefix("review-delegate.")
44
- original = events.find { |event| event["digest"] == digest && event["type"] == "authority_mutation" &&
45
- event.dig("payload", "operation") == "request_review" }
46
- raise AttemptErrors::UnauthorizedIdentity, "delegation requires a canonical request" unless original
47
- accepted = journal.mutation_result(original.dig("payload", "mutation_id"))
48
- selectors = params.slice("mapping_id", "assignment_id", "attempt_id").merge(
49
- "mutation_id" => original.dig("payload", "mutation_id"), "request_event_id" => digest,
50
- "journal_commit" => accepted.fetch("journal_commit"),
51
- "original_binding_digest" => original.dig("payload", "data", "original_binding_digest"))
52
- intent = @launch.launch_review_intent!(request: request.merge("operation" => "launch_review_intent",
53
- "mutation_id" => nil, "params" => selectors), peer: peer, role: role)
54
- unless Atoms::EvidenceDigest.digest(intent.dig(:data, "assignment_params")) == Atoms::EvidenceDigest.digest(params)
55
- raise AttemptErrors::UnauthorizedIdentity, "delegation differs from exact canonical request"
56
- end
57
- original
58
- end
59
-
60
- def cancel_review(request, params, map, peer, role, transfer)
61
- raise ArgumentError, "review cancellation forbids upload" unless transfer.nil?
62
- unless params["review_event_id"].is_a?(String) && params["review_event_id"].match?(/\A[0-9a-f]{64}\z/)
63
- raise ArgumentError, "review cancellation selector differs"
64
- end
65
- @launch.with_assignment(params: params, map: map) do |journal, _registration|
66
- protected_journal!(journal)
67
- events = attempt_events(journal, params)
68
- authenticate_review_cancellation!(events, params, map, peer, role)
69
- result = journal.mutate(assignment_id: params.fetch("assignment_id"), attempt_id: params.fetch("attempt_id"),
70
- mutation_id: request.fetch("mutation_id"), operation: "cancel_review",
71
- parameters_digest: Atoms::EvidenceDigest.digest(params), expected_generation: params.fetch("expected_generation"),
72
- with_replay: true) do |current_events, _commit, _generation|
73
- authenticate_review_cancellation!(current_events, params, map, peer, role)
74
- current = exact_candidate!(candidate(current_events), params)
75
- reservation = active_review_reservation(current_events, current)
76
- unless reservation && reservation.fetch("digest") == params.fetch("review_event_id")
77
- raise AttemptErrors::Conflict, "review reservation is no longer active"
78
- end
79
- {data: current.slice("head", "candidate_generation").merge(
80
- "review_event_id" => reservation.fetch("digest"),
81
- "review_id" => active_review_event(current_events, current)&.dig("payload", "data", "review_id"), "state" => "cancelled")}
82
- end
83
- review_event_reply(journal, result, request, params, "cancellation_event_id")
84
- end
85
- end
86
-
87
- def review_event_reply(journal, result, request, params, field)
88
- accepted = journal.read_events(params.fetch("assignment_id"), commit: result.fetch(:data).fetch("journal_commit")).find do |event|
89
- event["attempt_id"] == params.fetch("attempt_id") && event["type"] == "authority_mutation" &&
90
- event.dig("payload", "operation") == request.fetch("operation") &&
91
- event.dig("payload", "mutation_id") == request.fetch("mutation_id")
92
- end
93
- raise AttemptErrors::EvidenceUnavailable, "accepted review event is unavailable" unless accepted
94
- result.merge(data: result.fetch(:data).merge(field => accepted.fetch("digest")))
95
- end
96
-
97
- def authenticate_review_cancellation!(events, params, map, peer, role)
98
- origin = active_origin(events, params)
99
- @kernel.live!(peer)
100
- original = events.find { |event| event["digest"] == params.fetch("review_event_id") }
101
- unless original && original["type"] == "authority_mutation" &&
102
- %w[request_review assign_review].include?(original.dig("payload", "operation")) &&
103
- !original.dig("payload", "mutation_id").to_s.start_with?("review-delegate.") &&
104
- original.dig("payload", "data", "head") == params.fetch("head") &&
105
- original.dig("payload", "data", "candidate_generation") == params.fetch("candidate_generation")
106
- raise AttemptErrors::UnauthorizedIdentity, "review cancellation requires the exact reservation root"
107
- end
108
- if role == :launcher
109
- unless @kernel.same?(origin.fetch("launcher_identity"), peer)
110
- raise AttemptErrors::UnauthorizedIdentity, "cancellation requires original launcher birth"
111
- end
112
- worker_or_launcher!(peer, role, map, origin, launcher_only: true)
113
- elsif role == :reviewer && original.dig("payload", "operation") == "request_review"
114
- project = @deployment.project(map.fetch("project_id"))
115
- credentials = project.fetch("peer_credentials")[peer.fetch("uid").to_s]
116
- principal = peer.slice("uid", "gid", "groups").merge("role" => "reviewer")
117
- unless project.fetch("reviewer_uids").include?(peer.fetch("uid")) && peer.fetch("uid") != map.fetch("worker_uid") &&
118
- credentials && peer.values_at("gid", "groups") == credentials.values_at("gid", "groups") &&
119
- Atoms::EvidenceDigest.digest(principal) == Atoms::EvidenceDigest.digest(original.dig("payload", "data", "requester"))
120
- raise AttemptErrors::UnauthorizedIdentity, "cancellation requires the original mapped requester"
121
- end
122
- else
123
- raise AttemptErrors::UnauthorizedIdentity, "review reservation cancellation is unauthorized"
124
- end
125
- original
126
- end
127
- end
128
- end
129
- end
130
- end
@@ -1,38 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Ace
4
- module Assign
5
- module Authority
6
- # Protected admission remains here. The shared fixed service transitions
7
- # never manufacture a Deployment mapping or a protected launch origin.
8
- class Endcap
9
- include FixedServiceLifecycle
10
- include FixedPublicationLifecycle
11
-
12
- private
13
-
14
- def with_service_scope(**options, &block) = @launch.with_assignment(**options, &block)
15
- def service_journal!(journal) = protected_journal!(journal)
16
- def service_mapping!(id) = @deployment.mapping(id)
17
- def service_project!(id) = @deployment.project(id)
18
- def service_scope_open!(**options) = @launch.scope_open_for_effect!(**options)
19
- def service_preview_events!(**options) = @launch.preview_attempt_events!(**options)
20
- def service_origin!(events, params) = active_origin(events, params)
21
-
22
- def service_origin_fields!(origin)
23
- {"worker_process_binding" => origin.fetch("process_binding").fetch("process_identity"),
24
- "launch_ticket" => origin.fetch("launch_ticket"),
25
- "reservation_generation" => origin.fetch("reservation_generation")}
26
- end
27
-
28
- def service_origin_matches!(record, origin)
29
- expected = service_origin_fields!(origin)
30
- record.slice(*expected.keys) == expected
31
- end
32
-
33
- def service_worker!(*args, **options) = worker_or_launcher!(*args, **options)
34
- def service_review!(*args, **options) = approved_review!(*args, **options)
35
- end
36
- end
37
- end
38
- end
@@ -1,152 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Ace
4
- module Assign
5
- module Authority
6
- class Endcap
7
- private
8
-
9
- def authorize_service_settlement!(request, params, map, peer, role)
10
- @launch.with_assignment(params: params, map: map) do |journal, _registration|
11
- protected_journal!(journal)
12
- service_settlement_record!(journal, params, map, peer, role)
13
- end
14
- true
15
- end
16
-
17
- def service_settlement_record!(journal, params, map, peer, role, record: nil)
18
- record ||= journal.service_request(params.fetch("request_id"))
19
- raise AttemptErrors::NotFound, "canonical service request is missing" unless record
20
- service_executor!(peer, role, record)
21
- receiver = service_receiver!(peer, role, map, record.fetch("service_id"))
22
- credentials = @deployment.project(map.fetch("project_id")).fetch("peer_credentials").fetch(peer.fetch("uid").to_s)
23
- unless peer["gid"] == credentials.fetch("gid") && peer["groups"] == credentials.fetch("groups") &&
24
- receiver.fetch("executor_uid") == record.fetch("executor_uid") &&
25
- %w[assignment_id attempt_id mapping_id].all? { |key| record[key] == params[key] } &&
26
- record["project_id"] == map.fetch("project_id") && record["candidate_head"] == params.fetch("head") &&
27
- record["candidate_generation"] == params.fetch("candidate_generation") &&
28
- %w[issued dispatch_started].include?(record["dispatch_phase"])
29
- raise AttemptErrors::UnauthorizedIdentity, "original service settlement identity differs"
30
- end
31
- # The normal protected binding validates original ticket/claim, even
32
- # while effect policy, worker liveness or current candidate changed.
33
- ServiceEvidence.new(journal: journal).context(record)
34
- record
35
- rescue KeyError
36
- raise AttemptErrors::EvidenceUnavailable, "original service settlement binding is incomplete"
37
- end
38
-
39
- def dispatch_service_settlement(request, params, map, peer, role, transfer)
40
- authorize_service_settlement!(request, params, map, peer, role)
41
- completion = request.fetch("operation") == "complete_no_effect"
42
- admitted = completion && ReceiptTransfer.decode(input: transfer, receipt_sha256: params.fetch("receipt_sha256"),
43
- artifact_field: "evidence", reference_key: "ref")
44
- @launch.with_assignment(params: params, map: map) do |journal, _registration|
45
- record = service_settlement_record!(journal, params, map, peer, role)
46
- no_effect_completion_binding!(record, params, admitted) if completion
47
- journal.mutate(assignment_id: params.fetch("assignment_id"), attempt_id: params.fetch("attempt_id"),
48
- mutation_id: request.fetch("mutation_id"), operation: request.fetch("operation"),
49
- parameters_digest: Atoms::EvidenceDigest.digest(params),
50
- expected_generation: completion ? nil : params.fetch("expected_generation"),
51
- generation_mode: completion ? :recorded_completion : :expected, with_replay: true) do |events, commit, generation|
52
- current = service_settlement_record!(journal, params, map, peer, role)
53
- if completion
54
- no_effect_completion_plan(journal, events, current, params, admitted)
55
- else
56
- service_challenge_plan(journal, events, current, params, map, generation, commit)
57
- end
58
- end
59
- end
60
- end
61
-
62
- def service_challenge_projection(record)
63
- record.slice("request_id", "state", "dispatch_ticket_id", "claim_binding", "dispatch_phase").merge(
64
- "reconciliation_challenge" => %w[succeeded failed-settled].include?(record.fetch("state")) ? nil :
65
- record.slice("no_effect_challenge", "challenge_generation", "challenge_event_digest"))
66
- end
67
-
68
- def service_challenge_plan(journal, events, record, params, map, generation, commit)
69
- lineage = Molecules::ExecutionScopeLineage.new(events: events, project_id: map.fetch("project_id"),
70
- assignment_id: params.fetch("assignment_id"), attempt_id: params.fetch("attempt_id"), mapping_id: params.fetch("mapping_id"))
71
- unless lineage.sealed? && lineage.binding && record["reservation_generation"] == lineage.binding.fetch("reservation_generation")
72
- raise AttemptErrors::Conflict, "original service generation is not sealed"
73
- end
74
- return {data: service_challenge_projection(record)} if %w[succeeded failed-settled].include?(record.fetch("state"))
75
- unless %w[uncertain failed].include?(record.fetch("state"))
76
- raise AttemptErrors::EvidenceUnavailable, "original protected request has no uncertainty outcome"
77
- end
78
- if record["challenge_event_digest"]
79
- begin
80
- ServiceEvidence.new(journal: journal).challenge!(record, pending: {commit: commit})
81
- return {data: service_challenge_projection(record)}
82
- rescue AttemptErrors::EvidenceUnavailable
83
- # Only a positively authenticated later failure may supersede a
84
- # challenge. Corrupt/missing old history never becomes freshness.
85
- old = ServiceEvidence.new(journal: journal).challenge!(record, pending: {commit: commit}, current: false)
86
- raise unless events.drop_while { |event| event["digest"] != old.fetch("digest") }.drop(1).any? do |event|
87
- %w[service_claim service_transition].include?(event["type"]) && event.dig("payload", "request_id") == record.fetch("request_id") &&
88
- %w[uncertain failed].include?(event.dig("payload", "state"))
89
- end
90
- end
91
- end
92
- failure = events.reverse.find { |event| %w[service_claim service_transition].include?(event["type"]) &&
93
- event.dig("payload", "request_id") == record.fetch("request_id") && %w[uncertain failed].include?(event.dig("payload", "state")) }
94
- raise AttemptErrors::EvidenceUnavailable, "canonical service failure is missing" unless failure
95
- failure_prefix = events.take_while { |event| event["digest"] != failure.fetch("digest") }
96
- payload = record.slice("request_id", "input_digest", "claim_binding", "dispatch_ticket_id").merge(
97
- "version" => 1, "no_effect_challenge" => SecureRandom.hex(16), "challenge_generation" => generation + 1,
98
- "failure_event_digest" => failure.fetch("digest"), "failure_generation" => journal.authority_generation(failure_prefix) + 1)
99
- recorded_at = Time.now.utc
100
- challenge = Models::EvidenceEvent.build(type: "service_no_effect_challenge", attempt_id: params.fetch("attempt_id"),
101
- payload: payload, previous_digest: events.last&.fetch("digest"), recorded_at: recorded_at)
102
- replacement = record.merge(payload.slice("no_effect_challenge", "challenge_generation"), "challenge_event_digest" => challenge.fetch("digest"))
103
- {events: [{type: "service_no_effect_challenge", payload: payload, recorded_at: recorded_at}],
104
- data: service_challenge_projection(replacement), service_updates: [{request_id: record.fetch("request_id"),
105
- expected: record, replacement: replacement, event_type: "service_challenge"}]}
106
- end
107
-
108
- def no_effect_completion_binding!(record, params, admitted)
109
- selector = params.fetch("reconciliation_challenge")
110
- unless selector.is_a?(Hash) && selector.keys.sort == %w[no_effect_challenge challenge_generation challenge_event_digest].sort &&
111
- selector["no_effect_challenge"].is_a?(String) && selector["no_effect_challenge"].match?(/\A[0-9a-f]{32}\z/) &&
112
- selector["challenge_generation"].is_a?(Integer) && selector["challenge_generation"].positive? &&
113
- selector["challenge_event_digest"].is_a?(String) && selector["challenge_event_digest"].match?(/\A[0-9a-f]{64}\z/)
114
- raise ArgumentError, "no-effect challenge selector is malformed"
115
- end
116
- unless selector.all? { |key, value| record[key] == value } && record["claim_binding"] == params.fetch("claim_binding")
117
- raise AttemptErrors::Conflict, "no-effect challenge or claim differs"
118
- end
119
- receipt = admitted.fetch(:receipt)
120
- unless receipt.keys.sort == Molecules::EvidenceJournal::TERMINAL_RECEIPT_FIELDS.sort && receipt["outcome"] == "failed" &&
121
- Molecules::EvidenceJournal::TERMINAL_BINDING_FIELDS.all? { |field| receipt[field] == record[field] }
122
- raise AttemptErrors::ReceiptRejected, "no-effect receipt original binding differs"
123
- end
124
- digest = Atoms::EvidenceDigest.digest("challenge" => selector, "receipt_sha256" => admitted.fetch(:receipt_sha256),
125
- "artifacts" => admitted.fetch(:artifacts).map { |bytes| Digest::SHA256.hexdigest(bytes) })
126
- if record["state"] == "succeeded" || record["no_effect_completion_digest"] && record["no_effect_completion_digest"] != digest
127
- raise AttemptErrors::Conflict, "no-effect completion contradicts retained outcome"
128
- end
129
- digest
130
- end
131
-
132
- def no_effect_completion_plan(journal, events, record, params, admitted)
133
- digest = no_effect_completion_binding!(record, params, admitted)
134
- return {data: service_projection(record)} if record["no_effect_completion_digest"]
135
- owner = ServiceEvidence.new(journal: journal)
136
- challenge = owner.challenge!(record)
137
- if record["operation"] == "prune-preserved-workspace"
138
- owner.cleanup_inspection_inputs!(admitted.fetch(:artifacts), record, challenge)
139
- else
140
- admitted.fetch(:artifacts).each { |bytes| owner.inspection!(bytes, record, challenge) }
141
- end
142
- plan = Molecules::CanonicalEvidence.new(journal: journal).import_plan(**owner.context(record, no_effect: true),
143
- artifacts: admitted.fetch(:artifacts), admitted_after_event_digest: challenge.fetch("digest"))
144
- receipt = admitted.fetch(:receipt).merge("evidence" => plan.fetch(:references))
145
- replacement = record.merge("state" => "failed-settled", "receipt" => receipt, "no_effect_completion_digest" => digest)
146
- plan.merge(data: service_projection(replacement), service_updates: [{request_id: record.fetch("request_id"),
147
- expected: record, replacement: replacement, event_type: "service_transition"}])
148
- end
149
- end
150
- end
151
- end
152
- end
@@ -1,67 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "digest"
4
- require_relative "private_directory"
5
- require_relative "../molecules/canonical_evidence"
6
-
7
- module Ace
8
- module Assign
9
- module Authority
10
- # Receiver-side collection from its own fixed private handler staging.
11
- # These bytes are transport input, not accepted evidence. The authority
12
- # must import them through CanonicalEvidence in its terminal mutation.
13
- module EvidenceTransfer
14
- module_function
15
-
16
- def read_staged(root:, evidence:)
17
- root = PrivateDirectory.verify!(root)
18
- limits = Molecules::CanonicalEvidence
19
- unless evidence.is_a?(Array) && evidence.length.between?(1, limits::MAX_ARTIFACTS)
20
- reject!("Invalid staged evidence count")
21
- end
22
- total = 0
23
- evidence.map do |reference|
24
- unless reference.is_a?(Hash) && reference.keys.sort == %w[ref sha256] &&
25
- reference["ref"].is_a?(String) && reference["ref"].match?(%r{\A[a-zA-Z0-9_.-]+(?:/[a-zA-Z0-9_.-]+)*\z}) &&
26
- reference["ref"].bytesize <= 256 &&
27
- reference["ref"].split("/").none? { |part| %w[. ..].include?(part) } &&
28
- reference["sha256"].is_a?(String) && reference["sha256"].match?(/\A[0-9a-f]{64}\z/)
29
- reject!("Invalid staged evidence binding")
30
- end
31
- path = root
32
- parts = reference.fetch("ref").split("/")
33
- parts[0...-1].each do |part|
34
- path = File.join(path, part)
35
- stat = File.lstat(path)
36
- unless stat.directory? && !stat.symlink? && stat.uid == Process.uid && (stat.mode & 0022).zero?
37
- reject!("Staged evidence ancestry is not private")
38
- end
39
- end
40
- path = File.join(path, parts.last)
41
- File.open(path, File::RDONLY | File::NOFOLLOW | File::NONBLOCK) do |file|
42
- stat = file.stat
43
- unless stat.file? && stat.uid == Process.uid && stat.nlink == 1 &&
44
- (stat.mode & 0022).zero? && stat.size <= limits::MAX_ARTIFACT_BYTES
45
- reject!("Staged evidence is not a bounded private regular file")
46
- end
47
- bytes = (file.read(limits::MAX_ARTIFACT_BYTES + 1) || "").b
48
- total += bytes.bytesize
49
- unless bytes.bytesize <= limits::MAX_ARTIFACT_BYTES && total <= limits::MAX_TOTAL_BYTES &&
50
- Digest::SHA256.hexdigest(bytes) == reference["sha256"]
51
- reject!("Staged evidence bytes differ or exceed limits")
52
- end
53
- bytes
54
- end
55
- end
56
- rescue SystemCallError
57
- reject!("Staged evidence is unavailable or substituted")
58
- end
59
-
60
- def reject!(message)
61
- raise AttemptErrors::ReceiptRejected, message
62
- end
63
- private_class_method :reject!
64
- end
65
- end
66
- end
67
- end