ace-test-runner-e2e 0.41.0 → 0.43.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/CHANGELOG.md +10 -0
- data/handbook/guides/scenario-yml-reference.g.md +6 -5
- data/handbook/templates/scenario.yml.template.yml +2 -2
- data/lib/ace/test/end_to_end_runner/molecules/pipeline_prompt_bundler.rb +37 -4
- data/lib/ace/test/end_to_end_runner/molecules/release_manifest.rb +27 -1
- data/lib/ace/test/end_to_end_runner/molecules/setup_executor.rb +50 -20
- data/lib/ace/test/end_to_end_runner/organisms/test_orchestrator.rb +1 -1
- data/lib/ace/test/end_to_end_runner/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a10bfe582cd05ba82acd9de7b3cde9164748ae79f10197839d30ce1be1d5aa08
|
|
4
|
+
data.tar.gz: 92cb523accd05c05fbe2e7c76c1778204fb048b9e1794b8956d225bc257f2779
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2bf18a436f8f4148db5acc308df5f2071803c173539a1f6b60c5b6dd631ed176fb3c9dce8ce265af0cc493b07160b59b9373e81af877331cddd21571721694ed
|
|
7
|
+
data.tar.gz: 7c0024804c817351a3dc980b71496ac2d010f28c626206c2def6dd32cce48f35e01d73a9fbf8fd805c4691a3fcf3f2054c56dec51dd877026b0f65594c5a1986
|
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.43.0] - 2026-10-05
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- Frozen release manifests accept validated `runtime_dependencies` declarations for exact source-bound consumer edge verification. Missing required consumer declarations remain a proof failure.
|
|
14
|
+
|
|
15
|
+
## [0.42.0] - 2026-10-04
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
- The `tmux-session` setup step is replaced by `runtime-session`: `runtime: tmux` (default) keeps the detached-session behavior and exports `ACE_TMUX_SESSION` + `ACE_RUNTIME=tmux`; `runtime: herdr` inherits the caller's live herdr environment and exports `ACE_RUNTIME=herdr`, failing explicitly when no herdr context is live (no fabricated stand-in).
|
|
19
|
+
|
|
10
20
|
## [0.41.0] - 2026-09-29
|
|
11
21
|
|
|
12
22
|
### Changed
|
|
@@ -112,17 +112,18 @@ Available directives:
|
|
|
112
112
|
- `copy-fixtures` — Copy fixtures/ directory into sandbox
|
|
113
113
|
- `write-file:` — Write inline content to a file (`path:` + `content:`)
|
|
114
114
|
- `agent-env:` — Environment variables passed to the runner/verifier agent subprocess (not setup commands)
|
|
115
|
-
- `
|
|
116
|
-
- String form: `
|
|
117
|
-
- Hash form: `
|
|
118
|
-
-
|
|
115
|
+
- `runtime-session` — Establish a live terminal runtime context for the scenario
|
|
116
|
+
- String form: `runtime-session` (tmux session with scenario-based naming)
|
|
117
|
+
- Hash form: `runtime-session: { name-source: run-id }` (tmux session named by the E2E run ID)
|
|
118
|
+
- Hash form: `runtime-session: { runtime: herdr }` (inherits the caller's live HERDR_SESSION/HERDR_PANE; fails explicitly when absent)
|
|
119
|
+
- tmux sessions export ACE_TMUX_SESSION; both runtimes export ACE_RUNTIME. tmux sessions are removed by runner teardown after test execution
|
|
119
120
|
|
|
120
121
|
Example:
|
|
121
122
|
|
|
122
123
|
```yaml
|
|
123
124
|
setup:
|
|
124
125
|
- git-init
|
|
125
|
-
-
|
|
126
|
+
- runtime-session:
|
|
126
127
|
name-source: run-id
|
|
127
128
|
- run: "cp ${ACE_E2E_SOURCE_ROOT:-$PROJECT_ROOT_PATH}/mise.toml mise.toml && mise trust mise.toml"
|
|
128
129
|
- copy-fixtures
|
|
@@ -51,14 +51,14 @@ requires:
|
|
|
51
51
|
ruby: ">= 3.0"
|
|
52
52
|
|
|
53
53
|
# Setup directives (executed before test cases)
|
|
54
|
-
# Available: git-init, run:, copy-fixtures, write-file:, agent-env:,
|
|
54
|
+
# Available: git-init, run:, copy-fixtures, write-file:, agent-env:, runtime-session
|
|
55
55
|
# Rules:
|
|
56
56
|
# - setup is fail-fast (do not use `|| true`)
|
|
57
57
|
# - fixtures/setup own sandbox preparation; runner TCs do not re-implement setup
|
|
58
58
|
setup:
|
|
59
59
|
- git-init
|
|
60
60
|
# Optional detached tmux session for test isolation (uses unique run ID)
|
|
61
|
-
# -
|
|
61
|
+
# - runtime-session:
|
|
62
62
|
# name-source: run-id
|
|
63
63
|
- run: "cp ${ACE_E2E_SOURCE_ROOT:-$PROJECT_ROOT_PATH}/mise.toml mise.toml && mise trust mise.toml"
|
|
64
64
|
# Uncomment as needed:
|
|
@@ -163,9 +163,32 @@ module Ace
|
|
|
163
163
|
match ? match[1].upcase : nil
|
|
164
164
|
end
|
|
165
165
|
|
|
166
|
+
# Bundler machinery (installed gem trees, compact-index caches,
|
|
167
|
+
# config, shared-gem dirs) is install plumbing, not verifier
|
|
168
|
+
# evidence: `--full-index` alone pulls a ~97MB compact index, which
|
|
169
|
+
# previously got inlined into the verifier prompt whole.
|
|
170
|
+
BUNDLER_MACHINE_SEGMENTS = %w[
|
|
171
|
+
.bundle .gem bundler-cache bundler-home bundler-config
|
|
172
|
+
compact_index specifications cache bin
|
|
173
|
+
].freeze
|
|
174
|
+
MAX_ARTIFACT_FILE_BYTES = 256 * 1024
|
|
175
|
+
MAX_ARTIFACT_CONTENT_BYTES = 2 * 1024 * 1024
|
|
176
|
+
|
|
177
|
+
def artifact_evidence?(relative_path)
|
|
178
|
+
return false if relative_path.start_with?(".bundle/", ".gem/")
|
|
179
|
+
|
|
180
|
+
BUNDLER_MACHINE_SEGMENTS.none? do |segment|
|
|
181
|
+
relative_path.include?("/#{segment}/") || relative_path.start_with?("#{segment}/")
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
166
185
|
def build_artifact_section(sandbox_path)
|
|
167
186
|
sandbox_path = File.expand_path(sandbox_path)
|
|
168
|
-
files = Dir.glob(File.join(sandbox_path, "results", "**", "*"))
|
|
187
|
+
files = Dir.glob(File.join(sandbox_path, "results", "**", "*"))
|
|
188
|
+
.select { |f| File.file?(f) }
|
|
189
|
+
.map { |f| File.expand_path(f) }
|
|
190
|
+
.select { |f| artifact_evidence?(relative_path(f, sandbox_path)) }
|
|
191
|
+
.sort
|
|
169
192
|
tree_entries = files.map { |f| relative_path(f, sandbox_path) }
|
|
170
193
|
|
|
171
194
|
parts = []
|
|
@@ -186,12 +209,21 @@ module Ace
|
|
|
186
209
|
parts << "## File contents"
|
|
187
210
|
parts << ""
|
|
188
211
|
|
|
212
|
+
content_budget = MAX_ARTIFACT_CONTENT_BYTES
|
|
189
213
|
files.each do |file|
|
|
214
|
+
size = File.size(file)
|
|
215
|
+
body = if size <= MAX_ARTIFACT_FILE_BYTES
|
|
216
|
+
safe_read(file)
|
|
217
|
+
else
|
|
218
|
+
"#{safe_read(file, MAX_ARTIFACT_FILE_BYTES)}\n... [truncated, #{size} bytes total]"
|
|
219
|
+
end
|
|
220
|
+
content_budget -= body.bytesize
|
|
190
221
|
parts << "### `#{relative_path(file, sandbox_path)}`"
|
|
191
222
|
parts << "```"
|
|
192
|
-
parts <<
|
|
223
|
+
parts << body
|
|
193
224
|
parts << "```"
|
|
194
225
|
parts << ""
|
|
226
|
+
break if content_budget <= 0
|
|
195
227
|
end
|
|
196
228
|
|
|
197
229
|
parts.join("\n").rstrip
|
|
@@ -324,8 +356,9 @@ module Ace
|
|
|
324
356
|
File.expand_path(path).sub("#{File.expand_path(root)}/", "")
|
|
325
357
|
end
|
|
326
358
|
|
|
327
|
-
def safe_read(path)
|
|
328
|
-
File.binread(path
|
|
359
|
+
def safe_read(path, limit = nil)
|
|
360
|
+
raw = limit ? File.binread(path, limit) : File.binread(path)
|
|
361
|
+
raw.encode("UTF-8", invalid: :replace, undef: :replace, replace: "?")
|
|
329
362
|
end
|
|
330
363
|
end
|
|
331
364
|
end
|
|
@@ -4,6 +4,7 @@ require "digest"
|
|
|
4
4
|
require "fileutils"
|
|
5
5
|
require "json"
|
|
6
6
|
require "rubygems/version"
|
|
7
|
+
require "rubygems/requirement"
|
|
7
8
|
|
|
8
9
|
module Ace
|
|
9
10
|
module Test
|
|
@@ -124,7 +125,7 @@ module Ace
|
|
|
124
125
|
label = "packages[#{index}]"
|
|
125
126
|
raise Invalid, "#{label} must be an object" unless entry.is_a?(Hash)
|
|
126
127
|
|
|
127
|
-
reject_unknown_keys!(entry, %w[name artifact_version source_sha supersedes], label)
|
|
128
|
+
reject_unknown_keys!(entry, %w[name artifact_version source_sha supersedes runtime_dependencies], label)
|
|
128
129
|
|
|
129
130
|
name = entry["name"]
|
|
130
131
|
unless name.is_a?(String) && name.match?(GEM_NAME_PATTERN)
|
|
@@ -134,9 +135,34 @@ module Ace
|
|
|
134
135
|
validate_exact_version!(entry["artifact_version"], "#{label}(#{name}) artifact_version")
|
|
135
136
|
validate_source_sha!(entry["source_sha"], "#{label}(#{name}) source_sha")
|
|
136
137
|
validate_supersedes!(entry["supersedes"], entry["artifact_version"], "#{label}(#{name})")
|
|
138
|
+
validate_runtime_dependencies!(entry["runtime_dependencies"], label) if entry.key?("runtime_dependencies")
|
|
137
139
|
name
|
|
138
140
|
end
|
|
139
141
|
|
|
142
|
+
# These are frozen source declarations, not requirements inferred
|
|
143
|
+
# from whatever a registry happened to return during the proof.
|
|
144
|
+
def validate_runtime_dependencies!(raw, label)
|
|
145
|
+
unless raw.is_a?(Hash)
|
|
146
|
+
raise Invalid, "#{label} runtime_dependencies must be an object"
|
|
147
|
+
end
|
|
148
|
+
raw.each do |name, requirements|
|
|
149
|
+
unless name.is_a?(String) && name.match?(GEM_NAME_PATTERN) &&
|
|
150
|
+
requirements.is_a?(Array) && !requirements.empty? &&
|
|
151
|
+
requirements.uniq.length == requirements.length
|
|
152
|
+
raise Invalid, "#{label} has invalid runtime dependency declaration"
|
|
153
|
+
end
|
|
154
|
+
requirements.each do |requirement|
|
|
155
|
+
parsed = requirement.is_a?(String) ? Gem::Requirement.new(requirement) : nil
|
|
156
|
+
canonical = parsed&.requirements&.map { |operator, version| "#{operator} #{version}" }
|
|
157
|
+
unless canonical == [requirement]
|
|
158
|
+
raise Invalid, "#{label} runtime dependency requirements must be canonical strings"
|
|
159
|
+
end
|
|
160
|
+
rescue ArgumentError
|
|
161
|
+
raise Invalid, "#{label} has invalid runtime dependency requirement"
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
140
166
|
def validate_exact_version!(raw, label)
|
|
141
167
|
unless raw.is_a?(String) && !raw.strip.empty?
|
|
142
168
|
raise Invalid, "#{label} must be an exact version string, got #{raw.inspect}"
|
|
@@ -17,7 +17,7 @@ module Ace
|
|
|
17
17
|
#
|
|
18
18
|
# Processes the setup array from scenario.yml, running each action
|
|
19
19
|
# via Ruby system calls (no LLM involved). Supports: git-init,
|
|
20
|
-
# copy-fixtures, run, write-file, agent-env,
|
|
20
|
+
# copy-fixtures, run, write-file, agent-env, runtime-session, and
|
|
21
21
|
# release-manifest actions.
|
|
22
22
|
#
|
|
23
23
|
# Note: This is a Molecule because it performs filesystem I/O and
|
|
@@ -28,6 +28,7 @@ module Ace
|
|
|
28
28
|
STRIPPED_ENV_KEYS = %w[RUBYOPT RUBYLIB].freeze
|
|
29
29
|
RESERVED_ENV_KEYS = Molecules::SandboxRuntimeBuilder::RESERVED_ENV_KEYS + %w[
|
|
30
30
|
PATH HOME TMPDIR XDG_RUNTIME_DIR TMUX_TMPDIR ACE_TMUX_SESSION
|
|
31
|
+
ACE_RUNTIME HERDR_SESSION HERDR_PANE HERDR_WORKSPACE_ID
|
|
31
32
|
]
|
|
32
33
|
RELEASE_MANIFEST_DEFAULT_PATH = File.join(".ace-local", "release", "installation-manifest.json")
|
|
33
34
|
# Retry trust state lives in the report directory — host-side,
|
|
@@ -56,14 +57,14 @@ module Ace
|
|
|
56
57
|
# @param setup_steps [Array] Setup steps from scenario.yml
|
|
57
58
|
# @param sandbox_dir [String] Path to the sandbox directory
|
|
58
59
|
# @param fixture_source [String, nil] Path to the fixtures/ directory
|
|
59
|
-
# @param scenario_name [String, nil] Test ID for
|
|
60
|
-
# @param run_id [String, nil] Unique run ID for deterministic
|
|
60
|
+
# @param scenario_name [String, nil] Test ID for runtime session naming (e.g., "TS-OVERSEER-001")
|
|
61
|
+
# @param run_id [String, nil] Unique run ID for deterministic runtime session naming
|
|
61
62
|
# @param release_manifest_path [String, nil] Explicit absolute manifest path carried
|
|
62
63
|
# from the invoking process (ACE_RELEASE_MANIFEST). When nil, the release-manifest
|
|
63
64
|
# step falls back to the default path under ACE_E2E_SOURCE_ROOT.
|
|
64
65
|
# @param state_file [String, nil] Host-side path (report directory) for retry
|
|
65
66
|
# state. When nil, no retry state is recorded and the sandbox is never reused.
|
|
66
|
-
# @return [Hash] Result with :success, :steps_completed, :error, :env, :
|
|
67
|
+
# @return [Hash] Result with :success, :steps_completed, :error, :env, :runtime_session keys
|
|
67
68
|
def execute(setup_steps:, sandbox_dir:, fixture_source: nil, scenario_name: nil, run_id: nil, initial_env: {},
|
|
68
69
|
git_excludes: [], release_manifest_path: nil, state_file: nil)
|
|
69
70
|
# Initialized before any filesystem operation: an early failure
|
|
@@ -71,7 +72,7 @@ module Ace
|
|
|
71
72
|
# secondary error from nil state.
|
|
72
73
|
env = {}
|
|
73
74
|
steps_completed = 0
|
|
74
|
-
@
|
|
75
|
+
@runtime_session = nil
|
|
75
76
|
@teardown_env = nil
|
|
76
77
|
FileUtils.mkdir_p(sandbox_dir)
|
|
77
78
|
# A rerun invalidates any earlier success up front: state is
|
|
@@ -94,7 +95,7 @@ module Ace
|
|
|
94
95
|
@release_manifest_input = release_manifest_path
|
|
95
96
|
@release_manifest_state = nil
|
|
96
97
|
steps_completed = 0
|
|
97
|
-
@
|
|
98
|
+
@runtime_session = nil
|
|
98
99
|
@scenario_name = scenario_name
|
|
99
100
|
@run_id = run_id
|
|
100
101
|
@teardown_env = nil
|
|
@@ -137,7 +138,7 @@ module Ace
|
|
|
137
138
|
steps_completed: steps_completed,
|
|
138
139
|
error: nil,
|
|
139
140
|
env: merged,
|
|
140
|
-
|
|
141
|
+
runtime_session: @runtime_session
|
|
141
142
|
}
|
|
142
143
|
rescue => e
|
|
143
144
|
{
|
|
@@ -145,24 +146,24 @@ module Ace
|
|
|
145
146
|
steps_completed: steps_completed,
|
|
146
147
|
error: e.message,
|
|
147
148
|
env: merged_environment(env),
|
|
148
|
-
|
|
149
|
+
runtime_session: @runtime_session
|
|
149
150
|
}
|
|
150
151
|
end
|
|
151
152
|
|
|
152
|
-
# Clean up resources created during setup (e.g. tmux session)
|
|
153
|
+
# Clean up resources created during setup (e.g. tmux runtime session)
|
|
153
154
|
def teardown
|
|
154
|
-
return unless @
|
|
155
|
+
return unless @runtime_session
|
|
155
156
|
|
|
156
157
|
if @sandbox_backend
|
|
157
158
|
@sandbox_backend.capture3(
|
|
158
|
-
["tmux", "kill-session", "-t", @
|
|
159
|
+
["tmux", "kill-session", "-t", @runtime_session],
|
|
159
160
|
chdir: @teardown_env&.fetch("PROJECT_ROOT_PATH", Dir.pwd) || Dir.pwd,
|
|
160
161
|
env: @teardown_env || {}
|
|
161
162
|
)
|
|
162
163
|
else
|
|
163
|
-
@system_runner.call("tmux", "kill-session", "-t", @
|
|
164
|
+
@system_runner.call("tmux", "kill-session", "-t", @runtime_session, out: File::NULL, err: File::NULL)
|
|
164
165
|
end
|
|
165
|
-
@
|
|
166
|
+
@runtime_session = nil
|
|
166
167
|
end
|
|
167
168
|
|
|
168
169
|
private
|
|
@@ -179,8 +180,8 @@ module Ace
|
|
|
179
180
|
handle_git_init(sandbox_dir, env)
|
|
180
181
|
when "copy-fixtures"
|
|
181
182
|
handle_copy_fixtures(sandbox_dir, fixture_source)
|
|
182
|
-
when "
|
|
183
|
-
|
|
183
|
+
when "runtime-session"
|
|
184
|
+
handle_runtime_session(env)
|
|
184
185
|
when Hash
|
|
185
186
|
execute_hash_step(step, sandbox_dir, env)
|
|
186
187
|
else
|
|
@@ -200,8 +201,8 @@ module Ace
|
|
|
200
201
|
handle_write_file(value["path"], value["content"], sandbox_dir)
|
|
201
202
|
when "agent-env"
|
|
202
203
|
handle_env(value, env)
|
|
203
|
-
when "
|
|
204
|
-
|
|
204
|
+
when "runtime-session"
|
|
205
|
+
handle_runtime_session(env, value)
|
|
205
206
|
when "release-manifest"
|
|
206
207
|
handle_release_manifest(value, sandbox_dir, env)
|
|
207
208
|
else
|
|
@@ -209,8 +210,22 @@ module Ace
|
|
|
209
210
|
end
|
|
210
211
|
end
|
|
211
212
|
|
|
212
|
-
#
|
|
213
|
-
|
|
213
|
+
# Establish a live terminal runtime context for the scenario.
|
|
214
|
+
# runtime "tmux" (default) creates an isolated detached tmux
|
|
215
|
+
# session and exports ACE_TMUX_SESSION + ACE_RUNTIME=tmux.
|
|
216
|
+
# runtime "herdr" requires the caller's live HERDR_SESSION and
|
|
217
|
+
# HERDR_PANE (herdr workspaces are operator-owned; the runner
|
|
218
|
+
# never fabricates one) and exports ACE_RUNTIME=herdr. An absent
|
|
219
|
+
# herdr context fails the step explicitly — no scripted stand-in.
|
|
220
|
+
def handle_runtime_session(env, config = nil)
|
|
221
|
+
runtime = config.is_a?(Hash) ? config["runtime"].to_s.strip : ""
|
|
222
|
+
runtime = "tmux" if runtime.empty?
|
|
223
|
+
unless %w[tmux herdr].include?(runtime)
|
|
224
|
+
raise ArgumentError, "runtime-session supports runtimes: tmux, herdr (got '#{runtime}')"
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
return handle_herdr_session(env) if runtime == "herdr"
|
|
228
|
+
|
|
214
229
|
name_source = config.is_a?(Hash) ? config["name-source"] : nil
|
|
215
230
|
session_name = if name_source == "run-id" && @run_id && !@run_id.to_s.empty?
|
|
216
231
|
@run_id
|
|
@@ -229,8 +244,23 @@ module Ace
|
|
|
229
244
|
end
|
|
230
245
|
raise "Failed to create tmux session '#{session_name}': #{stderr.strip}" unless status.success?
|
|
231
246
|
|
|
232
|
-
@
|
|
247
|
+
@runtime_session = session_name
|
|
233
248
|
env["ACE_TMUX_SESSION"] = session_name
|
|
249
|
+
env["ACE_RUNTIME"] = "tmux"
|
|
250
|
+
@teardown_env = merged_environment(env)
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def handle_herdr_session(env)
|
|
254
|
+
merged = merged_environment(env)
|
|
255
|
+
herdr_session = merged["HERDR_SESSION"].to_s.strip
|
|
256
|
+
herdr_pane = merged["HERDR_PANE"].to_s.strip
|
|
257
|
+
if herdr_session.empty? || herdr_pane.empty?
|
|
258
|
+
raise "runtime-session herdr requires a live herdr context (HERDR_SESSION and HERDR_PANE); run this scenario inside herdr"
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
env["HERDR_SESSION"] = herdr_session
|
|
262
|
+
env["HERDR_PANE"] = herdr_pane
|
|
263
|
+
env["ACE_RUNTIME"] = "herdr"
|
|
234
264
|
@teardown_env = merged_environment(env)
|
|
235
265
|
end
|
|
236
266
|
|
|
@@ -396,7 +396,7 @@ module Ace
|
|
|
396
396
|
# Transient resources (tmux sessions) die with the first
|
|
397
397
|
# run's teardown; such scenarios always re-set-up on retry.
|
|
398
398
|
declares_transient = effective_steps.any? do |step|
|
|
399
|
-
step == "
|
|
399
|
+
step == "runtime-session" || (step.is_a?(Hash) && step.key?("runtime-session"))
|
|
400
400
|
end
|
|
401
401
|
state = declares_transient ? nil : Molecules::SetupExecutor.setup_state_for(
|
|
402
402
|
state_file, sandbox_path, setup_steps: effective_steps
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ace-test-runner-e2e
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.43.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Michal Czyz
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-
|
|
10
|
+
date: 2026-10-05 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: ace-support-cli
|