@bleedingdev/modern-js-server-runtime-extensions 0.0.0-trusted-publisher-bootstrap

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 (64) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +67 -0
  3. package/dist/cjs/contractGateAutopilot.js +162 -0
  4. package/dist/cjs/contractGateSnapshotStore.js +253 -0
  5. package/dist/cjs/env.js +58 -0
  6. package/dist/cjs/index.js +162 -0
  7. package/dist/cjs/mfCache.js +106 -0
  8. package/dist/cjs/moduleFederationCss.js +285 -0
  9. package/dist/cjs/runtimeFallbackSignal.js +311 -0
  10. package/dist/cjs/telemetry.js +373 -0
  11. package/dist/cjs/telemetryCore.js +819 -0
  12. package/dist/esm/contractGateAutopilot.mjs +124 -0
  13. package/dist/esm/contractGateSnapshotStore.mjs +190 -0
  14. package/dist/esm/env.mjs +17 -0
  15. package/dist/esm/index.mjs +6 -0
  16. package/dist/esm/mfCache.mjs +55 -0
  17. package/dist/esm/moduleFederationCss.mjs +225 -0
  18. package/dist/esm/runtimeFallbackSignal.mjs +222 -0
  19. package/dist/esm/telemetry.mjs +275 -0
  20. package/dist/esm/telemetryCore.mjs +759 -0
  21. package/dist/esm-node/contractGateAutopilot.mjs +125 -0
  22. package/dist/esm-node/contractGateSnapshotStore.mjs +192 -0
  23. package/dist/esm-node/env.mjs +18 -0
  24. package/dist/esm-node/index.mjs +7 -0
  25. package/dist/esm-node/mfCache.mjs +56 -0
  26. package/dist/esm-node/moduleFederationCss.mjs +226 -0
  27. package/dist/esm-node/runtimeFallbackSignal.mjs +223 -0
  28. package/dist/esm-node/telemetry.mjs +276 -0
  29. package/dist/esm-node/telemetryCore.mjs +760 -0
  30. package/dist/types/contractGateAutopilot.d.ts +35 -0
  31. package/dist/types/contractGateSnapshotStore.d.ts +57 -0
  32. package/dist/types/env.d.ts +40 -0
  33. package/dist/types/index.d.ts +6 -0
  34. package/dist/types/mfCache.d.ts +27 -0
  35. package/dist/types/moduleFederationCss.d.ts +87 -0
  36. package/dist/types/runtimeFallbackSignal.d.ts +94 -0
  37. package/dist/types/telemetry.d.ts +12 -0
  38. package/dist/types/telemetryCore.d.ts +257 -0
  39. package/package.json +69 -0
  40. package/rslib.config.mts +4 -0
  41. package/rstest.config.mts +7 -0
  42. package/src/contractGateAutopilot.ts +247 -0
  43. package/src/contractGateSnapshotStore.ts +420 -0
  44. package/src/env.ts +63 -0
  45. package/src/index.ts +84 -0
  46. package/src/mfCache.ts +119 -0
  47. package/src/moduleFederationCss.ts +473 -0
  48. package/src/runtimeFallbackSignal.ts +584 -0
  49. package/src/telemetry.ts +554 -0
  50. package/src/telemetryCore.ts +1332 -0
  51. package/tests/contractGateAutopilot.test.ts +203 -0
  52. package/tests/contractGateSnapshotStore.test.ts +223 -0
  53. package/tests/env.test.ts +73 -0
  54. package/tests/helpers.ts +19 -0
  55. package/tests/mfCache.test.ts +150 -0
  56. package/tests/moduleFederationCss.test.ts +392 -0
  57. package/tests/registration.test.ts +112 -0
  58. package/tests/telemetry.test.ts +360 -0
  59. package/tests/telemetryAutopilot.test.ts +993 -0
  60. package/tests/telemetryCanaryOrchestrator.test.ts +140 -0
  61. package/tests/telemetryLifecycle.test.ts +168 -0
  62. package/tests/telemetryTraceparent.test.ts +167 -0
  63. package/tests/tsconfig.json +11 -0
  64. package/tsconfig.json +10 -0
