about summary refs log tree commit diff
diff options
context:
space:
mode:
authorEugen Rochko <eugen@zeonfederated.com>2018-05-02 18:58:48 +0200
committerGitHub <noreply@github.com>2018-05-02 18:58:48 +0200
commitcb5b5cb5f79bb2187d8124df91af4c8e1bfd7256 (patch)
tree76f728b51beda1992b14e3bc83f6a67fdd77dce3
parent71a7cea73fdfb45d06986e108b2ce1dbf7e32579 (diff)
Slightly reduce RAM usage (#7301)
* No need to re-require sidekiq plugins, they are required via Gemfile

* Add derailed_benchmarks tool, no need to require TTY gems in Gemfile

* Replace ruby-oembed with FetchOEmbedService

Reduce startup by 45382 allocated objects

* Remove preloaded JSON-LD in favour of caching HTTP responses

Reduce boot RAM by about 6 MiB

* Fix tests

* Fix test suite by stubbing out JSON-LD contexts
-rw-r--r--Gemfile12
-rw-r--r--Gemfile.lock22
-rw-r--r--app/controllers/api/web/embeds_controller.rb11
-rw-r--r--app/controllers/settings/follower_domains_controller.rb2
-rw-r--r--app/helpers/jsonld_helper.rb17
-rw-r--r--app/lib/provider_discovery.rb47
-rw-r--r--app/services/fan_out_on_write_service.rb2
-rw-r--r--app/services/fetch_link_card_service.rb38
-rw-r--r--app/services/fetch_oembed_service.rb71
-rw-r--r--app/workers/scheduler/backup_cleanup_scheduler.rb1
-rw-r--r--app/workers/scheduler/doorkeeper_cleanup_scheduler.rb1
-rw-r--r--app/workers/scheduler/email_scheduler.rb1
-rw-r--r--app/workers/scheduler/feed_cleanup_scheduler.rb1
-rw-r--r--app/workers/scheduler/ip_cleanup_scheduler.rb1
-rw-r--r--app/workers/scheduler/media_cleanup_scheduler.rb1
-rw-r--r--app/workers/scheduler/subscriptions_cleanup_scheduler.rb2
-rw-r--r--app/workers/scheduler/subscriptions_scheduler.rb3
-rw-r--r--app/workers/scheduler/user_cleanup_scheduler.rb1
-rw-r--r--app/workers/soft_block_domain_followers_worker.rb2
-rw-r--r--config/initializers/json_ld.rb5
-rw-r--r--config/initializers/oembed.rb4
-rw-r--r--lib/json_ld/activitystreams.rb153
-rw-r--r--lib/json_ld/identity.rb86
-rw-r--r--lib/json_ld/security.rb50
-rw-r--r--lib/tasks/mastodon.rake2
-rw-r--r--spec/fixtures/requests/json-ld.activitystreams.txt391
-rw-r--r--spec/fixtures/requests/json-ld.identity.txt100
-rw-r--r--spec/fixtures/requests/json-ld.security.txt61
-rw-r--r--spec/lib/activitypub/linked_data_signature_spec.rb4
-rw-r--r--spec/rails_helper.rb14
-rw-r--r--spec/services/account_search_service_spec.rb2
-rw-r--r--spec/services/activitypub/fetch_remote_account_service_spec.rb2
-rw-r--r--spec/services/activitypub/fetch_remote_status_service_spec.rb2
-rw-r--r--spec/services/activitypub/process_account_service_spec.rb2
-rw-r--r--spec/services/activitypub/process_collection_service_spec.rb2
-rw-r--r--spec/services/after_block_service_spec.rb2
-rw-r--r--spec/services/authorize_follow_service_spec.rb2
-rw-r--r--spec/services/batched_remove_status_service_spec.rb2
-rw-r--r--spec/services/block_domain_from_account_service_spec.rb2
-rw-r--r--spec/services/block_domain_service_spec.rb2
-rw-r--r--spec/services/block_service_spec.rb2
-rw-r--r--spec/services/bootstrap_timeline_service_spec.rb2
-rw-r--r--spec/services/fan_out_on_write_service_spec.rb2
-rw-r--r--spec/services/favourite_service_spec.rb2
-rw-r--r--spec/services/fetch_atom_service_spec.rb2
-rw-r--r--spec/services/fetch_link_card_service_spec.rb2
-rw-r--r--spec/services/fetch_oembed_service_spec.rb (renamed from spec/lib/provider_discovery_spec.rb)49
-rw-r--r--spec/services/fetch_remote_account_service_spec.rb2
-rw-r--r--spec/services/fetch_remote_status_service_spec.rb2
-rw-r--r--spec/services/follow_service_spec.rb2
-rw-r--r--spec/services/mute_service_spec.rb2
-rw-r--r--spec/services/notify_service_spec.rb2
-rw-r--r--spec/services/post_status_service_spec.rb2
-rw-r--r--spec/services/precompute_feed_service_spec.rb2
-rw-r--r--spec/services/process_feed_service_spec.rb2
-rw-r--r--spec/services/process_interaction_service_spec.rb2
-rw-r--r--spec/services/process_mentions_service_spec.rb2
-rw-r--r--spec/services/pubsubhubbub/subscribe_service_spec.rb2
-rw-r--r--spec/services/pubsubhubbub/unsubscribe_service_spec.rb2
-rw-r--r--spec/services/reblog_service_spec.rb2
-rw-r--r--spec/services/reject_follow_service_spec.rb2
-rw-r--r--spec/services/remove_status_service_spec.rb2
-rw-r--r--spec/services/report_service_spec.rb2
-rw-r--r--spec/services/resolve_account_service_spec.rb2
-rw-r--r--spec/services/resolve_url_service_spec.rb2
-rw-r--r--spec/services/search_service_spec.rb2
-rw-r--r--spec/services/send_interaction_service_spec.rb2
-rw-r--r--spec/services/subscribe_service_spec.rb2
-rw-r--r--spec/services/suspend_account_service_spec.rb2
-rw-r--r--spec/services/unblock_domain_service_spec.rb2
-rw-r--r--spec/services/unblock_service_spec.rb2
-rw-r--r--spec/services/unfollow_service_spec.rb2
-rw-r--r--spec/services/unmute_service_spec.rb2
-rw-r--r--spec/services/unsubscribe_service_spec.rb2
-rw-r--r--spec/services/update_remote_profile_service_spec.rb2
-rw-r--r--spec/spec_helper.rb12
76 files changed, 784 insertions, 471 deletions
diff --git a/Gemfile b/Gemfile
index a33748568..f1665ce95 100644
--- a/Gemfile
+++ b/Gemfile
@@ -54,7 +54,7 @@ gem 'httplog', '~> 1.0'
 gem 'idn-ruby', require: 'idn'
 gem 'kaminari', '~> 1.1'
 gem 'link_header', '~> 0.0'
-gem 'mime-types', '~> 3.1'
+gem 'mime-types', '~> 3.1', require: 'mime/types/columnar'
 gem 'nokogiri', '~> 1.8'
 gem 'nsa', '~> 0.2'
 gem 'oj', '~> 3.5'
@@ -70,7 +70,6 @@ gem 'rails-settings-cached', '~> 0.6'
 gem 'redis', '~> 4.0', require: ['redis', 'redis/connection/hiredis']
 gem 'mario-redis-lock', '~> 1.2', require: 'redis_lock'
 gem 'rqrcode', '~> 0.10'
-gem 'ruby-oembed', '~> 0.12', require: 'oembed'
 gem 'ruby-progressbar', '~> 1.4'
 gem 'sanitize', '~> 4.6'
 gem 'sidekiq', '~> 5.1'
@@ -82,14 +81,14 @@ gem 'simple_form', '~> 4.0'
 gem 'sprockets-rails', '~> 3.2', require: 'sprockets/railtie'
 gem 'stoplight', '~> 2.1.3'
 gem 'strong_migrations', '~> 0.2'
-gem 'tty-command', '~> 0.8'
-gem 'tty-prompt', '~> 0.16'
+gem 'tty-command', '~> 0.8', require: false
+gem 'tty-prompt', '~> 0.16', require: false
 gem 'twitter-text', '~> 1.14'
 gem 'tzinfo-data', '~> 1.2018'
 gem 'webpacker', '~> 3.4'
 gem 'webpush'
 
-gem 'json-ld-preloaded', '~> 2.2'
+gem 'json-ld', '~> 2.2'
 gem 'rdf-normalize', '~> 0.3'
 
 group :development, :test do
@@ -135,6 +134,9 @@ group :development do
   gem 'capistrano-rails', '~> 1.3'
   gem 'capistrano-rbenv', '~> 2.1'
   gem 'capistrano-yarn', '~> 2.0'
+
+  gem 'derailed_benchmarks'
+  gem 'stackprof'
 end
 
 group :production do
diff --git a/Gemfile.lock b/Gemfile.lock
index d96165dcf..94ab0b7ca 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -75,6 +75,7 @@ GEM
       aws-sigv4 (~> 1.0)
     aws-sigv4 (1.0.2)
     bcrypt (3.1.11)
+    benchmark-ips (2.7.2)
     better_errors (2.4.0)
       coderay (>= 1.0.0)
       erubi (>= 1.0.0)
@@ -138,6 +139,14 @@ GEM
     css_parser (1.6.0)
       addressable
     debug_inspector (0.0.3)
+    derailed_benchmarks (1.3.4)
+      benchmark-ips (~> 2)
+      get_process_mem (~> 0)
+      heapy (~> 0)
+      memory_profiler (~> 0)
+      rack (>= 1)
+      rake (> 10, < 13)
+      thor (~> 0.19)
     devise (4.4.3)
       bcrypt (~> 3.0)
       orm_adapter (~> 0.1)
@@ -206,6 +215,7 @@ GEM
     fuubar (2.3.1)
       rspec-core (~> 3.0)
       ruby-progressbar (~> 1.4)
+    get_process_mem (0.2.1)
     globalid (0.4.1)
       activesupport (>= 4.2.0)
     goldfinger (2.1.0)
@@ -226,6 +236,7 @@ GEM
       concurrent-ruby (~> 1.0)
     hashdiff (0.3.7)
     hashie (3.5.7)
+    heapy (0.1.3)
     highline (1.7.10)
     hiredis (0.6.1)
     hitimes (1.2.6)
@@ -264,10 +275,6 @@ GEM
     json-ld (2.2.1)
       multi_json (~> 1.12)
       rdf (>= 2.2.8, < 4.0)
-    json-ld-preloaded (2.2.3)
-      json-ld (>= 2.2, < 4.0)
-      multi_json (~> 1.12)
-      rdf (>= 2.2, < 4.0)
     jsonapi-renderer (0.2.0)
     jwt (2.1.0)
     kaminari (1.1.1)
@@ -502,7 +509,6 @@ GEM
       rainbow (>= 2.2.2, < 4.0)
       ruby-progressbar (~> 1.7)
       unicode-display_width (~> 1.0, >= 1.0.1)
-    ruby-oembed (0.12.0)
     ruby-progressbar (1.9.0)
     ruby-saml (1.7.2)
       nokogiri (>= 1.5.10)
@@ -557,6 +563,7 @@ GEM
     sshkit (1.16.0)
       net-scp (>= 1.1.2)
       net-ssh (>= 2.8.0)
+    stackprof (0.2.11)
     statsd-ruby (1.2.1)
     stoplight (2.1.3)
     streamio-ffmpeg (3.0.2)
@@ -645,6 +652,7 @@ DEPENDENCIES
   chewy (~> 5.0)
   cld3 (~> 3.2.0)
   climate_control (~> 0.2)
+  derailed_benchmarks
   devise (~> 4.4)
   devise-two-factor (~> 3.0)
   devise_pam_authenticatable2 (~> 9.1)
@@ -668,7 +676,7 @@ DEPENDENCIES
   i18n-tasks (~> 0.9)
   idn-ruby
   iso-639
-  json-ld-preloaded (~> 2.2)
+  json-ld (~> 2.2)
   kaminari (~> 1.1)
   letter_opener (~> 1.4)
   letter_opener_web (~> 1.3)
@@ -714,7 +722,6 @@ DEPENDENCIES
   rspec-retry (~> 0.5)
   rspec-sidekiq (~> 3.0)
   rubocop (~> 0.55)
-  ruby-oembed (~> 0.12)
   ruby-progressbar (~> 1.4)
   sanitize (~> 4.6)
   scss_lint (~> 0.57)
@@ -726,6 +733,7 @@ DEPENDENCIES
   simple_form (~> 4.0)
   simplecov (~> 0.16)
   sprockets-rails (~> 3.2)
+  stackprof
   stoplight (~> 2.1.3)
   streamio-ffmpeg (~> 3.0)
   strong_migrations (~> 0.2)
diff --git a/app/controllers/api/web/embeds_controller.rb b/app/controllers/api/web/embeds_controller.rb
index f2fe74b17..987290a14 100644
--- a/app/controllers/api/web/embeds_controller.rb
+++ b/app/controllers/api/web/embeds_controller.rb
@@ -9,9 +9,12 @@ class Api::Web::EmbedsController < Api::Web::BaseController
     status = StatusFinder.new(params[:url]).status
     render json: status, serializer: OEmbedSerializer, width: 400
   rescue ActiveRecord::RecordNotFound
-    oembed = OEmbed::Providers.get(params[:url])
-    render json: Oj.dump(oembed.fields)
-  rescue OEmbed::NotFound
-    render json: {}, status: :not_found
+    oembed = FetchOEmbedService.new.call(params[:url])
+
+    if oembed
+      render json: oembed
+    else
+      render json: {}, status: :not_found
+    end
   end
 end
diff --git a/app/controllers/settings/follower_domains_controller.rb b/app/controllers/settings/follower_domains_controller.rb
index 213d9e96d..91b521e7f 100644
--- a/app/controllers/settings/follower_domains_controller.rb
+++ b/app/controllers/settings/follower_domains_controller.rb
@@ -1,7 +1,5 @@
 # frozen_string_literal: true
 
-require 'sidekiq-bulk'
-
 class Settings::FollowerDomainsController < ApplicationController
   layout 'admin'
 
diff --git a/app/helpers/jsonld_helper.rb b/app/helpers/jsonld_helper.rb
index a3cfdadb8..e9056166c 100644
--- a/app/helpers/jsonld_helper.rb
+++ b/app/helpers/jsonld_helper.rb
@@ -48,7 +48,7 @@ module JsonLdHelper
   end
 
   def canonicalize(json)
-    graph = RDF::Graph.new << JSON::LD::API.toRdf(json)
+    graph = RDF::Graph.new << JSON::LD::API.toRdf(json, documentLoader: method(:load_jsonld_context))
     graph.dump(:normalize)
   end
 
@@ -90,4 +90,19 @@ module JsonLdHelper
     request.add_headers('Accept' => 'application/activity+json, application/ld+json')
     request
   end
+
+  def load_jsonld_context(url, _options = {}, &_block)
+    json = Rails.cache.fetch("jsonld:context:#{url}", expires_in: 30.days, raw: true) do
+      request = Request.new(:get, url)
+      request.add_headers('Accept' => 'application/ld+json')
+
+      request.perform do |res|
+        raise JSON::LD::JsonLdError::LoadingDocumentFailed unless res.code == 200 && res.mime_type == 'application/ld+json'
+        res.body_with_limit
+      end
+    end
+
+    doc = JSON::LD::API::RemoteDocument.new(url, json)
+    block_given? ? yield(doc) : doc
+  end
 end
diff --git a/app/lib/provider_discovery.rb b/app/lib/provider_discovery.rb
deleted file mode 100644
index 3bec7211b..000000000
--- a/app/lib/provider_discovery.rb
+++ /dev/null
@@ -1,47 +0,0 @@
-# frozen_string_literal: true
-
-class ProviderDiscovery < OEmbed::ProviderDiscovery
-  class << self
-    def get(url, **options)
-      provider = discover_provider(url, options)
-
-      options.delete(:html)
-
-      provider.get(url, options)
-    end
-
-    def discover_provider(url, **options)
-      format = options[:format]
-
-      html = if options[:html]
-               Nokogiri::HTML(options[:html])
-             else
-               Request.new(:get, url).perform do |res|
-                 raise OEmbed::NotFound, url if res.code != 200 || res.mime_type != 'text/html'
-                 Nokogiri::HTML(res.body_with_limit)
-               end
-             end
-
-      if format.nil? || format == :json
-        provider_endpoint ||= html.at_xpath('//link[@type="application/json+oembed"]')&.attribute('href')&.value
-        format ||= :json if provider_endpoint
-      end
-
-      if format.nil? || format == :xml
-        provider_endpoint ||= html.at_xpath('//link[@type="text/xml+oembed"]')&.attribute('href')&.value
-        format ||= :xml if provider_endpoint
-      end
-
-      raise OEmbed::NotFound, url if provider_endpoint.nil?
-      begin
-        provider_endpoint = Addressable::URI.parse(provider_endpoint)
-        provider_endpoint.query = nil
-        provider_endpoint = provider_endpoint.to_s
-      rescue Addressable::URI::InvalidURIError
-        raise OEmbed::NotFound, url
-      end
-
-      OEmbed::Provider.new(provider_endpoint, format)
-    end
-  end
-end
diff --git a/app/services/fan_out_on_write_service.rb b/app/services/fan_out_on_write_service.rb
index 0f77556dc..510b80c82 100644
--- a/app/services/fan_out_on_write_service.rb
+++ b/app/services/fan_out_on_write_service.rb
@@ -1,7 +1,5 @@
 # frozen_string_literal: true
 
-require 'sidekiq-bulk'
-
 class FanOutOnWriteService < BaseService
   # Push a status into home and mentions feeds
   # @param [Status] status
diff --git a/app/services/fetch_link_card_service.rb b/app/services/fetch_link_card_service.rb
index d5920a417..77d4aa538 100644
--- a/app/services/fetch_link_card_service.rb
+++ b/app/services/fetch_link_card_service.rb
@@ -85,42 +85,40 @@ class FetchLinkCardService < BaseService
   end
 
   def attempt_oembed
-    embed = OEmbed::Providers.get(@url, html: @html)
+    embed = FetchOEmbedService.new.call(@url, html: @html)
 
-    return false unless embed.respond_to?(:type)
+    return false if embed.nil?
 
-    @card.type          = embed.type
-    @card.title         = embed.respond_to?(:title)         ? embed.title         : ''
-    @card.author_name   = embed.respond_to?(:author_name)   ? embed.author_name   : ''
-    @card.author_url    = embed.respond_to?(:author_url)    ? embed.author_url    : ''
-    @card.provider_name = embed.respond_to?(:provider_name) ? embed.provider_name : ''
-    @card.provider_url  = embed.respond_to?(:provider_url)  ? embed.provider_url  : ''
+    @card.type          = embed[:type]
+    @card.title         = embed[:title]         || ''
+    @card.author_name   = embed[:author_name]   || ''
+    @card.author_url    = embed[:author_url]    || ''
+    @card.provider_name = embed[:provider_name] || ''
+    @card.provider_url  = embed[:provider_url]  || ''
     @card.width         = 0
     @card.height        = 0
 
     case @card.type
     when 'link'
-      @card.image_remote_url = embed.thumbnail_url if embed.respond_to?(:thumbnail_url)
+      @card.image_remote_url = embed[:thumbnail_url] if embed[:thumbnail_url].present?
     when 'photo'
-      return false unless embed.respond_to?(:url)
+      return false if embed[:url].blank?
 
-      @card.embed_url        = embed.url
-      @card.image_remote_url = embed.url
-      @card.width            = embed.width.presence  || 0
-      @card.height           = embed.height.presence || 0
+      @card.embed_url        = embed[:url]
+      @card.image_remote_url = embed[:url]
+      @card.width            = embed[:width].presence  || 0
+      @card.height           = embed[:height].presence || 0
     when 'video'
-      @card.width            = embed.width.presence  || 0
-      @card.height           = embed.height.presence || 0
-      @card.html             = Formatter.instance.sanitize(embed.html, Sanitize::Config::MASTODON_OEMBED)
-      @card.image_remote_url = embed.thumbnail_url if embed.respond_to?(:thumbnail_url)
+      @card.width            = embed[:width].presence  || 0
+      @card.height           = embed[:height].presence || 0
+      @card.html             = Formatter.instance.sanitize(embed[:html], Sanitize::Config::MASTODON_OEMBED)
+      @card.image_remote_url = embed[:thumbnail_url] if embed[:thumbnail_url].present?
     when 'rich'
       # Most providers rely on <script> tags, which is a no-no
       return false
     end
 
     @card.save_with_optional_image!
-  rescue OEmbed::NotFound
-    false
   end
 
   def attempt_opengraph
diff --git a/app/services/fetch_oembed_service.rb b/app/services/fetch_oembed_service.rb
new file mode 100644
index 000000000..998228517
--- /dev/null
+++ b/app/services/fetch_oembed_service.rb
@@ -0,0 +1,71 @@
+# frozen_string_literal: true
+
+class FetchOEmbedService
+  attr_reader :url, :options, :format, :endpoint_url
+
+  def call(url, options = {})
+    @url     = url
+    @options = options
+
+    discover_endpoint!
+    fetch!
+  end
+
+  private
+
+  def discover_endpoint!
+    return if html.nil?
+
+    @format = @options[:format]
+    page    = Nokogiri::HTML(html)
+
+    if @format.nil? || @format == :json
+      @endpoint_url ||= page.at_xpath('//link[@type="application/json+oembed"]')&.attribute('href')&.value
+      @format       ||= :json if @endpoint_url
+    end
+
+    if @format.nil? || @format == :xml
+      @endpoint_url ||= page.at_xpath('//link[@type="text/xml+oembed"]')&.attribute('href')&.value
+      @format       ||= :xml if @endpoint_url
+    end
+
+    return if @endpoint_url.blank?
+
+    @endpoint_url = Addressable::URI.parse(@endpoint_url).to_s
+  rescue Addressable::URI::InvalidURIError
+    @endpoint_url = nil
+  end
+
+  def fetch!
+    return if @endpoint_url.blank?
+
+    body = Request.new(:get, @endpoint_url).perform do |res|
+      res.code != 200 ? nil : res.body_with_limit
+    end
+
+    validate(parse_for_format(body)) unless body.nil?
+  rescue Oj::ParseError, Ox::ParseError
+    nil
+  end
+
+  def parse_for_format(body)
+    case @format
+    when :json
+      Oj.load(body, mode: :strict)&.with_indifferent_access
+    when :xml
+      Ox.load(body, mode: :hash_no_attrs)&.with_indifferent_access&.dig(:oembed)
+    end
+  end
+
+  def validate(oembed)
+    oembed if oembed[:version] == '1.0' && oembed[:type].present?
+  end
+
+  def html
+    return @html if defined?(@html)
+
+    @html = @options[:html] || Request.new(:get, @url).perform do |res|
+      res.code != 200 || res.mime_type != 'text/html' ? nil : res.body_with_limit
+    end
+  end
+end
diff --git a/app/workers/scheduler/backup_cleanup_scheduler.rb b/app/workers/scheduler/backup_cleanup_scheduler.rb
index 7a9d4f894..5ab16c057 100644
--- a/app/workers/scheduler/backup_cleanup_scheduler.rb
+++ b/app/workers/scheduler/backup_cleanup_scheduler.rb
@@ -1,5 +1,4 @@
 # frozen_string_literal: true
-require 'sidekiq-scheduler'
 
 class Scheduler::BackupCleanupScheduler
   include Sidekiq::Worker
diff --git a/app/workers/scheduler/doorkeeper_cleanup_scheduler.rb b/app/workers/scheduler/doorkeeper_cleanup_scheduler.rb
index 6488798cd..bab4ae886 100644
--- a/app/workers/scheduler/doorkeeper_cleanup_scheduler.rb
+++ b/app/workers/scheduler/doorkeeper_cleanup_scheduler.rb
@@ -1,5 +1,4 @@
 # frozen_string_literal: true
-require 'sidekiq-scheduler'
 
 class Scheduler::DoorkeeperCleanupScheduler
   include Sidekiq::Worker
diff --git a/app/workers/scheduler/email_scheduler.rb b/app/workers/scheduler/email_scheduler.rb
index 24d0c0ebe..36866061b 100644
--- a/app/workers/scheduler/email_scheduler.rb
+++ b/app/workers/scheduler/email_scheduler.rb
@@ -1,5 +1,4 @@
 # frozen_string_literal: true
-require 'sidekiq-scheduler'
 
 class Scheduler::EmailScheduler
   include Sidekiq::Worker
diff --git a/app/workers/scheduler/feed_cleanup_scheduler.rb b/app/workers/scheduler/feed_cleanup_scheduler.rb
index 23fa7672b..42cf14128 100644
--- a/app/workers/scheduler/feed_cleanup_scheduler.rb
+++ b/app/workers/scheduler/feed_cleanup_scheduler.rb
@@ -1,5 +1,4 @@
 # frozen_string_literal: true
-require 'sidekiq-scheduler'
 
 class Scheduler::FeedCleanupScheduler
   include Sidekiq::Worker
diff --git a/app/workers/scheduler/ip_cleanup_scheduler.rb b/app/workers/scheduler/ip_cleanup_scheduler.rb
index a33ca031e..613a5e336 100644
--- a/app/workers/scheduler/ip_cleanup_scheduler.rb
+++ b/app/workers/scheduler/ip_cleanup_scheduler.rb
@@ -1,5 +1,4 @@
 # frozen_string_literal: true
-require 'sidekiq-scheduler'
 
 class Scheduler::IpCleanupScheduler
   include Sidekiq::Worker
diff --git a/app/workers/scheduler/media_cleanup_scheduler.rb b/app/workers/scheduler/media_cleanup_scheduler.rb
index ce32ce314..c35686fcb 100644
--- a/app/workers/scheduler/media_cleanup_scheduler.rb
+++ b/app/workers/scheduler/media_cleanup_scheduler.rb
@@ -1,5 +1,4 @@
 # frozen_string_literal: true
-require 'sidekiq-scheduler'
 
 class Scheduler::MediaCleanupScheduler
   include Sidekiq::Worker
diff --git a/app/workers/scheduler/subscriptions_cleanup_scheduler.rb b/app/workers/scheduler/subscriptions_cleanup_scheduler.rb
index 3b9211e81..af2ae3120 100644
--- a/app/workers/scheduler/subscriptions_cleanup_scheduler.rb
+++ b/app/workers/scheduler/subscriptions_cleanup_scheduler.rb
@@ -1,7 +1,5 @@
 # frozen_string_literal: true
 
-require 'sidekiq-scheduler'
-
 class Scheduler::SubscriptionsCleanupScheduler
   include Sidekiq::Worker
 
diff --git a/app/workers/scheduler/subscriptions_scheduler.rb b/app/workers/scheduler/subscriptions_scheduler.rb
index 469a3d2a6..dc16e85c2 100644
--- a/app/workers/scheduler/subscriptions_scheduler.rb
+++ b/app/workers/scheduler/subscriptions_scheduler.rb
@@ -1,8 +1,5 @@
 # frozen_string_literal: true
 
-require 'sidekiq-scheduler'
-require 'sidekiq-bulk'
-
 class Scheduler::SubscriptionsScheduler
   include Sidekiq::Worker
 
diff --git a/app/workers/scheduler/user_cleanup_scheduler.rb b/app/workers/scheduler/user_cleanup_scheduler.rb
index a8f8fbd83..245536cea 100644
--- a/app/workers/scheduler/user_cleanup_scheduler.rb
+++ b/app/workers/scheduler/user_cleanup_scheduler.rb
@@ -1,5 +1,4 @@
 # frozen_string_literal: true
-require 'sidekiq-scheduler'
 
 class Scheduler::UserCleanupScheduler
   include Sidekiq::Worker
diff --git a/app/workers/soft_block_domain_followers_worker.rb b/app/workers/soft_block_domain_followers_worker.rb
index ce76683c5..85445c7fb 100644
--- a/app/workers/soft_block_domain_followers_worker.rb
+++ b/app/workers/soft_block_domain_followers_worker.rb
@@ -1,7 +1,5 @@
 # frozen_string_literal: true
 
-require 'sidekiq-bulk'
-
 class SoftBlockDomainFollowersWorker
   include Sidekiq::Worker
 
diff --git a/config/initializers/json_ld.rb b/config/initializers/json_ld.rb
deleted file mode 100644
index 2ddc7352d..000000000
--- a/config/initializers/json_ld.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-# frozen_string_literal: true
-
-require_relative '../../lib/json_ld/identity'
-require_relative '../../lib/json_ld/security'
-require_relative '../../lib/json_ld/activitystreams'
diff --git a/config/initializers/oembed.rb b/config/initializers/oembed.rb
deleted file mode 100644
index 208e586cb..000000000
--- a/config/initializers/oembed.rb
+++ /dev/null
@@ -1,4 +0,0 @@
-# frozen_string_literal: true
-
-require_relative '../../app/lib/provider_discovery'
-OEmbed::Providers.register_fallback(ProviderDiscovery)
diff --git a/lib/json_ld/activitystreams.rb b/lib/json_ld/activitystreams.rb
deleted file mode 100644
index ce740f93b..000000000
--- a/lib/json_ld/activitystreams.rb
+++ /dev/null
@@ -1,153 +0,0 @@
-# -*- encoding: utf-8 -*-
-# frozen_string_literal: true
-# This file generated automatically from https://www.w3.org/ns/activitystreams
-require 'json/ld'
-class JSON::LD::Context
-  add_preloaded("https://www.w3.org/ns/activitystreams") do
-    new(vocab: "_:", processingMode: "json-ld-1.0", term_definitions: {
-      "Accept" => TermDefinition.new("Accept", id: "https://www.w3.org/ns/activitystreams#Accept", simple: true),
-      "Activity" => TermDefinition.new("Activity", id: "https://www.w3.org/ns/activitystreams#Activity", simple: true),
-      "Add" => TermDefinition.new("Add", id: "https://www.w3.org/ns/activitystreams#Add", simple: true),
-      "Announce" => TermDefinition.new("Announce", id: "https://www.w3.org/ns/activitystreams#Announce", simple: true),
-      "Application" => TermDefinition.new("Application", id: "https://www.w3.org/ns/activitystreams#Application", simple: true),
-      "Arrive" => TermDefinition.new("Arrive", id: "https://www.w3.org/ns/activitystreams#Arrive", simple: true),
-      "Article" => TermDefinition.new("Article", id: "https://www.w3.org/ns/activitystreams#Article", simple: true),
-      "Audio" => TermDefinition.new("Audio", id: "https://www.w3.org/ns/activitystreams#Audio", simple: true),
-      "Block" => TermDefinition.new("Block", id: "https://www.w3.org/ns/activitystreams#Block", simple: true),
-      "Collection" => TermDefinition.new("Collection", id: "https://www.w3.org/ns/activitystreams#Collection", simple: true),
-      "CollectionPage" => TermDefinition.new("CollectionPage", id: "https://www.w3.org/ns/activitystreams#CollectionPage", simple: true),
-      "Create" => TermDefinition.new("Create", id: "https://www.w3.org/ns/activitystreams#Create", simple: true),
-      "Delete" => TermDefinition.new("Delete", id: "https://www.w3.org/ns/activitystreams#Delete", simple: true),
-      "Dislike" => TermDefinition.new("Dislike", id: "https://www.w3.org/ns/activitystreams#Dislike", simple: true),
-      "Document" => TermDefinition.new("Document", id: "https://www.w3.org/ns/activitystreams#Document", simple: true),
-      "Event" => TermDefinition.new("Event", id: "https://www.w3.org/ns/activitystreams#Event", simple: true),
-      "Flag" => TermDefinition.new("Flag", id: "https://www.w3.org/ns/activitystreams#Flag", simple: true),
-      "Follow" => TermDefinition.new("Follow", id: "https://www.w3.org/ns/activitystreams#Follow", simple: true),
-      "Group" => TermDefinition.new("Group", id: "https://www.w3.org/ns/activitystreams#Group", simple: true),
-      "Ignore" => TermDefinition.new("Ignore", id: "https://www.w3.org/ns/activitystreams#Ignore", simple: true),
-      "Image" => TermDefinition.new("Image", id: "https://www.w3.org/ns/activitystreams#Image", simple: true),
-      "IntransitiveActivity" => TermDefinition.new("IntransitiveActivity", id: "https://www.w3.org/ns/activitystreams#IntransitiveActivity", simple: true),
-      "Invite" => TermDefinition.new("Invite", id: "https://www.w3.org/ns/activitystreams#Invite", simple: true),
-      "IsContact" => TermDefinition.new("IsContact", id: "https://www.w3.org/ns/activitystreams#IsContact", simple: true),
-      "IsFollowedBy" => TermDefinition.new("IsFollowedBy", id: "https://www.w3.org/ns/activitystreams#IsFollowedBy", simple: true),
-      "IsFollowing" => TermDefinition.new("IsFollowing", id: "https://www.w3.org/ns/activitystreams#IsFollowing", simple: true),
-      "IsMember" => TermDefinition.new("IsMember", id: "https://www.w3.org/ns/activitystreams#IsMember", simple: true),
-      "Join" => TermDefinition.new("Join", id: "https://www.w3.org/ns/activitystreams#Join", simple: true),
-      "Leave" => TermDefinition.new("Leave", id: "https://www.w3.org/ns/activitystreams#Leave", simple: true),
-      "Like" => TermDefinition.new("Like", id: "https://www.w3.org/ns/activitystreams#Like", simple: true),
-      "Link" => TermDefinition.new("Link", id: "https://www.w3.org/ns/activitystreams#Link", simple: true),
-      "Listen" => TermDefinition.new("Listen", id: "https://www.w3.org/ns/activitystreams#Listen", simple: true),
-      "Mention" => TermDefinition.new("Mention", id: "https://www.w3.org/ns/activitystreams#Mention", simple: true),
-      "Move" => TermDefinition.new("Move", id: "https://www.w3.org/ns/activitystreams#Move", simple: true),
-      "Note" => TermDefinition.new("Note", id: "https://www.w3.org/ns/activitystreams#Note", simple: true),
-      "Object" => TermDefinition.new("Object", id: "https://www.w3.org/ns/activitystreams#Object", simple: true),
-      "Offer" => TermDefinition.new("Offer", id: "https://www.w3.org/ns/activitystreams#Offer", simple: true),
-      "OrderedCollection" => TermDefinition.new("OrderedCollection", id: "https://www.w3.org/ns/activitystreams#OrderedCollection", simple: true),
-      "OrderedCollectionPage" => TermDefinition.new("OrderedCollectionPage", id: "https://www.w3.org/ns/activitystreams#OrderedCollectionPage", simple: true),
-      "Organization" => TermDefinition.new("Organization", id: "https://www.w3.org/ns/activitystreams#Organization", simple: true),
-      "Page" => TermDefinition.new("Page", id: "https://www.w3.org/ns/activitystreams#Page", simple: true),
-      "Person" => TermDefinition.new("Person", id: "https://www.w3.org/ns/activitystreams#Person", simple: true),
-      "Place" => TermDefinition.new("Place", id: "https://www.w3.org/ns/activitystreams#Place", simple: true),
-      "Profile" => TermDefinition.new("Profile", id: "https://www.w3.org/ns/activitystreams#Profile", simple: true),
-      "Question" => TermDefinition.new("Question", id: "https://www.w3.org/ns/activitystreams#Question", simple: true),
-      "Read" => TermDefinition.new("Read", id: "https://www.w3.org/ns/activitystreams#Read", simple: true),
-      "Reject" => TermDefinition.new("Reject", id: "https://www.w3.org/ns/activitystreams#Reject", simple: true),
-      "Relationship" => TermDefinition.new("Relationship", id: "https://www.w3.org/ns/activitystreams#Relationship", simple: true),
-      "Remove" => TermDefinition.new("Remove", id: "https://www.w3.org/ns/activitystreams#Remove", simple: true),
-      "Service" => TermDefinition.new("Service", id: "https://www.w3.org/ns/activitystreams#Service", simple: true),
-      "TentativeAccept" => TermDefinition.new("TentativeAccept", id: "https://www.w3.org/ns/activitystreams#TentativeAccept", simple: true),
-      "TentativeReject" => TermDefinition.new("TentativeReject", id: "https://www.w3.org/ns/activitystreams#TentativeReject", simple: true),
-      "Tombstone" => TermDefinition.new("Tombstone", id: "https://www.w3.org/ns/activitystreams#Tombstone", simple: true),
-      "Travel" => TermDefinition.new("Travel", id: "https://www.w3.org/ns/activitystreams#Travel", simple: true),
-      "Undo" => TermDefinition.new("Undo", id: "https://www.w3.org/ns/activitystreams#Undo", simple: true),
-      "Update" => TermDefinition.new("Update", id: "https://www.w3.org/ns/activitystreams#Update", simple: true),
-      "Video" => TermDefinition.new("Video", id: "https://www.w3.org/ns/activitystreams#Video", simple: true),
-      "View" => TermDefinition.new("View", id: "https://www.w3.org/ns/activitystreams#View", simple: true),
-      "accuracy" => TermDefinition.new("accuracy", id: "https://www.w3.org/ns/activitystreams#accuracy", type_mapping: "http://www.w3.org/2001/XMLSchema#float"),
-      "actor" => TermDefinition.new("actor", id: "https://www.w3.org/ns/activitystreams#actor", type_mapping: "@id"),
-      "altitude" => TermDefinition.new("altitude", id: "https://www.w3.org/ns/activitystreams#altitude", type_mapping: "http://www.w3.org/2001/XMLSchema#float"),
-      "anyOf" => TermDefinition.new("anyOf", id: "https://www.w3.org/ns/activitystreams#anyOf", type_mapping: "@id"),
-      "as" => TermDefinition.new("as", id: "https://www.w3.org/ns/activitystreams#", simple: true, prefix: true),
-      "attachment" => TermDefinition.new("attachment", id: "https://www.w3.org/ns/activitystreams#attachment", type_mapping: "@id"),
-      "attributedTo" => TermDefinition.new("attributedTo", id: "https://www.w3.org/ns/activitystreams#attributedTo", type_mapping: "@id"),
-      "audience" => TermDefinition.new("audience", id: "https://www.w3.org/ns/activitystreams#audience", type_mapping: "@id"),
-      "bcc" => TermDefinition.new("bcc", id: "https://www.w3.org/ns/activitystreams#bcc", type_mapping: "@id"),
-      "bto" => TermDefinition.new("bto", id: "https://www.w3.org/ns/activitystreams#bto", type_mapping: "@id"),
-      "cc" => TermDefinition.new("cc", id: "https://www.w3.org/ns/activitystreams#cc", type_mapping: "@id"),
-      "closed" => TermDefinition.new("closed", id: "https://www.w3.org/ns/activitystreams#closed", type_mapping: "http://www.w3.org/2001/XMLSchema#dateTime"),
-      "content" => TermDefinition.new("content", id: "https://www.w3.org/ns/activitystreams#content", simple: true),
-      "contentMap" => TermDefinition.new("contentMap", id: "https://www.w3.org/ns/activitystreams#content", container_mapping: "@language"),
-      "context" => TermDefinition.new("context", id: "https://www.w3.org/ns/activitystreams#context", type_mapping: "@id"),
-      "current" => TermDefinition.new("current", id: "https://www.w3.org/ns/activitystreams#current", type_mapping: "@id"),
-      "deleted" => TermDefinition.new("deleted", id: "https://www.w3.org/ns/activitystreams#deleted", type_mapping: "http://www.w3.org/2001/XMLSchema#dateTime"),
-      "describes" => TermDefinition.new("describes", id: "https://www.w3.org/ns/activitystreams#describes", type_mapping: "@id"),
-      "duration" => TermDefinition.new("duration", id: "https://www.w3.org/ns/activitystreams#duration", type_mapping: "http://www.w3.org/2001/XMLSchema#duration"),
-      "endTime" => TermDefinition.new("endTime", id: "https://www.w3.org/ns/activitystreams#endTime", type_mapping: "http://www.w3.org/2001/XMLSchema#dateTime"),
-      "endpoints" => TermDefinition.new("endpoints", id: "https://www.w3.org/ns/activitystreams#endpoints", type_mapping: "@id"),
-      "first" => TermDefinition.new("first", id: "https://www.w3.org/ns/activitystreams#first", type_mapping: "@id"),
-      "followers" => TermDefinition.new("followers", id: "https://www.w3.org/ns/activitystreams#followers", type_mapping: "@id"),
-      "following" => TermDefinition.new("following", id: "https://www.w3.org/ns/activitystreams#following", type_mapping: "@id"),
-      "formerType" => TermDefinition.new("formerType", id: "https://www.w3.org/ns/activitystreams#formerType", type_mapping: "@id"),
-      "generator" => TermDefinition.new("generator", id: "https://www.w3.org/ns/activitystreams#generator", type_mapping: "@id"),
-      "height" => TermDefinition.new("height", id: "https://www.w3.org/ns/activitystreams#height", type_mapping: "http://www.w3.org/2001/XMLSchema#nonNegativeInteger"),
-      "href" => TermDefinition.new("href", id: "https://www.w3.org/ns/activitystreams#href", type_mapping: "@id"),
-      "hreflang" => TermDefinition.new("hreflang", id: "https://www.w3.org/ns/activitystreams#hreflang", simple: true),
-      "icon" => TermDefinition.new("icon", id: "https://www.w3.org/ns/activitystreams#icon", type_mapping: "@id"),
-      "id" => TermDefinition.new("id", id: "@id", simple: true),
-      "image" => TermDefinition.new("image", id: "https://www.w3.org/ns/activitystreams#image", type_mapping: "@id"),
-      "inReplyTo" => TermDefinition.new("inReplyTo", id: "https://www.w3.org/ns/activitystreams#inReplyTo", type_mapping: "@id"),
-      "inbox" => TermDefinition.new("inbox", id: "http://www.w3.org/ns/ldp#inbox", type_mapping: "@id"),
-      "instrument" => TermDefinition.new("instrument", id: "https://www.w3.org/ns/activitystreams#instrument", type_mapping: "@id"),
-      "items" => TermDefinition.new("items", id: "https://www.w3.org/ns/activitystreams#items", type_mapping: "@id"),
-      "last" => TermDefinition.new("last", id: "https://www.w3.org/ns/activitystreams#last", type_mapping: "@id"),
-      "latitude" => TermDefinition.new("latitude", id: "https://www.w3.org/ns/activitystreams#latitude", type_mapping: "http://www.w3.org/2001/XMLSchema#float"),
-      "ldp" => TermDefinition.new("ldp", id: "http://www.w3.org/ns/ldp#", simple: true, prefix: true),
-      "liked" => TermDefinition.new("liked", id: "https://www.w3.org/ns/activitystreams#liked", type_mapping: "@id"),
-      "location" => TermDefinition.new("location", id: "https://www.w3.org/ns/activitystreams#location", type_mapping: "@id"),
-      "longitude" => TermDefinition.new("longitude", id: "https://www.w3.org/ns/activitystreams#longitude", type_mapping: "http://www.w3.org/2001/XMLSchema#float"),
-      "mediaType" => TermDefinition.new("mediaType", id: "https://www.w3.org/ns/activitystreams#mediaType", simple: true),
-      "name" => TermDefinition.new("name", id: "https://www.w3.org/ns/activitystreams#name", simple: true),
-      "nameMap" => TermDefinition.new("nameMap", id: "https://www.w3.org/ns/activitystreams#name", container_mapping: "@language"),
-      "next" => TermDefinition.new("next", id: "https://www.w3.org/ns/activitystreams#next", type_mapping: "@id"),
-      "oauthAuthorizationEndpoint" => TermDefinition.new("oauthAuthorizationEndpoint", id: "https://www.w3.org/ns/activitystreams#oauthAuthorizationEndpoint", type_mapping: "@id"),
-      "oauthTokenEndpoint" => TermDefinition.new("oauthTokenEndpoint", id: "https://www.w3.org/ns/activitystreams#oauthTokenEndpoint", type_mapping: "@id"),
-      "object" => TermDefinition.new("object", id: "https://www.w3.org/ns/activitystreams#object", type_mapping: "@id"),
-      "oneOf" => TermDefinition.new("oneOf", id: "https://www.w3.org/ns/activitystreams#oneOf", type_mapping: "@id"),
-      "orderedItems" => TermDefinition.new("orderedItems", id: "https://www.w3.org/ns/activitystreams#items", type_mapping: "@id", container_mapping: "@list"),
-      "origin" => TermDefinition.new("origin", id: "https://www.w3.org/ns/activitystreams#origin", type_mapping: "@id"),
-      "outbox" => TermDefinition.new("outbox", id: "https://www.w3.org/ns/activitystreams#outbox", type_mapping: "@id"),
-      "partOf" => TermDefinition.new("partOf", id: "https://www.w3.org/ns/activitystreams#partOf", type_mapping: "@id"),
-      "preferredUsername" => TermDefinition.new("preferredUsername", id: "https://www.w3.org/ns/activitystreams#preferredUsername", simple: true),
-      "prev" => TermDefinition.new("prev", id: "https://www.w3.org/ns/activitystreams#prev", type_mapping: "@id"),
-      "preview" => TermDefinition.new("preview", id: "https://www.w3.org/ns/activitystreams#preview", type_mapping: "@id"),
-      "provideClientKey" => TermDefinition.new("provideClientKey", id: "https://www.w3.org/ns/activitystreams#provideClientKey", type_mapping: "@id"),
-      "proxyUrl" => TermDefinition.new("proxyUrl", id: "https://www.w3.org/ns/activitystreams#proxyUrl", type_mapping: "@id"),
-      "published" => TermDefinition.new("published", id: "https://www.w3.org/ns/activitystreams#published", type_mapping: "http://www.w3.org/2001/XMLSchema#dateTime"),
-      "radius" => TermDefinition.new("radius", id: "https://www.w3.org/ns/activitystreams#radius", type_mapping: "http://www.w3.org/2001/XMLSchema#float"),
-      "rel" => TermDefinition.new("rel", id: "https://www.w3.org/ns/activitystreams#rel", simple: true),
-      "relationship" => TermDefinition.new("relationship", id: "https://www.w3.org/ns/activitystreams#relationship", type_mapping: "@id"),
-      "replies" => TermDefinition.new("replies", id: "https://www.w3.org/ns/activitystreams#replies", type_mapping: "@id"),
-      "result" => TermDefinition.new("result", id: "https://www.w3.org/ns/activitystreams#result", type_mapping: "@id"),
-      "sharedInbox" => TermDefinition.new("sharedInbox", id: "https://www.w3.org/ns/activitystreams#sharedInbox", type_mapping: "@id"),
-      "signClientKey" => TermDefinition.new("signClientKey", id: "https://www.w3.org/ns/activitystreams#signClientKey", type_mapping: "@id"),
-      "source" => TermDefinition.new("source", id: "https://www.w3.org/ns/activitystreams#source", simple: true),
-      "startIndex" => TermDefinition.new("startIndex", id: "https://www.w3.org/ns/activitystreams#startIndex", type_mapping: "http://www.w3.org/2001/XMLSchema#nonNegativeInteger"),
-      "startTime" => TermDefinition.new("startTime", id: "https://www.w3.org/ns/activitystreams#startTime", type_mapping: "http://www.w3.org/2001/XMLSchema#dateTime"),
-      "streams" => TermDefinition.new("streams", id: "https://www.w3.org/ns/activitystreams#streams", type_mapping: "@id"),
-      "subject" => TermDefinition.new("subject", id: "https://www.w3.org/ns/activitystreams#subject", type_mapping: "@id"),
-      "summary" => TermDefinition.new("summary", id: "https://www.w3.org/ns/activitystreams#summary", simple: true),
-      "summaryMap" => TermDefinition.new("summaryMap", id: "https://www.w3.org/ns/activitystreams#summary", container_mapping: "@language"),
-      "tag" => TermDefinition.new("tag", id: "https://www.w3.org/ns/activitystreams#tag", type_mapping: "@id"),
-      "target" => TermDefinition.new("target", id: "https://www.w3.org/ns/activitystreams#target", type_mapping: "@id"),
-      "to" => TermDefinition.new("to", id: "https://www.w3.org/ns/activitystreams#to", type_mapping: "@id"),
-      "totalItems" => TermDefinition.new("totalItems", id: "https://www.w3.org/ns/activitystreams#totalItems", type_mapping: "http://www.w3.org/2001/XMLSchema#nonNegativeInteger"),
-      "type" => TermDefinition.new("type", id: "@type", simple: true),
-      "units" => TermDefinition.new("units", id: "https://www.w3.org/ns/activitystreams#units", simple: true),
-      "updated" => TermDefinition.new("updated", id: "https://www.w3.org/ns/activitystreams#updated", type_mapping: "http://www.w3.org/2001/XMLSchema#dateTime"),
-      "uploadMedia" => TermDefinition.new("uploadMedia", id: "https://www.w3.org/ns/activitystreams#uploadMedia", type_mapping: "@id"),
-      "url" => TermDefinition.new("url", id: "https://www.w3.org/ns/activitystreams#url", type_mapping: "@id"),
-      "width" => TermDefinition.new("width", id: "https://www.w3.org/ns/activitystreams#width", type_mapping: "http://www.w3.org/2001/XMLSchema#nonNegativeInteger"),
-      "xsd" => TermDefinition.new("xsd", id: "http://www.w3.org/2001/XMLSchema#", simple: true, prefix: true)
-    })
-  end
-end
diff --git a/lib/json_ld/identity.rb b/lib/json_ld/identity.rb
deleted file mode 100644
index cfe50b956..000000000
--- a/lib/json_ld/identity.rb
+++ /dev/null
@@ -1,86 +0,0 @@
-# -*- encoding: utf-8 -*-
-# frozen_string_literal: true
-# This file generated automatically from https://w3id.org/identity/v1
-require 'json/ld'
-class JSON::LD::Context
-  add_preloaded("https://w3id.org/identity/v1") do
-    new(processingMode: "json-ld-1.0", term_definitions: {
-      "Credential" => TermDefinition.new("Credential", id: "https://w3id.org/credentials#Credential", simple: true),
-      "CryptographicKey" => TermDefinition.new("CryptographicKey", id: "https://w3id.org/security#Key", simple: true),
-      "CryptographicKeyCredential" => TermDefinition.new("CryptographicKeyCredential", id: "https://w3id.org/credentials#CryptographicKeyCredential", simple: true),
-      "EncryptedMessage" => TermDefinition.new("EncryptedMessage", id: "https://w3id.org/security#EncryptedMessage", simple: true),
-      "GraphSignature2012" => TermDefinition.new("GraphSignature2012", id: "https://w3id.org/security#GraphSignature2012", simple: true),
-      "Group" => TermDefinition.new("Group", id: "https://www.w3.org/ns/activitystreams#Group", simple: true),
-      "Identity" => TermDefinition.new("Identity", id: "https://w3id.org/identity#Identity", simple: true),
-      "LinkedDataSignature2015" => TermDefinition.new("LinkedDataSignature2015", id: "https://w3id.org/security#LinkedDataSignature2015", simple: true),
-      "Organization" => TermDefinition.new("Organization", id: "http://schema.org/Organization", simple: true),
-      "Person" => TermDefinition.new("Person", id: "http://schema.org/Person", simple: true),
-      "PostalAddress" => TermDefinition.new("PostalAddress", id: "http://schema.org/PostalAddress", simple: true),
-      "about" => TermDefinition.new("about", id: "http://schema.org/about", type_mapping: "@id"),
-      "accessControl" => TermDefinition.new("accessControl", id: "https://w3id.org/permissions#accessControl", type_mapping: "@id"),
-      "address" => TermDefinition.new("address", id: "http://schema.org/address", type_mapping: "@id"),
-      "addressCountry" => TermDefinition.new("addressCountry", id: "http://schema.org/addressCountry", simple: true),
-      "addressLocality" => TermDefinition.new("addressLocality", id: "http://schema.org/addressLocality", simple: true),
-      "addressRegion" => TermDefinition.new("addressRegion", id: "http://schema.org/addressRegion", simple: true),
-      "cipherAlgorithm" => TermDefinition.new("cipherAlgorithm", id: "https://w3id.org/security#cipherAlgorithm", simple: true),
-      "cipherData" => TermDefinition.new("cipherData", id: "https://w3id.org/security#cipherData", simple: true),
-      "cipherKey" => TermDefinition.new("cipherKey", id: "https://w3id.org/security#cipherKey", simple: true),
-      "claim" => TermDefinition.new("claim", id: "https://w3id.org/credentials#claim", type_mapping: "@id"),
-      "comment" => TermDefinition.new("comment", id: "http://www.w3.org/2000/01/rdf-schema#comment", simple: true),
-      "created" => TermDefinition.new("created", id: "http://purl.org/dc/terms/created", type_mapping: "http://www.w3.org/2001/XMLSchema#dateTime"),
-      "creator" => TermDefinition.new("creator", id: "http://purl.org/dc/terms/creator", type_mapping: "@id"),
-      "cred" => TermDefinition.new("cred", id: "https://w3id.org/credentials#", simple: true, prefix: true),
-      "credential" => TermDefinition.new("credential", id: "https://w3id.org/credentials#credential", type_mapping: "@id"),
-      "dc" => TermDefinition.new("dc", id: "http://purl.org/dc/terms/", simple: true, prefix: true),
-      "description" => TermDefinition.new("description", id: "http://schema.org/description", simple: true),
-      "digestAlgorithm" => TermDefinition.new("digestAlgorithm", id: "https://w3id.org/security#digestAlgorithm", simple: true),
-      "digestValue" => TermDefinition.new("digestValue", id: "https://w3id.org/security#digestValue", simple: true),
-      "domain" => TermDefinition.new("domain", id: "https://w3id.org/security#domain", simple: true),
-      "email" => TermDefinition.new("email", id: "http://schema.org/email", simple: true),
-      "expires" => TermDefinition.new("expires", id: "https://w3id.org/security#expiration", type_mapping: "http://www.w3.org/2001/XMLSchema#dateTime"),
-      "familyName" => TermDefinition.new("familyName", id: "http://schema.org/familyName", simple: true),
-      "givenName" => TermDefinition.new("givenName", id: "http://schema.org/givenName", simple: true),
-      "id" => TermDefinition.new("id", id: "@id", simple: true),
-      "identity" => TermDefinition.new("identity", id: "https://w3id.org/identity#", simple: true, prefix: true),
-      "identityService" => TermDefinition.new("identityService", id: "https://w3id.org/identity#identityService", type_mapping: "@id"),
-      "idp" => TermDefinition.new("idp", id: "https://w3id.org/identity#idp", type_mapping: "@id"),
-      "image" => TermDefinition.new("image", id: "http://schema.org/image", type_mapping: "@id"),
-      "initializationVector" => TermDefinition.new("initializationVector", id: "https://w3id.org/security#initializationVector", simple: true),
-      "issued" => TermDefinition.new("issued", id: "https://w3id.org/credentials#issued", type_mapping: "http://www.w3.org/2001/XMLSchema#dateTime"),
-      "issuer" => TermDefinition.new("issuer", id: "https://w3id.org/credentials#issuer", type_mapping: "@id"),
-      "label" => TermDefinition.new("label", id: "http://www.w3.org/2000/01/rdf-schema#label", simple: true),
-      "member" => TermDefinition.new("member", id: "http://schema.org/member", type_mapping: "@id"),
-      "memberOf" => TermDefinition.new("memberOf", id: "http://schema.org/memberOf", type_mapping: "@id"),
-      "name" => TermDefinition.new("name", id: "http://schema.org/name", simple: true),
-      "nonce" => TermDefinition.new("nonce", id: "https://w3id.org/security#nonce", simple: true),
-      "normalizationAlgorithm" => TermDefinition.new("normalizationAlgorithm", id: "https://w3id.org/security#normalizationAlgorithm", simple: true),
-      "owner" => TermDefinition.new("owner", id: "https://w3id.org/security#owner", type_mapping: "@id"),
-      "password" => TermDefinition.new("password", id: "https://w3id.org/security#password", simple: true),
-      "paymentProcessor" => TermDefinition.new("paymentProcessor", id: "https://w3id.org/payswarm#processor", simple: true),
-      "perm" => TermDefinition.new("perm", id: "https://w3id.org/permissions#", simple: true, prefix: true),
-      "postalCode" => TermDefinition.new("postalCode", id: "http://schema.org/postalCode", simple: true),
-      "preferences" => TermDefinition.new("preferences", id: "https://w3id.org/payswarm#preferences", type_mapping: "@vocab"),
-      "privateKey" => TermDefinition.new("privateKey", id: "https://w3id.org/security#privateKey", type_mapping: "@id"),
-      "privateKeyPem" => TermDefinition.new("privateKeyPem", id: "https://w3id.org/security#privateKeyPem", simple: true),
-      "ps" => TermDefinition.new("ps", id: "https://w3id.org/payswarm#", simple: true, prefix: true),
-      "publicKey" => TermDefinition.new("publicKey", id: "https://w3id.org/security#publicKey", type_mapping: "@id"),
-      "publicKeyPem" => TermDefinition.new("publicKeyPem", id: "https://w3id.org/security#publicKeyPem", simple: true),
-      "publicKeyService" => TermDefinition.new("publicKeyService", id: "https://w3id.org/security#publicKeyService", type_mapping: "@id"),
-      "rdf" => TermDefinition.new("rdf", id: "http://www.w3.org/1999/02/22-rdf-syntax-ns#", simple: true, prefix: true),
-      "rdfs" => TermDefinition.new("rdfs", id: "http://www.w3.org/2000/01/rdf-schema#", simple: true, prefix: true),
-      "recipient" => TermDefinition.new("recipient", id: "https://w3id.org/credentials#recipient", type_mapping: "@id"),
-      "revoked" => TermDefinition.new("revoked", id: "https://w3id.org/security#revoked", type_mapping: "http://www.w3.org/2001/XMLSchema#dateTime"),
-      "schema" => TermDefinition.new("schema", id: "http://schema.org/", simple: true, prefix: true),
-      "sec" => TermDefinition.new("sec", id: "https://w3id.org/security#", simple: true, prefix: true),
-      "signature" => TermDefinition.new("signature", id: "https://w3id.org/security#signature", simple: true),
-      "signatureAlgorithm" => TermDefinition.new("signatureAlgorithm", id: "https://w3id.org/security#signatureAlgorithm", simple: true),
-      "signatureValue" => TermDefinition.new("signatureValue", id: "https://w3id.org/security#signatureValue", simple: true),
-      "streetAddress" => TermDefinition.new("streetAddress", id: "http://schema.org/streetAddress", simple: true),
-      "title" => TermDefinition.new("title", id: "http://purl.org/dc/terms/title", simple: true),
-      "type" => TermDefinition.new("type", id: "@type", simple: true),
-      "url" => TermDefinition.new("url", id: "http://schema.org/url", type_mapping: "@id"),
-      "writePermission" => TermDefinition.new("writePermission", id: "https://w3id.org/permissions#writePermission", type_mapping: "@id"),
-      "xsd" => TermDefinition.new("xsd", id: "http://www.w3.org/2001/XMLSchema#", simple: true, prefix: true)
-    })
-  end
-end
diff --git a/lib/json_ld/security.rb b/lib/json_ld/security.rb
deleted file mode 100644
index 1230206f0..000000000
--- a/lib/json_ld/security.rb
+++ /dev/null
@@ -1,50 +0,0 @@
-# -*- encoding: utf-8 -*-
-# frozen_string_literal: true
-# This file generated automatically from https://w3id.org/security/v1
-require 'json/ld'
-class JSON::LD::Context
-  add_preloaded("https://w3id.org/security/v1") do
-    new(processingMode: "json-ld-1.0", term_definitions: {
-      "CryptographicKey" => TermDefinition.new("CryptographicKey", id: "https://w3id.org/security#Key", simple: true),
-      "EcdsaKoblitzSignature2016" => TermDefinition.new("EcdsaKoblitzSignature2016", id: "https://w3id.org/security#EcdsaKoblitzSignature2016", simple: true),
-      "EncryptedMessage" => TermDefinition.new("EncryptedMessage", id: "https://w3id.org/security#EncryptedMessage", simple: true),
-      "GraphSignature2012" => TermDefinition.new("GraphSignature2012", id: "https://w3id.org/security#GraphSignature2012", simple: true),
-      "LinkedDataSignature2015" => TermDefinition.new("LinkedDataSignature2015", id: "https://w3id.org/security#LinkedDataSignature2015", simple: true),
-      "LinkedDataSignature2016" => TermDefinition.new("LinkedDataSignature2016", id: "https://w3id.org/security#LinkedDataSignature2016", simple: true),
-      "authenticationTag" => TermDefinition.new("authenticationTag", id: "https://w3id.org/security#authenticationTag", simple: true),
-      "canonicalizationAlgorithm" => TermDefinition.new("canonicalizationAlgorithm", id: "https://w3id.org/security#canonicalizationAlgorithm", simple: true),
-      "cipherAlgorithm" => TermDefinition.new("cipherAlgorithm", id: "https://w3id.org/security#cipherAlgorithm", simple: true),
-      "cipherData" => TermDefinition.new("cipherData", id: "https://w3id.org/security#cipherData", simple: true),
-      "cipherKey" => TermDefinition.new("cipherKey", id: "https://w3id.org/security#cipherKey", simple: true),
-      "created" => TermDefinition.new("created", id: "http://purl.org/dc/terms/created", type_mapping: "http://www.w3.org/2001/XMLSchema#dateTime"),
-      "creator" => TermDefinition.new("creator", id: "http://purl.org/dc/terms/creator", type_mapping: "@id"),
-      "dc" => TermDefinition.new("dc", id: "http://purl.org/dc/terms/", simple: true, prefix: true),
-      "digestAlgorithm" => TermDefinition.new("digestAlgorithm", id: "https://w3id.org/security#digestAlgorithm", simple: true),
-      "digestValue" => TermDefinition.new("digestValue", id: "https://w3id.org/security#digestValue", simple: true),
-      "domain" => TermDefinition.new("domain", id: "https://w3id.org/security#domain", simple: true),
-      "encryptionKey" => TermDefinition.new("encryptionKey", id: "https://w3id.org/security#encryptionKey", simple: true),
-      "expiration" => TermDefinition.new("expiration", id: "https://w3id.org/security#expiration", type_mapping: "http://www.w3.org/2001/XMLSchema#dateTime"),
-      "expires" => TermDefinition.new("expires", id: "https://w3id.org/security#expiration", type_mapping: "http://www.w3.org/2001/XMLSchema#dateTime"),
-      "id" => TermDefinition.new("id", id: "@id", simple: true),
-      "initializationVector" => TermDefinition.new("initializationVector", id: "https://w3id.org/security#initializationVector", simple: true),
-      "iterationCount" => TermDefinition.new("iterationCount", id: "https://w3id.org/security#iterationCount", simple: true),
-      "nonce" => TermDefinition.new("nonce", id: "https://w3id.org/security#nonce", simple: true),
-      "normalizationAlgorithm" => TermDefinition.new("normalizationAlgorithm", id: "https://w3id.org/security#normalizationAlgorithm", simple: true),
-      "owner" => TermDefinition.new("owner", id: "https://w3id.org/security#owner", type_mapping: "@id"),
-      "password" => TermDefinition.new("password", id: "https://w3id.org/security#password", simple: true),
-      "privateKey" => TermDefinition.new("privateKey", id: "https://w3id.org/security#privateKey", type_mapping: "@id"),
-      "privateKeyPem" => TermDefinition.new("privateKeyPem", id: "https://w3id.org/security#privateKeyPem", simple: true),
-      "publicKey" => TermDefinition.new("publicKey", id: "https://w3id.org/security#publicKey", type_mapping: "@id"),
-      "publicKeyPem" => TermDefinition.new("publicKeyPem", id: "https://w3id.org/security#publicKeyPem", simple: true),
-      "publicKeyService" => TermDefinition.new("publicKeyService", id: "https://w3id.org/security#publicKeyService", type_mapping: "@id"),
-      "revoked" => TermDefinition.new("revoked", id: "https://w3id.org/security#revoked", type_mapping: "http://www.w3.org/2001/XMLSchema#dateTime"),
-      "salt" => TermDefinition.new("salt", id: "https://w3id.org/security#salt", simple: true),
-      "sec" => TermDefinition.new("sec", id: "https://w3id.org/security#", simple: true, prefix: true),
-      "signature" => TermDefinition.new("signature", id: "https://w3id.org/security#signature", simple: true),
-      "signatureAlgorithm" => TermDefinition.new("signatureAlgorithm", id: "https://w3id.org/security#signingAlgorithm", simple: true),
-      "signatureValue" => TermDefinition.new("signatureValue", id: "https://w3id.org/security#signatureValue", simple: true),
-      "type" => TermDefinition.new("type", id: "@type", simple: true),
-      "xsd" => TermDefinition.new("xsd", id: "http://www.w3.org/2001/XMLSchema#", simple: true, prefix: true)
-    })
-  end
-end
diff --git a/lib/tasks/mastodon.rake b/lib/tasks/mastodon.rake
index 7a337845e..00a85fa5e 100644
--- a/lib/tasks/mastodon.rake
+++ b/lib/tasks/mastodon.rake
@@ -2,6 +2,8 @@
 
 require 'optparse'
 require 'colorize'
+require 'tty-command'
+require 'tty-prompt'
 
 namespace :mastodon do
   desc 'Configure the instance for production use'
diff --git a/spec/fixtures/requests/json-ld.activitystreams.txt b/spec/fixtures/requests/json-ld.activitystreams.txt
new file mode 100644
index 000000000..395797b27
--- /dev/null
+++ b/spec/fixtures/requests/json-ld.activitystreams.txt
@@ -0,0 +1,391 @@
+HTTP/1.1 200 OK

+Date: Tue, 01 May 2018 23:25:57 GMT

+Content-Location: activitystreams.jsonld

+Vary: negotiate,accept

+TCN: choice

+Last-Modified: Mon, 16 Apr 2018 00:28:23 GMT

+ETag: "1eb0-569ec4caa97c0;d3-540ee27e0eec0"

+Accept-Ranges: bytes

+Content-Length: 7856

+Cache-Control: max-age=21600

+Expires: Wed, 02 May 2018 05:25:57 GMT

+P3P: policyref="http://www.w3.org/2014/08/p3p.xml"

+Access-Control-Allow-Origin: *

+Content-Type: application/ld+json

+Strict-Transport-Security: max-age=15552000; includeSubdomains; preload

+Content-Security-Policy: upgrade-insecure-requests

+

+{
+  "@context": {
+    "@vocab": "_:",
+    "xsd": "http://www.w3.org/2001/XMLSchema#",
+    "as": "https://www.w3.org/ns/activitystreams#",
+    "ldp": "http://www.w3.org/ns/ldp#",
+    "id": "@id",
+    "type": "@type",
+    "Accept": "as:Accept",
+    "Activity": "as:Activity",
+    "IntransitiveActivity": "as:IntransitiveActivity",
+    "Add": "as:Add",
+    "Announce": "as:Announce",
+    "Application": "as:Application",
+    "Arrive": "as:Arrive",
+    "Article": "as:Article",
+    "Audio": "as:Audio",
+    "Block": "as:Block",
+    "Collection": "as:Collection",
+    "CollectionPage": "as:CollectionPage",
+    "Relationship": "as:Relationship",
+    "Create": "as:Create",
+    "Delete": "as:Delete",
+    "Dislike": "as:Dislike",
+    "Document": "as:Document",
+    "Event": "as:Event",
+    "Follow": "as:Follow",
+    "Flag": "as:Flag",
+    "Group": "as:Group",
+    "Ignore": "as:Ignore",
+    "Image": "as:Image",
+    "Invite": "as:Invite",
+    "Join": "as:Join",
+    "Leave": "as:Leave",
+    "Like": "as:Like",
+    "Link": "as:Link",
+    "Mention": "as:Mention",
+    "Note": "as:Note",
+    "Object": "as:Object",
+    "Offer": "as:Offer",
+    "OrderedCollection": "as:OrderedCollection",
+    "OrderedCollectionPage": "as:OrderedCollectionPage",
+    "Organization": "as:Organization",
+    "Page": "as:Page",
+    "Person": "as:Person",
+    "Place": "as:Place",
+    "Profile": "as:Profile",
+    "Question": "as:Question",
+    "Reject": "as:Reject",
+    "Remove": "as:Remove",
+    "Service": "as:Service",
+    "TentativeAccept": "as:TentativeAccept",
+    "TentativeReject": "as:TentativeReject",
+    "Tombstone": "as:Tombstone",
+    "Undo": "as:Undo",
+    "Update": "as:Update",
+    "Video": "as:Video",
+    "View": "as:View",
+    "Listen": "as:Listen",
+    "Read": "as:Read",
+    "Move": "as:Move",
+    "Travel": "as:Travel",
+    "IsFollowing": "as:IsFollowing",
+    "IsFollowedBy": "as:IsFollowedBy",
+    "IsContact": "as:IsContact",
+    "IsMember": "as:IsMember",
+    "subject": {
+      "@id": "as:subject",
+      "@type": "@id"
+    },
+    "relationship": {
+      "@id": "as:relationship",
+      "@type": "@id"
+    },
+    "actor": {
+      "@id": "as:actor",
+      "@type": "@id"
+    },
+    "attributedTo": {
+      "@id": "as:attributedTo",
+      "@type": "@id"
+    },
+    "attachment": {
+      "@id": "as:attachment",
+      "@type": "@id"
+    },
+    "bcc": {
+      "@id": "as:bcc",
+      "@type": "@id"
+    },
+    "bto": {
+      "@id": "as:bto",
+      "@type": "@id"
+    },
+    "cc": {
+      "@id": "as:cc",
+      "@type": "@id"
+    },
+    "context": {
+      "@id": "as:context",
+      "@type": "@id"
+    },
+    "current": {
+      "@id": "as:current",
+      "@type": "@id"
+    },
+    "first": {
+      "@id": "as:first",
+      "@type": "@id"
+    },
+    "generator": {
+      "@id": "as:generator",
+      "@type": "@id"
+    },
+    "icon": {
+      "@id": "as:icon",
+      "@type": "@id"
+    },
+    "image": {
+      "@id": "as:image",
+      "@type": "@id"
+    },
+    "inReplyTo": {
+      "@id": "as:inReplyTo",
+      "@type": "@id"
+    },
+    "items": {
+      "@id": "as:items",
+      "@type": "@id"
+    },
+    "instrument": {
+      "@id": "as:instrument",
+      "@type": "@id"
+    },
+    "orderedItems": {
+      "@id": "as:items",
+      "@type": "@id",
+      "@container": "@list"
+    },
+    "last": {
+      "@id": "as:last",
+      "@type": "@id"
+    },
+    "location": {
+      "@id": "as:location",
+      "@type": "@id"
+    },
+    "next": {
+      "@id": "as:next",
+      "@type": "@id"
+    },
+    "object": {
+      "@id": "as:object",
+      "@type": "@id"
+    },
+    "oneOf": {
+      "@id": "as:oneOf",
+      "@type": "@id"
+    },
+    "anyOf": {
+      "@id": "as:anyOf",
+      "@type": "@id"
+    },
+    "closed": {
+      "@id": "as:closed",
+      "@type": "xsd:dateTime"
+    },
+    "origin": {
+      "@id": "as:origin",
+      "@type": "@id"
+    },
+    "accuracy": {
+      "@id": "as:accuracy",
+      "@type": "xsd:float"
+    },
+    "prev": {
+      "@id": "as:prev",
+      "@type": "@id"
+    },
+    "preview": {
+      "@id": "as:preview",
+      "@type": "@id"
+    },
+    "replies": {
+      "@id": "as:replies",
+      "@type": "@id"
+    },
+    "result": {
+      "@id": "as:result",
+      "@type": "@id"
+    },
+    "audience": {
+      "@id": "as:audience",
+      "@type": "@id"
+    },
+    "partOf": {
+      "@id": "as:partOf",
+      "@type": "@id"
+    },
+    "tag": {
+      "@id": "as:tag",
+      "@type": "@id"
+    },
+    "target": {
+      "@id": "as:target",
+      "@type": "@id"
+    },
+    "to": {
+      "@id": "as:to",
+      "@type": "@id"
+    },
+    "url": {
+      "@id": "as:url",
+      "@type": "@id"
+    },
+    "altitude": {
+      "@id": "as:altitude",
+      "@type": "xsd:float"
+    },
+    "content": "as:content",
+    "contentMap": {
+      "@id": "as:content",
+      "@container": "@language"
+    },
+    "name": "as:name",
+    "nameMap": {
+      "@id": "as:name",
+      "@container": "@language"
+    },
+    "duration": {
+      "@id": "as:duration",
+      "@type": "xsd:duration"
+    },
+    "endTime": {
+      "@id": "as:endTime",
+      "@type": "xsd:dateTime"
+    },
+    "height": {
+      "@id": "as:height",
+      "@type": "xsd:nonNegativeInteger"
+    },
+    "href": {
+      "@id": "as:href",
+      "@type": "@id"
+    },
+    "hreflang": "as:hreflang",
+    "latitude": {
+      "@id": "as:latitude",
+      "@type": "xsd:float"
+    },
+    "longitude": {
+      "@id": "as:longitude",
+      "@type": "xsd:float"
+    },
+    "mediaType": "as:mediaType",
+    "published": {
+      "@id": "as:published",
+      "@type": "xsd:dateTime"
+    },
+    "radius": {
+      "@id": "as:radius",
+      "@type": "xsd:float"
+    },
+    "rel": "as:rel",
+    "startIndex": {
+      "@id": "as:startIndex",
+      "@type": "xsd:nonNegativeInteger"
+    },
+    "startTime": {
+      "@id": "as:startTime",
+      "@type": "xsd:dateTime"
+    },
+    "summary": "as:summary",
+    "summaryMap": {
+      "@id": "as:summary",
+      "@container": "@language"
+    },
+    "totalItems": {
+      "@id": "as:totalItems",
+      "@type": "xsd:nonNegativeInteger"
+    },
+    "units": "as:units",
+    "updated": {
+      "@id": "as:updated",
+      "@type": "xsd:dateTime"
+    },
+    "width": {
+      "@id": "as:width",
+      "@type": "xsd:nonNegativeInteger"
+    },
+    "describes": {
+      "@id": "as:describes",
+      "@type": "@id"
+    },
+    "formerType": {
+      "@id": "as:formerType",
+      "@type": "@id"
+    },
+    "deleted": {
+      "@id": "as:deleted",
+      "@type": "xsd:dateTime"
+    },
+    "inbox": {
+      "@id": "ldp:inbox",
+      "@type": "@id"
+    },
+    "outbox": {
+      "@id": "as:outbox",
+      "@type": "@id"
+    },
+    "following": {
+      "@id": "as:following",
+      "@type": "@id"
+    },
+    "followers": {
+      "@id": "as:followers",
+      "@type": "@id"
+    },
+    "streams": {
+      "@id": "as:streams",
+      "@type": "@id"
+    },
+    "preferredUsername": "as:preferredUsername",
+    "endpoints": {
+      "@id": "as:endpoints",
+      "@type": "@id"
+    },
+    "uploadMedia": {
+      "@id": "as:uploadMedia",
+      "@type": "@id"
+    },
+    "proxyUrl": {
+      "@id": "as:proxyUrl",
+      "@type": "@id"
+    },
+    "liked": {
+      "@id": "as:liked",
+      "@type": "@id"
+    },
+    "oauthAuthorizationEndpoint": {
+      "@id": "as:oauthAuthorizationEndpoint",
+      "@type": "@id"
+    },
+    "oauthTokenEndpoint": {
+      "@id": "as:oauthTokenEndpoint",
+      "@type": "@id"
+    },
+    "provideClientKey": {
+      "@id": "as:provideClientKey",
+      "@type": "@id"
+    },
+    "signClientKey": {
+      "@id": "as:signClientKey",
+      "@type": "@id"
+    },
+    "sharedInbox": {
+      "@id": "as:sharedInbox",
+      "@type": "@id"
+    },
+    "Public": {
+      "@id": "as:Public",
+      "@type": "@id"
+    },
+    "source": "as:source",
+    "likes": {
+      "@id": "as:likes",
+      "@type": "@id"
+    },
+    "shares": {
+      "@id": "as:shares",
+      "@type": "@id"
+    }
+  }
+}
diff --git a/spec/fixtures/requests/json-ld.identity.txt b/spec/fixtures/requests/json-ld.identity.txt
new file mode 100644
index 000000000..8810526cb
--- /dev/null
+++ b/spec/fixtures/requests/json-ld.identity.txt
@@ -0,0 +1,100 @@
+HTTP/1.1 200 OK

+Accept-Ranges: bytes

+Access-Control-Allow-Headers: DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Accept-Encoding

+Access-Control-Allow-Origin: *

+Content-Type: application/ld+json

+Date: Tue, 01 May 2018 23:28:21 GMT

+Etag: "e26-547a6fc75b04a-gzip"

+Last-Modified: Fri, 03 Feb 2017 21:30:09 GMT

+Server: Apache/2.4.7 (Ubuntu)

+Vary: Accept-Encoding

+Transfer-Encoding: chunked

+

+{
+  "@context": {
+    "id": "@id",
+    "type": "@type",
+
+    "cred": "https://w3id.org/credentials#",
+    "dc": "http://purl.org/dc/terms/",
+    "identity": "https://w3id.org/identity#",
+    "perm": "https://w3id.org/permissions#",
+    "ps": "https://w3id.org/payswarm#",
+    "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
+    "rdfs": "http://www.w3.org/2000/01/rdf-schema#",
+    "sec": "https://w3id.org/security#",
+    "schema": "http://schema.org/",
+    "xsd": "http://www.w3.org/2001/XMLSchema#",
+
+    "Group": "https://www.w3.org/ns/activitystreams#Group",
+
+    "claim": {"@id": "cred:claim", "@type": "@id"},
+    "credential": {"@id": "cred:credential", "@type": "@id"},
+    "issued": {"@id": "cred:issued", "@type": "xsd:dateTime"},
+    "issuer": {"@id": "cred:issuer", "@type": "@id"},
+    "recipient": {"@id": "cred:recipient", "@type": "@id"},
+    "Credential": "cred:Credential",
+    "CryptographicKeyCredential": "cred:CryptographicKeyCredential",
+
+    "about": {"@id": "schema:about", "@type": "@id"},
+    "address": {"@id": "schema:address", "@type": "@id"},
+    "addressCountry": "schema:addressCountry",
+    "addressLocality": "schema:addressLocality",
+    "addressRegion": "schema:addressRegion",
+    "comment": "rdfs:comment",
+    "created": {"@id": "dc:created", "@type": "xsd:dateTime"},
+    "creator": {"@id": "dc:creator", "@type": "@id"},
+    "description": "schema:description",
+    "email": "schema:email",
+    "familyName": "schema:familyName",
+    "givenName": "schema:givenName",
+    "image": {"@id": "schema:image", "@type": "@id"},
+    "label": "rdfs:label",
+    "name": "schema:name",
+    "postalCode": "schema:postalCode",
+    "streetAddress": "schema:streetAddress",
+    "title": "dc:title",
+    "url": {"@id": "schema:url", "@type": "@id"},
+    "Person": "schema:Person",
+    "PostalAddress": "schema:PostalAddress",
+    "Organization": "schema:Organization",
+
+    "identityService": {"@id": "identity:identityService", "@type": "@id"},
+    "idp": {"@id": "identity:idp", "@type": "@id"},
+    "Identity": "identity:Identity",
+
+    "paymentProcessor": "ps:processor",
+    "preferences": {"@id": "ps:preferences", "@type": "@vocab"},
+
+    "cipherAlgorithm": "sec:cipherAlgorithm",
+    "cipherData": "sec:cipherData",
+    "cipherKey": "sec:cipherKey",
+    "digestAlgorithm": "sec:digestAlgorithm",
+    "digestValue": "sec:digestValue",
+    "domain": "sec:domain",
+    "expires": {"@id": "sec:expiration", "@type": "xsd:dateTime"},
+    "initializationVector": "sec:initializationVector",
+    "member": {"@id": "schema:member", "@type": "@id"},
+    "memberOf": {"@id": "schema:memberOf", "@type": "@id"},
+    "nonce": "sec:nonce",
+    "normalizationAlgorithm": "sec:normalizationAlgorithm",
+    "owner": {"@id": "sec:owner", "@type": "@id"},
+    "password": "sec:password",
+    "privateKey": {"@id": "sec:privateKey", "@type": "@id"},
+    "privateKeyPem": "sec:privateKeyPem",
+    "publicKey": {"@id": "sec:publicKey", "@type": "@id"},
+    "publicKeyPem": "sec:publicKeyPem",
+    "publicKeyService": {"@id": "sec:publicKeyService", "@type": "@id"},
+    "revoked": {"@id": "sec:revoked", "@type": "xsd:dateTime"},
+    "signature": "sec:signature",
+    "signatureAlgorithm": "sec:signatureAlgorithm",
+    "signatureValue": "sec:signatureValue",
+    "CryptographicKey": "sec:Key",
+    "EncryptedMessage": "sec:EncryptedMessage",
+    "GraphSignature2012": "sec:GraphSignature2012",
+    "LinkedDataSignature2015": "sec:LinkedDataSignature2015",
+
+    "accessControl": {"@id": "perm:accessControl", "@type": "@id"},
+    "writePermission": {"@id": "perm:writePermission", "@type": "@id"}
+  }
+}
diff --git a/spec/fixtures/requests/json-ld.security.txt b/spec/fixtures/requests/json-ld.security.txt
new file mode 100644
index 000000000..0d29903e6
--- /dev/null
+++ b/spec/fixtures/requests/json-ld.security.txt
@@ -0,0 +1,61 @@
+HTTP/1.1 200 OK

+Accept-Ranges: bytes

+Access-Control-Allow-Headers: DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Accept-Encoding

+Access-Control-Allow-Origin: *

+Content-Type: application/ld+json

+Date: Wed, 02 May 2018 16:25:32 GMT

+Etag: "7e3-5651ec0f7c5ed-gzip"

+Last-Modified: Tue, 13 Feb 2018 21:34:04 GMT

+Server: Apache/2.4.7 (Ubuntu)

+Vary: Accept-Encoding

+Content-Length: 2019

+

+{
+  "@context": {
+    "id": "@id",
+    "type": "@type",
+
+    "dc": "http://purl.org/dc/terms/",
+    "sec": "https://w3id.org/security#",
+    "xsd": "http://www.w3.org/2001/XMLSchema#",
+
+    "EcdsaKoblitzSignature2016": "sec:EcdsaKoblitzSignature2016",
+    "Ed25519Signature2018": "sec:Ed25519Signature2018",
+    "EncryptedMessage": "sec:EncryptedMessage",
+    "GraphSignature2012": "sec:GraphSignature2012",
+    "LinkedDataSignature2015": "sec:LinkedDataSignature2015",
+    "LinkedDataSignature2016": "sec:LinkedDataSignature2016",
+    "CryptographicKey": "sec:Key",
+
+    "authenticationTag": "sec:authenticationTag",
+    "canonicalizationAlgorithm": "sec:canonicalizationAlgorithm",
+    "cipherAlgorithm": "sec:cipherAlgorithm",
+    "cipherData": "sec:cipherData",
+    "cipherKey": "sec:cipherKey",
+    "created": {"@id": "dc:created", "@type": "xsd:dateTime"},
+    "creator": {"@id": "dc:creator", "@type": "@id"},
+    "digestAlgorithm": "sec:digestAlgorithm",
+    "digestValue": "sec:digestValue",
+    "domain": "sec:domain",
+    "encryptionKey": "sec:encryptionKey",
+    "expiration": {"@id": "sec:expiration", "@type": "xsd:dateTime"},
+    "expires": {"@id": "sec:expiration", "@type": "xsd:dateTime"},
+    "initializationVector": "sec:initializationVector",
+    "iterationCount": "sec:iterationCount",
+    "nonce": "sec:nonce",
+    "normalizationAlgorithm": "sec:normalizationAlgorithm",
+    "owner": {"@id": "sec:owner", "@type": "@id"},
+    "password": "sec:password",
+    "privateKey": {"@id": "sec:privateKey", "@type": "@id"},
+    "privateKeyPem": "sec:privateKeyPem",
+    "publicKey": {"@id": "sec:publicKey", "@type": "@id"},
+    "publicKeyBase58": "sec:publicKeyBase58",
+    "publicKeyPem": "sec:publicKeyPem",
+    "publicKeyService": {"@id": "sec:publicKeyService", "@type": "@id"},
+    "revoked": {"@id": "sec:revoked", "@type": "xsd:dateTime"},
+    "salt": "sec:salt",
+    "signature": "sec:signature",
+    "signatureAlgorithm": "sec:signingAlgorithm",
+    "signatureValue": "sec:signatureValue"
+  }
+}
diff --git a/spec/lib/activitypub/linked_data_signature_spec.rb b/spec/lib/activitypub/linked_data_signature_spec.rb
index a4d6fe8c3..1f413eec9 100644
--- a/spec/lib/activitypub/linked_data_signature_spec.rb
+++ b/spec/lib/activitypub/linked_data_signature_spec.rb
@@ -16,6 +16,10 @@ RSpec.describe ActivityPub::LinkedDataSignature do
 
   subject { described_class.new(json) }
 
+  before do
+    stub_jsonld_contexts!
+  end
+
   describe '#verify_account!' do
     context 'when signature matches' do
       let(:raw_signature) do
diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb
index dc1f32e08..c575128e4 100644
--- a/spec/rails_helper.rb
+++ b/spec/rails_helper.rb
@@ -50,6 +50,14 @@ RSpec.configure do |config|
     Capybara.app_host = "http#{https ? 's' : ''}://#{ENV.fetch('LOCAL_DOMAIN')}"
   end
 
+  config.before :each, type: :controller do
+    stub_jsonld_contexts!
+  end
+
+  config.before :each, type: :service do
+    stub_jsonld_contexts!
+  end
+
   config.after :each do
     Rails.cache.clear
 
@@ -69,3 +77,9 @@ end
 def attachment_fixture(name)
   File.open(File.join(Rails.root, 'spec', 'fixtures', 'files', name))
 end
+
+def stub_jsonld_contexts!
+  stub_request(:get, 'https://www.w3.org/ns/activitystreams').to_return(request_fixture('json-ld.activitystreams.txt'))
+  stub_request(:get, 'https://w3id.org/identity/v1').to_return(request_fixture('json-ld.identity.txt'))
+  stub_request(:get, 'https://w3id.org/security/v1').to_return(request_fixture('json-ld.security.txt'))
+end
diff --git a/spec/services/account_search_service_spec.rb b/spec/services/account_search_service_spec.rb
index c5ddc5844..c6cbdcce1 100644
--- a/spec/services/account_search_service_spec.rb
+++ b/spec/services/account_search_service_spec.rb
@@ -1,6 +1,6 @@
 require 'rails_helper'
 
-describe AccountSearchService do
+describe AccountSearchService, type: :service do
   describe '.call' do
     describe 'with a query to ignore' do
       it 'returns empty array for missing query' do
diff --git a/spec/services/activitypub/fetch_remote_account_service_spec.rb b/spec/services/activitypub/fetch_remote_account_service_spec.rb
index c50d3fb97..dba55c034 100644
--- a/spec/services/activitypub/fetch_remote_account_service_spec.rb
+++ b/spec/services/activitypub/fetch_remote_account_service_spec.rb
@@ -1,6 +1,6 @@
 require 'rails_helper'
 
-RSpec.describe ActivityPub::FetchRemoteAccountService do
+RSpec.describe ActivityPub::FetchRemoteAccountService, type: :service do
   subject { ActivityPub::FetchRemoteAccountService.new }
 
   let!(:actor) do
diff --git a/spec/services/activitypub/fetch_remote_status_service_spec.rb b/spec/services/activitypub/fetch_remote_status_service_spec.rb
index a533e8413..549eb80fa 100644
--- a/spec/services/activitypub/fetch_remote_status_service_spec.rb
+++ b/spec/services/activitypub/fetch_remote_status_service_spec.rb
@@ -1,6 +1,6 @@
 require 'rails_helper'
 
-RSpec.describe ActivityPub::FetchRemoteStatusService do
+RSpec.describe ActivityPub::FetchRemoteStatusService, type: :service do
   include ActionView::Helpers::TextHelper
 
   let(:sender) { Fabricate(:account) }
diff --git a/spec/services/activitypub/process_account_service_spec.rb b/spec/services/activitypub/process_account_service_spec.rb
index d67d72acb..d3318b2ed 100644
--- a/spec/services/activitypub/process_account_service_spec.rb
+++ b/spec/services/activitypub/process_account_service_spec.rb
@@ -1,6 +1,6 @@
 require 'rails_helper'
 
-RSpec.describe ActivityPub::ProcessAccountService do
+RSpec.describe ActivityPub::ProcessAccountService, type: :service do
   subject { described_class.new }
 
   context 'property values' do
diff --git a/spec/services/activitypub/process_collection_service_spec.rb b/spec/services/activitypub/process_collection_service_spec.rb
index 3cea970cf..e46f0ae45 100644
--- a/spec/services/activitypub/process_collection_service_spec.rb
+++ b/spec/services/activitypub/process_collection_service_spec.rb
@@ -1,6 +1,6 @@
 require 'rails_helper'
 
-RSpec.describe ActivityPub::ProcessCollectionService do
+RSpec.describe ActivityPub::ProcessCollectionService, type: :service do
   let(:actor) { Fabricate(:account, domain: 'example.com', uri: 'http://example.com/account') }
 
   let(:payload) do
diff --git a/spec/services/after_block_service_spec.rb b/spec/services/after_block_service_spec.rb
index 1b115c938..f63b2045a 100644
--- a/spec/services/after_block_service_spec.rb
+++ b/spec/services/after_block_service_spec.rb
@@ -1,6 +1,6 @@
 require 'rails_helper'
 
-RSpec.describe AfterBlockService do
+RSpec.describe AfterBlockService, type: :service do
   subject do
     -> { described_class.new.call(account, target_account) }
   end
diff --git a/spec/services/authorize_follow_service_spec.rb b/spec/services/authorize_follow_service_spec.rb
index 6ea4d83da..562ef0041 100644
--- a/spec/services/authorize_follow_service_spec.rb
+++ b/spec/services/authorize_follow_service_spec.rb
@@ -1,6 +1,6 @@
 require 'rails_helper'
 
-RSpec.describe AuthorizeFollowService do
+RSpec.describe AuthorizeFollowService, type: :service do
   let(:sender) { Fabricate(:account, username: 'alice') }
 
   subject { AuthorizeFollowService.new }
diff --git a/spec/services/batched_remove_status_service_spec.rb b/spec/services/batched_remove_status_service_spec.rb
index 437da2a9d..23c122e59 100644
--- a/spec/services/batched_remove_status_service_spec.rb
+++ b/spec/services/batched_remove_status_service_spec.rb
@@ -1,6 +1,6 @@
 require 'rails_helper'
 
-RSpec.describe BatchedRemoveStatusService do
+RSpec.describe BatchedRemoveStatusService, type: :service do
   subject { BatchedRemoveStatusService.new }
 
   let!(:alice)  { Fabricate(:account) }
diff --git a/spec/services/block_domain_from_account_service_spec.rb b/spec/services/block_domain_from_account_service_spec.rb
index e7ee34372..365c0a4ad 100644
--- a/spec/services/block_domain_from_account_service_spec.rb
+++ b/spec/services/block_domain_from_account_service_spec.rb
@@ -1,6 +1,6 @@
 require 'rails_helper'
 
-RSpec.describe BlockDomainFromAccountService do
+RSpec.describe BlockDomainFromAccountService, type: :service do
   let!(:wolf) { Fabricate(:account, username: 'wolf', domain: 'evil.org') }
   let!(:alice) { Fabricate(:account, username: 'alice') }
 
diff --git a/spec/services/block_domain_service_spec.rb b/spec/services/block_domain_service_spec.rb
index 5c2cfc8c7..7ef9e2770 100644
--- a/spec/services/block_domain_service_spec.rb
+++ b/spec/services/block_domain_service_spec.rb
@@ -1,6 +1,6 @@
 require 'rails_helper'
 
-RSpec.describe BlockDomainService do
+RSpec.describe BlockDomainService, type: :service do
   let(:bad_account) { Fabricate(:account, username: 'badguy666', domain: 'evil.org') }
   let(:bad_status1) { Fabricate(:status, account: bad_account, text: 'You suck') }
   let(:bad_status2) { Fabricate(:status, account: bad_account, text: 'Hahaha') }
diff --git a/spec/services/block_service_spec.rb b/spec/services/block_service_spec.rb
index c69ff7804..6584bb90e 100644
--- a/spec/services/block_service_spec.rb
+++ b/spec/services/block_service_spec.rb
@@ -1,6 +1,6 @@
 require 'rails_helper'
 
-RSpec.describe BlockService do
+RSpec.describe BlockService, type: :service do
   let(:sender) { Fabricate(:account, username: 'alice') }
 
   subject { BlockService.new }
diff --git a/spec/services/bootstrap_timeline_service_spec.rb b/spec/services/bootstrap_timeline_service_spec.rb
index 5189b1de8..a765de791 100644
--- a/spec/services/bootstrap_timeline_service_spec.rb
+++ b/spec/services/bootstrap_timeline_service_spec.rb
@@ -1,6 +1,6 @@
 require 'rails_helper'
 
-RSpec.describe BootstrapTimelineService do
+RSpec.describe BootstrapTimelineService, type: :service do
   subject { described_class.new }
 
   describe '#call' do
diff --git a/spec/services/fan_out_on_write_service_spec.rb b/spec/services/fan_out_on_write_service_spec.rb
index 764318e34..b7fc7f7ed 100644
--- a/spec/services/fan_out_on_write_service_spec.rb
+++ b/spec/services/fan_out_on_write_service_spec.rb
@@ -1,6 +1,6 @@
 require 'rails_helper'
 
-RSpec.describe FanOutOnWriteService do
+RSpec.describe FanOutOnWriteService, type: :service do
   let(:author)   { Fabricate(:account, username: 'tom') }
   let(:status)   { Fabricate(:status, text: 'Hello @alice #test', account: author) }
   let(:alice)    { Fabricate(:user, account: Fabricate(:account, username: 'alice')).account }
diff --git a/spec/services/favourite_service_spec.rb b/spec/services/favourite_service_spec.rb
index 5bf2c74a9..0a20ccf6e 100644
--- a/spec/services/favourite_service_spec.rb
+++ b/spec/services/favourite_service_spec.rb
@@ -1,6 +1,6 @@
 require 'rails_helper'
 
-RSpec.describe FavouriteService do
+RSpec.describe FavouriteService, type: :service do
   let(:sender) { Fabricate(:account, username: 'alice') }
 
   subject { FavouriteService.new }
diff --git a/spec/services/fetch_atom_service_spec.rb b/spec/services/fetch_atom_service_spec.rb
index 2bd127e92..bb233c12d 100644
--- a/spec/services/fetch_atom_service_spec.rb
+++ b/spec/services/fetch_atom_service_spec.rb
@@ -1,6 +1,6 @@
 require 'rails_helper'
 
-RSpec.describe FetchAtomService do
+RSpec.describe FetchAtomService, type: :service do
   describe '#call' do
     let(:url) { 'http://example.com' }
     subject { FetchAtomService.new.call(url) }
diff --git a/spec/services/fetch_link_card_service_spec.rb b/spec/services/fetch_link_card_service_spec.rb
index edacc4425..88c5339db 100644
--- a/spec/services/fetch_link_card_service_spec.rb
+++ b/spec/services/fetch_link_card_service_spec.rb
@@ -1,6 +1,6 @@
 require 'rails_helper'
 
-RSpec.describe FetchLinkCardService do
+RSpec.describe FetchLinkCardService, type: :service do
   subject { FetchLinkCardService.new }
 
   before do
diff --git a/spec/lib/provider_discovery_spec.rb b/spec/services/fetch_oembed_service_spec.rb
index de2ac16f9..706eb3f2a 100644
--- a/spec/lib/provider_discovery_spec.rb
+++ b/spec/services/fetch_oembed_service_spec.rb
@@ -2,7 +2,14 @@
 
 require 'rails_helper'
 
-describe ProviderDiscovery do
+describe FetchOEmbedService, type: :service do
+  subject { described_class.new }
+
+  before do
+    stub_request(:get, "https://host.test/provider.json").to_return(status: 404)
+    stub_request(:get, "https://host.test/provider.xml").to_return(status: 404)
+  end
+
   describe 'discover_provider' do
     context 'when status code is 200 and MIME type is text/html' do
       context 'Both of JSON and XML provider are discoverable' do
@@ -15,15 +22,15 @@ describe ProviderDiscovery do
         end
 
         it 'returns new OEmbed::Provider for JSON provider if :format option is set to :json' do
-          provider = ProviderDiscovery.discover_provider('https://host.test/oembed.html', format: :json)
-          expect(provider.endpoint).to eq 'https://host.test/provider.json'
-          expect(provider.format).to eq :json
+          subject.call('https://host.test/oembed.html', format: :json)
+          expect(subject.endpoint_url).to eq 'https://host.test/provider.json'
+          expect(subject.format).to eq :json
         end
 
         it 'returns new OEmbed::Provider for XML provider if :format option is set to :xml' do
-          provider = ProviderDiscovery.discover_provider('https://host.test/oembed.html', format: :xml)
-          expect(provider.endpoint).to eq 'https://host.test/provider.xml'
-          expect(provider.format).to eq :xml
+          subject.call('https://host.test/oembed.html', format: :xml)
+          expect(subject.endpoint_url).to eq 'https://host.test/provider.xml'
+          expect(subject.format).to eq :xml
         end
       end
 
@@ -37,9 +44,9 @@ describe ProviderDiscovery do
         end
 
         it 'returns new OEmbed::Provider for JSON provider' do
-          provider = ProviderDiscovery.discover_provider('https://host.test/oembed.html')
-          expect(provider.endpoint).to eq 'https://host.test/provider.json'
-          expect(provider.format).to eq :json
+          subject.call('https://host.test/oembed.html')
+          expect(subject.endpoint_url).to eq 'https://host.test/provider.json'
+          expect(subject.format).to eq :json
         end
       end
 
@@ -53,9 +60,9 @@ describe ProviderDiscovery do
         end
 
         it 'returns new OEmbed::Provider for XML provider' do
-          provider = ProviderDiscovery.discover_provider('https://host.test/oembed.html')
-          expect(provider.endpoint).to eq 'https://host.test/provider.xml'
-          expect(provider.format).to eq :xml
+          subject.call('https://host.test/oembed.html')
+          expect(subject.endpoint_url).to eq 'https://host.test/provider.xml'
+          expect(subject.format).to eq :xml
         end
       end
 
@@ -68,8 +75,8 @@ describe ProviderDiscovery do
           )
         end
 
