@superblocksteam/telemetry 2.0.129 → 2.0.130-next.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/README.md +27 -12
  2. package/dist/common/index.d.ts +0 -2
  3. package/dist/common/index.d.ts.map +1 -1
  4. package/dist/common/index.js +0 -2
  5. package/dist/common/index.js.map +1 -1
  6. package/dist/index.d.ts +1 -0
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +2 -0
  9. package/dist/index.js.map +1 -1
  10. package/dist/npm-registry/emitter.d.ts +227 -0
  11. package/dist/npm-registry/emitter.d.ts.map +1 -0
  12. package/dist/npm-registry/emitter.js +402 -0
  13. package/dist/npm-registry/emitter.js.map +1 -0
  14. package/dist/npm-registry/index.d.ts +12 -0
  15. package/dist/npm-registry/index.d.ts.map +1 -0
  16. package/dist/npm-registry/index.js +28 -0
  17. package/dist/npm-registry/index.js.map +1 -0
  18. package/dist/{common/sanitize-npm-attributes.d.ts → npm-registry/sanitize.d.ts} +19 -1
  19. package/dist/npm-registry/sanitize.d.ts.map +1 -0
  20. package/dist/{common/sanitize-npm-attributes.js → npm-registry/sanitize.js} +50 -2
  21. package/dist/npm-registry/sanitize.js.map +1 -0
  22. package/dist/npm-registry/spans.d.ts +61 -0
  23. package/dist/npm-registry/spans.d.ts.map +1 -0
  24. package/dist/npm-registry/spans.js +91 -0
  25. package/dist/npm-registry/spans.js.map +1 -0
  26. package/dist-esm/common/index.d.ts +0 -2
  27. package/dist-esm/common/index.d.ts.map +1 -1
  28. package/dist-esm/common/index.js +0 -2
  29. package/dist-esm/common/index.js.map +1 -1
  30. package/dist-esm/index.d.ts +1 -0
  31. package/dist-esm/index.d.ts.map +1 -1
  32. package/dist-esm/index.js +2 -0
  33. package/dist-esm/index.js.map +1 -1
  34. package/dist-esm/npm-registry/emitter.d.ts +227 -0
  35. package/dist-esm/npm-registry/emitter.d.ts.map +1 -0
  36. package/dist-esm/npm-registry/emitter.js +392 -0
  37. package/dist-esm/npm-registry/emitter.js.map +1 -0
  38. package/dist-esm/npm-registry/index.d.ts +12 -0
  39. package/dist-esm/npm-registry/index.d.ts.map +1 -0
  40. package/dist-esm/npm-registry/index.js +12 -0
  41. package/dist-esm/npm-registry/index.js.map +1 -0
  42. package/dist-esm/{common/sanitize-npm-attributes.d.ts → npm-registry/sanitize.d.ts} +19 -1
  43. package/dist-esm/npm-registry/sanitize.d.ts.map +1 -0
  44. package/dist-esm/{common/sanitize-npm-attributes.js → npm-registry/sanitize.js} +49 -2
  45. package/dist-esm/npm-registry/sanitize.js.map +1 -0
  46. package/dist-esm/npm-registry/spans.d.ts +61 -0
  47. package/dist-esm/npm-registry/spans.d.ts.map +1 -0
  48. package/dist-esm/npm-registry/spans.js +86 -0
  49. package/dist-esm/npm-registry/spans.js.map +1 -0
  50. package/package.json +4 -4
  51. package/dist/common/npm-install-metrics.d.ts +0 -88
  52. package/dist/common/npm-install-metrics.d.ts.map +0 -1
  53. package/dist/common/npm-install-metrics.js +0 -140
  54. package/dist/common/npm-install-metrics.js.map +0 -1
  55. package/dist/common/sanitize-npm-attributes.d.ts.map +0 -1
  56. package/dist/common/sanitize-npm-attributes.js.map +0 -1
  57. package/dist-esm/common/npm-install-metrics.d.ts +0 -88
  58. package/dist-esm/common/npm-install-metrics.d.ts.map +0 -1
  59. package/dist-esm/common/npm-install-metrics.js +0 -135
  60. package/dist-esm/common/npm-install-metrics.js.map +0 -1
  61. package/dist-esm/common/sanitize-npm-attributes.d.ts.map +0 -1
  62. package/dist-esm/common/sanitize-npm-attributes.js.map +0 -1
