ace-assign 0.70.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 (181) 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 -2099
  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 -1
  16. data/handbook/workflow-instructions/assign/drive.wf.md +47 -118
  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 +2 -1
  35. data/lib/ace/assign/organisms/assignment_executor.rb +0 -6
  36. data/lib/ace/assign/organisms/task_assignment_creator.rb +3 -8
  37. data/lib/ace/assign/version.rb +1 -1
  38. data/lib/ace/assign.rb +0 -6
  39. metadata +4 -161
  40. data/.ace-defaults/assign/presets/close-delivery.yml +0 -57
  41. data/.ace-defaults/assign/presets/deliver-release.yml +0 -60
  42. data/.ace-defaults/assign/presets/integrate-batch.yml +0 -72
  43. data/.ace-defaults/assign/presets/supervise-delivery.yml +0 -69
  44. data/docs/protected-authority.md +0 -282
  45. data/exe/ace-scope-ready +0 -55
  46. data/lib/ace/assign/atoms/assignment_scope.rb +0 -58
  47. data/lib/ace/assign/atoms/attempt_state_machine.rb +0 -84
  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/authority/assignment_definition.rb +0 -35
  52. data/lib/ace/assign/authority/assignment_inventory.rb +0 -290
  53. data/lib/ace/assign/authority/campaign_consumer_policy.rb +0 -100
  54. data/lib/ace/assign/authority/campaign_execution.rb +0 -119
  55. data/lib/ace/assign/authority/campaign_round_transfer.rb +0 -82
  56. data/lib/ace/assign/authority/candidate_transfer.rb +0 -327
  57. data/lib/ace/assign/authority/cleanup_result_protection.rb +0 -53
  58. data/lib/ace/assign/authority/client.rb +0 -257
  59. data/lib/ace/assign/authority/deployment.rb +0 -627
  60. data/lib/ace/assign/authority/deployment_history.rb +0 -146
  61. data/lib/ace/assign/authority/endcap.rb +0 -455
  62. data/lib/ace/assign/authority/endcap_attempts.rb +0 -274
  63. data/lib/ace/assign/authority/endcap_campaign_results.rb +0 -232
  64. data/lib/ace/assign/authority/endcap_campaign_rounds.rb +0 -266
  65. data/lib/ace/assign/authority/endcap_contexts.rb +0 -23
  66. data/lib/ace/assign/authority/endcap_inboxes.rb +0 -37
  67. data/lib/ace/assign/authority/endcap_prepared_work.rb +0 -80
  68. data/lib/ace/assign/authority/endcap_publication.rb +0 -207
  69. data/lib/ace/assign/authority/endcap_results.rb +0 -398
  70. data/lib/ace/assign/authority/endcap_review_requests.rb +0 -183
  71. data/lib/ace/assign/authority/endcap_reviews.rb +0 -130
  72. data/lib/ace/assign/authority/endcap_service_settlement.rb +0 -152
  73. data/lib/ace/assign/authority/endcap_services.rb +0 -598
  74. data/lib/ace/assign/authority/evidence_transfer.rb +0 -67
  75. data/lib/ace/assign/authority/execution_scope_observation.rb +0 -903
  76. data/lib/ace/assign/authority/inbox_context_original.rb +0 -113
  77. data/lib/ace/assign/authority/launch_attempt_consumers.rb +0 -51
  78. data/lib/ace/assign/authority/launch_campaign_registration.rb +0 -214
  79. data/lib/ace/assign/authority/launch_control_channel.rb +0 -313
  80. data/lib/ace/assign/authority/launch_control_exclusions.rb +0 -127
  81. data/lib/ace/assign/authority/launch_driver.rb +0 -393
  82. data/lib/ace/assign/authority/launch_input_inhibition.rb +0 -132
  83. data/lib/ace/assign/authority/launch_lifecycle.rb +0 -997
  84. data/lib/ace/assign/authority/launch_prepared_work.rb +0 -122
  85. data/lib/ace/assign/authority/launch_review.rb +0 -91
  86. data/lib/ace/assign/authority/launch_scope_abort.rb +0 -81
  87. data/lib/ace/assign/authority/launch_scope_admission.rb +0 -215
  88. data/lib/ace/assign/authority/launch_scope_close.rb +0 -146
  89. data/lib/ace/assign/authority/launch_scope_parent.rb +0 -44
  90. data/lib/ace/assign/authority/launch_scope_release.rb +0 -859
  91. data/lib/ace/assign/authority/launch_steering.rb +0 -353
  92. data/lib/ace/assign/authority/launch_stop.rb +0 -254
  93. data/lib/ace/assign/authority/maintenance_workspace_retirement.rb +0 -229
  94. data/lib/ace/assign/authority/ordinary_attempt_start.rb +0 -137
  95. data/lib/ace/assign/authority/ordinary_client.rb +0 -70
  96. data/lib/ace/assign/authority/ordinary_completion.rb +0 -94
  97. data/lib/ace/assign/authority/ordinary_effect_settlement.rb +0 -118
  98. data/lib/ace/assign/authority/ordinary_lifecycle.rb +0 -191
  99. data/lib/ace/assign/authority/ordinary_peer.rb +0 -54
  100. data/lib/ace/assign/authority/ordinary_progress.rb +0 -162
  101. data/lib/ace/assign/authority/ordinary_queue.rb +0 -115
  102. data/lib/ace/assign/authority/ordinary_results.rb +0 -196
  103. data/lib/ace/assign/authority/ordinary_server.rb +0 -75
  104. data/lib/ace/assign/authority/original_inbox_selection.rb +0 -41
  105. data/lib/ace/assign/authority/posix_acl.rb +0 -59
  106. data/lib/ace/assign/authority/prepared_input.rb +0 -130
  107. data/lib/ace/assign/authority/prepared_queue.rb +0 -185
  108. data/lib/ace/assign/authority/prepared_task_context.rb +0 -116
  109. data/lib/ace/assign/authority/prepared_work.rb +0 -304
  110. data/lib/ace/assign/authority/prepared_worker.rb +0 -69
  111. data/lib/ace/assign/authority/private_directory.rb +0 -33
  112. data/lib/ace/assign/authority/protected_assignment_context.rb +0 -226
  113. data/lib/ace/assign/authority/readiness_hook.rb +0 -120
  114. data/lib/ace/assign/authority/receipt_transfer.rb +0 -48
  115. data/lib/ace/assign/authority/router.rb +0 -81
  116. data/lib/ace/assign/authority/server.rb +0 -131
  117. data/lib/ace/assign/authority/service_cleanup_dispatch.rb +0 -88
  118. data/lib/ace/assign/authority/service_cleanup_evidence.rb +0 -180
  119. data/lib/ace/assign/authority/service_delivery_evidence.rb +0 -101
  120. data/lib/ace/assign/authority/service_evidence.rb +0 -286
  121. data/lib/ace/assign/authority/service_publication_evidence.rb +0 -110
  122. data/lib/ace/assign/authority/socket_server.rb +0 -255
  123. data/lib/ace/assign/authority/task_context_entry.rb +0 -32
  124. data/lib/ace/assign/authority/transfer_codec.rb +0 -187
  125. data/lib/ace/assign/cli/commands/attempt/base.rb +0 -84
  126. data/lib/ace/assign/cli/commands/attempt/evidence.rb +0 -28
  127. data/lib/ace/assign/cli/commands/attempt/finish.rb +0 -51
  128. data/lib/ace/assign/cli/commands/attempt/reconcile.rb +0 -49
  129. data/lib/ace/assign/cli/commands/attempt/start.rb +0 -45
  130. data/lib/ace/assign/cli/commands/attempt/status.rb +0 -54
  131. data/lib/ace/assign/cli/commands/authority/inbox_context_principal.rb +0 -25
  132. data/lib/ace/assign/cli/commands/authority/inbox_context_selection.rb +0 -28
  133. data/lib/ace/assign/cli/commands/authority/launch.rb +0 -97
  134. data/lib/ace/assign/cli/commands/authority/serve.rb +0 -31
  135. data/lib/ace/assign/cli/commands/authority/status.rb +0 -26
  136. data/lib/ace/assign/cli/commands/authority/task_context.rb +0 -31
  137. data/lib/ace/assign/cli/commands/authority/task_context_principal.rb +0 -25
  138. data/lib/ace/assign/cli/commands/authority/task_context_selection.rb +0 -30
  139. data/lib/ace/assign/cli/commands/authority/terminate.rb +0 -29
  140. data/lib/ace/assign/cli/commands/authority/worker.rb +0 -23
  141. data/lib/ace/assign/cli/commands/campaign_export_result.rb +0 -55
  142. data/lib/ace/assign/cli/commands/campaign_record_round.rb +0 -57
  143. data/lib/ace/assign/cli/commands/delivery.rb +0 -107
  144. data/lib/ace/assign/cli/commands/inbox_bind.rb +0 -34
  145. data/lib/ace/assign/cli/commands/protected_submission.rb +0 -61
  146. data/lib/ace/assign/cli/commands/resume.rb +0 -38
  147. data/lib/ace/assign/cli/commands/submit_candidate.rb +0 -34
  148. data/lib/ace/assign/cli/commands/submit_result.rb +0 -61
  149. data/lib/ace/assign/models/attempt.rb +0 -178
  150. data/lib/ace/assign/models/attempt_binding.rb +0 -116
  151. data/lib/ace/assign/models/evidence_event.rb +0 -86
  152. data/lib/ace/assign/models/execution_receipt.rb +0 -139
  153. data/lib/ace/assign/molecules/attempt_reconciler.rb +0 -127
  154. data/lib/ace/assign/molecules/attempt_store.rb +0 -201
  155. data/lib/ace/assign/molecules/canonical_attempt_state.rb +0 -151
  156. data/lib/ace/assign/molecules/canonical_evidence.rb +0 -153
  157. data/lib/ace/assign/molecules/canonical_read_commands.rb +0 -84
  158. data/lib/ace/assign/molecules/canonical_read_snapshot.rb +0 -313
  159. data/lib/ace/assign/molecules/evidence_calculator.rb +0 -237
  160. data/lib/ace/assign/molecules/evidence_journal.rb +0 -1912
  161. data/lib/ace/assign/molecules/execution_identity_resolver.rb +0 -137
  162. data/lib/ace/assign/molecules/execution_scope_lineage.rb +0 -345
  163. data/lib/ace/assign/molecules/fixed_effect_reference.rb +0 -31
  164. data/lib/ace/assign/molecules/installed_journal_read.rb +0 -143
  165. data/lib/ace/assign/molecules/journal_input_inhibition.rb +0 -78
  166. data/lib/ace/assign/molecules/journal_mutation.rb +0 -273
  167. data/lib/ace/assign/molecules/journal_prompt_mutation.rb +0 -241
  168. data/lib/ace/assign/molecules/ordinary_merge_cli.rb +0 -68
  169. data/lib/ace/assign/molecules/ordinary_merge_contract.rb +0 -410
  170. data/lib/ace/assign/molecules/original_launch_binding.rb +0 -37
  171. data/lib/ace/assign/molecules/proposal_journal.rb +0 -179
  172. data/lib/ace/assign/molecules/protected_control_exclusion.rb +0 -303
  173. data/lib/ace/assign/molecules/protected_workspace_exclusion.rb +0 -330
  174. data/lib/ace/assign/molecules/protected_workspace_provisioner.rb +0 -164
  175. data/lib/ace/assign/molecules/receipt_verifier.rb +0 -314
  176. data/lib/ace/assign/molecules/terminal_scope_receipt.rb +0 -88
  177. data/lib/ace/assign/organisms/attempt_coordinator.rb +0 -1337
  178. data/lib/ace/assign/organisms/delivery_coordinator.rb +0 -342
  179. data/lib/ace/assign/organisms/ordinary_merge_consumer.rb +0 -365
  180. data/lib/ace/assign/organisms/prepared_work_builder.rb +0 -194
  181. data/lib/ace/assign/organisms/protected_delivery_coordinator.rb +0 -100