-        it 'raises OEmbed::NotFound' do
-          expect { ProviderDiscovery.discover_provider('https://host.test/oembed.html') }.to raise_error OEmbed::NotFound
+        it 'returns nil' do
+          expect(subject.call('https://host.test/oembed.html')).to be_nil
         end
       end
 
@@ -82,8 +89,8 @@ describe ProviderDiscovery do
           )
         end
 
-        it 'raises OEmbed::NotFound' do
-          expect { ProviderDiscovery.discover_provider('https://host.test/oembed.html') }.to raise_error OEmbed::NotFound
+        it 'returns nil' do
+          expect(subject.call('https://host.test/oembed.html')).to be_nil
         end
       end
     end
@@ -97,8 +104,8 @@ describe ProviderDiscovery do
         )
       end
 
-      it 'raises OEmbed::NotFound' do
-        expect { ProviderDiscovery.discover_provider('https://host.test/oembed.html') }.to raise_error OEmbed::NotFound
+      it 'returns nil' do
+        expect(subject.call('https://host.test/oembed.html')).to be_nil
       end
     end
 
@@ -110,8 +117,8 @@ describe ProviderDiscovery do
         )
       end
 
-      it 'raises OEmbed::NotFound' do
-        expect { ProviderDiscovery.discover_provider('https://host.test/oembed.html') }.to raise_error OEmbed::NotFound
+      it 'returns nil' do
+        expect(subject.call('https://host.test/oembed.html')).to be_nil
       end
     end
   end
diff --git a/spec/services/fetch_remote_account_service_spec.rb b/spec/services/fetch_remote_account_service_spec.rb
index 4388d4cf4..1c3abe8f3 100644
--- a/spec/services/fetch_remote_account_service_spec.rb
+++ b/spec/services/fetch_remote_account_service_spec.rb
@@ -1,6 +1,6 @@
 require 'rails_helper'
 
-RSpec.describe FetchRemoteAccountService do
+RSpec.describe FetchRemoteAccountService, type: :service do
   let(:url) { 'https://example.com' }
   let(:prefetched_body) { nil }
   let(:protocol) { :ostatus }
diff --git a/spec/services/fetch_remote_status_service_spec.rb b/spec/services/fetch_remote_status_service_spec.rb
index fa5782b94..0df9c329a 100644
--- a/spec/services/fetch_remote_status_service_spec.rb
+++ b/spec/services/fetch_remote_status_service_spec.rb
@@ -1,6 +1,6 @@
 require 'rails_helper'
 
-RSpec.describe FetchRemoteStatusService do
+RSpec.describe FetchRemoteStatusService, type: :service do
   let(:account) { Fabricate(:account) }
   let(:prefetched_body) { nil }
   let(:valid_domain) { Rails.configuration.x.local_domain }
diff --git a/spec/services/follow_service_spec.rb b/spec/services/follow_service_spec.rb
index e59a2f1a6..3c4ec59be 100644
--- a/spec/services/follow_service_spec.rb
+++ b/spec/services/follow_service_spec.rb
@@ -1,6 +1,6 @@
 require 'rails_helper'
 
