fiber_audit 0.3.0 → 0.3.1

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.
data/CHANGELOG.md CHANGED
@@ -2,6 +2,35 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.3.1 (2026-08-23)
6
+
7
+ ### Added
8
+
9
+ - Added a lifecycle-owned operation-liveness monitor with strict policy transport,
10
+ explicit active/disabled/unsupported states, and bounded long-active
11
+ start/completion evidence independent of scheduler-heartbeat stalls.
12
+ - Added shared operation semantics and five Boolean/nil scheduler-classification
13
+ measurements to targeted operations, watchdog overlaps, and long-active events.
14
+ - Expanded the bounded scheduler semantic matrix for Process wait variants,
15
+ coordination hooks, localhost address resolution, and Ruby 4 IO-close
16
+ interruption; added a local measurement-only runtime probe benchmark.
17
+
18
+ ### Changed
19
+
20
+ - Scheduler snapshot failures now remain `nil`/unknown in JSONL schema 1.0 rather
21
+ than asserting false state.
22
+ - FA1006 keeps rule/operation identity while distinguishing socket allocation,
23
+ endpoint setup, local connection, and unknown subclass constructor semantics.
24
+ - FA1004 now reports medium severity without execution-context escalation and
25
+ describes possible shared-state exposure without claiming request leakage.
26
+ - Documented the tested support contract as CRuby 3.3, 3.4, and 4.0 on Ubuntu
27
+ Linux; the gem installation requirement remains Ruby 3.3 or newer.
28
+
29
+ ### Fixed
30
+
31
+ - Corrected scheduler snapshots to preserve the actual `Fiber#blocking?` Boolean
32
+ instead of converting both blocking and non-blocking Fibers to true.
33
+
5
34
  ## 0.3.0 (2026-08-12)
6
35
 
7
36
  ### Changed
data/README.md CHANGED
@@ -1,22 +1,27 @@
1
1
  # FiberAudit
2
2
 
3
3
  FiberAudit audits Ruby and Rails code for operations that require cooperation
4
- from a Fiber scheduler. Version 0.3.0 includes static analysis and an explicit,
5
- observational runtime audit with scheduler-capability and stall-overlap evidence.
4
+ from a Fiber scheduler. It provides Rubydex/Prism-backed static hypotheses and
5
+ an explicitly activated observational runtime recorder.
6
6
 
7
7
  > **Safety disclaimer:** FiberAudit does not prove that an application is
8
- > fiber-safe. Static findings are hypotheses, and absence of runtime events does
9
- > not establish complete coverage. FiberAudit does not emit unconditional `PASS`.
8
+ > fiber-safe. Runtime observations prove execution, not scheduler harm; temporal
9
+ > overlap and long-active duration do not prove causality or deadlock. Absence of
10
+ > events does not establish execution coverage. FiberAudit never emits an
11
+ > unconditional `PASS`.
10
12
 
11
13
  ## Requirements and installation
12
14
 
13
- FiberAudit v0.3.0 supports Ruby 3.3, 3.4, and 4.0.
15
+ The gem requires Ruby 3.3 or newer. CI tests CRuby 3.3, 3.4, and 4.0 on Ubuntu
16
+ Linux. Other Ruby engines and operating systems are not currently part of the
17
+ tested support contract. Native Rubydex packages must be available for the
18
+ selected platform.
14
19
 
15
20
  ```sh
16
21
  gem install fiber_audit
17
22
  ```
18
23
 
19
- Or add it to a bundle and run `bundle install`:
24
+ Or add it to a bundle:
20
25
 
21
26
  ```ruby
22
27
  gem "fiber_audit", require: false
@@ -24,15 +29,15 @@ gem "fiber_audit", require: false
24
29
 
25
30
  ## Quick start
26
31
 
27
- Run from a project directory or any directory beneath it:
32
+ Run from a project directory or beneath it:
28
33
 
29
34
  ```sh
30
35
  fiber-audit static
