lex-identity 0.3.0 → 0.4.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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +4 -6
  3. data/lex-identity.gemspec +12 -15
  4. data/lib/legion/extensions/identity/actors/credential_refresh.rb +45 -0
  5. data/lib/legion/extensions/identity/actors/orphan_check.rb +48 -0
  6. data/lib/legion/extensions/identity/client.rb +23 -0
  7. data/lib/legion/extensions/identity/helpers/dimensions.rb +71 -0
  8. data/lib/legion/extensions/identity/helpers/fingerprint.rb +166 -0
  9. data/lib/legion/extensions/identity/helpers/graph_client.rb +25 -0
  10. data/lib/legion/extensions/identity/helpers/graph_token.rb +32 -0
  11. data/lib/legion/extensions/identity/helpers/token_cache.rb +55 -0
  12. data/lib/legion/extensions/identity/helpers/vault_secrets.rb +76 -0
  13. data/lib/legion/extensions/identity/local_migrations/20260316000030_create_fingerprint.rb +20 -0
  14. data/lib/legion/extensions/identity/runners/entra.rb +398 -0
  15. data/lib/legion/extensions/identity/runners/identity.rb +86 -0
  16. data/lib/legion/extensions/identity/version.rb +1 -1
  17. data/lib/legion/extensions/identity.rb +14 -8
  18. data/spec/legion/extensions/identity/actors/orphan_check_spec.rb +104 -0
  19. data/spec/legion/extensions/identity/client_spec.rb +32 -0
  20. data/spec/legion/extensions/identity/helpers/dimensions_spec.rb +51 -0
  21. data/spec/legion/extensions/identity/helpers/fingerprint_spec.rb +66 -0
  22. data/spec/legion/extensions/identity/helpers/graph_client_spec.rb +19 -0
  23. data/spec/legion/extensions/identity/helpers/graph_token_spec.rb +31 -0
  24. data/spec/legion/extensions/identity/helpers/token_cache_spec.rb +50 -0
  25. data/spec/legion/extensions/identity/runners/entra_spec.rb +655 -0
  26. data/spec/legion/extensions/identity/runners/identity_spec.rb +61 -0
  27. data/spec/local_persistence_spec.rb +329 -0
  28. data/spec/spec_helper.rb +33 -0
  29. metadata +40 -67
  30. data/CHANGELOG.md +0 -27
  31. data/LICENSE +0 -21
  32. data/README.md +0 -230
  33. data/lib/legion/extensions/identity/helpers/browser.rb +0 -44
  34. data/lib/legion/extensions/identity/helpers/callback_server.rb +0 -170
  35. data/lib/legion/extensions/identity/helpers/oauth.rb +0 -62
  36. data/lib/legion/extensions/identity/helpers/pkce.rb +0 -20
  37. data/lib/legion/extensions/identity/helpers/provider.rb +0 -52
  38. data/lib/legion/extensions/identity/runners/base/oauth.rb +0 -140
  39. data/spec/support/provider_contract.rb +0 -49
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/identity/client'
4
+
5
+ RSpec.describe Legion::Extensions::Identity::Runners::Identity do
6
+ let(:client) { Legion::Extensions::Identity::Client.new }
7
+
8
+ describe '#observe_behavior' do
9
+ it 'records a single observation' do
10
+ result = client.observe_behavior(dimension: :communication_cadence, value: 0.7)
11
+ expect(result[:recorded]).to be true
12
+ expect(result[:observation_count]).to eq(1)
13
+ end
14
+ end
15
+
16
+ describe '#observe_all' do
17
+ it 'records multiple observations' do
18
+ result = client.observe_all(observations: {
19
+ communication_cadence: 0.6,
20
+ vocabulary_patterns: 0.7,
21
+ emotional_response: 0.5
22
+ })
23
+ expect(result[:dimensions_observed].size).to eq(3)
24
+ expect(result[:observation_count]).to eq(3)
25
+ end
26
+ end
27
+
28
+ describe '#check_entropy' do
29
+ it 'returns entropy classification' do
30
+ result = client.check_entropy
31
+ expect(result[:entropy]).to be_between(0.0, 1.0)
32
+ expect(result).to have_key(:classification)
33
+ expect(result).to have_key(:trend)
34
+ expect(result).to have_key(:in_range)
35
+ end
36
+
37
+ it 'warns on high entropy observations' do
38
+ # Build baseline
39
+ 20.times { client.observe_behavior(dimension: :communication_cadence, value: 0.5) }
40
+ # Check with very different observation
41
+ result = client.check_entropy(observations: { communication_cadence: 10.0 })
42
+ expect(result[:warning]).to eq(:possible_impersonation_or_drift) if result[:classification] == :high_entropy
43
+ end
44
+ end
45
+
46
+ describe '#identity_status' do
47
+ it 'returns model state' do
48
+ status = client.identity_status
49
+ expect(status).to have_key(:model)
50
+ expect(status).to have_key(:maturity)
51
+ expect(status).to have_key(:observation_count)
52
+ end
53
+ end
54
+
55
+ describe '#identity_maturity' do
56
+ it 'returns maturity level' do
57
+ result = client.identity_maturity
58
+ expect(result[:maturity]).to eq(:nascent)
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,329 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Local persistence spec for Legion::Extensions::Identity::Helpers::Fingerprint
4
+ #
5
+ # Strategy: stub Legion::Data::Local with a real in-memory Sequel SQLite database
6
+ # when sequel + sqlite3 are available, otherwise use a double that records calls.
7
+ # Either way, the Fingerprint save/load logic is exercised end-to-end.
8
+
9
+ begin
10
+ require 'sequel'
11
+ require 'sqlite3'
12
+ SEQUEL_AVAILABLE = true
13
+ rescue LoadError
14
+ SEQUEL_AVAILABLE = false
15
+ end
16
+
17
+ RSpec.describe Legion::Extensions::Identity::Helpers::Fingerprint, 'local persistence' do
18
+ # ---------------------------------------------------------------------------
19
+ # Helpers for setting up the in-memory DB (shared between contexts)
20
+ # ---------------------------------------------------------------------------
21
+
22
+ def build_in_memory_db
23
+ db = Sequel.sqlite
24
+ db.create_table(:identity_fingerprint) do
25
+ primary_key :id
26
+ String :dimension, null: false, unique: true
27
+ Float :mean, default: 0.0
28
+ Float :variance, default: 0.0
29
+ Integer :observations, default: 0
30
+ DateTime :last_observed
31
+ end
32
+ db.create_table(:identity_meta) do
33
+ primary_key :id
34
+ Integer :observation_count, default: 0
35
+ String :entropy_history, text: true
36
+ end
37
+ db
38
+ end
39
+
40
+ def stub_local(db)
41
+ local_mod = Module.new do
42
+ define_singleton_method(:connection) { db }
43
+ define_singleton_method(:connected?) { true }
44
+ end
45
+ stub_const('Legion::Data', Module.new)
46
+ stub_const('Legion::Data::Local', local_mod)
47
+ end
48
+
49
+ # ---------------------------------------------------------------------------
50
+ # When Sequel + sqlite3 are available: full round-trip integration tests
51
+ # ---------------------------------------------------------------------------
52
+
53
+ if SEQUEL_AVAILABLE
54
+ context 'with an in-memory SQLite database' do
55
+ let(:db) { build_in_memory_db }
56
+
57
+ before { stub_local(db) }
58
+
59
+ describe '#save_to_local' do
60
+ it 'returns true on first save' do
61
+ fp = described_class.new
62
+ expect(fp.save_to_local).to be true
63
+ end
64
+
65
+ it 'persists all 6 dimension rows to identity_fingerprint' do
66
+ fp = described_class.new
67
+ fp.observe(:communication_cadence, 0.8)
68
+ fp.save_to_local
69
+
70
+ rows = db[:identity_fingerprint].all
71
+ expect(rows.size).to eq(6)
72
+ end
73
+
74
+ it 'persists updated mean for observed dimension' do
75
+ fp = described_class.new
76
+ 10.times { fp.observe(:vocabulary_patterns, 0.9) }
77
+ fp.save_to_local
78
+
79
+ row = db[:identity_fingerprint].where(dimension: 'vocabulary_patterns').first
80
+ expect(row[:mean]).to be > 0.5
81
+ expect(row[:observations]).to eq(10)
82
+ end
83
+
84
+ it 'persists observation_count in identity_meta' do
85
+ fp = described_class.new
86
+ 3.times { fp.observe(:communication_cadence, 0.6) }
87
+ fp.save_to_local
88
+
89
+ meta = db[:identity_meta].first
90
+ expect(meta[:observation_count]).to eq(3)
91
+ end
92
+
93
+ it 'persists entropy_history as JSON in identity_meta' do
94
+ fp = described_class.new
95
+ fp.current_entropy(communication_cadence: 0.5)
96
+ fp.current_entropy(vocabulary_patterns: 0.6)
97
+ fp.save_to_local
98
+
99
+ meta = db[:identity_meta].first
100
+ parsed = JSON.parse(meta[:entropy_history])
101
+ expect(parsed.size).to eq(2)
102
+ expect(parsed.first).to have_key('entropy')
103
+ expect(parsed.first).to have_key('at')
104
+ end
105
+
106
+ it 'updates existing rows on second save (upsert)' do
107
+ fp = described_class.new
108
+ fp.observe(:emotional_response, 0.3)
109
+ fp.save_to_local
110
+
111
+ # mutate and save again
112
+ 5.times { fp.observe(:emotional_response, 0.9) }
113
+ fp.save_to_local
114
+
115
+ rows = db[:identity_fingerprint].where(dimension: 'emotional_response').all
116
+ expect(rows.size).to eq(1) # still one row, not two
117
+ expect(rows.first[:observations]).to eq(6)
118
+ end
119
+ end
120
+
121
+ describe '#load_from_local' do
122
+ it 'returns true when called with an empty DB' do
123
+ fp = described_class.new # load_from_local called in initialize
124
+ expect(fp.observation_count).to eq(0)
125
+ end
126
+
127
+ it 'restores model dimensions from DB rows' do
128
+ # Pre-seed DB directly
129
+ db[:identity_fingerprint].insert(
130
+ dimension: 'communication_cadence',
131
+ mean: 0.75, variance: 0.05, observations: 42,
132
+ last_observed: Time.now.utc
133
+ )
134
+
135
+ fp = described_class.new # triggers load_from_local
136
+ expect(fp.model[:communication_cadence][:mean]).to be_within(0.001).of(0.75)
137
+ expect(fp.model[:communication_cadence][:observations]).to eq(42)
138
+ end
139
+
140
+ it 'restores observation_count from identity_meta' do
141
+ db[:identity_meta].insert(observation_count: 57, entropy_history: '[]')
142
+
143
+ fp = described_class.new
144
+ expect(fp.observation_count).to eq(57)
145
+ end
146
+
147
+ it 'restores entropy_history from identity_meta JSON' do
148
+ history = [
149
+ { 'entropy' => 0.3, 'at' => Time.now.utc.iso8601 },
150
+ { 'entropy' => 0.4, 'at' => Time.now.utc.iso8601 }
151
+ ]
152
+ db[:identity_meta].insert(observation_count: 0, entropy_history: JSON.generate(history))
153
+
154
+ fp = described_class.new
155
+ expect(fp.entropy_history.size).to eq(2)
156
+ expect(fp.entropy_history.first[:entropy]).to be_within(0.001).of(0.3)
157
+ expect(fp.entropy_history.first[:at]).to be_a(Time)
158
+ end
159
+
160
+ it 'ignores DB rows for unknown dimensions' do
161
+ db[:identity_fingerprint].insert(
162
+ dimension: 'nonexistent_dimension',
163
+ mean: 0.9, variance: 0.1, observations: 5,
164
+ last_observed: nil
165
+ )
166
+
167
+ fp = described_class.new # must not raise
168
+ expect(fp.model.keys).to match_array(
169
+ %i[communication_cadence vocabulary_patterns emotional_response
170
+ decision_patterns contextual_consistency temporal_patterns]
171
+ )
172
+ end
173
+ end
174
+
175
+ describe 'full round-trip' do
176
+ it 'survives a save-then-load cycle with identical state' do
177
+ # Build state in first fingerprint instance
178
+ fp1 = described_class.new
179
+ 12.times { fp1.observe(:communication_cadence, 0.7) }
180
+ 8.times { fp1.observe(:vocabulary_patterns, 0.6) }
181
+ fp1.current_entropy(communication_cadence: 0.65)
182
+ fp1.current_entropy(vocabulary_patterns: 0.55)
183
+ fp1.save_to_local
184
+
185
+ # Load into a fresh instance (DB already populated)
186
+ fp2 = described_class.new
187
+
188
+ expect(fp2.observation_count).to eq(fp1.observation_count)
189
+ expect(fp2.entropy_history.size).to eq(fp1.entropy_history.size)
190
+
191
+ dims = %i[communication_cadence vocabulary_patterns]
192
+ dims.each do |dim|
193
+ expect(fp2.model[dim][:mean]).to be_within(0.0001).of(fp1.model[dim][:mean])
194
+ expect(fp2.model[dim][:variance]).to be_within(0.0001).of(fp1.model[dim][:variance])
195
+ expect(fp2.model[dim][:observations]).to eq(fp1.model[dim][:observations])
196
+ end
197
+ end
198
+
199
+ it 'preserves maturity after round-trip' do
200
+ fp1 = described_class.new
201
+ 15.times { fp1.observe(:decision_patterns, 0.5) }
202
+ expect(fp1.maturity).to eq(:developing)
203
+ fp1.save_to_local
204
+
205
+ fp2 = described_class.new
206
+ expect(fp2.maturity).to eq(:developing)
207
+ end
208
+
209
+ it 'preserves entropy trend direction after round-trip' do
210
+ fp1 = described_class.new
211
+ # Ascending entropy values — second half larger than first half
212
+ [0.1, 0.1, 0.1, 0.1, 0.7, 0.8, 0.9, 0.95, 0.98, 1.0].each do |e|
213
+ fp1.instance_variable_get(:@entropy_history) << { entropy: e, at: Time.now.utc }
214
+ end
215
+ fp1.save_to_local
216
+
217
+ fp2 = described_class.new
218
+ expect(fp2.entropy_history.map { |h| h[:entropy] }).to eq(
219
+ fp1.entropy_history.map { |h| h[:entropy] }
220
+ )
221
+ end
222
+ end
223
+ end
224
+
225
+ else
226
+ # ---------------------------------------------------------------------------
227
+ # Fallback: double-based tests when Sequel is not in the bundle
228
+ # ---------------------------------------------------------------------------
229
+
230
+ context 'when Sequel is not available (double-based fallback)' do
231
+ let(:fingerprint_rows) { {} }
232
+ let(:meta_rows) { [] }
233
+
234
+ let(:fp_dataset) do
235
+ d = double('fingerprint_dataset')
236
+ allow(d).to receive(:where) { |args|
237
+ scoped = double('scoped_fp_dataset')
238
+ allow(scoped).to receive(:first) { fingerprint_rows[args[:dimension]] }
239
+ allow(scoped).to receive(:update) { |row|
240
+ fingerprint_rows[row[:dimension] || args[:dimension]] = row
241
+ }
242
+ scoped
243
+ }
244
+ allow(d).to receive(:insert) { |row| fingerprint_rows[row[:dimension]] = row }
245
+ allow(d).to receive(:each) { |&block| fingerprint_rows.each_value(&block) }
246
+ allow(d).to receive(:first) { fingerprint_rows.values.first }
247
+ d
248
+ end
249
+
250
+ let(:meta_dataset) do
251
+ d = double('meta_dataset')
252
+ allow(d).to receive(:first) { meta_rows.first }
253
+ allow(d).to receive(:insert) { |row| meta_rows << row }
254
+ allow(d).to receive(:where) do
255
+ scoped = double('scoped_meta_dataset')
256
+ allow(scoped).to receive(:update) { |row| meta_rows[0] = meta_rows[0]&.merge(row) }
257
+ scoped
258
+ end
259
+ d
260
+ end
261
+
262
+ let(:db) do
263
+ d = double('Sequel::Database')
264
+ allow(d).to receive(:[]).with(:identity_fingerprint).and_return(fp_dataset)
265
+ allow(d).to receive(:[]).with(:identity_meta).and_return(meta_dataset)
266
+ d
267
+ end
268
+
269
+ before do
270
+ local_mod = Module.new do
271
+ define_singleton_method(:connection) { nil } # overridden per example
272
+ define_singleton_method(:connected?) { true }
273
+ end
274
+ stub_const('Legion::Data', Module.new)
275
+ stub_const('Legion::Data::Local', local_mod)
276
+ allow(Legion::Data::Local).to receive(:connection).and_return(db)
277
+ end
278
+
279
+ it 'calls insert on the fingerprint dataset during save_to_local' do
280
+ fp = described_class.new
281
+ expect(fp_dataset).to receive(:insert).at_least(:once)
282
+ fp.save_to_local
283
+ end
284
+
285
+ it 'calls insert on the meta dataset during save_to_local' do
286
+ fp = described_class.new
287
+ expect(meta_dataset).to receive(:insert).once
288
+ fp.save_to_local
289
+ end
290
+
291
+ it 'does not raise when local is unavailable (Legion::Data::Local not defined)' do
292
+ # Remove the stub so defined? check returns false
293
+ hide_const('Legion::Data::Local')
294
+ expect { described_class.new }.not_to raise_error
295
+ end
296
+ end
297
+ end
298
+
299
+ # ---------------------------------------------------------------------------
300
+ # Behaviour when Legion::Data::Local is not defined (always run)
301
+ # ---------------------------------------------------------------------------
302
+
303
+ describe 'when Legion::Data::Local is not available' do
304
+ before do
305
+ hide_const('Legion::Data::Local') if defined?(Legion::Data::Local)
306
+ end
307
+
308
+ it 'initialize completes without error' do
309
+ expect { described_class.new }.not_to raise_error
310
+ end
311
+
312
+ it 'save_to_local returns nil (guard short-circuits)' do
313
+ fp = described_class.new
314
+ expect(fp.save_to_local).to be_nil
315
+ end
316
+
317
+ it 'load_from_local returns nil (guard short-circuits)' do
318
+ fp = described_class.new
319
+ expect(fp.load_from_local).to be_nil
320
+ end
321
+
322
+ it 'model starts with fresh defaults' do
323
+ fp = described_class.new
324
+ expect(fp.model[:communication_cadence][:mean]).to eq(0.5)
325
+ expect(fp.observation_count).to eq(0)
326
+ expect(fp.entropy_history).to be_empty
327
+ end
328
+ end
329
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+
5
+ # Stub framework classes not available outside the full Legion runtime
6
+ module Legion
7
+ module Logging
8
+ def self.debug(_msg); end
9
+
10
+ def self.info(_msg); end
11
+
12
+ def self.warn(_msg); end
13
+
14
+ def self.error(_msg); end
15
+ end
16
+
17
+ module Extensions
18
+ module Actors
19
+ class Every; end # rubocop:disable Lint/EmptyClass
20
+ end
21
+ end
22
+ end
23
+
24
+ # Prevent re-require of actor base when identity.rb loads orphan_check
25
+ $LOADED_FEATURES << 'legion/extensions/actors/every'
26
+
27
+ require 'legion/extensions/identity'
28
+
29
+ RSpec.configure do |config|
30
+ config.example_status_persistence_file_path = '.rspec_status'
31
+ config.disable_monkey_patching!
32
+ config.expect_with(:rspec) { |c| c.syntax = :expect }
33
+ end
metadata CHANGED
@@ -1,112 +1,85 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lex-identity
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
- - LegionIO
7
+ - Esity
8
8
  bindir: bin
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
- name: faraday
14
- requirement: !ruby/object:Gem::Requirement
15
- requirements:
16
- - - "~>"
17
- - !ruby/object:Gem::Version
18
- version: '2.0'
19
- type: :runtime
20
- prerelease: false
21
- version_requirements: !ruby/object:Gem::Requirement
22
- requirements:
23
- - - "~>"
24
- - !ruby/object:Gem::Version
25
- version: '2.0'
26
- - !ruby/object:Gem::Dependency
27
- name: legion-cache
28
- requirement: !ruby/object:Gem::Requirement
29
- requirements:
30
- - - ">="
31
- - !ruby/object:Gem::Version
32
- version: 1.3.0
33
- type: :runtime
34
- prerelease: false
35
- version_requirements: !ruby/object:Gem::Requirement
36
- requirements:
37
- - - ">="
38
- - !ruby/object:Gem::Version
39
- version: 1.3.0
40
- - !ruby/object:Gem::Dependency
41
- name: legion-json
42
- requirement: !ruby/object:Gem::Requirement
43
- requirements:
44
- - - ">="
45
- - !ruby/object:Gem::Version
46
- version: 1.2.1
47
- type: :runtime
48
- prerelease: false
49
- version_requirements: !ruby/object:Gem::Requirement
50
- requirements:
51
- - - ">="
52
- - !ruby/object:Gem::Version
53
- version: 1.2.1
54
- - !ruby/object:Gem::Dependency
55
- name: legion-logging
13
+ name: sequel
56
14
  requirement: !ruby/object:Gem::Requirement
