hyperprobe-agent 1.2.27.pre.1 → 1.2.27.pre.3

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: 0fc98f264e7d9cd4fe3c902f686ba8e6b95e7c6f272126d4b6a811400f0eff39
4
- data.tar.gz: 67b10ac7149644e1a4644693e016b31b48dc8f6892fcc9c1c5e360e5604495a5
3
+ metadata.gz: 593d2fbffa7e11534488637e25c037d1ca8d0421e875d276fa20a33c11aaddf8
4
+ data.tar.gz: 4b8f1a9be42287874b9c02b8da146b946b0eb298579f81a6d9795fe2bf83e62c
5
5
  SHA512:
6
- metadata.gz: 44c3a8718c1df4b1b843f9320b4deb3237ab722af67128cb38670c80ebf1bb74e2a6a5f81ebb7d9b5d4f12af92b1ea43b6abcc13ec01f6a4a3f34500cf42006f
7
- data.tar.gz: 47e7bb3084e5700db38c353cf351046c7082abf417ab30a41350270c72329194b89cceba596df0cdc9cd53d0f40c3e05cc02ef712290895b9e1d302f39360f69
6
+ metadata.gz: 5267c5287adb166447b512c54397271096f48075f8d38746466229cfc1e80e568d16fd3d682260583cc50162390be2901ac45c2bdf283539a82796b6b3691032
7
+ data.tar.gz: 7af9d75f51f8fd1ea9f870662ffb8bdc5bc03ab8d755f12866072325b87f78dd1a327f51dc6182e4774b5661e47aaa8384f5ca751f7122a648102138d13f96d6
data/README.md CHANGED
@@ -1,49 +1,85 @@
1
1
  # HyperProbe Ruby Agent
2
2
 
3
- Production-grade, non-breaking live debugger and telemetry agent for Ruby and JRuby applications.
3
+ Live snapshots, dynamic logs, counters, metrics, and durations for Ruby applications, without debugger breakpoints that suspend execution. Probe evaluation still runs on application threads and has overhead; quotas, capture budgets, and the safety monitor limit instrumentation rather than guarantee zero impact.
4
4
 
5
- ## Overview
5
+ ## Runtime Compatibility
6
6
 
7
- The HyperProbe Ruby Agent allows developers to set non-breaking live probes (snapshots, logs, counters, metrics, and durations) in production environments without pausing execution or modifying source code.
7
+ | Runtime | Supported baseline | Transport and requirements |
8
+ | :--- | :--- | :--- |
9
+ | CRuby / MRI | Ruby 3.0+ (tested 4.0.6) | Native `grpc` gem; zero launch flags needed |
10
+ | JRuby | JRuby 9.3+ on Java 11+ | Bundled grpc-java transport and private protobuf codec; launch profile below |
11
+
12
+ RubyGems selects the `ruby` or `java` artifact. The Java artifact requires Ruby
13
+ >= 2.6 and has no `google-protobuf`, native `grpc`, or `ffi` runtime dependencies.
14
+ It bundles grpc-java and patched `protobuf-java` 3.25.9 core in a shaded JAR.
15
+ An SDK-private Ruby adapter uses Java `DynamicMessage` with a generated descriptor
16
+ of the existing SDK protocol schema; it neither provides nor modifies the host's
17
+ `Google::Protobuf` module. Java `com.google.protobuf` names are relocated under
18
+ `co.hyperprobe.internal.google.protobuf`. MRI remains Ruby >= 3.0 with its existing
19
+ `google-protobuf` (>= 3.25, < 5) and `grpc` (>= 1.50, < 2) dependencies.
20
+
21
+ The Java artifact does not use `java.net.http.HttpClient` or `jar-dependencies`.
22
+ Loading or connecting with the installed Java gem does not download dependencies;
23
+ Maven and a JDK are needed only to build it from source. Normal gem installation
24
+ still needs access to the selected artifacts and application dependencies.
25
+ Clean packaged Bundler specs passed on JRuby 9.3, 9.4, and 10.1. Choose a JDK
26
+ supported by your JRuby version too.
27
+
28
+ JRuby must retain line events and correct bindings after JIT compilation.
29
+ `--debug` alone is insufficient; use this complete process-start profile:
8
30
 
9
- ## Key Features
31
+ ```bash
32
+ jruby --debug \
33
+ -J-Djruby.ir.passes=AddCallProtocolInstructions,AddMissingInitsPass \
34
+ -J-Djruby.ir.jit.passes=AddCallProtocolInstructions,AddMissingInitsPass app.rb
35
+ # Or set before launching the application:
36
+ JRUBY_OPTS="--debug -J-Djruby.ir.passes=AddCallProtocolInstructions,AddMissingInitsPass -J-Djruby.ir.jit.passes=AddCallProtocolInstructions,AddMissingInitsPass" jruby app.rb
37
+ ```
10
38
 
