ace-assign 0.68.0 → 0.70.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/composition-rules.yml +0 -0
- data/.ace-defaults/assign/catalog/recipes/batch-tasks.recipe.yml +0 -0
- data/.ace-defaults/assign/catalog/recipes/documentation.recipe.yml +0 -0
- data/.ace-defaults/assign/catalog/recipes/fix-and-review.recipe.yml +0 -0
- data/.ace-defaults/assign/catalog/recipes/implement-simple.recipe.yml +0 -0
- data/.ace-defaults/assign/catalog/recipes/implement-with-pr.recipe.yml +0 -0
- data/.ace-defaults/assign/catalog/recipes/release-only.recipe.yml +0 -0
- data/.ace-defaults/assign/catalog/steps/apply-feedback.step.yml +0 -0
- data/.ace-defaults/assign/catalog/steps/commit.step.yml +0 -0
- data/.ace-defaults/assign/catalog/steps/create-retro.step.yml +0 -0
- data/.ace-defaults/assign/catalog/steps/fix-tests.step.yml +0 -0
- data/.ace-defaults/assign/catalog/steps/lint.step.yml +0 -0
- data/.ace-defaults/assign/catalog/steps/mark-task-done.step.yml +0 -0
- data/.ace-defaults/assign/catalog/steps/onboard-base.step.yml +0 -0
- data/.ace-defaults/assign/catalog/steps/onboard.step.yml +0 -0
- data/.ace-defaults/assign/catalog/steps/plan-task.step.yml +0 -0
- data/.ace-defaults/assign/catalog/steps/pre-commit-review.step.yml +0 -0
- data/.ace-defaults/assign/catalog/steps/push-to-remote.step.yml +0 -0
- data/.ace-defaults/assign/catalog/steps/rebase-with-main.step.yml +0 -0
- data/.ace-defaults/assign/catalog/steps/record-demo.step.yml +0 -0
- data/.ace-defaults/assign/catalog/steps/reflect-and-refactor.step.yml +0 -0
- data/.ace-defaults/assign/catalog/steps/reorganize-commits.step.yml +0 -0
- data/.ace-defaults/assign/catalog/steps/research.step.yml +0 -0
- data/.ace-defaults/assign/catalog/steps/review-pr.step.yml +0 -0
- data/.ace-defaults/assign/catalog/steps/security-audit.step.yml +0 -0
- data/.ace-defaults/assign/catalog/steps/split-subtree-root.step.yml +0 -0
- data/.ace-defaults/assign/catalog/steps/squash-changelog.step.yml +0 -0
- data/.ace-defaults/assign/catalog/steps/task-load.step.yml +0 -0
- data/.ace-defaults/assign/catalog/steps/update-docs.step.yml +0 -0
- data/.ace-defaults/assign/catalog/steps/verify-test-suite.step.yml +0 -0
- data/.ace-defaults/assign/catalog/steps/verify-test.step.yml +0 -0
- data/.ace-defaults/assign/catalog/steps/work-on-task.step.yml +0 -0
- data/.ace-defaults/assign/presets/deliver-release.yml +11 -8
- data/.ace-defaults/assign/presets/integrate-batch.yml +10 -5
- data/.ace-defaults/assign/presets/quick-implement.yml +0 -0
- data/.ace-defaults/assign/presets/release-only.yml +0 -0
- data/.ace-defaults/assign/presets/supervise-delivery.yml +6 -4
- data/.ace-defaults/assign/presets/work-on-docs.yml +0 -0
- data/.ace-defaults/assign/presets/work-on-task-in-place.yml +0 -0
- data/.ace-defaults/nav/protocols/skill-sources/ace-assign.yml +0 -0
- data/.ace-defaults/nav/protocols/wfi-sources/ace-assign.yml +0 -0
- data/CHANGELOG.md +17 -0
- data/Rakefile +0 -0
- data/docs/demo/canonical-skill-source.gif +0 -0
- data/docs/demo/fork-provider.cast +0 -0
- data/docs/demo/fork-provider.gif +0 -0
- data/docs/demo/fork-provider.recording.json +0 -0
- data/docs/demo/fork-provider.tape.yml +0 -0
- data/docs/getting-started.md +0 -0
- data/docs/handbook.md +0 -0
- data/handbook/guides/fork-context.g.md +0 -0
- data/handbook/skills/as-assign-add-task/SKILL.md +0 -0
- data/handbook/skills/as-assign-compose/SKILL.md +0 -0
- data/handbook/skills/as-assign-create/SKILL.md +0 -0
- data/handbook/skills/as-assign-drive/SKILL.md +0 -0
- data/handbook/skills/as-assign-prepare/SKILL.md +0 -0
- data/handbook/skills/as-assign-recover-fork/SKILL.md +0 -0
- data/handbook/skills/as-assign-run-in-batches/SKILL.md +0 -0
- data/handbook/skills/as-assign-start/SKILL.md +0 -0
- data/handbook/skills/as-create-retro-internal/SKILL.md +0 -0
- data/handbook/skills/as-mark-task-done-internal/SKILL.md +0 -0
- data/handbook/skills/as-reflect-and-refactor-internal/SKILL.md +0 -0
- data/handbook/skills/as-task-load-internal/SKILL.md +0 -0
- data/handbook/workflow-instructions/assign/add-task.wf.md +0 -0
- data/handbook/workflow-instructions/assign/compose.wf.md +0 -0
- data/handbook/workflow-instructions/assign/create-retro-internal.wf.md +0 -0
- data/handbook/workflow-instructions/assign/create.wf.md +0 -0
- data/handbook/workflow-instructions/assign/mark-task-done-internal.wf.md +0 -0
- data/handbook/workflow-instructions/assign/recover-fork.wf.md +0 -0
- data/handbook/workflow-instructions/assign/reflect-and-refactor-internal.wf.md +0 -0
- data/handbook/workflow-instructions/assign/run-in-batches.wf.md +0 -0
- data/handbook/workflow-instructions/assign/start.wf.md +0 -0
- data/handbook/workflow-instructions/assign/task-load-internal.wf.md +0 -0
- data/handbook/workflow-instructions/assign/verify-test-suite.wf.md +0 -0
- data/lib/ace/assign/atoms/assign_frontmatter_parser.rb +0 -0
- data/lib/ace/assign/atoms/assignment_scope.rb +5 -0
- data/lib/ace/assign/atoms/catalog_loader.rb +0 -0
- data/lib/ace/assign/atoms/composition_rules.rb +0 -0
- data/lib/ace/assign/atoms/number_generator.rb +0 -0
- data/lib/ace/assign/atoms/preset_expander.rb +0 -0
- data/lib/ace/assign/atoms/preset_loader.rb +0 -0
- data/lib/ace/assign/atoms/preset_step_resolver.rb +0 -0
- data/lib/ace/assign/atoms/step_file_parser.rb +0 -0
- data/lib/ace/assign/atoms/step_numbering.rb +0 -0
- data/lib/ace/assign/atoms/step_sorter.rb +0 -0
- data/lib/ace/assign/atoms/tree_formatter.rb +0 -0
- data/lib/ace/assign/authority/assignment_definition.rb +35 -0
- data/lib/ace/assign/authority/candidate_transfer.rb +2 -2
- data/lib/ace/assign/authority/endcap_attempts.rb +1 -1
- data/lib/ace/assign/authority/launch_control_exclusions.rb +3 -17
- data/lib/ace/assign/authority/ordinary_attempt_start.rb +137 -0
- data/lib/ace/assign/authority/ordinary_client.rb +70 -0
- data/lib/ace/assign/authority/ordinary_completion.rb +94 -0
- data/lib/ace/assign/authority/ordinary_effect_settlement.rb +118 -0
- data/lib/ace/assign/authority/ordinary_lifecycle.rb +191 -0
- data/lib/ace/assign/authority/ordinary_peer.rb +54 -0
- data/lib/ace/assign/authority/ordinary_progress.rb +162 -0
- data/lib/ace/assign/authority/ordinary_queue.rb +115 -0
- data/lib/ace/assign/authority/ordinary_results.rb +196 -0
- data/lib/ace/assign/authority/ordinary_server.rb +75 -0
- data/lib/ace/assign/authority/prepared_work.rb +2 -2
- data/lib/ace/assign/authority/protected_assignment_context.rb +3 -3
- data/lib/ace/assign/authority/router.rb +11 -6
- data/lib/ace/assign/authority/server.rb +11 -180
- data/lib/ace/assign/authority/socket_server.rb +255 -0
- data/lib/ace/assign/authority/transfer_codec.rb +2 -2
- data/lib/ace/assign/cli/commands/add.rb +0 -0
- data/lib/ace/assign/cli/commands/attempt/evidence.rb +2 -2
- data/lib/ace/assign/cli/commands/list.rb +0 -0
- data/lib/ace/assign/cli/commands/retry_cmd.rb +0 -0
- data/lib/ace/assign/models/assignment_info.rb +0 -0
- data/lib/ace/assign/models/queue_state.rb +0 -0
- data/lib/ace/assign/models/step.rb +0 -0
- data/lib/ace/assign/molecules/assignment_discoverer.rb +0 -0
- data/lib/ace/assign/molecules/canonical_read_commands.rb +84 -0
- data/lib/ace/assign/molecules/canonical_read_snapshot.rb +2 -63
- data/lib/ace/assign/molecules/evidence_journal.rb +141 -34
- data/lib/ace/assign/molecules/installed_journal_read.rb +143 -0
- data/lib/ace/assign/molecules/journal_mutation.rb +5 -3
- data/lib/ace/assign/molecules/lifecycle_exclusion.rb +13 -2
- data/lib/ace/assign/molecules/preset_inferrer.rb +0 -0
- data/lib/ace/assign/molecules/proposal_journal.rb +32 -4
- data/lib/ace/assign/molecules/skill_assign_source_resolver.rb +0 -0
- data/lib/ace/assign/molecules/step_renumberer.rb +0 -0
- data/lib/ace/assign/organisms/attempt_coordinator.rb +58 -36
- data/lib/ace/assign/organisms/ordinary_merge_consumer.rb +5 -5
- data/lib/ace/assign/version.rb +1 -1
- metadata +24 -10
|
File without changes
|
|
@@ -11,7 +11,7 @@ module Ace
|
|
|
11
11
|
PROPOSAL_REFERENCE = /\Aproposal-[0-9a-f]{24}-r[1-9][0-9]*\z/
|
|
12
12
|
PROPOSAL_BINDING = %w[operation project_id assignment_id attempt_id input_digest target candidate_head caller_uid].freeze
|
|
13
13
|
|
|
14
|
-
PROPOSAL_IMMUTABLE = (PROPOSAL_BINDING + %w[schema proposal_id revision revision_id request_id requester content_digest created_at authorization context options recommendation rationale prerequisites lifecycle_request revision_operation_id expected_source_revision]).freeze
|
|
14
|
+
PROPOSAL_IMMUTABLE = (PROPOSAL_BINDING + %w[schema proposal_id revision revision_id request_id requester content_digest created_at authorization context options recommendation rationale prerequisites lifecycle_request revision_operation_id expected_source_revision silence_delegated]).freeze
|
|
15
15
|
|
|
16
16
|
def proposal_history(id)
|
|
17
17
|
assignment_ids.flat_map do |assignment|
|
|
@@ -110,8 +110,7 @@ module Ace
|
|
|
110
110
|
valid &&= updated["revision_operation_id"].nil? && updated["expected_source_revision"].nil?
|
|
111
111
|
end
|
|
112
112
|
unresolved = previous && proposal_claims(previous["revision_id"]).any? do |claim|
|
|
113
|
-
claim["state"] != "succeeded" &&
|
|
114
|
-
!(claim["state"] == "failed-settled" && settlement_evidence_intact?(claim))
|
|
113
|
+
claim["state"] != "succeeded" && !proposal_no_effect?(claim)
|
|
115
114
|
end
|
|
116
115
|
raise AttemptErrors::ReceiptRejected, "Claimed proposal cannot be revised; reconcile the existing effect" if unresolved
|
|
117
116
|
raise AttemptErrors::ReceiptRejected, "Proposal revision must begin with exact immutable delivery scope" unless valid
|
|
@@ -119,10 +118,39 @@ module Ace
|
|
|
119
118
|
private :validate_proposal_revision!
|
|
120
119
|
|
|
121
120
|
def proposal_claims(reference)
|
|
122
|
-
service_request_records.select do |request|
|
|
121
|
+
claims = (service_request_records + fixed_merge_request_records).select do |request|
|
|
123
122
|
request["authorization"] == reference &&
|
|
124
123
|
!(request["state"] == "rejected" && request["consumed"] == false)
|
|
125
124
|
end
|
|
125
|
+
return claims if claims.empty?
|
|
126
|
+
events = read_events(claims.first.fetch("assignment_id"))
|
|
127
|
+
claims.sort_by do |claim|
|
|
128
|
+
kind = claim.key?("queue_entry") ? "merge_admission" : "service_claim"
|
|
129
|
+
events.index do |event|
|
|
130
|
+
event["type"] == kind && event["attempt_id"] == claim.fetch("attempt_id") &&
|
|
131
|
+
event.dig("payload", "request_id") == claim.fetch("request_id")
|
|
132
|
+
end || raise(AttemptErrors::EvidenceUnavailable, "Proposal claim has no canonical admission event")
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Request IDs are scoped by record family. A same-text ID in the other
|
|
137
|
+
# family is another effect, never an idempotent replay.
|
|
138
|
+
def proposal_claim_conflict(reference, request_id:, fixed:)
|
|
139
|
+
[[service_request_records, false], [fixed_merge_request_records, true]].each do |records, family|
|
|
140
|
+
conflict = records.find do |claim|
|
|
141
|
+
claim["authorization"] == reference &&
|
|
142
|
+
!(family == fixed && claim["request_id"] == request_id) &&
|
|
143
|
+
!(claim["state"] == "rejected" && claim["consumed"] == false) &&
|
|
144
|
+
!proposal_no_effect?(claim)
|
|
145
|
+
end
|
|
146
|
+
return conflict if conflict
|
|
147
|
+
end
|
|
148
|
+
nil
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def proposal_no_effect?(claim)
|
|
152
|
+
(claim["state"] == "failed-settled" && settlement_evidence_intact?(claim)) ||
|
|
153
|
+
(claim["state"] == "failed" && claim.key?("queue_entry") && fixed_no_effect_intact?(claim))
|
|
126
154
|
end
|
|
127
155
|
|
|
128
156
|
def proposal_authorize!(reference, binding)
|
|
File without changes
|
|
File without changes
|
|
@@ -37,7 +37,10 @@ module Ace
|
|
|
37
37
|
# @param identity_resolver [Molecules::ExecutionIdentityResolver, nil] Trust boundary
|
|
38
38
|
# @param verifier [Molecules::ReceiptVerifier, nil] Receipt verifier
|
|
39
39
|
def initialize(cache_base: nil, repo_root: nil, journal: nil, identity_resolver: nil, verifier: nil,
|
|
40
|
-
lifecycle_exclusion: nil)
|
|
40
|
+
lifecycle_exclusion: nil, recorded_binding_observer: nil)
|
|
41
|
+
unless recorded_binding_observer.nil? || recorded_binding_observer.respond_to?(:call)
|
|
42
|
+
raise ArgumentError, "recorded native binding observer must be callable"
|
|
43
|
+
end
|
|
41
44
|
@manager = Molecules::AssignmentManager.new(cache_base: cache_base)
|
|
42
45
|
@store = @manager.attempt_store
|
|
43
46
|
@repo_root = repo_root || Ace::Support::Fs::Molecules::ProjectRootFinder.find_or_current
|
|
@@ -45,6 +48,7 @@ module Ace
|
|
|
45
48
|
@identity_resolver = identity_resolver || Molecules::ExecutionIdentityResolver.new
|
|
46
49
|
@verifier = verifier || Molecules::ReceiptVerifier.new(identity_resolver: @identity_resolver)
|
|
47
50
|
@lifecycle_exclusion = lifecycle_exclusion
|
|
51
|
+
@recorded_binding_observer = recorded_binding_observer
|
|
48
52
|
end
|
|
49
53
|
|
|
50
54
|
attr_reader :store
|
|
@@ -59,16 +63,17 @@ module Ace
|
|
|
59
63
|
{events: [{type: "attempt_stopped", payload: payload}], blobs: {}}
|
|
60
64
|
end
|
|
61
65
|
|
|
62
|
-
# The
|
|
66
|
+
# The source-selected result owner authenticates imported bytes, candidate,
|
|
63
67
|
# closure and independent settlements at the same CAS prefix.
|
|
64
|
-
def self.finished_transition_plan(events:, receipt:)
|
|
68
|
+
def self.finished_transition_plan(events:, receipt:, authority:)
|
|
69
|
+
raise ArgumentError, "unknown result authority" unless %i[ordinary protected].include?(authority)
|
|
65
70
|
state = Molecules::CanonicalAttemptState.derive(events)
|
|
66
71
|
unless state == "running" && %w[succeeded failed].include?(receipt.fetch("verdict"))
|
|
67
72
|
raise AttemptErrors::Conflict, "Result finish requires a running attempt"
|
|
68
73
|
end
|
|
69
74
|
Atoms::AttemptStateMachine.transition!(state, receipt.fetch("verdict"))
|
|
70
75
|
{events: [{type: "receipt_accepted", payload: {"receipt" => receipt}},
|
|
71
|
-
{type: "transition", payload: {"from" => state, "to" => receipt.fetch("verdict"), "reason" => "
|
|
76
|
+
{type: "transition", payload: {"from" => state, "to" => receipt.fetch("verdict"), "reason" => "#{authority}_result_accepted"}}], blobs: {}}
|
|
72
77
|
end
|
|
73
78
|
|
|
74
79
|
def self.protected_recovery_decision(state:, effects_pending:, bound:, live:, checkpoint:)
|
|
@@ -113,21 +118,13 @@ module Ace
|
|
|
113
118
|
raise AttemptErrors::ReceiptRejected, "Native reverse requires an active managed attempt"
|
|
114
119
|
end
|
|
115
120
|
binding = reconciler.recorded_binding(attempt)
|
|
116
|
-
|
|
117
|
-
unless binding && observation["liveness"] == "live" &&
|
|
118
|
-
binding["process_identity"] == observation["identity"] &&
|
|
119
|
-
%w[runtime session pane].all? { |key| binding[key].is_a?(String) && !binding[key].empty? }
|
|
121
|
+
unless binding && %w[runtime session pane].all? { |key| binding[key].is_a?(String) && !binding[key].empty? }
|
|
120
122
|
raise AttemptErrors::ReceiptRejected, "Exact native owner binding is unavailable or changed"
|
|
121
123
|
end
|
|
122
|
-
unless caller_pid.nil?
|
|
123
|
-
|
|
124
|
-
raise AttemptErrors::UnauthorizedIdentity, "Kernel caller process is unavailable"
|
|
125
|
-
end
|
|
126
|
-
caller = Ace::Runtime.resolve(binding["runtime"]).process_binding(pane: binding["pane"], caller_pid: caller_pid)
|
|
127
|
-
unless caller == binding
|
|
128
|
-
raise AttemptErrors::UnauthorizedIdentity, "Caller does not descend from the recorded native owner"
|
|
129
|
-
end
|
|
124
|
+
unless caller_pid.nil? || (caller_pid.is_a?(Integer) && caller_pid.positive?)
|
|
125
|
+
raise AttemptErrors::UnauthorizedIdentity, "Kernel caller process is unavailable"
|
|
130
126
|
end
|
|
127
|
+
observe_runtime_binding!(attempt, binding, caller_pid)
|
|
131
128
|
latest = recover_managed_attempt(attempt.binding.assignment_id, attempt_id)
|
|
132
129
|
unless latest&.active? && latest.binding.to_h == attempt.binding.to_h && reconciler.recorded_binding(latest) == binding
|
|
133
130
|
raise AttemptErrors::ReceiptRejected, "Native owner changed during observation"
|
|
@@ -137,6 +134,28 @@ module Ace
|
|
|
137
134
|
raise AttemptErrors::ReceiptRejected, "Native owner observation unavailable (#{e.class})"
|
|
138
135
|
end
|
|
139
136
|
|
|
137
|
+
# An installed consumer selects its own bounded native reader. It must
|
|
138
|
+
# never fall back to the service process's ambient runtime on failure.
|
|
139
|
+
def observe_runtime_binding!(attempt, binding, caller_pid)
|
|
140
|
+
if @recorded_binding_observer
|
|
141
|
+
actual = @recorded_binding_observer.call(binding: binding, caller_pid: caller_pid)
|
|
142
|
+
unless actual == binding
|
|
143
|
+
raise AttemptErrors::UnauthorizedIdentity, "Installed native owner observation differs"
|
|
144
|
+
end
|
|
145
|
+
return
|
|
146
|
+
end
|
|
147
|
+
observation = reconciler.observation(attempt)
|
|
148
|
+
unless observation["liveness"] == "live" && binding["process_identity"] == observation["identity"]
|
|
149
|
+
raise AttemptErrors::ReceiptRejected, "Exact native owner binding is unavailable or changed"
|
|
150
|
+
end
|
|
151
|
+
return if caller_pid.nil?
|
|
152
|
+
caller = Ace::Runtime.resolve(binding["runtime"]).process_binding(pane: binding["pane"], caller_pid: caller_pid)
|
|
153
|
+
unless caller == binding
|
|
154
|
+
raise AttemptErrors::UnauthorizedIdentity, "Caller does not descend from the recorded native owner"
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
private :observe_runtime_binding!
|
|
158
|
+
|
|
140
159
|
# Claim an external service effect before dispatch. Only a managed,
|
|
141
160
|
# active attempt for the exact project and candidate may own it. The
|
|
142
161
|
# guard re-runs inside the journal lock per CAS attempt, so a
|
|
@@ -174,7 +193,10 @@ module Ace
|
|
|
174
193
|
end
|
|
175
194
|
|
|
176
195
|
def service_attempt(binding)
|
|
177
|
-
|
|
196
|
+
# An effect can never acquire authority from a requester cache, even
|
|
197
|
+
# when the assignment definition is missing from this consumer.
|
|
198
|
+
attempt = journal_for.derived_attempts(binding.fetch("assignment_id"))
|
|
199
|
+
.find { |candidate| candidate.attempt_id == binding.fetch("attempt_id") }
|
|
178
200
|
raise AttemptErrors::NotFound, "Attempt not found" unless attempt
|
|
179
201
|
unless attempt.managed? && attempt.active? &&
|
|
180
202
|
attempt.binding.assignment_id == binding["assignment_id"] &&
|
|
@@ -306,28 +328,29 @@ module Ace
|
|
|
306
328
|
# never need message-string discrimination: replay/binding conflicts
|
|
307
329
|
# stay Conflict, a consumed authorization is AuthorizationConflict.
|
|
308
330
|
guard = lambda do
|
|
309
|
-
conflict =
|
|
331
|
+
conflict = journal_for.fixed_authorization_conflict(record)
|
|
310
332
|
if conflict
|
|
311
333
|
raise AttemptErrors::AuthorizationConflict,
|
|
312
334
|
"Authorization reference is not currently valid"
|
|
313
335
|
end
|
|
314
336
|
nil
|
|
315
337
|
end
|
|
316
|
-
|
|
338
|
+
journal_for.admit_fixed_merge_request(record, guard: guard)
|
|
317
339
|
end
|
|
318
340
|
|
|
319
341
|
# Begin the fixed effect (pending -> uncertain). The single CAS
|
|
320
342
|
# transition is the at-most-one-merge proof: a restarted receiver
|
|
321
|
-
#
|
|
343
|
+
# refuses an already-started claim and must not dispatch again.
|
|
322
344
|
def begin_fixed_merge_effect(request_id)
|
|
323
|
-
|
|
345
|
+
journal_for.begin_fixed_merge_request(request_id)
|
|
324
346
|
end
|
|
325
347
|
|
|
326
348
|
# Settle the fixed effect with the exact terminal receipt. The
|
|
327
349
|
# journal validates the closed receipt schema and its binding to the
|
|
328
350
|
# admitted record before the transition lands.
|
|
329
|
-
def settle_fixed_merge_effect(request_id, terminal_status:, receipt:)
|
|
330
|
-
|
|
351
|
+
def settle_fixed_merge_effect(request_id, terminal_status:, receipt:, attestation: nil)
|
|
352
|
+
journal_for.settle_fixed_merge_request(request_id, terminal_status: terminal_status,
|
|
353
|
+
receipt: receipt, attestation: attestation)
|
|
331
354
|
end
|
|
332
355
|
|
|
333
356
|
# Read-only canonical record lookup by request ID: lets the producer
|
|
@@ -337,6 +360,13 @@ module Ace
|
|
|
337
360
|
journal_for.fixed_merge_request(request_id)
|
|
338
361
|
end
|
|
339
362
|
|
|
363
|
+
# Read-only listing of every canonical fixed merge record: lets the
|
|
364
|
+
# receiver restart reconciliation find its own still-pending records
|
|
365
|
+
# by binding. Read-only; performs no dispatch and owns no state.
|
|
366
|
+
def fixed_merge_records
|
|
367
|
+
journal_for.fixed_merge_request_records
|
|
368
|
+
end
|
|
369
|
+
|
|
340
370
|
# Read-only status: resolve the opaque reference against the
|
|
341
371
|
# canonical record and verify every selector. Performs no dispatch,
|
|
342
372
|
# retry, authorization refresh or selector repair. Returns the
|
|
@@ -345,7 +375,7 @@ module Ace
|
|
|
345
375
|
# without revealing whether another project holds the reference.
|
|
346
376
|
def fixed_merge_status(reference:, request_id:, project_id:, assignment_id:, attempt_id:,
|
|
347
377
|
queue_entry:, input_digest:)
|
|
348
|
-
record =
|
|
378
|
+
record = journal_for.fixed_merge_request(request_id)
|
|
349
379
|
return nil unless record
|
|
350
380
|
matches = reference == record["reference"] && input_digest == record["input_digest"] &&
|
|
351
381
|
[project_id, assignment_id, attempt_id, queue_entry].zip(
|
|
@@ -596,7 +626,7 @@ input:#{Regexp.escape(request.fetch("input_digest"))} outcome:(\S+)( no-effect:(
|
|
|
596
626
|
|
|
597
627
|
active_scopes = @store.list(assignment_id).select(&:active?).map(&:binding)
|
|
598
628
|
active_scopes.concat(journal_actives.map(&:binding))
|
|
599
|
-
blocker = active_scopes.find { |binding|
|
|
629
|
+
blocker = active_scopes.find { |binding| Atoms::AssignmentScope.overlap?(binding.scope, scope) }
|
|
600
630
|
if blocker
|
|
601
631
|
raise AttemptErrors::Conflict,
|
|
602
632
|
"Active attempt scope #{blocker.scope} overlaps requested scope #{scope} on #{assignment_id}; " \
|
|
@@ -835,9 +865,9 @@ input:#{Regexp.escape(request.fetch("input_digest"))} outcome:(\S+)( no-effect:(
|
|
|
835
865
|
unless %w[check review-collection review-approval].include?(kind) && check_name.is_a?(String) && !check_name.strip.empty?
|
|
836
866
|
raise AttemptErrors::ReceiptRejected, "Invalid evidence kind or check name"
|
|
837
867
|
end
|
|
838
|
-
if !historical_head.nil? && (
|
|
868
|
+
if !historical_head.nil? && (!historical_head.is_a?(String) ||
|
|
839
869
|
!historical_head.match?(/\A[0-9a-f]{40,64}\z/))
|
|
840
|
-
raise AttemptErrors::ReceiptRejected, "Historical evidence requires
|
|
870
|
+
raise AttemptErrors::ReceiptRejected, "Historical evidence requires an exact recorded head"
|
|
841
871
|
end
|
|
842
872
|
unless attempt_id.to_s.match?(/\A[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}\z/) &&
|
|
843
873
|
receipt_digest.to_s.match?(/\A[0-9a-f]{64}\z/)
|
|
@@ -987,14 +1017,6 @@ input:#{Regexp.escape(request.fetch("input_digest"))} outcome:(\S+)( no-effect:(
|
|
|
987
1017
|
raise Error, "Failed to generate a unique attempt ID for #{assignment_id} after 100 attempts"
|
|
988
1018
|
end
|
|
989
1019
|
|
|
990
|
-
# Ancestors and descendants overlap: an active attempt for 010 owns
|
|
991
|
-
# its 010.01 subtree, and vice versa.
|
|
992
|
-
def scopes_overlap?(left, right)
|
|
993
|
-
return true if Atoms::AssignmentScope.equal?(left, right)
|
|
994
|
-
|
|
995
|
-
left.start_with?("#{right}.") || right.start_with?("#{left}.")
|
|
996
|
-
end
|
|
997
|
-
|
|
998
1020
|
# Active attempts derived from the authoritative journal, used when
|
|
999
1021
|
# the local record may be missing (lost or wiped cache). Derivation
|
|
1000
1022
|
# lives in the journal (owner of attempt-state interpretation).
|
|
@@ -1052,7 +1074,7 @@ input:#{Regexp.escape(request.fetch("input_digest"))} outcome:(\S+)( no-effect:(
|
|
|
1052
1074
|
# merges both). The loser records a stopped transition and refuses.
|
|
1053
1075
|
def yield_lost_ownership_race(attempt, assignment_id, scope)
|
|
1054
1076
|
rival = journal_for.active_attempts(assignment_id).find do |other|
|
|
1055
|
-
other.attempt_id != attempt.attempt_id &&
|
|
1077
|
+
other.attempt_id != attempt.attempt_id && Atoms::AssignmentScope.overlap?(other.binding.scope, scope)
|
|
1056
1078
|
end
|
|
1057
1079
|
return attempt if rival.nil?
|
|
1058
1080
|
|
|
@@ -212,7 +212,7 @@ module Ace
|
|
|
212
212
|
return {state: "accepted", reference: ack.fetch("reference"),
|
|
213
213
|
request_id: ack.fetch("request_id"), input_digest: ack.fetch("input_digest")}
|
|
214
214
|
end
|
|
215
|
-
handle_refusal(binding, identity, branch[:payload], expected_digest: digest)
|
|
215
|
+
handle_refusal(binding, identity, branch[:payload], expected_digest: digest, request_id: selectors.fetch(:request_id))
|
|
216
216
|
end
|
|
217
217
|
|
|
218
218
|
# Closed outer envelope for admission responses: exactly {status,
|
|
@@ -241,7 +241,7 @@ module Ace
|
|
|
241
241
|
end
|
|
242
242
|
end
|
|
243
243
|
|
|
244
|
-
def handle_refusal(binding, identity, error, expected_digest:)
|
|
244
|
+
def handle_refusal(binding, identity, error, expected_digest:, request_id:)
|
|
245
245
|
refusal = begin
|
|
246
246
|
Molecules::OrdinaryMergeContract.validate_refusal!(error)
|
|
247
247
|
rescue ArgumentError
|
|
@@ -249,9 +249,9 @@ module Ace
|
|
|
249
249
|
"ordinary merge refusal is not the maintained contract; admission stays unconfirmed"
|
|
250
250
|
end
|
|
251
251
|
echo = refusal.fetch("input_digest")
|
|
252
|
-
if echo && echo != expected_digest
|
|
252
|
+
if refusal.fetch("request_id") != request_id || (echo && echo != expected_digest)
|
|
253
253
|
raise AttemptErrors::EvidenceUnavailable,
|
|
254
|
-
"ordinary merge refusal echoes a different
|
|
254
|
+
"ordinary merge refusal echoes a different request; admission stays unconfirmed"
|
|
255
255
|
end
|
|
256
256
|
if refusal.fetch("code") == Molecules::OrdinaryMergeContract::UNAVAILABLE_CODE
|
|
257
257
|
raise AttemptErrors::EvidenceUnavailable,
|
|
@@ -259,7 +259,7 @@ module Ace
|
|
|
259
259
|
end
|
|
260
260
|
record_event(binding, identity, {"stage" => RESULT_STAGE, "outcome" => "refused_no_effect",
|
|
261
261
|
"code" => refusal.fetch("code"), "message" => refusal.fetch("message"),
|
|
262
|
-
"request_id" =>
|
|
262
|
+
"request_id" => request_id, "input_digest" => expected_digest})
|
|
263
263
|
{state: "refused", code: refusal.fetch("code"), message: refusal.fetch("message")}
|
|
264
264
|
end
|
|
265
265
|
|
data/lib/ace/assign/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ace-assign
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.70.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Michal Czyz
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-10-
|
|
10
|
+
date: 2026-10-16 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: ace-llm-providers-cli
|
|
@@ -175,42 +175,42 @@ dependencies:
|
|
|
175
175
|
requirements:
|
|
176
176
|
- - "~>"
|
|
177
177
|
- !ruby/object:Gem::Version
|
|
178
|
-
version: 0.60.
|
|
178
|
+
version: 0.60.3
|
|
179
179
|
type: :runtime
|
|
180
180
|
prerelease: false
|
|
181
181
|
version_requirements: !ruby/object:Gem::Requirement
|
|
182
182
|
requirements:
|
|
183
183
|
- - "~>"
|
|
184
184
|
- !ruby/object:Gem::Version
|
|
185
|
-
version: 0.60.
|
|
185
|
+
version: 0.60.3
|
|
186
186
|
- !ruby/object:Gem::Dependency
|
|
187
187
|
name: ace-task
|
|
188
188
|
requirement: !ruby/object:Gem::Requirement
|
|
189
189
|
requirements:
|
|
190
190
|
- - "~>"
|
|
191
191
|
- !ruby/object:Gem::Version
|
|
192
|
-
version: '0.
|
|
192
|
+
version: '0.40'
|
|
193
193
|
type: :runtime
|
|
194
194
|
prerelease: false
|
|
195
195
|
version_requirements: !ruby/object:Gem::Requirement
|
|
196
196
|
requirements:
|
|
197
197
|
- - "~>"
|
|
198
198
|
- !ruby/object:Gem::Version
|
|
199
|
-
version: '0.
|
|
199
|
+
version: '0.40'
|
|
200
200
|
- !ruby/object:Gem::Dependency
|
|
201
201
|
name: ace-bundle
|
|
202
202
|
requirement: !ruby/object:Gem::Requirement
|
|
203
203
|
requirements:
|
|
204
204
|
- - "~>"
|
|
205
205
|
- !ruby/object:Gem::Version
|
|
206
|
-
version:
|
|
206
|
+
version: 0.45.1
|
|
207
207
|
type: :runtime
|
|
208
208
|
prerelease: false
|
|
209
209
|
version_requirements: !ruby/object:Gem::Requirement
|
|
210
210
|
requirements:
|
|
211
211
|
- - "~>"
|
|
212
212
|
- !ruby/object:Gem::Version
|
|
213
|
-
version:
|
|
213
|
+
version: 0.45.1
|
|
214
214
|
- !ruby/object:Gem::Dependency
|
|
215
215
|
name: ace-runtime
|
|
216
216
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -245,14 +245,14 @@ dependencies:
|
|
|
245
245
|
requirements:
|
|
246
246
|
- - "~>"
|
|
247
247
|
- !ruby/object:Gem::Version
|
|
248
|
-
version: 0.
|
|
248
|
+
version: '0.6'
|
|
249
249
|
type: :runtime
|
|
250
250
|
prerelease: false
|
|
251
251
|
version_requirements: !ruby/object:Gem::Requirement
|
|
252
252
|
requirements:
|
|
253
253
|
- - "~>"
|
|
254
254
|
- !ruby/object:Gem::Version
|
|
255
|
-
version: 0.
|
|
255
|
+
version: '0.6'
|
|
256
256
|
- !ruby/object:Gem::Dependency
|
|
257
257
|
name: ace-support-test-helpers
|
|
258
258
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -418,6 +418,7 @@ files:
|
|
|
418
418
|
- lib/ace/assign/atoms/step_numbering.rb
|
|
419
419
|
- lib/ace/assign/atoms/step_sorter.rb
|
|
420
420
|
- lib/ace/assign/atoms/tree_formatter.rb
|
|
421
|
+
- lib/ace/assign/authority/assignment_definition.rb
|
|
421
422
|
- lib/ace/assign/authority/assignment_inventory.rb
|
|
422
423
|
- lib/ace/assign/authority/campaign_consumer_policy.rb
|
|
423
424
|
- lib/ace/assign/authority/campaign_execution.rb
|
|
@@ -460,6 +461,16 @@ files:
|
|
|
460
461
|
- lib/ace/assign/authority/launch_steering.rb
|
|
461
462
|
- lib/ace/assign/authority/launch_stop.rb
|
|
462
463
|
- lib/ace/assign/authority/maintenance_workspace_retirement.rb
|
|
464
|
+
- lib/ace/assign/authority/ordinary_attempt_start.rb
|
|
465
|
+
- lib/ace/assign/authority/ordinary_client.rb
|
|
466
|
+
- lib/ace/assign/authority/ordinary_completion.rb
|
|
467
|
+
- lib/ace/assign/authority/ordinary_effect_settlement.rb
|
|
468
|
+
- lib/ace/assign/authority/ordinary_lifecycle.rb
|
|
469
|
+
- lib/ace/assign/authority/ordinary_peer.rb
|
|
470
|
+
- lib/ace/assign/authority/ordinary_progress.rb
|
|
471
|
+
- lib/ace/assign/authority/ordinary_queue.rb
|
|
472
|
+
- lib/ace/assign/authority/ordinary_results.rb
|
|
473
|
+
- lib/ace/assign/authority/ordinary_server.rb
|
|
463
474
|
- lib/ace/assign/authority/original_inbox_selection.rb
|
|
464
475
|
- lib/ace/assign/authority/posix_acl.rb
|
|
465
476
|
- lib/ace/assign/authority/prepared_input.rb
|
|
@@ -478,6 +489,7 @@ files:
|
|
|
478
489
|
- lib/ace/assign/authority/service_delivery_evidence.rb
|
|
479
490
|
- lib/ace/assign/authority/service_evidence.rb
|
|
480
491
|
- lib/ace/assign/authority/service_publication_evidence.rb
|
|
492
|
+
- lib/ace/assign/authority/socket_server.rb
|
|
481
493
|
- lib/ace/assign/authority/task_context_entry.rb
|
|
482
494
|
- lib/ace/assign/authority/transfer_codec.rb
|
|
483
495
|
- lib/ace/assign/cli.rb
|
|
@@ -533,6 +545,7 @@ files:
|
|
|
533
545
|
- lib/ace/assign/molecules/attempt_store.rb
|
|
534
546
|
- lib/ace/assign/molecules/canonical_attempt_state.rb
|
|
535
547
|
- lib/ace/assign/molecules/canonical_evidence.rb
|
|
548
|
+
- lib/ace/assign/molecules/canonical_read_commands.rb
|
|
536
549
|
- lib/ace/assign/molecules/canonical_read_snapshot.rb
|
|
537
550
|
- lib/ace/assign/molecules/evidence_calculator.rb
|
|
538
551
|
- lib/ace/assign/molecules/evidence_journal.rb
|
|
@@ -540,6 +553,7 @@ files:
|
|
|
540
553
|
- lib/ace/assign/molecules/execution_scope_lineage.rb
|
|
541
554
|
- lib/ace/assign/molecules/fixed_effect_reference.rb
|
|
542
555
|
- lib/ace/assign/molecules/fork_session_launcher.rb
|
|
556
|
+
- lib/ace/assign/molecules/installed_journal_read.rb
|
|
543
557
|
- lib/ace/assign/molecules/journal_input_inhibition.rb
|
|
544
558
|
- lib/ace/assign/molecules/journal_mutation.rb
|
|
545
559
|
- lib/ace/assign/molecules/journal_prompt_mutation.rb
|