57
15
  requirements:
58
16
  - - ">="
59
17
  - !ruby/object:Gem::Version
60
- version: 1.5.3
61
- type: :runtime
18
+ version: '5.70'
19
+ type: :development
62
20
  prerelease: false
63
21
  version_requirements: !ruby/object:Gem::Requirement
64
22
  requirements:
65
23
  - - ">="
66
24
  - !ruby/object:Gem::Version
67
- version: 1.5.3
25
+ version: '5.70'
68
26
  - !ruby/object:Gem::Dependency
69
- name: legion-settings
27
+ name: sqlite3
70
28
  requirement: !ruby/object:Gem::Requirement
71
29
  requirements:
72
30
  - - ">="
73
31
  - !ruby/object:Gem::Version
74
- version: 1.3.14
75
- type: :runtime
32
+ version: '2.0'
33
+ type: :development
76
34
  prerelease: false
77
35
  version_requirements: !ruby/object:Gem::Requirement
78
36
  requirements:
79
37
  - - ">="
80
38
  - !ruby/object:Gem::Version
81
- version: 1.3.14
82
- description: Base extension gem for shared behavior across LegionIO identity provider
83
- extensions.
39
+ version: '2.0'
40
+ description: Human partner identity modeling and behavioral entropy for brain-modeled
41
+ agentic AI
42
+ email:
43
+ - matthewdiverson@gmail.com
84
44
  executables: []
