ace-assign 0.63.1 → 0.66.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 (147) hide show
  1. checksums.yaml +4 -4
  2. data/.ace-defaults/assign/catalog/steps/auto-merge.step.yml +11 -0
  3. data/.ace-defaults/assign/catalog/steps/create-pr.step.yml +2 -2
  4. data/.ace-defaults/assign/catalog/steps/mark-pr-ready.step.yml +1 -1
  5. data/.ace-defaults/assign/catalog/steps/release-minor.step.yml +4 -2
  6. data/.ace-defaults/assign/catalog/steps/release.step.yml +4 -2
  7. data/.ace-defaults/assign/catalog/steps/update-pr-desc.step.yml +1 -1
  8. data/.ace-defaults/assign/presets/fix-bug.yml +15 -3
  9. data/.ace-defaults/assign/presets/work-on-task-ace-development.yml +16 -4
  10. data/.ace-defaults/assign/presets/work-on-task-auto-merge.yml +15 -3
  11. data/.ace-defaults/assign/presets/work-on-task.yml +16 -4
  12. data/CHANGELOG.md +62 -0
  13. data/docs/demo/canonical-skill-source.tape.yml +4 -4
  14. data/docs/protected-authority.md +282 -0
  15. data/docs/usage.md +205 -3
  16. data/exe/ace-scope-ready +55 -0
  17. data/handbook/workflow-instructions/assign/drive.wf.md +28 -3
  18. data/handbook/workflow-instructions/assign/prepare.wf.md +2 -2
  19. data/lib/ace/assign/atoms/attempt_state_machine.rb +8 -0
  20. data/lib/ace/assign/atoms/delivery_parameters.rb +19 -0
  21. data/lib/ace/assign/authority/assignment_inventory.rb +290 -0
  22. data/lib/ace/assign/authority/campaign_consumer_policy.rb +100 -0
  23. data/lib/ace/assign/authority/campaign_execution.rb +119 -0
  24. data/lib/ace/assign/authority/campaign_round_transfer.rb +82 -0
  25. data/lib/ace/assign/authority/candidate_transfer.rb +327 -0
  26. data/lib/ace/assign/authority/cleanup_result_protection.rb +53 -0
  27. data/lib/ace/assign/authority/client.rb +257 -0
  28. data/lib/ace/assign/authority/deployment.rb +627 -0
  29. data/lib/ace/assign/authority/deployment_history.rb +146 -0
  30. data/lib/ace/assign/authority/endcap.rb +455 -0
  31. data/lib/ace/assign/authority/endcap_attempts.rb +274 -0
  32. data/lib/ace/assign/authority/endcap_campaign_results.rb +232 -0
  33. data/lib/ace/assign/authority/endcap_campaign_rounds.rb +266 -0
  34. data/lib/ace/assign/authority/endcap_contexts.rb +23 -0
  35. data/lib/ace/assign/authority/endcap_inboxes.rb +37 -0
  36. data/lib/ace/assign/authority/endcap_prepared_work.rb +80 -0
  37. data/lib/ace/assign/authority/endcap_publication.rb +207 -0
  38. data/lib/ace/assign/authority/endcap_results.rb +398 -0
  39. data/lib/ace/assign/authority/endcap_review_requests.rb +183 -0
  40. data/lib/ace/assign/authority/endcap_reviews.rb +130 -0
  41. data/lib/ace/assign/authority/endcap_service_settlement.rb +152 -0
  42. data/lib/ace/assign/authority/endcap_services.rb +598 -0
  43. data/lib/ace/assign/authority/evidence_transfer.rb +67 -0
  44. data/lib/ace/assign/authority/execution_scope_observation.rb +903 -0
  45. data/lib/ace/assign/authority/inbox_context_original.rb +113 -0
  46. data/lib/ace/assign/authority/launch_attempt_consumers.rb +51 -0
  47. data/lib/ace/assign/authority/launch_campaign_registration.rb +214 -0
  48. data/lib/ace/assign/authority/launch_control_channel.rb +313 -0
  49. data/lib/ace/assign/authority/launch_control_exclusions.rb +141 -0
  50. data/lib/ace/assign/authority/launch_driver.rb +393 -0
  51. data/lib/ace/assign/authority/launch_input_inhibition.rb +132 -0
  52. data/lib/ace/assign/authority/launch_lifecycle.rb +997 -0
  53. data/lib/ace/assign/authority/launch_prepared_work.rb +122 -0
  54. data/lib/ace/assign/authority/launch_review.rb +91 -0
  55. data/lib/ace/assign/authority/launch_scope_abort.rb +81 -0
  56. data/lib/ace/assign/authority/launch_scope_admission.rb +215 -0
  57. data/lib/ace/assign/authority/launch_scope_close.rb +146 -0
  58. data/lib/ace/assign/authority/launch_scope_parent.rb +44 -0
  59. data/lib/ace/assign/authority/launch_scope_release.rb +859 -0
  60. data/lib/ace/assign/authority/launch_steering.rb +353 -0
  61. data/lib/ace/assign/authority/launch_stop.rb +254 -0
  62. data/lib/ace/assign/authority/maintenance_workspace_retirement.rb +229 -0
  63. data/lib/ace/assign/authority/original_inbox_selection.rb +41 -0
  64. data/lib/ace/assign/authority/posix_acl.rb +59 -0
  65. data/lib/ace/assign/authority/prepared_input.rb +130 -0
  66. data/lib/ace/assign/authority/prepared_queue.rb +185 -0
  67. data/lib/ace/assign/authority/prepared_task_context.rb +116 -0
  68. data/lib/ace/assign/authority/prepared_work.rb +304 -0
  69. data/lib/ace/assign/authority/prepared_worker.rb +69 -0
  70. data/lib/ace/assign/authority/private_directory.rb +33 -0
  71. data/lib/ace/assign/authority/protected_assignment_context.rb +226 -0
  72. data/lib/ace/assign/authority/readiness_hook.rb +120 -0
  73. data/lib/ace/assign/authority/receipt_transfer.rb +48 -0
  74. data/lib/ace/assign/authority/router.rb +76 -0
  75. data/lib/ace/assign/authority/server.rb +300 -0
  76. data/lib/ace/assign/authority/service_cleanup_dispatch.rb +88 -0
  77. data/lib/ace/assign/authority/service_cleanup_evidence.rb +180 -0
  78. data/lib/ace/assign/authority/service_delivery_evidence.rb +101 -0
  79. data/lib/ace/assign/authority/service_evidence.rb +286 -0
  80. data/lib/ace/assign/authority/service_publication_evidence.rb +110 -0
  81. data/lib/ace/assign/authority/task_context_entry.rb +32 -0
  82. data/lib/ace/assign/authority/transfer_codec.rb +187 -0
  83. data/lib/ace/assign/cli/commands/assignment_target.rb +15 -2
  84. data/lib/ace/assign/cli/commands/attempt/base.rb +49 -0
  85. data/lib/ace/assign/cli/commands/attempt/finish.rb +20 -4
  86. data/lib/ace/assign/cli/commands/attempt/reconcile.rb +11 -0
  87. data/lib/ace/assign/cli/commands/authority/inbox_context_principal.rb +25 -0
  88. data/lib/ace/assign/cli/commands/authority/inbox_context_selection.rb +28 -0
  89. data/lib/ace/assign/cli/commands/authority/launch.rb +97 -0
  90. data/lib/ace/assign/cli/commands/authority/serve.rb +31 -0
  91. data/lib/ace/assign/cli/commands/authority/status.rb +26 -0
  92. data/lib/ace/assign/cli/commands/authority/task_context.rb +31 -0
  93. data/lib/ace/assign/cli/commands/authority/task_context_principal.rb +25 -0
  94. data/lib/ace/assign/cli/commands/authority/task_context_selection.rb +30 -0
  95. data/lib/ace/assign/cli/commands/authority/terminate.rb +29 -0
  96. data/lib/ace/assign/cli/commands/authority/worker.rb +23 -0
  97. data/lib/ace/assign/cli/commands/campaign_export_result.rb +55 -0
  98. data/lib/ace/assign/cli/commands/campaign_record_round.rb +57 -0
  99. data/lib/ace/assign/cli/commands/create.rb +11 -1
  100. data/lib/ace/assign/cli/commands/delivery.rb +107 -0
  101. data/lib/ace/assign/cli/commands/fail.rb +6 -0
  102. data/lib/ace/assign/cli/commands/finish.rb +23 -2
  103. data/lib/ace/assign/cli/commands/fork_session.rb +6 -2
  104. data/lib/ace/assign/cli/commands/inbox_bind.rb +34 -0
  105. data/lib/ace/assign/cli/commands/protected_submission.rb +61 -0
  106. data/lib/ace/assign/cli/commands/resume.rb +38 -0
  107. data/lib/ace/assign/cli/commands/select.rb +2 -0
  108. data/lib/ace/assign/cli/commands/start.rb +2 -0
  109. data/lib/ace/assign/cli/commands/status.rb +4 -2
  110. data/lib/ace/assign/cli/commands/step.rb +3 -1
  111. data/lib/ace/assign/cli/commands/submit_candidate.rb +34 -0
  112. data/lib/ace/assign/cli/commands/submit_result.rb +61 -0
  113. data/lib/ace/assign/cli.rb +56 -4
  114. data/lib/ace/assign/errors.rb +139 -0
  115. data/lib/ace/assign/models/assignment.rb +26 -4
  116. data/lib/ace/assign/models/evidence_event.rb +4 -2
  117. data/lib/ace/assign/molecules/assignment_manager.rb +12 -2
  118. data/lib/ace/assign/molecules/attempt_reconciler.rb +51 -12
  119. data/lib/ace/assign/molecules/canonical_attempt_state.rb +151 -0
  120. data/lib/ace/assign/molecules/canonical_evidence.rb +153 -0
  121. data/lib/ace/assign/molecules/canonical_read_snapshot.rb +374 -0
  122. data/lib/ace/assign/molecules/evidence_journal.rb +922 -91
  123. data/lib/ace/assign/molecules/execution_identity_resolver.rb +49 -9
  124. data/lib/ace/assign/molecules/execution_scope_lineage.rb +345 -0
  125. data/lib/ace/assign/molecules/fork_session_launcher.rb +16 -6
  126. data/lib/ace/assign/molecules/journal_input_inhibition.rb +78 -0
  127. data/lib/ace/assign/molecules/journal_mutation.rb +271 -0
  128. data/lib/ace/assign/molecules/journal_prompt_mutation.rb +241 -0
  129. data/lib/ace/assign/molecules/lifecycle_exclusion.rb +182 -26
  130. data/lib/ace/assign/molecules/original_launch_binding.rb +37 -0
  131. data/lib/ace/assign/molecules/proposal_journal.rb +151 -0
  132. data/lib/ace/assign/molecules/protected_control_exclusion.rb +303 -0
  133. data/lib/ace/assign/molecules/protected_workspace_exclusion.rb +330 -0
  134. data/lib/ace/assign/molecules/protected_workspace_provisioner.rb +164 -0
  135. data/lib/ace/assign/molecules/queue_scanner.rb +26 -11
  136. data/lib/ace/assign/molecules/receipt_verifier.rb +68 -32
  137. data/lib/ace/assign/molecules/step_writer.rb +27 -5
  138. data/lib/ace/assign/molecules/terminal_scope_receipt.rb +88 -0
  139. data/lib/ace/assign/organisms/assignment_executor.rb +10 -4
  140. data/lib/ace/assign/organisms/attempt_coordinator.rb +302 -13
  141. data/lib/ace/assign/organisms/delivery_coordinator.rb +342 -0
  142. data/lib/ace/assign/organisms/prepared_work_builder.rb +194 -0
  143. data/lib/ace/assign/organisms/protected_delivery_coordinator.rb +100 -0
  144. data/lib/ace/assign/organisms/task_assignment_creator.rb +11 -4
  145. data/lib/ace/assign/version.rb +1 -1
  146. data/lib/ace/assign.rb +4 -102
  147. metadata +186 -8
