@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,91 @@
1
+ "use strict";
2
+ /**
3
+ * NPM Registry Spans (APPS-4378)
4
+ *
5
+ * OTel-native `startActiveSpan` helpers for the four named npm-registry spans.
6
+ * Using the global tracer (`trace.getTracer`) means LLMObs interop is
7
+ * automatic: a span started inside an `@llmobs.decorate`'d context becomes a
8
+ * child of that task span (shared global tracer provider); outside any LLMObs
9
+ * context (server verify, CLI bootstrap) it is a root span.
10
+ *
11
+ * `org_id` rides on spans (per-trace context), never on a metric label. Raw
12
+ * registry hostnames are bucketed via {@link bucketNpmRegistryHost} before
13
+ * they reach a span attribute, matching the metric path — the one exception
14
+ * is the structured-log path, which keeps the raw host (see
15
+ * {@link auditNpmRegistryHost} in ./sanitize).
16
+ */
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.NPM_REGISTRY_SPAN_ATTR = exports.NPM_REGISTRY_SPAN = void 0;
19
+ exports.withNpmRegistrySpan = withNpmRegistrySpan;
20
+ exports.npmPackageUnpackedSizeBytes = npmPackageUnpackedSizeBytes;
21
+ const api_1 = require("@opentelemetry/api");
22
+ /** Instrumentation scope for every npm-registry span. */
23
+ const TRACER_NAME = "superblocks.npm-registry";
24
+ /** The four named spans (APPS-4378). */
25
+ exports.NPM_REGISTRY_SPAN = {
26
+ VERIFY: "npm_registry.verify",
27
+ LOOKUP_PACKAGE: "npm_registry.lookup_package",
28
+ PREPARE_FOR_PRIVATE_REGISTRY: "npm_registry.prepare_for_private_registry",
29
+ SYNC_HOME_NPMRC: "npm_registry.sync_home_npmrc",
30
+ };
31
+ /**
32
+ * Canonical span-attribute keys, shared across the four spans so the
33
+ * dimension names never drift between call sites.
34
+ */
35
+ exports.NPM_REGISTRY_SPAN_ATTR = {
36
+ ORG_ID: "org_id",
37
+ /** Bucketed (`public_npm | private | unknown`) — never the raw host. */
38
+ REGISTRY_HOST: "registry_host",
39
+ OUTCOME: "outcome",
40
+ DURATION_MS: "duration_ms",
41
+ /** Richer `NpmRegistryFetchResult.source` for drill-down. */
42
+ SOURCE: "source",
43
+ REGISTRY_CONFIGURED: "registry_configured",
44
+ /** Tri-valued: `true | false | "unknown"`. */
45
+ ALLOW_INSTALL_SCRIPTS: "allow_install_scripts",
46
+ PACKAGE_NAME_BUCKET: "package_name_bucket",
47
+ /** Span-attribute ONLY — raw byte counts are unbounded, never a metric label. */
48
+ PACKAGE_UNPACKED_SIZE_BYTES: "package_unpacked_size_bytes",
49
+ };
50
+ /**
51
+ * Start an active npm-registry span over the global tracer, run `fn`, and
52
+ * end the span in `finally`. On throw, the error is recorded
53
+ * (`span.recordException`) and the span status set to ERROR before the throw
54
+ * propagates. `fn` receives the span so it can attach final attributes
55
+ * (outcome / duration) once the operation resolves.
56
+ */
57
+ async function withNpmRegistrySpan(spanName, initialAttributes, fn) {
58
+ const tracer = api_1.trace.getTracer(TRACER_NAME);
59
+ return tracer.startActiveSpan(spanName, async (span) => {
60
+ if (Object.keys(initialAttributes).length > 0) {
61
+ span.setAttributes(initialAttributes);
62
+ }
63
+ try {
64
+ return await fn(span);
65
+ }
66
+ catch (err) {
67
+ // `recordException` accepts Error | string; narrow rather than cast.
68
+ span.recordException(err instanceof Error ? err : String(err));
69
+ span.setStatus({ code: api_1.SpanStatusCode.ERROR });
70
+ throw err;
71
+ }
72
+ finally {
73
+ span.end();
74
+ }
75
+ });
76
+ }
77
+ /**
78
+ * Coerce a registry-reported `dist.unpackedSize` to a span-safe value: a
79
+ * finite, non-negative number, else `undefined`. A hostile or buggy registry
80
+ * cannot smuggle a non-numeric byte count onto the span through this guard.
81
+ *
82
+ * Callers MUST omit the attribute when this returns `undefined` rather than
83
+ * calling `setAttribute(key, undefined)` (some OTel exporters mishandle
84
+ * undefined values). The lookup call site guards with `if (size !== undefined)`.
85
+ */
86
+ function npmPackageUnpackedSizeBytes(raw) {
87
+ return typeof raw === "number" && Number.isFinite(raw) && raw >= 0
88
+ ? raw
89
+ : undefined;
90
+ }
91
+ //# sourceMappingURL=spans.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spans.js","sourceRoot":"","sources":["../../src/npm-registry/spans.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;GAcG;;;AA+CH,kDAqBC;AAWD,kEAIC;AAjFD,4CAK4B;AAE5B,yDAAyD;AACzD,MAAM,WAAW,GAAG,0BAA0B,CAAC;AAE/C,wCAAwC;AAC3B,QAAA,iBAAiB,GAAG;IAC/B,MAAM,EAAE,qBAAqB;IAC7B,cAAc,EAAE,6BAA6B;IAC7C,4BAA4B,EAAE,2CAA2C;IACzE,eAAe,EAAE,8BAA8B;CACvC,CAAC;AAEX;;;GAGG;AACU,QAAA,sBAAsB,GAAG;IACpC,MAAM,EAAE,QAAQ;IAChB,wEAAwE;IACxE,aAAa,EAAE,eAAe;IAC9B,OAAO,EAAE,SAAS;IAClB,WAAW,EAAE,aAAa;IAC1B,6DAA6D;IAC7D,MAAM,EAAE,QAAQ;IAChB,mBAAmB,EAAE,qBAAqB;IAC1C,8CAA8C;IAC9C,qBAAqB,EAAE,uBAAuB;IAC9C,mBAAmB,EAAE,qBAAqB;IAC1C,iFAAiF;IACjF,2BAA2B,EAAE,6BAA6B;CAClD,CAAC;AAEX;;;;;;GAMG;AACI,KAAK,UAAU,mBAAmB,CACvC,QAAgB,EAChB,iBAA6B,EAC7B,EAA8B;IAE9B,MAAM,MAAM,GAAG,WAAK,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;IAC5C,OAAO,MAAM,CAAC,eAAe,CAAC,QAAQ,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE;QACrD,IAAI,MAAM,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC9C,IAAI,CAAC,aAAa,CAAC,iBAAiB,CAAC,CAAC;QACxC,CAAC;QACD,IAAI,CAAC;YACH,OAAO,MAAM,EAAE,CAAC,IAAI,CAAC,CAAC;QACxB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,qEAAqE;YACrE,IAAI,CAAC,eAAe,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;YAC/D,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,oBAAc,CAAC,KAAK,EAAE,CAAC,CAAC;YAC/C,MAAM,GAAG,CAAC;QACZ,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,GAAG,EAAE,CAAC;QACb,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;GAQG;AACH,SAAgB,2BAA2B,CAAC,GAAY;IACtD,OAAO,OAAO,GAAG,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC;QAChE,CAAC,CAAC,GAAG;QACL,CAAC,CAAC,SAAS,CAAC;AAChB,CAAC"}
@@ -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"}
@@ -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.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","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.js","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"}
@@ -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-esm/index.js CHANGED
@@ -40,6 +40,8 @@
40
40
  export * from "./types/index.js";