85
45
  extensions: []
86
46
  extra_rdoc_files: []
87
47
  files:
88
- - CHANGELOG.md
89
48
  - Gemfile
90
- - LICENSE
91
- - README.md
92
49
  - lex-identity.gemspec
93
50
  - lib/legion/extensions/identity.rb
94
- - lib/legion/extensions/identity/helpers/browser.rb
95
- - lib/legion/extensions/identity/helpers/callback_server.rb
96
- - lib/legion/extensions/identity/helpers/oauth.rb
97
- - lib/legion/extensions/identity/helpers/pkce.rb
98
- - lib/legion/extensions/identity/helpers/provider.rb
99
- - lib/legion/extensions/identity/runners/base/oauth.rb
51
+ - lib/legion/extensions/identity/actors/credential_refresh.rb
52
+ - lib/legion/extensions/identity/actors/orphan_check.rb
53
+ - lib/legion/extensions/identity/client.rb
54
+ - lib/legion/extensions/identity/helpers/dimensions.rb
55
+ - lib/legion/extensions/identity/helpers/fingerprint.rb
56
+ - lib/legion/extensions/identity/helpers/graph_client.rb
57
+ - lib/legion/extensions/identity/helpers/graph_token.rb
58
+ - lib/legion/extensions/identity/helpers/token_cache.rb
59
+ - lib/legion/extensions/identity/helpers/vault_secrets.rb
60
+ - lib/legion/extensions/identity/local_migrations/20260316000030_create_fingerprint.rb
61
+ - lib/legion/extensions/identity/runners/entra.rb
62
+ - lib/legion/extensions/identity/runners/identity.rb
100
63
  - lib/legion/extensions/identity/version.rb
