lex-synapse 0.4.14 → 0.4.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ac485ed070081893c396ca551525535a381c7dd906f481768697978644611c81
4
- data.tar.gz: fa1d81ddeaf2d7193b5aee2f5145077c30c6bfb9f3be70b6b943e6605af70ea0
3
+ metadata.gz: '037082609e6545e0a3581981a602d49afdd4a532cf4b15d166f0cf04fd3a746f'
4
+ data.tar.gz: 42c81c769ef9f4367dec25d4713d5ab4bc4eb1491b88478bb365d5cbd3e2521e
5
5
  SHA512:
6
- metadata.gz: abbe160b11aac0d146b2fdfbddde252f9a74e85fccceba0ac9c09a155d2f181ad58e928e4fc0a423b2aaa01ec181b35721bc7b7ceb268f0d5297ba1f5ba0c776
7
- data.tar.gz: c3859b52b5edb5a68ccb21fd5866acf7a6a4a2ded6dd0cbc40e37f2a0b0cf195e37c04115ae7b892bc2b2e027798c51a04b18bcddbde8c0ecdfd492f9269a955
6
+ metadata.gz: a5f60904a36dcf8fb276c75683485a7c4ef8207b9b1f1b6c03cae33bd392fa85d4fc44beb605aeae62f492be50be546b90b209021361d1105c9adc43306bfe21
7
+ data.tar.gz: '059f003e2a808fe705e315b8c7c75c01631ac97ff86c2f675467dd563f37106ea039fc4aaddf32ad89315d4290226e0308d209c0e3df8ebd89644584ab315de9'
data/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.4.15] - 2026-10-29
4
+ ### Fixed
5
+ - Treat missing configured conditioner/transformer capabilities as unavailable rather than successful learning; retain legitimate observe/filter pass-through.
6
+ - Attribute reactive proposals to the signal created by that evaluation and report actual transformation execution.
7
+ - Restore standalone pain-triggered reversion, ignore pending evaluations in completed-outcome failure streaks, and return restored confidence.
8
+ - Make mutation/revert writes transactional and keep history versions unambiguous across repeated reversion, retaining actual before/after snapshots.
9
+
3
10
  ## [0.4.14] - 2026-06-01
4
11
  ### Fixed
5
12
  - Homeostasis actor: convert raw signal counts to signals/minute for correct spike/drought comparison; batch all updates in a single DB transaction
@@ -31,7 +31,7 @@ module Legion
31
31
  false
32
32
  end
33
33
 
34
- def action(**_opts)
34
+ def action(**)
35
35
  compute
36
36
  end
37
37
  end
@@ -11,7 +11,7 @@ module Legion
11
11
  def check_subtask? = false
12
12
  def generate_task? = false
13
13
 
14
- def action(**_opts)
14
+ def action(**)
15
15
  return { status: :skipped, reason: :no_data } unless defined?(Legion::Data)
16
16
 
17
17
  Data::Model.define_synapse_model
@@ -11,7 +11,7 @@ module Legion
11
11
  def check_subtask? = false
12
12
  def generate_task? = false
13
13
 
14
- def action(**_opts)
14
+ def action(**)
15
15
  return { status: :skipped, reason: :no_data } unless defined?(Legion::Data)
16
16
 
17
17
  results = { spikes: 0, droughts: 0, updated: 0 }
@@ -20,7 +20,7 @@ module Legion
20
20
  TIER_CRITICAL => 3.0
21
21
  }.freeze
22
22
 
23
- def compute(**_opts)
23
+ def compute(**)
24
24
  Data::Model.define_synapse_model
25
25
  return { success: false, error: 'data unavailable' } unless data_available?
26
26
 
@@ -27,6 +27,14 @@ module Legion
27
27
 
28
28
  # Step 1: Attention check
29
29
  attention_result = check_attention(synapse, payload, mode, conditioner_client)