11
- - **Snapshots**: Capture local variables across stack frames and evaluate watch expressions without pausing threads.
12
- - **Dynamic Logs**: Evaluate log templates (`${var}`) in context, with optional stdout emission and full value redaction.
13
- - **Metrics & Counters**: Measure event counts, custom expressions, and block/loop durations.
14
- - **Safety Shield**: Continuous thread lag monitoring and pause duration budgets with automated circuit breaker shedding (`GREEN`/`YELLOW`/`RED`).
15
- - **Quota Governance**: Token bucket rate limiting for hits per second and network bandwidth.
16
- - **Distributed Tracing**: Integrations with OpenTelemetry, Datadog (`ddtrace`), New Relic (`newrelic_rpm`), Elastic APM, and custom trace getters.
17
- - **Multi-Process & Serverless**: Native support for Puma/Unicorn worker forking and AWS Lambda execution.
18
- - **JRuby on JVM**: First-class JRuby support with native JVM HTTP/2 gRPC transport and hybrid Java object serialization.
39
+ For containers using the JRuby launcher, put the same complete profile in
40
+ `JRUBY_OPTS` before process startup. Use the JVM properties shown here rather
41
+ than generic `-Xir...` options, which older JRuby 9.3 direct `Main` rejects.
42
+ Direct `java -jar` / `org.jruby.Main` does not honor `JRUBY_OPTS`; put all JVM
43
+ properties before `-jar`:
19
44
 
20
- ## Runtime Compatibility
45
+ ```bash
46
+ java -Djruby.debug.fullTrace=true \
47
+ -Djruby.ir.passes=AddCallProtocolInstructions,AddMissingInitsPass \
48
+ -Djruby.ir.jit.passes=AddCallProtocolInstructions,AddMissingInitsPass \
49
+ -jar /path/to/jruby-complete.jar app.rb
50
+ ```
21
51
 
22
- | Platform | Supported Versions | Prerequisites |
23
- | :--- | :--- | :--- |
24
- | **Standard Ruby (CRuby / MRI)** | **Ruby 3.0, 3.1, 3.2, 3.3, 3.4, 4.0+** | None (Zero launch flags needed) |
25
- | **JRuby (Ruby on JVM)** | **JRuby 9.3+, 9.4+, 10.x+** | **Java 11+** and `JRUBY_OPTS="--debug"` |
52
+ JRuby 9.3 on Java 17 also requires standard runtime I/O compatibility opens:
53
+ `-J--add-opens=java.base/sun.nio.ch=ALL-UNNAMED` and
54
+ `-J--add-opens=java.base/java.io=ALL-UNNAMED` with the JRuby launcher. For direct
55
+ Java, use `--add-opens=java.base/sun.nio.ch=ALL-UNNAMED` and
56
+ `--add-opens=java.base/java.io=ALL-UNNAMED` before `-jar`. These are explicit
57
+ JRuby/JDK runtime requirements, not settings silently changed by the SDK.
58
+
59
+ `HyperProbe.start` checks the actual effective
60
+ `Java::OrgJruby::RubyInstanceConfig.FULL_TRACE_ENABLED` Java field (dot access),
61
+ plus `Options::IR_COMPILER_PASSES.load` and `Options::IR_JIT_PASSES.load` under
62
+ `Java::OrgJrubyUtilCli`. Both pass lists must equal the lists above. Environment
63
+ text or a system-property string alone is not proof that tracing is enabled.
64
+ Unsafe profiles disable SDK startup instead of changing process configuration.
65
+
66
+ This profile keeps JIT enabled without method exclusions, but changes
67
+ process-wide optimization passes. Benchmark your own application
68
+ before deployment; see the [transport README](transport/README.md).
26
69
 
27
70
  ## Installation
28
71
 
29
- Add to your `Gemfile`:
72
+ Add to your application's `Gemfile`, then run `bundle install`:
30
73
 
31
74
  ```ruby
32
75
  gem 'hyperprobe-agent'
33
76
  ```
34
77
 
35
- And run:
36
-
37
- ```bash
38
- bundle install
39
- ```
40
-
41
78
  ## Quickstart
42
79
 
43
- Create a dedicated `hyperprobe.rb` file in your application root:
80
+ Create an application initializer, for example `hyperprobe.rb`:
44
81
 
45
82
  ```ruby
46
- # hyperprobe.rb
47
83
  require 'hyperprobe'
48
84
 
49
85
  HyperProbe.start(
@@ -54,104 +90,297 @@ HyperProbe.start(
54
90
  )
55
91
  ```
56
92
 
57
- Require it as early as possible in your application entrypoint (e.g. `app.rb`, `server.rb`, `config/environment.rb`):
93
+ Require it early in your application entrypoint:
58
94
 
59
95
  ```ruby
60
- # app.rb
61
96
  require_relative 'hyperprobe'
62
-
63
- # ... start your web server / application ...
64
97
  ```