36
+ fiber-audit runtime -- bundle exec rspec
31
37
  ```
32
38
 
33
- FiberAudit walks upward to find the nearest `Gemfile`, `gems.rb`, or
34
- `config/application.rb`. It loads `.fiber-audit.yml` from that root when the
35
- file exists.
39
+ FiberAudit walks upward to the nearest `Gemfile`, `gems.rb`, or
40
+ `config/application.rb` and loads `.fiber-audit.yml` from that root when present.
36
41
 
37
42
  ```text
38
43
  fiber-audit static [--format text|json] [--config PATH] [--out PATH]
@@ -44,9 +49,9 @@ fiber-audit explain FA1001
44
49
  fiber-audit version
45
50
  ```
46
51
 
47
- Output defaults to text on a TTY and JSON when piped. `--out PATH` defaults to
48
- JSON, writes only the report to that file, and prints a one-line confirmation.
49
- Explicit `--format` always wins.
52
+ Static output defaults to text on a TTY and JSON when piped. `--out PATH`
53
+ defaults to JSON, writes only the report to that file, and prints a one-line
54
+ confirmation. Explicit `--format` always wins.
50
55
 
51
56
  ## Shipped rules
52
57
 
@@ -55,75 +60,124 @@ Explicit `--format` always wins.
55
60
  | FA1001 | Subprocess creation, replacement, waiting, and streams | info/medium |
56
61
  | FA1002 | Thread-wait scheduler coordination | low |
57
62
  | FA1003 | Synchronization scheduler coordination | low/info |
58
- | FA1004 | True Thread-variable state shared by sibling Fibers | high |
63
+ | FA1004 | Thread-variable access shared across Fibers on one Thread | medium |
59
64
  | FA1005 | `IO.select` scheduler capability requirement | medium |
60
- | FA1006 | Socket/DNS/I/O scheduler cooperation | low |
65
+ | FA1006 | Socket allocation and constructor endpoint semantics | low |
61
66
  | FA1007 | HTTP scheduler cooperation in request-like contexts | medium |
62
67
 
68
+ FA1004 reports true `thread_variable_get/set` access. It does not retain keys or
69
+ values and does not claim that request-sensitive leakage occurred. FA1006 keeps
70
+ stable `<Class>.new` operation identities while distinguishing inventory-only
71
+ allocation, address resolution/network endpoint setup, local connection, and
72
+ unknown IPSocket-subclass construction.
73
+
63
74
  Use `fiber-audit explain <RULE_ID>` for exact targets and remediation.
64
75
 
65
76
  ## Runtime audit
66
77
 
67
- The explicit runtime command observes only a command supplied after `--`; it
68
- never executes source fragments discovered by static analysis:
78
+ The runtime command observes only the command supplied after `--`; static
79
+ analysis never executes discovered source fragments. Each observed Ruby process
80
+ writes a separate owner-only JSONL schema `1.0` session under
81
+ `tmp/fiber-audit-runtime` by default.
69
82
 
70
- ```sh
71
- fiber-audit runtime -- bundle exec rspec
83
+ Targeted probes cover the operations represented by FA1001–FA1007. Events retain
84
+ canonical operation names, monotonic duration, conservative project-relative
85
+ callsites, execution context, ephemeral Thread/Fiber identities, and allowlisted
86
+ scalar measurements. They never retain commands, command arguments, URLs,
87
+ addresses, hosts, ports, headers, payloads, responses, return values, exception
88
+ messages, environment secrets, or thread-variable keys and values.
89
+
90
+ Libraries such as Open3, Monitor, Socket, Net::HTTP, and OpenURI may load after
91
+ runtime boot; FiberAudit rescans only known targets after `require`. Runtime
92
+ wrappers remain inert outside explicit activation and after deactivation or fork.
93
+
94
+ ### Scheduler snapshots and operation classification
95
+
96
+ A targeted operation snapshots scheduler state at its start. Measurement failure
97
+ is represented as `nil` (unknown), never coerced to false:
98
+
99
+ ```text
100
+ scheduler_present: true | false | nil
101
+ fiber_blocking: true | false | nil
102
+ scheduler_io_select_supported: true | false | nil
103
+ scheduler_process_wait_supported: true | false | nil
104
+ scheduler_address_resolve_supported: true | false | nil
72
105
  ```
