@capxul/observability 4.20.0-beta.3 → 4.20.0-beta.30

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.
@@ -1,8 +1,190 @@
1
- import { _ as CLI_COMMAND_NAMES, an as redactSecrets, en as isCapxulOperation, g as CLI_CAPABILITIES, in as isCredentialField, nn as CAPXUL_ERROR_CODES, on as redactUrlSecrets, rn as FAILURE_MODES, v as CLI_INVOCATION_KINDS } from "./src-CyMef9yy.mjs";
1
+ import { I as CLI_CAPABILITIES, L as CLI_COMMAND_NAMES, R as CLI_INVOCATION_KINDS, an as isCapxulOperation, cn as ORGANIZATION_SETUP_STATUSES, dn as isCredentialField, fn as CAPXUL_ERROR_CODES, ln as redactSecrets, pn as FAILURE_MODES, sn as ORGANIZATION_SETUP_REASONS, un as redactUrlSecrets } from "./src-L9SjUzWV.mjs";
2
2
  import { Cause, Clock, Context, Deferred, Duration, Effect, Exit, FiberSet, Layer, LogLevel, Logger, Metric, Option, References, Schedule, Stream, Tracer } from "effect";
3
- import { FetchHttpClient, Headers, HttpBody, HttpClient, HttpClientError, HttpClientResponse } from "effect/unstable/http";
4
- import { OtlpExporter, OtlpLogger, OtlpMetrics, OtlpSerialization, OtlpTracer } from "effect/unstable/observability";
5
- //#region src/engineering-delivery.ts
3
+ import { FetchHttpClient, Headers, HttpBody, HttpClient, HttpClientError, HttpClientResponse } from "effect/http";
4
+ import { OtlpExporter, OtlpLogger, OtlpMetrics, OtlpSerialization, OtlpTracer } from "effect/observability";
5
+ //#region ../protocol/src/primitives/record.ts
6
+ /** A non-null object that is not an array: a value whose fields can be read by name. */
7
+ function isRecord(value) {
8
+ return typeof value === "object" && value !== null && !Array.isArray(value);
9
+ }
10
+ /** The field `key` of `value` when `value` is a record, else undefined. */
11
+ function readField(value, key) {
12
+ return isRecord(value) ? value[key] : void 0;
13
+ }
14
+ //#endregion
15
+ //#region src/delivery/admission.ts
16
+ function newDeliveryState(budget, ownsMetrics) {
17
+ return {
18
+ budget,
19
+ ownsMetrics,
20
+ closed: Deferred.makeUnsafe(),
21
+ isClosed: false,
22
+ metricsRunning: false,
23
+ logs: [],
24
+ spans: /* @__PURE__ */ new Map(),
25
+ terminalBodies: /* @__PURE__ */ new WeakSet(),
26
+ bodyLeases: /* @__PURE__ */ new WeakMap(),
27
+ pendingBodies: /* @__PURE__ */ new Set()
28
+ };
29
+ }
30
+ /** Hold the records a body carries until its request finishes or the body is discarded. */
31
+ function holdBody(state, body, releases) {
32
+ if (releases.length === 0) return;
33
+ const lease = {
34
+ started: false,
35
+ release: () => {
36
+ if (!state.pendingBodies.delete(lease)) return;
37
+ state.bodyLeases.delete(body);
38
+ for (const release of releases) release();
39
+ }
40
+ };
41
+ state.bodyLeases.set(body, lease);
42
+ state.pendingBodies.add(lease);
43
+ }
44
+ const discardBody = (state, body) => state.bodyLeases.get(body)?.release();
45
+ const drainLogs = (state) => {
46
+ for (const release of state.logs.splice(0)) release();
47
+ };
48
+ function closeDelivery(state) {
49
+ state.isClosed = true;
50
+ Deferred.doneUnsafe(state.closed, Effect.void);
51
+ for (const lease of state.pendingBodies) if (!lease.started) lease.release();
52
+ drainLogs(state);
53
+ for (const release of state.spans.values()) release();
54
+ state.spans.clear();
55
+ if (state.ownsMetrics) state.budget.metricRegistry.clear();
56
+ }
57
+ const text = (value) => typeof value === "string" ? value.slice(0, 128) : "capxul.engineering";
58
+ const attributes = (input, limit = 32) => {
59
+ const out = {};
60
+ let count = 0;
61
+ let bytes = 0;
62
+ try {
63
+ for (const key in input) {
64
+ if (count++ >= limit) break;
65
+ if (key.length > 64) continue;
66
+ const value = Object.getOwnPropertyDescriptor(input, key)?.value;
67
+ const scalar = typeof value === "string" ? value.slice(0, 128) : typeof value === "boolean" || typeof value === "number" && Number.isFinite(value) ? value : void 0;
68
+ if (scalar === void 0) continue;
69
+ bytes += new TextEncoder().encode(JSON.stringify([key, scalar])).length;
70
+ if (bytes > (limit >= 24 ? 2e3 : 300)) break;
71
+ out[key] = scalar;
72
+ }
73
+ } catch {}
74
+ return out;
75
+ };
76
+ const terminalName = (name) => name === "cli.command.completed" || name === "cli.wallet.step" || CLI_COMMAND_NAMES.some((command) => name === `cli.command.${command}`);
77
+ //#endregion
78
+ //#region src/delivery/records.ts
79
+ function admittedLogger(state, delegate) {
80
+ return Logger.make((options) => {
81
+ if (state.isClosed) return;
82
+ let release;
83
+ try {
84
+ const message = text(Array.isArray(options.message) ? Object.getOwnPropertyDescriptor(options.message, "0")?.value : options.message);
85
+ release = state.budget.admitRecord(terminalName(message));
86
+ if (release === void 0) return;
87
+ const context = Context.add(options.fiber.context, References.CurrentLogAnnotations, attributes(options.fiber.getRef(References.CurrentLogAnnotations)));
88
+ const fiber = Object.create(options.fiber);
89
+ Object.defineProperty(fiber, "getRef", { value: (ref) => Context.get(context, ref) });
90
+ state.logs.push(release);
91
+ delegate.log({
92
+ ...options,
93
+ message,
94
+ cause: Cause.empty,
95
+ fiber
96
+ });
97
+ } catch {
98
+ if (release !== void 0) {
99
+ const index = state.logs.indexOf(release);
100
+ if (index !== -1) state.logs.splice(index, 1);
101
+ release();
102
+ }
103
+ state.budget.drop();
104
+ }
105
+ });
106
+ }
107
+ function admittedTracer(state, delegate) {
108
+ return Tracer.make({
109
+ span(options) {
110
+ const name = text(options.name);
111
+ const release = state.isClosed ? void 0 : state.budget.admitRecord(terminalName(name));
112
+ const spanOptions = {
113
+ ...options,
114
+ name,
115
+ annotations: Context.empty(),
116
+ links: []
117
+ };
118
+ const span = release === void 0 ? new Tracer.NativeSpan({
119
+ ...spanOptions,
120
+ sampled: false
121
+ }) : delegate.span(spanOptions);
122
+ if (release !== void 0) state.spans.set(span.spanId, release);
123
+ const wrapped = boundedSpan(state, span);
124
+ wrapped.addLinks(options.links ?? []);
125
+ return wrapped;
126
+ },
127
+ ...delegate.context === void 0 ? {} : { context: delegate.context.bind(delegate) }
128
+ });
129
+ }
130
+ /** The span as a producer sees it: at most 32 fields and 2000 attribute bytes, 2 events, 2 links. */
131
+ function boundedSpan(state, span) {
132
+ const { budget } = state;
133
+ const wrapped = Object.create(span);
134
+ let fields = 0, events = 0, links = 0, attributeBytes = 0;
135
+ let ended = false;
136
+ Object.defineProperties(wrapped, {
137
+ attribute: { value: (key, value) => {
138
+ if (fields >= 32 && !span.attributes.has(key)) {
139
+ budget.drop();
140
+ return;
141
+ }
142
+ const safe = attributes({ [key]: value }, 1);
143
+ if (!Object.hasOwn(safe, key)) return;
144
+ const previous = span.attributes.has(key) ? new TextEncoder().encode(JSON.stringify([key, span.attributes.get(key)])).length : 0;
145
+ const next = new TextEncoder().encode(JSON.stringify([key, safe[key]])).length;
146
+ if (attributeBytes - previous + next > 2e3) {
147
+ budget.drop();
148
+ return;
149
+ }
150
+ attributeBytes += next - previous;
151
+ if (!span.attributes.has(key)) fields += 1;
152
+ span.attribute(key, safe[key]);
153
+ } },
154
+ event: { value: (eventName, time, attrs) => {
155
+ if (events++ >= 2) {
156
+ budget.drop();
157
+ return;
158
+ }
159
+ span.event(text(eventName), time, attributes(attrs ?? {}, 4));
160
+ } },
161
+ addLinks: { value: (items) => {
162
+ for (const item of items.slice(0, Math.max(0, 2 - links))) {
163
+ if (!/^[a-f0-9]{32}$/u.test(item.span.traceId) || !/^[a-f0-9]{16}$/u.test(item.span.spanId)) {
164
+ budget.drop();
165
+ continue;
166
+ }
167
+ span.addLinks([{
168
+ span: Tracer.externalSpan({
169
+ traceId: item.span.traceId,
170
+ spanId: item.span.spanId,
171
+ sampled: item.span.sampled
172
+ }),
173
+ attributes: attributes(item.attributes, 4)
174
+ }]);
175
+ links += 1;
176
+ }
177
+ } },
178
+ end: { value: (time, exit) => {
179
+ if (ended) return;
180
+ ended = true;
181
+ span.end(time, Exit.isFailure(exit) ? Exit.fail("CLI operation failed") : Exit.void);
182
+ } }
183
+ });
184
+ return wrapped;
185
+ }
186
+ //#endregion
187
+ //#region src/delivery/budget.ts
6
188
  /** Fixed admission limits for one CLI invocation and its SDK client. */
