Ruby and Rails standards
Criteria verified as of August 2026. Re-verify on the web before committing to anything (§8).
1. Scope and triggers
Applies to all Ruby work: Rails applications, gems, Ruby scripts, Rakefiles, review and upgrade.
Triggers: .rb, .rake, .gemspec, .erb, Gemfile, Gemfile.lock, .ruby-version, .rubocop.yml,
config/, app/models, db/migrate, spec/, test/, sig/, sorbet/, bundle, rails, gem.
Sets criteria (what to use, what is vetoed, what to verify), not tutorials.
Not applicable: see api-design-standards (the API contract — resources, codes, pagination,
RFC 9457, versioning — is theirs; here only its implementation in controllers and serializers),
microservices-architecture-standards (where a service is cut and how they talk to each other; here only
the Ruby inside), kubernetes-standards and container-runtime-security-standards (OCI packaging
and runtime of the container running Puma), cicd-standards (the pipeline and its gates; here only which
tool is run and with what configuration), secrets-management-standards (owner of the choice
of secrets manager and of the secret scanner; here only that Rails does not put them in the repo),
appsec-standards and vulnerability-management-standards (threat modelling, process and triage;
here the code criteria and which gate breaks the build), sql-standards (the SQL language itself),
data-platform-standards, mysql-mariadb-dba-standards, oracle-dba-standards and
sqlserver-dba-standards (engine operation: tuning, replicas, backup, HA). Active Record decides
how the application accesses, not how the database is operated.
python-standards, go-standards, typescript-standards, jvm-spring-standards, rust-standards,
php-standards, elixir-erlang-standards (choice of language and implementation in each);
against elixir-erlang-standards in particular: they share syntactic origin and much of the
community, but not the execution model — the boundary is the BEAM (processes, supervision,
distribution) versus the CRuby VM (GVL, OS processes, Puma/Sidekiq).
bash-linux-scripting-standards (system automation: if the Ruby operations script is
glue around commands, it is theirs; if it needs data structures and tests, it comes back here),
iac-standards (the infrastructure DSL of Chef/Puppet/Vagrant belongs to iac-standards even if
written in Ruby; the Ruby that is written inside — libraries, custom resources, tests — belongs to
this skill), observability-standards (telemetry strategy and pipeline; here the instrumentation
in the code), message-brokers-standards (Kafka/RabbitMQ as infrastructure; here the Ruby
consumer and the job queue), crystal-standards (boundary named because the
confusion is real: Crystal's compatibility with Ruby is syntactic, not semantic nor of
libraries. There are no gems, no runtime method_missing, no dynamic monkey patching:
a Ruby file is not ported by changing its extension. If the reason to migrate is performance,
measure it first with YJIT), smalltalk-standards (direct lineage: Ruby's object and
message model comes from there; living Smalltalk —Pharo, Squeak, GemStone/S, VAST— is theirs).
2. Default toolchain
Verify the latest version on the web before pinning it in a real project (§8).
| Piece |
Choice |
Minimum |
Why |
| Runtime |
CRuby (MRI) |
3.4 in maintenance; 4.0 in greenfield |
4.0 shipped 2025-12-25 (4.0.6 as of 2026-07); 3.4 EOL 2028-03-31, 3.3 EOL 2027-03-31 |
| Version manager |
mise (or rbenv if the team already uses it) |
— |
asdf valid if it already manages other runtimes; never the system Ruby |
| Version pinning |
.ruby-version + ruby "x.y.z" in Gemfile |
— |
A single source of truth, read by the manager and by Bundler |
| Dependencies |
Bundler + committed Gemfile.lock |
— |
bundle install --frozen / BUNDLE_FROZEN=true in CI |
| Web framework |
Rails 8.1 |
8.0 minimum |
8.1 shipped 2025-10-22, EOL 2027-10-10; 7.2 EOL 2026-08-09; 8.1 requires Ruby ≥3.2 |
| Linter/formatter |
RuboCop 1.88+ with rubocop-rails, rubocop-rspec, rubocop-performance |
— |
MIT. Alternative: standard (closed config) |
| SAST |
Brakeman 8.x |
— |
⚠️ NOT MIT: "Brakeman Public Use License" (Synopsys) — commercial use requires a paid licence (§7) |
| SCA |
bundler-audit (ruby-advisory-db) + Dependabot/Renovate |
0.9.3+ |
GPL-3.0-or-later |
| Tests |
RSpec 3.13 or Minitest 6 (criteria below) |
— |
RSpec 4 only in beta as of 2026-08 (4.0.0.beta1, Feb 2026): not in production |
| Safe migrations |
strong_migrations |
2.8+ |
Blocks DDL that takes long locks |
| Jobs |
Solid Queue (Rails 8 default) or Sidekiq 8 |
— |
Solid Queue 1.6+; Sidekiq is LGPL-3.0, check the legal fit |
| Gradual typing |
RBS 4 + Steep 2 or Sorbet (criteria below) |
— |
Neither is mandatory; adopt only with a maintenance budget |
| Server |
Puma |
— |
Workers and threads sized, not left at defaults |
Ruby version policy: one Ruby minor per year, within 6 months of its release.
Ruby ≤3.2 is EOL (3.2 died 2026-03-31) → it is not deployed, not "we will upgrade later".
Rails: from supported release to supported release; an app on a Rails whose support has expired is an open
security incident, not technical debt.
JIT: YJIT is the production option (mature since Ruby 3.2, deployed at scale at Shopify,
Discourse, Mastodon; real gains of 15-25 % in Rails apps, much smaller if the bottleneck is the
database or an external API). ZJIT (Ruby 4.0, written in Rust, --zjit) is experimental:
Ruby 4.0's own notes describe it as faster than the interpreter but slower than
YJIT → forbidden in production until the official source says otherwise. Enable YJIT
explicitly (--yjit / RUBY_YJIT_ENABLE=1) and measure before and after; verify in §8 whether some
version already enables it by default. YJIT consumes extra memory: bound it with --yjit-mem-size.
RSpec vs Minitest — criteria, not sides:
- Minitest if: it is a gem, the team is small, you want zero DSL and the Rails default.
- RSpec if: the app is large, the team already masters it, and you exploit
shared_examples and domain
matchers. The cost is the DSL: nested let and implicit subject produce unreadable tests.
- Pick one per repo. Two test frameworks coexisting is debt, not flexibility.
Gradual typing — real state as of 2026-08 and criteria:
- RBS (official from the core team, ships with Ruby) 4.1.x + Steep 2.0: signatures in separate
.rbs files, structural typing. Advantage: it is the language standard. Cost: worse editor tooling
and signatures that drift out of sync with the code.
- Sorbet (Stripe/Shopify, Apache-2.0, continuous releases —
0.6.x with build-based versioning):
inline sig annotations + .rbi, nominal typing, checking also at runtime. Advantage: more polished
tooling and the largest industrial adoption base. Cost: invasive syntax in the Ruby code.
- Neither is the ecosystem default. Adopt types only if there is a domain with invariants that the
tests do not cover well and continuous budget to maintain the signatures. A published experiment
measured that raising test coverage found more bugs than gradual typing: if you have to choose
with limited resources, tests first.
- If you adopt: only one, gate in CI (
steep check or srb tc), and sig/sig/ mandatory on the public
API. Half-done typing without a gate is worth nothing.
3. Structure and conventions
- Convention over configuration: in Rails, respect the standard layout. Relocating directories or
inventing your own autoloading breaks Zeitwerk and adds nothing.
- Modern Rails = Rails 8 defaults: Propshaft (not Sprockets), Solid Queue / Solid Cache /
Solid Cable, Kamal 2 for deploy, Importmap or jsbundling depending on the front end. In existing apps the
migration to the "Solid" stack is optional and incremental — it is not a requirement to be current.
app/models is not the dumping ground for logic. Domain objects in app/services,
app/queries, app/policies, app/forms; the model persists and validates, it does not orchestrate.
- Active Record callbacks: the number one source of hidden coupling. Veto
after_save that
calls external services, sends mail or enqueues jobs without transactional control. Whatever triggers
effects goes in an explicit service object, invoked from the use case.
default_scope: forbidden. It contaminates every query, surprises in unscoped, breaks
create/update and makes any debugging unreadable. Use named scopes.
Concern with state: an ActiveSupport::Concern that adds callbacks and attributes to the
including class is inheritance in disguise. Concerns only for behaviour without shared state.
- Migrations and
schema.rb/structure.sql committed; a single source of truth for the schema.
If you use PostgreSQL features that schema.rb does not represent (types, functions, triggers,
exclusion constraints), switch to structure.sql — not "it almost works".
- Gem vs monolith: extract a gem only when there are ≥2 real consumers and an owner. An internal
gem with one consumer is a monolith with an extra repo, version and CI hop. Inside
the monolith, use modules/engines with explicit boundaries before splitting it.
- Publishable gems:
.gemspec with required_ruby_version, metadata["rubygems_mfa_required"] = "true", SemVer, changelog, and publication from CI with Trusted Publishing (OIDC) — never with
a long-lived API key in a secret nor gem push from the laptop.
Gemfile: no version → forbidden in production. Constraint by ~> in gems, exact in the lock.
No git: or path: pointing at branches on the main branch.
- Dependency policy: every new gem is justified (what does it solve that stdlib or
Rails does not?), maintenance is checked (last release, open issues, number of maintainers) and
licence. A gem with no release in >18 months is reviewed or replaced.
4. Quality and testing
- Format and lint:
rubocop with NewCops: enable and the extensions rubocop-rails,
rubocop-rspec/rubocop-minitest, rubocop-performance. Exact pin of the RuboCop version
in the Gemfile: every minor adds cops and breaks CI if it floats.
- Mandatory cops (do not disable): the whole
Security department (Security/Eval,
Security/Open, Security/YAMLLoad, Security/MarshalLoad, Security/JSONLoad),
Lint/* (especially Lint/SuppressedException, Lint/ShadowedException), Rails/SaveBang,
Rails/OutputSafety, Rails/SkipsModelValidations, Rails/UniqueValidationWithoutIndex,
Rails/HasManyOrHasOneDependent, Rails/Output, Rails/TimeZone.
- Debatable and team-adjustable:
Metrics/*, Style/*. Set the values once in
.rubocop.yml and stop arguing about them.
rubocop:disable always with a specific cop and a reason on the same line. .rubocop_todo.yml
is a debt list with an expiry date, not a permanent file.
standard (standardrb) as an alternative: when the team loses more time arguing about
.rubocop.yml than writing code. It is RuboCop with closed configuration. You give up the domain
cops (Rails, RSpec) unless you add their plugins. If you need your own rules, it is RuboCop.
- Tests:
- AAA, one failure reason per test, names that describe observable behaviour.
- Happy path and edges and errors: invalid input, non-existent record, denied permissions,
race conditions, external service timeouts, uniqueness validations under concurrency.
- Logic in tests is forbidden: no
if/each/calculations that rebuild the expected
result. Literal expected value or the test proves nothing.
factory_bot: minimal factories (only what is mandatory), build/build_stubbed by default and
create only when the DB is needed. Traits for variants; factories that create
association trees "just in case" are forbidden — they are the usual reason for a slow suite.
- Fixtures: valid and fast for stable reference data (countries, plans, roles). As the
base of all domain tests they become global coupling; do not mix them with
factories for the same thing.
- System tests (Capybara) only as many as needed: expensive and fragile. Pyramid, not hourglass.
- Forbidden:
sleep in tests, order dependence and calls to the public network (webmock/vcr with
reviewed cassettes and no secrets inside).
- Every fixed bug leaves a regression test. Flaky = fixed or deleted.
- CI gates (block merge, from cheap to expensive):
bundle install --frozen / bundle exec bundler-audit check --update
rubocop --parallel (or standardrb)
brakeman --no-pager -q -w2 (fails on high/medium confidence findings)
steep check / srb tc if the repo adopted types
rspec/rails test unit → integration with the same database as production
- Migration audit (
strong_migrations) and image build
- Rails 8.1 ships
bin/ci to run the same sequence locally: use it, but CI is the
authority, not the local hook. If CI does something irreproducible locally, it is a pipeline bug.
- Coverage (
simplecov) as a signal, not a target; agreed threshold, no tricks to raise it.
5. Stack security
- Deserialisation — Ruby's historic sink:
- ❌
YAML.load, YAML.load_file, Psych.load over untrusted input. Use
YAML.safe_load with explicit permitted_classes. (Psych 4+ makes load safe by default,
but do not depend on the version: write safe_load and leave the Security/YAMLLoad cop enabled.)
- ❌
Marshal.load over any data coming from outside the process. Never, without exception.
- ❌
JSON.load (use JSON.parse), ERB with a user-controllable template. Recent
precedent: CVE-2026-41316, bypass of ERB's deserialisation guard via
def_module/def_method/def_class — verify the advisory in §8.
- ❌ Session cookies with
Marshal as serialiser: use :json.
- Dynamic execution: ❌
eval, instance_eval, class_eval, binding.eval with user
data. ❌ send/public_send/constantize/safe_constantize with a name coming from the
request — if you need dynamic dispatch, an explicit allowlist of permitted symbols.
❌ system, backticks, Kernel#open, %x{} with interpolation of input.
- Mass assignment: strong parameters always (
params.require(...).permit(...)). ❌ permit!.
❌ permit including :role, :admin, :user_id or any authorisation field.
- SQL injection: only parameterised queries (hash conditions or
where("x = ?", v)). ❌
string interpolation in where, order, pluck, find_by_sql, joins. order with a
user parameter requires a column allowlist — it is the classic vector and the ORM does not protect you.
- XSS: ERB escapes by default; ❌
html_safe, raw and sanitize over user content without
a strict allowlist. Cop Rails/OutputSafety enabled. CSP configured (content_security_policy in
Rails), not permissive for convenience.
- CSRF:
protect_from_forgery with the default strategy (:exception), never :null_session
on endpoints with a session. ❌ global skip_before_action :verify_authenticity_token; if an API
endpoint needs it, it is because it should authenticate with a token and not with a session cookie.
- Authorisation: on every action, not just in the generic
before_action. Pundit/Action Policy
with default deny and a test that verifies no action is left without a policy. IDOR is prevented
by always querying from the user's scope (current_user.orders.find(params[:id])), never
Order.find(params[:id]).
- Secrets:
config/credentials.yml.enc is acceptable for small apps if master.key is not
in the repo and is injected by environment; in an organisation, an external manager (see
secrets-management-standards). ❌ secrets in plain config/*.yml, in logs or in fixtures.
filter_parameters covering password, token, secret, and the sensitive domain fields.
- SAST: Brakeman as a gate. ⚠️ Its licence is not OSS: "Brakeman Public Use License" from
Synopsys — analysing your own software is not "commercial use", but distributing it or including it in a
paid service does require a commercial licence. Verify the current text before putting it into a
product (§8).
- SCA and supply chain — RubyGems has been the scene of real and recent attacks:
- 2026-05: massive flood of accounts and malicious gems that forced RubyGems.org to temporarily
suspend registration of new accounts; none had a CVE and maintainer reputation
was useless (freshly created accounts).
- 2026-04/05: campaign attributed to "BufferZoneCorp", gems impersonating well-known names
(
activesupport-logger, devise-jwt) targeting CI runners to steal credentials.
- 2026-06: 14 gems used as a dead drop for data exfiltrated by a browser extension.
- 2026-07: "SleeperGem" campaign (
git_credential_manager, Dendreo) with a second-stage payload.
- Operational consequences, not optional:
bundler-audit as a gate; committed lockfile and
--frozen in CI; human review of every new gem (exact name, maintainer, downloads,
date of first release); bundle config set --local disable_platform_warnings does not replace
reading the Gemfile.lock diff in the PR; CI does not expose secrets to the installation of
dependencies (bundle install in a job without cloud credentials or a write GITHUB_TOKEN).
- A
gemspec can execute code during bundle install (native extensions, build hooks):
assume that installing a gem is executing third-party code and isolate the job accordingly.
- Consider a cooldown on new versions (delaying adoption of a freshly published release);
several tools and mirrors already offer it — verify which applies to your registry (§8).
- Publication: Trusted Publishing (OIDC) from GitHub Actions with
id-token: write and a protected
environment; rubygems_mfa_required in the gemspec. RubyGems.org's mandatory MFA covers only the
most downloaded gems: do not assume your gem is protected by registry policy.
- Errors: never a stack trace or internal path to the client;
config.consider_all_requests_local = false in production, neutral error page with a correlation id.
6. Performance and operability
- N+1 is a bug, not a pending optimisation:
includes/preload/eager_load as appropriate
(includes decides on its own; preload forces two queries; eager_load forces the JOIN). Automatic
detection in the test/development environment (bullet or Rails' strict_loading) and test
failure, not a log nobody reads. strict_loading by default in new models.
- Always paginate collections (default and maximum limit).
find_each/in_batches for
massive traversals; ❌ .all.each over a production table.
- Safe migrations (
strong_migrations): no adding a column with a non-volatile default on
old engines, no indexes without algorithm: :concurrently on PostgreSQL, no change_column that
rewrites the table, nor renaming/removing columns in the same release as the code that uses them.
Expand/contract pattern mandatory: the migration must be compatible with version N-1 of the
code throughout the rolling deploy. Destructive ones go in a later release.
- Jobs:
ActiveJob as the interface, explicit backend. Solid Queue (Rails 8 default, uses
FOR UPDATE SKIP LOCKED, does not require Redis) is the default option in greenfield; Sidekiq 8
if it is already there, if you need its ecosystem, or for measured performance — with the
LGPL-3.0 licence warning for Sidekiq OSS. Common rules:
- Serialisable and small arguments: pass IDs, not objects or payloads.
- Idempotency mandatory: every job is retried; if retrying it charges twice, it is a bug.
- Enqueue after the commit (
after_commit), never inside the transaction: the worker may
pick up the job before the row exists.
- Explicit timeouts and retry limits; monitored failure queue (dead set).
- Queues separated by latency (critical / default / slow), not by team.
- Long jobs: Rails 8.1 brings Active Job Continuations (splitting into resumable steps) —
relevant because a deploy with Kamal gives the jobs container a short shutdown window.
- Cache: Solid Cache (Rails 8 default, database-backed) or Redis/Valkey if it already exists.
Key with schema version, explicit TTL, designed invalidation. Russian doll caching in views
only if there is measurement behind it. ❌ cache without TTL "we will clean it by hand later".
- Puma:
WEB_CONCURRENCY (processes) and threads sized against the Active Record pool —
pool ≥ threads per process, or you will see checkout timeouts under load. The GVL means threads
only help with I/O; real CPU parallelism is processes.
- Timeouts on everything: HTTP client (explicit
open_timeout and read_timeout — many Ruby
clients have no default), statement_timeout in PostgreSQL, rack-timeout or equivalent at the
edge. Retries with backoff+jitter only on idempotent operations.
- Observability: structured logs with
request_id; ActiveSupport::Notifications /
Rails 8.1 Structured Event Reporting as the event source; OpenTelemetry for traces and metrics.
Liveness/readiness endpoints (rails/health out of the box, extend it with a dependency check).
❌ personal data or secrets in logs.
- Memory: CRuby fragments; watch RSS per worker and restart workers with a limit if needed
(
puma_worker_killer is a patch, not a diagnosis). Profile with stackprof/memory_profiler
before optimising; no speculative micro-optimisation.
- Orderly shutdown: SIGTERM drains in-flight requests and jobs; do not break it with
exit! or orphan
threads.
7. Long-term sustainability
- Cadence: security patches immediately; gem minors with Renovate/Dependabot and lock;
Ruby one minor a year; Rails from supported release to supported release. A big upgrade is
cheaper in small, frequent steps than as a six-month project every three years.
- Maintenance budget in every sprint. Rails deprecations (
ActiveSupport::Deprecation)
are resolved in the current release, they are not silenced.
.rubocop_todo.yml with a date; conscious debt = TODO with reason and a linked issue.
- Rails 8.1 allows marking associations
deprecated: true (:warn/:raise/:notify): use it to
retire dead relations measurably instead of "grepping for usages".
Prohibition list (veto):
- ❌ Deploying on an EOL version of Ruby (≤3.2 as of 2026-08) or of Rails without support.
- ❌
YAML.load / Marshal.load / JSON.load over untrusted input. FORBIDDEN without exception.
- ❌
eval/instance_eval/class_eval/send/public_send/constantize with user data.
- ❌
system/backticks/Kernel#open with interpolation of input.
- ❌ SQL by string interpolation in
where, order, pluck, joins, find_by_sql.
- ❌
permit!, or permitting role/ownership fields in strong parameters.
- ❌ Global
skip_before_action :verify_authenticity_token; protect_from_forgery with: :null_session
on routes with a session.
- ❌
html_safe/raw over user content.
- ❌
default_scope. ❌ Active Record callbacks with external effects (mail, HTTP, jobs).
- ❌
update_column/update_all/save(validate: false) to skip validations without a written reason.
- ❌
rescue => e that swallows the exception without logging or re-raising; rescue Exception.
- ❌ Monkey patching gems or the core in the app (use refinements or a fork with an upstream issue).
- ❌ Gems without a version in the
Gemfile; deploying without Gemfile.lock; bundle update without reviewing the diff.
- ❌ Installing dependencies in a CI job that has production credentials at hand.
- ❌ Tests with logic, with
sleep, order-dependent or that call the public network.
- ❌ ZJIT in production (experimental as of 2026-08, slower than YJIT according to the official notes).
- ❌ Destructive migration in the same release as the code that stops using the column.
- ❌ Enqueuing jobs inside the transaction that creates the data the job needs.
- ❌ Extracting an internal gem with a single consumer.
- ❌ Assuming that Brakeman or Sidekiq are MIT: verify their licence before putting them into a product.
8. Mandatory web verification
Before pinning versions or claims in a project, verify online:
- Ruby: latest stable and EOL calendar (
endoflife.date/ruby, ruby-lang.org/en/downloads).
Is 4.0 still the current series? Has 4.1 shipped? Is 3.3 already EOL (planned 2027-03-31)?
- Rails: supported version (
endoflife.date/rails, rubyonrails.org). 7.2 EOL 2026-08-09 and
8.0 EOL 2026-11-07 → check whether they have passed. Rails 9?
- JIT: does ZJIT already match or beat YJIT and is it recommended in production? Is YJIT enabled by
default in some version? Source: official release notes from ruby-lang.org, not blogs.
- Typing: state of RBS/Steep and of Sorbet — has either become the ecosystem default or has
typing entered the Ruby compiler? As of 2026-08 neither is mandatory.
- Tools: RuboCop (2.0?), RSpec (4.0 final already, or still in beta?), Minitest, Brakeman,
bundler-audit, strong_migrations, Solid Queue, Sidekiq. Pin the exact RuboCop version.
- Licences: Brakeman (Synopsys, not OSS), Sidekiq (LGPL-3.0), bundler-audit (GPL-3.0-or-later),
and any tool you are going to set as a default. Recent precedents of licence
changes that broke pipelines: Trivy and gitleaks (its action requires a commercial licence
for organisations from v2). Check the raw
LICENSE file, not the README badge.
- Security: advisories from ruby-lang.org and GitHub Advisories / osv.dev for Ruby, Rails and the gems
in the
Gemfile.lock before pinning versions. Verify specifically the CVEs cited here
(CVE-2026-41316 in ERB, CVE-2026-46727 in getaddrinfo) and whether there are later ones.
- RubyGems: state of the registry after the 2026 incidents (new MFA policies, version cooldown,
gem signing?), and whether RubyGems.org still has operational restrictions.
Declared gaps (not verified as of Aug 2026):
- Whether YJIT becomes enabled by default in some Ruby 4.x version: not verified.
- Exact coverage of RubyGems.org's mandatory MFA policy in 2026 (the download-based threshold
appears to still be in force, but no updated official announcement was located): not verified.
- General availability of Sigstore-style gem signing on RubyGems.org: not verified.
If the web contradicts this document, the web wins — flag the discrepancy.
1---2name: ruby-standards3description: Use when writing, reviewing or upgrading Ruby code and Rails applications - .rb/.rake/.gemspec/.erb files, Gemfile, Gemfile.lock, .ruby-version, Rakefile, config/application.rb, db/migrate, ActiveRecord models, Sidekiq or Solid Queue workers, RSpec spec/ or Minitest test/, .rubocop.yml, standardrb, Brakeman, bundler-audit, RBS sig/ or Sorbet sorbet/rbi, rbenv/mise/asdf Ruby toolchains, bundle exec, gem publishing to RubyGems, or Rails upgrades and YJIT/ZJIT tuning.4---56# Ruby and Rails standards78Criteria verified as of **August 2026**. Re-verify on the web before committing to anything (§8).910## 1. Scope and triggers1112Applies to all Ruby work: Rails applications, gems, Ruby scripts, Rakefiles, review and upgrade.13Triggers: `.rb`, `.rake`, `.gemspec`, `.erb`, `Gemfile`, `Gemfile.lock`, `.ruby-version`, `.rubocop.yml`,14`config/`, `app/models`, `db/migrate`, `spec/`, `test/`, `sig/`, `sorbet/`, `bundle`, `rails`, `gem`.15Sets **criteria** (what to use, what is vetoed, what to verify), not tutorials.1617**Not applicable**: see `api-design-standards` (the API **contract** — resources, codes, pagination,18RFC 9457, versioning — is theirs; here only its implementation in controllers and serializers),19`microservices-architecture-standards` (where a service is cut and how they talk to each other; here only20the Ruby inside), `kubernetes-standards` and `container-runtime-security-standards` (OCI packaging21and runtime of the container running Puma), `cicd-standards` (the pipeline and its gates; here only which22tool is run and with what configuration), `secrets-management-standards` (owner of the choice23of secrets manager and of the secret scanner; here only that Rails does not put them in the repo),24`appsec-standards` and `vulnerability-management-standards` (threat modelling, process and triage;25here the code criteria and which gate breaks the build), `sql-standards` (the SQL language itself),26`data-platform-standards`, `mysql-mariadb-dba-standards`, `oracle-dba-standards` and27`sqlserver-dba-standards` (engine operation: tuning, replicas, backup, HA). Active Record decides28**how the application accesses**, not how the database is operated.29`python-standards`, `go-standards`, `typescript-standards`, `jvm-spring-standards`, `rust-standards`,30`php-standards`, `elixir-erlang-standards` (choice of language and implementation in each);31against **`elixir-erlang-standards`** in particular: they share syntactic origin and much of the32community, but **not** the execution model — the boundary is the BEAM (processes, supervision,33distribution) versus the CRuby VM (GVL, OS processes, Puma/Sidekiq).34`bash-linux-scripting-standards` (system automation: if the Ruby operations script is35glue around commands, it is theirs; if it needs data structures and tests, it comes back here),36`iac-standards` (the **infrastructure DSL** of Chef/Puppet/Vagrant belongs to `iac-standards` even if37written in Ruby; the **Ruby that is written** inside — libraries, custom resources, tests — belongs to38this skill), `observability-standards` (telemetry strategy and pipeline; here the instrumentation39in the code), `message-brokers-standards` (Kafka/RabbitMQ as infrastructure; here the Ruby40consumer and the job queue), `crystal-standards` (boundary named because the41confusion is real: **Crystal's compatibility with Ruby is syntactic, not semantic nor of42libraries**. There are no gems, no runtime `method_missing`, no dynamic monkey patching:43a Ruby file is not ported by changing its extension. If the reason to migrate is performance,44measure it first with YJIT), `smalltalk-standards` (**direct lineage**: Ruby's object and45message model comes from there; living Smalltalk —Pharo, Squeak, GemStone/S, VAST— is theirs).4647## 2. Default toolchain4849> Verify the latest version on the web before pinning it in a real project (§8).5051| Piece | Choice | Minimum | Why |52|---|---|---|---|53| Runtime | **CRuby (MRI)** | **3.4** in maintenance; **4.0** in greenfield | 4.0 shipped 2025-12-25 (4.0.6 as of 2026-07); 3.4 EOL 2028-03-31, 3.3 EOL 2027-03-31 |54| Version manager | **mise** (or `rbenv` if the team already uses it) | — | `asdf` valid if it already manages other runtimes; **never** the system Ruby |55| Version pinning | `.ruby-version` + `ruby "x.y.z"` in `Gemfile` | — | A single source of truth, read by the manager and by Bundler |56| Dependencies | **Bundler** + committed `Gemfile.lock` | — | `bundle install --frozen` / `BUNDLE_FROZEN=true` in CI |57| Web framework | **Rails 8.1** | 8.0 minimum | 8.1 shipped 2025-10-22, EOL 2027-10-10; 7.2 EOL 2026-08-09; 8.1 requires Ruby ≥3.2 |58| Linter/formatter | **RuboCop 1.88+** with `rubocop-rails`, `rubocop-rspec`, `rubocop-performance` | — | MIT. Alternative: `standard` (closed config) |59| SAST | **Brakeman 8.x** | — | ⚠️ **NOT MIT**: "Brakeman Public Use License" (Synopsys) — commercial use requires a paid licence (§7) |60| SCA | `bundler-audit` (ruby-advisory-db) + Dependabot/Renovate | 0.9.3+ | GPL-3.0-or-later |61| Tests | **RSpec 3.13** or **Minitest 6** (criteria below) | — | RSpec 4 only in beta as of 2026-08 (`4.0.0.beta1`, Feb 2026): **not** in production |62| Safe migrations | **strong_migrations** | 2.8+ | Blocks DDL that takes long locks |63| Jobs | **Solid Queue** (Rails 8 default) or **Sidekiq 8** | — | Solid Queue 1.6+; Sidekiq is **LGPL-3.0**, check the legal fit |64| Gradual typing | **RBS 4 + Steep 2** or **Sorbet** (criteria below) | — | Neither is mandatory; adopt only with a maintenance budget |65| Server | **Puma** | — | Workers and threads sized, not left at defaults |6667**Ruby version policy**: one Ruby minor per year, within 6 months of its release.68Ruby ≤3.2 is EOL (3.2 died 2026-03-31) → **it is not deployed**, not "we will upgrade later".69Rails: from supported release to supported release; an app on a Rails whose support has expired is an open70security incident, not technical debt.7172**JIT**: **YJIT** is the production option (mature since Ruby 3.2, deployed at scale at Shopify,73Discourse, Mastodon; real gains of 15-25 % in Rails apps, much smaller if the bottleneck is the74database or an external API). **ZJIT** (Ruby 4.0, written in Rust, `--zjit`) is **experimental**:75Ruby 4.0's own notes describe it as faster than the interpreter but **slower than76YJIT** → forbidden in production until the official source says otherwise. Enable YJIT77explicitly (`--yjit` / `RUBY_YJIT_ENABLE=1`) and **measure before and after**; verify in §8 whether some78version already enables it by default. YJIT consumes extra memory: bound it with `--yjit-mem-size`.7980**RSpec vs Minitest — criteria, not sides**:81- **Minitest** if: it is a gem, the team is small, you want zero DSL and the Rails default.82- **RSpec** if: the app is large, the team already masters it, and you exploit `shared_examples` and domain83 matchers. The cost is the DSL: nested `let` and implicit `subject` produce unreadable tests.84- **Pick one per repo**. Two test frameworks coexisting is debt, not flexibility.8586**Gradual typing — real state as of 2026-08 and criteria**:87- **RBS** (official from the core team, ships with Ruby) 4.1.x + **Steep** 2.0: signatures in separate88 `.rbs` files, structural typing. Advantage: it is the language standard. Cost: worse editor tooling89 and signatures that drift out of sync with the code.90- **Sorbet** (Stripe/Shopify, Apache-2.0, continuous releases — `0.6.x` with build-based versioning):91 inline `sig` annotations + `.rbi`, nominal typing, checking also at runtime. Advantage: more polished92 tooling and the largest industrial adoption base. Cost: invasive syntax in the Ruby code.93- **Neither is the ecosystem default**. Adopt types only if there is a domain with invariants that the94 tests do not cover well and **continuous budget** to maintain the signatures. A published experiment95 measured that raising test coverage found more bugs than gradual typing: if you have to choose96 with limited resources, tests first.97- If you adopt: only one, gate in CI (`steep check` or `srb tc`), and `sig`/`sig/` mandatory on the public98 API. Half-done typing without a gate is worth nothing.99100## 3. Structure and conventions101102- **Convention over configuration**: in Rails, respect the standard layout. Relocating directories or103 inventing your own autoloading breaks Zeitwerk and adds nothing.104- Modern Rails = **Rails 8 defaults**: Propshaft (not Sprockets), Solid Queue / Solid Cache /105 Solid Cable, Kamal 2 for deploy, Importmap or jsbundling depending on the front end. In existing apps the106 migration to the "Solid" stack is **optional and incremental** — it is not a requirement to be current.107- `app/models` is not the dumping ground for logic. Domain objects in `app/services`,108 `app/queries`, `app/policies`, `app/forms`; the model persists and validates, it does not orchestrate.109- **Active Record callbacks**: the number one source of hidden coupling. Veto `after_save` that110 calls external services, sends mail or enqueues jobs without transactional control. Whatever triggers111 effects goes in an explicit service object, invoked from the use case.112- **`default_scope`: forbidden**. It contaminates every query, surprises in `unscoped`, breaks113 `create`/`update` and makes any debugging unreadable. Use named scopes.114- **`Concern` with state**: an `ActiveSupport::Concern` that adds callbacks and attributes to the115 including class is inheritance in disguise. Concerns only for behaviour without shared state.116- Migrations and `schema.rb`/`structure.sql` committed; a single source of truth for the schema.117 If you use PostgreSQL features that `schema.rb` does not represent (types, functions, triggers,118 exclusion constraints), switch to `structure.sql` — not "it almost works".119- **Gem vs monolith**: extract a gem only when there are ≥2 real consumers and an owner. An internal120 gem with one consumer is a monolith with an extra repo, version and CI hop. Inside121 the monolith, use modules/engines with explicit boundaries before splitting it.122- Publishable gems: `.gemspec` with `required_ruby_version`, `metadata["rubygems_mfa_required"] =123 "true"`, SemVer, changelog, and publication from CI with **Trusted Publishing (OIDC)** — never with124 a long-lived API key in a secret nor `gem push` from the laptop.125- `Gemfile`: no version → forbidden in production. Constraint by `~>` in gems, exact in the lock.126 No `git:` or `path:` pointing at branches on the main branch.127- **Dependency policy**: every new gem is justified (what does it solve that stdlib or128 Rails does not?), maintenance is checked (last release, open issues, number of maintainers) and129 licence. A gem with no release in >18 months is reviewed or replaced.130131## 4. Quality and testing132133- **Format and lint**: `rubocop` with `NewCops: enable` and the extensions `rubocop-rails`,134 `rubocop-rspec`/`rubocop-minitest`, `rubocop-performance`. **Exact** pin of the RuboCop version135 in the `Gemfile`: every minor adds cops and breaks CI if it floats.136 - Mandatory cops (do not disable): the whole `Security` department (`Security/Eval`,137 `Security/Open`, `Security/YAMLLoad`, `Security/MarshalLoad`, `Security/JSONLoad`),138 `Lint/*` (especially `Lint/SuppressedException`, `Lint/ShadowedException`), `Rails/SaveBang`,139 `Rails/OutputSafety`, `Rails/SkipsModelValidations`, `Rails/UniqueValidationWithoutIndex`,140 `Rails/HasManyOrHasOneDependent`, `Rails/Output`, `Rails/TimeZone`.141 - Debatable and team-adjustable: `Metrics/*`, `Style/*`. Set the values once in142 `.rubocop.yml` and stop arguing about them.143 - `rubocop:disable` **always** with a specific cop and a reason on the same line. `.rubocop_todo.yml`144 is a debt list with an expiry date, not a permanent file.145- **`standard` (standardrb)** as an alternative: when the team loses more time arguing about146 `.rubocop.yml` than writing code. It is RuboCop with closed configuration. You give up the domain147 cops (Rails, RSpec) unless you add their plugins. If you need your own rules, it is RuboCop.148- **Tests**:149 - AAA, one failure reason per test, names that describe observable behaviour.150 - Happy path **and edges and errors**: invalid input, non-existent record, denied permissions,151 race conditions, external service timeouts, uniqueness validations under concurrency.152 - **Logic in tests is forbidden**: no `if`/`each`/calculations that rebuild the expected153 result. Literal expected value or the test proves nothing.154 - **`factory_bot`**: minimal factories (only what is mandatory), `build`/`build_stubbed` by default and155 `create` only when the DB is needed. Traits for variants; factories that create156 association trees "just in case" are forbidden — they are the usual reason for a slow suite.157 - **Fixtures**: valid and fast for stable reference data (countries, plans, roles). As the158 base of all domain tests they become global coupling; do not mix them with159 factories for the same thing.160 - System tests (Capybara) only as many as needed: expensive and fragile. Pyramid, not hourglass.161 - Forbidden: `sleep` in tests, order dependence and calls to the public network (`webmock`/`vcr` with162 reviewed cassettes and no secrets inside).163 - Every fixed bug leaves a regression test. Flaky = fixed or deleted.164- **CI gates** (block merge, from cheap to expensive):165 1. `bundle install --frozen` / `bundle exec bundler-audit check --update`166 2. `rubocop --parallel` (or `standardrb`)167 3. `brakeman --no-pager -q -w2` (fails on high/medium confidence findings)168 4. `steep check` / `srb tc` if the repo adopted types169 5. `rspec`/`rails test` unit → integration with the **same** database as production170 6. Migration audit (`strong_migrations`) and image build171- Rails 8.1 ships `bin/ci` to run the same sequence locally: use it, but **CI is the172 authority**, not the local hook. If CI does something irreproducible locally, it is a pipeline bug.173- Coverage (`simplecov`) as a signal, not a target; agreed threshold, no tricks to raise it.174175## 5. Stack security176177- **Deserialisation — Ruby's historic sink**:178 - ❌ `YAML.load`, `YAML.load_file`, `Psych.load` over untrusted input. Use179 `YAML.safe_load` with explicit `permitted_classes`. (Psych 4+ makes `load` safe by default,180 but do **not** depend on the version: write `safe_load` and leave the `Security/YAMLLoad` cop enabled.)181 - ❌ `Marshal.load` over any data coming from outside the process. Never, without exception.182 - ❌ `JSON.load` (use `JSON.parse`), `ERB` with a user-controllable template. Recent183 precedent: CVE-2026-41316, bypass of `ERB`'s deserialisation guard via184 `def_module`/`def_method`/`def_class` — verify the advisory in §8.185 - ❌ Session cookies with `Marshal` as serialiser: use `:json`.186- **Dynamic execution**: ❌ `eval`, `instance_eval`, `class_eval`, `binding.eval` with user187 data. ❌ `send`/`public_send`/`constantize`/`safe_constantize` with a name coming from the188 request — if you need dynamic dispatch, an explicit allowlist of permitted symbols.189 ❌ `system`, backticks, `Kernel#open`, `%x{}` with interpolation of input.190- **Mass assignment**: strong parameters always (`params.require(...).permit(...)`). ❌ `permit!`.191 ❌ `permit` including `:role`, `:admin`, `:user_id` or any authorisation field.192- **SQL injection**: only parameterised queries (hash conditions or `where("x = ?", v)`). ❌193 string interpolation in `where`, `order`, `pluck`, `find_by_sql`, `joins`. `order` with a194 user parameter requires a column allowlist — it is the classic vector and the ORM does not protect you.195- **XSS**: ERB escapes by default; ❌ `html_safe`, `raw` and `sanitize` over user content without196 a strict allowlist. Cop `Rails/OutputSafety` enabled. CSP configured (`content_security_policy` in197 Rails), not permissive for convenience.198- **CSRF**: `protect_from_forgery` with the default strategy (`:exception`), never `:null_session`199 on endpoints with a session. ❌ global `skip_before_action :verify_authenticity_token`; if an API200 endpoint needs it, it is because it should authenticate with a token and not with a session cookie.201- **Authorisation**: on **every** action, not just in the generic `before_action`. Pundit/Action Policy202 with default deny and a test that verifies no action is left without a policy. IDOR is prevented203 by always querying from the user's scope (`current_user.orders.find(params[:id])`), never204 `Order.find(params[:id])`.205- **Secrets**: `config/credentials.yml.enc` is acceptable for small apps if `master.key` is **not**206 in the repo and is injected by environment; in an organisation, an external manager (see207 `secrets-management-standards`). ❌ secrets in plain `config/*.yml`, in logs or in fixtures.208 `filter_parameters` covering password, token, secret, and the sensitive domain fields.209- **SAST**: Brakeman as a gate. ⚠️ Its licence is **not OSS**: "Brakeman Public Use License" from210 Synopsys — analysing your own software is not "commercial use", but distributing it or including it in a211 paid service does require a commercial licence. Verify the current text before putting it into a212 product (§8).213- **SCA and supply chain** — RubyGems has been the scene of real and recent attacks:214 - 2026-05: massive flood of accounts and malicious gems that forced RubyGems.org to **temporarily215 suspend registration of new accounts**; none had a CVE and maintainer reputation216 was useless (freshly created accounts).217 - 2026-04/05: campaign attributed to "BufferZoneCorp", gems impersonating well-known names218 (`activesupport-logger`, `devise-jwt`) targeting **CI runners** to steal credentials.219 - 2026-06: 14 gems used as a *dead drop* for data exfiltrated by a browser extension.220 - 2026-07: "SleeperGem" campaign (`git_credential_manager`, `Dendreo`) with a second-stage payload.221 - Operational consequences, not optional: **`bundler-audit` as a gate**; committed lockfile and222 `--frozen` in CI; human review of **every new gem** (exact name, maintainer, downloads,223 date of first release); `bundle config set --local disable_platform_warnings` does not replace224 reading the `Gemfile.lock` diff in the PR; **CI does not expose secrets to the installation of225 dependencies** (`bundle install` in a job without cloud credentials or a write `GITHUB_TOKEN`).226 - A `gemspec` can execute code during `bundle install` (native extensions, build hooks):227 assume that installing a gem is **executing third-party code** and isolate the job accordingly.228 - Consider a *cooldown* on new versions (delaying adoption of a freshly published release);229 several tools and mirrors already offer it — verify which applies to your registry (§8).230- **Publication**: Trusted Publishing (OIDC) from GitHub Actions with `id-token: write` and a protected231 environment; `rubygems_mfa_required` in the gemspec. RubyGems.org's mandatory MFA covers only the232 most downloaded gems: do not assume your gem is protected by registry policy.233- **Errors**: never a stack trace or internal path to the client; `config.consider_all_requests_local =234 false` in production, neutral error page with a correlation id.235236## 6. Performance and operability237238- **N+1 is a bug, not a pending optimisation**: `includes`/`preload`/`eager_load` as appropriate239 (`includes` decides on its own; `preload` forces two queries; `eager_load` forces the JOIN). Automatic240 detection in the test/development environment (`bullet` or Rails' `strict_loading`) and **test241 failure**, not a log nobody reads. `strict_loading` by default in new models.242- **Always** paginate collections (default and maximum limit). `find_each`/`in_batches` for243 massive traversals; ❌ `.all.each` over a production table.244- **Safe migrations** (`strong_migrations`): no adding a column with a non-volatile default on245 old engines, no indexes without `algorithm: :concurrently` on PostgreSQL, no `change_column` that246 rewrites the table, nor renaming/removing columns in the same release as the code that uses them.247 **Expand/contract** pattern mandatory: the migration must be compatible with version N-1 of the248 code throughout the rolling deploy. Destructive ones go in a later release.249- **Jobs**: `ActiveJob` as the interface, explicit backend. **Solid Queue** (Rails 8 default, uses250 `FOR UPDATE SKIP LOCKED`, does not require Redis) is the default option in greenfield; **Sidekiq 8**251 if it is already there, if you need its ecosystem, or for measured performance — with the252 **LGPL-3.0** licence warning for Sidekiq OSS. Common rules:253 - Serialisable and **small** arguments: pass IDs, not objects or payloads.254 - **Idempotency mandatory**: every job is retried; if retrying it charges twice, it is a bug.255 - Enqueue **after** the commit (`after_commit`), never inside the transaction: the worker may256 pick up the job before the row exists.257 - Explicit timeouts and retry limits; monitored failure queue (dead set).258 - Queues separated by latency (critical / default / slow), not by team.259 - Long jobs: Rails 8.1 brings **Active Job Continuations** (splitting into resumable steps) —260 relevant because a deploy with Kamal gives the jobs container a short shutdown window.261- **Cache**: Solid Cache (Rails 8 default, database-backed) or Redis/Valkey if it already exists.262 Key with schema version, explicit TTL, designed invalidation. Russian doll caching in views263 only if there is measurement behind it. ❌ cache without TTL "we will clean it by hand later".264- **Puma**: `WEB_CONCURRENCY` (processes) and threads sized against the Active Record pool —265 `pool` ≥ threads per process, or you will see checkout timeouts under load. The GVL means threads266 only help with I/O; real CPU parallelism is processes.267- **Timeouts on everything**: HTTP client (explicit `open_timeout` and `read_timeout` — many Ruby268 clients have no default), `statement_timeout` in PostgreSQL, `rack-timeout` or equivalent at the269 edge. Retries with backoff+jitter only on idempotent operations.270- **Observability**: structured logs with `request_id`; `ActiveSupport::Notifications` /271 Rails 8.1 Structured Event Reporting as the event source; OpenTelemetry for traces and metrics.272 Liveness/readiness endpoints (`rails/health` out of the box, extend it with a dependency check).273 ❌ personal data or secrets in logs.274- **Memory**: CRuby fragments; watch RSS per worker and restart workers with a limit if needed275 (`puma_worker_killer` is a patch, not a diagnosis). Profile with `stackprof`/`memory_profiler`276 before optimising; no speculative micro-optimisation.277- Orderly shutdown: SIGTERM drains in-flight requests and jobs; do not break it with `exit!` or orphan278 threads.279280## 7. Long-term sustainability281282- **Cadence**: security patches immediately; gem minors with Renovate/Dependabot and lock;283 Ruby one minor a year; Rails from supported release to supported release. A big upgrade is284 cheaper in small, frequent steps than as a six-month project every three years.285- Maintenance budget in every sprint. Rails deprecations (`ActiveSupport::Deprecation`)286 are resolved in the current release, they are not silenced.287- `.rubocop_todo.yml` with a date; conscious debt = TODO with reason and a linked issue.288- Rails 8.1 allows marking associations `deprecated: true` (`:warn`/`:raise`/`:notify`): use it to289 retire dead relations measurably instead of "grepping for usages".290291**Prohibition list (veto):**292- ❌ Deploying on an EOL version of Ruby (≤3.2 as of 2026-08) or of Rails without support.293- ❌ `YAML.load` / `Marshal.load` / `JSON.load` over untrusted input. **FORBIDDEN** without exception.294- ❌ `eval`/`instance_eval`/`class_eval`/`send`/`public_send`/`constantize` with user data.295- ❌ `system`/backticks/`Kernel#open` with interpolation of input.296- ❌ SQL by string interpolation in `where`, `order`, `pluck`, `joins`, `find_by_sql`.297- ❌ `permit!`, or permitting role/ownership fields in strong parameters.298- ❌ Global `skip_before_action :verify_authenticity_token`; `protect_from_forgery with: :null_session`299 on routes with a session.300- ❌ `html_safe`/`raw` over user content.301- ❌ `default_scope`. ❌ Active Record callbacks with external effects (mail, HTTP, jobs).302- ❌ `update_column`/`update_all`/`save(validate: false)` to skip validations without a written reason.303- ❌ `rescue => e` that swallows the exception without logging or re-raising; `rescue Exception`.304- ❌ Monkey patching gems or the core in the app (use refinements or a fork with an upstream issue).305- ❌ Gems without a version in the `Gemfile`; deploying without `Gemfile.lock`; `bundle update` without reviewing the diff.306- ❌ Installing dependencies in a CI job that has production credentials at hand.307- ❌ Tests with logic, with `sleep`, order-dependent or that call the public network.308- ❌ ZJIT in production (experimental as of 2026-08, slower than YJIT according to the official notes).309- ❌ Destructive migration in the same release as the code that stops using the column.310- ❌ Enqueuing jobs inside the transaction that creates the data the job needs.311- ❌ Extracting an internal gem with a single consumer.312- ❌ Assuming that Brakeman or Sidekiq are MIT: verify their licence before putting them into a product.313314## 8. Mandatory web verification315316Before pinning versions or claims in a project, **verify online**:3171. **Ruby**: latest stable and EOL calendar (`endoflife.date/ruby`, ruby-lang.org/en/downloads).318 Is 4.0 still the current series? Has 4.1 shipped? Is 3.3 already EOL (planned 2027-03-31)?3192. **Rails**: supported version (`endoflife.date/rails`, rubyonrails.org). 7.2 EOL 2026-08-09 and320 8.0 EOL 2026-11-07 → check whether they have passed. Rails 9?3213. **JIT**: does ZJIT already match or beat YJIT and is it recommended in production? Is YJIT enabled by322 default in some version? Source: official release notes from ruby-lang.org, not blogs.3234. **Typing**: state of RBS/Steep and of Sorbet — has either become the ecosystem default or has324 typing entered the Ruby compiler? As of 2026-08 neither is mandatory.3255. **Tools**: RuboCop (2.0?), RSpec (4.0 final already, or still in beta?), Minitest, Brakeman,326 bundler-audit, strong_migrations, Solid Queue, Sidekiq. Pin the exact RuboCop version.3276. **Licences**: Brakeman (Synopsys, not OSS), Sidekiq (LGPL-3.0), bundler-audit (GPL-3.0-or-later),328 and any tool you are going to set as a default. Recent precedents of licence329 changes that broke pipelines: **Trivy** and **gitleaks** (its action requires a commercial licence330 for organisations from v2). Check the raw `LICENSE` file, not the README badge.3317. **Security**: advisories from ruby-lang.org and GitHub Advisories / osv.dev for Ruby, Rails and the gems332 in the `Gemfile.lock` before pinning versions. Verify specifically the CVEs cited here333 (CVE-2026-41316 in ERB, CVE-2026-46727 in getaddrinfo) and whether there are later ones.3348. **RubyGems**: state of the registry after the 2026 incidents (new MFA policies, version cooldown,335 gem signing?), and whether RubyGems.org still has operational restrictions.336337**Declared gaps (not verified as of Aug 2026)**:338- Whether YJIT becomes enabled by default in some Ruby 4.x version: **not verified**.339- Exact coverage of RubyGems.org's mandatory MFA policy in 2026 (the download-based threshold340 appears to still be in force, but no updated official announcement was located): **not verified**.341- General availability of Sigstore-style gem signing on RubyGems.org: **not verified**.342343If the web contradicts this document, **the web wins** — flag the discrepancy.