ace-bundle 0.44.2 → 0.45.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: 772a24588e3034043a2fd8a401702b531e98fcf4d4f1b8e9d54da2395e10f52b
4
- data.tar.gz: f05f0b8d92c6c2ae6a3921cb1a7dfb8078843f7da90b946268c3f6809639bd39
3
+ metadata.gz: 499c5ba424482d3c33a912bbf5fd65128e5963326095445b5a0fd081c9e30ae4
4
+ data.tar.gz: 6da09d023b7cb96e2ef3934974a0f48e614890666deb27928d12e6a43f0332f8
5
5
  SHA512:
6
- metadata.gz: 860184154f36ab8015ce3636ce2a03671073367a601d997296ae3190b470d817ebbfb7cb696bd51ae376ec4608005d4043cd124bb700e0184aa5af5e31d98554
7
- data.tar.gz: 3cbcc8c5019efbf54173be144686e453053c5c28298c8ae510223541158d891441464717a7ccf2482d863b56e5558b69d33fb3a00ebe8e9981eab0cdf5b77502
6
+ metadata.gz: f6c52aeb31efdd946038ed769c953c395b8557191ddad5ac173d6b8b56fc0297522bae4d46567c27c49d5c60c608c781ec8a5312844db51ea56c99c9a71c5030
7
+ data.tar.gz: 144acfa16735db88a83fdc9b1f48735f87b249519cae8ee0d4ba69a5af723e552819f1000fa0191a504fabebcf18575901457668f842789022ff57d128ce1b43
data/CHANGELOG.md CHANGED
@@ -6,6 +6,16 @@ The format is based on [Keep a Changelog][1], and this project adheres to [Seman
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.45.0] - 2026-10-10
10
+
11
+ ### Added
12
+
13
+ - Ship protected task-context loading with the Runtime 0.3 API and held artifact verification.
14
+
15
+ ### Fixed
16
+
17
+ - Require Runtime 0.3 and Herdr 0.5.1 so installed contexts cannot resolve missing producer modules.
18
+
9
19
  ## [0.44.2] - 2026-09-29
10
20
 
11
21
  ### Fixed
@@ -79,6 +79,9 @@ module Ace
79
79
  # Resource limits
80
80
  option :max_size, type: :integer, desc: "Maximum file size in bytes"
81
81
  option :timeout, type: :integer, desc: "Command timeout in seconds"
82
+ option :mapping, type: :string, desc: "Original protected mapping for captured task context"
83
+ option :assignment, type: :string, desc: "Original protected assignment ID@SCOPE"
84
+ option :attempt, type: :string, desc: "Original protected attempt for captured task context"
82
85
 
83
86
  # Standard options (inherited from Base but need explicit definition for ace-support-cli)
84
87
  option :version, type: :boolean, desc: "Show version information"