@@ -0,0 +1,402 @@
1
+ "use strict";
2
+ /**
3
+ * NPM Registry Metrics Emitter (APPS-4378 / APPS-4189)
4
+ *
5
+ * One emitter for the whole npm-registry subsystem. It owns every
6
+ * `superblocks.npm.*` metric and centralizes the sanitize-at-emit-boundary
7
+ * discipline: no raw registry hostname, package name, or token can escape via
8
+ * a metric label because every dimension is coerced to a closed enum or a
9
+ * bounded bucket here, the moment before it reaches an OTel instrument.
10
+ *
11
+ * Wiring follows the rest of the npm subsystem: the meter is resolved lazily
12
+ * from the OTel global (`metrics.getMeter`) and instruments are cached and
13
+ * re-bound when the global MeterProvider changes (the typical case is a test
14
+ * installing its own provider for isolation). The emitter is therefore a
15
+ * zero-config process singleton (`npmRegistryEmitter`) — no DI plumbing
16
+ * through AiServiceConfig / NpmRegistryClientDeps.
17
+ *
18
+ * Metrics (all `superblocks.npm.*`, durations in seconds):
19
+ *
20
+ * - `config.lookup_total` counter {result}
21
+ * - `config.lookup_duration_seconds` histogram {result} buckets [0.05,0.25,1,3,10,30]
22
+ * - `install.outcome_total` counter {outcome, configured, registry_host, runner}
23
+ * - `install.duration_seconds`histogram {…same…} buckets [0.5,2,5,15,60,300]
24
+ * - `install.status` gauge {…same…} last-seen 1=success/0=else
25
+ * - `probe.outcome_total` counter {outcome, registry_host}
26
+ * - `probe.duration_seconds` histogram {…same…} buckets [0.1,0.5,1,3,10,30]
27
+ * - `lookup.calls_total` counter {outcome, registry_host}
28
+ * - `lookup.duration_seconds` histogram {…same…} buckets [0.05,0.25,1,3,10,30]
29
+ * - `killswitch.enabled` gauge {} 1 when the feature is suppressed
30
+ *
31
+ * Cardinality budget (documented contract — must not regress without a
32
+ * migration plan; every dashboard / Datadog facet pins on these):
33
+ *
34
+ * - registry_host ≤ 3 (`public_npm | private | unknown`)
35
+ * - install.outcome ≤ 7 (`NPM_INSTALL_OUTCOMES`)
36
+ * - probe.outcome ≤ 5 (`NPM_PROBE_OUTCOMES`)
37
+ * - lookup.outcome ≤ 3 (`NPM_LOOKUP_TOOL_OUTCOMES`)
38
+ * - config.result ≤ 4 (`NPM_CONFIG_LOOKUP_RESULTS`)
39
+ * - configured ≤ 2, runner ≤ 4 (`NPM_INSTALL_RUNNERS`)
40
+ *
41
+ * Worst-case distinct series across every instrument is ≈ 560: the three
42
+ * install instruments (status gauge + counter + histogram) dominate at
43
+ * outcome×configured×registry_host×runner = 7×2×3×4 = 168 each → 504, plus
44
+ * probe 5×3×2=30, config 4×2=8, lookup 3×3×2=18, and the killswitch gauge 1.
45
+ * APPS-4544 raised the runner count from 3 to 4, which is why this grew from
46
+ * ≈430 (adding a runner value must increase the count, not decrease it). Still
47
+ * well under the internal 150K flag and the Datadog billing boundary. Per-pod
48
+ * `host` is stripped at the collector for the `superblocks.npm.*` family so
49
+ * series are aggregated by `service.name`.
50
+ *
51
+ * Each path has its OWN closed outcome enum + `normalize*` coercer because the
52
+ * paths legitimately diverge (probe has `ok`/`invalid_response`, install has
53
+ * `tls_failed`/`lockfile_url_mismatch`, …). `auth_failed` is deliberately a
54
+ * first-class outcome on install/probe — an expected, fast-failing
55
+ * customer-config mode — kept separable from infra failures (`unreachable` /
56
+ * `tls_failed`) so a future alert can page on infra spikes while routing auth
57
+ * to a dashboard. These short names diverge from the long-form `NPM_OUTCOMES`
58
+ * in `./sanitize` (which stays as the audit-event surface): the install path
59
+ * normalizes the long `NpmInstallBlocked.reason` values to the short forms.
60
+ */
61
+ Object.defineProperty(exports, "__esModule", { value: true });
62
+ exports.npmRegistryEmitter = exports.NpmRegistryEmitter = exports.NPM_LOOKUP_TOOL_OUTCOMES = exports.NPM_PROBE_OUTCOMES = exports.NPM_INSTALL_OUTCOMES = exports.NPM_CONFIG_LOOKUP_RESULTS = exports.NPM_INSTALL_RUNNERS = void 0;
63
+ exports.normalizeConfigLookupResult = normalizeConfigLookupResult;
64
+ exports.normalizeInstallOutcome = normalizeInstallOutcome;
65
+ exports.normalizeProbeOutcome = normalizeProbeOutcome;
66
+ exports.normalizeLookupToolOutcome = normalizeLookupToolOutcome;
67
+ exports.recordNpmInstall = recordNpmInstall;
68
+ exports._resetNpmRegistryEmitterForTesting = _resetNpmRegistryEmitterForTesting;
69
+ const api_1 = require("@opentelemetry/api");
70
+ const sanitize_js_1 = require("./sanitize.js");
71
+ /**
72
+ * Closed enum of npm install call sites that emit metrics. One value per call
73
+ * site Superblocks owns; renaming a value breaks every dashboard split on
74
+ * `runner`, so additions/removals need a migration plan.
75
+ *
76
+ * - `install_all` — full install from package.json (no specific
77
+ * packages). Covers both `AppShell.runNpmInstall`
78
+ * and `AppShell.runPackageManagerInstall`.
79
+ * - `install_specific` — `AppShell.installSpecificPackages` (`npm add`).
80
+ * - `prefetch` — `installTemplateModules` (template `node_modules`
81
+ * prefetch during AiService construction).
82
+ * - `auto_upgrade` — `upgradeCliWithPackageManager` in the CLI SDK
83
+ * (`automatic-upgrades.ts`). Owns the dev-server
84
+ * self-upgrade install; a perimeter block here
85
+ * crash-loops the live-edit pod, so the metric is
86
+ * the only signal that distinguishes that from a
87
+ * working upgrade. APPS-4544.
88
+ */
89
+ exports.NPM_INSTALL_RUNNERS = [
90
+ "auto_upgrade",
91
+ "install_all",
92
+ "install_specific",
93
+ "prefetch",
94
+ ];
95
+ const METER_NAME = "@superblocksteam/telemetry/npm-registry";
96
+ const METRIC_CONFIG_LOOKUP = "superblocks.npm.config.lookup_total";
97
+ const METRIC_CONFIG_DURATION = "superblocks.npm.config.lookup_duration_seconds";
98
+ const METRIC_INSTALL_OUTCOME = "superblocks.npm.install.outcome_total";
99
+ const METRIC_INSTALL_DURATION = "superblocks.npm.install.duration_seconds";
100
+ const METRIC_INSTALL_STATUS = "superblocks.npm.install.status";
101
+ const METRIC_PROBE_OUTCOME = "superblocks.npm.probe.outcome_total";
102
+ const METRIC_PROBE_DURATION = "superblocks.npm.probe.duration_seconds";
103
+ const METRIC_LOOKUP_CALLS = "superblocks.npm.lookup.calls_total";
104
+ const METRIC_LOOKUP_DURATION = "superblocks.npm.lookup.duration_seconds";
105
+ const METRIC_KILLSWITCH = "superblocks.npm.killswitch.enabled";
106
+ /**
107
+ * Explicit histogram boundaries (seconds). Coarse on purpose: latency
108
+ * histograms here exist to detect regressions / alert on pathological cases,
109
+ * not for fine percentile work, so 6 finite buckets beat OTel's default 12.
110
+ * The SDK appends an implicit `(last, +∞)` overflow bucket, so values past the
111
+ * top boundary are still counted — the arrays stay finite (no `Infinity`).
112
+ */
113
+ const INSTALL_DURATION_BUCKETS = [0.5, 2, 5, 15, 60, 300];
114
+ const PROBE_DURATION_BUCKETS = [0.1, 0.5, 1, 3, 10, 30];
115
+ const LOOKUP_DURATION_BUCKETS = [0.05, 0.25, 1, 3, 10, 30];
116
+ // Config lookups span sub-ms cache hits to multi-second cold fetches (network
117
+ // round-trip + possible JWT-refresh retry), so reuse the lookup-tool scale.
118
+ const CONFIG_DURATION_BUCKETS = [0.05, 0.25, 1, 3, 10, 30];
119
+ // ---------------------------------------------------------------------------
120
+ // Per-path closed outcome enums + emit-boundary coercers.
121
+ // ---------------------------------------------------------------------------
122
+ /**
123
+ * `config.lookup_total` result. Derived inside `NpmRegistryClient.getConfig`
124
+ * (the only place that knows cache hit vs miss): in-TTL non-empty hit → `hit`;
125
+ * fresh fetch with a configured registry → `miss`; empty / 404 / flag-off →
126
+ * `not_configured`; `stale` / `unreachable` / any throw → `error`.
127
+ */
128
+ exports.NPM_CONFIG_LOOKUP_RESULTS = [
129
+ "hit",
130
+ "miss",
131
+ "not_configured",
132
+ "error",
133
+ ];
134
+ /** Unknown config-lookup states are an error condition, not a new series. */
135
+ function normalizeConfigLookupResult(input) {
136
+ return (exports.NPM_CONFIG_LOOKUP_RESULTS.find((result) => result === input) ?? "error");
137
+ }
138
+ /**
139
+ * `install.outcome_total` outcome. Short forms; the install path normalizes
140
+ * the long `NpmInstallBlocked.reason` values (`registry_auth_failed`,
141
+ * `registry_unreachable`) to these via {@link normalizeInstallOutcome}.
142
+ */
143
+ exports.NPM_INSTALL_OUTCOMES = [
144
+ "success",
145
+ "auth_failed",
146
+ "not_in_registry",
147
+ "unreachable",
148
+ "tls_failed",
149
+ "lockfile_url_mismatch",
150
+ "other",
151
+ ];
152
+ /**
153
+ * Long-form `NpmInstallBlocked.reason` → short install-metric outcome.
154
+ * `NPM_INSTALL_OUTCOMES` above is the canonical list for the metric; a blocked
155
+ * reason that is neither already a short outcome nor aliased here folds to
156
+ * `other` on the metric (the precise reason still rides on the log/audit
157
+ * surface via `NPM_OUTCOMES` in ./sanitize). Add an entry here when a new
158
+ * `NpmInstallBlocked.reason` should get its own metric facet rather than
159
+ * collapsing to `other`.
160
+ */
161
+ const INSTALL_OUTCOME_ALIASES = new Map([
162
+ ["registry_auth_failed", "auth_failed"],
163
+ ["registry_unreachable", "unreachable"],
164
+ ]);
165
+ function normalizeInstallOutcome(input) {
166
+ if (input == null)
167
+ return "other";
168
+ return (exports.NPM_INSTALL_OUTCOMES.find((outcome) => outcome === input) ??
169
+ INSTALL_OUTCOME_ALIASES.get(input) ??
170
+ "other");
171
+ }
172
+ /** `probe.outcome_total` outcome. Mapped from the server `ProbeResult`. */
173
+ exports.NPM_PROBE_OUTCOMES = [
174
+ "ok",
175
+ "auth_failed",
176
+ "unreachable",
177
+ "tls_failed",
178
+ "invalid_response",
179
+ ];
180
+ /**
181
+ * Unknown → `unreachable`: the typed call site maps the closed `ProbeReason`
182
+ * exhaustively, so this floor only fires on a non-typed (JS) caller, where
183
+ * "we couldn't classify the probe" is safest reported as a connectivity
184
+ * failure rather than silently dropped or minted as a new series.
185
+ */
186
+ function normalizeProbeOutcome(input) {
187
+ return (exports.NPM_PROBE_OUTCOMES.find((outcome) => outcome === input) ?? "unreachable");
188
+ }
189
+ /** `lookup.calls_total` outcome (the Clark `lookupNpmPackage` tool). */
190
+ exports.NPM_LOOKUP_TOOL_OUTCOMES = [
191
+ "in_registry",
192
+ "not_in_registry",
193
+ "error",
194
+ ];
195
+ /** Auth / unreachable / fail-closed / throw all collapse to `error` here. */
196
+ function normalizeLookupToolOutcome(input) {
197
+ return (exports.NPM_LOOKUP_TOOL_OUTCOMES.find((outcome) => outcome === input) ?? "error");
198
+ }
199
+ const SECONDS_PER_MS = 1 / 1000;
200
+ const toSeconds = (durationMs) => Math.max(0, durationMs) * SECONDS_PER_MS;
201
+ /**
202
+ * The npm-registry metrics emitter. Constructed once as the
203
+ * {@link npmRegistryEmitter} singleton; no `Meter` is injected — instruments
204
+ * resolve lazily from the OTel global and re-bind on provider change.
205
+ */
206
+ class NpmRegistryEmitter {
207
+ /**
208
+ * Last-seen install status keyed by the attribute tuple. The observable
209
+ * gauge reads this each collection cycle. Every key component is coerced to
210
+ * a closed enum / bounded bucket at the emit boundary
211
+ * (`normalizeInstallOutcome`, `bucketNpmRegistryHost`, the `NpmInstallRunner`
212
+ * union, a boolean), so the map is **bounded by construction** at
213
+ * runner(4) × configured(2) × registry_host(3) × outcome(7) = 168 entries —
214
+ * it cannot grow with traffic, only with deliberate enum changes. Entries
215
+ * persist until process exit / `_resetForTesting` (a "last-seen for
216
+ * dashboards" gauge must not drop a combo and lie about the most recent
217
+ * outcome); no eviction is needed at this bound. The cardinality regression
218
+ * test pins the resulting series count.
219
+ */
220
+ lastInstallStatus = new Map();
221
+ /** Returns `true` when the kill-switch is asserting (feature suppressed). */
222
+ killswitchState;
223
+ cachedMeter;
224
+ cachedInstruments;
225
+ /**
226
+ * Lazy instrument factory. Re-creates instruments and re-registers the
227
+ * observable-gauge callbacks when the global MeterProvider changes (a stale
228
+ * instrument bound to a replaced provider silently drops data).
229
+ */
230
+ getInstruments() {
231
+ const meter = api_1.metrics.getMeter(METER_NAME);
232
+ if (meter === this.cachedMeter && this.cachedInstruments) {
233
+ return this.cachedInstruments;
234
+ }
235
+ this.cachedMeter = meter;
236
+ this.cachedInstruments = {
237
+ configCounter: meter.createCounter(METRIC_CONFIG_LOOKUP, {
238
+ description: "Count of NpmRegistryClient.getConfig outcomes (cache hit/miss, not-configured, error).",
239
+ unit: "{lookup}",
240
+ }),
241
+ configHistogram: meter.createHistogram(METRIC_CONFIG_DURATION, {
242
+ description: "Duration of NpmRegistryClient.getConfig resolutions (cache hits are sub-ms; misses include the network fetch).",
243
+ unit: "s",
244
+ advice: { explicitBucketBoundaries: CONFIG_DURATION_BUCKETS },
245
+ }),
246
+ installCounter: meter.createCounter(METRIC_INSTALL_OUTCOME, {
247
+ description: "Count of npm install outcomes from Superblocks-owned paths.",
248
+ unit: "{install}",
249
+ }),
250
+ installHistogram: meter.createHistogram(METRIC_INSTALL_DURATION, {
251
+ description: "Duration of npm install invocations.",
252
+ unit: "s",
253
+ advice: { explicitBucketBoundaries: INSTALL_DURATION_BUCKETS },
254
+ }),
255
+ probeCounter: meter.createCounter(METRIC_PROBE_OUTCOME, {
256
+ description: "Count of org npm-registry verify-probe outcomes.",
257
+ unit: "{probe}",
258
+ }),
259
+ probeHistogram: meter.createHistogram(METRIC_PROBE_DURATION, {
260
+ description: "Duration of org npm-registry verify probes.",
261
+ unit: "s",
262
+ advice: { explicitBucketBoundaries: PROBE_DURATION_BUCKETS },
263
+ }),
264
+ lookupCounter: meter.createCounter(METRIC_LOOKUP_CALLS, {
265
+ description: "Count of Clark lookupNpmPackage tool outcomes.",
266
+ unit: "{call}",
267
+ }),
268
+ lookupHistogram: meter.createHistogram(METRIC_LOOKUP_DURATION, {
269
+ description: "Duration of Clark lookupNpmPackage tool calls.",
270
+ unit: "s",
271
+ advice: { explicitBucketBoundaries: LOOKUP_DURATION_BUCKETS },
272
+ }),
273
+ };
274
+ const statusGauge = meter.createObservableGauge(METRIC_INSTALL_STATUS, {
275
+ description: "Last-seen npm install status (1 = success, 0 = anything else) per runner+configured+registry+outcome. For at-a-glance dashboards.",
276
+ });
277
+ statusGauge.addCallback((result) => {
278
+ // Observable callbacks run on the metric export thread; an uncaught
279
+ // throw here aborts the whole collection cycle and drops every metric.
280
+ // Never let one escape.
281
+ try {
282
+ for (const { value, attrs } of this.lastInstallStatus.values()) {
283
+ result.observe(value, attrs);
284
+ }
285
+ }
286
+ catch {
287
+ // Swallow: a metrics callback must not crash collection.
288
+ }
289
+ });
290
+ const killswitchGauge = meter.createObservableGauge(METRIC_KILLSWITCH, {
291
+ description: "1 when the npm-registry feature is suppressed (LD flag off / kill-switch active), 0 otherwise. One series per process.",
292
+ });
293
+ killswitchGauge.addCallback((result) => {
294
+ // Same export-thread safety as the status gauge: if the kill-switch
295
+ // state source throws (e.g. a flag client is momentarily unavailable),
296
+ // don't take down the whole npm-registry metric stream with it.
297
+ try {
298
+ if (this.killswitchState) {
299
+ result.observe(this.killswitchState() ? 1 : 0);
300
+ }
301
+ }
302
+ catch {
303
+ // Swallow: a metrics callback must not crash collection.
304
+ }
305
+ });
306
+ return this.cachedInstruments;
307
+ }
308
+ /**
309
+ * Emit one `config.lookup_total` + `config.lookup_duration_seconds`
310
+ * observation. `durationMs` is the time `getConfig` spent resolving — sub-ms
311
+ * for a cache hit, the network round-trip (+ any JWT-refresh retry) for a
312
+ * miss — so dashboards can tell fast cache hits from slow cold fetches.
313
+ */
314
+ recordConfigLookup(result, durationMs) {
315
+ const attrs = {
316
+ result: normalizeConfigLookupResult(result),
317
+ };
318
+ const { configCounter, configHistogram } = this.getInstruments();
319
+ configCounter.add(1, attrs);
320
+ configHistogram.record(toSeconds(durationMs), attrs);
321
+ }
322
+ /**
323
+ * Emit the install outcome counter + duration histogram + last-seen status
324
+ * gauge, and return the matching span-attribute pair for `llmobs.annotate`.
325
+ * Safe to call from a `finally` block.
326
+ */
327
+ recordInstall(input) {
328
+ const { installCounter, installHistogram } = this.getInstruments();
329
+ const registry_host = (0, sanitize_js_1.bucketNpmRegistryHost)(input.registryHost);
330
+ const outcome = normalizeInstallOutcome(input.outcome);
331
+ const attrs = {
332
+ runner: input.runner,
333
+ outcome,
334
+ configured: input.configured,
335
+ registry_host,
336
+ };
337
+ installCounter.add(1, attrs);
338
+ installHistogram.record(toSeconds(input.durationMs), attrs);
339
+ const key = `${attrs.runner}|${attrs.configured}|${attrs.registry_host}|${attrs.outcome}`;
340
+ this.lastInstallStatus.set(key, {
341
+ value: outcome === "success" ? 1 : 0,
342
+ attrs,
343
+ });
344
+ return { "npm.registry_host": registry_host, "npm.outcome": outcome };
345
+ }
346
+ /** Emit one `probe.outcome_total` + `probe.duration_seconds` observation. */
347
+ recordProbe(input) {
348
+ const { probeCounter, probeHistogram } = this.getInstruments();
349
+ const attrs = {
350
+ outcome: normalizeProbeOutcome(input.outcome),
351
+ registry_host: (0, sanitize_js_1.bucketNpmRegistryHost)(input.registryHost),
352
+ };
353
+ probeCounter.add(1, attrs);
354
+ probeHistogram.record(toSeconds(input.durationMs), attrs);
355
+ }
356
+ /** Emit one `lookup.calls_total` + `lookup.duration_seconds` observation. */
357
+ recordLookupTool(input) {
358
+ const { lookupCounter, lookupHistogram } = this.getInstruments();
359
+ const attrs = {
360
+ outcome: normalizeLookupToolOutcome(input.outcome),
361
+ registry_host: (0, sanitize_js_1.bucketNpmRegistryHost)(input.registryHost),
362
+ };
363
+ lookupCounter.add(1, attrs);
364
+ lookupHistogram.record(toSeconds(input.durationMs), attrs);
365
+ }
366
+ /**
367
+ * Register the kill-switch state source. `getCurrentState` returns `true`
368
+ * when the feature is suppressed (LD flag off). Idempotent — the latest
369
+ * source wins; the observable gauge reads it on each scrape. Calling this
370
+ * also ensures the instruments (and thus the gauge callback) are wired.
371
+ */
372
+ setKillswitch(getCurrentState) {
373
+ this.killswitchState = getCurrentState;
374
+ this.getInstruments();
375
+ }
376
+ /**
377
+ * Test-only: drop cached instruments, last-seen status, and the kill-switch
378
+ * source so the next emit re-binds to a freshly-installed test provider.
379
+ */
380
+ _resetForTesting() {
381
+ this.cachedMeter = undefined;
382
+ this.cachedInstruments = undefined;
383
+ this.lastInstallStatus.clear();
384
+ this.killswitchState = undefined;
385
+ }
386
+ }
387
+ exports.NpmRegistryEmitter = NpmRegistryEmitter;
388
+ /** Process-wide singleton. All npm-registry call sites emit through this. */
389
+ exports.npmRegistryEmitter = new NpmRegistryEmitter();
390
+ /**
391
+ * Back-compat free function for the install path (APPS-4189 call sites in
392
+ * `shell.ts` / `template-renderer.ts`). Delegates to the singleton and returns
393
+ * the span-attribute pair for `llmobs.annotate`.
394
+ */
395
+ function recordNpmInstall(input) {
396
+ return exports.npmRegistryEmitter.recordInstall(input);
397
+ }
398
+ /** Test-only reset for the singleton (see {@link NpmRegistryEmitter._resetForTesting}). */
399
+ function _resetNpmRegistryEmitterForTesting() {
400
+ exports.npmRegistryEmitter._resetForTesting();
401
+ }
402
+ //# sourceMappingURL=emitter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"emitter.js","sourceRoot":"","sources":["../../src/npm-registry/emitter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * NPM Registry Telemetry
3
+ *
4
+ * Sanitization helpers, the unified metrics emitter, and the named OTel spans
5
+ * for the npm-registry subsystem (config fetch, install, verify probe, the
6
+ * Clark lookup tool, CLI npmrc sync). Re-exported from the package root, so
7
+ * callers import from `@superblocksteam/telemetry` rather than this subpath.
8
+ */
9
+ export * from "./sanitize.js";
10
+ export * from "./emitter.js";
11
+ export * from "./spans.js";
12
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/npm-registry/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC"}
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ /**
3
+ * NPM Registry Telemetry
4
+ *
5
+ * Sanitization helpers, the unified metrics emitter, and the named OTel spans
6
+ * for the npm-registry subsystem (config fetch, install, verify probe, the
7
+ * Clark lookup tool, CLI npmrc sync). Re-exported from the package root, so
8
+ * callers import from `@superblocksteam/telemetry` rather than this subpath.
9
+ */
10
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ var desc = Object.getOwnPropertyDescriptor(m, k);
13
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
14
+ desc = { enumerable: true, get: function() { return m[k]; } };
15
+ }
16
+ Object.defineProperty(o, k2, desc);
17
+ }) : (function(o, m, k, k2) {
18
+ if (k2 === undefined) k2 = k;
19
+ o[k2] = m[k];
20
+ }));
21
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
22
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
23
+ };
24
+ Object.defineProperty(exports, "__esModule", { value: true });
25
+ __exportStar(require("./sanitize.js"), exports);
26
+ __exportStar(require("./emitter.js"), exports);
27
+ __exportStar(require("./spans.js"), exports);
28
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/npm-registry/index.ts"],"names":[],"mappings":";AAAA;;;;;;;GAOG;;;;;;;;;;;;;;;;AAEH,gDAA8B;AAC9B,+CAA6B;AAC7B,6CAA2B"}
@@ -43,6 +43,24 @@ export type NpmRegistryBucket = (typeof NPM_REGISTRY_BUCKETS)[number];
43
43
  * the false-negative of suppressing private-registry behavior in telemetry.
