@capxul/observability 4.20.0-beta.35 → 4.20.0-beta.37
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.
- package/README.md +3 -0
- package/dist/engineering.d.mts +9 -0
- package/dist/engineering.mjs +232 -129
- package/dist/index.d.mts +222 -170
- package/dist/index.mjs +2 -2
- package/dist/{src-C_wvz630.mjs → src-CkM01SMk.mjs} +181 -30
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -201,3 +201,6 @@ Set `privacy: "minimal"` for hosts that must export only closed operation/error
|
|
|
201
201
|
codes, timings, environment, and validated correlation. This opt-in removes
|
|
202
202
|
free-form logs, provider messages, URLs, bodies, and source paths, including
|
|
203
203
|
span exception events. Default consumers retain the existing redaction policy.
|
|
204
|
+
`minimalCorrelationId` is that projection's correlation rule: a host that shows
|
|
205
|
+
a correlation id to a person checks it first, so the id it shows is one the
|
|
206
|
+
export carries.
|
package/dist/engineering.d.mts
CHANGED
|
@@ -7,6 +7,7 @@ export declare const CLI_ENGINEERING_LIMITS: Readonly<{
|
|
|
7
7
|
records: 80;
|
|
8
8
|
ordinaryRecords: 64;
|
|
9
9
|
recordBytes: 16384;
|
|
10
|
+
recordFields: 48;
|
|
10
11
|
requests: 5;
|
|
11
12
|
ordinaryRequests: 2;
|
|
12
13
|
requestBytes: 1572864;
|
|
@@ -77,6 +78,14 @@ export declare const logEngineeringInfo: (message: string, attributes: Readonly<
|
|
|
77
78
|
/** Preserve domain exits while preventing the OTLP serializer from seeing raw causes. */
|
|
78
79
|
export declare const makeLeakSafeEngineeringTracer: (delegate: Tracer.Tracer, enabled?: () => boolean) => Tracer.Tracer;
|
|
79
80
|
//#endregion
|
|
81
|
+
//#region src/engineering/minimal.d.ts
|
|
82
|
+
/**
|
|
83
|
+
* A correlation id the minimal export carries: an identifier with no `/`, not
|
|
84
|
+
* `0x` hex, and unchanged by the secret mask. The CLI quotes a backend
|
|
85
|
+
* correlation id as its `ref` only when it passes, so the ref finds a row.
|
|
86
|
+
*/
|
|
87
|
+
export declare const minimalCorrelationId: (value: unknown) => value is string;
|
|
88
|
+
//#endregion
|
|
80
89
|
//#region src/engineering.d.ts
|
|
81
90
|
/** Shared browser/server OTLP layer. OtlpLogger merges with incumbent loggers once. */
|
|
82
91
|
export declare const makeEngineeringTelemetryLayer: (config: EngineeringTelemetryConfig) => Layer.Layer<EngineeringLog | OtlpExporter.Flusher, never, never>;
|
package/dist/engineering.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { G as CLI_COMMAND_NAMES, K as CLI_INVOCATION_KINDS, W as CLI_CAPABILITIES, _n as
|
|
1
|
+
import { Cn as PROVIDERS, En as FAILURE_MODES, G as CLI_COMMAND_NAMES, K as CLI_INVOCATION_KINDS, Sn as CAPXUL_ERROR_CODES, Tn as isCapxulError, W as CLI_CAPABILITIES, _n as ORGANIZATION_SETUP_REASONS, bn as redactUrlSecrets, hn as isCapxulOperation, q as FAILURE_CLASSES, vn as ORGANIZATION_SETUP_STATUSES, wn as PROVIDER_PROBLEMS, xn as isCredentialField, yn as redactSecrets } from "./src-CkM01SMk.mjs";
|
|
2
2
|
import { Cause, Clock, Context, Deferred, Duration, Effect, Exit, FiberSet, Layer, LogLevel, Logger, Metric, Option, References, Schedule, Stream, Tracer } from "effect";
|
|
3
3
|
import { FetchHttpClient, Headers, HttpBody, HttpClient, HttpClientError, HttpClientResponse } from "effect/http";
|
|
4
4
|
import { OtlpExporter, OtlpLogger, OtlpMetrics, OtlpSerialization, OtlpTracer } from "effect/observability";
|
|
@@ -12,6 +12,123 @@ function readField(value, key) {
|
|
|
12
12
|
return isRecord(value) ? value[key] : void 0;
|
|
13
13
|
}
|
|
14
14
|
//#endregion
|
|
15
|
+
//#region src/delivery/budget.ts
|
|
16
|
+
/** Fixed admission limits for one CLI invocation and its SDK client. */
|
|
17
|
+
const CLI_ENGINEERING_LIMITS = Object.freeze({
|
|
18
|
+
records: 80,
|
|
19
|
+
ordinaryRecords: 64,
|
|
20
|
+
recordBytes: 16384,
|
|
21
|
+
recordFields: 48,
|
|
22
|
+
requests: 5,
|
|
23
|
+
ordinaryRequests: 2,
|
|
24
|
+
requestBytes: 1572864,
|
|
25
|
+
metricSeries: 16,
|
|
26
|
+
requestTimeoutMs: 1e3
|
|
27
|
+
});
|
|
28
|
+
const metricNames = /* @__PURE__ */ new Set(["capxul.cli.command.count", "capxul.cli.command.duration"]);
|
|
29
|
+
const metricValues = {
|
|
30
|
+
command: CLI_COMMAND_NAMES,
|
|
31
|
+
capability: CLI_CAPABILITIES,
|
|
32
|
+
invocation_kind: CLI_INVOCATION_KINDS,
|
|
33
|
+
outcome: [
|
|
34
|
+
"succeeded",
|
|
35
|
+
"refused",
|
|
36
|
+
"failed",
|
|
37
|
+
"cancelled"
|
|
38
|
+
],
|
|
39
|
+
capxul_env: [
|
|
40
|
+
"development",
|
|
41
|
+
"staging",
|
|
42
|
+
"production",
|
|
43
|
+
"local"
|
|
44
|
+
],
|
|
45
|
+
unit: [
|
|
46
|
+
"1",
|
|
47
|
+
"s",
|
|
48
|
+
"ms"
|
|
49
|
+
]
|
|
50
|
+
};
|
|
51
|
+
/** Only this bounded registry is supplied to the CLI's metric producers. */
|
|
52
|
+
var CliMetricRegistry = class extends Map {
|
|
53
|
+
dropped;
|
|
54
|
+
enabled;
|
|
55
|
+
constructor(dropped, enabled = true) {
|
|
56
|
+
super();
|
|
57
|
+
this.dropped = dropped;
|
|
58
|
+
this.enabled = enabled;
|
|
59
|
+
}
|
|
60
|
+
set(key, entry) {
|
|
61
|
+
if (!this.enabled || !metricNames.has(entry.id) || !this.has(key) && this.size >= CLI_ENGINEERING_LIMITS.metricSeries) {
|
|
62
|
+
this.dropped();
|
|
63
|
+
return this;
|
|
64
|
+
}
|
|
65
|
+
const labels = Object.entries(entry.attributes ?? {});
|
|
66
|
+
const buckets = readField(entry.hooks.get(Context.empty()), "buckets");
|
|
67
|
+
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)) {
|
|
68
|
+
this.dropped();
|
|
69
|
+
return this;
|
|
70
|
+
}
|
|
71
|
+
return super.set(key, entry);
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
/** Shared, closure-owned admission state; no queue or mutable counters are exposed. */
|
|
75
|
+
function makeCliEngineeringBudget() {
|
|
76
|
+
let records = 0, peakRecords = 0, activeRequests = 0, requestBytes = 0;
|
|
77
|
+
let peakRequests = 0, peakRequestBytes = 0, dropped = 0;
|
|
78
|
+
const drop = () => {
|
|
79
|
+
dropped += 1;
|
|
80
|
+
};
|
|
81
|
+
const metricRegistry = new CliMetricRegistry(drop);
|
|
82
|
+
return Object.freeze({
|
|
83
|
+
metricRegistry,
|
|
84
|
+
emptyMetricRegistry: () => new CliMetricRegistry(drop, false),
|
|
85
|
+
drop,
|
|
86
|
+
admitRecord(terminal) {
|
|
87
|
+
if (records >= (terminal ? CLI_ENGINEERING_LIMITS.records : CLI_ENGINEERING_LIMITS.ordinaryRecords)) {
|
|
88
|
+
drop();
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
records += 1;
|
|
92
|
+
peakRecords = Math.max(peakRecords, records);
|
|
93
|
+
let released = false;
|
|
94
|
+
return () => {
|
|
95
|
+
if (!released) {
|
|
96
|
+
released = true;
|
|
97
|
+
records -= 1;
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
},
|
|
101
|
+
admitRequest(bytes, terminal) {
|
|
102
|
+
if (bytes > CLI_ENGINEERING_LIMITS.requestBytes || activeRequests >= (terminal ? CLI_ENGINEERING_LIMITS.requests : CLI_ENGINEERING_LIMITS.ordinaryRequests)) {
|
|
103
|
+
drop();
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
activeRequests += 1;
|
|
107
|
+
requestBytes += bytes;
|
|
108
|
+
peakRequests = Math.max(peakRequests, activeRequests);
|
|
109
|
+
peakRequestBytes = Math.max(peakRequestBytes, requestBytes);
|
|
110
|
+
return () => {
|
|
111
|
+
activeRequests -= 1;
|
|
112
|
+
requestBytes -= bytes;
|
|
113
|
+
};
|
|
114
|
+
},
|
|
115
|
+
snapshot() {
|
|
116
|
+
return {
|
|
117
|
+
records,
|
|
118
|
+
reservedRecordBytes: records * CLI_ENGINEERING_LIMITS.recordBytes,
|
|
119
|
+
peakRecords,
|
|
120
|
+
peakReservedRecordBytes: peakRecords * CLI_ENGINEERING_LIMITS.recordBytes,
|
|
121
|
+
activeRequests,
|
|
122
|
+
requestBytes,
|
|
123
|
+
peakRequests,
|
|
124
|
+
peakRequestBytes,
|
|
125
|
+
dropped,
|
|
126
|
+
metricSeries: metricRegistry.size
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
//#endregion
|
|
15
132
|
//#region src/delivery/admission.ts
|
|
16
133
|
function newDeliveryState(budget, ownsMetrics) {
|
|
17
134
|
return {
|
|
@@ -55,7 +172,7 @@ function closeDelivery(state) {
|
|
|
55
172
|
if (state.ownsMetrics) state.budget.metricRegistry.clear();
|
|
56
173
|
}
|
|
57
174
|
const text = (value) => typeof value === "string" ? value.slice(0, 128) : "capxul.engineering";
|
|
58
|
-
const attributes = (input, limit =
|
|
175
|
+
const attributes = (input, limit = CLI_ENGINEERING_LIMITS.recordFields) => {
|
|
59
176
|
const out = {};
|
|
60
177
|
let count = 0;
|
|
61
178
|
let bytes = 0;
|
|
@@ -75,6 +192,73 @@ const attributes = (input, limit = 32) => {
|
|
|
75
192
|
};
|
|
76
193
|
const terminalName = (name) => name === "cli.command.completed" || name === "cli.wallet.step" || CLI_COMMAND_NAMES.some((command) => name === `cli.command.${command}`);
|
|
77
194
|
//#endregion
|
|
195
|
+
//#region ../protocol/src/errors/identifier-mask.ts
|
|
196
|
+
/**
|
|
197
|
+
* A typed prefix over a 64-hex digest, such as `money_execution_<digest>`. A
|
|
198
|
+
* private key has the same 64 hex characters, so only Capxul's own id
|
|
199
|
+
* prefixes make the digest public (docs/rules/redaction.md); any other prefix
|
|
200
|
+
* keeps it masked.
|
|
201
|
+
*/
|
|
202
|
+
const PREFIXED_DIGEST = /\b[a-z]+(?:_[a-z]+)*_[0-9a-f]{64}\b/gu;
|
|
203
|
+
/**
|
|
204
|
+
* The prefixes of Capxul's ids over a 64-hex digest. A client needs the id to
|
|
205
|
+
* recover. A new id of this shape is added here, or its digest is masked at
|
|
206
|
+
* the wire.
|
|
207
|
+
*/
|
|
208
|
+
const CAPXUL_ID_PREFIXES = /* @__PURE__ */ new Set([
|
|
209
|
+
"invitation",
|
|
210
|
+
"money_execution",
|
|
211
|
+
"payment_command",
|
|
212
|
+
"payroll_run",
|
|
213
|
+
"permission_assignment",
|
|
214
|
+
"permission_command",
|
|
215
|
+
"permission_command_invitation",
|
|
216
|
+
"permission_invite"
|
|
217
|
+
]);
|
|
218
|
+
/** Marks an id while the text is masked: private-use characters no mask matches. */
|
|
219
|
+
const ID_MARK = /\uE000(\d+)\uE001/gu;
|
|
220
|
+
/**
|
|
221
|
+
* `redactSecrets` with Capxul identifiers kept whole. Each id is set aside
|
|
222
|
+
* first, so a credential label still masks whatever value it names, id-shaped
|
|
223
|
+
* or not; only an unlabelled id comes back.
|
|
224
|
+
*/
|
|
225
|
+
function maskKeepingIdentifiers(text, source) {
|
|
226
|
+
const ids = [];
|
|
227
|
+
const marked = text.replace(PREFIXED_DIGEST, (id) => {
|
|
228
|
+
if (!CAPXUL_ID_PREFIXES.has(id.slice(0, -65))) return id;
|
|
229
|
+
ids.push(id);
|
|
230
|
+
return `\uE000${String(ids.length - 1)}\uE001`;
|
|
231
|
+
});
|
|
232
|
+
const masked = redactSecrets(marked, source);
|
|
233
|
+
return ids.length === 0 ? masked : masked.replace(ID_MARK, (_, index) => ids[Number(index)] ?? "");
|
|
234
|
+
}
|
|
235
|
+
//#endregion
|
|
236
|
+
//#region ../protocol/src/errors/serialize.ts
|
|
237
|
+
/** Evidence strings are bounded: a provider body cannot inflate a failure. */
|
|
238
|
+
const EVIDENCE_TEXT_MAX = 2e3;
|
|
239
|
+
const UNPRINTABLE = "[unprintable]";
|
|
240
|
+
/**
|
|
241
|
+
* The masked, bounded message of a failure. A factory puts the field name
|
|
242
|
+
* after the words that describe it (`signature is invalid: …`), never before a
|
|
243
|
+
* colon, so the prose mask cannot read the field as a credential label and cut
|
|
244
|
+
* the next word (CAP-441). The message is masked whole, so a second mask on
|
|
245
|
+
* any surface leaves it as it is. A trace span passes its RPC upstream as
|
|
246
|
+
* `source`, so a key in that upstream's URL is masked too.
|
|
247
|
+
*/
|
|
248
|
+
function maskedMessage(error, source) {
|
|
249
|
+
try {
|
|
250
|
+
return boundedText(maskKeepingIdentifiers(error.message, source));
|
|
251
|
+
} catch {
|
|
252
|
+
return UNPRINTABLE;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
/** Text `boundedText` already cut: a second serialize (MCP after the SDK) keeps it as it is. */
|
|
256
|
+
const BOUNDED = new RegExp(`^[\\s\\S]{0,${String(EVIDENCE_TEXT_MAX)}}…\\(\\d{1,7} chars\\)$`);
|
|
257
|
+
function boundedText(masked) {
|
|
258
|
+
if (masked.length <= EVIDENCE_TEXT_MAX || BOUNDED.test(masked)) return masked;
|
|
259
|
+
return `${masked.slice(0, EVIDENCE_TEXT_MAX)}…(${String(masked.length)} chars)`;
|
|
260
|
+
}
|
|
261
|
+
//#endregion
|
|
78
262
|
//#region src/engineering/tracer.ts
|
|
79
263
|
const failureText = (failure, key, limit, source) => {
|
|
80
264
|
if (failure === null || typeof failure !== "object") return void 0;
|
|
@@ -92,8 +276,20 @@ const failureText = (failure, key, limit, source) => {
|
|
|
92
276
|
return;
|
|
93
277
|
}
|
|
94
278
|
};
|
|
279
|
+
/**
|
|
280
|
+
* A `CapxulError` reads as its failure row reads (`maskedMessage`), so a
|
|
281
|
+
* factory message keeps the word after its field name (CAP-427). The row
|
|
282
|
+
* reads the same error, so the exporter reads nothing more than the row does.
|
|
283
|
+
*/
|
|
284
|
+
const capxulMessage = (failure, source) => {
|
|
285
|
+
try {
|
|
286
|
+
return isCapxulError(failure) ? maskedMessage(failure, source).slice(0, 2048) : void 0;
|
|
287
|
+
} catch {
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
};
|
|
95
291
|
const exportFailure = (failure, source) => {
|
|
96
|
-
const message = failureText(failure, "message", 2048, source) ?? (typeof failure === "string" ? redactSecrets(failure, source).slice(0, 2048) : "Engineering operation failed");
|
|
292
|
+
const message = capxulMessage(failure, source) ?? failureText(failure, "message", 2048, source) ?? (typeof failure === "string" ? redactSecrets(failure, source).slice(0, 2048) : "Engineering operation failed");
|
|
97
293
|
const code = failureText(failure, "code", 128);
|
|
98
294
|
const error = new Error((code === void 0 ? message : `${message} [code=${code}]`).slice(0, 2048));
|
|
99
295
|
error.name = failureText(failure, "_tag", 128) ?? failureText(failure, "name", 128) ?? "Error";
|
|
@@ -244,7 +440,10 @@ function admittedTracer(state, delegate) {
|
|
|
244
440
|
...delegate.context === void 0 ? {} : { context: delegate.context.bind(delegate) }
|
|
245
441
|
});
|
|
246
442
|
}
|
|
247
|
-
/**
|
|
443
|
+
/**
|
|
444
|
+
* The span as a producer sees it: at most `recordFields` fields and 2000
|
|
445
|
+
* attribute bytes, 2 events, 2 links.
|
|
446
|
+
*/
|
|
248
447
|
function boundedSpan(state, span) {
|
|
249
448
|
const { budget } = state;
|
|
250
449
|
const wrapped = inheritSpan(span);
|
|
@@ -252,7 +451,7 @@ function boundedSpan(state, span) {
|
|
|
252
451
|
let ended = false;
|
|
253
452
|
Object.defineProperties(wrapped, {
|
|
254
453
|
attribute: { value: (key, value) => {
|
|
255
|
-
if (fields >=
|
|
454
|
+
if (fields >= CLI_ENGINEERING_LIMITS.recordFields && !span.attributes.has(key)) {
|
|
256
455
|
budget.drop();
|
|
257
456
|
return;
|
|
258
457
|
}
|
|
@@ -301,122 +500,6 @@ function boundedSpan(state, span) {
|
|
|
301
500
|
return wrapped;
|
|
302
501
|
}
|
|
303
502
|
//#endregion
|
|
304
|
-
//#region src/delivery/budget.ts
|
|
305
|
-
/** Fixed admission limits for one CLI invocation and its SDK client. */
|
|
306
|
-
const CLI_ENGINEERING_LIMITS = Object.freeze({
|
|
307
|
-
records: 80,
|
|
308
|
-
ordinaryRecords: 64,
|
|
309
|
-
recordBytes: 16384,
|
|
310
|
-
requests: 5,
|
|
311
|
-
ordinaryRequests: 2,
|
|
312
|
-
requestBytes: 1572864,
|
|
313
|
-
metricSeries: 16,
|
|
314
|
-
requestTimeoutMs: 1e3
|
|
315
|
-
});
|
|
316
|
-
const metricNames = /* @__PURE__ */ new Set(["capxul.cli.command.count", "capxul.cli.command.duration"]);
|
|
317
|
-
const metricValues = {
|
|
318
|
-
command: CLI_COMMAND_NAMES,
|
|
319
|
-
capability: CLI_CAPABILITIES,
|
|
320
|
-
invocation_kind: CLI_INVOCATION_KINDS,
|
|
321
|
-
outcome: [
|
|
322
|
-
"succeeded",
|
|
323
|
-
"refused",
|
|
324
|
-
"failed",
|
|
325
|
-
"cancelled"
|
|
326
|
-
],
|
|
327
|
-
capxul_env: [
|
|
328
|
-
"development",
|
|
329
|
-
"staging",
|
|
330
|
-
"production",
|
|
331
|
-
"local"
|
|
332
|
-
],
|
|
333
|
-
unit: [
|
|
334
|
-
"1",
|
|
335
|
-
"s",
|
|
336
|
-
"ms"
|
|
337
|
-
]
|
|
338
|
-
};
|
|
339
|
-
/** Only this bounded registry is supplied to the CLI's metric producers. */
|
|
340
|
-
var CliMetricRegistry = class extends Map {
|
|
341
|
-
dropped;
|
|
342
|
-
enabled;
|
|
343
|
-
constructor(dropped, enabled = true) {
|
|
344
|
-
super();
|
|
345
|
-
this.dropped = dropped;
|
|
346
|
-
this.enabled = enabled;
|
|
347
|
-
}
|
|
348
|
-
set(key, entry) {
|
|
349
|
-
if (!this.enabled || !metricNames.has(entry.id) || !this.has(key) && this.size >= CLI_ENGINEERING_LIMITS.metricSeries) {
|
|
350
|
-
this.dropped();
|
|
351
|
-
return this;
|
|
352
|
-
}
|
|
353
|
-
const labels = Object.entries(entry.attributes ?? {});
|
|
354
|
-
const buckets = readField(entry.hooks.get(Context.empty()), "buckets");
|
|
355
|
-
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)) {
|
|
356
|
-
this.dropped();
|
|
357
|
-
return this;
|
|
358
|
-
}
|
|
359
|
-
return super.set(key, entry);
|
|
360
|
-
}
|
|
361
|
-
};
|
|
362
|
-
/** Shared, closure-owned admission state; no queue or mutable counters are exposed. */
|
|
363
|
-
function makeCliEngineeringBudget() {
|
|
364
|
-
let records = 0, peakRecords = 0, activeRequests = 0, requestBytes = 0;
|
|
365
|
-
let peakRequests = 0, peakRequestBytes = 0, dropped = 0;
|
|
366
|
-
const drop = () => {
|
|
367
|
-
dropped += 1;
|
|
368
|
-
};
|
|
369
|
-
const metricRegistry = new CliMetricRegistry(drop);
|
|
370
|
-
return Object.freeze({
|
|
371
|
-
metricRegistry,
|
|
372
|
-
emptyMetricRegistry: () => new CliMetricRegistry(drop, false),
|
|
373
|
-
drop,
|
|
374
|
-
admitRecord(terminal) {
|
|
375
|
-
if (records >= (terminal ? CLI_ENGINEERING_LIMITS.records : CLI_ENGINEERING_LIMITS.ordinaryRecords)) {
|
|
376
|
-
drop();
|
|
377
|
-
return;
|
|
378
|
-
}
|
|
379
|
-
records += 1;
|
|
380
|
-
peakRecords = Math.max(peakRecords, records);
|
|
381
|
-
let released = false;
|
|
382
|
-
return () => {
|
|
383
|
-
if (!released) {
|
|
384
|
-
released = true;
|
|
385
|
-
records -= 1;
|
|
386
|
-
}
|
|
387
|
-
};
|
|
388
|
-
},
|
|
389
|
-
admitRequest(bytes, terminal) {
|
|
390
|
-
if (bytes > CLI_ENGINEERING_LIMITS.requestBytes || activeRequests >= (terminal ? CLI_ENGINEERING_LIMITS.requests : CLI_ENGINEERING_LIMITS.ordinaryRequests)) {
|
|
391
|
-
drop();
|
|
392
|
-
return;
|
|
393
|
-
}
|
|
394
|
-
activeRequests += 1;
|
|
395
|
-
requestBytes += bytes;
|
|
396
|
-
peakRequests = Math.max(peakRequests, activeRequests);
|
|
397
|
-
peakRequestBytes = Math.max(peakRequestBytes, requestBytes);
|
|
398
|
-
return () => {
|
|
399
|
-
activeRequests -= 1;
|
|
400
|
-
requestBytes -= bytes;
|
|
401
|
-
};
|
|
402
|
-
},
|
|
403
|
-
snapshot() {
|
|
404
|
-
return {
|
|
405
|
-
records,
|
|
406
|
-
reservedRecordBytes: records * CLI_ENGINEERING_LIMITS.recordBytes,
|
|
407
|
-
peakRecords,
|
|
408
|
-
peakReservedRecordBytes: peakRecords * CLI_ENGINEERING_LIMITS.recordBytes,
|
|
409
|
-
activeRequests,
|
|
410
|
-
requestBytes,
|
|
411
|
-
peakRequests,
|
|
412
|
-
peakRequestBytes,
|
|
413
|
-
dropped,
|
|
414
|
-
metricSeries: metricRegistry.size
|
|
415
|
-
};
|
|
416
|
-
}
|
|
417
|
-
});
|
|
418
|
-
}
|
|
419
|
-
//#endregion
|
|
420
503
|
//#region src/delivery/requests.ts
|
|
421
504
|
function admittedSerialization(state, delegate) {
|
|
422
505
|
return {
|
|
@@ -564,8 +647,8 @@ const validateEngineeringTelemetryConfig = (config) => {
|
|
|
564
647
|
let url;
|
|
565
648
|
try {
|
|
566
649
|
url = new URL(config.host);
|
|
567
|
-
} catch {
|
|
568
|
-
throw new TypeError("engineering telemetry host must be an absolute HTTPS URL");
|
|
650
|
+
} catch (cause) {
|
|
651
|
+
throw new TypeError("engineering telemetry host must be an absolute HTTPS URL", { cause });
|
|
569
652
|
}
|
|
570
653
|
if (url.protocol !== "https:" || url.username.length > 0 || url.password.length > 0 || url.pathname !== "/" || url.search.length > 0 || url.hash.length > 0 || !(url.hostname === "posthog.com" || url.hostname.endsWith(".posthog.com"))) throw new TypeError("engineering telemetry host must be a credential-free HTTPS origin");
|
|
571
654
|
if (!ENGINEERING_CAPXUL_ENV_SET.has(config.capxulEnv)) throw new TypeError("engineering telemetry capxulEnv is not canonical");
|
|
@@ -625,7 +708,8 @@ const MINIMAL_CORRELATIONS = /* @__PURE__ */ new Set([
|
|
|
625
708
|
"correlation_id",
|
|
626
709
|
"journey_id",
|
|
627
710
|
"invocation_id",
|
|
628
|
-
"posthogDistinctId"
|
|
711
|
+
"posthogDistinctId",
|
|
712
|
+
"failure_correlation_id"
|
|
629
713
|
]);
|
|
630
714
|
const MINIMAL_METRIC_LABELS = /* @__PURE__ */ new Set([
|
|
631
715
|
"command",
|
|
@@ -638,6 +722,12 @@ const MINIMAL_METRIC_LABELS = /* @__PURE__ */ new Set([
|
|
|
638
722
|
const MINIMAL_METRIC_NAMES = /* @__PURE__ */ new Set(["capxul.cli.command.count", "capxul.cli.command.duration"]);
|
|
639
723
|
const traceId = (value) => typeof value === "string" && /^(?!0+$)[0-9a-f]{32}$/u.test(value);
|
|
640
724
|
const spanId = (value) => typeof value === "string" && /^(?!0+$)[0-9a-f]{16}$/u.test(value);
|
|
725
|
+
/**
|
|
726
|
+
* A correlation id the minimal export carries: an identifier with no `/`, not
|
|
727
|
+
* `0x` hex, and unchanged by the secret mask. The CLI quotes a backend
|
|
728
|
+
* correlation id as its `ref` only when it passes, so the ref finds a row.
|
|
729
|
+
*/
|
|
730
|
+
const minimalCorrelationId = (value) => typeof value === "string" && /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u.test(value) && !/^0x[0-9a-f]+$/iu.test(value) && redactSecrets(value) === value;
|
|
641
731
|
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";
|
|
642
732
|
const MINIMAL_ENUM_VALUES = /* @__PURE__ */ new Map([
|
|
643
733
|
["outcome", [
|
|
@@ -649,6 +739,8 @@ const MINIMAL_ENUM_VALUES = /* @__PURE__ */ new Map([
|
|
|
649
739
|
]],
|
|
650
740
|
["mode", FAILURE_MODES],
|
|
651
741
|
["failure_mode", FAILURE_MODES],
|
|
742
|
+
["failure_class", FAILURE_CLASSES],
|
|
743
|
+
["problem", PROVIDER_PROBLEMS],
|
|
652
744
|
["capxul_env", ENGINEERING_CAPXUL_ENVS],
|
|
653
745
|
["producer", ["browser", "server"]],
|
|
654
746
|
["command", CLI_COMMAND_NAMES],
|
|
@@ -677,7 +769,7 @@ const MINIMAL_ENUM_VALUES = /* @__PURE__ */ new Map([
|
|
|
677
769
|
"healStaleCache",
|
|
678
770
|
"walletReady"
|
|
679
771
|
]],
|
|
680
|
-
["provider", [
|
|
772
|
+
["provider", [...PROVIDERS, "oidc"]],
|
|
681
773
|
["cli_identity_kind", ["anonymous", "identified"]],
|
|
682
774
|
["output_mode", ["human", "json"]],
|
|
683
775
|
["setup_status", ORGANIZATION_SETUP_STATUSES],
|
|
@@ -696,25 +788,36 @@ const MINIMAL_ENUM_VALUES = /* @__PURE__ */ new Map([
|
|
|
696
788
|
const MINIMAL_CODE_KEYS = /* @__PURE__ */ new Set([
|
|
697
789
|
"error.code",
|
|
698
790
|
"error_code",
|
|
791
|
+
"error_kind",
|
|
699
792
|
"failure_code",
|
|
700
|
-
"refusal_code"
|
|
701
|
-
"capxul_error_code"
|
|
793
|
+
"refusal_code"
|
|
702
794
|
]);
|
|
703
795
|
const MINIMAL_VERSION_KEYS = /* @__PURE__ */ new Set([
|
|
704
796
|
"sdk_version",
|
|
705
797
|
"cli_version",
|
|
706
798
|
"release"
|
|
707
799
|
]);
|
|
800
|
+
/** The failure row's identifiers that the `none` text policy lets through (ADR-0039 D6). */
|
|
801
|
+
const MINIMAL_FAILURE_IDENTIFIERS = /* @__PURE__ */ new Set([
|
|
802
|
+
"provider_operation",
|
|
803
|
+
"provider_code",
|
|
804
|
+
"field"
|
|
805
|
+
]);
|
|
806
|
+
/** What raised a failure: its layer (`convex`, `sdk`), and a CLI defect's error name. */
|
|
807
|
+
const MINIMAL_FAILURE_ORIGINS = /* @__PURE__ */ new Set(["failure_layer", "failure_error_name"]);
|
|
708
808
|
function minimalStringValue(key, text) {
|
|
709
809
|
if (key === "operation") return isCapxulOperation(text);
|
|
710
810
|
if (key === "organization_id") return /^org_[0-9A-Za-z]+$/u.test(text);
|
|
711
811
|
if (MINIMAL_CODE_KEYS.has(key)) return MINIMAL_ERROR_CODES.has(text);
|
|
712
812
|
if (MINIMAL_VERSION_KEYS.has(key)) return /^[A-Za-z0-9][A-Za-z0-9._+@-]{0,127}$/u.test(text);
|
|
813
|
+
if (MINIMAL_FAILURE_IDENTIFIERS.has(key)) return /^[A-Za-z0-9_][A-Za-z0-9_.:/[\]-]{0,127}$/u.test(text) && redactSecrets(text) === text;
|
|
814
|
+
if (MINIMAL_FAILURE_ORIGINS.has(key)) return /^[A-Za-z_][A-Za-z0-9_.-]{0,63}$/u.test(text) && redactSecrets(text) === text;
|
|
815
|
+
if (key === "request_id") return /^[0-9a-f]{16}$/u.test(text);
|
|
713
816
|
if (key === "trace_id") return traceId(text);
|
|
714
817
|
if (key === "span_id" || key === "parent_span_id") return spanId(text);
|
|
715
818
|
const values = MINIMAL_ENUM_VALUES.get(key);
|
|
716
819
|
if (values !== void 0) return values.includes(text);
|
|
717
|
-
return MINIMAL_CORRELATIONS.has(key) &&
|
|
820
|
+
return MINIMAL_CORRELATIONS.has(key) && minimalCorrelationId(text);
|
|
718
821
|
}
|
|
719
822
|
/** A named privacy projection, separate from the default credential-only redaction policy. */
|
|
720
823
|
function minimalAttributes(attributes, metric = false) {
|
|
@@ -1033,4 +1136,4 @@ const makeEngineeringTelemetryLayer = (config) => {
|
|
|
1033
1136
|
});
|
|
1034
1137
|
};
|
|
1035
1138
|
//#endregion
|
|
1036
|
-
export { CLI_ENGINEERING_LIMITS, ENGINEERING_CAPXUL_ENVS, flushEngineeringTelemetry, logEngineeringInfo, makeCliEngineeringBudget, makeEngineeringTelemetryLayer, makeLeakSafeEngineeringTracer, postHogOtlpEndpoints, traceHeaderFilter };
|
|
1139
|
+
export { CLI_ENGINEERING_LIMITS, ENGINEERING_CAPXUL_ENVS, flushEngineeringTelemetry, logEngineeringInfo, makeCliEngineeringBudget, makeEngineeringTelemetryLayer, makeLeakSafeEngineeringTracer, minimalCorrelationId, postHogOtlpEndpoints, traceHeaderFilter };
|