@@ -0,0 +1,178 @@
1
+ # frozen_string_literal: true
2
+ require "json"
3
+ require "tmpdir"
4
+ require "ace/runtime/molecules/protected_task_context_entry"
5
+ require "ace/herdr/molecules/bounded_process"
6
+
7
+ module Ace
8
+ module Bundle
9
+ module Molecules
10
+ # Fixed installed CLI boundary. Bundle does not import Assign, select its
11
+ # roster or resolve task context through project command templates.
12
+ class ProtectedTaskContext
13
+ TOKEN = /\A[A-Za-z0-9][A-Za-z0-9._-]{0,199}\z/
14
+ SCOPE = /\A[0-9]+(?:\.[0-9]+)*\z/
15
+ SHA = /\A[0-9a-f]{64}\z/
16
+ TEXT_LIMIT = 1_048_576
17
+ RESPONSE_LIMIT = 6_307_840
18
+ SELECTION_LIMIT = 65_536
19
+ PRINCIPAL_LIMIT = 1_024
20
+ STDERR_LIMIT = 16_384
21
+ DEADLINE = 30
22
+ SELECTOR_FIELDS = %w[mapping_id assignment_id attempt_id scope].freeze
23
+ ASSOCIATION_FIELDS = (SELECTOR_FIELDS + %w[definition_digest selection_sha256]).freeze
24
+
25
+ def initialize(entry_owner: Ace::Runtime::Molecules::ProtectedTaskContextEntry.new,
26
+ runner: Ace::Herdr::Molecules::BoundedProcess, uid: Process.uid, env: ENV)
27
+ @owner, @runner, @uid, @env = entry_owner, runner, uid, env
28
+ end
29
+
30
+ # nil means genuinely ordinary; every installed failure raises.
31
+ def load(uri, options: {})
32
+ selected = selected?(options)
33
+ @owner.with do |discovery|
34
+ unless discovery
35
+ unavailable!("installed entry is absent") if selected
36
+ next nil
37
+ end
38
+ principal = invoke(discovery, ["authority", "task-context-principal"], PRINCIPAL_LIMIT)
39
+ object!(principal, %w[schema uid protected_worker])
40
+ unless principal.fetch("schema") == "ace.assign.task-context-principal/v1" &&
41
+ principal.fetch("uid").is_a?(Integer) && principal.fetch("uid") == @uid && [true, false].include?(principal.fetch("protected_worker"))
42
+ mismatch!("actual caller classification")
43
+ end
44
+ next nil unless principal.fetch("protected_worker") || selected
45
+ task = uri.delete_prefix("task://")
46
+ mismatch!("task URI") unless uri.start_with?("task://") && task.match?(TOKEN)
47
+ selectors, argv = selectors!(options)
48
+ selection = invoke(discovery, ["authority", "task-context-selection", *argv], SELECTION_LIMIT)
49
+ object!(selection, ASSOCIATION_FIELDS + %w[schema task_context_entry])
50
+ association!(selection, selectors, "ace.assign.task-context-selection/v1")
51
+ original = validated_output_pin!(selection.fetch("task_context_entry"))
52
+ manifest_hint!(original)
53
+ result = invoke(original, ["authority", "task-context", *argv, "--task", task], RESPONSE_LIMIT)
54
+ object!(result, ASSOCIATION_FIELDS + %w[schema task_id text])
55
+ association!(result, selectors, "ace.assign.prepared-task-context/v1")
56
+ unless result.slice(*ASSOCIATION_FIELDS) == selection.slice(*ASSOCIATION_FIELDS) && result.fetch("task_id") == task
57
+ mismatch!("original prepared response association")
58
+ end
59
+ text = result.fetch("text")
60
+ mismatch!("captured text bound") unless text.is_a?(String) && text.valid_encoding? && text.bytesize.between?(1, TEXT_LIMIT)
61
+ text.freeze
62
+ end
63
+ rescue Ace::Runtime::Error, IOError, SystemCallError, Timeout::Error, Ace::Herdr::Molecules::BoundedProcess::PostLaunchError => error
64
+ unavailable!(error.message)
65
+ rescue KeyError, TypeError, NoMethodError, ArgumentError, JSON::ParserError, EncodingError
66
+ mismatch!("closed task-context response")
67
+ end
68
+
69
+ private
70
+
71
+ def selected?(options)
72
+ options.values_at(:mapping, :assignment, :attempt).any? { |value| value && !value.to_s.empty? } ||
73
+ %w[ACE_ASSIGN_LAUNCH_MAPPING ACE_ASSIGN_ASSIGNMENT_ID ACE_ASSIGN_ATTEMPT_ID ACE_ASSIGN_TASK_CONTEXT_ENTRY].any? { |key| !@env[key].to_s.empty? }
74
+ end
75
+
76
+ def selectors!(options)
77
+ mapping = hint!(options[:mapping], "ACE_ASSIGN_LAUNCH_MAPPING")
78
+ attempt = hint!(options[:attempt], "ACE_ASSIGN_ATTEMPT_ID")
79
+ scoped = hint!(options[:assignment], "ACE_ASSIGN_DEFAULT_TARGET", token: false)
80
+ assignment, scope = scoped.split("@", 2)
81
+ mismatch!("explicit scoped original assignment") unless assignment&.match?(TOKEN) && scope&.match?(SCOPE)
82
+ bare_hint = @env["ACE_ASSIGN_ASSIGNMENT_ID"].to_s
83
+ mismatch!("assignment hint differs") unless bare_hint.empty? || bare_hint == assignment
84
+ [{"mapping_id" => mapping, "assignment_id" => assignment, "attempt_id" => attempt, "scope" => scope},
85
+ ["--mapping", mapping, "--assignment", scoped, "--attempt", attempt]]
86
+ end
87
+
88
+ def hint!(explicit, key, token: true)
89
+ hint = @env[key].to_s
90
+ value = explicit || hint
91
+ unless value.is_a?(String) && value.encoding == Encoding::UTF_8 && value.valid_encoding? && !value.empty? &&
92
+ (hint.empty? || hint == value) && (!token || value.match?(TOKEN))
93
+ mismatch!("complete original selectors required")
94
+ end
95
+ value
96
+ end
97
+
98
+ def association!(record, selectors, schema)
99
+ unless record.fetch("schema") == schema && record.slice(*SELECTOR_FIELDS) == selectors &&
100
+ %w[definition_digest selection_sha256].all? { |key| record.fetch(key).is_a?(String) && record.fetch(key).match?(SHA) }
101
+ mismatch!("original prepared selectors or digest")
102
+ end
103
+ end
104
+
105
+ def manifest_hint!(pin)
106
+ hint = @env["ACE_ASSIGN_TASK_CONTEXT_ENTRY"].to_s
107
+ return if hint.empty?
108
+ mismatch!("manifest hint bound") if hint.bytesize > SELECTION_LIMIT
109
+ reference = JSON.parse(hint, create_additions: false, max_nesting: 8, allow_duplicate_key: false, allow_comments: false)
110
+ candidate = validated_output_pin!({"manifest" => reference, "wrapper" => pin.fetch("wrapper")})
111
+ mismatch!("manifest hint differs from original") unless candidate == pin
112
+ end
113
+
114
+ def validated_output_pin!(pin)
115
+ @owner.validate_pin!(pin)
116
+ rescue Ace::Runtime::Error, KeyError, TypeError, NoMethodError, ArgumentError
117
+ mismatch!("original entry reference fields")
118
+ end
119
+
120
+ def invoke(pin, arguments, limit)
121
+ @owner.with_entry(pin) do |entry|
122
+ with_selector(entry.pin.fetch("manifest")) do |selector|
123
+ result = @runner.call(["/proc/self/fd/4", "-I", "-S", "-B", "-", *arguments],
124
+ stdin_data: entry.body, timeout_s: DEADLINE, output_limit: limit, stderr_limit: STDERR_LIMIT,
125
+ descriptor_mapping: {4 => entry.interpreter, 5 => selector}, environment: {}, chdir: "/", cleanup_group: true)
126
+ unavailable!("fixed task-context command failed") unless result.status&.success?
127
+ mismatch!("fixed task-context output was truncated") if result.oversized
128
+ bytes = result.stdout
129
+ unless bytes.is_a?(String) && bytes.bytesize.between?(1, limit)
130
+ mismatch!("fixed task-context output bound")
131
+ end
132
+ text = bytes.dup.force_encoding(Encoding::UTF_8)
133
+ unless text.valid_encoding? && text.end_with?("\n") && text.count("\n") == 1
134
+ mismatch!("fixed task-context output framing")
135
+ end
136
+ record = JSON.parse(text, create_additions: false, max_nesting: 16, allow_duplicate_key: false, allow_comments: false)
137
+ mismatch!("fixed task-context output is not compact JSON") unless JSON.generate(record) + "\n" == text
138
+ record
139
+ end
140
+ end
141
+ end
142
+
143
+ # This private temporary transports only an untrusted manifest REF. The
144
+ # accepted code/interpreter come from retained protected handles above.
145
+ def with_selector(reference)
146
+ bytes = JSON.generate(reference) + "\n"
147
+ mismatch!("entry selector bound") if bytes.bytesize > SELECTION_LIMIT
148
+ Dir.mktmpdir("ace-task-context-", "/tmp") do |directory|
149
+ path = File.join(directory, "selector.json")
150
+ File.open(path, File::WRONLY | File::CREAT | File::EXCL, 0o600) do |writer|
151
+ writer.write(bytes); writer.flush
152
+ File.open(path, File::RDONLY | File::NOFOLLOW | File::NONBLOCK) do |reader|
153
+ unless reader.stat.file? && reader.stat.uid == Process.uid && (reader.stat.mode & 0o077).zero? &&
154
+ [reader.stat.dev, reader.stat.ino, reader.stat.size] == [writer.stat.dev, writer.stat.ino, bytes.bytesize]
155
+ unavailable!("private selector descriptor differs")
156
+ end
157
+ File.unlink(path)
158
+ yield reader
159
+ end
160
+ end
161
+ end
162
+ end
163
+
164
+ def object!(record, fields)
165
+ mismatch!("closed fixed task-context fields") unless record.is_a?(Hash) && record.keys.sort == fields.sort
166
+ end
167
+
168
+ def mismatch!(detail)
169
+ raise Ace::Bundle::Error, "prepared_input_mismatch: #{detail}"
170
+ end
171
+
172
+ def unavailable!(detail)
173
+ raise Ace::Bundle::Error, "prepared_input_unavailable: #{detail}"
174
+ end
175
+ end
176
+ end
177
+ end
178
+ end
@@ -21,6 +21,7 @@ require_relative "pr_bundle_loader"
21
21
  require_relative "../models/bundle_data"
