julewire-core 1.0.0

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.
Files changed (164) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +6 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +73 -0
  5. data/docs/advanced-configuration.md +66 -0
  6. data/docs/attribute-keys.md +74 -0
  7. data/docs/configuration.md +327 -0
  8. data/docs/context-and-propagation.md +353 -0
  9. data/docs/contracts.md +211 -0
  10. data/docs/development.md +49 -0
  11. data/docs/extensions-and-api.md +567 -0
  12. data/docs/health-schema.md +104 -0
  13. data/docs/instrumentation-cheatsheet.md +29 -0
  14. data/docs/internals.md +135 -0
  15. data/docs/outputs-and-lifecycle.md +206 -0
  16. data/docs/quickstart.md +133 -0
  17. data/docs/record-sources.md +17 -0
  18. data/docs/records-and-data-policy.md +230 -0
  19. data/docs/security-and-wire.md +45 -0
  20. data/docs/tail.md +91 -0
  21. data/exe/julewire +6 -0
  22. data/julewire-core.gemspec +41 -0
  23. data/lib/julewire/core/cli/doctor.rb +143 -0
  24. data/lib/julewire/core/cli/line_helpers.rb +77 -0
  25. data/lib/julewire/core/cli/log_formats/console_text.rb +25 -0
  26. data/lib/julewire/core/cli/log_formats/core_json_decoder.rb +46 -0
  27. data/lib/julewire/core/cli/log_formats/core_json_encoder.rb +21 -0
  28. data/lib/julewire/core/cli/log_formats/record_decoder.rb +39 -0
  29. data/lib/julewire/core/cli/log_formats.rb +123 -0
  30. data/lib/julewire/core/cli/tail.rb +153 -0
  31. data/lib/julewire/core/cli/transcode.rb +105 -0
  32. data/lib/julewire/core/cli.rb +73 -0
  33. data/lib/julewire/core/configuration.rb +99 -0
  34. data/lib/julewire/core/context_store.rb +384 -0
  35. data/lib/julewire/core/destinations/chaos_output.rb +91 -0
  36. data/lib/julewire/core/destinations/collection.rb +177 -0
  37. data/lib/julewire/core/destinations/definition.rb +125 -0
  38. data/lib/julewire/core/destinations/destination.rb +268 -0
  39. data/lib/julewire/core/destinations/registry.rb +81 -0
  40. data/lib/julewire/core/destinations/sink.rb +35 -0
  41. data/lib/julewire/core/destinations/synchronized_output.rb +57 -0
  42. data/lib/julewire/core/destinations/tail_sampling.rb +321 -0
  43. data/lib/julewire/core/destinations/write_step.rb +119 -0
  44. data/lib/julewire/core/destinations.rb +33 -0
  45. data/lib/julewire/core/diagnostics/callback_notifier.rb +63 -0
  46. data/lib/julewire/core/diagnostics/doctor.rb +114 -0
  47. data/lib/julewire/core/diagnostics/failure_snapshot.rb +39 -0
  48. data/lib/julewire/core/diagnostics/health.rb +144 -0
  49. data/lib/julewire/core/diagnostics/integration_health_store.rb +64 -0
  50. data/lib/julewire/core/diagnostics/internal_records.rb +61 -0
  51. data/lib/julewire/core/diagnostics/invalid_severity_reporter.rb +112 -0
  52. data/lib/julewire/core/diagnostics/meta_observer.rb +161 -0
  53. data/lib/julewire/core/diagnostics/process_integration_health.rb +26 -0
  54. data/lib/julewire/core/diagnostics/tail/renderer.rb +36 -0
  55. data/lib/julewire/core/diagnostics/tail.rb +168 -0
  56. data/lib/julewire/core/diagnostics.rb +8 -0
  57. data/lib/julewire/core/error.rb +7 -0
  58. data/lib/julewire/core/execution/boundary.rb +106 -0
  59. data/lib/julewire/core/execution/handle.rb +77 -0
  60. data/lib/julewire/core/execution/lineage.rb +192 -0
  61. data/lib/julewire/core/execution/measurement_handle.rb +28 -0
  62. data/lib/julewire/core/execution/no_current_error.rb +9 -0
  63. data/lib/julewire/core/execution/scope.rb +246 -0
  64. data/lib/julewire/core/execution/scope_fields.rb +76 -0
  65. data/lib/julewire/core/execution/scope_identity.rb +71 -0
  66. data/lib/julewire/core/execution/scope_snapshot.rb +92 -0
  67. data/lib/julewire/core/execution/summary_state.rb +206 -0
  68. data/lib/julewire/core/execution/view.rb +56 -0
  69. data/lib/julewire/core/facade_methods.rb +181 -0
  70. data/lib/julewire/core/fields/attribute_keys.rb +54 -0
  71. data/lib/julewire/core/fields/attributes_proxy.rb +11 -0
  72. data/lib/julewire/core/fields/bags.rb +123 -0
  73. data/lib/julewire/core/fields/carry_proxy.rb +22 -0
  74. data/lib/julewire/core/fields/context_proxy.rb +11 -0
  75. data/lib/julewire/core/fields/field_set.rb +78 -0
  76. data/lib/julewire/core/fields/field_stack.rb +269 -0
  77. data/lib/julewire/core/fields/internal/deletion.rb +68 -0
  78. data/lib/julewire/core/fields/internal.rb +87 -0
  79. data/lib/julewire/core/fields/lookup.rb +35 -0
  80. data/lib/julewire/core/fields/section_proxy.rb +88 -0
  81. data/lib/julewire/core/fields/stack_set.rb +69 -0
  82. data/lib/julewire/core/fields/static_labels.rb +43 -0
  83. data/lib/julewire/core/fields/summary_proxy.rb +62 -0
  84. data/lib/julewire/core/integration/configurable.rb +52 -0
  85. data/lib/julewire/core/integration/destination_health.rb +43 -0
  86. data/lib/julewire/core/integration/event_subscriber.rb +62 -0
  87. data/lib/julewire/core/integration/facade.rb +131 -0
  88. data/lib/julewire/core/integration/fork_hooks.rb +79 -0
  89. data/lib/julewire/core/integration/health.rb +41 -0
  90. data/lib/julewire/core/integration/ivar_state.rb +38 -0
  91. data/lib/julewire/core/integration/lifecycle.rb +22 -0
  92. data/lib/julewire/core/integration/scoped.rb +34 -0
  93. data/lib/julewire/core/integration/settings.rb +92 -0
  94. data/lib/julewire/core/integration/subscriber_install.rb +39 -0
  95. data/lib/julewire/core/integration/subscription.rb +29 -0
  96. data/lib/julewire/core/integration/values.rb +192 -0
  97. data/lib/julewire/core/lifecycle_error.rb +7 -0
  98. data/lib/julewire/core/local_storage.rb +91 -0
  99. data/lib/julewire/core/processing/level_threshold.rb +53 -0
  100. data/lib/julewire/core/processing/match.rb +74 -0
  101. data/lib/julewire/core/processing/pipeline.rb +360 -0
  102. data/lib/julewire/core/processing/processor_chain.rb +69 -0
  103. data/lib/julewire/core/processing/processor_registry.rb +115 -0
  104. data/lib/julewire/core/processing/processor_wrapper.rb +44 -0
  105. data/lib/julewire/core/processing/record_field_transform.rb +124 -0
  106. data/lib/julewire/core/processing/sampling.rb +109 -0
  107. data/lib/julewire/core/processing.rb +41 -0
  108. data/lib/julewire/core/propagation/carrier.rb +93 -0
  109. data/lib/julewire/core/propagation.rb +50 -0
  110. data/lib/julewire/core/records/console_formatter.rb +24 -0
  111. data/lib/julewire/core/records/deconstruct.rb +19 -0
  112. data/lib/julewire/core/records/display_message.rb +166 -0
  113. data/lib/julewire/core/records/draft.rb +576 -0
  114. data/lib/julewire/core/records/formatter.rb +14 -0
  115. data/lib/julewire/core/records/lazy_emit_input.rb +99 -0
  116. data/lib/julewire/core/records/metadata.rb +23 -0
  117. data/lib/julewire/core/records/public_projection.rb +51 -0
  118. data/lib/julewire/core/records/raw_input.rb +41 -0
  119. data/lib/julewire/core/records/record.rb +175 -0
  120. data/lib/julewire/core/records/severity.rb +44 -0
  121. data/lib/julewire/core/runtime.rb +515 -0
  122. data/lib/julewire/core/runtime_locator.rb +20 -0
  123. data/lib/julewire/core/runtime_registry.rb +48 -0
  124. data/lib/julewire/core/runtime_state.rb +39 -0
  125. data/lib/julewire/core/scheduling/deadline.rb +24 -0
  126. data/lib/julewire/core/scheduling/deadline_scheduler.rb +207 -0
  127. data/lib/julewire/core/scheduling/shared_scheduler.rb +48 -0
  128. data/lib/julewire/core/sentinel.rb +18 -0
  129. data/lib/julewire/core/serialization/backtrace_limiter.rb +50 -0
  130. data/lib/julewire/core/serialization/bounded_transform.rb +55 -0
  131. data/lib/julewire/core/serialization/bounded_traversal.rb +274 -0
  132. data/lib/julewire/core/serialization/deep_compact_empty.rb +67 -0
  133. data/lib/julewire/core/serialization/deep_freeze.rb +63 -0
  134. data/lib/julewire/core/serialization/encoding_sanitizer.rb +40 -0
  135. data/lib/julewire/core/serialization/exception_shape.rb +88 -0
  136. data/lib/julewire/core/serialization/json_encoder.rb +69 -0
  137. data/lib/julewire/core/serialization/serializer.rb +233 -0
  138. data/lib/julewire/core/serialization/serializer_pool.rb +21 -0
  139. data/lib/julewire/core/serialization/text_encoder.rb +147 -0
  140. data/lib/julewire/core/serialization/value_copy.rb +209 -0
  141. data/lib/julewire/core/serialization/value_traversal.rb +150 -0
  142. data/lib/julewire/core/testing/chaos/catalog.rb +72 -0
  143. data/lib/julewire/core/testing/chaos/core_runtime.rb +120 -0
  144. data/lib/julewire/core/testing/chaos/destination.rb +55 -0
  145. data/lib/julewire/core/testing/chaos/emitter.rb +20 -0
  146. data/lib/julewire/core/testing/chaos/raising_output.rb +42 -0
  147. data/lib/julewire/core/testing/chaos.rb +80 -0
  148. data/lib/julewire/core/testing/contracts/component.rb +162 -0
  149. data/lib/julewire/core/testing/contracts/deadline_scheduler.rb +59 -0
  150. data/lib/julewire/core/testing/contracts/integration.rb +166 -0
  151. data/lib/julewire/core/testing/contracts/integration_fields.rb +36 -0
  152. data/lib/julewire/core/testing/contracts/record_draft.rb +37 -0
  153. data/lib/julewire/core/testing/contracts/runtime.rb +178 -0
  154. data/lib/julewire/core/testing/contracts/wire.rb +60 -0
  155. data/lib/julewire/core/testing/contracts.rb +24 -0
  156. data/lib/julewire/core/testing/coverage.rb +58 -0
  157. data/lib/julewire/core/testing/test_reports.rb +78 -0
  158. data/lib/julewire/core/testing.rb +122 -0
  159. data/lib/julewire/core/validation.rb +69 -0
  160. data/lib/julewire/core/version.rb +7 -0
  161. data/lib/julewire/core.rb +80 -0
  162. data/lib/julewire/error.rb +5 -0
  163. data/lib/julewire-core.rb +3 -0
  164. metadata +237 -0
