conformance-kit 0.4.0 → 0.4.2

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: 6f0701a2aba3683c8dff95e66fb8721c6a5dc51211e3afdb10ab30eaf8dae480
4
- data.tar.gz: def47390a131f01068f3da132c1054ba763ceff96a64a103d7b39064e4d60f7b
3
+ metadata.gz: 7194b9197bb0f74d0984120097dcb41a80e518b9de1ba8818f0b0007b4614c09
4
+ data.tar.gz: d830a25c1493c79747f91312287f2b856f4450bd2f4636f6fd6bff9435c0ffcb
5
5
  SHA512:
6
- metadata.gz: b875f57d615bb14cfe2bbb42f0250409c3dcf91e30c344d44a59f3dc3a71a8ed4c7ddb64dc56972db6bf86292a4ee049483c9ee89fb49ad568fc0f3a6cd1e3d1
7
- data.tar.gz: 6b52bcfc88febff716fa8554ab6b4c44c3729b8762d94151f48c535a0af6d5207fa27db63e5e64a10d879fda0bf96653f3b7f2e10626df770342abb502c99eed
6
+ metadata.gz: 387a7061b29d5f30d6bbc0316ca9d68601b981bce2b37a0afc5d03fa462e1bb3803a5ad3362bf822c106f154ab5b6df614a69e26eff5df0791294da79dc01353
7
+ data.tar.gz: 2b47a5f0f484b3b71382ae21630255b821d4bb420512b5cc6e2e5be69a3d44f2a9b79072c937d0644c8209e16320b5099bf4ceee450560f70ae7d94a06acb4cd
data/CHANGELOG.md CHANGED
@@ -1,18 +1,26 @@
1
1
  # Changelog
2
2
 