65
98
 
66
- ## Environment Variables
99
+ Use the service ID, environment, broker URL, and commit SHA that identify your actual deployment. The SHA must match the deployed source; missing, blank, or `unknown` commit values prevent startup. For the repository's local demo, retain its existing service and broker URL rather than replacing them with these placeholders or another endpoint.
100
+
101
+ In ordinary (non-Lambda) mode, the first probe sync starts asynchronously on a background thread without waiting for the periodic interval. `HyperProbe.start` does not wait for broker connectivity or probe installation; early requests can run before probes arrive. Agent-owned load, startup, sync, capture, and shutdown failures are isolated from application code, not exposed as public failure exceptions. Host signals and explicit process termination (`SignalException` and `SystemExit`) retain their normal behavior; the agent does not cancel application shutdown. Startup returns an agent on success or `nil` when disabled, deferred, or unsuccessful; some failures produce diagnostics, and others are silent. A successful return is not proof of connectivity or telemetry delivery.
102
+
103
+ ## Configuration
67
104
 
68
- | Variable | Description | Default |
105
+ Explicit options take precedence over environment variables. The server can update capture/redaction configuration, and per-probe capture options can override global capture limits within hard caps.
106
+
107
+ | Variable | Meaning | Default |
69
108
  | :--- | :--- | :--- |
70
- | `HYPERPROBE_SERVICE_ID` | Unique UUID for the service | (Required) |
71
- | `HYPERPROBE_ENVIRONMENT` | Deployment environment (`production`, `staging`, etc.) | (Required) |
72
- | `HYPERPROBE_BROKER_URL` | gRPC Broker endpoint | (Required) |
73
- | `GIT_COMMIT` / `HYPERPROBE_COMMIT_SHA` | Current deployment commit SHA | (Required) |
74
- | `HYPERPROBE_DISABLED` | Set to `YES` to disable the agent | `NO` |
75
- | `HYPERPROBE_SYNC_INTERVAL_MS` | Probe synchronization interval | `60000` |
109
+ | `HYPERPROBE_SERVICE_ID` | Service UUID | Required |
110
+ | `HYPERPROBE_ENVIRONMENT` | Deployment environment | Required |
111
+ | `HYPERPROBE_BROKER_URL` | Broker gRPC endpoint | Required |
112
+ | `GIT_COMMIT` / `HYPERPROBE_COMMIT_SHA` | Deployment commit SHA, in that precedence order | Required |
113
+ | `HYPERPROBE_DISABLED` | Set to `YES` to disable startup | Unset |
114
+ | `HYPERPROBE_DISABLE_SAFE_EVALUATION` | Set to `true` to allow unrestricted Ruby execution in probe expressions (opt-in) | `false` |
115
+ | `HYPERPROBE_SYNC_INTERVAL_MS` | Periodic probe sync interval | `60000` |
76
116
  | `HYPERPROBE_FLUSH_INTERVAL_MS` | Telemetry batch flush interval | `1000` |
77
- | `HYPERPROBE_HITS_PER_SEC` | Maximum probe hits evaluated per second | `10` |
78
- | `HYPERPROBE_BANDWIDTH_KB_PER_SEC` | Maximum telemetry bandwidth in KB/s | `1024` |
79
- | `HYPERPROBE_MAX_LAG_MS` | Safety threshold for thread lag | `50` |
80
- | `HYPERPROBE_PAUSE_BUDGET_MS` | Maximum probe execution pause per second | `15` |
81
- | `HYPERPROBE_REDACT_KEYS` | Comma-separated list of sensitive key patterns | `password,secret,token,...` |
82
- | `HYPERPROBE_REDACT_VALUES` | Comma-separated list of sensitive value regexes | `""` |
117
+ | `HYPERPROBE_RPC_TIMEOUT_SEC` | Broker RPC deadline | `10` |
118
+ | `HYPERPROBE_MAX_QUEUE_SIZE` | Maximum queued telemetry events | `100` |
119
+ | `HYPERPROBE_HITS_PER_SEC` | Probe evaluation quota | `10` |
120
+ | `HYPERPROBE_BANDWIDTH_KB_PER_SEC` | Telemetry bandwidth quota | `1024` |
121
+ | `HYPERPROBE_MAX_LAG_MS` | Thread-lag threshold (MRI only; ignored on JRuby) | `50` |
122
+ | `HYPERPROBE_PAUSE_BUDGET_MS` | Probe-handler time budget per second (MRI only; ignored on JRuby) | `15` |
123
+ | `HYPERPROBE_COOLDOWN_SEC` | Safety cooldown duration | `10` |
124
+ | `HYPERPROBE_MAX_OBJECT_DEPTH` | Object recursion depth | `3` |
125
+ | `HYPERPROBE_MAX_ARRAY_LENGTH` | Items captured per sequence | `3` |
126
+ | `HYPERPROBE_MAX_OBJECT_PROPERTIES` | Properties captured per object | `50` |
127
+ | `HYPERPROBE_MAX_STRING_LENGTH` | Snapshot string byte limit | `1024` |
128
+ | `HYPERPROBE_STACK_FRAME_DEPTH` | Number of frames, including the hit frame | `3` |
129
+ | `HYPERPROBE_REDACT_KEYS` | Comma-separated restricted patterns | `password,secret,token,authorization,cookie,key,signature` |
130
+ | `HYPERPROBE_REDACT_VALUES` | Comma-separated restricted patterns | Empty |
83
131
 
