ace-lab 0.5.2 → 0.7.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.
@@ -0,0 +1,422 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "digest"
4
+ require "json"
5
+ require "open3"
6
+ require_relative "protected_service_request"
7
+ require_relative "../molecules/ordinary_socket"
8
+ require "ace/assign/atoms/fixed_merge_schemas"
9
+
10
+ module Ace
11
+ module Lab
12
+ module Organisms
13
+ # The ordinary fixed merge admission producer (spec 8x3.t.xz9.4). One
14
+ # mode of the maintained `service request/status` vocabulary: it
15
+ # canonicalizes the immutable request binding, authenticates the
16
+ # verified project overseer against the installed ordinary mapping,
17
+ # resolves the named queue entry and attempt, transports the request
18
+ # to the configured ordinary receiver, and emits the adopted ace-lab
19
+ # envelopes — never a second protocol. Refusals are pre-effect and
20
+ # carry no durable reference; unavailability is a caller-side blocker;
21
+ # the canonical Assign attempt/evidence state stays the sole
22
+ # queue/effect record, so this class owns no ledger.
23
+ class OrdinaryMergeRequest
24
+ MODE = "ordinary-merge"
25
+ OPERATION = "merge"
26
+
27
+ # The closed pre-effect refusal codes (exhaustive pre-effect mapping).
28
+ CODES = %w[ordinary_merge_invalid_request ordinary_merge_unauthenticated
29
+ ordinary_merge_forbidden ordinary_merge_selection_mismatch
30
+ ordinary_merge_authorization_invalid ordinary_merge_provenance_mismatch
31
+ ordinary_merge_target_changed ordinary_merge_evidence_invalid
32
+ ordinary_merge_request_conflict ordinary_merge_unavailable].freeze
33
+
34
+ # Selector keys this producer accepts; anything else — including the
35
+ # protected mapping/scope/service/generation selectors and any
36
+ # executable/argv/UID/socket/evidence-path/credential/token selector
37
+ # — refuses before effect.
38
+ REQUEST_KEYS = %i[project assignment attempt queue_entry operation input authorization
39
+ request_id mode].freeze
40
+ STATUS_KEYS = %i[project assignment attempt queue_entry reference request input_digest
41
+ mode format].freeze
42
+
43
+ ID = Molecules::ServicePolicy::ID
44
+
45
+ def initialize(topology: nil, policy: nil, coordinator: nil, repo_root: nil, ingress: nil)
46
+ @topology = topology || TopologyService.from_config
47
+ @repo_root = repo_root || git_toplevel
48
+ @coordinator = coordinator ||
49
+ Ace::Assign::Organisms::AttemptCoordinator.new(repo_root: @repo_root)
50
+ @policy = policy || Molecules::ServicePolicy.new(
51
+ Molecules::GrantResolver.trusted_document(Ace::Lab.authorization_path)
52
+ )
53
+ @ingress = ingress
54
+ end
55
+
56
+ def request(project:, assignment:, attempt:, queue_entry:, operation:, input:,
57
+ authorization:, request_id:, **options)
58
+ digest = nil
59
+ unless (options.keys - REQUEST_KEYS).empty?
60
+ invalid!("unknown request selector: #{unknown(options.keys, REQUEST_KEYS)}", request_id)
61
+ end
62
+ invalid!("operation must be merge", request_id) unless operation == OPERATION
63
+ identifiers!(project: project, assignment: assignment, attempt: attempt,
64
+ queue_entry: queue_entry, request_id: request_id)
65
+ unless authorization.is_a?(String) && authorization.match?(ID)
66
+ invalid!("invalid authorization reference", request_id)
67
+ end
68
+ document = load_request_document(input, request_id)
69
+ validate_document!(document, request_id)
70
+ digest = input_digest(project: project, assignment: assignment, attempt: attempt,
71
+ queue_entry: queue_entry, authorization: authorization, request_id: request_id,
72
+ document: document)
73
+ principal = authenticate!(project)
74
+ existing = @coordinator.fixed_merge_record(request_id)
75
+ if existing
76
+ return ack_envelope(existing) if exact_replay?(existing, project: project,
77
+ assignment: assignment, attempt: attempt, queue_entry: queue_entry,
78
+ authorization: authorization, digest: digest)
79
+ conflict!("request ID already exists with a different binding", request_id, digest)
80
+ end
81
+ receiver = receiver_mapping!(project)
82
+ resolve_selection!(project: project, assignment: assignment, attempt: attempt,
83
+ queue_entry: queue_entry)
84
+ authorize!(authorization, project: project, assignment: assignment, attempt: attempt,
85
+ queue_entry: queue_entry, document: document, digest: digest)
86
+ verify_candidate_head!(document)
87
+ submission = {"project_id" => project, "assignment_id" => assignment,
88
+ "attempt_id" => attempt, "queue_entry" => queue_entry, "operation" => OPERATION,
89
+ "authorization" => authorization, "request_id" => request_id,
90
+ "input_digest" => digest, "request" => document, "principal" => principal,
91
+ "receiver_id" => receiver.fetch("receiver_id"),
92
+ "configuration_sha256" => receiver.fetch("configuration_sha256")}
93
+ response = deliver(receiver, submission)
94
+ normalize_response(response, request_id: request_id, input_digest: digest)
95
+ rescue OrdinaryMergeRefusal => e
96
+ self.class.refusal_envelope(e.code, e.detail, request_id: e.request_id || request_id,
97
+ input_digest: e.input_digest || digest)
98
+ rescue Ace::Assign::AttemptErrors::AuthorizationConflict => e
99
+ self.class.refusal_envelope("ordinary_merge_authorization_invalid", e.message,
100
+ request_id: request_id, input_digest: digest)
101
+ rescue Ace::Assign::AttemptErrors::Conflict => e
102
+ self.class.refusal_envelope("ordinary_merge_request_conflict", e.message,
103
+ request_id: request_id, input_digest: digest)
104
+ rescue Molecules::OrdinarySocket::OrdinaryUnavailable => e
105
+ self.class.refusal_envelope("ordinary_merge_unavailable", e.message,
106
+ request_id: request_id, input_digest: digest)
107
+ rescue Ace::Assign::AttemptErrors::EvidenceUnavailable, Ace::Lab::InvalidConfigurationError => e
108
+ # Canonical evidence or installed configuration is unreadable: a
109
+ # caller-side blocker (endpoint/configuration failed), never a
110
+ # refusal and never a durable reference.
111
+ self.class.refusal_envelope("ordinary_merge_unavailable", e.message,
112
+ request_id: request_id, input_digest: digest)
113
+ end
114
+
115
+ # Read-only status: resolve the opaque reference against the canonical
116
+ # record with every selector; performs no dispatch, retry,
117
+ # authorization refresh or selector repair. Read-only status survives
118
+ # rollback and receiver downtime because the canonical Assign state —
119
+ # never a receiver — owns the record.
120
+ def status(project:, assignment:, attempt:, queue_entry:, reference:, request:,
121
+ input_digest:, **options)
122
+ unless (options.keys - STATUS_KEYS).empty?
123
+ return status_error(self.class.refusal_envelope("ordinary_merge_invalid_request",
124
+ "unknown status selector: #{unknown(options.keys, STATUS_KEYS)}",
125
+ request_id: request, input_digest: nil,
126
+ schema: Ace::Assign::Atoms::FixedMergeSchemas::STATUS_ERROR_SCHEMA_ID))
127
+ end
128
+ identifiers!(project: project, assignment: assignment, attempt: attempt,
129
+ queue_entry: queue_entry, request_id: request)
130
+ unless options[:format] == "json"
131
+ invalid!("status requires --format json", request)
132
+ end
133
+ unless reference.is_a?(String) && Ace::Assign::Molecules::FixedEffectReference.valid?(reference)
134
+ invalid!("invalid reference", request)
135
+ end
136
+ unless input_digest.is_a?(String) && input_digest.match?(/\A[0-9a-f]{64}\z/)
137
+ invalid!("invalid input digest", request)
138
+ end
139
+ authenticate!(project)
140
+ record = @coordinator.fixed_merge_status(reference: reference, request_id: request,
141
+ project_id: project, assignment_id: assignment, attempt_id: attempt,
142
+ queue_entry: queue_entry, input_digest: input_digest)
143
+ # Unknown or mismatched references answer identically, without
144
+ # revealing whether another project holds the reference.
145
+ return status_error(selection_error) unless record
146
+ {"status" => "ok", "data" => {
147
+ "schema" => Ace::Assign::Atoms::FixedMergeSchemas::STATUS_SCHEMA_ID,
148
+ "state" => record.fetch("state"),
149
+ "reference" => record.fetch("reference"),
150
+ "request_id" => record.fetch("request_id"),
151
+ "input_digest" => record.fetch("input_digest"),
152
+ "receipt" => %w[pending uncertain].include?(record.fetch("state")) ? nil : record["receipt"]
153
+ }}
154
+ rescue OrdinaryMergeRefusal => e
155
+ status_error(self.class.refusal_envelope(e.code, e.detail, request_id: request, input_digest: nil,
156
+ schema: Ace::Assign::Atoms::FixedMergeSchemas::STATUS_ERROR_SCHEMA_ID))
157
+ rescue Ace::Assign::AttemptErrors::EvidenceUnavailable, Ace::Lab::InvalidConfigurationError => e
158
+ # Canonical state or installed configuration unreadable: the status
159
+ # command degrades to the caller-side blocker envelope, never to a
160
+ # fabricated state or a raw crash.
161
+ status_error(self.class.refusal_envelope("ordinary_merge_unavailable", e.message,
162
+ request_id: request, input_digest: nil,
163
+ schema: Ace::Assign::Atoms::FixedMergeSchemas::STATUS_ERROR_SCHEMA_ID))
164
+ end
165
+
166
+ # ---- envelope composition (the adopted ace-lab vocabulary) ---------
167
+
168
+ def ack_envelope(record)
169
+ {"status" => "ok", "data" => {
170
+ "schema" => Ace::Assign::Atoms::FixedMergeSchemas::ACK_SCHEMA_ID,
171
+ "type" => "accepted",
172
+ "request_id" => record.fetch("request_id"),
173
+ "input_digest" => record.fetch("input_digest"),
174
+ "reference" => record.fetch("reference")
175
+ }}
176
+ end
177
+
178
+ def self.sanitize(message)
179
+ cleaned = message.to_s.scrub.gsub(/\s+/, " ").strip[0, 512]
180
+ cleaned.empty? ? "ordinary merge request refused" : cleaned
181
+ end
182
+
183
+ # Envelope composition is instance-independent; the CLI reuses it for
184
+ # mode-selection refusals before any producer exists.
185
+ def self.refusal_envelope(code, message, request_id:, input_digest:,
186
+ schema: Ace::Assign::Atoms::FixedMergeSchemas::REFUSAL_SCHEMA_ID)
187
+ raise ArgumentError, "unknown ordinary merge refusal code" unless CODES.include?(code)
188
+ echo = request_id.to_s[0, 256]
189
+ echo = "unknown" if echo.empty?
190
+ {"status" => "error", "error" => {
191
+ "schema" => schema,
192
+ "code" => code,
193
+ "message" => sanitize(message),
194
+ "request_id" => echo,
195
+ "input_digest" => input_digest
196
+ }}
197
+ end
198
+
199
+ private
200
+
201
+ # The immutable binding digest — spec ruling r4: lowercase hex SHA-256
202
+ # over the canonical JSON (recursively key-sorted, compact, UTF-8) of
203
+ # the full eight-field binding. Computed once here at admission; the
204
+ # same shared implementation re-derives it for replay and receipt
205
+ # verification, so the pair can never diverge.
206
+ def input_digest(project:, assignment:, attempt:, queue_entry:, authorization:,
207
+ request_id:, document:)
208
+ Ace::Assign::Atoms::FixedMergeSchemas.canonical_request_digest(
209
+ project: project, assignment: assignment, attempt: attempt,
210
+ queue_entry: queue_entry, authorization: authorization,
211
+ request_id: request_id, input: document
212
+ )
213
+ end
214
+
215
+ def validate_document!(document, request_id)
216
+ Ace::Assign::Atoms::FixedMergeSchemas.validate_request!(document)
217
+ Ace::Assign::Atoms::FixedMergeSchemas.require_request_joins!(document)
218
+ rescue Ace::Assign::Atoms::FixedMergeSchemas::SchemaError => e
219
+ invalid!(e.message, request_id)
220
+ end
221
+
222
+ # The verified local process owner is the only caller identity; it
223
+ # must be the configured ordinary project overseer principal.
224
+ def authenticate!(project)
225
+ identities = Molecules::CallerAuthorizer.local_identity
226
+ refused("ordinary_merge_unauthenticated", "verified local identity is unavailable") if identities.empty?
227
+ receiver = @policy.ordinary_receiver!(project)
228
+ unless receiver.is_a?(Hash) && identities.include?(receiver.fetch("principal"))
229
+ refused("ordinary_merge_forbidden",
230
+ "authenticated principal is not the configured ordinary project overseer")
231
+ end
232
+ receiver.fetch("principal")
233
+ end
234
+
235
+ # The installed ordinary section supplies identity and placement only.
236
+ # A disabled or absent admission mapping is rollback: a caller-side
237
+ # blocker that never redirects ordinary callers and never deletes the
238
+ # canonical records — read-only status stays fully available.
239
+ def receiver_mapping!(project)
240
+ receiver = @policy.ordinary_receiver!(project)
241
+ if receiver.nil? || receiver.fetch("admission", "enabled") != "enabled"
242
+ raise Molecules::OrdinarySocket::OrdinaryUnavailable,
243
+ "ordinary merge admission is not available in the installed configuration"
244
+ end
245
+ receiver.merge("configuration_sha256" => Atoms::ServiceInput.digest(@policy.document))
246
+ end
247
+
248
+ def resolve_selection!(project:, assignment:, attempt:, queue_entry:)
249
+ @coordinator.service_attempt("assignment_id" => assignment, "attempt_id" => attempt,
250
+ "project_id" => project)
251
+ rescue Ace::Assign::AttemptErrors::NotFound, Ace::Assign::AttemptErrors::ReceiptRejected,
252
+ Ace::Assign::AttemptErrors::InvalidState => e
253
+ refused("ordinary_merge_selection_mismatch", e.message)
254
+ end
255
+
256
+ def authorize!(authorization, project:, assignment:, attempt:, queue_entry:, document:, digest:)
257
+ binding = {"operation" => OPERATION, "project_id" => project,
258
+ "assignment_id" => assignment, "attempt_id" => attempt, "queue_entry" => queue_entry,
259
+ "input_digest" => digest, "forge" => document.fetch("forge"),
260
+ "pr_url" => document.fetch("pr_url"), "head_repository" => document.fetch("head_repository"),
261
+ "head_ref" => document.fetch("head_ref"), "candidate_head" => document.fetch("candidate_head"),
262
+ "base_repository" => document.fetch("base_repository"), "base_ref" => document.fetch("base_ref"),
263
+ "expected_base_head" => document.fetch("expected_base_head")}
264
+ @policy.authorize_ordinary!(authorization, binding)
265
+ rescue SecurityError => e
266
+ # Spec table (ruling r4): the authorization decision or reference is
267
+ # invalid for this request — missing, expired, revoked, out of scope,
268
+ # bound to another operation/project/PR/head, or not an ordinary
269
+ # merge decision at all. Authorization-decision mismatches NEVER use
270
+ # provenance_mismatch; that code is reserved for registered
271
+ # provenance drift verified by the receiver (and relayed verbatim).
272
+ refused("ordinary_merge_authorization_invalid", e.message)
273
+ end
274
+
275
+ def verify_candidate_head!(document)
276
+ unless current_head == document.fetch("candidate_head")
277
+ refused("ordinary_merge_target_changed", "candidate head moved from the immutable request")
278
+ end
279
+ end
280
+
281
+ def deliver(receiver, submission)
282
+ ingress = @ingress || default_ingress(receiver)
283
+ ingress.call(submission: submission)
284
+ end
285
+
286
+ def default_ingress(receiver)
287
+ socket = Molecules::OrdinarySocket.new(socket_path: receiver.fetch("socket_path"),
288
+ receiver_uid: receiver.fetch("receiver_uid"))
289
+ ->(submission:) { socket.submit(submission) }
290
+ end
291
+
292
+ # A valid receiver acknowledgment is relayed exactly; a valid receiver
293
+ # refusal is relayed with its closed code; anything else is a lost or
294
+ # unauthenticated response — a caller-side blocker, never a proven
295
+ # refusal and never a resubmission authorization.
296
+ def normalize_response(response, request_id:, input_digest:)
297
+ unless response.is_a?(Hash) && (response.keys - %w[status data error]).empty? &&
298
+ %w[ok error].include?(response["status"])
299
+ raise Molecules::OrdinarySocket::OrdinaryUnavailable, "receiver response envelope is invalid"
300
+ end
301
+ return normalize_ack(response, request_id: request_id, input_digest: input_digest) if
302
+ response.fetch("status") == "ok"
303
+ normalize_refusal(response, request_id: request_id, input_digest: input_digest)
304
+ end
305
+
306
+ def normalize_ack(response, request_id:, input_digest:)
307
+ data = response.fetch("data") { {} }
308
+ expected = %w[schema type request_id input_digest reference]
309
+ digest = data["input_digest"]
310
+ unless data.is_a?(Hash) && (data.keys - expected).empty? &&
311
+ expected.all? { |key| data[key].is_a?(String) && !data[key].empty? } &&
312
+ data.fetch("schema") == Ace::Assign::Atoms::FixedMergeSchemas::ACK_SCHEMA_ID &&
313
+ data.fetch("type") == "accepted" && data.fetch("request_id") == request_id &&
314
+ digest == input_digest && digest.match?(/\A[0-9a-f]{64}\z/) &&
315
+ Ace::Assign::Molecules::FixedEffectReference.valid?(data.fetch("reference"))
316
+ raise Molecules::OrdinarySocket::OrdinaryUnavailable,
317
+ "receiver acknowledgment is invalid or lost before durability could be confirmed"
318
+ end
319
+ {"status" => "ok", "data" => data.slice(*expected)}
320
+ end
321
+
322
+ def normalize_refusal(response, request_id:, input_digest:)
323
+ error = response.fetch("error") { {} }
324
+ expected = %w[schema code message request_id input_digest]
325
+ valid = error.is_a?(Hash) && (error.keys - expected).empty? &&
326
+ error.fetch("schema") == Ace::Assign::Atoms::FixedMergeSchemas::REFUSAL_SCHEMA_ID &&
327
+ CODES.include?(error.fetch("code")) &&
328
+ error.fetch("message").is_a?(String) && !error.fetch("message").empty? &&
329
+ error.fetch("message").length <= 512 &&
330
+ error.fetch("request_id") == request_id &&
331
+ (error.fetch("input_digest").nil? || error.fetch("input_digest") == input_digest)
332
+ unless valid
333
+ raise Molecules::OrdinarySocket::OrdinaryUnavailable, "receiver refusal envelope is invalid"
334
+ end
335
+ {"status" => "error", "error" => error.slice(*expected)}
336
+ end
337
+
338
+ def selection_error
339
+ self.class.refusal_envelope("ordinary_merge_selection_mismatch",
340
+ "reference and selectors do not match a canonical ordinary merge record",
341
+ request_id: nil, input_digest: nil,
342
+ schema: Ace::Assign::Atoms::FixedMergeSchemas::STATUS_ERROR_SCHEMA_ID)
343
+ end
344
+
345
+ def status_error(envelope)
346
+ {"status" => "error", "error" => envelope.fetch("error")}
347
+ end
348
+
349
+ def identifiers!(project:, assignment:, attempt:, queue_entry:, request_id:)
350
+ {"project" => project, "assignment" => assignment, "attempt" => attempt,
351
+ "queue_entry" => queue_entry, "request_id" => request_id}.each do |name, value|
352
+ invalid!("invalid #{name} ID", request_id) unless value.is_a?(String) && value.match?(ID)
353
+ end
354
+ end
355
+
356
+ def load_request_document(input, request_id)
357
+ Atoms::ServiceInput.load(input)
358
+ rescue ArgumentError => e
359
+ invalid!(e.message, request_id)
360
+ end
361
+
362
+ def unknown(keys, allowed)
363
+ (keys - allowed).sort.join(", ")
364
+ end
365
+
366
+ # A pre-effect refusal carries its closed code, sanitized message and
367
+ # the request binding for the wire envelope — never a reference.
368
+ OrdinaryMergeRefusal = Class.new(::StandardError) do
369
+ attr_reader :code, :detail, :request_id, :input_digest
370
+
371
+ def initialize(code, detail, request_id: nil, input_digest: nil)
372
+ @code = code
373
+ @detail = detail
374
+ @request_id = request_id
375
+ @input_digest = input_digest
376
+ super(detail)
377
+ end
378
+ end
379
+
380
+ def invalid!(message, request_id)
381
+ raise OrdinaryMergeRefusal.new("ordinary_merge_invalid_request", message, request_id: request_id)
382
+ end
383
+
384
+ def conflict!(message, request_id, digest)
385
+ raise OrdinaryMergeRefusal.new("ordinary_merge_request_conflict", message,
386
+ request_id: request_id, input_digest: digest)
387
+ end
388
+
389
+ # A replay must match every caller-supplied immutable field: command
390
+ # selectors, authorization, input byte meaning and canonical digest.
391
+ def exact_replay?(existing, project:, assignment:, attempt:, queue_entry:,
392
+ authorization:, digest:)
393
+ existing["state"] &&
394
+ existing.slice("project_id", "assignment_id", "attempt_id", "queue_entry",
395
+ "authorization", "input_digest") == {
396
+ "project_id" => project, "assignment_id" => assignment, "attempt_id" => attempt,
397
+ "queue_entry" => queue_entry, "authorization" => authorization, "input_digest" => digest
398
+ }
399
+ end
400
+
401
+ def refused(code, message)
402
+ raise OrdinaryMergeRefusal.new(code, message)
403
+ end
404
+
405
+ # The git toplevel (not a package directory that happens to hold a
406
+ # Rakefile) bounds candidate-head verification for nested invocations.
407
+ def git_toplevel
408
+ out, status = Open3.capture2("git", "rev-parse", "--show-toplevel")
409
+ (status&.success? ? out.strip : Dir.pwd)
410
+ rescue Errno::ENOENT
411
+ Dir.pwd
412
+ end
413
+
414
+ def current_head
415
+ out, _stderr, status = Open3.capture3("git", "rev-parse", "HEAD", chdir: @repo_root)
416
+ refused("ordinary_merge_target_changed", "candidate head is unavailable") unless status.success?
417
+ out.strip
418
+ end
419
+ end
420
+ end
421
+ end
422
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Ace
4
4
  module Lab
