@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
package/README.md CHANGED
@@ -151,17 +151,26 @@ Telemetry attributes describing npm install attempts (`registry_host`,
151
151
  `package_name`, `npm.outcome`) ship out of cloud-prem and must be sanitized
152
152
  **at the emit boundary** so customer infrastructure hostnames and registry
153
153
  tokens never appear in exported traces, metrics, logs, or audit events.
154
- Every emitter (P6.2 metrics, P6.3 audit events, any future runner) MUST
155
- pass values through the helpers in
156
- [`common/sanitize-npm-attributes.ts`](src/common/sanitize-npm-attributes.ts);
157
- the dashboard/collector layer is not the right place to sanitize.
154
+ Every emitter (the `NpmRegistryEmitter`, P6.3 audit events, any future
155
+ runner) MUST pass values through the helpers in
156
+ [`npm-registry/sanitize.ts`](src/npm-registry/sanitize.ts); the
157
+ dashboard/collector layer is not the right place to sanitize.
158
+
159
+ The metrics emitter and the four named OTel spans live alongside it in
160
+ [`src/npm-registry/`](src/npm-registry/) (`emitter.ts`, `spans.ts`). The
161
+ emitter defines its own **per-path** short outcome enums
162
+ (`install`/`probe`/`lookup`/`config`, e.g. `auth_failed`) — the long-form
163
+ `NPM_OUTCOMES` below stays as the **audit-event** surface
164
+ (`registry_auth_failed`); the divergence is intentional until an explicit
165
+ audit-schema migration. `registry_host` is bucketed on the metric/span path
166
+ but kept **raw** (guarded by `auditNpmRegistryHost`) on per-org log lines.
158
167
 
159
168
  | Attribute | Rule | Helper |
160
169
  | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------- |
161
170
  | `registry_host` | Bucket to `public_npm` / `private` / `unknown`. Raw hostnames never ship. | `bucketNpmRegistryHost` |
162
171
  | `package_name_bucket` | Metric destinations: bucket to `public` / `private` / `unknown` / `invalid`. Cardinality-safe; never carries the raw name. | `bucketNpmPackageName` (or `npmAttributesForMetric`) |
163
172
  | `package_name` | Audit/log destinations only: emit verbatim (lowercased) if it parses as a valid npm name and is not a known-secret value pattern; else `invalid`. | `sanitizeNpmPackageName` (or `npmAttributesForAudit`) |
164
- | `npm.outcome` | Closed allowlist of 6 values; unknown coerces to `other`. | `normalizeNpmOutcome` |
173
+ | `npm.outcome` | Audit-surface closed allowlist of 7 values; unknown coerces to `other`. (Metric paths use the shorter per-path enums in `emitter.ts`.) | `normalizeNpmOutcome` |
165
174
  | Registry token | Never an attribute. Bucketing strips it from URLs; `tier2-traces` lists token-shaped attribute NAMES as forbidden and `containsForbiddenPattern` rejects token-shaped VALUES (including `npm_<36+>`) smuggled into the package name. | n/a |
166
175
 
167
176
  ### `registry_host`
@@ -238,23 +247,29 @@ argue about the policy.
238
247
 
239
248
  ### `npm.outcome`
240
249
 
241
- Closed enum, allowlist explicit. The first five values match the
242
- structured error from APPS-4179 (P1.2) and APPS-4195 (P1.5); `other` is
243
- the catch-all so dashboards never see free-form strings.
250
+ Closed enum, allowlist explicit. The first values match the structured
251
+ error from APPS-4179 (P1.2) and APPS-4195 (P1.5); `tls_failed` was added in
252
+ APPS-4381; `other` is the catch-all so dashboards never see free-form
253
+ strings.
244
254
 
245
255
  ```
246
256
  success | not_in_registry | registry_unreachable | registry_auth_failed
247
- | lockfile_url_mismatch | other
257
+ | tls_failed | lockfile_url_mismatch | other
248
258
  ```
249
259
 