@@ -0,0 +1,146 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ace
4
+ module Assign
5
+ module Authority
6
+ class LaunchLifecycle
7
+ # Source-owned close core. Its first accepted mutation seals admission
8
+ # and caches the retained running reply before any manager stop. A
9
+ # subsequent fresh mutation may record positive observation; replay of
10
+ # the first mutation always keeps its original reply and performs no I/O.
11
+ def close_execution_scope!(params:, peer:, role:)
12
+ strict!(params, %w[mapping_id assignment_id attempt_id mutation_id expected_generation])
13
+ %w[assignment_id attempt_id mutation_id].each { |key| token!(params.fetch(key)) }
14
+ generation!(params.fetch("expected_generation"))
15
+ @kernel.live!(peer)
16
+ map = @deployment.mapping(params.fetch("mapping_id"))
17
+ journal = journal_for(map)
18
+ input = params.reject { |key, _| key == "mutation_id" }
19
+ digest = Digest::SHA256.hexdigest(JSON.generate(canonical(input)))
20
+ stop_after_commit = false
21
+ inhibit_after_commit = false
22
+ result = with_containment_exclusion(params, map, journal) do
23
+ result = @mutex.synchronize do
24
+ current = journal.read_events(params.fetch("assignment_id")).select { |event| event["attempt_id"] == params.fetch("attempt_id") }
25
+ scope_close_owner!(params, map, current, peer, role)
26
+ journal.mutate(assignment_id: params.fetch("assignment_id"), attempt_id: params.fetch("attempt_id"),
27
+ mutation_id: params.fetch("mutation_id"), operation: "close_execution_scope", parameters_digest: digest,
28
+ expected_generation: params.fetch("expected_generation"), with_replay: true) do |events, _commit, _generation|
29
+ lineage = scope_close_owner!(params, map, events, peer, role)
30
+ closure = scope_closure_plan!(lineage: lineage, params: params, map: map, events: events, journal: journal, commit: _commit)
31
+ stop_after_commit = closure.fetch(:stop_required)
32
+ inhibit_after_commit = closure.fetch(:inhibit_required)
33
+ closure.fetch(:plan)
34
+ end
35
+ end
36
+ result
37
+ end
38
+ if !result.fetch(:replayed) && inhibit_after_commit
39
+ request_original_input_inhibition!(params, map, journal)
40
+ end
41
+ if !result.fetch(:replayed) && stop_after_commit
42
+ with_containment_exclusion(params, map, journal) do
43
+ @mutex.synchronize do
44
+ current = journal.read_events(params.fetch("assignment_id")).select { |event| event["attempt_id"] == params.fetch("attempt_id") }
45
+ scope_close_owner!(params, map, current, peer, role)
46
+ end
47
+ scope_observer_for(params.fetch("mapping_id")).stop_sealed_service!
48
+ end
49
+ end
50
+ result
51
+ end
52
+
53
+ def observe_execution_scope!(params:, peer:, role:)
54
+ strict!(params, %w[mapping_id assignment_id attempt_id])
55
+ %w[assignment_id attempt_id].each { |key| token!(params.fetch(key)) }
56
+ @kernel.live!(peer)
57
+ map = @deployment.mapping(params.fetch("mapping_id"))
58
+ journal = journal_for(map)
59
+ with_containment_exclusion(params, map, journal) do
60
+ @mutex.synchronize do
61
+ commit = journal.ref_value
62
+ events = journal.read_events(params.fetch("assignment_id"), commit: commit).select { |event| event["attempt_id"] == params.fetch("attempt_id") }
63
+ lineage = scope_close_owner!(params, map, events, peer, role)
64
+ projection = {"attempt_id" => params.fetch("attempt_id"), "scope_generation" => lineage.binding&.fetch("scope_generation"),
65
+ "state" => "unverifiable", "proof_id" => nil, "required_action" => "inspect_exact_scope",
66
+ "generation" => journal.authority_generation(events), "journal_commit" => commit}
67
+ return projection unless lineage.binding
68
+ begin
69
+ observation = scope_observer_for(params.fetch("mapping_id")).observe(lineage)
70
+ if pending_prompt_issuers?(events, journal, commit)
71
+ projection.merge("required_action" => "drain_original_prompt_issuer")
72
+ elsif lineage.proof_event
73
+ scope_observer_for(params.fetch("mapping_id")).verify_closed!(lineage)
74
+ projection.merge("state" => "closed_no_writers", "proof_id" => lineage.proof_id, "required_action" => nil)
75
+ elsif lineage.sealed? && observation.fetch("populated").zero?
76
+ projection.merge("required_action" => "close_scope")
77
+ else
78
+ projection.merge("state" => "running", "required_action" => "close_scope")
79
+ end
80
+ rescue Ace::Runtime::RuntimeUnavailableError, AttemptErrors::EvidenceUnavailable
81
+ projection
82
+ end
83
+ end
84
+ end
85
+ end
86
+
87
+ private
88
+
89
+ # Shared source-only plan for close and public stop. It does not issue
90
+ # manager/native control, mutate canonical state or acquire exclusions.
91
+ # Each CAS invocation derives its own post-commit work flags anew.
92
+ def scope_closure_plan!(lineage:, params:, map:, events:, journal:, commit:)
93
+ stop_required = false
94
+ inhibit_required = false
95
+ binding = lineage.binding
96
+ raise AttemptErrors::EvidenceUnavailable, "original parent binding is missing" unless binding
97
+ projection = {"attempt_id" => params.fetch("attempt_id"), "scope_generation" => binding.fetch("scope_generation"),
98
+ "state" => "running", "proof_id" => nil, "required_action" => "close_scope"}
99
+ plan = if !lineage.sealed?
100
+ stop_required = true
101
+ inhibit_required = true
102
+ payload = {"scope_generation" => binding.fetch("scope_generation"), "scope_binding_event_id" => lineage.binding_event.fetch("digest")}
103
+ {events: [{type: "scope_sealed", payload: payload}], blobs: {}, data: projection}
104
+ elsif @native_issuers.key?(native_issuer_key(params, map)) || pending_prompt_issuers?(events, journal, commit)
105
+ # A live issuer may still start after this inactive snapshot.
106
+ inhibit_required = true
107
+ {events: [], blobs: {}, data: projection}
108
+ elsif lineage.proof_event
109
+ scope_observer_for(params.fetch("mapping_id")).verify_closed!(lineage)
110
+ {events: [], blobs: {}, data: projection.merge("state" => "closed_no_writers", "proof_id" => lineage.proof_id, "required_action" => nil)}
111
+ elsif scope_observer_for(params.fetch("mapping_id")).sealed_service_stop_required?(lineage)
112
+ # A fresh canonical close resumes a stop lost after the seal.
113
+ # Replaying either mutation performs no manager I/O.
114
+ stop_required = true
115
+ {events: [], blobs: {}, data: projection}
116
+ else
117
+ # This call must return a fresh validated observation, never
118
+ # a cached population, caller JSON or a manifest boolean.
119
+ payload = scope_observer_for(params.fetch("mapping_id")).closed_observation_for_proof!(lineage, events: events)
120
+ expected = binding.slice("scope_generation", "boot_id", "slice_invocation_id", "cgroup_identity").merge(
121
+ "scope_binding_event_id" => lineage.binding_event.fetch("digest"), "seal_event_id" => lineage.seal_event.fetch("digest"), "populated" => 0)
122
+ unless payload == expected
123
+ raise AttemptErrors::EvidenceUnavailable, "fresh observation does not name the retained sealed parent"
124
+ end
125
+ at = Time.now.utc
126
+ event = Models::EvidenceEvent.build(type: "scope_closed_no_writers", attempt_id: params.fetch("attempt_id"),
127
+ payload: payload, previous_digest: events.last&.fetch("digest"), recorded_at: at)
128
+ {events: [{type: "scope_closed_no_writers", payload: payload, recorded_at: at}], blobs: {},
129
+ data: projection.merge("state" => "closed_no_writers", "proof_id" => event.fetch("digest"), "required_action" => nil)}
130
+ end
131
+ {plan: plan, stop_required: stop_required, inhibit_required: inhibit_required}
132
+ end
133
+
134
+ def scope_close_owner!(params, map, events, peer, role)
135
+ state = states(events)[params.fetch("attempt_id")]
136
+ unless state && state["mapping_id"] == params.fetch("mapping_id") &&
137
+ (role == :supervisor || role == :launcher && @kernel.same?(state.fetch("launcher_identity"), peer))
138
+ raise AttemptErrors::UnauthorizedIdentity, "scope closure requires the original launch owner"
139
+ end
140
+ Molecules::ExecutionScopeLineage.new(events: events, project_id: map.fetch("project_id"),
141
+ assignment_id: params.fetch("assignment_id"), attempt_id: params.fetch("attempt_id"), mapping_id: params.fetch("mapping_id"))
142
+ end
143
+ end
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ace
4
+ module Assign
5
+ module Authority
6
+ class LaunchLifecycle
7
+ private
8
+
9
+ # Called inside the existing slot/assignment exclusion, after the fresh
10
+ # reservation CAS and outside its authority mutex/journal ref lock.
11
+ # Mutation replay and restart never call this activation path.
12
+ def provision_reserved_parent_held!(reservation, map, journal)
13
+ params = reservation.slice("mapping_id", "assignment_id", "attempt_id")
14
+ context = params.merge("project_id" => map.fetch("project_id"),
15
+ "reservation_generation" => reservation.fetch("reservation_generation"),
16
+ "scope_generation" => reservation.fetch("generation") + 1)
17
+ observer = scope_observer_for(params.fetch("mapping_id"))
18
+ binding = observer.activate_parent!(context)
19
+ @mutex.synchronize do
20
+ journal.mutate(assignment_id: params.fetch("assignment_id"), attempt_id: params.fetch("attempt_id"),
21
+ mutation_id: "scope-parent-#{Digest::SHA256.hexdigest(params.fetch('attempt_id'))[0, 48]}",
22
+ operation: "scope_parent_binding", parameters_digest: Digest::SHA256.hexdigest(JSON.generate(canonical(context))),
23
+ expected_generation: reservation.fetch("generation"), with_replay: true) do |events, _commit, _generation|
24
+ state = states(events)[params.fetch("attempt_id")]
25
+ unless state && state["phase"] == "reserved" && state["launch_ticket"] == reservation.fetch("launch_ticket") &&
26
+ events.none? { |event| %w[scope_bound scope_native_bound scope_child_bound scope_sealed].include?(event["type"]) ||
27
+ event["type"] == "authority_mutation" && event.dig("payload", "operation") == "scope_service_admission" }
28
+ raise AttemptErrors::Conflict, "parent activation cannot replace or reopen an existing generation"
29
+ end
30
+ at = Time.now.utc
31
+ event = Models::EvidenceEvent.build(type: "scope_bound", attempt_id: params.fetch("attempt_id"),
32
+ payload: binding, previous_digest: events.last&.fetch("digest"), recorded_at: at)
33
+ lineage = Molecules::ExecutionScopeLineage.new(events: events + [event], project_id: map.fetch("project_id"),
34
+ assignment_id: params.fetch("assignment_id"), attempt_id: params.fetch("attempt_id"), mapping_id: params.fetch("mapping_id"))
35
+ observer.observe(lineage)
36
+ {events: [{type: "scope_bound", payload: binding, recorded_at: at}], blobs: {}, data: state.merge(
37
+ "scope_generation" => binding.fetch("scope_generation"), "scope_binding_event_id" => event.fetch("digest"))}
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end