30
+ # Unavailable execution is not evidence for confidence or proposal learning.
31
+ if attention_result[:unavailable]
32
+ return {
33
+ success: mode == :observe, mode: mode, passed: attention_result[:passed],
34
+ transformed: false, result: payload, error: attention_result[:error],
35
+ latency_ms: ::Process.clock_gettime(::Process::CLOCK_MONOTONIC, :millisecond) - start_time
36
+ }
37
+ end
30
38
 
31
39
  # Step 2: Transform (if attention passed and confidence allows)
32
40
  transform_result = if attention_result[:passed] && Helpers::Confidence.can_transform?(synapse.confidence)
@@ -36,15 +44,19 @@ module Legion
36
44
  end
37
45
 
38
46
  elapsed = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC, :millisecond) - start_time
47
+ if transform_result[:unavailable]
48
+ return {
49
+ success: false, mode: mode, passed: attention_result[:passed],
50
+ transformed: false, result: payload, error: transform_result[:error], latency_ms: elapsed
51
+ }
52
+ end
39
53
 
40
54
  # Step 3: Record signal
41
- record_signal(synapse, attention_result[:passed], transform_result[:success], elapsed)
55
+ signal_record = record_signal(synapse, attention_result[:passed], transform_result[:success], elapsed)
42
56
 
43
57
  # Step 4: Adjust confidence (skip if run_transform already penalized for validation failure)
44
- if transform_result[:validation_failure]
45
- # run_transform already adjusted confidence; reload to get the updated value
46
- new_confidence = synapse.confidence
47
- else
58
+ new_confidence = synapse.confidence
59
+ unless transform_result[:validation_failure]
48
60
  event = transform_result[:success] ? :success : :failure
49
61
  new_confidence = Helpers::Confidence.adjust(synapse.confidence, event)
50
62
  synapse.update(confidence: new_confidence)
@@ -52,7 +64,6 @@ module Legion
52
64
 
53
65
  # Step 5: Generate proposals if autonomous
54
66
  if Helpers::Confidence.can_self_modify?(new_confidence) && Helpers::Proposals.reactive?
55
- signal_record = Data::Model::SynapseSignal.where(synapse_id: synapse.id).order(Sequel.desc(:id)).first
56
67
  propose_reactive(
57
68
  synapse: synapse, payload: payload, signal_id: signal_record&.id,
58
69
  attention_result: attention_result, transform_result: transform_result,
@@ -64,7 +75,7 @@ module Legion
64
75
  success: transform_result[:success],
65
76
  mode: mode,
66
77
  passed: attention_result[:passed],
67
- transformed: transform_result[:success],
78
+ transformed: transform_result[:transformed] == true,
68
79
  result: transform_result[:result],
69
80
  latency_ms: elapsed
70
81
  }
@@ -74,13 +85,10 @@ module Legion
74
85
 
75
86
  def check_attention(synapse, payload, mode, conditioner_client)
76
87
  return { passed: true } if synapse.attention.nil? || synapse.attention.empty?
88
+ return { passed: mode == :observe, unavailable: true, error: 'conditioner unavailable' } unless conditioner_client.respond_to?(:evaluate)
77
89
 
78
- passed = if conditioner_client
79
- result = conditioner_client.evaluate(conditions: Legion::JSON.load(synapse.attention), values: payload)
80
- result[:valid]
81
- else
82
- true
83
- end
90
+ result = conditioner_client.evaluate(conditions: Legion::JSON.load(synapse.attention), values: payload)
91
+ passed = result[:valid]
84
92
 
85
93
  # In OBSERVE mode, always pass through regardless of result
86
94
  passed = true if mode == :observe
@@ -90,7 +98,7 @@ module Legion
90
98
 
91
99
  def run_transform(synapse, payload, transformer_client)
92
100
  return { success: true, result: payload } if synapse.transform.nil? || synapse.transform.empty?
93
- return { success: true, result: payload } unless transformer_client
101
+ return { success: false, result: payload, unavailable: true, error: 'transformer unavailable' } unless transformer_client.respond_to?(:transform)
94
102
 
95
103
  transform_def = Legion::JSON.load(synapse.transform)
