declarative_policy 2.0.1 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 704af6c0500c00a0e6c6797dd5b496c098db86f33ccc1ef2496be37e43b33e03
4
- data.tar.gz: 2236adb02dbee28b6565fc72541fe2fbee5a087ef15cc2fe1f0d060ce1eece13
3
+ metadata.gz: 735e59059f970e9e39c6fc37b1d97f7b20c030a472ca287cb95d4503a168c3a3
4
+ data.tar.gz: e4bd527d1ab32d86a77666e9b0c175a2e5c1cc8d4627b94fa3d01eb8c6dc6e10
5
5
  SHA512:
6
- metadata.gz: c3841495e1922ae72f704524e40ca080da4838cfe075d401350212f5b31f116f6003aa3e371d2a0084a11986b4745bb82946ad84b61247a19b59470a26e24755
7
- data.tar.gz: f3f0d97ba2b9e56079d5307c135ec2b15c73759661f32e6cda2db44ab0fa21002598725cf0c762ca85c0580ae1c5b7c397115f185ec94c9da600cfaf19f6e590
6
+ metadata.gz: 65796bc8d62ce7648433adba6feb99e78e2fff32c27902318a8394973fad38a8b7a476b8a13806178c8d4b20f8f5385e398336c2872a6c58122d213c8d864fbb
7
+ data.tar.gz: 4ab6e8f55e9efadbf2169bd4b385ec8620fdca8247cfcc1b977fc3ac5fb02f045fb1616caef9878fd0e92f37f6a98c0a58b04f458c9b2c67aec98d4b22545b99
data/CHANGELOG.md CHANGED
@@ -1,3 +1,5 @@
1
+ Starting from version 2.0, changelog entries are tracked via https://gitlab.com/gitlab-org/ruby/gems/declarative-policy/-/releases
2
+
1
3
  2.0.0:
2
4
 
3
5
  - Drop explicit support for Ruby 2.6 and 2.7 by removing those versions from
data/README.md CHANGED
@@ -202,6 +202,20 @@ https://gitlab.com/gitlab-org/ruby/gems/declarative-policy. This project is inte
202
202
  a safe, welcoming space for collaboration, and contributors are expected to