73
106
 
74
- Each observed Ruby process writes a separate owner-only JSONL session under
75
- `tmp/fiber-audit-runtime` by default. Targeted probes observe the operations
76
- represented by FA1001–FA1007: subprocess calls, thread waits, synchronization,
77
- thread-local access, explicit select, direct sockets, and synchronous HTTP.
78
- Events contain canonical operation names, monotonic duration, and a conservative
79
- project-relative callsite. They never contain commands, URLs, addresses, ports,
80
- headers, payloads, responses, exception data, or thread-local keys and values.
81
- Libraries such as Open3, Monitor, Socket, Net::HTTP, and OpenURI may be loaded
82
- after runtime boot; FiberAudit rescans only these known targets after `require`.
83
-
84
- Rails execution contexts (`:request`, `:middleware`, `:job`, `:websocket`) are
85
- captured automatically when Rails integration is active. A bounded, PID- and
86
- Thread-aware immutable context chain uses inheritable Ruby Fiber storage, so
87
- child Fibers receive a snapshot of their parent's logical context. FiberAudit's
88
- `ExecutionContext.clear!` explicitly detaches the current Fiber without allowing
89
- enclosing scopes to restore stale context. Rails boundaries use prepend hooks that become inert
90
- after deactivation or fork, preserving application semantics without interfering
91
- with normal Rails operation. The integration supports late loading: hooks are
92
- installed when Rails components become available, even after runtime boot.
93
- Raw runtime JSONL now records bounded `scheduler_stall_operation_overlap`
94
- events linking active operation sequences to watchdog stalls. This establishes
95
- temporal overlap, not causality. Combined static/runtime reporting remains future work.
96
-
97
- The scheduler watchdog records one bounded start/completion pair when its
98
- scheduler-owned heartbeat stops progressing past the configured threshold.
99
- Scheduler-friendly waits should continue heartbeats. A session also records an
100
- explicit watchdog state:
107
+ Operation-specific evidence adds five Boolean/nil measurements:
108
+
109
+ ```text
110
+ operation_wait_possible
111
+ operation_inventory_only
112
+ operation_scheduler_capability_required
113
+ operation_scheduler_capability_supported
114
+ operation_scheduler_cooperation_available
115
+ ```
116
+
117
+ `operation_scheduler_cooperation_available: true` means the captured scheduler
118
+ and Fiber mode were compatible and, for an optional capability, the captured
119
+ hook was supported. Required core coordination hooks such as `block` and
120
+ `kernel_sleep` are inferred from known scheduler presence rather than measured
121
+ separately. This does not prove that the operation cooperated or completed
122
+ without delay. `nil` remains unknown or not applicable.
123
+
124
+ ### Scheduler watchdog
125
+
126
+ The watchdog records one bounded start/completion pair when a scheduler-owned
127
+ heartbeat stops progressing past its threshold. Scheduler-friendly waits should
128
+ continue heartbeats. State events are:
101
129
 
102
130
  - `watchdog_active` — a heartbeat ran under an installed scheduler;
103
131
  - `watchdog_absent` — no scheduler was observed;
104
- - `watchdog_unsupported` — a scheduler could not safely host the heartbeat;
132
+ - `watchdog_unsupported` — the scheduler could not safely host observation;
105
133
  - `watchdog_disabled` — watchdog policy disabled observation.
106
134
 