101
- - spec/support/provider_contract.rb
64
+ - spec/legion/extensions/identity/actors/orphan_check_spec.rb
65
+ - spec/legion/extensions/identity/client_spec.rb
66
+ - spec/legion/extensions/identity/helpers/dimensions_spec.rb
67
+ - spec/legion/extensions/identity/helpers/fingerprint_spec.rb
68
+ - spec/legion/extensions/identity/helpers/graph_client_spec.rb
69
+ - spec/legion/extensions/identity/helpers/graph_token_spec.rb
70
+ - spec/legion/extensions/identity/helpers/token_cache_spec.rb
71
+ - spec/legion/extensions/identity/runners/entra_spec.rb
72
+ - spec/legion/extensions/identity/runners/identity_spec.rb
73
+ - spec/local_persistence_spec.rb
74
+ - spec/spec_helper.rb
102
75
  homepage: https://github.com/LegionIO/lex-identity
103
76
  licenses:
104
77
  - MIT
105
78
  metadata:
106
79
  homepage_uri: https://github.com/LegionIO/lex-identity
107
80
  source_code_uri: https://github.com/LegionIO/lex-identity
108
- documentation_uri: https://github.com/LegionIO/lex-identity/blob/main/README.md
109
- changelog_uri: https://github.com/LegionIO/lex-identity/blob/main/CHANGELOG.md
81
+ documentation_uri: https://github.com/LegionIO/lex-identity
82
+ changelog_uri: https://github.com/LegionIO/lex-identity
110
83
  bug_tracker_uri: https://github.com/LegionIO/lex-identity/issues