-RSpec.describe FollowService do
+RSpec.describe FollowService, type: :service do
   let(:sender) { Fabricate(:account, username: 'alice') }
 
   subject { FollowService.new }
diff --git a/spec/services/mute_service_spec.rb b/spec/services/mute_service_spec.rb
index 2b3e3e152..4bb839b8d 100644
--- a/spec/services/mute_service_spec.rb
+++ b/spec/services/mute_service_spec.rb
@@ -1,6 +1,6 @@
 require 'rails_helper'
 
-RSpec.describe MuteService do
+RSpec.describe MuteService, type: :service do
   subject do
     -> { described_class.new.call(account, target_account) }
   end
diff --git a/spec/services/notify_service_spec.rb b/spec/services/notify_service_spec.rb
index abe557cf3..d34667943 100644
--- a/spec/services/notify_service_spec.rb
+++ b/spec/services/notify_service_spec.rb
@@ -1,6 +1,6 @@
 require 'rails_helper'
 
-RSpec.describe NotifyService do
+RSpec.describe NotifyService, type: :service do
   subject do
     -> { described_class.new.call(recipient, activity) }
   end
diff --git a/spec/services/post_status_service_spec.rb b/spec/services/post_status_service_spec.rb
index 92fbc73cd..40fa8fbef 100644
--- a/spec/services/post_status_service_spec.rb
+++ b/spec/services/post_status_service_spec.rb
@@ -1,6 +1,6 @@
 require 'rails_helper'
 
