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.
- 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 -2099
- 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 -1
- data/handbook/workflow-instructions/assign/drive.wf.md +47 -118
- 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/molecules/runtime_control_surface_runner.rb +2 -1
- 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 -161
- 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/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/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_transfer.rb +0 -327
- 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/deployment.rb +0 -627
- data/lib/ace/assign/authority/deployment_history.rb +0 -146
- data/lib/ace/assign/authority/endcap.rb +0 -455
- 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 -207
- 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_settlement.rb +0 -152
- data/lib/ace/assign/authority/endcap_services.rb +0 -598
- 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/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 -94
- data/lib/ace/assign/authority/ordinary_effect_settlement.rb +0 -118
- data/lib/ace/assign/authority/ordinary_lifecycle.rb +0 -191
- 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_results.rb +0 -196
- data/lib/ace/assign/authority/ordinary_server.rb +0 -75
- 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 -304
- 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/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 -187
- 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 -153
- 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 -1912
- 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 -273
- 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 -314
- data/lib/ace/assign/molecules/terminal_scope_receipt.rb +0 -88
- data/lib/ace/assign/organisms/attempt_coordinator.rb +0 -1337
- data/lib/ace/assign/organisms/delivery_coordinator.rb +0 -342
- 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,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
|