22
22
  require_relative "../atoms/content_checker"
23
23
  require_relative "../atoms/typo_detector"
24
+ require_relative "../molecules/protected_task_context"
24
25
 
25
26
  module Ace
26
27
  module Bundle
@@ -986,19 +987,17 @@ module Ace
986
987
  # Process files
987
988
  if config["files"] && config["files"].any?
988
989
  # Resolve any protocol references (e.g., wfi://workflow-name)
989
- resolved_files = config["files"].filter_map do |file_ref|
990
- resolved = resolve_file_reference(file_ref)
991
- if !resolved && @review_safe_mode
990
+ source_inputs = resolve_file_inputs(config["files"]) do |file_ref|
991
+ if @review_safe_mode
992
992
  data[:errors] << "Unresolved file source: #{file_ref}"
993
993
  end
994
- resolved
995
994
  end
995
+ resolved_files = source_inputs.filter_map { |entry| entry[:path] }
996
996
  missing_sources = missing_review_sources(resolved_files, base_dir: @options[:base_dir] || project_root,
997
997
  exclude: config["exclude"] || [])
998
998
  missing_sources.each do |pattern|
999
999
  data[:errors] << "Unmatched required file pattern: #{pattern}"
1000
1000
  end
1001
- resolved_files -= missing_sources
1002
1001
 