84
- ---
132
+ Use `capture_closures: true` as a start option (or the corresponding server/probe setting) to opt into closure capture. There is no `HYPERPROBE_CAPTURE_CLOSURES` environment option. Fork deferral uses the Ruby keyword `defer_start: true`.
85
133
 
86
- ## JRuby Support (Ruby on the JVM)
134
+ Use HTTPS for TLS. Raw `host:port` and `http://host:port` use plaintext gRPC. Do not add a trailing slash or query parameters. JRuby uses the JVM trust store and hostname verification; private CAs should be configured with standard JVM trust-store options, not disabled certificate verification.
87
135
 
88
- HyperProbe provides native, first-class support for JRuby applications running on the JVM.
136
+ ### Runtime Safety
89
137
 
90
- ### Prerequisites for JRuby
91
- 1. **JRuby Version**: JRuby 9.3+, 9.4+, or 10.x+.
92
- 2. **Java Runtime**: Java 11, 17, 21, or 25+.
93
- 3. **Debug Flag**: The JVM must be launched with `--debug` or `JRUBY_OPTS="--debug"`.
138
+ MRI retains timing-based safety: repeated safety-thread lag or a cumulative
139
+ probe-handler time budget breach can suspend instrumentation.
94
140
 
95
- ### Why is `--debug` Required on JRuby?
96
- JRuby's JIT (Just-In-Time) compiler aggressively optimizes hot methods into raw JVM bytecode, stripping out line-level checkpoints and local variable names by default.
141
+ JRuby uses JVM heap safety, matching the Java SDK's memory thresholds. Heap is
142
+ sampled every 5 seconds, starting 5 seconds after monitor startup. Available
143
+ headroom is `(freeMemory + maxMemory - totalMemory) / maxMemory`, including
144
+ uncommitted heap capacity, not free system RAM. Below 15% is YELLOW (warning);
145
+ below 5% is RED (suspend probes). At exactly 5% health is YELLOW, and at exactly
146
+ 15% it is GREEN. These heap thresholds are fixed.
97
147
 
98
- Running with `--debug` instructs JRuby's JVM compiler to:
99
- * Retain line-by-line tracing checkpoints so probes trigger reliably on every execution.
100
- * Preserve local variable tables in memory for snapshot capture.
148
+ On JRuby, probe-handler time budget breaches and safety-thread lag do not emit
149
+ logs, change heap health, or suspend probes. Timing measurements remain internal;
150
+ the lag and pause-budget options have no effect on JRuby safety. Handler elapsed
151
+ time is not a JVM-wide pause or a complete measure of instrumentation overhead.
101
152
 
102
- > **Pre-Flight Protection**: If JRuby is started without `--debug`, HyperProbe detects this at startup, logs an informative error message, and safely aborts initialization without attaching hooks or degrading performance.
153
+ Both runtimes resume suspended probes only after the configured cooldown has
154
+ elapsed and health is GREEN. Hit/bandwidth quotas, capture limits, redaction,
155
+ bounded queues, and exception isolation remain enabled. Ephemeral Lambda mode
156
+ continues to bypass the background safety monitor.
103
157
 
104
- ### How to Run JRuby with HyperProbe
158
+ JRuby's memory-only enforcement does not bound capture latency, prevent every
159
+ out-of-memory failure, or undo the process-wide tracing profile. Benchmark
160
+ application overhead before production use.
105
161
 