107
- Absent or unsupported monitoring, a clean session, and absence of stall events
108
- are **not** proof of fiber safety. Native work that holds Ruby's GVL can also
109
- prevent the watchdog thread from running until that work returns.
135
+ Bounded `scheduler_stall_operation_overlap` events associate active operation
136
+ sequences with a stall on the same Thread. They establish temporal overlap, not
137
+ causality. Native work retaining Ruby's GVL can prevent the watchdog Thread from
138
+ running until the work returns.
139
+
140
+ ### Long-active operation monitor
141
+
142
+ The independent operation-liveness monitor polls the bounded active-operation
143
+ registry. By default it polls every 100 ms and emits after an observed operation
144
+ remains active for strictly more than 1 second. All targeted operation types are
145
+ eligible, but entries beyond the snapshot bound can remain unobserved under
146
+ registry pressure; truncation is explicit and absence is not a coverage claim:
147
+
148
+ - `operation_liveness_active`;
149
+ - `operation_liveness_disabled`;
150
+ - `operation_liveness_unsupported`;
151
+ - `operation_long_active_started`;
152
+ - `operation_long_active_completed`.
153
+
154
+ Ordinary registry removal closes a pair with `operation_finished: true`.
155
+ Shutdown closes an open pair with `operation_finished: false`; that value does
156
+ not mean the application operation failed. Long-active evidence means only that
157
+ a targeted operation remained registered across the threshold. It is not a
158
+ scheduler stall, proven deadlock, or proof of scheduler harm.
159
+
160
+ Watchdog, liveness-state, overlap, and long-active events bypass random sampling
161
+ but still consume recorder rate, event, record-size, and session-size budgets.
162
+ Snapshot and per-poll truncation, drops, unsupported states, internal errors, and
163
+ incomplete sessions remain visible. A successful `exec` may intentionally leave
164
+ a session without `session_end`.
165
+
166
+ Rails execution contexts (`request`, `middleware`, `job`, and `websocket`) are
167
+ captured when Rails integration is active. Bounded immutable Fiber storage
168
+ propagates logical context to child Fibers without retaining request data.
169
+ Combined static/runtime reporting remains future work.
110
170
 
111
171
  ## Configuration
112
172
 
113
- Copy `.fiber-audit.example.yml` to `.fiber-audit.yml` in the project root.
114
- Paths and globs are rooted at the detected project. An explicit `--config`
115
- path is resolved from the directory where the command was invoked.
173
+ Copy `.fiber-audit.example.yml` to `.fiber-audit.yml`. Paths and globs are rooted
174
+ at the detected project; explicit `--config` is resolved from the invocation
175
+ directory.
116
176
 
117
177
  ```yaml
118
178
  static:
119
- include:
120
- - app/**/*.rb
121
- - lib/**/*.rb
122
- - config/**/*.rb
123
- exclude:
124
- - vendor/**/*
125
- - tmp/**/*
126
- - db/schema.rb
179
+ include: [app/**/*.rb, lib/**/*.rb, config/**/*.rb]
180
+ exclude: [vendor/**/*, tmp/**/*, db/schema.rb]
127
181
  suppressions_path: .fiber-audit-suppressions.yml
128
182
 
129
183
  rules:
@@ -141,40 +195,45 @@ runtime:
141
195
  mode: strict
142
196
  sampling:
143
197
  rate: 0.1
198
+ overhead:
199
+ max_events_per_second: 100
200
+ max_events_per_session: 10000
201
+ max_record_bytes: 16384
202
+ max_session_bytes: 10485760
144
203
  watchdog:
145
204
  enabled: true
146
205
  heartbeat_interval_ms: 25
147
206
  stall_threshold_ms: 100
148
207
  max_frames: 20
208
+ operation_liveness:
209
+ enabled: true
210
+ poll_interval_ms: 100
211
+ long_active_threshold_ms: 1000
149
212
  fail_open: true
150
213
  ```
151
214
 
152
- `--min-severity` overrides `report.min_severity` for one run. Severity ordering
153
- is `critical`, `high`, `medium`, `low`, `info`; findings below the threshold
154
- are omitted and do not affect the exit code. The default `low` threshold keeps
155
- informational findings silent.
215
+ Configuration is strict: unknown sections or keys, invalid types, and values
216
+ outside policy bounds return exit code 2. `--min-severity` overrides
217
+ `report.min_severity` for one static run. Severity ordering is `critical`,
218
+ `high`, `medium`, `low`, `info`; the default `low` threshold omits informational
219
+ findings.
156
220
 