250
- Coercion happens at the emit boundary never at the dashboard.
260
+ This long-form enum is the **audit-event** surface. The metric emitter
261
+ (APPS-4378) uses shorter per-path enums in
262
+ [`emitter.ts`](src/npm-registry/emitter.ts) — install `auth_failed` /
263
+ `unreachable`, probe `ok` / `invalid_response`, etc. — and normalizes the
264
+ long forms to them. Coercion happens at the emit boundary — never at the
265
+ dashboard.
251
266
 
252
267
  ### Source-of-truth + acceptance
253
268
 
254
269
  **Decision (APPS-4190, signed off in #19574):** `package_name` is destination-dependent — bucketed (`package_name_bucket`) for metric destinations, verbatim-lowercased for audit/log destinations. Future P6.2/P6.3 emitters do NOT re-argue this; tighten the helper instead.
255
270
 
256
- - Helper: [`packages/telemetry/src/common/sanitize-npm-attributes.ts`](src/common/sanitize-npm-attributes.ts)
257
- - Tests: [`sanitize-npm-attributes.test.ts`](src/common/sanitize-npm-attributes.test.ts) — covers each rule + a token-leak regression that asserts no token-bearing input ever appears in output
271
+ - Helper: [`packages/telemetry/src/npm-registry/sanitize.ts`](src/npm-registry/sanitize.ts)
272
+ - Tests: [`sanitize.test.ts`](src/npm-registry/sanitize.test.ts) — covers each rule + a token-leak regression that asserts no token-bearing input ever appears in output
258
273
  - Forbidden-attribute backstop: `npm.token` / `npm_token` / `npm.registry_token` / `registry_token` in `FORBIDDEN_TIER_2_SPAN_ATTRIBUTES`
259
274
  - Ticket: [APPS-4190](https://linear.app/superblocks/issue/APPS-4190) — referenced from downstream tickets (APPS-4189 metrics, APPS-4191 audit events)
260
275
 
@@ -14,7 +14,5 @@ export * from "./guardrails.js";
14
14
  export * from "./contracts/tier2-traces.js";
15
15
  export * from "./log-sanitizer.js";
16
16
  export * from "./normalize-metric-path.js";
17
- export * from "./sanitize-npm-attributes.js";
18
- export * from "./npm-install-metrics.js";
19
17
  export * from "./tier-hints.js";
20
18
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/common/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,cAAc,sBAAsB,CAAC;AACrC,cAAc,0BAA0B,CAAC;AACzC,cAAc,eAAe,CAAC;AAC9B,cAAc,uBAAuB,CAAC;AACtC,cAAc,iBAAiB,CAAC;AAChC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,oBAAoB,CAAC;AACnC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,0BAA0B,CAAC;AACzC,cAAc,iBAAiB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/common/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,cAAc,sBAAsB,CAAC;AACrC,cAAc,0BAA0B,CAAC;AACzC,cAAc,eAAe,CAAC;AAC9B,cAAc,uBAAuB,CAAC;AACtC,cAAc,iBAAiB,CAAC;AAChC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,oBAAoB,CAAC;AACnC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,iBAAiB,CAAC"}
@@ -30,7 +30,5 @@ __exportStar(require("./guardrails.js"), exports);
30
30
  __exportStar(require("./contracts/tier2-traces.js"), exports);
31
31
  __exportStar(require("./log-sanitizer.js"), exports);
32
32
  __exportStar(require("./normalize-metric-path.js"), exports);
33
- __exportStar(require("./sanitize-npm-attributes.js"), exports);
34
- __exportStar(require("./npm-install-metrics.js"), exports);
35
33
  __exportStar(require("./tier-hints.js"), exports);
36
34
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/common/index.ts"],"names":[],"mappings":";AAAA;;;;;;;GAOG;;;;;;;;;;;;;;;;AAEH,uDAAqC;AACrC,2DAAyC;AACzC,gDAA8B;AAC9B,wDAAsC;AACtC,kDAAgC;AAChC,8DAA4C;AAC5C,qDAAmC;AACnC,6DAA2C;AAC3C,+DAA6C;AAC7C,2DAAyC;AACzC,kDAAgC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/common/index.ts"],"names":[],"mappings":";AAAA;;;;;;;GAOG;;;;;;;;;;;;;;;;AAEH,uDAAqC;AACrC,2DAAyC;AACzC,gDAA8B;AAC9B,wDAAsC;AACtC,kDAAgC;AAChC,8DAA4C;AAC5C,qDAAmC;AACnC,6DAA2C;AAC3C,kDAAgC"}
package/dist/index.d.ts CHANGED
@@ -38,6 +38,7 @@
38
38
  */
39
39
  export * from "./types/index.js";
40
40
  export * from "./common/index.js";
41
+ export * from "./npm-registry/index.js";
41
42
  export * from "./llmobs/index.js";
42
43
  export { REDACTED_PLACEHOLDER } from "./common/index.js";
43
44
  export { createSafeLogger, getLoggingPolicy, getMinimumPinoLevelForDeployment, getMinimumPinoLevelFromLoggingPolicy, getTelemetryInstance, initNodeTelemetry, isTelemetryInitialized, LogExportMode, redactForbiddenFields, resetTelemetry, type NodeTelemetryInstance, type LoggingPolicyConfig, type SafeLoggerLike, MetricsClient, type Labels, type CounterHandle, type GaugeHandle, type HistogramHandle, TracedSocket, type TracedSocketConfig, type TracedSocketOptions, } from "./node/index.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAGH,cAAc,kBAAkB,CAAC;AAGjC,cAAc,mBAAmB,CAAC;AAGlC,cAAc,mBAAmB,CAAC;AAIlC,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EACL,gBAAgB,EAChB,gBAAgB,EAChB,gCAAgC,EAChC,oCAAoC,EACpC,oBAAoB,EACpB,iBAAiB,EACjB,sBAAsB,EACtB,aAAa,EACb,qBAAqB,EACrB,cAAc,EACd,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,EACxB,KAAK,cAAc,EACnB,aAAa,EACb,KAAK,MAAM,EACX,KAAK,aAAa,EAClB,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,YAAY,EACZ,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,GACzB,MAAM,iBAAiB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAGH,cAAc,kBAAkB,CAAC;AAGjC,cAAc,mBAAmB,CAAC;AAGlC,cAAc,yBAAyB,CAAC;AAGxC,cAAc,mBAAmB,CAAC;AAIlC,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EACL,gBAAgB,EAChB,gBAAgB,EAChB,gCAAgC,EAChC,oCAAoC,EACpC,oBAAoB,EACpB,iBAAiB,EACjB,sBAAsB,EACtB,aAAa,EACb,qBAAqB,EACrB,cAAc,EACd,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,EACxB,KAAK,cAAc,EACnB,aAAa,EACb,KAAK,MAAM,EACX,KAAK,aAAa,EAClB,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,YAAY,EACZ,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,GACzB,MAAM,iBAAiB,CAAC"}
package/dist/index.js CHANGED
@@ -57,6 +57,8 @@ exports.TracedSocket = exports.MetricsClient = exports.resetTelemetry = exports.
57
57
  __exportStar(require("./types/index.js"), exports);
58
58
  // Re-export common utilities
59
59
  __exportStar(require("./common/index.js"), exports);
60
+ // Re-export npm-registry telemetry (sanitization, metrics emitter, spans)
61
+ __exportStar(require("./npm-registry/index.js"), exports);
60
62
  // Re-export LLMObs tiered telemetry
61
63
  __exportStar(require("./llmobs/index.js"), exports);
62
64
  // Re-export Node.js telemetry functions for convenience
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;;;;;;;;;;;;;;;;;AAEH,kBAAkB;AAClB,mDAAiC;AAEjC,6BAA6B;AAC7B,oDAAkC;AAElC,oCAAoC;AACpC,oDAAkC;AAElC,wDAAwD;AACxD,2FAA2F;AAC3F,8CAAyD;AAAhD,gHAAA,oBAAoB,OAAA;AAC7B,4CAsByB;AArBvB,4GAAA,gBAAgB,OAAA;AAChB,4GAAA,gBAAgB,OAAA;AAChB,4HAAA,gCAAgC,OAAA;AAChC,gIAAA,oCAAoC,OAAA;AACpC,gHAAA,oBAAoB,OAAA;AACpB,6GAAA,iBAAiB,OAAA;AACjB,kHAAA,sBAAsB,OAAA;AACtB,yGAAA,aAAa,OAAA;AACb,iHAAA,qBAAqB,OAAA;AACrB,0GAAA,cAAc,OAAA;AAId,yGAAA,aAAa,OAAA;AAKb,wGAAA,YAAY,OAAA;AAKd,wFAAwF;AACxF,kBAAkB;AAClB,yCAAyC;AACzC,yCAAyC;AACzC,wCAAwC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;;;;;;;;;;;;;;;;;AAEH,kBAAkB;AAClB,mDAAiC;AAEjC,6BAA6B;AAC7B,oDAAkC;AAElC,0EAA0E;AAC1E,0DAAwC;AAExC,oCAAoC;AACpC,oDAAkC;AAElC,wDAAwD;AACxD,2FAA2F;AAC3F,8CAAyD;AAAhD,gHAAA,oBAAoB,OAAA;AAC7B,4CAsByB;AArBvB,4GAAA,gBAAgB,OAAA;AAChB,4GAAA,gBAAgB,OAAA;AAChB,4HAAA,gCAAgC,OAAA;AAChC,gIAAA,oCAAoC,OAAA;AACpC,gHAAA,oBAAoB,OAAA;AACpB,6GAAA,iBAAiB,OAAA;AACjB,kHAAA,sBAAsB,OAAA;AACtB,yGAAA,aAAa,OAAA;AACb,iHAAA,qBAAqB,OAAA;AACrB,0GAAA,cAAc,OAAA;AAId,yGAAA,aAAa,OAAA;AAKb,wGAAA,YAAY,OAAA;AAKd,wFAAwF;AACxF,kBAAkB;AAClB,yCAAyC;AACzC,yCAAyC;AACzC,wCAAwC"}
@@ -0,0 +1,227 @@
1
+ /**
2
+ * NPM Registry Metrics Emitter (APPS-4378 / APPS-4189)
3
+ *
4
+ * One emitter for the whole npm-registry subsystem. It owns every
5
+ * `superblocks.npm.*` metric and centralizes the sanitize-at-emit-boundary
6
+ * discipline: no raw registry hostname, package name, or token can escape via
7
+ * a metric label because every dimension is coerced to a closed enum or a
8
+ * bounded bucket here, the moment before it reaches an OTel instrument.
9
+ *
10
+ * Wiring follows the rest of the npm subsystem: the meter is resolved lazily
11
+ * from the OTel global (`metrics.getMeter`) and instruments are cached and
12
+ * re-bound when the global MeterProvider changes (the typical case is a test
13
+ * installing its own provider for isolation). The emitter is therefore a
14
+ * zero-config process singleton (`npmRegistryEmitter`) — no DI plumbing
15
+ * through AiServiceConfig / NpmRegistryClientDeps.
16
+ *
17
+ * Metrics (all `superblocks.npm.*`, durations in seconds):
18
+ *
19
+ * - `config.lookup_total` counter {result}
20
+ * - `config.lookup_duration_seconds` histogram {result} buckets [0.05,0.25,1,3,10,30]
21
+ * - `install.outcome_total` counter {outcome, configured, registry_host, runner}
22
+ * - `install.duration_seconds`histogram {…same…} buckets [0.5,2,5,15,60,300]
23
+ * - `install.status` gauge {…same…} last-seen 1=success/0=else
24
+ * - `probe.outcome_total` counter {outcome, registry_host}
25
+ * - `probe.duration_seconds` histogram {…same…} buckets [0.1,0.5,1,3,10,30]
26
+ * - `lookup.calls_total` counter {outcome, registry_host}
27
+ * - `lookup.duration_seconds` histogram {…same…} buckets [0.05,0.25,1,3,10,30]
28
+ * - `killswitch.enabled` gauge {} 1 when the feature is suppressed
29
+ *
30
+ * Cardinality budget (documented contract — must not regress without a
31
+ * migration plan; every dashboard / Datadog facet pins on these):
32
+ *
33
+ * - registry_host ≤ 3 (`public_npm | private | unknown`)
34
+ * - install.outcome ≤ 7 (`NPM_INSTALL_OUTCOMES`)
35
+ * - probe.outcome ≤ 5 (`NPM_PROBE_OUTCOMES`)
36
+ * - lookup.outcome ≤ 3 (`NPM_LOOKUP_TOOL_OUTCOMES`)
37
+ * - config.result ≤ 4 (`NPM_CONFIG_LOOKUP_RESULTS`)
38
+ * - configured ≤ 2, runner ≤ 4 (`NPM_INSTALL_RUNNERS`)
39
+ *
40
+ * Worst-case distinct series across every instrument is ≈ 560: the three
41
+ * install instruments (status gauge + counter + histogram) dominate at
42
+ * outcome×configured×registry_host×runner = 7×2×3×4 = 168 each → 504, plus
43
+ * probe 5×3×2=30, config 4×2=8, lookup 3×3×2=18, and the killswitch gauge 1.
44
+ * APPS-4544 raised the runner count from 3 to 4, which is why this grew from
45
+ * ≈430 (adding a runner value must increase the count, not decrease it). Still
46
+ * well under the internal 150K flag and the Datadog billing boundary. Per-pod
47
+ * `host` is stripped at the collector for the `superblocks.npm.*` family so
48
+ * series are aggregated by `service.name`.
49
+ *
50
+ * Each path has its OWN closed outcome enum + `normalize*` coercer because the
51
+ * paths legitimately diverge (probe has `ok`/`invalid_response`, install has
52
+ * `tls_failed`/`lockfile_url_mismatch`, …). `auth_failed` is deliberately a
53
+ * first-class outcome on install/probe — an expected, fast-failing
54
+ * customer-config mode — kept separable from infra failures (`unreachable` /
55
+ * `tls_failed`) so a future alert can page on infra spikes while routing auth
56
+ * to a dashboard. These short names diverge from the long-form `NPM_OUTCOMES`
57
+ * in `./sanitize` (which stays as the audit-event surface): the install path
58
+ * normalizes the long `NpmInstallBlocked.reason` values to the short forms.
59
+ */
60
+ import { type NpmRegistryBucket } from "./sanitize.js";
61
+ /**
62
+ * Closed enum of npm install call sites that emit metrics. One value per call
63
+ * site Superblocks owns; renaming a value breaks every dashboard split on
64
+ * `runner`, so additions/removals need a migration plan.
65
+ *
66
+ * - `install_all` — full install from package.json (no specific
67
+ * packages). Covers both `AppShell.runNpmInstall`
68
+ * and `AppShell.runPackageManagerInstall`.
69
+ * - `install_specific` — `AppShell.installSpecificPackages` (`npm add`).
70
+ * - `prefetch` — `installTemplateModules` (template `node_modules`
71
+ * prefetch during AiService construction).
72
+ * - `auto_upgrade` — `upgradeCliWithPackageManager` in the CLI SDK
73
+ * (`automatic-upgrades.ts`). Owns the dev-server
74
+ * self-upgrade install; a perimeter block here
75
+ * crash-loops the live-edit pod, so the metric is
76
+ * the only signal that distinguishes that from a
77
+ * working upgrade. APPS-4544.
78
+ */
79
+ export declare const NPM_INSTALL_RUNNERS: readonly ["auto_upgrade", "install_all", "install_specific", "prefetch"];
80
+ export type NpmInstallRunner = (typeof NPM_INSTALL_RUNNERS)[number];
81
+ /**
82
+ * `config.lookup_total` result. Derived inside `NpmRegistryClient.getConfig`
83
+ * (the only place that knows cache hit vs miss): in-TTL non-empty hit → `hit`;
84
+ * fresh fetch with a configured registry → `miss`; empty / 404 / flag-off →
85
+ * `not_configured`; `stale` / `unreachable` / any throw → `error`.
86
+ */
87
+ export declare const NPM_CONFIG_LOOKUP_RESULTS: readonly ["hit", "miss", "not_configured", "error"];
88
+ export type NpmConfigLookupResult = (typeof NPM_CONFIG_LOOKUP_RESULTS)[number];
89
+ /** Unknown config-lookup states are an error condition, not a new series. */
90
+ export declare function normalizeConfigLookupResult(input: string | null | undefined): NpmConfigLookupResult;
91
+ /**
92
+ * `install.outcome_total` outcome. Short forms; the install path normalizes
93
+ * the long `NpmInstallBlocked.reason` values (`registry_auth_failed`,
94
+ * `registry_unreachable`) to these via {@link normalizeInstallOutcome}.
95
+ */
96
+ export declare const NPM_INSTALL_OUTCOMES: readonly ["success", "auth_failed", "not_in_registry", "unreachable", "tls_failed", "lockfile_url_mismatch", "other"];
97
+ export type NpmInstallOutcome = (typeof NPM_INSTALL_OUTCOMES)[number];
98
+ export declare function normalizeInstallOutcome(input: string | null | undefined): NpmInstallOutcome;
99
+ /** `probe.outcome_total` outcome. Mapped from the server `ProbeResult`. */
100
+ export declare const NPM_PROBE_OUTCOMES: readonly ["ok", "auth_failed", "unreachable", "tls_failed", "invalid_response"];
101
+ export type NpmProbeOutcome = (typeof NPM_PROBE_OUTCOMES)[number];
102
+ /**
103
+ * Unknown → `unreachable`: the typed call site maps the closed `ProbeReason`
104
+ * exhaustively, so this floor only fires on a non-typed (JS) caller, where
105
+ * "we couldn't classify the probe" is safest reported as a connectivity
106
+ * failure rather than silently dropped or minted as a new series.
107
+ */
108
+ export declare function normalizeProbeOutcome(input: string | null | undefined): NpmProbeOutcome;
109
+ /** `lookup.calls_total` outcome (the Clark `lookupNpmPackage` tool). */
110
+ export declare const NPM_LOOKUP_TOOL_OUTCOMES: readonly ["in_registry", "not_in_registry", "error"];
111
+ export type NpmLookupToolOutcome = (typeof NPM_LOOKUP_TOOL_OUTCOMES)[number];
112
+ /** Auth / unreachable / fail-closed / throw all collapse to `error` here. */
113
+ export declare function normalizeLookupToolOutcome(input: string | null | undefined): NpmLookupToolOutcome;
114
+ export interface NpmInstallMetricInput {
115
+ /** Which install call site emitted this event. */
116
+ runner: NpmInstallRunner;
117
+ /**
118
+ * Registry URL or bare hostname (`null`/`undefined` → `unknown`). Always
119
+ * bucketed via `bucketNpmRegistryHost`, so raw hostnames cannot leak.
120
+ */
121
+ registryHost: string | null | undefined;
122
+ /**
123
+ * Outcome key. Free-form on input; coerced to {@link NPM_INSTALL_OUTCOMES}
124
+ * at the emit boundary (long `NpmInstallBlocked.reason` forms are mapped to
125
+ * their short equivalents; unknown → `other`). Use `"success"` on the happy
126
+ * path.
127
+ */
128
+ outcome: string | null | undefined;
129
+ /**
130
+ * Whether the org had a private registry configured for this install
131
+ * (`result.config.configured`). Lets dashboards separate
132
+ * configured-but-failed from never-configured.
133
+ */
134
+ configured: boolean;
135
+ /** Elapsed wall-clock time in milliseconds; clamped at 0, emitted as seconds. */
136
+ durationMs: number;
137
+ }
138
+ export interface NpmProbeMetricInput {
139
+ registryHost: string | null | undefined;
140
+ outcome: string | null | undefined;
141
+ durationMs: number;
142
+ }
143
+ export interface NpmLookupToolMetricInput {
144
+ registryHost: string | null | undefined;
145
+ outcome: string | null | undefined;
146
+ durationMs: number;
147
+ }
148
+ /**
149
+ * Attribute set shaped for `llmobs.annotate({ tags })`. Returned by
150
+ * `recordInstall` so the active install span carries the exact values that
151
+ * went into the metric pipeline.
152
+ */
153
+ export interface NpmInstallSpanAttributes {
154
+ "npm.registry_host": NpmRegistryBucket;
155
+ "npm.outcome": NpmInstallOutcome;
156
+ }
157
+ /**
158
+ * The npm-registry metrics emitter. Constructed once as the
159
+ * {@link npmRegistryEmitter} singleton; no `Meter` is injected — instruments
160
+ * resolve lazily from the OTel global and re-bind on provider change.
161
+ */
162
+ export declare class NpmRegistryEmitter {
163
+ /**
164
+ * Last-seen install status keyed by the attribute tuple. The observable
165
+ * gauge reads this each collection cycle. Every key component is coerced to
166
+ * a closed enum / bounded bucket at the emit boundary
167
+ * (`normalizeInstallOutcome`, `bucketNpmRegistryHost`, the `NpmInstallRunner`
168
+ * union, a boolean), so the map is **bounded by construction** at
169
+ * runner(4) × configured(2) × registry_host(3) × outcome(7) = 168 entries —
170
+ * it cannot grow with traffic, only with deliberate enum changes. Entries
171
+ * persist until process exit / `_resetForTesting` (a "last-seen for
172
+ * dashboards" gauge must not drop a combo and lie about the most recent
173
+ * outcome); no eviction is needed at this bound. The cardinality regression
174
+ * test pins the resulting series count.
175
+ */
176
+ private readonly lastInstallStatus;
177
+ /** Returns `true` when the kill-switch is asserting (feature suppressed). */
178
+ private killswitchState;
179
+ private cachedMeter;
180
+ private cachedInstruments;
181
+ /**
182
+ * Lazy instrument factory. Re-creates instruments and re-registers the
183
+ * observable-gauge callbacks when the global MeterProvider changes (a stale
184
+ * instrument bound to a replaced provider silently drops data).
185
+ */
186
+ private getInstruments;
187
+ /**
188
+ * Emit one `config.lookup_total` + `config.lookup_duration_seconds`
189
+ * observation. `durationMs` is the time `getConfig` spent resolving — sub-ms
190
+ * for a cache hit, the network round-trip (+ any JWT-refresh retry) for a
191
+ * miss — so dashboards can tell fast cache hits from slow cold fetches.
192
+ */
193
+ recordConfigLookup(result: string | null | undefined, durationMs: number): void;
194
+ /**
195
+ * Emit the install outcome counter + duration histogram + last-seen status
196
+ * gauge, and return the matching span-attribute pair for `llmobs.annotate`.
197
+ * Safe to call from a `finally` block.
198
+ */
199
+ recordInstall(input: NpmInstallMetricInput): NpmInstallSpanAttributes;
200
+ /** Emit one `probe.outcome_total` + `probe.duration_seconds` observation. */
201
+ recordProbe(input: NpmProbeMetricInput): void;
202
+ /** Emit one `lookup.calls_total` + `lookup.duration_seconds` observation. */
203
+ recordLookupTool(input: NpmLookupToolMetricInput): void;
204
+ /**
205
+ * Register the kill-switch state source. `getCurrentState` returns `true`
206
+ * when the feature is suppressed (LD flag off). Idempotent — the latest
207
+ * source wins; the observable gauge reads it on each scrape. Calling this
208
+ * also ensures the instruments (and thus the gauge callback) are wired.
209
+ */
210
+ setKillswitch(getCurrentState: () => boolean): void;
211
+ /**
212
+ * Test-only: drop cached instruments, last-seen status, and the kill-switch
213
+ * source so the next emit re-binds to a freshly-installed test provider.
214
+ */
215
+ _resetForTesting(): void;
216
+ }
217
+ /** Process-wide singleton. All npm-registry call sites emit through this. */
218
+ export declare const npmRegistryEmitter: NpmRegistryEmitter;
219
+ /**
220
+ * Back-compat free function for the install path (APPS-4189 call sites in
221
+ * `shell.ts` / `template-renderer.ts`). Delegates to the singleton and returns
222
+ * the span-attribute pair for `llmobs.annotate`.
223
+ */
224
+ export declare function recordNpmInstall(input: NpmInstallMetricInput): NpmInstallSpanAttributes;
225
+ /** Test-only reset for the singleton (see {@link NpmRegistryEmitter._resetForTesting}). */
226
+ export declare function _resetNpmRegistryEmitterForTesting(): void;
227
+ //# sourceMappingURL=emitter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"emitter.d.ts","sourceRoot":"","sources":["../../src/npm-registry/emitter.ts"],"names":[],"mappings":"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"}