Module: Legion::MCP::ContextGuard

Extended by:
Logging::Helper
Defined in:
lib/legion/mcp/context_guard.rb

Constant Summary collapse

DEFAULT_MAX_STALE_SECONDS =
3600
DEFAULT_RAPID_FIRE_THRESHOLD =
5
DEFAULT_RAPID_FIRE_WINDOW_SECS =
600
DEFAULT_ANOMALY_MISS_THRESHOLD =
2

Class Method Summary collapse

Class Method Details

.anomalous?(pattern) ⇒ Boolean

Returns:

  • (Boolean)


41
42
43
# File 'lib/legion/mcp/context_guard.rb', line 41

def anomalous?(pattern)
  (pattern[:miss_count] || 0) >= anomaly_miss_threshold
end

.anomaly_failure(pattern) ⇒ Object



91
92
93
# File 'lib/legion/mcp/context_guard.rb', line 91

def anomaly_failure(pattern)
  { passed: false, guard: :anomaly, reason: "#{pattern[:miss_count]} consecutive misses" }
end

.anomaly_miss_thresholdObject



72
73
74
# File 'lib/legion/mcp/context_guard.rb', line 72

def anomaly_miss_threshold
  DEFAULT_ANOMALY_MISS_THRESHOLD
end

.check(pattern, _params, _context) ⇒ Object



15
16
17
18
19
20
21
# File 'lib/legion/mcp/context_guard.rb', line 15

def check(pattern, _params, _context)
  return staleness_failure(pattern) if stale?(pattern)
  return anomaly_failure(pattern) if anomalous?(pattern)
  return rapid_fire_failure(pattern) if rapid_fire?(pattern[:intent_hash])

  { passed: true }
end

.max_stale_secondsObject



60
61
62
# File 'lib/legion/mcp/context_guard.rb', line 60

def max_stale_seconds
  setting(:max_stale_seconds) || DEFAULT_MAX_STALE_SECONDS
end

.mutexObject



104
105
106
# File 'lib/legion/mcp/context_guard.rb', line 104

def mutex
  @mutex ||= Mutex.new
end

.rapid_fire?(intent_hash) ⇒ Boolean

Returns:

  • (Boolean)


45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/legion/mcp/context_guard.rb', line 45

def rapid_fire?(intent_hash)
  return false unless intent_hash

  window = Time.now - rapid_fire_window_seconds
  count = mutex.synchronize do
    entries = requests[intent_hash]
    return false unless entries

    entries.reject! { |t| t < window }
    entries.size
  end

  count > rapid_fire_threshold
end

.rapid_fire_failure(_pattern) ⇒ Object



95
96
97
98
# File 'lib/legion/mcp/context_guard.rb', line 95

def rapid_fire_failure(_pattern)
  { passed: false, guard: :rapid_fire,
    reason: "exceeded #{rapid_fire_threshold} requests in #{rapid_fire_window_seconds}s" }
end

.rapid_fire_thresholdObject



64
65
66
# File 'lib/legion/mcp/context_guard.rb', line 64

def rapid_fire_threshold
  setting(:rapid_fire_threshold) || DEFAULT_RAPID_FIRE_THRESHOLD
end

.rapid_fire_window_secondsObject



68
69
70
# File 'lib/legion/mcp/context_guard.rb', line 68

def rapid_fire_window_seconds
  setting(:rapid_fire_window_seconds) || DEFAULT_RAPID_FIRE_WINDOW_SECS
end

.record_request(intent_hash) ⇒ Object



23
24
25
26
27
28
# File 'lib/legion/mcp/context_guard.rb', line 23

def record_request(intent_hash)
  mutex.synchronize do
    requests[intent_hash] ||= []
    requests[intent_hash] << Time.now
  end
end

.requestsObject



100
101
102
# File 'lib/legion/mcp/context_guard.rb', line 100

def requests
  @requests ||= {}
end

.reset!Object



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

def reset!
  mutex.synchronize { requests.clear }
end

.setting(key) ⇒ Object



76
77
78
79
80
81
82
83
84
# File 'lib/legion/mcp/context_guard.rb', line 76

def setting(key)
  return nil unless defined?(Legion::Settings)

  Legion::Settings.dig(:mcp, :tier0, :guards, key)
rescue StandardError => e
  handle_exception(e, level: :warn, operation: 'legion.mcp.context_guard.setting')
  log.warn("ContextGuard#setting failed for key #{key}: #{e.message}")
  nil
end

.stale?(pattern) ⇒ Boolean

Returns:

  • (Boolean)


34
35
36
37
38
39
# File 'lib/legion/mcp/context_guard.rb', line 34

def stale?(pattern)
  last_hit = pattern[:last_hit_at]
  return false unless last_hit

  (Time.now - last_hit) > max_stale_seconds
end

.staleness_failure(pattern) ⇒ Object



86
87
88
89
# File 'lib/legion/mcp/context_guard.rb', line 86

def staleness_failure(pattern)
  age = pattern[:last_hit_at] ? (Time.now - pattern[:last_hit_at]).round(0) : 0
  { passed: false, guard: :staleness, reason: "pattern stale (#{age}s since last hit)" }
end