@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.
- package/LICENSE +21 -0
- package/README.md +67 -0
- package/dist/cjs/contractGateAutopilot.js +162 -0
- package/dist/cjs/contractGateSnapshotStore.js +253 -0
- package/dist/cjs/env.js +58 -0
- package/dist/cjs/index.js +162 -0
- package/dist/cjs/mfCache.js +106 -0
- package/dist/cjs/moduleFederationCss.js +285 -0
- package/dist/cjs/runtimeFallbackSignal.js +311 -0
- package/dist/cjs/telemetry.js +373 -0
- package/dist/cjs/telemetryCore.js +819 -0
- package/dist/esm/contractGateAutopilot.mjs +124 -0
- package/dist/esm/contractGateSnapshotStore.mjs +190 -0
- package/dist/esm/env.mjs +17 -0
- package/dist/esm/index.mjs +6 -0
- package/dist/esm/mfCache.mjs +55 -0
- package/dist/esm/moduleFederationCss.mjs +225 -0
- package/dist/esm/runtimeFallbackSignal.mjs +222 -0
- package/dist/esm/telemetry.mjs +275 -0
- package/dist/esm/telemetryCore.mjs +759 -0
- package/dist/esm-node/contractGateAutopilot.mjs +125 -0
- package/dist/esm-node/contractGateSnapshotStore.mjs +192 -0
- package/dist/esm-node/env.mjs +18 -0
- package/dist/esm-node/index.mjs +7 -0
- package/dist/esm-node/mfCache.mjs +56 -0
- package/dist/esm-node/moduleFederationCss.mjs +226 -0
- package/dist/esm-node/runtimeFallbackSignal.mjs +223 -0
- package/dist/esm-node/telemetry.mjs +276 -0
- package/dist/esm-node/telemetryCore.mjs +760 -0
- package/dist/types/contractGateAutopilot.d.ts +35 -0
- package/dist/types/contractGateSnapshotStore.d.ts +57 -0
- package/dist/types/env.d.ts +40 -0
- package/dist/types/index.d.ts +6 -0
- package/dist/types/mfCache.d.ts +27 -0
- package/dist/types/moduleFederationCss.d.ts +87 -0
- package/dist/types/runtimeFallbackSignal.d.ts +94 -0
- package/dist/types/telemetry.d.ts +12 -0
- package/dist/types/telemetryCore.d.ts +257 -0
- package/package.json +69 -0
- package/rslib.config.mts +4 -0
- package/rstest.config.mts +7 -0
- package/src/contractGateAutopilot.ts +247 -0
- package/src/contractGateSnapshotStore.ts +420 -0
- package/src/env.ts +63 -0
- package/src/index.ts +84 -0
- package/src/mfCache.ts +119 -0
- package/src/moduleFederationCss.ts +473 -0
- package/src/runtimeFallbackSignal.ts +584 -0
- package/src/telemetry.ts +554 -0
- package/src/telemetryCore.ts +1332 -0
- package/tests/contractGateAutopilot.test.ts +203 -0
- package/tests/contractGateSnapshotStore.test.ts +223 -0
- package/tests/env.test.ts +73 -0
- package/tests/helpers.ts +19 -0
- package/tests/mfCache.test.ts +150 -0
- package/tests/moduleFederationCss.test.ts +392 -0
- package/tests/registration.test.ts +112 -0
- package/tests/telemetry.test.ts +360 -0
- package/tests/telemetryAutopilot.test.ts +993 -0
- package/tests/telemetryCanaryOrchestrator.test.ts +140 -0
- package/tests/telemetryLifecycle.test.ts +168 -0
- package/tests/telemetryTraceparent.test.ts +167 -0
- package/tests/tsconfig.json +11 -0
- package/tsconfig.json +10 -0
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import "node:module";
|
|
2
|
+
import { createHash, timingSafeEqual } from "node:crypto";
|
|
3
|
+
import { CONTRACT_GATE_SNAPSHOT_SCHEMA_VERSION } from "./contractGateSnapshotStore.mjs";
|
|
4
|
+
const DEFAULT_RUNTIME_FALLBACK_SIGNAL_ENDPOINT = '/_modern/contract-gates/runtime-fallback';
|
|
5
|
+
const DEFAULT_RUNTIME_STATUS_ENDPOINT = '/_modern/runtime/status';
|
|
6
|
+
const DEFAULT_RUNTIME_FALLBACK_GATE_NAME = 'runtime-mf-fallback-health';
|
|
7
|
+
const DEFAULT_RUNTIME_FALLBACK_FAILURE_HOLD_MS = 300000;
|
|
8
|
+
const DEFAULT_RUNTIME_FALLBACK_MAX_BODY_BYTES = 16384;
|
|
9
|
+
const DEFAULT_RUNTIME_FALLBACK_AUTH_HEADER = 'x-modernjs-runtime-signal-token';
|
|
10
|
+
const DEFAULT_RUNTIME_FALLBACK_TRUST_MAX_SIGNALS_PER_WINDOW = 30;
|
|
11
|
+
const DEFAULT_RUNTIME_FALLBACK_TRUST_WINDOW_MS = 60000;
|
|
12
|
+
const DEFAULT_RUNTIME_FALLBACK_TRUST_DEDUPE_WINDOW_MS = 10000;
|
|
13
|
+
function resolveRuntimeFallbackSignalEndpoint(configuredEndpoint) {
|
|
14
|
+
const rawEndpoint = configuredEndpoint?.trim();
|
|
15
|
+
if (!rawEndpoint) return DEFAULT_RUNTIME_FALLBACK_SIGNAL_ENDPOINT;
|
|
16
|
+
if (rawEndpoint.startsWith('/')) return rawEndpoint;
|
|
17
|
+
try {
|
|
18
|
+
return new URL(rawEndpoint).pathname || DEFAULT_RUNTIME_FALLBACK_SIGNAL_ENDPOINT;
|
|
19
|
+
} catch (_error) {
|
|
20
|
+
return `/${rawEndpoint.replace(/^\/+/, '')}`;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function createRuntimeSignalError(message, code) {
|
|
24
|
+
const error = new Error(message);
|
|
25
|
+
error.code = code;
|
|
26
|
+
return error;
|
|
27
|
+
}
|
|
28
|
+
function getUtf8ByteLength(input) {
|
|
29
|
+
if ("u" > typeof Buffer) return Buffer.byteLength(input);
|
|
30
|
+
return new TextEncoder().encode(input).length;
|
|
31
|
+
}
|
|
32
|
+
function normalizeRuntimeSignalOrigin(value) {
|
|
33
|
+
if ('string' != typeof value || 0 === value.trim().length) return;
|
|
34
|
+
try {
|
|
35
|
+
return new URL(value).origin;
|
|
36
|
+
} catch (_error) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function normalizeRuntimeSignalAppName(payload) {
|
|
41
|
+
if ('string' != typeof payload.appName) return 'unknown';
|
|
42
|
+
const normalized = payload.appName.trim();
|
|
43
|
+
return normalized.length > 0 ? normalized : 'unknown';
|
|
44
|
+
}
|
|
45
|
+
function normalizeRuntimeSignalRuntimeDigest(payload) {
|
|
46
|
+
if ('string' == typeof payload.runtimeDigest && payload.runtimeDigest.trim()) return payload.runtimeDigest.trim();
|
|
47
|
+
const metadata = payload.metadata;
|
|
48
|
+
if (metadata && 'object' == typeof metadata && !Array.isArray(metadata) && 'string' == typeof metadata.runtimeDigest) {
|
|
49
|
+
const digest = String(metadata.runtimeDigest).trim();
|
|
50
|
+
if (digest) return digest;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function normalizeRuntimeFallbackSignalAuthConfig(configured) {
|
|
54
|
+
const headerName = 'string' == typeof configured?.headerName && configured.headerName.trim() ? configured.headerName.trim().toLowerCase() : DEFAULT_RUNTIME_FALLBACK_AUTH_HEADER;
|
|
55
|
+
const expectedFromEnv = 'string' == typeof configured?.expectedValueEnv && configured.expectedValueEnv.trim().length > 0 ? process.env[configured.expectedValueEnv.trim()] : void 0;
|
|
56
|
+
const expectedFromConfig = 'string' == typeof configured?.expectedValue && configured.expectedValue.trim().length > 0 ? configured.expectedValue.trim() : void 0;
|
|
57
|
+
const expectedValue = expectedFromConfig || expectedFromEnv;
|
|
58
|
+
const enabled = configured?.enabled === true;
|
|
59
|
+
if (enabled && !expectedValue) throw new Error('[telemetry.canary.autopilot.runtimeFallbackSignal] auth.enabled is true but no expected token is configured');
|
|
60
|
+
return {
|
|
61
|
+
enabled,
|
|
62
|
+
headerName,
|
|
63
|
+
expectedValue
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function normalizeRequiredRuntimeFallbackSignalAuthConfig(configured) {
|
|
67
|
+
if (configured?.enabled === false) throw new Error('[telemetry.canary.autopilot.runtimeFallbackSignal] the endpoint cannot be enabled with auth disabled; configure auth.expectedValue or auth.expectedValueEnv');
|
|
68
|
+
try {
|
|
69
|
+
return normalizeRuntimeFallbackSignalAuthConfig({
|
|
70
|
+
...configured,
|
|
71
|
+
enabled: true
|
|
72
|
+
});
|
|
73
|
+
} catch (_error) {
|
|
74
|
+
throw new Error('[telemetry.canary.autopilot.runtimeFallbackSignal] enabling the endpoint requires an auth token; configure auth.expectedValue or auth.expectedValueEnv');
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function safeTokenEquals(candidate, expected) {
|
|
78
|
+
const candidateDigest = createHash('sha256').update(candidate).digest();
|
|
79
|
+
const expectedDigest = createHash('sha256').update(expected).digest();
|
|
80
|
+
return timingSafeEqual(candidateDigest, expectedDigest);
|
|
81
|
+
}
|
|
82
|
+
function enforceRuntimeFallbackSignalAuthToken(token, authConfig) {
|
|
83
|
+
if (!authConfig.enabled) return;
|
|
84
|
+
if (!token || !authConfig.expectedValue || !safeTokenEquals(token, authConfig.expectedValue)) throw createRuntimeSignalError('runtime fallback signal auth failed', 'UNAUTHORIZED');
|
|
85
|
+
}
|
|
86
|
+
function enforceRuntimeFallbackSignalAuth(c, runtimeSignalConfig) {
|
|
87
|
+
enforceRuntimeFallbackSignalAuthToken(c.req.header(runtimeSignalConfig.auth.headerName), runtimeSignalConfig.auth);
|
|
88
|
+
}
|
|
89
|
+
function normalizeRuntimeFallbackTrustPolicy(configured) {
|
|
90
|
+
const allowedApps = Array.isArray(configured?.allowedApps) ? configured.allowedApps.map((item)=>'string' == typeof item ? item.trim() : '').filter(Boolean) : [];
|
|
91
|
+
const allowedEntryOrigins = Array.isArray(configured?.allowedEntryOrigins) ? configured.allowedEntryOrigins.map((item)=>normalizeRuntimeSignalOrigin(item)).filter((item)=>Boolean(item)) : [];
|
|
92
|
+
const expectedRuntimeDigestsRaw = configured?.expectedRuntimeDigests || {};
|
|
93
|
+
const expectedRuntimeDigests = {};
|
|
94
|
+
Object.entries(expectedRuntimeDigestsRaw).forEach(([appName, digest])=>{
|
|
95
|
+
if ('string' == typeof appName && appName.trim().length > 0 && 'string' == typeof digest && digest.trim().length > 0) expectedRuntimeDigests[appName.trim()] = digest.trim();
|
|
96
|
+
});
|
|
97
|
+
return {
|
|
98
|
+
allowedApps,
|
|
99
|
+
allowedEntryOrigins,
|
|
100
|
+
expectedRuntimeDigests,
|
|
101
|
+
enforceRuntimeDigest: configured?.enforceRuntimeDigest === true,
|
|
102
|
+
maxSignalsPerWindow: Math.max(1, Math.floor(configured?.maxSignalsPerWindow ?? DEFAULT_RUNTIME_FALLBACK_TRUST_MAX_SIGNALS_PER_WINDOW)),
|
|
103
|
+
windowMs: Math.max(1000, Math.floor(configured?.windowMs ?? DEFAULT_RUNTIME_FALLBACK_TRUST_WINDOW_MS)),
|
|
104
|
+
dedupeWindowMs: Math.max(0, Math.floor(configured?.dedupeWindowMs ?? DEFAULT_RUNTIME_FALLBACK_TRUST_DEDUPE_WINDOW_MS))
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
function createRuntimeFallbackSignalRuntimeState() {
|
|
108
|
+
return {
|
|
109
|
+
rateLimitBySource: new Map(),
|
|
110
|
+
dedupeByFingerprint: new Map()
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
function cleanupRuntimeFallbackSignalRuntimeState(now, runtimeState, trustPolicy) {
|
|
114
|
+
const dedupeExpiryMs = Math.max(trustPolicy.dedupeWindowMs, trustPolicy.windowMs, 1000);
|
|
115
|
+
runtimeState.dedupeByFingerprint.forEach((lastSeenAt, fingerprint)=>{
|
|
116
|
+
if (now - lastSeenAt > dedupeExpiryMs) runtimeState.dedupeByFingerprint.delete(fingerprint);
|
|
117
|
+
});
|
|
118
|
+
runtimeState.rateLimitBySource.forEach((state, source)=>{
|
|
119
|
+
if (now - state.windowStartedAt > 2 * trustPolicy.windowMs) runtimeState.rateLimitBySource.delete(source);
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
function enforceRuntimeFallbackSignalTrustPolicy(payload, runtimeSignalContext, source = {}) {
|
|
123
|
+
const { trustPolicy, runtimeState } = runtimeSignalContext;
|
|
124
|
+
const now = Date.now();
|
|
125
|
+
cleanupRuntimeFallbackSignalRuntimeState(now, runtimeState, trustPolicy);
|
|
126
|
+
const appName = normalizeRuntimeSignalAppName(payload);
|
|
127
|
+
const entryOrigin = normalizeRuntimeSignalOrigin(payload.entry);
|
|
128
|
+
const runtimeDigest = normalizeRuntimeSignalRuntimeDigest(payload);
|
|
129
|
+
if (trustPolicy.allowedApps.length > 0 && !trustPolicy.allowedApps.includes(appName)) throw createRuntimeSignalError(`runtime fallback signal app "${appName}" is not trusted`, 'UNTRUSTED_SOURCE');
|
|
130
|
+
if (trustPolicy.allowedEntryOrigins.length > 0) {
|
|
131
|
+
if (!entryOrigin || !trustPolicy.allowedEntryOrigins.includes(entryOrigin)) throw createRuntimeSignalError(`runtime fallback signal entry origin "${entryOrigin || 'unknown'}" is not trusted`, 'UNTRUSTED_SOURCE');
|
|
132
|
+
}
|
|
133
|
+
const expectedDigest = trustPolicy.expectedRuntimeDigests[appName];
|
|
134
|
+
if (expectedDigest && runtimeDigest !== expectedDigest) throw createRuntimeSignalError(`runtime fallback runtimeDigest mismatch for app "${appName}"`, 'UNTRUSTED_SOURCE');
|
|
135
|
+
if (trustPolicy.enforceRuntimeDigest && !runtimeDigest) throw createRuntimeSignalError(`runtime fallback signal for app "${appName}" is missing runtimeDigest`, 'UNTRUSTED_SOURCE');
|
|
136
|
+
const dedupeFingerprint = JSON.stringify({
|
|
137
|
+
appName,
|
|
138
|
+
entryOrigin: entryOrigin || 'unknown',
|
|
139
|
+
reason: payload.reason || 'runtime_fallback',
|
|
140
|
+
phase: payload.phase || 'unknown',
|
|
141
|
+
runtimeDigest: runtimeDigest || 'unknown'
|
|
142
|
+
});
|
|
143
|
+
const dedupeWindowMs = trustPolicy.dedupeWindowMs;
|
|
144
|
+
if (dedupeWindowMs > 0) {
|
|
145
|
+
const lastSeenAt = runtimeState.dedupeByFingerprint.get(dedupeFingerprint);
|
|
146
|
+
runtimeState.dedupeByFingerprint.set(dedupeFingerprint, now);
|
|
147
|
+
if ('number' == typeof lastSeenAt && now - lastSeenAt <= dedupeWindowMs) return {
|
|
148
|
+
deduped: true
|
|
149
|
+
};
|
|
150
|
+
} else runtimeState.dedupeByFingerprint.set(dedupeFingerprint, now);
|
|
151
|
+
const remoteAddress = source.remoteAddress?.trim();
|
|
152
|
+
const sourceKey = remoteAddress || 'unknown-remote';
|
|
153
|
+
const rateState = runtimeState.rateLimitBySource.get(sourceKey);
|
|
154
|
+
if (!rateState || now - rateState.windowStartedAt > trustPolicy.windowMs) runtimeState.rateLimitBySource.set(sourceKey, {
|
|
155
|
+
count: 1,
|
|
156
|
+
windowStartedAt: now
|
|
157
|
+
});
|
|
158
|
+
else {
|
|
159
|
+
if (rateState.count >= trustPolicy.maxSignalsPerWindow) throw createRuntimeSignalError(`runtime fallback signal rate-limited for source "${sourceKey}"`, 'RATE_LIMITED');
|
|
160
|
+
rateState.count += 1;
|
|
161
|
+
}
|
|
162
|
+
return {
|
|
163
|
+
deduped: false
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
async function parseRuntimeFallbackSignalPayload(c, maxBodyBytes) {
|
|
167
|
+
const contentLengthHeader = c.req.header('content-length');
|
|
168
|
+
if (contentLengthHeader) {
|
|
169
|
+
const contentLength = Number.parseInt(contentLengthHeader, 10);
|
|
170
|
+
if (Number.isFinite(contentLength) && contentLength > maxBodyBytes) throw createRuntimeSignalError('runtime fallback signal payload too large', 'PAYLOAD_TOO_LARGE');
|
|
171
|
+
}
|
|
172
|
+
const rawBody = await c.req.raw.text();
|
|
173
|
+
const payload = parseRuntimeFallbackSignalPayloadFromRawBody(rawBody, maxBodyBytes);
|
|
174
|
+
return {
|
|
175
|
+
rawBody,
|
|
176
|
+
payload
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
function parseRuntimeFallbackSignalPayloadFromRawBody(rawBody, maxBodyBytes) {
|
|
180
|
+
if (!rawBody || 0 === rawBody.trim().length) throw createRuntimeSignalError('runtime fallback signal body is empty', 'INVALID_PAYLOAD');
|
|
181
|
+
if (getUtf8ByteLength(rawBody) > maxBodyBytes) throw createRuntimeSignalError('runtime fallback signal payload too large', 'PAYLOAD_TOO_LARGE');
|
|
182
|
+
let payload;
|
|
183
|
+
try {
|
|
184
|
+
payload = JSON.parse(rawBody);
|
|
185
|
+
} catch (_error) {
|
|
186
|
+
throw createRuntimeSignalError('runtime fallback signal body must be valid JSON', 'INVALID_PAYLOAD');
|
|
187
|
+
}
|
|
188
|
+
if (!payload || 'object' != typeof payload || Array.isArray(payload)) throw createRuntimeSignalError('runtime fallback signal body must be a JSON object', 'INVALID_PAYLOAD');
|
|
189
|
+
return payload;
|
|
190
|
+
}
|
|
191
|
+
function getRuntimeSignalErrorStatusCode(signalError) {
|
|
192
|
+
if ('PAYLOAD_TOO_LARGE' === signalError.code) return 413;
|
|
193
|
+
if ('INVALID_PAYLOAD' === signalError.code) return 400;
|
|
194
|
+
if ('UNAUTHORIZED' === signalError.code) return 401;
|
|
195
|
+
if ('RATE_LIMITED' === signalError.code) return 429;
|
|
196
|
+
if ('UNTRUSTED_SOURCE' === signalError.code) return 403;
|
|
197
|
+
return 500;
|
|
198
|
+
}
|
|
199
|
+
async function persistRuntimeFallbackContractGate(payload, runtimeSignalConfig) {
|
|
200
|
+
const now = Date.now();
|
|
201
|
+
const gateSnapshotStore = await runtimeSignalConfig.gateSnapshotStore;
|
|
202
|
+
const snapshot = await gateSnapshotStore.readSnapshot() || {};
|
|
203
|
+
const existingGates = snapshot.gates && 'object' == typeof snapshot.gates ? snapshot.gates : {};
|
|
204
|
+
const reason = 'string' == typeof payload.reason ? payload.reason : 'runtime_fallback';
|
|
205
|
+
const phase = 'string' == typeof payload.phase ? payload.phase : 'unknown';
|
|
206
|
+
const appName = 'string' == typeof payload.appName ? payload.appName : 'unknown';
|
|
207
|
+
const entry = 'string' == typeof payload.entry ? payload.entry : void 0;
|
|
208
|
+
snapshot.schemaVersion = 'number' == typeof snapshot.schemaVersion ? snapshot.schemaVersion : CONTRACT_GATE_SNAPSHOT_SCHEMA_VERSION;
|
|
209
|
+
snapshot.updatedAt = now;
|
|
210
|
+
snapshot.gates = {
|
|
211
|
+
...existingGates,
|
|
212
|
+
[runtimeSignalConfig.gateName]: {
|
|
213
|
+
passed: false,
|
|
214
|
+
reason: `runtime_fallback:${reason} phase=${phase} app=${appName}${entry ? ` entry=${entry}` : ''}`,
|
|
215
|
+
updatedAt: now,
|
|
216
|
+
expiresAt: now + runtimeSignalConfig.failureHoldMs,
|
|
217
|
+
source: 'runtime-mf-fallback-signal',
|
|
218
|
+
metadata: payload
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
await gateSnapshotStore.writeSnapshot(snapshot);
|
|
222
|
+
}
|
|
223
|
+
export { DEFAULT_RUNTIME_FALLBACK_FAILURE_HOLD_MS, DEFAULT_RUNTIME_FALLBACK_GATE_NAME, DEFAULT_RUNTIME_FALLBACK_MAX_BODY_BYTES, DEFAULT_RUNTIME_FALLBACK_SIGNAL_ENDPOINT, DEFAULT_RUNTIME_STATUS_ENDPOINT, createRuntimeFallbackSignalRuntimeState, createRuntimeSignalError, enforceRuntimeFallbackSignalAuth, enforceRuntimeFallbackSignalAuthToken, enforceRuntimeFallbackSignalTrustPolicy, getRuntimeSignalErrorStatusCode, normalizeRequiredRuntimeFallbackSignalAuthConfig, normalizeRuntimeFallbackSignalAuthConfig, normalizeRuntimeFallbackTrustPolicy, parseRuntimeFallbackSignalPayload, parseRuntimeFallbackSignalPayloadFromRawBody, persistRuntimeFallbackContractGate, resolveRuntimeFallbackSignalEndpoint };
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import "node:module";
|
|
2
|
+
import { parseTraceparent } from "@modern-js/create-request/server";
|
|
3
|
+
import { logger } from "@modern-js/utils";
|
|
4
|
+
import { ContractGateAutopilot } from "./contractGateAutopilot.mjs";
|
|
5
|
+
import { DEFAULT_CONTRACT_GATE_SNAPSHOT_PATH, resolveContractGateSnapshotPath, resolveContractGateSnapshotStore } from "./contractGateSnapshotStore.mjs";
|
|
6
|
+
import { parseServerRuntimeExtensionsEnv } from "./env.mjs";
|
|
7
|
+
import { DEFAULT_RUNTIME_FALLBACK_FAILURE_HOLD_MS, DEFAULT_RUNTIME_FALLBACK_GATE_NAME, DEFAULT_RUNTIME_FALLBACK_MAX_BODY_BYTES, DEFAULT_RUNTIME_FALLBACK_SIGNAL_ENDPOINT, DEFAULT_RUNTIME_STATUS_ENDPOINT, createRuntimeFallbackSignalRuntimeState, createRuntimeSignalError, enforceRuntimeFallbackSignalAuth, enforceRuntimeFallbackSignalAuthToken, enforceRuntimeFallbackSignalTrustPolicy, getRuntimeSignalErrorStatusCode, normalizeRequiredRuntimeFallbackSignalAuthConfig, normalizeRuntimeFallbackSignalAuthConfig, normalizeRuntimeFallbackTrustPolicy, parseRuntimeFallbackSignalPayload, parseRuntimeFallbackSignalPayloadFromRawBody, persistRuntimeFallbackContractGate, resolveRuntimeFallbackSignalEndpoint } from "./runtimeFallbackSignal.mjs";
|
|
8
|
+
import { TelemetryCanaryOrchestrator, TelemetryRegistry, TelemetryStartupHealthError, createOtlpTelemetryExporter, createTelemetryAwareMetrics, createVictoriaMetricsTelemetryExporter, maybeWarnLegacyOtlpEndpoint, toTelemetryEnvelope } from "./telemetryCore.mjs";
|
|
9
|
+
function emitCanaryDecisionMetric(registry, decision, action) {
|
|
10
|
+
try {
|
|
11
|
+
registry.enqueueMetric({
|
|
12
|
+
name: `telemetry.canary.${action}`,
|
|
13
|
+
value: 1,
|
|
14
|
+
unit: 'count',
|
|
15
|
+
tags: {
|
|
16
|
+
action,
|
|
17
|
+
state: decision.state,
|
|
18
|
+
failures: String(decision.failures.length)
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
} catch (_error) {}
|
|
22
|
+
}
|
|
23
|
+
const hasEnabledTelemetryExporters = (config)=>Boolean(config?.exporters?.otlp?.enabled || config?.exporters?.victoriaMetrics?.enabled);
|
|
24
|
+
const resolveTelemetrySloOptions = (sloConfig, warnSink = (message)=>logger.warn(message))=>({
|
|
25
|
+
queueUtilizationWarnThreshold: sloConfig?.queueUtilizationWarnThreshold,
|
|
26
|
+
queueDroppedWarnThreshold: sloConfig?.queueDroppedWarnThreshold,
|
|
27
|
+
alertCooldownMs: sloConfig?.alertCooldownMs,
|
|
28
|
+
onAlert: (alert)=>{
|
|
29
|
+
warnSink(`[telemetry.slo] ${alert.type} threshold=${alert.threshold} value=${alert.value} depth=${alert.queueDepth}/${alert.queueCapacity} dropped=${alert.totalDropped}`);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
const getRequestRemoteAddress = (c)=>{
|
|
33
|
+
const env = c.env;
|
|
34
|
+
const remoteAddress = env?.node?.req?.socket?.remoteAddress;
|
|
35
|
+
return 'string' == typeof remoteAddress && remoteAddress.trim().length > 0 ? remoteAddress.trim() : void 0;
|
|
36
|
+
};
|
|
37
|
+
const activeTelemetryLaneClosers = new Set();
|
|
38
|
+
let telemetryBeforeExitHookInstalled = false;
|
|
39
|
+
const ensureTelemetryBeforeExitHook = ()=>{
|
|
40
|
+
if (telemetryBeforeExitHookInstalled) return;
|
|
41
|
+
telemetryBeforeExitHookInstalled = true;
|
|
42
|
+
process.on('beforeExit', ()=>{
|
|
43
|
+
for (const close of [
|
|
44
|
+
...activeTelemetryLaneClosers
|
|
45
|
+
])close();
|
|
46
|
+
});
|
|
47
|
+
};
|
|
48
|
+
const injectTelemetryPlugin = ()=>({
|
|
49
|
+
name: '@modern-js/inject-telemetry',
|
|
50
|
+
setup (api) {
|
|
51
|
+
const serverConfig = api.getServerConfig();
|
|
52
|
+
const telemetryConfig = serverConfig?.server?.telemetry;
|
|
53
|
+
if (!telemetryConfig) return;
|
|
54
|
+
if (true !== telemetryConfig.enabled && !hasEnabledTelemetryExporters(telemetryConfig)) return;
|
|
55
|
+
const { middlewares, metaName, appDirectory } = api.getServerContext();
|
|
56
|
+
const serviceName = telemetryConfig.service || metaName || 'modern-js';
|
|
57
|
+
const moduleName = telemetryConfig.module || 'server';
|
|
58
|
+
const environmentName = telemetryConfig.environment || parseServerRuntimeExtensionsEnv().environmentName;
|
|
59
|
+
const registry = new TelemetryRegistry({
|
|
60
|
+
service: serviceName,
|
|
61
|
+
module: moduleName,
|
|
62
|
+
environment: environmentName,
|
|
63
|
+
samplingRate: telemetryConfig.samplingRate,
|
|
64
|
+
flushIntervalMs: telemetryConfig.flushIntervalMs,
|
|
65
|
+
maxBatchSize: telemetryConfig.maxBatchSize,
|
|
66
|
+
maxQueueSize: telemetryConfig.maxQueueSize,
|
|
67
|
+
redactionKeys: telemetryConfig.redactionKeys,
|
|
68
|
+
slo: resolveTelemetrySloOptions(telemetryConfig.slo)
|
|
69
|
+
});
|
|
70
|
+
let canaryOrchestrator;
|
|
71
|
+
let contractGateAutopilot;
|
|
72
|
+
let runtimeFallbackSignalConfig;
|
|
73
|
+
let runtimeStatusAuthConfig;
|
|
74
|
+
let gateSnapshotStorePromise;
|
|
75
|
+
const canaryConfig = telemetryConfig.canary;
|
|
76
|
+
if (canaryConfig?.enabled) {
|
|
77
|
+
const contractGates = canaryConfig.contractGates;
|
|
78
|
+
canaryOrchestrator = new TelemetryCanaryOrchestrator({
|
|
79
|
+
registry,
|
|
80
|
+
evaluationIntervalMs: canaryConfig.evaluationIntervalMs,
|
|
81
|
+
minConsecutiveHealthyEvaluations: canaryConfig.minConsecutiveHealthyEvaluations,
|
|
82
|
+
rollbackConsecutiveFailures: canaryConfig.rollbackConsecutiveFailures,
|
|
83
|
+
maxQueueUtilization: canaryConfig.maxQueueUtilization,
|
|
84
|
+
maxTotalDropped: canaryConfig.maxTotalDropped,
|
|
85
|
+
maxUnhealthyExporters: canaryConfig.maxUnhealthyExporters,
|
|
86
|
+
requiredContractGates: Object.keys(contractGates || {}),
|
|
87
|
+
onPromote: (decision)=>{
|
|
88
|
+
emitCanaryDecisionMetric(registry, decision, 'promote');
|
|
89
|
+
},
|
|
90
|
+
onRollback: (decision)=>{
|
|
91
|
+
emitCanaryDecisionMetric(registry, decision, 'rollback');
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
if (contractGates) canaryOrchestrator.setContractGates(contractGates);
|
|
95
|
+
const autopilotEnabled = canaryConfig.autopilot?.enabled ?? true;
|
|
96
|
+
if (autopilotEnabled) {
|
|
97
|
+
const gateSnapshotPath = resolveContractGateSnapshotPath(appDirectory, canaryConfig.autopilot?.gateSnapshotPath);
|
|
98
|
+
gateSnapshotStorePromise = resolveContractGateSnapshotStore({
|
|
99
|
+
appDirectory,
|
|
100
|
+
gateSnapshotPath: gateSnapshotPath || DEFAULT_CONTRACT_GATE_SNAPSHOT_PATH,
|
|
101
|
+
stateStore: canaryConfig.autopilot?.stateStore
|
|
102
|
+
});
|
|
103
|
+
const runtimeSignalConfig = canaryConfig.autopilot?.runtimeFallbackSignal;
|
|
104
|
+
const runtimeSignalEnabled = runtimeSignalConfig?.enabled === true;
|
|
105
|
+
if (runtimeSignalEnabled && gateSnapshotStorePromise) runtimeFallbackSignalConfig = {
|
|
106
|
+
endpoint: resolveRuntimeFallbackSignalEndpoint(runtimeSignalConfig?.endpoint),
|
|
107
|
+
gateName: runtimeSignalConfig?.gateName?.trim() || DEFAULT_RUNTIME_FALLBACK_GATE_NAME,
|
|
108
|
+
gateSnapshotStore: gateSnapshotStorePromise,
|
|
109
|
+
failureHoldMs: Math.max(1000, runtimeSignalConfig?.failureHoldMs ?? DEFAULT_RUNTIME_FALLBACK_FAILURE_HOLD_MS),
|
|
110
|
+
maxBodyBytes: Math.max(512, runtimeSignalConfig?.maxBodyBytes ?? DEFAULT_RUNTIME_FALLBACK_MAX_BODY_BYTES),
|
|
111
|
+
auth: normalizeRequiredRuntimeFallbackSignalAuthConfig(runtimeSignalConfig?.auth),
|
|
112
|
+
trustPolicy: normalizeRuntimeFallbackTrustPolicy(runtimeSignalConfig?.trustPolicy),
|
|
113
|
+
runtimeState: createRuntimeFallbackSignalRuntimeState()
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
runtimeStatusAuthConfig = runtimeFallbackSignalConfig?.auth ?? normalizeRuntimeFallbackSignalAuthConfig(canaryConfig.autopilot?.runtimeFallbackSignal?.auth);
|
|
117
|
+
}
|
|
118
|
+
if (runtimeFallbackSignalConfig) {
|
|
119
|
+
const signalConfig = runtimeFallbackSignalConfig;
|
|
120
|
+
middlewares.push({
|
|
121
|
+
name: 'telemetry-runtime-fallback-signal',
|
|
122
|
+
path: signalConfig.endpoint,
|
|
123
|
+
method: 'post',
|
|
124
|
+
order: 'pre',
|
|
125
|
+
handler: async (c)=>{
|
|
126
|
+
try {
|
|
127
|
+
enforceRuntimeFallbackSignalAuth(c, signalConfig);
|
|
128
|
+
const { payload } = await parseRuntimeFallbackSignalPayload(c, signalConfig.maxBodyBytes);
|
|
129
|
+
const trustResult = enforceRuntimeFallbackSignalTrustPolicy(payload, signalConfig, {
|
|
130
|
+
remoteAddress: getRequestRemoteAddress(c)
|
|
131
|
+
});
|
|
132
|
+
if (trustResult.deduped) return c.json({
|
|
133
|
+
ok: true,
|
|
134
|
+
deduped: true
|
|
135
|
+
}, 202);
|
|
136
|
+
await persistRuntimeFallbackContractGate(payload, signalConfig);
|
|
137
|
+
return c.json({
|
|
138
|
+
ok: true
|
|
139
|
+
}, 202);
|
|
140
|
+
} catch (error) {
|
|
141
|
+
const signalError = error;
|
|
142
|
+
const status = getRuntimeSignalErrorStatusCode(signalError);
|
|
143
|
+
return c.json({
|
|
144
|
+
ok: false,
|
|
145
|
+
error: signalError instanceof Error ? signalError.message : String(signalError)
|
|
146
|
+
}, status);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
middlewares.push({
|
|
152
|
+
name: 'telemetry-runtime-status',
|
|
153
|
+
path: DEFAULT_RUNTIME_STATUS_ENDPOINT,
|
|
154
|
+
method: 'get',
|
|
155
|
+
order: 'pre',
|
|
156
|
+
handler: async (c)=>{
|
|
157
|
+
try {
|
|
158
|
+
if (!runtimeStatusAuthConfig?.enabled) return c.json({
|
|
159
|
+
ok: true,
|
|
160
|
+
timestamp: Date.now()
|
|
161
|
+
});
|
|
162
|
+
enforceRuntimeFallbackSignalAuthToken(c.req.header(runtimeStatusAuthConfig.headerName), runtimeStatusAuthConfig);
|
|
163
|
+
return c.json({
|
|
164
|
+
ok: true,
|
|
165
|
+
timestamp: Date.now(),
|
|
166
|
+
telemetry: {
|
|
167
|
+
queueStats: registry.getQueueStats(),
|
|
168
|
+
exporterHealth: registry.getExporterHealth()
|
|
169
|
+
},
|
|
170
|
+
canary: canaryOrchestrator ? {
|
|
171
|
+
enabled: true,
|
|
172
|
+
...canaryOrchestrator.getStatusSnapshot()
|
|
173
|
+
} : {
|
|
174
|
+
enabled: false
|
|
175
|
+
},
|
|
176
|
+
runtimeFallbackSignal: runtimeFallbackSignalConfig ? {
|
|
177
|
+
enabled: true,
|
|
178
|
+
endpoint: runtimeFallbackSignalConfig.endpoint,
|
|
179
|
+
gateName: runtimeFallbackSignalConfig.gateName,
|
|
180
|
+
failureHoldMs: runtimeFallbackSignalConfig.failureHoldMs,
|
|
181
|
+
maxBodyBytes: runtimeFallbackSignalConfig.maxBodyBytes,
|
|
182
|
+
auth: {
|
|
183
|
+
enabled: runtimeFallbackSignalConfig.auth.enabled,
|
|
184
|
+
headerName: runtimeFallbackSignalConfig.auth.headerName
|
|
185
|
+
},
|
|
186
|
+
trustPolicy: {
|
|
187
|
+
allowedApps: runtimeFallbackSignalConfig.trustPolicy.allowedApps,
|
|
188
|
+
allowedEntryOrigins: runtimeFallbackSignalConfig.trustPolicy.allowedEntryOrigins,
|
|
189
|
+
enforceRuntimeDigest: runtimeFallbackSignalConfig.trustPolicy.enforceRuntimeDigest,
|
|
190
|
+
expectedRuntimeDigestsCount: Object.keys(runtimeFallbackSignalConfig.trustPolicy.expectedRuntimeDigests).length,
|
|
191
|
+
maxSignalsPerWindow: runtimeFallbackSignalConfig.trustPolicy.maxSignalsPerWindow,
|
|
192
|
+
windowMs: runtimeFallbackSignalConfig.trustPolicy.windowMs,
|
|
193
|
+
dedupeWindowMs: runtimeFallbackSignalConfig.trustPolicy.dedupeWindowMs
|
|
194
|
+
}
|
|
195
|
+
} : {
|
|
196
|
+
enabled: false
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
} catch (error) {
|
|
200
|
+
const signalError = error;
|
|
201
|
+
return c.json({
|
|
202
|
+
ok: false,
|
|
203
|
+
error: signalError instanceof Error ? signalError.message : String(signalError)
|
|
204
|
+
}, getRuntimeSignalErrorStatusCode(signalError));
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
middlewares.push({
|
|
209
|
+
name: 'inject-telemetry',
|
|
210
|
+
handler: async (c, next)=>{
|
|
211
|
+
const monitors = c.get('monitors');
|
|
212
|
+
if (monitors) {
|
|
213
|
+
const traceContext = parseTraceparent(c.req.header('traceparent'));
|
|
214
|
+
const monitor = (event)=>{
|
|
215
|
+
registry.enqueue(toTelemetryEnvelope(event, {
|
|
216
|
+
service: serviceName,
|
|
217
|
+
module: moduleName,
|
|
218
|
+
environment: environmentName,
|
|
219
|
+
traceId: traceContext?.traceId,
|
|
220
|
+
spanId: traceContext?.spanId,
|
|
221
|
+
attributes: {
|
|
222
|
+
requestMethod: c.req.method,
|
|
223
|
+
requestPath: c.req.path
|
|
224
|
+
}
|
|
225
|
+
}));
|
|
226
|
+
};
|
|
227
|
+
monitors.push(monitor);
|
|
228
|
+
}
|
|
229
|
+
await next();
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
let telemetryLaneClosed = false;
|
|
233
|
+
const closeTelemetryLane = async ()=>{
|
|
234
|
+
if (telemetryLaneClosed) return;
|
|
235
|
+
telemetryLaneClosed = true;
|
|
236
|
+
activeTelemetryLaneClosers.delete(closeTelemetryLane);
|
|
237
|
+
contractGateAutopilot?.stop();
|
|
238
|
+
canaryOrchestrator?.stop();
|
|
239
|
+
await registry.shutdown();
|
|
240
|
+
};
|
|
241
|
+
let prepared = false;
|
|
242
|
+
api.onPrepare(async ()=>{
|
|
243
|
+
if (prepared) return;
|
|
244
|
+
prepared = true;
|
|
245
|
+
const { nodeServer } = api.getServerContext();
|
|
246
|
+
if (nodeServer && 'function' == typeof nodeServer.once) nodeServer.once('close', ()=>{
|
|
247
|
+
closeTelemetryLane();
|
|
248
|
+
});
|
|
249
|
+
activeTelemetryLaneClosers.add(closeTelemetryLane);
|
|
250
|
+
ensureTelemetryBeforeExitHook();
|
|
251
|
+
if (telemetryConfig.exporters?.otlp?.enabled) {
|
|
252
|
+
maybeWarnLegacyOtlpEndpoint(telemetryConfig.exporters.otlp.endpoint);
|
|
253
|
+
await registry.register(createOtlpTelemetryExporter(telemetryConfig.exporters.otlp));
|
|
254
|
+
}
|
|
255
|
+
if (telemetryConfig.exporters?.victoriaMetrics?.enabled) await registry.register(createVictoriaMetricsTelemetryExporter(telemetryConfig.exporters.victoriaMetrics));
|
|
256
|
+
await registry.startupHealthCheck({
|
|
257
|
+
failLoud: telemetryConfig.failLoudStartup ?? true
|
|
258
|
+
});
|
|
259
|
+
if (!canaryOrchestrator) return;
|
|
260
|
+
canaryOrchestrator.start();
|
|
261
|
+
if (gateSnapshotStorePromise) {
|
|
262
|
+
const gateSnapshotStore = await gateSnapshotStorePromise;
|
|
263
|
+
contractGateAutopilot = new ContractGateAutopilot({
|
|
264
|
+
orchestrator: canaryOrchestrator,
|
|
265
|
+
gateSnapshotPath: resolveContractGateSnapshotPath(appDirectory, canaryConfig?.autopilot?.gateSnapshotPath),
|
|
266
|
+
gateSnapshotStore,
|
|
267
|
+
pollIntervalMs: canaryConfig?.autopilot?.pollIntervalMs,
|
|
268
|
+
gateStaleAfterMs: canaryConfig?.autopilot?.gateStaleAfterMs
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
if (contractGateAutopilot) await contractGateAutopilot.start();
|
|
272
|
+
canaryOrchestrator.evaluate();
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
export { DEFAULT_RUNTIME_FALLBACK_SIGNAL_ENDPOINT, DEFAULT_RUNTIME_STATUS_ENDPOINT, TelemetryCanaryOrchestrator, TelemetryRegistry, TelemetryStartupHealthError, createOtlpTelemetryExporter, createRuntimeFallbackSignalRuntimeState, createRuntimeSignalError, createTelemetryAwareMetrics, createVictoriaMetricsTelemetryExporter, enforceRuntimeFallbackSignalAuthToken, enforceRuntimeFallbackSignalTrustPolicy, getRuntimeSignalErrorStatusCode, hasEnabledTelemetryExporters, injectTelemetryPlugin, normalizeRequiredRuntimeFallbackSignalAuthConfig, normalizeRuntimeFallbackSignalAuthConfig, normalizeRuntimeFallbackTrustPolicy, parseRuntimeFallbackSignalPayloadFromRawBody, resolveRuntimeFallbackSignalEndpoint, resolveTelemetrySloOptions };
|