lex-cognitive-offloading 0.1.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.
@@ -0,0 +1,158 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::CognitiveOffloading::Helpers::ExternalStore do
4
+ let(:store) { described_class.new(name: 'notes_db', store_type: :database) }
5
+
6
+ describe '#initialize' do
7
+ it 'assigns a UUID id' do
8
+ expect(store.id).to match(/\A[0-9a-f-]{36}\z/)
9
+ end
10
+
11
+ it 'stores name' do
12
+ expect(store.name).to eq('notes_db')
13
+ end
14
+
15
+ it 'stores store_type' do
16
+ expect(store.store_type).to eq(:database)
17
+ end
18
+
19
+ it 'defaults trust to DEFAULT_STORE_TRUST (0.7)' do
20
+ expect(store.trust).to eq(0.7)
21
+ end
22
+
23
+ it 'initializes items_stored to 0' do
24
+ expect(store.items_stored).to eq(0)
25
+ end
26
+
27
+ it 'initializes successful_retrievals to 0' do
28
+ expect(store.successful_retrievals).to eq(0)
29
+ end
30
+
31
+ it 'initializes failed_retrievals to 0' do
32
+ expect(store.failed_retrievals).to eq(0)
33
+ end
34
+
35
+ it 'records created_at as a Time' do
36
+ expect(store.created_at).to be_a(Time)
37
+ end
38
+ end
39
+
40
+ describe '#increment_items!' do
41
+ it 'increments items_stored' do
42
+ store.increment_items!
43
+ expect(store.items_stored).to eq(1)
44
+ end
45
+
46
+ it 'returns self for chaining' do
47
+ expect(store.increment_items!).to eq(store)
48
+ end
49
+ end
50
+
51
+ describe '#record_success!' do
52
+ it 'increments successful_retrievals' do
53
+ store.record_success!
54
+ expect(store.successful_retrievals).to eq(1)
55
+ end
56
+
57
+ it 'boosts trust by TRUST_BOOST' do
58
+ trust_before = store.trust
59
+ store.record_success!
60
+ expect(store.trust).to be > trust_before
61
+ end
62
+
63
+ it 'does not exceed trust of 1.0' do
64
+ 10.times { store.record_success! }
65
+ expect(store.trust).to be <= 1.0
66
+ end
67
+
68
+ it 'returns self for chaining' do
69
+ expect(store.record_success!).to eq(store)
70
+ end
71
+ end
72
+
73
+ describe '#record_failure!' do
74
+ it 'increments failed_retrievals' do
75
+ store.record_failure!
76
+ expect(store.failed_retrievals).to eq(1)
77
+ end
78
+
79
+ it 'decays trust by TRUST_DECAY' do
80
+ trust_before = store.trust
81
+ store.record_failure!
82
+ expect(store.trust).to be < trust_before
83
+ end
84
+
85
+ it 'does not drop trust below 0.0' do
86
+ 20.times { store.record_failure! }
87
+ expect(store.trust).to be >= 0.0
88
+ end
89
+
90
+ it 'returns self for chaining' do
91
+ expect(store.record_failure!).to eq(store)
92
+ end
93
+ end
94
+
95
+ describe '#retrieval_rate' do
96
+ it 'returns 0.0 with no retrievals' do
97
+ expect(store.retrieval_rate).to eq(0.0)
98
+ end
99
+
100
+ it 'returns 1.0 when all retrievals succeed' do
101
+ 3.times { store.record_success! }
102
+ expect(store.retrieval_rate).to eq(1.0)
103
+ end
104
+
105
+ it 'returns 0.5 for equal success/failure' do
106
+ store.record_success!
107
+ store.record_failure!
108
+ expect(store.retrieval_rate).to eq(0.5)
109
+ end
110
+
111
+ it 'rounds to 10 decimal places' do
112
+ 2.times { store.record_success! }
113
+ store.record_failure!
114
+ expect(store.retrieval_rate).to eq((2.0 / 3).round(10))
115
+ end
116
+ end
117
+
118
+ describe '#reliable?' do
119
+ it 'returns true when trust >= 0.7' do
120
+ expect(store.reliable?).to be true
121
+ end
122
+
123
+ it 'returns false when trust drops below 0.7' do
124
+ 4.times { store.record_failure! }
125
+ expect(store.reliable?).to be false
126
+ end
127
+ end
128
+
129
+ describe '#trust_label' do
130
+ it 'returns :trusted for default trust of 0.7' do
131
+ expect(store.trust_label).to eq(:trusted)
132
+ end
133
+
134
+ it 'returns :highly_trusted after many successes' do
135
+ 5.times { store.record_success! }
136
+ expect(store.trust_label).to eq(:highly_trusted)
137
+ end
138
+
139
+ it 'returns :unreliable after many failures' do
140
+ 30.times { store.record_failure! }
141
+ expect(store.trust_label).to eq(:unreliable)
142
+ end
143
+ end
144
+
145
+ describe '#to_h' do
146
+ it 'returns a hash with expected keys' do
147
+ h = store.to_h
148
+ expect(h).to include(:id, :name, :store_type, :trust, :trust_label, :items_stored,
149
+ :successful_retrievals, :failed_retrievals, :retrieval_rate,
150
+ :reliable, :created_at)
151
+ end
152
+
153
+ it 'reflects current trust value' do
154
+ store.record_failure!
155
+ expect(store.to_h[:trust]).to eq(store.trust)
156
+ end
157
+ end
158
+ end
@@ -0,0 +1,123 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::CognitiveOffloading::Helpers::OffloadedItem do
4
+ let(:store_id) { SecureRandom.uuid }
5
+ let(:item) do
6
+ described_class.new(
7
+ content: 'The capital of France is Paris',
8
+ item_type: :fact,
9
+ importance: 0.8,
10
+ store_id: store_id
11
+ )
12
+ end
13
+
14
+ describe '#initialize' do
15
+ it 'assigns a UUID id' do
16
+ expect(item.id).to match(/\A[0-9a-f-]{36}\z/)
17
+ end
18
+
19
+ it 'stores content' do
20
+ expect(item.content).to eq('The capital of France is Paris')
21
+ end
22
+
23
+ it 'stores item_type' do
24
+ expect(item.item_type).to eq(:fact)
25
+ end
26
+
27
+ it 'stores importance' do
28
+ expect(item.importance).to eq(0.8)
29
+ end
30
+
31
+ it 'stores store_id' do
32
+ expect(item.store_id).to eq(store_id)
33
+ end
34
+
35
+ it 'clamps importance above 1.0 to 1.0' do
36
+ overimportant = described_class.new(content: 'x', item_type: :fact, importance: 1.5, store_id: store_id)
37
+ expect(overimportant.importance).to eq(1.0)
38
+ end
39
+
40
+ it 'clamps importance below 0.0 to 0.0' do
41
+ negative = described_class.new(content: 'x', item_type: :fact, importance: -0.5, store_id: store_id)
42
+ expect(negative.importance).to eq(0.0)
43
+ end
44
+
45
+ it 'initializes retrieved_count to 0' do
46
+ expect(item.retrieved_count).to eq(0)
47
+ end
48
+
49
+ it 'initializes last_retrieved_at to nil' do
50
+ expect(item.last_retrieved_at).to be_nil
51
+ end
52
+
53
+ it 'records offloaded_at as a Time' do
54
+ expect(item.offloaded_at).to be_a(Time)
55
+ end
56
+ end
57
+
58
+ describe '#retrieve!' do
59
+ it 'increments retrieved_count' do
60
+ item.retrieve!
61
+ expect(item.retrieved_count).to eq(1)
62
+ end
63
+
64
+ it 'updates last_retrieved_at' do
65
+ item.retrieve!
66
+ expect(item.last_retrieved_at).to be_a(Time)
67
+ end
68
+
69
+ it 'returns self for chaining' do
70
+ expect(item.retrieve!).to eq(item)
71
+ end
72
+
73
+ it 'accumulates multiple retrievals' do
74
+ 3.times { item.retrieve! }
75
+ expect(item.retrieved_count).to eq(3)
76
+ end
77
+ end
78
+
79
+ describe '#stale?' do
80
+ it 'returns false for newly offloaded item with no retrievals' do
81
+ expect(item.stale?(threshold_seconds: 3600)).to be false
82
+ end
83
+
84
+ it 'returns true when last retrieved time exceeds threshold' do
85
+ item.retrieve!
86
+ item.instance_variable_set(:@last_retrieved_at, Time.now.utc - 7200)
87
+ expect(item.stale?(threshold_seconds: 3600)).to be true
88
+ end
89
+
90
+ it 'returns false when within threshold' do
91
+ item.retrieve!
92
+ expect(item.stale?(threshold_seconds: 3600)).to be false
93
+ end
94
+ end
95
+
96
+ describe '#importance_label' do
97
+ it 'returns :critical for high importance' do
98
+ expect(item.importance_label).to eq(:critical)
99
+ end
100
+
101
+ it 'returns :trivial for very low importance' do
102
+ trivial = described_class.new(content: 'x', item_type: :fact, importance: 0.1, store_id: store_id)
103
+ expect(trivial.importance_label).to eq(:trivial)
104
+ end
105
+
106
+ it 'returns :moderate for mid-range importance' do
107
+ moderate = described_class.new(content: 'x', item_type: :fact, importance: 0.5, store_id: store_id)
108
+ expect(moderate.importance_label).to eq(:moderate)
109
+ end
110
+ end
111
+
112
+ describe '#to_h' do
113
+ it 'returns a hash with expected keys' do
114
+ h = item.to_h
115
+ expect(h).to include(:id, :content, :item_type, :importance, :importance_label,
116
+ :store_id, :offloaded_at, :retrieved_count, :last_retrieved_at)
117
+ end
118
+
119
+ it 'rounds importance to 10 decimal places' do
120
+ expect(item.to_h[:importance]).to eq(item.importance.round(10))
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,250 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::CognitiveOffloading::Helpers::OffloadingEngine do
4
+ let(:engine) { described_class.new }
5
+ let(:store) { engine.register_store(name: 'my_store', store_type: :database) }
6
+
7
+ describe '#register_store' do
8
+ it 'creates and returns a store' do
9
+ expect(store).to be_a(Legion::Extensions::CognitiveOffloading::Helpers::ExternalStore)
10
+ end
11
+
12
+ it 'adds the store to @stores' do
13
+ store
14
+ expect(engine.stores.size).to eq(1)
15
+ end
16
+
17
+ it 'returns nil when at MAX_STORES limit' do
18
+ stub_const('Legion::Extensions::CognitiveOffloading::Helpers::Constants::MAX_STORES', 1)
19
+ engine.register_store(name: 'first', store_type: :file)
20
+ second = engine.register_store(name: 'second', store_type: :file)
21
+ expect(second).to be_nil
22
+ end
23
+ end
24
+
25
+ describe '#offload' do
26
+ it 'creates and returns an offloaded item' do
27
+ item = engine.offload(content: 'remember this', item_type: :fact, importance: 0.6, store_id: store.id)
28
+ expect(item).to be_a(Legion::Extensions::CognitiveOffloading::Helpers::OffloadedItem)
29
+ end
30
+
31
+ it 'adds item to @items' do
32
+ engine.offload(content: 'remember this', item_type: :fact, importance: 0.6, store_id: store.id)
33
+ expect(engine.items.size).to eq(1)
34
+ end
35
+
36
+ it 'increments items_stored on the store' do
37
+ engine.offload(content: 'x', item_type: :fact, importance: 0.5, store_id: store.id)
38
+ expect(store.items_stored).to eq(1)
39
+ end
40
+
41
+ it 'returns nil for unknown store_id' do
42
+ item = engine.offload(content: 'x', item_type: :fact, importance: 0.5, store_id: 'bad-id')
43
+ expect(item).to be_nil
44
+ end
45
+
46
+ it 'returns nil when at MAX_ITEMS limit' do
47
+ stub_const('Legion::Extensions::CognitiveOffloading::Helpers::Constants::MAX_ITEMS', 1)
48
+ engine.offload(content: 'first', item_type: :fact, importance: 0.5, store_id: store.id)
49
+ second = engine.offload(content: 'second', item_type: :fact, importance: 0.5, store_id: store.id)
50
+ expect(second).to be_nil
51
+ end
52
+ end
53
+
54
+ describe '#retrieve' do
55
+ let!(:item) { engine.offload(content: 'test', item_type: :reminder, importance: 0.4, store_id: store.id) }
56
+
57
+ it 'returns the item' do
58
+ retrieved = engine.retrieve(item_id: item.id)
59
+ expect(retrieved).to eq(item)
60
+ end
61
+
62
+ it 'increments item retrieved_count' do
63
+ engine.retrieve(item_id: item.id)
64
+ expect(item.retrieved_count).to eq(1)
65
+ end
66
+
67
+ it 'records success on the store' do
68
+ engine.retrieve(item_id: item.id)
69
+ expect(store.successful_retrievals).to eq(1)
70
+ end
71
+
72
+ it 'returns nil for unknown item_id' do
73
+ expect(engine.retrieve(item_id: 'no-such-item')).to be_nil
74
+ end
75
+ end
76
+
77
+ describe '#retrieve_failed' do
78
+ let!(:item) { engine.offload(content: 'test', item_type: :fact, importance: 0.5, store_id: store.id) }
79
+
80
+ it 'returns the item' do
81
+ expect(engine.retrieve_failed(item_id: item.id)).to eq(item)
82
+ end
83
+
84
+ it 'records failure on the store' do
85
+ engine.retrieve_failed(item_id: item.id)
86
+ expect(store.failed_retrievals).to eq(1)
87
+ end
88
+
89
+ it 'decays store trust' do
90
+ trust_before = store.trust
91
+ engine.retrieve_failed(item_id: item.id)
92
+ expect(store.trust).to be < trust_before
93
+ end
94
+
95
+ it 'returns nil for unknown item_id' do
96
+ expect(engine.retrieve_failed(item_id: 'ghost')).to be_nil
97
+ end
98
+ end
99
+
100
+ describe '#items_in_store' do
101
+ it 'returns items belonging to the given store' do
102
+ engine.offload(content: 'a', item_type: :fact, importance: 0.5, store_id: store.id)
103
+ engine.offload(content: 'b', item_type: :fact, importance: 0.5, store_id: store.id)
104
+ other_store = engine.register_store(name: 'other', store_type: :file)
105
+ engine.offload(content: 'c', item_type: :fact, importance: 0.5, store_id: other_store.id)
106
+
107
+ items = engine.items_in_store(store_id: store.id)
108
+ expect(items.size).to eq(2)
109
+ end
110
+
111
+ it 'returns empty array for store with no items' do
112
+ expect(engine.items_in_store(store_id: store.id)).to eq([])
113
+ end
114
+ end
115
+
116
+ describe '#items_by_type' do
117
+ before do
118
+ engine.offload(content: 'a', item_type: :fact, importance: 0.5, store_id: store.id)
119
+ engine.offload(content: 'b', item_type: :fact, importance: 0.6, store_id: store.id)
120
+ engine.offload(content: 'c', item_type: :plan, importance: 0.7, store_id: store.id)
121
+ end
122
+
123
+ it 'returns items of the given type' do
124
+ expect(engine.items_by_type(item_type: :fact).size).to eq(2)
125
+ end
126
+
127
+ it 'returns empty array for unused type' do
128
+ expect(engine.items_by_type(item_type: :calculation)).to eq([])
129
+ end
130
+ end
131
+
132
+ describe '#most_important_offloaded' do
133
+ before do
134
+ engine.offload(content: 'low', item_type: :fact, importance: 0.2, store_id: store.id)
135
+ engine.offload(content: 'high', item_type: :fact, importance: 0.9, store_id: store.id)
136
+ engine.offload(content: 'mid', item_type: :fact, importance: 0.5, store_id: store.id)
137
+ end
138
+
139
+ it 'returns items sorted by importance descending' do
140
+ items = engine.most_important_offloaded(limit: 3)
141
+ importances = items.map(&:importance)
142
+ expect(importances).to eq(importances.sort.reverse)
143
+ end
144
+
145
+ it 'respects limit' do
146
+ items = engine.most_important_offloaded(limit: 2)
147
+ expect(items.size).to eq(2)
148
+ end
149
+
150
+ it 'returns the highest importance item first' do
151
+ items = engine.most_important_offloaded(limit: 1)
152
+ expect(items.first.importance).to eq(0.9)
153
+ end
154
+ end
155
+
156
+ describe '#offloading_ratio' do
157
+ it 'returns 0.0 with no items' do
158
+ expect(engine.offloading_ratio).to eq(0.0)
159
+ end
160
+
161
+ it 'increases as items are added' do
162
+ engine.offload(content: 'x', item_type: :fact, importance: 0.5, store_id: store.id)
163
+ expect(engine.offloading_ratio).to be > 0.0
164
+ end
165
+ end
166
+
167
+ describe '#overall_store_trust' do
168
+ it 'returns 0.0 with no stores' do
169
+ expect(described_class.new.overall_store_trust).to eq(0.0)
170
+ end
171
+
172
+ it 'returns average trust across stores' do
173
+ s1 = engine.register_store(name: 's1', store_type: :file)
174
+ s2 = engine.register_store(name: 's2', store_type: :notes)
175
+ s1.record_failure!
176
+ expected = ((s1.trust + s2.trust + store.trust) / 3.0).round(10)
177
+ expect(engine.overall_store_trust).to eq(expected)
178
+ end
179
+ end
180
+
181
+ describe '#most_trusted_store' do
182
+ it 'returns nil with no stores' do
183
+ expect(described_class.new.most_trusted_store).to be_nil
184
+ end
185
+
186
+ it 'returns the store with highest trust' do
187
+ s2 = engine.register_store(name: 's2', store_type: :file)
188
+ s2.record_success!
189
+ expect(engine.most_trusted_store.trust).to be >= store.trust
190
+ end
191
+ end
192
+
193
+ describe '#least_trusted_store' do
194
+ it 'returns nil with no stores' do
195
+ expect(described_class.new.least_trusted_store).to be_nil
196
+ end
197
+
198
+ it 'returns the store with lowest trust' do
199
+ s2 = engine.register_store(name: 's2', store_type: :file)
200
+ 3.times { s2.record_failure! }
201
+ expect(engine.least_trusted_store.trust).to be <= store.trust
202
+ end
203
+ end
204
+
205
+ describe '#offloading_report' do
206
+ before do
207
+ engine.offload(content: 'fact one', item_type: :fact, importance: 0.8, store_id: store.id)
208
+ engine.offload(content: 'plan one', item_type: :plan, importance: 0.6, store_id: store.id)
209
+ end
210
+
211
+ it 'includes total_items count' do
212
+ expect(engine.offloading_report[:total_items]).to eq(2)
213
+ end
214
+
215
+ it 'includes total_stores count' do
216
+ expect(engine.offloading_report[:total_stores]).to eq(1)
217
+ end
218
+
219
+ it 'includes offloading_ratio' do
220
+ expect(engine.offloading_report[:offloading_ratio]).to be > 0.0
221
+ end
222
+
223
+ it 'includes offloading_label' do
224
+ expect(engine.offloading_report[:offloading_label]).to be_a(Symbol)
225
+ end
226
+
227
+ it 'includes overall_store_trust' do
228
+ expect(engine.offloading_report[:overall_store_trust]).to be_a(Float)
229
+ end
230
+
231
+ it 'includes stores_summary array' do
232
+ expect(engine.offloading_report[:stores_summary]).to be_an(Array)
233
+ end
234
+
235
+ it 'includes items_by_type breakdown' do
236
+ breakdown = engine.offloading_report[:items_by_type]
237
+ expect(breakdown[:fact]).to eq(1)
238
+ expect(breakdown[:plan]).to eq(1)
239
+ end
240
+ end
241
+
242
+ describe '#to_h' do
243
+ it 'returns items and stores as hashes' do
244
+ engine.offload(content: 'x', item_type: :fact, importance: 0.5, store_id: store.id)
245
+ h = engine.to_h
246
+ expect(h).to have_key(:items)
247
+ expect(h).to have_key(:stores)
248
+ end
249
+ end
250
+ end