@apifuse/provider-sdk 2.2.0-beta.3 → 2.2.0-beta.4

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 (165) hide show
  1. package/AUTHORING.md +14 -0
  2. package/CHANGELOG.md +4 -0
  3. package/bin/apifuse-check.ts +18 -58
  4. package/bin/apifuse-create.ts +1 -1
  5. package/bin/apifuse-dev.ts +27 -52
  6. package/bin/apifuse-pack-smoke.ts +22 -81
  7. package/bin/apifuse-pack-types.ts +266 -0
  8. package/bin/apifuse-perf.ts +45 -127
  9. package/bin/apifuse-record.ts +23 -61
  10. package/bin/apifuse-submit-check.ts +26 -21
  11. package/bin/apifuse.ts +1 -1
  12. package/bin/submit-check-xml.ts +1 -1
  13. package/dist/auth-turn/index.d.ts +1 -1
  14. package/dist/auth.d.ts +2 -2
  15. package/dist/auth.js +9 -18
  16. package/dist/ceremonies/index.d.ts +1 -1
  17. package/dist/ceremonies/index.js +8 -18
  18. package/dist/config/loader.d.ts +2 -2
  19. package/dist/config/loader.js +19 -42
  20. package/dist/contract-serialization.d.ts +2 -2
  21. package/dist/contract-serialization.js +3 -6
  22. package/dist/contract-types.d.ts +2 -2
  23. package/dist/contract.d.ts +3 -3
  24. package/dist/contract.js +4 -6
  25. package/dist/define.d.ts +8 -1
  26. package/dist/define.js +57 -115
  27. package/dist/dev.d.ts +1 -1
  28. package/dist/dev.js +1 -1
  29. package/dist/errors.d.ts +1 -1
  30. package/dist/errors.js +1 -3
  31. package/dist/i18n/catalog.d.ts +2 -2
  32. package/dist/i18n/catalog.js +4 -10
  33. package/dist/i18n/index.d.ts +2 -2
  34. package/dist/i18n/index.js +2 -2
  35. package/dist/i18n/keys.d.ts +2 -2
  36. package/dist/index.d.ts +43 -42
  37. package/dist/index.js +38 -37
  38. package/dist/lint.js +8 -15
  39. package/dist/provider.d.ts +11 -11
  40. package/dist/provider.js +9 -9
  41. package/dist/public-schema-field-lint.d.ts +1 -1
  42. package/dist/recipes/gov-api.js +1 -1
  43. package/dist/runtime/auth-flow.d.ts +1 -1
  44. package/dist/runtime/auth-flow.js +3 -3
  45. package/dist/runtime/browser.d.ts +1 -1
  46. package/dist/runtime/browser.js +15 -29
  47. package/dist/runtime/cache.d.ts +1 -1
  48. package/dist/runtime/cache.js +4 -8
  49. package/dist/runtime/choice.d.ts +1 -1
  50. package/dist/runtime/choice.js +12 -34
  51. package/dist/runtime/credential.d.ts +1 -1
  52. package/dist/runtime/credential.js +1 -1
  53. package/dist/runtime/env.d.ts +1 -1
  54. package/dist/runtime/executor.d.ts +1 -1
  55. package/dist/runtime/executor.js +2 -2
  56. package/dist/runtime/http.d.ts +2 -2
  57. package/dist/runtime/http.js +5 -5
  58. package/dist/runtime/insights.d.ts +1 -1
  59. package/dist/runtime/insights.js +6 -13
  60. package/dist/runtime/instrumentation.d.ts +2 -2
  61. package/dist/runtime/instrumentation.js +5 -15
  62. package/dist/runtime/keyring.js +1 -1
  63. package/dist/runtime/namespace.js +1 -1
  64. package/dist/runtime/otlp.d.ts +1 -1
  65. package/dist/runtime/perf.d.ts +1 -1
  66. package/dist/runtime/provider.d.ts +1 -1
  67. package/dist/runtime/provider.js +1 -2
  68. package/dist/runtime/proxy-errors.d.ts +1 -1
  69. package/dist/runtime/proxy-errors.js +3 -5
  70. package/dist/runtime/proxy-retry-policy.d.ts +2 -2
  71. package/dist/runtime/proxy-retry-policy.js +2 -2
  72. package/dist/runtime/proxy-telemetry.d.ts +1 -1
  73. package/dist/runtime/proxy-telemetry.js +16 -48
  74. package/dist/runtime/redis.d.ts +1 -1
  75. package/dist/runtime/redis.js +2 -4
  76. package/dist/runtime/request-options.d.ts +1 -1
  77. package/dist/runtime/state.d.ts +2 -2
  78. package/dist/runtime/state.js +3 -3
  79. package/dist/runtime/stealth.d.ts +2 -2
  80. package/dist/runtime/stealth.js +6 -6
  81. package/dist/runtime/stt.d.ts +1 -1
  82. package/dist/runtime/stt.js +11 -15
  83. package/dist/runtime/trace.d.ts +2 -2
  84. package/dist/runtime/trace.js +2 -4
  85. package/dist/runtime/waterfall.d.ts +1 -1
  86. package/dist/schema.d.ts +1 -1
  87. package/dist/schema.js +7 -15
  88. package/dist/serve.d.ts +1 -1
  89. package/dist/serve.js +1 -1
  90. package/dist/server/index.d.ts +7 -7
  91. package/dist/server/index.js +6 -6
  92. package/dist/server/self-test-redaction.d.ts +1 -1
  93. package/dist/server/self-test-redaction.js +1 -1
  94. package/dist/server/self-test.d.ts +3 -3
  95. package/dist/server/self-test.js +3 -3
  96. package/dist/server/serve.d.ts +3 -3
  97. package/dist/server/serve.js +36 -49
  98. package/dist/server/types.js +2 -7
  99. package/dist/stealth/profiles.d.ts +1 -1
  100. package/dist/stealth/profiles.js +5 -14
  101. package/dist/stream.d.ts +1 -1
  102. package/dist/testing/index.d.ts +2 -2
  103. package/dist/testing/index.js +2 -2
  104. package/dist/testing/run.d.ts +1 -1
  105. package/dist/testing/run.js +7 -14
  106. package/dist/types.d.ts +57 -0
  107. package/dist/user-input.d.ts +30 -0
  108. package/dist/user-input.js +66 -0
  109. package/package.json +6 -4
  110. package/src/auth-turn/index.ts +1 -1
  111. package/src/auth.ts +28 -86
  112. package/src/ceremonies/index.ts +24 -78
  113. package/src/config/loader.ts +58 -176
  114. package/src/contract-serialization.ts +5 -11
  115. package/src/contract-types.ts +2 -2
  116. package/src/contract.ts +12 -28
  117. package/src/define.ts +174 -495
  118. package/src/dev.ts +4 -9
  119. package/src/errors.ts +4 -11
  120. package/src/i18n/catalog.ts +10 -32
  121. package/src/i18n/index.ts +2 -2
  122. package/src/i18n/keys.ts +5 -11
  123. package/src/index.ts +44 -42
  124. package/src/lint.ts +89 -159
  125. package/src/provider.ts +12 -11
  126. package/src/public-schema-field-lint.ts +7 -33
  127. package/src/recipes/gov-api.ts +2 -5
  128. package/src/runtime/auth-flow.ts +5 -7
  129. package/src/runtime/browser.ts +64 -187
  130. package/src/runtime/cache.ts +20 -67
  131. package/src/runtime/choice.ts +55 -129
  132. package/src/runtime/credential.ts +2 -2
  133. package/src/runtime/env.ts +1 -1
  134. package/src/runtime/executor.ts +9 -19
  135. package/src/runtime/http.ts +7 -7
  136. package/src/runtime/insights.ts +15 -53
  137. package/src/runtime/instrumentation.ts +19 -58
  138. package/src/runtime/keyring.ts +7 -19
  139. package/src/runtime/namespace.ts +2 -7
  140. package/src/runtime/otlp.ts +12 -23
  141. package/src/runtime/perf.ts +1 -1
  142. package/src/runtime/provider.ts +4 -9
  143. package/src/runtime/proxy-errors.ts +17 -38
  144. package/src/runtime/proxy-retry-policy.ts +3 -3
  145. package/src/runtime/proxy-telemetry.ts +23 -72
  146. package/src/runtime/redis.ts +4 -12
  147. package/src/runtime/request-options.ts +4 -13
  148. package/src/runtime/state.ts +30 -109
  149. package/src/runtime/stealth.ts +8 -8
  150. package/src/runtime/stt.ts +38 -94
  151. package/src/runtime/trace.ts +14 -44
  152. package/src/runtime/waterfall.ts +5 -18
  153. package/src/schema.ts +23 -84
  154. package/src/serve.ts +1 -1
  155. package/src/server/index.ts +7 -7
  156. package/src/server/self-test-redaction.ts +2 -2
  157. package/src/server/self-test.ts +5 -5
  158. package/src/server/serve.ts +87 -242
  159. package/src/server/types.ts +6 -19
  160. package/src/stealth/profiles.ts +10 -26
  161. package/src/stream.ts +8 -19
  162. package/src/testing/index.ts +2 -2
  163. package/src/testing/run.ts +19 -67
  164. package/src/types.ts +58 -0
  165. package/src/user-input.ts +118 -0
package/src/define.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import ms from "ms";
2
2
 