111
84
  rubygems_mfa_required: 'true'
112
85
  rdoc_options: []
@@ -125,5 +98,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
125
98
  requirements: []
126
99
  rubygems_version: 3.6.9
127
100
  specification_version: 4
128
- summary: Shared LegionIO identity extension base
101
+ summary: LEX Identity
129
102
  test_files: []
data/CHANGELOG.md DELETED
@@ -1,27 +0,0 @@
1
- # Changelog
2
-
3
- ## [0.3.0] - 2026-11-09
4
-
5
- ### Added
6
- - Explicit provider-neutral browser login combining PKCE, authorization, a state-correlated loopback callback, and token exchange.
7
- - A single-use callback listener with bounded reads/writes, request limits, optional fixed-port binding, and cleanup on every exit.
8
- - Callback security and lifecycle specs for invalid state, ambiguous parameters, provider denial, stalled clients, timeout, and listener disposal.
9
-
10
- ### Changed
11
- - Moved reusable OAuth runners to `runners/base/oauth.rb` so generic endpoint and browser operations are not automatically exposed through extension API routes. Consumers must explicitly require the new path.
12
-
13
- ## [0.2.0] - 2026-11-07
14
-
15
- ### Added
16
- - Provider integration helpers for resolver registration, lease construction, and broker credential access using one provider name.
17
- - Optional PKCE generation and browser helpers.
18
- - Provider-neutral OAuth authorization, code exchange, refresh, and deadline-bounded device-code polling.
19
- - Reusable provider conformance examples covering the core resolver's metadata and resolution contracts.
20
- - Shared OAuth runner compatible with standard extension discovery; the base extension is not itself an identity provider or remotely invocable.
21
-
22
- ## [0.1.0] - 2026-11-07
23
-
24
- ### Added
25
- - Initial gem packaging, namespace, and version.
26
- - Standard actors, runners, helpers, and spec directories.
27
- - Shared CI/release workflow, dependency updates, and RuboCop/RSpec configuration.
data/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 LegionIO
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.