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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,43 @@
1
- import { Duration, Effect, Layer, Tracer } from "effect";
2
- import { OtlpExporter } from "effect/unstable/observability";
1
+ import { Context, Duration, Effect, Layer, Logger, Metric, References, Tracer } from "effect";
2
+ import { HttpClient, HttpClientError } from "effect/unstable/http";
3
+ import { OtlpExporter, OtlpSerialization } from "effect/unstable/observability";
4
+ //#region src/engineering-delivery.d.ts
5
+ /** Fixed admission limits for one CLI invocation and its SDK client. */
6
+ declare const CLI_ENGINEERING_LIMITS: Readonly<{
7
+ records: 80;
8
+ ordinaryRecords: 64;
9
+ recordBytes: 16384;
10
+ requests: 5;
11
+ ordinaryRequests: 2;
12
+ requestBytes: 1572864;
13
+ metricSeries: 16;
14
+ requestTimeoutMs: 1000;
15
+ }>;
16
+ type MetricEntry = Metric.Metric.Metadata<unknown, unknown>;
17
+ interface CliEngineeringStats {
18
+ readonly records: number;
19
+ readonly reservedRecordBytes: number;
20
+ readonly peakRecords: number;
21
+ readonly peakReservedRecordBytes: number;
22
+ readonly activeRequests: number;
23
+ readonly requestBytes: number;
24
+ readonly peakRequests: number;
25
+ readonly peakRequestBytes: number;
26
+ /** Rejected records, fields, metric registrations and export attempts; not delivered-event count. */
27
+ readonly dropped: number;
28
+ readonly metricSeries: number;
29
+ }
30
+ /** Shared, closure-owned admission state; no queue or mutable counters are exposed. */
31
+ declare function makeCliEngineeringBudget(): Readonly<{
32
+ metricRegistry: Map<string, MetricEntry>;
33
+ emptyMetricRegistry: () => Map<string, MetricEntry>;
34
+ drop: () => void;
35
+ admitRecord(terminal: boolean): (() => void) | undefined;
36
+ admitRequest(bytes: number, terminal: boolean): (() => void) | undefined;
37
+ snapshot(): CliEngineeringStats;
38
+ }>;
39
+ type CliEngineeringBudget = ReturnType<typeof makeCliEngineeringBudget>;
40
+ //#endregion
3
41
  //#region src/engineering.d.ts
4
42
  type EngineeringProducer = "browser" | "server";
5
43
  declare const ENGINEERING_CAPXUL_ENVS: readonly ["development", "staging", "production", "local"];
@@ -17,6 +55,8 @@ interface EngineeringTelemetryConfig {
17
55
  readonly privacy?: "minimal";
18
56
  readonly shutdownTimeout?: Duration.Input;
19
57
  readonly exportInterval?: Duration.Input;
58
+ /** CLI-only shared admission and transport lifetime. Omission preserves host defaults. */
59
+ readonly deliveryBudget?: CliEngineeringBudget;
20
60
  }
21
61
  declare const traceHeaderFilter: (_name: string) => boolean;