44
44
  */
45
45
  export declare function bucketNpmRegistryHost(input: string | null | undefined): NpmRegistryBucket;
46
+ /**
47
+ * Audit/log-destination form of `registry_host`. Unlike the metric/span
48
+ * path (which buckets to `public_npm | private | unknown` so a raw customer
49
+ * hostname never reaches a shared, cross-tenant destination), logs are
50
+ * per-org and retained internally, so on-call needs the **raw** hostname for
51
+ * forensic value ("which registry did org X actually hit?"). This helper is
52
+ * the log-boundary guard: it returns the host verbatim EXCEPT when it
53
+ *
54
+ * - matches a known-secret value shape (`containsForbiddenPattern`) — a
55
+ * developer-error backstop against a token smuggled in via a host arg, or
56
+ * - contains an ASCII C0 control character or DEL — a log-injection /
57
+ * line-spoofing payload,
58
+ *
59
+ * in which case it returns `"invalid"`. null/blank → `"unknown"`. The value
60
+ * is trimmed but otherwise unchanged (not lowercased): the host is the
61
+ * forensic signal and case is preserved as attempted.
62
+ */
63
+ export declare function auditNpmRegistryHost(input: string | null | undefined): string;
46
64
  /**
47
65
  * Audit/log-destination form of the `package_name` attribute. Returns the
48
66
  * lowercased input verbatim when it parses as an npm name AND does not
@@ -153,4 +171,4 @@ export declare function npmAttributesForAudit(input: NpmAttributesInput): NpmAud
153
171
  * for triage; the host identity is what we must drop.
154
172
  */
