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 +4 -4
- data/CHANGELOG.md +12 -4
- data/README.md +5 -4
- data/lib/conformance_kit/shareability.rb +2 -2
- data/lib/conformance_kit/store.rb +15 -4
- data/lib/conformance_kit/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7194b9197bb0f74d0984120097dcb41a80e518b9de1ba8818f0b0007b4614c09
|
|
4
|
+
data.tar.gz: d830a25c1493c79747f91312287f2b856f4450bd2f4636f6fd6bff9435c0ffcb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
12
|
-
# build a value
|
|
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
|
|
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
|
-
|
|
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
|