96
104
  result = transformer_client.transform(
@@ -101,7 +109,7 @@ module Legion
101
109
  )
102
110
 
103
111
  if result[:success]
104
- { success: true, result: result[:result] }
112
+ { success: true, transformed: true, result: result[:result] }
105
113
  else
106
114
  new_conf = Helpers::Confidence.adjust(synapse.confidence, :validation_failure)
107
115
  synapse.update(confidence: new_conf)
@@ -19,23 +19,25 @@ module Legion
19
19
  return { success: false, error: "invalid mutation_type: #{mutation_type}" } unless VALID_MUTATION_TYPES.include?(mutation_type)
20
20
  return { success: false, error: "invalid trigger: #{trigger}" } unless VALID_TRIGGERS.include?(trigger)
21
21
 
22
- before_state = snapshot(synapse)
23
- apply_changes(synapse, mutation_type, changes)
24
- after_state = snapshot(synapse)
22
+ synapse.db.transaction do
23
+ synapse.lock!
24
+ before_state = snapshot(synapse)
25
+ new_version = [synapse.version, synapse.mutations_dataset.max(:version) || 1].max + 1
26
+ apply_changes(synapse, mutation_type, changes)
27
+ synapse.update(version: new_version)
28
+ after_state = snapshot(synapse)
25
29
 
26
- new_version = synapse.version + 1
27
- synapse.update(version: new_version)
30
+ Data::Model::SynapseMutation.create(
31
+ synapse_id: synapse.id,
32
+ version: new_version,
33
+ mutation_type: mutation_type,
34
+ before_state: Legion::JSON.dump(before_state),
35
+ after_state: Legion::JSON.dump(after_state),
36
+ trigger: trigger
37
+ )
28
38
 
29
- Data::Model::SynapseMutation.create(
30
- synapse_id: synapse.id,
31
- version: new_version,
32
- mutation_type: mutation_type,
33
- before_state: Legion::JSON.dump(before_state),
34
- after_state: Legion::JSON.dump(after_state),
35
- trigger: trigger
36
- )
37
-
38
- { success: true, version: new_version, synapse_id: synapse.id }
39
+ { success: true, version: new_version, synapse_id: synapse.id }
40
+ end
39
41
  end
40
42
 
41
43
  private
@@ -46,7 +48,8 @@ module Legion
46
48
  transform: synapse.transform,
47
49
  routing_strategy: synapse.routing_strategy,
48
50
  confidence: synapse.confidence,
49
- status: synapse.status
51
+ status: synapse.status,
52
+ version: synapse.version
50
53
  }
51
54
  end
52
55
 
@@ -3,12 +3,15 @@
3
3
  require_relative '../helpers/confidence'
4
4
  require_relative '../data/models/synapse'
5
5
  require_relative '../data/models/synapse_signal'
6
+ require_relative 'revert'
6
7
 
7
8
  module Legion
8
9
  module Extensions
9
10
  module Synapse
10
11
  module Runners
11
12
  module Pain
13
+ include Legion::Extensions::Synapse::Runners::Revert
14
+
12
15
  CONSECUTIVE_FAILURE_THRESHOLD = 3
13
16
 
14
17
  def handle_pain(synapse_id:, task_id: nil)
@@ -40,6 +43,11 @@ module Legion
40
43
  result[:action] = :auto_revert
41
44
  revert_result = revert(synapse_id: synapse_id, trigger: 'pain')
42
45
  result[:reverted] = revert_result[:success] == true
46
+ if result[:reverted]
47
+ synapse.refresh
48
+ new_confidence = synapse.confidence
49
+ result[:confidence] = new_confidence
50
+ end
43
51
  end
44
52
 
45
53
  # Check if confidence dropped below autonomy threshold
@@ -59,6 +67,7 @@ module Legion
59
67
 
60
68
  def count_consecutive_failures(synapse)
61
69
  recent = synapse.signals_dataset
70
+ .exclude(downstream_outcome: nil)
62
71
  .order(Sequel.desc(:id))
63
72
  .limit(CONSECUTIVE_FAILURE_THRESHOLD)