1003
1002
  aggregator = Ace::Core::Molecules::FileAggregator.new(
1004
1003
  max_size: config["max_size"] || @options[:max_size],
@@ -1008,15 +1007,7 @@ module Ace
1008
1007
  exclude: config["exclude"] || []
1009
1008
  )
1010
1009
 
1011
- # Check if any patterns contain glob characters
1012
- has_globs = resolved_files.any? { |f| f.include?("*") || f.include?("?") || f.include?("[") }
1013
-
1014
- # Use aggregate for globs, aggregate_files for literal paths
1015
- result = if has_globs
1016
- aggregator.aggregate(resolved_files)
1017
- else
1018
- aggregator.aggregate_files(resolved_files)
1019
- end
1010
+ result = aggregate_file_inputs(source_inputs, missing_sources, aggregator)
1020
1011
  data[:files] = result[:files]
1021
1012
  data[:errors].concat(result[:errors])
1022
1013
  end
@@ -1271,8 +1262,13 @@ module Ace
1271
1262
  end
1272
1263
 
1273
1264
  def load_protocol(protocol_ref)
1265
+ if (text = protected_task_text(protocol_ref))
1266
+ bundle = Models::BundleData.new(content: text, metadata: {protocol_ref: protocol_ref, protected_prepared: true})
1267
+ bundle.source_files << {path: protocol_ref, content: text}
1268
+ return bundle
1269
+ end
1274
1270
  # Resolve protocol using ace-nav
1275
- resolved_path = resolve_protocol(protocol_ref)
1271
+ resolved_path = resolve_protocol(protocol_ref, protected_checked: true)
1276
1272
 
1277
1273
  if resolved_path && File.exist?(resolved_path)
1278
1274
  # Load the resolved file
@@ -1287,7 +1283,10 @@ module Ace
1287
1283
  end
1288
1284
  end
1289
1285
 
1290
- def resolve_protocol(protocol_ref)
1286
+ def resolve_protocol(protocol_ref, protected_checked: false)
1287
+ if !protected_checked && protected_task_text(protocol_ref)
1288
+ raise Ace::Bundle::Error, "Captured protected task context must be consumed as text, never a reopened pathname"
1289
+ end
1291
1290
  require "ace/support/nav"
1292
1291
  engine = Ace::Support::Nav::Organisms::NavigationEngine.new
1293
1292
  if @review_safe_mode && engine.cmd_protocol?(protocol_ref.split("://", 2).first)