106
- #### 1. In Docker / Kubernetes
107
- Add the environment variable in your `Dockerfile` or Deployment manifest:
108
- ```dockerfile
109
- ENV JRUBY_OPTS="--debug"
110
- ```
162
+ ## Expressions
163
+
164
+ By default, conditions, watches, log placeholders, metrics, and duration correlation expressions use a small, bounded interpreter rather than Ruby `eval` or `Binding#eval`. Safe mode validates the complete expression, including branches that would be short-circuited.
165
+
166
+ Supported syntax in safe mode:
167
+
168
+ - Local variable names; `nil`, `true`, `false`, numeric, ordinary quoted string, and simple symbol literals; array/hash literals and parentheses.
169
+ - Basic arithmetic `+ - * / %`, unary `+ -`, primitive comparisons `== != < <= > >=`, boolean `! not && || and or`, and the ternary `condition ? a : b`.
170
+ - String concatenation and ordered string comparisons on exact built-in strings. Numeric arithmetic requires exact built-in integers/floats; equality requires primitives, not arbitrary objects or containers.
171
+ - `.length` / `.size` (optionally with empty parentheses) on exact built-in `String`, `Array`, or `Hash` values only.
172
+ - A single integer subscript on exact built-in arrays/strings; primitive-key subscripts on small exact built-in hashes. Hash lookup scans without calling default procs or application `hash`/`eql?` hooks; identity hashes and unsupported keys are rejected.
173
+
174
+ Examples: `total > 100 && items.length > 0`, `items[0]`, `payload[:status]`, `count + 1`. Logs accept `${count}` and `#{count}` placeholders using this same grammar, not Ruby string interpolation execution.
175
+
176
+ In safe mode, arbitrary method calls and getters such as `user.name`, `record.save`, `object.to_s`, and `java_object.getName()` are forbidden. So are constants, `self`, instance/global/class variable access, assignments, blocks, loops, backticks, regex literals, ranges, exponentiation, interpolated string literals, and general function calls. An object's presence in a watch does not authorize calling methods on it: snapshot serialization handles it separately under the rules below.
177
+
178
+ Expressions are limited to 256 source bytes, 128 compiler nodes, nesting depth 32, and 512 interpreter steps. Evaluated strings are limited to 4096 bytes and integers to 1024 bits. Hash lookups and log-rendered collections are limited to 128 items; MRI also rejects hash lookups whose storage exceeds 131072 bytes. Log templates/output have a 16384-byte bound and at most 32 placeholders, with rendered values limited to 4096 bytes. Unsupported or over-budget work becomes an evaluation/capture error or is skipped at the agent boundary; it does not gain access to general Ruby execution.
179
+
180
+ ### Unrestricted Evaluation (Opt-In)
181
+
182
+ On MRI and JRuby, enable method calls and ordinary Ruby execution with:
111
183
 
112
- #### 2. Command Line / Local
113
- Launch JRuby with the `--debug` flag:
114
184
  ```bash
115
- jruby --debug app.rb
116
- # or:
117
- JRUBY_OPTS="--debug" jruby app.rb
185
+ export HYPERPROBE_DISABLE_SAFE_EVALUATION=true
118
186
  ```
119
187
 