-RSpec.describe PostStatusService do
+RSpec.describe PostStatusService, type: :service do
   subject { PostStatusService.new }
 
   it 'creates a new status' do
diff --git a/spec/services/precompute_feed_service_spec.rb b/spec/services/precompute_feed_service_spec.rb
index 43340bffc..1f6b6ed88 100644
--- a/spec/services/precompute_feed_service_spec.rb
+++ b/spec/services/precompute_feed_service_spec.rb
@@ -2,7 +2,7 @@
 
 require 'rails_helper'
 
-RSpec.describe PrecomputeFeedService do
+RSpec.describe PrecomputeFeedService, type: :service do
   subject { PrecomputeFeedService.new }
 
   describe 'call' do
diff --git a/spec/services/process_feed_service_spec.rb b/spec/services/process_feed_service_spec.rb
index aca675dc6..d8b065063 100644
--- a/spec/services/process_feed_service_spec.rb
+++ b/spec/services/process_feed_service_spec.rb
@@ -1,6 +1,6 @@
 require 'rails_helper'
 
-RSpec.describe ProcessFeedService do
+RSpec.describe ProcessFeedService, type: :service do
   subject { ProcessFeedService.new }
 
   describe 'processing a feed' do
diff --git a/spec/services/process_interaction_service_spec.rb b/spec/services/process_interaction_service_spec.rb
index 3ea7aec59..b858c19d0 100644
--- a/spec/services/process_interaction_service_spec.rb
+++ b/spec/services/process_interaction_service_spec.rb
@@ -1,6 +1,6 @@
 require 'rails_helper'
 