157
221
  ## Suppressions
158
222
 
159
223
  Every suppression requires a non-empty reason. Directive-looking text inside
160
224
  strings, heredocs, or regular expressions is ignored.
161
225
 
162
- Suppress one line:
163
-
164
226
  ```ruby
165
227
  system(command) # fiber-audit:disable FA1001 -- trusted maintenance command
166
228
  ```
167
229
 
168
- Suppress a block:
169
-
170
230
  ```ruby
171
231
  # fiber-audit:disable FA1003 -- protected legacy boundary
172
232
  mutex.synchronize { update_record }
173
233
  # fiber-audit:enable FA1003
174
234
  ```
175
235
 
176
- A separate YAML file can suppress by rule and optionally by symbol or
177
- operation. Point `static.suppressions_path` at the file:
236
+ YAML suppressions can match rule and optionally symbol or operation:
178
237
 
179
238
  ```yaml
180
239
  suppressions:
@@ -184,29 +243,23 @@ suppressions:
184
243
  reason: isolated worker process with an external timeout
185
244
  ```
186
245
 
187
- Missing reasons and invalid configuration return exit code 2.
188
-
189
- ## Static statuses
246
+ ## Static statuses and exit codes
190
247
 
191
248
  - `FAIL` — at least one critical or high finding.
192
- - `REVIEW` — a medium finding, or a non-informational low/unknown-confidence
193
- finding.
249
+ - `REVIEW` — a medium finding, or non-informational low/unknown-confidence risk.
194
250
  - `PASS_WITH_WARNINGS` — only low or informational findings.
195
251
  - `NO_FINDINGS` — no findings at the configured threshold.
196
252
 
197
- FiberAudit never emits unconditional `PASS` in v0.3.0.
198
-
199
- ## Exit codes
253
+ FiberAudit never emits unconditional `PASS`.
200
254
 
201
255
  | Code | Meaning |
202
256
  |---|---|
203
257
  | 0 | No active finding at or above the configured threshold |
204
258
  | 1 | One or more active findings at or above the threshold |
205
259
  | 2 | Invalid options, configuration, analysis, or report output |
206
- | 3 | Reserved; never emitted by v0.3.0 |
260
+ | 3 | Reserved; not emitted |
207
261
 
208
- Source parse errors are included in report data while analysis continues on
209
- other files.
262
+ Source parse errors remain report data while analysis continues on other files.
210
263
 
211
264
  ## Development and semantic verification
212
265
 
@@ -214,19 +267,19 @@ other files.
214
267
  bundle exec rspec
215
268
  bundle exec rubocop
216
269
  bundle exec ruby script/scheduler-semantics
270
+ FIBER_AUDIT_BENCH_ITERATIONS=2000 bundle exec ruby benchmark/runtime_probe_overhead.rb
217
271
  gem build fiber_audit.gemspec
218
272
  bundle exec rake release:sanity
