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,113 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "original_inbox_selection"
4
- require_relative "../molecules/original_launch_binding"
5
- require_relative "../molecules/execution_scope_lineage"
6
-
7
- module Ace
8
- module Assign
9
- module Authority
10
- # Canonical original identity only. No native thread, effect permission,
11
- # live process observation, context lock or journal mutation is returned.
12
- class InboxContextOriginal
13
- OPERATIONS = ["inbox_context_original"].freeze
14
- PARAMETERS = %w[assignment_id attempt_id event_id inbox_context_id mapping_id payload_sha256 purpose].freeze
15
- NATIVE_FIELDS = %w[scope_generation scope_binding_event_id service_invocation_id server_identity socket_identity workspace_id].freeze
16
- TOKEN = /\A[A-Za-z0-9][A-Za-z0-9._-]{0,127}\z/
17
- SHA = /\A[0-9a-f]{64}\z/
18
-
19
- def initialize(deployment:, history:, authority_id:, journals:,
20
- kernel: Ace::Runtime::Molecules::ProtectedLinux.new)
21
- @deployment, @history, @authority_id, @journals, @kernel = deployment, history, authority_id, journals, kernel
22
- end
23
-
24
- def dispatch(request:, peer:, role:)
25
- unless request.is_a?(Hash) && request.keys.sort == %w[mutation_id operation params project_id version] &&
26
- request["version"].is_a?(Integer) && request["version"] == 1 && request["operation"] == OPERATIONS.first &&
27
- request["mutation_id"].nil? && role == :context_owner && request["params"].is_a?(Hash) &&
28
- request["params"].keys.sort == PARAMETERS
29
- raise AttemptErrors::UnauthorizedIdentity, "original inbox query differs"
30
- end
31
- params = request.fetch("params")
32
- unless %w[assignment_id attempt_id event_id inbox_context_id mapping_id].all? { |key| params[key].is_a?(String) && TOKEN.match?(params[key]) } &&
33
- %w[payload_sha256].all? { |key| params[key].is_a?(String) && SHA.match?(params[key]) } &&
34
- %w[enqueue deliver].include?(params["purpose"])
35
- raise AttemptErrors::UnauthorizedIdentity, "original inbox selectors differ"
36
- end
37
- map = @deployment.mapping(params.fetch("mapping_id"))
38
- context = @deployment.inbox_context(params.fetch("mapping_id"), params.fetch("inbox_context_id"))
39
- unless map.values_at("authority_id", "project_id") == [@authority_id, request.fetch("project_id")] &&
40
- context.fetch("owner_credentials").values_at("uid", "gid", "groups") == peer.values_at("uid", "gid", "groups")
41
- raise AttemptErrors::UnauthorizedIdentity, "original inbox query requires its fixed owner"
42
- end
43
- @kernel.live!(peer)
44
- journal = @journals.fetch(map.fetch("project_id"))
45
- project = @deployment.project(map.fetch("project_id"))
46
- unless journal.evidence_mode == :protected && project.values_at("journal_repository", "evidence_git_ref", "evidence_checkout_root") ==
47
- [journal.repo_root, journal.ref, journal.checkout_root]
48
- raise AttemptErrors::EvidenceUnavailable, "original inbox journal association differs"
49
- end
50
- commit = journal.ref_value
51
- journal.verify_commit!(commit)
52
- events = journal.read_events(params.fetch("assignment_id"), commit: commit).select { |event| event["attempt_id"] == params.fetch("attempt_id") }
53
- original, original_map = OriginalInboxSelection.new(history: @history, authority_id: @authority_id)
54
- .load!(events: events, params: params, map: map, journal: journal, commit: commit)
55
- original_context = original.inbox_context(params.fetch("mapping_id"), params.fetch("inbox_context_id"))
56
- unless original_context.fetch("native_mapping_id") == params.fetch("mapping_id")
57
- raise AttemptErrors::EvidenceUnavailable, "original inbox native mapping differs"
58
- end
59
- lineage = Molecules::ExecutionScopeLineage.new(events: events, project_id: original_map.fetch("project_id"),
60
- assignment_id: params.fetch("assignment_id"), attempt_id: params.fetch("attempt_id"), mapping_id: params.fetch("mapping_id"))
61
- child = lineage.child_event&.dig("payload", "original_process_binding")
62
- records = events.select { |event| event["type"] == "authority_mutation" && event.dig("payload", "operation") == "record_launch" }
63
- raise AttemptErrors::EvidenceUnavailable, "original inbox launch missing" unless records.one? && child
64
- state = records.first.fetch("payload").fetch("data")
65
- raise AttemptErrors::EvidenceUnavailable, "original inbox child differs" unless state.fetch("process_binding") == child
66
- guarded = Molecules::OriginalLaunchBinding.verify!(events: events, state: state, params: params)
67
- registration = {"event_id" => params.fetch("event_id"), "attempt_id" => params.fetch("attempt_id"),
68
- "payload_sha256" => params.fetch("payload_sha256")}
69
- bindings = events.select { |event| event["type"] == "inbox_binding" && event.dig("payload", "event_id") == params.fetch("event_id") }
70
- unless bindings.size <= 1 && (params.fetch("purpose") == "enqueue" || bindings.one?) && bindings.all? { |event|
71
- event.fetch("payload") == {"attempt_id" => params.fetch("attempt_id"), "event_id" => params.fetch("event_id"),
72
- "inbox_context_id" => params.fetch("inbox_context_id"), "registration" => registration} }
73
- raise AttemptErrors::EvidenceUnavailable, "original inbox registration differs"
74
- end
75
- digests = [guarded.fetch("binding_digest"), lineage.native_event.fetch("digest"), lineage.child_event.fetch("digest")] + bindings.map { |event| event.fetch("digest") }
76
- journal.event_commits!(assignment_id: params.fetch("assignment_id"), event_digests: digests, commit: commit)
77
- @kernel.live!(peer)
78
- channel = original_map.fetch("native")
79
- server = lineage.native_event.fetch("payload").fetch("server_identity")
80
- path = channel.fetch("socket_path")
81
- unless path.is_a?(String) && path.valid_encoding? && path.bytesize.between?(1, 107) &&
82
- path.start_with?("/") && !path.include?("\0") && File.expand_path(path) == path &&
83
- channel.fetch("version") == "0.9.3" && channel.fetch("protocol") == 22 &&
84
- channel.fetch("workspace_id") == lineage.native_event.fetch("payload").fetch("workspace_id") &&
85
- server.values_at("uid", "gid", "groups") == original_map.values_at("worker_uid", "worker_gid", "worker_groups")
86
- raise AttemptErrors::EvidenceUnavailable, "original inbox native channel differs"
87
- end
88
- data = params.slice("assignment_id", "attempt_id", "event_id", "inbox_context_id", "mapping_id", "purpose")
89
- .merge("schema" => "ace.assign.inbox-context-original/v1", "project_id" => map.fetch("project_id"),
90
- "commit" => commit, "registration" => registration, "registered" => bindings.one?,
91
- "original_binding_digest" => guarded.fetch("binding_digest"), "process_binding" => child,
92
- "guarded_origin" => guarded.fetch("origin"), "native_binding" => lineage.native_event.fetch("payload").slice(*NATIVE_FIELDS),
93
- "native_channel" => channel.slice("socket_path", "version"))
94
- raise AttemptErrors::EvidenceUnavailable, "original inbox projection exceeds bound" if JSON.generate(data).bytesize > 15_360
95
- {data: immutable(data), replayed: false}
96
- rescue KeyError, TypeError, ArgumentError, NoMethodError, Ace::Herdr::Error, Ace::Runtime::RuntimeUnavailableError
97
- raise AttemptErrors::EvidenceUnavailable, "original inbox evidence is unavailable"
98
- end
99
-
100
- private
101
-
102
- def immutable(value)
103
- case value
104
- when Hash then value.to_h { |key, item| [key.dup.freeze, immutable(item)] }.freeze
105
- when Array then value.map { |item| immutable(item) }.freeze
106
- when String then value.dup.freeze
107
- else value.freeze
108
- end
109
- end
110
- end
111
- end
112
- end
113
- end
@@ -1,51 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Ace
4
- module Assign
5
- module Authority
6
- class LaunchLifecycle
7
- # Caller owns the existing slot/assignment exclusion. This verifies
8
- # an already accepted closure; it never seals, stops or observes anew.
9
- def completion_scope!(journal:, events:, params:, map:, commit:)
10
- lineage = Molecules::ExecutionScopeLineage.new(events: events, project_id: map.fetch("project_id"),
11
- assignment_id: params.fetch("assignment_id"), attempt_id: params.fetch("attempt_id"),
12
- mapping_id: params.fetch("mapping_id"))
13
- unless lineage.proof_event && !pending_prompt_issuers?(events, journal, commit)
14
- raise AttemptErrors::EvidenceUnavailable, "Original scope closure is unavailable"
15
- end
16
- scope_observer_for(params.fetch("mapping_id")).verify_closed!(lineage)
17
- lineage
18
- end
19
-
20
- # Reuse the complete immutable inventory/terminal/release owner for
21
- # recovery and finish replay; no current process grants terminality.
22
- def completion_terminal!(journal:, commit:, params:, map:)
23
- entries = inventory_index!(journal, commit, params.fetch("mapping_id"), map).select do |entry|
24
- entry.fetch(:selector).values_at("assignment_id", "attempt_id") == params.values_at("assignment_id", "attempt_id")
25
- end
26
- raise AttemptErrors::NotFound, "Canonical attempt is missing" if entries.empty?
27
- raise AttemptErrors::EvidenceUnavailable, "Canonical attempt is ambiguous" unless entries.one?
28
- inventory_row!(journal, commit, entries.first)
29
- end
30
-
31
- def recovery_observation!(journal:, events:, params:, map:, commit:)
32
- state = origin(events, **params.slice("assignment_id", "attempt_id", "mapping_id").transform_keys(&:to_sym))
33
- return {"liveness" => "unknown"} unless %w[bound issued].include?(state["phase"])
34
- original, original_map = inventory_original_deployment!(journal, events, params.fetch("mapping_id"), map)
35
- lineage = Molecules::ExecutionScopeLineage.new(events: events, project_id: original_map.fetch("project_id"),
36
- assignment_id: params.fetch("assignment_id"), attempt_id: params.fetch("attempt_id"), mapping_id: params.fetch("mapping_id"))
37
- return {"liveness" => "unknown"} if lineage.sealed?
38
- # Disposable local cache is not a checkpoint authority. Authenticate
39
- # the retained original prepared selection with the existing owner.
40
- if state.fetch("phase") == "issued"
41
- original_prepared_registration!(journal: journal, params: params, commit: commit)
42
- else
43
- original_bound_prepared_registration!(journal: journal, params: params, commit: commit)
44
- end
45
- Molecules::AttemptReconciler.protected_observation(kernel: @kernel, binding: state.fetch("process_binding"),
46
- observer: maintenance_scope_observer_for(original, params.fetch("mapping_id")), lineage: lineage)
47
- end
48
- end
49
- end
50
- end
51
- end
@@ -1,214 +0,0 @@
1
- # frozen_string_literal: true
2
- require "ace/review/organisms/campaign_manager"
3
- require_relative "campaign_consumer_policy"
4
-
5
- module Ace
6
- module Assign
7
- module Authority
8
- class LaunchLifecycle
9
- # Called by the terminal owner at its selected CAS prefix. Discover
10
- # children from canonical registration; no caller list can omit one.
11
- def campaign_children_settled!(journal:, commit:, params:, map:)
12
- parent = preview_attempt_definition!(journal: journal, commit: commit, params: params, map: map)
13
- return true unless parent.review_campaign
14
- inventory = journal.canonical_event_inventory!(commit: commit)
15
- inventory.fetch("events").each do |assignment, events|
16
- next if assignment == params.fetch("assignment_id")
17
- registrations = events.select { |event| event.dig("payload", "operation") == "register_assignment" }
18
- next if registrations.empty?
19
- registration = registrations.last.fetch("payload").fetch("data")
20
- child = inventory_definition!(journal, commit,
21
- {selector: {"assignment_id" => assignment, "attempt_id" => nil}, registration: registration})
22
- execution = child.campaign_execution
23
- next unless execution && execution.values_at("parent_assignment_id", "parent_attempt_id") ==
24
- params.values_at("assignment_id", "attempt_id")
25
- original = control_original_descriptor!(registration.fetch("lifecycle_control"))
26
- mapping_id = registration.fetch("mapping_id")
27
- child_map = original.mapping(mapping_id)
28
- authenticate_inventory_registration!(journal, assignment, events, registration, mapping_id,
29
- child_map, commit, inventory.fetch("introductions").fetch(assignment))
30
- rows = inventory_index!(journal, commit, mapping_id, child_map).select { |entry|
31
- entry.fetch(:selector).fetch("assignment_id") == assignment }
32
- unless !rows.empty? && rows.all? { |entry|
33
- row = inventory_row!(journal, commit, entry)
34
- %w[succeeded failed stopped].include?(row["canonical_state"]) &&
35
- row["terminal_event_id"] && row["reservation_release_event_id"] }
36
- raise AttemptErrors::Conflict, "campaign parent has an unsettled registered child"
37
- end
38
- end
39
- true
40
- rescue KeyError, TypeError
41
- raise AttemptErrors::EvidenceUnavailable, "campaign child completion provenance is unavailable"
42
- end
43
-
44
- private
45
-
46
- # Resolve both immutable canonical owners before entering exclusions.
47
- def campaign_child_context!(params, context, map, journal)
48
- value = if params.key?("definition_bytes")
49
- JSON.parse(params.fetch("definition_bytes"))
50
- elsif context.fetch(:registration)
51
- registration = context.fetch(:registration)
52
- bytes = journal.blob(registration.fetch("definition_ref"), commit: context.fetch(:commit))
53
- unless bytes.is_a?(String) && bytes.bytesize <= 32_768 &&
54
- Digest::SHA256.hexdigest(bytes) == registration.fetch("definition_digest")
55
- raise AttemptErrors::EvidenceUnavailable, "campaign definition content differs"
56
- end
57
- decoded = JSON.parse(bytes, create_additions: false, max_nesting: 32, allow_duplicate_key: false, allow_comments: false)
58
- return context unless decoded.is_a?(Hash) && decoded.key?("campaign_execution")
59
- inventory_definition!(journal, context.fetch(:commit),
60
- {selector: {"assignment_id" => params.fetch("assignment_id"), "attempt_id" => params["attempt_id"]},
61
- registration: registration}).to_h
62
- else
63
- return context
64
- end
65
- return context unless value.key?("campaign_execution")
66
- execution = value.fetch("campaign_execution")
67
- unless execution.is_a?(Hash) && execution.keys.sort == CampaignExecution::FIELDS
68
- raise ArgumentError, "campaign execution fields differ"
69
- end
70
- parent_id, attempt = execution.values_at("parent_assignment_id", "parent_attempt_id")
71
- token!(parent_id); token!(attempt)
72
- raise ArgumentError, "campaign child cannot be its own parent" if parent_id == params.fetch("assignment_id")
73
- inventory = journal.canonical_event_inventory!(commit: context.fetch(:commit))
74
- reserves = inventory.fetch("events").fetch(parent_id).select { |event|
75
- event["attempt_id"] == attempt && event.dig("payload", "operation") == "reserve_attempt" }
76
- raise AttemptErrors::EvidenceUnavailable, "campaign parent reservation differs" unless reserves.one?
77
- parent_mapping = reserves.first.fetch("payload").fetch("data").fetch("mapping_id")
78
- parent_params = {"assignment_id" => parent_id, "attempt_id" => attempt, "mapping_id" => parent_mapping,
79
- "scope" => execution.fetch("parent_scope"), "base" => execution.fetch("base"), "head" => execution.fetch("head"),
80
- "candidate_generation" => execution.fetch("parent_candidate_generation")}.freeze
81
- parent_map = @deployment.mapping(parent_mapping)
82
- parent_context = control_registration_context!(parent_params, parent_map, journal, commit: context.fetch(:commit))
83
- original_map = parent_context.fetch(:map)
84
- parent = preview_attempt_definition!(journal: journal, commit: context.fetch(:commit), params: parent_params, map: original_map)
85
- selected = CampaignExecution.validate_parent!(parent.review_campaign)
86
- unless value["parent"] == parent_id && value["task_id"] == parent.task_id && value["project_id"] == parent.project_id &&
87
- parent.project_id == map.fetch("project_id") &&
88
- parent_context.fetch(:selection).fetch("root_identity") == context.fetch(:selection).fetch("root_identity") &&
89
- original_map.fetch("authority_id") == context.fetch(:map).fetch("authority_id")
90
- raise AttemptErrors::EvidenceUnavailable, "campaign parent task or control owner differs"
91
- end
92
- CampaignExecution.validate!(execution, parent: parent_id, policy: selected.fetch("policy"))
93
- context.merge(campaign: {params: parent_params, map: parent_map, context: parent_context,
94
- definition: parent, selected: selected, execution: execution, child_assignment: params.fetch("assignment_id")}.freeze).freeze
95
- rescue KeyError, TypeError
96
- raise AttemptErrors::EvidenceUnavailable, "campaign parent canonical registration is unavailable"
97
- end
98
-
99
- def recheck_campaign_parent!(context, journal, commit: journal.ref_value)
100
- campaign = context.fetch(:campaign)
101
- parent = campaign.fetch(:context)
102
- recheck_control_registration!(parent, campaign.fetch(:params), campaign.fetch(:map), journal,
103
- commit: commit, held_exclusion: context.fetch(:exclusion))
104
- unless @result_owner&.respond_to?(:campaign_parent_candidate!)
105
- raise AttemptErrors::EvidenceUnavailable, "campaign parent candidate owner is unavailable"
106
- end
107
- @result_owner.campaign_parent_candidate!(journal: journal, commit: commit,
108
- params: campaign.fetch(:params), map: parent.fetch(:map))
109
- true
110
- end
111
-
112
- # Called inside the existing lifecycle exclusions and authority mutex,
113
- # before entering the journal mutation; the store lock spans CAS retries.
114
- def with_parent_campaign_registration(params, context, replay:)
115
- # A stored mutation remains a historical observation after supersession.
116
- # The journal still verifies its operation and exact parameters below.
117
- return yield if replay
118
- if context[:campaign]
119
- return with_child_campaign_registration(params, context) { |guard| yield guard }
120
- end
121
- return yield unless params.key?("definition_bytes")
122
- definition = JSON.parse(params.fetch("definition_bytes"))
123
- return yield unless definition.key?("review_campaign")
124
- selected = CampaignExecution.validate_parent!(definition.fetch("review_campaign"))
125
- manager = campaign_manager_for!(context)
126
- manager.with_campaign_registration!(selected.fetch("campaign_id"), subject: selected.fetch("subject"),
127
- contract_identity: selected.fetch("contract_identity"), policy: selected.fetch("policy")) { yield }
128
- rescue KeyError, TypeError, JSON::ParserError, Ace::Review::Atoms::CampaignContract::Invalid
129
- raise AttemptErrors::EvidenceUnavailable, "registered parent campaign is unavailable"
130
- end
131
-
132
- def campaign_manager_for!(context, **evidence)
133
- descriptor = context.fetch(:descriptor)
134
- map = context.fetch(:map)
135
- project = descriptor.project(map.fetch("project_id"))
136
- service = descriptor.authority(map.fetch("authority_id"))
137
- %w[campaign_repository campaign_store_root].each do |key|
138
- path = project.fetch(key)
139
- wire.root_path!(path, directory: true, owner: service.fetch("uid"))
140
- unless (File.stat(path).mode & 0o077).zero?
141
- raise AttemptErrors::EvidenceUnavailable, "campaign owner root must remain private"
142
- end
143
- end
144
- store = Ace::Review::Molecules::CampaignStore.new(root: project.fetch("campaign_store_root"))
145
- Ace::Review::Organisms::CampaignManager.new(repo_root: project.fetch("campaign_repository"), store: store, **evidence)
146
- end
147
-
148
- def with_child_campaign_registration(params, context)
149
- campaign = context.fetch(:campaign)
150
- recheck_campaign_parent!(context, journals.fetch(context.fetch(:map).fetch("project_id")))
151
- manager = campaign_manager_for!(campaign.fetch(:context))
152
- deployment = @deployment
153
- descriptor_sha = protected_descriptor_sha256!
154
- policy_ref = deployment.project(context.fetch(:map).fetch("project_id")).fetch("campaign_policy")
155
- CampaignConsumerPolicy.new.with(policy_ref) do |profiles, artifacts|
156
- manager.with_execution_round!(campaign.fetch(:execution).fetch("campaign_id"),
157
- round_id: campaign.fetch(:execution).fetch("round_id"), consumer_profiles: profiles) do |round, round_guard|
158
- selected = campaign.fetch(:selected)
159
- unless round.slice("campaign_id", "subject", "contract_identity", "policy") == selected.reject { |key, _| key == "version" }
160
- raise AttemptErrors::EvidenceUnavailable, "campaign child differs from original parent contract"
161
- end
162
- CampaignExecution.validate_round!(campaign.fetch(:execution), parent: campaign.fetch(:definition).id, round: round)
163
- guard = lambda do |journal, commit|
164
- unless @deployment.equal?(deployment) && protected_descriptor_sha256! == descriptor_sha
165
- raise AttemptErrors::Conflict, "current campaign policy descriptor changed"
166
- end
167
- artifacts.verify_unchanged!
168
- round_guard.call
169
- recheck_campaign_parent!(context, journal, commit: commit)
170
- unique_campaign_child!(context, journal, commit) if params.key?("definition_bytes")
171
- end
172
- yield guard
173
- end
174
- end
175
- rescue KeyError, TypeError, Ace::Review::Atoms::CampaignContract::Invalid, Ace::Runtime::RuntimeUnavailableError
176
- raise AttemptErrors::EvidenceUnavailable, "campaign child pinned round is unavailable"
177
- end
178
-
179
- # One immutable phase definition, discovered from the existing canonical
180
- # registration owner. A caller-provided child list cannot omit a sibling.
181
- def unique_campaign_child!(context, journal, commit)
182
- selected = context.fetch(:campaign).fetch(:execution)
183
- fields = %w[parent_assignment_id parent_attempt_id parent_candidate_generation round_id phase]
184
- fields += case selected.fetch("phase")
185
- when "collection" then ["review_scope"]
186
- when "check" then ["check_name"]
187
- else []
188
- end
189
- inventory = journal.canonical_event_inventory!(commit: commit)
190
- inventory.fetch("events").each do |assignment, events|
191
- next if assignment == context.fetch(:campaign).fetch(:definition).id
192
- events.select { |event| event.dig("payload", "operation") == "register_assignment" }.each do |event|
193
- registration = event.fetch("payload").fetch("data")
194
- entry = {selector: {"assignment_id" => assignment, "attempt_id" => nil}, registration: registration}
195
- definition = inventory_definition!(journal, commit, entry)
196
- child = definition.campaign_execution
197
- next unless child && child.slice(*fields) == selected.slice(*fields)
198
- # Re-registration of this exact child is permitted; a distinct
199
- # assignment cannot replace the retained phase owner.
200
- current_assignment = context.fetch(:campaign).fetch(:child_assignment)
201
- next if assignment == current_assignment
202
- original = control_original_descriptor!(registration.fetch("lifecycle_control"))
203
- map_id = registration.fetch("mapping_id")
204
- authenticate_inventory_registration!(journal, assignment, events, registration, map_id,
205
- original.mapping(map_id), commit, inventory.fetch("introductions").fetch(assignment))
206
- raise AttemptErrors::Conflict, "campaign phase already has a registered child"
207
- end
208
- end
209
- true
210
- end
211
- end
212
- end
213
- end
214
- end