@apifuse/provider-sdk 2.2.0-beta.15 → 2.2.0-beta.17

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.
@@ -8,6 +8,12 @@ export const NODEMAVEN_FILTER_ENV = "APIFUSE__PROXY__NODEMAVEN_FILTER";
8
8
 
9
9
  export const NODEMAVEN_GATEWAY_HOST = "gate.nodemaven.com";
10
10
 
11
+ export type NodemavenCredentials = {
12
+ readonly username: string;
13
+ readonly password: string;
14
+ readonly filter?: string;
15
+ };
16
+
11
17
  /** Both schemes tunnel bytes end-to-end, preserving the client TLS handshake. */
12
18
  export type ProxyProtocol = "http" | "socks5";
13
19
 
@@ -44,8 +50,8 @@ function readNodemavenPassword(): string | undefined {
44
50
  return process.env[NODEMAVEN_PASSWORD_ENV]?.trim() || undefined;
45
51
  }
46
52
 
47
- function resolveNodemavenFilter(): string {
48
- const raw = process.env[NODEMAVEN_FILTER_ENV]?.trim().toLowerCase();
53
+ function resolveNodemavenFilter(value: string | undefined): string {
54
+ const raw = value?.trim().toLowerCase();
49
55
  if (!raw) return DEFAULT_NODEMAVEN_FILTER;
50
56
  if (!NODEMAVEN_FILTERS.has(raw)) {
51
57
  throw new Error(`${NODEMAVEN_FILTER_ENV} must be "medium" or "high"`);
@@ -133,6 +139,8 @@ function selectPort(protocol: ProxyProtocol, sid: string, poolIndex: number): nu
133
139
 
134
140
  export type NodemavenSynthesisInput = {
135
141
  policy: ProviderProxyPolicy;
142
+ /** Explicit credentials; synthesis never reads process-global state. */
143
+ credentials: NodemavenCredentials;
136
144
  affinityKey: string | undefined;
137
145
  protocol: ProxyProtocol;
138
146
  poolIndex: number;
@@ -159,15 +167,15 @@ export type NodemavenSynthesis = {
159
167
  * There is no allocation API — geo/session are encoded in the username.
160
168
  */
161
169
  export function synthesizeNodemavenProxy(input: NodemavenSynthesisInput): NodemavenSynthesis {
162
- const username = readNodemavenUsername();
163
- const password = readNodemavenPassword();
170
+ const username = input.credentials.username.trim();
171
+ const password = input.credentials.password.trim();
164
172
  if (!username || !password) {
165
173
  throw new Error(
166
174
  `NodeMaven credentials missing: set ${NODEMAVEN_USERNAME_ENV} and ${NODEMAVEN_PASSWORD_ENV}.`,
167
175
  );
168
176
  }
169
177
 
170
- const filter = resolveNodemavenFilter();
178
+ const filter = resolveNodemavenFilter(input.credentials.filter);
171
179
  const sid = deriveSid(input.policy, input.affinityKey, input.poolIndex, input.refreshEpoch);
172
180
  const port = selectPort(input.protocol, sid, input.poolIndex);
173
181
  const lifetimeMinutes = nodemavenLifetimeMinutes(input.policy);
@@ -4,6 +4,10 @@ import { join } from "node:path";
4
4
  import { Hono } from "hono";
5
5
  import { z } from "zod";
6
6
  import { AuthAbortError, createAuthFlowHelpers } from "../auth.js";
7
+ import {
8
+ SDK_OWNED_PROVIDER_ERROR_CODES,
9
+ SDK_RUNTIME_OWNED_ERROR_CODES,
10
+ } from "../error-resolution.js";
7
11
  import {
8
12
  AuthError,
9
13
  isProviderError,
@@ -36,7 +40,10 @@ import { createEnvContext } from "../runtime/env.js";
36
40
  import { executeOperation } from "../runtime/executor.js";
37
41
  import { createHttpClient } from "../runtime/http.js";
38
42
  import { wrapWithInstrumentation } from "../runtime/instrumentation.js";
39
- import { createNativeNetworkClient } from "../runtime/native-network.js";
43
+ import {
44
+ createEnvVendorCredentialResolver,
45
+ createNativeNetworkClient,
46
+ } from "../runtime/native-network.js";
40
47
  import { getProviderBaseUrl } from "../runtime/provider.js";
41
48
  import {
42
49
  PROXY_AUTH_IP_DENIED_CODE,
@@ -79,8 +86,10 @@ import type {
79
86
  FlowContextStore,
80
87
  HttpRetrySummary,
81
88
  OperationDefinition,
89
+ OperationErrorCode,
82
90
  OperationHttpStreamTransport,
83
91
  OperationSseTransport,
92
+ ProviderErrorStatus,
84
93
  ProviderContext,
85
94
  ProviderDefinition,
86
95
  ProviderProxyPolicy,
@@ -89,6 +98,7 @@ import type {
89
98
  StealthClient,
90
99
  SttContext,
91
100
  } from "../types.js";
101
+ import { VALID_OPERATION_ERROR_STATUSES } from "../types.js";
92
102
  import {
93
103
  createSelfTestApp,
94
104
  createSelfTestAuthFlowInvoke,
@@ -369,6 +379,7 @@ function createProviderContext(
369
379
  egress: provider.native.network,
370
380
  proxyPolicy: resolveNativeProxyPolicy(provider),
371
381
  affinityKey: proxyClientOptions.affinityKey,
382
+ credentials: createEnvVendorCredentialResolver(env),
372
383
  }),
373
384
  },
374
385
  }
@@ -482,6 +493,9 @@ function createAuthFlowContext(
482
493
  egress: provider.native.network,
483
494
  proxyPolicy: resolveNativeProxyPolicy(provider),
484
495
  affinityKey: proxyClientOptions.affinityKey,
496
+ credentials: createEnvVendorCredentialResolver(
497
+ createEnvContext(provider.secrets?.map((secret) => secret.name)),
498
+ ),
485
499
  }),
486
500
  },
487
501
  }
@@ -644,8 +658,12 @@ function zodDetails(error: z.ZodError): Array<{
644
658
  }));
645
659
  }
646
660
 
647
- function toErrorResponse(error: unknown, requestId?: string): OperationErrorResponse {
648
- const observability = errorObservabilityDetails(error);
661
+ function toErrorResponse(
662
+ error: unknown,
663
+ requestId?: string,
664
+ declaredErrorCode?: OperationErrorCode,
665
+ ): OperationErrorResponse {
666
+ const observability = errorObservabilityDetails(error, declaredErrorCode);
649
667
  if (error instanceof StatefulRoutingDeadlineError) {
650
668
  return {
651
669
  error: {
@@ -709,7 +727,13 @@ function toErrorResponse(error: unknown, requestId?: string): OperationErrorResp
709
727
  // narrowing from a ProviderError-typed value would collapse the negative branch
710
728
  // to `never`. Narrowing from unknown avoids that while still recognizing errors
711
729
  // from a duplicate SDK module instance.
712
- function providerObservabilityDetails(error: unknown): ErrorObservabilityDetails | undefined {
730
+ function providerObservabilityDetails(
731
+ error: unknown,
732
+ declaredErrorCode?: OperationErrorCode,
733
+ ): ErrorObservabilityDetails | undefined {
734
+ const declaredRetryable = sdkOwnsErrorResolution(error)
735
+ ? undefined
736
+ : declaredErrorCode?.retryable;
713
737
  // Session-expiry surfaces the credential_expired category + the opt-in
714
738
  // retryable signal so Gateway/Credential Service can refresh and re-drive the
715
739
  // operation (see design.md §4.3 D3). Without this branch the auth error would
@@ -719,7 +743,7 @@ function providerObservabilityDetails(error: unknown): ErrorObservabilityDetails
719
743
  return {
720
744
  category: error.options?.category ?? "credential_expired",
721
745
  taxonomyVersion: PROVIDER_OBSERVABILITY_TAXONOMY_VERSION,
722
- retryable: error.options?.retryable ?? false,
746
+ retryable: error.options?.retryable ?? declaredRetryable ?? false,
723
747
  };
724
748
  }
725
749
  // Missing-secret errors carry the canonical credential_unavailable category
@@ -731,7 +755,7 @@ function providerObservabilityDetails(error: unknown): ErrorObservabilityDetails
731
755
  return {
732
756
  category: error.options?.category ?? "credential_unavailable",
733
757
  taxonomyVersion: PROVIDER_OBSERVABILITY_TAXONOMY_VERSION,
734
- retryable: error.options?.retryable ?? false,
758
+ retryable: error.options?.retryable ?? declaredRetryable ?? false,
735
759
  };
736
760
  }
737
761
  if (!isTransportError(error)) {
@@ -766,18 +790,27 @@ function providerObservabilityDetails(error: unknown): ErrorObservabilityDetails
766
790
  };
767
791
  }
768
792
 
769
- function errorObservabilityDetails(error: unknown): ErrorObservabilityDetails {
770
- const providerDetails = providerObservabilityDetails(error);
793
+ function errorObservabilityDetails(
794
+ error: unknown,
795
+ declaredErrorCode?: OperationErrorCode,
796
+ ): ErrorObservabilityDetails {
797
+ const effectiveDeclaration = sdkOwnsErrorResolution(error) ? undefined : declaredErrorCode;
798
+ const providerDetails = providerObservabilityDetails(error, effectiveDeclaration);
771
799
  if (providerDetails) return providerDetails;
772
800
 
773
801
  if (error instanceof z.ZodError || isValidationError(error)) {
802
+ const declaredStatus = effectiveDeclaration?.status;
774
803
  return {
775
804
  category:
776
805
  isProviderError(error) && error.options?.category
777
806
  ? error.options.category
778
- : "input_validation",
807
+ : isEmittableErrorStatus(declaredStatus) && declaredStatus >= 500
808
+ ? "provider_error"
809
+ : "input_validation",
779
810
  taxonomyVersion: PROVIDER_OBSERVABILITY_TAXONOMY_VERSION,
780
- retryable: isProviderError(error) ? (error.options?.retryable ?? false) : false,
811
+ retryable: isProviderError(error)
812
+ ? (error.options?.retryable ?? effectiveDeclaration?.retryable ?? false)
813
+ : false,
781
814
  };
782
815
  }
783
816
 
@@ -793,7 +826,7 @@ function errorObservabilityDetails(error: unknown): ErrorObservabilityDetails {
793
826
  return {
794
827
  category: error.options?.category ?? "provider_error",
795
828
  taxonomyVersion: PROVIDER_OBSERVABILITY_TAXONOMY_VERSION,
796
- retryable: error.options?.retryable ?? false,
829
+ retryable: error.options?.retryable ?? effectiveDeclaration?.retryable ?? false,
797
830
  };
798
831
  }
799
832
 
@@ -804,9 +837,16 @@ function errorObservabilityDetails(error: unknown): ErrorObservabilityDetails {
804
837
  };
805
838
  }
806
839
 
807
- function responseWithErrorObservability(response: Response, error: unknown): Response {
840
+ function responseWithErrorObservability(
841
+ response: Response,
842
+ error: unknown,
843
+ declaredErrorCode?: OperationErrorCode,
844
+ ): Response {
808
845
  const headers = new Headers(response.headers);
809
- headers.set(ERROR_OBSERVABILITY_HEADER, JSON.stringify(errorObservabilityDetails(error)));
846
+ headers.set(
847
+ ERROR_OBSERVABILITY_HEADER,
848
+ JSON.stringify(errorObservabilityDetails(error, declaredErrorCode)),
849
+ );
810
850
  return new Response(response.body, {
811
851
  status: response.status,
812
852
  statusText: response.statusText,
@@ -838,7 +878,14 @@ function publicProviderErrorMessage(error: ProviderError): string {
838
878
  return error.message;
839
879
  }
840
880
 
841
- function toStatusCode(error: unknown): 400 | 401 | 404 | 429 | 500 | 502 | 503 | 504 {
881
+ function isEmittableErrorStatus(value: unknown): value is ProviderErrorStatus {
882
+ return (
883
+ typeof value === "number" &&
884
+ VALID_OPERATION_ERROR_STATUSES.some((status) => status === value)
885
+ );
886
+ }
887
+
888
+ function toStatusCode(error: unknown, declaredErrorCode?: OperationErrorCode): ProviderErrorStatus {
842
889
  if (error instanceof z.ZodError) {
843
890
  return 400;
844
891
  }
@@ -846,6 +893,12 @@ function toStatusCode(error: unknown): 400 | 401 | 404 | 429 | 500 | 502 | 503 |
846
893
  return 504;
847
894
  }
848
895
  if (isProviderError(error)) {
896
+ if (
897
+ !sdkOwnsErrorResolution(error) &&
898
+ isEmittableErrorStatus(declaredErrorCode?.status)
899
+ ) {
900
+ return declaredErrorCode.status;
901
+ }
849
902
  switch (error.code) {
850
903
  case "AUTH_REQUIRED":
851
904
  case "reauth_required":
@@ -883,82 +936,39 @@ function toStatusCode(error: unknown): 400 | 401 | 404 | 429 | 500 | 502 | 503 |
883
936
  return 500;
884
937
  }
885
938
 
886
- // Codes emitted by SDK-owned paths must never be attributed to provider
887
- // authors by the unregistered-code signal, even when their intentional status
888
- // is 500. Provider-authored codes not in this registry retain the signal.
889
- const SDK_OWNED_PROVIDER_ERROR_CODES = new Set([
890
- "AUTH_PROMPT_UNAVAILABLE",
891
- "BROWSER_CDP_POOL_REQUIRED",
892
- "BROWSER_RUNTIME_UNSUPPORTED",
893
- "STEALTH_RUNTIME_UNSUPPORTED",
894
- "SSE_EVENT_UNDECLARED",
895
- "STREAM_EVENT_TOO_LARGE",
896
- "STREAM_CHUNK_TOO_LARGE",
897
- "SSE_RESULT_UNSUPPORTED",
898
- "STREAM_RESULT_UNSUPPORTED",
899
- "AUTH_FLOW_NOT_CONFIGURED",
900
- "refresh_not_supported",
901
- "RUNTIME_UNSUPPORTED",
902
- "PROVIDER_STATE_UNSUPPORTED",
903
- "CHOICE_TOKEN_MASTER_SECRET_NOT_CONFIGURED",
904
- "CHOICE_STATE_PAYLOAD_TOO_LARGE",
905
- "CHOICE_STATE_UNAVAILABLE",
906
- "CHOICE_CONTEXT_REQUIRED",
907
- "unsupported_stealth_cookie_store_version",
908
- "provider_secret_error",
909
- "credential_key_error",
910
- "credential_mode_error",
911
- "flow_expired",
912
- "turn_validation_error",
913
- "context_access_error",
914
- "UNSUPPORTED_STT_OPTION",
915
- "INVALID_STT_AUDIO",
916
- "STT_AUDIO_TOO_LARGE",
917
- "STT_UPSTREAM_FAILED",
918
- "INVALID_STT_VERIFICATION_CODE_OPTIONS",
919
- "NO_CODE_FOUND",
920
- "AMBIGUOUS_CODE",
921
- "retry_invalid_policy",
922
- "retry_unsafe_method",
923
- "stealth_cookie_store_serialize_failed",
924
- "response_too_large",
925
- "transport_stream_unavailable",
926
- "transport_invalid_method",
927
- "http_transport_override_unsupported",
928
- "http_redirect_policy_invalid",
929
- "http_redirect_stopped",
930
- "http_redirect_max_hops",
931
- "http_redirect_missing_location",
932
- "http_redirect_loop",
933
- "transport_invalid_url",
934
- "retry_exhausted",
935
- "auth_abort_unsafe_data",
936
- "credentials_auth_missing_credential_keys",
937
- "credentials_auth_missing_credential",
938
- "credentials_auth_invalid_login_result",
939
- "credentials_auth_unknown_challenge",
940
- "credentials_auth_unknown_pending_challenge",
941
- "STATEFUL_FORWARDING_NOT_CONFIGURED",
942
- "STATEFUL_FORWARDING_SIGNATURE_MISSING",
943
- "STATEFUL_FORWARDING_NONCE_INVALID",
944
- "STATEFUL_FORWARDING_TIMESTAMP_INVALID",
945
- "STATEFUL_FORWARDING_SIGNATURE_INVALID",
946
- "STATEFUL_FORWARDING_REPLAY_DETECTED",
947
- "STATEFUL_FORWARDING_REPLAY_CACHE_FULL",
948
- "STATEFUL_FORWARDING_ENVELOPE_INVALID",
949
- "STATEFUL_FORWARDING_PROVIDER_MISMATCH",
950
- "STATEFUL_FORWARDING_SOURCE_POD_MISMATCH",
951
- "STATEFUL_FORWARDING_OWNER_FENCE_INVALID",
952
- "STATEFUL_FORWARDING_REQUEST_FAILED",
953
- "STATEFUL_FORWARDING_CONTEXT_MISSING",
954
- "STATEFUL_FORWARDING_BAD_RESPONSE",
955
- "STATEFUL_INTERNAL_EXECUTOR_NOT_CONFIGURED",
956
- "STATEFUL_FILE_FORWARDING_UNSUPPORTED",
957
- "STATEFUL_CONTROL_PLANE_OPERATION_AMBIGUOUS",
958
- "STATEFUL_CONTROL_PLANE_REQUEST_FAILED",
959
- "STATEFUL_CONTROL_PLANE_HTTP_ERROR",
960
- "STATEFUL_CONTROL_PLANE_INVALID_RESPONSE",
961
- ]);
939
+ function sdkOwnsErrorResolution(error: unknown): boolean {
940
+ if (isSessionExpiredError(error)) return true;
941
+ if (isTransportError(error)) return true;
942
+ if (error instanceof z.ZodError) return true;
943
+ if (error instanceof StatefulRoutingDeadlineError) return true;
944
+ return (
945
+ isProviderError(error) &&
946
+ typeof error.code === "string" &&
947
+ SDK_RUNTIME_OWNED_ERROR_CODES.has(error.code)
948
+ );
949
+ }
950
+
951
+ type OperationErrorCodeLookup = ReadonlyMap<string, ReadonlyMap<string, OperationErrorCode>>;
952
+
953
+ function buildOperationErrorCodeLookup(provider: ProviderDefinition): OperationErrorCodeLookup {
954
+ return new Map(
955
+ Object.entries(provider.operations).flatMap(([operationId, operation]) => {
956
+ const errorCodes = operation.docs?.errorCodes;
957
+ return errorCodes?.length
958
+ ? [[operationId, new Map(errorCodes.map((entry) => [entry.code, entry]))] as const]
959
+ : [];
960
+ }),
961
+ );
962
+ }
963
+
964
+ function declaredErrorCodeFor(
965
+ error: unknown,
966
+ operationId: string | undefined,
967
+ lookup: OperationErrorCodeLookup,
968
+ ): OperationErrorCode | undefined {
969
+ if (!operationId || !isProviderError(error) || typeof error.code !== "string") return undefined;
970
+ return lookup.get(operationId)?.get(error.code);
971
+ }
962
972
 
963
973
  function extractRequestId(raw: unknown): string | undefined {
964
974
  if (!raw || typeof raw !== "object") {
@@ -978,6 +988,7 @@ function logProviderError(
978
988
  error: unknown,
979
989
  status: number,
980
990
  cost: ProviderRequestCost,
991
+ declaredErrorCode?: OperationErrorCode,
981
992
  ): void {
982
993
  const code = isProviderError(error)
983
994
  ? (error.code ?? "provider_error")
@@ -988,13 +999,14 @@ function logProviderError(
988
999
  : "internal_error";
989
1000
  const errorClass = error instanceof Error ? error.name : typeof error;
990
1001
  const message = error instanceof Error ? error.message : String(error);
991
- const details = errorObservabilityDetails(error);
1002
+ const details = errorObservabilityDetails(error, declaredErrorCode);
992
1003
  const isUnregisteredProviderErrorCode =
993
1004
  status === 500 &&
994
1005
  isProviderError(error) &&
995
1006
  !isValidationError(error) &&
996
1007
  typeof error.code === "string" &&
997
- !SDK_OWNED_PROVIDER_ERROR_CODES.has(error.code);
1008
+ !SDK_OWNED_PROVIDER_ERROR_CODES.has(error.code) &&
1009
+ declaredErrorCode === undefined;
998
1010
  const emit = typeof logger === "function" ? logger : defaultProviderServerLogger;
999
1011
  emit({
1000
1012
  level: status >= 500 ? "error" : "warn",
@@ -1729,6 +1741,7 @@ export function createServerApp(
1729
1741
  validateStatefulServerConfig(options);
1730
1742
  const app = new Hono();
1731
1743
  const logger = options.logger ?? defaultProviderServerLogger;
1744
+ const operationErrorCodes = buildOperationErrorCodeLookup(provider);
1732
1745
  const statefulForwardingReplayCache = new StatefulForwardingReplayCache(
1733
1746
  options.statefulForwarding?.replayCacheMaxEntries ??
1734
1747
  DEFAULT_STATEFUL_FORWARDING_REPLAY_CACHE_MAX_ENTRIES,
@@ -1774,6 +1787,7 @@ export function createServerApp(
1774
1787
  app.post(STATEFUL_INTERNAL_OPERATIONS_ROUTE, async (c) => {
1775
1788
  let rawBodyText = "";
1776
1789
  let rawBody: unknown;
1790
+ let operationId: string | undefined;
1777
1791
  const operation = "stateful-internal";
1778
1792
  const requestCost = startRequestCost();
1779
1793
  try {
@@ -1884,7 +1898,7 @@ export function createServerApp(
1884
1898
  });
1885
1899
  }
1886
1900
  const request = operationRequestFromForwardingEnvelope(envelope);
1887
- const operationId = envelope.operationId;
1901
+ operationId = envelope.operationId;
1888
1902
  const ctx = createProviderContext(provider, request, operationId, options, state);
1889
1903
  if (deadlineAtMs !== undefined && deadlineAtMs <= Date.now()) {
1890
1904
  throw new StatefulRoutingDeadlineError(envelope.requestId, envelope.deadlineAt as string);
@@ -1908,7 +1922,8 @@ export function createServerApp(
1908
1922
  );
1909
1923
  return c.json({ data: output });
1910
1924
  } catch (error) {
1911
- const status = toStatusCode(error);
1925
+ const declaredErrorCode = declaredErrorCodeFor(error, operationId, operationErrorCodes);
1926
+ const status = toStatusCode(error, declaredErrorCode);
1912
1927
  if (isProviderError(error) && error.code === "STATEFUL_FORWARDING_REPLAY_CACHE_FULL") {
1913
1928
  c.header("Retry-After", String(STATEFUL_FORWARDING_REPLAY_RETRY_AFTER_SECONDS));
1914
1929
  }
@@ -1917,15 +1932,17 @@ export function createServerApp(
1917
1932
  logger,
1918
1933
  provider,
1919
1934
  "operation",
1920
- operation,
1935
+ operationId || operation,
1921
1936
  requestId,
1922
1937
  error,
1923
1938
  status,
1924
1939
  finishRequestCost(requestCost),
1940
+ declaredErrorCode,
1925
1941
  );
1926
1942
  return responseWithErrorObservability(
1927
- c.json(toErrorResponse(error, requestId), status),
1943
+ c.json(toErrorResponse(error, requestId, declaredErrorCode), status),
1928
1944
  error,
1945
+ declaredErrorCode,
1929
1946
  );
1930
1947
  }
1931
1948
  });
@@ -1976,7 +1993,8 @@ export function createServerApp(
1976
1993
  );
1977
1994
  return c.json(response);
1978
1995
  } catch (error) {
1979
- const status = toStatusCode(error);
1996
+ const declaredErrorCode = declaredErrorCodeFor(error, operation, operationErrorCodes);
1997
+ const status = toStatusCode(error, declaredErrorCode);
1980
1998
  const requestId = extractRequestId(rawBody);
1981
1999
  logProviderError(
1982
2000
  logger,
@@ -1987,12 +2005,14 @@ export function createServerApp(
1987
2005
  error,
1988
2006
  status,
1989
2007
  finishRequestCost(requestCost),
2008
+ declaredErrorCode,
1990
2009
  );
1991
2010
  const telemetryHeader = proxyTelemetry.toHeaderValue();
1992
2011
  if (telemetryHeader) c.header(PROVIDER_TELEMETRY_HEADER, telemetryHeader);
1993
2012
  return responseWithErrorObservability(
1994
- c.json(toErrorResponse(error, requestId), status),
2013
+ c.json(toErrorResponse(error, requestId, declaredErrorCode), status),
1995
2014
  error,
2015
+ declaredErrorCode,
1996
2016
  );
1997
2017
  }
1998
2018
  });
package/src/types.ts CHANGED
@@ -719,9 +719,13 @@ export interface HealthMonitorProbeOverride {
719
719
  degradedThresholdMs?: number;
720
720
  }
721
721
 
722
+ export const VALID_OPERATION_ERROR_STATUSES = [400, 401, 404, 429, 500, 502, 503, 504] as const;
723
+
724
+ export type ProviderErrorStatus = (typeof VALID_OPERATION_ERROR_STATUSES)[number];
725
+
722
726
  export interface OperationErrorCode {
723
727
  code: string;
724
- status?: number;
728
+ status?: ProviderErrorStatus;
725
729
  description: string;
726
730
  retryable?: boolean;
727
731
  }
@@ -1314,6 +1318,10 @@ export interface NativeTcpEgressRule {
1314
1318
  /**
1315
1319
  * Bounded native TCP egress discovered through a declared bootstrap endpoint.
1316
1320
  * Host suffixes are exact DNS suffixes, not wildcard patterns.
1321
+ * Dynamic rules must declare at least one target host selector through
1322
+ * targetHostSuffixes and/or targetIpv4Cidrs. IPv4-literal grant targets match
1323
+ * only targetIpv4Cidrs, while DNS-name targets match only targetHostSuffixes.
1324
+ * CIDRs are exact IPv4 networks in a.b.c.d/nn form.
1317
1325
  *
1318
1326
  * Dynamic rules are ordered. The first rule whose source, target, port, and TLS
1319
1327
  * selectors match exclusively owns the grant; its ttlMs and maxGrants bounds
@@ -1326,7 +1334,8 @@ export interface NativeTcpDynamicEgressRule {
1326
1334
  readonly sourceHostSuffixes?: readonly string[];
1327
1335
  readonly sourcePorts?: readonly number[];
1328
1336
  readonly sourcePortRanges?: readonly NativeTcpPortRange[];
1329
- readonly targetHostSuffixes: readonly string[];
1337
+ readonly targetHostSuffixes?: readonly string[];
1338
+ readonly targetIpv4Cidrs?: readonly string[];
1330
1339
  readonly targetPorts?: readonly number[];
1331
1340
  readonly targetPortRanges?: readonly NativeTcpPortRange[];
1332
1341
  readonly tls: NativeTcpTlsMode;