@@ -1,34 +0,0 @@
1
- # frozen_string_literal: true
2
- require_relative "protected_submission"
3
- module Ace
4
- module Assign
5
- module CLI
6
- module Commands
7
- class SubmitCandidate < Ace::Support::Cli::Command
8
- include Ace::Support::Cli::Base
9
- include ProtectedSubmission
10
- desc "Submit original protected worker bundle bytes"
11
- option :mapping, desc: "Installed mapping"
12
- option :assignment, desc: "Original assignment"
13
- option :attempt, desc: "Original attempt"
14
- option :head, desc: "Exact candidate head"
15
- option :candidate_generation, desc: "Expected current candidate generation (zero before first submission)"
16
- option :expected_generation, desc: "Persisted authority generation"
17
- option :mutation, desc: "Stable original mutation"
18
- option :bundle, desc: "Bounded local byte file"
19
-
20
- def call(**options)
21
- usage = "submit-candidate with original selectors, generation, mutation and --bundle FILE"
22
- client, params, mutation = submission_request(options, usage)
23
- head = require_option(options, :head, usage)
24
- raise Ace::Support::Cli::Error, "Head must be an exact SHA-1" unless head.match?(/\A[0-9a-f]{40}\z/)
25
- params.merge!("head" => head, "candidate_generation" => integer_option(options, :candidate_generation, usage))
26
- bytes = input_bytes(require_option(options, :bundle, usage), limit: 64 * 1024 * 1024)
27
- parts = [bytes]
28
- emit_json(upload_call(client, "submit_candidate", params, mutation, parts, :candidate))
29
- end
30
- end
31
- end
32
- end
33
- end
34
- end
@@ -1,61 +0,0 @@
1
- # frozen_string_literal: true
2
- require_relative "protected_submission"
3
- require_relative "../../authority/receipt_transfer"
4
- module Ace
5
- module Assign
6
- module CLI
7
- module Commands
8
- class SubmitResult < Ace::Support::Cli::Command
9
- include Ace::Support::Cli::Base
10
- include ProtectedSubmission
11
- desc "Submit original protected worker receipt bytes"
12
- option :mapping, desc: "Installed mapping"
13
- option :assignment, desc: "Original assignment"
14
- option :attempt, desc: "Original attempt"
15
- option :head, desc: "Exact candidate head"
16
- option :candidate_generation, desc: "Positive candidate generation"
17
- option :expected_generation, desc: "Persisted authority generation"
18
- option :mutation, desc: "Stable original mutation"
19
- option :receipt, desc: "Bounded local byte file"
20
- option :artifact, type: :array, desc: "Ordered artifact byte files"
21
-
22
- def call(**options)
23
- usage = "submit-result with original selectors, generation, mutation and --receipt FILE"
24
- client, params, mutation = submission_request(options, usage)
25
- head = require_option(options, :head, usage)
26
- raise Ace::Support::Cli::Error, "Head must be an exact SHA-1" unless head.match?(/\A[0-9a-f]{40}\z/)
27
- params.merge!("head" => head, "candidate_generation" => integer_option(options, :candidate_generation, usage, positive: true))
28
- bytes = input_bytes(require_option(options, :receipt, usage), limit: 16 * 1024)
29
- receipt = input_json(bytes)
30
- raise Ace::Support::Cli::Error, "Receipt must be an object" unless receipt.is_a?(Hash)
31
- allowed = Models::ExecutionReceipt.from_h(receipt).to_h.keys
32
- if (receipt.keys - allowed).any? || Models::ExecutionReceipt.forbidden_field?(receipt)
33
- raise Ace::Support::Cli::Error, "Receipt contains unsupported fields"
34
- end
35
- unless receipt.values_at("assignment_id", "attempt_id", "head") == params.values_at("assignment_id", "attempt_id", "head") &&
36
- Models::ExecutionReceipt::VERDICTS.include?(receipt["verdict"])
37
- raise Ace::Support::Cli::Error, "Receipt identity or verdict differs"
38
- end
39
- files = options.fetch(:artifact, [])
40
- unless files.is_a?(Array) && files.size <= 16 && !(receipt["verdict"] == "succeeded" && files.empty?)
41
- raise Ace::Support::Cli::Error, "Receipt artifact declarations are invalid"
42
- end
43
- artifacts = files.map { |path| input_bytes(path, limit: 64 * 1024, nonempty: false) }
44
- parts = [bytes, *artifacts]
45
- digest = Digest::SHA256.hexdigest(bytes)
46
- input = Struct.new(:parts) do
47
- def count = parts.length
48
- def bytes(index: 0) = parts.fetch(index)
49
- end.new(parts)
50
- # Reuse the maintained ordered artifact validator. This is input
51
- # checking only; canonical result/producer admission remains Endcap's.
52
- Ace::Assign::Authority::ReceiptTransfer.decode(input: input, receipt_sha256: digest,
53
- artifact_field: "artifacts", reference_key: "path")
54
- params["receipt_sha256"] = digest
55
- emit_json(upload_call(client, "submit_result", params, mutation, parts, :receipt_artifacts))
56
- end
57
- end
58
- end
59
- end
60
- end
61
- end
@@ -1,178 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "../atoms/attempt_state_machine"
4
- require_relative "attempt_binding"
5
-
6
- module Ace
7
- module Assign
8
- module Models
9
- # Mutable derived state of an assignment attempt.
10
- #
11
- # The binding is immutable; an Attempt carries the derived execution
12
- # state: the current state-machine state, the pinned candidate head,
13
- # the latest evidence journal commit, accepted receipts (by digest),
14
- # recorded effects, and the append-only local event trail for
15
- # taskless attempts. `base_head` (binding) and `candidate_head`
16
- # (deliverable) are always tracked separately.
17
- class Attempt
18
- attr_reader :binding, :state, :candidate_head, :journal_commit,
19
- :accepted_receipts, :effects, :events, :updated_at
20
-
21
- # @param binding [Models::AttemptBinding] Immutable start-time binding
22
- # @param state [String] State-machine state
23
- # @param candidate_head [String, nil] Pinned deliverable head
24
- # @param journal_commit [String, nil] Latest evidence journal commit SHA
25
- # @param accepted_receipts [Array<Hash>] Accepted receipt payloads (digests included)
26
- # @param effects [Array<Hash>] Recorded effects with intent/receipt linkage
27
- # @param events [Array<Hash>] Local event trail (taskless attempts only)
28
- # @param updated_at [Time] Last state change
29
- def initialize(binding:, state: "reserved", candidate_head: nil, journal_commit: nil,
30
- accepted_receipts: [], effects: [], events: [], updated_at: nil)
31
- @binding = binding
32
- @state = state.to_s
33
- @candidate_head = candidate_head
34
- @journal_commit = journal_commit
35
- @accepted_receipts = accepted_receipts.dup.freeze
36
- @effects = effects.dup.freeze
37
- @events = events.dup.freeze
38
- @updated_at = updated_at || binding.created_at
39
- end
40
-
41
- # @return [String] Immutable attempt ID
42
- def attempt_id
43
- binding.attempt_id
44
- end
45
-
46
- # @return [Boolean] True if no further transitions or effects are allowed
47
- def terminal?
48
- Atoms::AttemptStateMachine.terminal?(state)
49
- end
50
-
51
- # @return [Boolean] True if the attempt awaits reconciliation
52
- def uncertain?
53
- state == "uncertain"
54
- end
55
-
56
- # @return [Boolean] True if the attempt owns an active subtree slot
57
- def active?
58
- %w[reserved running uncertain].include?(state)
59
- end
60
-
61
- # @return [String] "git" or "local_only" recovery mode
62
- def recovery_mode
63
- binding.recovery_mode
64
- end
65
-
66
- # @return [Boolean] True when managed evidence-ref journaling applies
67
- def managed?
68
- binding.managed?
69
- end
70
-
71
- # Effects whose intent was recorded but whose accepted receipt is missing.
72
- #
73
- # @return [Array<Hash>] Unresolved effect records
74
- def unresolved_effects
75
- effects.reject { |effect| effect["receipt_digest"] }
76
- end
77
-
78
- # Return a new Attempt with the state transitioned.
79
- #
80
- # @param to [String] Target state (validated against the state machine)
81
- # @param at [Time] Transition time
82
- # @return [Attempt] New attempt instance
83
- # @raise [Ace::Assign::AttemptErrors::InvalidTransition] if illegal
84
- def transition(to, at: Time.now.utc)
85
- Atoms::AttemptStateMachine.transition!(state, to)
86
- with(state: to.to_s, updated_at: at)
87
- end
88
-
89
- # Return a new Attempt with updated derived fields. The binding is
90
- # never replaced.
91
- #
92
- # @param attrs [Hash] Fields to override (state, candidate_head,
93
- # journal_commit, accepted_receipts, effects, events, updated_at)
94
- # @return [Attempt] New attempt instance
95
- def with(**attrs)
96
- Attempt.new(
97
- binding: binding,
98
- state: attrs.fetch(:state, state),
99
- candidate_head: attrs.fetch(:candidate_head, candidate_head),
100
- journal_commit: attrs.fetch(:journal_commit, journal_commit),
101
- accepted_receipts: attrs.fetch(:accepted_receipts, accepted_receipts),
102
- effects: attrs.fetch(:effects, effects),
103
- events: attrs.fetch(:events, events),
104
- updated_at: attrs.fetch(:updated_at, Time.now.utc)
105
- )
106
- end
107
-
108
- # Convert to a hash for canonical serialization.
109
- # @return [Hash] Attempt data with string keys
110
- def to_h
111
- {
112
- "binding" => binding.to_h,
113
- "state" => state,
114
- "candidate_head" => candidate_head,
115
- "journal_commit" => journal_commit,
116
- "accepted_receipts" => accepted_receipts,
117
- "effects" => effects,
118
- "events" => events,
119
- "updated_at" => updated_at.iso8601
120
- }
121
- end
122
-
123
- # JSON projection exposed by public commands. Contains attempt
124
- # ID, state, binding facts, references and digests only — never
125
- # credentials, receipt bytes, or terminal output.
126
- #
127
- # @return [Hash] Redacted projection
128
- def projection
129
- {
130
- "attempt_id" => attempt_id,
131
- "state" => state,
132
- "assignment_id" => binding.assignment_id,
133
- "scope" => binding.scope,
134
- "project_id" => binding.project_id,
135
- "task_id" => binding.task_id,
136
- "actor" => binding.actor,
137
- "role" => binding.role,
138
- "runtime" => binding.runtime,
139
- "base_head" => binding.base_head,
140
- "candidate_head" => candidate_head,
141
- "evidence_git_ref" => binding.evidence_git_ref,
142
- "journal_commit" => journal_commit,
143
- "recovery_mode" => recovery_mode,
144
- "accepted_receipt_digests" => accepted_receipts.map { |receipt| receipt["digest"] },
145
- "unresolved_effects" => unresolved_effects.map { |effect| effect["operation"] },
146
- "created_at" => binding.created_at.iso8601,
147
- "updated_at" => updated_at.iso8601
148
- }
149
- end
150
-
151
- # Rebuild an attempt from serialized data.
152
- #
153
- # @param data [Hash] Serialized attempt (string keys)
154
- # @return [Attempt] Rebuilt attempt
155
- def self.from_h(data)
156
- new(
157
- binding: AttemptBinding.from_h(data["binding"]),
158
- state: data["state"],
159
- candidate_head: data["candidate_head"],
160
- journal_commit: data["journal_commit"],
161
- accepted_receipts: data["accepted_receipts"] || [],
162
- effects: data["effects"] || [],
163
- events: data["events"] || [],
164
- updated_at: parse_time(data["updated_at"])
165
- )
166
- end
167
-
168
- def self.parse_time(value)
169
- return nil if value.nil?
170
- return value if value.is_a?(Time)
171
- require "time"
172
- Time.parse(value)
173
- end
174
- private_class_method :parse_time
175
- end
176
- end
177
- end
178
- end
@@ -1,116 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Ace
4
- module Assign
5
- module Models
6
- # Immutable start-time facts for an assignment attempt.
7
- #
8
- # A binding freezes exactly what an attempt owns: the immutable attempt
9
- # ID, the assignment and step/subtree scope, the project, the
10
- # authenticated actor/role and runtime identity resolved at the
11
- # execution boundary, the source task ID, the base head captured once at
12
- # start, and the configured evidence ref. Bindings are never mutated;
13
- # changes require a new attempt.
14
- class AttemptBinding
15
- attr_reader :attempt_id, :assignment_id, :scope, :project_id, :task_id,
16
- :actor, :role, :runtime, :base_head, :evidence_git_ref, :created_at
17
-
18
- # @param attempt_id [String] Immutable attempt ID (ace-b36ts)
19
- # @param assignment_id [String] Owning assignment ID
20
- # @param scope [String] Canonical step/subtree scope
21
- # @param project_id [String] Project the attempt executes in
22
- # @param actor [String] Authenticated actor from the execution boundary
23
- # @param role [String] Trusted role (coordinator, service, worker)
24
- # @param runtime [String] Runtime identity of the executing boundary
25
- # @param base_head [String] Git HEAD captured exactly once at start
26
- # @param task_id [String, nil] Source task ID (nil for taskless assignments)
27
- # @param evidence_git_ref [String, nil] Evidence ref for managed attempts
28
- # @param created_at [Time] Binding creation time
29
- def initialize(attempt_id:, assignment_id:, scope:, project_id:, actor:, role:, runtime:, base_head:,
30
- task_id: nil, evidence_git_ref: nil, created_at:)
31
- @attempt_id = attempt_id.freeze
32
- @assignment_id = assignment_id.freeze
33
- @scope = scope.freeze
34
- @project_id = project_id.freeze
35
- @task_id = task_id&.freeze
36
- @actor = actor.freeze
37
- @role = role.freeze
38
- @runtime = runtime.freeze
39
- @base_head = base_head.freeze
40
- @evidence_git_ref = evidence_git_ref&.freeze
41
- @created_at = created_at
42
- end
43
-
44
- # @return [Boolean] True when the attempt is bound to a source task and
45
- # eligible for managed evidence-ref delivery
46
- def managed?
47
- !task_id.nil? && !task_id.to_s.strip.empty?
48
- end
49
-
50
- # Recovery mode implied by task attachment: git-backed journaling for
51
- # task-attached attempts, disposable local state otherwise.
52
- #
53
- # @return [String] "git" or "local_only"
54
- def recovery_mode
55
- managed? ? "git" : "local_only"
56
- end
57
-
58
- # Identity facts repeated by receipts must match the binding exactly.
59
- #
60
- # @param other [AttemptBinding] Binding to compare
61
- # @return [Boolean] True if both bindings own the same attempt identity
62
- def same_identity?(other)
63
- attempt_id == other.attempt_id &&
64
- assignment_id == other.assignment_id &&
65
- scope == other.scope &&
66
- project_id == other.project_id
67
- end
68
-
69
- # Convert to a hash for canonical serialization.
70
- # @return [Hash] Binding data with string keys
71
- def to_h
72
- {
73
- "attempt_id" => attempt_id,
74
- "assignment_id" => assignment_id,
75
- "scope" => scope,
76
- "project_id" => project_id,
77
- "task_id" => task_id,
78
- "actor" => actor,
79
- "role" => role,
80
- "runtime" => runtime,
81
- "base_head" => base_head,
82
- "evidence_git_ref" => evidence_git_ref,
83
- "created_at" => created_at.iso8601
84
- }
85
- end
86
-
87
- # Rebuild a binding from serialized data.
88
- #
89
- # @param data [Hash] Serialized binding (string keys)
90
- # @return [AttemptBinding] Rebuilt binding
91
- def self.from_h(data)
92
- new(
93
- attempt_id: data["attempt_id"],
94
- assignment_id: data["assignment_id"],
95
- scope: data["scope"],
96
- project_id: data["project_id"],
97
- task_id: data["task_id"],
98
- actor: data["actor"],
99
- role: data["role"],
100
- runtime: data["runtime"],
101
- base_head: data["base_head"],
102
- evidence_git_ref: data["evidence_git_ref"],
103
- created_at: parse_time(data["created_at"])
104
- )
105
- end
106
-
107
- def self.parse_time(value)
108
- return value if value.is_a?(Time)
109
- require "time"
110
- Time.parse(value)
111
- end
112
- private_class_method :parse_time
113
- end
114
- end
115
- end
116
- end
@@ -1,86 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "../atoms/evidence_digest"
4
-
5
- module Ace
6
- module Assign
7
- module Models
8
- # Append-only accepted evidence event.
9
- #
10
- # Evidence events are the journal trail of an attempt: intent,
11
- # process start, accepted receipts, state transitions, candidate
12
- # invalidations, and reconciliations. Each event chains to the digest
13
- # of its predecessor for the attempt, so accepted history cannot be
14
- # reordered or silently rewritten. Events carry bounded, non-secret
15
- # metadata only.
16
- module EvidenceEvent
17
- TYPES = %w[
18
- intent process_start receipt_accepted transition attempt_stopped
19
- candidate_invalidated reconciliation
20
- service_claim service_transition service_challenge service_no_effect_challenge service_publication_challenge service_publication_issuing recovery_observation inbox_binding delivery
21
- authority_mutation evidence_import proposal_state result_submitted prompt_issued prompt_outcome prompt_completion_observed input_inhibited
22
- scope_provisioning scope_bound scope_native_bound scope_child_bound scope_sealed scope_closed_no_writers
23
- merge_admission merge_transition
24
- ].freeze
25
-
26
- # Build an event chained to a previous digest.
27
- #
28
- # @param type [String] Event type (see TYPES)
29
- # @param attempt_id [String] Owning attempt ID
30
- # @param payload [Hash] Bounded, non-secret event metadata
31
- # @param previous_digest [String, nil] Digest of the previous event (chain)
32
- # @param recorded_at [Time] Event creation time
33
- # @return [Hash] Serializable event with type, payload, digests, timestamp
34
- # @raise [ArgumentError] for unknown event types
35
- def self.build(type:, attempt_id:, payload:, previous_digest: nil, recorded_at: Time.now.utc)
36
- raise ArgumentError, "Unknown evidence event type: #{type}" unless TYPES.include?(type.to_s)
37
-
38
- recorded_at = recorded_at.utc
39
- body = {
40
- "type" => type.to_s,
41
- "attempt_id" => attempt_id,
42
- "payload" => payload,
43
- "previous_digest" => previous_digest,
44
- "recorded_at" => recorded_at.iso8601
45
- }
46
- body.merge("digest" => Atoms::EvidenceDigest.digest(body))
47
- end
48
-
49
- # Validate that a stored event is intact: its digest must match a
50
- # recomputation over its own body.
51
- #
52
- # @param event [Hash] Stored event
53
- # @return [Boolean] True if the event digest verifies
54
- def self.valid?(event)
55
- stored = event["digest"]
56
- return false if stored.nil?
57
-
58
- body = {
59
- "type" => event["type"],
60
- "attempt_id" => event["attempt_id"],
61
- "payload" => event["payload"],
62
- "previous_digest" => event["previous_digest"],
63
- "recorded_at" => event["recorded_at"]
64
- }
65
- Atoms::EvidenceDigest.digest(body) == stored
66
- end
67
-
68
- # Validate a whole chain: every event verifies and links to its
69
- # predecessor.
70
- #
71
- # @param events [Array<Hash>] Ordered event list
72
- # @return [Boolean] True if the chain is intact
73
- def self.chain_valid?(events)
74
- previous = nil
75
- events.all? do |event|
76
- return false unless valid?(event)
77
- return false unless event["previous_digest"] == previous
78
-
79
- previous = event["digest"]
80
- true
81
- end
82
- end
83
- end
84
- end
85
- end
86
- end
@@ -1,139 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "../atoms/evidence_digest"
4
-
5
- module Ace
6
- module Assign
7
- module Models
8
- # Structured, non-secret execution receipt.
9
- #
10
- # A receipt is the only accepted proof that an attempted operation
11
- # completed: it names the attributed producer, the attempted operation,
12
- # the actual tested/reviewed head, the scope, a verdict, the required
13
- # artifacts with SHA-256 digests, and executed checks. Receipts never
14
- # carry credentials, terminal bytes, or receipt-file payloads; journal
15
- # entries keep metadata and digests only.
16
- class ExecutionReceipt
17
- VERDICTS = %w[succeeded failed].freeze
18
-
19
- # Field names rejected outright in receipt payloads: accepted evidence
20
- # is structured metadata, never raw process output or credentials.
21
- FORBIDDEN_FIELDS = %w[
22
- stdout stderr terminal_output log logs credentials secret secrets
23
- token password api_key private_key env environment
24
- ].freeze
25
-
26
- attr_reader :attempt_id, :assignment_id, :project_id, :scope, :operation,
27
- :producer, :head, :verdict, :artifacts, :checks, :review, :campaign, :recorded_at, :digest
28
-
29
- # @param attempt_id [String] Attempt the receipt belongs to
30
- # @param assignment_id [String] Owning assignment ID
31
- # @param project_id [String] Project the operation ran in
32
- # @param scope [String] Canonical step/subtree scope
33
- # @param operation [String] Attempted operation name
34
- # @param producer [Hash] Attributed producer (actor, role, runtime)
35
- # @param head [String] Actual tested/reviewed head
36
- # @param verdict [String] "succeeded" or "failed"
37
- # @param artifacts [Array<Hash>] Artifacts with path + sha256
38
- # @param checks [Array<Hash>] Executed checks with name + verdict
39
- # @param review [Hash, nil] Reviewer verdict for review receipts
40
- # @param recorded_at [Time] Receipt creation time
41
- # @param digest [String, nil] Canonical digest (computed when nil)
42
- def initialize(attempt_id:, assignment_id:, project_id:, scope:, operation:, producer:, head:, verdict:,
43
- artifacts: [], checks: [], review: nil, campaign: nil, recorded_at:, digest: nil)
44
- @attempt_id = attempt_id
45
- @assignment_id = assignment_id
46
- @project_id = project_id
47
- @scope = scope
48
- @operation = operation
49
- @producer = producer
50
- @head = head
51
- @verdict = verdict
52
- @artifacts = artifacts
53
- @checks = checks
54
- @review = review
55
- @campaign = campaign
56
- @recorded_at = recorded_at
57
- @digest = digest || Atoms::EvidenceDigest.digest(digest_payload)
58
- end
59
-
60
- # Canonical payload covered by the receipt digest (excludes the digest
61
- # itself and the record timestamp, which is assigned at acceptance).
62
- #
63
- # @return [Hash] Digest payload
64
- def digest_payload
65
- payload = {
66
- "attempt_id" => attempt_id,
67
- "assignment_id" => assignment_id,
68
- "project_id" => project_id,
69
- "scope" => scope,
70
- "operation" => operation,
71
- "producer" => producer,
72
- "head" => head,
73
- "verdict" => verdict,
74
- "artifacts" => artifacts,
75
- "checks" => checks,
76
- "review" => review
77
- }
78
- payload["campaign"] = campaign if campaign
79
- payload
80
- end
81
-
82
- # Convert to a hash for serialization.
83
- # @return [Hash] Receipt data with string keys
84
- def to_h
85
- digest_payload.merge(
86
- "recorded_at" => recorded_at.iso8601,
87
- "digest" => digest
88
- )
89
- end
90
-
91
- # Rebuild a receipt from serialized or submitted data.
92
- #
93
- # @param data [Hash] Receipt data (string keys); missing digest is computed
94
- # @return [ExecutionReceipt] Receipt instance
95
- def self.from_h(data)
96
- new(
97
- attempt_id: data["attempt_id"],
98
- assignment_id: data["assignment_id"],
99
- project_id: data["project_id"],
100
- scope: data["scope"],
101
- operation: data["operation"],
102
- producer: data["producer"] || {},
103
- head: data["head"],
104
- verdict: data["verdict"],
105
- artifacts: data["artifacts"] || [],
106
- checks: data["checks"] || [],
107
- review: data["review"],
108
- campaign: data["campaign"],
109
- recorded_at: parse_time(data["recorded_at"]) || Time.now.utc,
110
- digest: data["digest"]
111
- )
112
- end
113
-
114
- # Reject payloads that carry forbidden fields anywhere in the tree.
115
- #
116
- # @param data [Hash] Raw submitted receipt data
117
- # @return [Boolean] True if a forbidden field is present
118
- def self.forbidden_field?(data)
119
- case data
120
- when Hash
121
- data.any? { |key, value| FORBIDDEN_FIELDS.include?(key.to_s) || forbidden_field?(value) }
122
- when Array
123
- data.any? { |item| forbidden_field?(item) }
124
- else
125
- false
126
- end
127
- end
128
-
129
- def self.parse_time(value)
130
- return nil if value.nil?
131
- return value if value.is_a?(Time)
132
- require "time"
133
- Time.parse(value)
134
- end
135
- private_class_method :parse_time
136
- end
137
- end
138
- end
139
- end