7
189
  const CLI_ENGINEERING_LIMITS = Object.freeze({
8
190
  records: 80,
@@ -52,9 +234,8 @@ var CliMetricRegistry = class extends Map {
52
234
  return this;
53
235
  }
54
236
  const labels = Object.entries(entry.attributes ?? {});
55
- const state = entry.hooks.get(Context.empty());
56
- const buckets = typeof state === "object" && state !== null && "buckets" in state ? state.buckets : void 0;
57
- if (!["Counter", "Histogram"].includes(entry.type) || (entry.description?.length ?? 0) > 128 || labels.length > 6 || labels.some(([name, value]) => !metricValues[name]?.includes(String(value))) || entry.type === "Histogram" && (!Array.isArray(buckets) || buckets.length > 32)) {
237
+ const buckets = readField(entry.hooks.get(Context.empty()), "buckets");
238
+ if (!["Counter", "Histogram"].includes(entry.type) || (entry.description?.length ?? 0) > 128 || labels.length > 6 || labels.some(([name, value]) => !metricValues[name]?.includes(value)) || entry.type === "Histogram" && (!Array.isArray(buckets) || buckets.length > 32)) {
58
239
  this.dropped();
59
240
  return this;
60
241
  }
@@ -118,266 +299,123 @@ function makeCliEngineeringBudget() {
118
299
  }
119
300
  });
120
301
  }