120
- ### JRuby Architecture Highlights
121
- * **Zero C-Extensions**: On JRuby, HyperProbe automatically uses Java's built-in `java.net.http.HttpClient` (HTTP/2) for gRPC communication to the Logger. No native compilation or C-extensions are required.
122
- * **Hybrid Object Serialization**: Automatically detects and serializes both native Ruby objects and Java collections (`java.util.Map`, `java.util.List`, `java.util.Set`, and Java POJOs).
123
- * **Multi-Threaded Safety**: Thread-safe synchronization across parallel JVM worker threads.
188
+ Alternatively, add `disable_safe_evaluation: true` (or `disableSafeEvaluation: true`)
189
+ to your existing `HyperProbe.start` options. Only boolean `true` or a trimmed,
190
+ case-insensitive `"true"` string opts in. Explicit options take precedence over
191
+ the environment, including `false`; the agent resolves this setting at startup.
192
+ Individual probes and broker capture settings cannot enable it.
193
+
194
+ This bypasses AST validation and the restricted interpreter for conditions,
195
+ watches, log placeholders, metrics, and duration correlations. Expressions run
196
+ through `Binding#eval` in the probe-hit frame. A warning is emitted once at startup
197
+ when safe evaluation is disabled.
198
+
199
+ **Only enable this for trusted probes.** Expressions can modify application
200
+ state, perform I/O, block, or terminate the process. There is no sandbox or
201
+ execution timeout. Source/template size limits, output formatting and metric
202
+ type checks, redaction, capture limits, and quotas remain, but they do not bound
203
+ or undo the work performed by arbitrary Ruby code. Log placeholders still use
204
+ the existing `${...}` / `#{...}` template parser and bounded value formatter.
205
+
206
+ ## Snapshot Scope
207
+
208
+ On MRI, snapshots capture locals from the probe-hit frame and available Ruby caller frames, including blocks, up to `stack_frame_depth` (default 3, maximum 16). The MRI gem includes `debug_inspector` to obtain frame locations and bindings from the same native stack snapshot. Caller frames without an available binding retain empty local scopes. JRuby captures locals only from the probe-hit frame; its caller scopes remain empty.
209
+
210
+ MRI skips caller inspection when the visible stack exceeds 128 frames. Caller local-table extraction also rejects large compiled code trees before expanding them (32 instruction sequences and 16 KiB of aggregate native ISeq storage). Rejected callers retain empty scopes, including when closure capture is enabled. These are conservative admission checks, not hard native allocation or latency guarantees. Metadata caches use weak keys so reloaded code is not retained.
211
+
212
+ Closure variables are excluded by default. The probe-hit frame uses installation-time lexical analysis of the source file. The source must match the loaded code. Missing/unparseable/oversized source, ambiguous lines, or unsupported parser output make scope ownership unknown; in that case locals are omitted by default instead of capturing everything visible in the binding. Analysis is bounded to 512 KiB source, 20000 AST nodes/tokens, and depth 128. MRI caller frames use their compiled local-variable tables instead, avoiding source reads during capture. Only local-name metadata is cached, never live bindings.
213
+
214
+ With explicit `capture_closures: true`, visible binding variables not proven local are included in a separately marked `closure` scope. This includes unknown-scope variables; it is an intentional privacy opt-in, not a claim of perfect ownership inference. At most 128 visible binding names are considered. Watches evaluate against the hit binding independently of automatic closure exclusion, so an explicit watch can name a closure variable even when automatic closure capture is off.
215
+
216
+ ## Serialization And Redaction
217
+
218
+ Serialization binds captured core implementations directly rather than dispatching application overrides. Ruby object instance variables can be captured using trusted core ivar enumeration and reads, without invoking getters, `inspect`, or custom `to_s`/`to_json`. Exceptions and unsupported built-in subclasses remain opaque summaries. Collection support includes exact built-in Ruby arrays, hashes, and sets, plus supported structs and `OpenStruct` storage.
219
+
220
+ On JRuby, only exact Java `ArrayList`, `LinkedList`, `LinkedHashMap`, and `LinkedHashSet` classes are traversed. Arbitrary Java objects, POJOs, exceptions, collection subclasses, `HashMap`, and `HashSet` are opaque. No POJO getters, Throwable accessors, `equals`, or `toString` are invoked for capture.
221
+
222
+ Snapshot limits are clamped to depth 16, 128 sequence items/properties, and 4096 string bytes. A shared capture budget across watches and captured scopes allows 512 nodes and 65536 bytes of conservative accounting, reserving 4096 bytes for exhaustion markers. Strings include allowance for JSON escaping in that accounting. Integers over 256 bits are summarized; cycles/repeated references produce reference markers. Limits produce summaries, truncation markers, or `[Capture budget exhausted]`, not an unlimited traversal.
223
+
224
+ The byte budget is not a promise that an entire serialized telemetry event is at most 64 KiB. Final JSON normalization has separate bounded headroom for wrappers/markers (depth 32, 1024 items/properties, 8192 string bytes, 2048 nodes, and 131072 accounting bytes); event metadata and generated markers also add overhead. Native Ruby ivar-name enumeration and binding-local enumeration materialize full name lists before bounded field reads. These operations are not hard wall-clock or allocation bounds. MRI hashes with storage over 131072 bytes are summarized to avoid sparse scans.
124
225
 
125
- ---
226
+ ### Restricted Redaction Patterns
126
227
 
127
- ## Puma / Unicorn Multi-Worker Integration
228
+ Key and value matching is case-insensitive and uses ordinary Ruby regex search semantics, not exact whole-string matching unless anchored. Patterns are deliberately restricted to avoid pathological regex execution:
229
+
230
+ - Each key/value pattern list permits at most 16 patterns, each at most 256 bytes before whitespace trimming.
231
+ - Unescaped literal characters are ASCII letters/digits, space, underscore, colon, `@`, comma, slash, and hyphen.
232
+ - Literal punctuation `. ^ $ | ? * + ( ) [ ] { } \ -` can be backslash-escaped.
233
+ - Nonempty literal alternatives can be separated by `|`. One optional `^` at the start and one optional `$` at the end are allowed. These retain Ruby line-anchor and alternation precedence semantics; they are not automatically grouped or converted to whole-string anchors.
234
+ - No repetition, groups, lookaround, character classes, backreferences, or escapes such as `\d`, `\s`, `\A`, and `\z` are allowed. An unescaped dot is not allowed.
235
+
236
+ Examples of accepted patterns: `password`, `token|secret`, `^authorization$`, `api\.key`. `.*secret.*`, `(token)+`, and `[0-9]+` are rejected. Environment variables split on commas; use an option array to include a literal comma in a pattern. Option lists replace defaults rather than extending them.
237
+
238
+ Invalid syntax, an oversized list/pattern, or invalid serializer pattern input fails closed: that entire key or value matcher becomes match-all, rather than dropping just the invalid pattern. Empty lists disable that matcher; whitespace-only patterns are ignored, disabling the matcher if none remain. Key matches replace the associated value with `[REDACTED Key]`; value matches replace matching string portions with `[REDACTED Value]`. Truncated or opaque keys are treated as sensitive when key redaction is enabled. With value redaction enabled, a truncated snapshot string is redacted rather than exposing an unchecked prefix. Dynamic logs apply value redaction to the evaluated text before display truncation and optional stdout emission; key redaction alone does not redact log text.
239
+
240
+ Redaction is not secret discovery: configure value patterns for sensitive string content and review what probes capture. Numeric values are not converted into strings for value-pattern matching.
241
+
242
+ Custom `set_trace_id` callbacks and installed APM integrations are trusted application integrations, not sandboxed probe expressions. They must be nonblocking and side-effect-free. The SDK cannot safely preempt arbitrary application/native code or guarantee recovery from process-wide resource exhaustion.
243
+
244
+ ## Puma / Unicorn Forking
245
+
246
+ For MRI preloaded multi-worker servers, defer startup in the parent **before any active agent/client has been created**:
247
+
248
+ ```ruby
249
+ # Preloaded application initializer
250
+ require 'hyperprobe'
251
+
252
+ HyperProbe.start(
253
+ service_id: ENV['HYPERPROBE_SERVICE_ID'],
254
+ environment: ENV['HYPERPROBE_ENVIRONMENT'],
255
+ broker_url: ENV['HYPERPROBE_BROKER_URL'],
256
+ commit_sha: ENV['GIT_COMMIT'],
257
+ defer_start: true
258
+ )
259
+ ```
128
260
 
