specguard-ruby 0.3.30 → 0.3.32

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: 916a7326516ee737b20d0a0790fd6b6a0f1abe56404083ad67fe61d7d7fb5c46
4
- data.tar.gz: 0b39ff94f46fb053ea659cb5f69f878bf86a3b04ed1e0e630394769bed0d6589
3
+ metadata.gz: 4e2f8dd966880d2075d3989184c5f9df89d702445edcdf88583c0b8976021a46
4
+ data.tar.gz: 4e5d985eda3fb38fc4832412585f6f389a5d9429c915bc9797d02d349576a713
5
5
  SHA512:
6
- metadata.gz: 0d721e5d2d50648b3dfc7cf22ebbd2af16a9edd422feffa80b6d32d1092860c145ef6598919a802e3341a3eaa9fea6341fa26cab71f8abef33e0ff9d74d127b6
7
- data.tar.gz: 77a24a2798088706388f6ef2ba7c6d4bd648d4fd152b1dcf55eb9e391409a0ece0ec3046d6b0b14bfaa24db03e68dac7e89d4eef340d6c34ba6b9512acdfda7a
6
+ metadata.gz: dfc1753a08f2e9c713c61ed12ac18b557f5e32fbaa3577735383ac6adea9bf237ff6374baf6fe276e6735c0e2da38344574c2ab6b02de1a8f42fe41c3fc51aa9
7
+ data.tar.gz: eca2722d9dcfa6117c21ee589b2b2afdfa422c9983a49d66ee80974574e33f8163f10c2ccab65345badef6dec01d94ad25b436980d5d87beb81b224c3c2c6d1b
data/README.md CHANGED
@@ -474,6 +474,15 @@ export SPECGUARD_API_KEY=… # from your repository's settings
474
474
  export SPECGUARD_TIMEOUT=10 # optional; seconds, applied to connect and read
475
475
  ```
476
476
 
477
+ **Using an `sga_` agent key? Also set `SPECGUARD_REPOSITORY_ID`.** An agent key
478
+ covers a set of repositories, so the key cannot say which one a run belongs to;
479
+ the request has to. With `SPECGUARD_REPOSITORY_ID=42` the run is POSTed to
480
+ `<endpoint>/api/v1/repositories/42/ingest` instead of `<endpoint>/api/v1/ingest`
481
+ (`config.repository_id` in Ruby). Under a repository `sgk_` key leave it unset —
482
+ if you do set it, the server ignores it. The id may contain only letters, digits,
483
+ `-` and `_`; anything else is refused before a request is made. A `404` with an
484
+ id configured means no repository with that id is available to the key.
485
+
477
486
  ```ruby
478
487
  # ...or in Ruby, if you would rather not use the environment
479
488
  SpecGuard.configure do |config|
@@ -515,6 +524,7 @@ offered to the endpoint and not accepted — so the run can be replayed later wi
515
524
  ```
516
525
  SpecGuard: could not deliver test telemetry (HTTP 401 — the API key was not
517
526
  accepted). Falling back to log/test_results.jsonl; the test run is unaffected.
527
+ Once the delivery is fixed, replay this run with: bundle exec specguard-ingest log/test_results.jsonl
518
528
  ```
519
529
 
520
530
  **That line carries the endpoint's own words when it has any.** A `400` refusal
@@ -527,7 +537,8 @@ SpecGuard: could not deliver test telemetry (HTTP 400 — the endpoint rejected
527
537
  the payload — spec 3 (spec/orders_spec.rb:9): line_number is required and must
528
538
  be a positive integer; spec 7 (spec/orders_spec.rb:31): outcome must be one of
529
539
  passed, failed, pending). Falling back to log/test_results.jsonl; the test run
530
- is unaffected.
540
+ is unaffected. Once the delivery is fixed, replay this run with: bundle exec
541
+ specguard-ingest log/test_results.jsonl
531
542
  ```
532
543
 
533
544
  It stays **one** line whatever comes back. A systemic problem can have the
@@ -628,8 +639,9 @@ specguard-ingest: lines 1, 2 carried ci_run_id 17442 and each came back with
628
639
  test_run_id 41f2c9b8 — the endpoint folded them onto one run
629
640
  ```
630
641
 