155
173
  export declare function redactNpmRegistryHostsFromText(input: string): string;
156
- //# sourceMappingURL=sanitize-npm-attributes.d.ts.map
174
+ //# sourceMappingURL=sanitize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sanitize.d.ts","sourceRoot":"","sources":["../../src/npm-registry/sanitize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAIH,eAAO,MAAM,oBAAoB,+CAIvB,CAAC;AACX,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,CAAC,CAAC;AAKtE;;;;;;;;;;;;;GAaG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAC/B,iBAAiB,CAkBnB;AAkBD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAS7E;AAeD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAC/B,MAAM,CAWR;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,wBAAwB,sDAK3B,CAAC;AACX,MAAM,MAAM,oBAAoB,GAAG,CAAC,OAAO,wBAAwB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE7E,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAChC,cAAc,EAAE,iBAAiB,GAChC,oBAAoB,CAetB;AAED;;;;;;GAMG;AACH,eAAO,MAAM,YAAY,yIAQf,CAAC;AACX,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,CAAC,CAAC;AAIvD,iEAAiE;AACjE,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAC/B,UAAU,CAGZ;AAED,MAAM,WAAW,kBAAkB;IACjC,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED,0EAA0E;AAC1E,MAAM,WAAW,mBAAmB;IAClC,aAAa,EAAE,iBAAiB,CAAC;IACjC,mBAAmB,EAAE,oBAAoB,CAAC;IAC1C,OAAO,EAAE,UAAU,CAAC;CACrB;AAED;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC,aAAa,EAAE,iBAAiB,CAAC;IACjC,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,UAAU,CAAC;CACrB;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,kBAAkB,GACxB,mBAAmB,CAUrB;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,kBAAkB,GACxB,kBAAkB,CAMpB;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,8BAA8B,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CA0BpE"}
@@ -30,13 +30,14 @@
30
30
  Object.defineProperty(exports, "__esModule", { value: true });