5
- VERSION = "0.5.2"
5
+ VERSION = "0.7.0"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ace-lab
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.2
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michal Czyz
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2026-10-12 00:00:00.000000000 Z
10
+ date: 2026-10-16 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: ace-runtime
@@ -71,28 +71,42 @@ dependencies:
71
71
  requirements:
72
72
  - - "~>"
73
73
  - !ruby/object:Gem::Version
74
- version: '0.67'
74
+ version: '0.69'
75
75
  type: :runtime
76
76
  prerelease: false
77
77
  version_requirements: !ruby/object:Gem::Requirement
78
78
  requirements:
79
79
  - - "~>"
80
80
  - !ruby/object:Gem::Version
81
- version: '0.67'
81
+ version: '0.69'
82
82
  - !ruby/object:Gem::Dependency
83
83
  name: ace-hitl
84
84
  requirement: !ruby/object:Gem::Requirement
85
85
  requirements:
86
86
  - - "~>"
87
87
  - !ruby/object:Gem::Version
88
- version: 0.14.0
88
+ version: '0.15'
89
89
  type: :runtime
90
90
  prerelease: false
91
91
  version_requirements: !ruby/object:Gem::Requirement
92
92
  requirements:
93
93
  - - "~>"
94
94
  - !ruby/object:Gem::Version
