diff --git a/Gemfile b/Gemfile
index 876bcfaabf96959c484ee951ad7fc7b9aded3bd7..556b40317a7eac823cb83b7229f511dcac87059a 100644
--- a/Gemfile
+++ b/Gemfile
@@ -23,6 +23,9 @@ gem 'grape-path-helpers', '~> 1.6.1'
 gem 'faraday', '~> 1.0'
 gem 'marginalia', '~> 1.10.0'
 
+# Authorization
+gem 'declarative_policy', '~> 1.0.0'
+
 # Authentication libraries
 gem 'devise', '~> 4.7.2'
 gem 'bcrypt', '~> 3.1', '>= 3.1.14'
diff --git a/Gemfile.lock b/Gemfile.lock
index 19fad573b63854c8cc31eb305ea7f446da5bc566..358d58cf5665cd7c9c388b83944c7c69c056c76b 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -244,6 +244,7 @@ GEM
       html-pipeline
     declarative (0.0.20)
     declarative-option (0.1.0)
+    declarative_policy (1.0.0)
     default_value_for (3.4.0)
       activerecord (>= 3.2.0, < 7.0)
     deprecation_toolkit (1.5.1)
@@ -1383,6 +1384,7 @@ DEPENDENCIES
   crystalball (~> 0.7.0)
   database_cleaner (~> 1.7.0)
   deckar01-task_list (= 2.3.1)
+  declarative_policy (~> 1.0.0)
   default_value_for (~> 3.4.0)
   deprecation_toolkit (~> 1.5.1)
   derailed_benchmarks
