Test the compatibility range instead of asserting it - #14
Open
VSN2015 wants to merge 1 commit into
Open
Conversation
This was referenced Sep 4, 2026
VSN2015
commented
Sep 5, 2026
| # already loaded, because concurrent-ruby 1.3.5 stopped requiring it for them. | ||
| # One stdlib require makes `require "permittable"` work on every activesupport | ||
| # version the gemspec claims, whatever the host's own boot order. | ||
| require "logger" |
Owner
Author
There was a problem hiding this comment.
Crucial fix: explicitly requiring logger ahead of active_support prevents NameError on ActiveSupport <= 7.0.8.4 under concurrent-ruby >= 1.3.5.
| # ... default:` — how the contract registry is declared — arrived in 5.2, so | ||
| # 5.0 and 5.1 cannot declare a contract at all, and 5.2/6.0 predate Ruby 3.x | ||
| # support, which this gem's own Ruby floor requires. | ||
| spec.add_runtime_dependency "activesupport", ">= 6.1", "< 9" |
Owner
Author
There was a problem hiding this comment.
Accurate adjustment of the runtime dependency floor to >= 6.1. Given class_attribute ..., default: requirements and Ruby 3.x compatibility, this reflects the true supported range.
|
|
||
| puts "declared runtime dependencies" | ||
| check "activesupport is the only one" do | ||
| deps = Gem::Specification.find_by_name("permittable").runtime_dependencies.map(&:name).sort |
Owner
Author
There was a problem hiding this comment.
Very nice smoke test. Validating runtime_dependencies programmatically against the installed gem guarantees no transitive or dev gems leak into runtime requirements.
VSN2015
force-pushed
the
ci/compatibility-matrix
branch
from
September 11, 2026 22:01
4bc8900 to
2109249
Compare
CI ran exactly one combination — the newest of everything — while the gemspec advertised activesupport >= 5.0, < 9. Testing the range found two real problems. On activesupport 5.0 and 5.1 a contract cannot be declared at all. The registry is a `class_attribute :permittable_contracts, default: []`, and class_attribute's default: arrived in Rails 5.2, so permit_params died on `undefined method '+' for nil:NilClass`. Nobody reported it because nobody could have: Rails 5 doesn't run on Ruby 3.2, which is this gem's own floor. On activesupport <= 7.0.8.4, `require "permittable"` itself raised NameError: uninitialized constant ActiveSupport::LoggerThreadSafeLevel::Logger — concurrent-ruby 1.3.5 stopped requiring `logger` for them, and the gem's first line is `require "active_support"`. Fixed with one stdlib require ahead of it, so the gem loads whatever the host's own boot order; the spec_helper needs the same, since it requires activerecord first. The floor is now >= 6.1, which is the oldest line the full suite is actually run against. 5.2 and 6.0 do load and validate, but they predate Ruby 3.x support and claiming an untested version is the habit this commit is breaking. The matrix is one pinned gemfile per activesupport line (plain BUNDLE_GEMFILE, no new dev dependency) crossed with the supported Rubies as an explicit include list, so it doubles as the answer to "which combinations are supported?". Variant lockfiles stay uncommitted on purpose: each run resolves the newest patch of its line, so a regression fails CI rather than being frozen out by a stale lock. A second job proves the gemspec's central claim, which the spec suite cannot: it bundles actionpack and activerecord to exercise the integration and drift paths. runtime-deps installs the built gem with nothing but its declared dependencies, asserts actionpack, activerecord and rails are genuinely absent, then exercises every controller-free surface — standalone contracts, the concern on a params duck, unknown: :error with no logger to warn through, sensitive: with no Railtie, monitor mode, instrumentation, JSON Schema, OpenAPI, and the generator. Every respond_to?/defined? guard in the gem is a promise; a missing one now fails CI. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
VSN2015
force-pushed
the
ci/compatibility-matrix
branch
from
September 11, 2026 22:10
2109249 to
9c6236f
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
CI ran exactly one combination — the newest of everything — while the gemspec advertised
activesupport >= 5.0, < 9. Testing the range found two real problems.Finding 1: no contract can be declared on activesupport 5.0 or 5.1
The registry is
class_attribute :permittable_contracts, default: [], andclass_attribute'sdefault:arrived in Rails 5.2. On 5.0/5.1 it's silently ignored, so the attribute isnil:Nobody reported it because nobody could have — Rails 5 doesn't run on Ruby 3.2, which is this gem's own floor. The advertised range was simply wrong.
Finding 2:
require "permittable"raised on activesupport ≤ 7.0.8.4concurrent-ruby 1.3.5 stopped requiring
loggerfor those versions, and this gem's first line isrequire "active_support". Fixed with one stdlibrequire "logger"ahead of it, so the gem loads whatever the host's own boot order happens to be.spec/spec_helper.rbneeds the same, since it requires activerecord first.The floor is now
>= 6.1That's the oldest line the full suite is actually run against. 5.2 and 6.0 do load and validate (verified), but they predate Ruby 3.x support — and claiming an untested version is exactly the habit this PR breaks. The gemspec now says why, in a comment, so nobody has to rediscover it.
The matrix
One pinned gemfile per activesupport line under
gemfiles/— plainBUNDLE_GEMFILE, no new dev dependency — crossed with the supported Rubies as an explicit include list rather than a cross product, so it doubles as the answer to "which combinations are supported?":Gemfile)Variant lockfiles stay uncommitted on purpose: each run resolves the newest patch of its line, so a regression in a supported version fails CI instead of being frozen out by a stale lock.
Verified locally on Ruby 3.2 — 199 examples, 0 failures on every one of 6.1, 7.0, 7.1, 7.2, 8.0, 8.1 and the root Gemfile.
A job that proves the gem's central claim
"activesupport is the only runtime dependency" is the gemspec's main promise, and the spec suite cannot test it — it bundles actionpack and activerecord to exercise the integration and schema-drift paths.
The new
runtime-depsjob installs the built gem with nothing but its declared dependencies, asserts actionpack/activerecord/rails are genuinely absent, and then exercises every controller-free surface:Every
respond_to?/defined?guard in the gem is a promise. A missing one now fails CI instead of a user's boot. The nested-plain-hash check is the 0.5.1 regression, permanently pinned outside Rails.Verification
runtime_deps_smoke.rbpasses against the built gem in an isolatedGEM_HOME