121
- const text = (value) => typeof value === "string" ? value.slice(0, 128) : "capxul.engineering";
122
- const attributes = (input, limit = 32) => {
123
- const out = {};
124
- let count = 0;
125
- let bytes = 0;
126
- try {
127
- for (const key in input) {
128
- if (count++ >= limit) break;
129
- if (key.length > 64) continue;
130
- const value = Object.getOwnPropertyDescriptor(input, key)?.value;
131
- const scalar = typeof value === "string" ? value.slice(0, 128) : typeof value === "boolean" || typeof value === "number" && Number.isFinite(value) ? value : void 0;
132
- if (scalar === void 0) continue;
133
- bytes += new TextEncoder().encode(JSON.stringify([key, scalar])).length;
134
- if (bytes > (limit >= 24 ? 2e3 : 300)) break;
135
- out[key] = scalar;
136
- }
137
- } catch {}
138
- return out;
139
- };
140
- const terminalName = (name) => name === "cli.command.completed" || CLI_COMMAND_NAMES.some((command) => name === `cli.command.${command}`);
141
- /** Native exporters retain their serialization and scheduling; this owns admission only. */
142
- function cliEngineeringDelivery(budget, ownsMetrics) {
143
- const closed = Deferred.makeUnsafe();
144
- let isClosed = false;
145
- let metricsRunning = false;
146
- const logs = [];
147
- const spans = /* @__PURE__ */ new Map();
148
- const terminalBodies = /* @__PURE__ */ new WeakSet();
149
- const bodyLeases = /* @__PURE__ */ new WeakMap();
150
- const pendingBodies = /* @__PURE__ */ new Set();
151
- const holdBody = (body, releases) => {
152
- if (releases.length === 0) return;
153
- const lease = {
154
- started: false,
155
- release: () => {
156
- if (!pendingBodies.delete(lease)) return;
157
- bodyLeases.delete(body);
302
+ //#endregion
303
+ //#region src/delivery/requests.ts
304
+ function admittedSerialization(state, delegate) {
305
+ return {
306
+ logs(data) {
307
+ const releases = state.logs.splice(0);
308
+ try {
309
+ const body = delegate.logs(data);
310
+ if (data.resourceLogs.some((r) => r.scopeLogs.some((s) => s.logRecords?.some((l) => terminalName(l.body?.stringValue ?? ""))))) state.terminalBodies.add(body);
311
+ holdBody(state, body, releases);
312
+ return body;
313
+ } catch (error) {
158
314
  for (const release of releases) release();
315
+ throw error;
159
316
  }
160
- };
161
- bodyLeases.set(body, lease);
162
- pendingBodies.add(lease);
163
- };
164
- const discardBody = (body) => bodyLeases.get(body)?.release();
165
- const drainLogs = () => {
166
- for (const release of logs.splice(0)) release();
167
- };
168
- const close = () => {
169
- isClosed = true;
170
- Deferred.doneUnsafe(closed, Effect.void);
171
- for (const lease of pendingBodies) if (!lease.started) lease.release();
172
- drainLogs();
173
- for (const release of spans.values()) release();
174
- spans.clear();
175
- if (ownsMetrics) budget.metricRegistry.clear();
317
+ },
318
+ traces(data) {
319
+ const releases = [];
320
+ for (const resource of data.resourceSpans) for (const scope of resource.scopeSpans) for (const span of scope.spans) {
321
+ const release = state.spans.get(span.spanId);
322
+ if (release !== void 0) releases.push(release);
323
+ state.spans.delete(span.spanId);
324
+ }
325
+ try {
326
+ const body = delegate.traces(data);
327
+ if (data.resourceSpans.some((r) => r.scopeSpans.some((s) => s.spans.some((span) => terminalName(span.name))))) state.terminalBodies.add(body);
328
+ holdBody(state, body, releases);
329
+ return body;
330
+ } catch (error) {
331
+ for (const release of releases) release();
332
+ throw error;
333
+ }
334
+ },
335
+ metrics(data) {
336
+ const body = delegate.metrics(data);
337
+ state.terminalBodies.add(body);
338
+ return body;
339
+ }
176
340
  };
341
+ }
342
+ /** A 429's Retry-After, bounded to two seconds; any other failure retries at once. */
343
+ const retryAfter = Schedule.spaced("100 millis").pipe(Schedule.addDelay(({ input }) => Effect.map(Clock.currentTimeMillis, (now) => {
344
+ if (!HttpClientError.isHttpClientError(input) || input.reason._tag !== "StatusCodeError" || input.reason.response.status !== 429) return Duration.zero;
345
+ const header = input.reason.response.headers["retry-after"];
346
+ const numeric = Number(header);
347
+ const delay = header === void 0 ? 2e3 : Number.isFinite(numeric) ? numeric * 1e3 : Date.parse(header) - now;
348
+ return Duration.millis(Number.isFinite(delay) ? Math.min(2e3, Math.max(0, delay)) : 2e3);
349
+ })));
350
+ function admittedTransport(state, client, enabled = () => true) {
351
+ const { budget } = state;
352
+ const retrying = HttpClient.transform(client, (response, request) => Effect.suspend(() => {
353
+ if (enabled()) return response;
354
+ budget.drop();
355
+ return Effect.succeed(HttpClientResponse.fromWeb(request, new Response(null)));
356
+ })).pipe(HttpClient.transformResponse((response) => Effect.tap(response, (result) => {
357
+ let received = 0;
358
+ return Stream.runForEach(result.stream, (chunk) => Effect.sync(() => {
359
+ received += chunk.byteLength;
360
+ if (received > 65536) throw new Error("Engineering response limit exceeded");
361
+ })).pipe(Effect.catchReason("HttpClientError", "EmptyBodyError", () => Effect.void));
362
+ }))).pipe(HttpClient.filterStatusOk, HttpClient.retryTransient({
363
+ times: 1,
364
+ schedule: retryAfter
365
+ }));
366
+ return HttpClient.transform(retrying, (response, request) => Effect.suspend(() => {
367
+ if (state.isClosed) {
368
+ discardBody(state, request.body);
369
+ return Effect.succeed(HttpClientResponse.fromWeb(request, new Response(null)));
370
+ }
371
+ const droppedResponse = () => HttpClientResponse.fromWeb(request, new Response(null, { status: 200 }));
372
+ if (request.body._tag === "Empty") {
373
+ discardBody(state, request.body);
374
+ return Effect.succeed(droppedResponse());
375
+ }
376
+ const bytes = request.body.contentLength;
377
+ const release = budget.admitRequest(bytes ?? Number.POSITIVE_INFINITY, state.terminalBodies.has(request.body));
378
+ if (release === void 0) {
379
+ discardBody(state, request.body);
380
+ return Effect.succeed(droppedResponse());
381
+ }
382
+ const lease = state.bodyLeases.get(request.body);
383
+ if (lease !== void 0) lease.started = true;
384
+ return response.pipe(Effect.asVoid, Effect.timeoutOption(CLI_ENGINEERING_LIMITS.requestTimeoutMs), Effect.raceFirst(Deferred.await(state.closed).pipe(Effect.andThen(Effect.interrupt))), Effect.tap((result) => Effect.sync(() => {
385
+ if (Option.isNone(result)) budget.drop();
386
+ })), Effect.catchCause(() => Effect.sync(() => {
387
+ budget.drop();
388
+ })), Effect.as(droppedResponse()), Effect.ensuring(Effect.sync(() => {
389
+ release();
390
+ discardBody(state, request.body);
391
+ })));
392
+ }));
393
+ }
394
+ //#endregion
395
+ //#region src/engineering-delivery.ts
396
+ /** Native exporters retain their serialization and scheduling; this owns admission only. */
397
+ function cliEngineeringDelivery(budget, ownsMetrics) {
398
+ const state = newDeliveryState(budget, ownsMetrics);
177
399
  return {
178
- close,
179
- discardBody,
400
+ close: () => closeDelivery(state),
401
+ discardBody: (body) => discardBody(state, body),
180
402
  metrics(run) {
181
403
  return Effect.suspend(() => {
182
- if (isClosed || metricsRunning) return Effect.void;
183
- metricsRunning = true;
404
+ if (state.isClosed || state.metricsRunning) return Effect.void;
405
+ state.metricsRunning = true;
184
406
  return run.pipe(Effect.ensuring(Effect.sync(() => {
185
- metricsRunning = false;
407
+ state.metricsRunning = false;
186
408
  })));
187
409
  });
188
410
  },
189
- logger(delegate) {
190
- return Logger.make((options) => {
191
- if (isClosed) return;
192
- let release;
193
- try {
194
- const message = text(Array.isArray(options.message) ? Object.getOwnPropertyDescriptor(options.message, "0")?.value : options.message);
195
- release = budget.admitRecord(terminalName(message));
196
- if (release === void 0) return;
197
- const context = Context.add(options.fiber.context, References.CurrentLogAnnotations, attributes(options.fiber.getRef(References.CurrentLogAnnotations)));
198
- const fiber = Object.create(options.fiber);
199
- Object.defineProperty(fiber, "getRef", { value: (ref) => Context.get(context, ref) });
200
- logs.push(release);
201
- delegate.log({
202
- ...options,
203
- message,
204
- cause: Cause.empty,
205
- fiber
206
- });
207
- } catch {
208
- if (release !== void 0) {
209
- const index = logs.indexOf(release);
210
- if (index !== -1) logs.splice(index, 1);
211
- release();
212
- }
213
- budget.drop();
214
- }
215
- });
216
- },
217
- tracer(delegate) {
218
- return Tracer.make({
219
- span(options) {
220
- const name = text(options.name);
221
- const release = isClosed ? void 0 : budget.admitRecord(terminalName(name));
222
- const spanOptions = {
223
- ...options,
224
- name,
225
- annotations: Context.empty(),
226
- links: []
227
- };
228
- const span = release === void 0 ? new Tracer.NativeSpan({
229
- ...spanOptions,
230
- sampled: false
231
- }) : delegate.span(spanOptions);
232
- if (release !== void 0) spans.set(span.spanId, release);
233
- const wrapped = Object.create(span);
234
- let fields = 0, events = 0, links = 0, attributeBytes = 0;
235
- let ended = false;
236
- Object.defineProperties(wrapped, {
237
- attribute: { value: (key, value) => {
238
- if (fields >= 32 && !span.attributes.has(key)) {
239
- budget.drop();
240
- return;
241
- }
242
- const safe = attributes({ [key]: value }, 1);
243
- if (!Object.hasOwn(safe, key)) return;
244
- const previous = span.attributes.has(key) ? new TextEncoder().encode(JSON.stringify([key, span.attributes.get(key)])).length : 0;
245
- const next = new TextEncoder().encode(JSON.stringify([key, safe[key]])).length;
246
- if (attributeBytes - previous + next > 2e3) {
247
- budget.drop();
248
- return;
249
- }
250
- attributeBytes += next - previous;
251
- if (!span.attributes.has(key)) fields += 1;
252
- span.attribute(key, safe[key]);
253
- } },
254
- event: { value: (eventName, time, attrs) => {
255
- if (events++ >= 2) {
256
- budget.drop();
257
- return;
258
- }
259
- span.event(text(eventName), time, attributes(attrs ?? {}, 4));
260
- } },
261
- addLinks: { value: (items) => {
262
- for (const item of items.slice(0, Math.max(0, 2 - links))) {
263
- if (!/^[a-f0-9]{32}$/u.test(item.span.traceId) || !/^[a-f0-9]{16}$/u.test(item.span.spanId)) {
264
- budget.drop();
265
- continue;
266
- }
267
- span.addLinks([{
268
- span: Tracer.externalSpan({
269
- traceId: item.span.traceId,
270
- spanId: item.span.spanId,
271
- sampled: item.span.sampled
272
- }),
273
- attributes: attributes(item.attributes, 4)
274
- }]);
275
- links += 1;
276
- }
277
- } },
278
- end: { value: (time, exit) => {
279
- if (ended) return;
280
- ended = true;
281
- span.end(time, Exit.isFailure(exit) ? Exit.fail("CLI operation failed") : Exit.void);
282
- } }
283
- });
284
- wrapped.addLinks(options.links ?? []);
285
- return wrapped;
286
- },
287
- ...delegate.context === void 0 ? {} : { context: delegate.context.bind(delegate) }
288
- });
289
- },
290
- serialization(delegate) {
291
- return {
292
- logs(data) {
293
- const releases = logs.splice(0);
294
- try {
295
- const body = delegate.logs(data);
296
- if (data.resourceLogs.some((r) => r.scopeLogs.some((s) => s.logRecords?.some((l) => l.body?.stringValue === "cli.command.completed")))) terminalBodies.add(body);
297
- holdBody(body, releases);
298
- return body;
299
- } catch (error) {
300
- for (const release of releases) release();
301
- throw error;
302
- }
303
- },
304
- traces(data) {
305
- const releases = [];
306
- for (const resource of data.resourceSpans) for (const scope of resource.scopeSpans) for (const span of scope.spans) {
307
- const release = spans.get(span.spanId);
308
- if (release !== void 0) releases.push(release);
309
- spans.delete(span.spanId);
310
- }
311
- try {
312
- const body = delegate.traces(data);
313
- if (data.resourceSpans.some((r) => r.scopeSpans.some((s) => s.spans.some((span) => terminalName(span.name))))) terminalBodies.add(body);
314
- holdBody(body, releases);
315
- return body;
316
- } catch (error) {
317
- for (const release of releases) release();
318
- throw error;
319
- }
320
- },
321
- metrics(data) {
322
- const body = delegate.metrics(data);
323
- terminalBodies.add(body);
324
- return body;
325
- }
326
- };
327
- },
328
- transport(client, enabled = () => true) {
329
- const retrying = HttpClient.transform(client, (response, request) => Effect.suspend(() => {
330
- if (enabled()) return response;
331
- budget.drop();
332
- return Effect.succeed(HttpClientResponse.fromWeb(request, new Response(null)));
333
- })).pipe(HttpClient.transformResponse((response) => Effect.tap(response, (result) => {
334
- let received = 0;
335
- return Stream.runForEach(result.stream, (chunk) => Effect.sync(() => {
336
- received += chunk.byteLength;
337
- if (received > 65536) throw new Error("Engineering response limit exceeded");
338
- })).pipe(Effect.catchTag("HttpClientError", (error) => error.reason._tag === "EmptyBodyError" ? Effect.void : Effect.fail(error)));
339
- }))).pipe(HttpClient.filterStatusOk, HttpClient.retryTransient({
340
- times: 1,
341
- schedule: Schedule.spaced("100 millis").pipe(Schedule.addDelay(({ input }) => Effect.map(Clock.currentTimeMillis, (now) => {
342
- if (!HttpClientError.isHttpClientError(input) || input.reason._tag !== "StatusCodeError" || input.reason.response.status !== 429) return Duration.zero;
343
- const header = input.reason.response.headers["retry-after"];
344
- const numeric = Number(header);
345
- const delay = header === void 0 ? 2e3 : Number.isFinite(numeric) ? numeric * 1e3 : Date.parse(header) - now;
346
- return Duration.millis(Number.isFinite(delay) ? Math.min(2e3, Math.max(0, delay)) : 2e3);
347
- })))
348
- }));
349
- return HttpClient.transform(retrying, (response, request) => Effect.suspend(() => {
350
- if (isClosed) {
351
- discardBody(request.body);
352
- return Effect.succeed(HttpClientResponse.fromWeb(request, new Response(null)));
353
- }
354
- const droppedResponse = () => HttpClientResponse.fromWeb(request, new Response(null, { status: 200 }));
355
- if (request.body._tag === "Empty") {
356
- discardBody(request.body);
357
- return Effect.succeed(droppedResponse());
358
- }
359
- const bytes = request.body.contentLength;
360
- const release = budget.admitRequest(bytes ?? Number.POSITIVE_INFINITY, terminalBodies.has(request.body));
361
- if (release === void 0) {
362
- discardBody(request.body);
363
- return Effect.succeed(droppedResponse());
364
- }
365
- const lease = bodyLeases.get(request.body);
366
- if (lease !== void 0) lease.started = true;
367
- return response.pipe(Effect.asVoid, Effect.timeoutOption(CLI_ENGINEERING_LIMITS.requestTimeoutMs), Effect.raceFirst(Deferred.await(closed).pipe(Effect.andThen(Effect.interrupt))), Effect.tap((result) => Effect.sync(() => {
368
- if (Option.isNone(result)) budget.drop();
369
- })), Effect.catchCause(() => Effect.sync(() => {
370
- budget.drop();
371
- })), Effect.as(droppedResponse()), Effect.ensuring(Effect.sync(() => {
372
- release();
373
- discardBody(request.body);
374
- })));
375
- }));
376
- }
411
+ logger: (delegate) => admittedLogger(state, delegate),
412
+ tracer: (delegate) => admittedTracer(state, delegate),
413
+ serialization: (delegate) => admittedSerialization(state, delegate),
414
+ transport: (client, enabled) => admittedTransport(state, client, enabled)
377
415
  };
378
416
  }
379
417
  //#endregion
380
- //#region src/engineering.ts
418
+ //#region src/engineering/config.ts
381
419
  const ENGINEERING_CAPXUL_ENVS = [
382
420
  "development",
383
421
  "staging",
@@ -385,7 +423,10 @@ const ENGINEERING_CAPXUL_ENVS = [
385
423
  "local"
386
424
  ];
387
425
  const traceHeaderFilter = (_name) => true;
388
- const credentialHeaderPattern = Object.assign(/./u, { test: (name) => isCredentialField(name, "header") });
426
+ const credentialHeaderPattern = Object.assign(/./u, { exec: (name) => isCredentialField(name, "header") ? Object.assign([name], {
427
+ index: 0,
428
+ input: name
429
+ }) : null });
389
430
  const postHogOtlpEndpoints = (host) => {
390
431
  const base = host.replace(/\/+$/, "");
391
432
  return {
@@ -424,6 +465,8 @@ const validateEngineeringTelemetryConfig = (config) => {
424
465
  headers: Object.freeze({ authorization: headerEntries[0][1] })
425
466
  };
426
467
  };
468
+ //#endregion
469
+ //#region src/engineering/tracer.ts
427
470
  const failureText = (failure, key, limit, source) => {
428
471
  if (failure === null || typeof failure !== "object") return void 0;
429
472
  try {
@@ -457,7 +500,7 @@ const browserExportClient = (client) => HttpClient.transform(client, (response,
457
500
  if (request.body._tag === "Stream" || request.body._tag === "FormData" || bytes === void 0 || !Number.isSafeInteger(bytes) || bytes < 0 || unfinishedBrowserExportBytes + bytes > BROWSER_KEEPALIVE_BYTES) return;
458
501
  unfinishedBrowserExportBytes += bytes;
459
502
  return bytes;
460
- }), (bytes) => response.pipe(Effect.provideService(FetchHttpClient.RequestInit, { keepalive: bytes !== void 0 }), Effect.tap((result) => bytes === void 0 ? Effect.void : Stream.runDrain(result.stream).pipe(Effect.catch((error) => error.reason._tag === "EmptyBodyError" ? Effect.void : Effect.fail(error))))), (bytes) => Effect.sync(() => {
503
+ }), (bytes) => response.pipe(Effect.provideService(FetchHttpClient.RequestInit, { keepalive: bytes !== void 0 }), Effect.tap((result) => bytes === void 0 ? Effect.void : Stream.runDrain(result.stream).pipe(Effect.catchIf((error) => error.reason._tag === "EmptyBodyError", () => Effect.void)))), (bytes) => Effect.sync(() => {
461
504
  if (bytes !== void 0) unfinishedBrowserExportBytes -= bytes;
462
505
  })));
463
506
  const spanUpstream = (span) => {
@@ -477,11 +520,15 @@ function redactHttpPath(path, fullUrl, source) {
477
520
  const prefix = path.startsWith("/") ? origin : "";
478
521
  return redactUrlSecrets(`${prefix}${path}`, source).slice(prefix.length, prefix.length + 2048);
479
522
  }
523
+ /** A span whose prototype is `span`: it answers every Span member until an own property overrides it. */
524
+ function inheritSpan(span) {
525
+ return Object.create(span);
526
+ }
480
527
  /** Preserve domain exits while preventing the OTLP serializer from seeing raw causes. */
481
528
  const makeLeakSafeEngineeringTracer = (delegate, enabled = () => true) => Tracer.make({
482
529
  span(options) {
483
530
  const span = enabled() ? delegate.span(options) : new Tracer.NativeSpan(options);
484
- const wrapped = Object.create(span);
531
+ const wrapped = inheritSpan(span);
485
532
  Object.defineProperty(wrapped, "attribute", { value: (name, value) => {
486
533
  const source = spanUpstream(span);
487
534
  const header = /^http\.(?:request|response)\.header\.(.+)$/u.exec(name)?.[1];
@@ -521,20 +568,25 @@ const makeLeakSafeEngineeringTracer = (delegate, enabled = () => true) => Tracer
521
568
  },
522
569
  ...delegate.context === void 0 ? {} : { context: delegate.context.bind(delegate) }
523
570
  });
571
+ //#endregion
572
+ //#region src/engineering/minimal.ts
524
573
  const MINIMAL_LOG_NAMES = /* @__PURE__ */ new Set([
525
574
  "capxul_sdk_operation",
526
575
  "identity.transition",
527
576
  "convex.connection.changed",
528
577
  "convex.action.connection_lost",
529
578
  "cli.command.completed",
530
- "cli.sdk.operation.failed"
579
+ "cli.sdk.operation.failed",
580
+ "cli.signer.wait",
581
+ "cli.wallet.step"
531
582
  ]);
532
583
  const MINIMAL_ERROR_CODES = /* @__PURE__ */ new Set([
533
584
  ...CAPXUL_ERROR_CODES,
534
585
  "CLI_USAGE",
535
586
  "CLI_CONFIG",
536
587
  "CLI_STORAGE",
537
- "CLI_DEFECT"
588
+ "CLI_DEFECT",
589
+ "CLI_DEADLINE"
538
590
  ]);
539
591
  const MINIMAL_NUMBERS = /* @__PURE__ */ new Set([
540
592
  "schema_version",
@@ -546,7 +598,14 @@ const MINIMAL_NUMBERS = /* @__PURE__ */ new Set([
546
598
  "http_status",
547
599
  "rpc.code",
548
600
  "connection_count",
549
- "connection_retries"
601
+ "connection_retries",
602
+ "elapsed_ms",
603
+ "total_ms",
604
+ "token_calls",
605
+ "call",
606
+ "attempt",
607
+ "bridge_protocol_version",
608
+ "httpStatus"
550
609
  ]);
551
610
  const MINIMAL_CORRELATIONS = /* @__PURE__ */ new Set([
552
611
  "correlation_id",
@@ -581,8 +640,39 @@ const MINIMAL_ENUM_VALUES = /* @__PURE__ */ new Map([
581
640
  ["command", CLI_COMMAND_NAMES],
582
641
  ["capability", CLI_CAPABILITIES],
583
642
  ["invocation_kind", CLI_INVOCATION_KINDS],
584
- ["cli_identity_kind", ["anonymous"]],
643
+ ["diagnostic_scope", [
644
+ "openfort.step",
645
+ "openfort.token",
646
+ "openfort.encryptionSession",
647
+ "openfort.embeddedState",
648
+ "openfort.storageCleared",
649
+ "openfort.configure",
650
+ "openfort.get",
651
+ "openfort.address",
652
+ "openfort.staleCacheHeal",
653
+ "openfort.signerRecovery",
654
+ "openfort.signerStatusListener"
655
+ ]],
656
+ ["step", [
657
+ "initialize",
658
+ "token",
659
+ "encryptionSession",
660
+ "embeddedState",
661
+ "configure",
662
+ "get",
663
+ "healStaleCache",
664
+ "walletReady"
665
+ ]],
666
+ ["provider", ["better-auth", "oidc"]],
667
+ ["cli_identity_kind", ["anonymous", "identified"]],
585
668
  ["output_mode", ["human", "json"]],
669
+ ["setup_status", ORGANIZATION_SETUP_STATUSES],
670
+ ["setup_reason", ORGANIZATION_SETUP_REASONS],
671
+ ["signer_stage", [
672
+ "address",
673
+ "readiness",
674
+ "sign"
675
+ ]],
586
676
  ["unit", [
587
677
  "1",
588
678
  "s",
@@ -603,6 +693,7 @@ const MINIMAL_VERSION_KEYS = /* @__PURE__ */ new Set([
603
693
  ]);
604
694
  function minimalStringValue(key, text) {
605
695
  if (key === "operation") return isCapxulOperation(text);
696
+ if (key === "organization_id") return /^org_[0-9A-Za-z]+$/u.test(text);
606
697
  if (MINIMAL_CODE_KEYS.has(key)) return MINIMAL_ERROR_CODES.has(text);
607
698
  if (MINIMAL_VERSION_KEYS.has(key)) return /^[A-Za-z0-9][A-Za-z0-9._+@-]{0,127}$/u.test(text);
608
699
  if (key === "trace_id") return traceId(text);
@@ -628,6 +719,8 @@ function minimalAttributes(attributes, metric = false) {
628
719
  }] : [];
629
720
  });
630
721
  }
722
+ //#endregion
723
+ //#region src/engineering/metric-points.ts
631
724
  const finiteProtocolNumber = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
632
725
  const protocolCount = (value, maximum = Number.MAX_SAFE_INTEGER) => typeof value === "number" && Number.isSafeInteger(value) && value >= 0 && value <= maximum ? value : void 0;
633
726
  const protocolFlags = (value) => protocolCount(Object.getOwnPropertyDescriptor(value, "flags")?.value, 4294967295);
@@ -677,6 +770,8 @@ function minimalHistogramPoints(points) {
677
770
  }];
678
771
  });
679
772
  }
773
+ //#endregion
774
+ //#region src/engineering/serialization.ts
680
775
  function minimalSerialization(config) {
681
776
  const service = config.serviceName ?? "capxul-sdk";
682
777
  const resource = { attributes: Object.entries({
@@ -767,7 +862,7 @@ function minimalSerialization(config) {
767
862
  const gauge = metric.gauge === void 0 ? void 0 : { dataPoints: minimalNumberPoints(metric.gauge.dataPoints) };
768
863
  const sum = metric.sum === void 0 ? void 0 : {
769
864
  aggregationTemporality: protocolCount(metric.sum.aggregationTemporality, 2) ?? 0,
770
- isMonotonic: metric.sum.isMonotonic === true,
865
+ isMonotonic: metric.sum.isMonotonic,
771
866
  dataPoints: minimalNumberPoints(metric.sum.dataPoints)
772
867
  };
773
868
  const histogram = metric.histogram === void 0 ? void 0 : {
@@ -797,14 +892,18 @@ function minimalSerialization(config) {
797
892
  }
798
893
  };
799
894
  }
895
+ //#endregion
896
+ //#region src/engineering/logs.ts
800
897
  /** Attempt to drain all engineering signals; completion does not prove ingestion. */
801
898
  const flushEngineeringTelemetry = Effect.flatMap(OtlpExporter.Flusher, (flusher) => flusher.flush);
802
899
  var EngineeringLog = class extends Context.Service()("capxul/EngineeringLog") {};
803
900
  /** Deliver an owned engineering record without changing the caller's console verbosity. */
804
901
  const logEngineeringInfo = (message, attributes) => {
805
902
  const record = Effect.logInfo(message).pipe(Effect.annotateLogs(attributes));
806
- return Effect.flatMap(LogLevel.isEnabled("Info"), (visible) => visible ? record : Effect.flatMap(EngineeringLog, (logger) => record.pipe(Effect.provideService(Logger.CurrentLoggers, /* @__PURE__ */ new Set([logger])), Effect.provideService(References.MinimumLogLevel, "All"))));
903
+ return Effect.flatMap(LogLevel.isEnabled("Info"), (visible) => visible ? record : Effect.flatMap(Effect.serviceOption(EngineeringLog), (logger) => logger._tag === "None" ? Effect.void : record.pipe(Effect.provideService(Logger.CurrentLoggers, /* @__PURE__ */ new Set([logger.value])), Effect.provideService(References.MinimumLogLevel, "All"))));
807
904
  };
905
+ //#endregion
906
+ //#region src/engineering.ts
808
907
  /** Shared browser/server OTLP layer. OtlpLogger merges with incumbent loggers once. */
809
908
  const makeEngineeringTelemetryLayer = (config) => {
810
909
  const validated = validateEngineeringTelemetryConfig(config);