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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 872e980260f49400124a887b0a30dc0acb5a763440d17b20afc7c837a1e0ce4d
4
- data.tar.gz: d6912d4151a41cac307e1e828733306b7ea1f32d85c126b13f2b8b5ac2fe4cb7
3
+ metadata.gz: 6ae04ddeb792bd593f11c4b121cf778a1b6fb14b9827de61aca08d88363f3cb8
4
+ data.tar.gz: 1c9c72455e758c50f9bdc00995a32d9bb2c7a18d1140f2203020467b08c91b1b
5
5
  SHA512:
6
- metadata.gz: 7c6bf0e53ee3d3b6de474c5c3ff4f5d4d0dfc41dfa2ccf288af43e94056937ba4d4202426caf6ad24a8258fef0a330ef96e13f1b4db2935e82f367b9d03d4e0f
7
- data.tar.gz: a62fda0ef8c6434646bed44760708f28bc8826c92a3e0a4572b2a68693c5ceac23defcd3b4f43b529ccb2050eb44a67ec8a65d9f0cfb16172ff5f347810ff66d
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
- gem 'legion-gaia', path: '../../legion-gaia'
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
@@ -25,5 +25,4 @@ Gem::Specification.new do |spec|
25
25
  Dir.glob('{lib,spec}/**/*') + %w[lex-governance.gemspec Gemfile]
26
26
  end
27
27
  spec.require_paths = ['lib']
28
- spec.add_development_dependency 'legion-gaia'
29
28
  end
@@ -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
@@ -3,7 +3,7 @@
3
3
  module Legion
4
4
  module Extensions
5
5
  module Governance
6
- VERSION = '0.1.1'
6
+ VERSION = '0.2.0'
7
7
  end
8
8
  end
9
9
  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.1.1
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: