ace-assign 0.71.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.
- checksums.yaml +4 -4
- data/.ace-defaults/assign/catalog/steps/auto-merge.step.yml +8 -8
- data/.ace-defaults/assign/catalog/steps/create-pr.step.yml +1 -1
- data/.ace-defaults/assign/catalog/steps/mark-pr-ready.step.yml +1 -1
- data/.ace-defaults/assign/config.yml +0 -13
- data/.ace-defaults/assign/presets/fix-bug.yml +1 -1
- data/.ace-defaults/assign/presets/work-on-task-ace-development.yml +1 -1
- data/.ace-defaults/assign/presets/work-on-task-auto-merge.yml +1 -1
- data/.ace-defaults/assign/presets/work-on-task.yml +1 -1
- data/CHANGELOG.md +3 -2119
- data/README.md +58 -31
- data/docs/exit-codes.md +36 -44
- data/docs/getting-started.md +9 -4
- data/docs/usage.md +129 -522
- data/handbook/skills/as-assign-drive/SKILL.md +1 -4
- data/handbook/workflow-instructions/assign/drive.wf.md +47 -195
- data/handbook/workflow-instructions/assign/prepare.wf.md +1 -1
- data/handbook/workflow-instructions/assign/verify-test-suite.wf.md +8 -2
- data/lib/ace/assign/cli/commands/assignment_target.rb +2 -15
- data/lib/ace/assign/cli/commands/create.rb +1 -8
- data/lib/ace/assign/cli/commands/fail.rb +0 -6
- data/lib/ace/assign/cli/commands/finish.rb +2 -23
- data/lib/ace/assign/cli/commands/fork_session.rb +0 -1
- data/lib/ace/assign/cli/commands/select.rb +0 -1
- data/lib/ace/assign/cli/commands/start.rb +0 -2
- data/lib/ace/assign/cli/commands/status.rb +1 -31
- data/lib/ace/assign/cli/commands/step.rb +1 -3
- data/lib/ace/assign/cli.rb +1 -85
- data/lib/ace/assign/errors.rb +8 -80
- data/lib/ace/assign/models/assignment.rb +5 -28
- data/lib/ace/assign/molecules/assignment_manager.rb +0 -26
- data/lib/ace/assign/molecules/fork_session_launcher.rb +4 -12
- data/lib/ace/assign/molecules/lifecycle_exclusion.rb +15 -112
- data/lib/ace/assign/organisms/assignment_executor.rb +0 -6
- data/lib/ace/assign/organisms/task_assignment_creator.rb +3 -8
- data/lib/ace/assign/version.rb +1 -1
- data/lib/ace/assign.rb +0 -6
- metadata +4 -176
- data/.ace-defaults/assign/presets/close-delivery.yml +0 -57
- data/.ace-defaults/assign/presets/deliver-release.yml +0 -60
- data/.ace-defaults/assign/presets/integrate-batch.yml +0 -72
- data/.ace-defaults/assign/presets/supervise-delivery.yml +0 -69
- data/docs/protected-authority.md +0 -282
- data/exe/ace-scope-ready +0 -55
- data/lib/ace/assign/atoms/assignment_scope.rb +0 -58
- data/lib/ace/assign/atoms/attempt_state_machine.rb +0 -84
- data/lib/ace/assign/atoms/candidate_limits.rb +0 -20
- data/lib/ace/assign/atoms/delivery_parameters.rb +0 -19
- data/lib/ace/assign/atoms/evidence_digest.rb +0 -78
- data/lib/ace/assign/atoms/fixed_merge_schemas.rb +0 -398
- data/lib/ace/assign/atoms/git_blob_batch.rb +0 -59
- data/lib/ace/assign/authority/assignment_definition.rb +0 -35
- data/lib/ace/assign/authority/assignment_inventory.rb +0 -290
- data/lib/ace/assign/authority/campaign_consumer_policy.rb +0 -100
- data/lib/ace/assign/authority/campaign_execution.rb +0 -119
- data/lib/ace/assign/authority/campaign_round_transfer.rb +0 -82
- data/lib/ace/assign/authority/candidate_blob_batch.rb +0 -55
- data/lib/ace/assign/authority/candidate_head_comparison.rb +0 -139
- data/lib/ace/assign/authority/candidate_transfer.rb +0 -357
- data/lib/ace/assign/authority/cleanup_result_protection.rb +0 -53
- data/lib/ace/assign/authority/client.rb +0 -257
- data/lib/ace/assign/authority/command_execution.rb +0 -180
- data/lib/ace/assign/authority/deployment.rb +0 -627
- data/lib/ace/assign/authority/deployment_history.rb +0 -146
- data/lib/ace/assign/authority/endcap.rb +0 -457
- data/lib/ace/assign/authority/endcap_attempts.rb +0 -274
- data/lib/ace/assign/authority/endcap_campaign_results.rb +0 -232
- data/lib/ace/assign/authority/endcap_campaign_rounds.rb +0 -266
- data/lib/ace/assign/authority/endcap_contexts.rb +0 -23
- data/lib/ace/assign/authority/endcap_inboxes.rb +0 -37
- data/lib/ace/assign/authority/endcap_prepared_work.rb +0 -80
- data/lib/ace/assign/authority/endcap_publication.rb +0 -41
- data/lib/ace/assign/authority/endcap_results.rb +0 -398
- data/lib/ace/assign/authority/endcap_review_requests.rb +0 -183
- data/lib/ace/assign/authority/endcap_reviews.rb +0 -130
- data/lib/ace/assign/authority/endcap_service_context.rb +0 -38
- data/lib/ace/assign/authority/endcap_service_settlement.rb +0 -152
- data/lib/ace/assign/authority/evidence_transfer.rb +0 -67
- data/lib/ace/assign/authority/execution_scope_observation.rb +0 -903
- data/lib/ace/assign/authority/fixed_publication_lifecycle.rb +0 -176
- data/lib/ace/assign/authority/fixed_service_lifecycle.rb +0 -594
- data/lib/ace/assign/authority/inbox_context_original.rb +0 -113
- data/lib/ace/assign/authority/launch_attempt_consumers.rb +0 -51
- data/lib/ace/assign/authority/launch_campaign_registration.rb +0 -214
- data/lib/ace/assign/authority/launch_control_channel.rb +0 -313
- data/lib/ace/assign/authority/launch_control_exclusions.rb +0 -127
- data/lib/ace/assign/authority/launch_driver.rb +0 -393
- data/lib/ace/assign/authority/launch_input_inhibition.rb +0 -132
- data/lib/ace/assign/authority/launch_lifecycle.rb +0 -997
- data/lib/ace/assign/authority/launch_prepared_work.rb +0 -122
- data/lib/ace/assign/authority/launch_review.rb +0 -91
- data/lib/ace/assign/authority/launch_scope_abort.rb +0 -81
- data/lib/ace/assign/authority/launch_scope_admission.rb +0 -215
- data/lib/ace/assign/authority/launch_scope_close.rb +0 -146
- data/lib/ace/assign/authority/launch_scope_parent.rb +0 -44
- data/lib/ace/assign/authority/launch_scope_release.rb +0 -859
- data/lib/ace/assign/authority/launch_steering.rb +0 -353
- data/lib/ace/assign/authority/launch_stop.rb +0 -254
- data/lib/ace/assign/authority/maintenance_workspace_retirement.rb +0 -229
- data/lib/ace/assign/authority/ordinary_attempt_start.rb +0 -137
- data/lib/ace/assign/authority/ordinary_client.rb +0 -70
- data/lib/ace/assign/authority/ordinary_completion.rb +0 -104
- data/lib/ace/assign/authority/ordinary_effect_settlement.rb +0 -118
- data/lib/ace/assign/authority/ordinary_lifecycle.rb +0 -227
- data/lib/ace/assign/authority/ordinary_merge_evidence.rb +0 -81
- data/lib/ace/assign/authority/ordinary_peer.rb +0 -54
- data/lib/ace/assign/authority/ordinary_progress.rb +0 -162
- data/lib/ace/assign/authority/ordinary_queue.rb +0 -115
- data/lib/ace/assign/authority/ordinary_release_artifacts.rb +0 -143
- data/lib/ace/assign/authority/ordinary_results.rb +0 -206
- data/lib/ace/assign/authority/ordinary_review_evidence.rb +0 -180
- data/lib/ace/assign/authority/ordinary_reviews.rb +0 -214
- data/lib/ace/assign/authority/ordinary_server.rb +0 -75
- data/lib/ace/assign/authority/ordinary_supervisor.rb +0 -99
- data/lib/ace/assign/authority/original_inbox_selection.rb +0 -41
- data/lib/ace/assign/authority/posix_acl.rb +0 -59
- data/lib/ace/assign/authority/prepared_input.rb +0 -130
- data/lib/ace/assign/authority/prepared_queue.rb +0 -185
- data/lib/ace/assign/authority/prepared_task_context.rb +0 -116
- data/lib/ace/assign/authority/prepared_work.rb +0 -309
- data/lib/ace/assign/authority/prepared_worker.rb +0 -69
- data/lib/ace/assign/authority/private_directory.rb +0 -33
- data/lib/ace/assign/authority/protected_assignment_context.rb +0 -226
- data/lib/ace/assign/authority/readiness_hook.rb +0 -120
- data/lib/ace/assign/authority/receipt_transfer.rb +0 -48
- data/lib/ace/assign/authority/release_artifact_manifest.rb +0 -91
- data/lib/ace/assign/authority/retained_input.rb +0 -53
- data/lib/ace/assign/authority/router.rb +0 -81
- data/lib/ace/assign/authority/server.rb +0 -131
- data/lib/ace/assign/authority/service_cleanup_dispatch.rb +0 -88
- data/lib/ace/assign/authority/service_cleanup_evidence.rb +0 -180
- data/lib/ace/assign/authority/service_delivery_evidence.rb +0 -101
- data/lib/ace/assign/authority/service_evidence.rb +0 -286
- data/lib/ace/assign/authority/service_publication_evidence.rb +0 -110
- data/lib/ace/assign/authority/socket_server.rb +0 -255
- data/lib/ace/assign/authority/task_context_entry.rb +0 -32
- data/lib/ace/assign/authority/transfer_codec.rb +0 -189
- data/lib/ace/assign/cli/commands/attempt/base.rb +0 -84
- data/lib/ace/assign/cli/commands/attempt/evidence.rb +0 -28
- data/lib/ace/assign/cli/commands/attempt/finish.rb +0 -51
- data/lib/ace/assign/cli/commands/attempt/reconcile.rb +0 -49
- data/lib/ace/assign/cli/commands/attempt/start.rb +0 -45
- data/lib/ace/assign/cli/commands/attempt/status.rb +0 -54
- data/lib/ace/assign/cli/commands/authority/inbox_context_principal.rb +0 -25
- data/lib/ace/assign/cli/commands/authority/inbox_context_selection.rb +0 -28
- data/lib/ace/assign/cli/commands/authority/launch.rb +0 -97
- data/lib/ace/assign/cli/commands/authority/serve.rb +0 -31
- data/lib/ace/assign/cli/commands/authority/status.rb +0 -26
- data/lib/ace/assign/cli/commands/authority/task_context.rb +0 -31
- data/lib/ace/assign/cli/commands/authority/task_context_principal.rb +0 -25
- data/lib/ace/assign/cli/commands/authority/task_context_selection.rb +0 -30
- data/lib/ace/assign/cli/commands/authority/terminate.rb +0 -29
- data/lib/ace/assign/cli/commands/authority/worker.rb +0 -23
- data/lib/ace/assign/cli/commands/campaign_export_result.rb +0 -55
- data/lib/ace/assign/cli/commands/campaign_record_round.rb +0 -57
- data/lib/ace/assign/cli/commands/delivery.rb +0 -107
- data/lib/ace/assign/cli/commands/inbox_bind.rb +0 -34
- data/lib/ace/assign/cli/commands/protected_submission.rb +0 -61
- data/lib/ace/assign/cli/commands/resume.rb +0 -38
- data/lib/ace/assign/cli/commands/submit_candidate.rb +0 -34
- data/lib/ace/assign/cli/commands/submit_result.rb +0 -61
- data/lib/ace/assign/models/attempt.rb +0 -178
- data/lib/ace/assign/models/attempt_binding.rb +0 -116
- data/lib/ace/assign/models/evidence_event.rb +0 -86
- data/lib/ace/assign/models/execution_receipt.rb +0 -139
- data/lib/ace/assign/molecules/attempt_reconciler.rb +0 -127
- data/lib/ace/assign/molecules/attempt_store.rb +0 -201
- data/lib/ace/assign/molecules/canonical_attempt_state.rb +0 -151
- data/lib/ace/assign/molecules/canonical_evidence.rb +0 -170
- data/lib/ace/assign/molecules/canonical_read_commands.rb +0 -84
- data/lib/ace/assign/molecules/canonical_read_snapshot.rb +0 -313
- data/lib/ace/assign/molecules/evidence_calculator.rb +0 -237
- data/lib/ace/assign/molecules/evidence_journal.rb +0 -1899
- data/lib/ace/assign/molecules/execution_identity_resolver.rb +0 -137
- data/lib/ace/assign/molecules/execution_scope_lineage.rb +0 -345
- data/lib/ace/assign/molecules/fixed_effect_reference.rb +0 -31
- data/lib/ace/assign/molecules/installed_journal_read.rb +0 -143
- data/lib/ace/assign/molecules/journal_input_inhibition.rb +0 -78
- data/lib/ace/assign/molecules/journal_mutation.rb +0 -274
- data/lib/ace/assign/molecules/journal_prompt_mutation.rb +0 -241
- data/lib/ace/assign/molecules/ordinary_merge_cli.rb +0 -68
- data/lib/ace/assign/molecules/ordinary_merge_contract.rb +0 -410
- data/lib/ace/assign/molecules/original_launch_binding.rb +0 -37
- data/lib/ace/assign/molecules/proposal_journal.rb +0 -179
- data/lib/ace/assign/molecules/protected_control_exclusion.rb +0 -303
- data/lib/ace/assign/molecules/protected_workspace_exclusion.rb +0 -330
- data/lib/ace/assign/molecules/protected_workspace_provisioner.rb +0 -164
- data/lib/ace/assign/molecules/receipt_verifier.rb +0 -320
- data/lib/ace/assign/molecules/terminal_scope_receipt.rb +0 -88
- data/lib/ace/assign/organisms/attempt_coordinator.rb +0 -1374
- data/lib/ace/assign/organisms/delivery_coordinator.rb +0 -342
- data/lib/ace/assign/organisms/ordinary_driver.rb +0 -310
- data/lib/ace/assign/organisms/ordinary_merge_consumer.rb +0 -365
- data/lib/ace/assign/organisms/prepared_work_builder.rb +0 -194
- data/lib/ace/assign/organisms/protected_delivery_coordinator.rb +0 -100
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
require_relative "protected_submission"
|
|
3
|
-
require_relative "../../authority/campaign_round_transfer"
|
|
4
|
-
|
|
5
|
-
module Ace
|
|
6
|
-
module Assign
|
|
7
|
-
module CLI
|
|
8
|
-
module Commands
|
|
9
|
-
class CampaignRecordRound < Ace::Support::Cli::Command
|
|
10
|
-
include Ace::Support::Cli::Base
|
|
11
|
-
include ProtectedSubmission
|
|
12
|
-
desc "Consume settled protected campaign children into the original R1 store"
|
|
13
|
-
option :mapping, desc: "Original installed parent mapping"
|
|
14
|
-
option :assignment, desc: "Original parent assignment"
|
|
15
|
-
option :attempt, desc: "Original parent attempt"
|
|
16
|
-
option :head, desc: "Exact parent candidate head"
|
|
17
|
-
option :candidate_generation, desc: "Exact parent candidate counter"
|
|
18
|
-
option :mutation, desc: "Stable R1 round attempt ID"
|
|
19
|
-
option :input, desc: "Closed bounded round envelope"
|
|
20
|
-
option :artifact, type: :array, desc: "Ordered report byte files"
|
|
21
|
-
|
|
22
|
-
def call(**options)
|
|
23
|
-
usage = "campaign-record-round with original parent selectors, --mutation ID and --input FILE"
|
|
24
|
-
context = protected_context(options)
|
|
25
|
-
raise AttemptErrors::EvidenceUnavailable, "Campaign consumption requires installed authority" unless context
|
|
26
|
-
params = {"assignment_id" => require_option(options, :assignment, usage),
|
|
27
|
-
"attempt_id" => require_option(options, :attempt, usage), "head" => require_option(options, :head, usage),
|
|
28
|
-
"candidate_generation" => integer_option(options, :candidate_generation, usage, positive: true)}
|
|
29
|
-
unless params["head"].match?(/\A[0-9a-f]{40}\z/)
|
|
30
|
-
raise Ace::Support::Cli::Error, "Campaign head must be an exact SHA-1"
|
|
31
|
-
end
|
|
32
|
-
context.verify_attempt_hints!(assignment_id: params.fetch("assignment_id"), attempt_id: params.fetch("attempt_id"))
|
|
33
|
-
mutation = require_option(options, :mutation, usage)
|
|
34
|
-
bytes = input_bytes(require_option(options, :input, usage), limit: 16 * 1024)
|
|
35
|
-
files = options.fetch(:artifact, [])
|
|
36
|
-
raise Ace::Support::Cli::Error, "Campaign artifacts require at most 16 files" unless files.is_a?(Array) && files.size <= 16
|
|
37
|
-
parts = [bytes, *files.map { |path| input_bytes(path, limit: 64 * 1024, nonempty: false) }]
|
|
38
|
-
params["input_sha256"] = Digest::SHA256.hexdigest(bytes)
|
|
39
|
-
input = Struct.new(:parts) do
|
|
40
|
-
def count = parts.length
|
|
41
|
-
def bytes(index:) = parts.fetch(index)
|
|
42
|
-
end.new(parts)
|
|
43
|
-
admitted = Ace::Assign::Authority::CampaignRoundTransfer.decode(input: input, sha256: params.fetch("input_sha256"))
|
|
44
|
-
unless admitted.fetch(:round).values_at("attempt_id", "head") == [mutation, params.fetch("head")]
|
|
45
|
-
raise Ace::Support::Cli::Error, "Campaign input request ID or head differs"
|
|
46
|
-
end
|
|
47
|
-
emit_json(upload_call(context.client(options: options), "campaign_record_round", params, mutation, parts, :receipt_artifacts))
|
|
48
|
-
rescue SecurityError, Ace::Runtime::RuntimeUnavailableError
|
|
49
|
-
protected_transport_unavailable!
|
|
50
|
-
rescue ArgumentError => error
|
|
51
|
-
raise Ace::Support::Cli::Error, error.message
|
|
52
|
-
end
|
|
53
|
-
end
|
|
54
|
-
end
|
|
55
|
-
end
|
|
56
|
-
end
|
|
57
|
-
end
|
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
require "json"
|
|
3
|
-
require_relative "../../authority/protected_assignment_context"
|
|
4
|
-
require_relative "../../organisms/protected_delivery_coordinator"
|
|
5
|
-
|
|
6
|
-
module Ace
|
|
7
|
-
module Assign
|
|
8
|
-
module CLI
|
|
9
|
-
module Commands
|
|
10
|
-
class Delivery < Ace::Support::Cli::Command
|
|
11
|
-
desc "Execute or reconcile attempt-bound forge delivery"
|
|
12
|
-
option :assignment, required: true, desc: "Managed assignment ID"
|
|
13
|
-
option :attempt, required: true, desc: "Active delivery attempt ID"
|
|
14
|
-
option :operation, required: true, desc: "create, update, ready, review, merge, status"
|
|
15
|
-
option :parameters, desc: "JSON delivery parameters (otherwise assignment source delivery mapping)"
|
|
16
|
-
option :title, desc: "PR title"
|
|
17
|
-
option :body_file, desc: "PR description file"
|
|
18
|
-
option :tests, desc: "JSON reference to coordinator-accepted test attempt and receipt digest"
|
|
19
|
-
option :review, desc: "JSON reference to coordinator-accepted independent review attempt and receipt digest"
|
|
20
|
-
option :service_request, desc: "Original authorized PR service request ID"
|
|
21
|
-
option :mapping, desc: "Installed original protected mapping selector"
|
|
22
|
-
option :scope, desc: "Original prepared worker scope"
|
|
23
|
-
option :candidate_head, desc: "Exact original protected candidate SHA"
|
|
24
|
-
option :candidate_generation, type: :integer, desc: "Exact original protected candidate generation"
|
|
25
|
-
option :input_digest, desc: "Exact original accepted service input SHA256"
|
|
26
|
-
option :target, desc: "Exact original target repository or PR URL"
|
|
27
|
-
option :artifact_digest, desc: "Original target artifact SHA256, if present"
|
|
28
|
-
|
|
29
|
-
def initialize(protected_context: nil)
|
|
30
|
-
@protected_assignment_context = protected_context
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
def call(**options)
|
|
34
|
-
context = @protected_assignment_context ||= Ace::Assign::Authority::ProtectedAssignmentContext.load
|
|
35
|
-
protected = context.protected_participant? || options[:mapping] || context.mapping_hint?
|
|
36
|
-
if protected
|
|
37
|
-
unless context.protected_worker?
|
|
38
|
-
raise AttemptErrors::EvidenceUnavailable, "protected delivery is worker-only"
|
|
39
|
-
end
|
|
40
|
-
unless %w[create update ready merge status].include?(options[:operation]) && %i[title body_file tests review].all? { |key| options[key].nil? }
|
|
41
|
-
raise ArgumentError, "protected delivery accepts canonical PR results/status only; local input/evidence flags are unavailable"
|
|
42
|
-
end
|
|
43
|
-
input = context.resolve(options: options, assignment_id: options[:assignment], scope: options[:scope])
|
|
44
|
-
raise AttemptErrors::EvidenceUnavailable, "original protected prepared input is unavailable" unless input
|
|
45
|
-
result = Organisms::ProtectedDeliveryCoordinator.new(client: context.client(options: options),
|
|
46
|
-
project_id: input.work.manifest.fetch("project_id")).perform(assignment_id: options[:assignment],
|
|
47
|
-
attempt_id: options[:attempt], operation: options[:operation], service_request_id: options[:service_request],
|
|
48
|
-
candidate_head: options[:candidate_head], candidate_generation: options[:candidate_generation],
|
|
49
|
-
input_digest: options[:input_digest], target: {"resource" => options[:target], "artifact_digest" => options[:artifact_digest]},
|
|
50
|
-
parameters: read_json(options[:parameters]))
|
|
51
|
-
puts JSON.generate(json_value(result))
|
|
52
|
-
return
|
|
53
|
-
end
|
|
54
|
-
if %i[mapping scope candidate_head candidate_generation input_digest target artifact_digest].any? { |key| options[key] }
|
|
55
|
-
raise ArgumentError, "protected delivery selectors cannot select ordinary local delivery"
|
|
56
|
-
end
|
|
57
|
-
result = build_delivery.perform(assignment_id: options[:assignment], attempt_id: options[:attempt],
|
|
58
|
-
operation: options[:operation], parameters: read_json(options[:parameters]),
|
|
59
|
-
title: options[:title], body: options[:body_file] && File.read(options[:body_file]),
|
|
60
|
-
tests: read_json(options[:tests]), review: read_json(options[:review]),
|
|
61
|
-
service_request_id: options[:service_request])
|
|
62
|
-
puts JSON.generate(json_value(result))
|
|
63
|
-
rescue ArgumentError, Ace::Git::Error, Ace::Assign::Error, Ace::Runtime::RuntimeUnavailableError,
|
|
64
|
-
JSON::ParserError, SecurityError, SystemCallError => e
|
|
65
|
-
message = e.is_a?(JSON::ParserError) ? "protected installed selection unavailable" : e.message
|
|
66
|
-
raise Ace::Support::Cli::Error, message
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
private
|
|
70
|
-
|
|
71
|
-
def build_delivery
|
|
72
|
-
Organisms::DeliveryCoordinator.new(repo_root: Ace::Support::Fs::Molecules::ProjectRootFinder.find_or_current)
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
def read_json(path)
|
|
76
|
-
return nil unless path
|
|
77
|
-
bytes = File.open(path, File::RDONLY | File::NOFOLLOW | File::NONBLOCK) do |file|
|
|
78
|
-
before = file.stat
|
|
79
|
-
raise ArgumentError, "Delivery input must be a bounded regular file" unless before.file? && before.size <= 65_536
|
|
80
|
-
value = file.read(65_537)
|
|
81
|
-
after = file.stat
|
|
82
|
-
unless value && value.bytesize <= 65_536 &&
|
|
83
|
-
[before.dev, before.ino, before.size, before.mtime, before.ctime] == [after.dev, after.ino, after.size, after.mtime, after.ctime]
|
|
84
|
-
raise ArgumentError, "Delivery input changed or exceeds its bound"
|
|
85
|
-
end
|
|
86
|
-
value
|
|
87
|
-
end
|
|
88
|
-
data = JSON.parse(bytes, max_nesting: 16, create_additions: false,
|
|
89
|
-
allow_duplicate_key: false, allow_comments: false, allow_nan: false)
|
|
90
|
-
raise ArgumentError, "Delivery input must be a JSON object" unless data.is_a?(Hash)
|
|
91
|
-
data
|
|
92
|
-
rescue JSON::ParserError
|
|
93
|
-
raise ArgumentError, "Delivery input must be valid JSON"
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
def json_value(value)
|
|
97
|
-
case value
|
|
98
|
-
when Hash then value.transform_values { |item| json_value(item) }
|
|
99
|
-
when Array then value.map { |item| json_value(item) }
|
|
100
|
-
else value.respond_to?(:to_h) ? json_value(value.to_h) : value
|
|
101
|
-
end
|
|
102
|
-
end
|
|
103
|
-
end
|
|
104
|
-
end
|
|
105
|
-
end
|
|
106
|
-
end
|
|
107
|
-
end
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
require_relative "attempt/base"
|
|
3
|
-
|
|
4
|
-
module Ace
|
|
5
|
-
module Assign
|
|
6
|
-
module CLI
|
|
7
|
-
module Commands
|
|
8
|
-
class InboxBind < Ace::Support::Cli::Command
|
|
9
|
-
include Ace::Support::Cli::Base
|
|
10
|
-
include Attempt::Base
|
|
11
|
-
|
|
12
|
-
desc "Bind an original protected Inbox registration before scope closure"
|
|
13
|
-
option :mapping, desc: "Exact installed mapping ID"
|
|
14
|
-
option :assignment, desc: "Exact original assignment ID"
|
|
15
|
-
option :attempt, desc: "Exact original attempt ID"
|
|
16
|
-
option :event, desc: "Original Inbox event ID"
|
|
17
|
-
option :inbox_context, desc: "Installed Inbox context ID"
|
|
18
|
-
option :mutation, desc: "Stable original authority mutation ID"
|
|
19
|
-
option :expected_generation, desc: "Original authority mutation generation"
|
|
20
|
-
|
|
21
|
-
def call(**options)
|
|
22
|
-
context = protected_context(options)
|
|
23
|
-
raise AttemptErrors::EvidenceUnavailable, "Inbox binding requires installed protected authority" unless context
|
|
24
|
-
usage = "inbox-bind --mapping MAP --assignment ID --attempt ID --event ID --inbox-context ID --mutation ID --expected-generation N"
|
|
25
|
-
client, params, mutation = protected_attempt_request(context, options, usage)
|
|
26
|
-
params.merge!("event_id" => require_option(options, :event, usage),
|
|
27
|
-
"inbox_context_id" => require_option(options, :inbox_context, usage))
|
|
28
|
-
emit_json(protected_call(client, "bind_inbox", params, mutation))
|
|
29
|
-
end
|
|
30
|
-
end
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
end
|
|
34
|
-
end
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
require_relative "attempt/base"
|
|
3
|
-
require "digest"
|
|
4
|
-
|
|
5
|
-
module Ace
|
|
6
|
-
module Assign
|
|
7
|
-
module CLI
|
|
8
|
-
module Commands
|
|
9
|
-
# Local files provide bounded bytes; only the existing authority grants admission.
|
|
10
|
-
module ProtectedSubmission
|
|
11
|
-
include Attempt::Base
|
|
12
|
-
private
|
|
13
|
-
|
|
14
|
-
def require_option(options, key, usage)
|
|
15
|
-
value = options[key].to_s.strip
|
|
16
|
-
raise Ace::Support::Cli::Error, "Missing --#{key}: usage: ace-assign #{usage}" if value.empty?
|
|
17
|
-
value
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def submission_request(options, usage)
|
|
21
|
-
context = protected_context(options)
|
|
22
|
-
raise AttemptErrors::EvidenceUnavailable, "Submission requires installed protected authority" unless context
|
|
23
|
-
protected_attempt_request(context, options, usage)
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
def input_bytes(path, limit:, nonempty: true)
|
|
27
|
-
File.open(path, File::RDONLY | File::NOFOLLOW | File::NONBLOCK) do |file|
|
|
28
|
-
before = file.stat
|
|
29
|
-
raise Ace::Support::Cli::Error, "Input must be a bounded regular file" unless before.file? && before.size.between?(nonempty ? 1 : 0, limit)
|
|
30
|
-
bytes = file.read(limit + 1)
|
|
31
|
-
after = file.stat
|
|
32
|
-
selected = File.lstat(path)
|
|
33
|
-
identity = ->(stat) { [stat.dev, stat.ino, stat.size, stat.mtime, stat.ctime] }
|
|
34
|
-
unless bytes.bytesize == before.size && identity.call(before) == identity.call(after) &&
|
|
35
|
-
identity.call(after) == identity.call(selected) && !selected.symlink?
|
|
36
|
-
raise Ace::Support::Cli::Error, "Input changed during bounded read"
|
|
37
|
-
end
|
|
38
|
-
bytes.freeze
|
|
39
|
-
end
|
|
40
|
-
rescue SystemCallError, IOError
|
|
41
|
-
raise Ace::Support::Cli::Error, "Input file is unavailable"
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
def input_json(bytes)
|
|
45
|
-
text = bytes.dup.force_encoding(Encoding::UTF_8)
|
|
46
|
-
raise Ace::Support::Cli::Error, "Input JSON is malformed" unless text.valid_encoding?
|
|
47
|
-
JSON.parse(text, create_additions: false, max_nesting: 32, allow_duplicate_key: false, allow_comments: false)
|
|
48
|
-
rescue JSON::ParserError
|
|
49
|
-
raise Ace::Support::Cli::Error, "Input JSON is malformed"
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
def upload_call(client, operation, params, mutation, parts, purpose)
|
|
53
|
-
client.call(operation, params, mutation_id: mutation, upload_parts: parts, purpose: purpose, timeout: 30).data
|
|
54
|
-
rescue SecurityError, Ace::Runtime::RuntimeUnavailableError, SystemCallError, IOError
|
|
55
|
-
protected_transport_unavailable!
|
|
56
|
-
end
|
|
57
|
-
end
|
|
58
|
-
end
|
|
59
|
-
end
|
|
60
|
-
end
|
|
61
|
-
end
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "json"
|
|
4
|
-
|
|
5
|
-
module Ace
|
|
6
|
-
module Assign
|
|
7
|
-
module CLI
|
|
8
|
-
module Commands
|
|
9
|
-
class Resume < Ace::Support::Cli::Command
|
|
10
|
-
include Ace::Support::Cli::Base
|
|
11
|
-
include AssignmentTarget
|
|
12
|
-
desc "Recover accepted assignment state without launching another writer"
|
|
13
|
-
option :assignment, desc: "Assignment ID"
|
|
14
|
-
option :mapping, desc: "Installed protected launch mapping ID"
|
|
15
|
-
option :attempt, desc: "Original protected attempt ID"
|
|
16
|
-
option :dry_run, type: :boolean, default: false, desc: "Inspect recovery without writing observations"
|
|
17
|
-
|
|
18
|
-
def call(**options)
|
|
19
|
-
target = resolve_assignment_target(options)
|
|
20
|
-
if target.prepared_input
|
|
21
|
-
raise Ace::Support::Cli::Error, "protected resume only supports --dry-run" unless options[:dry_run]
|
|
22
|
-
result = build_executor_for_target(target).status
|
|
23
|
-
descriptor = target.prepared_input.descriptor
|
|
24
|
-
puts JSON.generate(descriptor.slice("assignment_id", "mapping_id", "attempt_id", "scope", "definition_digest", "selection_sha256").merge(
|
|
25
|
-
"state" => result.fetch(:state).assignment_state.to_s, "dry_run" => true))
|
|
26
|
-
return
|
|
27
|
-
end
|
|
28
|
-
id = options[:assignment].to_s.strip
|
|
29
|
-
raise Ace::Support::Cli::Error, "--assignment ID is required" if id.empty?
|
|
30
|
-
|
|
31
|
-
puts JSON.generate(Organisms::AttemptCoordinator.new.resume(
|
|
32
|
-
assignment_id: id, dry_run: options[:dry_run]))
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
end
|
|
38
|
-
end
|
|
@@ -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: Ace::Assign::Authority::CandidateTransfer::MAX_BYTES)
|
|
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
|