95
- version: 0.14.0
95
+ version: '0.15'
96
+ - !ruby/object:Gem::Dependency
97
+ name: ace-git-forgejo
98
+ requirement: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - "~>"
101
+ - !ruby/object:Gem::Version
102
+ version: '0.7'
103
+ type: :runtime
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - "~>"
108
+ - !ruby/object:Gem::Version
109
+ version: '0.7'
96
110
  - !ruby/object:Gem::Dependency
97
111
  name: ace-support-test-helpers
98
112
  requirement: !ruby/object:Gem::Requirement
@@ -219,9 +233,16 @@ files:
219
233
  - lib/ace/lab/molecules/caller_authorizer.rb
220
234
  - lib/ace/lab/molecules/capability_router.rb
221
235
  - lib/ace/lab/molecules/exact_resolver.rb
236
+ - lib/ace/lab/molecules/forge_merge_config.rb
237
+ - lib/ace/lab/molecules/forgejo_merge_adapter.rb
222
238
  - lib/ace/lab/molecules/grant_resolver.rb
223
239
  - lib/ace/lab/molecules/hitl_authorizer.rb
224
240
  - lib/ace/lab/molecules/inventory_query.rb
241
+ - lib/ace/lab/molecules/ordinary_admission_resolver.rb
242
+ - lib/ace/lab/molecules/ordinary_ff_merge.rb
243
+ - lib/ace/lab/molecules/ordinary_listener.rb
244
+ - lib/ace/lab/molecules/ordinary_receiver_config.rb
245
+ - lib/ace/lab/molecules/ordinary_socket.rb
225
246
  - lib/ace/lab/molecules/protected_cleanup_owner_admission.rb
226
247
  - lib/ace/lab/molecules/protected_cleanup_owner_client.rb
227
248
  - lib/ace/lab/molecules/protected_cleanup_owner_identity.rb
@@ -232,6 +253,8 @@ files:
232
253
  - lib/ace/lab/molecules/service_policy.rb
233
254
  - lib/ace/lab/molecules/topology_loader.rb
234
255
  - lib/ace/lab/organisms/authority_composition.rb
256
+ - lib/ace/lab/organisms/ordinary_merge_receiver.rb
257
+ - lib/ace/lab/organisms/ordinary_merge_request.rb
235
258
  - lib/ace/lab/organisms/protected_cleanup_owner.rb
236
259
  - lib/ace/lab/organisms/protected_service_client.rb
237
260
  - lib/ace/lab/organisms/protected_service_listener.rb