219
273
  ```
220
274
 
221
- The semantic probe uses only local Threads, pipes, and child processes. It checks
222
- scheduler coordination, Fiber-storage inheritance, Thread/Fiber state semantics,
223
- and rejected scheduler replacement against each supported Ruby in CI.
224
-
225
- Relevant Ruby contracts:
275
+ The semantic script uses bounded local Threads, pipes, localhost resolution, and
276
+ child processes. CI exercises it through the spec suite on tested CRuby versions.
277
+ It verifies only scheduler capabilities consumed by FiberAudit; it is not a full
278
+ scheduler-conformance suite.
226
279
 
227
- - [Fiber and inheritable storage](https://docs.ruby-lang.org/en/3.4/Fiber.html)
228
- - [Fiber::Scheduler hooks](https://docs.ruby-lang.org/en/3.4/Fiber/Scheduler.html)
229
- - [Thread Fiber-local and Thread-wide storage](https://docs.ruby-lang.org/en/3.4/Thread.html#method-i-5B-5D)
280
+ The benchmark reports absent, installed/inactive, active sampling-zero, and
281
+ active sampling-one workloads in isolated subprocesses. It is diagnostic only:
282
+ there is no host-dependent CI timing threshold.
230
283
 
231
- See [ARCHITECTURE.md](ARCHITECTURE.md) for implementation boundaries, runtime
232
- architecture, raw stall-overlap evidence, and deferred combined reporting.
284
+ See [ARCHITECTURE.md](ARCHITECTURE.md) for implementation boundaries and the
285
+ runtime truthfulness, privacy, lifecycle, and schema contracts.
@@ -101,6 +101,7 @@ module FiberAudit
101
101
  environment = Runtime::Environment.child_environment(
102
102
  settings: settings,
103
103
  watchdog_policy: configuration.runtime_watchdog_policy,
104
+ operation_liveness_policy: configuration.runtime_operation_liveness_policy,
104
105
  probes_enabled: true
105
106
  )
106
107
  Runtime::Supervisor.new(
@@ -202,7 +203,8 @@ module FiberAudit
202
203
  min_severity: severity,
203
204
  suppressions_path: configuration.suppressions_path,
204
205
  runtime_policy: configuration.runtime_policy,
205
- runtime_watchdog_policy: configuration.runtime_watchdog_policy
206
+ runtime_watchdog_policy: configuration.runtime_watchdog_policy,
207
+ runtime_operation_liveness_policy: configuration.runtime_operation_liveness_policy
206
208
  )
207
209
  end
208
210
 
@@ -6,6 +6,7 @@ require_relative 'errors'
6
6
  require_relative 'findings/severity'
7
7
  require_relative 'runtime/policy'
8
8
  require_relative 'runtime/watchdog_policy'
9
+ require_relative 'runtime/operation_liveness_policy'
9
10
 
10
11
  module FiberAudit
11
12
  # rubocop:disable Metrics/ClassLength
@@ -14,7 +15,8 @@ module FiberAudit
14
15
  KNOWN_STATIC_KEYS = %w[include exclude suppressions_path].freeze
15
16
  KNOWN_REPORT_KEYS = %w[formats min_severity].freeze
16
17
  KNOWN_RULE_KEYS = %w[enabled severity].freeze
17
- KNOWN_RUNTIME_KEYS = %w[redaction sampling overhead watchdog fail_open].freeze
18
+ KNOWN_RUNTIME_KEYS = %w[redaction sampling overhead watchdog operation_liveness fail_open].freeze
19
+ KNOWN_OPERATION_LIVENESS_KEYS = %w[enabled poll_interval_ms long_active_threshold_ms].freeze
18
20
  KNOWN_REDACTION_KEYS = %w[mode].freeze
19
21
  KNOWN_SAMPLING_KEYS = %w[rate].freeze
20
22
  KNOWN_OVERHEAD_KEYS = %w[
@@ -50,7 +52,8 @@ module FiberAudit
50
52
 
51
53
  attr_reader :static_include, :static_exclude, :rules_config,
52
54
  :report_formats, :min_severity, :suppressions_path,
53
- :runtime_policy, :runtime_watchdog_policy
55
+ :runtime_policy, :runtime_watchdog_policy,
56
+ :runtime_operation_liveness_policy
54
57
 
55
58
  def initialize(
56
59
  static_include: DEFAULT_STATIC_INCLUDE,
@@ -60,12 +63,13 @@ module FiberAudit
60
63
  min_severity: :low,
61
64
  suppressions_path: nil,
62
65
  runtime_policy: Runtime::Policy.new,
63
- runtime_watchdog_policy: Runtime::WatchdogPolicy.new
66
+ runtime_watchdog_policy: Runtime::WatchdogPolicy.new,
67
+ runtime_operation_liveness_policy: Runtime::OperationLivenessPolicy.new
64
68
  )
65
69
  validate_types!(
66
70
  static_include, static_exclude, rules_config,
67
71
  report_formats, min_severity, suppressions_path, runtime_policy,
68
- runtime_watchdog_policy
72
+ runtime_watchdog_policy, runtime_operation_liveness_policy
69
73
  )
70
74
 
71
75
  @static_include = static_include
@@ -76,6 +80,7 @@ module FiberAudit
76
80
  @suppressions_path = suppressions_path
77
81
  @runtime_policy = runtime_policy
78
82
  @runtime_watchdog_policy = runtime_watchdog_policy
83
+ @runtime_operation_liveness_policy = runtime_operation_liveness_policy
79
84
  end
80
85
 
81
86
  def rule_enabled?(rule_id)
@@ -113,7 +118,8 @@ module FiberAudit
113
118
  min_severity: report.fetch('min_severity', :low),
114
119
  suppressions_path: static['suppressions_path'],
115
120
  runtime_policy: runtime_policy_from(runtime),
116
- runtime_watchdog_policy: runtime_watchdog_policy_from(runtime)
121
+ runtime_watchdog_policy: runtime_watchdog_policy_from(runtime),
122
+ runtime_operation_liveness_policy: runtime_operation_liveness_policy_from(runtime)
117
123
  )
118
124
  end
119
125
 
@@ -166,6 +172,7 @@ module FiberAudit
166
172
  validate_runtime_mapping!(runtime, 'sampling', KNOWN_SAMPLING_KEYS)
167
173
  validate_runtime_mapping!(runtime, 'overhead', KNOWN_OVERHEAD_KEYS)
168
174
  validate_runtime_mapping!(runtime, 'watchdog', KNOWN_WATCHDOG_KEYS)
175
+ validate_runtime_mapping!(runtime, 'operation_liveness', KNOWN_OPERATION_LIVENESS_KEYS)
169
176
  end
170
177
 
171
178
  def validate_runtime_mapping!(runtime, key, allowed)
@@ -214,6 +221,19 @@ module FiberAudit
214
221
  raise ConfigurationError, "#{path} is invalid: #{e.message}"
215
222
  end
216
223
 
224
+ def runtime_operation_liveness_policy_from(runtime)
225
+ values = runtime.fetch('operation_liveness', {})
226
+ defaults = Runtime::OperationLivenessPolicy::DEFAULTS
227
+ Runtime::OperationLivenessPolicy.new(
228
+ enabled: values.fetch('enabled', defaults[:enabled]),
229
+ poll_interval_ms: values.fetch('poll_interval_ms', defaults[:poll_interval_ms]),
230
+ long_active_threshold_ms: values.fetch('long_active_threshold_ms', defaults[:long_active_threshold_ms])
231
+ )
232
+ rescue RuntimeContractError => e
233
+ field = e.message.split.first
234
+ raise ConfigurationError, "runtime.operation_liveness.#{field} is invalid: #{e.message}"
235
+ end
236
+
217
237
  def check_unknown_keys(hash, allowed, path)
218
238
  unknown = hash.keys - allowed
219
239
  return if unknown.empty?
@@ -229,7 +249,8 @@ module FiberAudit
229
249
 
230
250
  def validate_types!(
231
251
  include_patterns, exclude_patterns, rules,
232
- formats, _severity, suppressions, runtime_policy, watchdog_policy
252
+ formats, _severity, suppressions, runtime_policy, watchdog_policy,
253
+ operation_liveness_policy
233
254
  )
234
255
  unless include_patterns.is_a?(Array) &&
235
256
  include_patterns.all?(String)
@@ -256,6 +277,10 @@ module FiberAudit
256
277
  raise ConfigurationError,
257
278
  'runtime_watchdog_policy must be a FiberAudit::Runtime::WatchdogPolicy'
258
279
  end
280
+ unless operation_liveness_policy.is_a?(Runtime::OperationLivenessPolicy)
281
+ raise ConfigurationError,
282
+ 'runtime_operation_liveness_policy must be a FiberAudit::Runtime::OperationLivenessPolicy'
283
+ end
259
284
 
260
285
  return if suppressions.nil? || suppressions.is_a?(String)
261
286