@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,584 @@
1
+ import { createHash, timingSafeEqual } from 'node:crypto';
2
+ import type { Context, ServerEnv } from '@modern-js/server-core';
3
+ import {
4
+ CONTRACT_GATE_SNAPSHOT_SCHEMA_VERSION,
5
+ type ContractGateSnapshotStore,
6
+ type GateSnapshot,
7
+ } from './contractGateSnapshotStore';
8
+
9
+ export const DEFAULT_RUNTIME_FALLBACK_SIGNAL_ENDPOINT =
10
+ '/_modern/contract-gates/runtime-fallback';
11
+ export const DEFAULT_RUNTIME_STATUS_ENDPOINT = '/_modern/runtime/status';
12
+ export const DEFAULT_RUNTIME_FALLBACK_GATE_NAME = 'runtime-mf-fallback-health';
13
+ export const DEFAULT_RUNTIME_FALLBACK_FAILURE_HOLD_MS = 5 * 60_000;
14
+ export const DEFAULT_RUNTIME_FALLBACK_MAX_BODY_BYTES = 16 * 1024;
15
+ const DEFAULT_RUNTIME_FALLBACK_AUTH_HEADER = 'x-modernjs-runtime-signal-token';
16
+ const DEFAULT_RUNTIME_FALLBACK_TRUST_MAX_SIGNALS_PER_WINDOW = 30;
17
+ const DEFAULT_RUNTIME_FALLBACK_TRUST_WINDOW_MS = 60_000;
18
+ const DEFAULT_RUNTIME_FALLBACK_TRUST_DEDUPE_WINDOW_MS = 10_000;
19
+
20
+ export type RuntimeSignalErrorCode =
21
+ | 'PAYLOAD_TOO_LARGE'
22
+ | 'INVALID_PAYLOAD'
23
+ | 'RATE_LIMITED'
24
+ | 'UNAUTHORIZED'
25
+ | 'UNTRUSTED_SOURCE';
26
+
27
+ export type RuntimeSignalError = Error & {
28
+ code?: RuntimeSignalErrorCode;
29
+ };
30
+
31
+ export type RuntimeFallbackSignalTrustPolicy = {
32
+ allowedApps: string[];
33
+ allowedEntryOrigins: string[];
34
+ expectedRuntimeDigests: Record<string, string>;
35
+ enforceRuntimeDigest: boolean;
36
+ maxSignalsPerWindow: number;
37
+ windowMs: number;
38
+ dedupeWindowMs: number;
39
+ };
40
+
41
+ type RuntimeFallbackSignalRateLimitState = {
42
+ count: number;
43
+ windowStartedAt: number;
44
+ };
45
+
46
+ export type RuntimeFallbackSignalAuthConfig = {
47
+ enabled: boolean;
48
+ headerName: string;
49
+ expectedValue?: string;
50
+ };
51
+
52
+ export type RuntimeFallbackSignalRuntimeState = {
53
+ rateLimitBySource: Map<string, RuntimeFallbackSignalRateLimitState>;
54
+ dedupeByFingerprint: Map<string, number>;
55
+ };
56
+
57
+ export type RuntimeFallbackSignalTrustContext = {
58
+ trustPolicy: RuntimeFallbackSignalTrustPolicy;
59
+ runtimeState: RuntimeFallbackSignalRuntimeState;
60
+ };
61
+
62
+ export type RuntimeFallbackSignalConfig = {
63
+ endpoint: string;
64
+ gateName: string;
65
+ gateSnapshotStore: Promise<ContractGateSnapshotStore>;
66
+ failureHoldMs: number;
67
+ maxBodyBytes: number;
68
+ auth: RuntimeFallbackSignalAuthConfig;
69
+ trustPolicy: RuntimeFallbackSignalTrustPolicy;
70
+ runtimeState: RuntimeFallbackSignalRuntimeState;
71
+ };
72
+
73
+ export function resolveRuntimeFallbackSignalEndpoint(
74
+ configuredEndpoint?: string,
75
+ ) {
76
+ const rawEndpoint = configuredEndpoint?.trim();
77
+ if (!rawEndpoint) {
78
+ return DEFAULT_RUNTIME_FALLBACK_SIGNAL_ENDPOINT;
79
+ }
80
+
81
+ if (rawEndpoint.startsWith('/')) {
82
+ return rawEndpoint;
83
+ }
84
+
85
+ try {
86
+ return (
87
+ new URL(rawEndpoint).pathname || DEFAULT_RUNTIME_FALLBACK_SIGNAL_ENDPOINT
88
+ );
89
+ } catch (_error) {
90
+ return `/${rawEndpoint.replace(/^\/+/, '')}`;
91
+ }
92
+ }
93
+
94
+ export function createRuntimeSignalError(
95
+ message: string,
96
+ code: RuntimeSignalError['code'],
97
+ ) {
98
+ const error = new Error(message) as RuntimeSignalError;
99
+ error.code = code;
100
+ return error;
101
+ }
102
+
103
+ function getUtf8ByteLength(input: string) {
104
+ if (typeof Buffer !== 'undefined') {
105
+ return Buffer.byteLength(input);
106
+ }
107
+ return new TextEncoder().encode(input).length;
108
+ }
109
+
110
+ function normalizeRuntimeSignalOrigin(value: unknown) {
111
+ if (typeof value !== 'string' || value.trim().length === 0) {
112
+ return undefined;
113
+ }
114
+
115
+ try {
116
+ return new URL(value).origin;
117
+ } catch (_error) {
118
+ return undefined;
119
+ }
120
+ }
121
+
122
+ function normalizeRuntimeSignalAppName(payload: Record<string, unknown>) {
123
+ if (typeof payload.appName !== 'string') {
124
+ return 'unknown';
125
+ }
126
+ const normalized = payload.appName.trim();
127
+ return normalized.length > 0 ? normalized : 'unknown';
128
+ }
129
+
130
+ function normalizeRuntimeSignalRuntimeDigest(payload: Record<string, unknown>) {
131
+ if (
132
+ typeof payload.runtimeDigest === 'string' &&
133
+ payload.runtimeDigest.trim()
134
+ ) {
135
+ return payload.runtimeDigest.trim();
136
+ }
137
+
138
+ const metadata = payload.metadata;
139
+ if (
140
+ metadata &&
141
+ typeof metadata === 'object' &&
142
+ !Array.isArray(metadata) &&
143
+ typeof (metadata as Record<string, unknown>).runtimeDigest === 'string'
144
+ ) {
145
+ const digest = String(
146
+ (metadata as Record<string, unknown>).runtimeDigest,
147
+ ).trim();
148
+ if (digest) {
149
+ return digest;
150
+ }
151
+ }
152
+
153
+ return undefined;
154
+ }
155
+
156
+ export function normalizeRuntimeFallbackSignalAuthConfig(
157
+ configured:
158
+ | {
159
+ enabled?: boolean;
160
+ headerName?: string;
161
+ expectedValue?: string;
162
+ expectedValueEnv?: string;
163
+ }
164
+ | undefined,
165
+ ): RuntimeFallbackSignalAuthConfig {
166
+ const headerName =
167
+ typeof configured?.headerName === 'string' && configured.headerName.trim()
168
+ ? configured.headerName.trim().toLowerCase()
169
+ : DEFAULT_RUNTIME_FALLBACK_AUTH_HEADER;
170
+ const expectedFromEnv =
171
+ typeof configured?.expectedValueEnv === 'string' &&
172
+ configured.expectedValueEnv.trim().length > 0
173
+ ? process.env[configured.expectedValueEnv.trim()]
174
+ : undefined;
175
+ const expectedFromConfig =
176
+ typeof configured?.expectedValue === 'string' &&
177
+ configured.expectedValue.trim().length > 0
178
+ ? configured.expectedValue.trim()
179
+ : undefined;
180
+ const expectedValue = expectedFromConfig || expectedFromEnv;
181
+ const enabled = configured?.enabled === true;
182
+
183
+ if (enabled && !expectedValue) {
184
+ throw new Error(
185
+ '[telemetry.canary.autopilot.runtimeFallbackSignal] auth.enabled is true but no expected token is configured',
186
+ );
187
+ }
188
+
189
+ return {
190
+ enabled,
191
+ headerName,
192
+ expectedValue,
193
+ };
194
+ }
195
+
196
+ /**
197
+ * Normalizes the auth config for the runtime fallback signal endpoint when the
198
+ * endpoint itself is enabled. The endpoint can persist failing contract gates
199
+ * (a canary kill switch), so it always requires a token: auth cannot be
200
+ * disabled and a token must be configured via `auth.expectedValue` or
201
+ * `auth.expectedValueEnv`.
202
+ */
203
+ export function normalizeRequiredRuntimeFallbackSignalAuthConfig(
204
+ configured: Parameters<typeof normalizeRuntimeFallbackSignalAuthConfig>[0],
205
+ ): RuntimeFallbackSignalAuthConfig {
206
+ if (configured?.enabled === false) {
207
+ throw new Error(
208
+ '[telemetry.canary.autopilot.runtimeFallbackSignal] the endpoint cannot be enabled with auth disabled; configure auth.expectedValue or auth.expectedValueEnv',
209
+ );
210
+ }
211
+
212
+ try {
213
+ return normalizeRuntimeFallbackSignalAuthConfig({
214
+ ...configured,
215
+ enabled: true,
216
+ });
217
+ } catch (_error) {
218
+ throw new Error(
219
+ '[telemetry.canary.autopilot.runtimeFallbackSignal] enabling the endpoint requires an auth token; configure auth.expectedValue or auth.expectedValueEnv',
220
+ );
221
+ }
222
+ }
223
+
224
+ /**
225
+ * Constant-time token comparison. Both sides are hashed first so neither the
226
+ * comparison time nor the early length check leaks information about the
227
+ * expected secret.
228
+ */
229
+ function safeTokenEquals(candidate: string, expected: string) {
230
+ const candidateDigest = createHash('sha256').update(candidate).digest();
231
+ const expectedDigest = createHash('sha256').update(expected).digest();
232
+ return timingSafeEqual(candidateDigest, expectedDigest);
233
+ }
234
+
235
+ export function enforceRuntimeFallbackSignalAuthToken(
236
+ token: string | undefined,
237
+ authConfig: RuntimeFallbackSignalAuthConfig,
238
+ ) {
239
+ if (!authConfig.enabled) {
240
+ return;
241
+ }
242
+
243
+ if (
244
+ !token ||
245
+ !authConfig.expectedValue ||
246
+ !safeTokenEquals(token, authConfig.expectedValue)
247
+ ) {
248
+ throw createRuntimeSignalError(
249
+ 'runtime fallback signal auth failed',
250
+ 'UNAUTHORIZED',
251
+ );
252
+ }
253
+ }
254
+
255
+ export function enforceRuntimeFallbackSignalAuth(
256
+ c: Context<ServerEnv>,
257
+ runtimeSignalConfig: RuntimeFallbackSignalConfig,
258
+ ) {
259
+ enforceRuntimeFallbackSignalAuthToken(
260
+ c.req.header(runtimeSignalConfig.auth.headerName),
261
+ runtimeSignalConfig.auth,
262
+ );
263
+ }
264
+
265
+ export function normalizeRuntimeFallbackTrustPolicy(
266
+ configured:
267
+ | {
268
+ allowedApps?: string[];
269
+ allowedEntryOrigins?: string[];
270
+ expectedRuntimeDigests?: Record<string, string>;
271
+ enforceRuntimeDigest?: boolean;
272
+ maxSignalsPerWindow?: number;
273
+ windowMs?: number;
274
+ dedupeWindowMs?: number;
275
+ }
276
+ | undefined,
277
+ ): RuntimeFallbackSignalTrustPolicy {
278
+ const allowedApps = Array.isArray(configured?.allowedApps)
279
+ ? configured!.allowedApps
280
+ .map(item => (typeof item === 'string' ? item.trim() : ''))
281
+ .filter(Boolean)
282
+ : [];
283
+ const allowedEntryOrigins = Array.isArray(configured?.allowedEntryOrigins)
284
+ ? configured!.allowedEntryOrigins
285
+ .map(item => normalizeRuntimeSignalOrigin(item))
286
+ .filter((item): item is string => Boolean(item))
287
+ : [];
288
+
289
+ const expectedRuntimeDigestsRaw = configured?.expectedRuntimeDigests || {};
290
+ const expectedRuntimeDigests: Record<string, string> = {};
291
+ Object.entries(expectedRuntimeDigestsRaw).forEach(([appName, digest]) => {
292
+ if (
293
+ typeof appName === 'string' &&
294
+ appName.trim().length > 0 &&
295
+ typeof digest === 'string' &&
296
+ digest.trim().length > 0
297
+ ) {
298
+ expectedRuntimeDigests[appName.trim()] = digest.trim();
299
+ }
300
+ });
301
+
302
+ return {
303
+ allowedApps,
304
+ allowedEntryOrigins,
305
+ expectedRuntimeDigests,
306
+ enforceRuntimeDigest: configured?.enforceRuntimeDigest === true,
307
+ maxSignalsPerWindow: Math.max(
308
+ 1,
309
+ Math.floor(
310
+ configured?.maxSignalsPerWindow ??
311
+ DEFAULT_RUNTIME_FALLBACK_TRUST_MAX_SIGNALS_PER_WINDOW,
312
+ ),
313
+ ),
314
+ windowMs: Math.max(
315
+ 1_000,
316
+ Math.floor(
317
+ configured?.windowMs ?? DEFAULT_RUNTIME_FALLBACK_TRUST_WINDOW_MS,
318
+ ),
319
+ ),
320
+ dedupeWindowMs: Math.max(
321
+ 0,
322
+ Math.floor(
323
+ configured?.dedupeWindowMs ??
324
+ DEFAULT_RUNTIME_FALLBACK_TRUST_DEDUPE_WINDOW_MS,
325
+ ),
326
+ ),
327
+ };
328
+ }
329
+
330
+ export function createRuntimeFallbackSignalRuntimeState(): RuntimeFallbackSignalRuntimeState {
331
+ return {
332
+ rateLimitBySource: new Map(),
333
+ dedupeByFingerprint: new Map(),
334
+ };
335
+ }
336
+
337
+ function cleanupRuntimeFallbackSignalRuntimeState(
338
+ now: number,
339
+ runtimeState: RuntimeFallbackSignalRuntimeState,
340
+ trustPolicy: RuntimeFallbackSignalTrustPolicy,
341
+ ) {
342
+ const dedupeExpiryMs = Math.max(
343
+ trustPolicy.dedupeWindowMs,
344
+ trustPolicy.windowMs,
345
+ 1_000,
346
+ );
347
+ runtimeState.dedupeByFingerprint.forEach((lastSeenAt, fingerprint) => {
348
+ if (now - lastSeenAt > dedupeExpiryMs) {
349
+ runtimeState.dedupeByFingerprint.delete(fingerprint);
350
+ }
351
+ });
352
+
353
+ runtimeState.rateLimitBySource.forEach((state, source) => {
354
+ if (now - state.windowStartedAt > trustPolicy.windowMs * 2) {
355
+ runtimeState.rateLimitBySource.delete(source);
356
+ }
357
+ });
358
+ }
359
+
360
+ export type RuntimeFallbackSignalSource = {
361
+ /**
362
+ * Server-trusted connection identity (socket remote address). Never derive
363
+ * this from request headers or the payload: both are attacker-controlled
364
+ * and would let callers reset their own rate-limit budget.
365
+ */
366
+ remoteAddress?: string;
367
+ };
368
+
369
+ export function enforceRuntimeFallbackSignalTrustPolicy(
370
+ payload: Record<string, unknown>,
371
+ runtimeSignalContext: RuntimeFallbackSignalTrustContext,
372
+ source: RuntimeFallbackSignalSource = {},
373
+ ) {
374
+ const { trustPolicy, runtimeState } = runtimeSignalContext;
375
+ const now = Date.now();
376
+ cleanupRuntimeFallbackSignalRuntimeState(now, runtimeState, trustPolicy);
377
+
378
+ const appName = normalizeRuntimeSignalAppName(payload);
379
+ const entryOrigin = normalizeRuntimeSignalOrigin(payload.entry);
380
+ const runtimeDigest = normalizeRuntimeSignalRuntimeDigest(payload);
381
+
382
+ if (
383
+ trustPolicy.allowedApps.length > 0 &&
384
+ !trustPolicy.allowedApps.includes(appName)
385
+ ) {
386
+ throw createRuntimeSignalError(
387
+ `runtime fallback signal app "${appName}" is not trusted`,
388
+ 'UNTRUSTED_SOURCE',
389
+ );
390
+ }
391
+
392
+ if (trustPolicy.allowedEntryOrigins.length > 0) {
393
+ if (
394
+ !entryOrigin ||
395
+ !trustPolicy.allowedEntryOrigins.includes(entryOrigin)
396
+ ) {
397
+ throw createRuntimeSignalError(
398
+ `runtime fallback signal entry origin "${entryOrigin || 'unknown'}" is not trusted`,
399
+ 'UNTRUSTED_SOURCE',
400
+ );
401
+ }
402
+ }
403
+
404
+ const expectedDigest = trustPolicy.expectedRuntimeDigests[appName];
405
+ if (expectedDigest && runtimeDigest !== expectedDigest) {
406
+ throw createRuntimeSignalError(
407
+ `runtime fallback runtimeDigest mismatch for app "${appName}"`,
408
+ 'UNTRUSTED_SOURCE',
409
+ );
410
+ }
411
+
412
+ if (trustPolicy.enforceRuntimeDigest && !runtimeDigest) {
413
+ throw createRuntimeSignalError(
414
+ `runtime fallback signal for app "${appName}" is missing runtimeDigest`,
415
+ 'UNTRUSTED_SOURCE',
416
+ );
417
+ }
418
+
419
+ const dedupeFingerprint = JSON.stringify({
420
+ appName,
421
+ entryOrigin: entryOrigin || 'unknown',
422
+ reason: payload.reason || 'runtime_fallback',
423
+ phase: payload.phase || 'unknown',
424
+ runtimeDigest: runtimeDigest || 'unknown',
425
+ });
426
+ const dedupeWindowMs = trustPolicy.dedupeWindowMs;
427
+ if (dedupeWindowMs > 0) {
428
+ const lastSeenAt = runtimeState.dedupeByFingerprint.get(dedupeFingerprint);
429
+ runtimeState.dedupeByFingerprint.set(dedupeFingerprint, now);
430
+ if (typeof lastSeenAt === 'number' && now - lastSeenAt <= dedupeWindowMs) {
431
+ return {
432
+ deduped: true,
433
+ };
434
+ }
435
+ } else {
436
+ runtimeState.dedupeByFingerprint.set(dedupeFingerprint, now);
437
+ }
438
+
439
+ // Rate-limit on the server-observed connection identity, not on
440
+ // payload-derived values (appName/entryOrigin) that an attacker can rotate
441
+ // to mint fresh rate-limit budgets.
442
+ const remoteAddress = source.remoteAddress?.trim();
443
+ const sourceKey = remoteAddress || 'unknown-remote';
444
+ const rateState = runtimeState.rateLimitBySource.get(sourceKey);
445
+ if (!rateState || now - rateState.windowStartedAt > trustPolicy.windowMs) {
446
+ runtimeState.rateLimitBySource.set(sourceKey, {
447
+ count: 1,
448
+ windowStartedAt: now,
449
+ });
450
+ } else {
451
+ if (rateState.count >= trustPolicy.maxSignalsPerWindow) {
452
+ throw createRuntimeSignalError(
453
+ `runtime fallback signal rate-limited for source "${sourceKey}"`,
454
+ 'RATE_LIMITED',
455
+ );
456
+ }
457
+ rateState.count += 1;
458
+ }
459
+
460
+ return {
461
+ deduped: false,
462
+ };
463
+ }
464
+
465
+ export async function parseRuntimeFallbackSignalPayload(
466
+ c: Context<ServerEnv>,
467
+ maxBodyBytes: number,
468
+ ) {
469
+ const contentLengthHeader = c.req.header('content-length');
470
+ if (contentLengthHeader) {
471
+ const contentLength = Number.parseInt(contentLengthHeader, 10);
472
+ if (Number.isFinite(contentLength) && contentLength > maxBodyBytes) {
473
+ throw createRuntimeSignalError(
474
+ 'runtime fallback signal payload too large',
475
+ 'PAYLOAD_TOO_LARGE',
476
+ );
477
+ }
478
+ }
479
+
480
+ const rawBody = await c.req.raw.text();
481
+ const payload = parseRuntimeFallbackSignalPayloadFromRawBody(
482
+ rawBody,
483
+ maxBodyBytes,
484
+ );
485
+ return {
486
+ rawBody,
487
+ payload,
488
+ };
489
+ }
490
+
491
+ export function parseRuntimeFallbackSignalPayloadFromRawBody(
492
+ rawBody: string,
493
+ maxBodyBytes: number,
494
+ ) {
495
+ if (!rawBody || rawBody.trim().length === 0) {
496
+ throw createRuntimeSignalError(
497
+ 'runtime fallback signal body is empty',
498
+ 'INVALID_PAYLOAD',
499
+ );
500
+ }
501
+ if (getUtf8ByteLength(rawBody) > maxBodyBytes) {
502
+ throw createRuntimeSignalError(
503
+ 'runtime fallback signal payload too large',
504
+ 'PAYLOAD_TOO_LARGE',
505
+ );
506
+ }
507
+
508
+ let payload: unknown;
509
+ try {
510
+ payload = JSON.parse(rawBody);
511
+ } catch (_error) {
512
+ throw createRuntimeSignalError(
513
+ 'runtime fallback signal body must be valid JSON',
514
+ 'INVALID_PAYLOAD',
515
+ );
516
+ }
517
+
518
+ if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
519
+ throw createRuntimeSignalError(
520
+ 'runtime fallback signal body must be a JSON object',
521
+ 'INVALID_PAYLOAD',
522
+ );
523
+ }
524
+
525
+ return payload as Record<string, unknown>;
526
+ }
527
+
528
+ export function getRuntimeSignalErrorStatusCode(
529
+ signalError: RuntimeSignalError,
530
+ ): 400 | 401 | 403 | 413 | 429 | 500 {
531
+ if (signalError.code === 'PAYLOAD_TOO_LARGE') {
532
+ return 413;
533
+ }
534
+ if (signalError.code === 'INVALID_PAYLOAD') {
535
+ return 400;
536
+ }
537
+ if (signalError.code === 'UNAUTHORIZED') {
538
+ return 401;
539
+ }
540
+ if (signalError.code === 'RATE_LIMITED') {
541
+ return 429;
542
+ }
543
+ if (signalError.code === 'UNTRUSTED_SOURCE') {
544
+ return 403;
545
+ }
546
+ return 500;
547
+ }
548
+
549
+ export async function persistRuntimeFallbackContractGate(
550
+ payload: Record<string, unknown>,
551
+ runtimeSignalConfig: RuntimeFallbackSignalConfig,
552
+ ) {
553
+ const now = Date.now();
554
+ const gateSnapshotStore = await runtimeSignalConfig.gateSnapshotStore;
555
+ const snapshot: GateSnapshot = (await gateSnapshotStore.readSnapshot()) || {};
556
+ const existingGates =
557
+ snapshot.gates && typeof snapshot.gates === 'object' ? snapshot.gates : {};
558
+
559
+ const reason =
560
+ typeof payload.reason === 'string' ? payload.reason : 'runtime_fallback';
561
+ const phase = typeof payload.phase === 'string' ? payload.phase : 'unknown';
562
+ const appName =
563
+ typeof payload.appName === 'string' ? payload.appName : 'unknown';
564
+ const entry = typeof payload.entry === 'string' ? payload.entry : undefined;
565
+
566
+ snapshot.schemaVersion =
567
+ typeof snapshot.schemaVersion === 'number'
568
+ ? snapshot.schemaVersion
569
+ : CONTRACT_GATE_SNAPSHOT_SCHEMA_VERSION;
570
+ snapshot.updatedAt = now;
571
+ snapshot.gates = {
572
+ ...existingGates,
573
+ [runtimeSignalConfig.gateName]: {
574
+ passed: false,
575
+ reason: `runtime_fallback:${reason} phase=${phase} app=${appName}${entry ? ` entry=${entry}` : ''}`,
576
+ updatedAt: now,
577
+ expiresAt: now + runtimeSignalConfig.failureHoldMs,
578
+ source: 'runtime-mf-fallback-signal',
579
+ metadata: payload,
580
+ },
581
+ };
582
+
583
+ await gateSnapshotStore.writeSnapshot(snapshot);
584
+ }