631
- It reads the same `SPECGUARD_ENDPOINT`, `SPECGUARD_API_KEY` and
632
- `SPECGUARD_TIMEOUT` the formatter does, and sends each line through the same
642
+ It reads the same `SPECGUARD_ENDPOINT`, `SPECGUARD_API_KEY`, `SPECGUARD_REPOSITORY_ID`
643
+ (for `sga_` agent keys — a replay queue written under one is re-delivered to the same
644
+ repository-scoped route) and `SPECGUARD_TIMEOUT` the formatter does, and sends each line through the same
633
645
  transport — so a run that was refused for a rotated key appears on the platform
634
646
  once the secret is fixed, with the shard folding described in
635
647
  [If you shard your suite](#if-you-shard-your-suite) applying exactly as it would
@@ -1212,9 +1224,11 @@ export SPECGUARD_ENDPOINT=https://specguard.internal.example.com
1212
1224
  nothing any other formatter wrote, is read or forwarded.
1213
1225
  - **No environment.** SpecGuard's own code reads a fixed list of variables and
1214
1226
  no others: the ones named in the envelope table above, which fill
1215
- `commit_sha`, `branch`, `ci_run_id` and `shard_id`; plus four that configure
1227
+ `commit_sha`, `branch`, `ci_run_id` and `shard_id`; plus five that configure
1216
1228
  the gem itself rather than describing your suite — `SPECGUARD_ENDPOINT` (where
1217
- to send the run), `SPECGUARD_OUTPUT_PATH` (where to write the replay queue —
1229
+ to send the run), `SPECGUARD_REPOSITORY_ID` (which repository an `sga_` agent
1230
+ key's run belongs to — sent as a path segment of the request URL, nothing else),
1231
+ `SPECGUARD_OUTPUT_PATH` (where to write the replay queue —
1218
1232
  runs the endpoint refused or could not be reached for), `SPECGUARD_LOCAL_OUTPUT_PATH`
1219
1233
  (where to write the local development record when there is no key),
1220
1234
  `SPECGUARD_TIMEOUT` (how long to wait), and
@@ -318,6 +318,7 @@ module SpecGuard
318
318
  LOCAL_OUTPUT_PATH_KEYS = %w[SPECGUARD_LOCAL_OUTPUT_PATH].freeze
319
319
  ENDPOINT_KEYS = %w[SPECGUARD_ENDPOINT].freeze
320
320
  API_KEY_KEYS = %w[SPECGUARD_API_KEY].freeze
321
+ REPOSITORY_ID_KEYS = %w[SPECGUARD_REPOSITORY_ID].freeze
321
322
  TIMEOUT_KEYS = %w[SPECGUARD_TIMEOUT].freeze
322
323
 
323
324
  # The commit the suite ran against. `nil` when nothing said.
@@ -344,7 +345,9 @@ module SpecGuard
344
345
  attr_accessor :local_output_path
345
346
  # The SpecGuard installation to POST to, scheme and host only:
346
347
  # `https://specguard.example.com`. The `/api/v1/ingest` path is the
347
- # platform's contract, not a setting. `nil` when nothing said.
348
+ # platform's contract, not a setting — with one exception: when
349
+ # {#repository_id} is set the path is `/api/v1/repositories/<id>/ingest`
350
+ # instead. `nil` when nothing said.
348
351
  attr_accessor :endpoint
349
352
  # The repository's SpecGuard API key. Present means "deliver over HTTP".
350
353
  #
@@ -354,6 +357,16 @@ module SpecGuard
354
357
  # nobody can retroactively fix. Send whatever is configured and let a 401
355
358
  # be the answer.
356
359
  attr_accessor :api_key
360
+ # The repository the run belongs to, sent as a path segment
361
+ # (`/api/v1/repositories/<id>/ingest`). Needed by an `sga_` agent key,
362
+ # which covers a set of repositories and so cannot name the run's
363
+ # repository itself; under an `sgk_` key the platform ignores it. `nil`
364
+ # when unset or blank, which keeps the plain `/api/v1/ingest` path.
365
+ #
366
+ # Deliberately **not** format-checked against the key prefix or a numeric
367
+ # shape (same reasoning as {#api_key}): the platform's 400/403/404 is the
368
+ # answer. {Transport} only refuses ids that are unsafe as a path segment.
369
+ attr_accessor :repository_id
357
370
  # Seconds, applied to opening the connection and to reading the response.
358
371
  attr_accessor :timeout
359
372
 
@@ -374,6 +387,7 @@ module SpecGuard
374
387
  @local_output_path = first_present(env, LOCAL_OUTPUT_PATH_KEYS) || DEFAULT_LOCAL_OUTPUT_PATH
375
388
  @endpoint = first_present(env, ENDPOINT_KEYS)
376
389
  @api_key = first_present(env, API_KEY_KEYS)
390
+ @repository_id = first_present(env, REPOSITORY_ID_KEYS)
377
391
  @timeout = seconds(first_present(env, TIMEOUT_KEYS)) || DEFAULT_TIMEOUT_SECONDS
378
392
  end
379
393
 
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SpecGuard
4
+ module Client
5
+ # The two clauses that finish a delivery-failure warning line, shared by
6
+ # the RSpec formatter and the Minitest reporter.
7
+ #
8
+ # Both clients print the same sentence after their own prefix and status
9
+ # clause: where the run ended up, and (when it was saved) the command that
10
+ # gets it back. They used to carry two inline copies kept equal
11
+ # "word-for-word" by hand, and the pair had already been co-edited in
12
+ # separate commits (SPGD-1139, SPGD-1413). One definition here means a
13
+ # wording change cannot reach one client and miss the other.
14
+ #
15
+ # What stays in each client: the prefix, the warning budget (`@warned`),
16
+ # and the `append` that decides whether `error` is nil. Those differ by
17
+ # design; only the clause text is shared.
18
+ module DeliveryWarning
19
+ module_function
20
+
21
+ # Report, not promise. Composed after the write so it can tell the
22
+ # operator which of the two things actually happened. `error` is the
23
+ # client's `append` answer: `nil` means the queue took the run (the
24
+ # success arm, {#replay_clause}); anything else means both sinks are
25
+ # gone and the run really is lost (SPGD-1413).
26
+ def sink_clause(path, error)
27
+ return replay_clause(path) if error.nil?
28
+
29
+ "The replay queue #{path} could not be written either " \
30
+ "(#{error.class}: #{error.message}), so this run's telemetry was lost."
31
+ end
32
+
33
+ # The success arm (SPGD-1139): where the run sits AND the command that
34
+ # gets it back, fix-the-delivery first and replay after — the order the
35
+ # ingest CLI's own doctrine describes. The lost-run arm deliberately
36
+ # names no command: with no queue written there is nothing to replay.
37
+ def replay_clause(path)
38
+ "Falling back to #{path}; the test run is unaffected. " \
39
+ "Once the delivery is fixed, replay this run with: bundle exec specguard-ingest #{path}"
40
+ end
41
+ end
42
+ end
43
+ end
@@ -739,8 +739,7 @@ module SpecGuard
739
739
  raise UsageError, "no endpoint is configured (set SPECGUARD_ENDPOINT)" if blank?(configuration.endpoint)
740
740
  raise UsageError, "no API key is configured (set SPECGUARD_API_KEY)" if blank?(configuration.api_key)
741
741
 
742
- transport = Transport.new(endpoint: configuration.endpoint, api_key: configuration.api_key,
743
- timeout: configuration.timeout)
742
+ transport = Transport.from_configuration(configuration)
744
743
  # Asked once, here, so a malformed `SPECGUARD_ENDPOINT` is one exit 2
745
744
  # rather than N identical `:failed` lines. `Transport#deliver` would
746
745
  # otherwise swallow the same `ArgumentError` once per line and report a
@@ -127,6 +127,21 @@ module SpecGuard
127
127
  # Part of the platform's contract, so it is not configurable — the
128
128
  # `endpoint` setting is the installation's address and nothing more.
129
129
  PATH = "/api/v1/ingest"
130
+
131
+ # The repository-scoped form of the same endpoint (`post "ingest"` nested
132
+ # under `resources :repositories`). It exists because an `sga_` agent key
133
+ # covers a SET of repositories, so the credential cannot name the run's
134
+ # repository and the request has to, as a path segment. `%s` is only ever
135
+ # filled with an id that passed {REPOSITORY_ID_PATTERN}. Under an `sgk_`
136
+ # key the server ignores the segment, so {PATH} stays the default.
137
+ REPOSITORY_PATH = "/api/v1/repositories/%s/ingest"
138
+
139
+ # What a repository id may look like before it becomes a path segment.
140
+ # Deliberately not "digits": the platform's ids are numeric today and the
141
+ # gem must not bake that in. Dots are excluded outright, which is what
142
+ # refuses `..` (percent-escaping would not neutralise a dot segment), and
143
+ # `/`, `?`, `#` can never survive as path structure.
144
+ REPOSITORY_ID_PATTERN = /\A[0-9A-Za-z_-]+\z/
130
145
  CONTENT_TYPE = "application/json"
131
146
  USER_AGENT = "specguard-ruby/#{SpecGuard::VERSION}"
132
147
  CONTENT_ENCODING = "gzip"
@@ -167,7 +182,12 @@ module SpecGuard
167
182
  # same reason turned around: a 202 whose body will not parse is still an
168
183
  # acceptance, and relabelling it would tell the operator something untrue
169
184
  # about a run the platform has already stored. See {#test_run_id}.
170
- Result = Struct.new(:outcome, :code, :error, :reasons, :body, keyword_init: true) do
185
+ #
186
+ # `repository_scoped` is true when the request went to the repository-
187
+ # scoped route. It only changes the 404 sentence: under that route a 404
188
+ # means no repository with that id is available to this key, not a wrong
189
+ # URL. Nil/false (every pre-existing `Result.new`) keeps today's wording.
190
+ Result = Struct.new(:outcome, :code, :error, :reasons, :body, :repository_scoped, keyword_init: true) do
171
191
  # The status codes worth spelling out, because each implies a different
172
192
  # thing for the reader to *do*. A 401 means "rotate or fix the key"; a
173
193
  # 400 means "the payload this gem built was refused", which is a bug
@@ -193,6 +213,10 @@ module SpecGuard
193
213
  # tells the reader how widespread it is.
194
214
  MAX_RENDERED_REASONS = 3
195
215
 
216
+ # The 404 sentence when a repository id was sent. See `repository_scoped`.
217
+ REPOSITORY_NOT_FOUND_ADVICE =
218
+ "no repository with that id is available to this API key — check SPECGUARD_REPOSITORY_ID"
219
+
196
220
  # Ceiling on the spelled-out reasons, before the `and N more` tail,
197
221
  # which is appended afterwards so the count can never be the thing that
198
222
  # gets truncated away.
@@ -224,13 +248,19 @@ module SpecGuard
224
248
  def reason
225
249
  case outcome
226
250
  when :success then nil
227
- when :rejected then [+"HTTP #{code}", ADVICE[code], rendered_reasons].compact.join(" — ")
251
+ when :rejected then [+"HTTP #{code}", advice, rendered_reasons].compact.join(" — ")
228
252
  else "#{error.class}: #{error.message}"
229
253
  end
230
254
  end
231
255
 
232
256
  private
233
257
 
258
+ def advice
259
+ return REPOSITORY_NOT_FOUND_ADVICE if code == 404 && repository_scoped
260
+
261
+ ADVICE[code]
262
+ end
263
+
234
264
  # The refusal, bounded and flattened to fit on the one line. `nil` when
235
265
  # there is nothing readable to add, which is what keeps the bare
236
266
  # `HTTP 400 — the endpoint rejected the payload` intact for every body
@@ -263,6 +293,21 @@ module SpecGuard
263
293
  end
264
294
  end
265
295
 
296
+ # Builds a transport from a {Configuration}, projecting the four settings
297
+ # every ingest writer forwards (endpoint, api_key, timeout, repository_id)
298
+ # so a new keyword is threaded in one place rather than in each writer.
299
+ #
300
+ # @param configuration [Configuration, #endpoint, #api_key, #timeout, #repository_id]
301
+ # @return [Transport]
302
+ def self.from_configuration(configuration)
303
+ new(
304
+ endpoint: configuration.endpoint,
305
+ api_key: configuration.api_key,
306
+ timeout: configuration.timeout,
307
+ repository_id: configuration.repository_id
308
+ )
309
+ end
310
+
266
311
  # @param endpoint [String, nil] the installation's base URL. Any trailing
267
312
  # slashes are dropped; a path prefix is preserved, so an installation
268
313
  # behind `https://tools.example.com/specguard` works.
@@ -270,9 +315,15 @@ module SpecGuard
270
315
  # @param timeout [Numeric, String, nil] seconds. Anything that is not a
271
316
  # positive finite number falls back to the default rather than raising:
272
317
  # a typo in `SPECGUARD_TIMEOUT` must not be able to fail a suite.
273
- def initialize(endpoint:, api_key:, timeout: Configuration::DEFAULT_TIMEOUT_SECONDS)
318
+ # @param repository_id [String, Integer, nil] when present, POST to the
319
+ # repository-scoped route an `sga_` agent key needs. nil/blank keeps the
320
+ # `sgk_` path byte-identical. An id outside {REPOSITORY_ID_PATTERN} makes
321
+ # {#uri} raise `ArgumentError`.
322
+ def initialize(endpoint:, api_key:, timeout: Configuration::DEFAULT_TIMEOUT_SECONDS, repository_id: nil)
274
323
  @endpoint = endpoint
275
324
  @api_key = api_key
325
+ @repository_id = repository_id.to_s.strip
326
+ @repository_id = nil if @repository_id.empty?
276
327
  @timeout = sanitize_timeout(timeout)
277
328
  end
278
329
 
@@ -301,7 +352,8 @@ module SpecGuard
301
352
  return Result.new(outcome: :success, code: code, body: success_body(response)) if
302
353
  response.is_a?(Net::HTTPSuccess)
303
354
 
304
- Result.new(outcome: :rejected, code: code, reasons: refusal_reasons(response))
355
+ Result.new(outcome: :rejected, code: code, reasons: refusal_reasons(response),
356
+ repository_scoped: !@repository_id.nil?)
305
357
  rescue ScriptError, StandardError => e
306
358
  # Connection refused, DNS failure, TLS failure, open/read timeout, a
307
359
  # malformed endpoint — one family, one shape. `ScriptError` is in the
@@ -427,7 +479,7 @@ module SpecGuard
427
479
  base = @endpoint.to_s.strip
428
480
  raise ArgumentError, "no endpoint is configured (set SPECGUARD_ENDPOINT)" if base.empty?
429
481
 
430
- parsed = URI.parse(base.sub(%r{/+\z}, "") + PATH)
482
+ parsed = URI.parse(base.sub(%r{/+\z}, "") + ingest_path)
431
483
  # `URI::HTTPS < URI::HTTP`, so this admits both and rejects the
432
484
  # scheme-less `specguard.example.com` that `URI.parse` happily returns
433
485
  # as a `URI::Generic` with a nil host — which `Net::HTTP` would then
@@ -438,6 +490,17 @@ module SpecGuard
438
490
  parsed
439
491
  end
440
492
 
493
+ def ingest_path
494
+ return PATH if @repository_id.nil?
495
+
496
+ unless REPOSITORY_ID_PATTERN.match?(@repository_id)
497
+ raise ArgumentError,
498
+ "SPECGUARD_REPOSITORY_ID must contain only letters, digits, '-' and '_', got #{@repository_id.inspect}"
499
+ end
500
+
501
+ format(REPOSITORY_PATH, @repository_id)
502
+ end
503
+
441
504
  def sanitize_timeout(value)
442
505
  parsed = Float(value, exception: false)
443
506
  return Configuration::DEFAULT_TIMEOUT_SECONDS unless parsed&.finite? && parsed.positive?
@@ -9,6 +9,7 @@ require "fileutils"
9
9
  # loads them on a minitest-only consumer's machine.
10
10
  require_relative "../client"
11
11
  require_relative "../client/configuration"
12
+ require_relative "../client/delivery_warning"
12
13
 
13
14
  # The annotation lookup is the third framework-free half: what counts as a
14
15
  # test's annotation is a property of the source line, not of the framework
@@ -244,11 +245,7 @@ module SpecGuard
244
245
 
245
246
  def transport_for
246
247
  require_relative "../client/transport"
247
- SpecGuard::Client::Transport.new(
248
- endpoint: @configuration.endpoint,
249
- api_key: @configuration.api_key,
250
- timeout: @configuration.timeout
251
- )
248
+ SpecGuard::Client::Transport.from_configuration(@configuration)
252
249
  end
253
250
 
254
251
  def row_for(result)
@@ -403,12 +400,10 @@ module SpecGuard
403
400
  end
404
401
 
405
402
  # Report, not promise — see `#fall_back` for why that is the whole of
406
- # this slice, and why the order above it did not move.
403
+ # this slice, and why the order above it did not move. The wording is
404
+ # shared with the RSpec formatter in {Client::DeliveryWarning}.
407
405
  def sink_clause(path, error)
408
- return "Falling back to the replay queue. The test run is unaffected." if error.nil?
409
-
410
- "The replay queue #{path} could not be written either " \
411
- "(#{error.class}: #{error.message}), so this run's telemetry was lost."
406
+ SpecGuard::Client::DeliveryWarning.sink_clause(path, error)
412
407
  end
413
408
 
414
409
  # Once per process, like the formatter's `warn_once`: fifty failing
@@ -48,6 +48,7 @@ require "fileutils"
48
48
  require_relative "../rspec"
49
49
  require_relative "../client/configuration"
50
50
  require_relative "../client/transport"
51
+ require_relative "../client/delivery_warning"
51
52
  # Brings the linter's discovery chain with it (Scanner, Finding, Schema). That
52
53
  # direction is safe — `specguard/rspec` does not require `rspec/core`, so the
53
54
  # linter stays loadable without RSpec; it is only the reverse that would break
@@ -1037,11 +1038,7 @@ module SpecGuard
1037
1038
  end
1038
1039
 
1039
1040
  def transport_for(configuration)
1040
- SpecGuard::Client::Transport.new(
1041
- endpoint: configuration.endpoint,
1042
- api_key: configuration.api_key,
1043
- timeout: configuration.timeout
1044
- )
1041
+ SpecGuard::Client::Transport.from_configuration(configuration)
1045
1042
  end
1046
1043
 
1047
1044
  def blank?(value)
@@ -1200,12 +1197,10 @@ module SpecGuard
1200
1197
 
1201
1198
  # Report, not promise. Composed after the write so it can tell the operator
1202
1199
  # which of the two things actually happened — see {#fall_back} for why that
1203
- # is the whole of this slice, and why the order above it did not move.
1200
+ # is the whole of this slice, and why the order above it did not move. The
1201
+ # wording is shared with the Minitest reporter in {Client::DeliveryWarning}.
1204
1202
  def sink_clause(path, error)
1205
- return "Falling back to #{path}; the test run is unaffected." if error.nil?
1206
-
1207
- "The replay queue #{path} could not be written either " \
1208
- "(#{error.class}: #{error.message}), so this run's telemetry was lost."
1203
+ SpecGuard::Client::DeliveryWarning.sink_clause(path, error)
1209
1204
  end
1210
1205
 
1211
1206
  def emit_warning(line)
@@ -7,5 +7,5 @@
7
7
  # framework-scoped version constant a lie, so the number moved here and the
8
8
  # rspec module now points at it for back-compat.
9
9
  module SpecGuard
10
- VERSION = "0.3.30"
10
+ VERSION = "0.3.32"
11
11
  end
@@ -58,9 +58,9 @@ if git rev-parse -q --verify "refs/tags/v$NEW_VERSION" >/dev/null || \
58
58
  exit 1
59
59
  fi
60
60
  if command -v curl >/dev/null 2>&1; then
61
- if curl -fsS --max-time 10 "https://rubygems.org/api/v1/versions/specguard-rspec.json" 2>/dev/null \
61
+ if curl -fsS --max-time 10 "https://rubygems.org/api/v1/versions/specguard-ruby.json" 2>/dev/null \
62
62
  | grep -q "\"number\":\"$NEW_VERSION\""; then
63
- echo "Error: specguard-rspec $NEW_VERSION is already published on RubyGems"
63
+ echo "Error: specguard-ruby $NEW_VERSION is already published on RubyGems"
64
64
  exit 1
65
65
  fi
66
66
  fi
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: specguard-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.30
4
+ version: 0.3.32
5
5
  platform: ruby
6
6
  authors:
7
7
  - specguard Agent
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-10-02 00:00:00.000000000 Z
11
+ date: 2026-10-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: json
@@ -52,6 +52,7 @@ files:
52
52
  - lib/specguard/client/annotation_scanner.rb
53
53
  - lib/specguard/client/cli.rb
54
54
  - lib/specguard/client/configuration.rb
55
+ - lib/specguard/client/delivery_warning.rb
55
56
  - lib/specguard/client/file_selector.rb
56
57
  - lib/specguard/client/finding.rb
57
58
  - lib/specguard/client/ingest_cli.rb