41
41
  // Re-export common utilities
42
42
  export * from "./common/index.js";
43
+ // Re-export npm-registry telemetry (sanitization, metrics emitter, spans)
44
+ export * from "./npm-registry/index.js";
43
45
  // Re-export LLMObs tiered telemetry
44
46
  export * from "./llmobs/index.js";
45
47
  // Re-export Node.js telemetry functions for convenience
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAEH,kBAAkB;AAClB,cAAc,kBAAkB,CAAC;AAEjC,6BAA6B;AAC7B,cAAc,mBAAmB,CAAC;AAElC,oCAAoC;AACpC,cAAc,mBAAmB,CAAC;AAElC,wDAAwD;AACxD,2FAA2F;AAC3F,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,EAId,aAAa,EAKb,YAAY,GAGb,MAAM,iBAAiB,CAAC;AAEzB,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,cAAc,kBAAkB,CAAC;AAEjC,6BAA6B;AAC7B,cAAc,mBAAmB,CAAC;AAElC,0EAA0E;AAC1E,cAAc,yBAAyB,CAAC;AAExC,oCAAoC;AACpC,cAAc,mBAAmB,CAAC;AAElC,wDAAwD;AACxD,2FAA2F;AAC3F,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,EAId,aAAa,EAKb,YAAY,GAGb,MAAM,iBAAiB,CAAC;AAEzB,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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"}