129
- When using multi-worker servers like Puma or Unicorn in cluster mode on CRuby:
261
+ Deferral validates and stores configuration, returns `nil`, and creates no agent, broker client, or background loops in the parent. Start a fresh child-owned agent in each worker:
130
262
 
131
263
  ```ruby
132
264
  # config/puma.rb
265
+ preload_app!
133
266
  on_worker_boot do
134
267
  HyperProbe.after_fork
135
268
  end
136
269
  ```
137
270
 
138
- *(Note: On JRuby, Puma runs in multi-threaded mode on the JVM without process forking, so `after_fork` is not required).*
271
+ For Unicorn, use its worker hook instead:
272
+
273
+ ```ruby
274
+ # config/unicorn.rb
275
+ preload_app true
276
+ after_fork do |_server, _worker|
277
+ HyperProbe.after_fork
278
+ end
279
+ ```
280
+
281
+ If an active native gRPC client was inherited, `after_fork` disables the inherited agent in the child without calling, closing, or replacing that client. That child cannot restart HyperProbe, even via another `start` call. Restart the worker deployment with deferred initialization; adding `after_fork` alone to an already active preloaded agent is not a recovery mechanism. The parent is unaffected. JRuby uses threaded workers rather than this MRI fork path and does not need `after_fork`.
139
282
 
140
- ---
283
+ ## AWS Lambda
141
284
 
142
- ## AWS Lambda Integration
285
+ For a file named `app.rb`, configure the Lambda handler as `app.lambda_handler`:
143
286
 
144
287
  ```ruby
145
288
  require 'hyperprobe'
146
289
 
147
- handler = ->(event:, context:) {
148
- # Handler logic
149
- { statusCode: 200, body: "OK" }
150
- }
290
+ HYPERPROBE_HANDLER = HyperProbe.wrap_lambda(
291
+ service_id: ENV['HYPERPROBE_SERVICE_ID'],
292
+ environment: ENV['HYPERPROBE_ENVIRONMENT'],
293
+ broker_url: ENV['HYPERPROBE_BROKER_URL'],
294
+ commit_sha: ENV['GIT_COMMIT']
295
+ ) do |event:, context:|
296
+ { statusCode: 200, body: 'OK' }
297
+ end
298
+
299
+ def lambda_handler(event:, context:)
300
+ HYPERPROBE_HANDLER.call(event: event, context: context)
301
+ end
302
+ ```
303
+
304
+ Ephemeral Lambda mode syncs before the handler and attempts a flush afterward, subject to remaining execution time. Options `lambda_sync_timeout_ms` (default `2000`) and `flush_timeout_ms` (default `1500`) bound these RPCs. Instrumentation failure must not replace the handler's result or mask its exception; application exceptions still propagate normally. Local emulators (`IS_OFFLINE=true` or `AWS_SAM_LOCAL=true`) use ordinary background mode.
305
+
306
+ ## Development And Release
151
307
 
