ace-test-runner-e2e 0.41.0 → 0.42.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '023288b5afdd9d9303ed0a4b439503062611cb9cd364e29f68aef91dab4a85c6'
4
- data.tar.gz: 30026fd012aa74ebe994d0f41b0bcd809d6bf0599f4a842ad087756e88592d8b
3
+ metadata.gz: f8c03365266949f68dd3364885060b9c27b4087d3ff84b0b42ac5c231ab092ed
4
+ data.tar.gz: ea5b5009323c90d85ec27e417ae012ea09c2af8838b248bf8057caf2fd9174cd
5
5
  SHA512:
6
- metadata.gz: '062688fc71b5c282c9789c0c60cd1242a4b454436f6f98cfb39475fccc33025da06962c5e18c85728766dd59c3fe49aaff35bc987c4585c75b5218d5027c0dd1'
7
- data.tar.gz: c06d63cd12734b3ac962e0699ecab8934e7f8797ae92b8ee0cf4fef2c3992c980e1e2f664b93e7d844b2bf7564b1003513d8b1c0ec51808e7fee6742d9a49645
6
+ metadata.gz: e3a65486f96beb2288ca5648d16305da412e1d90f69afc6586818db90f029e8d3dbb3037ca3a780619cf0b067d18d2c96b739dcdf724c7a4dd6ad0a090b00dce
7
+ data.tar.gz: 34c8755617d888e23307c21c53b21fd0de777195c6c6ac77d7eaf79cc3cc0efe8ba9fd5b8e1741e5ddb29ccaea7d9c181eb10c80b541d026d24d2713edadda9d
data/CHANGELOG.md CHANGED
@@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.42.0] - 2026-10-04
11
+
12
+ ### Added
13
+ - 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).
14
+
10
15
  ## [0.41.0] - 2026-09-29
11
16
 
12
17
  ### 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
- - `tmux-session` — Create a detached tmux session
116
- - String form: `tmux-session` (uses scenario-based naming)
117
- - Hash form: `tmux-session: { name-source: run-id }` (uses unique E2E run ID as session name)
118
- - Runner teardown removes the created session after test execution
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
- - tmux-session:
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:, tmux-session
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
- # - tmux-session:
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", "**", "*")).select { |f| File.file?(f) }.sort
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 << safe_read(file)
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).encode("UTF-8", invalid: :replace, undef: :replace, replace: "?")
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
@@ -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, tmux-session, and
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 tmux session naming (e.g., "TS-OVERSEER-001")
60
- # @param run_id [String, nil] Unique run ID for deterministic tmux session naming
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, :tmux_session keys
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
- @tmux_session = nil
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
- @tmux_session = nil
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
- tmux_session: @tmux_session
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
- tmux_session: @tmux_session
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 @tmux_session
155
+ return unless @runtime_session
155
156
 
156
157
  if @sandbox_backend
157
158
  @sandbox_backend.capture3(
158
- ["tmux", "kill-session", "-t", @tmux_session],
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", @tmux_session, out: File::NULL, err: File::NULL)
164
+ @system_runner.call("tmux", "kill-session", "-t", @runtime_session, out: File::NULL, err: File::NULL)
164
165
  end
165
- @tmux_session = nil
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 "tmux-session"
183
- handle_tmux_session(env)
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 "tmux-session"
204
- handle_tmux_session(env, value)
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
- # Create an isolated detached tmux session and store its name in env
213
- def handle_tmux_session(env, config = nil)
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
- @tmux_session = session_name
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 == "tmux-session" || (step.is_a?(Hash) && step.key?("tmux-session"))
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
@@ -3,7 +3,7 @@
3
3
  module Ace
4
4
  module Test
5
5
  module EndToEndRunner
6
- VERSION = "0.41.0"
6
+ VERSION = "0.42.0"
7
7
  end
8
8
  end
9
9
  end
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.41.0
4
+ version: 0.42.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michal Czyz
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2026-09-30 00:00:00.000000000 Z
10
+ date: 2026-10-04 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: ace-support-cli