Module: Legion::DigitalWorker

Defined in:
lib/legion/digital_worker.rb,
lib/legion/digital_worker/airb.rb,
lib/legion/digital_worker/registry.rb,
lib/legion/digital_worker/lifecycle.rb,
lib/legion/digital_worker/risk_tier.rb,
lib/legion/digital_worker/registration.rb,
lib/legion/digital_worker/value_metrics.rb

Defined Under Namespace

Modules: Airb, Lifecycle, Registration, Registry, RiskTier, ValueMetrics

Class Method Summary collapse

Class Method Details

.active_local_idsObject



88
89
90
91
92
# File 'lib/legion/digital_worker.rb', line 88

def active_local_ids
  return [] unless defined?(Registry)

  Registry.local_worker_ids
end

.active_workersObject



34
35
36
# File 'lib/legion/digital_worker.rb', line 34

def active_workers
  Legion::Data::Model::DigitalWorker.where(lifecycle_state: 'active')
end

.by_owner(owner_msid:) ⇒ Object



38
39
40
# File 'lib/legion/digital_worker.rb', line 38

def by_owner(owner_msid:)
  Legion::Data::Model::DigitalWorker.where(owner_msid: owner_msid)
end

.by_team(team:) ⇒ Object



42
43
44
# File 'lib/legion/digital_worker.rb', line 42

def by_team(team:)
  Legion::Data::Model::DigitalWorker.where(team: team)
end

.detect_orphans(stale_days: 7) ⇒ Object



56
57
58
59
60
61
62
# File 'lib/legion/digital_worker.rb', line 56

def detect_orphans(stale_days: 7)
  cutoff = Time.now.utc - (stale_days * 86_400)
  active = Legion::Data::Model::DigitalWorker.where(lifecycle_state: 'active')
  active.all.select do |w|
    w.last_heartbeat_at.nil? || w.last_heartbeat_at < cutoff
  end
end

.find(worker_id:) ⇒ Object



26
27
28
# File 'lib/legion/digital_worker.rb', line 26

def find(worker_id:)
  Legion::Data::Model::DigitalWorker.first(worker_id: worker_id)
end

.find_by_entra_app(entra_app_id:) ⇒ Object



30
31
32
# File 'lib/legion/digital_worker.rb', line 30

def find_by_entra_app(entra_app_id:)
  Legion::Data::Model::DigitalWorker.first(entra_app_id: entra_app_id)
end

.heartbeat(worker_id:, health_status: 'healthy', health_node: nil) ⇒ Object



46
47
48
49
50
51
52
53
54
# File 'lib/legion/digital_worker.rb', line 46

def heartbeat(worker_id:, health_status: 'healthy', health_node: nil)
  worker = Legion::Data::Model::DigitalWorker.first(worker_id: worker_id)
  return nil unless worker

  updates = { last_heartbeat_at: Time.now.utc, health_status: health_status }
  updates[:health_node] = health_node if health_node
  worker.update(updates)
  worker
end

.pause_orphans!(stale_days: 7, by: 'system:orphan_detection') ⇒ Object



64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/legion/digital_worker.rb', line 64

def pause_orphans!(stale_days: 7, by: 'system:orphan_detection')
  orphans = detect_orphans(stale_days: stale_days)
  orphans.each do |worker|
    Lifecycle.transition!(
      worker,
      to_state:           'paused',
      by:                 by,
      reason:             "no heartbeat for #{stale_days}+ days",
      authority_verified: true
    )
    if defined?(Legion::Events)
      Legion::Events.emit('worker.orphan_detected', {
                            worker_id:         worker.worker_id,
                            owner_msid:        worker.owner_msid,
                            last_heartbeat_at: worker.last_heartbeat_at,
                            at:                Time.now.utc
                          })
    end
  rescue Lifecycle::InvalidTransition => e
    Legion::Logging.debug("[OrphanDetection] skip #{worker.worker_id}: #{e.message}") if defined?(Legion::Logging)
  end
  orphans
end

.register(name:, extension_name:, entra_app_id:, owner_msid:, **opts) ⇒ Object



8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# File 'lib/legion/digital_worker.rb', line 8

def register(name:, extension_name:, entra_app_id:, owner_msid:, **opts)
  Legion::Data::Model::DigitalWorker.create(
    worker_id:       SecureRandom.uuid,
    name:            name,
    extension_name:  extension_name,
    entra_app_id:    entra_app_id,
    owner_msid:      owner_msid,
    owner_name:      opts[:owner_name],
    business_role:   opts[:business_role],
    risk_tier:       opts[:risk_tier],
    team:            opts[:team],
    manager_msid:    opts[:manager_msid],
    lifecycle_state: 'bootstrap',
    consent_tier:    'supervised',
    trust_score:     0.0
  )
end