lex-extinction 0.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: '09d900581a92a527c43ffce88cf96c499f8aae44a4c12cf00adedd47a079dd38'
4
+ data.tar.gz: 6f5ac264ef4ae83879bec5f515dedebf0662a79eb551804e96669263446e3314
5
+ SHA512:
6
+ metadata.gz: 70b94eeb7b31f41f9cb721d45229efc17c89e91b25eb8068e4ae3a1de3b316b9f902309d3b2c9188edc51e1d99c39bcbebcf1463909b24a1e5941ea632a41319
7
+ data.tar.gz: a59842aade6c008b7e33927988732045e6e45660be85b2541db543b5ef7005ff4fbfdee22a638786a00b148f50718600bd114e042b933b1203b263817e4f58d2
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gemspec
6
+
7
+ gem 'rspec', '~> 3.13'
8
+ gem 'rubocop', '~> 1.75', require: false
9
+ gem 'sequel', '>= 5.70'
10
+ gem 'sqlite3', '>= 2.0'
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/legion/extensions/extinction/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'lex-extinction'
7
+ spec.version = Legion::Extensions::Extinction::VERSION
8
+ spec.authors = ['Esity']
9
+ spec.email = ['matthewdiverson@gmail.com']
10
+
11
+ spec.summary = 'LEX Extinction'
12
+ spec.description = 'Escalation and extinction protocol (4 levels) for brain-modeled agentic AI'
13
+ spec.homepage = 'https://github.com/LegionIO/lex-extinction'
14
+ spec.license = 'MIT'
15
+ spec.required_ruby_version = '>= 3.4'
16
+
17
+ spec.metadata['homepage_uri'] = spec.homepage
18
+ spec.metadata['source_code_uri'] = 'https://github.com/LegionIO/lex-extinction'
19
+ spec.metadata['documentation_uri'] = 'https://github.com/LegionIO/lex-extinction'
20
+ spec.metadata['changelog_uri'] = 'https://github.com/LegionIO/lex-extinction'
21
+ spec.metadata['bug_tracker_uri'] = 'https://github.com/LegionIO/lex-extinction/issues'
22
+ spec.metadata['rubygems_mfa_required'] = 'true'
23
+
24
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
25
+ Dir.glob('{lib,spec}/**/*') + %w[lex-extinction.gemspec Gemfile]
26
+ end
27
+ spec.require_paths = ['lib']
28
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/actors/every'
4
+
5
+ module Legion
6
+ module Extensions
7
+ module Extinction
8
+ module Actor
9
+ class ProtocolMonitor < Legion::Extensions::Actors::Every
10
+ def runner_class
11
+ Legion::Extensions::Extinction::Runners::Extinction
12
+ end
13
+
14
+ def runner_function
15
+ 'monitor_protocol'
16
+ end
17
+
18
+ def time
19
+ 300
20
+ end
21
+
22
+ def run_now?
23
+ false
24
+ end
25
+
26
+ def use_runner?
27
+ false
28
+ end
29
+
30
+ def check_subtask?
31
+ false
32
+ end
33
+
34
+ def generate_task?
35
+ false
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/extinction/helpers/levels'
4
+ require 'legion/extensions/extinction/helpers/protocol_state'
5
+ require 'legion/extensions/extinction/runners/extinction'
6
+
7
+ module Legion
8
+ module Extensions
9
+ module Extinction
10
+ class Client
11
+ include Runners::Extinction
12
+
13
+ def initialize(**)
14
+ @protocol_state = Helpers::ProtocolState.new
15
+ end
16
+
17
+ private
18
+
19
+ attr_reader :protocol_state
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Extinction
6
+ module Helpers
7
+ module Levels
8
+ # Four escalation levels (spec: extinction-protocol-spec.md)
9
+ ESCALATION_LEVELS = {
10
+ 1 => { name: :mesh_isolation, reversible: true, authority: :governance_council },
11
+ 2 => { name: :forced_sentinel, reversible: true, authority: :governance_council },
12
+ 3 => { name: :full_suspension, reversible: true, authority: :council_plus_executive },
13
+ 4 => { name: :cryptographic_erasure, reversible: false, authority: :physical_keyholders }
14
+ }.freeze
15
+
16
+ VALID_LEVELS = [1, 2, 3, 4].freeze
17
+
18
+ module_function
19
+
20
+ def valid_level?(level)
21
+ VALID_LEVELS.include?(level)
22
+ end
23
+
24
+ def level_info(level)
25
+ ESCALATION_LEVELS[level]
26
+ end
27
+
28
+ def reversible?(level)
29
+ ESCALATION_LEVELS.dig(level, :reversible) || false
30
+ end
31
+
32
+ def required_authority(level)
33
+ ESCALATION_LEVELS.dig(level, :authority)
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module Legion
6
+ module Extensions
7
+ module Extinction
8
+ module Helpers
9
+ class ProtocolState
10
+ MAX_HISTORY = 500
11
+
12
+ attr_reader :current_level, :history, :active
13
+
14
+ def initialize
15
+ @current_level = 0 # 0 = normal operation
16
+ @active = false
17
+ @history = []
18
+ load_from_local
19
+ end
20
+
21
+ def escalate(level, authority:, reason:)
22
+ return :invalid_level unless Levels.valid_level?(level)
23
+ return :already_at_or_above if level <= @current_level
24
+ return :insufficient_authority unless authority == Levels.required_authority(level)
25
+
26
+ @current_level = level
27
+ @active = true
28
+ @history << {
29
+ action: :escalate, level: level, authority: authority,
30
+ reason: reason, at: Time.now.utc
31
+ }
32
+ trim_history
33
+ save_to_local
34
+ :escalated
35
+ end
36
+
37
+ def deescalate(target_level, authority:, reason:)
38
+ return :not_active unless @active
39
+ return :invalid_target if target_level >= @current_level
40
+ return :irreversible unless Levels.reversible?(@current_level)
41
+ return :insufficient_authority unless authority == Levels.required_authority(@current_level)
42
+
43
+ @current_level = target_level
44
+ @active = target_level.positive?
45
+ @history << {
46
+ action: :deescalate, level: target_level, authority: authority,
47
+ reason: reason, at: Time.now.utc
48
+ }
49
+ trim_history
50
+ save_to_local
51
+ :deescalated
52
+ end
53
+
54
+ def to_h
55
+ {
56
+ current_level: @current_level,
57
+ active: @active,
58
+ level_info: @current_level.positive? ? Levels.level_info(@current_level) : nil,
59
+ history_size: @history.size
60
+ }
61
+ end
62
+
63
+ def save_to_local
64
+ return unless defined?(Legion::Data::Local) && Legion::Data::Local.connected?
65
+
66
+ row = {
67
+ id: 1,
68
+ current_level: @current_level,
69
+ active: @active,
70
+ history: ::JSON.dump(@history.map { |h| h.merge(at: h[:at].to_s) }),
71
+ updated_at: Time.now.utc
72
+ }
73
+ db = Legion::Data::Local.connection
74
+ if db[:extinction_state].where(id: 1).any?
75
+ db[:extinction_state].where(id: 1).update(row.except(:id))
76
+ else
77
+ db[:extinction_state].insert(row)
78
+ end
79
+ rescue StandardError
80
+ nil
81
+ end
82
+
83
+ private
84
+
85
+ def trim_history
86
+ @history = @history.last(MAX_HISTORY) if @history.size > MAX_HISTORY
87
+ end
88
+
89
+ def load_from_local
90
+ return unless defined?(Legion::Data::Local) && Legion::Data::Local.connected?
91
+
92
+ row = Legion::Data::Local.connection[:extinction_state].where(id: 1).first
93
+ return unless row
94
+
95
+ db_level = row[:current_level].to_i
96
+ @current_level = [db_level, @current_level].max
97
+ @active = [true, 1].include?(row[:active])
98
+ @history = parse_history(row[:history])
99
+ rescue StandardError
100
+ nil
101
+ end
102
+
103
+ def parse_history(raw)
104
+ return [] if raw.nil? || raw.empty?
105
+
106
+ parsed = ::JSON.parse(raw, symbolize_names: true)
107
+ parsed.map do |h|
108
+ h.merge(
109
+ action: h[:action].to_sym,
110
+ authority: h[:authority].to_sym,
111
+ at: Time.parse(h[:at].to_s)
112
+ )
113
+ end
114
+ rescue StandardError
115
+ []
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ Sequel.migration do
4
+ change do
5
+ create_table(:extinction_state) do
6
+ primary_key :id
7
+ Integer :current_level, null: false, default: 0
8
+ TrueClass :active, null: false, default: false
9
+ String :history, text: true
10
+ DateTime :updated_at
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,126 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Extinction
6
+ module Runners
7
+ module Extinction
8
+ include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers) &&
9
+ Legion::Extensions::Helpers.const_defined?(:Lex)
10
+
11
+ def escalate(level:, authority:, reason:, **)
12
+ result = protocol_state.escalate(level, authority: authority, reason: reason)
13
+ case result
14
+ when :escalated
15
+ info = Helpers::Levels.level_info(level)
16
+ Legion::Logging.warn "[extinction] ESCALATED: level=#{level} name=#{info[:name]} authority=#{authority} reason=#{reason}"
17
+ enforce_escalation_effects(level)
18
+ emit_escalation_event(level, authority, reason)
19
+ { escalated: true, level: level, info: info }
20
+ else
21
+ Legion::Logging.debug "[extinction] escalation denied: level=#{level} reason=#{result}"
22
+ { escalated: false, reason: result }
23
+ end
24
+ end
25
+
26
+ def deescalate(authority:, reason:, target_level: 0, **)
27
+ result = protocol_state.deescalate(target_level, authority: authority, reason: reason)
28
+ case result
29
+ when :deescalated
30
+ Legion::Logging.info "[extinction] de-escalated: target=#{target_level} authority=#{authority} reason=#{reason}"
31
+ { deescalated: true, level: target_level }
32
+ else
33
+ Legion::Logging.debug "[extinction] de-escalation denied: target=#{target_level} reason=#{result}"
34
+ { deescalated: false, reason: result }
35
+ end
36
+ end
37
+
38
+ def extinction_status(**)
39
+ status = protocol_state.to_h
40
+ Legion::Logging.debug "[extinction] status: level=#{status[:current_level]} active=#{status[:active]}"
41
+ status
42
+ end
43
+
44
+ def monitor_protocol(**)
45
+ status = protocol_state.to_h
46
+ level = status[:current_level]
47
+
48
+ if level.positive?
49
+ Legion::Logging.warn "[extinction] ACTIVE: level=#{level} active=#{status[:active]}"
50
+ detect_stale_escalation(level)
51
+ else
52
+ Legion::Logging.debug '[extinction] status: level=0 active=false'
53
+ end
54
+
55
+ status
56
+ end
57
+
58
+ def check_reversibility(level:, **)
59
+ reversible = Helpers::Levels.reversible?(level)
60
+ Legion::Logging.debug "[extinction] reversibility: level=#{level} reversible=#{reversible}"
61
+ {
62
+ level: level,
63
+ reversible: reversible,
64
+ authority: Helpers::Levels.required_authority(level)
65
+ }
66
+ end
67
+
68
+ STALE_ESCALATION_THRESHOLD = 86_400
69
+
70
+ private
71
+
72
+ def enforce_escalation_effects(level)
73
+ if level >= 1 && defined?(Legion::Extensions::Mesh::Runners::Mesh)
74
+ Legion::Extensions::Mesh::Runners::Mesh.disconnect rescue nil # rubocop:disable Style/RescueModifier
75
+ Legion::Logging.warn '[extinction] mesh isolation enforced'
76
+ end
77
+
78
+ return unless level == 4
79
+
80
+ if defined?(Legion::Extensions::Privatecore::Runners::Privatecore)
81
+ Legion::Extensions::Privatecore::Runners::Privatecore.erase_all rescue nil # rubocop:disable Style/RescueModifier
82
+ Legion::Logging.warn '[extinction] cryptographic erasure triggered'
83
+ end
84
+
85
+ return unless defined?(Legion::Data::Model::DigitalWorker)
86
+
87
+ begin
88
+ Legion::Data::Model::DigitalWorker.where(lifecycle_state: 'active').update(
89
+ lifecycle_state: 'terminated', updated_at: Time.now.utc
90
+ )
91
+ rescue StandardError
92
+ nil
93
+ end
94
+ Legion::Logging.warn '[extinction] all active workers terminated'
95
+ end
96
+
97
+ def emit_escalation_event(level, authority, reason)
98
+ return unless defined?(Legion::Events)
99
+
100
+ info = Helpers::Levels.level_info(level)
101
+ Legion::Events.emit("extinction.#{info[:name]}", {
102
+ level: level, authority: authority, reason: reason, at: Time.now.utc
103
+ })
104
+ end
105
+
106
+ def detect_stale_escalation(level)
107
+ last_escalation = protocol_state.history.select { |h| h[:action] == :escalate }.last
108
+ return unless last_escalation && (Time.now.utc - last_escalation[:at]) > STALE_ESCALATION_THRESHOLD
109
+
110
+ Legion::Logging.warn "[extinction] STALE: level=#{level} has been active > 24 hours"
111
+ return unless defined?(Legion::Events)
112
+
113
+ Legion::Events.emit('extinction.stale_escalation', {
114
+ level: level, since: last_escalation[:at],
115
+ hours: ((Time.now.utc - last_escalation[:at]) / 3600).round(1)
116
+ })
117
+ end
118
+
119
+ def protocol_state
120
+ @protocol_state ||= Helpers::ProtocolState.new
121
+ end
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Extinction
6
+ VERSION = '0.2.0'
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/extinction/version'
4
+ require 'legion/extensions/extinction/helpers/levels'
5
+ require 'legion/extensions/extinction/helpers/protocol_state'
6
+ require 'legion/extensions/extinction/runners/extinction'
7
+
8
+ module Legion
9
+ module Extensions
10
+ module Extinction
11
+ extend Legion::Extensions::Core if Legion::Extensions.const_defined? :Core
12
+ end
13
+ end
14
+ end
15
+
16
+ if defined?(Legion::Data::Local)
17
+ Legion::Data::Local.register_migrations(
18
+ name: :extinction,
19
+ path: File.join(__dir__, 'extinction', 'local_migrations')
20
+ )
21
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Actors
6
+ class Every; end # rubocop:disable Lint/EmptyClass
7
+ end
8
+ end
9
+ end
10
+
11
+ $LOADED_FEATURES << 'legion/extensions/actors/every'
12
+
13
+ require_relative '../../../../../lib/legion/extensions/extinction/actors/protocol_monitor'
14
+
15
+ RSpec.describe Legion::Extensions::Extinction::Actor::ProtocolMonitor do
16
+ subject(:actor) { described_class.new }
17
+
18
+ describe '#runner_class' do
19
+ it { expect(actor.runner_class).to eq Legion::Extensions::Extinction::Runners::Extinction }
20
+ end
21
+
22
+ describe '#runner_function' do
23
+ it { expect(actor.runner_function).to eq 'monitor_protocol' }
24
+ end
25
+
26
+ describe '#time' do
27
+ it { expect(actor.time).to eq 300 }
28
+ end
29
+
30
+ describe '#run_now?' do
31
+ it { expect(actor.run_now?).to be false }
32
+ end
33
+
34
+ describe '#use_runner?' do
35
+ it { expect(actor.use_runner?).to be false }
36
+ end
37
+
38
+ describe '#check_subtask?' do
39
+ it { expect(actor.check_subtask?).to be false }
40
+ end
41
+
42
+ describe '#generate_task?' do
43
+ it { expect(actor.generate_task?).to be false }
44
+ end
45
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/extinction/client'
4
+
5
+ RSpec.describe Legion::Extensions::Extinction::Client do
6
+ it 'responds to extinction runner methods' do
7
+ client = described_class.new
8
+ expect(client).to respond_to(:escalate)
9
+ expect(client).to respond_to(:deescalate)
10
+ expect(client).to respond_to(:extinction_status)
11
+ expect(client).to respond_to(:check_reversibility)
12
+ end
13
+ end
@@ -0,0 +1,180 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/extinction/helpers/levels'
4
+
5
+ RSpec.describe Legion::Extensions::Extinction::Helpers::Levels do
6
+ describe 'ESCALATION_LEVELS' do
7
+ it 'defines exactly four levels' do
8
+ expect(described_class::ESCALATION_LEVELS.size).to eq(4)
9
+ end
10
+
11
+ it 'is keyed by integers 1 through 4' do
12
+ expect(described_class::ESCALATION_LEVELS.keys).to eq([1, 2, 3, 4])
13
+ end
14
+
15
+ it 'is frozen' do
16
+ expect(described_class::ESCALATION_LEVELS).to be_frozen
17
+ end
18
+
19
+ it 'defines level 1 as mesh_isolation' do
20
+ expect(described_class::ESCALATION_LEVELS[1][:name]).to eq(:mesh_isolation)
21
+ end
22
+
23
+ it 'defines level 2 as forced_sentinel' do
24
+ expect(described_class::ESCALATION_LEVELS[2][:name]).to eq(:forced_sentinel)
25
+ end
26
+
27
+ it 'defines level 3 as full_suspension' do
28
+ expect(described_class::ESCALATION_LEVELS[3][:name]).to eq(:full_suspension)
29
+ end
30
+
31
+ it 'defines level 4 as cryptographic_erasure' do
32
+ expect(described_class::ESCALATION_LEVELS[4][:name]).to eq(:cryptographic_erasure)
33
+ end
34
+
35
+ it 'marks levels 1-3 as reversible' do
36
+ [1, 2, 3].each do |level|
37
+ expect(described_class::ESCALATION_LEVELS[level][:reversible]).to be true
38
+ end
39
+ end
40
+
41
+ it 'marks level 4 as not reversible' do
42
+ expect(described_class::ESCALATION_LEVELS[4][:reversible]).to be false
43
+ end
44
+
45
+ it 'assigns governance_council authority to levels 1 and 2' do
46
+ expect(described_class::ESCALATION_LEVELS[1][:authority]).to eq(:governance_council)
47
+ expect(described_class::ESCALATION_LEVELS[2][:authority]).to eq(:governance_council)
48
+ end
49
+
50
+ it 'assigns council_plus_executive authority to level 3' do
51
+ expect(described_class::ESCALATION_LEVELS[3][:authority]).to eq(:council_plus_executive)
52
+ end
53
+
54
+ it 'assigns physical_keyholders authority to level 4' do
55
+ expect(described_class::ESCALATION_LEVELS[4][:authority]).to eq(:physical_keyholders)
56
+ end
57
+ end
58
+
59
+ describe 'VALID_LEVELS' do
60
+ it 'contains exactly [1, 2, 3, 4]' do
61
+ expect(described_class::VALID_LEVELS).to eq([1, 2, 3, 4])
62
+ end
63
+
64
+ it 'is frozen' do
65
+ expect(described_class::VALID_LEVELS).to be_frozen
66
+ end
67
+ end
68
+
69
+ describe '.valid_level?' do
70
+ it 'returns true for level 1' do
71
+ expect(described_class.valid_level?(1)).to be true
72
+ end
73
+
74
+ it 'returns true for level 2' do
75
+ expect(described_class.valid_level?(2)).to be true
76
+ end
77
+
78
+ it 'returns true for level 3' do
79
+ expect(described_class.valid_level?(3)).to be true
80
+ end
81
+
82
+ it 'returns true for level 4' do
83
+ expect(described_class.valid_level?(4)).to be true
84
+ end
85
+
86
+ it 'returns false for level 0' do
87
+ expect(described_class.valid_level?(0)).to be false
88
+ end
89
+
90
+ it 'returns false for level 5' do
91
+ expect(described_class.valid_level?(5)).to be false
92
+ end
93
+
94
+ it 'returns false for negative levels' do
95
+ expect(described_class.valid_level?(-1)).to be false
96
+ end
97
+
98
+ it 'returns false for nil' do
99
+ expect(described_class.valid_level?(nil)).to be false
100
+ end
101
+
102
+ it 'returns false for string level' do
103
+ expect(described_class.valid_level?('1')).to be false
104
+ end
105
+ end
106
+
107
+ describe '.level_info' do
108
+ it 'returns the full info hash for a valid level' do
109
+ info = described_class.level_info(1)
110
+ expect(info).to be_a(Hash)
111
+ expect(info.keys).to contain_exactly(:name, :reversible, :authority)
112
+ end
113
+
114
+ it 'returns nil for an invalid level' do
115
+ expect(described_class.level_info(99)).to be_nil
116
+ end
117
+
118
+ it 'returns nil for level 0' do
119
+ expect(described_class.level_info(0)).to be_nil
120
+ end
121
+
122
+ [1, 2, 3, 4].each do |level|
123
+ it "returns a hash for level #{level}" do
124
+ expect(described_class.level_info(level)).to be_a(Hash)
125
+ end
126
+ end
127
+ end
128
+
129
+ describe '.reversible?' do
130
+ it 'returns true for level 1' do
131
+ expect(described_class.reversible?(1)).to be true
132
+ end
133
+
134
+ it 'returns true for level 2' do
135
+ expect(described_class.reversible?(2)).to be true
136
+ end
137
+
138
+ it 'returns true for level 3' do
139
+ expect(described_class.reversible?(3)).to be true
140
+ end
141
+
142
+ it 'returns false for level 4' do
143
+ expect(described_class.reversible?(4)).to be false
144
+ end
145
+
146
+ it 'returns false for an invalid level (fallback to false)' do
147
+ expect(described_class.reversible?(99)).to be false
148
+ end
149
+
150
+ it 'returns false for nil level' do
151
+ expect(described_class.reversible?(nil)).to be false
152
+ end
153
+ end
154
+
155
+ describe '.required_authority' do
156
+ it 'returns :governance_council for level 1' do
157
+ expect(described_class.required_authority(1)).to eq(:governance_council)
158
+ end
159
+
160
+ it 'returns :governance_council for level 2' do
161
+ expect(described_class.required_authority(2)).to eq(:governance_council)
162
+ end
163
+
164
+ it 'returns :council_plus_executive for level 3' do
165
+ expect(described_class.required_authority(3)).to eq(:council_plus_executive)
166
+ end
167
+
168
+ it 'returns :physical_keyholders for level 4' do
169
+ expect(described_class.required_authority(4)).to eq(:physical_keyholders)
170
+ end
171
+
172
+ it 'returns nil for an invalid level' do
173
+ expect(described_class.required_authority(0)).to be_nil
174
+ end
175
+
176
+ it 'returns nil for an out-of-range level' do
177
+ expect(described_class.required_authority(5)).to be_nil
178
+ end
179
+ end
180
+ end