lex-scheduler 0.2.0 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c30e79b68a5c57ce1c69e7acddcce1bc472d16dfd14dde5bba5ab7c90bc14130
4
- data.tar.gz: b38c37b9f1822a06a3a970e1aba03ef9c4650488fd6caad4ec733b624ee82c1f
3
+ metadata.gz: 17c661a4da86c3c66a0d66f54c6339e2724766bc2ae0231fadf5f167675e6c28
4
+ data.tar.gz: f73724797cd54b5932d57d0e83aae28f6f6cf2197312665771887cce22861371
5
5
  SHA512:
6
- metadata.gz: 19570dc6a5141dbd6f293aa4ff6f1cdcc9e3629986d1c265224322ca1655707d6ede50294c826c8555decf6c42759a7987a2c55f33a1d7dbf29a203137aa7f50
7
- data.tar.gz: 9239fbc82e2c015af2edc457d00f15611969f1e81726bf027ae769194173186c180a45575e4b2ac178a13b7ac5337ba4117fb05d46365837f2e08625ef283f87
6
+ metadata.gz: 320d897e6b2cb4f78f4b1cb52b927cd89ae88d6fb900a35612b0334f566ab96d24009d230f5f88f126367cd3d4065cd9161d2fa957d3e80adc0a52c3bd37bb10
7
+ data.tar.gz: c3950163ef797ffc297276efd96516eb39406741be6bc9d8d68e089e91a44b819685ba9c0f333ec0a4cbc5f3b3d0afc54d393aa96e3ea06877255cd78632c642
data/.gitignore CHANGED
@@ -9,3 +9,4 @@
9
9
 
10
10
  # rspec failure tracking
11
11
  .rspec_status
12
+ Gemfile.lock
data/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.3.0] - 2026-03-18
4
+
5
+ ### Fixed
6
+ - Migrations 001/002 rewritten as Sequel DSL (cross-DB: SQLite, PostgreSQL, MySQL)
7
+ - Migration 005 column type `File` -> `String, text: true`
8
+ - ScheduleLog model class name (was defining duplicate `Schedule`)
9
+ - Queue TTL from 5ms to 5000ms (messages were expiring instantly)
10
+ - Nil guard on `last_run` (was TypeError on new schedules)
11
+ - Nil guard on function lookup (was NoMethodError on missing function)
12
+ - Removed dead cron guard (`Time.now < previous_time` was always false)
13
+ - ScheduleLog records now created after each dispatch
14
+ - Entry point `data_required?` is now class method only (framework requirement)
15
+
16
+ ### Added
17
+ - Standalone `Scheduler::Client` for programmatic schedule management
18
+ - Comprehensive spec coverage (92%+)
19
+
20
+ ### Removed
21
+ - ModeScheduler, ModeTransition, EmergencyPromotion runners (dead code, no actor wiring)
22
+ - Dead `message_example` in Refresh message (copy-paste from lex-node)
23
+
3
24
  ## [0.2.0] - 2026-03-17
4
25
 
5
26
  ### Added
data/CLAUDE.md CHANGED
@@ -10,7 +10,7 @@ Core Legion Extension that manages scheduled, delayed, and cron-style task execu
10
10
 
11
11
  **GitHub**: https://github.com/LegionIO/lex-scheduler
12
12
  **License**: MIT
13
- **Version**: 0.1.3
13
+ **Version**: 0.3.0
14
14
 
15
15
  ## Architecture
16
16
 
data/Gemfile CHANGED
@@ -4,9 +4,11 @@ source 'https://rubygems.org'
4
4
 
5
5
  gemspec
6
6
 
7
- gem 'bundler', '>= 2'
8
- gem 'legionio'
9
- gem 'rake'
10
- gem 'rspec'
11
- gem 'rubocop'
12
- gem 'simplecov'
7
+ group :test do
8
+ gem 'rake'
9
+ gem 'rspec', '~> 3.13'
10
+ gem 'rubocop', '~> 1.75'
11
+ gem 'sequel'
12
+ gem 'simplecov'
13
+ gem 'sqlite3'
14
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fugit'
4
+ require_relative 'runners/schedule'
5
+
6
+ module Legion
7
+ module Extensions
8
+ module Scheduler
9
+ class Client
10
+ include Runners::Schedule
11
+
12
+ def initialize(data_model: nil, fugit: nil)
13
+ @data_model = data_model
14
+ @fugit = fugit || Fugit
15
+ end
16
+
17
+ def models_class
18
+ @data_model || Data::Model
19
+ end
20
+
21
+ def log
22
+ @log ||= defined?(Legion::Logging) ? Legion::Logging : Logger.new($stdout)
23
+ end
24
+
25
+ def settings
26
+ { options: {} }
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -1,26 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  Sequel.migration do
4
- up do
5
- run '
6
- create table schedules (
7
- id int auto_increment,
8
- function_id int null,
9
- active tinyint default 1 not null,
10
- `interval` int null,
11
- cron varchar(255) null,
12
- name varchar(255) null,
13
- description blob null,
14
- task_ttl int null,
15
- last_run datetime null,
16
- created datetime default CURRENT_TIMESTAMP not null,
17
- updated datetime null,
18
- constraint schedules_pk
19
- primary key (id)
20
- );'
21
- end
22
-
23
- down do
24
- drop_table :schedules
4
+ change do
5
+ create_table(:schedules) do
6
+ primary_key :id
7
+ foreign_key :function_id, :functions, null: true
8
+ String :name, null: false
9
+ Integer :interval, null: true
10
+ String :cron, null: true, text: true
11
+ Integer :task_ttl, null: true
12
+ TrueClass :active, default: true
13
+ DateTime :last_run, null: true
14
+ DateTime :created, default: Sequel::CURRENT_TIMESTAMP
15
+ DateTime :updated, null: true
16
+ end
25
17
  end
26
18
  end
@@ -1,21 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  Sequel.migration do
4
- up do
5
- run '
6
- create table schedule_logs (
7
- id int auto_increment,
8
- schedule_id int not null,
9
- status varchar(255) null,
10
- time_queued datetime default CURRENT_TIMESTAMP not null,
11
- task_id int null,
12
- created datetime default CURRENT_TIMESTAMP not null,
13
- updated datetime null,
14
- constraint schedule_logs_pk primary key (id)
15
- );'
16
- end
17
-
18
- down do
19
- drop_table :schedule_logs
4
+ change do
5
+ create_table(:schedule_logs) do
6
+ primary_key :id
7
+ foreign_key :schedule_id, :schedules, null: true
8
+ foreign_key :task_id, :tasks, null: true
9
+ foreign_key :function_id, :functions, null: true
10
+ TrueClass :success, null: true
11
+ String :status, null: true
12
+ DateTime :created, default: Sequel::CURRENT_TIMESTAMP
13
+ end
20
14
  end
21
15
  end
@@ -3,7 +3,7 @@
3
3
  Sequel.migration do
4
4
  change do
5
5
  alter_table(:schedules) do
6
- add_column :payload, File, null: false, default: '{}'
6
+ add_column :payload, String, text: true, null: true, default: '{}'
7
7
  end
8
8
  end
9
9
  end
@@ -5,10 +5,8 @@ module Legion
5
5
  module Scheduler
6
6
  module Data
7
7
  module Model
8
- class Schedule < Sequel::Model
9
- one_to_many :schedule_logs
10
- many_to_one :task
11
- many_to_one :function
8
+ class ScheduleLog < Sequel::Model(:schedule_logs)
9
+ many_to_one :schedule, class: '::Legion::Extensions::Scheduler::Data::Model::Schedule'
12
10
  end
13
11
  end
14
12
  end
@@ -25,25 +25,37 @@ module Legion
25
25
  return unless Legion::Cache.get('scheduler_schedule_lock') == Legion::Settings[:client][:name]
26
26
 
27
27
  models_class::Schedule.where(active: 1).each do |row|
28
+ last_run = row.values[:last_run] || Time.at(0)
29
+
28
30
  if row.values[:interval].is_a?(Integer) && row.values[:interval].positive?
29
- next if (Time.now - row.values[:last_run]) < row.values[:interval]
31
+ next if (Time.now - last_run) < row.values[:interval]
30
32
  elsif row.values[:cron].is_a? String
31
33
  cron_class = Fugit.parse(row.values[:cron])
32
34
  if cron_class.respond_to? :to_sec
33
- next if (Time.now - row.values[:last_run]) < cron_class.to_sec
35
+ next if (Time.now - last_run) < cron_class.to_sec
34
36
  elsif cron_class.respond_to? :previous_time
35
- next if Time.now < Time.parse(cron_class.previous_time.to_s)
36
- next if row.values[:last_run] > Time.parse(cron_class.previous_time.to_s)
37
+ prev = Time.parse(cron_class.previous_time.to_s)
38
+ next if last_run > prev
37
39
  end
38
40
  end
39
41
 
40
42
  function = Legion::Data::Model::Function[row.values[:function_id]]
43
+ next unless function
41
44
 
42
45
  send_task(transformation: row.values[:transformation],
43
46
  function_id: row.values[:function_id],
44
47
  expiration: row.values[:task_ttl],
45
48
  function: function.values[:name],
46
49
  **Legion::JSON.load(row.values[:payload]))
50
+
51
+ models_class::ScheduleLog.insert(
52
+ schedule_id: row.values[:id],
53
+ function_id: row.values[:function_id],
54
+ success: true,
55
+ status: 'dispatched',
56
+ created: Sequel::CURRENT_TIMESTAMP
57
+ )
58
+
47
59
  row.update(last_run: Sequel::CURRENT_TIMESTAMP)
48
60
  end
49
61
  end
@@ -28,15 +28,6 @@ module Legion
28
28
  runner_class: 'Legion::Extensions::Scheduler::Runners::Schedule'
29
29
  }
30
30
  end
31
-
32
- def message_example
33
- { function: 'push_cluster_secret',
34
- node_name: Legion::Settings[:client][:name],
35
- queue_name: "node.#{Legion::Settings[:client][:name]}",
36
- runner_class: 'Legion::Extensions::Node::Runners::Crypt',
37
- # public_key: Base64.encode64(Legion::Crypt.public_key) }
38
- public_key: Legion::Crypt.public_key }
39
- end
40
31
  end
41
32
  end
42
33
  end
@@ -10,7 +10,7 @@ module Legion
10
10
  {
11
11
  arguments: {
12
12
  'x-single-active-consumer': true,
13
- 'x-message-ttl': 5
13
+ 'x-message-ttl': 5000
14
14
  },
15
15
  auto_delete: false
16
16
  }
@@ -3,7 +3,7 @@
3
3
  module Legion
4
4
  module Extensions
5
5
  module Scheduler
6
- VERSION = '0.2.0'
6
+ VERSION = '0.3.0'
7
7
  end
8
8
  end
9
9
  end
@@ -10,10 +10,6 @@ module Legion
10
10
  def self.data_required?
11
11
  true
12
12
  end
13
-
14
- def data_required?
15
- true
16
- end
17
13
  end
18
14
  end
19
15
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lex-scheduler
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity
@@ -38,7 +38,6 @@ files:
38
38
  - CLAUDE.md
39
39
  - Dockerfile
40
40
  - Gemfile
41
- - Gemfile.lock
42
41
  - LICENSE.txt
43
42
  - README.md
44
43
  - Rakefile
@@ -47,6 +46,7 @@ files:
47
46
  - lib/legion/extensions/scheduler.rb
48
47
  - lib/legion/extensions/scheduler/actors/run_scheduler.rb
49
48
  - lib/legion/extensions/scheduler/actors/schedule_task.rb
49
+ - lib/legion/extensions/scheduler/client.rb
50
50
  - lib/legion/extensions/scheduler/data/migrations/001_schedule_table.rb
51
51
  - lib/legion/extensions/scheduler/data/migrations/002_schedule_log.rb
52
52
  - lib/legion/extensions/scheduler/data/migrations/003_schedule_indexes.rb
@@ -55,9 +55,6 @@ files:
55
55
  - lib/legion/extensions/scheduler/data/migrations/006_add_transform_to_schedule.rb
56
56
  - lib/legion/extensions/scheduler/data/models/schedule.rb
57
57
  - lib/legion/extensions/scheduler/data/models/schedule_log.rb
58
- - lib/legion/extensions/scheduler/runners/emergency_promotion.rb
59
- - lib/legion/extensions/scheduler/runners/mode_scheduler.rb
60
- - lib/legion/extensions/scheduler/runners/mode_transition.rb
61
58
  - lib/legion/extensions/scheduler/runners/schedule.rb
62
59
  - lib/legion/extensions/scheduler/transport/messages/refresh.rb
63
60
  - lib/legion/extensions/scheduler/transport/messages/send_task.rb
data/Gemfile.lock DELETED
@@ -1,166 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- lex-scheduler (0.2.0)
5
- fugit (>= 1.9)
6
-
7
- GEM
8
- remote: https://rubygems.org/
9
- specs:
10
- addressable (2.8.9)
11
- public_suffix (>= 2.0.2, < 8.0)
12
- amq-protocol (2.5.1)
13
- ast (2.4.3)
14
- aws-eventstream (1.4.0)
15
- aws-sigv4 (1.12.1)
16
- aws-eventstream (~> 1, >= 1.0.2)
17
- base64 (0.3.0)
18
- bigdecimal (4.0.1)
19
- bunny (2.24.0)
20
- amq-protocol (~> 2.3)
21
- sorted_set (~> 1, >= 1.0.2)
22
- concurrent-ruby (1.3.6)
23
- concurrent-ruby-ext (1.3.6)
24
- concurrent-ruby (= 1.3.6)
25
- connection_pool (2.5.5)
26
- daemons (1.4.1)
27
- dalli (5.0.2)
28
- logger
29
- diff-lcs (1.6.2)
30
- docile (1.4.1)
31
- et-orbi (1.4.0)
32
- tzinfo
33
- fugit (1.12.1)
34
- et-orbi (~> 1.4)
35
- raabro (~> 1.4)
36
- json (2.19.1)
37
- json-schema (6.2.0)
38
- addressable (~> 2.8)
39
- bigdecimal (>= 3.1, < 5)
40
- json_pure (2.8.1)
41
- language_server-protocol (3.17.0.5)
42
- legion-cache (1.2.0)
43
- connection_pool (>= 2.2.3)
44
- dalli (>= 2.7)
45
- legion-logging
46
- legion-settings
47
- redis (>= 4.2)
48
- legion-crypt (1.2.0)
49
- vault (>= 0.15.0)
50
- legion-json (1.2.0)
51
- json_pure
52
- multi_json
53
- legion-logging (1.2.0)
54
- rainbow (~> 3)
55
- legion-settings (1.2.0)
56
- legion-json (>= 1.2)
57
- legion-transport (1.2.0)
58
- bunny (>= 2.17.0)
59
- concurrent-ruby (>= 1.1.7)
60
- legion-json
61
- legion-settings
62
- legionio (1.2.1)
63
- concurrent-ruby (>= 1.1.7)
64
- concurrent-ruby-ext (>= 1.1.7)
65
- daemons (>= 1.3.1)
66
- legion-cache (>= 0.2.0)
67
- legion-crypt (>= 0.2.0)
68
- legion-json (>= 0.2.0)
69
- legion-logging (>= 0.2.0)
70
- legion-settings (>= 0.2.0)
71
- legion-transport (>= 1.1.9)
72
- lex-node
73
- oj (>= 3.10)
74
- thor (>= 1)
75
- lex-node (0.2.0)
76
- lint_roller (1.1.0)
77
- logger (1.7.0)
78
- mcp (0.8.0)
79
- json-schema (>= 4.1)
80
- multi_json (1.19.1)
81
- net-http-persistent (4.0.8)
82
- connection_pool (>= 2.2.4, < 4)
83
- oj (3.16.16)
84
- bigdecimal (>= 3.0)
85
- ostruct (>= 0.2)
86
- ostruct (0.6.3)
87
- parallel (1.27.0)
88
- parser (3.3.10.2)
89
- ast (~> 2.4.1)
90
- racc
91
- prism (1.9.0)
92
- public_suffix (7.0.5)
93
- raabro (1.4.0)
94
- racc (1.8.1)
95
- rainbow (3.1.1)
96
- rake (13.3.1)
97
- rbtree (0.4.6)
98
- redis (5.4.1)
99
- redis-client (>= 0.22.0)
100
- redis-client (0.27.0)
101
- connection_pool
102
- regexp_parser (2.11.3)
103
- rspec (3.13.2)
104
- rspec-core (~> 3.13.0)
105
- rspec-expectations (~> 3.13.0)
106
- rspec-mocks (~> 3.13.0)
107
- rspec-core (3.13.6)
108
- rspec-support (~> 3.13.0)
109
- rspec-expectations (3.13.5)
110
- diff-lcs (>= 1.2.0, < 2.0)
111
- rspec-support (~> 3.13.0)
112
- rspec-mocks (3.13.8)
113
- diff-lcs (>= 1.2.0, < 2.0)
114
- rspec-support (~> 3.13.0)
115
- rspec-support (3.13.7)
116
- rubocop (1.85.1)
117
- json (~> 2.3)
118
- language_server-protocol (~> 3.17.0.2)
119
- lint_roller (~> 1.1.0)
120
- mcp (~> 0.6)
121
- parallel (~> 1.10)
122
- parser (>= 3.3.0.2)
123
- rainbow (>= 2.2.2, < 4.0)
124
- regexp_parser (>= 2.9.3, < 3.0)
125
- rubocop-ast (>= 1.49.0, < 2.0)
126
- ruby-progressbar (~> 1.7)
127
- unicode-display_width (>= 2.4.0, < 4.0)
128
- rubocop-ast (1.49.1)
129
- parser (>= 3.3.7.2)
130
- prism (~> 1.7)
131
- ruby-progressbar (1.13.0)
132
- simplecov (0.22.0)
133
- docile (~> 1.1)
134
- simplecov-html (~> 0.11)
135
- simplecov_json_formatter (~> 0.1)
136
- simplecov-html (0.13.2)
137
- simplecov_json_formatter (0.1.4)
138
- sorted_set (1.1.0)
139
- rbtree
140
- thor (1.5.0)
141
- tzinfo (2.0.6)
142
- concurrent-ruby (~> 1.0)
143
- unicode-display_width (3.2.0)
144
- unicode-emoji (~> 4.1)
145
- unicode-emoji (4.2.0)
146
- vault (0.20.0)
147
- aws-sigv4
148
- base64
149
- connection_pool (~> 2.4)
150
- net-http-persistent (~> 4.0, >= 4.0.2)
151
-
152
- PLATFORMS
153
- arm64-darwin-25
154
- ruby
155
-
156
- DEPENDENCIES
157
- bundler (>= 2)
158
- legionio
159
- lex-scheduler!
160
- rake
161
- rspec
162
- rubocop
163
- simplecov
164
-
165
- BUNDLED WITH
166
- 2.6.9
@@ -1,41 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Legion
4
- module Extensions
5
- module Scheduler
6
- module Runners
7
- module EmergencyPromotion
8
- DEFAULT_PATTERNS = %w[extinction.* governance.consent_violation governance.shadow_ai_detected].freeze
9
-
10
- def check_emergency(event_name:, **)
11
- return { promoted: false, reason: 'not_emergency' } unless emergency_pattern?(event_name)
12
- return { promoted: false, reason: 'already_active' } if current_mode == :active
13
-
14
- result = transition_to(target_mode: :active, reason: "emergency:#{event_name}", force: true)
15
- log_emergency(event_name)
16
- { promoted: result[:transitioned], event: event_name, transition: result }
17
- end
18
-
19
- private
20
-
21
- def emergency_pattern?(event_name)
22
- patterns = emergency_patterns
23
- patterns.any? { |p| File.fnmatch?(p, event_name) }
24
- end
25
-
26
- def log_emergency(event_name)
27
- return unless defined?(Legion::Logging) && Legion::Logging.respond_to?(:warn, true)
28
-
29
- Legion::Logging.send(:warn, "[scheduler] Emergency promotion: #{event_name}")
30
- end
31
-
32
- def emergency_patterns
33
- settings = scheduler_settings
34
- patterns = settings[:emergency_patterns]
35
- patterns.is_a?(Array) ? patterns : DEFAULT_PATTERNS
36
- end
37
- end
38
- end
39
- end
40
- end
41
- end
@@ -1,67 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Legion
4
- module Extensions
5
- module Scheduler
6
- module Runners
7
- module ModeScheduler
8
- MODES = {
9
- active: { tick_interval: 1, cognitive: true, dream: false },
10
- idle: { tick_interval: 5, cognitive: true, dream: false },
11
- dream: { tick_interval: 10, cognitive: false, dream: true },
12
- maintenance: { tick_interval: 0, cognitive: false, dream: false }
13
- }.freeze
14
-
15
- def evaluate_schedule(current_time: Time.now, **)
16
- schedules = load_schedules
17
- applicable = schedules.select { |s| matches_time?(s[:schedule], current_time) }
18
-
19
- return { mode: :idle, reason: 'no_matching_schedule', mode_config: MODES[:idle] } if applicable.empty?
20
-
21
- winner = applicable.max_by { |s| s[:priority] || 0 }
22
- mode = winner[:mode].to_sym
23
- { mode: mode, reason: 'scheduled', schedule: winner, mode_config: MODES[mode] || MODES[:idle] }
24
- end
25
-
26
- private
27
-
28
- def matches_time?(schedule, time)
29
- case schedule
30
- when 'default'
31
- true
32
- when /\Aweekday:(\d+)-(\d+)\z/
33
- start_h = ::Regexp.last_match(1).to_i
34
- end_h = ::Regexp.last_match(2).to_i
35
- (1..5).include?(time.wday) && time.hour >= start_h && time.hour < end_h
36
- when /\Aweekend:(\d+)-(\d+)\z/
37
- start_h = ::Regexp.last_match(1).to_i
38
- end_h = ::Regexp.last_match(2).to_i
39
- [0, 6].include?(time.wday) && time.hour >= start_h && time.hour < end_h
40
- when /\Adaily:(\d+)-(\d+)\z/
41
- start_h = ::Regexp.last_match(1).to_i
42
- end_h = ::Regexp.last_match(2).to_i
43
- time.hour >= start_h && time.hour < end_h
44
- else
45
- false
46
- end
47
- end
48
-
49
- def load_schedules
50
- settings = scheduler_settings
51
- raw = settings[:mode_schedule]
52
- return [] unless raw.is_a?(Array)
53
-
54
- raw.map { |s| s.transform_keys(&:to_sym) }
55
- end
56
-
57
- def scheduler_settings
58
- settings = Legion::Settings[:scheduler]
59
- settings.is_a?(Hash) ? settings : {}
60
- rescue StandardError
61
- {}
62
- end
63
- end
64
- end
65
- end
66
- end
67
- end
@@ -1,56 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Legion
4
- module Extensions
5
- module Scheduler
6
- module Runners
7
- module ModeTransition
8
- def transition_to(target_mode:, reason: 'scheduled', force: false, **)
9
- current = current_mode
10
- target = target_mode.to_sym
11
-
12
- return { transitioned: false, reason: 'already_in_mode' } if current == target
13
- return { transitioned: false, reason: 'blocked_by_critical' } if !force && critical_tasks_active?
14
-
15
- apply_mode(target)
16
- emit_transition_event(from: current, to: target, reason: reason)
17
- { transitioned: true, from: current, to: target, reason: reason }
18
- end
19
-
20
- def current_mode(**)
21
- if defined?(Legion::Gaia) && Legion::Gaia.respond_to?(:mode)
22
- Legion::Gaia.mode
23
- else
24
- :active
25
- end
26
- end
27
-
28
- private
29
-
30
- def critical_tasks_active?
31
- return false unless defined?(Legion::Data::Model::Task)
32
-
33
- Legion::Data::Model::Task.where(status: 'running', priority: 'critical').any?
34
- rescue StandardError
35
- false
36
- end
37
-
38
- def apply_mode(mode)
39
- config = ModeScheduler::MODES[mode] || ModeScheduler::MODES[:idle]
40
- return unless defined?(Legion::Gaia) && Legion::Gaia.respond_to?(:heartbeat_interval=)
41
-
42
- Legion::Gaia.heartbeat_interval = config[:tick_interval]
43
- end
44
-
45
- def emit_transition_event(from:, to:, reason:)
46
- return unless defined?(Legion::Events)
47
-
48
- Legion::Events.emit('scheduler.mode_changed', {
49
- from: from, to: to, reason: reason, changed_at: Time.now.utc
50
- })
51
- end
52
- end
53
- end
54
- end
55
- end
56
- end