diff --git a/.licensed.yml b/.licensed.yml
index 178118521fd..f89daae2bd0 100644
--- a/.licensed.yml
+++ b/.licensed.yml
@@ -13,6 +13,7 @@ allowed:
 ignored:
   bundler:
     - rchardet # Ruby terms
+    - strscan # Ruby
 
 reviewed:
   bundler:
@@ -31,10 +32,16 @@ reviewed:
     - highline # GPL-2.0 OR Ruby terms
     - htmlentities # MIT
     - image_size # MIT
+    - io-wait # Ruby terms
     - json # Ruby terms
     - jwt # MIT
     - kgio # LGPL-2.1+
     - logstash-event # Apache-2.0
+    - net-http # Ruby
+    - net-imap # Ruby
+    - net-pop # Ruby
+    - net-protocol # Ruby
+    - net-smtp # Ruby
     - omniauth # MIT
     - openssl # Ruby terms
     - pg # Ruby terms
@@ -44,5 +51,7 @@ reviewed:
     - rubyzip # Ruby terms
     - sidekiq # LGPL (Sidekiq)
     - tilt
+    - timeout # Ruby
     - unf # BSD-2-Clause
-    - unicorn
\ No newline at end of file
+    - unicorn
+    - uri # Ruby
diff --git a/Gemfile b/Gemfile
index c66a3f25542..b7c7012bb1d 100644
--- a/Gemfile
+++ b/Gemfile
@@ -18,7 +18,7 @@ else
   # this allows us to include the bits of rails we use without pieces we do not.
   #
   # To issue a rails update bump the version number here
-  rails_version = '6.1.4.7'
+  rails_version = '7.0.2.4'
   gem 'actionmailer', rails_version
   gem 'actionpack', rails_version
   gem 'actionview', rails_version
@@ -68,7 +68,7 @@ gem 'http_accept_language', require: false
 gem 'discourse-ember-rails', '0.18.6', require: 'ember-rails'
 gem 'discourse-ember-source', '~> 3.12.2'
 gem 'ember-handlebars-template', '0.8.0'
-gem 'discourse-fonts'
+gem 'discourse-fonts', require: 'discourse_fonts'
 
 gem 'barber'
 
@@ -190,7 +190,7 @@ if ENV["ALLOW_DEV_POPULATE"] == "1"
   gem 'discourse_dev_assets'
   gem 'faker', "~> 2.16"
 else
-  group :development do
+  group :development, :test do
     gem 'discourse_dev_assets'
     gem 'faker', "~> 2.16"
   end
@@ -268,3 +268,7 @@ gem 'colored2', require: false
 gem 'maxminddb'
 
 gem 'rails_failover', require: false
+
+# workaround for faraday-net_http, see
+# https://github.com/ruby/net-imap/issues/16#issuecomment-803086765
+gem 'net-http'
diff --git a/Gemfile.lock b/Gemfile.lock
index 0b297095957..fa91ed2dc7e 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -8,22 +8,25 @@ GIT
 GEM
   remote: https://rubygems.org/
   specs:
-    actionmailer (6.1.4.7)
-      actionpack (= 6.1.4.7)
-      actionview (= 6.1.4.7)
-      activejob (= 6.1.4.7)
-      activesupport (= 6.1.4.7)
+    actionmailer (7.0.2.4)
+      actionpack (= 7.0.2.4)
+      actionview (= 7.0.2.4)
+      activejob (= 7.0.2.4)
+      activesupport (= 7.0.2.4)
       mail (~> 2.5, >= 2.5.4)
+      net-imap
+      net-pop
+      net-smtp
       rails-dom-testing (~> 2.0)
-    actionpack (6.1.4.7)
-      actionview (= 6.1.4.7)
-      activesupport (= 6.1.4.7)
-      rack (~> 2.0, >= 2.0.9)
+    actionpack (7.0.2.4)
+      actionview (= 7.0.2.4)
+      activesupport (= 7.0.2.4)
+      rack (~> 2.0, >= 2.2.0)
       rack-test (>= 0.6.3)
       rails-dom-testing (~> 2.0)
       rails-html-sanitizer (~> 1.0, >= 1.2.0)
-    actionview (6.1.4.7)
-      activesupport (= 6.1.4.7)
+    actionview (7.0.2.4)
+      activesupport (= 7.0.2.4)
       builder (~> 3.1)
       erubi (~> 1.4)
       rails-dom-testing (~> 2.0)
@@ -32,20 +35,19 @@ GEM
       actionview (>= 6.0.a)
     active_model_serializers (0.8.4)
       activemodel (>= 3.0)
-    activejob (6.1.4.7)
-      activesupport (= 6.1.4.7)
+    activejob (7.0.2.4)
+      activesupport (= 7.0.2.4)
       globalid (>= 0.3.6)
-    activemodel (6.1.4.7)
-      activesupport (= 6.1.4.7)
-    activerecord (6.1.4.7)
-      activemodel (= 6.1.4.7)
-      activesupport (= 6.1.4.7)
-    activesupport (6.1.4.7)
+    activemodel (7.0.2.4)
+      activesupport (= 7.0.2.4)
+    activerecord (7.0.2.4)
+      activemodel (= 7.0.2.4)
+      activesupport (= 7.0.2.4)
+    activesupport (7.0.2.4)
       concurrent-ruby (~> 1.0, >= 1.0.2)
       i18n (>= 1.6, < 2)
       minitest (>= 5.1)
       tzinfo (~> 2.0)
-      zeitwerk (~> 2.3)
     addressable (2.8.0)
       public_suffix (>= 2.0.2, < 5.0)
     annotate (3.2.0)
@@ -106,6 +108,7 @@ GEM
     debug_inspector (1.1.0)
     diff-lcs (1.5.0)
     diffy (3.4.0)
+    digest (3.1.0)
     discourse-ember-rails (0.18.6)
       active_model_serializers
       ember-data-source (>= 1.0.0.beta.5)
@@ -185,6 +188,7 @@ GEM
       progress (~> 3.0, >= 3.0.1)
     image_size (3.0.1)
     in_threads (1.6.0)
+    io-wait (0.2.1)
     ipaddr (1.2.4)
     jmespath (1.6.1)
     jquery-rails (4.4.0)
@@ -246,6 +250,24 @@ GEM
     multi_xml (0.6.0)
     multipart-post (2.1.1)
     mustache (1.1.1)
+    net-http (0.2.0)
+      net-protocol
+      uri
+    net-imap (0.2.3)
+      digest
+      net-protocol
+      strscan
+    net-pop (0.1.1)
+      digest
+      net-protocol
+      timeout
+    net-protocol (0.1.2)
+      io-wait
+      timeout
+    net-smtp (0.3.1)
+      digest
+      net-protocol
+      timeout
     nio4r (2.5.8)
     nokogiri (1.13.4)
       mini_portile2 (~> 2.8.0)
@@ -332,12 +354,13 @@ GEM
     rails_multisite (4.0.1)
       activerecord (> 5.0, < 7.1)
       railties (> 5.0, < 7.1)
-    railties (6.1.4.7)
-      actionpack (= 6.1.4.7)
-      activesupport (= 6.1.4.7)
+    railties (7.0.2.4)
+      actionpack (= 7.0.2.4)
+      activesupport (= 7.0.2.4)
       method_source
-      rake (>= 0.13)
+      rake (>= 12.2)
       thor (~> 1.0)
+      zeitwerk (~> 2.5)
     rainbow (3.1.1)
     raindrops (0.20.0)
     rake (13.0.6)
@@ -452,9 +475,11 @@ GEM
       sprockets (>= 3.0.0)
     sshkey (2.0.0)
     stackprof (0.2.19)
+    strscan (3.0.1)
     test-prof (1.0.8)
     thor (1.2.1)
     tilt (2.0.10)
+    timeout (0.2.0)
     tzinfo (2.0.4)
       concurrent-ruby (~> 1.0)
     uglifier (4.2.0)
@@ -467,6 +492,7 @@ GEM
       kgio (~> 2.6)
       raindrops (~> 0.7)
     uniform_notifier (1.16.0)
+    uri (0.11.0)
     uri_template (0.7.0)
     webmock (3.14.0)
       addressable (>= 2.8.0)
@@ -489,14 +515,14 @@ PLATFORMS
   x86_64-linux
 
 DEPENDENCIES
-  actionmailer (= 6.1.4.7)
-  actionpack (= 6.1.4.7)
-  actionview (= 6.1.4.7)
+  actionmailer (= 7.0.2.4)
+  actionpack (= 7.0.2.4)
+  actionview (= 7.0.2.4)
   actionview_precompiler
   active_model_serializers (~> 0.8.3)
-  activemodel (= 6.1.4.7)
-  activerecord (= 6.1.4.7)
-  activesupport (= 6.1.4.7)
+  activemodel (= 7.0.2.4)
+  activerecord (= 7.0.2.4)
+  activesupport (= 7.0.2.4)
   addressable
   annotate
   aws-sdk-s3
@@ -556,6 +582,7 @@ DEPENDENCIES
   mocha
   multi_json
   mustache
+  net-http
   nokogiri
   oj
   omniauth
@@ -575,7 +602,7 @@ DEPENDENCIES
   rack-protection
   rails_failover
   rails_multisite
-  railties (= 6.1.4.7)
+  railties (= 7.0.2.4)
   rake
   rb-fsevent
   rbtrace
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 4e6b69a49cc..3507affa497 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -68,7 +68,7 @@ class ApplicationController < ActionController::Base
   def use_crawler_layout?
     @use_crawler_layout ||=
       request.user_agent &&
-      (request.content_type.blank? || request.content_type.include?('html')) &&
+      (request.media_type.blank? || request.media_type.include?('html')) &&
       !['json', 'rss'].include?(params[:format]) &&
       (has_escaped_fragment? || params.key?("print") || show_browser_update? ||
       CrawlerDetection.crawler?(request.user_agent, request.headers["HTTP_VIA"])
@@ -287,7 +287,7 @@ class ApplicationController < ActionController::Base
       # cause category / topic was deleted
       if permalink.present? && permalink.target_url
         # permalink present, redirect to that URL
-        redirect_with_client_support permalink.target_url, status: :moved_permanently
+        redirect_with_client_support permalink.target_url, status: :moved_permanently, allow_other_host: true
         return
       end
     end
@@ -834,7 +834,7 @@ class ApplicationController < ActionController::Base
       end
 
       if UserApiKey.allowed_scopes.superset?(Set.new(["one_time_password"]))
-        redirect_to("#{params[:auth_redirect]}?otp=true")
+        redirect_to("#{params[:auth_redirect]}?otp=true", allow_other_host: true)
         return
       end
     end
diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb
index b7e0df39c52..f1292d4fe08 100644
--- a/app/controllers/posts_controller.rb
+++ b/app/controllers/posts_controller.rb
@@ -1,6 +1,9 @@
 # frozen_string_literal: true
 
 class PostsController < ApplicationController
+  # Bug with Rails 7+
+  # see https://github.com/rails/rails/issues/44867
+  self._flash_types -= [:notice]
 
   requires_login except: [
     :show,
diff --git a/app/controllers/session_controller.rb b/app/controllers/session_controller.rb
index caa63596098..beefb526e20 100644
--- a/app/controllers/session_controller.rb
+++ b/app/controllers/session_controller.rb
@@ -33,7 +33,7 @@ class SessionController < ApplicationController
       if SiteSetting.verbose_discourse_connect_logging
         Rails.logger.warn("Verbose SSO log: Started SSO process\n\n#{sso.diagnostics}")
       end
-      redirect_to sso_url(sso)
+      redirect_to sso_url(sso), allow_other_host: true
     else
       render body: nil, status: 404
     end
@@ -69,14 +69,14 @@ class SessionController < ApplicationController
         # for the login modal
         cookies[:sso_destination_url] = data[:sso_redirect_url]
       else
-        redirect_to data[:sso_redirect_url]
+        redirect_to data[:sso_redirect_url], allow_other_host: true
       end
     elsif result.no_second_factors_enabled?
       if request.xhr?
         # for the login modal
         cookies[:sso_destination_url] = result.data[:sso_redirect_url]
       else
-        redirect_to result.data[:sso_redirect_url]
+        redirect_to result.data[:sso_redirect_url], allow_other_host: true
       end
     elsif result.second_factor_auth_completed?
       redirect_url = result.data[:sso_redirect_url]
@@ -169,7 +169,7 @@ class SessionController < ApplicationController
         # they are already pre-approved because they have been invited
         if SiteSetting.must_approve_users? && !user.approved? && invite.blank?
           if SiteSetting.discourse_connect_not_approved_url.present?
-            redirect_to SiteSetting.discourse_connect_not_approved_url
+            redirect_to SiteSetting.discourse_connect_not_approved_url, allow_other_host: true
           else
             render_sso_error(text: I18n.t("discourse_connect.account_not_approved"), status: 403)
           end
@@ -220,7 +220,7 @@ class SessionController < ApplicationController
           return_path = path("/")
         end
 
-        redirect_to return_path
+        redirect_to return_path, allow_other_host: true
       else
         render_sso_error(text: I18n.t("discourse_connect.not_found"), status: 500)
       end
@@ -583,7 +583,7 @@ class SessionController < ApplicationController
         redirect_url: redirect_url
       }
     else
-      redirect_to redirect_url
+      redirect_to redirect_url, allow_other_host: true
     end
   end
 
diff --git a/app/controllers/static_controller.rb b/app/controllers/static_controller.rb
index 5a509932230..ded832cb47b 100644
--- a/app/controllers/static_controller.rb
+++ b/app/controllers/static_controller.rb
@@ -30,7 +30,7 @@ class StaticController < ApplicationController
     if map.has_key?(@page)
       site_setting_key = map[@page][:redirect]
       url = SiteSetting.get(site_setting_key) if site_setting_key
-      return redirect_to(url) if url.present?
+      return redirect_to(url, allow_other_host: true) if url.present?
     end
 
     # The /guidelines route ALWAYS shows our FAQ, ignoring the faq_url site setting.
diff --git a/app/controllers/svg_sprite_controller.rb b/app/controllers/svg_sprite_controller.rb
index b5b6db3731e..5366654d9df 100644
--- a/app/controllers/svg_sprite_controller.rb
+++ b/app/controllers/svg_sprite_controller.rb
@@ -15,7 +15,7 @@ class SvgSpriteController < ApplicationController
       theme_id = params[:theme_id].to_i if params[:theme_id].present?
 
       if SvgSprite.version(theme_id) != params[:version]
-        return redirect_to UrlHelper.absolute((SvgSprite.path(theme_id)))
+        return redirect_to UrlHelper.absolute((SvgSprite.path(theme_id))), allow_other_host: true
       end
 
       svg_sprite = "window.__svg_sprite = #{SvgSprite.bundle(theme_id).inspect};"
diff --git a/app/controllers/uploads_controller.rb b/app/controllers/uploads_controller.rb
index 891320f7e35..9c87a938690 100644
--- a/app/controllers/uploads_controller.rb
+++ b/app/controllers/uploads_controller.rb
@@ -118,7 +118,7 @@ class UploadsController < ApplicationController
       if Discourse.store.internal?
         send_file_local_upload(upload)
       else
-        redirect_to Discourse.store.url_for(upload, force_download: force_download?)
+        redirect_to Discourse.store.url_for(upload, force_download: force_download?), allow_other_host: true
       end
     else
       render_404
@@ -149,7 +149,7 @@ class UploadsController < ApplicationController
     # private, so we don't want to go to the CDN url just yet otherwise we
     # will get a 403. if the upload is not secure we assume the ACL is public
     signed_secure_url = Discourse.store.signed_url_for_path(path_with_ext)
-    redirect_to upload.secure? ? signed_secure_url : Discourse.store.cdn_url(upload.url)
+    redirect_to upload.secure? ? signed_secure_url : Discourse.store.cdn_url(upload.url), allow_other_host: true
   end
 
   def handle_secure_upload_request(upload, path_with_ext = nil)
@@ -166,14 +166,14 @@ class UploadsController < ApplicationController
     # url_for figures out the full URL, handling multisite DBs,
     # and will return a presigned URL for the upload
     if path_with_ext.blank?
-      return redirect_to Discourse.store.url_for(upload, force_download: force_download?)
+      return redirect_to Discourse.store.url_for(upload, force_download: force_download?), allow_other_host: true
     end
 
     redirect_to Discourse.store.signed_url_for_path(
       path_with_ext,
       expires_in: S3Helper::DOWNLOAD_URL_EXPIRES_AFTER_SECONDS,
       force_download: force_download?
-    )
+    ), allow_other_host: true
   end
 
   def metadata
diff --git a/app/controllers/user_api_keys_controller.rb b/app/controllers/user_api_keys_controller.rb
index 402a38e1d97..0a34a33d65f 100644
--- a/app/controllers/user_api_keys_controller.rb
+++ b/app/controllers/user_api_keys_controller.rb
@@ -97,7 +97,7 @@ class UserApiKeysController < ApplicationController
       query_attributes << "oneTimePassword=#{CGI.escape(otp_payload)}" if scopes.include?("one_time_password")
       uri.query = query_attributes.compact.join('&')
 
-      redirect_to(uri.to_s)
+      redirect_to(uri.to_s, allow_other_host: true)
     else
       respond_to do |format|
         format.html { render :show }
@@ -138,7 +138,7 @@ class UserApiKeysController < ApplicationController
     otp_payload = one_time_password(public_key, current_user.username)
 
     redirect_path = "#{params[:auth_redirect]}?oneTimePassword=#{CGI.escape(otp_payload)}"
-    redirect_to(redirect_path)
+    redirect_to(redirect_path, allow_other_host: true)
   end
 
   def revoke
diff --git a/app/controllers/user_avatars_controller.rb b/app/controllers/user_avatars_controller.rb
index 2d339e9f545..cc28143c0db 100644
--- a/app/controllers/user_avatars_controller.rb
+++ b/app/controllers/user_avatars_controller.rb
@@ -112,7 +112,7 @@ class UserAvatarsController < ApplicationController
     if !Discourse.avatar_sizes.include?(size) && Discourse.store.external?
       closest = Discourse.avatar_sizes.to_a.min { |a, b| (size - a).abs <=> (size - b).abs }
       avatar_url = UserAvatar.local_avatar_url(hostname, user.encoded_username(lower: true), upload_id, closest)
-      return redirect_to cdn_path(avatar_url)
+      return redirect_to cdn_path(avatar_url), allow_other_host: true
     end
 
     upload = Upload.find_by(id: upload_id) if user&.user_avatar&.contains_upload?(upload_id)
@@ -120,7 +120,7 @@ class UserAvatarsController < ApplicationController
 
     if user.uploaded_avatar && !upload
       avatar_url = UserAvatar.local_avatar_url(hostname, user.encoded_username(lower: true), user.uploaded_avatar_id, size)
-      return redirect_to cdn_path(avatar_url)
+      return redirect_to cdn_path(avatar_url), allow_other_host: true
     elsif upload && optimized = get_optimized_image(upload, size)
       if optimized.local?
         optimized_path = Discourse.store.path_for(optimized)
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index 768053e00a6..2a0c900c8ca 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -1024,7 +1024,7 @@ class UsersController < ApplicationController
       if SiteSetting.enable_discourse_connect_provider && payload = cookies.delete(:sso_payload)
         return redirect_to(session_sso_provider_url + "?" + payload)
       elsif destination_url = cookies.delete(:destination_url)
-        return redirect_to(destination_url)
+        return redirect_to(destination_url, allow_other_host: true)
       else
         return redirect_to(path('/'))
       end
@@ -1086,7 +1086,7 @@ class UsersController < ApplicationController
         if Wizard.user_requires_completion?(@user)
           return redirect_to(wizard_path)
         elsif destination_url.present?
-          return redirect_to(destination_url)
+          return redirect_to(destination_url, allow_other_host: true)
         elsif SiteSetting.enable_discourse_connect_provider && payload = cookies.delete(:sso_payload)
           return redirect_to(session_sso_provider_url + "?" + payload)
         end
diff --git a/app/jobs/regular/group_smtp_email.rb b/app/jobs/regular/group_smtp_email.rb
index dbe9f822245..b06801be188 100644
--- a/app/jobs/regular/group_smtp_email.rb
+++ b/app/jobs/regular/group_smtp_email.rb
@@ -1,7 +1,5 @@
 # frozen_string_literal: true
 
-require_dependency 'email/sender'
-
 module Jobs
   class GroupSmtpEmail < ::Jobs::Base
     include Skippable
diff --git a/app/jobs/scheduled/old_keys_reminder.rb b/app/jobs/scheduled/old_keys_reminder.rb
index f5318c70079..de824fa5d0e 100644
--- a/app/jobs/scheduled/old_keys_reminder.rb
+++ b/app/jobs/scheduled/old_keys_reminder.rb
@@ -57,8 +57,8 @@ module Jobs
     end
 
     def keys_list
-      messages = old_site_settings_keys.map { |key| "#{key.name} - #{key.updated_at.to_date.to_s(:db)}" }
-      old_api_keys.each_with_object(messages) { |key, array| array << "#{[key.description, key.user&.username, key.created_at.to_date.to_s(:db)].compact.join(" - ")}" }
+      messages = old_site_settings_keys.map { |key| "#{key.name} - #{key.updated_at.to_date.to_fs(:db)}" }
+      old_api_keys.each_with_object(messages) { |key, array| array << "#{[key.description, key.user&.username, key.created_at.to_date.to_fs(:db)].compact.join(" - ")}" }
       messages.join("\n")
     end
   end
diff --git a/app/mailers/group_smtp_mailer.rb b/app/mailers/group_smtp_mailer.rb
index d47a75051ef..42a2c9a082f 100644
--- a/app/mailers/group_smtp_mailer.rb
+++ b/app/mailers/group_smtp_mailer.rb
@@ -1,7 +1,5 @@
 # frozen_string_literal: true
 
-require_dependency 'email/message_builder'
-
 class GroupSmtpMailer < ActionMailer::Base
   include Email::BuildEmailHelper
 
diff --git a/app/models/theme.rb b/app/models/theme.rb
index 596db2e2b2f..5c510086f7f 100644
--- a/app/models/theme.rb
+++ b/app/models/theme.rb
@@ -1,6 +1,5 @@
 # frozen_string_literal: true
 
-require_dependency 'global_path'
 require 'csv'
 require 'json_schemer'
 
diff --git a/app/models/topic_list.rb b/app/models/topic_list.rb
index 2c564087ed8..bfa6960e0f3 100644
--- a/app/models/topic_list.rb
+++ b/app/models/topic_list.rb
@@ -153,7 +153,9 @@ class TopicList
       ft.topic_list = self
     end
 
-    ActiveRecord::Associations::Preloader.new.preload(@topics, [:image_upload, topic_thumbnails: :optimized_image])
+    ActiveRecord::Associations::Preloader
+      .new(records: @topics, associations: [:image_upload, topic_thumbnails: :optimized_image])
+      .call
 
     if preloaded_custom_fields.present?
       Topic.preload_custom_fields(@topics, preloaded_custom_fields)
diff --git a/app/models/translation_override.rb b/app/models/translation_override.rb
index 85b09c0e74a..932ac08fa66 100644
--- a/app/models/translation_override.rb
+++ b/app/models/translation_override.rb
@@ -1,7 +1,5 @@
 # frozen_string_literal: true
 
-require "i18n/i18n_interpolation_keys_finder"
-
 class TranslationOverride < ActiveRecord::Base
   # Allowlist i18n interpolation keys that can be included when customizing translations
   ALLOWED_CUSTOM_INTERPOLATION_KEYS = {
diff --git a/app/services/bookmarkable.rb b/app/services/bookmarkable.rb
index 525f07ee65b..b5cd2e8ab60 100644
--- a/app/services/bookmarkable.rb
+++ b/app/services/bookmarkable.rb
@@ -73,8 +73,8 @@ class Bookmarkable
   # @param [Array] bookmarks The array of bookmarks after initial listing and filtering, note this is
   #                          array _not_ an ActiveRecord::Relation.
   def perform_preload(bookmarks)
-    ActiveRecord::Associations::Preloader.new.preload(
-      Bookmark.select_type(bookmarks, model.to_s), { bookmarkable: preload_associations }
-    )
+    ActiveRecord::Associations::Preloader
+      .new(records: Bookmark.select_type(bookmarks, model.to_s), associations: [bookmarkable: preload_associations])
+      .call
   end
 end
diff --git a/config/application.rb b/config/application.rb
index f17ea293a33..882ccda5b67 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -21,7 +21,7 @@ require 'action_mailer/railtie'
 require 'sprockets/railtie'
 
 # Plugin related stuff
-require_relative '../lib/plugin_initialization_guard'
+require_relative '../lib/plugin'
 require_relative '../lib/discourse_event'
 require_relative '../lib/discourse_plugin_registry'
 
@@ -31,7 +31,13 @@ require_relative '../lib/plugin_gem'
 require_relative '../app/models/global_setting'
 GlobalSetting.configure!
 if GlobalSetting.load_plugins?
-  require_relative '../lib/custom_setting_providers'
+  # Support for plugins to register custom setting providers. They can do this
+  # by having a file, `register_provider.rb` in their root that will be run
+  # at this point.
+
+  Dir.glob(File.join(File.dirname(__FILE__), '../plugins', '*', "register_provider.rb")) do |p|
+    require p
+  end
 end
 GlobalSetting.load_defaults
 if GlobalSetting.try(:cdn_url).present? && GlobalSetting.cdn_url !~ /^https?:\/\//
@@ -85,14 +91,11 @@ module Discourse
     # Application configuration should go into files in config/initializers
     # -- all .rb files in that directory are automatically loaded.
 
-    # this pattern is somewhat odd but the reloader gets very
-    # confused here if we load the deps without `lib` it thinks
-    # discourse.rb is under the discourse folder incorrectly
-    require_dependency 'lib/discourse'
-    require_dependency 'lib/js_locale_helper'
+    require 'discourse'
+    require 'js_locale_helper'
 
     # tiny file needed by site settings
-    require_dependency 'lib/highlight_js/highlight_js'
+    require 'highlight_js'
 
     # we skip it cause we configure it in the initializer
     # the railtie for message_bus would insert it in the
@@ -109,120 +112,19 @@ module Discourse
     # issue is image_optim crashes on missing dependencies
     config.assets.image_optim = false
 
-    config.autoloader = :zeitwerk
-
     # Custom directories with classes and modules you want to be autoloadable.
-    config.autoload_paths += Dir["#{config.root}/lib"]
-    config.autoload_paths += Dir["#{config.root}/lib/common_passwords"]
-    config.autoload_paths += Dir["#{config.root}/lib/highlight_js"]
-    config.autoload_paths += Dir["#{config.root}/lib/i18n"]
-    config.autoload_paths += Dir["#{config.root}/lib/validators/"]
-
-    Rails.autoloaders.main.ignore(Dir["#{config.root}/app/models/reports"])
-    Rails.autoloaders.main.ignore(Dir["#{config.root}/lib/freedom_patches"])
-
-    def watchable_args
-      files, dirs = super
-
-      # Skip the assets directory. It doesn't contain any .rb files, so watching it
-      # is just slowing things down and raising warnings about node_modules symlinks
-      app_file_extensions = dirs.delete("#{config.root}/app")
-      Dir["#{config.root}/app/*"].reject { |path| path.end_with? "/assets" }.each do |path|
-        dirs[path] = app_file_extensions
-      end
-
-      [files, dirs]
-    end
+    config.autoload_paths << "#{root}/lib"
+    config.autoload_paths << "#{root}/lib/guardian"
+    config.autoload_paths << "#{root}/lib/i18n"
+    config.autoload_paths << "#{root}/lib/validators"
 
     # Only load the plugins named here, in the order given (default is alphabetical).
     # :all can be used as a placeholder for all plugins not explicitly named.
     # config.plugins = [ :exception_notification, :ssl_requirement, :all ]
 
-    config.assets.paths += %W(#{config.root}/config/locales #{config.root}/public/javascripts)
-
     # Allows us to skip minification on some files
     config.assets.skip_minification = []
 
-    # explicitly precompile any images in plugins ( /assets/images ) path
-    config.assets.precompile += [lambda do |filename, path|
-      path =~ /assets\/images/ && !%w(.js .css).include?(File.extname(filename))
-    end]
-
-    config.assets.precompile += %w{
-      vendor.js
-      admin.js
-      browser-detect.js
-      browser-update.js
-      break_string.js
-      ember_jquery.js
-      pretty-text-bundle.js
-      wizard-application.js
-      wizard-vendor.js
-      markdown-it-bundle.js
-      service-worker.js
-      google-tag-manager.js
-      google-universal-analytics-v3.js
-      google-universal-analytics-v4.js
-      start-discourse.js
-      print-page.js
-      omniauth-complete.js
-      activate-account.js
-      auto-redirect.js
-      wizard-start.js
-      locales/i18n.js
-      discourse/app/lib/webauthn.js
-      confirm-new-email/confirm-new-email.js
-      confirm-new-email/bootstrap.js
-      onpopstate-handler.js
-      embed-application.js
-      discourse/tests/active-plugins.js
-      admin-plugins.js
-      discourse/tests/test_starter.js
-    }
-
-    if EmberCli.enabled?
-      config.assets.precompile += %w{
-        discourse.js
-        test-support.js
-        test-helpers.js
-        scripts/discourse-test-listen-boot
-        scripts/discourse-boot
-      }
-    else
-      config.assets.precompile += %w{
-        application.js
-        discourse/tests/test-support-rails.js
-        discourse/tests/test-helpers-rails.js
-        vendor-theme-tests.js
-      }
-    end
-
-    # Precompile all available locales
-    unless GlobalSetting.try(:omit_base_locales)
-      Dir.glob("#{config.root}/app/assets/javascripts/locales/*.js.erb").each do |file|
-        config.assets.precompile << "locales/#{file.match(/([a-z_A-Z]+\.js)\.erb$/)[1]}"
-      end
-    end
-
-    # out of the box sprockets 3 grabs loose files that are hanging in assets,
-    # the exclusion list does not include hbs so you double compile all this stuff
-    initializer :fix_sprockets_loose_file_searcher, after: :set_default_precompile do |app|
-      app.config.assets.precompile.delete(Sprockets::Railtie::LOOSE_APP_ASSETS)
-
-      # We don't want application from node_modules, only from the root
-      app.config.assets.precompile.delete(/(?:\/|\\|\A)application\.(css|js)$/)
-      app.config.assets.precompile += ['application.js']
-
-      start_path = ::Rails.root.join("app/assets").to_s
-      exclude = ['.es6', '.hbs', '.hbr', '.js', '.css', '.lock', '.json', '.log', '.html', '']
-      app.config.assets.precompile << lambda do |logical_path, filename|
-        filename.start_with?(start_path) &&
-        !filename.include?("/node_modules/") &&
-        !filename.include?("/dist/") &&
-        !exclude.include?(File.extname(logical_path))
-      end
-    end
-
     # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
     # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
     config.time_zone = 'UTC'
@@ -234,24 +136,6 @@ module Discourse
     # Configure the default encoding used in templates for Ruby 1.9.
     config.encoding = 'utf-8'
 
-    # Configure sensitive parameters which will be filtered from the log file.
-    config.filter_parameters += [
-      :password,
-      :pop3_polling_password,
-      :api_key,
-      :s3_secret_access_key,
-      :twitter_consumer_secret,
-      :facebook_app_secret,
-      :github_client_secret,
-      :second_factor_token,
-    ]
-
-    # Enable the asset pipeline
-    config.assets.enabled = true
-
-    # Version of your assets, change this if you want to expire all your assets
-    config.assets.version = '1.2.5'
-
     # see: http://stackoverflow.com/questions/11894180/how-does-one-correctly-add-custom-sql-dml-in-migrations/11894420#11894420
     config.active_record.schema_format = :sql
 
@@ -336,45 +220,21 @@ module Discourse
     if Rails.env.test? && GlobalSetting.load_plugins?
       Discourse.activate_plugins!
     elsif GlobalSetting.load_plugins?
-      plugin_initialization_guard do
+      Plugin.initialization_guard do
         Discourse.activate_plugins!
       end
     end
 
-    Discourse.find_plugin_js_assets(include_disabled: true).each do |file|
-      config.assets.precompile << "#{file}.js"
-    end
-
     # Use discourse-fonts gem to symlink fonts and generate .scss file
     fonts_path = File.join(config.root, 'public/fonts')
     Discourse::Utils.atomic_ln_s(DiscourseFonts.path_for_fonts, fonts_path)
 
-    require_dependency 'stylesheet/manager'
-    require_dependency 'svg_sprite/svg_sprite'
+    require 'stylesheet/manager'
+    require 'svg_sprite'
 
     config.after_initialize do
-      # require common dependencies that are often required by plugins
-      # in the past observers would load them as side-effects
-      # correct behavior is for plugins to require stuff they need,
-      # however it would be a risky and breaking change not to require here
-      require_dependency 'category'
-      require_dependency 'post'
-      require_dependency 'topic'
-      require_dependency 'user'
-      require_dependency 'post_action'
-      require_dependency 'post_revision'
-      require_dependency 'notification'
-      require_dependency 'topic_user'
-      require_dependency 'topic_view'
-      require_dependency 'topic_list'
-      require_dependency 'group'
-      require_dependency 'user_field'
-      require_dependency 'post_action_type'
-      # Ensure that Discourse event triggers for web hooks are loaded
-      require_dependency 'web_hook'
-
       # Load plugins
-      plugin_initialization_guard do
+      Plugin.initialization_guard do
         Discourse.plugins.each(&:notify_after_initialize)
       end
 
diff --git a/config/environment.rb b/config/environment.rb
index 516a139c3a8..7bb5d95ae90 100644
--- a/config/environment.rb
+++ b/config/environment.rb
@@ -1,12 +1,12 @@
 # frozen_string_literal: true
 
-# Load the rails application
-require File.expand_path('../application', __FILE__)
+# Load the Rails application.
+require_relative "application"
 
-# Initialize the rails application
-Discourse::Application.initialize!
+# Initialize the Rails application.
+Rails.application.initialize!
 
 # When in "dev" mode, ensure we won't be sending any emails
-if Rails.env.development? && ActionMailer::Base.smtp_settings != { address: "localhost", port: 1025 }
+if Rails.env.development? && ActionMailer::Base.smtp_settings.slice(:address, :port) != { address: "localhost", port: 1025 }
   fail "In development mode, you should be using mailhog otherwise you might end up sending thousands of digest emails"
 end
diff --git a/config/initializers/000-zeitwerk.rb b/config/initializers/000-zeitwerk.rb
index 7fa14e8bf44..0d5e55d3d57 100644
--- a/config/initializers/000-zeitwerk.rb
+++ b/config/initializers/000-zeitwerk.rb
@@ -36,5 +36,15 @@ Rails.autoloaders.each do |autoloader|
     'onceoff' => 'Jobs',
     'regular' => 'Jobs',
     'scheduled' => 'Jobs',
+    'google_oauth2_authenticator' => 'GoogleOAuth2Authenticator',
+    'omniauth_strategies' => 'OmniAuthStrategies',
+    'csrf_token_verifier' => 'CSRFTokenVerifier',
+    'html' => 'HTML',
+    'json' => 'JSON'
   )
 end
+Rails.autoloaders.main.ignore("lib/tasks",
+                              "lib/generators",
+                              "lib/freedom_patches",
+                              "lib/i18n/backend",
+                              "lib/unicorn_logstash_patch.rb")
diff --git a/config/initializers/002-rails_failover.rb b/config/initializers/002-rails_failover.rb
index 7ff49b775c4..3f1463fdd87 100644
--- a/config/initializers/002-rails_failover.rb
+++ b/config/initializers/002-rails_failover.rb
@@ -30,14 +30,14 @@ if defined?(RailsFailover::ActiveRecord)
   return unless Rails.configuration.active_record_rails_failover
 
   if Rails.configuration.multisite
-    if ActiveRecord::Base.current_role == ActiveRecord::Base.reading_role
+    if ActiveRecord::Base.current_role == ActiveRecord.reading_role
       RailsMultisite::ConnectionManagement.default_connection_handler =
-        ActiveRecord::Base.connection_handlers[ActiveRecord::Base.reading_role]
+        ActiveRecord::Base.connection_handlers[ActiveRecord.reading_role]
     end
   end
 
   RailsFailover::ActiveRecord.on_failover do |role|
-    if role == ActiveRecord::Base.writing_role # Multisite master
+    if role == ActiveRecord.writing_role # Multisite master
       RailsMultisite::ConnectionManagement.each_connection do
         Discourse.enable_readonly_mode(Discourse::PG_READONLY_MODE_KEY)
       end
@@ -47,16 +47,16 @@ if defined?(RailsFailover::ActiveRecord)
       end
 
       # Test connection to the master, and trigger master failover if needed
-      ActiveRecord::Base.connected_to(role: ActiveRecord::Base.writing_role) do
+      ActiveRecord::Base.connected_to(role: ActiveRecord.writing_role) do
         ActiveRecord::Base.connection.active?
       rescue PG::ConnectionBad, PG::UnableToSend, PG::ServerError
-        RailsFailover::ActiveRecord.verify_primary(ActiveRecord::Base.writing_role)
+        RailsFailover::ActiveRecord.verify_primary(ActiveRecord.writing_role)
       end
     end
   end
 
   RailsFailover::ActiveRecord.on_fallback do |role|
-    if role == ActiveRecord::Base.writing_role # Multisite master
+    if role == ActiveRecord.writing_role # Multisite master
       RailsMultisite::ConnectionManagement.each_connection do
         Discourse.disable_readonly_mode(Discourse::PG_READONLY_MODE_KEY)
       end
@@ -68,7 +68,7 @@ if defined?(RailsFailover::ActiveRecord)
 
     if Rails.configuration.multisite
       RailsMultisite::ConnectionManagement.default_connection_handler =
-        ActiveRecord::Base.connection_handlers[ActiveRecord::Base.writing_role]
+        ActiveRecord::Base.connection_handlers[ActiveRecord.writing_role]
     end
   end
 
diff --git a/config/initializers/005-site_settings.rb b/config/initializers/005-site_settings.rb
index 4ecaa85df33..9286affcbf0 100644
--- a/config/initializers/005-site_settings.rb
+++ b/config/initializers/005-site_settings.rb
@@ -6,17 +6,15 @@
 Discourse.git_version
 
 if GlobalSetting.skip_redis?
-  # Requiring this file explicitly prevents it from being autoloaded and so the
-  # provider attribute is not cleared
-  require File.expand_path('../../../app/models/site_setting', __FILE__)
-
   require 'site_settings/local_process_provider'
   Rails.cache = Discourse.cache
-  SiteSetting.provider = SiteSettings::LocalProcessProvider.new
+  Rails.application.config.to_prepare do
+    SiteSetting.provider = SiteSettings::LocalProcessProvider.new
+  end
   return
 end
 
-reload_settings = lambda {
+Rails.application.config.to_prepare do
   RailsMultisite::ConnectionManagement.safe_each_connection do
     begin
       SiteSetting.refresh!
@@ -28,12 +26,4 @@ reload_settings = lambda {
       # This will happen when migrating a new database
     end
   end
-}
-
-reload_settings.call
-
-if !Rails.configuration.cache_classes
-  ActiveSupport::Reloader.to_prepare do
-    reload_settings.call
-  end
 end
diff --git a/config/initializers/006-ensure_login_hint.rb b/config/initializers/006-ensure_login_hint.rb
index 837ab3f1ab8..06debac7564 100644
--- a/config/initializers/006-ensure_login_hint.rb
+++ b/config/initializers/006-ensure_login_hint.rb
@@ -2,30 +2,32 @@
 
 return if GlobalSetting.skip_db?
 
-# Some sanity checking so we don't count on an unindexed column on boot
-begin
-  if ActiveRecord::Base.connection.table_exists?(:users) &&
-     User.limit(20).count < 20 &&
-     User.where(admin: true).human_users.count == 0
+Rails.application.config.to_prepare do
+  # Some sanity checking so we don't count on an unindexed column on boot
+  begin
+    if ActiveRecord::Base.connection.table_exists?(:users) &&
+       User.limit(20).count < 20 &&
+       User.where(admin: true).human_users.count == 0
 
-    notice =
-      if GlobalSetting.developer_emails.blank?
-        "Congratulations, you installed Discourse! Unfortunately, no administrator emails were defined during setup, so finalizing the configuration <a href='https://meta.discourse.org/t/create-admin-account-from-console/17274'>may be difficult</a>."
-      else
-        emails = GlobalSetting.developer_emails.split(",")
-        if emails.length > 1
-          emails = emails[0..-2].join(', ') << " or #{emails[-1]} "
+      notice =
+        if GlobalSetting.developer_emails.blank?
+          "Congratulations, you installed Discourse! Unfortunately, no administrator emails were defined during setup, so finalizing the configuration <a href='https://meta.discourse.org/t/create-admin-account-from-console/17274'>may be difficult</a>."
         else
-          emails = emails[0]
+          emails = GlobalSetting.developer_emails.split(",")
+          if emails.length > 1
+            emails = emails[0..-2].join(', ') << " or #{emails[-1]} "
+          else
+            emails = emails[0]
+          end
+          "Congratulations, you installed Discourse! Register a new admin account with #{emails} to finalize configuration."
         end
-        "Congratulations, you installed Discourse! Register a new admin account with #{emails} to finalize configuration."
-      end
 
-    if notice != SiteSetting.global_notice
-      SiteSetting.global_notice = notice
-      SiteSetting.has_login_hint = true
+      if notice != SiteSetting.global_notice
+        SiteSetting.global_notice = notice
+        SiteSetting.has_login_hint = true
+      end
     end
+  rescue ActiveRecord::NoDatabaseError
+    # Database might not have been created
   end
-rescue ActiveRecord::NoDatabaseError
-  # Database might not have been created
 end
diff --git a/config/initializers/100-onebox_options.rb b/config/initializers/100-onebox_options.rb
index d98886e2ba3..3d2e4a2f055 100644
--- a/config/initializers/100-onebox_options.rb
+++ b/config/initializers/100-onebox_options.rb
@@ -1,16 +1,18 @@
 # frozen_string_literal: true
 
-if Rails.env.development? && SiteSetting.port.to_i > 0
-  Onebox.options = {
-    twitter_client: TwitterApi,
-    redirect_limit: 3,
-    user_agent: "Discourse Forum Onebox v#{Discourse::VERSION::STRING}",
-    allowed_ports: [80, 443, SiteSetting.port.to_i]
-  }
-else
-  Onebox.options = {
-    twitter_client: TwitterApi,
-    redirect_limit: 3,
-    user_agent: "Discourse Forum Onebox v#{Discourse::VERSION::STRING}"
-  }
+Rails.application.config.to_prepare do
+  if Rails.env.development? && SiteSetting.port.to_i > 0
+    Onebox.options = {
+      twitter_client: TwitterApi,
+      redirect_limit: 3,
+      user_agent: "Discourse Forum Onebox v#{Discourse::VERSION::STRING}",
+      allowed_ports: [80, 443, SiteSetting.port.to_i]
+    }
+  else
+    Onebox.options = {
+      twitter_client: TwitterApi,
+      redirect_limit: 3,
+      user_agent: "Discourse Forum Onebox v#{Discourse::VERSION::STRING}"
+    }
+  end
 end
diff --git a/config/initializers/100-push-notifications.rb b/config/initializers/100-push-notifications.rb
index 6b16a07a8e6..4a8166c077c 100644
--- a/config/initializers/100-push-notifications.rb
+++ b/config/initializers/100-push-notifications.rb
@@ -2,30 +2,32 @@
 
 return if GlobalSetting.skip_db?
 
-require_dependency 'webpush'
+Rails.application.config.to_prepare do
+  require 'webpush'
 
-def generate_vapid_key?
-  SiteSetting.vapid_public_key.blank? ||
-    SiteSetting.vapid_private_key.blank? ||
-    SiteSetting.vapid_public_key_bytes.blank? ||
-    SiteSetting.vapid_base_url != Discourse.base_url
-end
+  def generate_vapid_key?
+    SiteSetting.vapid_public_key.blank? ||
+      SiteSetting.vapid_private_key.blank? ||
+      SiteSetting.vapid_public_key_bytes.blank? ||
+      SiteSetting.vapid_base_url != Discourse.base_url
+  end
 
-SiteSetting.vapid_base_url = Discourse.base_url if SiteSetting.vapid_base_url.blank?
+  SiteSetting.vapid_base_url = Discourse.base_url if SiteSetting.vapid_base_url.blank?
 
-if generate_vapid_key?
-  vapid_key = Webpush.generate_key
-  SiteSetting.vapid_public_key = vapid_key.public_key
-  SiteSetting.vapid_private_key = vapid_key.private_key
+  if generate_vapid_key?
+    vapid_key = Webpush.generate_key
+    SiteSetting.vapid_public_key = vapid_key.public_key
+    SiteSetting.vapid_private_key = vapid_key.private_key
 
-  SiteSetting.vapid_public_key_bytes = Base64.urlsafe_decode64(SiteSetting.vapid_public_key).bytes.join("|")
-  SiteSetting.vapid_base_url = Discourse.base_url
+    SiteSetting.vapid_public_key_bytes = Base64.urlsafe_decode64(SiteSetting.vapid_public_key).bytes.join("|")
+    SiteSetting.vapid_base_url = Discourse.base_url
 
-  if ActiveRecord::Base.connection.table_exists?(:push_subscriptions)
-    PushSubscription.delete_all
+    if ActiveRecord::Base.connection.table_exists?(:push_subscriptions)
+      PushSubscription.delete_all
+    end
+  end
+
+  DiscourseEvent.on(:user_logged_out) do |user|
+    PushNotificationPusher.clear_subscriptions(user)
   end
 end
-
-DiscourseEvent.on(:user_logged_out) do |user|
-  PushNotificationPusher.clear_subscriptions(user)
-end
diff --git a/config/initializers/100-session_store.rb b/config/initializers/100-session_store.rb
index 1827f6e552a..b2ce5d47b46 100644
--- a/config/initializers/100-session_store.rb
+++ b/config/initializers/100-session_store.rb
@@ -1,21 +1,21 @@
 # frozen_string_literal: true
 
 # Be sure to restart your server when you modify this file.
-#
-require_dependency 'discourse_cookie_store'
 
-if Rails.env == "development" && SiteSetting.force_https
-  STDERR.puts
-  STDERR.puts "WARNING: force_https is enabled in dev"
-  STDERR.puts "It is very unlikely you are running HTTPS in dev."
-  STDERR.puts "Without HTTPS your session cookie will not work"
-  STDERR.puts "Try: bin/rails c"
-  STDERR.puts "SiteSetting.force_https = false"
-  STDERR.puts
-end
-
-Discourse::Application.config.session_store(
+Rails.application.config.session_store(
   :discourse_cookie_store,
   key: '_forum_session',
   path: (Rails.application.config.relative_url_root.nil?) ? '/' : Rails.application.config.relative_url_root
 )
+
+Rails.application.config.to_prepare do
+  if Rails.env.development? && SiteSetting.force_https
+    STDERR.puts
+    STDERR.puts "WARNING: force_https is enabled in dev"
+    STDERR.puts "It is very unlikely you are running HTTPS in dev."
+    STDERR.puts "Without HTTPS your session cookie will not work"
+    STDERR.puts "Try: bin/rails c"
+    STDERR.puts "SiteSetting.force_https = false"
+    STDERR.puts
+  end
+end
diff --git a/config/initializers/101-lograge.rb b/config/initializers/101-lograge.rb
index bcbcc4c2a21..311c64c7533 100644
--- a/config/initializers/101-lograge.rb
+++ b/config/initializers/101-lograge.rb
@@ -1,117 +1,119 @@
 # frozen_string_literal: true
 
-if (Rails.env.production? && SiteSetting.logging_provider == 'lograge') || (ENV["ENABLE_LOGRAGE"] == "1")
-  require 'lograge'
+Rails.application.config.to_prepare do
+  if (Rails.env.production? && SiteSetting.logging_provider == 'lograge') || (ENV["ENABLE_LOGRAGE"] == "1")
+    require 'lograge'
 
-  if Rails.configuration.multisite
-    Rails.logger.formatter = ActiveSupport::Logger::SimpleFormatter.new
-  end
+    if Rails.configuration.multisite
+      Rails.logger.formatter = ActiveSupport::Logger::SimpleFormatter.new
+    end
 
-  Rails.application.configure do
-    config.lograge.enabled = true
+    Rails.application.configure do
+      config.lograge.enabled = true
 
-    Lograge.ignore(lambda do |event|
-      # this is our hijack magic status,
-      # no point logging this cause we log again
-      # direct from hijack
-      event.payload[:status] == 418
-    end)
+      Lograge.ignore(lambda do |event|
+        # this is our hijack magic status,
+        # no point logging this cause we log again
+        # direct from hijack
+        event.payload[:status] == 418
+      end)
 
-    config.lograge.custom_payload do |controller|
-      begin
-        username =
-          begin
-            if controller.respond_to?(:current_user)
-              controller.current_user&.username
+      config.lograge.custom_payload do |controller|
+        begin
+          username =
+            begin
+              if controller.respond_to?(:current_user)
+                controller.current_user&.username
+              end
+            rescue Discourse::InvalidAccess, Discourse::ReadOnly, ActiveRecord::ReadOnlyError
+              nil
             end
-          rescue Discourse::InvalidAccess, Discourse::ReadOnly, ActiveRecord::ReadOnlyError
-            nil
-          end
 
-        ip =
-          begin
-            controller.request.remote_ip
-          rescue ActionDispatch::RemoteIp::IpSpoofAttackError
-            nil
-          end
+          ip =
+            begin
+              controller.request.remote_ip
+            rescue ActionDispatch::RemoteIp::IpSpoofAttackError
+              nil
+            end
 
-        {
-          ip: ip,
-          username: username
-        }
-      rescue => e
-        Rails.logger.warn("Failed to append custom payload: #{e.message}\n#{e.backtrace.join("\n")}")
-        {}
+          {
+            ip: ip,
+            username: username
+          }
+        rescue => e
+          Rails.logger.warn("Failed to append custom payload: #{e.message}\n#{e.backtrace.join("\n")}")
+          {}
+        end
       end
-    end
 
-    config.lograge.custom_options = lambda do |event|
-      begin
-        exceptions = %w(controller action format id)
+      config.lograge.custom_options = lambda do |event|
+        begin
+          exceptions = %w(controller action format id)
 
-        params = event.payload[:params].except(*exceptions)
+          params = event.payload[:params].except(*exceptions)
 
-        if (file = params[:file]) && file.respond_to?(:headers)
-          params[:file] = file.headers
+          if (file = params[:file]) && file.respond_to?(:headers)
+            params[:file] = file.headers
+          end
+
+          if (files = params[:files]) && files.respond_to?(:map)
+            params[:files] = files.map do |f|
+              f.respond_to?(:headers) ? f.headers : f
+            end
+          end
+
+          output = {
+            params: params.to_query,
+            database: RailsMultisite::ConnectionManagement.current_db,
+          }
+
+          if data = (Thread.current[:_method_profiler] || event.payload[:timings])
+            sql = data[:sql]
+
+            if sql
+              output[:db] = sql[:duration] * 1000
+              output[:db_calls] = sql[:calls]
+            end
+
+            redis = data[:redis]
+
+            if redis
+              output[:redis] = redis[:duration] * 1000
+              output[:redis_calls] = redis[:calls]
+            end
+
+            net = data[:net]
+
+            if net
+              output[:net] = net[:duration] * 1000
+              output[:net_calls] = net[:calls]
+            end
+          end
+
+          output
+        rescue RateLimiter::LimitExceeded
+          # no idea who this is, but they are limited
+          {}
+        rescue => e
+          Rails.logger.warn("Failed to append custom options: #{e.message}\n#{e.backtrace.join("\n")}")
+          {}
         end
-
-        if (files = params[:files]) && files.respond_to?(:map)
-          params[:files] = files.map do |f|
-            f.respond_to?(:headers) ? f.headers : f
-          end
-        end
-
-        output = {
-          params: params.to_query,
-          database: RailsMultisite::ConnectionManagement.current_db,
-        }
-
-        if data = (Thread.current[:_method_profiler] || event.payload[:timings])
-          sql = data[:sql]
-
-          if sql
-            output[:db] = sql[:duration] * 1000
-            output[:db_calls] = sql[:calls]
-          end
-
-          redis = data[:redis]
-
-          if redis
-            output[:redis] = redis[:duration] * 1000
-            output[:redis_calls] = redis[:calls]
-          end
-
-          net = data[:net]
-
-          if net
-            output[:net] = net[:duration] * 1000
-            output[:net_calls] = net[:calls]
-          end
-        end
-
-        output
-      rescue RateLimiter::LimitExceeded
-        # no idea who this is, but they are limited
-        {}
-      rescue => e
-        Rails.logger.warn("Failed to append custom options: #{e.message}\n#{e.backtrace.join("\n")}")
-        {}
       end
-    end
 
-    if ENV["LOGSTASH_URI"]
-      config.lograge.formatter = Lograge::Formatters::Logstash.new
+      if ENV["LOGSTASH_URI"]
+        config.lograge.formatter = Lograge::Formatters::Logstash.new
 
-      require 'discourse_logstash_logger'
+        require 'discourse_logstash_logger'
 
-      config.lograge.logger = DiscourseLogstashLogger.logger(
-        uri: ENV['LOGSTASH_URI'], type: :rails
-      )
+        config.lograge.logger = DiscourseLogstashLogger.logger(
+          uri: ENV['LOGSTASH_URI'], type: :rails
+        )
 
-      # Remove ActiveSupport::Logger from the chain and replace with Lograge's
-      # logger
-      Rails.logger.chained.pop
-      Rails.logger.chain(config.lograge.logger)
+        # Remove ActiveSupport::Logger from the chain and replace with Lograge's
+        # logger
+        Rails.logger.chained.pop
+        Rails.logger.chain(config.lograge.logger)
+      end
     end
   end
 end
diff --git a/config/initializers/assets.rb b/config/initializers/assets.rb
new file mode 100644
index 00000000000..306dd044ab0
--- /dev/null
+++ b/config/initializers/assets.rb
@@ -0,0 +1,99 @@
+# frozen_string_literal: true
+
+# Be sure to restart your server when you modify this file.
+
+# Enable the asset pipeline
+Rails.application.config.assets.enabled = true
+
+# Version of your assets, change this if you want to expire all your assets.
+Rails.application.config.assets.version = "1.2.5"
+
+# Add additional assets to the asset load path.
+Rails.application.config.assets.paths << "#{Rails.root}/config/locales"
+Rails.application.config.assets.paths << "#{Rails.root}/public/javascripts"
+
+# Precompile additional assets.
+# application.js, application.css, and all non-JS/CSS in the app/assets
+# folder are already added.
+
+# explicitly precompile any images in plugins ( /assets/images ) path
+Rails.application.config.assets.precompile += [lambda do |filename, path|
+  path =~ /assets\/images/ && !%w(.js .css).include?(File.extname(filename))
+end]
+
+Rails.application.config.assets.precompile += %w{
+  vendor.js
+  admin.js
+  browser-detect.js
+  browser-update.js
+  break_string.js
+  ember_jquery.js
+  pretty-text-bundle.js
+  wizard-application.js
+  wizard-vendor.js
+  markdown-it-bundle.js
+  service-worker.js
+  google-tag-manager.js
+  google-universal-analytics-v3.js
+  google-universal-analytics-v4.js
+  start-discourse.js
+  print-page.js
+  omniauth-complete.js
+  activate-account.js
+  auto-redirect.js
+  wizard-start.js
+  locales/i18n.js
+  discourse/app/lib/webauthn.js
+  confirm-new-email/confirm-new-email.js
+  confirm-new-email/bootstrap.js
+  onpopstate-handler.js
+  embed-application.js
+  discourse/tests/active-plugins.js
+  admin-plugins.js
+  discourse/tests/test_starter.js
+  }
+
+if EmberCli.enabled?
+  Rails.application.config.assets.precompile += %w{
+      discourse.js
+      test-support.js
+      test-helpers.js
+      scripts/discourse-test-listen-boot
+      scripts/discourse-boot
+    }
+else
+  Rails.application.config.assets.precompile += %w{
+    application.js
+    discourse/tests/test-support-rails.js
+    discourse/tests/test-helpers-rails.js
+    vendor-theme-tests.js
+  }
+end
+
+# Precompile all available locales
+unless GlobalSetting.try(:omit_base_locales)
+  Dir.glob("#{Rails.root}/app/assets/javascripts/locales/*.js.erb").each do |file|
+    Rails.application.config.assets.precompile << "locales/#{file.match(/([a-z_A-Z]+\.js)\.erb$/)[1]}"
+  end
+end
+
+# out of the box sprockets 3 grabs loose files that are hanging in assets,
+# the exclusion list does not include hbs so you double compile all this stuff
+Rails.application.config.assets.precompile.delete(Sprockets::Railtie::LOOSE_APP_ASSETS)
+
+# We don't want application from node_modules, only from the root
+Rails.application.config.assets.precompile.delete(/(?:\/|\\|\A)application\.(css|js)$/)
+Rails.application.config.assets.precompile += ['application.js']
+
+start_path = ::Rails.root.join("app/assets").to_s
+exclude = ['.es6', '.hbs', '.hbr', '.js', '.css', '.lock', '.json', '.log', '.html', '']
+Rails.application.config.assets.precompile << lambda do |logical_path, filename|
+  filename.start_with?(start_path) &&
+  !filename.include?("/node_modules/") &&
+  !filename.include?("/dist/") &&
+  !exclude.include?(File.extname(logical_path))
+end
+
+Discourse.find_plugin_js_assets(include_disabled: true).each do |file|
+  Rails.application.config.assets.precompile << "#{file}.js"
+end
diff --git a/config/initializers/filter_parameter_logging.rb b/config/initializers/filter_parameter_logging.rb
new file mode 100644
index 00000000000..af4c108bf6e
--- /dev/null
+++ b/config/initializers/filter_parameter_logging.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+# Be sure to restart your server when you modify this file.
+
+# Configure sensitive parameters which will be filtered from the log file.
+Rails.application.config.filter_parameters += [
+  :password,
+  :pop3_polling_password,
+  :api_key,
+  :s3_secret_access_key,
+  :twitter_consumer_secret,
+  :facebook_app_secret,
+  :github_client_secret,
+  :second_factor_token,
+]
diff --git a/config/initializers/new_framework_defaults_7_0.rb b/config/initializers/new_framework_defaults_7_0.rb
new file mode 100644
index 00000000000..69f37343efa
--- /dev/null
+++ b/config/initializers/new_framework_defaults_7_0.rb
@@ -0,0 +1,106 @@
+# frozen_string_literal: true
+
+# Be sure to restart your server when you modify this file.
+#
+# This file eases your Rails 7.0 framework defaults upgrade.
+#
+# Uncomment each configuration one by one to switch to the new default.
+# Once your application is ready to run with all new defaults, you can remove
+# this file and set the `config.load_defaults` to `7.0`.
+#
+# Read the Guide for Upgrading Ruby on Rails for more info on each option.
+# https://guides.rubyonrails.org/upgrading_ruby_on_rails.html
+
+# `button_to` view helper will render `<button>` element, regardless of whether
+# or not the content is passed as the first argument or as a block.
+Rails.application.config.action_view.button_to_generates_button_tag = true
+
+# `stylesheet_link_tag` view helper will not render the media attribute by default.
+Rails.application.config.action_view.apply_stylesheet_media_default = false
+
+# Change the digest class for the key generators to `OpenSSL::Digest::SHA256`.
+# Changing this default means invalidate all encrypted messages generated by
+# your application and, all the encrypted cookies. Only change this after you
+# rotated all the messages using the key rotator.
+#
+# See upgrading guide for more information on how to build a rotator.
+# https://guides.rubyonrails.org/v7.0/upgrading_ruby_on_rails.html
+# Rails.application.config.active_support.key_generator_hash_digest_class = OpenSSL::Digest::SHA256
+
+# Change the digest class for ActiveSupport::Digest.
+# Changing this default means that for example Etags change and
+# various cache keys leading to cache invalidation.
+# Rails.application.config.active_support.hash_digest_class = OpenSSL::Digest::SHA256
+
+# Don't override ActiveSupport::TimeWithZone.name and use the default Ruby
+# implementation.
+Rails.application.config.active_support.remove_deprecated_time_with_zone_name = true
+
+# Change the format of the cache entry.
+# Changing this default means that all new cache entries added to the cache
+# will have a different format that is not supported by Rails 6.1 applications.
+# Only change this value after your application is fully deployed to Rails 7.0
+# and you have no plans to rollback.
+# Rails.application.config.active_support.cache_format_version = 7.0
+
+# Calls `Rails.application.executor.wrap` around test cases.
+# This makes test cases behave closer to an actual request or job.
+# Several features that are normally disabled in test, such as Active Record query cache
+# and asynchronous queries will then be enabled.
+Rails.application.config.active_support.executor_around_test_case = true
+
+# Define the isolation level of most of Rails internal state.
+# If you use a fiber based server or job processor, you should set it to `:fiber`.
+# Otherwise the default of `:thread` if preferable.
+Rails.application.config.active_support.isolation_level = :thread
+
+# Set both the `:open_timeout` and `:read_timeout` values for `:smtp` delivery method.
+Rails.application.config.action_mailer.smtp_timeout = 5
+
+# Automatically infer `inverse_of` for associations with a scope.
+Rails.application.config.active_record.automatic_scope_inversing = true
+
+# Raise when running tests if fixtures contained foreign key violations
+Rails.application.config.active_record.verify_foreign_keys_for_fixtures = true
+
+# Disable partial inserts.
+# This default means that all columns will be referenced in INSERT queries
+# regardless of whether they have a default or not.
+Rails.application.config.active_record.partial_inserts = false
+#
+# Protect from open redirect attacks in `redirect_back_or_to` and `redirect_to`.
+Rails.application.config.action_controller.raise_on_open_redirects = true
+
+# If you're upgrading and haven't set `cookies_serializer` previously, your cookie serializer
+# was `:marshal`. Convert all cookies to JSON, using the `:hybrid` formatter.
+#
+# If you're confident all your cookies are JSON formatted, you can switch to the `:json` formatter.
+#
+# Continue to use `:marshal` for backward-compatibility with old cookies.
+#
+# If you have configured the serializer elsewhere, you can remove this.
+#
+# See https://guides.rubyonrails.org/action_controller_overview.html#cookies for more information.
+Rails.application.config.action_dispatch.cookies_serializer = :marshal
+
+# Enable parameter wrapping for JSON.
+# Previously this was set in an initializer. It's fine to keep using that initializer if you've customized it.
+# To disable parameter wrapping entirely, set this config to `false`.
+# Rails.application.config.action_controller.wrap_parameters_by_default = true
+
+# Specifies whether generated namespaced UUIDs follow the RFC 4122 standard for namespace IDs provided as a
+# `String` to `Digest::UUID.uuid_v3` or `Digest::UUID.uuid_v5` method calls.
+#
+# See https://guides.rubyonrails.org/configuring.html#config-active-support-use-rfc4122-namespaced-uuids for
+# more information.
+Rails.application.config.active_support.use_rfc4122_namespaced_uuids = true
+
+# Change the default headers to disable browsers' flawed legacy XSS protection.
+Rails.application.config.action_dispatch.default_headers = {
+  "X-Frame-Options" => "SAMEORIGIN",
+  "X-XSS-Protection" => "0",
+  "X-Content-Type-Options" => "nosniff",
+  "X-Download-Options" => "noopen",
+  "X-Permitted-Cross-Domain-Policies" => "none",
+  "Referrer-Policy" => "strict-origin-when-cross-origin"
+}
diff --git a/db/migrate/20160303183607_clear_common_passwords_cache.rb b/db/migrate/20160303183607_clear_common_passwords_cache.rb
index 421301e9ed9..92816452630 100644
--- a/db/migrate/20160303183607_clear_common_passwords_cache.rb
+++ b/db/migrate/20160303183607_clear_common_passwords_cache.rb
@@ -1,6 +1,6 @@
 # frozen_string_literal: true
 
-require "common_passwords/common_passwords"
+require "common_passwords"
 
 class ClearCommonPasswordsCache < ActiveRecord::Migration[4.2]
   def change
diff --git a/lib/discourse_cookie_store.rb b/lib/action_dispatch/session/discourse_cookie_store.rb
similarity index 100%
rename from lib/discourse_cookie_store.rb
rename to lib/action_dispatch/session/discourse_cookie_store.rb
diff --git a/lib/auth/github_authenticator.rb b/lib/auth/github_authenticator.rb
index c082bd021eb..7a64e089fe2 100644
--- a/lib/auth/github_authenticator.rb
+++ b/lib/auth/github_authenticator.rb
@@ -1,6 +1,6 @@
 # frozen_string_literal: true
 
-require_dependency 'has_errors'
+require 'has_errors'
 
 class Auth::GithubAuthenticator < Auth::ManagedAuthenticator
 
diff --git a/lib/auth/oauth2_authenticator.rb b/lib/auth/oauth2_authenticator.rb
deleted file mode 100644
index 78880319a6d..00000000000
--- a/lib/auth/oauth2_authenticator.rb
+++ /dev/null
@@ -1,59 +0,0 @@
-# frozen_string_literal: true
-
-class Auth::OAuth2Authenticator < Auth::Authenticator
-
-  def name
-    @name
-  end
-
-  # only option at the moment is :trusted
-  def initialize(name, opts = {})
-    Discourse.deprecate("OAuth2Authenticator is deprecated. Use `ManagedAuthenticator` and `UserAssociatedAccount` instead. For more information, see https://meta.discourse.org/t/106695", drop_from: '2.9.0', output_in_test: true)
-    @name = name
-    @opts = opts
-  end
-
-  def after_authenticate(auth_token)
-
-    result = Auth::Result.new
-
-    oauth2_provider = auth_token[:provider]
-    oauth2_uid = auth_token[:uid]
-    data = auth_token[:info]
-    result.email = email = data[:email]
-    result.name = name = data[:name]
-
-    oauth2_user_info = Oauth2UserInfo.find_by(uid: oauth2_uid, provider: oauth2_provider)
-
-    if !oauth2_user_info && @opts[:trusted] && user = User.find_by_email(email)
-      oauth2_user_info = Oauth2UserInfo.create(uid: oauth2_uid,
-                                               provider: oauth2_provider,
-                                               name: name,
-                                               email: email,
-                                               user: user)
-    end
-
-    result.user = oauth2_user_info.try(:user)
-    result.email_valid = @opts[:trusted]
-
-    result.extra_data = {
-      uid: oauth2_uid,
-      provider: oauth2_provider
-    }
-
-    result
-  end
-
-  def after_create_account(user, auth)
-    data = auth[:extra_data]
-    association = Oauth2UserInfo.find_or_initialize_by(provider: data[:provider], uid: data[:uid])
-    association.user = user
-    association.email = auth[:email]
-    association.save!
-  end
-
-  def description_for_user(user)
-    info = Oauth2UserInfo.find_by(user_id: user.id, provider: @name)
-    info&.email || info&.name || info&.uid || ""
-  end
-end
diff --git a/lib/auth/result.rb b/lib/auth/result.rb
index 09298af2d4e..675f40eadb0 100644
--- a/lib/auth/result.rb
+++ b/lib/auth/result.rb
@@ -75,7 +75,7 @@ class Auth::Result
 
   def self.from_session_data(data, user:)
     result = new
-    data = data.symbolize_keys
+    data = data.with_indifferent_access
     SESSION_ATTRIBUTES.each { |att| result.public_send("#{att}=", data[att]) }
     result.user = user
     result
diff --git a/lib/backup_restore/backup_store.rb b/lib/backup_restore/backup_store.rb
index 2243837fd0c..65a3acaa951 100644
--- a/lib/backup_restore/backup_store.rb
+++ b/lib/backup_restore/backup_store.rb
@@ -10,10 +10,10 @@ module BackupRestore
     def self.create(opts = {})
       case opts[:location] || SiteSetting.backup_location
       when BackupLocationSiteSetting::LOCAL
-        require_dependency "backup_restore/local_backup_store"
+        require "backup_restore/local_backup_store"
         BackupRestore::LocalBackupStore.new(opts)
       when BackupLocationSiteSetting::S3
-        require_dependency "backup_restore/s3_backup_store"
+        require "backup_restore/s3_backup_store"
         BackupRestore::S3BackupStore.new(opts)
       end
     end
diff --git a/lib/common_passwords/common_passwords.rb b/lib/common_passwords.rb
similarity index 100%
rename from lib/common_passwords/common_passwords.rb
rename to lib/common_passwords.rb
diff --git a/lib/content_security_policy/builder.rb b/lib/content_security_policy/builder.rb
index 9ff21f4af6f..af240829ba8 100644
--- a/lib/content_security_policy/builder.rb
+++ b/lib/content_security_policy/builder.rb
@@ -1,5 +1,5 @@
 # frozen_string_literal: true
-require_dependency 'content_security_policy/default'
+require 'content_security_policy/default'
 
 class ContentSecurityPolicy
   class Builder
diff --git a/lib/content_security_policy/default.rb b/lib/content_security_policy/default.rb
index 8029bad9e4f..1350874000e 100644
--- a/lib/content_security_policy/default.rb
+++ b/lib/content_security_policy/default.rb
@@ -1,5 +1,5 @@
 # frozen_string_literal: true
-require_dependency 'content_security_policy'
+require 'content_security_policy'
 
 class ContentSecurityPolicy
   class Default
diff --git a/lib/content_security_policy/middleware.rb b/lib/content_security_policy/middleware.rb
index 0435529bff7..54ec0f5a656 100644
--- a/lib/content_security_policy/middleware.rb
+++ b/lib/content_security_policy/middleware.rb
@@ -1,5 +1,5 @@
 # frozen_string_literal: true
-require_dependency 'content_security_policy'
+require 'content_security_policy'
 
 class ContentSecurityPolicy
   class Middleware
diff --git a/lib/custom_setting_providers.rb b/lib/custom_setting_providers.rb
deleted file mode 100644
index e096e900a89..00000000000
--- a/lib/custom_setting_providers.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-# frozen_string_literal: true
-
-# Support for plugins to register custom setting providers. They can do this
-# by having a file, `register_provider.rb` in their root that will be run
-# at this point.
-
-Dir.glob(File.join(File.dirname(__FILE__), '../plugins', '*', "register_provider.rb")) do |p|
-  require p
-end
diff --git a/lib/db_helper.rb b/lib/db_helper.rb
index 6510d49d973..6bc79b044ef 100644
--- a/lib/db_helper.rb
+++ b/lib/db_helper.rb
@@ -1,6 +1,6 @@
 # frozen_string_literal: true
 
-require_dependency "migration/base_dropper"
+require "migration/base_dropper"
 
 class DbHelper
 
diff --git a/lib/discourse.rb b/lib/discourse.rb
index 6c9ae948f2c..e4a5c49c12c 100644
--- a/lib/discourse.rb
+++ b/lib/discourse.rb
@@ -2,8 +2,8 @@
 
 require 'cache'
 require 'open3'
-require_dependency 'plugin/instance'
-require_dependency 'version'
+require 'plugin/instance'
+require 'version'
 
 module Discourse
   DB_POST_MIGRATE_PATH ||= "db/post_migrate"
diff --git a/lib/file_store/local_store.rb b/lib/file_store/local_store.rb
index 9e704efee98..4922eca7077 100644
--- a/lib/file_store/local_store.rb
+++ b/lib/file_store/local_store.rb
@@ -1,6 +1,6 @@
 # frozen_string_literal: true
 
-require_dependency 'file_store/base_store'
+require 'file_store/base_store'
 
 module FileStore
 
diff --git a/lib/file_store/s3_store.rb b/lib/file_store/s3_store.rb
index 951be4f6184..54d8dff720f 100644
--- a/lib/file_store/s3_store.rb
+++ b/lib/file_store/s3_store.rb
@@ -2,9 +2,9 @@
 
 require "uri"
 require "mini_mime"
-require_dependency "file_store/base_store"
-require_dependency "s3_helper"
-require_dependency "file_helper"
+require "file_store/base_store"
+require "s3_helper"
+require "file_helper"
 
 module FileStore
 
diff --git a/lib/freedom_patches/active_record_postgresql_adapter.rb b/lib/freedom_patches/active_record_postgresql_adapter.rb
deleted file mode 100644
index bf64f9233c5..00000000000
--- a/lib/freedom_patches/active_record_postgresql_adapter.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-# frozen_string_literal: true
-
-# Pulls in https://github.com/rails/rails/pull/42368 early since the query is
-# definitely more efficient as it does not involved the PG planner.
-# Remove once Rails 7 has been released.
-module ActiveRecord
-  module ConnectionAdapters
-    class PostgreSQLAdapter
-      def active?
-        @lock.synchronize do
-          @connection.query ";"
-        end
-        true
-      rescue PG::Error
-        false
-      end
-    end
-  end
-end
diff --git a/lib/freedom_patches/ar_references_fix.rb b/lib/freedom_patches/ar_references_fix.rb
new file mode 100644
index 00000000000..ba9db28010c
--- /dev/null
+++ b/lib/freedom_patches/ar_references_fix.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+# This patch is a backport of https://github.com/rails/rails/pull/42350
+# It fixes a bug introduced by Rails which affects reference columns marking
+# them as integer instead of bigint.
+#
+# This should be deleted when version 7.0.3 is released.
+module FreedomPatches
+  module ArReferencesFix
+    module SchemaDefinition
+      def index_options(table_name)
+        index_options = as_options(index)
+
+        # legacy reference index names are used on versions 6.0 and earlier
+        return index_options if options[:_uses_legacy_reference_index_name]
+
+        index_options[:name] ||= polymorphic_index_name(table_name) if polymorphic
+        index_options
+      end
+
+      ActiveRecord::ConnectionAdapters::ReferenceDefinition.prepend(self)
+    end
+  end
+end
+
+class ActiveRecord::Migration::Compatibility::V6_0
+  module TableDefinition
+    def references(*args, **options)
+      options[:_uses_legacy_reference_index_name] = true
+      super
+    end
+    alias :belongs_to :references
+  end
+
+  def add_reference(table_name, ref_name, **options)
+    if connection.adapter_name == "SQLite"
+      options[:type] = :integer
+    end
+    options[:_uses_legacy_reference_index_name] = true
+    super
+  end
+  alias :add_belongs_to :add_reference
+
+  def compatible_table_definition(t)
+    class << t
+      prepend TableDefinition
+    end
+    super
+  end
+end
diff --git a/lib/freedom_patches/rails_multisite.rb b/lib/freedom_patches/rails_multisite.rb
index 35c6b82624c..99244669d7e 100644
--- a/lib/freedom_patches/rails_multisite.rb
+++ b/lib/freedom_patches/rails_multisite.rb
@@ -10,7 +10,7 @@ module RailsMultisite
           break if !defined?(RailsFailover::ActiveRecord)
           break if db == RailsMultisite::ConnectionManagement::DEFAULT
 
-          reading_role = :"#{db}_#{ActiveRecord::Base.reading_role}"
+          reading_role = :"#{db}_#{ActiveRecord.reading_role}"
           spec = RailsMultisite::ConnectionManagement.connection_spec(db: db)
 
           ActiveRecord::Base.connection_handlers[reading_role] ||= begin
diff --git a/lib/highlight_js/highlight_js.rb b/lib/highlight_js.rb
similarity index 100%
rename from lib/highlight_js/highlight_js.rb
rename to lib/highlight_js.rb
diff --git a/lib/html_to_markdown.rb b/lib/html_to_markdown.rb
index 6cd05ded831..bcd0da81322 100644
--- a/lib/html_to_markdown.rb
+++ b/lib/html_to_markdown.rb
@@ -59,13 +59,13 @@ class HtmlToMarkdown
           before, after = parent.children.slice_when { |n| n == br }.to_a
 
           if before.size > 1
-            b = Nokogiri::XML::Node.new(parent.name, doc)
+            b = Nokogiri::XML::Node.new(parent.name, doc.document)
             before[0...-1].each { |c| b.add_child(c) }
             parent.previous = b if b.inner_html.present?
           end
 
           if after.present?
-            a = Nokogiri::XML::Node.new(parent.name, doc)
+            a = Nokogiri::XML::Node.new(parent.name, doc.document)
             after.each { |c| a.add_child(c) }
             parent.next = a if a.inner_html.present?
           end
diff --git a/lib/i18n/duplicate_key_finder.rb b/lib/i18n/duplicate_key_finder.rb
index c1e99c93a7f..65b0f33d0e9 100644
--- a/lib/i18n/duplicate_key_finder.rb
+++ b/lib/i18n/duplicate_key_finder.rb
@@ -1,6 +1,6 @@
 # frozen_string_literal: true
 
-require_relative "locale_file_walker"
+require "locale_file_walker"
 
 class DuplicateKeyFinder < LocaleFileWalker
 
diff --git a/lib/middleware/anonymous_cache.rb b/lib/middleware/anonymous_cache.rb
index 270e71fb947..b9e0d0db8aa 100644
--- a/lib/middleware/anonymous_cache.rb
+++ b/lib/middleware/anonymous_cache.rb
@@ -1,9 +1,9 @@
 # frozen_string_literal: true
 
-require_dependency "mobile_detection"
-require_dependency "crawler_detection"
-require_dependency "guardian"
-require_dependency "http_language_parser"
+require "mobile_detection"
+require "crawler_detection"
+require "guardian"
+require "http_language_parser"
 
 module Middleware
   class AnonymousCache
diff --git a/lib/migration/column_dropper.rb b/lib/migration/column_dropper.rb
index 754f5bf8267..71e472baa49 100644
--- a/lib/migration/column_dropper.rb
+++ b/lib/migration/column_dropper.rb
@@ -1,6 +1,6 @@
 # frozen_string_literal: true
 
-require_dependency 'migration/base_dropper'
+require 'migration/base_dropper'
 
 module Migration
   class ColumnDropper
diff --git a/lib/migration/table_dropper.rb b/lib/migration/table_dropper.rb
index 8b7e89a3749..bbd27f02e5b 100644
--- a/lib/migration/table_dropper.rb
+++ b/lib/migration/table_dropper.rb
@@ -1,6 +1,6 @@
 # frozen_string_literal: true
 
-require_dependency 'migration/base_dropper'
+require 'migration/base_dropper'
 
 module Migration
   class Migration::TableDropper
diff --git a/lib/plugin.rb b/lib/plugin.rb
new file mode 100644
index 00000000000..6a788379a8d
--- /dev/null
+++ b/lib/plugin.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+module Plugin
+  def self.initialization_guard(&block)
+    begin
+      block.call
+    rescue => error
+      plugins_directory = Rails.root + 'plugins'
+
+      if error.backtrace && error.backtrace_locations
+        plugin_path = error.backtrace_locations.lazy.map do |location|
+          Pathname.new(location.absolute_path)
+            .ascend
+            .lazy
+            .find { |path| path.parent == plugins_directory }
+        end.next
+
+        raise unless plugin_path
+
+        stack_trace = error.backtrace.each_with_index.inject([]) do |messages, (line, index)|
+          if index == 0
+            messages << "#{line}: #{error} (#{error.class})"
+          else
+            messages << "\t#{index}: from #{line}"
+          end
+        end.reverse.join("\n")
+
+        STDERR.puts <<~TEXT
+          #{stack_trace}
+
+          ** INCOMPATIBLE PLUGIN **
+
+          You are unable to build Discourse due to errors in the plugin at
+          #{plugin_path}
+
+          Please try removing this plugin and rebuilding again!
+        TEXT
+      else
+        STDERR.puts <<~TEXT
+          ** PLUGIN FAILURE **
+
+          You are unable to build Discourse due to this error during plugin
+          initialization:
+
+          #{error}
+
+          #{error.backtrace.join("\n")}
+        TEXT
+      end
+      exit 1
+    end
+  end
+end
diff --git a/lib/plugin/instance.rb b/lib/plugin/instance.rb
index 8482ff02109..574ad98f493 100644
--- a/lib/plugin/instance.rb
+++ b/lib/plugin/instance.rb
@@ -2,8 +2,8 @@
 
 require 'digest/sha1'
 require 'fileutils'
-require_dependency 'plugin/metadata'
-require_dependency 'auth'
+require 'plugin/metadata'
+require 'auth'
 
 class Plugin::CustomEmoji
   CACHE_KEY ||= "plugin-emoji"
diff --git a/lib/plugin_initialization_guard.rb b/lib/plugin_initialization_guard.rb
deleted file mode 100644
index 9f9676ef159..00000000000
--- a/lib/plugin_initialization_guard.rb
+++ /dev/null
@@ -1,51 +0,0 @@
-# frozen_string_literal: true
-
-def plugin_initialization_guard(&block)
-  begin
-    block.call
-  rescue => error
-    plugins_directory = Rails.root + 'plugins'
-
-    if error.backtrace && error.backtrace_locations
-      plugin_path = error.backtrace_locations.lazy.map do |location|
-        Pathname.new(location.absolute_path)
-          .ascend
-          .lazy
-          .find { |path| path.parent == plugins_directory }
-      end.next
-
-      raise unless plugin_path
-
-      stack_trace = error.backtrace.each_with_index.inject([]) do |messages, (line, index)|
-        if index == 0
-          messages << "#{line}: #{error} (#{error.class})"
-        else
-          messages << "\t#{index}: from #{line}"
-        end
-      end.reverse.join("\n")
-
-      STDERR.puts <<~TEXT
-        #{stack_trace}
-
-        ** INCOMPATIBLE PLUGIN **
-
-        You are unable to build Discourse due to errors in the plugin at
-        #{plugin_path}
-
-        Please try removing this plugin and rebuilding again!
-      TEXT
-    else
-      STDERR.puts <<~TEXT
-        ** PLUGIN FAILURE **
-
-        You are unable to build Discourse due to this error during plugin
-        initialization:
-
-        #{error}
-
-        #{error.backtrace.join("\n")}
-      TEXT
-    end
-    exit 1
-  end
-end
diff --git a/lib/post_action_result.rb b/lib/post_action_result.rb
index 9959821f248..88a7b5d0ce5 100644
--- a/lib/post_action_result.rb
+++ b/lib/post_action_result.rb
@@ -1,6 +1,6 @@
 # frozen_string_literal: true
 
-require_dependency 'has_errors'
+require 'has_errors'
 
 class PostActionResult
   include HasErrors
diff --git a/lib/require_dependency_backward_compatibility.rb b/lib/require_dependency_backward_compatibility.rb
index ced0bbbeeb4..efec53ced3d 100644
--- a/lib/require_dependency_backward_compatibility.rb
+++ b/lib/require_dependency_backward_compatibility.rb
@@ -18,5 +18,5 @@ module RequireDependencyBackwardCompatibility
     super
   end
 
-  ActiveSupport::Dependencies::ZeitwerkIntegration::RequireDependency.prepend(self)
+  Object.prepend(self)
 end
diff --git a/lib/reviewable/actions.rb b/lib/reviewable/actions.rb
index ba7858d87d2..3343ac0fb83 100644
--- a/lib/reviewable/actions.rb
+++ b/lib/reviewable/actions.rb
@@ -1,6 +1,6 @@
 # frozen_string_literal: true
 
-require_dependency 'reviewable/collection'
+require 'reviewable/collection'
 
 class Reviewable < ActiveRecord::Base
   class Actions < Reviewable::Collection
diff --git a/lib/s3_cors_rulesets.rb b/lib/s3_cors_rulesets.rb
index 788c6f6bb22..3c4e293dd81 100644
--- a/lib/s3_cors_rulesets.rb
+++ b/lib/s3_cors_rulesets.rb
@@ -1,6 +1,6 @@
 # frozen_string_literal: true
 
-require_dependency "s3_helper"
+require "s3_helper"
 
 class S3CorsRulesets
   ASSETS = {
diff --git a/lib/stylesheet/compiler.rb b/lib/stylesheet/compiler.rb
index 72231f41635..ba4769a2650 100644
--- a/lib/stylesheet/compiler.rb
+++ b/lib/stylesheet/compiler.rb
@@ -1,7 +1,6 @@
 # frozen_string_literal: true
 
 require 'stylesheet/importer'
-require 'stylesheet/functions'
 
 module Stylesheet
 
diff --git a/lib/stylesheet/functions.rb b/lib/stylesheet/functions.rb
deleted file mode 100644
index 072998ab767..00000000000
--- a/lib/stylesheet/functions.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-# frozen_string_literal: true
-
-module Stylesheet
-  module ScssFunctions
-    def asset_url(path)
-      Discourse.deprecate("The `asset-url` SCSS function is deprecated. Use `absolute-image-url` instead.", drop_from: '2.9.0')
-      SassC::Script::Value::String.new("url('#{ActionController::Base.helpers.asset_url(path.value)}')")
-    end
-    def image_url(path)
-      Discourse.deprecate("The `image-url` SCSS function is deprecated. Use `absolute-image-url` instead.", drop_from: '2.9.0')
-      SassC::Script::Value::String.new("url('#{ActionController::Base.helpers.image_url(path.value)}')")
-    end
-  end
-end
-
-::SassC::Script::Functions.include(Stylesheet::ScssFunctions)
diff --git a/lib/stylesheet/importer.rb b/lib/stylesheet/importer.rb
index 36de290b259..c53b5cb2d2c 100644
--- a/lib/stylesheet/importer.rb
+++ b/lib/stylesheet/importer.rb
@@ -1,6 +1,6 @@
 # frozen_string_literal: true
 
-require_dependency 'global_path'
+require 'global_path'
 
 module Stylesheet
   class Importer
diff --git a/lib/stylesheet/manager.rb b/lib/stylesheet/manager.rb
index 8ec676d1a1d..328762f06c9 100644
--- a/lib/stylesheet/manager.rb
+++ b/lib/stylesheet/manager.rb
@@ -1,7 +1,7 @@
 # frozen_string_literal: true
 
-require_dependency 'distributed_cache'
-require_dependency 'stylesheet/compiler'
+require 'distributed_cache'
+require 'stylesheet/compiler'
 
 module Stylesheet; end
 
diff --git a/lib/svg_sprite/svg_sprite.rb b/lib/svg_sprite.rb
similarity index 100%
rename from lib/svg_sprite/svg_sprite.rb
rename to lib/svg_sprite.rb
diff --git a/lib/tasks/emoji.rake b/lib/tasks/emoji.rake
index 6471730edc1..f85e22b379b 100644
--- a/lib/tasks/emoji.rake
+++ b/lib/tasks/emoji.rake
@@ -5,7 +5,7 @@ require "fileutils"
 require "json"
 require "nokogiri"
 require "open-uri"
-require_dependency "file_helper"
+require "file_helper"
 
 EMOJI_GROUPS_PATH ||= "lib/emoji/groups.json"
 
diff --git a/lib/tasks/images.rake b/lib/tasks/images.rake
index b7af043ddf8..653b305f23d 100644
--- a/lib/tasks/images.rake
+++ b/lib/tasks/images.rake
@@ -1,6 +1,6 @@
 # frozen_string_literal: true
 
-require_dependency "file_helper"
+require "file_helper"
 
 task "images:compress" => :environment do
   images = Dir.glob("#{Rails.root}/app/**/*.png")
diff --git a/lib/tasks/topics.rake b/lib/tasks/topics.rake
index f979e625c7e..e3bf5790e02 100644
--- a/lib/tasks/topics.rake
+++ b/lib/tasks/topics.rake
@@ -1,6 +1,6 @@
 # frozen_string_literal: true
 
-require_dependency "rake_helpers"
+require "rake_helpers"
 
 def close_old_topics(category)
   topics = Topic.where(closed: false, category_id: category.id)
diff --git a/lib/tasks/uploads.rake b/lib/tasks/uploads.rake
index e3428629c08..3d24f95695d 100644
--- a/lib/tasks/uploads.rake
+++ b/lib/tasks/uploads.rake
@@ -8,7 +8,7 @@ require "base62"
 #                                    gather                                    #
 ################################################################################
 
-require_dependency "rake_helpers"
+require "rake_helpers"
 
 task "uploads:gather" => :environment do
   ENV["RAILS_DB"] ? gather_uploads : gather_uploads_for_all_sites
diff --git a/lib/theme_store/zip_exporter.rb b/lib/theme_store/zip_exporter.rb
index ed1d64c187d..87a9c8bd066 100644
--- a/lib/theme_store/zip_exporter.rb
+++ b/lib/theme_store/zip_exporter.rb
@@ -1,6 +1,6 @@
 # frozen_string_literal: true
 
-require_dependency 'compression/zip'
+require 'compression/zip'
 
 module ThemeStore; end
 
diff --git a/lib/theme_store/zip_importer.rb b/lib/theme_store/zip_importer.rb
index deff98d3dfd..ff98ea49019 100644
--- a/lib/theme_store/zip_importer.rb
+++ b/lib/theme_store/zip_importer.rb
@@ -1,6 +1,6 @@
 # frozen_string_literal: true
 
-require_dependency 'compression/engine'
+require 'compression/engine'
 
 module ThemeStore; end
 
diff --git a/lib/validators/upload_validator.rb b/lib/validators/upload_validator.rb
index 450c8dafc39..7dd4f757812 100644
--- a/lib/validators/upload_validator.rb
+++ b/lib/validators/upload_validator.rb
@@ -1,6 +1,6 @@
 # frozen_string_literal: true
 
-require_dependency "file_helper"
+require "file_helper"
 
 module Validators; end
 
diff --git a/spec/integrity/i18n_spec.rb b/spec/integrity/i18n_spec.rb
index 8f2dbfc1328..2ab21f50699 100644
--- a/spec/integrity/i18n_spec.rb
+++ b/spec/integrity/i18n_spec.rb
@@ -1,7 +1,5 @@
 # frozen_string_literal: true
 
-require "i18n/duplicate_key_finder"
-
 def extract_locale(path)
   path[/\.([^.]{2,})\.yml$/, 1]
 end
diff --git a/spec/integrity/site_setting_spec.rb b/spec/integrity/site_setting_spec.rb
index 5d7ecbf58df..51d17551837 100644
--- a/spec/integrity/site_setting_spec.rb
+++ b/spec/integrity/site_setting_spec.rb
@@ -1,7 +1,5 @@
 # frozen_string_literal: true
 
-require "i18n/duplicate_key_finder"
-
 describe "site setting integrity checks" do
   let(:site_setting_file) { File.join(Rails.root, 'config', 'site_settings.yml') }
   let(:yaml) { YAML.load_file(site_setting_file) }
diff --git a/spec/jobs/create_linked_topic_spec.rb b/spec/jobs/create_linked_topic_spec.rb
index ccc0a16fa15..e8ff6745542 100644
--- a/spec/jobs/create_linked_topic_spec.rb
+++ b/spec/jobs/create_linked_topic_spec.rb
@@ -1,13 +1,11 @@
 # frozen_string_literal: true
 
 describe Jobs::CreateLinkedTopic do
-
   it "returns when the post cannot be found" do
     expect { Jobs::CreateLinkedTopic.new.perform(post_id: 1, sync_exec: true) }.not_to raise_error
   end
 
   context 'with a post' do
-
     fab!(:category) { Fabricate(:category) }
     fab!(:topic) { Fabricate(:topic, category: category) }
     fab!(:post) do
@@ -54,5 +52,4 @@ describe Jobs::CreateLinkedTopic do
       expect(linked_topic.sequence).to eq(2)
     end
   end
-
 end
diff --git a/spec/jobs/feature_topic_users_spec.rb b/spec/jobs/feature_topic_users_spec.rb
index 84d375da8c9..3d4aa0fe3d7 100644
--- a/spec/jobs/feature_topic_users_spec.rb
+++ b/spec/jobs/feature_topic_users_spec.rb
@@ -1,7 +1,6 @@
 # frozen_string_literal: true
 
 describe Jobs::FeatureTopicUsers do
-
   it "raises an error without a topic_id" do
     expect { Jobs::FeatureTopicUsers.new.execute({}) }.to raise_error(Discourse::InvalidParameters)
   end
@@ -32,16 +31,13 @@ describe Jobs::FeatureTopicUsers do
       Jobs::FeatureTopicUsers.new.execute(topic_id: topic.id)
       expect(topic.reload.featured_user_ids.include?(evil_trout.id)).to eq(false)
     end
-
   end
 
   context "participant count" do
-
     let!(:post) { create_post }
     let(:topic) { post.topic }
 
     it "it works as expected" do
-
       # It has 1 participant after creation
       expect(topic.participant_count).to eq(1)
 
@@ -58,9 +54,6 @@ describe Jobs::FeatureTopicUsers do
       create_post(topic: topic, user: Fabricate(:evil_trout))
       Jobs::FeatureTopicUsers.new.execute(topic_id: topic.id)
       expect(topic.reload.participant_count).to eq(2)
-
     end
-
   end
-
 end
diff --git a/spec/jobs/old_keys_reminder_spec.rb b/spec/jobs/old_keys_reminder_spec.rb
index ac00d45b9ad..26407b31fc1 100644
--- a/spec/jobs/old_keys_reminder_spec.rb
+++ b/spec/jobs/old_keys_reminder_spec.rb
@@ -28,9 +28,9 @@ describe Jobs::OldKeysReminder do
 
       As a courtesy, we wanted to let you know that the following credentials used on your Discourse instance have not been updated in more than two years:
 
-      google_oauth2_client_secret - #{google_secret.updated_at.to_date.to_s(:db)}
-      github_client_secret - #{github_secret.updated_at.to_date.to_s(:db)}
-      api key description - #{api_key.created_at.to_date.to_s(:db)}
+      google_oauth2_client_secret - #{google_secret.updated_at.to_date.to_fs(:db)}
+      github_client_secret - #{github_secret.updated_at.to_date.to_fs(:db)}
+      api key description - #{api_key.created_at.to_date.to_fs(:db)}
 
       No action is required at this time, however, it is considered good security practice to cycle all your important credentials every few years.
     TEXT
@@ -45,11 +45,11 @@ describe Jobs::OldKeysReminder do
 
       As a courtesy, we wanted to let you know that the following credentials used on your Discourse instance have not been updated in more than two years:
 
-      google_oauth2_client_secret - #{google_secret.updated_at.to_date.to_s(:db)}
-      github_client_secret - #{github_secret.updated_at.to_date.to_s(:db)}
-      twitter_consumer_secret - #{recent_twitter_secret.updated_at.to_date.to_s(:db)}
-      api key description - #{api_key.created_at.to_date.to_s(:db)}
-      recent api key description - #{admin.username} - #{recent_api_key.created_at.to_date.to_s(:db)}
+      google_oauth2_client_secret - #{google_secret.updated_at.to_date.to_fs(:db)}
+      github_client_secret - #{github_secret.updated_at.to_date.to_fs(:db)}
+      twitter_consumer_secret - #{recent_twitter_secret.updated_at.to_date.to_fs(:db)}
+      api key description - #{api_key.created_at.to_date.to_fs(:db)}
+      recent api key description - #{admin.username} - #{recent_api_key.created_at.to_date.to_fs(:db)}
 
       No action is required at this time, however, it is considered good security practice to cycle all your important credentials every few years.
     TEXT
diff --git a/spec/jobs/process_post_spec.rb b/spec/jobs/process_post_spec.rb
index 6d5d65bb791..fc20d8f5083 100644
--- a/spec/jobs/process_post_spec.rb
+++ b/spec/jobs/process_post_spec.rb
@@ -1,13 +1,11 @@
 # frozen_string_literal: true
 
 describe Jobs::ProcessPost do
-
   it "returns when the post cannot be found" do
     expect { Jobs::ProcessPost.new.perform(post_id: 1, sync_exec: true) }.not_to raise_error
   end
 
   context 'with a post' do
-
     fab!(:post) { Fabricate(:post) }
 
     it 'does not erase posts when CookedPostProcessor malfunctions' do
@@ -90,5 +88,4 @@ describe Jobs::ProcessPost do
       expect(post.topic.reload.excerpt).to eq("Some OP content")
     end
   end
-
 end
diff --git a/spec/jobs/pull_hotlinked_images_spec.rb b/spec/jobs/pull_hotlinked_images_spec.rb
index 09fca0e6ab3..416d9a80ab4 100644
--- a/spec/jobs/pull_hotlinked_images_spec.rb
+++ b/spec/jobs/pull_hotlinked_images_spec.rb
@@ -1,7 +1,6 @@
 # frozen_string_literal: true
 
 describe Jobs::PullHotlinkedImages do
-
   let(:image_url) { "http://wiki.mozilla.org/images/2/2e/Longcat1.png" }
   let(:broken_image_url) { "http://wiki.mozilla.org/images/2/2e/Longcat2.png" }
   let(:large_image_url) { "http://wiki.mozilla.org/images/2/2e/Longcat3.png" }
diff --git a/spec/jobs/send_system_message_spec.rb b/spec/jobs/send_system_message_spec.rb
index db6540eee5e..5497e78dbfa 100644
--- a/spec/jobs/send_system_message_spec.rb
+++ b/spec/jobs/send_system_message_spec.rb
@@ -1,7 +1,6 @@
 # frozen_string_literal: true
 
 describe Jobs::SendSystemMessage do
-
   it "raises an error without a user_id" do
     expect { Jobs::SendSystemMessage.new.execute(message_type: 'welcome_invite') }.to raise_error(Discourse::InvalidParameters)
   end
@@ -11,7 +10,6 @@ describe Jobs::SendSystemMessage do
   end
 
   context 'with valid parameters' do
-
     fab!(:user) { Fabricate(:user) }
 
     it "should call SystemMessage.create" do
@@ -24,7 +22,5 @@ describe Jobs::SendSystemMessage do
       SystemMessage.any_instance.expects(:create).with('post_hidden', options)
       Jobs::SendSystemMessage.new.execute(user_id: user.id, message_type: 'post_hidden', message_options: options)
     end
-
   end
-
 end
diff --git a/spec/lib/guardian_spec.rb b/spec/lib/guardian_spec.rb
index 4eb53beb696..57a464fc193 100644
--- a/spec/lib/guardian_spec.rb
+++ b/spec/lib/guardian_spec.rb
@@ -1,7 +1,5 @@
 # frozen_string_literal: true
 
-require 'guardian'
-
 describe Guardian do
 
   fab!(:user) { Fabricate(:user) }
diff --git a/spec/lib/hijack_spec.rb b/spec/lib/hijack_spec.rb
index 43bb9814c92..beb8b8622ab 100644
--- a/spec/lib/hijack_spec.rb
+++ b/spec/lib/hijack_spec.rb
@@ -193,7 +193,7 @@ describe Hijack do
     Process.stubs(:clock_gettime).returns(1.0)
     tester.hijack_test do
       Process.stubs(:clock_gettime).returns(2.0)
-      redirect_to 'http://awesome.com'
+      redirect_to 'http://awesome.com', allow_other_host: true
     end
 
     result = "HTTP/1.1 302 Found\r\nLocation: http://awesome.com\r\nContent-Type: text/html; charset=utf-8\r\nContent-Length: 84\r\nConnection: close\r\nX-Runtime: 1.000000\r\n\r\n<html><body>You are being <a href=\"http://awesome.com\">redirected</a>.</body></html>"
diff --git a/spec/lib/i18n/i18n_interpolation_keys_finder_spec.rb b/spec/lib/i18n/i18n_interpolation_keys_finder_spec.rb
index a19c70cc631..2fd987bee52 100644
--- a/spec/lib/i18n/i18n_interpolation_keys_finder_spec.rb
+++ b/spec/lib/i18n/i18n_interpolation_keys_finder_spec.rb
@@ -1,7 +1,5 @@
 # frozen_string_literal: true
 
-require "i18n/i18n_interpolation_keys_finder"
-
 RSpec.describe I18nInterpolationKeysFinder do
   describe '#find' do
     it 'should return the right keys' do
diff --git a/spec/lib/onpdiff_spec.rb b/spec/lib/onpdiff_spec.rb
index d025b843590..182e0ee5dfe 100644
--- a/spec/lib/onpdiff_spec.rb
+++ b/spec/lib/onpdiff_spec.rb
@@ -1,7 +1,5 @@
 # frozen_string_literal: true
 
-require 'onpdiff'
-
 describe ONPDiff do
 
   describe "diff" do
diff --git a/spec/lib/stylesheet/compiler_spec.rb b/spec/lib/stylesheet/compiler_spec.rb
index 609c319baea..ef1a3b5c754 100644
--- a/spec/lib/stylesheet/compiler_spec.rb
+++ b/spec/lib/stylesheet/compiler_spec.rb
@@ -74,20 +74,6 @@ describe Stylesheet::Compiler do
     end
   end
 
-  it "supports asset-url" do
-    css, _map = Stylesheet::Compiler.compile(".body{background-image: asset-url('/images/favicons/github.png');}", "test.scss")
-
-    expect(css).to include("url('/images/favicons/github.png')")
-    expect(css).not_to include('asset-url')
-  end
-
-  it "supports image-url" do
-    css, _map = Stylesheet::Compiler.compile(".body{background-image: image-url('/favicons/github.png');}", "test.scss")
-
-    expect(css).to include("url('/favicons/github.png')")
-    expect(css).not_to include('image-url')
-  end
-
   it "supports absolute-image-url" do
     scss = Stylesheet::Importer.new({}).prepended_scss
     scss += ".body{background-image: absolute-image-url('/favicons/github.png');}"
diff --git a/spec/lib/validators/category_search_priority_weights_validator_spec.rb b/spec/lib/validators/category_search_priority_weights_validator_spec.rb
index a809635510b..86c081ff134 100644
--- a/spec/lib/validators/category_search_priority_weights_validator_spec.rb
+++ b/spec/lib/validators/category_search_priority_weights_validator_spec.rb
@@ -1,7 +1,5 @@
 # frozen_string_literal: true
 
-require 'validators/category_search_priority_weights_validator'
-
 RSpec.describe CategorySearchPriorityWeightsValidator do
   it "should validate the results correctly" do
     [1, 1.1].each do |value|
diff --git a/spec/lib/validators/max_emojis_validator_spec.rb b/spec/lib/validators/max_emojis_validator_spec.rb
index 8a5e90917e5..b134e72257d 100644
--- a/spec/lib/validators/max_emojis_validator_spec.rb
+++ b/spec/lib/validators/max_emojis_validator_spec.rb
@@ -1,8 +1,6 @@
 # encoding: UTF-8
 # frozen_string_literal: true
 
-require 'validators/max_emojis_validator'
-
 describe MaxEmojisValidator do
 
   # simulate Rails behavior (singleton)
diff --git a/spec/lib/validators/quality_title_validator_spec.rb b/spec/lib/validators/quality_title_validator_spec.rb
index 2403490515c..42f6d40cf11 100644
--- a/spec/lib/validators/quality_title_validator_spec.rb
+++ b/spec/lib/validators/quality_title_validator_spec.rb
@@ -1,7 +1,6 @@
 # encoding: UTF-8
 # frozen_string_literal: true
 
-require 'validators/quality_title_validator'
 require 'ostruct'
 
 module QualityTitleValidatorSpec
diff --git a/spec/lib/validators/topic_title_length_validator_spec.rb b/spec/lib/validators/topic_title_length_validator_spec.rb
index 47745ca6481..04ddbf4a1eb 100644
--- a/spec/lib/validators/topic_title_length_validator_spec.rb
+++ b/spec/lib/validators/topic_title_length_validator_spec.rb
@@ -1,8 +1,6 @@
 # encoding: UTF-8
 # frozen_string_literal: true
 
-require 'validators/topic_title_length_validator'
-
 describe TopicTitleLengthValidator do
 
   # simulate Rails behavior (singleton)
diff --git a/spec/lib/validators/url_validator_spec.rb b/spec/lib/validators/url_validator_spec.rb
index adf1a8337dd..146b168f352 100644
--- a/spec/lib/validators/url_validator_spec.rb
+++ b/spec/lib/validators/url_validator_spec.rb
@@ -1,7 +1,5 @@
 # frozen_string_literal: true
 
-require 'validators/topic_title_length_validator'
-
 RSpec.describe UrlValidator do
   let(:record) { Fabricate.build(:user_profile, user: Fabricate.build(:user)) }
   let(:validator) { described_class.new(attributes: :website) }
diff --git a/spec/models/given_daily_like_spec.rb b/spec/models/given_daily_like_spec.rb
index cf0b6863b2f..3ba850d6329 100644
--- a/spec/models/given_daily_like_spec.rb
+++ b/spec/models/given_daily_like_spec.rb
@@ -3,8 +3,8 @@
 describe GivenDailyLike do
 
   it 'no errors without a user' do
-    expect(-> { GivenDailyLike.increment_for(nil) }).not_to raise_error
-    expect(-> { GivenDailyLike.decrement_for(nil) }).not_to raise_error
+    expect { GivenDailyLike.increment_for(nil) }.not_to raise_error
+    expect { GivenDailyLike.decrement_for(nil) }.not_to raise_error
   end
 
   context 'with a user' do
diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb
index 1b633bd3117..2c408aa03cd 100644
--- a/spec/rails_helper.rb
+++ b/spec/rails_helper.rb
@@ -487,7 +487,7 @@ end
 
 def decrypt_auth_cookie(cookie)
   request = ActionDispatch::Request.new(create_request_env.merge("HTTP_COOKIE" => "_t=#{cookie}"))
-  request.cookie_jar.encrypted["_t"]
+  request.cookie_jar.encrypted["_t"].with_indifferent_access
 end
 
 class SpecSecureRandom
diff --git a/spec/requests/invites_controller_spec.rb b/spec/requests/invites_controller_spec.rb
index 3a67d6f2c23..dd5e7d29eba 100644
--- a/spec/requests/invites_controller_spec.rb
+++ b/spec/requests/invites_controller_spec.rb
@@ -22,13 +22,22 @@ describe InvitesController do
       end
     end
 
-    it 'shows unobfuscated email if email data is present in authentication data' do
-      ActionDispatch::Request.any_instance.stubs(:session).returns(authentication: { email: invite.email })
-      get "/invites/#{invite.invite_key}"
-      expect(response.status).to eq(200)
-      expect(response.body).to have_tag(:script, with: { src: "/assets/#{EmberCli.transform_name("application")}.js" })
-      expect(response.body).to include(invite.email)
-      expect(response.body).not_to include('i*****g@a***********e.ooo')
+    context 'when email data is present in authentication data' do
+      let(:store) { ActionDispatch::Session::CookieStore.new({}) }
+      let(:session_stub) { ActionDispatch::Request::Session.create(store, ActionDispatch::TestRequest.create, {}) }
+
+      before do
+        session_stub[:authentication] = { email: invite.email }
+        ActionDispatch::Request.any_instance.stubs(:session).returns(session_stub)
+      end
+
+      it 'shows unobfuscated email' do
+        get "/invites/#{invite.invite_key}"
+        expect(response.status).to eq(200)
+        expect(response.body).to have_tag(:script, with: { src: "/assets/#{EmberCli.transform_name("application")}.js" })
+        expect(response.body).to include(invite.email)
+        expect(response.body).not_to include('i*****g@a***********e.ooo')
+      end
     end
 
     it 'shows default user fields' do
diff --git a/spec/services/email_settings_exception_handler_spec.rb b/spec/services/email_settings_exception_handler_spec.rb
index b7d16d5e99a..0d525d0a8a5 100644
--- a/spec/services/email_settings_exception_handler_spec.rb
+++ b/spec/services/email_settings_exception_handler_spec.rb
@@ -38,7 +38,7 @@ RSpec.describe EmailSettingsExceptionHandler do
     end
 
     it "formats a Net::SMTPAuthenticationError with application-specific password Gmail error" do
-      exception = Net::SMTPAuthenticationError.new("Application-specific password required")
+      exception = Net::SMTPAuthenticationError.new(nil, message: "Application-specific password required")
       expect(subject.class.friendly_exception_message(exception, "smtp.gmail.com")).to eq(
         I18n.t("email_settings.authentication_error_gmail_app_password")
       )
@@ -52,15 +52,15 @@ RSpec.describe EmailSettingsExceptionHandler do
     end
 
     it "formats a Net::SMTPSyntaxError, Net::SMTPFatalError, and Net::SMTPUnknownError" do
-      exception = Net::SMTPSyntaxError.new("bad syntax")
+      exception = Net::SMTPSyntaxError.new(nil, message: "bad syntax")
       expect(subject.class.friendly_exception_message(exception, "smtp.test.com")).to eq(
         I18n.t("email_settings.smtp_unhandled_error", message: exception.message)
       )
-      exception = Net::SMTPFatalError.new("fatal")
+      exception = Net::SMTPFatalError.new(nil, message: "fatal")
       expect(subject.class.friendly_exception_message(exception, "smtp.test.com")).to eq(
         I18n.t("email_settings.smtp_unhandled_error", message: exception.message)
       )
-      exception = Net::SMTPUnknownError.new("unknown")
+      exception = Net::SMTPUnknownError.new(nil, message: "unknown")
       expect(subject.class.friendly_exception_message(exception, "smtp.test.com")).to eq(
         I18n.t("email_settings.smtp_unhandled_error", message: exception.message)
       )
diff --git a/spec/services/email_settings_validator_spec.rb b/spec/services/email_settings_validator_spec.rb
index fee23271ff8..8abde6d3662 100644
--- a/spec/services/email_settings_validator_spec.rb
+++ b/spec/services/email_settings_validator_spec.rb
@@ -112,7 +112,7 @@ RSpec.describe EmailSettingsValidator do
 
     it "logs a warning if debug: true passed in and still raises the error" do
       Rails.logger.expects(:warn).with(regexp_matches(/\[EmailSettingsValidator\] Error encountered/)).at_least_once
-      net_smtp_stub.stubs(:start).raises(Net::SMTPAuthenticationError, "invalid credentials")
+      net_smtp_stub.stubs(:start).raises(Net::SMTPAuthenticationError, stub(message: "invalid credentials"))
       expect { subject.class.validate_smtp(host: host, port: port, username: username, password: password, debug: true, domain: domain) }.to raise_error(Net::SMTPAuthenticationError)
     end
 
diff --git a/spec/views/omniauth_callbacks/failure.html.erb_spec.rb b/spec/views/omniauth_callbacks/failure.html.erb_spec.rb
index 157d4aa4fff..96a5a01a7ea 100644
--- a/spec/views/omniauth_callbacks/failure.html.erb_spec.rb
+++ b/spec/views/omniauth_callbacks/failure.html.erb_spec.rb
@@ -1,12 +1,13 @@
 # frozen_string_literal: true
 
 describe "users/omniauth_callbacks/failure.html.erb" do
-
-  it "renders the failure page" do
+  before do
     flash[:error] = I18n.t("login.omniauth_error", strategy: 'test')
-      render
-
-      expect(rendered.match(I18n.t("login.omniauth_error.generic", strategy: 'test'))).not_to eq(nil)
   end
 
+  it "renders the failure page" do
+    render template: 'users/omniauth_callbacks/failure'
+
+    expect(rendered).to match I18n.t("login.omniauth_error.generic", strategy: 'test')
+  end
 end