64
73
  .all
@@ -14,45 +14,45 @@ module Legion
14
14
  synapse = Data::Model::Synapse[synapse_id]
15
15
  return { success: false, error: 'synapse not found' } unless synapse
16
16
 
17
- # to_version specifies which mutation record to look up (by version number).
18
- # Reverting mutation at version N restores its before_state, landing at N-1.
19
- mutation_version = to_version || synapse.version
20
- return { success: false, error: 'no previous version' } if mutation_version < 2
21
-
22
- mutation = Data::Model::SynapseMutation.where(
23
- synapse_id: synapse.id,
24
- version: mutation_version
25
- ).first
26
-
27
- return { success: false, error: "mutation version #{mutation_version} not found" } unless mutation
28
-
29
- restored_version = mutation_version - 1
30
- before_state = Legion::JSON.load(mutation.before_state)
31
- revert_version = synapse.version + 1
32
- synapse.update(
33
- attention: before_state[:attention],
34
- transform: before_state[:transform],
35
- routing_strategy: before_state[:routing_strategy],
36
- confidence: before_state[:confidence] || synapse.confidence,
37
- status: before_state[:status] || synapse.status,
38
- version: restored_version
39
- )
40
-
41
- # Mark the reverted mutation
42
- mutation.update(outcome: 'reverted')
43
-
44
- # Record the revert as a new mutation (uses a unique version to avoid collision)
45
- Data::Model::SynapseMutation.create(
46
- synapse_id: synapse.id,
47
- version: revert_version,
48
- mutation_type: 'confidence_changed',
49
- before_state: mutation.after_state,
50
- after_state: mutation.before_state,
51
- trigger: trigger,
52
- outcome: 'reverted'
53
- )
54
-
55
- { success: true, reverted_to: restored_version, synapse_id: synapse.id }
17
+ synapse.db.transaction do
18
+ synapse.lock!
19
+ mutation_version = to_version || synapse.version
20
+ next { success: false, error: 'no previous version' } if mutation_version < 2
21
+
22
+ mutation = Data::Model::SynapseMutation.where(
23
+ synapse_id: synapse.id,
24
+ version: mutation_version
25
+ ).first
26
+
27
+ next { success: false, error: "mutation version #{mutation_version} not found" } unless mutation
28
+
29
+ before_state = Legion::JSON.load(mutation.before_state)
30
+ restored_version = before_state[:version] || (mutation_version - 1)
31
+ revert_version = [synapse.version, synapse.mutations_dataset.max(:version) || 1].max + 1
32
+ current_state = synapse.values.slice(:attention, :transform, :routing_strategy, :confidence, :status, :version)
33
+ synapse.update(
34
+ attention: before_state[:attention],
35
+ transform: before_state[:transform],
36
+ routing_strategy: before_state[:routing_strategy],
37
+ confidence: before_state[:confidence] || synapse.confidence,
38
+ status: before_state[:status] || synapse.status,
39
+ version: restored_version
40
+ )
41
+
42
+ mutation.update(outcome: 'reverted')
43
+ restored_state = synapse.values.slice(:attention, :transform, :routing_strategy, :confidence, :status, :version)
44
+ Data::Model::SynapseMutation.create(
45
+ synapse_id: synapse.id,
46
+ version: revert_version,
47
+ mutation_type: 'confidence_changed',
48
+ before_state: Legion::JSON.dump(current_state),
49
+ after_state: Legion::JSON.dump(restored_state),
50
+ trigger: trigger,
51
+ outcome: 'reverted'
52
+ )
53
+
54
+ { success: true, reverted_to: restored_version, synapse_id: synapse.id }
55
+ end
56
56
  end
57
57
  end
58
58
  end
@@ -3,7 +3,7 @@
3
3
  module Legion
4
4
  module Extensions
5
5
  module Synapse
6
- VERSION = '0.4.14'
6
+ VERSION = '0.4.15'
7
7
  end
8
8
  end
9
9
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lex-synapse
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.14
4
+ version: 0.4.15
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity