ace-hitl 0.15.0 → 0.16.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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/.ace-defaults/hitl/config.yml +0 -0
  3. data/.ace-defaults/nav/protocols/skill-sources/ace-hitl.yml +0 -0
  4. data/.ace-defaults/nav/protocols/wfi-sources/ace-hitl.yml +0 -0
  5. data/CHANGELOG.md +10 -0
  6. data/Rakefile +0 -0
  7. data/docs/demo/ace-hitl-scope-and-answer.tape.yml +0 -0
  8. data/docs/demo/fixtures/demo-context.md +0 -0
  9. data/docs/usage.md +12 -2
  10. data/handbook/README.md +0 -0
  11. data/handbook/skills/as-hitl/SKILL.md +0 -0
  12. data/handbook/workflow-instructions/hitl.wf.md +0 -0
  13. data/lib/ace/hitl/atoms/hitl_effect_validator.rb +0 -0
  14. data/lib/ace/hitl/atoms/hitl_file_pattern.rb +0 -0
  15. data/lib/ace/hitl/atoms/hitl_id_formatter.rb +0 -0
  16. data/lib/ace/hitl/cli/commands/create.rb +0 -0
  17. data/lib/ace/hitl/cli/commands/list.rb +0 -0
  18. data/lib/ace/hitl/cli/commands/serve.rb +1 -0
  19. data/lib/ace/hitl/cli/commands/show.rb +0 -0
  20. data/lib/ace/hitl/lifecycle/policy.rb +34 -3
  21. data/lib/ace/hitl/lifecycle/proposals.rb +12 -0
  22. data/lib/ace/hitl/lifecycle/service.rb +5 -3
  23. data/lib/ace/hitl/lifecycle/tls_access_policy.rb +12 -1
  24. data/lib/ace/hitl/lifecycle/tls_authority.rb +14 -5
  25. data/lib/ace/hitl/lifecycle/tls_service.rb +4 -3
  26. data/lib/ace/hitl/models/hitl_event.rb +0 -0
  27. data/lib/ace/hitl/molecules/hitl_answer_editor.rb +0 -0
  28. data/lib/ace/hitl/molecules/hitl_config_loader.rb +0 -0
  29. data/lib/ace/hitl/molecules/hitl_creator.rb +0 -0
  30. data/lib/ace/hitl/molecules/hitl_display_formatter.rb +0 -0
  31. data/lib/ace/hitl/molecules/hitl_loader.rb +0 -0
  32. data/lib/ace/hitl/molecules/hitl_resolver.rb +0 -0
  33. data/lib/ace/hitl/molecules/hitl_scanner.rb +0 -0
  34. data/lib/ace/hitl/molecules/resume_dispatcher.rb +0 -0
  35. data/lib/ace/hitl/molecules/worktree_scope_resolver.rb +0 -0
  36. data/lib/ace/hitl/proposals/policy.rb +1 -0
  37. data/lib/ace/hitl/providers/lab/assignment_binding.rb +20 -1
  38. data/lib/ace/hitl/version.rb +1 -1
  39. metadata +20 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '0827bd8ade04c613021128ac335a1d0fea74d5954e718122bd243a780bebb675'
4
- data.tar.gz: df48a9612ca6a87670142ee8f511a837e206af2294a1a609be8045bfc14c1830
3
+ metadata.gz: 6d2bae48cdaf02e617f52c254eb78f5a8dae316be06a8787d4282041c01eacc3
4
+ data.tar.gz: 81410404b3ad0e07900497a9f62594aba56aaf987ed2d816d208bcab112b096c
5
5
  SHA512:
