@ontrails/core 1.0.0-beta.2 → 1.0.0-beta.21

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 (245) hide show
  1. package/CHANGELOG.md +283 -5
  2. package/README.md +113 -33
  3. package/package.json +11 -1
  4. package/src/activation-provenance.ts +111 -0
  5. package/src/activation-source-compatibility.ts +430 -0
  6. package/src/activation-source-projection.ts +221 -0
  7. package/src/activation-source.ts +91 -0
  8. package/src/blob-ref.ts +51 -0
  9. package/src/branded.ts +1 -1
  10. package/src/compose-batch.ts +69 -0
  11. package/src/compose-schema.ts +36 -0
  12. package/src/context.ts +44 -10
  13. package/src/contour.ts +346 -0
  14. package/src/derive.ts +59 -18
  15. package/src/detours.ts +8 -0
  16. package/src/draft.ts +342 -0
  17. package/src/error-projection.ts +51 -0
  18. package/src/errors.ts +309 -48
  19. package/src/execute.ts +1577 -0
  20. package/src/fire.ts +1169 -0
  21. package/src/index.ts +477 -32
  22. package/src/internal/fork-ctx.ts +69 -0
  23. package/src/layer-projection.ts +193 -0
  24. package/src/layer.ts +43 -6
  25. package/src/observe.ts +361 -0
  26. package/src/path-security.ts +1 -1
  27. package/src/permits.ts +12 -0
  28. package/src/redaction/patterns.ts +6 -3
  29. package/src/resilience.ts +11 -11
  30. package/src/resource-config.ts +792 -0
  31. package/src/resource.ts +194 -0
  32. package/src/result.ts +36 -4
  33. package/src/run.ts +76 -0
  34. package/src/schedule-runtime.ts +689 -0
  35. package/src/schedule.ts +326 -0
  36. package/src/serialization.ts +119 -39
  37. package/src/signal-diagnostics.ts +633 -0
  38. package/src/signal-ref.ts +87 -0
  39. package/src/signal.ts +104 -0
  40. package/src/store/accessor-protocol.ts +56 -0
  41. package/src/store/index.ts +4 -0
  42. package/src/structured-examples.ts +248 -0
  43. package/src/surface-derivation.ts +91 -0
  44. package/src/surface-filter.ts +176 -0
  45. package/src/surface-versioning.ts +42 -0
  46. package/src/topo.ts +769 -57
  47. package/src/tracing.ts +345 -0
  48. package/src/trail.ts +1161 -29
  49. package/src/trails/derive-trail.ts +842 -0
  50. package/src/trails/index.ts +9 -0
  51. package/src/trails/ingest.ts +139 -0
  52. package/src/trails-db.ts +208 -0
  53. package/src/transport-error-map.ts +174 -0
  54. package/src/type-utils.ts +87 -0
  55. package/src/types.ts +254 -12
  56. package/src/validate-established-topo.ts +69 -0
  57. package/src/validate-topo.ts +442 -35
  58. package/src/validation.ts +152 -4
  59. package/src/version-marker.ts +716 -0
  60. package/src/version-resolution.ts +308 -0
  61. package/src/version-runtime.ts +120 -0
  62. package/src/webhook.ts +278 -0
  63. package/src/workspace.ts +1 -1
  64. package/src/zod-wrappers.ts +72 -0
  65. package/.turbo/turbo-build.log +0 -1
  66. package/.turbo/turbo-lint.log +0 -3
  67. package/.turbo/turbo-typecheck.log +0 -1
  68. package/dist/adapters.d.ts +0 -39
  69. package/dist/adapters.d.ts.map +0 -1
  70. package/dist/adapters.js +0 -2
  71. package/dist/adapters.js.map +0 -1
  72. package/dist/blob-ref.d.ts +0 -20
  73. package/dist/blob-ref.d.ts.map +0 -1
  74. package/dist/blob-ref.js +0 -22
  75. package/dist/blob-ref.js.map +0 -1
  76. package/dist/branded.d.ts +0 -36
  77. package/dist/branded.d.ts.map +0 -1
  78. package/dist/branded.js +0 -89
  79. package/dist/branded.js.map +0 -1
  80. package/dist/collections.d.ts +0 -31
  81. package/dist/collections.d.ts.map +0 -1
  82. package/dist/collections.js +0 -60
  83. package/dist/collections.js.map +0 -1
  84. package/dist/context.d.ts +0 -10
  85. package/dist/context.d.ts.map +0 -1
  86. package/dist/context.js +0 -15
  87. package/dist/context.js.map +0 -1
  88. package/dist/derive.d.ts +0 -33
  89. package/dist/derive.d.ts.map +0 -1
  90. package/dist/derive.js +0 -122
  91. package/dist/derive.js.map +0 -1
  92. package/dist/errors.d.ts +0 -83
  93. package/dist/errors.d.ts.map +0 -1
  94. package/dist/errors.js +0 -142
  95. package/dist/errors.js.map +0 -1
  96. package/dist/event.d.ts +0 -45
  97. package/dist/event.d.ts.map +0 -1
  98. package/dist/event.js +0 -17
  99. package/dist/event.js.map +0 -1
  100. package/dist/fetch.d.ts +0 -15
  101. package/dist/fetch.d.ts.map +0 -1
  102. package/dist/fetch.js +0 -102
  103. package/dist/fetch.js.map +0 -1
  104. package/dist/guards.d.ts +0 -17
  105. package/dist/guards.d.ts.map +0 -1
  106. package/dist/guards.js +0 -25
  107. package/dist/guards.js.map +0 -1
  108. package/dist/health.d.ts +0 -18
  109. package/dist/health.d.ts.map +0 -1
  110. package/dist/health.js +0 -5
  111. package/dist/health.js.map +0 -1
  112. package/dist/hike.d.ts +0 -36
  113. package/dist/hike.d.ts.map +0 -1
  114. package/dist/hike.js +0 -20
  115. package/dist/hike.js.map +0 -1
  116. package/dist/index.d.ts +0 -34
  117. package/dist/index.d.ts.map +0 -1
  118. package/dist/index.js +0 -38
  119. package/dist/index.js.map +0 -1
  120. package/dist/job.d.ts +0 -24
  121. package/dist/job.d.ts.map +0 -1
  122. package/dist/job.js +0 -17
  123. package/dist/job.js.map +0 -1
  124. package/dist/layer.d.ts +0 -17
  125. package/dist/layer.d.ts.map +0 -1
  126. package/dist/layer.js +0 -21
  127. package/dist/layer.js.map +0 -1
  128. package/dist/path-security.d.ts +0 -28
  129. package/dist/path-security.d.ts.map +0 -1
  130. package/dist/path-security.js +0 -63
  131. package/dist/path-security.js.map +0 -1
  132. package/dist/patterns/bulk.d.ts +0 -15
  133. package/dist/patterns/bulk.d.ts.map +0 -1
  134. package/dist/patterns/bulk.js +0 -14
  135. package/dist/patterns/bulk.js.map +0 -1
  136. package/dist/patterns/change.d.ts +0 -10
  137. package/dist/patterns/change.d.ts.map +0 -1
  138. package/dist/patterns/change.js +0 -10
  139. package/dist/patterns/change.js.map +0 -1
  140. package/dist/patterns/date-range.d.ts +0 -10
  141. package/dist/patterns/date-range.d.ts.map +0 -1
  142. package/dist/patterns/date-range.js +0 -10
  143. package/dist/patterns/date-range.js.map +0 -1
  144. package/dist/patterns/index.d.ts +0 -9
  145. package/dist/patterns/index.d.ts.map +0 -1
  146. package/dist/patterns/index.js +0 -9
  147. package/dist/patterns/index.js.map +0 -1
  148. package/dist/patterns/pagination.d.ts +0 -18
  149. package/dist/patterns/pagination.d.ts.map +0 -1
  150. package/dist/patterns/pagination.js +0 -18
  151. package/dist/patterns/pagination.js.map +0 -1
  152. package/dist/patterns/progress.d.ts +0 -11
  153. package/dist/patterns/progress.d.ts.map +0 -1
  154. package/dist/patterns/progress.js +0 -11
  155. package/dist/patterns/progress.js.map +0 -1
  156. package/dist/patterns/sorting.d.ts +0 -13
  157. package/dist/patterns/sorting.d.ts.map +0 -1
  158. package/dist/patterns/sorting.js +0 -10
  159. package/dist/patterns/sorting.js.map +0 -1
  160. package/dist/patterns/status.d.ts +0 -15
  161. package/dist/patterns/status.d.ts.map +0 -1
  162. package/dist/patterns/status.js +0 -9
  163. package/dist/patterns/status.js.map +0 -1
  164. package/dist/patterns/timestamps.d.ts +0 -10
  165. package/dist/patterns/timestamps.d.ts.map +0 -1
  166. package/dist/patterns/timestamps.js +0 -10
  167. package/dist/patterns/timestamps.js.map +0 -1
  168. package/dist/redaction/index.d.ts +0 -4
  169. package/dist/redaction/index.d.ts.map +0 -1
  170. package/dist/redaction/index.js +0 -3
  171. package/dist/redaction/index.js.map +0 -1
  172. package/dist/redaction/patterns.d.ts +0 -9
  173. package/dist/redaction/patterns.d.ts.map +0 -1
  174. package/dist/redaction/patterns.js +0 -39
  175. package/dist/redaction/patterns.js.map +0 -1
  176. package/dist/redaction/redactor.d.ts +0 -27
  177. package/dist/redaction/redactor.d.ts.map +0 -1
  178. package/dist/redaction/redactor.js +0 -89
  179. package/dist/redaction/redactor.js.map +0 -1
  180. package/dist/resilience.d.ts +0 -34
  181. package/dist/resilience.d.ts.map +0 -1
  182. package/dist/resilience.js +0 -164
  183. package/dist/resilience.js.map +0 -1
  184. package/dist/result.d.ts +0 -57
  185. package/dist/result.d.ts.map +0 -1
  186. package/dist/result.js +0 -145
  187. package/dist/result.js.map +0 -1
  188. package/dist/serialization.d.ts +0 -27
  189. package/dist/serialization.d.ts.map +0 -1
  190. package/dist/serialization.js +0 -115
  191. package/dist/serialization.js.map +0 -1
  192. package/dist/topo.d.ts +0 -18
  193. package/dist/topo.d.ts.map +0 -1
  194. package/dist/topo.js +0 -74
  195. package/dist/topo.js.map +0 -1
  196. package/dist/trail.d.ts +0 -83
  197. package/dist/trail.d.ts.map +0 -1
  198. package/dist/trail.js +0 -16
  199. package/dist/trail.js.map +0 -1
  200. package/dist/types.d.ts +0 -46
  201. package/dist/types.d.ts.map +0 -1
  202. package/dist/types.js +0 -2
  203. package/dist/types.js.map +0 -1
  204. package/dist/validate-topo.d.ts +0 -24
  205. package/dist/validate-topo.d.ts.map +0 -1
  206. package/dist/validate-topo.js +0 -108
  207. package/dist/validate-topo.js.map +0 -1
  208. package/dist/validation.d.ts +0 -27
  209. package/dist/validation.d.ts.map +0 -1
  210. package/dist/validation.js +0 -134
  211. package/dist/validation.js.map +0 -1
  212. package/dist/workspace.d.ts +0 -25
  213. package/dist/workspace.d.ts.map +0 -1
  214. package/dist/workspace.js +0 -57
  215. package/dist/workspace.js.map +0 -1
  216. package/src/__tests__/blob-ref.test.ts +0 -103
  217. package/src/__tests__/branded.test.ts +0 -148
  218. package/src/__tests__/collections.test.ts +0 -126
  219. package/src/__tests__/context.test.ts +0 -66
  220. package/src/__tests__/derive.test.ts +0 -159
  221. package/src/__tests__/errors.test.ts +0 -309
  222. package/src/__tests__/event.test.ts +0 -82
  223. package/src/__tests__/fetch.test.ts +0 -217
  224. package/src/__tests__/guards.test.ts +0 -102
  225. package/src/__tests__/hike.test.ts +0 -117
  226. package/src/__tests__/job.test.ts +0 -98
  227. package/src/__tests__/layer.test.ts +0 -224
  228. package/src/__tests__/path-security.test.ts +0 -114
  229. package/src/__tests__/patterns.test.ts +0 -273
  230. package/src/__tests__/redaction.test.ts +0 -244
  231. package/src/__tests__/resilience.test.ts +0 -246
  232. package/src/__tests__/result.test.ts +0 -155
  233. package/src/__tests__/serialization.test.ts +0 -236
  234. package/src/__tests__/topo.test.ts +0 -184
  235. package/src/__tests__/trail.test.ts +0 -179
  236. package/src/__tests__/validate-topo.test.ts +0 -201
  237. package/src/__tests__/validation.test.ts +0 -283
  238. package/src/__tests__/workspace.test.ts +0 -183
  239. package/src/adapters.ts +0 -68
  240. package/src/event.ts +0 -77
  241. package/src/health.ts +0 -23
  242. package/src/hike.ts +0 -77
  243. package/src/job.ts +0 -20
  244. package/tsconfig.json +0 -9
  245. package/tsconfig.tsbuildinfo +0 -1