-RSpec.describe ProcessInteractionService do
+RSpec.describe ProcessInteractionService, type: :service do
   let(:receiver) { Fabricate(:user, email: 'alice@example.com', account: Fabricate(:account, username: 'alice')).account }
   let(:sender)   { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob')).account }
   let(:remote_sender) { Fabricate(:account, username: 'carol', domain: 'localdomain.com', uri: 'https://webdomain.com/users/carol') }
diff --git a/spec/services/process_mentions_service_spec.rb b/spec/services/process_mentions_service_spec.rb
index 19a8678f0..963924fa9 100644
--- a/spec/services/process_mentions_service_spec.rb
+++ b/spec/services/process_mentions_service_spec.rb
@@ -1,6 +1,6 @@
 require 'rails_helper'
 
-RSpec.describe ProcessMentionsService do
+RSpec.describe ProcessMentionsService, type: :service do
   let(:account) { Fabricate(:account, username: 'alice') }
   let(:status)  { Fabricate(:status, account: account, text: "Hello @#{remote_user.acct}") }
 
diff --git a/spec/services/pubsubhubbub/subscribe_service_spec.rb b/spec/services/pubsubhubbub/subscribe_service_spec.rb
index 82094117b..01c956230 100644
--- a/spec/services/pubsubhubbub/subscribe_service_spec.rb
+++ b/spec/services/pubsubhubbub/subscribe_service_spec.rb
@@ -2,7 +2,7 @@
 
 require 'rails_helper'
 
-describe Pubsubhubbub::SubscribeService do
+describe Pubsubhubbub::SubscribeService, type: :service do
   describe '#call' do
     subject { described_class.new }
     let(:user_account) { Fabricate(:account) }
diff --git a/spec/services/pubsubhubbub/unsubscribe_service_spec.rb b/spec/services/pubsubhubbub/unsubscribe_service_spec.rb
index 59054ed99..7ed9fc5af 100644
--- a/spec/services/pubsubhubbub/unsubscribe_service_spec.rb
+++ b/spec/services/pubsubhubbub/unsubscribe_service_spec.rb
@@ -2,7 +2,7 @@
 
 require 'rails_helper'
 
-describe Pubsubhubbub::UnsubscribeService do
+describe Pubsubhubbub::UnsubscribeService, type: :service do
   describe '#call' do
     subject { described_class.new }
 
diff --git a/spec/services/reblog_service_spec.rb b/spec/services/reblog_service_spec.rb
index 19d3bb6cb..2755da772 100644
--- a/spec/services/reblog_service_spec.rb
+++ b/spec/services/reblog_service_spec.rb
@@ -1,6 +1,6 @@
 require 'rails_helper'
 
-RSpec.describe ReblogService do
+RSpec.describe ReblogService, type: :service do
   let(:alice)  { Fabricate(:account, username: 'alice') }
 
   context 'OStatus' do
diff --git a/spec/services/reject_follow_service_spec.rb b/spec/services/reject_follow_service_spec.rb
index bf49dd2c9..e5ac37ed9 100644
--- a/spec/services/reject_follow_service_spec.rb
+++ b/spec/services/reject_follow_service_spec.rb
@@ -1,6 +1,6 @@
 require 'rails_helper'
 
-RSpec.describe RejectFollowService do
+RSpec.describe RejectFollowService, type: :service do
   let(:sender) { Fabricate(:account, username: 'alice') }
 
   subject { RejectFollowService.new }
diff --git a/spec/services/remove_status_service_spec.rb b/spec/services/remove_status_service_spec.rb
index 5bb75b820..2134f51fd 100644
--- a/spec/services/remove_status_service_spec.rb
+++ b/spec/services/remove_status_service_spec.rb
@@ -1,6 +1,6 @@
 require 'rails_helper'
 
-RSpec.describe RemoveStatusService do
+RSpec.describe RemoveStatusService, type: :service do
   subject { RemoveStatusService.new }
 
   let!(:alice)  { Fabricate(:account) }
diff --git a/spec/services/report_service_spec.rb b/spec/services/report_service_spec.rb
index 2f926ef00..2c392d376 100644
--- a/spec/services/report_service_spec.rb
+++ b/spec/services/report_service_spec.rb
@@ -1,6 +1,6 @@
 require 'rails_helper'
 
-RSpec.describe ReportService do
+RSpec.describe ReportService, type: :service do
   subject { described_class.new }
 
   let(:source_account) { Fabricate(:account) }
diff --git a/spec/services/resolve_account_service_spec.rb b/spec/services/resolve_account_service_spec.rb
index 84dfe578a..f4c810f75 100644
--- a/spec/services/resolve_account_service_spec.rb
+++ b/spec/services/resolve_account_service_spec.rb
@@ -1,6 +1,6 @@
 require 'rails_helper'
 
-RSpec.describe ResolveAccountService do
+RSpec.describe ResolveAccountService, type: :service do
   subject { described_class.new }
 
   before do
diff --git a/spec/services/resolve_url_service_spec.rb b/spec/services/resolve_url_service_spec.rb
index 1e9be4c07..7bb5d1940 100644
--- a/spec/services/resolve_url_service_spec.rb
+++ b/spec/services/resolve_url_service_spec.rb
@@ -2,7 +2,7 @@
 
 require 'rails_helper'
 
-describe ResolveURLService do
+describe ResolveURLService, type: :service do
   subject { described_class.new }
 
   describe '#call' do
diff --git a/spec/services/search_service_spec.rb b/spec/services/search_service_spec.rb
index 957b60c7d..673de5233 100644
--- a/spec/services/search_service_spec.rb
+++ b/spec/services/search_service_spec.rb
@@ -2,7 +2,7 @@
 
 require 'rails_helper'
 
-describe SearchService do
+describe SearchService, type: :service do
   subject { described_class.new }
 
   describe '#call' do
diff --git a/spec/services/send_interaction_service_spec.rb b/spec/services/send_interaction_service_spec.rb
index ff08394b0..710d8184c 100644
--- a/spec/services/send_interaction_service_spec.rb
+++ b/spec/services/send_interaction_service_spec.rb
@@ -1,6 +1,6 @@
 require 'rails_helper'
 
-RSpec.describe SendInteractionService do
+RSpec.describe SendInteractionService, type: :service do
   subject { SendInteractionService.new }
 
   it 'sends an XML envelope to the Salmon end point of remote user'
diff --git a/spec/services/subscribe_service_spec.rb b/spec/services/subscribe_service_spec.rb
index 835be5ec5..10bdb1ba8 100644
--- a/spec/services/subscribe_service_spec.rb
+++ b/spec/services/subscribe_service_spec.rb
@@ -1,6 +1,6 @@
 require 'rails_helper'
 
-RSpec.describe SubscribeService do
+RSpec.describe SubscribeService, type: :service do
   let(:account) { Fabricate(:account, username: 'bob', domain: 'example.com', hub_url: 'http://hub.example.com') }
   subject { SubscribeService.new }
 
diff --git a/spec/services/suspend_account_service_spec.rb b/spec/services/suspend_account_service_spec.rb
index 1cb647e8d..fd303a9d5 100644
--- a/spec/services/suspend_account_service_spec.rb
+++ b/spec/services/suspend_account_service_spec.rb
@@ -1,6 +1,6 @@
 require 'rails_helper'
 
-RSpec.describe SuspendAccountService do
+RSpec.describe SuspendAccountService, type: :service do
   describe '#call' do
     subject do
       -> { described_class.new.call(account) }
diff --git a/spec/services/unblock_domain_service_spec.rb b/spec/services/unblock_domain_service_spec.rb
index c32e5d655..8e8893d63 100644
--- a/spec/services/unblock_domain_service_spec.rb
+++ b/spec/services/unblock_domain_service_spec.rb
@@ -2,7 +2,7 @@
 
 require 'rails_helper'
 
-describe UnblockDomainService do
+describe UnblockDomainService, type: :service do
   subject { described_class.new }
 
   describe 'call' do
diff --git a/spec/services/unblock_service_spec.rb b/spec/services/unblock_service_spec.rb
index ca7a6b77e..5835b912b 100644
--- a/spec/services/unblock_service_spec.rb
+++ b/spec/services/unblock_service_spec.rb
@@ -1,6 +1,6 @@
 require 'rails_helper'
 
-RSpec.describe UnblockService do
+RSpec.describe UnblockService, type: :service do
   let(:sender) { Fabricate(:account, username: 'alice') }
 
   subject { UnblockService.new }
diff --git a/spec/services/unfollow_service_spec.rb b/spec/services/unfollow_service_spec.rb
index 021e76782..c5914c818 100644
--- a/spec/services/unfollow_service_spec.rb
+++ b/spec/services/unfollow_service_spec.rb
@@ -1,6 +1,6 @@
 require 'rails_helper'
 
-RSpec.describe UnfollowService do
+RSpec.describe UnfollowService, type: :service do
   let(:sender) { Fabricate(:account, username: 'alice') }
 
   subject { UnfollowService.new }
diff --git a/spec/services/unmute_service_spec.rb b/spec/services/unmute_service_spec.rb
index 5dc971fb1..8463eb283 100644
--- a/spec/services/unmute_service_spec.rb
+++ b/spec/services/unmute_service_spec.rb
@@ -1,5 +1,5 @@
 require 'rails_helper'
 
-RSpec.describe UnmuteService do
+RSpec.describe UnmuteService, type: :service do
   subject { UnmuteService.new }
 end
diff --git a/spec/services/unsubscribe_service_spec.rb b/spec/services/unsubscribe_service_spec.rb
index 2a02f4c75..54d4b1b53 100644
--- a/spec/services/unsubscribe_service_spec.rb
+++ b/spec/services/unsubscribe_service_spec.rb
@@ -1,6 +1,6 @@
 require 'rails_helper'
 
-RSpec.describe UnsubscribeService do
+RSpec.describe UnsubscribeService, type: :service do
   let(:account) { Fabricate(:account, username: 'bob', domain: 'example.com', hub_url: 'http://hub.example.com') }
   subject { UnsubscribeService.new }
 
diff --git a/spec/services/update_remote_profile_service_spec.rb b/spec/services/update_remote_profile_service_spec.rb
index 64ec2dbbb..7ac3a809a 100644
--- a/spec/services/update_remote_profile_service_spec.rb
+++ b/spec/services/update_remote_profile_service_spec.rb
@@ -1,6 +1,6 @@
 require 'rails_helper'
 
-RSpec.describe UpdateRemoteProfileService do
+RSpec.describe UpdateRemoteProfileService, type: :service do
   let(:xml) { File.read(File.join(Rails.root, 'spec', 'fixtures', 'push', 'feed.atom')) }
 
   subject { UpdateRemoteProfileService.new }
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 66ac75ee0..903032937 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -1,4 +1,4 @@
-require 'rspec/retry'
+#require 'rspec/retry'
 require 'simplecov'
 
 GC.disable
@@ -12,8 +12,8 @@ end
 gc_counter = -1
 
 RSpec.configure do |config|
-  config.verbose_retry = true
-  config.display_try_failure_messages = true
+  #config.verbose_retry = true
+  #config.display_try_failure_messages = true
 
   config.expect_with :rspec do |expectations|
     expectations.include_chain_clauses_in_custom_matcher_descriptions = true
@@ -29,9 +29,9 @@ RSpec.configure do |config|
     end
   end
 
-  config.around :each do |ex|
-    ex.run_with_retry retry: 3
-  end
+  #config.around :each do |ex|
+  #  ex.run_with_retry retry: 3
+  #end
 
   config.before :suite do
     Chewy.strategy(:bypass)