diff --git a/app/policies/nil_policy.rb b/app/policies/nil_policy.rb
deleted file mode 100644
index fc969f8cd05bbd3baa92b591195329d16de768c6..0000000000000000000000000000000000000000
--- a/app/policies/nil_policy.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-# frozen_string_literal: true
-
-class NilPolicy < BasePolicy
-  rule { default }.prevent_all
-end
diff --git a/config/initializers/declarative_policy.rb b/config/initializers/declarative_policy.rb
new file mode 100644
index 0000000000000000000000000000000000000000..492675848093587ca966cd924f68e5dcff70f159
--- /dev/null
+++ b/config/initializers/declarative_policy.rb
@@ -0,0 +1,5 @@
+# frozen_string_literal: true
+
+DeclarativePolicy.configure do
+  named_policy :global, ::GlobalPolicy
+end
diff --git a/lib/declarative_policy.rb b/lib/declarative_policy.rb
deleted file mode 100644
index bd1c121fe7923b640f8f6cf71dd1b62183432f88..0000000000000000000000000000000000000000
--- a/lib/declarative_policy.rb
+++ /dev/null
@@ -1,112 +0,0 @@
-# frozen_string_literal: true
-
-require_dependency 'declarative_policy/cache'
-require_dependency 'declarative_policy/condition'
-require_dependency 'declarative_policy/delegate_dsl'
-require_dependency 'declarative_policy/policy_dsl'
-require_dependency 'declarative_policy/rule_dsl'
-require_dependency 'declarative_policy/preferred_scope'
-require_dependency 'declarative_policy/rule'
-require_dependency 'declarative_policy/runner'
-require_dependency 'declarative_policy/step'
-
-require_dependency 'declarative_policy/base'
-
-module DeclarativePolicy
-  extend PreferredScope
-
-  CLASS_CACHE_MUTEX = Mutex.new
-  CLASS_CACHE_IVAR = :@__DeclarativePolicy_CLASS_CACHE
-
-  class << self
-    def policy_for(user, subject, opts = {})
-      cache = opts[:cache] || {}
-      key = Cache.policy_key(user, subject)
-
-      cache[key] ||=
-        # to avoid deadlocks in multi-threaded environment when
-        # autoloading is enabled, we allow concurrent loads,
-        # https://gitlab.com/gitlab-org/gitlab-foss/issues/48263
-        ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
-          class_for(subject).new(user, subject, opts)
-        end
-    end
-
-    def class_for(subject)
-      return GlobalPolicy if subject == :global
-      return NilPolicy if subject.nil?
-
-      subject = find_delegate(subject)
-
-      policy_class = class_for_class(subject.class)
-      raise "no policy for #{subject.class.name}" if policy_class.nil?
-
-      policy_class
-    end
-
-    def has_policy?(subject)
-      !class_for_class(subject.class).nil?
-    end
-
-    private
-
-    # This method is heavily cached because there are a lot of anonymous
-    # modules in play in a typical rails app, and #name performs quite
-    # slowly for anonymous classes and modules.
-    #
-    # See https://bugs.ruby-lang.org/issues/11119
-    #
-    # if the above bug is resolved, this caching could likely be removed.
-    def class_for_class(subject_class)
-      unless subject_class.instance_variable_defined?(CLASS_CACHE_IVAR)
-        CLASS_CACHE_MUTEX.synchronize do
-          # re-check in case of a race
-          break if subject_class.instance_variable_defined?(CLASS_CACHE_IVAR)
-
-          policy_class = compute_class_for_class(subject_class)
-          subject_class.instance_variable_set(CLASS_CACHE_IVAR, policy_class)
-        end
-      end
-
-      subject_class.instance_variable_get(CLASS_CACHE_IVAR)
-    end
-
-    def compute_class_for_class(subject_class)
-      if subject_class.respond_to?(:declarative_policy_class)
-        return subject_class.declarative_policy_class.constantize
-      end
-
-      subject_class.ancestors.each do |klass|
-        name = klass.name
-
-        next unless name
-
-        begin
-          policy_class = "#{name}Policy".constantize
-
-          # NOTE: the < operator here tests whether policy_class
-          # inherits from Base. We can't use #is_a? because that
-          # tests for *instances*, not *subclasses*.
-          return policy_class if policy_class < Base
-        rescue NameError
-          nil
-        end
-      end
-
-      nil
-    end
-
-    def find_delegate(subject)
-      seen = Set.new
-
-      while subject.respond_to?(:declarative_policy_delegate)
-        raise ArgumentError, "circular delegations" if seen.include?(subject.object_id)
-
-        seen << subject.object_id
-        subject = subject.declarative_policy_delegate
-      end
-
-      subject
-    end
-  end
-end
diff --git a/lib/declarative_policy/base.rb b/lib/declarative_policy/base.rb
deleted file mode 100644
index 49cbdd2aeb4e1cf7846ce0bc3c4b58596ec617c7..0000000000000000000000000000000000000000
--- a/lib/declarative_policy/base.rb
+++ /dev/null
@@ -1,354 +0,0 @@
-# frozen_string_literal: true
-
-module DeclarativePolicy
-  class Base
-    # A map of ability => list of rules together with :enable
-    # or :prevent actions. Used to look up which rules apply to
-    # a given ability. See Base.ability_map
-    class AbilityMap
-      attr_reader :map
-      def initialize(map = {})
-        @map = map
-      end
-
-      # This merge behavior is different than regular hashes - if both
-      # share a key, the values at that key are concatenated, rather than
-      # overridden.
-      def merge(other)
-        conflict_proc = proc { |key, my_val, other_val| my_val + other_val }
-        AbilityMap.new(@map.merge(other.map, &conflict_proc))
-      end
-
-      def actions(key)
-        @map[key] ||= []
-      end
-
-      def enable(key, rule)
-        actions(key) << [:enable, rule]
-      end
-
-      def prevent(key, rule)
-        actions(key) << [:prevent, rule]
-      end
-    end
-
-    class << self
-      # The `own_ability_map` vs `ability_map` distinction is used so that
-      # the data structure is properly inherited - with subclasses recursively
-      # merging their parent class.
-      #
-      # This pattern is also used for conditions, global_actions, and delegations.
-      def ability_map
-        if self == Base
-          own_ability_map
-        else
-          superclass.ability_map.merge(own_ability_map)
-        end
-      end
-
-      def own_ability_map
-        @own_ability_map ||= AbilityMap.new
-      end
-
-      # an inheritable map of conditions, by name
-      def conditions
-        if self == Base
-          own_conditions
-        else
-          superclass.conditions.merge(own_conditions)
-        end
-      end
-
-      def own_conditions
-        @own_conditions ||= {}
-      end
-
-      # a list of global actions, generated by `prevent_all`. these aren't
-      # stored in `ability_map` because they aren't indexed by a particular
-      # ability.
-      def global_actions
-        if self == Base
-          own_global_actions
-        else
-          superclass.global_actions + own_global_actions
-        end
-      end
-
-      def own_global_actions
-        @own_global_actions ||= []
-      end
-
-      # an inheritable map of delegations, indexed by name (which may be
-      # autogenerated)
-      def delegations
-        if self == Base
-          own_delegations
-        else
-          superclass.delegations.merge(own_delegations)
-        end
-      end
-
-      def own_delegations
-        @own_delegations ||= {}
-      end
-
-      # all the [rule, action] pairs that apply to a particular ability.
-      # we combine the specific ones looked up in ability_map with the global
-      # ones.
-      def configuration_for(ability)
-        ability_map.actions(ability) + global_actions
-      end
-
-      ### declaration methods ###
-
-      def delegate(name = nil, &delegation_block)
-        if name.nil?
-          @delegate_name_counter ||= 0
-          @delegate_name_counter += 1
-          name = :"anonymous_#{@delegate_name_counter}"
-        end
-
-        name = name.to_sym
-
-        if delegation_block.nil?
-          delegation_block = proc { @subject.__send__(name) } # rubocop:disable GitlabSecurity/PublicSend
-        end
-
-        own_delegations[name] = delegation_block
-      end
-
-      # Declare that the given abilities should not be read from delegates.
-      #
-      # This is useful if you have an ability that you want to define
-      # differently in a policy than in a delegated policy, but still want to
-      # delegate all other abilities.
-      #
-      # example:
-      #
-      #   delegate { @subect.parent }
-      #
-      #   overrides :drive_car, :watch_tv
-      #
-      def overrides(*names)
-        @overrides ||= [].to_set
-        @overrides.merge(names)
-      end
-
-      # Declares a rule, constructed using RuleDsl, and returns
-      # a PolicyDsl which is used for registering the rule with
-      # this class. PolicyDsl will call back into Base.enable_when,
-      # Base.prevent_when, and Base.prevent_all_when.
-      def rule(&block)
-        rule = RuleDsl.new(self).instance_eval(&block)
-        PolicyDsl.new(self, rule)
-      end
-
-      # A hash in which to store calls to `desc` and `with_scope`, etc.
-      def last_options
-        @last_options ||= {}.with_indifferent_access
-      end
-
-      # retrieve and zero out the previously set options (used in .condition)
-      def last_options!
-        last_options.tap { @last_options = nil }
-      end
-
-      # Declare a description for the following condition. Currently unused,
-      # but opens the potential for explaining to users why they were or were
-      # not able to do something.
-      def desc(description)
-        last_options[:description] = description
-      end
-
-      def with_options(opts = {})
-        last_options.merge!(opts)
-      end
-
-      def with_scope(scope)
-        with_options scope: scope
-      end
-
-      def with_score(score)
-        with_options score: score
-      end
-
-      # Declares a condition. It gets stored in `own_conditions`, and generates
-      # a query method based on the condition's name.
-      def condition(name, opts = {}, &value)
-        name = name.to_sym
-
-        opts = last_options!.merge(opts)
-        opts[:context_key] ||= self.name
-
-        condition = Condition.new(name, opts, &value)
-
-        self.own_conditions[name] = condition
-
-        define_method(:"#{name}?") { condition(name).pass? }
-      end
-
-      # These next three methods are mainly called from PolicyDsl,
-      # and are responsible for "inverting" the relationship between
-      # an ability and a rule. We store in `ability_map` a map of
-      # abilities to rules that affect them, together with a
-      # symbol indicating :prevent or :enable.
-      def enable_when(abilities, rule)
-        abilities.each { |a| own_ability_map.enable(a, rule) }
-      end
-
-      def prevent_when(abilities, rule)
-        abilities.each { |a| own_ability_map.prevent(a, rule) }
-      end
-
-      # we store global prevents (from `prevent_all`) separately,
-      # so that they can be combined into every decision made.
-      def prevent_all_when(rule)
-        own_global_actions << [:prevent, rule]
-      end
-    end
-
-    # A policy object contains a specific user and subject on which
-    # to compute abilities. For this reason it's sometimes called
-    # "context" within the framework.
-    #
-    # It also stores a reference to the cache, so it can be used
-    # to cache computations by e.g. ManifestCondition.
-    attr_reader :user, :subject
-    def initialize(user, subject, opts = {})
-      @user = user
-      @subject = subject
-      @cache = opts[:cache] || {}
-    end
-
-    # helper for checking abilities on this and other subjects
-    # for the current user.
-    def can?(ability, new_subject = :_self)
-      return allowed?(ability) if new_subject == :_self
-
-      policy_for(new_subject).allowed?(ability)
-    end
-
-    # This is the main entry point for permission checks. It constructs
-    # or looks up a Runner for the given ability and asks it if it passes.
-    def allowed?(*abilities)
-      abilities.all? { |a| runner(a).pass? }
-    end
-
-    # The inverse of #allowed?, used mainly in specs.
-    def disallowed?(*abilities)
-      abilities.all? { |a| !runner(a).pass? }
-    end
-
-    # computes the given ability and prints a helpful debugging output
-    # showing which
-    def debug(ability, *args)
-      runner(ability).debug(*args)
-    end
-
-    desc "Unknown user"
-    condition(:anonymous, scope: :user, score: 0) { @user.nil? }
-
-    desc "By default"
-    condition(:default, scope: :global, score: 0) { true }
-
-    def repr
-      subject_repr =
-        if @subject.respond_to?(:id)
-          "#{@subject.class.name}/#{@subject.id}"
-        else
-          @subject.inspect
-        end
-
-      user_repr =
-        if @user
-          @user.to_reference
-        else
-          "<anonymous>"
-        end
-
-      "(#{user_repr} : #{subject_repr})"
-    end
-
-    def inspect
-      "#<#{self.class.name} #{repr}>"
-    end
-
-    # returns a Runner for the given ability, capable of computing whether
-    # the ability is allowed. Runners are cached on the policy (which itself
-    # is cached on @cache), and caches its result. This is how we perform caching
-    # at the ability level.
-    def runner(ability)
-      ability = ability.to_sym
-      @runners ||= {}
-      @runners[ability] ||=
-        begin
-          own_runner = Runner.new(own_steps(ability))
-          if self.class.overrides.include?(ability)
-            own_runner
-          else
-            delegated_runners = delegated_policies.values.compact.map { |p| p.runner(ability) }
-            delegated_runners.inject(own_runner, &:merge_runner)
-          end
-        end
-    end
-
-    # Helpers for caching. Used by ManifestCondition in performing condition
-    # computation.
-    #
-    # NOTE we can't use ||= here because the value might be the
-    # boolean `false`
-    def cache(key)
-      return @cache[key] if cached?(key)
-
-      @cache[key] = yield
-    end
-
-    def cached?(key)
-      !@cache[key].nil?
-    end
-
-    # returns a ManifestCondition capable of computing itself. The computation
-    # will use our own @cache.
-    def condition(name)
-      name = name.to_sym
-      @_conditions ||= {}
-      @_conditions[name] ||=
-        begin
-          raise "invalid condition #{name}" unless self.class.conditions.key?(name)
-
-          ManifestCondition.new(self.class.conditions[name], self)
-        end
-    end
-
-    # used in specs - returns true if there is no possible way for any action
-    # to be allowed, determined only by the global :prevent_all rules.
-    def banned?
-      global_steps = self.class.global_actions.map { |(action, rule)| Step.new(self, rule, action) }
-      !Runner.new(global_steps).pass?
-    end
-
-    # A list of other policies that we've delegated to (see `Base.delegate`)
-    def delegated_policies
-      @delegated_policies ||= self.class.delegations.transform_values do |block|
-        new_subject = instance_eval(&block)
-
-        # never delegate to nil, as that would immediately prevent_all
-        next if new_subject.nil?
-
-        policy_for(new_subject)
-      end
-    end
-
-    def policy_for(other_subject)
-      DeclarativePolicy.policy_for(@user, other_subject, cache: @cache)
-    end
-
-    protected
-
-    # constructs steps that come from this policy and not from any delegations
-    def own_steps(ability)
-      rules = self.class.configuration_for(ability)
-      rules.map { |(action, rule)| Step.new(self, rule, action) }
-    end
-  end
-end
diff --git a/lib/declarative_policy/cache.rb b/lib/declarative_policy/cache.rb
deleted file mode 100644
index 13006e56454b43b6d6c2a43fef56cde4186dd885..0000000000000000000000000000000000000000
--- a/lib/declarative_policy/cache.rb
+++ /dev/null
@@ -1,39 +0,0 @@
-# frozen_string_literal: true
-
-module DeclarativePolicy
-  module Cache
-    class << self
-      def user_key(user)
-        return '<anonymous>' if user.nil?
-
-        id_for(user)
-      end
-
-      def policy_key(user, subject)
-        u = user_key(user)
-        s = subject_key(subject)
-        "/dp/policy/#{u}/#{s}"
-      end
-
-      def subject_key(subject)
-        return '<nil>' if subject.nil?
-        return subject.inspect if subject.is_a?(Symbol)
-
-        "#{subject.class.name}:#{id_for(subject)}"
-      end
-
-      private
-
-      def id_for(obj)
-        id =
-          begin
-            obj.id
-          rescue NoMethodError
-            nil
-          end
-
-        id || "##{obj.object_id}"
-      end
-    end
-  end
-end
diff --git a/lib/declarative_policy/condition.rb b/lib/declarative_policy/condition.rb
deleted file mode 100644
index b77f40b10939f90bc7c3f5aa37a272a669ef1c53..0000000000000000000000000000000000000000
--- a/lib/declarative_policy/condition.rb
+++ /dev/null
@@ -1,105 +0,0 @@
-# frozen_string_literal: true
-
-module DeclarativePolicy
-  # A Condition is the data structure that is created by the
-  # `condition` declaration on DeclarativePolicy::Base. It is
-  # more or less just a struct of the data passed to that
-  # declaration. It holds on to the block to be instance_eval'd
-  # on a context (instance of Base) later, via #compute.
-  class Condition
-    attr_reader :name, :description, :scope
-    attr_reader :manual_score
-    attr_reader :context_key
-    def initialize(name, opts = {}, &compute)
-      @name = name
-      @compute = compute
-      @scope = opts.fetch(:scope, :normal)
-      @description = opts.delete(:description)
-      @context_key = opts[:context_key]
-      @manual_score = opts.fetch(:score, nil)
-    end
-
-    def compute(context)
-      !!context.instance_eval(&@compute)
-    end
-
-    def key
-      "#{@context_key}/#{@name}"
-    end
-  end
-
-  # In contrast to a Condition, a ManifestCondition contains
-  # a Condition and a context object, and is capable of calculating
-  # a result itself. This is the return value of Base#condition.
-  class ManifestCondition
-    def initialize(condition, context)
-      @condition = condition
-      @context = context
-    end
-
-    # The main entry point - does this condition pass? We reach into
-    # the context's cache here so that we can share in the global
-    # cache (often RequestStore or similar).
-    def pass?
-      @context.cache(cache_key) { @condition.compute(@context) }
-    end
-
-    # Whether we've already computed this condition.
-    def cached?
-      @context.cached?(cache_key)
-    end
-
-    # This is used to score Rule::Condition. See Rule::Condition#score
-    # and Runner#steps_by_score for how scores are used.
-    #
-    # The number here is intended to represent, abstractly, how
-    # expensive it would be to calculate this condition.
-    #
-    # See #cache_key for info about @condition.scope.
-    def score
-      # If we've been cached, no computation is necessary.
-      return 0 if cached?
-
-      # Use the override from condition(score: ...) if present
-      return @condition.manual_score if @condition.manual_score
-
-      # Global scope rules are cheap due to max cache sharing
-      return 2 if  @condition.scope == :global
-
-      # "Normal" rules can't share caches with any other policies
-      return 16 if @condition.scope == :normal
-
-      # otherwise, we're :user or :subject scope, so it's 4 if
-      # the caller has declared a preference
-      return 4 if @condition.scope == DeclarativePolicy.preferred_scope
-
-      # and 8 for all other :user or :subject scope conditions.
-      8
-    end
-
-    private
-
-    # This method controls the caching for the condition. This is where
-    # the condition(scope: ...) option comes into play. Notice that
-    # depending on the scope, we may cache only by the user or only by
-    # the subject, resulting in sharing across different policy objects.
-    def cache_key
-      @cache_key ||=
-        case @condition.scope
-        when :normal  then "/dp/condition/#{@condition.key}/#{user_key},#{subject_key}"
-        when :user    then "/dp/condition/#{@condition.key}/#{user_key}"
-        when :subject then "/dp/condition/#{@condition.key}/#{subject_key}"
-        when :global  then "/dp/condition/#{@condition.key}"
-        else raise 'invalid scope'
-        end
-    end
-
-    def user_key
-      Cache.user_key(@context.user)
-    end
-
-    def subject_key
-      Cache.subject_key(@context.subject)
-    end
-  end
-end
diff --git a/lib/declarative_policy/delegate_dsl.rb b/lib/declarative_policy/delegate_dsl.rb
deleted file mode 100644
index 67e3429b6966253cd84239bb18ff9af99fafa5ec..0000000000000000000000000000000000000000
--- a/lib/declarative_policy/delegate_dsl.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-# frozen_string_literal: true
-
-module DeclarativePolicy
-  # Used when the name of a delegate is mentioned in
-  # the rule DSL.
-  class DelegateDsl
-    def initialize(rule_dsl, delegate_name)
-      @rule_dsl = rule_dsl
-      @delegate_name = delegate_name
-    end
-
-    def method_missing(msg, *args)
-      return super unless args.empty? && !block_given?
-
-      @rule_dsl.delegate(@delegate_name, msg)
-    end
-  end
-end
diff --git a/lib/declarative_policy/policy_dsl.rb b/lib/declarative_policy/policy_dsl.rb
deleted file mode 100644
index 69a2bbcc79eb852a33023c31c1e7806dfd941a80..0000000000000000000000000000000000000000
--- a/lib/declarative_policy/policy_dsl.rb
+++ /dev/null
@@ -1,46 +0,0 @@
-# frozen_string_literal: true
-
-module DeclarativePolicy
-  # The return value of a rule { ... } declaration.
-  # Can call back to register rules with the containing
-  # Policy class (context_class here). See Base.rule
-  #
-  # Note that the #policy method just performs an #instance_eval,
-  # which is useful for multiple #enable or #prevent calls.
-  #
-  # Also provides a #method_missing proxy to the context
-  # class's class methods, so that helper methods can be
-  # defined and used in a #policy { ... } block.
-  class PolicyDsl
-    def initialize(context_class, rule)
-      @context_class = context_class
-      @rule = rule
-    end
-
-    def policy(&block)
-      instance_eval(&block)
-    end
-
-    def enable(*abilities)
-      @context_class.enable_when(abilities, @rule)
-    end
-
-    def prevent(*abilities)
-      @context_class.prevent_when(abilities, @rule)
-    end
-
-    def prevent_all
-      @context_class.prevent_all_when(@rule)
-    end
-
-    def method_missing(msg, *args, &block)
-      return super unless @context_class.respond_to?(msg)
-
-      @context_class.__send__(msg, *args, &block) # rubocop:disable GitlabSecurity/PublicSend
-    end
-
-    def respond_to_missing?(msg)
-      @context_class.respond_to?(msg) || super
-    end
-  end
-end
diff --git a/lib/declarative_policy/preferred_scope.rb b/lib/declarative_policy/preferred_scope.rb
deleted file mode 100644
index 9e5120865932e92e9d9f04341cb931e496c449b1..0000000000000000000000000000000000000000
--- a/lib/declarative_policy/preferred_scope.rb
+++ /dev/null
@@ -1,31 +0,0 @@
-# frozen_string_literal: true
-
-module DeclarativePolicy
-  module PreferredScope
-    PREFERRED_SCOPE_KEY = :"DeclarativePolicy.preferred_scope"
-
-    def with_preferred_scope(scope)
-      old_scope = Thread.current[PREFERRED_SCOPE_KEY]
-      Thread.current[PREFERRED_SCOPE_KEY] = scope
-      yield
-    ensure
-      Thread.current[PREFERRED_SCOPE_KEY] = old_scope
-    end
-
-    def preferred_scope
-      Thread.current[PREFERRED_SCOPE_KEY]
-    end
-
-    def user_scope(&block)
-      with_preferred_scope(:user, &block)
-    end
-
-    def subject_scope(&block)
-      with_preferred_scope(:subject, &block)
-    end
-
-    def preferred_scope=(scope)
-      Thread.current[PREFERRED_SCOPE_KEY] = scope
-    end
-  end
-end
diff --git a/lib/declarative_policy/rule.rb b/lib/declarative_policy/rule.rb
deleted file mode 100644
index 964d35cde9e6ae59fb7f65ea576eb246b2b9c6a4..0000000000000000000000000000000000000000
--- a/lib/declarative_policy/rule.rb
+++ /dev/null
@@ -1,312 +0,0 @@
-# frozen_string_literal: true
-
-module DeclarativePolicy
-  module Rule
-    # A Rule is the object that results from the `rule` declaration,
-    # usually built using the DSL in `RuleDsl`. It is a basic logical
-    # combination of building blocks, and is capable of deciding,
-    # given a context (instance of DeclarativePolicy::Base) whether it
-    # passes or not. Note that this decision doesn't by itself know
-    # how that affects the actual ability decision - for that, a
-    # `Step` is used.
-    class Base
-      def self.make(*args)
-        new(*args).simplify
-      end
-
-      # true or false whether this rule passes.
-      # `context` is a policy - an instance of
-      # DeclarativePolicy::Base.
-      def pass?(context)
-        raise 'abstract'
-      end
-
-      # same as #pass? except refuses to do any I/O,
-      # returning nil if the result is not yet cached.
-      # used for accurately scoring And/Or
-      def cached_pass?(context)
-        raise 'abstract'
-      end
-
-      # abstractly, how long would it take to compute
-      # this rule? lower-scored rules are tried first.
-      def score(context)
-        raise 'abstract'
-      end
-
-      # unwrap double negatives and nested and/or
-      def simplify
-        self
-      end
-
-      # convenience combination methods
-      def or(other)
-        Or.make([self, other])
-      end
-
-      def and(other)
-        And.make([self, other])
-      end
-
-      def negate
-        Not.make(self)
-      end
-
-      alias_method :|, :or
-      alias_method :&, :and
-      alias_method :~@, :negate
-
-      def inspect
-        "#<Rule #{repr}>"
-      end
-    end
-
-    # A rule that checks a condition. This is the
-    # type of rule that results from a basic bareword
-    # in the rule dsl (see RuleDsl#method_missing).
-    class Condition < Base
-      def initialize(name)
-        @name = name
-      end
-
-      # we delegate scoring to the condition. See
-      # ManifestCondition#score.
-      def score(context)
-        context.condition(@name).score
-      end
-
-      # Let the ManifestCondition from the context
-      # decide whether we pass.
-      def pass?(context)
-        context.condition(@name).pass?
-      end
-
-      # returns nil unless it's already cached
-      def cached_pass?(context)
-        condition = context.condition(@name)
-        return unless condition.cached?
-
-        condition.pass?
-      end
-
-      def description(context)
-        context.class.conditions[@name].description
-      end
-
-      def repr
-        @name.to_s
-      end
-    end
-
-    # A rule constructed from DelegateDsl - using a condition from a
-    # delegated policy.
-    class DelegatedCondition < Base
-      # Internal use only - this is rescued each time it's raised.
-      MissingDelegate = Class.new(StandardError)
-
-      def initialize(delegate_name, name)
-        @delegate_name = delegate_name
-        @name = name
-      end
-
-      def delegated_context(context)
-        policy = context.delegated_policies[@delegate_name]
-        raise MissingDelegate if policy.nil?
-
-        policy
-      end
-
-      def score(context)
-        delegated_context(context).condition(@name).score
-      rescue MissingDelegate
-        0
-      end
-
-      def cached_pass?(context)
-        condition = delegated_context(context).condition(@name)
-        return unless condition.cached?
-
-        condition.pass?
-      rescue MissingDelegate
-        false
-      end
-
-      def pass?(context)
-        delegated_context(context).condition(@name).pass?
-      rescue MissingDelegate
-        false
-      end
-
-      def repr
-        "#{@delegate_name}.#{@name}"
-      end
-    end
-
-    # A rule constructed from RuleDsl#can?. Computes a different ability
-    # on the same subject.
-    class Ability < Base
-      attr_reader :ability
-      def initialize(ability)
-        @ability = ability
-      end
-
-      # We ask the ability's runner for a score
-      def score(context)
-        context.runner(@ability).score
-      end
-
-      def pass?(context)
-        context.allowed?(@ability)
-      end
-
-      def cached_pass?(context)
-        runner = context.runner(@ability)
-        return unless runner.cached?
-
-        runner.pass?
-      end
-
-      def description(context)
-        "User can #{@ability.inspect}"
-      end
-
-      def repr
-        "can?(#{@ability.inspect})"
-      end
-    end
-
-    # Logical `and`, containing a list of rules. Only passes
-    # if all of them do.
-    class And < Base
-      attr_reader :rules
-      def initialize(rules)
-        @rules = rules
-      end
-
-      def simplify
-        simplified_rules = @rules.flat_map do |rule|
-          simplified = rule.simplify
-          case simplified
-          when And then simplified.rules
-          else [simplified]
-          end
-        end
-
-        And.new(simplified_rules)
-      end
-
-      def score(context)
-        return 0 unless cached_pass?(context).nil?
-
-        # note that cached rules will have score 0 anyways.
-        @rules.map { |r| r.score(context) }.inject(0, :+)
-      end
-
-      def pass?(context)
-        # try to find a cached answer before
-        # checking in order
-        cached = cached_pass?(context)
-        return cached unless cached.nil?
-
-        @rules.all? { |r| r.pass?(context) }
-      end
-
-      def cached_pass?(context)
-        @rules.each do |rule|
-          pass = rule.cached_pass?(context)
-
-          return pass if pass.nil? || pass == false
-        end
-
-        true
-      end
-
-      def repr
-        "all?(#{rules.map(&:repr).join(', ')})"
-      end
-    end
-
-    # Logical `or`. Mirrors And.
-    class Or < Base
-      attr_reader :rules
-      def initialize(rules)
-        @rules = rules
-      end
-
-      def pass?(context)
-        cached = cached_pass?(context)
-        return cached unless cached.nil?
-
-        @rules.any? { |r| r.pass?(context) }
-      end
-
-      def simplify
-        simplified_rules = @rules.flat_map do |rule|
-          simplified = rule.simplify
-          case simplified
-          when Or then simplified.rules
-          else [simplified]
-          end
-        end
-
-        Or.new(simplified_rules)
-      end
-
-      def cached_pass?(context)
-        @rules.each do |rule|
-          pass = rule.cached_pass?(context)
-
-          return pass if pass.nil? || pass == true
-        end
-
-        false
-      end
-
-      def score(context)
-        return 0 unless cached_pass?(context).nil?
-
-        @rules.map { |r| r.score(context) }.inject(0, :+)
-      end
-
-      def repr
-        "any?(#{@rules.map(&:repr).join(', ')})"
-      end
-    end
-
-    class Not < Base
-      attr_reader :rule
-      def initialize(rule)
-        @rule = rule
-      end
-
-      def simplify
-        case @rule
-        when And then Or.new(@rule.rules.map(&:negate)).simplify
-        when Or then And.new(@rule.rules.map(&:negate)).simplify
-        when Not then @rule.rule.simplify
-        else Not.new(@rule.simplify)
-        end
-      end
-
-      def pass?(context)
-        !@rule.pass?(context)
-      end
-
-      def cached_pass?(context)
-        case @rule.cached_pass?(context)
-        when nil then nil
-        when true then false
-        when false then true
-        end
-      end
-
-      def score(context)
-        @rule.score(context)
-      end
-
-      def repr
-        "~#{@rule.repr}"
-      end
-    end
-  end
-end
diff --git a/lib/declarative_policy/rule_dsl.rb b/lib/declarative_policy/rule_dsl.rb
deleted file mode 100644
index 85da7f261fa5a125c2dbc582f287e16352e1f8b7..0000000000000000000000000000000000000000
--- a/lib/declarative_policy/rule_dsl.rb
+++ /dev/null
@@ -1,47 +0,0 @@
-# frozen_string_literal: true
-
-module DeclarativePolicy
-  # The DSL evaluation context inside rule { ... } blocks.
-  # Responsible for creating and combining Rule objects.
-  #
-  # See Base.rule
-  class RuleDsl
-    def initialize(context_class)
-      @context_class = context_class
-    end
-
-    def can?(ability)
-      Rule::Ability.new(ability)
-    end
-
-    def all?(*rules)
-      Rule::And.make(rules)
-    end
-
-    def any?(*rules)
-      Rule::Or.make(rules)
-    end
-
-    def none?(*rules)
-      ~Rule::Or.new(rules)
-    end
-
-    def cond(condition)
-      Rule::Condition.new(condition)
-    end
-
-    def delegate(delegate_name, condition)
-      Rule::DelegatedCondition.new(delegate_name, condition)
-    end
-
-    def method_missing(msg, *args)
-      return super unless args.empty? && !block_given?
-
-      if @context_class.delegations.key?(msg)
-        DelegateDsl.new(self, msg)
-      else
-        cond(msg.to_sym)
-      end
-    end
-  end
-end
diff --git a/lib/declarative_policy/runner.rb b/lib/declarative_policy/runner.rb
deleted file mode 100644
index 59588b4d84ea39f9039595343b02fc664f3ea549..0000000000000000000000000000000000000000
--- a/lib/declarative_policy/runner.rb
+++ /dev/null
@@ -1,196 +0,0 @@
-# frozen_string_literal: true
-
-module DeclarativePolicy
-  class Runner
-    class State
-      def initialize
-        @enabled = false
-        @prevented = false
-      end
-
-      def enable!
-        @enabled = true
-      end
-
-      def enabled?
-        @enabled
-      end
-
-      def prevent!
-        @prevented = true
-      end
-
-      def prevented?
-        @prevented
-      end
-
-      def pass?
-        !prevented? && enabled?
-      end
-    end
-
-    # a Runner contains a list of Steps to be run.
-    attr_reader :steps
-    def initialize(steps)
-      @steps = steps
-      @state = nil
-    end
-
-    # We make sure only to run any given Runner once,
-    # and just continue to use the resulting @state
-    # that's left behind.
-    def cached?
-      !!@state
-    end
-
-    # used by Rule::Ability. See #steps_by_score
-    def score
-      return 0 if cached?
-
-      steps.map(&:score).inject(0, :+)
-    end
-
-    def merge_runner(other)
-      Runner.new(@steps + other.steps)
-    end
-
-    # The main entry point, called for making an ability decision.
-    # See #run and DeclarativePolicy::Base#can?
-    def pass?
-      run unless cached?
-
-      @state.pass?
-    end
-
-    # see DeclarativePolicy::Base#debug
-    def debug(out = $stderr)
-      run(out)
-    end
-
-    private
-
-    def flatten_steps!
-      @steps = @steps.flat_map { |s| s.flattened(@steps) }
-    end
-
-    # This method implements the semantic of "one enable and no prevents".
-    # It relies on #steps_by_score for the main loop, and updates @state
-    # with the result of the step.
-    def run(debug = nil)
-      @state = State.new
-
-      steps_by_score do |step, score|
-        break if !debug && @state.prevented?
-
-        passed = nil
-        case step.action
-        when :enable then
-          # we only check :enable actions if they have a chance of
-          # changing the outcome - if no other rule has enabled or
-          # prevented.
-          unless @state.enabled? || @state.prevented?
-            passed = step.pass?
-            @state.enable! if passed
-          end
-
-          debug << inspect_step(step, score, passed) if debug
-        when :prevent then
-          # we only check :prevent actions if the state hasn't already
-          # been prevented.
-          unless @state.prevented?
-            passed = step.pass?
-            @state.prevent! if passed
-          end
-
-          debug << inspect_step(step, score, passed) if debug
-        else raise "invalid action #{step.action.inspect}"
-        end
-      end
-
-      @state
-    end
-
-    # This is the core spot where all those `#score` methods matter.
-    # It is critical for performance to run steps in the correct order,
-    # so that we don't compute expensive conditions (potentially n times
-    # if we're called on, say, a large list of users).
-    #
-    # In order to determine the cheapest step to run next, we rely on
-    # Step#score, which returns a numerical rating of how expensive
-    # it would be to calculate - the lower the better. It would be
-    # easy enough to statically sort by these scores, but we can do
-    # a little better - the scores are cache-aware (conditions that
-    # are already in the cache have score 0), which means that running
-    # a step can actually change the scores of other steps.
-    #
-    # So! The way we sort here involves re-scoring at every step. This
-    # is by necessity quadratic, but most of the time the number of steps
-    # will be low. But just in case, if the number of steps exceeds 50,
-    # we print a warning and fall back to a static sort.
-    #
-    # For each step, we yield the step object along with the computed score
-    # for debugging purposes.
-    def steps_by_score
-      flatten_steps!
-
-      if @steps.size > 50
-        warn "DeclarativePolicy: large number of steps (#{steps.size}), falling back to static sort"
-
-        @steps.map { |s| [s.score, s] }.sort_by { |(score, _)| score }.each do |(score, step)|
-          yield step, score
-        end
-
-        return
-      end
-
-      remaining_steps = Set.new(@steps)
-      remaining_enablers, remaining_preventers = remaining_steps.partition(&:enable?).map { |s| Set.new(s) }
-
-      loop do
-        if @state.enabled?
-          # Once we set this, we never need to unset it, because a single
-          # prevent will stop this from being enabled
-          remaining_steps = remaining_preventers
-        else
-          # if the permission hasn't yet been enabled and we only have
-          # prevent steps left, we short-circuit the state here
-          @state.prevent! if remaining_enablers.empty?
-        end
-
-        return if remaining_steps.empty?
-
-        lowest_score = Float::INFINITY
-        next_step = nil
-
-        remaining_steps.each do |step|
-          score = step.score
-
-          if score < lowest_score
-            next_step = step
-            lowest_score = score
-          end
-
-          break if lowest_score == 0
-        end
-
-        [remaining_steps, remaining_enablers, remaining_preventers].each do |set|
-          set.delete(next_step)
-        end
-
-        yield next_step, lowest_score
-      end
-    end
-
-    # Formatter for debugging output.
-    def inspect_step(step, original_score, passed)
-      symbol =
-        case passed
-        when true then '+'
-        when false then '-'
-        when nil then ' '
-        end
-
-      "#{symbol} [#{original_score.to_i}] #{step.repr}\n"
-    end
-  end
-end
diff --git a/lib/declarative_policy/step.rb b/lib/declarative_policy/step.rb
deleted file mode 100644
index c289c17cc19ab4cea2be69947d8e95efa246ec05..0000000000000000000000000000000000000000
--- a/lib/declarative_policy/step.rb
+++ /dev/null
@@ -1,88 +0,0 @@
-# frozen_string_literal: true
-
-module DeclarativePolicy
-  # This object represents one step in the runtime decision of whether
-  # an ability is allowed. It contains a Rule and a context (instance
-  # of DeclarativePolicy::Base), which contains the user, the subject,
-  # and the cache. It also contains an "action", which is the symbol
-  # :prevent or :enable.
-  class Step
-    attr_reader :context, :rule, :action
-    def initialize(context, rule, action)
-      @context = context
-      @rule = rule
-      @action = action
-    end
-
-    # In the flattening process, duplicate steps may be generated in the
-    # same rule. This allows us to eliminate those (see Runner#steps_by_score
-    # and note its use of a Set)
-    def ==(other)
-      @context == other.context && @rule == other.rule && @action == other.action
-    end
-
-    # In the runner, steps are sorted dynamically by score, so that
-    # we are sure to compute them in close to the optimal order.
-    #
-    # See also Rule#score, ManifestCondition#score, and Runner#steps_by_score.
-    def score
-      # we slightly prefer the preventative actions
-      # since they are more likely to short-circuit
-      case @action
-      when :prevent
-        @rule.score(@context) * (7.0 / 8)
-      when :enable
-        @rule.score(@context)
-      end
-    end
-
-    def with_action(action)
-      Step.new(@context, @rule, action)
-    end
-
-    def enable?
-      @action == :enable
-    end
-
-    def prevent?
-      @action == :prevent
-    end
-
-    # This rather complex method allows us to split rules into parts so that
-    # they can be sorted independently for better optimization
-    def flattened(roots)
-      case @rule
-      when Rule::Or
-        # A single `Or` step is the same as each of its elements as separate steps
-        @rule.rules.flat_map { |r| Step.new(@context, r, @action).flattened(roots) }
-      when Rule::Ability
-        # This looks like a weird micro-optimization but it buys us quite a lot
-        # in some cases. If we depend on an Ability (i.e. a `can?(...)` rule),
-        # and that ability *only* has :enable actions (modulo some actions that
-        # we already have taken care of), then its rules can be safely inlined.
-        steps = @context.runner(@rule.ability).steps.reject { |s| roots.include?(s) }
-
-        if steps.all?(&:enable?)
-          # in the case that we are a :prevent step, each inlined step becomes
-          # an independent :prevent, even though it was an :enable in its initial
-          # context.
-          steps.map! { |s| s.with_action(:prevent) } if prevent?
-
-          steps.flat_map { |s| s.flattened(roots) }
-        else
-          [self]
-        end
-      else
-        [self]
-      end
-    end
-
-    def pass?
-      @rule.pass?(@context)
-    end
-
-    def repr
-      "#{@action} when #{@rule.repr} (#{@context.repr})"
-    end
-  end
-end
diff --git a/spec/lib/declarative_policy/overrides_spec.rb b/spec/lib/declarative_policy/overrides_spec.rb
deleted file mode 100644
index 84dc8f7ac71180e4c01866fd4e9e47fed25742f8..0000000000000000000000000000000000000000
--- a/spec/lib/declarative_policy/overrides_spec.rb
+++ /dev/null
@@ -1,82 +0,0 @@
-# frozen_string_literal: true
-
-require 'fast_spec_helper'
-require_dependency 'rspec-parameterized'
-
-RSpec.describe 'DeclarativePolicy overrides' do
-  let(:foo_policy) do
-    Class.new(DeclarativePolicy::Base) do
-      condition(:foo_prop_cond) { @subject.foo_prop }
-
-      rule { foo_prop_cond }.policy do
-        enable :common_ability
-        enable :foo_prop_ability
-      end
-    end
-  end
-
-  let(:bar_policy) do
-    Class.new(DeclarativePolicy::Base) do
-      delegate { @subject.foo }
-
-      overrides :common_ability
-
-      condition(:bar_prop_cond) { @subject.bar_prop }
-
-      rule { bar_prop_cond }.policy do
-        enable :common_ability
-        enable :bar_prop_ability
-      end
-
-      rule { bar_prop_cond & can?(:foo_prop_ability) }.policy do
-        enable :combined_ability
-      end
-    end
-  end
-
-  before do
-    stub_const('Foo', Struct.new(:foo_prop))
-    stub_const('FooPolicy', foo_policy)
-    stub_const('Bar', Struct.new(:foo, :bar_prop))
-    stub_const('BarPolicy', bar_policy)
-  end
-
-  where(:foo_prop, :bar_prop) do
-    [
-      [true, true],
-      [true, false],
-      [false, true],
-      [false, false]
-    ]
-  end
-
-  with_them do
-    let(:foo) { Foo.new(foo_prop) }
-    let(:bar) { Bar.new(foo, bar_prop) }
-
-    it 'determines the correct bar_prop_ability (non-delegated) permissions for bar' do
-      policy = DeclarativePolicy.policy_for(nil, bar)
-      expect(policy.allowed?(:bar_prop_ability)).to eq(bar_prop)
-    end
-
-    it 'determines the correct foo_prop (non-overridden) permissions for bar' do
-      policy = DeclarativePolicy.policy_for(nil, bar)
-      expect(policy.allowed?(:foo_prop_ability)).to eq(foo_prop)
-    end
-
-    it 'determines the correct common_ability (overridden) permissions for bar' do
-      policy = DeclarativePolicy.policy_for(nil, bar)
-      expect(policy.allowed?(:common_ability)).to eq(bar_prop)
-    end
-
-    it 'determines the correct common_ability permissions for foo' do
-      policy = DeclarativePolicy.policy_for(nil, foo)
-      expect(policy.allowed?(:common_ability)).to eq(foo_prop)
-    end
-
-    it 'allows combinations of overridden and inherited values' do
-      policy = DeclarativePolicy.policy_for(nil, bar)
-      expect(policy.allowed?(:combined_ability)).to eq(foo_prop && bar_prop)
-    end
-  end
-end
diff --git a/spec/lib/declarative_policy_spec.rb b/spec/lib/declarative_policy_spec.rb
deleted file mode 100644
index fc21bd43f489eb5c5f91c7271be28597882159ad..0000000000000000000000000000000000000000
--- a/spec/lib/declarative_policy_spec.rb
+++ /dev/null
@@ -1,38 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe DeclarativePolicy do
-  describe '.class_for' do
-    it 'uses declarative_policy_class if present' do
-      instance = Gitlab::ErrorTracking::ErrorEvent.new
-
-      expect(described_class.class_for(instance)).to eq(ErrorTracking::BasePolicy)
-    end
-
-    it 'infers policy class from name' do
-      instance = PersonalSnippet.new
-
-      expect(described_class.class_for(instance)).to eq(PersonalSnippetPolicy)
-    end
-
-    it 'raises error if not found' do
-      instance = Object.new
-
-      expect { described_class.class_for(instance) }.to raise_error('no policy for Object')
-    end
-
-    context 'when found policy class does not inherit base' do
-      before do
-        stub_const('Foo', Class.new)
-        stub_const('FooPolicy', Class.new)
-      end
-
-      it 'raises error if inferred class does not inherit Base' do
-        instance = Foo.new
-
-        expect { described_class.class_for(instance) }.to raise_error('no policy for Foo')
-      end
-    end
-  end
-end