lex-cognitive-synthesis 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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 794a2635b1df3494d23c85016118e87cabd698c1f11da3c360a2985d398af0e2
4
+ data.tar.gz: b61b06852ae00e857131c9413bb347ff146d28809ebac36f9c1e62ebfae1a214
5
+ SHA512:
6
+ metadata.gz: ef493128ca5d01bb34dff75ec3a4f71991deae44d3f2bcf6fef7846902e57d54f038cf24c1f0e7c3defcce7d31e6f89cff302a97849ff47d5b0c8bf207ef179a
7
+ data.tar.gz: e369eca98a77a7d8be01ad95c2eb60c33a0cd603d30514e7bc7013e8d8da6575c9458466ba645c7db59830a81648976598972c0d2ed128ce320a363f5ea69958
data/Gemfile ADDED
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gemspec
6
+
7
+ gem 'rspec', '~> 3.13'
8
+ gem 'rubocop', '~> 1.75', require: false
9
+ gem 'rubocop-rspec', require: false
10
+
11
+ gem 'legion-gaia', path: '../../legion-gaia'
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/legion/extensions/cognitive_synthesis/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'lex-cognitive-synthesis'
7
+ spec.version = Legion::Extensions::CognitiveSynthesis::VERSION
8
+ spec.authors = ['Esity']
9
+ spec.email = ['matthewdiverson@gmail.com']
10
+
11
+ spec.summary = 'LEX Cognitive Synthesis'
12
+ spec.description = 'Cognitive binding engine — combines disparate cognitive streams ' \
13
+ '(emotional, perceptual, memorial, predictive) into unified coherent ' \
14
+ 'representations for brain-modeled agentic AI'
15
+ spec.homepage = 'https://github.com/LegionIO/lex-cognitive-synthesis'
16
+ spec.license = 'MIT'
17
+ spec.required_ruby_version = '>= 3.4'
18
+
19
+ spec.metadata['homepage_uri'] = spec.homepage
20
+ spec.metadata['source_code_uri'] = 'https://github.com/LegionIO/lex-cognitive-synthesis'
21
+ spec.metadata['documentation_uri'] = 'https://github.com/LegionIO/lex-cognitive-synthesis'
22
+ spec.metadata['changelog_uri'] = 'https://github.com/LegionIO/lex-cognitive-synthesis'
23
+ spec.metadata['bug_tracker_uri'] = 'https://github.com/LegionIO/lex-cognitive-synthesis/issues'
24
+ spec.metadata['rubygems_mfa_required'] = 'true'
25
+
26
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
27
+ Dir.glob('{lib,spec}/**/*') + %w[lex-cognitive-synthesis.gemspec Gemfile]
28
+ end
29
+ spec.require_paths = ['lib']
30
+ spec.add_development_dependency 'legion-gaia'
31
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/cognitive_synthesis/helpers/constants'
4
+ require 'legion/extensions/cognitive_synthesis/helpers/synthesis_stream'
5
+ require 'legion/extensions/cognitive_synthesis/helpers/synthesis'
6
+ require 'legion/extensions/cognitive_synthesis/helpers/synthesis_engine'
7
+ require 'legion/extensions/cognitive_synthesis/runners/cognitive_synthesis'
8
+
9
+ module Legion
10
+ module Extensions
11
+ module CognitiveSynthesis
12
+ class Client
13
+ include Runners::CognitiveSynthesis
14
+
15
+ def initialize(engine: nil, **)
16
+ @synthesis_engine = engine || Helpers::SynthesisEngine.new
17
+ end
18
+
19
+ private
20
+
21
+ attr_reader :synthesis_engine
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module CognitiveSynthesis
6
+ module Helpers
7
+ module Constants
8
+ MAX_STREAMS = 50
9
+ MAX_SYNTHESES = 200
10
+ DEFAULT_WEIGHT = 0.5
11
+ COHERENCE_THRESHOLD = 0.6
12
+ NOVELTY_THRESHOLD = 0.7
13
+ FRESHNESS_DECAY = 0.02
14
+ MIN_STREAMS_FOR_SYNTHESIS = 2
15
+
16
+ STREAM_TYPES = %i[emotional perceptual memorial predictive reasoning social identity motor].freeze
17
+
18
+ COHERENCE_LABELS = {
19
+ (0.8..) => :unified,
20
+ (0.6...0.8) => :coherent,
21
+ (0.4...0.6) => :fragmented,
22
+ (0.2...0.4) => :dissonant,
23
+ (..0.2) => :chaotic
24
+ }.freeze
25
+
26
+ CONFIDENCE_LABELS = {
27
+ (0.8..) => :certain,
28
+ (0.6...0.8) => :confident,
29
+ (0.4...0.6) => :uncertain,
30
+ (0.2...0.4) => :doubtful,
31
+ (..0.2) => :guessing
32
+ }.freeze
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+
5
+ module Legion
6
+ module Extensions
7
+ module CognitiveSynthesis
8
+ module Helpers
9
+ class Synthesis
10
+ include Constants
11
+
12
+ attr_reader :id, :streams, :coherence, :novelty, :confidence, :content, :created_at
13
+
14
+ def initialize(streams:, coherence:, novelty:, confidence:, content:)
15
+ @id = SecureRandom.uuid
16
+ @streams = streams
17
+ @coherence = coherence.clamp(0.0, 1.0)
18
+ @novelty = novelty.clamp(0.0, 1.0)
19
+ @confidence = confidence.clamp(0.0, 1.0)
20
+ @content = content
21
+ @created_at = Time.now.utc
22
+ end
23
+
24
+ def fragmented?
25
+ @coherence < COHERENCE_THRESHOLD
26
+ end
27
+
28
+ def novel?
29
+ @novelty > NOVELTY_THRESHOLD
30
+ end
31
+
32
+ def coherence_label
33
+ COHERENCE_LABELS.find { |range, _| range.cover?(@coherence) }&.last || :chaotic
34
+ end
35
+
36
+ def confidence_label
37
+ CONFIDENCE_LABELS.find { |range, _| range.cover?(@confidence) }&.last || :guessing
38
+ end
39
+
40
+ def to_h
41
+ {
42
+ id: @id,
43
+ streams: @streams,
44
+ coherence: @coherence,
45
+ novelty: @novelty,
46
+ confidence: @confidence,
47
+ content: @content,
48
+ fragmented: fragmented?,
49
+ novel: novel?,
50
+ coherence_label: coherence_label,
51
+ confidence_label: confidence_label,
52
+ created_at: @created_at
53
+ }
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,209 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module CognitiveSynthesis
6
+ module Helpers
7
+ class SynthesisEngine
8
+ include Constants
9
+
10
+ attr_reader :streams, :syntheses
11
+
12
+ def initialize
13
+ @streams = {}
14
+ @syntheses = []
15
+ end
16
+
17
+ def add_stream(stream_type:, content:, weight: DEFAULT_WEIGHT, confidence: DEFAULT_WEIGHT, **)
18
+ return { success: false, error: :invalid_stream_type, valid_types: STREAM_TYPES } unless STREAM_TYPES.include?(stream_type)
19
+
20
+ stream = SynthesisStream.new(
21
+ stream_type: stream_type,
22
+ content: content,
23
+ weight: weight,
24
+ confidence: confidence
25
+ )
26
+
27
+ @streams[stream.id] = stream
28
+ prune_streams! if @streams.size > MAX_STREAMS
29
+
30
+ Legion::Logging.debug "[cognitive_synthesis] stream added id=#{stream.id[0..7]} " \
31
+ "type=#{stream_type} weight=#{weight.round(2)}"
32
+
33
+ { success: true, stream_id: stream.id, stream_type: stream_type }
34
+ end
35
+
36
+ def remove_stream(stream_id:, **)
37
+ removed = @streams.delete(stream_id)
38
+ if removed
39
+ Legion::Logging.debug "[cognitive_synthesis] stream removed id=#{stream_id[0..7]}"
40
+ { success: true, stream_id: stream_id }
41
+ else
42
+ { success: false, error: :not_found }
43
+ end
44
+ end
45
+
46
+ def synthesize!(**)
47
+ active = @streams.values.reject(&:stale?)
48
+
49
+ if active.size < MIN_STREAMS_FOR_SYNTHESIS
50
+ Legion::Logging.debug "[cognitive_synthesis] synthesize! skipped: only #{active.size} active streams"
51
+ return { success: false, error: :insufficient_streams, active_count: active.size, required: MIN_STREAMS_FOR_SYNTHESIS }
52
+ end
53
+
54
+ coherence = compute_coherence(active)
55
+ novelty = compute_novelty(active)
56
+ confidence = compute_weighted_confidence(active)
57
+ content = merge_content(active)
58
+
59
+ synthesis = Synthesis.new(
60
+ streams: active.map(&:id),
61
+ coherence: coherence,
62
+ novelty: novelty,
63
+ confidence: confidence,
64
+ content: content
65
+ )
66
+
67
+ @syntheses << synthesis
68
+ @syntheses.shift while @syntheses.size > MAX_SYNTHESES
69
+
70
+ Legion::Logging.info "[cognitive_synthesis] synthesis id=#{synthesis.id[0..7]} " \
71
+ "coherence=#{coherence.round(2)} novelty=#{novelty.round(2)} " \
72
+ "streams=#{active.size} label=#{synthesis.coherence_label}"
73
+
74
+ { success: true, synthesis: synthesis.to_h }
75
+ end
76
+
77
+ def decay_all!(**)
78
+ before = @streams.size
79
+ @streams.each_value(&:decay_freshness!)
80
+ @streams.reject! { |_, s| s.stale? }
81
+ removed = before - @streams.size
82
+
83
+ Legion::Logging.debug "[cognitive_synthesis] decay_all! removed=#{removed} remaining=#{@streams.size}"
84
+ { success: true, streams_removed: removed, streams_remaining: @streams.size }
85
+ end
86
+
87
+ def stream_conflict?(stream_id_a:, stream_id_b:, **)
88
+ a = @streams[stream_id_a]
89
+ b = @streams[stream_id_b]
90
+
91
+ return { success: false, error: :stream_not_found } unless a && b
92
+
93
+ weight_opposition = opposing_weights?(a, b)
94
+ content_conflict = conflicting_content?(a, b)
95
+ conflict = weight_opposition || content_conflict
96
+
97
+ Legion::Logging.debug "[cognitive_synthesis] conflict check #{stream_id_a[0..7]}<>#{stream_id_b[0..7]} " \
98
+ "result=#{conflict}"
99
+
100
+ {
101
+ success: true,
102
+ conflict: conflict,
103
+ weight_opposition: weight_opposition,
104
+ content_conflict: content_conflict
105
+ }
106
+ end
107
+
108
+ def dominant_stream(**)
109
+ return { success: false, error: :no_streams } if @streams.empty?
110
+
111
+ stream = @streams.values.max_by(&:effective_weight)
112
+ Legion::Logging.debug "[cognitive_synthesis] dominant stream id=#{stream.id[0..7]} " \
113
+ "effective_weight=#{stream.effective_weight.round(4)}"
114
+ { success: true, stream: stream.to_h }
115
+ end
116
+
117
+ def synthesis_history(limit: 10, **)
118
+ recent = @syntheses.last(limit)
119
+ { success: true, syntheses: recent.map(&:to_h), count: recent.size }
120
+ end
121
+
122
+ def average_coherence(window: 10, **)
123
+ recent = @syntheses.last(window)
124
+ return { success: true, average_coherence: 0.0, sample_size: 0 } if recent.empty?
125
+
126
+ avg = recent.sum(&:coherence).round(10) / recent.size
127
+ { success: true, average_coherence: avg.round(10), sample_size: recent.size }
128
+ end
129
+
130
+ def to_h
131
+ {
132
+ stream_count: @streams.size,
133
+ synthesis_count: @syntheses.size,
134
+ active_streams: @streams.values.reject(&:stale?).size,
135
+ stale_streams: @streams.values.count(&:stale?),
136
+ average_coherence: @syntheses.empty? ? 0.0 : (@syntheses.sum(&:coherence).round(10) / @syntheses.size).round(6)
137
+ }
138
+ end
139
+
140
+ private
141
+
142
+ def prune_streams!
143
+ overflow = @streams.size - MAX_STREAMS
144
+ return if overflow <= 0
145
+
146
+ ids_to_prune = @streams.min_by(overflow) { |_, s| s.effective_weight }.map(&:first)
147
+ ids_to_prune.each { |id| @streams.delete(id) }
148
+ end
149
+
150
+ def compute_coherence(active)
151
+ return 1.0 if active.size == 1
152
+
153
+ weights = active.map(&:effective_weight)
154
+ mean = weights.sum.round(10) / weights.size
155
+ variance = weights.sum { |w| ((w - mean)**2).round(10) }.round(10) / weights.size
156
+ std_dev = Math.sqrt(variance)
157
+
158
+ (1.0 - [std_dev, 1.0].min).round(10)
159
+ end
160
+
161
+ def compute_novelty(active)
162
+ return 1.0 if @syntheses.empty?
163
+
164
+ last_syn = @syntheses.last
165
+ last_ids = Set.new(last_syn.streams)
166
+ current_ids = Set.new(active.map(&:id))
167
+
168
+ overlap = (last_ids & current_ids).size.to_f
169
+ union = (last_ids | current_ids).size.to_f
170
+
171
+ return 1.0 if union.zero?
172
+
173
+ jaccard_similarity = overlap / union
174
+ (1.0 - jaccard_similarity).round(10)
175
+ end
176
+
177
+ def compute_weighted_confidence(active)
178
+ total_weight = active.sum(&:effective_weight).round(10)
179
+ return 0.0 if total_weight.zero?
180
+
181
+ weighted_sum = active.sum { |s| (s.confidence * s.effective_weight).round(10) }.round(10)
182
+ (weighted_sum / total_weight).round(10)
183
+ end
184
+
185
+ def merge_content(active)
186
+ dominant = active.max_by(&:effective_weight)
187
+ {
188
+ dominant_type: dominant.stream_type,
189
+ dominant_weight: dominant.effective_weight.round(6),
190
+ stream_types: active.map(&:stream_type).uniq,
191
+ stream_count: active.size,
192
+ payload_snapshot: dominant.content
193
+ }
194
+ end
195
+
196
+ def opposing_weights?(str_a, str_b)
197
+ (str_a.weight - str_b.weight).abs > 0.5
198
+ end
199
+
200
+ def conflicting_content?(str_a, str_b)
201
+ keys_a = str_a.content.is_a?(Hash) ? str_a.content.keys : []
202
+ keys_b = str_b.content.is_a?(Hash) ? str_b.content.keys : []
203
+ (keys_a & keys_b).any? { |k| str_a.content[k] != str_b.content[k] }
204
+ end
205
+ end
206
+ end
207
+ end
208
+ end
209
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+
5
+ module Legion
6
+ module Extensions
7
+ module CognitiveSynthesis
8
+ module Helpers
9
+ class SynthesisStream
10
+ include Constants
11
+
12
+ attr_reader :id, :stream_type, :content, :weight, :confidence, :freshness, :created_at
13
+
14
+ def initialize(stream_type:, content:, weight: DEFAULT_WEIGHT, confidence: DEFAULT_WEIGHT)
15
+ @id = SecureRandom.uuid
16
+ @stream_type = stream_type
17
+ @content = content
18
+ @weight = weight.clamp(0.0, 1.0)
19
+ @confidence = confidence.clamp(0.0, 1.0)
20
+ @freshness = 1.0
21
+ @created_at = Time.now.utc
22
+ end
23
+
24
+ def decay_freshness!
25
+ @freshness = (@freshness - FRESHNESS_DECAY).clamp(0.0, 1.0)
26
+ end
27
+
28
+ def stale?
29
+ @freshness < 0.1
30
+ end
31
+
32
+ def effective_weight
33
+ (@weight * @freshness * @confidence).round(10)
34
+ end
35
+
36
+ def coherence_label
37
+ COHERENCE_LABELS.find { |range, _| range.cover?(@weight) }&.last || :chaotic
38
+ end
39
+
40
+ def confidence_label
41
+ CONFIDENCE_LABELS.find { |range, _| range.cover?(@confidence) }&.last || :guessing
42
+ end
43
+
44
+ def to_h
45
+ {
46
+ id: @id,
47
+ stream_type: @stream_type,
48
+ content: @content,
49
+ weight: @weight,
50
+ confidence: @confidence,
51
+ freshness: @freshness,
52
+ effective_weight: effective_weight,
53
+ stale: stale?,
54
+ coherence_label: coherence_label,
55
+ confidence_label: confidence_label,
56
+ created_at: @created_at
57
+ }
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module CognitiveSynthesis
6
+ module Runners
7
+ module CognitiveSynthesis
8
+ include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers) &&
9
+ Legion::Extensions::Helpers.const_defined?(:Lex)
10
+
11
+ def add_stream(stream_type:, content:, weight: Helpers::Constants::DEFAULT_WEIGHT,
12
+ confidence: Helpers::Constants::DEFAULT_WEIGHT, engine: nil, **)
13
+ target = engine || synthesis_engine
14
+ result = target.add_stream(stream_type: stream_type, content: content,
15
+ weight: weight, confidence: confidence)
16
+ Legion::Logging.debug "[cognitive_synthesis] runner add_stream type=#{stream_type}"
17
+ result
18
+ end
19
+
20
+ def remove_stream(stream_id:, engine: nil, **)
21
+ target = engine || synthesis_engine
22
+ Legion::Logging.debug "[cognitive_synthesis] runner remove_stream id=#{stream_id[0..7]}"
23
+ target.remove_stream(stream_id: stream_id)
24
+ end
25
+
26
+ def synthesize(engine: nil, **)
27
+ target = engine || synthesis_engine
28
+ Legion::Logging.debug '[cognitive_synthesis] runner synthesize'
29
+ target.synthesize!
30
+ end
31
+
32
+ def decay_streams(engine: nil, **)
33
+ target = engine || synthesis_engine
34
+ Legion::Logging.debug '[cognitive_synthesis] runner decay_streams'
35
+ target.decay_all!
36
+ end
37
+
38
+ def check_conflict(stream_id_a:, stream_id_b:, engine: nil, **)
39
+ target = engine || synthesis_engine
40
+ Legion::Logging.debug "[cognitive_synthesis] runner check_conflict #{stream_id_a[0..7]}<>#{stream_id_b[0..7]}"
41
+ target.stream_conflict?(stream_id_a: stream_id_a, stream_id_b: stream_id_b)
42
+ end
43
+
44
+ def dominant_stream(engine: nil, **)
45
+ target = engine || synthesis_engine
46
+ Legion::Logging.debug '[cognitive_synthesis] runner dominant_stream'
47
+ target.dominant_stream
48
+ end
49
+
50
+ def synthesis_history(limit: 10, engine: nil, **)
51
+ target = engine || synthesis_engine
52
+ Legion::Logging.debug "[cognitive_synthesis] runner synthesis_history limit=#{limit}"
53
+ target.synthesis_history(limit: limit)
54
+ end
55
+
56
+ def average_coherence(window: 10, engine: nil, **)
57
+ target = engine || synthesis_engine
58
+ Legion::Logging.debug "[cognitive_synthesis] runner average_coherence window=#{window}"
59
+ target.average_coherence(window: window)
60
+ end
61
+
62
+ def status(engine: nil, **)
63
+ target = engine || synthesis_engine
64
+ stats = target.to_h
65
+ Legion::Logging.debug "[cognitive_synthesis] runner status streams=#{stats[:stream_count]}"
66
+ { success: true }.merge(stats)
67
+ end
68
+
69
+ private
70
+
71
+ def synthesis_engine
72
+ @synthesis_engine ||= Helpers::SynthesisEngine.new
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module CognitiveSynthesis
6
+ VERSION = '0.1.0'
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/cognitive_synthesis/version'
4
+ require 'legion/extensions/cognitive_synthesis/helpers/constants'
5
+ require 'legion/extensions/cognitive_synthesis/helpers/synthesis_stream'
6
+ require 'legion/extensions/cognitive_synthesis/helpers/synthesis'
7
+ require 'legion/extensions/cognitive_synthesis/helpers/synthesis_engine'
8
+ require 'legion/extensions/cognitive_synthesis/runners/cognitive_synthesis'
9
+
10
+ module Legion
11
+ module Extensions
12
+ module CognitiveSynthesis
13
+ extend Legion::Extensions::Core if Legion::Extensions.const_defined? :Core
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/cognitive_synthesis/client'
4
+
5
+ RSpec.describe Legion::Extensions::CognitiveSynthesis::Client do
6
+ it 'responds to all runner methods' do
7
+ client = described_class.new
8
+ %i[add_stream remove_stream synthesize decay_streams check_conflict
9
+ dominant_stream synthesis_history average_coherence status].each do |method|
10
+ expect(client).to respond_to(method)
11
+ end
12
+ end
13
+
14
+ it 'accepts an injected engine' do
15
+ engine = Legion::Extensions::CognitiveSynthesis::Helpers::SynthesisEngine.new
16
+ client = described_class.new(engine: engine)
17
+ client.add_stream(stream_type: :emotional, content: {})
18
+ expect(engine.streams.size).to eq(1)
19
+ end
20
+
21
+ it 'creates its own engine when none injected' do
22
+ client = described_class.new
23
+ result = client.add_stream(stream_type: :emotional, content: {})
24
+ expect(result[:success]).to be true
25
+ end
26
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::CognitiveSynthesis::Helpers::Constants do
4
+ subject(:mod) { described_class }
5
+
6
+ describe 'MAX_STREAMS' do
7
+ it 'is 50' do
8
+ expect(mod::MAX_STREAMS).to eq(50)
9
+ end
10
+ end
11
+
12
+ describe 'MAX_SYNTHESES' do
13
+ it 'is 200' do
14
+ expect(mod::MAX_SYNTHESES).to eq(200)
15
+ end
16
+ end
17
+
18
+ describe 'DEFAULT_WEIGHT' do
19
+ it 'is 0.5' do
20
+ expect(mod::DEFAULT_WEIGHT).to eq(0.5)
21
+ end
22
+ end
23
+
24
+ describe 'COHERENCE_THRESHOLD' do
25
+ it 'is 0.6' do
26
+ expect(mod::COHERENCE_THRESHOLD).to eq(0.6)
27
+ end
28
+ end
29
+
30
+ describe 'NOVELTY_THRESHOLD' do
31
+ it 'is 0.7' do
32
+ expect(mod::NOVELTY_THRESHOLD).to eq(0.7)
33
+ end
34
+ end
35
+
36
+ describe 'FRESHNESS_DECAY' do
37
+ it 'is 0.02' do
38
+ expect(mod::FRESHNESS_DECAY).to eq(0.02)
39
+ end
40
+ end
41
+
42
+ describe 'MIN_STREAMS_FOR_SYNTHESIS' do
43
+ it 'is 2' do
44
+ expect(mod::MIN_STREAMS_FOR_SYNTHESIS).to eq(2)
45
+ end
46
+ end
47
+
48
+ describe 'STREAM_TYPES' do
49
+ it 'includes all expected types' do
50
+ expect(mod::STREAM_TYPES).to include(:emotional, :perceptual, :memorial, :predictive,
51
+ :reasoning, :social, :identity, :motor)
52
+ end
53
+
54
+ it 'is frozen' do
55
+ expect(mod::STREAM_TYPES).to be_frozen
56
+ end
57
+ end
58
+
59
+ describe 'COHERENCE_LABELS' do
60
+ it 'maps high values to :unified' do
61
+ label = mod::COHERENCE_LABELS.find { |range, _| range.cover?(0.9) }&.last
62
+ expect(label).to eq(:unified)
63
+ end
64
+
65
+ it 'maps low values to :chaotic' do
66
+ label = mod::COHERENCE_LABELS.find { |range, _| range.cover?(0.1) }&.last
67
+ expect(label).to eq(:chaotic)
68
+ end
69
+
70
+ it 'maps mid values to :coherent' do
71
+ label = mod::COHERENCE_LABELS.find { |range, _| range.cover?(0.7) }&.last
72
+ expect(label).to eq(:coherent)
73
+ end
74
+ end
75
+
76
+ describe 'CONFIDENCE_LABELS' do
77
+ it 'maps high values to :certain' do
78
+ label = mod::CONFIDENCE_LABELS.find { |range, _| range.cover?(0.95) }&.last
79
+ expect(label).to eq(:certain)
80
+ end
81
+
82
+ it 'maps low values to :guessing' do
83
+ label = mod::CONFIDENCE_LABELS.find { |range, _| range.cover?(0.05) }&.last
84
+ expect(label).to eq(:guessing)
85
+ end
86
+ end
87
+ end