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,59 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
require "fiddle"
|
|
3
|
-
|
|
4
|
-
module Ace
|
|
5
|
-
module Assign
|
|
6
|
-
module Authority
|
|
7
|
-
# Linux POSIX access ACL evaluation for a fixed unprivileged principal.
|
|
8
|
-
# No credential switching; unsupported or unreadable policy refuses admission.
|
|
9
|
-
class PosixAcl
|
|
10
|
-
def entries(path, attribute: "system.posix_acl_access")
|
|
11
|
-
unless %w[system.posix_acl_access system.posix_acl_default].include?(attribute)
|
|
12
|
-
raise ArgumentError, "unsupported POSIX ACL attribute"
|
|
13
|
-
end
|
|
14
|
-
raise Ace::Runtime::RuntimeUnavailableError, "receiver ACL inspection requires Linux" unless RUBY_PLATFORM.include?("linux")
|
|
15
|
-
function = Fiddle::Function.new(Fiddle::Handle::DEFAULT["lgetxattr"],
|
|
16
|
-
[Fiddle::TYPE_VOIDP, Fiddle::TYPE_VOIDP, Fiddle::TYPE_VOIDP, Fiddle::TYPE_SIZE_T], Fiddle::TYPE_SSIZE_T)
|
|
17
|
-
buffer = Fiddle::Pointer.malloc(8192, Fiddle::RUBY_FREE)
|
|
18
|
-
count = function.call(path, attribute, buffer, 8192)
|
|
19
|
-
return nil if count == -1 && Fiddle.last_error == Errno::ENODATA::Errno
|
|
20
|
-
raise Ace::Runtime::RuntimeUnavailableError, "receiver access ACL is unreadable" if count < 4 || (count - 4) % 8 != 0
|
|
21
|
-
bytes = buffer[0, count]
|
|
22
|
-
raise Ace::Runtime::RuntimeUnavailableError, "unknown receiver ACL version" unless bytes.unpack1("L<") == 2
|
|
23
|
-
rows = bytes.byteslice(4..).unpack("S<S<L<" * ((count - 4) / 8)).each_slice(3).to_a
|
|
24
|
-
validate!(rows)
|
|
25
|
-
rows
|
|
26
|
-
rescue Fiddle::DLError
|
|
27
|
-
raise Ace::Runtime::RuntimeUnavailableError, "receiver ACL inspection is unavailable"
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
def searchable?(path, stat:, uid:, groups:)
|
|
31
|
-
rows = entries(path)
|
|
32
|
-
unless rows
|
|
33
|
-
shift = uid == stat.uid ? 6 : (groups.include?(stat.gid) ? 3 : 0)
|
|
34
|
-
return ((stat.mode >> shift) & 1) == 1
|
|
35
|
-
end
|
|
36
|
-
return (rows.find { |tag, _perm, _id| tag == 1 }[1] & 1) == 1 if uid == stat.uid
|
|
37
|
-
mask = rows.find { |tag, _perm, _id| tag == 16 }&.at(1) || 7
|
|
38
|
-
named = rows.find { |tag, _perm, id| tag == 2 && id == uid }
|
|
39
|
-
return (named[1] & mask & 1) == 1 if named
|
|
40
|
-
matched = rows.select { |tag, _perm, id| (tag == 4 && groups.include?(stat.gid)) || (tag == 8 && groups.include?(id)) }
|
|
41
|
-
return matched.any? { |_tag, perm, _id| (perm & mask & 1) == 1 } unless matched.empty?
|
|
42
|
-
(rows.find { |tag, _perm, _id| tag == 32 }[1] & 1) == 1
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
private
|
|
46
|
-
|
|
47
|
-
def validate!(rows)
|
|
48
|
-
valid = rows.all? { |tag, perm, id| [1, 2, 4, 8, 16, 32].include?(tag) && (0..7).cover?(perm) &&
|
|
49
|
-
([2, 8].include?(tag) ? id != 0xffffffff : id == 0xffffffff) }
|
|
50
|
-
valid &&= [1, 4, 32].all? { |tag| rows.count { |row| row[0] == tag } == 1 }
|
|
51
|
-
valid &&= rows.count { |row| row[0] == 16 } <= 1
|
|
52
|
-
valid &&= [2, 8].all? { |tag| ids = rows.select { |row| row[0] == tag }.map(&:last); ids.uniq == ids }
|
|
53
|
-
valid &&= !rows.any? { |row| [2, 8].include?(row[0]) } || rows.count { |row| row[0] == 16 } == 1
|
|
54
|
-
raise Ace::Runtime::RuntimeUnavailableError, "invalid receiver access ACL" unless valid
|
|
55
|
-
end
|
|
56
|
-
end
|
|
57
|
-
end
|
|
58
|
-
end
|
|
59
|
-
end
|
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "shellwords"
|
|
4
|
-
require_relative "client"
|
|
5
|
-
require_relative "prepared_work"
|
|
6
|
-
|
|
7
|
-
module Ace
|
|
8
|
-
module Assign
|
|
9
|
-
module Authority
|
|
10
|
-
# One authenticated original fetch and one immutable verified work graph.
|
|
11
|
-
# Caller identifiers select; the maintained Client/Endcap authenticates.
|
|
12
|
-
class PreparedInput
|
|
13
|
-
attr_reader :descriptor, :work, :working_directory
|
|
14
|
-
|
|
15
|
-
def self.fetch(client:, assignment_id:, attempt_id:)
|
|
16
|
-
reply = client.call("evidence_fetch", {"assignment_id" => assignment_id, "attempt_id" => attempt_id,
|
|
17
|
-
"kind" => "prepared_work", "purpose_id" => "original_prepared_work", "artifact_id" => "prepared_bundle"},
|
|
18
|
-
download: true, purpose: :candidate, timeout: 30)
|
|
19
|
-
new(reply: reply, candidate_loader: -> {
|
|
20
|
-
client.call("evidence_fetch", {"assignment_id" => assignment_id, "attempt_id" => attempt_id,
|
|
21
|
-
"kind" => "campaign_candidate", "purpose_id" => "original_campaign_candidate", "artifact_id" => "candidate_bundle"},
|
|
22
|
-
download: true, purpose: :candidate, timeout: 30)
|
|
23
|
-
})
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
def initialize(reply:, candidate_reply: nil, candidate_loader: nil)
|
|
27
|
-
@descriptor = reply.data.fetch("descriptor")
|
|
28
|
-
Molecules::LifecycleExclusion.workspace_reader(projection: descriptor.fetch("workspace_exclusion"))
|
|
29
|
-
unless descriptor.dig("workspace_exclusion", "root_resource", "view_path") ==
|
|
30
|
-
"/run/ace/lifecycle-exclusion/#{descriptor.fetch('mapping_id')}"
|
|
31
|
-
raise AttemptErrors::EvidenceUnavailable, "prepared_input_unavailable: original lifecycle mapping differs"
|
|
32
|
-
end
|
|
33
|
-
unless reply.parts.is_a?(Array) && reply.parts.one?
|
|
34
|
-
raise AttemptErrors::EvidenceUnavailable, "prepared_input_unavailable: one original bundle required"
|
|
35
|
-
end
|
|
36
|
-
root = PrivateDirectory.verify!(descriptor.fetch("original_worker_scratch_root"))
|
|
37
|
-
@work = PreparedWork.admit(bytes: reply.parts.first, head: descriptor.fetch("prepared_head"),
|
|
38
|
-
tree: descriptor.fetch("prepared_tree"), sha256: descriptor.fetch("sha256"), size: descriptor.fetch("bytes"), root: root)
|
|
39
|
-
expected = work.reference(head: descriptor.fetch("prepared_head"), tree: descriptor.fetch("prepared_tree"))
|
|
40
|
-
definition = work.definition_bytes(head: descriptor.fetch("prepared_head"), tree: descriptor.fetch("prepared_tree"))
|
|
41
|
-
unless expected == descriptor.slice(*expected.keys) &&
|
|
42
|
-
descriptor.values_at("assignment_id", "project_id") == work.manifest.values_at("assignment_id", "project_id") &&
|
|
43
|
-
Digest::SHA256.hexdigest(definition) == descriptor.fetch("definition_digest")
|
|
44
|
-
raise AttemptErrors::EvidenceUnavailable, "prepared_input_mismatch: original graph association"
|
|
45
|
-
end
|
|
46
|
-
execution = work.definition["campaign_execution"]
|
|
47
|
-
if execution
|
|
48
|
-
candidate_reply ||= candidate_loader&.call
|
|
49
|
-
candidate = candidate_reply&.data&.fetch("descriptor")
|
|
50
|
-
unless candidate.is_a?(Hash) && candidate.keys.sort == %w[head tree bytes sha256 candidate_generation campaign_execution mapping_id assignment_id attempt_id project_id original_worker_identity original_worker_scratch_root original_binding_digest].sort &&
|
|
51
|
-
candidate.slice("mapping_id", "assignment_id", "attempt_id", "project_id", "original_worker_identity", "original_worker_scratch_root", "original_binding_digest") ==
|
|
52
|
-
descriptor.slice("mapping_id", "assignment_id", "attempt_id", "project_id", "original_worker_identity", "original_worker_scratch_root", "original_binding_digest") &&
|
|
53
|
-
candidate.fetch("campaign_execution") == execution && candidate.fetch("head") == execution.fetch("head") &&
|
|
54
|
-
candidate.fetch("candidate_generation") == execution.fetch("parent_candidate_generation") &&
|
|
55
|
-
candidate_reply.parts.is_a?(Array) && candidate_reply.parts.one?
|
|
56
|
-
raise AttemptErrors::EvidenceUnavailable, "campaign original candidate input differs"
|
|
57
|
-
end
|
|
58
|
-
materialized = CandidateTransfer.new(root: root).materialize_repository(bytes: candidate_reply.parts.first,
|
|
59
|
-
sha256: candidate.fetch("sha256"), size: candidate.fetch("bytes"), head: candidate.fetch("head"),
|
|
60
|
-
tree: candidate.fetch("tree"), root: root)
|
|
61
|
-
@working_directory = materialized.fetch("directory").freeze
|
|
62
|
-
elsif candidate_reply
|
|
63
|
-
raise AttemptErrors::EvidenceUnavailable, "ordinary prepared input forbids a campaign candidate"
|
|
64
|
-
end
|
|
65
|
-
@descriptor = JSON.parse(JSON.generate(descriptor))
|
|
66
|
-
pending = [@descriptor]
|
|
67
|
-
until pending.empty?
|
|
68
|
-
value = pending.pop
|
|
69
|
-
pending.concat(value.values) if value.is_a?(Hash)
|
|
70
|
-
pending.concat(value) if value.is_a?(Array)
|
|
71
|
-
value.freeze
|
|
72
|
-
end
|
|
73
|
-
freeze
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
def render(text)
|
|
77
|
-
values = descriptor.slice("mapping_id", "assignment_id", "scope", "attempt_id")
|
|
78
|
-
rendered = +""
|
|
79
|
-
cursor = 0
|
|
80
|
-
# Only an opening delimiter introduces a token. Closing braces in
|
|
81
|
-
# literal JSON are preserved; inserted selector values are not parsed.
|
|
82
|
-
while (opening = text.index("{{", cursor))
|
|
83
|
-
rendered << text[cursor...opening]
|
|
84
|
-
closing = text.index("}}", opening + 2)
|
|
85
|
-
unless closing
|
|
86
|
-
raise AttemptErrors::EvidenceUnavailable, "prepared_input_invalid: unresolved instruction token"
|
|
87
|
-
end
|
|
88
|
-
token = text[(opening + 2)...closing]
|
|
89
|
-
if token.include?("{") || token.include?("}")
|
|
90
|
-
raise AttemptErrors::EvidenceUnavailable, "prepared_input_invalid: unresolved instruction token"
|
|
91
|
-
end
|
|
92
|
-
field = token.delete_prefix("admitted.")
|
|
93
|
-
unless token == "admitted." + field && values.key?(field)
|
|
94
|
-
raise AttemptErrors::EvidenceUnavailable, "prepared_input_invalid: unknown instruction token"
|
|
95
|
-
end
|
|
96
|
-
rendered << Shellwords.escape(values.fetch(field))
|
|
97
|
-
cursor = closing + 2
|
|
98
|
-
end
|
|
99
|
-
rendered << text[cursor..]
|
|
100
|
-
rendered.freeze
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
def drive_prompt
|
|
104
|
-
target = Shellwords.escape(descriptor.fetch("assignment_id") + "@" + descriptor.fetch("scope"))
|
|
105
|
-
selectors = "--assignment #{target} --mapping #{Shellwords.escape(descriptor.fetch('mapping_id'))} --attempt #{Shellwords.escape(descriptor.fetch('attempt_id'))}"
|
|
106
|
-
instructions = ["Drive the authenticated captured assignment subtree. Use ace-assign status #{selectors}, step #{selectors}, start #{selectors}, finish #{selectors} --message REPORT, and fail #{selectors} --message REASON. Do not fork-run, add, retry, renumber, or expand another graph."]
|
|
107
|
-
if (execution = work.definition["campaign_execution"])
|
|
108
|
-
instructions << "This is a registered campaign #{execution.fetch('phase')} child. The provider working directory is the private authenticated parent candidate repository at #{Shellwords.escape(execution.fetch('head'))}, with original base #{Shellwords.escape(execution.fetch('base'))}. Do not edit or advance that candidate. Execute the captured #{Shellwords.escape(execution.fetch('operation'))} phase and retain its actual #{Shellwords.escape(execution.fetch('check_name'))} check and report artifacts. Submit an ordinary campaign-free result (campaign null), operation #{Shellwords.escape(execution.fetch('operation'))}, exact original head/base, and actual check outcome. The existing independent reviewer must validate those same artifact bytes; an approval child must retain the exact canonical independent review actor/verdict. No session metadata or completed queue step counts as a settled R1 campaign round."
|
|
109
|
-
end
|
|
110
|
-
original = "--mapping #{Shellwords.escape(descriptor.fetch('mapping_id'))} --assignment #{Shellwords.escape(descriptor.fetch('assignment_id'))} --attempt #{Shellwords.escape(descriptor.fetch('attempt_id'))}"
|
|
111
|
-
instructions << "Before the original worker exits, explicitly submit its tested candidate and real execution result through the protected authority. Queue completion is not candidate/result acceptance. Read ace-assign authority status #{original}; persist its authority_generation with the complete next command arguments and ordered byte inputs. For each NEW operation use a new explicit mutation and a newly selected generation; for exact retries preserve the original generation, mutation and bytes."
|
|
112
|
-
instructions << "Submit one prepared Git bundle: ace-assign submit-candidate #{original} --head HEAD --candidate-generation CURRENT_CANDIDATE --expected-generation G --mutation CANDIDATE_MUTATION --bundle FILE. Select CURRENT_CANDIDATE from status result_candidate_generation, using 0 when null. Persist the returned candidate_generation and head as REVIEWED_CANDIDATE and HEAD; never derive the next counter by arithmetic or from queue completion."
|
|
113
|
-
instructions << "Have an independent installed reviewer run ace-overseer review --project #{Shellwords.escape(descriptor.fetch('project_id'))} --agent #{Shellwords.escape(descriptor.fetch('mapping_id'))} --assignment #{Shellwords.escape(descriptor.fetch('assignment_id'))} --attempt #{Shellwords.escape(descriptor.fetch('attempt_id'))} --head HEAD --candidate-generation REVIEWED_CANDIDATE --expected-generation G --mutation REVIEW_REQUEST --accept-mutation REVIEW_ACCEPT. Do not impersonate that principal. The review consumer owns actual candidate export, independent checks and canonical acceptance. After review, read authority status again before the first result submission."
|
|
114
|
-
instructions << "Produce the real receipt with Ace::Assign::Models::ExecutionReceipt.new/to_h: original assignment/attempt/project and scope #{Shellwords.escape(descriptor.fetch('scope'))}, exact tested candidate head, installed worker actor/role worker/runtime herdr, actual operation/verdict/executed checks and ordered artifact path/SHA256 pairs. No invented successful check or queue-to-receipt conversion. Submit JSON bytes and matching ordered artifact files with ace-assign submit-result #{original} --head HEAD --candidate-generation REVIEWED_CANDIDATE --expected-generation G --mutation RESULT_MUTATION --receipt FILE --artifact FILE ... . Failed empty artifacts use no --artifact; succeeded requires evidence. Supervisor finish is separate and revalidates result, independent review and no-writer/settlement proof."
|
|
115
|
-
instructions << "For an authorized merge step captured INSIDE this subtree, use the protected delivery rule in wfi://assign/drive. Never execute an outer preset step or expand this graph. Retain the exact approved candidate, structured input bytes, authorization and request ID before the first request; select authority_generation for that new request only."
|
|
116
|
-
service = "--project #{Shellwords.escape(descriptor.fetch('project_id'))} #{original} --scope #{Shellwords.escape(descriptor.fetch('scope'))} --candidate-head HEAD --candidate-generation REVIEWED_CANDIDATE"
|
|
117
|
-
instructions << "Request the uniquely installed authorized merge receiver: ace-lab service request #{service} --service SERVICE --operation merge --authorization AUTHORIZATION --request-id REQUEST --expected-generation G --input INPUT_FILE. Credentials alone are not authorization. Persist the returned original input_digest and target with all request arguments. Lost/uncertain replies never permit another effect or refreshed-generation retry."
|
|
118
|
-
instructions << "Inspect the same request: ace-lab service status #{service} --request REQUEST --input-digest INPUT_DIGEST --target TARGET. Only canonical succeeded evidence permits ace-assign delivery #{original} --scope #{Shellwords.escape(descriptor.fetch('scope'))} --operation merge --service-request REQUEST --candidate-head HEAD --candidate-generation REVIEWED_CANDIDATE --input-digest INPUT_DIGEST --target TARGET. This consumption verifies the imported receipt and never executes merge. Refusal, unavailable or uncertain leaves the active delivery step unfinished; report its exact blocker. Finish the queue step only after succeeded canonical delivery."
|
|
119
|
-
work.manifest.fetch("context").each do |entry|
|
|
120
|
-
instructions << render(work.files.fetch(entry.fetch("text").fetch("filename")))
|
|
121
|
-
end
|
|
122
|
-
work.manifest.fetch("steps").each do |entry|
|
|
123
|
-
instructions << "Step #{entry.fetch('number')}:\n" + render(work.parse_queue_step!(work.files.fetch("steps/" + entry.fetch("filename"))).fetch(:body))
|
|
124
|
-
end
|
|
125
|
-
instructions.join("\n\n").freeze
|
|
126
|
-
end
|
|
127
|
-
end
|
|
128
|
-
end
|
|
129
|
-
end
|
|
130
|
-
end
|
|
@@ -1,185 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "fileutils"
|
|
4
|
-
require "securerandom"
|
|
5
|
-
require "yaml"
|
|
6
|
-
require_relative "prepared_work"
|
|
7
|
-
require_relative "private_directory"
|
|
8
|
-
require_relative "../molecules/lifecycle_exclusion"
|
|
9
|
-
require_relative "../organisms/assignment_executor"
|
|
10
|
-
|
|
11
|
-
module Ace
|
|
12
|
-
module Assign
|
|
13
|
-
module Authority
|
|
14
|
-
# Existing assignment/progress files, rooted only in the authenticated
|
|
15
|
-
# original worker scratch. This cache never supplies authority or a graph.
|
|
16
|
-
class PreparedQueue
|
|
17
|
-
attr_reader :cache_base, :directory, :work, :descriptor
|
|
18
|
-
|
|
19
|
-
def initialize(work:, descriptor:)
|
|
20
|
-
@work, @descriptor = work, descriptor
|
|
21
|
-
@scratch = PrivateDirectory.verify!(descriptor.fetch("original_worker_scratch_root"))
|
|
22
|
-
%w[mapping_id attempt_id assignment_id].each do |field|
|
|
23
|
-
value = descriptor.fetch(field)
|
|
24
|
-
unavailable!("queue identity") unless value.is_a?(String) && value.match?(PreparedWork::TOKEN)
|
|
25
|
-
end
|
|
26
|
-
@cache_base = File.join(@scratch, "prepared-queues", descriptor.fetch("mapping_id"), descriptor.fetch("attempt_id"))
|
|
27
|
-
@directory = File.join(cache_base, descriptor.fetch("assignment_id"))
|
|
28
|
-
definition = JSON.parse(work.definition_bytes(head: descriptor.fetch("prepared_head"), tree: descriptor.fetch("prepared_tree")))
|
|
29
|
-
unavailable!("captured assignment metadata") unless %w[name source_config created_at].all? do |field|
|
|
30
|
-
definition[field].is_a?(String) && !definition[field].empty?
|
|
31
|
-
end
|
|
32
|
-
validate_timestamp!(definition.fetch("created_at"))
|
|
33
|
-
validate_timestamp!(definition["updated_at"]) if definition.key?("updated_at")
|
|
34
|
-
@metadata = Models::Assignment.from_h(definition, cache_dir: directory).to_h
|
|
35
|
-
lock_root = File.join(@scratch, "prepared-queue-exclusion")
|
|
36
|
-
private_directory!(lock_root, create: true)
|
|
37
|
-
@exclusion = Molecules::LifecycleExclusion.new(root: lock_root)
|
|
38
|
-
@key = @exclusion.assignment_key(descriptor.values_at("mapping_id", "attempt_id", "assignment_id").join(":"))
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
# Called ONLY after the fixed adapter proves actual original self. Later
|
|
42
|
-
# CLI consumers use with_executor and cannot recreate a missing queue.
|
|
43
|
-
def activate_original!
|
|
44
|
-
@exclusion.with_exclusive(@key) do
|
|
45
|
-
publish_initial! unless File.exist?(directory) || File.symlink?(directory)
|
|
46
|
-
executor = verified_executor
|
|
47
|
-
result = executor.status
|
|
48
|
-
state = result.fetch(:state)
|
|
49
|
-
unavailable!("selected queue has failed progress") unless state.failed.empty?
|
|
50
|
-
return nil if state.subtree_complete?(descriptor.fetch("scope"))
|
|
51
|
-
unless state.steps.all? { |step| step.status == :pending }
|
|
52
|
-
unavailable!("original adapter has already activated queue progress")
|
|
53
|
-
end
|
|
54
|
-
root = state.find_by_number(descriptor.fetch("scope"))
|
|
55
|
-
unavailable!("original selected fork root") unless root && root.fork?
|
|
56
|
-
executor.step_writer.mark_active(root.file_path)
|
|
57
|
-
# Same held-byte consumer validates the resulting progress before
|
|
58
|
-
# returning to the adapter that may now start its provider.
|
|
59
|
-
executor.status
|
|
60
|
-
root
|
|
61
|
-
end
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
def with_executor
|
|
65
|
-
@exclusion.with_exclusive(@key) do
|
|
66
|
-
unavailable!("published queue is unavailable; no reconstruction") unless File.directory?(directory)
|
|
67
|
-
yield verified_executor
|
|
68
|
-
end
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
private
|
|
72
|
-
|
|
73
|
-
def verified_executor
|
|
74
|
-
private_directory!(directory)
|
|
75
|
-
private_directory!(File.join(directory, "steps"))
|
|
76
|
-
private_directory!(File.join(directory, "reports"))
|
|
77
|
-
metadata = work.parse_queue_metadata!(held_read(File.join(directory, "assignment.yaml"), PreparedWork::MAX_MANIFEST))
|
|
78
|
-
unavailable!("queue assignment metadata differs") unless metadata.is_a?(Hash) &&
|
|
79
|
-
metadata.reject { |key, _| key == "updated_at" } == @metadata.reject { |key, _| key == "updated_at" }
|
|
80
|
-
validate_timestamp!(metadata["updated_at"]) if metadata.key?("updated_at")
|
|
81
|
-
unavailable!("queue job differs") unless held_read(File.join(directory, "job.yaml"), PreparedWork::MAX_TEXT) == work.files.fetch("job.yaml")
|
|
82
|
-
owner = self
|
|
83
|
-
scanner = Molecules::QueueScanner.new
|
|
84
|
-
parser = scanner.method(:scan_captured)
|
|
85
|
-
scanner.define_singleton_method(:scan) do |steps_dir, assignment:|
|
|
86
|
-
raise AttemptErrors::EvidenceUnavailable, "prepared queue scanner path differs" unless steps_dir == File.join(owner.directory, "steps")
|
|
87
|
-
steps, reports = owner.send(:captured_queue)
|
|
88
|
-
parser.call(steps: steps, assignment: assignment, reports: reports)
|
|
89
|
-
end
|
|
90
|
-
Organisms::AssignmentExecutor.new(cache_base: cache_base,
|
|
91
|
-
selected_assignment: Models::Assignment.from_h(metadata, cache_dir: directory), queue_scanner: scanner)
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
def captured_queue
|
|
95
|
-
expected = work.manifest.fetch("steps").map { |entry| entry.fetch("filename") }
|
|
96
|
-
steps_dir = File.join(directory, "steps")
|
|
97
|
-
unavailable!("queue step inventory differs") unless Dir.children(steps_dir).sort == expected.sort
|
|
98
|
-
steps = expected.to_h do |filename|
|
|
99
|
-
bytes = held_read(File.join(steps_dir, filename), PreparedWork::MAX_TEXT)
|
|
100
|
-
parsed = work.parse_queue_step!(bytes)
|
|
101
|
-
accepted = work.parse_queue_step!(work.files.fetch("steps/" + filename))
|
|
102
|
-
projection = ->(value) { value.fetch(:frontmatter).reject { |key, _| PreparedWork::PROGRESS.include?(key) } }
|
|
103
|
-
unless projection.call(parsed) == projection.call(accepted) && parsed.fetch(:body) == accepted.fetch(:body) &&
|
|
104
|
-
%w[pending active done failed].include?(parsed.fetch(:frontmatter).fetch("status", "pending"))
|
|
105
|
-
unavailable!("queue work-bearing input differs")
|
|
106
|
-
end
|
|
107
|
-
[filename, bytes]
|
|
108
|
-
end
|
|
109
|
-
reports_dir = File.join(directory, "reports")
|
|
110
|
-
allowed = work.manifest.fetch("steps").map do |entry|
|
|
111
|
-
name = work.parse_queue_step!(work.files.fetch("steps/" + entry.fetch("filename"))).fetch(:frontmatter).fetch("name")
|
|
112
|
-
Atoms::StepFileParser.generate_report_filename(entry.fetch("number"), name)
|
|
113
|
-
end
|
|
114
|
-
reports = Dir.children(reports_dir).to_h do |filename|
|
|
115
|
-
unavailable!("queue report inventory differs") unless allowed.include?(filename)
|
|
116
|
-
bytes = held_read(File.join(reports_dir, filename), PreparedWork::MAX_TEXT)
|
|
117
|
-
work.parse_queue_step!(bytes)
|
|
118
|
-
[filename, bytes]
|
|
119
|
-
end
|
|
120
|
-
[steps.freeze, reports.freeze]
|
|
121
|
-
end
|
|
122
|
-
|
|
123
|
-
def publish_initial!
|
|
124
|
-
private_directory!(File.join(@scratch, "prepared-queues"), create: true)
|
|
125
|
-
private_directory!(File.dirname(cache_base), create: true)
|
|
126
|
-
private_directory!(cache_base, create: true)
|
|
127
|
-
stage = File.join(cache_base, ".prepared-#{SecureRandom.hex(12)}")
|
|
128
|
-
Dir.mkdir(stage, 0700)
|
|
129
|
-
%w[steps reports].each { |name| Dir.mkdir(File.join(stage, name), 0700) }
|
|
130
|
-
durable_write(File.join(stage, "assignment.yaml"), @metadata.to_yaml)
|
|
131
|
-
durable_write(File.join(stage, "job.yaml"), work.files.fetch("job.yaml"))
|
|
132
|
-
work.manifest.fetch("steps").each do |entry|
|
|
133
|
-
name = entry.fetch("filename")
|
|
134
|
-
durable_write(File.join(stage, "steps", name), work.files.fetch("steps/" + name))
|
|
135
|
-
end
|
|
136
|
-
%w[steps reports].each { |name| File.open(File.join(stage, name), File::RDONLY) { |file| file.fsync } }
|
|
137
|
-
File.open(stage, File::RDONLY) { |file| file.fsync }
|
|
138
|
-
unavailable!("queue publication collision") if File.exist?(directory) || File.symlink?(directory)
|
|
139
|
-
File.rename(stage, directory)
|
|
140
|
-
File.open(cache_base, File::RDONLY) { |file| file.fsync }
|
|
141
|
-
ensure
|
|
142
|
-
FileUtils.remove_entry(stage) if stage && File.directory?(stage) && !File.symlink?(stage)
|
|
143
|
-
end
|
|
144
|
-
|
|
145
|
-
def private_directory!(path, create: false)
|
|
146
|
-
Dir.mkdir(path, 0700) if create && !File.exist?(path) && !File.symlink?(path)
|
|
147
|
-
PrivateDirectory.verify!(path)
|
|
148
|
-
end
|
|
149
|
-
|
|
150
|
-
def durable_write(path, bytes)
|
|
151
|
-
File.open(path, File::WRONLY | File::CREAT | File::EXCL | File::NOFOLLOW, 0600) do |file|
|
|
152
|
-
file.write(bytes); file.flush; file.fsync
|
|
153
|
-
end
|
|
154
|
-
end
|
|
155
|
-
|
|
156
|
-
def held_read(path, limit)
|
|
157
|
-
File.open(path, File::RDONLY | File::NOFOLLOW | File::NONBLOCK) do |file|
|
|
158
|
-
before = file.stat
|
|
159
|
-
unavailable!("queue held file kind/size") unless before.file? && before.uid == Process.uid &&
|
|
160
|
-
(before.mode & 0077).zero? && before.size.between?(1, limit)
|
|
161
|
-
bytes = file.read(limit + 1).force_encoding(Encoding::UTF_8)
|
|
162
|
-
after = file.stat
|
|
163
|
-
unavailable!("queue held file changed") unless bytes.valid_encoding? && !bytes.include?("\0") &&
|
|
164
|
-
bytes.bytesize == before.size && [before.dev, before.ino, before.size, before.mtime, before.ctime] ==
|
|
165
|
-
[after.dev, after.ino, after.size, after.mtime, after.ctime]
|
|
166
|
-
bytes.freeze
|
|
167
|
-
end
|
|
168
|
-
rescue SystemCallError
|
|
169
|
-
unavailable!("queue held file unavailable")
|
|
170
|
-
end
|
|
171
|
-
|
|
172
|
-
def unavailable!(reason)
|
|
173
|
-
raise AttemptErrors::EvidenceUnavailable, "prepared_input_unavailable: #{reason}"
|
|
174
|
-
end
|
|
175
|
-
|
|
176
|
-
def validate_timestamp!(value)
|
|
177
|
-
unavailable!("captured assignment timestamp") unless value.is_a?(String)
|
|
178
|
-
Time.iso8601(value)
|
|
179
|
-
rescue ArgumentError
|
|
180
|
-
unavailable!("captured assignment timestamp")
|
|
181
|
-
end
|
|
182
|
-
end
|
|
183
|
-
end
|
|
184
|
-
end
|
|
185
|
-
end
|
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative "protected_assignment_context"
|
|
4
|
-
require_relative "task_context_entry"
|
|
5
|
-
|
|
6
|
-
module Ace
|
|
7
|
-
module Assign
|
|
8
|
-
module Authority
|
|
9
|
-
# The selected immutable Lab owner sets the hook before entry evaluation.
|
|
10
|
-
# Its presence alone never grants scope; original fetch admission decides.
|
|
11
|
-
class PreparedTaskContext
|
|
12
|
-
SCHEMA = "ace.assign.prepared-task-context/v1"
|
|
13
|
-
MAX_RESPONSE = 6_307_840
|
|
14
|
-
PRINCIPAL_SCHEMA = "ace.assign.task-context-principal/v1"
|
|
15
|
-
SELECTION_SCHEMA = "ace.assign.task-context-selection/v1"
|
|
16
|
-
MAX_PRINCIPAL = 1_024
|
|
17
|
-
MAX_SELECTION = 65_536
|
|
18
|
-
|
|
19
|
-
def initialize(context: nil)
|
|
20
|
-
@context = context
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def call(mapping:, assignment:, attempt:, task:)
|
|
24
|
-
unavailable!("task-context task selector") unless task.is_a?(String) && task.match?(PreparedWork::TOKEN)
|
|
25
|
-
selected_entry = selected_entry!
|
|
26
|
-
input = resolved_input(mapping: mapping, assignment: assignment, attempt: attempt)
|
|
27
|
-
unavailable!("original protected task-context admission") unless input.descriptor.fetch("task_context_entry") == selected_entry
|
|
28
|
-
item = input.work.manifest.fetch("context").find { |entry| entry.fetch("task_id") == task }
|
|
29
|
-
unavailable!("task is outside captured original context") unless item
|
|
30
|
-
text = input.work.files.fetch(item.fetch("text").fetch("filename"))
|
|
31
|
-
record = input.descriptor.slice("mapping_id", "assignment_id", "attempt_id", "scope", "definition_digest", "selection_sha256")
|
|
32
|
-
.merge("schema" => SCHEMA, "task_id" => task, "text" => text)
|
|
33
|
-
response!(record, MAX_RESPONSE)
|
|
34
|
-
rescue ArgumentError, KeyError
|
|
35
|
-
unavailable!("task-context input or original entry differs")
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
def principal
|
|
39
|
-
selected_entry!
|
|
40
|
-
context = installed_context!
|
|
41
|
-
response!({"schema" => PRINCIPAL_SCHEMA, "uid" => context.uid, "protected_worker" => context.protected_worker?}, MAX_PRINCIPAL)
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
def selection(mapping:, assignment:, attempt:)
|
|
45
|
-
selected_entry!
|
|
46
|
-
# Current discovery selects a trusted classifier only. Original fetch
|
|
47
|
-
# independently selects the retained entry; no text leaves this mode.
|
|
48
|
-
input = resolved_input(mapping: mapping, assignment: assignment, attempt: attempt)
|
|
49
|
-
response!(input.descriptor.slice("mapping_id", "assignment_id", "attempt_id", "scope", "definition_digest", "selection_sha256", "task_context_entry")
|
|
50
|
-
.merge("schema" => SELECTION_SCHEMA), MAX_SELECTION)
|
|
51
|
-
rescue ArgumentError, KeyError
|
|
52
|
-
unavailable!("task-context original selection differs")
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
def inbox_principal
|
|
56
|
-
selected_entry!
|
|
57
|
-
context = installed_context!
|
|
58
|
-
response!({"schema" => "ace.assign.inbox-context-principal/v1", "uid" => context.uid,
|
|
59
|
-
"protected_participant" => context.protected_participant?}, MAX_PRINCIPAL)
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
def inbox_selection(project:, mapping:, inbox_context:)
|
|
63
|
-
selected_entry!
|
|
64
|
-
installed_context!.inbox_context_selection(project: project, mapping: mapping, inbox_context: inbox_context) do |record|
|
|
65
|
-
response!(record.merge("schema" => "ace.assign.inbox-context-selection/v1"), 16_384)
|
|
66
|
-
end
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
private
|
|
70
|
-
|
|
71
|
-
def selected_entry!
|
|
72
|
-
unless Object.const_defined?(:ACE_PROTECTED_TASK_CONTEXT_ENTRY, false)
|
|
73
|
-
unavailable!("selected original task-context entry is absent")
|
|
74
|
-
end
|
|
75
|
-
selected_entry = Object.const_get(:ACE_PROTECTED_TASK_CONTEXT_ENTRY, false)
|
|
76
|
-
TaskContextEntry.validate!(selected_entry)
|
|
77
|
-
objects = [selected_entry, *selected_entry.keys, *selected_entry.values]
|
|
78
|
-
selected_entry.values.each { |reference| objects.concat(reference.keys); objects.concat(reference.values) }
|
|
79
|
-
unavailable!("selected task-context entry is mutable") unless objects.all?(&:frozen?)
|
|
80
|
-
selected_entry
|
|
81
|
-
rescue ArgumentError, KeyError, TypeError
|
|
82
|
-
unavailable!("task-context selected entry differs")
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
def installed_context!
|
|
86
|
-
@context ||= ProtectedAssignmentContext.load
|
|
87
|
-
unavailable!("published task-context installed owner is absent") unless @context.installed?
|
|
88
|
-
@context
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
def resolved_input(mapping:, assignment:, attempt:)
|
|
92
|
-
unless [mapping, attempt].all? { |value| value.is_a?(String) && value.match?(PreparedWork::TOKEN) } && assignment.is_a?(String)
|
|
93
|
-
unavailable!("task-context selectors")
|
|
94
|
-
end
|
|
95
|
-
assignment_id, scope = assignment.split("@", 2)
|
|
96
|
-
unless assignment_id&.match?(PreparedWork::TOKEN) && scope&.match?(PreparedWork::SCOPE)
|
|
97
|
-
unavailable!("task-context scoped assignment")
|
|
98
|
-
end
|
|
99
|
-
input = installed_context!.resolve(options: {mapping: mapping, attempt: attempt}, assignment_id: assignment_id, scope: scope)
|
|
100
|
-
unavailable!("original protected task-context admission") unless input
|
|
101
|
-
input
|
|
102
|
-
end
|
|
103
|
-
|
|
104
|
-
def response!(record, limit)
|
|
105
|
-
response = JSON.generate(record) + "\n"
|
|
106
|
-
unavailable!("task-context response bound") if response.bytesize > limit
|
|
107
|
-
response.freeze
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
def unavailable!(reason)
|
|
111
|
-
raise AttemptErrors::EvidenceUnavailable, "prepared_input_unavailable: #{reason}"
|
|
112
|
-
end
|
|
113
|
-
end
|
|
114
|
-
end
|
|
115
|
-
end
|
|
116
|
-
end
|