@@ -1317,10 +1316,16 @@ module Ace
1317
1316
  nil
1318
1317
  end
1319
1318
 
1320
- def resolve_file_reference(file_ref)
1319
+ def protected_task_text(reference)
1320
+ return nil unless reference.is_a?(String) && reference.start_with?("task://")
1321
+ @protected_task_context ||= Molecules::ProtectedTaskContext.new
1322
+ @protected_task_context.load(reference, options: @options)
1323
+ end
1324
+
1325
+ def resolve_file_reference(file_ref, protected_checked: false)
1321
1326
  # Check if it's a protocol reference (contains ://)
1322
1327
  if file_ref.match?(/^[\w-]+:\/\//)
1323
- resolve_protocol(file_ref)
1328
+ resolve_protocol(file_ref, protected_checked: protected_checked)
1324
1329
  elsif file_ref.start_with?("./") && @template_dir
1325
1330
  # Resolve ./ paths relative to the template file's directory
1326
1331
  File.join(@template_dir, file_ref)
@@ -1330,6 +1335,45 @@ module Ace
1330
1335
  end
1331
1336
  end
1332
1337
 
1338
+ def resolve_file_inputs(references)
1339
+ references.filter_map do |reference|
1340
+ if (text = protected_task_text(reference))
1341
+ {record: {path: reference, content: text}}
1342
+ elsif (path = resolve_file_reference(reference, protected_checked: true))
1343
+ {path: path}
1344
+ else
1345
+ yield reference if block_given?
1346
+ nil
1347
+ end
1348
+ end
1349
+ end
1350
+
1351
+ def aggregate_file_inputs(inputs, missing, aggregator)
1352
+ paths = inputs.filter_map { |entry| entry[:path] } - missing
1353
+ unless inputs.any? { |entry| entry[:record] }
1354
+ return aggregate_paths(paths, aggregator)
1355
+ end
1356
+ # Retained protocol text enters the existing content record interface,
1357
+ # without a temporary pathname. Preserve mixed source order.
1358
+ inputs.each_with_object({files: [], errors: []}) do |entry, result|
1359
+ if entry[:record]
1360
+ result[:files] << entry.fetch(:record)
1361
+ elsif !missing.include?(entry.fetch(:path))
1362
+ part = aggregate_paths([entry.fetch(:path)], aggregator)
1363
+ result[:files].concat(part.fetch(:files))
1364
+ result[:errors].concat(part.fetch(:errors))
1365
+ end
1366
+ end
1367
+ end
1368
+
1369
+ def aggregate_paths(paths, aggregator)
1370
+ if paths.any? { |path| path.include?("*") || path.include?("?") || path.include?("[") }
1371
+ aggregator.aggregate(paths)
1372
+ else
1373
+ aggregator.aggregate_files(paths)
1374
+ end
1375
+ end
1376
+
1333
1377
  # Process content for a specific section
1334
1378
  def process_section_content(bundle, section_name, section_data, options, bundle_config = {})
1335
1379
  # Process all content types that are present in the section
@@ -1356,21 +1400,19 @@ module Ace
1356
1400
  return unless files.any?
1357
1401
 
1358
1402
  # Resolve any protocol references (e.g., wfi://workflow-name)
1359
- resolved_files = files.filter_map do |file_ref|
1360
- resolved = resolve_file_reference(file_ref)
1361
- if !resolved && @review_safe_mode
1403
+ source_inputs = resolve_file_inputs(files) do |file_ref|
1404
+ if @review_safe_mode
1362
1405
  bundle.metadata[:errors] ||= []
1363
1406
  bundle.metadata[:errors] << "Section '#{section_name}': unresolved file source: #{file_ref}"
1364
1407
  end
1365
- resolved
1366
1408
  end
1409
+ resolved_files = source_inputs.filter_map { |entry| entry[:path] }
1367
1410
  missing_sources = missing_review_sources(resolved_files, base_dir: options[:base_dir] || project_root,
1368
1411
  exclude: section_data[:exclude] || section_data["exclude"] || [])
1369
1412
  missing_sources.each do |pattern|
1370
1413
  bundle.metadata[:errors] ||= []
1371
1414
  bundle.metadata[:errors] << "Section '#{section_name}': unmatched required file pattern: #{pattern}"
1372
1415
  end
1373
- resolved_files -= missing_sources
1374
1416
 
1375
1417
  aggregator = Ace::Core::Molecules::FileAggregator.new(
1376
1418
  max_size: options[:max_size] || options["max_size"],
@@ -1380,15 +1422,7 @@ module Ace
1380
1422
  exclude: section_data[:exclude] || section_data["exclude"] || []
1381
1423
  )
1382
1424
 
1383
- # Check if any patterns contain glob characters
1384
- has_globs = resolved_files.any? { |f| f.include?("*") || f.include?("?") || f.include?("[") }
1385
-
1386
- # Use aggregate for globs, aggregate_files for literal paths to preserve order
1387
- result = if has_globs
1388
- aggregator.aggregate(resolved_files)
1389
- else
1390
- aggregator.aggregate_files(resolved_files)
1391
- end
1425
+ result = aggregate_file_inputs(source_inputs, missing_sources, aggregator)
1392
1426
 
1393
1427
  # Store section files in section data
1394
1428
  section_data[:_processed_files] = result[:files]
@@ -1491,6 +1525,13 @@ module Ace
1491
1525
  files_in_content = bundle.source_files.any? &&
1492
1526
  (!bundle_config["embed_document_source"] || @section_processor.has_sections?({"bundle" => bundle_config}))
1493
1527
 
1528
+ if (text = protected_task_text(base_ref))
1529
+ bundle.content = [text, (bundle.content if files_in_content)].compact.join("\n\n")
1530
+ bundle.metadata.merge!(base_ref: base_ref, base_type: "protected_prepared")
1531
+ bundle.source_files << {path: base_ref, content: text}
1532
+ return
1533
+ end
1534
+
1494
1535
  # Check if base_ref looks like a file reference (has protocol, slashes, or is a known path pattern)
1495
1536
  # This heuristic helps prioritize file resolution for extension-less files
1496
1537
  has_protocol = base_ref.match?(/^[\w-]+:\/\//)
@@ -1499,7 +1540,7 @@ module Ace
1499
1540
  (@review_safe_mode && base_ref.match?(/\.(?:md|markdown|txt|json|ya?ml)\z/i))
1500
1541
 
1501
1542
  # Try to resolve as file reference first (handles extension-less files like README, CONTEXT)
1502
- resolved_path = resolve_file_reference(base_ref)
1543
+ resolved_path = resolve_file_reference(base_ref, protected_checked: true)
1503
1544
 
1504
1545
  # Check if we successfully resolved to an existing file
1505
1546
  if resolved_path && File.exist?(resolved_path)
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Ace
4
4
  module Bundle
5
- VERSION = "0.44.2"
5
+ VERSION = "0.45.0"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ace-bundle
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.44.2
4
+ version: 0.45.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-10 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: ace-support-cli
@@ -107,6 +107,34 @@ dependencies:
107
107
  - - "~>"
108
108
  - !ruby/object:Gem::Version
109
109
  version: '0.28'
110
+ - !ruby/object:Gem::Dependency
111
+ name: ace-runtime
112
+ requirement: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - "~>"
115
+ - !ruby/object:Gem::Version
116
+ version: '0.3'
117
+ type: :runtime
118
+ prerelease: false
119
+ version_requirements: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - "~>"
122
+ - !ruby/object:Gem::Version
123
+ version: '0.3'
124
+ - !ruby/object:Gem::Dependency
125
+ name: ace-herdr
126
+ requirement: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - "~>"
129
+ - !ruby/object:Gem::Version
130
+ version: 0.5.1
131
+ type: :runtime
132
+ prerelease: false
133
+ version_requirements: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - "~>"
136
+ - !ruby/object:Gem::Version
137
+ version: 0.5.1
110
138
  - !ruby/object:Gem::Dependency
111
139
  name: ace-compressor
112
140
  requirement: !ruby/object:Gem::Requirement
@@ -172,6 +200,7 @@ files:
172
200
  - lib/ace/bundle/molecules/bundle_file_writer.rb
173
201
  - lib/ace/bundle/molecules/bundle_merger.rb
174
202
  - lib/ace/bundle/molecules/preset_manager.rb
203
+ - lib/ace/bundle/molecules/protected_task_context.rb
175
204
  - lib/ace/bundle/molecules/section_compressor.rb
176
205
  - lib/ace/bundle/molecules/section_formatter.rb
177
206
  - lib/ace/bundle/molecules/section_processor.rb