31
31
  exports.NPM_OUTCOMES = exports.NPM_PACKAGE_NAME_BUCKETS = exports.NPM_REGISTRY_BUCKETS = void 0;
32
32
  exports.bucketNpmRegistryHost = bucketNpmRegistryHost;
33
+ exports.auditNpmRegistryHost = auditNpmRegistryHost;
33
34
  exports.sanitizeNpmPackageName = sanitizeNpmPackageName;
34
35
  exports.bucketNpmPackageName = bucketNpmPackageName;
35
36
  exports.normalizeNpmOutcome = normalizeNpmOutcome;
36
37
  exports.npmAttributesForMetric = npmAttributesForMetric;
37
38
  exports.npmAttributesForAudit = npmAttributesForAudit;
38
39
  exports.redactNpmRegistryHostsFromText = redactNpmRegistryHostsFromText;
39
- const tier2_traces_js_1 = require("./contracts/tier2-traces.js");
40
+ const tier2_traces_js_1 = require("../common/contracts/tier2-traces.js");
40
41
  exports.NPM_REGISTRY_BUCKETS = [
41
42
  "public_npm",
42
43
  "private",
@@ -80,6 +81,53 @@ function bucketNpmRegistryHost(input) {
80
81
  return "unknown";
81
82
  return PUBLIC_NPM_HOSTS.has(host) ? "public_npm" : "private";
82
83
  }
84
+ /**
85
+ * Control characters (ASCII C0 range + DEL) that must never reach a log
86
+ * line: a newline/carriage-return lets a hostile value forge a second log
87
+ * record, and other C0 bytes corrupt structured-log parsers.
88
+ */
89
+ function hasControlChar(value) {
90
+ // ASCII C0 controls (0x00-0x1F) + DEL (0x7F). A regex literal holding these
91
+ // raw bytes is invisible in source and trips no-control-regex, so we scan
92
+ // code points instead.
93
+ for (let i = 0; i < value.length; i++) {
94
+ const code = value.charCodeAt(i);
95
+ if (code <= 0x1f || code === 0x7f)
96
+ return true;
97
+ }
98
+ return false;
99
+ }
100
+ /**
101
+ * Audit/log-destination form of `registry_host`. Unlike the metric/span
102
+ * path (which buckets to `public_npm | private | unknown` so a raw customer
103
+ * hostname never reaches a shared, cross-tenant destination), logs are
104
+ * per-org and retained internally, so on-call needs the **raw** hostname for
105
+ * forensic value ("which registry did org X actually hit?"). This helper is
106
+ * the log-boundary guard: it returns the host verbatim EXCEPT when it
107
+ *
108
+ * - matches a known-secret value shape (`containsForbiddenPattern`) — a
109
+ * developer-error backstop against a token smuggled in via a host arg, or
110
+ * - contains an ASCII C0 control character or DEL — a log-injection /
111
+ * line-spoofing payload,
112
+ *
113
+ * in which case it returns `"invalid"`. null/blank → `"unknown"`. The value
114
+ * is trimmed but otherwise unchanged (not lowercased): the host is the
115
+ * forensic signal and case is preserved as attempted.
116
+ */
117
+ function auditNpmRegistryHost(input) {
118
+ if (input == null)
119
+ return "unknown";
120
+ const trimmed = input.trim();
121
+ if (trimmed === "")
122
+ return "unknown";
123
+ // Secret-shape check runs on the trimmed (case-preserved) value: AKIA/JWT
124
+ // patterns are case-sensitive and must be caught before they reach a log.
125
+ if ((0, tier2_traces_js_1.containsForbiddenPattern)(trimmed))
126
+ return "invalid";
127
+ if (hasControlChar(trimmed))
128
+ return "invalid";
129
+ return trimmed;
130
+ }
83
131
  /** npm package name spec (lowercased): optional `@scope/` prefix + name body. */
84
132
  const VALID_PACKAGE_NAME_RE = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/;
85
133
  /**
@@ -265,4 +313,4 @@ function redactNpmRegistryHostsFromText(input) {
265
313
  out = out.replace(/\bconnect\s+(E[A-Z]+)\s+([0-9a-fA-F.:]+)(?::\d+)?/g, (_m, code) => `connect ${code} <host>`);
266
314
  return out;
267
315
  }
268
- //# sourceMappingURL=sanitize-npm-attributes.js.map
316
+ //# sourceMappingURL=sanitize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sanitize.js","sourceRoot":"","sources":["../../src/npm-registry/sanitize.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * NPM Registry Spans (APPS-4378)
3
+ *
4
+ * OTel-native `startActiveSpan` helpers for the four named npm-registry spans.
5
+ * Using the global tracer (`trace.getTracer`) means LLMObs interop is
6
+ * automatic: a span started inside an `@llmobs.decorate`'d context becomes a
7
+ * child of that task span (shared global tracer provider); outside any LLMObs
8
+ * context (server verify, CLI bootstrap) it is a root span.
9
+ *
10
+ * `org_id` rides on spans (per-trace context), never on a metric label. Raw
11
+ * registry hostnames are bucketed via {@link bucketNpmRegistryHost} before
12
+ * they reach a span attribute, matching the metric path — the one exception
13
+ * is the structured-log path, which keeps the raw host (see
14
+ * {@link auditNpmRegistryHost} in ./sanitize).
15
+ */
16
+ import { type Attributes, type Span } from "@opentelemetry/api";
17
+ /** The four named spans (APPS-4378). */
18
+ export declare const NPM_REGISTRY_SPAN: {
19
+ readonly VERIFY: "npm_registry.verify";
20
+ readonly LOOKUP_PACKAGE: "npm_registry.lookup_package";
21
+ readonly PREPARE_FOR_PRIVATE_REGISTRY: "npm_registry.prepare_for_private_registry";
22
+ readonly SYNC_HOME_NPMRC: "npm_registry.sync_home_npmrc";
23
+ };
24
+ /**
25
+ * Canonical span-attribute keys, shared across the four spans so the
26
+ * dimension names never drift between call sites.
27
+ */
28
+ export declare const NPM_REGISTRY_SPAN_ATTR: {
29
+ readonly ORG_ID: "org_id";
30
+ /** Bucketed (`public_npm | private | unknown`) — never the raw host. */
31
+ readonly REGISTRY_HOST: "registry_host";
32
+ readonly OUTCOME: "outcome";
33
+ readonly DURATION_MS: "duration_ms";
34
+ /** Richer `NpmRegistryFetchResult.source` for drill-down. */
35
+ readonly SOURCE: "source";
36
+ readonly REGISTRY_CONFIGURED: "registry_configured";
37
+ /** Tri-valued: `true | false | "unknown"`. */
38
+ readonly ALLOW_INSTALL_SCRIPTS: "allow_install_scripts";
39
+ readonly PACKAGE_NAME_BUCKET: "package_name_bucket";
40
+ /** Span-attribute ONLY — raw byte counts are unbounded, never a metric label. */
41
+ readonly PACKAGE_UNPACKED_SIZE_BYTES: "package_unpacked_size_bytes";
42
+ };
43
+ /**
44
+ * Start an active npm-registry span over the global tracer, run `fn`, and
45
+ * end the span in `finally`. On throw, the error is recorded
46
+ * (`span.recordException`) and the span status set to ERROR before the throw
47
+ * propagates. `fn` receives the span so it can attach final attributes
48
+ * (outcome / duration) once the operation resolves.
49
+ */
50
+ export declare function withNpmRegistrySpan<T>(spanName: string, initialAttributes: Attributes, fn: (span: Span) => Promise<T>): Promise<T>;
51
+ /**
52
+ * Coerce a registry-reported `dist.unpackedSize` to a span-safe value: a
53
+ * finite, non-negative number, else `undefined`. A hostile or buggy registry
54
+ * cannot smuggle a non-numeric byte count onto the span through this guard.
55
+ *
56
+ * Callers MUST omit the attribute when this returns `undefined` rather than
57
+ * calling `setAttribute(key, undefined)` (some OTel exporters mishandle
58
+ * undefined values). The lookup call site guards with `if (size !== undefined)`.
59
+ */
60
+ export declare function npmPackageUnpackedSizeBytes(raw: unknown): number | undefined;
61
+ //# sourceMappingURL=spans.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spans.d.ts","sourceRoot":"","sources":["../../src/npm-registry/spans.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EACL,KAAK,UAAU,EACf,KAAK,IAAI,EAGV,MAAM,oBAAoB,CAAC;AAK5B,wCAAwC;AACxC,eAAO,MAAM,iBAAiB;;;;;CAKpB,CAAC;AAEX;;;GAGG;AACH,eAAO,MAAM,sBAAsB;;IAEjC,wEAAwE;;;;IAIxE,6DAA6D;;;IAG7D,8CAA8C;;;IAG9C,iFAAiF;;CAEzE,CAAC;AAEX;;;;;;GAMG;AACH,wBAAsB,mBAAmB,CAAC,CAAC,EACzC,QAAQ,EAAE,MAAM,EAChB,iBAAiB,EAAE,UAAU,EAC7B,EAAE,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,GAC7B,OAAO,CAAC,CAAC,CAAC,CAiBZ;AAED;;;;;;;;GAQG;AACH,wBAAgB,2BAA2B,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAI5E"}