lex-governance 0.1.1 → 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 +4 -4
- data/Gemfile +5 -1
- data/lex-governance.gemspec +0 -1
- data/lib/legion/extensions/governance/actors/shadow_ai_scan.rb +15 -0
- data/lib/legion/extensions/governance/runners/shadow_ai.rb +89 -0
- data/lib/legion/extensions/governance/version.rb +1 -1
- data/lib/legion/extensions/governance.rb +2 -0
- data/spec/legion/extensions/governance/runners/shadow_ai_spec.rb +65 -0
- data/spec/spec_helper.rb +26 -0
- metadata +5 -16
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6ae04ddeb792bd593f11c4b121cf778a1b6fb14b9827de61aca08d88363f3cb8
|
|
4
|
+
data.tar.gz: 1c9c72455e758c50f9bdc00995a32d9bb2c7a18d1140f2203020467b08c91b1b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6cf0a327af81329db2a4f8255c0ac4fdbcdccbfab56ef9803eb70ea14880bedd8bb99f1161381eeed56345ed9b9c6fcd81e3c2e77c92971dabf0e32a8da6bb56
|
|
7
|
+
data.tar.gz: 1f2834223f2a83f382e89684afbdffae40f913e36d174f55ef3596feade744f842fa62716ff544da6eb88ad4e9575143a83da85a7deae9865eb0b0aeb9db30cb
|
data/Gemfile
CHANGED
|
@@ -7,4 +7,8 @@ gemspec
|
|
|
7
7
|
gem 'rspec', '~> 3.13'
|
|
8
8
|
gem 'rubocop', '~> 1.75', require: false
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
if File.directory?(File.expand_path('../../legion-gaia', __dir__))
|
|
11
|
+
gem 'legion-gaia', path: '../../legion-gaia'
|
|
12
|
+
else
|
|
13
|
+
gem 'legion-gaia'
|
|
14
|
+
end
|
data/lex-governance.gemspec
CHANGED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Governance
|
|
6
|
+
module Actors
|
|
7
|
+
class ShadowAiScan < Legion::Extensions::Actors::Every
|
|
8
|
+
def runner_class = Runners::ShadowAi
|
|
9
|
+
def runner_function = 'full_scan'
|
|
10
|
+
def time = 86_400
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Governance
|
|
6
|
+
module Runners
|
|
7
|
+
module ShadowAi
|
|
8
|
+
def scan_unregistered_extensions(**)
|
|
9
|
+
installed = Bundler.load.specs.select { |s| s.name.start_with?('lex-') }.map(&:name)
|
|
10
|
+
registered = registered_extension_names
|
|
11
|
+
|
|
12
|
+
unregistered = installed - registered
|
|
13
|
+
{ installed: installed.size, registered: registered.size, unregistered: unregistered }
|
|
14
|
+
rescue StandardError => e
|
|
15
|
+
{ installed: 0, registered: 0, unregistered: [], error: e.message }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def check_llm_bypass_indicators(**)
|
|
19
|
+
indicators = []
|
|
20
|
+
indicators << :direct_openai_key if ENV.key?('OPENAI_API_KEY') && !provider_enabled?(:openai)
|
|
21
|
+
indicators << :direct_anthropic_key if ENV.key?('ANTHROPIC_API_KEY') && !provider_enabled?(:anthropic)
|
|
22
|
+
{ indicators: indicators, bypassed: !indicators.empty? }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def check_airb_compliance(**)
|
|
26
|
+
return { checked: 0, source: :unavailable } unless defined?(Legion::Data::Model::DigitalWorker)
|
|
27
|
+
|
|
28
|
+
workers = Legion::Data::Model::DigitalWorker.where(lifecycle_state: 'active').all
|
|
29
|
+
non_compliant = workers.select do |w|
|
|
30
|
+
risk = w.respond_to?(:risk_tier) ? w.risk_tier : nil
|
|
31
|
+
%w[high critical].include?(risk) && w.respond_to?(:airb_status) && w.airb_status != 'approved'
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
{ checked: workers.size, compliant: workers.size - non_compliant.size,
|
|
35
|
+
non_compliant: non_compliant.map(&:worker_id) }
|
|
36
|
+
rescue StandardError => e
|
|
37
|
+
{ checked: 0, error: e.message }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def full_scan(**)
|
|
41
|
+
extensions = scan_unregistered_extensions
|
|
42
|
+
bypass = check_llm_bypass_indicators
|
|
43
|
+
compliance = check_airb_compliance
|
|
44
|
+
|
|
45
|
+
has_issues = extensions[:unregistered]&.any? || bypass[:bypassed] || compliance[:non_compliant]&.any?
|
|
46
|
+
emit_shadow_event(extensions, bypass, compliance) if has_issues
|
|
47
|
+
|
|
48
|
+
{ extensions: extensions, bypass: bypass, compliance: compliance, issues_found: has_issues }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
def registered_extension_names
|
|
54
|
+
return [] unless defined?(Legion::Data) && Legion::Data.respond_to?(:connection)
|
|
55
|
+
|
|
56
|
+
conn = Legion::Data.connection
|
|
57
|
+
return [] unless conn&.table_exists?(:extension_registry)
|
|
58
|
+
|
|
59
|
+
conn[:extension_registry].select_map(:gem_name)
|
|
60
|
+
rescue StandardError
|
|
61
|
+
[]
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def provider_enabled?(provider)
|
|
65
|
+
llm = Legion::Settings[:llm]
|
|
66
|
+
return false unless llm.is_a?(Hash)
|
|
67
|
+
|
|
68
|
+
providers = llm[:providers]
|
|
69
|
+
return false unless providers.is_a?(Hash)
|
|
70
|
+
|
|
71
|
+
providers.dig(provider, :enabled) == true
|
|
72
|
+
rescue StandardError
|
|
73
|
+
false
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def emit_shadow_event(extensions, bypass, compliance)
|
|
77
|
+
return unless defined?(Legion::Events)
|
|
78
|
+
|
|
79
|
+
Legion::Events.emit('governance.shadow_ai_detected', {
|
|
80
|
+
unregistered: extensions[:unregistered],
|
|
81
|
+
bypass: bypass[:indicators],
|
|
82
|
+
non_compliant: compliance[:non_compliant]
|
|
83
|
+
})
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -4,6 +4,8 @@ require 'legion/extensions/governance/version'
|
|
|
4
4
|
require 'legion/extensions/governance/helpers/layers'
|
|
5
5
|
require 'legion/extensions/governance/helpers/proposal'
|
|
6
6
|
require 'legion/extensions/governance/runners/governance'
|
|
7
|
+
require 'legion/extensions/governance/runners/shadow_ai'
|
|
8
|
+
require 'legion/extensions/governance/actors/shadow_ai_scan'
|
|
7
9
|
|
|
8
10
|
module Legion
|
|
9
11
|
module Extensions
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
RSpec.describe Legion::Extensions::Governance::Runners::ShadowAi do
|
|
6
|
+
let(:host) { Object.new.extend(described_class) }
|
|
7
|
+
|
|
8
|
+
describe '#check_llm_bypass_indicators' do
|
|
9
|
+
it 'detects direct API key when provider not enabled' do
|
|
10
|
+
allow(ENV).to receive(:key?).and_call_original
|
|
11
|
+
allow(ENV).to receive(:key?).with('OPENAI_API_KEY').and_return(true)
|
|
12
|
+
allow(ENV).to receive(:key?).with('ANTHROPIC_API_KEY').and_return(false)
|
|
13
|
+
allow(Legion::Settings).to receive(:[]).with(:llm).and_return(
|
|
14
|
+
{ providers: { openai: { enabled: false } } }
|
|
15
|
+
)
|
|
16
|
+
result = host.check_llm_bypass_indicators
|
|
17
|
+
expect(result[:bypassed]).to be true
|
|
18
|
+
expect(result[:indicators]).to include(:direct_openai_key)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it 'returns clean when no bypass indicators' do
|
|
22
|
+
allow(ENV).to receive(:key?).with('OPENAI_API_KEY').and_return(false)
|
|
23
|
+
allow(ENV).to receive(:key?).with('ANTHROPIC_API_KEY').and_return(false)
|
|
24
|
+
result = host.check_llm_bypass_indicators
|
|
25
|
+
expect(result[:bypassed]).to be false
|
|
26
|
+
expect(result[:indicators]).to be_empty
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it 'does not flag when provider is enabled' do
|
|
30
|
+
allow(ENV).to receive(:key?).and_call_original
|
|
31
|
+
allow(ENV).to receive(:key?).with('OPENAI_API_KEY').and_return(true)
|
|
32
|
+
allow(ENV).to receive(:key?).with('ANTHROPIC_API_KEY').and_return(false)
|
|
33
|
+
allow(Legion::Settings).to receive(:[]).with(:llm).and_return(
|
|
34
|
+
{ providers: { openai: { enabled: true } } }
|
|
35
|
+
)
|
|
36
|
+
result = host.check_llm_bypass_indicators
|
|
37
|
+
expect(result[:bypassed]).to be false
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
describe '#check_airb_compliance' do
|
|
42
|
+
it 'returns unavailable when data model not loaded' do
|
|
43
|
+
result = host.check_airb_compliance
|
|
44
|
+
expect(result[:source]).to eq(:unavailable)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
describe '#full_scan' do
|
|
49
|
+
it 'returns combined results' do
|
|
50
|
+
allow(host).to receive(:scan_unregistered_extensions).and_return(
|
|
51
|
+
{ installed: 5, registered: 5, unregistered: [] }
|
|
52
|
+
)
|
|
53
|
+
allow(host).to receive(:check_llm_bypass_indicators).and_return(
|
|
54
|
+
{ indicators: [], bypassed: false }
|
|
55
|
+
)
|
|
56
|
+
allow(host).to receive(:check_airb_compliance).and_return(
|
|
57
|
+
{ checked: 0, source: :unavailable }
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
result = host.full_scan
|
|
61
|
+
expect(result[:issues_found]).to be_falsey
|
|
62
|
+
expect(result[:extensions][:installed]).to eq(5)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
data/spec/spec_helper.rb
CHANGED
|
@@ -9,8 +9,34 @@ module Legion
|
|
|
9
9
|
def self.warn(_msg); end
|
|
10
10
|
def self.error(_msg); end
|
|
11
11
|
end
|
|
12
|
+
|
|
13
|
+
module Extensions
|
|
14
|
+
module Actors
|
|
15
|
+
class Every; end # rubocop:disable Lint/EmptyClass
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
module Settings
|
|
20
|
+
@store = {}
|
|
21
|
+
|
|
22
|
+
class << self
|
|
23
|
+
def [](key)
|
|
24
|
+
@store[key.to_sym] ||= {}
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def reset!
|
|
28
|
+
@store = {}
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
module Events
|
|
34
|
+
def self.emit(_name, _payload); end
|
|
35
|
+
end
|
|
12
36
|
end
|
|
13
37
|
|
|
38
|
+
$LOADED_FEATURES << 'legion/extensions/actors/every'
|
|
39
|
+
|
|
14
40
|
require 'legion/extensions/governance'
|
|
15
41
|
|
|
16
42
|
RSpec.configure do |config|
|
metadata
CHANGED
|
@@ -1,28 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: lex-governance
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Esity
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
-
dependencies:
|
|
12
|
-
- !ruby/object:Gem::Dependency
|
|
13
|
-
name: legion-gaia
|
|
14
|
-
requirement: !ruby/object:Gem::Requirement
|
|
15
|
-
requirements:
|
|
16
|
-
- - ">="
|
|
17
|
-
- !ruby/object:Gem::Version
|
|
18
|
-
version: '0'
|
|
19
|
-
type: :development
|
|
20
|
-
prerelease: false
|
|
21
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
-
requirements:
|
|
23
|
-
- - ">="
|
|
24
|
-
- !ruby/object:Gem::Version
|
|
25
|
-
version: '0'
|
|
11
|
+
dependencies: []
|
|
26
12
|
description: Four-layer distributed governance protocol for brain-modeled agentic
|
|
27
13
|
AI
|
|
28
14
|
email:
|
|
@@ -34,17 +20,20 @@ files:
|
|
|
34
20
|
- Gemfile
|
|
35
21
|
- lex-governance.gemspec
|
|
36
22
|
- lib/legion/extensions/governance.rb
|
|
23
|
+
- lib/legion/extensions/governance/actors/shadow_ai_scan.rb
|
|
37
24
|
- lib/legion/extensions/governance/actors/vote_timeout.rb
|
|
38
25
|
- lib/legion/extensions/governance/client.rb
|
|
39
26
|
- lib/legion/extensions/governance/helpers/layers.rb
|
|
40
27
|
- lib/legion/extensions/governance/helpers/proposal.rb
|
|
41
28
|
- lib/legion/extensions/governance/runners/governance.rb
|
|
29
|
+
- lib/legion/extensions/governance/runners/shadow_ai.rb
|
|
42
30
|
- lib/legion/extensions/governance/version.rb
|
|
43
31
|
- spec/legion/extensions/governance/actors/vote_timeout_spec.rb
|
|
44
32
|
- spec/legion/extensions/governance/client_spec.rb
|
|
45
33
|
- spec/legion/extensions/governance/helpers/layers_spec.rb
|
|
46
34
|
- spec/legion/extensions/governance/helpers/proposal_spec.rb
|
|
47
35
|
- spec/legion/extensions/governance/runners/governance_spec.rb
|
|
36
|
+
- spec/legion/extensions/governance/runners/shadow_ai_spec.rb
|
|
48
37
|
- spec/spec_helper.rb
|
|
49
38
|
homepage: https://github.com/LegionIO/lex-governance
|
|
50
39
|
licenses:
|