152
- exports.handler = HyperProbe.wrap_lambda(
153
- service_id: '<service-uuid>',
154
- environment: 'production',
155
- broker_url: 'https://logger.app.hyperprobe.co'
156
- ) { |event:, context:| handler.call(event: event, context: context) }
308
+ From `agents/ruby-sdk`, with MRI 3.0+ and Bundler available:
309
+
310
+ ```bash
311
+ bundle install
312
+ bundle exec rake transport:build
313
+ bundle exec rspec
314
+ ```
315
+
316
+ The transport build requires Maven and JDK 11+ (`mvn`, `java`, and `javac`) and can download build dependencies. Build it before the full suite because packaging/JRuby interoperability tests need the JAR. To build only the MRI gem without Maven/Java, use `bundle exec rake build:ruby`; `bundle exec rake build` produces both platforms.
317
+
318
+ Prepare JRuby's own gem environment with `rspec` (no Google Ruby protobuf gem is
319
+ needed), then run:
320
+
321
+ ```bash
322
+ HYPERPROBE_MRI_RUBY="$(command -v ruby)" jruby --debug \
323
+ -J-Djruby.ir.passes=AddCallProtocolInstructions,AddMissingInitsPass \
324
+ -J-Djruby.ir.jit.passes=AddCallProtocolInstructions,AddMissingInitsPass -S rspec
325
+ ```
326
+
327
+ That MRI executable must have `grpc`, `google-protobuf`, and `rspec` installed for
328
+ the local interop server. Resolve development dependencies for the chosen runtime
329
+ rather than reusing a frozen MRI lockfile on JRuby; the legacy demo bundle for
330
+ Ruby < 3.2 uses Bundler 2.4.22. The release script supplies the
331
+ complete required JRuby profile and runs JRuby tests without `bundle exec`, so
332
+ RSpec must be discoverable by that interpreter, not only installed into an
333
+ MRI-specific Bundler path. Set `HYPERPROBE_JRUBY=/path/to/jruby` to select it.
334
+
335
+ Regenerate Ruby protobuf/gRPC stubs on MRI with the development bundle enabled:
336
+
337
+ ```bash
338
+ bundle exec rake proto:generate
339
+ ```
340
+
341
+ `grpc-tools` is a development/test Gemfile dependency only, not an installed SDK runtime dependency. Generated Ruby stubs and the Java adapter's schema descriptor are tracked; inspect their diff after generation. Maven output, bundled build JARs, gem artifacts, and Ruby LSP caches are ignored.
342
+
343
+ The SDK release validator builds both artifacts, runs MRI and JRuby tests, and validates package platform/dependency/JAR metadata:
344
+
345
+ ```bash
346
+ bash publish_rubygems.sh --dry-run
347
+ ```
348
+
349
+ This does not publish, but does build, may fetch Maven dependencies, and may update `Gemfile.lock`. `--version VERSION` (or the `VERSION` environment variable) also **writes `lib/hyperprobe/version.rb`**, including in a dry run; the standalone publisher does not restore these files. Publishing with `--yes` uploads both `pkg/hyperprobe-agent-VERSION.gem` and `pkg/hyperprobe-agent-VERSION-java.gem` at the same version. A failure on the second upload can leave the first artifact published; inspect both platforms before retrying. `--skip-tests` bypasses both runtime suites and is not a substitute for release validation.
350
+
351
+ The root `build.sh` requires a clean worktree, checks both Ruby runtimes plus Maven/JDK tools before changing versions, and installs the MRI bundle before invoking the SDK validator. Bundler install output is suppressed to avoid leaking authenticated source URLs. On successful root builds, it restores only Ruby's tracked `lib/hyperprobe/version.rb` and `Gemfile.lock` when the Ruby version stage was entered. Failed builds retain the existing rollback behavior and can leave temporary versions/lockfile changes for inspection; unrelated rollback logic is unchanged. Do not use root `build.sh` for a Ruby-only check: it also builds other SDKs, Docker, and the extension, and `SHOULD_PUBLISH=yes` performs remote writes.
352
+
353
+ Offline source/syntax checks that do not run the root build or publisher:
354
+
355
+ ```bash
356
+ bash -n ../../build.sh
357
+ bash -n publish_rubygems.sh
358
+ ruby -c Gemfile
359
+ ruby -c Rakefile
360
+ ruby -rrspec/core -e 'exit RSpec::Core::Runner.run(ARGV)' spec/release_integration_spec.rb
361
+ ```
362
+
363
+ The focused release checks require RSpec already installed, but not a built transport or a running demo/broker. Keep release credentials out of command arguments and logs; use the publisher's documented credential environment/file mechanisms.
364
+
365
+ ## Local Demo Verification
366
+
367
+ Keep the existing service ID and broker URL in `agents/ruby-demo-app/start.sh`. Start the backend from the repository root with `pnpm run dev`. From `agents/ruby-demo-app`, install and run one runtime at a time:
368
+
369
+ ```bash
370
+ bundle install
371
+ bash start.sh
372
+ ```
373
+
374
+ For JRuby, use the `--jruby` flag:
375
+
376
+ ```bash
377
+ bash start.sh --jruby
378
+ ```
379
+
380
+ To test a checkout event that triggers live probes, send a request:
381
+
382
+ ```bash
383
+ curl -X POST http://localhost:4003/checkout \
384
+ -H "Content-Type: application/json" \
385
+ -d '{"userId":"test-user","items":[{"price":10,"quantity":1},{"price":200,"quantity":1}]}'
157
386
  ```