6
- metadata.gz: b0f0b827b307e8baf05a25c9503455fde43c8734e121f7d3f5f222a4d0a0aa201653bb346366fd6b8445c700031113068ff133cf24e4566b825e4bf59633f622
7
- data.tar.gz: 462e2cef1a68a7a4a3975b96b324977223101a53ba384940875197e61c103054d3e52cbb83d4337f98aea0f5c92dfd306d0c372caa61e2bd8ab20517aca73cf3
6
+ metadata.gz: e5ad8fad6083826ca4f1fb6220d7bf87bd302482bbbe946cc93778bf83279d3cf728898e9f2c9bb286ebac9e98a278b5129ab3914d1ee7b354c2befd07400129
7
+ data.tar.gz: 1b0bae700322c0fae15a9f6ab81a7efd5faad863db9f729115909c95e66087be26b2217ad037e5e21ea3a7e7dc7bd418afa3dd1b01654f8f5fcda36007fd5b3d
File without changes
File without changes
data/CHANGELOG.md CHANGED
@@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.16.0] - 2026-10-16
11
+
12
+ ### Added
13
+
14
+ - Require explicit predelegation for sixteen-hour proposal decisions and bind ordinary project proposals to the selected canonical owner. Refresh project and owner grants at transitions and replay; compose existing Unix/TLS listeners under the authority lifetime.
15
+
16
+ ### Fixed
17
+
18
+ - Read TLS authority configuration through bounded protected descriptors, rejecting symlinks, unsafe ancestors, special files and concurrent replacement before startup. Declare the direct Runtime dependency used by this reader.
19
+
10
20
  ## [0.15.0] - 2026-10-14
11
21
 
12
22
  ### Added
