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.
- checksums.yaml +4 -4
- data/.ace-defaults/assign/catalog/steps/auto-merge.step.yml +11 -0
- data/.ace-defaults/assign/catalog/steps/create-pr.step.yml +2 -2
- data/.ace-defaults/assign/catalog/steps/mark-pr-ready.step.yml +1 -1
- data/.ace-defaults/assign/catalog/steps/release-minor.step.yml +4 -2
- data/.ace-defaults/assign/catalog/steps/release.step.yml +4 -2
- data/.ace-defaults/assign/catalog/steps/update-pr-desc.step.yml +1 -1
- data/.ace-defaults/assign/presets/fix-bug.yml +15 -3
- data/.ace-defaults/assign/presets/work-on-task-ace-development.yml +16 -4
- data/.ace-defaults/assign/presets/work-on-task-auto-merge.yml +15 -3
- data/.ace-defaults/assign/presets/work-on-task.yml +16 -4
- data/CHANGELOG.md +62 -0
- data/docs/demo/canonical-skill-source.tape.yml +4 -4
- data/docs/protected-authority.md +282 -0
- data/docs/usage.md +205 -3
- data/exe/ace-scope-ready +55 -0
- data/handbook/workflow-instructions/assign/drive.wf.md +28 -3
- data/handbook/workflow-instructions/assign/prepare.wf.md +2 -2
- data/lib/ace/assign/atoms/attempt_state_machine.rb +8 -0
- data/lib/ace/assign/atoms/delivery_parameters.rb +19 -0
- data/lib/ace/assign/authority/assignment_inventory.rb +290 -0
- data/lib/ace/assign/authority/campaign_consumer_policy.rb +100 -0
- data/lib/ace/assign/authority/campaign_execution.rb +119 -0
- data/lib/ace/assign/authority/campaign_round_transfer.rb +82 -0
- data/lib/ace/assign/authority/candidate_transfer.rb +327 -0
- data/lib/ace/assign/authority/cleanup_result_protection.rb +53 -0
- data/lib/ace/assign/authority/client.rb +257 -0
- data/lib/ace/assign/authority/deployment.rb +627 -0
- data/lib/ace/assign/authority/deployment_history.rb +146 -0
- data/lib/ace/assign/authority/endcap.rb +455 -0
- data/lib/ace/assign/authority/endcap_attempts.rb +274 -0
- data/lib/ace/assign/authority/endcap_campaign_results.rb +232 -0
- data/lib/ace/assign/authority/endcap_campaign_rounds.rb +266 -0
- data/lib/ace/assign/authority/endcap_contexts.rb +23 -0
- data/lib/ace/assign/authority/endcap_inboxes.rb +37 -0
- data/lib/ace/assign/authority/endcap_prepared_work.rb +80 -0
- data/lib/ace/assign/authority/endcap_publication.rb +207 -0
- data/lib/ace/assign/authority/endcap_results.rb +398 -0
- data/lib/ace/assign/authority/endcap_review_requests.rb +183 -0
- data/lib/ace/assign/authority/endcap_reviews.rb +130 -0
- data/lib/ace/assign/authority/endcap_service_settlement.rb +152 -0
- data/lib/ace/assign/authority/endcap_services.rb +598 -0
- data/lib/ace/assign/authority/evidence_transfer.rb +67 -0
- data/lib/ace/assign/authority/execution_scope_observation.rb +903 -0
- data/lib/ace/assign/authority/inbox_context_original.rb +113 -0
- data/lib/ace/assign/authority/launch_attempt_consumers.rb +51 -0
- data/lib/ace/assign/authority/launch_campaign_registration.rb +214 -0
- data/lib/ace/assign/authority/launch_control_channel.rb +313 -0
- data/lib/ace/assign/authority/launch_control_exclusions.rb +141 -0
- data/lib/ace/assign/authority/launch_driver.rb +393 -0
- data/lib/ace/assign/authority/launch_input_inhibition.rb +132 -0
- data/lib/ace/assign/authority/launch_lifecycle.rb +997 -0
- data/lib/ace/assign/authority/launch_prepared_work.rb +122 -0
- data/lib/ace/assign/authority/launch_review.rb +91 -0
- data/lib/ace/assign/authority/launch_scope_abort.rb +81 -0
- data/lib/ace/assign/authority/launch_scope_admission.rb +215 -0
- data/lib/ace/assign/authority/launch_scope_close.rb +146 -0
- data/lib/ace/assign/authority/launch_scope_parent.rb +44 -0
- data/lib/ace/assign/authority/launch_scope_release.rb +859 -0
- data/lib/ace/assign/authority/launch_steering.rb +353 -0
- data/lib/ace/assign/authority/launch_stop.rb +254 -0
- data/lib/ace/assign/authority/maintenance_workspace_retirement.rb +229 -0
- data/lib/ace/assign/authority/original_inbox_selection.rb +41 -0
- data/lib/ace/assign/authority/posix_acl.rb +59 -0
- data/lib/ace/assign/authority/prepared_input.rb +130 -0
- data/lib/ace/assign/authority/prepared_queue.rb +185 -0
- data/lib/ace/assign/authority/prepared_task_context.rb +116 -0
- data/lib/ace/assign/authority/prepared_work.rb +304 -0
- data/lib/ace/assign/authority/prepared_worker.rb +69 -0
- data/lib/ace/assign/authority/private_directory.rb +33 -0
- data/lib/ace/assign/authority/protected_assignment_context.rb +226 -0
- data/lib/ace/assign/authority/readiness_hook.rb +120 -0
- data/lib/ace/assign/authority/receipt_transfer.rb +48 -0
- data/lib/ace/assign/authority/router.rb +76 -0
- data/lib/ace/assign/authority/server.rb +300 -0
- data/lib/ace/assign/authority/service_cleanup_dispatch.rb +88 -0
- data/lib/ace/assign/authority/service_cleanup_evidence.rb +180 -0
- data/lib/ace/assign/authority/service_delivery_evidence.rb +101 -0
- data/lib/ace/assign/authority/service_evidence.rb +286 -0
- data/lib/ace/assign/authority/service_publication_evidence.rb +110 -0
- data/lib/ace/assign/authority/task_context_entry.rb +32 -0
- data/lib/ace/assign/authority/transfer_codec.rb +187 -0
- data/lib/ace/assign/cli/commands/assignment_target.rb +15 -2
- data/lib/ace/assign/cli/commands/attempt/base.rb +49 -0
- data/lib/ace/assign/cli/commands/attempt/finish.rb +20 -4
- data/lib/ace/assign/cli/commands/attempt/reconcile.rb +11 -0
- data/lib/ace/assign/cli/commands/authority/inbox_context_principal.rb +25 -0
- data/lib/ace/assign/cli/commands/authority/inbox_context_selection.rb +28 -0
- data/lib/ace/assign/cli/commands/authority/launch.rb +97 -0
- data/lib/ace/assign/cli/commands/authority/serve.rb +31 -0
- data/lib/ace/assign/cli/commands/authority/status.rb +26 -0
- data/lib/ace/assign/cli/commands/authority/task_context.rb +31 -0
- data/lib/ace/assign/cli/commands/authority/task_context_principal.rb +25 -0
- data/lib/ace/assign/cli/commands/authority/task_context_selection.rb +30 -0
- data/lib/ace/assign/cli/commands/authority/terminate.rb +29 -0
- data/lib/ace/assign/cli/commands/authority/worker.rb +23 -0
- data/lib/ace/assign/cli/commands/campaign_export_result.rb +55 -0
- data/lib/ace/assign/cli/commands/campaign_record_round.rb +57 -0
- data/lib/ace/assign/cli/commands/create.rb +11 -1
- data/lib/ace/assign/cli/commands/delivery.rb +107 -0
- data/lib/ace/assign/cli/commands/fail.rb +6 -0
- data/lib/ace/assign/cli/commands/finish.rb +23 -2
- data/lib/ace/assign/cli/commands/fork_session.rb +6 -2
- data/lib/ace/assign/cli/commands/inbox_bind.rb +34 -0
- data/lib/ace/assign/cli/commands/protected_submission.rb +61 -0
- data/lib/ace/assign/cli/commands/resume.rb +38 -0
- data/lib/ace/assign/cli/commands/select.rb +2 -0
- data/lib/ace/assign/cli/commands/start.rb +2 -0
- data/lib/ace/assign/cli/commands/status.rb +4 -2
- data/lib/ace/assign/cli/commands/step.rb +3 -1
- data/lib/ace/assign/cli/commands/submit_candidate.rb +34 -0
- data/lib/ace/assign/cli/commands/submit_result.rb +61 -0
- data/lib/ace/assign/cli.rb +56 -4
- data/lib/ace/assign/errors.rb +139 -0
- data/lib/ace/assign/models/assignment.rb +26 -4
- data/lib/ace/assign/models/evidence_event.rb +4 -2
- data/lib/ace/assign/molecules/assignment_manager.rb +12 -2
- data/lib/ace/assign/molecules/attempt_reconciler.rb +51 -12
- data/lib/ace/assign/molecules/canonical_attempt_state.rb +151 -0
- data/lib/ace/assign/molecules/canonical_evidence.rb +153 -0
- data/lib/ace/assign/molecules/canonical_read_snapshot.rb +374 -0
- data/lib/ace/assign/molecules/evidence_journal.rb +922 -91
- data/lib/ace/assign/molecules/execution_identity_resolver.rb +49 -9
- data/lib/ace/assign/molecules/execution_scope_lineage.rb +345 -0
- data/lib/ace/assign/molecules/fork_session_launcher.rb +16 -6
- data/lib/ace/assign/molecules/journal_input_inhibition.rb +78 -0
- data/lib/ace/assign/molecules/journal_mutation.rb +271 -0
- data/lib/ace/assign/molecules/journal_prompt_mutation.rb +241 -0
- data/lib/ace/assign/molecules/lifecycle_exclusion.rb +182 -26
- data/lib/ace/assign/molecules/original_launch_binding.rb +37 -0
- data/lib/ace/assign/molecules/proposal_journal.rb +151 -0
- data/lib/ace/assign/molecules/protected_control_exclusion.rb +303 -0
- data/lib/ace/assign/molecules/protected_workspace_exclusion.rb +330 -0
- data/lib/ace/assign/molecules/protected_workspace_provisioner.rb +164 -0
- data/lib/ace/assign/molecules/queue_scanner.rb +26 -11
- data/lib/ace/assign/molecules/receipt_verifier.rb +68 -32
- data/lib/ace/assign/molecules/step_writer.rb +27 -5
- data/lib/ace/assign/molecules/terminal_scope_receipt.rb +88 -0
- data/lib/ace/assign/organisms/assignment_executor.rb +10 -4
- data/lib/ace/assign/organisms/attempt_coordinator.rb +302 -13
- data/lib/ace/assign/organisms/delivery_coordinator.rb +342 -0
- data/lib/ace/assign/organisms/prepared_work_builder.rb +194 -0
- data/lib/ace/assign/organisms/protected_delivery_coordinator.rb +100 -0
- data/lib/ace/assign/organisms/task_assignment_creator.rb +11 -4
- data/lib/ace/assign/version.rb +1 -1
- data/lib/ace/assign.rb +4 -102
- metadata +186 -8
|
@@ -0,0 +1,34 @@
|
|
|
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
|
|
@@ -0,0 +1,61 @@
|
|
|
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
|
data/lib/ace/assign/cli.rb
CHANGED
|
@@ -44,6 +44,7 @@ require_relative "molecules/runtime_control_surface_runner"
|
|
|
44
44
|
require_relative "molecules/preset_inferrer"
|
|
45
45
|
require_relative "molecules/evidence_calculator"
|
|
46
46
|
require_relative "molecules/evidence_journal"
|
|
47
|
+
require_relative "molecules/canonical_evidence"
|
|
47
48
|
require_relative "molecules/execution_identity_resolver"
|
|
48
49
|
require_relative "molecules/receipt_verifier"
|
|
49
50
|
require_relative "molecules/attempt_reconciler"
|
|
@@ -53,9 +54,16 @@ require_relative "organisms/assignment_executor"
|
|
|
53
54
|
require_relative "organisms/attempt_coordinator"
|
|
54
55
|
|
|
55
56
|
# Commands
|
|
56
|
-
require_relative "cli/commands/
|
|
57
|
+
require_relative "cli/commands/delivery"
|
|
57
58
|
require_relative "cli/commands/assignment_target"
|
|
59
|
+
require_relative "cli/commands/create"
|
|
58
60
|
require_relative "cli/commands/status"
|
|
61
|
+
require_relative "cli/commands/resume"
|
|
62
|
+
require_relative "cli/commands/inbox_bind"
|
|
63
|
+
require_relative "cli/commands/submit_candidate"
|
|
64
|
+
require_relative "cli/commands/submit_result"
|
|
65
|
+
require_relative "cli/commands/campaign_record_round"
|
|
66
|
+
require_relative "cli/commands/campaign_export_result"
|
|
59
67
|
require_relative "cli/commands/step"
|
|
60
68
|
require_relative "cli/commands/start"
|
|
61
69
|
require_relative "cli/commands/finish"
|
|
@@ -66,6 +74,16 @@ require_relative "cli/commands/list"
|
|
|
66
74
|
require_relative "cli/commands/select"
|
|
67
75
|
require_relative "cli/commands/fork_run"
|
|
68
76
|
require_relative "cli/commands/fork_session"
|
|
77
|
+
require_relative "cli/commands/authority/serve"
|
|
78
|
+
require_relative "cli/commands/authority/launch"
|
|
79
|
+
require_relative "cli/commands/authority/status"
|
|
80
|
+
require_relative "cli/commands/authority/terminate"
|
|
81
|
+
require_relative "cli/commands/authority/worker"
|
|
82
|
+
require_relative "cli/commands/authority/task_context"
|
|
83
|
+
require_relative "cli/commands/authority/task_context_principal"
|
|
84
|
+
require_relative "cli/commands/authority/task_context_selection"
|
|
85
|
+
require_relative "cli/commands/authority/inbox_context_principal"
|
|
86
|
+
require_relative "cli/commands/authority/inbox_context_selection"
|
|
69
87
|
require_relative "cli/commands/attempt/base"
|
|
70
88
|
require_relative "cli/commands/attempt/start"
|
|
71
89
|
require_relative "cli/commands/attempt/status"
|
|
@@ -84,6 +102,21 @@ module Ace
|
|
|
84
102
|
# Application commands with descriptions (for help output)
|
|
85
103
|
REGISTERED_COMMANDS = [
|
|
86
104
|
["create", "Create assignment from preset or YAML"],
|
|
105
|
+
["authority serve", "Serve the installed protected assignment owner"],
|
|
106
|
+
["authority launch", "Launch one mapped native gated worker"],
|
|
107
|
+
["authority status", "Inspect protected launch ownership"],
|
|
108
|
+
["authority terminate", "Terminate the original native gated child"],
|
|
109
|
+
["authority worker", "Consume original authenticated prepared work"],
|
|
110
|
+
["authority task-context", "Read captured original task context through the fixed entry"],
|
|
111
|
+
["authority task-context-principal", "Classify the actual caller through retained protected owners"],
|
|
112
|
+
["authority task-context-selection", "Read the authenticated original context entry selection"],
|
|
113
|
+
["authority inbox-context-principal", "Classify the actual caller through every retained protected owner"],
|
|
114
|
+
["authority inbox-context-selection", "Read the fixed installed inbox context selection"],
|
|
115
|
+
["delivery", "Execute or reconcile attempt-bound forge delivery"],
|
|
116
|
+
["submit-candidate", "Submit original protected worker candidate bytes"],
|
|
117
|
+
["submit-result", "Submit original protected worker receipt and artifact bytes"],
|
|
118
|
+
["campaign-export-result", "Download the original parent campaign's verified accepted result"],
|
|
119
|
+
["campaign-record-round", "Consume canonical settled children into the original campaign store"],
|
|
87
120
|
["status", "Show assignment status"],
|
|
88
121
|
["step", "Show step instructions"],
|
|
89
122
|
["start", "Start next workable step"],
|
|
@@ -97,8 +130,9 @@ module Ace
|
|
|
97
130
|
["attempt start", "Start a scoped attempt for an assignment step"],
|
|
98
131
|
["attempt status", "Show attempt status for an assignment"],
|
|
99
132
|
["attempt evidence", "Read accepted current-head check evidence"],
|
|
100
|
-
["attempt finish", "Finish
|
|
101
|
-
["attempt reconcile", "
|
|
133
|
+
["attempt finish", "Finish a protected canonical result or an ordinary local receipt"],
|
|
134
|
+
["attempt reconcile", "Recover an exact protected attempt or reconcile an ordinary attempt"],
|
|
135
|
+
["inbox-bind", "Bind an original protected Inbox registration"],
|
|
102
136
|
].freeze
|
|
103
137
|
|
|
104
138
|
HELP_EXAMPLES = [
|
|
@@ -110,7 +144,8 @@ module Ace
|
|
|
110
144
|
"cat report.md | ace-assign finish # Complete step via stdin",
|
|
111
145
|
"ace-assign fork-run 010.01 # Run subtree in subprocess",
|
|
112
146
|
"ace-assign attempt start --assignment ID --step 010 --project ID",
|
|
113
|
-
"ace-assign attempt finish --attempt ID --receipt receipt.json"
|
|
147
|
+
"ace-assign attempt finish --attempt ID --receipt receipt.json",
|
|
148
|
+
"ace-assign resume --assignment ID --dry-run"
|
|
114
149
|
].freeze
|
|
115
150
|
|
|
116
151
|
# Captured command exit code from last run
|
|
@@ -146,7 +181,18 @@ module Ace
|
|
|
146
181
|
end
|
|
147
182
|
|
|
148
183
|
# Register commands (wrapped to capture exit codes)
|
|
184
|
+
register "authority serve", wrap_command(Commands::Authority::Serve)
|
|
185
|
+
register "authority launch", wrap_command(Commands::Authority::Launch)
|
|
186
|
+
register "authority status", wrap_command(Commands::Authority::Status)
|
|
187
|
+
register "authority terminate", wrap_command(Commands::Authority::Terminate)
|
|
188
|
+
register "authority worker", wrap_command(Commands::Authority::Worker)
|
|
189
|
+
register "authority task-context", wrap_command(Commands::Authority::TaskContext)
|
|
190
|
+
register "authority task-context-principal", wrap_command(Commands::Authority::TaskContextPrincipal)
|
|
191
|
+
register "authority task-context-selection", wrap_command(Commands::Authority::TaskContextSelection)
|
|
192
|
+
register "authority inbox-context-principal", wrap_command(Commands::Authority::InboxContextPrincipal)
|
|
193
|
+
register "authority inbox-context-selection", wrap_command(Commands::Authority::InboxContextSelection)
|
|
149
194
|
register "create", wrap_command(Commands::Create)
|
|
195
|
+
register "delivery", wrap_command(Commands::Delivery)
|
|
150
196
|
register "status", wrap_command(Commands::Status)
|
|
151
197
|
register "step", wrap_command(Commands::Step)
|
|
152
198
|
register "start", wrap_command(Commands::Start)
|
|
@@ -163,6 +209,12 @@ module Ace
|
|
|
163
209
|
register "attempt evidence", wrap_command(Commands::Attempt::Evidence)
|
|
164
210
|
register "attempt finish", wrap_command(Commands::Attempt::Finish)
|
|
165
211
|
register "attempt reconcile", wrap_command(Commands::Attempt::Reconcile)
|
|
212
|
+
register "resume", wrap_command(Commands::Resume)
|
|
213
|
+
register "inbox-bind", wrap_command(Commands::InboxBind)
|
|
214
|
+
register "submit-candidate", wrap_command(Commands::SubmitCandidate)
|
|
215
|
+
register "submit-result", wrap_command(Commands::SubmitResult)
|
|
216
|
+
register "campaign-export-result", wrap_command(Commands::CampaignExportResult)
|
|
217
|
+
register "campaign-record-round", wrap_command(Commands::CampaignRecordRound)
|
|
166
218
|
|
|
167
219
|
# Register version command
|
|
168
220
|
version_cmd = Ace::Support::Cli::VersionCommand.build(
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require "ace/support/cli/error"
|
|
3
|
+
|
|
4
|
+
module Ace
|
|
5
|
+
module Assign
|
|
6
|
+
# Base error class for all ace-assign exceptions.
|
|
7
|
+
# Inherits from Ace::Support::Cli::Error to support exception-based
|
|
8
|
+
# exit code pattern (per ADR-023).
|
|
9
|
+
#
|
|
10
|
+
# Subclasses should call super with appropriate exit_code.
|
|
11
|
+
class Error < Ace::Support::Cli::Error
|
|
12
|
+
def initialize(message, exit_code: 1)
|
|
13
|
+
super
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Assignment-related errors
|
|
18
|
+
module AssignmentErrors
|
|
19
|
+
class NotFound < Error
|
|
20
|
+
def initialize(message = "Assignment not found")
|
|
21
|
+
super(message, exit_code: 2)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
class NoActive < Error
|
|
26
|
+
def initialize(message = "No active assignment")
|
|
27
|
+
super(message, exit_code: 2)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Config-related errors
|
|
33
|
+
module ConfigErrors
|
|
34
|
+
class NotFound < Error
|
|
35
|
+
def initialize(message = "Configuration not found")
|
|
36
|
+
super(message, exit_code: 3)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Step-related errors
|
|
42
|
+
module StepErrors
|
|
43
|
+
class NotFound < Error
|
|
44
|
+
def initialize(message = "Step not found")
|
|
45
|
+
super(message, exit_code: 4)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
class InvalidState < Error; end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Attempt/evidence-related errors (exit code 5)
|
|
53
|
+
module AttemptErrors
|
|
54
|
+
class InvalidTransition < Error
|
|
55
|
+
def initialize(message = "Illegal attempt state transition")
|
|
56
|
+
super(message, exit_code: 5)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
class InvalidScope < Error
|
|
61
|
+
def initialize(message = "Invalid attempt scope")
|
|
62
|
+
super(message, exit_code: 5)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Duplicate start with a conflicting binding for an owned subtree
|
|
67
|
+
class Conflict < Error
|
|
68
|
+
def initialize(message = "Conflicting active attempt for scope")
|
|
69
|
+
super(message, exit_code: 5)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
class NotFound < Error
|
|
74
|
+
def initialize(message = "Attempt not found")
|
|
75
|
+
super(message, exit_code: 5)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Untrusted or unknown execution identity; authority fails closed
|
|
80
|
+
class UnauthorizedIdentity < Error
|
|
81
|
+
def initialize(message = "Unknown or unauthorized execution identity")
|
|
82
|
+
super(message, exit_code: 5)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Receipt failed verification (binding, head, digest, artifacts, review)
|
|
87
|
+
class ReceiptRejected < Error
|
|
88
|
+
def initialize(message = "Receipt rejected")
|
|
89
|
+
super(message, exit_code: 5)
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Byte framing failed before any business receipt could be admitted.
|
|
94
|
+
class MalformedTransfer < ReceiptRejected; end
|
|
95
|
+
|
|
96
|
+
# Recovery settled a prior effect, but did not perform this request.
|
|
97
|
+
class CurrentEffectRequired < ReceiptRejected
|
|
98
|
+
def initialize(operation:, recovered_operation:)
|
|
99
|
+
super("Recovered #{recovered_operation}; requested #{operation} still needs execution")
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Attempt state forbids the requested effect (terminal, uncertain, blocked)
|
|
104
|
+
class InvalidState < Error
|
|
105
|
+
def initialize(message = "Attempt state forbids the requested operation")
|
|
106
|
+
super(message, exit_code: 5)
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Evidence storage is missing or unwritable; external effects are blocked
|
|
111
|
+
class EvidenceUnavailable < Error
|
|
112
|
+
def initialize(message = "Evidence storage unavailable")
|
|
113
|
+
super(message, exit_code: 5)
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Complete maintenance exclusions are busy or their admission deadline expired.
|
|
118
|
+
class MaintenanceBusy < EvidenceUnavailable
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# One canonical inventory row cannot fit the fixed protocol frame.
|
|
122
|
+
class BoundedResultUnavailable < EvidenceUnavailable
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Exhaustively authenticated service inventory remains unsettled.
|
|
126
|
+
class ServiceSettlementPending < EvidenceUnavailable
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Exhaustively authenticated current Inbox claims remain queued.
|
|
130
|
+
class InboxSettlementPending < EvidenceUnavailable
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Positively admitted original Inbox work remains pending, not corrupt.
|
|
134
|
+
class InboxContextPending < EvidenceUnavailable
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
end
|
|
139
|
+
end
|
|
@@ -20,7 +20,7 @@ module Ace
|
|
|
20
20
|
# but tests would need to be updated to provide all required fields.
|
|
21
21
|
class Assignment
|
|
22
22
|
attr_reader :id, :name, :description, :created_at, :updated_at, :source_config, :cache_dir, :parent,
|
|
23
|
-
:task_id, :project_id
|
|
23
|
+
:task_id, :project_id, :campaign_execution, :review_campaign
|
|
24
24
|
|
|
25
25
|
# @param id [String] Unique assignment ID (6-char compact timestamp)
|
|
26
26
|
# @param name [String] Human-readable assignment name
|
|
@@ -33,7 +33,7 @@ module Ace
|
|
|
33
33
|
# @param task_id [String, nil] Source task ID attached to this assignment (nil = taskless)
|
|
34
34
|
# @param project_id [String, nil] Project ID this assignment executes in
|
|
35
35
|
def initialize(id:, name:, created_at:, source_config:, description: nil, updated_at: nil, cache_dir: nil,
|
|
36
|
-
parent: nil, task_id: nil, project_id: nil)
|
|
36
|
+
parent: nil, task_id: nil, project_id: nil, campaign_execution: nil, review_campaign: nil)
|
|
37
37
|
@id = id.freeze
|
|
38
38
|
@name = name.freeze
|
|
39
39
|
@description = description&.freeze
|
|
@@ -44,6 +44,24 @@ module Ace
|
|
|
44
44
|
@parent = parent&.freeze
|
|
45
45
|
@task_id = task_id&.freeze
|
|
46
46
|
@project_id = project_id&.freeze
|
|
47
|
+
if !campaign_execution.nil? && !review_campaign.nil?
|
|
48
|
+
raise ArgumentError, "assignment cannot own both parent campaign and child execution"
|
|
49
|
+
end
|
|
50
|
+
@campaign_execution = immutable_campaign_execution(campaign_execution) unless campaign_execution.nil?
|
|
51
|
+
@review_campaign = immutable_campaign_execution(review_campaign) unless review_campaign.nil?
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Keep registered definition bytes stable without retaining caller-owned
|
|
55
|
+
# mutable objects. Authority validates the closed linkage and provenance.
|
|
56
|
+
private def immutable_campaign_execution(value)
|
|
57
|
+
case value
|
|
58
|
+
when Hash
|
|
59
|
+
value.to_h { |key, item| [immutable_campaign_execution(key), immutable_campaign_execution(item)] }.freeze
|
|
60
|
+
when Array then value.map { |item| immutable_campaign_execution(item) }.freeze
|
|
61
|
+
when String then value.dup.freeze
|
|
62
|
+
when Integer, NilClass, TrueClass, FalseClass then value
|
|
63
|
+
else raise ArgumentError, "invalid campaign execution value"
|
|
64
|
+
end
|
|
47
65
|
end
|
|
48
66
|
|
|
49
67
|
# @return [Boolean] True when a source task is attached; taskless
|
|
@@ -65,7 +83,9 @@ module Ace
|
|
|
65
83
|
"source_config" => source_config,
|
|
66
84
|
"parent" => parent,
|
|
67
85
|
"task_id" => task_id,
|
|
68
|
-
"project_id" => project_id
|
|
86
|
+
"project_id" => project_id,
|
|
87
|
+
"campaign_execution" => campaign_execution,
|
|
88
|
+
"review_campaign" => review_campaign
|
|
69
89
|
}.compact
|
|
70
90
|
end
|
|
71
91
|
|
|
@@ -84,7 +104,9 @@ module Ace
|
|
|
84
104
|
cache_dir: cache_dir,
|
|
85
105
|
parent: data["parent"],
|
|
86
106
|
task_id: data["task_id"],
|
|
87
|
-
project_id: data["project_id"]
|
|
107
|
+
project_id: data["project_id"],
|
|
108
|
+
campaign_execution: data["campaign_execution"],
|
|
109
|
+
review_campaign: data["review_campaign"]
|
|
88
110
|
)
|
|
89
111
|
end
|
|
90
112
|
|
|
@@ -15,9 +15,11 @@ module Ace
|
|
|
15
15
|
# metadata only.
|
|
16
16
|
module EvidenceEvent
|
|
17
17
|
TYPES = %w[
|
|
18
|
-
intent process_start receipt_accepted transition
|
|
18
|
+
intent process_start receipt_accepted transition attempt_stopped
|
|
19
19
|
candidate_invalidated reconciliation
|
|
20
|
-
service_claim service_transition
|
|
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
|
|
21
23
|
].freeze
|
|
22
24
|
|
|
23
25
|
# Build an event chained to a previous digest.
|
|
@@ -13,7 +13,8 @@ module Ace
|
|
|
13
13
|
# Uses ace-b36ts for assignment ID generation.
|
|
14
14
|
class AssignmentManager
|
|
15
15
|
# @param cache_base [String] Base cache directory
|
|
16
|
-
def initialize(cache_base: nil)
|
|
16
|
+
def initialize(cache_base: nil, selected_assignment: nil)
|
|
17
|
+
@selected_assignment = selected_assignment
|
|
17
18
|
@cache_base = cache_base || Ace::Assign.cache_dir
|
|
18
19
|
end
|
|
19
20
|
|
|
@@ -85,6 +86,7 @@ module Ace
|
|
|
85
86
|
#
|
|
86
87
|
# @return [Models::Assignment, nil] Most recent assignment or nil
|
|
87
88
|
def find_active
|
|
89
|
+
return @selected_assignment if @selected_assignment
|
|
88
90
|
return nil unless File.directory?(@cache_base)
|
|
89
91
|
|
|
90
92
|
# Priority 1: use .current symlink if it exists (explicit selection)
|
|
@@ -185,6 +187,7 @@ module Ace
|
|
|
185
187
|
)
|
|
186
188
|
|
|
187
189
|
write_assignment_file(updated)
|
|
190
|
+
@selected_assignment = updated if @selected_assignment && @selected_assignment.id == updated.id
|
|
188
191
|
|
|
189
192
|
# Update .latest symlink since this assignment was just updated
|
|
190
193
|
update_latest_symlink(assignment.id)
|
|
@@ -259,7 +262,14 @@ module Ace
|
|
|
259
262
|
|
|
260
263
|
def write_assignment_file(assignment)
|
|
261
264
|
assignment_file = File.join(assignment.cache_dir, "assignment.yaml")
|
|
262
|
-
|
|
265
|
+
if @selected_assignment
|
|
266
|
+
File.open(assignment_file, File::WRONLY | File::TRUNC | File::NOFOLLOW | File::NONBLOCK) do |file|
|
|
267
|
+
raise ArgumentError, "selected assignment metadata is not regular" unless file.stat.file?
|
|
268
|
+
file.write(assignment.to_h.to_yaml); file.flush; file.fsync
|
|
269
|
+
end
|
|
270
|
+
else
|
|
271
|
+
File.write(assignment_file, assignment.to_h.to_yaml)
|
|
272
|
+
end
|
|
263
273
|
end
|
|
264
274
|
|
|
265
275
|
def load_from_file(assignment_file)
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
+
require_relative "canonical_attempt_state"
|
|
3
|
+
|
|
4
|
+
require "json"
|
|
2
5
|
|
|
3
6
|
module Ace
|
|
4
7
|
module Assign
|
|
@@ -6,7 +9,7 @@ module Ace
|
|
|
6
9
|
# Conservative classification and resolution of interrupted attempts.
|
|
7
10
|
#
|
|
8
11
|
# Classification of a `running` attempt found after a lost session:
|
|
9
|
-
# - no recorded process start -> `
|
|
12
|
+
# - no recorded process start -> `uncertain` (launch may be unbound)
|
|
10
13
|
# - recorded process still verifiably live -> keep `running`
|
|
11
14
|
# - otherwise -> `uncertain` (the operation may have completed without
|
|
12
15
|
# a durable receipt)
|
|
@@ -17,11 +20,28 @@ module Ace
|
|
|
17
20
|
# effects are never retried automatically, and an uncertain attempt
|
|
18
21
|
# without a receipt stays uncertain.
|
|
19
22
|
class AttemptReconciler
|
|
23
|
+
# The protected boundary supplies authenticated original scope facts
|
|
24
|
+
# and exact kernel identities; never a runtime discovered by pane name.
|
|
25
|
+
def self.protected_observation(kernel:, binding:, observer:, lineage:)
|
|
26
|
+
return {"liveness" => "unknown"} if lineage.sealed?
|
|
27
|
+
kernel.live!(binding.fetch("process_identity"))
|
|
28
|
+
kernel.live!(binding.fetch("native_origin").fetch("server_identity"))
|
|
29
|
+
observation = observer.observe(lineage)
|
|
30
|
+
unless observation.is_a?(Hash) && observation["populated"].is_a?(Integer) && observation["populated"].positive?
|
|
31
|
+
return {"liveness" => "unknown"}
|
|
32
|
+
end
|
|
33
|
+
{"liveness" => "live", "identity" => binding.fetch("process_identity"), "runtime_binding" => binding}
|
|
34
|
+
rescue AttemptErrors::EvidenceUnavailable, Ace::Runtime::RuntimeUnavailableError
|
|
35
|
+
{"liveness" => "unknown"}
|
|
36
|
+
end
|
|
37
|
+
|
|
20
38
|
# @param journal [EvidenceJournal, nil] Managed evidence journal
|
|
21
39
|
# @param verifier [ReceiptVerifier, nil] Receipt verifier (coordinator supplies on use)
|
|
22
|
-
def initialize(journal: nil, verifier: nil)
|
|
40
|
+
def initialize(journal: nil, verifier: nil, observer: nil, runtime_resolver: Ace::Runtime)
|
|
23
41
|
@journal = journal
|
|
24
42
|
@verifier = verifier
|
|
43
|
+
@observer = observer || Ace::Runtime::Molecules::ProcessIdentity.new
|
|
44
|
+
@runtime_resolver = runtime_resolver
|
|
25
45
|
end
|
|
26
46
|
|
|
27
47
|
# Classify a running attempt after an interruption.
|
|
@@ -29,9 +49,11 @@ module Ace
|
|
|
29
49
|
# @param attempt [Models::Attempt] Running attempt
|
|
30
50
|
# @return [Symbol] :stopped, :live, or :uncertain
|
|
31
51
|
def classify(attempt)
|
|
52
|
+
events = events_for(attempt)
|
|
53
|
+
if events.any? { |event| event["type"] == "attempt_stopped" } && CanonicalAttemptState.derive(events) == "stopped"
|
|
54
|
+
return :stopped
|
|
55
|
+
end
|
|
32
56
|
return :live if process_live?(attempt)
|
|
33
|
-
return :stopped unless process_started?(attempt)
|
|
34
|
-
|
|
35
57
|
:uncertain
|
|
36
58
|
end
|
|
37
59
|
|
|
@@ -44,15 +66,27 @@ module Ace
|
|
|
44
66
|
# @param attempt [Models::Attempt] Attempt to inspect
|
|
45
67
|
# @return [Boolean] True when the recorded process is verifiably alive
|
|
46
68
|
def process_live?(attempt)
|
|
47
|
-
|
|
48
|
-
|
|
69
|
+
observation(attempt)["liveness"] == "live"
|
|
70
|
+
end
|
|
49
71
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
72
|
+
def observation(attempt)
|
|
73
|
+
payload = start_event(attempt)&.dig("payload")
|
|
74
|
+
observation = @observer.observe(payload&.dig("process_identity"))
|
|
75
|
+
binding = payload&.dig("runtime_binding")
|
|
76
|
+
return observation unless binding && observation["liveness"] == "live"
|
|
77
|
+
|
|
78
|
+
live = @runtime_resolver.resolve(binding["runtime"]).process_binding(
|
|
79
|
+
pane: binding["pane"], caller_pid: binding.dig("process_identity", "pid"))
|
|
80
|
+
return observation if binding == live
|
|
81
|
+
|
|
82
|
+
observation.merge("liveness" => "unknown", "reason" => "native process/session binding changed")
|
|
83
|
+
rescue Ace::Runtime::Error
|
|
84
|
+
{"liveness" => "unknown", "observed_at" => Time.now.utc.iso8601,
|
|
85
|
+
"reason" => "native process/session observation unavailable"}
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def events(attempt)
|
|
89
|
+
events_for(attempt)
|
|
56
90
|
end
|
|
57
91
|
|
|
58
92
|
# Runtime recorded at the attempt's process start — the boundary any
|
|
@@ -64,6 +98,11 @@ module Ace
|
|
|
64
98
|
start_event(attempt)&.dig("payload", "runtime")
|
|
65
99
|
end
|
|
66
100
|
|
|
101
|
+
def recorded_binding(attempt)
|
|
102
|
+
binding = start_event(attempt)&.dig("payload", "runtime_binding")
|
|
103
|
+
JSON.parse(JSON.generate(binding)) if binding.is_a?(Hash)
|
|
104
|
+
end
|
|
105
|
+
|
|
67
106
|
private
|
|
68
107
|
|
|
69
108
|
def start_event(attempt)
|