@@ -0,0 +1,554 @@
1
+ import { parseTraceparent } from '@modern-js/create-request/server';
2
+ import type {
3
+ Context,
4
+ Next,
5
+ ServerEnv,
6
+ ServerPlugin,
7
+ ServerTelemetryUserConfig,
8
+ } from '@modern-js/server-core';
9
+ import type { CoreMonitor } from '@modern-js/types';
10
+ import { logger } from '@modern-js/utils';
11
+ import { ContractGateAutopilot } from './contractGateAutopilot';
12
+ import {
13
+ type ContractGateSnapshotStore,
14
+ DEFAULT_CONTRACT_GATE_SNAPSHOT_PATH,
15
+ resolveContractGateSnapshotPath,
16
+ resolveContractGateSnapshotStore,
17
+ } from './contractGateSnapshotStore';
18
+ import { parseServerRuntimeExtensionsEnv } from './env';
19
+ import {
20
+ createRuntimeFallbackSignalRuntimeState,
21
+ DEFAULT_RUNTIME_FALLBACK_FAILURE_HOLD_MS,
22
+ DEFAULT_RUNTIME_FALLBACK_GATE_NAME,
23
+ DEFAULT_RUNTIME_FALLBACK_MAX_BODY_BYTES,
24
+ DEFAULT_RUNTIME_STATUS_ENDPOINT,
25
+ enforceRuntimeFallbackSignalAuth,
26
+ enforceRuntimeFallbackSignalAuthToken,
27
+ enforceRuntimeFallbackSignalTrustPolicy,
28
+ getRuntimeSignalErrorStatusCode,
29
+ normalizeRequiredRuntimeFallbackSignalAuthConfig,
30
+ normalizeRuntimeFallbackSignalAuthConfig,
31
+ normalizeRuntimeFallbackTrustPolicy,
32
+ parseRuntimeFallbackSignalPayload,
33
+ persistRuntimeFallbackContractGate,
34
+ type RuntimeFallbackSignalAuthConfig,
35
+ type RuntimeFallbackSignalConfig,
36
+ type RuntimeSignalError,
37
+ resolveRuntimeFallbackSignalEndpoint,
38
+ } from './runtimeFallbackSignal';
39
+ import {
40
+ createOtlpTelemetryExporter,
41
+ createVictoriaMetricsTelemetryExporter,
42
+ maybeWarnLegacyOtlpEndpoint,
43
+ type TelemetryCanaryDecision,
44
+ TelemetryCanaryOrchestrator,
45
+ TelemetryRegistry,
46
+ type TelemetryRegistryOptions,
47
+ toTelemetryEnvelope,
48
+ } from './telemetryCore';
49
+
50
+ export {
51
+ createRuntimeFallbackSignalRuntimeState,
52
+ createRuntimeSignalError,
53
+ DEFAULT_RUNTIME_FALLBACK_SIGNAL_ENDPOINT,
54
+ DEFAULT_RUNTIME_STATUS_ENDPOINT,
55
+ enforceRuntimeFallbackSignalAuthToken,
56
+ enforceRuntimeFallbackSignalTrustPolicy,
57
+ getRuntimeSignalErrorStatusCode,
58
+ normalizeRequiredRuntimeFallbackSignalAuthConfig,
59
+ normalizeRuntimeFallbackSignalAuthConfig,
60
+ normalizeRuntimeFallbackTrustPolicy,
61
+ parseRuntimeFallbackSignalPayloadFromRawBody,
62
+ type RuntimeFallbackSignalAuthConfig,
63
+ type RuntimeFallbackSignalRuntimeState,
64
+ type RuntimeFallbackSignalSource,
65
+ type RuntimeFallbackSignalTrustContext,
66
+ type RuntimeFallbackSignalTrustPolicy,
67
+ type RuntimeSignalError,
68
+ type RuntimeSignalErrorCode,
69
+ resolveRuntimeFallbackSignalEndpoint,
70
+ } from './runtimeFallbackSignal';
71
+ export {
72
+ createOtlpTelemetryExporter,
73
+ createTelemetryAwareMetrics,
74
+ createVictoriaMetricsTelemetryExporter,
75
+ type OtlpExporterOptions,
76
+ type TelemetryCanaryAction,
77
+ type TelemetryCanaryContractGateStatus,
78
+ type TelemetryCanaryDecision,
79
+ type TelemetryCanaryFailure,
80
+ type TelemetryCanaryFailureReason,
81
+ TelemetryCanaryOrchestrator,
82
+ type TelemetryCanaryOrchestratorOptions,
83
+ type TelemetryCanaryState,
84
+ type TelemetryCanaryStatusSnapshot,
85
+ type TelemetryEnvelope,
86
+ type TelemetryExporter,
87
+ type TelemetryExporterHealthStatus,
88
+ type TelemetryQueueStats,
89
+ TelemetryRegistry,
90
+ type TelemetryRegistryOptions,
91
+ type TelemetrySignalType,
92
+ type TelemetrySloAlert,
93
+ type TelemetrySloAlertType,
94
+ TelemetryStartupHealthError,
95
+ type VictoriaMetricsExporterOptions,
96
+ } from './telemetryCore';
97
+
98
+ function emitCanaryDecisionMetric(
99
+ registry: TelemetryRegistry,
100
+ decision: TelemetryCanaryDecision,
101
+ action: 'promote' | 'rollback',
102
+ ) {
103
+ try {
104
+ registry.enqueueMetric({
105
+ name: `telemetry.canary.${action}`,
106
+ value: 1,
107
+ unit: 'count',
108
+ tags: {
109
+ action,
110
+ state: decision.state,
111
+ failures: String(decision.failures.length),
112
+ },
113
+ });
114
+ } catch (_error) {
115
+ // Canary decision metrics are best-effort and must never break request flow.
116
+ }
117
+ }
118
+
119
+ export const hasEnabledTelemetryExporters = (
120
+ config: ServerTelemetryUserConfig | undefined,
121
+ ) =>
122
+ Boolean(
123
+ config?.exporters?.otlp?.enabled ||
124
+ config?.exporters?.victoriaMetrics?.enabled,
125
+ );
126
+
127
+ /**
128
+ * Builds the registry SLO options from `server.telemetry.slo`, attaching a
129
+ * logger sink so threshold breaches surface as server warnings (parity with
130
+ * the pre-extraction prod-server harness).
131
+ */
132
+ export const resolveTelemetrySloOptions = (
133
+ sloConfig: ServerTelemetryUserConfig['slo'],
134
+ warnSink: (message: string) => void = message => logger.warn(message),
135
+ ): NonNullable<TelemetryRegistryOptions['slo']> => ({
136
+ queueUtilizationWarnThreshold: sloConfig?.queueUtilizationWarnThreshold,
137
+ queueDroppedWarnThreshold: sloConfig?.queueDroppedWarnThreshold,
138
+ alertCooldownMs: sloConfig?.alertCooldownMs,
139
+ onAlert: alert => {
140
+ warnSink(
141
+ `[telemetry.slo] ${alert.type} threshold=${alert.threshold} value=${alert.value} depth=${alert.queueDepth}/${alert.queueCapacity} dropped=${alert.totalDropped}`,
142
+ );
143
+ },
144
+ });
145
+
146
+ const getRequestRemoteAddress = (c: Context<ServerEnv>) => {
147
+ const env = c.env as
148
+ | {
149
+ node?: {
150
+ req?: {
151
+ socket?: {
152
+ remoteAddress?: string;
153
+ };
154
+ };
155
+ };
156
+ }
157
+ | undefined;
158
+ const remoteAddress = env?.node?.req?.socket?.remoteAddress;
159
+ return typeof remoteAddress === 'string' && remoteAddress.trim().length > 0
160
+ ? remoteAddress.trim()
161
+ : undefined;
162
+ };
163
+
164
+ /**
165
+ * Active telemetry lane disposers, flushed by a single shared process
166
+ * `beforeExit` hook (a per-lane listener would accumulate listeners across
167
+ * dev-server restarts and embedded multi-server setups).
168
+ */
169
+ const activeTelemetryLaneClosers = new Set<() => Promise<void>>();
170
+ let telemetryBeforeExitHookInstalled = false;
171
+ const ensureTelemetryBeforeExitHook = () => {
172
+ if (telemetryBeforeExitHookInstalled) {
173
+ return;
174
+ }
175
+ telemetryBeforeExitHookInstalled = true;
176
+ process.on('beforeExit', () => {
177
+ for (const close of [...activeTelemetryLaneClosers]) {
178
+ void close();
179
+ }
180
+ });
181
+ };
182
+
183
+ export const injectTelemetryPlugin = (): ServerPlugin => ({
184
+ name: '@modern-js/inject-telemetry',
185
+ setup(api) {
186
+ const serverConfig = api.getServerConfig();
187
+ const telemetryConfig = serverConfig?.server?.telemetry;
188
+ if (!telemetryConfig) {
189
+ return;
190
+ }
191
+
192
+ if (
193
+ telemetryConfig.enabled !== true &&
194
+ !hasEnabledTelemetryExporters(telemetryConfig)
195
+ ) {
196
+ return;
197
+ }
198
+
199
+ const { middlewares, metaName, appDirectory } = api.getServerContext();
200
+ const serviceName = telemetryConfig.service || metaName || 'modern-js';
201
+ const moduleName = telemetryConfig.module || 'server';
202
+ const environmentName =
203
+ telemetryConfig.environment ||
204
+ parseServerRuntimeExtensionsEnv().environmentName;
205
+
206
+ const registry = new TelemetryRegistry({
207
+ service: serviceName,
208
+ module: moduleName,
209
+ environment: environmentName,
210
+ samplingRate: telemetryConfig.samplingRate,
211
+ flushIntervalMs: telemetryConfig.flushIntervalMs,
212
+ maxBatchSize: telemetryConfig.maxBatchSize,
213
+ maxQueueSize: telemetryConfig.maxQueueSize,
214
+ redactionKeys: telemetryConfig.redactionKeys,
215
+ slo: resolveTelemetrySloOptions(telemetryConfig.slo),
216
+ });
217
+
218
+ let canaryOrchestrator: TelemetryCanaryOrchestrator | undefined;
219
+ let contractGateAutopilot: ContractGateAutopilot | undefined;
220
+ let runtimeFallbackSignalConfig: RuntimeFallbackSignalConfig | undefined;
221
+ let runtimeStatusAuthConfig: RuntimeFallbackSignalAuthConfig | undefined;
222
+ let gateSnapshotStorePromise:
223
+ | Promise<ContractGateSnapshotStore>
224
+ | undefined;
225
+
226
+ const canaryConfig = telemetryConfig.canary;
227
+ if (canaryConfig?.enabled) {
228
+ const contractGates = canaryConfig.contractGates as
229
+ | Record<string, boolean | { passed: boolean; reason?: string }>
230
+ | undefined;
231
+
232
+ canaryOrchestrator = new TelemetryCanaryOrchestrator({
233
+ registry,
234
+ evaluationIntervalMs: canaryConfig.evaluationIntervalMs,
235
+ minConsecutiveHealthyEvaluations:
236
+ canaryConfig.minConsecutiveHealthyEvaluations,
237
+ rollbackConsecutiveFailures: canaryConfig.rollbackConsecutiveFailures,
238
+ maxQueueUtilization: canaryConfig.maxQueueUtilization,
239
+ maxTotalDropped: canaryConfig.maxTotalDropped,
240
+ maxUnhealthyExporters: canaryConfig.maxUnhealthyExporters,
241
+ requiredContractGates: Object.keys(contractGates || {}),
242
+ onPromote: decision => {
243
+ emitCanaryDecisionMetric(registry, decision, 'promote');
244
+ },
245
+ onRollback: decision => {
246
+ emitCanaryDecisionMetric(registry, decision, 'rollback');
247
+ },
248
+ });
249
+
250
+ if (contractGates) {
251
+ canaryOrchestrator.setContractGates(contractGates);
252
+ }
253
+
254
+ const autopilotEnabled = canaryConfig.autopilot?.enabled ?? true;
255
+ if (autopilotEnabled) {
256
+ const gateSnapshotPath = resolveContractGateSnapshotPath(
257
+ appDirectory,
258
+ canaryConfig.autopilot?.gateSnapshotPath,
259
+ );
260
+ gateSnapshotStorePromise = resolveContractGateSnapshotStore({
261
+ appDirectory,
262
+ gateSnapshotPath:
263
+ gateSnapshotPath || DEFAULT_CONTRACT_GATE_SNAPSHOT_PATH,
264
+ stateStore: canaryConfig.autopilot?.stateStore,
265
+ });
266
+
267
+ const runtimeSignalConfig =
268
+ canaryConfig.autopilot?.runtimeFallbackSignal;
269
+ // The signal endpoint can persist failing contract gates and force
270
+ // the canary orchestrator into rollback, so it is opt-in (default
271
+ // OFF) and requires a configured auth token when enabled.
272
+ const runtimeSignalEnabled = runtimeSignalConfig?.enabled === true;
273
+ if (runtimeSignalEnabled && gateSnapshotStorePromise) {
274
+ runtimeFallbackSignalConfig = {
275
+ endpoint: resolveRuntimeFallbackSignalEndpoint(
276
+ runtimeSignalConfig?.endpoint,
277
+ ),
278
+ gateName:
279
+ runtimeSignalConfig?.gateName?.trim() ||
280
+ DEFAULT_RUNTIME_FALLBACK_GATE_NAME,
281
+ gateSnapshotStore: gateSnapshotStorePromise,
282
+ failureHoldMs: Math.max(
283
+ 1_000,
284
+ runtimeSignalConfig?.failureHoldMs ??
285
+ DEFAULT_RUNTIME_FALLBACK_FAILURE_HOLD_MS,
286
+ ),
287
+ maxBodyBytes: Math.max(
288
+ 512,
289
+ runtimeSignalConfig?.maxBodyBytes ??
290
+ DEFAULT_RUNTIME_FALLBACK_MAX_BODY_BYTES,
291
+ ),
292
+ auth: normalizeRequiredRuntimeFallbackSignalAuthConfig(
293
+ runtimeSignalConfig?.auth,
294
+ ),
295
+ trustPolicy: normalizeRuntimeFallbackTrustPolicy(
296
+ runtimeSignalConfig?.trustPolicy,
297
+ ),
298
+ runtimeState: createRuntimeFallbackSignalRuntimeState(),
299
+ };
300
+ }
301
+ }
302
+
303
+ // The runtime status endpoint exposes detail only to authenticated
304
+ // callers. Auth can be configured through runtimeFallbackSignal.auth
305
+ // even when the signal endpoint itself stays disabled.
306
+ runtimeStatusAuthConfig =
307
+ runtimeFallbackSignalConfig?.auth ??
308
+ normalizeRuntimeFallbackSignalAuthConfig(
309
+ canaryConfig.autopilot?.runtimeFallbackSignal?.auth,
310
+ );
311
+ }
312
+
313
+ if (runtimeFallbackSignalConfig) {
314
+ const signalConfig = runtimeFallbackSignalConfig;
315
+ middlewares.push({
316
+ name: 'telemetry-runtime-fallback-signal',
317
+ path: signalConfig.endpoint,
318
+ method: 'post',
319
+ order: 'pre',
320
+ handler: async (c: Context<ServerEnv>) => {
321
+ try {
322
+ enforceRuntimeFallbackSignalAuth(c, signalConfig);
323
+ const { payload } = await parseRuntimeFallbackSignalPayload(
324
+ c,
325
+ signalConfig.maxBodyBytes,
326
+ );
327
+ const trustResult = enforceRuntimeFallbackSignalTrustPolicy(
328
+ payload,
329
+ signalConfig,
330
+ {
331
+ remoteAddress: getRequestRemoteAddress(c),
332
+ },
333
+ );
334
+ if (trustResult.deduped) {
335
+ return c.json({ ok: true, deduped: true }, 202);
336
+ }
337
+ await persistRuntimeFallbackContractGate(payload, signalConfig);
338
+ return c.json({ ok: true }, 202);
339
+ } catch (error) {
340
+ const signalError = error as RuntimeSignalError;
341
+ const status = getRuntimeSignalErrorStatusCode(signalError);
342
+ return c.json(
343
+ {
344
+ ok: false,
345
+ error:
346
+ signalError instanceof Error
347
+ ? signalError.message
348
+ : String(signalError),
349
+ },
350
+ status,
351
+ );
352
+ }
353
+ },
354
+ });
355
+ }
356
+
357
+ middlewares.push({
358
+ name: 'telemetry-runtime-status',
359
+ path: DEFAULT_RUNTIME_STATUS_ENDPOINT,
360
+ method: 'get',
361
+ order: 'pre',
362
+ handler: async (c: Context<ServerEnv>) => {
363
+ try {
364
+ // Telemetry/canary/trust internals are only disclosed to
365
+ // authenticated callers. Without a configured auth token the
366
+ // endpoint stays a bare health probe.
367
+ if (!runtimeStatusAuthConfig?.enabled) {
368
+ return c.json({
369
+ ok: true,
370
+ timestamp: Date.now(),
371
+ });
372
+ }
373
+
374
+ enforceRuntimeFallbackSignalAuthToken(
375
+ c.req.header(runtimeStatusAuthConfig.headerName),
376
+ runtimeStatusAuthConfig,
377
+ );
378
+
379
+ return c.json({
380
+ ok: true,
381
+ timestamp: Date.now(),
382
+ telemetry: {
383
+ queueStats: registry.getQueueStats(),
384
+ exporterHealth: registry.getExporterHealth(),
385
+ },
386
+ canary: canaryOrchestrator
387
+ ? {
388
+ enabled: true,
389
+ ...canaryOrchestrator.getStatusSnapshot(),
390
+ }
391
+ : {
392
+ enabled: false,
393
+ },
394
+ runtimeFallbackSignal: runtimeFallbackSignalConfig
395
+ ? {
396
+ enabled: true,
397
+ endpoint: runtimeFallbackSignalConfig.endpoint,
398
+ gateName: runtimeFallbackSignalConfig.gateName,
399
+ failureHoldMs: runtimeFallbackSignalConfig.failureHoldMs,
400
+ maxBodyBytes: runtimeFallbackSignalConfig.maxBodyBytes,
401
+ auth: {
402
+ enabled: runtimeFallbackSignalConfig.auth.enabled,
403
+ headerName: runtimeFallbackSignalConfig.auth.headerName,
404
+ },
405
+ trustPolicy: {
406
+ allowedApps:
407
+ runtimeFallbackSignalConfig.trustPolicy.allowedApps,
408
+ allowedEntryOrigins:
409
+ runtimeFallbackSignalConfig.trustPolicy
410
+ .allowedEntryOrigins,
411
+ enforceRuntimeDigest:
412
+ runtimeFallbackSignalConfig.trustPolicy
413
+ .enforceRuntimeDigest,
414
+ expectedRuntimeDigestsCount: Object.keys(
415
+ runtimeFallbackSignalConfig.trustPolicy
416
+ .expectedRuntimeDigests,
417
+ ).length,
418
+ maxSignalsPerWindow:
419
+ runtimeFallbackSignalConfig.trustPolicy
420
+ .maxSignalsPerWindow,
421
+ windowMs: runtimeFallbackSignalConfig.trustPolicy.windowMs,
422
+ dedupeWindowMs:
423
+ runtimeFallbackSignalConfig.trustPolicy.dedupeWindowMs,
424
+ },
425
+ }
426
+ : {
427
+ enabled: false,
428
+ },
429
+ });
430
+ } catch (error) {
431
+ const signalError = error as RuntimeSignalError;
432
+ return c.json(
433
+ {
434
+ ok: false,
435
+ error:
436
+ signalError instanceof Error
437
+ ? signalError.message
438
+ : String(signalError),
439
+ },
440
+ getRuntimeSignalErrorStatusCode(signalError),
441
+ );
442
+ }
443
+ },
444
+ });
445
+
446
+ middlewares.push({
447
+ name: 'inject-telemetry',
448
+ handler: async (c: Context<ServerEnv>, next: Next) => {
449
+ const monitors = c.get('monitors');
450
+ if (monitors) {
451
+ const traceContext = parseTraceparent(c.req.header('traceparent'));
452
+ const monitor: CoreMonitor = event => {
453
+ registry.enqueue(
454
+ toTelemetryEnvelope(event, {
455
+ service: serviceName,
456
+ module: moduleName,
457
+ environment: environmentName,
458
+ traceId: traceContext?.traceId,
459
+ spanId: traceContext?.spanId,
460
+ attributes: {
461
+ requestMethod: c.req.method,
462
+ requestPath: c.req.path,
463
+ },
464
+ }),
465
+ );
466
+ };
467
+ monitors.push(monitor);
468
+ }
469
+
470
+ await next();
471
+ },
472
+ });
473
+
474
+ let telemetryLaneClosed = false;
475
+ const closeTelemetryLane = async () => {
476
+ if (telemetryLaneClosed) {
477
+ return;
478
+ }
479
+ telemetryLaneClosed = true;
480
+ activeTelemetryLaneClosers.delete(closeTelemetryLane);
481
+ contractGateAutopilot?.stop();
482
+ canaryOrchestrator?.stop();
483
+ await registry.shutdown();
484
+ };
485
+
486
+ let prepared = false;
487
+ api.onPrepare(async () => {
488
+ if (prepared) {
489
+ return;
490
+ }
491
+ prepared = true;
492
+
493
+ // Shutdown path for the telemetry lane: flush pending envelopes and
494
+ // stop canary/autopilot pollers when the node server closes (this also
495
+ // covers dev-server restarts, which close the previous node server
496
+ // before assembling a new one) with process beforeExit as the
497
+ // final-flush floor when no node server handle exists.
498
+ const { nodeServer } = api.getServerContext() as {
499
+ nodeServer?: {
500
+ once?: (event: string, listener: () => void) => unknown;
501
+ };
502
+ };
503
+ if (nodeServer && typeof nodeServer.once === 'function') {
504
+ nodeServer.once('close', () => {
505
+ void closeTelemetryLane();
506
+ });
507
+ }
508
+ activeTelemetryLaneClosers.add(closeTelemetryLane);
509
+ ensureTelemetryBeforeExitHook();
510
+
511
+ if (telemetryConfig.exporters?.otlp?.enabled) {
512
+ maybeWarnLegacyOtlpEndpoint(telemetryConfig.exporters.otlp.endpoint);
513
+ await registry.register(
514
+ createOtlpTelemetryExporter(telemetryConfig.exporters.otlp),
515
+ );
516
+ }
517
+
518
+ if (telemetryConfig.exporters?.victoriaMetrics?.enabled) {
519
+ await registry.register(
520
+ createVictoriaMetricsTelemetryExporter(
521
+ telemetryConfig.exporters.victoriaMetrics,
522
+ ),
523
+ );
524
+ }
525
+
526
+ await registry.startupHealthCheck({
527
+ failLoud: telemetryConfig.failLoudStartup ?? true,
528
+ });
529
+
530
+ if (!canaryOrchestrator) {
531
+ return;
532
+ }
533
+
534
+ canaryOrchestrator.start();
535
+ if (gateSnapshotStorePromise) {
536
+ const gateSnapshotStore = await gateSnapshotStorePromise;
537
+ contractGateAutopilot = new ContractGateAutopilot({
538
+ orchestrator: canaryOrchestrator,
539
+ gateSnapshotPath: resolveContractGateSnapshotPath(
540
+ appDirectory,
541
+ canaryConfig?.autopilot?.gateSnapshotPath,
542
+ ),
543
+ gateSnapshotStore,
544
+ pollIntervalMs: canaryConfig?.autopilot?.pollIntervalMs,
545
+ gateStaleAfterMs: canaryConfig?.autopilot?.gateStaleAfterMs,
546
+ });
547
+ }
548
+ if (contractGateAutopilot) {
549
+ await contractGateAutopilot.start();
550
+ }
551
+ canaryOrchestrator.evaluate();
552
+ });
553
+ },
554
+ });