3
- ## 0.4.0
3
+ ## 0.4.2 - 2026-10-17
4
+
5
+ - A Proc or lock found inside another value now reads as one sentence: `[:save] is a Proc that is never shareable: ...` and `.@lock is a Thread::Mutex that is main-only: ...` instead of `is a Proc is never shareable` (in `conformance-audit` findings and `"a boot-finalized configuration"` failures; a top-level one reads `a Proc that is never shareable`).
6
+
7
+ ## 0.4.1 - 2026-10-16
8
+
9
+ - `ConformanceKit::Store.fetch` on a non-main Ractor below Ruby 3.4: threads that race on the first fetch now get the first value stored instead of each keeping its own (the race shrinks from the whole initializer to the publish; a thread switch inside the publish itself remains possible, as no Ractor-local lock exists before 3.4) (found by zoolutions/docs-kit#103, whose copy of the Store had the same `||= [yield]`).
10
+
11
+ ## 0.4.0 - 2026-10-15
4
12
 
5
13
  - `main_only:` takes `ancestor:` rules (with optional `ivars:`): framework class state on the gem's own subclasses (ActiveRecord, ActionController, Rails::Engine, ActiveSupport::Concern, ...) in one entry per framework instead of one per ivar per class. Constants are never covered; unused rules are listed as stale; an entry with an unknown key, or an ancestor every module has (`Object`, `Module`, ...), is a config error. `Audit::Finding` gains a `rule` member (the covering rule, or nil).
6
14
  - `conformance-audit` no longer crashes on a `BasicObject` value (ActiveRecord keeps one in an unresolved model's `@primary_key`): the finding names its class through `Kernel#class`.
7
15
 
8
- ## 0.3.0
16
+ ## 0.3.0 - 2026-10-15
9
17
 
10
18
  Breaking (pre-1.0): `conformance.yml` is a reusable workflow that each repo calls, instead of one an org ruleset requires. It runs on `workflow_call` only. A repo adds a caller pinned to a release tag (`uses: zoolutions/conformance-kit/.github/workflows/conformance.yml@vX.Y.Z`, see the README) and makes `conformance / Conformance settings` and `conformance / Conformance (Ruby 4.0)` required checks in its own branch ruleset. Repos outside zoolutions can use it the same way.
11
19
 
12
20
  - The version handshake reads `job.workflow_ref` (the called workflow's own tag) instead of `github.workflow_ref` (the caller's).
13
21
  - This repo calls its own workflow through `.github/workflows/conformance-self.yml`.
14
22
 
15
- ## 0.2.0
23
+ ## 0.2.0 - 2026-10-14
16
24
 
17
25
  Breaking (pre-1.0): the reusable workflows `ractor.yml` and `probes.yml` are replaced by `conformance.yml`, which an org ruleset requires at a release tag. A repo opts in with `.conformance.yml`, `gem "conformance-kit", "~> 0.2"` and `gem "rubocop-thread_safety", ">= 0.8"` in its Gemfile, and `inherit_gem: { conformance-kit: config/rubocop.yml }` in `.rubocop.yml` (`conformance-kit doctor` checks all three); it needs no workflow edits.
18
26
 
@@ -22,7 +30,7 @@ Breaking (pre-1.0): the reusable workflows `ractor.yml` and `probes.yml` are rep
22
30
  - `audit.strict_autoloads: true` makes `--strict` fail on unloaded autoloads not listed in `audit.skip_autoloads` (default `false`, 0.1's behaviour).
23
31
  - `.github/workflows/conformance.yml`: the Ruby 4.0 conformance job (with a `postgres:18` service and the version handshake from `github.workflow_ref`) and the report-only compiler probes.
24
32
 
25
- ## 0.1.0
33
+ ## 0.1.0 - 2026-10-14
26
34
 
27
35
  - Shared examples (`require "conformance_kit/rspec"`): `"thread-safe under contention"`, `"fiber-safe under an Async reactor"`, `"a boot-finalized configuration"` and `"runs inside a non-main Ractor"` (Ruby 4.0+, tagged `:ractor`).
28
36
  - The same checks as Minitest assertions (`require "conformance_kit/minitest"`): `assert_thread_safe`, `assert_fiber_safe`, `assert_boot_finalized`, `assert_runs_in_ractor`.
data/README.md CHANGED
@@ -16,7 +16,7 @@ Background, the finalize-at-boot pattern and the settled decisions: [zoolutions/
16
16
  ```ruby
17
17
  # Gemfile
18
18
  group :development, :test do
19
- gem "conformance-kit", "~> 0.3", require: false
19
+ gem "conformance-kit", "~> 0.4", require: false
20
20
  gem "rubocop-thread_safety", ">= 0.8", require: false # for config/rubocop.yml
21
21
  gem "async", require: false # optional: runs the fiber example
22
22
  end
@@ -109,7 +109,7 @@ Requires the files, walks every loaded module under the namespaces (`--require`
109
109
 
110
110
  ```
111
111
  conformance-audit: 2 findings in MyGem (1 allowlisted main-only; 14 modules audited, 0 unloaded autoloads skipped)
112
- MyGem::LOCK (constant) Thread::Mutex: a Thread::Mutex is main-only: keep per-Ractor state in ConformanceKit::Store, or allowlist it main_only
112
+ MyGem::LOCK (constant) Thread::Mutex: a Thread::Mutex that is main-only: keep per-Ractor state in ConformanceKit::Store, or allowlist it main_only
113
113
  MyGem::Config.@colors (module ivar) Hash: [:primary] is an Array that is not frozen: freeze it at boot (Ractor.make_shareable)
114
114
  ```
115
115
 
@@ -182,7 +182,7 @@ Turns on the six `rubocop-thread_safety` cops (`ClassAndModuleAttributes`, `Clas
182
182
  Conformance runs as **one reusable workflow in this repo**, `.github/workflows/conformance.yml`. Any repo can call it, inside zoolutions or not. A repo opts in with
183
183
 
184
184
  1. `.conformance.yml` (above),
185
- 2. the Gemfile lines (`conformance-kit ~> 0.3`, `rubocop-thread_safety >= 0.8`),
185
+ 2. the Gemfile lines (`conformance-kit ~> 0.4`, `rubocop-thread_safety >= 0.8`),
186
186
  3. `inherit_gem: { conformance-kit: config/rubocop.yml }` in `.rubocop.yml`,
187
187
  4. a caller, pinned to a release tag:
188
188
 
@@ -199,7 +199,7 @@ permissions:
199
199
 
200
200
  jobs:
201
201
  conformance:
202
- uses: zoolutions/conformance-kit/.github/workflows/conformance.yml@v0.3.0
202
+ uses: zoolutions/conformance-kit/.github/workflows/conformance.yml@v0.4.2
203
203
  ```
204
204
 
205
205
  To make it a merge requirement, add `conformance / Conformance settings` and `conformance / Conformance (Ruby 4.0)` to the required status checks in the repo's branch ruleset. Require both: if the settings job fails, the gate is skipped, and a skipped check does not block a merge. The prefix is the caller's job id (`conformance` above) and the Ruby is `ractor.ruby`, so the names change with either. Leave the probes out: they never fail. A repo or org that only allows selected actions must allow `zoolutions/conformance-kit/.github/workflows/conformance.yml@*`.
@@ -220,6 +220,7 @@ version: 2
220
220
  multi-ecosystem-groups:
221
221
  conformance-kit:
222
222
  schedule: { interval: weekly }
223
+ labels: ["dependencies", "devops"] # your repo's own type + area labels
223
224
  updates:
224
225
  - package-ecosystem: github-actions
225
226
  directory: /
@@ -79,10 +79,10 @@ module ConformanceKit
79
79
  def reason(value)
80
80
  name = class_name(value)
81
81
  if callable?(value)
82
- "#{article(name)} #{name} is never shareable: use Ractor.shareable_proc (Ruby 4.0) " \
82
+ "#{article(name)} #{name} that is never shareable: use Ractor.shareable_proc (Ruby 4.0) " \
83
83
  "or Ractor.make_shareable on a capture-free proc"
84
84
  elsif lock?(value) || concurrent?(value)
85
- "#{article(name)} #{name} is main-only: keep per-Ractor state in ConformanceKit::Store, " \
85
+ "#{article(name)} #{name} that is main-only: keep per-Ractor state in ConformanceKit::Store, " \
86
86
  "or allowlist it main_only"
87
87
  elsif !frozen?(value)
88
88
  "#{article(name)} #{name} that is not frozen: freeze it at boot (Ractor.make_shareable)"
@@ -8,8 +8,11 @@ module ConformanceKit
8
8
  # a Mutex, created at load (never lazily, so there is no check-then-set race);
9
9
  # it is the kit's one allowlisted main-only state (.conformance.yml). A
10
10
  # non-main Ractor on 3.2/3.3 cannot read it, so it keeps its own values in
11
- # Ractor-local storage instead (not locked: one Ractor's threads may race to
12
- # build a value, as with any `||=`).
11
+ # Ractor-local storage instead. No lock can be shared there, so threads of
12
+ # one Ractor may each build a value; they all return the first one
13
+ # published, except in the rare case of a thread switch inside the publish
14
+ # itself (`Ractor.current[slot] ||= built` is a read and a write), which no
15
+ # Ractor-local primitive before 3.4 can close.
13
16
  module Store
14
17
  # rubocop:disable ThreadSafety/MutableClassInstanceVariable -- main-only fallback, guarded by @fallback_lock
15
18
  @fallback_lock = Mutex.new
@@ -31,9 +34,17 @@ module ConformanceKit
31
34
 
32
35
  def main_ractor? = !defined?(::Ractor) || ::Ractor.current == ::Ractor.main
33
36
 
34
- # Boxed, so a nil or false value is cached rather than rebuilt on every fetch.
37
+ # Boxed, so a nil or false value is cached rather than rebuilt on every
38
+ # fetch. Built before the check-and-publish, never between them: threads
39
+ # that raced on the build return whichever value was stored first (see
40
+ # the module comment for the switch-inside-the-publish limit).
35
41
  def ractor_local(key)
36
- (::Ractor.current[:"conformance_kit_store/#{key}"] ||= [yield]).first
42
+ slot = :"conformance_kit_store/#{key}"
43
+ stored = ::Ractor.current[slot]
44
+ return stored.first if stored
45
+
46
+ built = [yield]
47
+ (::Ractor.current[slot] ||= built).first
37
48
  end
38
49
 
39
50
  public
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ConformanceKit
4
- VERSION = "0.4.0"
4
+ VERSION = "0.4.2"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: conformance-kit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.4.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - mhenrixon