package/src/tracing.ts ADDED
@@ -0,0 +1,345 @@
1
+ /**
2
+ * Intrinsic tracing primitives.
3
+ *
4
+ * This module is the home for the trace record type, the sink interface,
5
+ * the sink registry, and the helpers `executeTrail` uses to create root
6
+ * trace records and child spans. Core keeps this minimal contract public so
7
+ * `@ontrails/observe`, `@ontrails/tracing`, adapters, and tests share the
8
+ * same intrinsic execution record shape.
9
+ *
10
+ * Tracing is intrinsic: every `executeTrail` call automatically produces a
11
+ * root `TraceRecord`, `ctx.trace(label, fn)` creates nested child spans, and
12
+ * the signal runtime records lifecycle points underneath the active producer
13
+ * trace. A default no-op sink is installed at module load so core never
14
+ * crashes when no real sink has been registered.
15
+ */
16
+
17
+ /** Signal lifecycle records emitted by the typed signal runtime. */
18
+ export type SignalTraceRecordName =
19
+ | 'signal.fired'
20
+ | 'signal.handler.completed'
21
+ | 'signal.handler.failed'
22
+ | 'signal.handler.invoked'
23
+ | 'signal.handler.predicate_failed'
24
+ | 'signal.handler.predicate_matched'
25
+ | 'signal.handler.predicate_skipped'
26
+ | 'signal.invalid';
27
+
28
+ /** Activation boundary records emitted by runtime materializers. */
29
+ export type ActivationTraceRecordName =
30
+ | 'activation.cycle_detected'
31
+ | 'activation.scheduled'
32
+ | 'activation.webhook'
33
+ | 'activation.webhook.invalid';
34
+
35
+ /** Evidence of a single trail execution, manual span, activation boundary, or signal lifecycle point. */
36
+ export interface TraceRecord {
37
+ readonly id: string;
38
+ readonly traceId: string;
39
+ readonly rootId: string;
40
+ readonly parentId?: string | undefined;
41
+ readonly kind: 'activation' | 'signal' | 'span' | 'trail';
42
+ readonly name: string;
43
+ readonly trailId?: string | undefined;
44
+ readonly surface?: 'cli' | 'mcp' | 'http' | 'ws' | undefined;
45
+ readonly intent?: 'read' | 'write' | 'destroy' | undefined;
46
+ readonly startedAt: number;
47
+ readonly endedAt?: number | undefined;
48
+ readonly status: 'ok' | 'err' | 'cancelled';
49
+ readonly errorCategory?: string | undefined;
50
+ readonly sampled?: boolean | undefined;
51
+ readonly permit?:
52
+ | { readonly id: string; readonly tenantId?: string }
53
+ | undefined;
54
+ readonly attrs: Readonly<Record<string, unknown>>;
55
+ }
56
+
57
+ /**
58
+ * Minimal shape a tracing sink must satisfy.
59
+ *
60
+ * Kept intentionally tiny so adapters in `@ontrails/tracing`,
61
+ * `@ontrails/observe`, and user code can all satisfy it without
62
+ * additional dependencies.
63
+ */
64
+ export interface TraceSink {
65
+ readonly write: (record: TraceRecord) => void | Promise<void>;
66
+ }
67
+
68
+ /** Trace context carried through trail execution via `ctx.extensions`. */
69
+ export interface TraceContext {
70
+ readonly traceId: string;
71
+ readonly spanId: string;
72
+ readonly rootId: string;
73
+ readonly sampled: boolean;
74
+ }
75
+
76
+ /** Key used to store trace context in `ctx.extensions`. */
77
+ export const TRACE_CONTEXT_KEY = '__trace_context';
78
+
79
+ /** Read trace context from trail context extensions. */
80
+ export const getTraceContext = (ctx: {
81
+ readonly extensions?: Readonly<Record<string, unknown>> | undefined;
82
+ }): TraceContext | undefined =>
83
+ ctx.extensions?.[TRACE_CONTEXT_KEY] as TraceContext | undefined;
84
+
85
+ // ---------------------------------------------------------------------------
86
+ // Default no-op sink + sink registry
87
+ // ---------------------------------------------------------------------------
88
+
89
+ /** No-op sink installed by default so core never crashes without configuration. */
90
+ export const NOOP_SINK: TraceSink = {
91
+ // oxlint-disable-next-line no-empty-function -- intentional no-op
92
+ write: () => {},
93
+ };
94
+
95
+ // oxlint-disable-next-line eslint-plugin-jest/require-hook -- module-level sink registry, not test setup
96
+ let currentSink: TraceSink = NOOP_SINK;
97
+
98
+ /**
99
+ * Register a trace sink globally.
100
+ *
101
+ * All trails executed via `executeTrail` will write their completed trace
102
+ * records to this sink, as will every `ctx.trace(label, fn)` child span.
103
+ * Registering `undefined` or calling {@link clearTraceSink} resets back to
104
+ * the default no-op sink.
105
+ */
106
+ export const registerTraceSink = (sink: TraceSink | undefined): void => {
107
+ currentSink = sink ?? NOOP_SINK;
108
+ };
109
+
110
+ /** Retrieve the currently registered sink (never undefined). */
111
+ export const getTraceSink = (): TraceSink => currentSink;
112
+
113
+ /** True when tracing is effectively disabled and executeTrail should skip allocation. */
114
+ export const isTracingDisabled = (sink: TraceSink = currentSink): boolean =>
115
+ sink === NOOP_SINK;
116
+
117
+ /** Reset the sink registry back to the default no-op sink. */
118
+ export const clearTraceSink = (): void => {
119
+ currentSink = NOOP_SINK;
120
+ };
121
+
122
+ // ---------------------------------------------------------------------------
123
+ // Record + span helpers
124
+ // ---------------------------------------------------------------------------
125
+
126
+ /** Options for creating a trail-scoped {@link TraceRecord}. */
127
+ interface CreateTraceRecordOptions {
128
+ readonly trailId: string;
129
+ readonly traceId?: string | undefined;
130
+ readonly parentId?: string | undefined;
131
+ readonly rootId?: string | undefined;
132
+ readonly surface?: TraceRecord['surface'];
133
+ readonly intent?: TraceRecord['intent'];
134
+ readonly sampled?: boolean | undefined;
135
+ readonly permit?:
136
+ | { readonly id: string; readonly tenantId?: string }
137
+ | undefined;
138
+ }
139
+
140
+ interface CreateActivationTraceRecordOptions {
141
+ readonly attrs?: Readonly<Record<string, unknown>> | undefined;
142
+ readonly parentId?: string | undefined;
143
+ readonly rootId?: string | undefined;
144
+ readonly traceId?: string | undefined;
145
+ readonly sampled?: boolean | undefined;
146
+ }
147
+
148
+ /** Create a fresh trail-kind {@link TraceRecord}. */
149
+ export const createTraceRecord = (
150
+ options: CreateTraceRecordOptions
151
+ ): TraceRecord => {
152
+ const id = Bun.randomUUIDv7();
153
+ const traceId = options.traceId ?? Bun.randomUUIDv7();
154
+
155
+ return {
156
+ attrs: {},
157
+ endedAt: undefined,
158
+ id,
159
+ intent: options.intent,
160
+ kind: 'trail',
161
+ name: options.trailId,
162
+ parentId: options.parentId,
163
+ permit: options.permit,
164
+ rootId: options.rootId ?? id,
165
+ sampled: options.sampled,
166
+ startedAt: Date.now(),
167
+ status: 'ok',
168
+ surface: options.surface,
169
+ traceId,
170
+ trailId: options.trailId,
171
+ };
172
+ };
173
+
174
+ /** Create an activation-kind {@link TraceRecord}. */
175
+ export const createActivationTraceRecord = (
176
+ name: ActivationTraceRecordName,
177
+ options: CreateActivationTraceRecordOptions = {}
178
+ ): TraceRecord => {
179
+ const id = Bun.randomUUIDv7();
180
+ const traceId = options.traceId ?? Bun.randomUUIDv7();
181
+
182
+ return {
183
+ attrs: options.attrs ?? {},
184
+ endedAt: undefined,
185
+ errorCategory: undefined,
186
+ id,
187
+ intent: undefined,
188
+ kind: 'activation',
189
+ name,
190
+ parentId: options.parentId,
191
+ permit: undefined,
192
+ rootId: options.rootId ?? id,
193
+ sampled: options.sampled,
194
+ startedAt: Date.now(),
195
+ status: 'ok',
196
+ surface: undefined,
197
+ traceId,
198
+ trailId: undefined,
199
+ };
200
+ };
201
+
202
+ /** Build a span record from a parent trace context. */
203
+ export const createSpanRecord = (
204
+ parent: TraceContext,
205
+ label: string
206
+ ): TraceRecord => ({
207
+ attrs: {},
208
+ endedAt: undefined,
209
+ errorCategory: undefined,
210
+ id: Bun.randomUUIDv7(),
211
+ intent: undefined,
212
+ kind: 'span',
213
+ name: label,
214
+ parentId: parent.spanId,
215
+ rootId: parent.rootId,
216
+ sampled: parent.sampled,
217
+ startedAt: Date.now(),
218
+ status: 'ok',
219
+ surface: undefined,
220
+ traceId: parent.traceId,
221
+ trailId: undefined,
222
+ });
223
+
224
+ /** Build a signal lifecycle record from a parent trace context. */
225
+ export const createSignalTraceRecord = (
226
+ parent: TraceContext,
227
+ name: SignalTraceRecordName,
228
+ attrs: Readonly<Record<string, unknown>> = {}
229
+ ): TraceRecord => ({
230
+ attrs,
231
+ endedAt: undefined,
232
+ errorCategory: undefined,
233
+ id: Bun.randomUUIDv7(),
234
+ intent: undefined,
235
+ kind: 'signal',
236
+ name,
237
+ parentId: parent.spanId,
238
+ rootId: parent.rootId,
239
+ sampled: parent.sampled,
240
+ startedAt: Date.now(),
241
+ status: 'ok',
242
+ surface: undefined,
243
+ traceId: parent.traceId,
244
+ trailId: undefined,
245
+ });
246
+
247
+ /** Use a completed record as the current trace parent for subsequent trail execution. */
248
+ export const traceContextFromRecord = (record: TraceRecord): TraceContext => ({
249
+ rootId: record.rootId,
250
+ sampled: record.sampled ?? true,
251
+ spanId: record.id,
252
+ traceId: record.traceId,
253
+ });
254
+
255
+ /** Mark a record as completed with timing and status. */
256
+ export const completeRecord = (
257
+ record: TraceRecord,
258
+ status: TraceRecord['status'],
259
+ errorCategory?: string | undefined
260
+ ): TraceRecord => ({
261
+ ...record,
262
+ endedAt: Date.now(),
263
+ errorCategory,
264
+ status,
265
+ });
266
+
267
+ /**
268
+ * Best-effort sink write that never throws.
269
+ *
270
+ * Returns `true` when the sink accepted the record, `false` when the write
271
+ * threw. Most callers can ignore the return value -- it exists so that
272
+ * callers that hand the written record back as a parent trace context
273
+ * (e.g. {@link writeActivationTraceRecord}) can refuse to do so when the
274
+ * record never actually made it to storage.
275
+ */
276
+ export const writeToSink = async (
277
+ sink: TraceSink,
278
+ record: TraceRecord
279
+ ): Promise<boolean> => {
280
+ try {
281
+ await Promise.resolve(sink.write(record));
282
+ return true;
283
+ } catch {
284
+ // Sink failures must never affect trail result delivery.
285
+ return false;
286
+ }
287
+ };
288
+
289
+ /** Best-effort write for signal lifecycle records, no-op when tracing is disabled. */
290
+ export const writeSignalTraceRecord = async (
291
+ ctx: { readonly extensions?: Readonly<Record<string, unknown>> | undefined },
292
+ name: SignalTraceRecordName,
293
+ attrs: Readonly<Record<string, unknown>>,
294
+ status: TraceRecord['status'] = 'ok',
295
+ errorCategory?: string | undefined,
296
+ sink: TraceSink = getTraceSink()
297
+ ): Promise<void> => {
298
+ const parent = getTraceContext(ctx);
299
+ if (parent === undefined || isTracingDisabled(sink)) {
300
+ return;
301
+ }
302
+ await writeToSink(
303
+ sink,
304
+ completeRecord(
305
+ createSignalTraceRecord(parent, name, attrs),
306
+ status,
307
+ errorCategory
308
+ )
309
+ );
310
+ };
311
+
312
+ /** Best-effort write for activation boundary records, no-op when tracing is disabled. */
313
+ export const writeActivationTraceRecord = async (
314
+ name: ActivationTraceRecordName,
315
+ attrs: Readonly<Record<string, unknown>>,
316
+ status: TraceRecord['status'] = 'ok',
317
+ errorCategory?: string | undefined,
318
+ parent?: TraceContext | undefined,
319
+ sink: TraceSink = getTraceSink()
320
+ ): Promise<TraceRecord | undefined> => {
321
+ if (isTracingDisabled(sink)) {
322
+ return undefined;
323
+ }
324
+ const record = completeRecord(
325
+ createActivationTraceRecord(name, {
326
+ attrs,
327
+ parentId: parent?.spanId,
328
+ rootId: parent?.rootId,
329
+ // Parentless activations are the root span of their trace tree. Default
330
+ // sampled to true so the activation boundary stays consistent with
331
+ // child trail records, which default sampled=true via
332
+ // traceContextFromRecord. Inconsistent sampled flags within a trace
333
+ // break filters/exporters that gate on the activation boundary.
334
+ sampled: parent?.sampled ?? true,
335
+ traceId: parent?.traceId,
336
+ }),
337
+ status,
338
+ errorCategory
339
+ );
340
+ const written = await writeToSink(sink, record);
341
+ // When the sink dropped the record we must not hand it back to callers as
342
+ // a parent trace context -- subsequent child writes would reference an
343
+ // activation span that never reached storage, producing broken lineage.
344
+ return written ? record : undefined;
345
+ };