lex-cognitive-surplus 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.
- checksums.yaml +7 -0
- data/Gemfile +11 -0
- data/LICENSE +21 -0
- data/README.md +65 -0
- data/lex-cognitive-surplus.gemspec +29 -0
- data/lib/legion/extensions/cognitive_surplus/actors/replenish.rb +41 -0
- data/lib/legion/extensions/cognitive_surplus/client.rb +24 -0
- data/lib/legion/extensions/cognitive_surplus/helpers/allocation.rb +43 -0
- data/lib/legion/extensions/cognitive_surplus/helpers/constants.rb +51 -0
- data/lib/legion/extensions/cognitive_surplus/helpers/surplus_engine.rb +115 -0
- data/lib/legion/extensions/cognitive_surplus/runners/surplus.rb +82 -0
- data/lib/legion/extensions/cognitive_surplus/version.rb +9 -0
- data/lib/legion/extensions/cognitive_surplus.rb +15 -0
- data/spec/legion/extensions/cognitive_surplus/client_spec.rb +17 -0
- data/spec/legion/extensions/cognitive_surplus/helpers/allocation_spec.rb +51 -0
- data/spec/legion/extensions/cognitive_surplus/helpers/constants_spec.rb +88 -0
- data/spec/legion/extensions/cognitive_surplus/helpers/surplus_engine_spec.rb +159 -0
- data/spec/legion/extensions/cognitive_surplus/runners/surplus_spec.rb +119 -0
- data/spec/spec_helper.rb +20 -0
- metadata +78 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 4d1218ba7a84db0e3fce82ba7d687bc72d748be64f25685f533ac917bb2f2d7e
|
|
4
|
+
data.tar.gz: 54e81c8dbf7544966cbb68a8d9bcd0452743cb65692ae2ba49c5a15937ba9193
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 23b974597e6d8e95cf1a41542be23b73f381c886d4093b04b320e24f63be5aa610247e70d044d34de0e1799cd39c194a1d4f0aa8c094d2c50f168daafab48ef0
|
|
7
|
+
data.tar.gz: a644352549a5675bdaf311ad9e7e695ce1b3a96ffbdc639e456c8d6703466518d9b957e1dee3edc7f70d41f78460f0e369ad7a5e919316c35654332fff6e361e
|
data/Gemfile
ADDED
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Matthew Iverson
|
|
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.
|
data/README.md
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# lex-cognitive-surplus
|
|
2
|
+
|
|
3
|
+
A LegionIO cognitive architecture extension that models cognitive surplus — the available mental bandwidth beyond what is committed to current tasks. Capacity can be committed, allocated to named activities, and automatically replenished over time.
|
|
4
|
+
|
|
5
|
+
## What It Does
|
|
6
|
+
|
|
7
|
+
Tracks a single cognitive capacity pool (total = 1.0) divided into three layers:
|
|
8
|
+
|
|
9
|
+
- **Committed**: capacity actively consumed by ongoing work
|
|
10
|
+
- **Reserved**: baseline always held back (0.1) — never allocatable
|
|
11
|
+
- **Active allocations**: named reservations for specific activity types
|
|
12
|
+
|
|
13
|
+
Available surplus is what remains after all three. When surplus falls below 0.15 (the threshold), no new allocations are accepted. The built-in actor replenishes capacity automatically every 60 seconds by reducing committed load.
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
```ruby
|
|
18
|
+
require 'lex-cognitive-surplus'
|
|
19
|
+
|
|
20
|
+
client = Legion::Extensions::CognitiveSurplus::Client.new
|
|
21
|
+
|
|
22
|
+
# Check current surplus
|
|
23
|
+
client.surplus_status
|
|
24
|
+
# => { available_surplus: 0.9, surplus_label: :abundant, quality: 1.0, quality_label: :peak, ... }
|
|
25
|
+
|
|
26
|
+
# Commit capacity to a task
|
|
27
|
+
client.commit_capacity(amount: 0.4)
|
|
28
|
+
# => { committed: 0.4, available_surplus: 0.5 }
|
|
29
|
+
|
|
30
|
+
# Allocate some surplus for exploration
|
|
31
|
+
result = client.allocate_surplus(activity_type: :exploration, amount: 0.2)
|
|
32
|
+
# => { allocated: true, allocation_id: "uuid...", amount: 0.2, quality: 0.6, activity_type: :exploration }
|
|
33
|
+
|
|
34
|
+
# Release the allocation when done
|
|
35
|
+
client.release_surplus(allocation_id: result[:allocation_id])
|
|
36
|
+
# => { released: true, allocation_id: "uuid...", amount: 0.2 }
|
|
37
|
+
|
|
38
|
+
# Uncommit task capacity when work is finished
|
|
39
|
+
client.uncommit_capacity(amount: 0.4)
|
|
40
|
+
# => { committed: 0.0, available_surplus: 0.9 }
|
|
41
|
+
|
|
42
|
+
# Simulate a demanding event depleting surplus
|
|
43
|
+
client.deplete_surplus(amount: 0.3)
|
|
44
|
+
# => { depleted: true, amount: 0.3, available_surplus: 0.6 }
|
|
45
|
+
|
|
46
|
+
# Manually trigger replenishment (also fires automatically every 60s)
|
|
47
|
+
client.replenish_surplus
|
|
48
|
+
# => { replenished: true, gained: 0.05, available_surplus: 0.65 }
|
|
49
|
+
|
|
50
|
+
# List active allocations
|
|
51
|
+
client.surplus_allocations
|
|
52
|
+
# => { allocations: [...], count: 0 }
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Development
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
bundle install
|
|
59
|
+
bundle exec rspec
|
|
60
|
+
bundle exec rubocop
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## License
|
|
64
|
+
|
|
65
|
+
MIT
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'lib/legion/extensions/cognitive_surplus/version'
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = 'lex-cognitive-surplus'
|
|
7
|
+
spec.version = Legion::Extensions::CognitiveSurplus::VERSION
|
|
8
|
+
spec.authors = ['Esity']
|
|
9
|
+
spec.email = ['matthewdiverson@gmail.com']
|
|
10
|
+
|
|
11
|
+
spec.summary = 'LEX Cognitive Surplus'
|
|
12
|
+
spec.description = 'Cognitive surplus capacity modeling for brain-modeled agentic AI'
|
|
13
|
+
spec.homepage = 'https://github.com/LegionIO/lex-cognitive-surplus'
|
|
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-cognitive-surplus'
|
|
19
|
+
spec.metadata['documentation_uri'] = 'https://github.com/LegionIO/lex-cognitive-surplus'
|
|
20
|
+
spec.metadata['changelog_uri'] = 'https://github.com/LegionIO/lex-cognitive-surplus'
|
|
21
|
+
spec.metadata['bug_tracker_uri'] = 'https://github.com/LegionIO/lex-cognitive-surplus/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-cognitive-surplus.gemspec Gemfile LICENSE README.md]
|
|
26
|
+
end
|
|
27
|
+
spec.require_paths = ['lib']
|
|
28
|
+
spec.add_development_dependency 'legion-gaia'
|
|
29
|
+
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 CognitiveSurplus
|
|
8
|
+
module Actor
|
|
9
|
+
class Replenish < Legion::Extensions::Actors::Every
|
|
10
|
+
def runner_class
|
|
11
|
+
Legion::Extensions::CognitiveSurplus::Runners::Surplus
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def runner_function
|
|
15
|
+
'replenish_surplus'
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def time
|
|
19
|
+
60
|
|
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,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/extensions/cognitive_surplus/helpers/constants'
|
|
4
|
+
require 'legion/extensions/cognitive_surplus/helpers/allocation'
|
|
5
|
+
require 'legion/extensions/cognitive_surplus/helpers/surplus_engine'
|
|
6
|
+
require 'legion/extensions/cognitive_surplus/runners/surplus'
|
|
7
|
+
|
|
8
|
+
module Legion
|
|
9
|
+
module Extensions
|
|
10
|
+
module CognitiveSurplus
|
|
11
|
+
class Client
|
|
12
|
+
include Runners::Surplus
|
|
13
|
+
|
|
14
|
+
def initialize(**)
|
|
15
|
+
@surplus_engine = Helpers::SurplusEngine.new
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
attr_reader :surplus_engine
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'securerandom'
|
|
4
|
+
|
|
5
|
+
module Legion
|
|
6
|
+
module Extensions
|
|
7
|
+
module CognitiveSurplus
|
|
8
|
+
module Helpers
|
|
9
|
+
class Allocation
|
|
10
|
+
attr_reader :id, :activity_type, :amount, :quality, :created_at
|
|
11
|
+
|
|
12
|
+
def initialize(activity_type:, amount:, quality:)
|
|
13
|
+
@id = SecureRandom.uuid
|
|
14
|
+
@activity_type = activity_type
|
|
15
|
+
@amount = amount.round(10)
|
|
16
|
+
@quality = quality.round(10)
|
|
17
|
+
@created_at = Time.now.utc
|
|
18
|
+
@released = false
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def release!
|
|
22
|
+
@released = true
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def active?
|
|
26
|
+
!@released
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def to_h
|
|
30
|
+
{
|
|
31
|
+
id: @id,
|
|
32
|
+
activity_type: @activity_type,
|
|
33
|
+
amount: @amount,
|
|
34
|
+
quality: @quality,
|
|
35
|
+
active: active?,
|
|
36
|
+
created_at: @created_at
|
|
37
|
+
}
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module CognitiveSurplus
|
|
6
|
+
module Helpers
|
|
7
|
+
module Constants
|
|
8
|
+
TOTAL_CAPACITY = 1.0
|
|
9
|
+
MIN_RESERVE = 0.1
|
|
10
|
+
SURPLUS_THRESHOLD = 0.15
|
|
11
|
+
REPLENISH_RATE = 0.05
|
|
12
|
+
DEPLETION_RATE = 0.08
|
|
13
|
+
|
|
14
|
+
SURPLUS_LABELS = {
|
|
15
|
+
abundant: (0.6..1.0),
|
|
16
|
+
moderate: (0.3...0.6),
|
|
17
|
+
scarce: (0.15...0.3),
|
|
18
|
+
depleted: (0.0...0.15)
|
|
19
|
+
}.freeze
|
|
20
|
+
|
|
21
|
+
QUALITY_LABELS = {
|
|
22
|
+
peak: (0.8..1.0),
|
|
23
|
+
rested: (0.6...0.8),
|
|
24
|
+
residual: (0.3...0.6),
|
|
25
|
+
degraded: (0.0...0.3)
|
|
26
|
+
}.freeze
|
|
27
|
+
|
|
28
|
+
ALLOCATION_TYPES = %i[exploration consolidation speculation maintenance creative].freeze
|
|
29
|
+
|
|
30
|
+
module_function
|
|
31
|
+
|
|
32
|
+
def surplus_label(amount)
|
|
33
|
+
SURPLUS_LABELS.find { |_label, range| range.cover?(amount) }&.first || :none
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def quality_label(quality)
|
|
37
|
+
QUALITY_LABELS.find { |_label, range| range.cover?(quality) }&.first || :unknown
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def valid_allocation_type?(type)
|
|
41
|
+
ALLOCATION_TYPES.include?(type)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def clamp(value, min = 0.0, max = 1.0)
|
|
45
|
+
value.clamp(min, max)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module CognitiveSurplus
|
|
6
|
+
module Helpers
|
|
7
|
+
class SurplusEngine
|
|
8
|
+
include Constants
|
|
9
|
+
|
|
10
|
+
attr_reader :committed, :reserved, :allocations
|
|
11
|
+
|
|
12
|
+
def initialize
|
|
13
|
+
@committed = 0.0
|
|
14
|
+
@reserved = Constants::MIN_RESERVE
|
|
15
|
+
@allocations = {}
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def available_surplus
|
|
19
|
+
used = @committed + @reserved + active_allocated
|
|
20
|
+
Constants.clamp(Constants::TOTAL_CAPACITY - used).round(10)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def surplus_quality
|
|
24
|
+
idle = Constants::TOTAL_CAPACITY - @committed
|
|
25
|
+
raw = idle / Constants::TOTAL_CAPACITY
|
|
26
|
+
Constants.clamp(raw).round(10)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def allocate!(activity_type:, amount:)
|
|
30
|
+
return { allocated: false, reason: :invalid_type } unless Constants.valid_allocation_type?(activity_type)
|
|
31
|
+
return { allocated: false, reason: :below_threshold } if available_surplus < Constants::SURPLUS_THRESHOLD
|
|
32
|
+
|
|
33
|
+
clamped = Constants.clamp(amount, 0.0, available_surplus).round(10)
|
|
34
|
+
return { allocated: false, reason: :insufficient_surplus } if clamped <= 0.0
|
|
35
|
+
|
|
36
|
+
quality = surplus_quality
|
|
37
|
+
allocation = Allocation.new(activity_type: activity_type, amount: clamped, quality: quality)
|
|
38
|
+
@allocations[allocation.id] = allocation
|
|
39
|
+
|
|
40
|
+
{
|
|
41
|
+
allocated: true,
|
|
42
|
+
allocation_id: allocation.id,
|
|
43
|
+
amount: clamped,
|
|
44
|
+
quality: quality,
|
|
45
|
+
activity_type: activity_type
|
|
46
|
+
}
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def release!(allocation_id:)
|
|
50
|
+
allocation = @allocations[allocation_id]
|
|
51
|
+
return { released: false, reason: :not_found } unless allocation
|
|
52
|
+
return { released: false, reason: :already_released } unless allocation.active?
|
|
53
|
+
|
|
54
|
+
allocation.release!
|
|
55
|
+
{ released: true, allocation_id: allocation_id, amount: allocation.amount }
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def commit!(amount:)
|
|
59
|
+
clamped = Constants.clamp(amount.round(10), 0.0, Constants::TOTAL_CAPACITY - @reserved)
|
|
60
|
+
@committed = Constants.clamp((@committed + clamped).round(10))
|
|
61
|
+
{ committed: @committed, available_surplus: available_surplus }
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def uncommit!(amount:)
|
|
65
|
+
clamped = Constants.clamp(amount.round(10), 0.0, @committed)
|
|
66
|
+
@committed = Constants.clamp((@committed - clamped).round(10))
|
|
67
|
+
{ committed: @committed, available_surplus: available_surplus }
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def replenish!
|
|
71
|
+
old_committed = @committed
|
|
72
|
+
@committed = Constants.clamp((@committed - Constants::REPLENISH_RATE).round(10))
|
|
73
|
+
gained = (old_committed - @committed).round(10)
|
|
74
|
+
{ replenished: true, gained: gained, available_surplus: available_surplus }
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def deplete!(amount:)
|
|
78
|
+
delta = Constants.clamp(amount.round(10), 0.0, available_surplus)
|
|
79
|
+
@committed = Constants.clamp((@committed + delta).round(10))
|
|
80
|
+
{ depleted: true, amount: delta, available_surplus: available_surplus }
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def surplus_report
|
|
84
|
+
surplus = available_surplus
|
|
85
|
+
{
|
|
86
|
+
total_capacity: Constants::TOTAL_CAPACITY,
|
|
87
|
+
committed: @committed.round(10),
|
|
88
|
+
reserved: @reserved.round(10),
|
|
89
|
+
active_allocated: active_allocated.round(10),
|
|
90
|
+
available_surplus: surplus,
|
|
91
|
+
surplus_label: Constants.surplus_label(surplus),
|
|
92
|
+
quality: surplus_quality,
|
|
93
|
+
quality_label: Constants.quality_label(surplus_quality),
|
|
94
|
+
allocation_count: active_allocations.size
|
|
95
|
+
}
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def to_h
|
|
99
|
+
surplus_report.merge(allocations: @allocations.transform_values(&:to_h))
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
private
|
|
103
|
+
|
|
104
|
+
def active_allocations
|
|
105
|
+
@allocations.values.select(&:active?)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def active_allocated
|
|
109
|
+
active_allocations.sum(&:amount)
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module CognitiveSurplus
|
|
6
|
+
module Runners
|
|
7
|
+
module Surplus
|
|
8
|
+
include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers) &&
|
|
9
|
+
Legion::Extensions::Helpers.const_defined?(:Lex)
|
|
10
|
+
|
|
11
|
+
def surplus_status(**)
|
|
12
|
+
report = surplus_engine.surplus_report
|
|
13
|
+
Legion::Logging.debug "[cognitive_surplus] status surplus=#{report[:available_surplus].round(2)} " \
|
|
14
|
+
"label=#{report[:surplus_label]} quality=#{report[:quality_label]}"
|
|
15
|
+
report
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def allocate_surplus(activity_type:, amount: Helpers::Constants::SURPLUS_THRESHOLD, **)
|
|
19
|
+
result = surplus_engine.allocate!(activity_type: activity_type, amount: amount)
|
|
20
|
+
if result[:allocated]
|
|
21
|
+
Legion::Logging.info "[cognitive_surplus] allocated #{amount.round(2)} to " \
|
|
22
|
+
"#{activity_type} quality=#{result[:quality].round(2)} id=#{result[:allocation_id][0..7]}"
|
|
23
|
+
else
|
|
24
|
+
Legion::Logging.debug "[cognitive_surplus] allocate failed: #{result[:reason]}"
|
|
25
|
+
end
|
|
26
|
+
result
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def release_surplus(allocation_id:, **)
|
|
30
|
+
result = surplus_engine.release!(allocation_id: allocation_id)
|
|
31
|
+
if result[:released]
|
|
32
|
+
Legion::Logging.info "[cognitive_surplus] released allocation #{allocation_id[0..7]} amount=#{result[:amount].round(2)}"
|
|
33
|
+
else
|
|
34
|
+
Legion::Logging.debug "[cognitive_surplus] release failed: #{result[:reason]} id=#{allocation_id[0..7]}"
|
|
35
|
+
end
|
|
36
|
+
result
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def commit_capacity(amount:, **)
|
|
40
|
+
result = surplus_engine.commit!(amount: amount)
|
|
41
|
+
Legion::Logging.debug "[cognitive_surplus] committed #{amount.round(2)} committed=#{result[:committed].round(2)} " \
|
|
42
|
+
"surplus=#{result[:available_surplus].round(2)}"
|
|
43
|
+
result
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def uncommit_capacity(amount:, **)
|
|
47
|
+
result = surplus_engine.uncommit!(amount: amount)
|
|
48
|
+
Legion::Logging.debug "[cognitive_surplus] uncommitted #{amount.round(2)} committed=#{result[:committed].round(2)} " \
|
|
49
|
+
"surplus=#{result[:available_surplus].round(2)}"
|
|
50
|
+
result
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def replenish_surplus(**)
|
|
54
|
+
result = surplus_engine.replenish!
|
|
55
|
+
Legion::Logging.debug "[cognitive_surplus] replenished gained=#{result[:gained].round(2)} " \
|
|
56
|
+
"surplus=#{result[:available_surplus].round(2)}"
|
|
57
|
+
result
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def deplete_surplus(amount:, **)
|
|
61
|
+
result = surplus_engine.deplete!(amount: amount)
|
|
62
|
+
Legion::Logging.debug "[cognitive_surplus] depleted amount=#{result[:amount].round(2)} " \
|
|
63
|
+
"surplus=#{result[:available_surplus].round(2)}"
|
|
64
|
+
result
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def surplus_allocations(**)
|
|
68
|
+
allocations = surplus_engine.allocations.values.select(&:active?).map(&:to_h)
|
|
69
|
+
Legion::Logging.debug "[cognitive_surplus] allocations count=#{allocations.size}"
|
|
70
|
+
{ allocations: allocations, count: allocations.size }
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
private
|
|
74
|
+
|
|
75
|
+
def surplus_engine
|
|
76
|
+
@surplus_engine ||= Helpers::SurplusEngine.new
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/extensions/cognitive_surplus/version'
|
|
4
|
+
require 'legion/extensions/cognitive_surplus/helpers/constants'
|
|
5
|
+
require 'legion/extensions/cognitive_surplus/helpers/allocation'
|
|
6
|
+
require 'legion/extensions/cognitive_surplus/helpers/surplus_engine'
|
|
7
|
+
require 'legion/extensions/cognitive_surplus/runners/surplus'
|
|
8
|
+
|
|
9
|
+
module Legion
|
|
10
|
+
module Extensions
|
|
11
|
+
module CognitiveSurplus
|
|
12
|
+
extend Legion::Extensions::Core if Legion::Extensions.const_defined? :Core
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/extensions/cognitive_surplus/client'
|
|
4
|
+
|
|
5
|
+
RSpec.describe Legion::Extensions::CognitiveSurplus::Client do
|
|
6
|
+
it 'responds to surplus runner methods' do
|
|
7
|
+
client = described_class.new
|
|
8
|
+
expect(client).to respond_to(:surplus_status)
|
|
9
|
+
expect(client).to respond_to(:allocate_surplus)
|
|
10
|
+
expect(client).to respond_to(:release_surplus)
|
|
11
|
+
expect(client).to respond_to(:commit_capacity)
|
|
12
|
+
expect(client).to respond_to(:uncommit_capacity)
|
|
13
|
+
expect(client).to respond_to(:replenish_surplus)
|
|
14
|
+
expect(client).to respond_to(:deplete_surplus)
|
|
15
|
+
expect(client).to respond_to(:surplus_allocations)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
RSpec.describe Legion::Extensions::CognitiveSurplus::Helpers::Allocation do
|
|
4
|
+
subject(:allocation) { described_class.new(activity_type: :exploration, amount: 0.2, quality: 0.75) }
|
|
5
|
+
|
|
6
|
+
describe '#initialize' do
|
|
7
|
+
it 'assigns a uuid id' do
|
|
8
|
+
expect(allocation.id).to match(/\A[0-9a-f-]{36}\z/)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
it 'stores activity_type' do
|
|
12
|
+
expect(allocation.activity_type).to eq(:exploration)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it 'rounds amount to 10 decimal places' do
|
|
16
|
+
expect(allocation.amount).to eq(0.2)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it 'rounds quality to 10 decimal places' do
|
|
20
|
+
expect(allocation.quality).to eq(0.75)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it 'sets created_at' do
|
|
24
|
+
expect(allocation.created_at).to be_a(Time)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it 'starts active' do
|
|
28
|
+
expect(allocation.active?).to be true
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
describe '#release!' do
|
|
33
|
+
it 'deactivates the allocation' do
|
|
34
|
+
allocation.release!
|
|
35
|
+
expect(allocation.active?).to be false
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
describe '#to_h' do
|
|
40
|
+
it 'returns a hash with expected keys' do
|
|
41
|
+
h = allocation.to_h
|
|
42
|
+
expect(h).to include(:id, :activity_type, :amount, :quality, :active, :created_at)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
it 'reflects active state' do
|
|
46
|
+
expect(allocation.to_h[:active]).to be true
|
|
47
|
+
allocation.release!
|
|
48
|
+
expect(allocation.to_h[:active]).to be false
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
RSpec.describe Legion::Extensions::CognitiveSurplus::Helpers::Constants do
|
|
4
|
+
describe 'constants' do
|
|
5
|
+
it 'defines TOTAL_CAPACITY as 1.0' do
|
|
6
|
+
expect(described_class::TOTAL_CAPACITY).to eq(1.0)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
it 'defines MIN_RESERVE as 0.1' do
|
|
10
|
+
expect(described_class::MIN_RESERVE).to eq(0.1)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
it 'defines SURPLUS_THRESHOLD as 0.15' do
|
|
14
|
+
expect(described_class::SURPLUS_THRESHOLD).to eq(0.15)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it 'defines REPLENISH_RATE as 0.05' do
|
|
18
|
+
expect(described_class::REPLENISH_RATE).to eq(0.05)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it 'defines DEPLETION_RATE as 0.08' do
|
|
22
|
+
expect(described_class::DEPLETION_RATE).to eq(0.08)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it 'defines all ALLOCATION_TYPES' do
|
|
26
|
+
expect(described_class::ALLOCATION_TYPES).to include(:exploration, :consolidation, :speculation, :maintenance, :creative)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
describe '.surplus_label' do
|
|
31
|
+
it 'returns :abundant for high surplus' do
|
|
32
|
+
expect(described_class.surplus_label(0.8)).to eq(:abundant)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it 'returns :moderate for moderate surplus' do
|
|
36
|
+
expect(described_class.surplus_label(0.4)).to eq(:moderate)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
it 'returns :scarce for scarce surplus' do
|
|
40
|
+
expect(described_class.surplus_label(0.2)).to eq(:scarce)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
it 'returns :depleted for very low surplus' do
|
|
44
|
+
expect(described_class.surplus_label(0.05)).to eq(:depleted)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
it 'returns :none for zero surplus' do
|
|
48
|
+
expect(described_class.surplus_label(0.0)).to eq(:depleted)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
describe '.quality_label' do
|
|
53
|
+
it 'returns :peak for high quality' do
|
|
54
|
+
expect(described_class.quality_label(0.9)).to eq(:peak)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
it 'returns :rested for good quality' do
|
|
58
|
+
expect(described_class.quality_label(0.7)).to eq(:rested)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
it 'returns :residual for moderate quality' do
|
|
62
|
+
expect(described_class.quality_label(0.4)).to eq(:residual)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it 'returns :degraded for low quality' do
|
|
66
|
+
expect(described_class.quality_label(0.1)).to eq(:degraded)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
describe '.valid_allocation_type?' do
|
|
71
|
+
it 'returns true for valid types' do
|
|
72
|
+
expect(described_class.valid_allocation_type?(:exploration)).to be true
|
|
73
|
+
expect(described_class.valid_allocation_type?(:creative)).to be true
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
it 'returns false for invalid types' do
|
|
77
|
+
expect(described_class.valid_allocation_type?(:unknown)).to be false
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
describe '.clamp' do
|
|
82
|
+
it 'clamps to [0.0, 1.0] by default' do
|
|
83
|
+
expect(described_class.clamp(1.5)).to eq(1.0)
|
|
84
|
+
expect(described_class.clamp(-0.5)).to eq(0.0)
|
|
85
|
+
expect(described_class.clamp(0.5)).to eq(0.5)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
RSpec.describe Legion::Extensions::CognitiveSurplus::Helpers::SurplusEngine do
|
|
4
|
+
subject(:engine) { described_class.new }
|
|
5
|
+
|
|
6
|
+
describe '#available_surplus' do
|
|
7
|
+
it 'starts at TOTAL_CAPACITY minus MIN_RESERVE' do
|
|
8
|
+
expected = (Legion::Extensions::CognitiveSurplus::Helpers::Constants::TOTAL_CAPACITY -
|
|
9
|
+
Legion::Extensions::CognitiveSurplus::Helpers::Constants::MIN_RESERVE).round(10)
|
|
10
|
+
expect(engine.available_surplus).to eq(expected)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
it 'decreases after commit' do
|
|
14
|
+
before = engine.available_surplus
|
|
15
|
+
engine.commit!(amount: 0.2)
|
|
16
|
+
expect(engine.available_surplus).to be < before
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
describe '#surplus_quality' do
|
|
21
|
+
it 'returns 1.0 when nothing is committed' do
|
|
22
|
+
expect(engine.surplus_quality).to eq(1.0)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it 'decreases as committed increases' do
|
|
26
|
+
engine.commit!(amount: 0.5)
|
|
27
|
+
expect(engine.surplus_quality).to be < 1.0
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
describe '#allocate!' do
|
|
32
|
+
it 'returns allocated: true for valid type with sufficient surplus' do
|
|
33
|
+
result = engine.allocate!(activity_type: :exploration, amount: 0.1)
|
|
34
|
+
expect(result[:allocated]).to be true
|
|
35
|
+
expect(result[:allocation_id]).to match(/\A[0-9a-f-]{36}\z/)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
it 'returns error for invalid allocation type' do
|
|
39
|
+
result = engine.allocate!(activity_type: :invalid, amount: 0.1)
|
|
40
|
+
expect(result[:allocated]).to be false
|
|
41
|
+
expect(result[:reason]).to eq(:invalid_type)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
it 'returns below_threshold when surplus is too low' do
|
|
45
|
+
engine.commit!(amount: 0.9)
|
|
46
|
+
result = engine.allocate!(activity_type: :exploration, amount: 0.05)
|
|
47
|
+
expect(result[:allocated]).to be false
|
|
48
|
+
expect(result[:reason]).to eq(:below_threshold)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
it 'includes quality in result' do
|
|
52
|
+
result = engine.allocate!(activity_type: :creative, amount: 0.1)
|
|
53
|
+
expect(result[:quality]).to be_between(0.0, 1.0)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
describe '#release!' do
|
|
58
|
+
it 'releases an active allocation' do
|
|
59
|
+
alloc = engine.allocate!(activity_type: :consolidation, amount: 0.1)
|
|
60
|
+
result = engine.release!(allocation_id: alloc[:allocation_id])
|
|
61
|
+
expect(result[:released]).to be true
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
it 'returns not_found for unknown id' do
|
|
65
|
+
result = engine.release!(allocation_id: 'nonexistent')
|
|
66
|
+
expect(result[:released]).to be false
|
|
67
|
+
expect(result[:reason]).to eq(:not_found)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
it 'returns already_released for double release' do
|
|
71
|
+
alloc = engine.allocate!(activity_type: :speculation, amount: 0.1)
|
|
72
|
+
engine.release!(allocation_id: alloc[:allocation_id])
|
|
73
|
+
result = engine.release!(allocation_id: alloc[:allocation_id])
|
|
74
|
+
expect(result[:released]).to be false
|
|
75
|
+
expect(result[:reason]).to eq(:already_released)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
describe '#commit!' do
|
|
80
|
+
it 'increases committed and decreases surplus' do
|
|
81
|
+
surplus_before = engine.available_surplus
|
|
82
|
+
result = engine.commit!(amount: 0.3)
|
|
83
|
+
expect(result[:committed]).to eq(0.3)
|
|
84
|
+
expect(result[:available_surplus]).to be < surplus_before
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
describe '#uncommit!' do
|
|
89
|
+
it 'decreases committed and increases surplus' do
|
|
90
|
+
engine.commit!(amount: 0.4)
|
|
91
|
+
committed_before = engine.committed
|
|
92
|
+
result = engine.uncommit!(amount: 0.2)
|
|
93
|
+
expect(result[:committed]).to be < committed_before
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
it 'does not go below zero' do
|
|
97
|
+
result = engine.uncommit!(amount: 1.0)
|
|
98
|
+
expect(result[:committed]).to eq(0.0)
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
describe '#replenish!' do
|
|
103
|
+
it 'reduces committed by REPLENISH_RATE' do
|
|
104
|
+
engine.commit!(amount: 0.5)
|
|
105
|
+
before = engine.committed
|
|
106
|
+
engine.replenish!
|
|
107
|
+
expect(engine.committed).to be < before
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
it 'returns gained amount' do
|
|
111
|
+
engine.commit!(amount: 0.3)
|
|
112
|
+
result = engine.replenish!
|
|
113
|
+
expect(result[:gained]).to eq(Legion::Extensions::CognitiveSurplus::Helpers::Constants::REPLENISH_RATE)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
it 'does not go below zero committed' do
|
|
117
|
+
engine.replenish!
|
|
118
|
+
expect(engine.committed).to eq(0.0)
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
describe '#deplete!' do
|
|
123
|
+
it 'increases committed by amount' do
|
|
124
|
+
before = engine.committed
|
|
125
|
+
engine.deplete!(amount: 0.1)
|
|
126
|
+
expect(engine.committed).to be > before
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
it 'does not exceed available surplus' do
|
|
130
|
+
result = engine.deplete!(amount: 2.0)
|
|
131
|
+
expect(engine.available_surplus).to be >= 0.0
|
|
132
|
+
expect(result[:depleted]).to be true
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
describe '#surplus_report' do
|
|
137
|
+
it 'returns all required fields' do
|
|
138
|
+
report = engine.surplus_report
|
|
139
|
+
expect(report).to include(
|
|
140
|
+
:total_capacity, :committed, :reserved, :active_allocated,
|
|
141
|
+
:available_surplus, :surplus_label, :quality, :quality_label, :allocation_count
|
|
142
|
+
)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
it 'surplus_label reflects current amount' do
|
|
146
|
+
report = engine.surplus_report
|
|
147
|
+
expect(report[:surplus_label]).to be_a(Symbol)
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
describe '#to_h' do
|
|
152
|
+
it 'includes allocations hash' do
|
|
153
|
+
engine.allocate!(activity_type: :maintenance, amount: 0.1)
|
|
154
|
+
h = engine.to_h
|
|
155
|
+
expect(h[:allocations]).to be_a(Hash)
|
|
156
|
+
expect(h[:allocations].size).to eq(1)
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/extensions/cognitive_surplus/client'
|
|
4
|
+
|
|
5
|
+
RSpec.describe Legion::Extensions::CognitiveSurplus::Runners::Surplus do
|
|
6
|
+
let(:client) { Legion::Extensions::CognitiveSurplus::Client.new }
|
|
7
|
+
|
|
8
|
+
describe '#surplus_status' do
|
|
9
|
+
it 'returns a surplus report hash' do
|
|
10
|
+
result = client.surplus_status
|
|
11
|
+
expect(result).to include(:available_surplus, :surplus_label, :quality, :quality_label)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
it 'available_surplus is between 0 and 1' do
|
|
15
|
+
result = client.surplus_status
|
|
16
|
+
expect(result[:available_surplus]).to be_between(0.0, 1.0)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
describe '#allocate_surplus' do
|
|
21
|
+
it 'allocates surplus for a valid activity type' do
|
|
22
|
+
result = client.allocate_surplus(activity_type: :exploration, amount: 0.1)
|
|
23
|
+
expect(result[:allocated]).to be true
|
|
24
|
+
expect(result[:allocation_id]).to match(/\A[0-9a-f-]{36}\z/)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it 'returns error for invalid activity type' do
|
|
28
|
+
result = client.allocate_surplus(activity_type: :bogus, amount: 0.1)
|
|
29
|
+
expect(result[:allocated]).to be false
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it 'uses default amount of SURPLUS_THRESHOLD' do
|
|
33
|
+
result = client.allocate_surplus(activity_type: :creative)
|
|
34
|
+
expect(result[:allocated]).to be true
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
describe '#release_surplus' do
|
|
39
|
+
it 'releases an active allocation' do
|
|
40
|
+
alloc = client.allocate_surplus(activity_type: :consolidation, amount: 0.1)
|
|
41
|
+
result = client.release_surplus(allocation_id: alloc[:allocation_id])
|
|
42
|
+
expect(result[:released]).to be true
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
it 'returns not_found for unknown allocation id' do
|
|
46
|
+
result = client.release_surplus(allocation_id: 'no-such-id')
|
|
47
|
+
expect(result[:released]).to be false
|
|
48
|
+
expect(result[:reason]).to eq(:not_found)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
describe '#commit_capacity' do
|
|
53
|
+
it 'commits capacity and reduces available surplus' do
|
|
54
|
+
before = client.surplus_status[:available_surplus]
|
|
55
|
+
client.commit_capacity(amount: 0.2)
|
|
56
|
+
after = client.surplus_status[:available_surplus]
|
|
57
|
+
expect(after).to be < before
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
describe '#uncommit_capacity' do
|
|
62
|
+
it 'uncommits capacity and increases available surplus' do
|
|
63
|
+
client.commit_capacity(amount: 0.3)
|
|
64
|
+
before = client.surplus_status[:available_surplus]
|
|
65
|
+
client.uncommit_capacity(amount: 0.15)
|
|
66
|
+
after = client.surplus_status[:available_surplus]
|
|
67
|
+
expect(after).to be > before
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
describe '#replenish_surplus' do
|
|
72
|
+
it 'returns replenished: true' do
|
|
73
|
+
result = client.replenish_surplus
|
|
74
|
+
expect(result[:replenished]).to be true
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
it 'includes gained amount' do
|
|
78
|
+
client.commit_capacity(amount: 0.3)
|
|
79
|
+
result = client.replenish_surplus
|
|
80
|
+
expect(result[:gained]).to be >= 0.0
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
describe '#deplete_surplus' do
|
|
85
|
+
it 'reduces available surplus' do
|
|
86
|
+
before = client.surplus_status[:available_surplus]
|
|
87
|
+
client.deplete_surplus(amount: 0.1)
|
|
88
|
+
after = client.surplus_status[:available_surplus]
|
|
89
|
+
expect(after).to be < before
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
it 'returns depleted: true' do
|
|
93
|
+
result = client.deplete_surplus(amount: 0.05)
|
|
94
|
+
expect(result[:depleted]).to be true
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
describe '#surplus_allocations' do
|
|
99
|
+
it 'returns empty allocations initially' do
|
|
100
|
+
result = client.surplus_allocations
|
|
101
|
+
expect(result[:count]).to eq(0)
|
|
102
|
+
expect(result[:allocations]).to eq([])
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
it 'lists active allocations' do
|
|
106
|
+
client.allocate_surplus(activity_type: :speculation, amount: 0.1)
|
|
107
|
+
client.allocate_surplus(activity_type: :maintenance, amount: 0.1)
|
|
108
|
+
result = client.surplus_allocations
|
|
109
|
+
expect(result[:count]).to eq(2)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
it 'excludes released allocations' do
|
|
113
|
+
alloc = client.allocate_surplus(activity_type: :exploration, amount: 0.1)
|
|
114
|
+
client.release_surplus(allocation_id: alloc[:allocation_id])
|
|
115
|
+
result = client.surplus_allocations
|
|
116
|
+
expect(result[:count]).to eq(0)
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
data/spec/spec_helper.rb
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'bundler/setup'
|
|
4
|
+
|
|
5
|
+
module Legion
|
|
6
|
+
module Logging
|
|
7
|
+
def self.debug(_msg); end
|
|
8
|
+
def self.info(_msg); end
|
|
9
|
+
def self.warn(_msg); end
|
|
10
|
+
def self.error(_msg); end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
require 'legion/extensions/cognitive_surplus'
|
|
15
|
+
|
|
16
|
+
RSpec.configure do |config|
|
|
17
|
+
config.example_status_persistence_file_path = '.rspec_status'
|
|
18
|
+
config.disable_monkey_patching!
|
|
19
|
+
config.expect_with(:rspec) { |c| c.syntax = :expect }
|
|
20
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: lex-cognitive-surplus
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Esity
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
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'
|
|
26
|
+
description: Cognitive surplus capacity modeling for brain-modeled agentic AI
|
|
27
|
+
email:
|
|
28
|
+
- matthewdiverson@gmail.com
|
|
29
|
+
executables: []
|
|
30
|
+
extensions: []
|
|
31
|
+
extra_rdoc_files: []
|
|
32
|
+
files:
|
|
33
|
+
- Gemfile
|
|
34
|
+
- LICENSE
|
|
35
|
+
- README.md
|
|
36
|
+
- lex-cognitive-surplus.gemspec
|
|
37
|
+
- lib/legion/extensions/cognitive_surplus.rb
|
|
38
|
+
- lib/legion/extensions/cognitive_surplus/actors/replenish.rb
|
|
39
|
+
- lib/legion/extensions/cognitive_surplus/client.rb
|
|
40
|
+
- lib/legion/extensions/cognitive_surplus/helpers/allocation.rb
|
|
41
|
+
- lib/legion/extensions/cognitive_surplus/helpers/constants.rb
|
|
42
|
+
- lib/legion/extensions/cognitive_surplus/helpers/surplus_engine.rb
|
|
43
|
+
- lib/legion/extensions/cognitive_surplus/runners/surplus.rb
|
|
44
|
+
- lib/legion/extensions/cognitive_surplus/version.rb
|
|
45
|
+
- spec/legion/extensions/cognitive_surplus/client_spec.rb
|
|
46
|
+
- spec/legion/extensions/cognitive_surplus/helpers/allocation_spec.rb
|
|
47
|
+
- spec/legion/extensions/cognitive_surplus/helpers/constants_spec.rb
|
|
48
|
+
- spec/legion/extensions/cognitive_surplus/helpers/surplus_engine_spec.rb
|
|
49
|
+
- spec/legion/extensions/cognitive_surplus/runners/surplus_spec.rb
|
|
50
|
+
- spec/spec_helper.rb
|
|
51
|
+
homepage: https://github.com/LegionIO/lex-cognitive-surplus
|
|
52
|
+
licenses:
|
|
53
|
+
- MIT
|
|
54
|
+
metadata:
|
|
55
|
+
homepage_uri: https://github.com/LegionIO/lex-cognitive-surplus
|
|
56
|
+
source_code_uri: https://github.com/LegionIO/lex-cognitive-surplus
|
|
57
|
+
documentation_uri: https://github.com/LegionIO/lex-cognitive-surplus
|
|
58
|
+
changelog_uri: https://github.com/LegionIO/lex-cognitive-surplus
|
|
59
|
+
bug_tracker_uri: https://github.com/LegionIO/lex-cognitive-surplus/issues
|
|
60
|
+
rubygems_mfa_required: 'true'
|
|
61
|
+
rdoc_options: []
|
|
62
|
+
require_paths:
|
|
63
|
+
- lib
|
|
64
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - ">="
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '3.4'
|
|
69
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
70
|
+
requirements:
|
|
71
|
+
- - ">="
|
|
72
|
+
- !ruby/object:Gem::Version
|
|
73
|
+
version: '0'
|
|
74
|
+
requirements: []
|
|
75
|
+
rubygems_version: 3.6.9
|
|
76
|
+
specification_version: 4
|
|
77
|
+
summary: LEX Cognitive Surplus
|
|
78
|
+
test_files: []
|