203
203
  adhere to the [GitLab code of conduct](https://about.gitlab.com/community/contribute/code-of-conduct/).
204
204
 
205
+ ## Release process
206
+
207
+ We release `declarative_policy` on an ad-hoc basis. There is no regularity to when
208
+ we release, we just release when we make a change - no matter the size of the
209
+ change.
210
+
211
+ To release a new version:
212
+
213
+ 1. Create a Merge Request.
214
+ 1. Use Merge Request template [Release.md](https://gitlab.com/gitlab-org/ruby/gems/declarative-policy/-/blob/main/.gitlab/merge_request_templates/Release.md).
215
+ 1. Follow the instructions.
216
+ 1. After the Merge Request has been merged, a new gem version is [published automatically](https://gitlab.com/gitlab-org/components/gem-release).
217
+ 1. Once the new gem version is visible on [RubyGems.org](https://rubygems.org/gems/declarative_policy), it is recommended to update [GitLab's `Gemfile`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/Gemfile) to bump the `declarative_policy` Ruby gem to the new version also.
218
+
205
219
  ## License
206
220
 
207
221
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -210,4 +224,4 @@ The gem is available as open source under the terms of the [MIT License](https:/
210
224
 
211
225
  Everyone interacting in the `DeclarativePolicy` project's codebase, issue
212
226
  trackers, chat rooms and mailing lists is expected to follow
213
- the [code of conduct](https://github.com/[USERNAME]/declarative-policy/blob/master/CODE_OF_CONDUCT.md).
227
+ the [code of conduct](https://gitlab.com/gitlab-org/ruby/gems/declarative-policy/blob/main/CODE_OF_CONDUCT.md).
@@ -23,7 +23,7 @@ Gem::Specification.new do |spec|
23
23
 
24
24
  spec.metadata['homepage_uri'] = spec.homepage
25
25
  spec.metadata['source_code_uri'] = 'https://gitlab.com/gitlab-org/ruby/gems/declarative-policy'
26
- spec.metadata['changelog_uri'] = 'https://gitlab.com/gitlab-org/ruby/gems/declarative-policy/-/blob/main/CHANGELOG.md'
26
+ spec.metadata['changelog_uri'] = 'https://gitlab.com/gitlab-org/ruby/gems/declarative-policy/-/releases'
27
27
 
28
28
  spec.metadata['rubygems_mfa_required'] = 'false'
29
29
 
@@ -124,6 +124,42 @@ rule { old_enough_to_drive }.policy do
124
124
  end
125
125
  ```
126
126
 
127
+ #### `prevent_all`
128
+
129
+ To prevent all abilities at once, use `prevent_all`:
130
+
131
+ ```ruby
132
+ rule { banned }.prevent_all
133
+ ```
134
+
135
+ This is equivalent to adding a `prevent` for every ability in the policy. It is
136
+ commonly used to deny all access when a precondition fails (e.g. a user is
137
+ suspended or a resource is locked).
138
+
139
+ #### `prevent_all` with exceptions
140
+
141
+ To prevent all abilities **except** specific ones, pass a block with `except`
142
+ declarations:
143
+
144
+ ```ruby
145
+ rule { suspended }.prevent_all do
146
+ except :read
147
+ except :appeal_suspension
148
+ end
149
+ ```
150
+
151
+ Multiple abilities can also be listed in a single `except` call:
152
+
153
+ ```ruby
154
+ rule { suspended }.prevent_all do
155
+ except :read, :list, :appeal_suspension
156
+ end
157
+ ```
158
+
159
+ Excepted abilities are excluded from the blanket prevent and follow normal
160
+ enable/prevent evaluation. Non-excepted abilities are prevented when the rule's
161
+ condition holds, regardless of any `enable` rules.
162
+
127
163
  Rule blocks do not have access to the internal state of the policy, and cannot
128
164
  access the `@user` or `@subject`, or any methods on the policy instance. You
129
165
  should not perform I/O in a rule. They exist solely to define the logical rules
@@ -6,6 +6,8 @@ module DeclarativePolicy
6
6
  # or :prevent actions. Used to look up which rules apply to
7
7
  # a given ability. See Base.ability_map
8
8
  class AbilityMap
9
+ EMPTY_ACTIONS = [].freeze
10
+
9
11
  attr_reader :map
10
12
 
11
13
  def initialize(map = {})
@@ -20,16 +22,31 @@ module DeclarativePolicy
20
22
  AbilityMap.new(@map.merge(other.map, &conflict_proc))
21
23
  end
22
24
 
25
+ # Does not add the ability if it is missing. An application may check
26
+ # abilities named by user input, and storing those would grow the map
27
+ # without limit.
23
28
  def actions(key)
24
- @map[key] ||= []
29
+ @map[key] || EMPTY_ACTIONS
30
+ end
31
+
32
+ def key?(key)
33
+ @map.key?(key)
25
34
  end
26
35
 
27
36
  def enable(key, rule)
28
- actions(key) << [:enable, rule]
37
+ rules_for(key) << [:enable, rule]
29
38
  end
30
39
 
31
40
  def prevent(key, rule)
32
- actions(key) << [:prevent, rule]
41
+ rules_for(key) << [:prevent, rule]
42
+ end
43
+
44
+ private
45
+
46
+ # The writer's counterpart to #actions: this one does create the list,
47
+ # because a rule has to be appended to something.
48
+ def rules_for(key)
49
+ @map[key] ||= []
33
50
  end
34
51
  end
35
52
 
@@ -51,18 +68,25 @@ module DeclarativePolicy
51
68
  end
52
69
  end
53
70
 
71
+ # Counts declarations across all policies, so the caches below can tell
72
+ # that something was declared since they were built. Without it a policy
73
+ # loaded or reopened later would be seen with an out of date set of rules.
74
+ @definition_generation = 0
75
+
54
76
  class << self
55
77
  # The `own_ability_map` vs `ability_map` distinction is used so that
56
78
  # the data structure is properly inherited - with subclasses recursively
57
79
  # merging their parent class.
58
80
  #
59
81
  # This pattern is also used for conditions, global_actions, and delegations.
82
+ #
83
+ # The combined result is cached because rebuilding it is expensive, and
84
+ # every policy object would otherwise redo it, once per subject when
85
+ # authorizing a batch.
60
86
  def ability_map
61
- if self == Base
62
- own_ability_map
63
- else
64
- superclass.ability_map.merge(own_ability_map)
65
- end
87
+ return own_ability_map if self == Base
88
+
89
+ memoized_definition(:ability_map) { superclass.ability_map.merge(own_ability_map) }
66
90
  end
67
91
 
68
92
  def own_ability_map
@@ -71,11 +95,9 @@ module DeclarativePolicy
71
95
 
72
96
  # an inheritable map of conditions, by name
73
97
  def conditions
74
- if self == Base
75
- own_conditions
76
- else
77
- superclass.conditions.merge(own_conditions)
78
- end
98
+ return own_conditions if self == Base
99
+
100
+ memoized_definition(:conditions) { superclass.conditions.merge(own_conditions) }
79
101
  end
80
102
 
81
103
  def own_conditions
@@ -86,11 +108,9 @@ module DeclarativePolicy
86
108
  # stored in `ability_map` because they aren't indexed by a particular
87
109
  # ability.
88
110
  def global_actions
89
- if self == Base
90
- own_global_actions
91
- else
92
- superclass.global_actions + own_global_actions
93
- end
111
+ return own_global_actions if self == Base
112
+
113
+ memoized_definition(:global_actions) { superclass.global_actions + own_global_actions }
94
114
  end
95
115
 
96
116
  def own_global_actions
@@ -100,22 +120,37 @@ module DeclarativePolicy
100
120
  # an inheritable map of delegations, indexed by name (which may be
101
121
  # autogenerated)
102
122
  def delegations
103
- if self == Base
104
- own_delegations
105
- else
106
- superclass.delegations.merge(own_delegations)
107
- end
123
+ return own_delegations if self == Base
124
+
125
+ memoized_definition(:delegations) { superclass.delegations.merge(own_delegations) }
108
126
  end
109
127
 
110
128
  def own_delegations
111
129
  @own_delegations ||= {}
112
130
  end
113
131
 
132
+ # How many declarations (conditions, rules, delegations) have been made
133
+ # across all policies so far. Save this value and compare it later to
134
+ # tell whether any policy has been defined or reopened since.
135
+ def definition_generation
136
+ Base.instance_variable_get(:@definition_generation)
137
+ end
138
+
139
+ # Clears the cached results for every policy class. Declaration methods
140
+ # like condition and enable_when call this for you; call it yourself
141
+ # only if you write to own_conditions and friends directly.
142
+ def invalidate_definitions!
143
+ Base.instance_variable_set(:@definition_generation, definition_generation + 1)
144
+ end
145
+
114
146
  # all the [rule, action] pairs that apply to a particular ability.
115
147
  # we combine the specific ones looked up in ability_map with the global
116
- # ones.
148
+ # ones, filtering out any global actions that have excepted this ability.
117
149
  def configuration_for(ability)
118
- ability_map.actions(ability) + global_actions
150
+ return build_configuration_for(ability) unless ability_map.key?(ability)
151
+
152
+ configurations = memoized_definition(:configurations) { {} }
153
+ configurations[ability] ||= build_configuration_for(ability)
119
154
  end
120
155
 
121
156
  ### declaration methods ###
@@ -134,6 +169,7 @@ module DeclarativePolicy
134
169
  # rubocop: enable GitlabSecurity/PublicSend
135
170
 
136
171
  own_delegations[name] = delegation_block
172
+ invalidate_definitions!
137
173
  end
138
174
 
139
175
  # Declare that the given abilities should not be read from delegates.
@@ -196,6 +232,7 @@ module DeclarativePolicy
196
232
  condition = Condition.new(condition_name, condition_options(opts), &value)
197
233
 
198
234
  own_conditions[condition_name] = condition
235
+ invalidate_definitions!
199
236
 
200
237
  define_method(:"#{condition_name}?") { condition(condition_name).pass? }
201
238
  end
@@ -207,20 +244,51 @@ module DeclarativePolicy
207
244
  # symbol indicating :prevent or :enable.
208
245
  def enable_when(abilities, rule)
209
246
  abilities.each { |a| own_ability_map.enable(a, rule) }
247
+ invalidate_definitions!
210
248
  end
211
249
 
212
250
  def prevent_when(abilities, rule)
213
251
  abilities.each { |a| own_ability_map.prevent(a, rule) }
252
+ invalidate_definitions!
214
253
  end
215
254
 
216
255
  # we store global prevents (from `prevent_all`) separately,
217
256
  # so that they can be combined into every decision made.
218
- def prevent_all_when(rule)
219
- own_global_actions << [:prevent, rule]
257
+ # The optional `except` parameter is a Set of abilities
258
+ # that should be excluded from the blanket prevent.
259
+ def prevent_all_when(rule, except: nil)
260
+ own_global_actions << [:prevent, rule, except]
261
+ invalidate_definitions!
220
262
  end
221
263
 
222
264
  private
223
265
 
266
+ def build_configuration_for(ability)
267
+ applicable_globals = global_actions.filter_map do |(action, rule, exceptions)|
268
+ next if exceptions&.include?(ability)
269
+
270
+ [action, rule]
271
+ end
272
+
273
+ (ability_map.actions(ability) + applicable_globals).freeze
274
+ end
275
+
276
+ # Caches this class's combined result (its own declarations plus its
277
+ # superclass's) until any policy anywhere declares something new.
278
+ def memoized_definition(name)
279
+ generation = Base.definition_generation
280
+
281
+ unless @memoized_definitions_at == generation
282
+ @definitions = {}
283
+ @memoized_definitions_at = generation
284
+ end
285
+
286
+ defs = @definitions
287
+ return defs[name] if defs.key?(name)
288
+
289
+ defs.store(name, yield)
290
+ end
291
+
224
292
  # retrieve and zero out the previously set options (used in .condition)
225
293
  def condition_options(opts)
226
294
  # The context_key distinguishes two conditions of the same name.
@@ -255,6 +323,8 @@ module DeclarativePolicy
255
323
  # This is the main entry point for permission checks. It constructs
256
324
  # or looks up a Runner for the given ability and asks it if it passes.
257
325
  def allowed?(*abilities)
326
+ return false if abilities.empty?
327
+
258
328
  abilities.all? { |a| runner(a).pass? }
259
329
  end
260
330
 
@@ -343,16 +413,34 @@ module DeclarativePolicy
343
413
  @_conditions ||= {}
344
414
  @_conditions[name] ||=
345
415
  begin
346
- raise "invalid condition #{name}" unless self.class.conditions.key?(name)
416
+ definition = self.class.conditions[name]
417
+ raise "invalid condition #{name}" unless definition
347
418
 
348
- ManifestCondition.new(self.class.conditions[name], self)
419
+ ManifestCondition.new(definition, self)
349
420
  end
350
421
  end
351
422
 
423
+ def user_cache_key
424
+ @user_cache_key ||= Cache.user_key(@user)
425
+ end
426
+
427
+ def subject_cache_key
428
+ @subject_cache_key ||= Cache.subject_key(@subject)
429
+ end
430
+
352
431
  # used in specs - returns true if there is no possible way for any action
353
432
  # to be allowed, determined only by the global :prevent_all rules.
433
+ # Only considers global actions with no exceptions (a prevent_all with
434
+ # exceptions cannot fully ban, since the excepted abilities may still pass).
354
435
  def banned?
355
- global_steps = self.class.global_actions.map { |(action, rule)| Step.new(self, rule, action) }
436
+ global_steps = self.class.global_actions.filter_map do |(action, rule, exceptions)|
437
+ next if exceptions && !exceptions.empty?
438
+
439
+ Step.new(self, rule, action)
440
+ end
441
+
442
+ return false if global_steps.empty?
443
+
356
444
  !Runner.new(global_steps).pass?
357
445
  end
358
446
 
@@ -7,7 +7,7 @@ module DeclarativePolicy
7
7
  # declaration. It holds on to the block to be instance_eval'd
8
8
  # on a context (instance of Base) later, via #compute.
9
9
  class Condition
10
- attr_reader :name, :description, :scope, :manual_score, :context_key
10
+ attr_reader :name, :description, :scope, :manual_score, :context_key, :key
11
11
 
12
12
  VALID_SCOPES = %i[user_and_subject user subject global].freeze
13
13
  ALLOWED_SCOPES = VALID_SCOPES + %i[normal]
@@ -20,16 +20,13 @@ module DeclarativePolicy
20
20
  @description = opts.delete(:description)
21
21
  @context_key = opts[:context_key]
22
22
  @manual_score = opts.fetch(:score, nil)
23
+ @key = "#{@context_key}/#{@name}".freeze
23
24
  end
24
25
 
25
26
  def compute(context)
26
27
  !!context.instance_eval(&@compute)
27
28
  end
28
29
 
29
- def key
30
- "#{@context_key}/#{@name}"
31
- end
32
-
33
30
  private
34
31
 
35
32
  def fetch_scope(options)
@@ -107,17 +104,17 @@ module DeclarativePolicy
107
104
  when :user then "/dp/condition/#{@condition.key}/#{user_key}"
108
105
  when :subject then "/dp/condition/#{@condition.key}/#{subject_key}"
109
106
  else "/dp/condition/#{@condition.key}/#{user_key},#{subject_key}"
110
- end
107
+ end.freeze
111
108
  end
112
109
 
113
110
  private
114
111
 
115
112
  def user_key
116
- Cache.user_key(@context.user)
113
+ @context.user_cache_key
117
114
  end
118
115
 
119
116
  def subject_key
120
- Cache.subject_key(@context.subject)
117
+ @context.subject_cache_key
121
118
  end
122
119
  end
123
120
  end
@@ -29,8 +29,14 @@ module DeclarativePolicy
29
29
  @context_class.prevent_when(abilities, @rule)
30
30
  end
31
31
 
32
- def prevent_all
33
- @context_class.prevent_all_when(@rule)
32
+ def prevent_all(&block)
33
+ if block
34
+ dsl = PreventAllDsl.new
35
+ dsl.instance_eval(&block)
36
+ @context_class.prevent_all_when(@rule, except: dsl.exceptions)
37
+ else
38
+ @context_class.prevent_all_when(@rule)
39
+ end
34
40
  end
35
41
 
36
42
  def method_missing(msg, ...)
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'set'
4
+
5
+ module DeclarativePolicy
6
+ # A small DSL class used within a prevent_all { ... } block
7
+ # to capture exception abilities.
8
+ #
9
+ # Usage:
10
+ # rule { some_condition }.prevent_all do
11
+ # except :read
12
+ # except :list
13
+ # end
14
+ class PreventAllDsl
15
+ attr_reader :exceptions
16
+
17
+ def initialize
18
+ @exceptions = Set.new
19
+ end
20
+
21
+ def except(*abilities)
22
+ @exceptions.merge(abilities)
23
+ end
24
+ end
25
+ end
@@ -5,12 +5,10 @@ require 'set'
5
5
  module DeclarativePolicy
6
6
  class Runner
7
7
  class State
8
- attr_reader :called_conditions
9
-
10
8
  def initialize
11
9
  @enabled = false
12
10
  @prevented = false
13
- @called_conditions = Set.new
11
+ @called = nil
14
12
  end
15
13
 
16
14
  def enable!
@@ -34,7 +32,31 @@ module DeclarativePolicy
34
32
  end
35
33
 
36
34
  def register(manifest_condition)
37
- @called_conditions << manifest_condition.cache_key
35
+ called[manifest_condition.cache_key] = true
36
+ end
37
+
38
+ def called_conditions
39
+ return Set.new unless @called
40
+
41
+ Set.new(@called.keys)
42
+ end
43
+
44
+ def absorb(other)
45
+ return if other.equal?(self) || other.called_any.nil?
46
+
47
+ called.update(other.called_any)
48
+ end
49
+
50
+ protected
51
+
52
+ def called_any
53
+ @called
54
+ end
55
+
56
+ private
57
+
58
+ def called
59
+ @called ||= {}
38
60
  end
39
61
  end
40
62
 
@@ -81,7 +103,7 @@ module DeclarativePolicy
81
103
  run unless cached?
82
104
 
83
105
  parent_state = Thread.current[:declarative_policy_current_runner_state]
84
- parent_state&.called_conditions&.merge(@state.called_conditions)
106
+ parent_state&.absorb(@state)
85
107
 
86
108
  @state.pass?
87
109
  end
@@ -175,49 +197,53 @@ module DeclarativePolicy
175
197
  return
176
198
  end
177
199
 
178
- remaining_steps = Set.new(@steps)
179
- remaining_enablers, remaining_preventers = remaining_steps.partition(&:enable?).map { |s| Set.new(s) }
200
+ remaining = @steps.uniq
201
+ enablers_left = remaining.count(&:enable?)
202
+ preventers_only = false
180
203
 
181
204
  loop do
182
205
  if @state.enabled?
183
206
  # Once we set this, we never need to unset it, because a single
184
207
  # prevent will stop this from being enabled
185
- remaining_steps = remaining_preventers
186
- elsif remaining_enablers.empty?
208
+ unless preventers_only
209
+ remaining.reject!(&:enable?)
210
+ preventers_only = true
211
+ end
212
+ elsif enablers_left.zero?
187
213
  # if the permission hasn't yet been enabled and we only have
188
214
  # prevent steps left, we short-circuit the state here
189
215
  @state.prevent!
190
216
  return unless debugging
191
217
  end
192
218
 
193
- return if remaining_steps.empty?
219
+ return if remaining.empty?
194
220
 
195
- next_step, lowest_score = next_step_and_score(remaining_steps)
196
-
197
- [remaining_steps, remaining_enablers, remaining_preventers].each do |set|
198
- set.delete(next_step)
199
- end
221
+ lowest_index, lowest_score = cheapest_step(remaining)
222
+ step = remaining.delete_at(lowest_index)
223
+ enablers_left -= 1 if step.enable?
200
224
 
201
- yield next_step, lowest_score
225
+ yield step, lowest_score
202
226
  end
203
227
  end
204
228
 
205
- def next_step_and_score(remaining_steps)
229
+ def cheapest_step(remaining)
206
230
  lowest_score = Float::INFINITY
207
- next_step = nil
231
+ lowest_index = 0
232
+ index = 0
208
233
 
209
- remaining_steps.each do |step|
210
- score = step.score
234
+ while index < remaining.size
235
+ score = remaining[index].score
211
236
 
212
237
  if score < lowest_score
213
- next_step = step
214
238
  lowest_score = score
239
+ lowest_index = index
240
+ break if score.zero?
215
241
  end
216
242
 
217
- break if lowest_score.zero?
243
+ index += 1
218
244
  end
219
245
 
220
- [next_step, lowest_score]
246
+ [lowest_index, lowest_score]
221
247
  end
222
248
 
223
249
  # Formatter for debugging output.
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DeclarativePolicy
4
- VERSION = '2.0.1'
4
+ VERSION = '2.2.0'
5
5
  end
@@ -4,6 +4,7 @@ require 'set'
4
4
  require_relative 'declarative_policy/cache'
5
5
  require_relative 'declarative_policy/condition'
6
6
  require_relative 'declarative_policy/delegate_dsl'
7
+ require_relative 'declarative_policy/prevent_all_dsl'
7
8
  require_relative 'declarative_policy/policy_dsl'
8
9
  require_relative 'declarative_policy/rule_dsl'
9
10
  require_relative 'declarative_policy/preferred_scope'
@@ -36,7 +37,7 @@ module DeclarativePolicy
36
37
  policies = cache.select { |k, _| k.is_a?(String) && k.start_with?('/dp/policy/') }
37
38
 
38
39
  policies.each_value do |policy|
39
- policy.runners.each do |runner|
40
+ policy.runners.each_value do |runner|
40
41
  runner.uncache! if keys.intersect?(runner.dependencies)
41
42
  end
42
43
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: declarative_policy
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.1
4
+ version: 2.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - group::authorization
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-08-23 00:00:00.000000000 Z
11
+ date: 2026-09-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: benchmark-ips
@@ -140,6 +140,7 @@ files:
140
140
  - lib/declarative_policy/nil_policy.rb
141
141
  - lib/declarative_policy/policy_dsl.rb
142
142
  - lib/declarative_policy/preferred_scope.rb
143
+ - lib/declarative_policy/prevent_all_dsl.rb
143
144
  - lib/declarative_policy/rule.rb
144
145
  - lib/declarative_policy/rule_dsl.rb
145
146
  - lib/declarative_policy/runner.rb
@@ -151,7 +152,7 @@ licenses:
151
152
  metadata:
152
153
  homepage_uri: https://gitlab.com/gitlab-org/ruby/gems/declarative-policy
153
154
  source_code_uri: https://gitlab.com/gitlab-org/ruby/gems/declarative-policy
154
- changelog_uri: https://gitlab.com/gitlab-org/ruby/gems/declarative-policy/-/blob/main/CHANGELOG.md
155
+ changelog_uri: https://gitlab.com/gitlab-org/ruby/gems/declarative-policy/-/releases
155
156
  rubygems_mfa_required: 'false'
156
157
  post_install_message:
157
158
  rdoc_options: []