Why
Before any version moves, five pieces of prep are independently useful and remove ways the later steps could fail quietly. None of them changes a version or any user-visible behaviour.
Scope
- Pin
active_model_serializers to 0.9.8. The constraint is ~> 0.9, which also permits 0.10.x. AMS 0.10 is a rewrite with no embed: / embed_in_root: — options used in 22 places across 10 serializers. A stray bundle update would silently change the JSON shape of every endpoint and break both the Ember and native clients. (Confirmed: a plain bundle lock --update pulls 0.9.8 -> 0.10.16.)
- Replace
Rails.application.secrets. Deprecated in 7.1, removed in 7.2, and the only deprecation the app emits. config/secrets.yml only read three values, all already env-backed, so this deletes an indirection rather than migrating to credentials.
- Make the backend lint jobs reproducible.
erb-lint installs the gem unpinned outside the bundle, runs with --autocorrect (fixes in place, exits 0 — so it gates nothing), and is the only job not passing working-directory: backend to ruby/setup-ruby, so it resolves the root .ruby-version while every other job uses backend/. Also gives brakeman the needs: changes guard the other three jobs have.
- Raise on deprecation warnings in test. Turns each later upgrade step into a pass/fail signal instead of stderr noise.
- Drop
rails_12factor. A no-op since Rails 5; production.rb already sets both behaviours explicitly. No release since 2016.
Two traps found while doing this
- The
test: block of secrets.yml was load-bearing: it pinned tomorrow_io_key to a value baked into the WeatherRetriever VCR cassette URI, and smtp_email_from to a value asserted in two mailer specs. Deleting the file naively turns both to nil and breaks CI.
- Raising on deprecations needs a prerequisite: when
DEVISE_SECRET_KEY is blank, Devise::SecretKeyFinder reads Rails.application.secrets from inside the gem. CI sets no such variable and has no .env, so this already fires on every CI run and would abort the boot. Fixed by having the Devise initializer fall back to secret_key_base directly, which is where Devise's own chain ends anyway.
Acceptance criteria
Blocks
Everything else in the sequence: #881, #882, #883, #884, #885, #886, #887, #888.
Part of the Ruby + Rails upgrade sequence. Ruby 3.2 reached end of life on 2026-04-01 and the backend is on Rails 7.1; the goal is Ruby 3.4.10 and Rails 8.1, in steps that each keep CI green and are independently deployable.
Why
Before any version moves, five pieces of prep are independently useful and remove ways the later steps could fail quietly. None of them changes a version or any user-visible behaviour.
Scope
active_model_serializersto0.9.8. The constraint is~> 0.9, which also permits0.10.x. AMS 0.10 is a rewrite with noembed:/embed_in_root:— options used in 22 places across 10 serializers. A straybundle updatewould silently change the JSON shape of every endpoint and break both the Ember and native clients. (Confirmed: a plainbundle lock --updatepulls 0.9.8 -> 0.10.16.)Rails.application.secrets. Deprecated in 7.1, removed in 7.2, and the only deprecation the app emits.config/secrets.ymlonly read three values, all already env-backed, so this deletes an indirection rather than migrating to credentials.erb-lintinstalls the gem unpinned outside the bundle, runs with--autocorrect(fixes in place, exits 0 — so it gates nothing), and is the only job not passingworking-directory: backendtoruby/setup-ruby, so it resolves the root.ruby-versionwhile every other job usesbackend/. Also givesbrakemantheneeds: changesguard the other three jobs have.rails_12factor. A no-op since Rails 5;production.rbalready sets both behaviours explicitly. No release since 2016.Two traps found while doing this
test:block ofsecrets.ymlwas load-bearing: it pinnedtomorrow_io_keyto a value baked into the WeatherRetriever VCR cassette URI, andsmtp_email_fromto a value asserted in two mailer specs. Deleting the file naively turns both toniland breaks CI.DEVISE_SECRET_KEYis blank,Devise::SecretKeyFinderreadsRails.application.secretsfrom inside the gem. CI sets no such variable and has no.env, so this already fires on every CI run and would abort the boot. Fixed by having the Devise initializer fall back tosecret_key_basedirectly, which is where Devise's own chain ends anyway.Acceptance criteria
bundle exec rspecgreenbundle exec standardrbexits 0bundle exec erblint --lint-allexits 0 without--autocorrectmaster(currently 4 weak-confidence, 0 errors)Blocks
Everything else in the sequence: #881, #882, #883, #884, #885, #886, #887, #888.
Part of the Ruby + Rails upgrade sequence. Ruby 3.2 reached end of life on 2026-04-01 and the backend is on Rails 7.1; the goal is Ruby 3.4.10 and Rails 8.1, in steps that each keep CI green and are independently deployable.