@superblocksteam/telemetry 2.0.129 → 2.0.130-next.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -12
- package/dist/common/index.d.ts +0 -2
- package/dist/common/index.d.ts.map +1 -1
- package/dist/common/index.js +0 -2
- package/dist/common/index.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/npm-registry/emitter.d.ts +227 -0
- package/dist/npm-registry/emitter.d.ts.map +1 -0
- package/dist/npm-registry/emitter.js +402 -0
- package/dist/npm-registry/emitter.js.map +1 -0
- package/dist/npm-registry/index.d.ts +12 -0
- package/dist/npm-registry/index.d.ts.map +1 -0
- package/dist/npm-registry/index.js +28 -0
- package/dist/npm-registry/index.js.map +1 -0
- package/dist/{common/sanitize-npm-attributes.d.ts → npm-registry/sanitize.d.ts} +19 -1
- package/dist/npm-registry/sanitize.d.ts.map +1 -0
- package/dist/{common/sanitize-npm-attributes.js → npm-registry/sanitize.js} +50 -2
- package/dist/npm-registry/sanitize.js.map +1 -0
- package/dist/npm-registry/spans.d.ts +61 -0
- package/dist/npm-registry/spans.d.ts.map +1 -0
- package/dist/npm-registry/spans.js +91 -0
- package/dist/npm-registry/spans.js.map +1 -0
- package/dist-esm/common/index.d.ts +0 -2
- package/dist-esm/common/index.d.ts.map +1 -1
- package/dist-esm/common/index.js +0 -2
- package/dist-esm/common/index.js.map +1 -1
- package/dist-esm/index.d.ts +1 -0
- package/dist-esm/index.d.ts.map +1 -1
- package/dist-esm/index.js +2 -0
- package/dist-esm/index.js.map +1 -1
- package/dist-esm/npm-registry/emitter.d.ts +227 -0
- package/dist-esm/npm-registry/emitter.d.ts.map +1 -0
- package/dist-esm/npm-registry/emitter.js +392 -0
- package/dist-esm/npm-registry/emitter.js.map +1 -0
- package/dist-esm/npm-registry/index.d.ts +12 -0
- package/dist-esm/npm-registry/index.d.ts.map +1 -0
- package/dist-esm/npm-registry/index.js +12 -0
- package/dist-esm/npm-registry/index.js.map +1 -0
- package/dist-esm/{common/sanitize-npm-attributes.d.ts → npm-registry/sanitize.d.ts} +19 -1
- package/dist-esm/npm-registry/sanitize.d.ts.map +1 -0
- package/dist-esm/{common/sanitize-npm-attributes.js → npm-registry/sanitize.js} +49 -2
- package/dist-esm/npm-registry/sanitize.js.map +1 -0
- package/dist-esm/npm-registry/spans.d.ts +61 -0
- package/dist-esm/npm-registry/spans.d.ts.map +1 -0
- package/dist-esm/npm-registry/spans.js +86 -0
- package/dist-esm/npm-registry/spans.js.map +1 -0
- package/package.json +4 -4
- package/dist/common/npm-install-metrics.d.ts +0 -88
- package/dist/common/npm-install-metrics.d.ts.map +0 -1
- package/dist/common/npm-install-metrics.js +0 -140
- package/dist/common/npm-install-metrics.js.map +0 -1
- package/dist/common/sanitize-npm-attributes.d.ts.map +0 -1
- package/dist/common/sanitize-npm-attributes.js.map +0 -1
- package/dist-esm/common/npm-install-metrics.d.ts +0 -88
- package/dist-esm/common/npm-install-metrics.d.ts.map +0 -1
- package/dist-esm/common/npm-install-metrics.js +0 -135
- package/dist-esm/common/npm-install-metrics.js.map +0 -1
- package/dist-esm/common/sanitize-npm-attributes.d.ts.map +0 -1
- 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 (
|
|
155
|
-
pass values through the helpers in
|
|
156
|
-
[`
|
|
157
|
-
|
|
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` |
|
|
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
|
|
242
|
-
|
|
243
|
-
the catch-all so dashboards never see free-form
|
|
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
|
-
|
|
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/
|
|
257
|
-
- Tests: [`sanitize
|
|
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
|
|
package/dist/common/index.d.ts
CHANGED
|
@@ -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,
|
|
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"}
|
package/dist/common/index.js
CHANGED
|
@@ -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
|
package/dist/common/index.js.map
CHANGED
|
@@ -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
|
|
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";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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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"}
|