3
- import { ProviderError, ValidationError } from "./errors";
4
- import { safeParseSchemaSync } from "./schema";
3
+ import { ProviderError, ValidationError } from "./errors.js";
4
+ import { safeParseSchemaSync } from "./schema.js";
5
5
  import type {
6
6
  AuthConfig,
7
7
  BrowserEngine,
@@ -22,6 +22,7 @@ import type {
22
22
  OperationWebSocketTransport,
23
23
  ProviderAccessConfig,
24
24
  ProviderDefinition,
25
+ ProviderDeploymentOverrides,
25
26
  ProviderHealthMonitorConfig,
26
27
  ProviderProxyConfig,
27
28
  ProviderPublicProfile,
@@ -32,7 +33,7 @@ import type {
32
33
  SchemaLike,
33
34
  SmsOtpMatcherDefinition,
34
35
  StealthPlatform,
35
- } from "./types";
36
+ } from "./types.js";
36
37
  import {
37
38
  HEALTH_CHECK_DEGRADED_THRESHOLD_MS_MAX,
38
39
  HEALTH_CHECK_DEGRADED_THRESHOLD_MS_MIN,
@@ -48,7 +49,7 @@ import {
48
49
  STREAM_IDLE_TIMEOUT_MS_MIN,
49
50
  STREAM_MAX_DURATION_MS_MAX,
50
51
  STREAM_MAX_DURATION_MS_MIN,
51
- } from "./types";
52
+ } from "./types.js";
52
53
 
53
54
  type ProviderImplementationSourceAccess =
54
55
  | "official_api"
@@ -74,23 +75,10 @@ interface ProviderImplementationProfile {
74
75
  const CONNECTOR_ID_REGEX = /^[a-z][a-z0-9]*(-[a-z][a-z0-9]*)*$/;
75
76
  const OPERATION_ID_REGEX = /^[a-z][a-z0-9]*(?:[-_][a-z0-9]+)*$/;
76
77
  const VALID_RUNTIMES = ["standard", "shared", "browser"] as const;
77
- const VALID_AUTH_MODES = [
78
- "none",
79
- "platform-managed",
80
- "credentials",
81
- "oauth2",
82
- ] as const;
78
+ const VALID_AUTH_MODES = ["none", "platform-managed", "credentials", "oauth2"] as const;
83
79
  const VALID_PROVIDER_ACCESS_VISIBILITIES = ["public", "early_access"] as const;
84
- const VALID_PROVIDER_PROXY_MODES = [
85
- "disabled",
86
- "optional",
87
- "required",
88
- ] as const;
89
- const VALID_PROVIDER_PROXY_PROVIDERS = [
90
- "smartproxy",
91
- "decodo",
92
- "custom",
93
- ] as const;
80
+ const VALID_PROVIDER_PROXY_MODES = ["disabled", "optional", "required"] as const;
81
+ const VALID_PROVIDER_PROXY_PROVIDERS = ["smartproxy", "decodo", "custom"] as const;
94
82
  const VALID_PROVIDER_PROXY_AFFINITIES = [
95
83
  "request",
96
84
  "operation",
@@ -101,23 +89,9 @@ const VALID_PROVIDER_STT_MODES = ["optional", "required"] as const;
101
89
  const SMARTPROXY_APP_KEY_SECRET = "APIFUSE__PROXY__SMARTPROXY_APP_KEY";
102
90
  const RESERVED_OPERATION_IDS = new Set(["auth", "health"]);
103
91
  const MCP_TOOL_NAME_REGEX = /^[A-Za-z][A-Za-z0-9_]{0,127}$/;
104
- const VALID_OPERATION_RISK_CLASSES = [
105
- "read",
106
- "write",
107
- "destructive",
108
- "external-send",
109
- ] as const;
110
- const VALID_OPERATION_APPROVAL_POLICIES = [
111
- "never",
112
- "risk-based",
113
- "always",
114
- ] as const;
115
- const VALID_OPERATION_TRANSPORT_KINDS = [
116
- "json",
117
- "sse",
118
- "http-stream",
119
- "websocket",
120
- ] as const;
92
+ const VALID_OPERATION_RISK_CLASSES = ["read", "write", "destructive", "external-send"] as const;
93
+ const VALID_OPERATION_APPROVAL_POLICIES = ["never", "risk-based", "always"] as const;
94
+ const VALID_OPERATION_TRANSPORT_KINDS = ["json", "sse", "http-stream", "websocket"] as const;
121
95
  const SSE_EVENT_NAME_REGEX = /^[A-Za-z][A-Za-z0-9_.-]{0,127}$/;
122
96
  const WEBSOCKET_SUBPROTOCOL_REGEX = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
123
97
 
@@ -135,18 +109,16 @@ function msDurationMs(value: string): number {
135
109
  function parsePositiveMsDuration(value: string): number | undefined {
136
110
  const trimmed = value.trim();
137
111
  if (!MS_DURATION_PATTERN.test(trimmed)) return undefined;
138
- const parsed = ms(
139
- (trimmed.startsWith("+") ? trimmed.slice(1) : trimmed) as ms.StringValue,
140
- );
112
+ const parsed = ms((trimmed.startsWith("+") ? trimmed.slice(1) : trimmed) as ms.StringValue);
141
113
  if (!Number.isFinite(parsed) || parsed <= 0) return undefined;
142
114
  return parsed;
143
115
  }
144
116
 
145
117
  type ProviderOperation = OperationDefinition<SchemaLike, SchemaLike>;
146
- type OperationConfig<
147
- TInput extends SchemaLike,
148
- TOutput extends SchemaLike,
149
- > = Omit<OperationDefinition<TInput, TOutput>, "handler"> & {
118
+ type OperationConfig<TInput extends SchemaLike, TOutput extends SchemaLike> = Omit<
119
+ OperationDefinition<TInput, TOutput>,
120
+ "handler"
121
+ > & {
150
122
  handler(
151
123
  ctx: Parameters<OperationDefinition<TInput, TOutput>["handler"]>[0],
152
124
  input: InferSchemaOutput<TInput>,
@@ -154,59 +126,44 @@ type OperationConfig<
154
126
  | OperationHandlerResult<InferSchemaOutput<TOutput>>
155
127
  | Promise<OperationHandlerResult<InferSchemaOutput<TOutput>>>;
156
128
  };
157
- type OperationMapConfig<TOperations extends Record<string, ProviderOperation>> =
158
- {
159
- [K in keyof TOperations]: TOperations[K] extends OperationDefinition<
160
- infer TInput,
161
- infer TOutput
162
- >
163
- ? OperationConfig<TInput, TOutput> | OperationDefinition<TInput, TOutput>
164
- : never;
165
- };
166
- type StreamOperationConfig<
167
- TInput extends SchemaLike,
168
- TOutput extends SchemaLike,
169
- > =
129
+ type OperationMapConfig<TOperations extends Record<string, ProviderOperation>> = {
130
+ [K in keyof TOperations]: TOperations[K] extends OperationDefinition<infer TInput, infer TOutput>
131
+ ? OperationConfig<TInput, TOutput> | OperationDefinition<TInput, TOutput>
132
+ : never;
133
+ };
134
+ type StreamOperationConfig<TInput extends SchemaLike, TOutput extends SchemaLike> =
170
135
  | SseOperationConfig<TInput, TOutput>
171
136
  | HttpStreamOperationConfig<TInput, TOutput>
172
137
  | WebSocketOperationConfig<TInput, TOutput>;
173
- type SseOperationConfig<
174
- TInput extends SchemaLike,
175
- TOutput extends SchemaLike,
176
- > = Omit<OperationConfig<TInput, TOutput>, "handler" | "transport"> & {
138
+ type SseOperationConfig<TInput extends SchemaLike, TOutput extends SchemaLike> = Omit<
139
+ OperationConfig<TInput, TOutput>,
140
+ "handler" | "transport"
141
+ > & {
177
142
  transport: OperationSseTransport;
178
143
  handler(
179
144
  ctx: Parameters<OperationDefinition<TInput, TOutput>["handler"]>[0],
180
145
  input: InferSchemaOutput<TInput>,
181
- ):
182
- | AsyncIterable<ProviderStreamEvent>
183
- | Promise<AsyncIterable<ProviderStreamEvent>>;
146
+ ): AsyncIterable<ProviderStreamEvent> | Promise<AsyncIterable<ProviderStreamEvent>>;
184
147
  };
185
- type HttpStreamOperationConfig<
186
- TInput extends SchemaLike,
187
- TOutput extends SchemaLike,
188
- > = Omit<OperationConfig<TInput, TOutput>, "handler" | "transport"> & {
148
+ type HttpStreamOperationConfig<TInput extends SchemaLike, TOutput extends SchemaLike> = Omit<
149
+ OperationConfig<TInput, TOutput>,
150
+ "handler" | "transport"
151
+ > & {
189
152
  transport: OperationHttpStreamTransport;
190
153
  handler(
191
154
  ctx: Parameters<OperationDefinition<TInput, TOutput>["handler"]>[0],
192
155
  input: InferSchemaOutput<TInput>,
193
- ):
194
- | Response
195
- | ReadableStream<Uint8Array>
196
- | Promise<Response | ReadableStream<Uint8Array>>;
156
+ ): Response | ReadableStream<Uint8Array> | Promise<Response | ReadableStream<Uint8Array>>;
197
157
  };
198
- type WebSocketOperationConfig<
199
- TInput extends SchemaLike,
200
- TOutput extends SchemaLike,
201
- > = Omit<OperationConfig<TInput, TOutput>, "handler" | "transport"> & {
158
+ type WebSocketOperationConfig<TInput extends SchemaLike, TOutput extends SchemaLike> = Omit<
159
+ OperationConfig<TInput, TOutput>,
160
+ "handler" | "transport"
161
+ > & {
202
162
  transport: OperationWebSocketTransport;
203
163
  handler(
204
164
  ctx: Parameters<OperationDefinition<TInput, TOutput>["handler"]>[0],
205
165
  input: InferSchemaOutput<TInput>,
206
- ):
207
- | Response
208
- | ReadableStream<Uint8Array>
209
- | Promise<Response | ReadableStream<Uint8Array>>;
166
+ ): Response | ReadableStream<Uint8Array> | Promise<Response | ReadableStream<Uint8Array>>;
210
167
  };
211
168
 
212
169
  type AuthStartNoInputGuard<TConfig> = TConfig extends {
@@ -221,12 +178,17 @@ type AuthStartNoInputGuard<TConfig> = TConfig extends {
221
178
  : unknown
222
179
  : unknown;
223
180
 
224
- export interface ProviderConfig<
225
- TOperations extends Record<string, ProviderOperation>,
226
- > {
181
+ export interface ProviderConfig<TOperations extends Record<string, ProviderOperation>> {
227
182
  id: string;
228
183
  version: string;
229
184
  runtime: "standard" | "shared" | "browser";
185
+ /**
186
+ * Optional deployment overrides, passed through verbatim onto the returned
187
+ * provider definition. The SDK types this field but does not deep-validate
188
+ * it — the APIFuse registry builder owns deployment validation and
189
+ * resolves omitted fields against the runtime deployment profiles.
190
+ */
191
+ deployment?: ProviderDeploymentOverrides;
230
192
  allowedHosts?: string[];
231
193
  stealth?: {
232
194
  profile: string;
@@ -270,35 +232,24 @@ export interface ProviderConfig<
270
232
  }
271
233
 
272
234
  /** Define one provider operation with schema-driven handler inference. */
273
- export function defineOperation<
274
- TInput extends SchemaLike,
275
- TOutput extends SchemaLike,
276
- >(
235
+ export function defineOperation<TInput extends SchemaLike, TOutput extends SchemaLike>(
277
236
  operation: OperationConfig<TInput, TOutput>,
278
237
  ): OperationDefinition<TInput, TOutput> {
279
238
  return operation;
280
239
  }
281
240
 
282
241
  /** Define a non-JSON provider operation with explicit transport metadata. */
283
- export function defineStreamOperation<
284
- TInput extends SchemaLike,
285
- TOutput extends SchemaLike,
286
- >(
242
+ export function defineStreamOperation<TInput extends SchemaLike, TOutput extends SchemaLike>(
287
243
  operation: StreamOperationConfig<TInput, TOutput>,
288
244
  ): OperationDefinition<TInput, TOutput> {
289
245
  return operation;
290
246
  }
291
247
 
292
- function assertObjectConfig(
293
- value: unknown,
294
- ): asserts value is Record<string, unknown> {
248
+ function assertObjectConfig(value: unknown): asserts value is Record<string, unknown> {
295
249
  if (!value || typeof value !== "object") {
296
- throw new ProviderError(
297
- "defineProvider config must be an object. Offending field: config",
298
- {
299
- fix: "Pass defineProvider({ id, version, runtime, meta, operations })",
300
- },
301
- );
250
+ throw new ProviderError("defineProvider config must be an object. Offending field: config", {
251
+ fix: "Pass defineProvider({ id, version, runtime, meta, operations })",
252
+ });
302
253
  }
303
254
  }
304
255
  function assertRequiredField(
@@ -336,25 +287,10 @@ function validateProviderShape(config: unknown): void {
336
287
  assertRequiredField(config, "meta", String(config.id));
337
288
  assertRequiredField(config, "operations", String(config.id));
338
289
  if (typeof config.runtime === "string")
339
- assertLiteralField(
340
- config.runtime,
341
- "runtime",
342
- VALID_RUNTIMES,
343
- String(config.id),
344
- );
290
+ assertLiteralField(config.runtime, "runtime", VALID_RUNTIMES, String(config.id));
345
291
  const auth = config.auth;
346
- if (
347
- auth &&
348
- typeof auth === "object" &&
349
- "mode" in auth &&
350
- typeof auth.mode === "string"
351
- )
352
- assertLiteralField(
353
- auth.mode,
354
- "auth.mode",
355
- VALID_AUTH_MODES,
356
- String(config.id),
357
- );
292
+ if (auth && typeof auth === "object" && "mode" in auth && typeof auth.mode === "string")
293
+ assertLiteralField(auth.mode, "auth.mode", VALID_AUTH_MODES, String(config.id));
358
294
  if (auth && typeof auth === "object" && "exchange" in auth) {
359
295
  throw new ProviderError(
360
296
  `Provider "${String(config.id)}" auth.exchange is not part of the Provider SDK auth contract`,
@@ -390,9 +326,7 @@ function validateProviderShape(config: unknown): void {
390
326
  },
391
327
  );
392
328
  }
393
- const accessRecord: Record<string, unknown> = Object.fromEntries(
394
- Object.entries(access),
395
- );
329
+ const accessRecord: Record<string, unknown> = Object.fromEntries(Object.entries(access));
396
330
  for (const key of Object.keys(accessRecord)) {
397
331
  if (key !== "visibility") {
398
332
  throw new ValidationError(`Unknown field "${key}" on access.`, {
@@ -437,31 +371,13 @@ function validateProviderProxy(config: {
437
371
  },
438
372
  );
439
373
  }
440
- rejectUnknownFields(
441
- proxy,
442
- new Set(["mode", "provider", "geo", "session"]),
443
- "proxy",
444
- );
445
- assertLiteralField(
446
- proxy.mode,
447
- "proxy.mode",
448
- VALID_PROVIDER_PROXY_MODES,
449
- config.id,
450
- );
374
+ rejectUnknownFields(proxy, new Set(["mode", "provider", "geo", "session"]), "proxy");
375
+ assertLiteralField(proxy.mode, "proxy.mode", VALID_PROVIDER_PROXY_MODES, config.id);
451
376
  if (proxy.provider !== undefined) {
452
- assertLiteralField(
453
- proxy.provider,
454
- "proxy.provider",
455
- VALID_PROVIDER_PROXY_PROVIDERS,
456
- config.id,
457
- );
377
+ assertLiteralField(proxy.provider, "proxy.provider", VALID_PROVIDER_PROXY_PROVIDERS, config.id);
458
378
  }
459
379
  if (proxy.geo !== undefined) {
460
- if (
461
- !proxy.geo ||
462
- typeof proxy.geo !== "object" ||
463
- Array.isArray(proxy.geo)
464
- ) {
380
+ if (!proxy.geo || typeof proxy.geo !== "object" || Array.isArray(proxy.geo)) {
465
381
  throw new ValidationError(
466
382
  `Provider "${config.id}" has invalid proxy.geo: must be an object.`,
467
383
  {
@@ -469,11 +385,7 @@ function validateProviderProxy(config: {
469
385
  },
470
386
  );
471
387
  }
472
- rejectUnknownFields(
473
- proxy.geo,
474
- new Set(["country", "subdivision", "city"]),
475
- "proxy.geo",
476
- );
388
+ rejectUnknownFields(proxy.geo, new Set(["country", "subdivision", "city"]), "proxy.geo");
477
389
  if (proxy.geo.country !== undefined) {
478
390
  assertIsoCountry(proxy.geo.country, "proxy.geo.country");
479
391
  }
@@ -487,11 +399,7 @@ function validateProviderProxy(config: {
487
399
  }
488
400
  }
489
401
  if (proxy.session !== undefined) {
490
- if (
491
- !proxy.session ||
492
- typeof proxy.session !== "object" ||
493
- Array.isArray(proxy.session)
494
- ) {
402
+ if (!proxy.session || typeof proxy.session !== "object" || Array.isArray(proxy.session)) {
495
403
  throw new ValidationError(
496
404
  `Provider "${config.id}" has invalid proxy.session: must be an object.`,
497
405
  {
@@ -513,19 +421,13 @@ function validateProviderProxy(config: {
513
421
  );
514
422
  }
515
423
  const lifetime = proxy.session.lifetimeMinutes;
516
- if (
517
- lifetime !== undefined &&
518
- (!Number.isFinite(lifetime) || lifetime <= 0)
519
- ) {
424
+ if (lifetime !== undefined && (!Number.isFinite(lifetime) || lifetime <= 0)) {
520
425
  throw new ValidationError(
521
426
  `Provider "${config.id}" has invalid proxy.session.lifetimeMinutes: must be a positive number of minutes.`,
522
427
  );
523
428
  }
524
429
  const poolSize = proxy.session.poolSize;
525
- if (
526
- poolSize !== undefined &&
527
- (!Number.isInteger(poolSize) || poolSize <= 0)
528
- ) {
430
+ if (poolSize !== undefined && (!Number.isInteger(poolSize) || poolSize <= 0)) {
529
431
  throw new ValidationError(
530
432
  `Provider "${config.id}" has invalid proxy.session.poolSize: must be a positive integer.`,
531
433
  );
@@ -533,8 +435,7 @@ function validateProviderProxy(config: {
533
435
  }
534
436
  if (proxy.mode === "required" && proxy.provider === "smartproxy") {
535
437
  const hasSmartproxySecret = config.secrets?.some(
536
- (secret) =>
537
- secret.name === SMARTPROXY_APP_KEY_SECRET && secret.required !== false,
438
+ (secret) => secret.name === SMARTPROXY_APP_KEY_SECRET && secret.required !== false,
538
439
  );
539
440
  if (!hasSmartproxySecret) {
540
441
  throw new ValidationError(
@@ -547,17 +448,13 @@ function validateProviderProxy(config: {
547
448
  }
548
449
  }
549
450
 
550
- function validateProviderStt(config: {
551
- id: string;
552
- stt?: ProviderSttConfig;
553
- }): void {
451
+ function validateProviderStt(config: { id: string; stt?: ProviderSttConfig }): void {
554
452
  const stt = config.stt;
555
453
  if (stt === undefined) return;
556
454
  if (!stt || typeof stt !== "object" || Array.isArray(stt)) {
557
- throw new ValidationError(
558
- `Provider "${config.id}" has invalid stt: must be an object.`,
559
- { fix: `Use stt: { mode: "required" } or stt: { mode: "optional" }.` },
560
- );
455
+ throw new ValidationError(`Provider "${config.id}" has invalid stt: must be an object.`, {
456
+ fix: `Use stt: { mode: "required" } or stt: { mode: "optional" }.`,
457
+ });
561
458
  }
562
459
  rejectUnknownFields(stt, new Set(["mode"]), "stt");
563
460
  assertLiteralField(stt.mode, "stt.mode", VALID_PROVIDER_STT_MODES, config.id);
@@ -584,16 +481,9 @@ function validateOperationIds(
584
481
  );
585
482
  }
586
483
  }
587
- const OPERATION_CONTRACT_VERSION_REGEX =
588
- /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/;
589
- const OPERATION_SENSITIVE_PATH_REGEX =
590
- /^(?:[A-Za-z0-9_$-]+|\*)(?:\.(?:[A-Za-z0-9_$-]+|\*))*$/;
591
- const VALID_OPERATION_LIFECYCLES = [
592
- "stable",
593
- "beta",
594
- "deprecated",
595
- "removed",
596
- ] as const;
484
+ const OPERATION_CONTRACT_VERSION_REGEX = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/;
485
+ const OPERATION_SENSITIVE_PATH_REGEX = /^(?:[A-Za-z0-9_$-]+|\*)(?:\.(?:[A-Za-z0-9_$-]+|\*))*$/;
486
+ const VALID_OPERATION_LIFECYCLES = ["stable", "beta", "deprecated", "removed"] as const;
597
487
 
598
488
  function assertNonEmptyString(
599
489
  value: unknown,
@@ -624,10 +514,7 @@ function validateToolRouterMetadata(
624
514
  },
625
515
  );
626
516
  }
627
- if (
628
- toolRouter.name !== undefined &&
629
- !MCP_TOOL_NAME_REGEX.test(toolRouter.name)
630
- ) {
517
+ if (toolRouter.name !== undefined && !MCP_TOOL_NAME_REGEX.test(toolRouter.name)) {
631
518
  throw new ValidationError(
632
519
  `Provider "${providerId}" operation "${operationName}" has invalid operations.${operationName}.toolRouter.name: expected an MCP-safe name.`,
633
520
  {
@@ -699,10 +586,7 @@ function validateOperationContracts(
699
586
  providerId,
700
587
  );
701
588
  }
702
- if (
703
- contract.lifecycle === "deprecated" ||
704
- contract.lifecycle === "removed"
705
- ) {
589
+ if (contract.lifecycle === "deprecated" || contract.lifecycle === "removed") {
706
590
  if (!contract.deprecation || typeof contract.deprecation !== "object") {
707
591
  throw new ValidationError(
708
592
  `Provider "${providerId}" operation "${operationName}" is ${contract.lifecycle} but lacks operations.${operationName}.contract.deprecation metadata.`,
@@ -750,10 +634,7 @@ function validateOperationAnnotations(
750
634
  fix: `Set ${field} to an integer in [${OPERATION_TIMEOUT_MS_MIN}, ${OPERATION_TIMEOUT_MS_MAX}] (milliseconds).`,
751
635
  },
752
636
  );
753
- if (
754
- timeoutMs < OPERATION_TIMEOUT_MS_MIN ||
755
- timeoutMs > OPERATION_TIMEOUT_MS_MAX
756
- )
637
+ if (timeoutMs < OPERATION_TIMEOUT_MS_MIN || timeoutMs > OPERATION_TIMEOUT_MS_MAX)
757
638
  throw new ValidationError(
758
639
  `Provider "${providerId}" has invalid ${field}: ${timeoutMs} is outside [${OPERATION_TIMEOUT_MS_MIN}, ${OPERATION_TIMEOUT_MS_MAX}] ms.`,
759
640
  {
@@ -951,12 +832,7 @@ function validateOperationTransports(
951
832
  },
952
833
  );
953
834
  }
954
- assertLiteralField(
955
- kind,
956
- `${fieldPath}.kind`,
957
- VALID_OPERATION_TRANSPORT_KINDS,
958
- providerId,
959
- );
835
+ assertLiteralField(kind, `${fieldPath}.kind`, VALID_OPERATION_TRANSPORT_KINDS, providerId);
960
836
 
961
837
  switch (kind) {
962
838
  case "json":
@@ -988,16 +864,9 @@ function validateOperationTransports(
988
864
  STREAM_MAX_DURATION_MS_MAX,
989
865
  "max duration",
990
866
  );
991
- assertPositiveBytes(
992
- Reflect.get(transport, "maxEventBytes"),
993
- `${fieldPath}.maxEventBytes`,
994
- );
867
+ assertPositiveBytes(Reflect.get(transport, "maxEventBytes"), `${fieldPath}.maxEventBytes`);
995
868
  const resumable = Reflect.get(transport, "resumable");
996
- if (
997
- resumable !== undefined &&
998
- resumable !== false &&
999
- resumable !== "last-event-id"
1000
- ) {
869
+ if (resumable !== undefined && resumable !== false && resumable !== "last-event-id") {
1001
870
  throw new ValidationError(
1002
871
  `Provider "${providerId}" operation "${operationName}" has invalid ${fieldPath}.resumable: expected false or "last-event-id".`,
1003
872
  {
@@ -1017,12 +886,7 @@ function validateOperationTransports(
1017
886
  rejectUnknownFields(transport, HTTP_STREAM_TRANSPORT_FIELDS, fieldPath);
1018
887
  const contentType = Reflect.get(transport, "contentType");
1019
888
  if (contentType !== undefined) {
1020
- assertNonEmptyString(
1021
- contentType,
1022
- `${fieldPath}.contentType`,
1023
- providerId,
1024
- operationName,
1025
- );
889
+ assertNonEmptyString(contentType, `${fieldPath}.contentType`, providerId, operationName);
1026
890
  }
1027
891
  assertStreamMs(
1028
892
  Reflect.get(transport, "idleTimeoutMs"),
@@ -1038,10 +902,7 @@ function validateOperationTransports(
1038
902
  STREAM_MAX_DURATION_MS_MAX,
1039
903
  "max duration",
1040
904
  );
1041
- assertPositiveBytes(
1042
- Reflect.get(transport, "maxChunkBytes"),
1043
- `${fieldPath}.maxChunkBytes`,
1044
- );
905
+ assertPositiveBytes(Reflect.get(transport, "maxChunkBytes"), `${fieldPath}.maxChunkBytes`);
1045
906
  break;
1046
907
  }
1047
908
  case "websocket": {
@@ -1066,10 +927,7 @@ function validateOperationTransports(
1066
927
  );
1067
928
  }
1068
929
  for (const subprotocol of subprotocols) {
1069
- if (
1070
- typeof subprotocol !== "string" ||
1071
- !WEBSOCKET_SUBPROTOCOL_REGEX.test(subprotocol)
1072
- ) {
930
+ if (typeof subprotocol !== "string" || !WEBSOCKET_SUBPROTOCOL_REGEX.test(subprotocol)) {
1073
931
  throw new ValidationError(
1074
932
  `Provider "${providerId}" operation "${operationName}" has invalid ${fieldPath}.subprotocols: each subprotocol must be an RFC token string.`,
1075
933
  {
@@ -1093,10 +951,7 @@ function validateOperationTransports(
1093
951
  STREAM_MAX_DURATION_MS_MAX,
1094
952
  "max duration",
1095
953
  );
1096
- assertPositiveBytes(
1097
- Reflect.get(transport, "maxFrameBytes"),
1098
- `${fieldPath}.maxFrameBytes`,
1099
- );
954
+ assertPositiveBytes(Reflect.get(transport, "maxFrameBytes"), `${fieldPath}.maxFrameBytes`);
1100
955
  break;
1101
956
  }
1102
957
  }
@@ -1159,10 +1014,7 @@ function levenshtein(a: string, b: string): number {
1159
1014
  return prev[n] ?? 0;
1160
1015
  }
1161
1016
 
1162
- function suggestField(
1163
- unknown: string,
1164
- candidates: ReadonlySet<string>,
1165
- ): string | undefined {
1017
+ function suggestField(unknown: string, candidates: ReadonlySet<string>): string | undefined {
1166
1018
  let best: string | undefined;
1167
1019
  let bestDist = 3;
1168
1020
  for (const candidate of candidates) {
@@ -1175,11 +1027,7 @@ function suggestField(
1175
1027
  return best;
1176
1028
  }
1177
1029
 
1178
- function rejectUnknownFields(
1179
- value: object,
1180
- allowed: ReadonlySet<string>,
1181
- fieldPath: string,
1182
- ): void {
1030
+ function rejectUnknownFields(value: object, allowed: ReadonlySet<string>, fieldPath: string): void {
1183
1031
  for (const key of Object.keys(value)) {
1184
1032
  if (allowed.has(key)) continue;
1185
1033
  const hint = suggestField(key, allowed);
@@ -1218,23 +1066,12 @@ function validateProviderHealthMonitor(
1218
1066
  field: "healthMonitor" | "healthProbe" = "healthMonitor",
1219
1067
  ): void {
1220
1068
  if (healthMonitor === undefined) return;
1221
- if (
1222
- !healthMonitor ||
1223
- typeof healthMonitor !== "object" ||
1224
- Array.isArray(healthMonitor)
1225
- )
1226
- throw new ValidationError(
1227
- `Provider "${providerId}" has invalid ${field}: must be an object.`,
1228
- {
1229
- fix: `Set ${field} to { requiredSecrets?: string[]; serviceAccount?: string }`,
1230
- },
1231
- );
1069
+ if (!healthMonitor || typeof healthMonitor !== "object" || Array.isArray(healthMonitor))
1070
+ throw new ValidationError(`Provider "${providerId}" has invalid ${field}: must be an object.`, {
1071
+ fix: `Set ${field} to { requiredSecrets?: string[]; serviceAccount?: string }`,
1072
+ });
1232
1073
  const healthMonitorRecord = Object.fromEntries(Object.entries(healthMonitor));
1233
- rejectUnknownFields(
1234
- healthMonitorRecord,
1235
- PROVIDER_HEALTH_MONITOR_FIELDS,
1236
- field,
1237
- );
1074
+ rejectUnknownFields(healthMonitorRecord, PROVIDER_HEALTH_MONITOR_FIELDS, field);
1238
1075
  if (healthMonitorRecord.defaultProbeTimeoutMs !== undefined) {
1239
1076
  assertBoundedIntegerMs(
1240
1077
  healthMonitorRecord.defaultProbeTimeoutMs,
@@ -1302,11 +1139,7 @@ function validateProviderHealthMonitor(
1302
1139
 
1303
1140
  const probeOverrides = healthMonitorRecord.probeOverrides;
1304
1141
  if (probeOverrides !== undefined) {
1305
- if (
1306
- !probeOverrides ||
1307
- typeof probeOverrides !== "object" ||
1308
- Array.isArray(probeOverrides)
1309
- )
1142
+ if (!probeOverrides || typeof probeOverrides !== "object" || Array.isArray(probeOverrides))
1310
1143
  throw new ValidationError(
1311
1144
  `Provider "${providerId}" has invalid ${field}.probeOverrides: must be an object keyed by probe id.`,
1312
1145
  );
@@ -1372,14 +1205,8 @@ function validateHealthCheckCase(
1372
1205
  ): void {
1373
1206
  const fieldPath = `operations.${operationName}.healthCheck.cases[${caseIndex}]`;
1374
1207
  if (!caseValue || typeof caseValue !== "object" || Array.isArray(caseValue))
1375
- throw new ValidationError(
1376
- `Provider "${providerId}" ${fieldPath} must be an object.`,
1377
- );
1378
- rejectUnknownFields(
1379
- caseValue as Record<string, unknown>,
1380
- HEALTH_CHECK_CASE_FIELDS,
1381
- fieldPath,
1382
- );
1208
+ throw new ValidationError(`Provider "${providerId}" ${fieldPath} must be an object.`);
1209
+ rejectUnknownFields(caseValue as Record<string, unknown>, HEALTH_CHECK_CASE_FIELDS, fieldPath);
1383
1210
  const c = caseValue as HealthCheckCase;
1384
1211
  if (typeof c.name !== "string" || c.name.length === 0)
1385
1212
  throw new ValidationError(
@@ -1407,15 +1234,11 @@ function validateHealthCheckCase(
1407
1234
  `Provider "${providerId}" ${fieldPath}.degradedThresholdMs must be an integer degraded threshold in [${HEALTH_CHECK_DEGRADED_THRESHOLD_MS_MIN}, ${HEALTH_CHECK_DEGRADED_THRESHOLD_MS_MAX}] ms.`,
1408
1235
  );
1409
1236
  if (c.timeoutMs !== undefined) {
1410
- assertBoundedIntegerMs(
1411
- c.timeoutMs,
1412
- `Provider "${providerId}" ${fieldPath}.timeoutMs`,
1413
- {
1414
- min: HEALTH_CHECK_TIMEOUT_MS_MIN,
1415
- max: HEALTH_CHECK_TIMEOUT_MS_MAX,
1416
- label: "timeout",
1417
- },
1418
- );
1237
+ assertBoundedIntegerMs(c.timeoutMs, `Provider "${providerId}" ${fieldPath}.timeoutMs`, {
1238
+ min: HEALTH_CHECK_TIMEOUT_MS_MIN,
1239
+ max: HEALTH_CHECK_TIMEOUT_MS_MAX,
1240
+ label: "timeout",
1241
+ });
1419
1242
  }
1420
1243
  if (
1421
1244
  c.expectedStatus !== undefined &&
@@ -1431,21 +1254,11 @@ function validateHealthCheckCase(
1431
1254
  );
1432
1255
  }
1433
1256
 
1434
- function validateHealthCheckSuite(
1435
- providerId: string,
1436
- operationName: string,
1437
- suite: unknown,
1438
- ): void {
1257
+ function validateHealthCheckSuite(providerId: string, operationName: string, suite: unknown): void {
1439
1258
  const fieldPath = `operations.${operationName}.healthCheck`;
1440
1259
  if (!suite || typeof suite !== "object" || Array.isArray(suite))
1441
- throw new ValidationError(
1442
- `Provider "${providerId}" ${fieldPath} must be an object.`,
1443
- );
1444
- rejectUnknownFields(
1445
- suite as Record<string, unknown>,
1446
- HEALTH_CHECK_SUITE_FIELDS,
1447
- fieldPath,
1448
- );
1260
+ throw new ValidationError(`Provider "${providerId}" ${fieldPath} must be an object.`);
1261
+ rejectUnknownFields(suite as Record<string, unknown>, HEALTH_CHECK_SUITE_FIELDS, fieldPath);
1449
1262
  const s = suite as HealthCheckSuite;
1450
1263
  if (!isPositiveMsDurationString(s.interval))
1451
1264
  throw new ValidationError(
@@ -1455,11 +1268,7 @@ function validateHealthCheckSuite(
1455
1268
  },
1456
1269
  );
1457
1270
  if (s.schedule !== undefined) {
1458
- if (
1459
- !s.schedule ||
1460
- typeof s.schedule !== "object" ||
1461
- Array.isArray(s.schedule)
1462
- ) {
1271
+ if (!s.schedule || typeof s.schedule !== "object" || Array.isArray(s.schedule)) {
1463
1272
  throw new ValidationError(
1464
1273
  `Provider "${providerId}" ${fieldPath}.schedule must be an object.`,
1465
1274
  );
@@ -1469,11 +1278,7 @@ function validateHealthCheckSuite(
1469
1278
  `Provider "${providerId}" ${fieldPath}.schedule.jitter is not supported for operation healthCheck schedules. Use schedule.randomize instead.`,
1470
1279
  );
1471
1280
  }
1472
- rejectUnknownFields(
1473
- s.schedule,
1474
- new Set(["randomize"]),
1475
- `${fieldPath}.schedule`,
1476
- );
1281
+ rejectUnknownFields(s.schedule, new Set(["randomize"]), `${fieldPath}.schedule`);
1477
1282
  const randomize = Reflect.get(s.schedule, "randomize");
1478
1283
  if (randomize !== undefined) {
1479
1284
  validateScheduleRandomization(
@@ -1484,15 +1289,11 @@ function validateHealthCheckSuite(
1484
1289
  }
1485
1290
  }
1486
1291
  if (s.timeoutMs !== undefined) {
1487
- assertBoundedIntegerMs(
1488
- s.timeoutMs,
1489
- `Provider "${providerId}" ${fieldPath}.timeoutMs`,
1490
- {
1491
- min: HEALTH_CHECK_TIMEOUT_MS_MIN,
1492
- max: HEALTH_CHECK_TIMEOUT_MS_MAX,
1493
- label: "timeout",
1494
- },
1495
- );
1292
+ assertBoundedIntegerMs(s.timeoutMs, `Provider "${providerId}" ${fieldPath}.timeoutMs`, {
1293
+ min: HEALTH_CHECK_TIMEOUT_MS_MIN,
1294
+ max: HEALTH_CHECK_TIMEOUT_MS_MAX,
1295
+ label: "timeout",
1296
+ });
1496
1297
  }
1497
1298
  if (s.degradedThresholdMs !== undefined) {
1498
1299
  assertBoundedIntegerMs(
@@ -1505,10 +1306,7 @@ function validateHealthCheckSuite(
1505
1306
  },
1506
1307
  );
1507
1308
  }
1508
- if (
1509
- s.requiresConnection !== undefined &&
1510
- typeof s.requiresConnection !== "boolean"
1511
- )
1309
+ if (s.requiresConnection !== undefined && typeof s.requiresConnection !== "boolean")
1512
1310
  throw new ValidationError(
1513
1311
  `Provider "${providerId}" ${fieldPath}.requiresConnection must be a boolean.`,
1514
1312
  );
@@ -1540,14 +1338,8 @@ function validateHealthCheckUnsupported(
1540
1338
  unsupported: unknown,
1541
1339
  ): void {
1542
1340
  const fieldPath = `operations.${operationName}.healthCheckUnsupported`;
1543
- if (
1544
- !unsupported ||
1545
- typeof unsupported !== "object" ||
1546
- Array.isArray(unsupported)
1547
- )
1548
- throw new ValidationError(
1549
- `Provider "${providerId}" ${fieldPath} must be an object.`,
1550
- );
1341
+ if (!unsupported || typeof unsupported !== "object" || Array.isArray(unsupported))
1342
+ throw new ValidationError(`Provider "${providerId}" ${fieldPath} must be an object.`);
1551
1343
  rejectUnknownFields(
1552
1344
  unsupported as Record<string, unknown>,
1553
1345
  HEALTH_CHECK_UNSUPPORTED_FIELDS,
@@ -1581,12 +1373,7 @@ const HEALTH_JOURNEY_FIELDS = new Set([
1581
1373
  "steps",
1582
1374
  "run",
1583
1375
  ]);
1584
- const HEALTH_JOURNEY_SCHEDULE_FIELDS = new Set([
1585
- "kind",
1586
- "interval",
1587
- "jitter",
1588
- "randomize",
1589
- ]);
1376
+ const HEALTH_JOURNEY_SCHEDULE_FIELDS = new Set(["kind", "interval", "jitter", "randomize"]);
1590
1377
  const HEALTH_JOURNEY_STEP_FIELDS = new Set([
1591
1378
  "id",
1592
1379
  "description",
@@ -1605,10 +1392,7 @@ const HEALTH_JOURNEY_MANUAL_TRIGGER_FIELDS = new Set([
1605
1392
  "minManualInterval",
1606
1393
  "publicRationale",
1607
1394
  ]);
1608
- const HEALTH_JOURNEY_MANUAL_TRIGGER_DISABLED_FIELDS = new Set([
1609
- "enabled",
1610
- "reason",
1611
- ]);
1395
+ const HEALTH_JOURNEY_MANUAL_TRIGGER_DISABLED_FIELDS = new Set(["enabled", "reason"]);
1612
1396
  const HEALTH_JOURNEY_MANUAL_TRIGGER_ENABLED_FIELDS = new Set([
1613
1397
  "enabled",
1614
1398
  "requiresAcknowledgement",
@@ -1628,32 +1412,18 @@ function validateHealthJourneyManualTrigger(
1628
1412
  manualTrigger: unknown,
1629
1413
  ): void {
1630
1414
  const fieldPath = `healthJourneys.${journeyId}.manualTrigger`;
1631
- if (
1632
- !manualTrigger ||
1633
- typeof manualTrigger !== "object" ||
1634
- Array.isArray(manualTrigger)
1635
- ) {
1415
+ if (!manualTrigger || typeof manualTrigger !== "object" || Array.isArray(manualTrigger)) {
1636
1416
  throw new ValidationError(
1637
1417
  `Provider "${providerId}" ${fieldPath} must be an object when present.`,
1638
1418
  );
1639
1419
  }
1640
- rejectUnknownFields(
1641
- manualTrigger,
1642
- HEALTH_JOURNEY_MANUAL_TRIGGER_FIELDS,
1643
- fieldPath,
1644
- );
1420
+ rejectUnknownFields(manualTrigger, HEALTH_JOURNEY_MANUAL_TRIGGER_FIELDS, fieldPath);
1645
1421
  const enabled = Reflect.get(manualTrigger, "enabled");
1646
1422
  if (typeof enabled !== "boolean") {
1647
- throw new ValidationError(
1648
- `Provider "${providerId}" ${fieldPath}.enabled must be a boolean.`,
1649
- );
1423
+ throw new ValidationError(`Provider "${providerId}" ${fieldPath}.enabled must be a boolean.`);
1650
1424
  }
1651
1425
  if (enabled === false) {
1652
- rejectUnknownFields(
1653
- manualTrigger,
1654
- HEALTH_JOURNEY_MANUAL_TRIGGER_DISABLED_FIELDS,
1655
- fieldPath,
1656
- );
1426
+ rejectUnknownFields(manualTrigger, HEALTH_JOURNEY_MANUAL_TRIGGER_DISABLED_FIELDS, fieldPath);
1657
1427
  if (
1658
1428
  Reflect.get(manualTrigger, "reason") !== undefined &&
1659
1429
  (typeof Reflect.get(manualTrigger, "reason") !== "string" ||
@@ -1665,25 +1435,15 @@ function validateHealthJourneyManualTrigger(
1665
1435
  }
1666
1436
  return;
1667
1437
  }
1668
- rejectUnknownFields(
1669
- manualTrigger,
1670
- HEALTH_JOURNEY_MANUAL_TRIGGER_ENABLED_FIELDS,
1671
- fieldPath,
1672
- );
1673
- const requiresAcknowledgement = Reflect.get(
1674
- manualTrigger,
1675
- "requiresAcknowledgement",
1676
- );
1438
+ rejectUnknownFields(manualTrigger, HEALTH_JOURNEY_MANUAL_TRIGGER_ENABLED_FIELDS, fieldPath);
1439
+ const requiresAcknowledgement = Reflect.get(manualTrigger, "requiresAcknowledgement");
1677
1440
  if (typeof requiresAcknowledgement !== "boolean") {
1678
1441
  throw new ValidationError(
1679
1442
  `Provider "${providerId}" ${fieldPath}.requiresAcknowledgement must be a boolean.`,
1680
1443
  );
1681
1444
  }
1682
1445
  const risk = Reflect.get(manualTrigger, "risk");
1683
- if (
1684
- typeof risk !== "string" ||
1685
- !HEALTH_JOURNEY_MANUAL_TRIGGER_RISKS.has(risk)
1686
- ) {
1446
+ if (typeof risk !== "string" || !HEALTH_JOURNEY_MANUAL_TRIGGER_RISKS.has(risk)) {
1687
1447
  throw new ValidationError(
1688
1448
  `Provider "${providerId}" ${fieldPath}.risk must be one of read_only, writes_external_state, or sms_or_payment.`,
1689
1449
  );
@@ -1694,10 +1454,7 @@ function validateHealthJourneyManualTrigger(
1694
1454
  );
1695
1455
  }
1696
1456
  const minManualInterval = Reflect.get(manualTrigger, "minManualInterval");
1697
- assertIsoDuration(
1698
- minManualInterval,
1699
- `Provider "${providerId}" ${fieldPath}.minManualInterval`,
1700
- );
1457
+ assertIsoDuration(minManualInterval, `Provider "${providerId}" ${fieldPath}.minManualInterval`);
1701
1458
  if (isoDurationMs(minManualInterval) <= 0) {
1702
1459
  throw new ValidationError(
1703
1460
  `Provider "${providerId}" ${fieldPath}.minManualInterval must be a positive duration.`,
@@ -1728,18 +1485,14 @@ const SMS_ORIGIN_FIELDS_BY_KIND: Record<string, ReadonlySet<string>> = {
1728
1485
  e164: new Set(["kind", "value", "display"]),
1729
1486
  nationalServiceCode: new Set(["kind", "country", "value", "display"]),
1730
1487
  };
1731
- const DURATION_RE =
1732
- /^P(?=\d|T\d)(?:\d+D)?(?:T(?:\d+H)?(?:\d+M)?(?:\d+(?:\.\d+)?S)?)?$/;
1488
+ const DURATION_RE = /^P(?=\d|T\d)(?:\d+D)?(?:T(?:\d+H)?(?:\d+M)?(?:\d+(?:\.\d+)?S)?)?$/;
1733
1489
  const E164_RE = /^\+[1-9]\d{1,14}$/;
1734
1490
  const ISO_COUNTRY_RE = /^[A-Z]{2}$/;
1735
1491
  const NATIONAL_SERVICE_CODE_RE = /^[0-9]{2,15}$/;
1736
1492
  const BCP47_RE = /^[A-Za-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$/;
1737
1493
  const JOURNEY_ID_RE = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
1738
1494
 
1739
- function assertIsoDuration(
1740
- value: unknown,
1741
- fieldPath: string,
1742
- ): asserts value is string {
1495
+ function assertIsoDuration(value: unknown, fieldPath: string): asserts value is string {
1743
1496
  if (typeof value !== "string" || !DURATION_RE.test(value)) {
1744
1497
  throw new ValidationError(
1745
1498
  `${fieldPath} must be an ISO 8601 duration for example PT8H or PT2M30S.`,
@@ -1754,15 +1507,10 @@ function isoDurationMs(value: string): number {
1754
1507
  const hours = Number(/(\d+)H/.exec(value)?.[1] ?? 0);
1755
1508
  const minutes = Number(/(\d+)M/.exec(value)?.[1] ?? 0);
1756
1509
  const seconds = Number(/(\d+(?:\.\d+)?)S/.exec(value)?.[1] ?? 0);
1757
- return (
1758
- days * 86_400_000 + hours * 3_600_000 + minutes * 60_000 + seconds * 1_000
1759
- );
1510
+ return days * 86_400_000 + hours * 3_600_000 + minutes * 60_000 + seconds * 1_000;
1760
1511
  }
1761
1512
 
1762
- function scheduleRandomizationMs(
1763
- randomize: unknown,
1764
- fieldPath: string,
1765
- ): number {
1513
+ function scheduleRandomizationMs(randomize: unknown, fieldPath: string): number {
1766
1514
  const mode = Reflect.get(randomize as object, "mode");
1767
1515
  switch (mode) {
1768
1516
  case "centered": {
@@ -1776,9 +1524,7 @@ function scheduleRandomizationMs(
1776
1524
  return isoDurationMs(maxDelay);
1777
1525
  }
1778
1526
  default:
1779
- throw new ValidationError(
1780
- `${fieldPath}.mode must be "centered" or "delayed".`,
1781
- );
1527
+ throw new ValidationError(`${fieldPath}.mode must be "centered" or "delayed".`);
1782
1528
  }
1783
1529
  }
1784
1530
 
@@ -1792,25 +1538,18 @@ function validateScheduleRandomization(
1792
1538
  }
1793
1539
  const mode = Reflect.get(randomize, "mode");
1794
1540
  const allowedFields =
1795
- mode === "centered"
1796
- ? new Set(["mode", "maxOffset"])
1797
- : new Set(["mode", "maxDelay"]);
1541
+ mode === "centered" ? new Set(["mode", "maxOffset"]) : new Set(["mode", "maxDelay"]);
1798
1542
  rejectUnknownFields(randomize, allowedFields, fieldPath);
1799
1543
  const offsetMs = scheduleRandomizationMs(randomize, fieldPath);
1800
1544
  if (offsetMs <= 0) {
1801
1545
  throw new ValidationError(`${fieldPath} duration must be positive.`);
1802
1546
  }
1803
1547
  if (offsetMs >= intervalMs) {
1804
- throw new ValidationError(
1805
- `${fieldPath} duration must be shorter than schedule interval.`,
1806
- );
1548
+ throw new ValidationError(`${fieldPath} duration must be shorter than schedule interval.`);
1807
1549
  }
1808
1550
  }
1809
1551
 
1810
- function assertIsoCountry(
1811
- value: unknown,
1812
- fieldPath: string,
1813
- ): asserts value is string {
1552
+ function assertIsoCountry(value: unknown, fieldPath: string): asserts value is string {
1814
1553
  if (typeof value !== "string" || !ISO_COUNTRY_RE.test(value)) {
1815
1554
  throw new ValidationError(
1816
1555
  `${fieldPath} must be an ISO 3166-1 alpha-2 country code for example KR.`,
@@ -1833,9 +1572,7 @@ function normalizeIntervalDuration(input: string): string {
1833
1572
  ? durationMs / 3_600_000
1834
1573
  : durationMs / 86_400_000;
1835
1574
  if (!Number.isInteger(amount) || amount <= 0) {
1836
- throw new ValidationError(
1837
- `Journey schedule interval must be a positive duration.`,
1838
- );
1575
+ throw new ValidationError(`Journey schedule interval must be a positive duration.`);
1839
1576
  }
1840
1577
  if (unit === "s") return `PT${amount}S`;
1841
1578
  if (unit === "m") return `PT${amount}M`;
@@ -1894,11 +1631,7 @@ function countCapturingGroups(pattern: RegExp): number {
1894
1631
  if (inCharacterClass || char !== "(") continue;
1895
1632
  const next = source[i + 1];
1896
1633
  if (next === "?" && source[i + 2] !== "<") continue;
1897
- if (
1898
- next === "?" &&
1899
- source[i + 2] === "<" &&
1900
- (source[i + 3] === "=" || source[i + 3] === "!")
1901
- )
1634
+ if (next === "?" && source[i + 2] === "<" && (source[i + 3] === "=" || source[i + 3] === "!"))
1902
1635
  continue;
1903
1636
  count += 1;
1904
1637
  }
@@ -1917,9 +1650,7 @@ function validateSmsOrigin(origin: unknown, fieldPath: string): void {
1917
1650
  }
1918
1651
  const kind = Reflect.get(origin, "kind");
1919
1652
  if (kind !== "e164" && kind !== "nationalServiceCode") {
1920
- throw new ValidationError(
1921
- `${fieldPath}.kind must be "e164" or "nationalServiceCode".`,
1922
- );
1653
+ throw new ValidationError(`${fieldPath}.kind must be "e164" or "nationalServiceCode".`);
1923
1654
  }
1924
1655
  rejectUnknownFields(origin, SMS_ORIGIN_FIELDS_BY_KIND[kind], fieldPath);
1925
1656
  if (kind === "e164") {
@@ -1946,9 +1677,7 @@ function validateSmsOrigin(origin: unknown, fieldPath: string): void {
1946
1677
  Reflect.get(origin, "display") !== undefined &&
1947
1678
  typeof Reflect.get(origin, "display") !== "string"
1948
1679
  ) {
1949
- throw new ValidationError(
1950
- `${fieldPath}.display must be a string when present.`,
1951
- );
1680
+ throw new ValidationError(`${fieldPath}.display must be a string when present.`);
1952
1681
  }
1953
1682
  }
1954
1683
 
@@ -1962,9 +1691,7 @@ function validateSmsOtpMatcher(
1962
1691
  rejectUnknownFields(matcher, SMS_OTP_MATCHER_FIELDS, fieldPath);
1963
1692
  const matcherId = Reflect.get(matcher, "id");
1964
1693
  if (typeof matcherId !== "string" || !JOURNEY_ID_RE.test(matcherId)) {
1965
- throw new ValidationError(
1966
- `${fieldPath}.id must be a kebab-case identifier.`,
1967
- );
1694
+ throw new ValidationError(`${fieldPath}.id must be a kebab-case identifier.`);
1968
1695
  }
1969
1696
  assertIsoCountry(Reflect.get(matcher, "country"), `${fieldPath}.country`);
1970
1697
  if (
@@ -1972,24 +1699,18 @@ function validateSmsOtpMatcher(
1972
1699
  (typeof Reflect.get(matcher, "locale") !== "string" ||
1973
1700
  !BCP47_RE.test(Reflect.get(matcher, "locale")))
1974
1701
  ) {
1975
- throw new ValidationError(
1976
- `${fieldPath}.locale must be a BCP 47 locale for example ko-KR.`,
1977
- );
1702
+ throw new ValidationError(`${fieldPath}.locale must be a BCP 47 locale for example ko-KR.`);
1978
1703
  }
1979
1704
  if (
1980
1705
  Reflect.get(matcher, "phoneNumber") !== undefined &&
1981
1706
  (typeof Reflect.get(matcher, "phoneNumber") !== "string" ||
1982
1707
  !E164_RE.test(Reflect.get(matcher, "phoneNumber")))
1983
1708
  ) {
1984
- throw new ValidationError(
1985
- `${fieldPath}.phoneNumber must be an ITU-T E.164 number.`,
1986
- );
1709
+ throw new ValidationError(`${fieldPath}.phoneNumber must be an ITU-T E.164 number.`);
1987
1710
  }
1988
1711
  const origins = Reflect.get(matcher, "origins");
1989
1712
  if (!Array.isArray(origins) || origins.length === 0) {
1990
- throw new ValidationError(
1991
- `${fieldPath}.origins must be a non-empty array.`,
1992
- );
1713
+ throw new ValidationError(`${fieldPath}.origins must be a non-empty array.`);
1993
1714
  }
1994
1715
  for (const [index, origin] of origins.entries()) {
1995
1716
  validateSmsOrigin(origin, `${fieldPath}.origins[${index}]`);
@@ -2010,10 +1731,7 @@ function validateSmsOtpMatcher(
2010
1731
  );
2011
1732
  }
2012
1733
  const regex = pattern instanceof RegExp ? pattern : new RegExp(pattern);
2013
- if (
2014
- countCapturingGroups(regex) !== 1 &&
2015
- Reflect.get(code, "capture") === undefined
2016
- ) {
1734
+ if (countCapturingGroups(regex) !== 1 && Reflect.get(code, "capture") === undefined) {
2017
1735
  throw new ValidationError(
2018
1736
  `${fieldPath}.code.pattern must contain exactly one OTP capture or declare code.capture.`,
2019
1737
  );
@@ -2023,20 +1741,12 @@ function validateSmsOtpMatcher(
2023
1741
  typeof Reflect.get(code, "capture") !== "string" &&
2024
1742
  typeof Reflect.get(code, "capture") !== "number"
2025
1743
  ) {
2026
- throw new ValidationError(
2027
- `${fieldPath}.code.capture must be a string or number when present.`,
2028
- );
1744
+ throw new ValidationError(`${fieldPath}.code.capture must be a string or number when present.`);
2029
1745
  }
2030
1746
  assertIsoDuration(Reflect.get(matcher, "maxAge"), `${fieldPath}.maxAge`);
2031
- assertIsoDuration(
2032
- Reflect.get(matcher, "waitTimeout"),
2033
- `${fieldPath}.waitTimeout`,
2034
- );
1747
+ assertIsoDuration(Reflect.get(matcher, "waitTimeout"), `${fieldPath}.waitTimeout`);
2035
1748
  if (Reflect.get(matcher, "clockSkew") !== undefined)
2036
- assertIsoDuration(
2037
- Reflect.get(matcher, "clockSkew"),
2038
- `${fieldPath}.clockSkew`,
2039
- );
1749
+ assertIsoDuration(Reflect.get(matcher, "clockSkew"), `${fieldPath}.clockSkew`);
2040
1750
  }
2041
1751
 
2042
1752
  export function defineSmsOtpMatcher(
@@ -2068,9 +1778,7 @@ export function defineSmsOtpMatcher(
2068
1778
  return matcher;
2069
1779
  }
2070
1780
 
2071
- export function defineHealthJourney(
2072
- config: HealthJourneyDefinition,
2073
- ): HealthJourneyDefinition {
1781
+ export function defineHealthJourney(config: HealthJourneyDefinition): HealthJourneyDefinition {
2074
1782
  return config;
2075
1783
  }
2076
1784
 
@@ -2081,22 +1789,15 @@ function validateHealthJourneySchedule(
2081
1789
  ): void {
2082
1790
  const fieldPath = `healthJourneys.${journeyId}.schedule`;
2083
1791
  if (!schedule || typeof schedule !== "object" || Array.isArray(schedule)) {
2084
- throw new ValidationError(
2085
- `Provider "${providerId}" ${fieldPath} must be an object.`,
2086
- );
1792
+ throw new ValidationError(`Provider "${providerId}" ${fieldPath} must be an object.`);
2087
1793
  }
2088
1794
  rejectUnknownFields(schedule, HEALTH_JOURNEY_SCHEDULE_FIELDS, fieldPath);
2089
1795
  if (Reflect.get(schedule, "kind") !== "interval")
2090
- throw new ValidationError(
2091
- `Provider "${providerId}" ${fieldPath}.kind must be "interval".`,
2092
- );
1796
+ throw new ValidationError(`Provider "${providerId}" ${fieldPath}.kind must be "interval".`);
2093
1797
  const interval = Reflect.get(schedule, "interval");
2094
1798
  assertIsoDuration(interval, `Provider "${providerId}" ${fieldPath}.interval`);
2095
1799
  const randomize = Reflect.get(schedule, "randomize");
2096
- if (
2097
- Reflect.get(schedule, "jitter") !== undefined &&
2098
- randomize !== undefined
2099
- ) {
1800
+ if (Reflect.get(schedule, "jitter") !== undefined && randomize !== undefined) {
2100
1801
  throw new ValidationError(
2101
1802
  `Provider "${providerId}" ${fieldPath} cannot define both jitter and randomize.`,
2102
1803
  );
@@ -2123,17 +1824,13 @@ function validateHealthJourneys(
2123
1824
  const covered = new Set<string>();
2124
1825
  if (healthJourneys === undefined) return covered;
2125
1826
  if (!Array.isArray(healthJourneys)) {
2126
- throw new ValidationError(
2127
- `Provider "${providerId}" healthJourneys must be an array.`,
2128
- );
1827
+ throw new ValidationError(`Provider "${providerId}" healthJourneys must be an array.`);
2129
1828
  }
2130
1829
  const journeyIds = new Set<string>();
2131
1830
  for (const [index, journey] of healthJourneys.entries()) {
2132
1831
  const prefix = `healthJourneys[${index}]`;
2133
1832
  if (!journey || typeof journey !== "object" || Array.isArray(journey)) {
2134
- throw new ValidationError(
2135
- `Provider "${providerId}" ${prefix} must be an object.`,
2136
- );
1833
+ throw new ValidationError(`Provider "${providerId}" ${prefix} must be an object.`);
2137
1834
  }
2138
1835
  rejectUnknownFields(journey, HEALTH_JOURNEY_FIELDS, prefix);
2139
1836
  if (typeof journey.id !== "string" || !JOURNEY_ID_RE.test(journey.id)) {
@@ -2147,10 +1844,7 @@ function validateHealthJourneys(
2147
1844
  );
2148
1845
  journeyIds.add(journey.id);
2149
1846
  validateHealthJourneySchedule(providerId, journey.id, journey.schedule);
2150
- if (
2151
- !Array.isArray(journey.coversOperations) ||
2152
- journey.coversOperations.length === 0
2153
- ) {
1847
+ if (!Array.isArray(journey.coversOperations) || journey.coversOperations.length === 0) {
2154
1848
  throw new ValidationError(
2155
1849
  `Provider "${providerId}" healthJourneys.${journey.id}.coversOperations must be a non-empty array.`,
2156
1850
  );
@@ -2185,10 +1879,7 @@ function validateHealthJourneys(
2185
1879
  `Provider "${providerId}" healthJourneys.${journey.id}.smsMatchers must be an array.`,
2186
1880
  );
2187
1881
  for (const [matcherIndex, matcher] of journey.smsMatchers.entries()) {
2188
- validateSmsOtpMatcher(
2189
- matcher,
2190
- `healthJourneys.${journey.id}.smsMatchers[${matcherIndex}]`,
2191
- );
1882
+ validateSmsOtpMatcher(matcher, `healthJourneys.${journey.id}.smsMatchers[${matcherIndex}]`);
2192
1883
  if (matcherIds.has(matcher.id))
2193
1884
  throw new ValidationError(
2194
1885
  `Provider "${providerId}" healthJourneys.${journey.id}.smsMatchers has duplicate matcher id "${matcher.id}".`,
@@ -2199,9 +1890,7 @@ function validateHealthJourneys(
2199
1890
  for (const [stepIndex, step] of journey.steps.entries()) {
2200
1891
  const stepPath = `healthJourneys.${journey.id}.steps[${stepIndex}]`;
2201
1892
  if (!step || typeof step !== "object" || Array.isArray(step))
2202
- throw new ValidationError(
2203
- `Provider "${providerId}" ${stepPath} must be an object.`,
2204
- );
1893
+ throw new ValidationError(`Provider "${providerId}" ${stepPath} must be an object.`);
2205
1894
  rejectUnknownFields(step, HEALTH_JOURNEY_STEP_FIELDS, stepPath);
2206
1895
  if (typeof step.id !== "string" || !JOURNEY_ID_RE.test(step.id))
2207
1896
  throw new ValidationError(
@@ -2211,20 +1900,13 @@ function validateHealthJourneys(
2211
1900
  throw new ValidationError(
2212
1901
  `Provider "${providerId}" ${stepPath}.operationId references unknown operation "${step.operationId}".`,
2213
1902
  );
2214
- if (
2215
- step.usesSmsMatcher !== undefined &&
2216
- !matcherIds.has(step.usesSmsMatcher)
2217
- )
1903
+ if (step.usesSmsMatcher !== undefined && !matcherIds.has(step.usesSmsMatcher))
2218
1904
  throw new ValidationError(
2219
1905
  `Provider "${providerId}" ${stepPath}.usesSmsMatcher references unknown matcher "${step.usesSmsMatcher}".`,
2220
1906
  );
2221
1907
  }
2222
1908
  if (journey.manualTrigger !== undefined)
2223
- validateHealthJourneyManualTrigger(
2224
- providerId,
2225
- journey.id,
2226
- journey.manualTrigger,
2227
- );
1909
+ validateHealthJourneyManualTrigger(providerId, journey.id, journey.manualTrigger);
2228
1910
  if (journey.timeout !== undefined)
2229
1911
  assertIsoDuration(
2230
1912
  journey.timeout,
@@ -2270,23 +1952,10 @@ function validateOperationHealthChecks(
2270
1952
  fix: `Remove either operations.${operationName}.healthCheck or operations.${operationName}.healthCheckUnsupported.`,
2271
1953
  },
2272
1954
  );
2273
- if (hasCheck)
2274
- validateHealthCheckSuite(
2275
- providerId,
2276
- operationName,
2277
- operation.healthCheck,
2278
- );
1955
+ if (hasCheck) validateHealthCheckSuite(providerId, operationName, operation.healthCheck);
2279
1956
  if (hasUnsupported)
2280
- validateHealthCheckUnsupported(
2281
- providerId,
2282
- operationName,
2283
- operation.healthCheckUnsupported,
2284
- );
2285
- if (
2286
- !hasCheck &&
2287
- !hasUnsupported &&
2288
- !journeyCoveredOperations.has(operationName)
2289
- )
1957
+ validateHealthCheckUnsupported(providerId, operationName, operation.healthCheckUnsupported);
1958
+ if (!hasCheck && !hasUnsupported && !journeyCoveredOperations.has(operationName))
2290
1959
  throw new ValidationError(
2291
1960
  `Provider "${providerId}" operation "${operationName}" declares neither healthCheck nor healthCheckUnsupported.`,
2292
1961
  {
@@ -2341,6 +2010,19 @@ function validateOperationFixtures(
2341
2010
  }
2342
2011
  }
2343
2012
 
2013
+ /**
2014
+ * Shallow shape guard only: the `deployment` object is passed through
2015
+ * verbatim and deliberately not deep-validated by the SDK — the APIFuse
2016
+ * registry builder owns deployment validation and profile resolution.
2017
+ */
2018
+ function validateProviderDeployment(providerId: string, deployment: unknown): void {
2019
+ if (deployment === undefined) return;
2020
+ if (!deployment || typeof deployment !== "object" || Array.isArray(deployment))
2021
+ throw new ProviderError(`Provider "${providerId}" deployment must be an object when present`, {
2022
+ fix: 'Pass deployment: { runtime: "shared" | "dedicated" | "browser", ... } or remove the field',
2023
+ });
2024
+ }
2025
+
2344
2026
  export function defineProvider<
2345
2027
  TOperations extends Record<string, ProviderOperation>,
2346
2028
  TConfig extends ProviderConfig<TOperations>,
@@ -2353,12 +2035,9 @@ export function defineProvider<
2353
2035
  fix: 'Use lowercase alphanumeric with dashes, e.g., "korea-air-quality"',
2354
2036
  });
2355
2037
  if (Object.keys(config.operations).length === 0)
2356
- throw new ProviderError(
2357
- `Provider "${config.id}" must define at least one operation`,
2358
- {
2359
- fix: "Add at least one operation to the operations object",
2360
- },
2361
- );
2038
+ throw new ProviderError(`Provider "${config.id}" must define at least one operation`, {
2039
+ fix: "Add at least one operation to the operations object",
2040
+ });
2362
2041
  validateOperationIds(config.id, config.operations);
2363
2042
  validateOperationAnnotations(config.id, config.operations);
2364
2043
  validateOperationObservability(config.id, config.operations);
@@ -2370,11 +2049,7 @@ export function defineProvider<
2370
2049
  config.operations,
2371
2050
  config.healthJourneys,
2372
2051
  );
2373
- validateOperationHealthChecks(
2374
- config.id,
2375
- config.operations,
2376
- journeyCoveredOperations,
2377
- );
2052
+ validateOperationHealthChecks(config.id, config.operations, journeyCoveredOperations);
2378
2053
  if (config.healthMonitor !== undefined && config.healthProbe !== undefined)
2379
2054
  throw new ValidationError(
2380
2055
  `Provider "${config.id}" declares both healthMonitor and healthProbe. They are aliases; declare exactly one.`,
@@ -2388,6 +2063,7 @@ export function defineProvider<
2388
2063
  config.healthProbe !== undefined ? "healthProbe" : "healthMonitor",
2389
2064
  );
2390
2065
  validateOperationFixtures(config.id, config.operations);
2066
+ validateProviderDeployment(config.id, config.deployment);
2391
2067
  validateProviderProxy(config);
2392
2068
  validateProviderStt(config);
2393
2069
  if (config.runtime === "browser" && !config.browser)
@@ -2406,6 +2082,9 @@ export function defineProvider<
2406
2082
  id: config.id,
2407
2083
  version: config.version,
2408
2084
  runtime: config.runtime,
2085
+ // Verbatim passthrough: deployment validation and profile resolution
2086
+ // are owned by the APIFuse registry builder, not the SDK.
2087
+ deployment: config.deployment,
2409
2088
  allowedHosts: config.allowedHosts,
2410
2089
  stealth: config.stealth,
2411
2090
  proxy: config.proxy,