22
62
  declare const postHogOtlpEndpoints: (host: string) => {
@@ -28,7 +68,11 @@ declare const postHogOtlpEndpoints: (host: string) => {
28
68
  declare const makeLeakSafeEngineeringTracer: (delegate: Tracer.Tracer, enabled?: () => boolean) => Tracer.Tracer;
29
69
  /** Attempt to drain all engineering signals; completion does not prove ingestion. */
30
70
  declare const flushEngineeringTelemetry: Effect.Effect<void, never, OtlpExporter.Flusher>;
71
+ declare const EngineeringLog_base: Context.ServiceClass<EngineeringLog, "capxul/EngineeringLog", Logger.Logger<unknown, void>>;
72
+ declare class EngineeringLog extends EngineeringLog_base {}
73
+ /** Deliver an owned engineering record without changing the caller's console verbosity. */
74
+ declare const logEngineeringInfo: (message: string, attributes: Readonly<Record<string, unknown>>) => Effect.Effect<void, never, EngineeringLog>;
31
75
  /** Shared browser/server OTLP layer. OtlpLogger merges with incumbent loggers once. */
32
- declare const makeEngineeringTelemetryLayer: (config: EngineeringTelemetryConfig) => Layer.Layer<OtlpExporter.Flusher, never, never>;
76
+ declare const makeEngineeringTelemetryLayer: (config: EngineeringTelemetryConfig) => Layer.Layer<EngineeringLog | OtlpExporter.Flusher, never, never>;
33
77
  //#endregion
34
- export { ENGINEERING_CAPXUL_ENVS, EngineeringCapxulEnv, EngineeringProducer, EngineeringTelemetryConfig, flushEngineeringTelemetry, makeEngineeringTelemetryLayer, makeLeakSafeEngineeringTracer, postHogOtlpEndpoints, traceHeaderFilter };
78
+ export { CLI_ENGINEERING_LIMITS, type CliEngineeringBudget, type CliEngineeringStats, ENGINEERING_CAPXUL_ENVS, EngineeringCapxulEnv, EngineeringProducer, EngineeringTelemetryConfig, flushEngineeringTelemetry, logEngineeringInfo, makeCliEngineeringBudget, makeEngineeringTelemetryLayer, makeLeakSafeEngineeringTracer, postHogOtlpEndpoints, traceHeaderFilter };
@@ -1,7 +1,382 @@
1
- import { c as redactSecrets, i as CAPXUL_ERROR_CODES, l as redactUrlSecrets, n as isCapxulOperation, o as FAILURE_MODES, s as isCredentialField } from "./operations-sJPrehYY.mjs";
2
- import { Cause, Effect, Exit, FiberSet, Layer, Logger, Stream, Tracer } from "effect";
3
- import { FetchHttpClient, Headers, HttpBody, HttpClient, HttpClientResponse } from "effect/unstable/http";
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";
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
4
  import { OtlpExporter, OtlpLogger, OtlpMetrics, OtlpSerialization, OtlpTracer } from "effect/unstable/observability";
5
+ //#region src/engineering-delivery.ts
6
+ /** Fixed admission limits for one CLI invocation and its SDK client. */
7
+ const CLI_ENGINEERING_LIMITS = Object.freeze({
8
+ records: 80,
9
+ ordinaryRecords: 64,
10
+ recordBytes: 16384,
11
+ requests: 5,
12
+ ordinaryRequests: 2,
13
+ requestBytes: 1572864,
14
+ metricSeries: 16,
15
+ requestTimeoutMs: 1e3
16
+ });
17
+ const metricNames = /* @__PURE__ */ new Set(["capxul.cli.command.count", "capxul.cli.command.duration"]);
18
+ const metricValues = {
19
+ command: CLI_COMMAND_NAMES,
20
+ capability: CLI_CAPABILITIES,
21
+ invocation_kind: CLI_INVOCATION_KINDS,
22
+ outcome: [
23
+ "succeeded",
24
+ "refused",
25
+ "failed",
26
+ "cancelled"
27
+ ],
28
+ capxul_env: [
29
+ "development",
30
+ "staging",
31
+ "production",
32
+ "local"
33
+ ],
34
+ unit: [
35
+ "1",
36
+ "s",
37
+ "ms"
38
+ ]
39
+ };
40
+ /** Only this bounded registry is supplied to the CLI's metric producers. */
41
+ var CliMetricRegistry = class extends Map {
42
+ dropped;
43
+ enabled;
44
+ constructor(dropped, enabled = true) {
45
+ super();
46
+ this.dropped = dropped;
47
+ this.enabled = enabled;
48
+ }
49
+ set(key, entry) {
50
+ if (!this.enabled || !metricNames.has(entry.id) || !this.has(key) && this.size >= CLI_ENGINEERING_LIMITS.metricSeries) {
51
+ this.dropped();
52
+ return this;
53
+ }
54
+ 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)) {
58
+ this.dropped();
59
+ return this;
60
+ }
61
+ return super.set(key, entry);
62
+ }
63
+ };
64
+ /** Shared, closure-owned admission state; no queue or mutable counters are exposed. */
65
+ function makeCliEngineeringBudget() {
66
+ let records = 0, peakRecords = 0, activeRequests = 0, requestBytes = 0;
67
+ let peakRequests = 0, peakRequestBytes = 0, dropped = 0;
68
+ const drop = () => {
69
+ dropped += 1;
70
+ };
71
+ const metricRegistry = new CliMetricRegistry(drop);
72
+ return Object.freeze({
73
+ metricRegistry,
74
+ emptyMetricRegistry: () => new CliMetricRegistry(drop, false),
75
+ drop,
76
+ admitRecord(terminal) {
77
+ if (records >= (terminal ? CLI_ENGINEERING_LIMITS.records : CLI_ENGINEERING_LIMITS.ordinaryRecords)) {
78
+ drop();
79
+ return;
80
+ }
81
+ records += 1;
82
+ peakRecords = Math.max(peakRecords, records);
83
+ let released = false;
84
+ return () => {
85
+ if (!released) {
86
+ released = true;
87
+ records -= 1;
88
+ }
89
+ };
90
+ },
91
+ admitRequest(bytes, terminal) {
92
+ if (bytes > CLI_ENGINEERING_LIMITS.requestBytes || activeRequests >= (terminal ? CLI_ENGINEERING_LIMITS.requests : CLI_ENGINEERING_LIMITS.ordinaryRequests)) {
93
+ drop();
94
+ return;
95
+ }
96
+ activeRequests += 1;
97
+ requestBytes += bytes;
98
+ peakRequests = Math.max(peakRequests, activeRequests);
99
+ peakRequestBytes = Math.max(peakRequestBytes, requestBytes);
100
+ return () => {
101
+ activeRequests -= 1;
102
+ requestBytes -= bytes;
103
+ };
104
+ },
105
+ snapshot() {
106
+ return {
107
+ records,
108
+ reservedRecordBytes: records * CLI_ENGINEERING_LIMITS.recordBytes,
109
+ peakRecords,
110
+ peakReservedRecordBytes: peakRecords * CLI_ENGINEERING_LIMITS.recordBytes,
111
+ activeRequests,
112
+ requestBytes,
113
+ peakRequests,
114
+ peakRequestBytes,
115
+ dropped,
116
+ metricSeries: metricRegistry.size
117
+ };
118
+ }
119
+ });
120
+ }
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);
158
+ for (const release of releases) release();
159
+ }
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();
176
+ };
177
+ return {
178
+ close,
179
+ discardBody,
180
+ metrics(run) {
181
+ return Effect.suspend(() => {
182
+ if (isClosed || metricsRunning) return Effect.void;
183
+ metricsRunning = true;
184
+ return run.pipe(Effect.ensuring(Effect.sync(() => {
185
+ metricsRunning = false;
186
+ })));
187
+ });
188
+ },
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
+ }
377
+ };
378
+ }
379
+ //#endregion
5
380
  //#region src/engineering.ts