@@ -0,0 +1,353 @@
1
+ # Context and Propagation
2
+
3
+ Context is ambient application data for the current execution/fiber. Carry is
4
+ small propagated integration/correlation data. Attributes are emitted
5
+ record-local data for integrations and formatters, but are not propagated.
6
+ Summary is final-only data for the current execution.
7
+
8
+ ```ruby
9
+ Julewire.context.add(request_id: "req-1")
10
+ Julewire.carry.add(http: { request_headers: { traceparent: "..." } })
11
+ Julewire.attributes.add(my_app: { shard: "a" })
12
+
13
+ Julewire.with_execution(type: :job, id: "job-1", labels: { worker: "billing" }) do
14
+ Julewire.context.add(worker: "background")
15
+ Julewire.attributes.add(runtime: { queue: "default" })
16
+ Julewire.summary.increment(:records_seen)
17
+ Julewire.measure(:upstream) { call_upstream }
18
+ Julewire.summary.append(:warnings, "slow upstream")
19
+
20
+ Julewire.emit(message: "processed")
21
+ end
22
+ ```
23
+
24
+ Context appears on point logs and summaries. Carry is available to processors,
25
+ formatters, and propagation envelopes, but the default formatter does not write
26
+ it to JSON output. Integration-specific attribute namespaces appear on emitted
27
+ records and summaries. Neutral fields coordinate formatters through the record's
28
+ `neutral` section and are stripped by the default formatter. Use
29
+ `Julewire.summary.add_attributes` to add attributes to a final summary. Summary
30
+ payload data appears only on the final summary. Attributes and summary data are
31
+ never propagated.
32
+
33
+ Nested executions inherit current attributes and neutral fields by default so
34
+ in-request child operations keep formatter coordination fields. Integrations
35
+ that restore a remote unit of work, such as a background job, can start the
36
+ execution with `inherit_attributes: false` and provide only that unit's fields.
37
+ Execution-level `labels:` apply to point records and the final summary for that
38
+ execution. Per-record labels can still override them.
39
+
40
+ ## Merge Semantics
41
+
42
+ Nested `attributes` are deep-merged. This lets integrations add their own
43
+ namespaces without replacing each other.
44
+
45
+ `context`, `carry`, and `labels` merge at the top level on a record.
46
+ When context or carry overlays are stacked with `with`, an overlapping top-level
47
+ key replaces that whole value for lookups, snapshots, and emitted records.
48
+
49
+ Summary helpers have their own merge rules: `summary.add_attributes` deep-merges
50
+ attributes, while `summary.add` stores summary payload fields by key.
51
+
52
+ If you accept the block argument from `with_execution`, it is a read-only
53
+ execution view. Scalar execution fields are captured when the view is created;
54
+ field sections such as context, carry, and summary are materialized lazily on
55
+ first read. Mutating the live execution goes through `Julewire.context`,
56
+ `Julewire.carry`, `Julewire.attributes`, and `Julewire.summary`.
57
+
58
+ Deferred execution handles are not thread-safe mutation surfaces. If an
59
+ integration shares a handle across threads, it must serialize calls that add or
60
+ overlay context, carry, attributes, labels, or summary fields.
61
+
62
+ Ambient context is fiber-local. Applications that reuse fibers outside a
63
+ Julewire execution scope should overwrite or clear context before unrelated work
64
+ uses the same fiber.
65
+
66
+ ## Context Helpers
67
+
68
+ ```ruby
69
+ Julewire.context.add(user_id: "user-1")
70
+
71
+ Julewire.context.with(order_id: "order-1") do
72
+ Julewire.emit(message: "inside order")
73
+ end
74
+
75
+ Julewire.context[:user_id]
76
+ ```
77
+
78
+ Public helpers accept string or symbol keys and normalize strings to symbols
79
+ when data enters core. After that boundary, use symbol keys.
80
+
81
+ Runtime helpers are best effort. Non-hash positional values are captured under
82
+ `:value` instead of raising from application code.
83
+
84
+ ## Carry Helpers
85
+
86
+ ```ruby
87
+ Julewire.carry.add(http: { request_headers: { traceparent: "..." } })
88
+
89
+ Julewire.carry.with(worker: { queue: "critical" }) do
90
+ Julewire.emit(message: "inside worker")
91
+ end
92
+
93
+ Julewire.carry.delete(:http, :request_headers, :authorization)
94
+
95
+ Julewire.carry.without(:http, :request_headers, :traceparent) do
96
+ Julewire.emit(message: "without trace carry")
97
+ end
98
+
99
+ Julewire.carry[:http]
100
+ ```
101
+
102
+ Carry is for small facts that integrations and formatters need on every record
103
+ and across propagation boundaries. It is not application log content, a privacy
104
+ boundary, or summary storage. Avoid large blobs unless a processor policy will
105
+ handle them before formatting.
106
+
107
+ `carry.delete` is persistent until the same path is added again with
108
+ `carry.add`. That delete masks scoped `carry.with` overlays too. Use
109
+ `carry.without` for a temporary block-only mask.
110
+
111
+ ## Summary Helpers
112
+
113
+ ```ruby
114
+ Julewire.summary.add(plan: "pro")
115
+ Julewire.summary.increment(:upstream_calls)
116
+ Julewire.measure(:upstream) { call_upstream }
117
+ Julewire.summary.increment_attribute(:active_job, :continuation_steps_completed)
118
+ Julewire.summary.append(:warnings, "slow upstream")
119
+ ```
120
+
121
+ `Julewire.summary.active?` reports whether the current fiber has a live
122
+ execution scope. `add`, `increment`, and `append` are strict: they raise
123
+ `Julewire::Core::Execution::NoCurrentError` outside `with_execution` instead of
124
+ silently dropping summary data.
125
+
126
+ `append` is non-raising for normal summary values:
127
+
128
+ - missing key becomes `[value]`
129
+ - array gets appended
130
+ - scalar becomes `[existing, value]`
131
+
132
+ `increment` adds numeric payload values normally. `increment_attribute`
133
+ increments nested summary attributes by path. If a key already holds
134
+ non-numeric summary data, core preserves the existing value and appends the
135
+ increment using the same array conversion rule.
136
+
137
+ `Julewire.measure(:upstream) { ... }` returns the block value, increments
138
+ `payload.upstream_count`, and accumulates elapsed milliseconds in
139
+ `metrics.upstream_duration_ms`. It records the timing even when the measured
140
+ block raises.
141
+
142
+ Use `Julewire.measure_start(:upstream)` when the measured work cannot fit in a
143
+ block. It returns an idempotent handle; call `finish` once the work is done.
144
+
145
+ Logging should not crash the app, but core also should not silently throw away
146
+ surprising data.
147
+
148
+ Summary records default to `source: "julewire"` and
149
+ `event: "#{type}.completed"`. Caller code can override that shape:
150
+
151
+ ```ruby
152
+ Julewire.with_execution(
153
+ type: :operation,
154
+ summary_source: "app",
155
+ summary_event: "operation.completed"
156
+ ) do
157
+ Julewire.summary.add(status: 200)
158
+ end
159
+ ```
160
+
161
+ Error summaries emit with `severity: :error` unless the error path supplies an
162
+ explicit severity. Successful summaries use `summary_severity:` when set,
163
+ otherwise they use normal severity defaults.
164
+
165
+ ## Current Execution
166
+
167
+ ```ruby
168
+ execution = Julewire.current_execution
169
+ execution.context_hash
170
+ execution.carry_hash
171
+ execution.summary_hash
172
+ ```
173
+
174
+ `current_execution?` is the cheap predicate. `current_execution` returns a
175
+ read-only execution view with lazily materialized field sections. Mutating the
176
+ live execution goes through `Julewire.context`, `Julewire.carry`,
177
+ `Julewire.attributes`, and `Julewire.summary`.
178
+
179
+ ## Deferred Execution Handles
180
+
181
+ Integrations that start work in one place and finish it later can use an
182
+ explicit handle:
183
+
184
+ ```ruby
185
+ handle = Julewire.start_execution(type: :request, id: "req-1")
186
+
187
+ status, headers, body = handle.run do
188
+ app.call(env)
189
+ end
190
+
191
+ body = ContextRestoringBody.new(body, handle) do
192
+ handle.finish(reason: :closed, fields: { status: status })
193
+ end
194
+ ```
195
+
196
+ `finish` is idempotent. The first finish emits the summary; later close hooks
197
+ or timeout callbacks are no-ops. `run` restores the handle context and finishes
198
+ on exceptions, but successful deferred executions must call `finish` explicitly.
199
+ Finished summaries include a visible `julewire.completion` attribute with the
200
+ first finish reason.
201
+ Timeout summaries should say they timed out:
202
+
203
+ ```ruby
204
+ handle.finish(reason: :timeout, fields: { completion_timeout_ms: 30_000 })
205
+ ```
206
+
207
+ Use `handle.with_context` around late body iteration, callbacks, or stream
208
+ cleanup when those operations should inherit the original execution context.
209
+ This is an integration primitive; ordinary application code should prefer
210
+ `Julewire.with_execution`.
211
+
212
+ ## Propagation Envelopes
213
+
214
+ ```ruby
215
+ envelope = Julewire::Core::Propagation.capture
216
+
217
+ Julewire::Core::Propagation.restore(envelope) do
218
+ Julewire.emit(message: "restored")
219
+ end
220
+ ```
221
+
222
+ Propagation restores context, carry, and execution metadata into direct point
223
+ records and into the next execution scope. It does not propagate summary data.
224
+ Execution metadata is restored as ordinary execution fields. Use
225
+ `Julewire.with_execution(..., fields: { trace_id: "..." })` for custom
226
+ execution fields at the public API boundary.
227
+
228
+ Propagation serializes values through the core serializer. That means values
229
+ cross a log-safe boundary, not an object-identity boundary.
230
+
231
+ Core does not redact propagation envelopes. Do not put secrets in context or
232
+ carry unless your app or processor policy handles them.
233
+
234
+ By default, a new local execution started after restore gets a new local
235
+ lineage. At trusted boundaries where the remote execution should be the parent,
236
+ use `link_executions: true`:
237
+
238
+ ```ruby
239
+ Julewire::Core::Propagation.restore(envelope, link_executions: true) do
240
+ Julewire.with_execution(type: :job, id: "job-1") { perform_job }
241
+ end
242
+ ```
243
+
244
+ Restoring an envelope is an explicit trust decision. Core validates shape, and
245
+ outbound injection can enforce a byte limit, but core does not authenticate who
246
+ wrote the carrier. At untrusted boundaries, extract only the carrier fields you
247
+ intentionally accept, apply any application spoofing/filter policy first, then
248
+ call `Carrier.restore` on that filtered carrier map.
249
+
250
+ ## Flat Carriers
251
+
252
+ Adapters that cross a serialized boundary can put the propagation envelope into
253
+ a flat string carrier:
254
+
255
+ ```ruby
256
+ headers = Julewire::Core::Propagation::Carrier.inject({})
257
+
258
+ Julewire::Core::Propagation::Carrier.restore(headers) do
259
+ Julewire.emit(message: "restored from headers")
260
+ end
261
+ ```
262
+
263
+ For inbound request headers, keep restoration explicit:
264
+
265
+ ```ruby
266
+ trusted = filter_trusted_headers(request.headers)
267
+ Julewire::Core::Propagation::Carrier.restore(trusted) do
268
+ handle_request
269
+ end
270
+ ```
271
+
272
+ Use `max_bytes:` when the carrier target has practical size constraints:
273
+
274
+ ```ruby
275
+ headers = Julewire::Core::Propagation::Carrier.inject({}, max_bytes: 8 * 1024)
276
+ # => nil when the serialized carrier is too large; the carrier key is removed
277
+ ```
278
+
279
+ The default carrier key is `"julewire"`. Carriers are intentionally
280
+ provider-neutral: core stores only the Julewire propagation envelope. External
281
+ header conventions stay outside core.
282
+
283
+ Carriers are transport metadata. They may become visible if an application logs
284
+ full carrier maps. Keep integrations responsible for deciding which external
285
+ metadata to mirror into carry, and keep normal processors in the destination
286
+ path for emitted records.
287
+
288
+ ## Thread and Fiber Wrappers
289
+
290
+ Raw threads and raw fibers do not magically inherit Julewire context. Use the
291
+ wrappers when you want propagation:
292
+
293
+ ```ruby
294
+ Julewire.context.add(request_id: "req-1")
295
+
296
+ thread = Julewire.thread do
297
+ Julewire.emit(message: "from thread")
298
+ end
299
+
300
+ thread.join
301
+ ```
302
+
303
+ ```ruby
304
+ fiber = Julewire.fiber do
305
+ Julewire.emit(message: "from fiber")
306
+ end
307
+
308
+ fiber.resume
309
+ ```
310
+
311
+ Each wrapper captures the current propagation envelope once and restores it
312
+ around the block.
313
+
314
+ Thread and fiber wrappers use a local snapshot, not the serialized propagation
315
+ envelope. Ruby values keep their local shape across those same-process
316
+ boundaries. `Propagation.capture` remains the serialized/log-safe envelope for
317
+ cross-runtime and external transport boundaries.
318
+
319
+ Restored execution metadata is available to direct point logs when no live
320
+ execution scope is already active. A live scope wins over the restored snapshot.
321
+ The snapshot is also inherited by the next `with_execution` opened inside the
322
+ wrapper. It is not a live execution scope by itself, so `Julewire.summary` is
323
+ unavailable unless the wrapped code opens a new `with_execution`.
324
+
325
+ ## Remote Runtime Hooks
326
+
327
+ Core keeps a small remote-envelope hook so bridge code can send normalized
328
+ input, context, and scope snapshots back to another runtime. The receiving
329
+ runtime's configuration, processors, level, destinations, labels, and outputs
330
+ remain authoritative.
331
+
332
+ Inside a remote runtime, `Julewire.reset!` is local to that runtime storage. It
333
+ does not reset another runtime's configuration, destinations, or health
334
+ counters.
335
+
336
+ ## Reset Semantics
337
+
338
+ `Julewire.reset!` clears:
339
+
340
+ - active configuration
341
+ - current fiber context store
342
+ - the old active pipeline/output lifecycle
343
+ - live runtime post-close drop and callback-failure state
344
+
345
+ `health[:counts]` is different: it is monotonic for the current runtime object
346
+ and survives `reset!`. Use it for process-lifetime runtime lifecycle scrapes.
347
+
348
+ Other long-lived threads/fibers keep their own context stores until they reset
349
+ or exit. Long-lived workers should reset or replace per-execution context on
350
+ each unit of work.
351
+
352
+ Main runtime context uses storage on the current Ruby fiber. Non-main ractors
353
+ use thread-local storage because bridge runtimes are ractor-local.
data/docs/contracts.md ADDED
@@ -0,0 +1,211 @@
1
+ # Contracts
2
+
3
+ Core has five contract levels. Keep changes honest by deciding which level a
4
+ behavior belongs to before preserving or breaking it.
5
+
6
+ ## Public application API
7
+
8
+ These APIs are for application code and scripts:
9
+
10
+ - Runtime configuration and lifecycle: `Julewire.configure`,
11
+ `Julewire.config`, `Julewire.reset!`, `Julewire.flush`, `Julewire.close`,
12
+ `Julewire.after_fork!`, and `Julewire.health`.
13
+ - `Julewire.runtime` for explicit secondary pipelines with independent
14
+ configuration, destinations, processors, health, and lifecycle.
15
+ - `Julewire.labels` for process labels shared by the active runtime.
16
+ - `Julewire.emit` and severity helpers: `Julewire.debug`, `Julewire.info`,
17
+ `Julewire.warn`, `Julewire.error`, `Julewire.fatal`, and
18
+ `Julewire.unknown`.
19
+ - Execution, context, attributes, carry, and summary facades:
20
+ `Julewire.with_execution`, `Julewire.context`, `Julewire.attributes`,
21
+ `Julewire.carry`, `Julewire.summary`, `Julewire.measure`,
22
+ `Julewire.measure_start`, `Julewire.start_execution`, and
23
+ `Julewire.current_execution` / `Julewire.current_execution?`.
24
+ - Local diagnostics and development helpers: `Julewire.doctor`,
25
+ `Julewire.tail`, `Julewire.dev!`, `Julewire.punk!`, and
26
+ `Julewire.observe_self!`.
27
+ - Context propagation wrappers: `Julewire.thread` and `Julewire.fiber`.
28
+ - Destination configuration through `config.destinations.use`.
29
+ - `Julewire::Core::Propagation::Carrier` and `Julewire::Core::Propagation` for explicit
30
+ cross-boundary propagation.
31
+
32
+ Application-facing logging calls contain Julewire `StandardError` failures.
33
+ They do not hide application exceptions.
34
+
35
+ ## Extension contract
36
+
37
+ These APIs are intentionally available to integration code, but they are not
38
+ general application surface:
39
+
40
+ - `Julewire::RecordDraft` as the processor mutation surface and
41
+ raw-input construction surface used by core and integrations before records
42
+ become immutable. Prefer `transform_field!`, `transform_section!`, and
43
+ `transform_record!` for replacement transforms.
44
+ - Immutable `Julewire::Record` instances for destinations and formatters.
45
+ - `Julewire::Record::REQUIRED_KEYS` and `HASH_SECTIONS` for processors that
46
+ need to preserve core record shape.
47
+ - `record.lineage` for explicit execution lineage access. Normalized record
48
+ data keeps only cheap execution identity; full ancestors are read through the
49
+ lineage accessor.
50
+ - `Julewire::Core::Records::PublicProjection`, the public record projection
51
+ used by the default formatter and serializer.
52
+ - `Julewire::Core::Records::PublicProjection::INTERNAL_EXECUTION_KEYS` for provider
53
+ formatters that expose a public execution payload.
54
+ - Encoder objects that respond to `call(payload)` and return strings for direct
55
+ destinations.
56
+ - The five-method destination duck type: `name`, `emit`, `flush`, `close`, and
57
+ `health`.
58
+ - `Julewire::Core::Destinations.normalize_name` for destination-name
59
+ normalization shared by destination adapters.
60
+ - `Julewire::TailSampling` as a destination wrapper for execution-level tail
61
+ sampling.
62
+ - `Julewire::Testing::CaptureDestination`, `Julewire::Testing::NullOutput`,
63
+ `Julewire::Testing.capture`, `Julewire::Testing.configure_capture_destination`,
64
+ `Julewire::Testing::Contracts`, `Julewire::Testing::Chaos`, and
65
+ `Julewire::Testing::Coverage` as extension test support.
66
+ - `RuntimeLocator.current.emit_without_level` for host-process integrations
67
+ that already apply their framework's level gate.
68
+
69
+ Extensions should consume these contracts rather than reaching into pipeline,
70
+ runtime, storage, or destination internals.
71
+
72
+ Testing support is shipped for integration authors, but helper names may still
73
+ change when the ecosystem cleanup demands it.
74
+
75
+ `Julewire::Testing::Contracts` currently ships these shared assertions for
76
+ extension and integration gems:
77
+
78
+ - `assert_julewire_bounded_transform_spi_contract`
79
+ - `assert_julewire_deadline_scheduler_spi_contract`
80
+ - `assert_julewire_destination_contract`
81
+ - `assert_julewire_execution_boundary_contract`
82
+ - `assert_julewire_failure_containment_contract`
83
+ - `assert_julewire_formatter_contract`
84
+ - `assert_julewire_integration_failure_contract`
85
+ - `assert_julewire_integration_health_contract`
86
+ - `assert_julewire_integration_ivar_state_contract`
87
+ - `assert_julewire_integration_payload_contract`
88
+ - `assert_julewire_integration_spi_contract`
89
+ - `assert_julewire_integration_timestamp_contract`
90
+ - `assert_julewire_integration_value_contract`
91
+ - `assert_julewire_processor_contract`
92
+ - `assert_julewire_propagation_contract`
93
+ - `assert_julewire_record_draft_transform_contract`
94
+ - `assert_julewire_record_shape_contract`
95
+ - `assert_julewire_record_source_contract`
96
+ - `assert_julewire_runtime_integration_contract`
97
+ - `assert_julewire_truncation_marker_spi_contract`
98
+ - `assert_julewire_validation_spi_contract`
99
+
100
+ `Julewire::Testing::Chaos` currently ships containment helpers for extension
101
+ test suites:
102
+
103
+ - `assert_contained`
104
+ - `assert_core_runtime_containment`
105
+ - `assert_destination_chaos_contract`
106
+ - `assert_discovered_chaos_contracts`
107
+ - `assert_emitter_chaos_contract`
108
+ - `catalog`
109
+ - `raiser`
110
+
111
+ ## Integration SPI
112
+
113
+ These APIs are for framework, provider, and transport integrations that need a
114
+ little more structure than application code. They are public to integrations,
115
+ but they are not intended as general application API:
116
+
117
+ - `Julewire::Core::Integration::Health` for contained process-level
118
+ integration health and scoped health wrappers.
119
+ - `Julewire::Core::Integration::Facade` for integration-owned emits,
120
+ execution boundaries, field overlays, and summary enrichment.
121
+ - `Julewire::Core::Integration::Values::Read` for hash/object value reads
122
+ including `value`, `hash_value`, `nested_value`, `path_value`,
123
+ `first_value`, and `blank?`.
124
+ - `Julewire::Core::Integration::Values::Shape` for normalized timestamps, payload
125
+ normalization, field appends, and source-location shaping.
126
+ - `Julewire::Core::Integration::Lifecycle` for optional require containment and
127
+ process-local `after_fork` hooks.
128
+ - `Julewire::Core::Integration` helper classes/modules for one-time ivar state,
129
+ subscriber install helpers, event-subscriber health wrappers, config settings
130
+ helpers, and subscription handles.
131
+ - `Julewire::Core::Integration::DestinationHealth` for destination-style
132
+ integrations that need Julewire-shaped counters, failure snapshots, and loss
133
+ snapshots without exposing core's internal health cells.
134
+ - `Julewire::Core::Destinations::WriteStep` for destination-style integrations
135
+ that reuse core's format, encode, bound, write, and counter sequence while
136
+ keeping their own lifecycle and failure policy.
137
+ - `Julewire::Core::CLI::LogFormats` for provider-owned CLI file-tail decoding
138
+ and transcoding formats.
139
+ - `Julewire::Core::Processing.register` for integration-owned processor kinds,
140
+ and `Julewire::Core::Processing::RecordFieldTransform` for processors that
141
+ need core's normalized record-section walk while keeping their own filtering
142
+ policy.
143
+ - `Julewire::Core::Fields::AttributeKeys` for provider-neutral formatter
144
+ coordination attributes.
145
+ - `Julewire::Core::Fields::Bags` for record field-bag capabilities such as
146
+ emitted output sections, transform containers, propagation, and delete-path
147
+ support.
148
+ - `Julewire::Core.sentinel(:name)` for readable, frozen identity markers when
149
+ an integration needs a private empty or missing-value sentinel.
150
+ - `Julewire::Core.deep_compact_empty` for core-compatible empty-field pruning.
151
+ - `Julewire::Core::Scheduling::DeadlineScheduler` for integration-local timeout callbacks
152
+ that need Julewire's fork-reset behavior.
153
+ - `Julewire::Core::Scheduling::SharedScheduler` for process-wide main-ractor
154
+ timeouts shared by integrations that should not own separate scheduler
155
+ threads.
156
+ - `Julewire::Core::Validation` for shared option and limit validation.
157
+ - `Julewire::Core::Serialization::BoundedTransform` for processors and integrations that need
158
+ core-compatible bounded traversal before core serializes the result.
159
+ - `Julewire::Core::Diagnostics::CallbackNotifier` and
160
+ `Julewire::Core::Diagnostics::FailureSnapshot` for destination-style
161
+ integrations that need core-compatible callback and health failure shape.
162
+ - `Julewire::Core::Records::DisplayMessage` and
163
+ `Julewire::Core::Records::Metadata` for formatters and destination-style
164
+ integrations that need the same display-message fallback or safe record
165
+ coordinates as core.
166
+ - `Julewire::Core::Records::Severity` for integrations that normalize
167
+ framework-native levels before handing records to core.
168
+ - `Julewire::Serializer.truncation_metadata` and serializer constants that
169
+ define core-compatible truncation markers when an integration must shape
170
+ bounded data before core serializes it.
171
+ Integrations may use this SPI when they need to match core behavior exactly.
172
+ New SPI use should be covered by either core contract tests or integration
173
+ tests that would fail on incompatible core changes.
174
+
175
+ ## Bridge SPI
176
+
177
+ These APIs are for runtime bridges that need to run Julewire facade calls in a
178
+ different Ruby isolation boundary:
179
+
180
+ - `Julewire::Core::RuntimeLocator.current=` to install the bridge runtime.
181
+ - `Julewire::Core::UNSET`, `Julewire::Core.emit_input`, and
182
+ `Julewire::Core::Records::LazyEmitInput` to mirror facade emit semantics
183
+ across an isolation boundary.
184
+ - `Julewire::Core::Execution::Boundary` for shared execution boundary behavior.
185
+ - `Julewire::Core::ContextStore.current`,
186
+ `Julewire::Core::Fields::ContextProxy`,
187
+ `Julewire::Core::Fields::AttributesProxy`,
188
+ `Julewire::Core::Fields::CarryProxy`, and
189
+ `Julewire::Core::Fields::SummaryProxy` for bridge-local field bags.
190
+ - `Julewire::Core::Execution::ScopeSnapshot` for detached execution scope transfer.
191
+ - Parent-runtime hooks: `emit_envelope`, `emit_summary_record`, and `flush`.
192
+ `emit_envelope` accepts detached input, context, carry, attributes, neutral,
193
+ scope snapshot, and an `enforce_level:` flag.
194
+
195
+ Bridge runtimes may expose `emit_without_level` when integration code inside
196
+ the bridge has already applied its own level gate. Parent runtime labels,
197
+ processors, destinations, and outputs remain parent-owned.
198
+
199
+ ## Internal implementation
200
+
201
+ These details are intentionally private and can move freely:
202
+
203
+ - `Runtime`, `Processing::Pipeline`, destination-set, and lifecycle state layout.
204
+ - Field stacks, local storage, and ractor lookup internals.
205
+ - Counter storage, callback-notifier plumbing, and health implementation
206
+ mechanics.
207
+ - Serializer traversal internals and value-copy helpers.
208
+
209
+ Tests may characterize internal behavior only when it protects a public or
210
+ extension contract, such as exception fidelity, cycle safety, fork safety, or
211
+ wire-shape stability.
@@ -0,0 +1,49 @@
1
+ # Development
2
+
3
+ Install dependencies:
4
+
5
+ ```sh
6
+ bundle install
7
+ ```
8
+
9
+ Run the default checks:
10
+
11
+ ```sh
12
+ bundle exec rake
13
+ ```
14
+
15
+ The default task runs:
16
+
17
+ - Minitest
18
+ - RuboCop
19
+ - Flay
20
+ - Debride
21
+ - Bundler Audit
22
+
23
+ ## Contracts
24
+
25
+ Before changing public APIs or extension seams, read `docs/contracts.md`.
26
+ Breaking changes are allowed while Julewire is pre-release, but every change
27
+ should be explicit about whether it is changing application API, extension
28
+ contract, or private implementation.
29
+
30
+ ## Coverage
31
+
32
+ Tests run with SimpleCov gates for line and branch coverage. Core keeps
33
+ remote-envelope contract coverage, not bridge implementation tests.
34
+
35
+ Race tests use queue handshakes where possible. Avoid sleep-based waits unless
36
+ the sleep is the thing under test.
37
+
38
+ ## Lockfile
39
+
40
+ `Gemfile.lock` is committed for reproducible local development and CI. Runtime
41
+ dependencies still belong in the gemspec.
42
+
43
+ ## Packaging Notes
44
+
45
+ The gemspec is the runtime dependency contract. `Gemfile` is for development
46
+ tooling.
47
+
48
+ The gem is a library. Executables are not part of the public package unless
49
+ they are intentionally moved under `exe/` and documented.