data/Rakefile CHANGED
File without changes
File without changes
File without changes
data/docs/usage.md CHANGED
@@ -358,8 +358,11 @@ The JSON proposal requires operation, target (`resource` and optional `artifact_
358
358
  candidate_head, input_digest, context, options, recommendation and prerequisites; rationale
359
359
  is optional. Contents are bounded and non-secret. Input digest uses the canonical service
360
360
  input digest. Candidate head is separate from base head and the evidence journal commit.
361
- Every precisely presented operation is eligible for sixteen-hour silence, including
362
- publishing, deployment and access/privilege expansion. Fixed service scope, current head,
361
+ Only operation classes explicitly delegated in trusted project policy are eligible
362
+ for sixteen-hour silence. Delegation must exist when the revision is created and
363
+ remain current at the deadline; a later grant cannot approve an older revision.
364
+ Without delegation the proposal still accepts an explicit correlated decision.
365
+ Fixed service scope, current head,
363
366
  independently executed review/tests, bound running attempt and operation-specific OTP gates
364
367
  still apply at execution. Proposal authorization never supplies credentials or runs a callback.
365
368
 
@@ -423,6 +426,8 @@ hitl:
423
426
  service_uid: 1200
424
427
  transport_uids: [1201]
425
428
  proposal_uids: [1202]
429
+ silence_operations:
430
+ ace: [merge]
426
431
  authorization:
427
432
  principals:
428
433
  "1201":
@@ -438,6 +443,11 @@ resolve silence. Missing role or project admission refuses the operation,
438
443
  including direct library calls. This is a source configuration contract;
439
444
  installed deployment adoption remains part of gad.8 acceptance.
440
445
 
446
+ `silence_operations` is an exact project-to-operation list, with no wildcards or
447
+ implicit publish/deploy/access grants. It never expands command or credential
448
+ authority. TLS authorities read the same trusted grants document selected by
449
+ `--grants-path`; certificate authentication alone grants no silence decision.
450
+
441
451
  ### Controlled installed proposal verification
442
452
 
443
453
  From an ACE checkout with Ruby, tmux and the complete dependency archives cached locally:
data/handbook/README.md CHANGED
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -69,6 +69,7 @@ module Ace
69
69
  binding: binding_policy,
70
70
  project: authority.project_id,
71
71
  credential: authority.credential,
72
+ decision_policy: Providers::Lab.grants_policy(grants_path: grants_path(options)),
72
73
  ssl_context: authority.ssl_context!,
73
74
  listen: authority.listen,
74
75
  port: authority.port,
File without changes
@@ -105,6 +105,14 @@ module Ace
105
105
  def proposal?(_peer, project:)
106
106
  false
107
107
  end
108
+
109
+ def proposal_silence?(caller_uid:, requester:, project:, operation:)
110
+ false
111
+ end
112
+
113
+ def silence_operation?(project:, operation:)
114
+ false
115
+ end
108
116
  # @return [Boolean] true when the peer may deliver answers and
109
117
  # observe pending/states for the given project
110
118
  def transport?(_peer, project: nil)
@@ -151,13 +159,36 @@ module Ace
151
159
  # an ordinary requester or transport grant.
152
160
  def proposal?(peer, project:)
153
161
  current = document
162
+ proposal_principal?(peer.uid, project, current)
163
+ end
164
+
165
+ # A proposer grant allows asking. Only this separate, exact project
166
+ # operation list delegates a decision after the acknowledged window.
167
+ def proposal_silence?(caller_uid:, requester:, project:, operation:)
168
+ current = document
169
+ proposal_principal?(caller_uid, project, current) &&
170
+ delegated_operation?(project, operation, current)
171
+ end
172
+
173
+ def silence_operation?(project:, operation:)
174
+ delegated_operation?(project, operation, document)
175
+ end
176
+
177
+ private
178
+
179
+ def proposal_principal?(uid, project, current)
154
180
  uids = hitl_facts(current)["proposal_uids"]
155
- projects = authorized_projects(peer.uid, current)
156
- uids.is_a?(Array) && uids.include?(peer.uid) && projects &&
181
+ projects = authorized_projects(uid, current)
182
+ uid.is_a?(Integer) && uids.is_a?(Array) && uids.include?(uid) && projects &&
157
183
  (projects.empty? || projects.include?(project.to_s))
158
184
  end
159
185
 
160
- private
186
+ def delegated_operation?(project, operation, current)
187
+ delegations = hitl_facts(current)["silence_operations"]
188
+ operations = delegations.is_a?(Hash) && delegations[project]
189
+ operations.is_a?(Array) && operation.is_a?(String) &&
190
+ operation.match?(/\A[a-z][a-z0-9-]{0,63}\z/) && operations.include?(operation)
191
+ end
161
192
 
162
193
  def document
163
194
  @document || TrustedFile.read_yaml(@grants_path) || {}
@@ -54,6 +54,7 @@ module Ace
54
54
  claims = proposal_journal(project).proposal_claims(record["revision_id"])
55
55
  claim = claims.last
56
56
  outcome = case claim && claim["state"]
57
+ when "pending" then "queued"
57
58
  when "accepted" then "executing"
58
59
  when "succeeded" then "succeeded"
59
60
  when "failed", "failed-settled", "rejected" then "failed"
@@ -88,6 +89,7 @@ module Ace
88
89
  record = proposal_for_request!(request, project: project)
89
90
  require_transport!("proposal reconciliation", {"project" => record["project_id"]})
90
91
  proposal_transition(record) do |current|
92
+ next current unless silence_delegated?(current)
91
93
  Ace::Hitl::Proposals::Policy.resolve(current, checkpoint: checkpoint, now: proposal_now)
92
94
  end
93
95
  end
@@ -136,6 +138,11 @@ module Ace
136
138
 
137
139
  private
138
140
 
141
+ def silence_delegated?(record)
142
+ @policy.proposal_silence?(caller_uid: record["caller_uid"], requester: record["requester"],
143
+ project: record["project_id"], operation: record["operation"])
144
+ end
145
+
139
146
  def require_proposer!(project)
140
147
  unless @policy.respond_to?(:proposal?) && @policy.proposal?(@identity, project: project)
141
148
  raise PermissionError, "proposal requires the installed second-commander decision principal"
@@ -189,6 +196,8 @@ module Ace
189
196
  def proposal_transition(record, &policy)
190
197
  proposal_journal(record.fetch("project_id")).change_proposal(record["proposal_id"], assignment_id: record["assignment_id"],
191
198
  attempt_id: record["attempt_id"]) do |current|
199
+ # Selection may change while waiting for the canonical ref lock.
200
+ proposal_journal(record.fetch("project_id"))
192
201
  # A reply to an older revision cannot modify the new decision.
193
202
  next current unless current["request_id"] == record["request_id"]
194
203
  policy.call(current)
@@ -203,6 +212,7 @@ module Ace
203
212
  replay = nil
204
213
  record = @binding.with_active(assignment: assignment, attempt: attempt, project: project, requester: @identity.username) do
205
214
  proposal_journal(project).change_proposal(id, assignment_id: assignment, attempt_id: attempt) do |previous|
215
+ proposal_journal(project)
206
216
  if operation_id
207
217
  replay = proposal_revision_replay(id, expected_revision, operation_id, content, project: project)
208
218
  next previous if replay
@@ -228,6 +238,8 @@ module Ace
228
238
  "attempt_id" => attempt, "project_id" => project, "requester" => @identity.username,
229
239
  "caller_uid" => @identity.uid, "content_digest" => Digest::SHA256.hexdigest(JSON.generate(content)),
230
240
  "state" => "awaiting-delivery", "created_at" => proposal_now,
241
+ "silence_delegated" => @policy.proposal_silence?(caller_uid: @identity.uid,
242
+ requester: @identity.username, project: project, operation: content.fetch("operation")),
231
243
  "authorization" => request, "lifecycle_request" => projection).tap do |value|
232
244
  if operation_id
233
245
  value["revision_operation_id"] = operation_id
@@ -186,10 +186,12 @@ module Ace
186
186
  # the service identity or root — otherwise anyone who can replace
187
187
  # the socket can impersonate the service.
188
188
  def verify_control_group!
189
- group = Etc.getgrnam(@group)
190
- if group.gid.zero? || BROAD_CONTROL_GROUPS.include?(group.name)
189
+ # Named broad groups are forbidden even on a host where that
190
+ # deployment group has not been created yet.
191
+ group = Etc.getgrnam(@group) unless BROAD_CONTROL_GROUPS.include?(@group)
192
+ if group.nil? || group.gid.zero? || BROAD_CONTROL_GROUPS.include?(group.name)
191
193
  raise TransportError,
192
- "HITL boundary control group is too broad: #{group.name} " \
194
+ "HITL boundary control group is too broad: #{@group} " \
193
195
  "(configure a dedicated lifecycle group; root and #{Store::DEFAULT_GROUP} are refused)"
194
196
  end
195
197
 
@@ -10,8 +10,9 @@ module Ace
10
10
  # even when issued by the same CA, and no operation against any
11
11
  # other project is reachable through this authority.
12
12
  class TlsAccessPolicy < AccessPolicy
13
- def initialize(project:)
13
+ def initialize(project:, decision_policy: AccessPolicy.new)
14
14
  @project = project.to_s
15
+ @decision_policy = decision_policy
15
16
  end
16
17
 
17
18
  def transport?(peer, project: nil)
@@ -22,6 +23,16 @@ module Ace
22
23
  transport?(peer, project: project)
23
24
  end
24
25
 
26
+ def proposal_silence?(caller_uid:, requester:, project:, operation:)
27
+ return false unless project == @project
28
+ # The transport peer is authenticated separately. A local proposal
29
+ # retains its original kernel principal when Hermes reconciles it.
30
+ return @decision_policy.proposal_silence?(caller_uid: caller_uid, requester: requester,
31
+ project: project, operation: operation) unless caller_uid.nil?
32
+ requester == "urn:ace:hitl:#{@project}:hermes-client" &&
33
+ @decision_policy.silence_operation?(project: project, operation: operation)
34
+ end
35
+
25
36
  # A TLS authority has no Unix service uid; client endpoint
26
37
  # verification is certificate-based.
27
38
  def service_uid
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "ipaddr"
4
4
  require "json"
5
+ require "ace/runtime/molecules/protected_artifact_set"
5
6
  require_relative "tls_credentials"
6
7
  require_relative "credential_path_policy"
7
8
  require_relative "tls_material"
@@ -22,16 +23,24 @@ module Ace
22
23
  SCHEMA = "ace.hitl.tls.authority/v1"
23
24
  FIELDS = %w[schema project_id listen port service_user service_group
24
25
  tls_server_credential].freeze
26
+ MAX_BYTES = 65_536
25
27
 
26
28
  attr_reader :project_id, :listen, :port, :service_user, :service_group,
27
29
  :credential, :path
28
30
 
29
31
  def self.load(path)
30
- config = JSON.parse(File.read(path))
31
- authority = new(config, path: path)
32
- CredentialPathPolicy.verify_runtime_file!(path, authority.service_group)
33
- authority
34
- rescue JSON::ParserError, SystemCallError, IOError
32
+ reader = Ace::Runtime::Molecules::ProtectedArtifactSet.new(file_limit: MAX_BYTES, total_limit: MAX_BYTES)
33
+ reader.with do |held|
34
+ bytes, = held.read_path!(path)
35
+ config = JSON.parse(bytes, create_additions: false, allow_duplicate_key: false, max_nesting: 16)
36
+ authority = new(config, path: path)
37
+ CredentialPathPolicy.verify_runtime_file!(path, service_group: authority.service_group)
38
+ # Exact mode/group validation and parsing must refer to the same
39
+ # bounded protected inode, including every retained ancestor.
40
+ held.verify_unchanged!
41
+ authority
42
+ end
43
+ rescue JSON::ParserError, SystemCallError, IOError, Ace::Runtime::Error
35
44
  raise TransportError, "the HITL TLS authority configuration is unavailable or malformed: #{path}"
36
45
  end
37
46
 
@@ -35,7 +35,7 @@ module Ace
35
35
 
36
36
  def initialize(root:, binding:, project:, credential:, ssl_context:,
37
37
  listen: "127.0.0.1", port:, group: Store::DEFAULT_GROUP, vault: nil, logger: nil,
38
- idle_seconds: DEFAULT_IDLE_SECONDS, proposal_clock: -> { Time.now.utc })
38
+ idle_seconds: DEFAULT_IDLE_SECONDS, proposal_clock: -> { Time.now.utc }, decision_policy: AccessPolicy.new)
39
39
  @root = root
40
40
  @binding = binding
41
41
  @project = TlsCredentials.project_id(project)
@@ -48,6 +48,7 @@ module Ace
48
48
  @logger = logger
49
49
  @idle_seconds = idle_seconds
50
50
  @proposal_clock = proposal_clock
51
+ @policy = TlsAccessPolicy.new(project: @project, decision_policy: decision_policy)
51
52
  @max_connections = MAX_CONNECTIONS
52
53
  @connections = 0
53
54
  @handler_threads = []
@@ -223,7 +224,7 @@ module Ace
223
224
  Store.new(
224
225
  root: @root,
225
226
  binding: @binding,
226
- policy: TlsAccessPolicy.new(project: @project),
227
+ policy: @policy,
227
228
  identity: peer,
228
229
  vault: @vault,
229
230
  proposal_clock: @proposal_clock, stopping: -> { @stopping }, group: @group
@@ -233,7 +234,7 @@ module Ace
233
234
  end
234
235
 
235
236
  def root_store
236
- Store.new(root: @root, binding: @binding, policy: TlsAccessPolicy.new(project: @project), group: @group)
237
+ Store.new(root: @root, binding: @binding, policy: @policy, group: @group)
237
238
  end
238
239
  end
239
240
  end
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -108,6 +108,7 @@ module Ace
108
108
 
109
109
  def resolve(record, checkpoint:, now:)
110
110
  return record unless record["state"] == "awaiting-decision"
111
+ return record unless record["silence_delegated"] == true
111
112
  return record if timestamp(now) < timestamp(record.fetch("deadline"))
112
113
  valid = checkpoint["schema"] == "ace.hitl.hermes.ingress-checkpoint/v1" &&
113
114
  checkpoint["request"] == record["request_id"] && checkpoint["revision"] == record["revision_id"] &&
@@ -16,9 +16,14 @@ module Ace
16
16
  # authority. Every doubt maps to the classified BindingError —
17
17
  # unknown identity/authority is an error, never permission.
18
18
  class AssignmentBinding < Lifecycle::Binding
19
- def initialize(coordinator: nil, repo_root: nil)
19
+ def initialize(coordinator: nil, repo_root: nil, project: nil, owner_guard: nil)
20
+ unless (project.nil? && owner_guard.nil?) ||
21
+ (project.is_a?(String) && !project.empty? && owner_guard.respond_to?(:call))
22
+ raise ArgumentError, "installed binding needs both a fixed project and its owner guard"
23
+ end
20
24
  @coordinator = coordinator
21
25
  @repo_root = repo_root
26
+ @project, @owner_guard = project, owner_guard
22
27
  end
23
28
 
24
29
  def validate_request(assignment:, attempt:, project:, requester:, caller_pid: nil)
@@ -30,6 +35,7 @@ module Ace
30
35
  # Read-only owner verification can run inside an already-held
31
36
  # assignment exclusion without reacquiring that exclusion.
32
37
  def reverse_address(assignment:, attempt:, project:, caller_pid:)
38
+ verify_selection!(project)
33
39
  unless caller_pid.is_a?(Integer) && caller_pid.positive?
34
40
  raise Lifecycle::BindingError, "kernel requester process identity is unavailable"
35
41
  end
@@ -49,15 +55,26 @@ module Ace
49
55
  unless project.is_a?(String) && !project.empty?
50
56
  raise Lifecycle::BindingError, "proposal requires an explicit project"
51
57
  end
58
+ verify_selection!(project)
52
59
  coordinator.send(:journal_for)
53
60
  end
54
61
 
55
62
  def proposal_projects
63
+ if @project
64
+ verify_selection!(@project)
65
+ return [@project]
66
+ end
56
67
  coordinator.send(:journal_for).proposals.map { |record| record.fetch("project_id") }.uniq.sort
57
68
  end
58
69
 
59
70
  private
60
71
 
72
+ def verify_selection!(project)
73
+ return unless @project
74
+ raise Lifecycle::BindingError, "project differs from installed HITL owner" unless project == @project
75
+ @owner_guard.call
76
+ end
77
+
61
78
  def with_verified(assignment_id, attempt_id, project_id, requester)
62
79
  # ONLY the authority verification is error-mapped: the
63
80
  # caller's transition (answer hand-off, commits, effects)
@@ -66,12 +83,14 @@ module Ace
66
83
  # failure (review 8x327bud). The exclusion still spans the
67
84
  # whole transition, so the phase flag separates the two.
68
85
  verifying = true
86
+ verify_selection!(project_id)
69
87
  coordinator.with_verified_attempt(
70
88
  assignment_id: assignment_id,
71
89
  attempt_id: attempt_id,
72
90
  project_id: project_id,
73
91
  requester: requester
74
92
  ) do |attempt|
93
+ verify_selection!(project_id)
75
94
  verifying = false
76
95
  yield attempt
77
96
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Ace
4
4
  module Hitl
5
- VERSION = "0.15.0"
5
+ VERSION = "0.16.0"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ace-hitl
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.15.0
4
+ version: 0.16.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michal Czyz
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2026-10-14 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-hitl-contract
@@ -113,28 +113,42 @@ dependencies:
113
113
  requirements:
114
114
  - - "~>"
115
115
  - !ruby/object:Gem::Version
116
- version: '0.67'
116
+ version: '0.70'
117
117
  type: :runtime
118
118
  prerelease: false
119
119
  version_requirements: !ruby/object:Gem::Requirement
120
120
  requirements:
121
121
  - - "~>"
122
122
  - !ruby/object:Gem::Version
123
- version: '0.67'
123
+ version: '0.70'
124
124
  - !ruby/object:Gem::Dependency
125
125
  name: ace-herdr
126
126
  requirement: !ruby/object:Gem::Requirement
127
127
  requirements:
128
128
  - - "~>"
129
129
  - !ruby/object:Gem::Version
130
- version: 0.5.1
130
+ version: '0.6'
131
131
  type: :runtime
132
132
  prerelease: false
133
133
  version_requirements: !ruby/object:Gem::Requirement
134
134
  requirements:
135
135
  - - "~>"
136
136
  - !ruby/object:Gem::Version
137
- version: 0.5.1
137
+ version: '0.6'
138
+ - !ruby/object:Gem::Dependency
139
+ name: ace-runtime
140
+ requirement: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - "~>"
143
+ - !ruby/object:Gem::Version
144
+ version: '0.3'
145
+ type: :runtime
146
+ prerelease: false
147
+ version_requirements: !ruby/object:Gem::Requirement
148
+ requirements:
149
+ - - "~>"
150
+ - !ruby/object:Gem::Version
151
+ version: '0.3'
138
152
  description: Provides the dedicated ace-hitl package surface for human in the loop
139
153
  (HITL) semantics and CLI workflows.
140
154
  email: