Module: FiberAudit::Runtime::Environment

Defined in:
lib/fiber_audit/runtime/environment.rb

Overview

rubocop:disable Metrics/ModuleLength

Defined Under Namespace

Classes: Settings

Constant Summary collapse

PROTOCOL_VERSION =
1
ACTIVATION_KEY =
'FIBER_AUDIT_RUNTIME_BOOT'
SETTINGS_KEY =
'FIBER_AUDIT_RUNTIME_SETTINGS'
FAILURE_MODE_KEY =
'FIBER_AUDIT_RUNTIME_FAILURE_MODE'
WATCHDOG_SETTINGS_KEY =
'FIBER_AUDIT_RUNTIME_WATCHDOG_SETTINGS'
PROBES_KEY =
'FIBER_AUDIT_RUNTIME_PROBES'
BOOT_REQUIRE =
'-rfiber_audit/runtime/boot'
MAX_SETTINGS_BYTES =
16_384
MAX_WATCHDOG_SETTINGS_BYTES =
1_024
SETTINGS_KEYS =
%w[protocol_version launch_id project_root output_directory policy].freeze
POLICY_KEYS =
%w[
  redaction sampling_rate max_events_per_second max_events_per_session
  max_record_bytes max_session_bytes fail_open
].freeze
WATCHDOG_KEYS =
%w[
  protocol_version enabled heartbeat_interval_ms stall_threshold_ms max_frames
].freeze

Class Method Summary collapse

Class Method Details

.activated?(environment = ENV) ⇒ Boolean

Returns:

  • (Boolean)

Raises:



149
150
151
152
153
154
155
# File 'lib/fiber_audit/runtime/environment.rb', line 149

def activated?(environment = ENV)
  marker = environment[ACTIVATION_KEY]
  return false if marker.nil?
  return true if marker == '1'

  raise RuntimeContractError, "#{ACTIVATION_KEY} must be 1"
end

.build(policy:, output_directory:, project_root:, launch_id: SecureRandom.uuid) ⇒ Object



57
58
59
60
61
62
63
64
65
# File 'lib/fiber_audit/runtime/environment.rb', line 57

def build(policy:, output_directory:, project_root:, launch_id: SecureRandom.uuid)
  Settings.new(
    protocol_version: PROTOCOL_VERSION,
    launch_id: launch_id,
    project_root: project_root,
    output_directory: output_directory,
    policy: policy
  )
end

.child_environment(settings:, watchdog_policy: nil, probes_enabled: false, base_environment: ENV, library_path: default_library_path) ⇒ Object



173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
# File 'lib/fiber_audit/runtime/environment.rb', line 173

def child_environment(
  settings:,
  watchdog_policy: nil,
  probes_enabled: false,
  base_environment: ENV,
  library_path: default_library_path
)
  require_settings!(settings)
  require_watchdog_policy!(watchdog_policy) if watchdog_policy
  raise RuntimeContractError, 'probes_enabled must be a Boolean' unless [true, false].include?(probes_enabled)
  raise RuntimeContractError, 'base_environment must be a Hash-like object' unless base_environment.respond_to?(:[])

  environment = {
    ACTIVATION_KEY => '1',
    SETTINGS_KEY => dump(settings),
    FAILURE_MODE_KEY => settings.policy.fail_open? ? 'open' : 'closed',
    'RUBYOPT' => prepend_token(base_environment['RUBYOPT'], BOOT_REQUIRE, separator: ' '),
    'RUBYLIB' => prepend_token(base_environment['RUBYLIB'], library_path, separator: File::PATH_SEPARATOR)
  }
  environment[WATCHDOG_SETTINGS_KEY] = dump_watchdog_policy(watchdog_policy) if watchdog_policy
  environment[PROBES_KEY] = '1' if probes_enabled
  environment.transform_values(&:freeze).freeze
end

.dump(settings) ⇒ Object

Raises:



67
68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/fiber_audit/runtime/environment.rb', line 67

def dump(settings)
  require_settings!(settings)
  payload = {
    'protocol_version' => settings.protocol_version,
    'launch_id' => settings.launch_id,
    'project_root' => settings.project_root,
    'output_directory' => settings.output_directory,
    'policy' => policy_payload(settings.policy)
  }
  encoded = JSON.generate(payload)
  raise RuntimeSafetyError, 'runtime activation settings are too large' if encoded.bytesize > MAX_SETTINGS_BYTES

  encoded.freeze
end

.dump_watchdog_policy(policy) ⇒ Object



109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/fiber_audit/runtime/environment.rb', line 109

def dump_watchdog_policy(policy)
  require_watchdog_policy!(policy)
  payload = {
    'protocol_version' => PROTOCOL_VERSION,
    'enabled' => policy.enabled,
    'heartbeat_interval_ms' => policy.heartbeat_interval_ms,
    'stall_threshold_ms' => policy.stall_threshold_ms,
    'max_frames' => policy.max_frames
  }
  encoded = JSON.generate(payload)
  if encoded.bytesize > MAX_WATCHDOG_SETTINGS_BYTES
    raise RuntimeSafetyError, 'runtime watchdog activation settings are too large'
  end

  encoded.freeze
end

.failure_mode(environment = ENV) ⇒ Object



157
158
159
160
161
162
163
# File 'lib/fiber_audit/runtime/environment.rb', line 157

def failure_mode(environment = ENV)
  value = environment[FAILURE_MODE_KEY]
  return :open if value.nil? || value == 'open'
  return :closed if value == 'closed'

  raise RuntimeContractError, "#{FAILURE_MODE_KEY} must be open or closed"
end

.load(environment = ENV) ⇒ Object



82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/fiber_audit/runtime/environment.rb', line 82

def load(environment = ENV)
  return unless activated?(environment)

  value = environment.fetch(SETTINGS_KEY) do
    raise RuntimeContractError, "missing runtime activation variable: #{SETTINGS_KEY}"
  end
  unless value.is_a?(String) && value.valid_encoding? && value.bytesize <= MAX_SETTINGS_BYTES
    raise RuntimeContractError, 'runtime activation settings are invalid'
  end

  payload = JSON.parse(value)
  require_exact_keys!(payload, SETTINGS_KEYS, 'runtime activation settings')
  policy_values = payload.fetch('policy')
  require_exact_keys!(policy_values, POLICY_KEYS, 'runtime activation policy')
  settings = Settings.new(
    protocol_version: payload.fetch('protocol_version'),
    launch_id: payload.fetch('launch_id'),
    project_root: payload.fetch('project_root'),
    output_directory: payload.fetch('output_directory'),
    policy: Policy.new(**symbolize_policy(policy_values))
  )
  validate_failure_mode!(environment, settings.policy)
  settings
rescue JSON::ParserError, ArgumentError => e
  raise RuntimeContractError, "invalid runtime activation settings: #{e.message}"
end

.load_watchdog_policy(environment = ENV) ⇒ Object



126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/fiber_audit/runtime/environment.rb', line 126

def load_watchdog_policy(environment = ENV)
  value = environment[WATCHDOG_SETTINGS_KEY]
  return WatchdogPolicy::DISABLED if value.nil?
  unless value.is_a?(String) && value.valid_encoding? && value.bytesize <= MAX_WATCHDOG_SETTINGS_BYTES
    raise RuntimeContractError, 'runtime watchdog activation settings are invalid'
  end

  payload = JSON.parse(value)
  require_exact_keys!(payload, WATCHDOG_KEYS, 'runtime watchdog activation settings')
  unless payload.fetch('protocol_version') == PROTOCOL_VERSION
    raise RuntimeContractError, "runtime watchdog activation protocol must be #{PROTOCOL_VERSION}"
  end

  WatchdogPolicy.new(
    enabled: payload.fetch('enabled'),
    heartbeat_interval_ms: payload.fetch('heartbeat_interval_ms'),
    stall_threshold_ms: payload.fetch('stall_threshold_ms'),
    max_frames: payload.fetch('max_frames')
  )
rescue JSON::ParserError, ArgumentError => e
  raise RuntimeContractError, "invalid runtime watchdog activation settings: #{e.message}"
end

.normalize_directory(value, field) ⇒ Object



212
213
214
215
216
217
# File 'lib/fiber_audit/runtime/environment.rb', line 212

def normalize_directory(value, field)
  normalized = normalize_absolute_path(value, field)
  raise RuntimeContractError, "#{field} must be an existing directory" unless File.directory?(normalized)

  normalized.freeze
end

.prepare_output_directory(path) ⇒ Object



197
198
199
200
201
202
203
204
205
206
207
208
209
210
# File 'lib/fiber_audit/runtime/environment.rb', line 197

def prepare_output_directory(path)
  normalized = normalize_absolute_path(path, 'output directory')
  if File.exist?(normalized)
    raise RuntimeSafetyError, "runtime output is not a directory: #{normalized}" unless File.directory?(normalized)

    return normalized
  end

  FileUtils.mkdir_p(normalized, mode: 0o700)
  File.chmod(0o700, normalized)
  normalized
rescue SystemCallError => e
  raise RuntimeSafetyError, "cannot prepare runtime output directory: #{e.message}"
end

.probes_enabled?(environment = ENV) ⇒ Boolean

Returns:

  • (Boolean)

Raises:



165
166
167
168
169
170
171
# File 'lib/fiber_audit/runtime/environment.rb', line 165

def probes_enabled?(environment = ENV)
  value = environment[PROBES_KEY]
  return false if value.nil?
  return true if value == '1'

  raise RuntimeContractError, "#{PROBES_KEY} must be 1 when present"
end