6
381
  const ENGINEERING_CAPXUL_ENVS = [
7
382
  "development",
@@ -176,10 +551,13 @@ const MINIMAL_NUMBERS = /* @__PURE__ */ new Set([
176
551
  const MINIMAL_CORRELATIONS = /* @__PURE__ */ new Set([
177
552
  "correlation_id",
178
553
  "journey_id",
179
- "invocation_id"
554
+ "invocation_id",
555
+ "posthogDistinctId"
180
556
  ]);
181
557
  const MINIMAL_METRIC_LABELS = /* @__PURE__ */ new Set([
182
558
  "command",
559
+ "capability",
560
+ "invocation_kind",
183
561
  "outcome",
184
562
  "capxul_env",
185
563
  "unit"
@@ -187,7 +565,7 @@ const MINIMAL_METRIC_LABELS = /* @__PURE__ */ new Set([
187
565
  const MINIMAL_METRIC_NAMES = /* @__PURE__ */ new Set(["capxul.cli.command.count", "capxul.cli.command.duration"]);
188
566
  const traceId = (value) => typeof value === "string" && /^(?!0+$)[0-9a-f]{32}$/u.test(value);
189
567
  const spanId = (value) => typeof value === "string" && /^(?!0+$)[0-9a-f]{16}$/u.test(value);
190
- const minimalName = (value) => typeof value === "string" && (MINIMAL_LOG_NAMES.has(value) || isCapxulOperation(value)) ? value : "capxul.engineering";
568
+ const minimalName = (value) => typeof value === "string" && (MINIMAL_LOG_NAMES.has(value) || isCapxulOperation(value) || CLI_COMMAND_NAMES.some((command) => value === `cli.command.${command}`)) ? value : "capxul.engineering";
191
569
  const MINIMAL_ENUM_VALUES = /* @__PURE__ */ new Map([
192
570
  ["outcome", [
193
571
  "succeeded",
@@ -200,7 +578,10 @@ const MINIMAL_ENUM_VALUES = /* @__PURE__ */ new Map([
200
578
  ["failure_mode", FAILURE_MODES],
201
579
  ["capxul_env", ENGINEERING_CAPXUL_ENVS],
202
580
  ["producer", ["browser", "server"]],
203
- ["command", ["doctor"]],
581
+ ["command", CLI_COMMAND_NAMES],
582
+ ["capability", CLI_CAPABILITIES],
583
+ ["invocation_kind", CLI_INVOCATION_KINDS],
584
+ ["cli_identity_kind", ["anonymous"]],
204
585
  ["output_mode", ["human", "json"]],
205
586
  ["unit", [
206
587
  "1",
@@ -418,10 +799,17 @@ function minimalSerialization(config) {
418
799
  }
419
800
  /** Attempt to drain all engineering signals; completion does not prove ingestion. */
420
801
  const flushEngineeringTelemetry = Effect.flatMap(OtlpExporter.Flusher, (flusher) => flusher.flush);
802
+ var EngineeringLog = class extends Context.Service()("capxul/EngineeringLog") {};
803
+ /** Deliver an owned engineering record without changing the caller's console verbosity. */
804
+ const logEngineeringInfo = (message, attributes) => {
805
+ 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"))));
807
+ };
421
808
  /** Shared browser/server OTLP layer. OtlpLogger merges with incumbent loggers once. */
422
809
  const makeEngineeringTelemetryLayer = (config) => {
423
810
  const validated = validateEngineeringTelemetryConfig(config);
424
811
  return Layer.suspend(() => {
812
+ const bounded = validated.deliveryBudget === void 0 ? void 0 : cliEngineeringDelivery(validated.deliveryBudget, validated.serviceName === "capxul-cli");
425
813
  const endpoints = postHogOtlpEndpoints(validated.host);
426
814
  let disabled = false;
427
815
  const enabled = () => {
@@ -435,7 +823,12 @@ const makeEngineeringTelemetryLayer = (config) => {
435
823
  };
436
824
  const delivery = {
437
825
  ...validated.shutdownTimeout === void 0 ? {} : { shutdownTimeout: validated.shutdownTimeout },
438
- ...validated.exportInterval === void 0 ? {} : { exportInterval: validated.exportInterval }
826
+ ...validated.exportInterval === void 0 ? {} : { exportInterval: validated.exportInterval },
827
+ ...bounded === void 0 ? {} : {
828
+ maxBatchSize: Number.MAX_SAFE_INTEGER,
829
+ exportInterval: 1e3,
830
+ shutdownTimeout: validated.serviceName === "capxul-cli" ? 0 : 500
831
+ }
439
832
  };
440
833
  const resource = {
441
834
  serviceName: validated.serviceName ?? "capxul-sdk",
@@ -451,22 +844,38 @@ const makeEngineeringTelemetryLayer = (config) => {
451
844
  headers: validated.headers,
452
845
  resource,
453
846
  ...delivery
454
- }).pipe(Effect.map((tracer) => makeLeakSafeEngineeringTracer(tracer, enabled)))).pipe(Layer.provideMerge(OtlpExporter.layerFlusher));
455
- const logging = Logger.layer([OtlpLogger.make({
847
+ }).pipe(Effect.map((tracer) => makeLeakSafeEngineeringTracer(bounded?.tracer(tracer) ?? tracer, enabled)))).pipe(Layer.provideMerge(OtlpExporter.layerFlusher));
848
+ const ownedLogging = Layer.effect(EngineeringLog, OtlpLogger.make({
456
849
  url: endpoints.logs,
457
850
  headers: validated.headers,
458
851
  resource,
459
- ...delivery
460
- }).pipe(Effect.map((logger) => Logger.make((options) => {
461
- if (enabled()) logger.log(options);
462
- })))], { mergeWithExisting: true }).pipe(Layer.provideMerge(OtlpExporter.layerFlusher));
463
- const metrics = OtlpMetrics.layer({
852
+ ...delivery,
853
+ ...bounded === void 0 ? {} : { excludeLogSpans: true }
854
+ }).pipe(Effect.map((nativeLogger) => {
855
+ const logger = bounded?.logger(nativeLogger) ?? nativeLogger;
856
+ return Logger.make((options) => {
857
+ if (enabled()) logger.log(options);
858
+ });
859
+ }))).pipe(Layer.provideMerge(OtlpExporter.layerFlusher));
860
+ const logging = Logger.layer([EngineeringLog], { mergeWithExisting: true }).pipe(Layer.provideMerge(ownedLogging));
861
+ const metricConfig = {
464
862
  url: endpoints.metrics,
465
863
  headers: validated.headers,
466
864
  resource,
467
865
  temporality: "delta",
468
866
  ...delivery
469
- });
867
+ };
868
+ const metrics = bounded === void 0 ? OtlpMetrics.layer(metricConfig) : validated.serviceName !== "capxul-cli" ? Layer.empty : Layer.effectDiscard(Effect.gen(function* () {
869
+ const flusher = yield* OtlpExporter.Flusher;
870
+ yield* OtlpMetrics.make({
871
+ ...metricConfig,
872
+ exportInterval: Number.POSITIVE_INFINITY,
873
+ shutdownTimeout: 0
874
+ }).pipe(Effect.provideService(OtlpExporter.Flusher, {
875
+ ...flusher,
876
+ register: (run) => flusher.register(bounded.metrics(run))
877
+ }));
878
+ })).pipe(Layer.provideMerge(OtlpExporter.layerFlusher));
470
879
  const headerPolicy = Layer.merge(Layer.succeed(HttpClient.TracerHeaderFilter, traceHeaderFilter), Layer.succeed(Headers.CurrentRedactedNames, [credentialHeaderPattern]));
471
880
  const browserLifecycle = Layer.effectDiscard(Effect.gen(function* () {
472
881
  if (validated.producer !== "browser" || !hasBrowserDocument()) return;
@@ -489,18 +898,26 @@ const makeEngineeringTelemetryLayer = (config) => {
489
898
  }));
490
899
  }));
491
900
  const transport = Layer.effect(HttpClient.HttpClient, Effect.map(HttpClient.HttpClient, (client) => {
492
- const selectedTransport = validated.producer === "browser" && hasBrowserDocument() ? browserExportClient(client) : client;
493
- return HttpClient.transform(selectedTransport, (response, request) => Effect.suspend(() => enabled() && request.body._tag !== "Empty" ? response : Effect.succeed(HttpClientResponse.fromWeb(request, new Response(null, { status: 200 })))));
901
+ const originalTransport = validated.producer === "browser" && hasBrowserDocument() ? browserExportClient(client) : client;
902
+ const selectedTransport = bounded?.transport(originalTransport, enabled) ?? originalTransport;
903
+ return HttpClient.transform(selectedTransport, (response, request) => Effect.suspend(() => {
904
+ if (enabled() && request.body._tag !== "Empty") return response;
905
+ bounded?.discardBody(request.body);
906
+ return Effect.succeed(HttpClientResponse.fromWeb(request, new Response(null, { status: 200 })));
907
+ }));
494
908
  })).pipe(Layer.provide(FetchHttpClient.layer));
495
909
  const serialization = Layer.effect(OtlpSerialization.OtlpSerialization, Effect.map(OtlpSerialization.OtlpSerialization, (json) => {
496
910
  const projected = minimalSerialization(validated);
497
- return validated.privacy === "minimal" ? projected : {
911
+ const selected = validated.privacy === "minimal" ? projected : {
498
912
  ...json,
499
913
  metrics: projected.metrics
500
914
  };
915
+ return bounded?.serialization(selected) ?? selected;
501
916
  })).pipe(Layer.provide(OtlpSerialization.layerJson));
502
- return browserLifecycle.pipe(Layer.provideMerge(Layer.mergeAll(tracing, logging, metrics, headerPolicy)), Layer.provide(serialization), Layer.provide(transport));
917
+ const combined = browserLifecycle.pipe(Layer.provideMerge(Layer.mergeAll(tracing, logging, metrics, headerPolicy)), Layer.provide(serialization), Layer.provide(transport));
918
+ const scoped = bounded === void 0 ? combined : combined.pipe(Layer.provideMerge(Layer.effectDiscard(Effect.addFinalizer(() => Effect.sync(bounded.close)))));
919
+ return validated.deliveryBudget === void 0 ? scoped : scoped.pipe(Layer.provideMerge(Layer.succeed(Metric.MetricRegistry, validated.serviceName === "capxul-cli" ? validated.deliveryBudget.metricRegistry : validated.deliveryBudget.emptyMetricRegistry())));
503
920
  });
504
921
  };
505
922
  //#endregion
506
- export { ENGINEERING_CAPXUL_ENVS, flushEngineeringTelemetry, makeEngineeringTelemetryLayer, makeLeakSafeEngineeringTracer, postHogOtlpEndpoints, traceHeaderFilter };
923
+ export { CLI_ENGINEERING_LIMITS, ENGINEERING_CAPXUL_ENVS, flushEngineeringTelemetry, logEngineeringInfo, makeCliEngineeringBudget, makeEngineeringTelemetryLayer, makeLeakSafeEngineeringTracer, postHogOtlpEndpoints, traceHeaderFilter };