@apifuse/provider-sdk 2.1.0-beta.9 → 2.2.0-beta.10

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 (263) hide show
  1. package/AUTHORING.md +240 -0
  2. package/CHANGELOG.md +93 -0
  3. package/README.md +26 -10
  4. package/SUBMISSION.md +11 -12
  5. package/bin/apifuse-check.ts +44 -59
  6. package/bin/apifuse-create.ts +1 -1
  7. package/bin/apifuse-dev.ts +27 -52
  8. package/bin/apifuse-pack-check.ts +36 -0
  9. package/bin/apifuse-pack-smoke.ts +22 -81
  10. package/bin/apifuse-pack-types.ts +266 -0
  11. package/bin/apifuse-perf.ts +45 -127
  12. package/bin/apifuse-record.ts +53 -70
  13. package/bin/apifuse-submit-check.ts +2177 -353
  14. package/bin/apifuse-sync-assets.ts +117 -0
  15. package/bin/apifuse.ts +1 -1
  16. package/bin/submit-check-delimited-text.ts +50 -0
  17. package/bin/submit-check-xml-semantics.ts +204 -0
  18. package/bin/submit-check-xml.ts +134 -0
  19. package/dist/auth-turn/auth-turn.v1.schema.json +89 -0
  20. package/dist/auth-turn/fixtures/invalid/empty-kind.json +4 -0
  21. package/dist/auth-turn/fixtures/invalid/expires-at-not-string.json +5 -0
  22. package/dist/auth-turn/fixtures/invalid/missing-kind.json +3 -0
  23. package/dist/auth-turn/fixtures/invalid/missing-turn-id.json +3 -0
  24. package/dist/auth-turn/fixtures/invalid/timing-unknown-field.json +7 -0
  25. package/dist/auth-turn/fixtures/invalid/turn-id-snake-case.json +4 -0
  26. package/dist/auth-turn/fixtures/invalid/unknown-top-level-field.json +5 -0
  27. package/dist/auth-turn/fixtures/valid/abort.json +8 -0
  28. package/dist/auth-turn/fixtures/valid/challenge.json +17 -0
  29. package/dist/auth-turn/fixtures/valid/complete.json +13 -0
  30. package/dist/auth-turn/fixtures/valid/form.json +14 -0
  31. package/dist/auth-turn/fixtures/valid/message.json +13 -0
  32. package/dist/auth-turn/fixtures/valid/multi_choice.json +15 -0
  33. package/dist/auth-turn/fixtures/valid/pending.json +5 -0
  34. package/dist/auth-turn/fixtures/valid/poll.json +9 -0
  35. package/dist/auth-turn/fixtures/valid/redirect.json +16 -0
  36. package/dist/auth-turn/fixtures/valid/retry.json +8 -0
  37. package/dist/auth-turn/fixtures/valid/unknown-kind.json +7 -0
  38. package/dist/auth-turn/index.d.ts +195 -0
  39. package/dist/auth-turn/index.js +133 -0
  40. package/dist/auth.d.ts +76 -0
  41. package/dist/auth.js +427 -0
  42. package/dist/ceremonies/index.d.ts +1 -1
  43. package/dist/ceremonies/index.js +14 -48
  44. package/dist/cli/commands.d.ts +1 -1
  45. package/dist/cli/commands.js +8 -0
  46. package/dist/cli/create.d.ts +3 -0
  47. package/dist/cli/create.js +47 -33
  48. package/dist/cli/prompt-assets.d.ts +80 -0
  49. package/dist/cli/prompt-assets.js +743 -0
  50. package/dist/cli/templates/provider/.agents/skills/fixtures-and-recording/SKILL.md.tpl +58 -0
  51. package/dist/cli/templates/provider/.agents/skills/health-checks-and-fail-closed/SKILL.md.tpl +65 -0
  52. package/dist/cli/templates/provider/.agents/skills/normalization-standards/SKILL.md.tpl +57 -0
  53. package/dist/cli/templates/provider/.agents/skills/pagination-and-counts/SKILL.md.tpl +52 -0
  54. package/dist/cli/templates/provider/.agents/skills/upstream-contract-verification/SKILL.md.tpl +45 -0
  55. package/dist/cli/templates/provider/.agents/skills/upstream-notes/README.md.tpl +13 -0
  56. package/dist/cli/templates/provider/.dockerignore.tpl +22 -0
  57. package/dist/cli/templates/provider/.gitignore.tpl +22 -0
  58. package/dist/cli/templates/provider/AGENTS.md.tpl +96 -0
  59. package/dist/cli/templates/provider/Dockerfile.tpl +7 -0
  60. package/dist/cli/templates/provider/README.md.tpl +163 -0
  61. package/dist/cli/templates/provider/dev.ts.tpl +5 -0
  62. package/dist/cli/templates/provider/domain/README.md.tpl +3 -0
  63. package/dist/cli/templates/provider/index.test.ts.tpl +13 -0
  64. package/dist/cli/templates/provider/index.ts.tpl +15 -0
  65. package/dist/cli/templates/provider/mappers/README.md.tpl +3 -0
  66. package/dist/cli/templates/provider/meta.ts.tpl +7 -0
  67. package/dist/cli/templates/provider/operations/index.ts.tpl +5 -0
  68. package/dist/cli/templates/provider/operations/ping.ts.tpl +24 -0
  69. package/dist/cli/templates/provider/schemas/ping.ts.tpl +24 -0
  70. package/dist/cli/templates/provider/start.ts.tpl +5 -0
  71. package/dist/cli/templates/provider/upstream/README.md.tpl +3 -0
  72. package/dist/config/loader.d.ts +149 -8
  73. package/dist/config/loader.js +378 -89
  74. package/dist/contract-serialization.d.ts +2 -2
  75. package/dist/contract-serialization.js +3 -6
  76. package/dist/contract-types.d.ts +2 -2
  77. package/dist/contract.d.ts +3 -3
  78. package/dist/contract.js +5 -6
  79. package/dist/define.d.ts +13 -1
  80. package/dist/define.js +245 -178
  81. package/dist/dev.d.ts +1 -1
  82. package/dist/dev.js +1 -1
  83. package/dist/errors.d.ts +4 -1
  84. package/dist/errors.js +48 -0
  85. package/dist/i18n/catalog.d.ts +2 -2
  86. package/dist/i18n/catalog.js +4 -10
  87. package/dist/i18n/index.d.ts +2 -2
  88. package/dist/i18n/index.js +2 -2
  89. package/dist/i18n/keys.d.ts +2 -2
  90. package/dist/index.d.ts +44 -41
  91. package/dist/index.js +39 -36
  92. package/dist/lint.d.ts +1 -0
  93. package/dist/lint.js +35 -15
  94. package/dist/provider.d.ts +11 -9
  95. package/dist/provider.js +9 -8
  96. package/dist/public-schema-field-lint.d.ts +1 -1
  97. package/dist/recipes/gov-api.js +1 -1
  98. package/dist/runtime/auth-flow.d.ts +1 -1
  99. package/dist/runtime/auth-flow.js +4 -2
  100. package/dist/runtime/browser.d.ts +1 -1
  101. package/dist/runtime/browser.js +214 -25
  102. package/dist/runtime/cache.d.ts +1 -1
  103. package/dist/runtime/cache.js +4 -8
  104. package/dist/runtime/choice.d.ts +1 -1
  105. package/dist/runtime/choice.js +31 -35
  106. package/dist/runtime/credential.d.ts +1 -1
  107. package/dist/runtime/credential.js +1 -1
  108. package/dist/runtime/env.d.ts +1 -1
  109. package/dist/runtime/executor.d.ts +1 -1
  110. package/dist/runtime/executor.js +15 -3
  111. package/dist/runtime/http.d.ts +2 -2
  112. package/dist/runtime/http.js +160 -344
  113. package/dist/runtime/insights.d.ts +1 -1
  114. package/dist/runtime/insights.js +6 -13
  115. package/dist/runtime/instrumentation.d.ts +2 -2
  116. package/dist/runtime/instrumentation.js +56 -19
  117. package/dist/runtime/keyring.js +1 -1
  118. package/dist/runtime/namespace.js +1 -1
  119. package/dist/runtime/otlp.d.ts +1 -1
  120. package/dist/runtime/perf.d.ts +1 -1
  121. package/dist/runtime/provider.d.ts +1 -1
  122. package/dist/runtime/provider.js +1 -2
  123. package/dist/runtime/proxy-errors.d.ts +1 -1
  124. package/dist/runtime/proxy-errors.js +9 -7
  125. package/dist/runtime/proxy-nodemaven.d.ts +35 -0
  126. package/dist/runtime/proxy-nodemaven.js +128 -0
  127. package/dist/runtime/proxy-retry-policy.d.ts +40 -0
  128. package/dist/runtime/proxy-retry-policy.js +326 -0
  129. package/dist/runtime/proxy-telemetry.d.ts +2 -1
  130. package/dist/runtime/proxy-telemetry.js +55 -52
  131. package/dist/runtime/redis.d.ts +1 -1
  132. package/dist/runtime/redis.js +2 -4
  133. package/dist/runtime/request-options.d.ts +1 -1
  134. package/dist/runtime/secrets.d.ts +27 -0
  135. package/dist/runtime/secrets.js +51 -0
  136. package/dist/runtime/state.d.ts +2 -2
  137. package/dist/runtime/state.js +15 -4
  138. package/dist/runtime/stealth.d.ts +7 -4
  139. package/dist/runtime/stealth.js +257 -215
  140. package/dist/runtime/stt.d.ts +1 -1
  141. package/dist/runtime/stt.js +11 -15
  142. package/dist/runtime/trace.d.ts +2 -2
  143. package/dist/runtime/trace.js +2 -4
  144. package/dist/runtime/waterfall.d.ts +1 -1
  145. package/dist/schema.d.ts +1 -1
  146. package/dist/schema.js +7 -15
  147. package/dist/serve.d.ts +1 -1
  148. package/dist/serve.js +1 -1
  149. package/dist/server/index.d.ts +7 -3
  150. package/dist/server/index.js +6 -2
  151. package/dist/server/self-test-input-tokens.d.ts +1 -0
  152. package/dist/server/self-test-input-tokens.js +37 -0
  153. package/dist/server/self-test-redaction.d.ts +20 -0
  154. package/dist/server/self-test-redaction.js +70 -0
  155. package/dist/server/self-test-token.d.ts +30 -0
  156. package/dist/server/self-test-token.js +50 -0
  157. package/dist/server/self-test.d.ts +199 -0
  158. package/dist/server/self-test.js +1113 -0
  159. package/dist/server/serve.d.ts +14 -3
  160. package/dist/server/serve.js +135 -64
  161. package/dist/server/types.d.ts +10 -9
  162. package/dist/server/types.js +3 -7
  163. package/dist/stealth/profiles.d.ts +1 -1
  164. package/dist/stealth/profiles.js +5 -14
  165. package/dist/stream.d.ts +1 -1
  166. package/dist/testing/index.d.ts +2 -2
  167. package/dist/testing/index.js +2 -2
  168. package/dist/testing/run.d.ts +1 -1
  169. package/dist/testing/run.js +12 -15
  170. package/dist/types.d.ts +237 -1
  171. package/dist/user-input.d.ts +30 -0
  172. package/dist/user-input.js +66 -0
  173. package/package.json +16 -5
  174. package/src/auth-turn/auth-turn.v1.schema.json +89 -0
  175. package/src/auth-turn/fixtures/invalid/empty-kind.json +4 -0
  176. package/src/auth-turn/fixtures/invalid/expires-at-not-string.json +5 -0
  177. package/src/auth-turn/fixtures/invalid/missing-kind.json +3 -0
  178. package/src/auth-turn/fixtures/invalid/missing-turn-id.json +3 -0
  179. package/src/auth-turn/fixtures/invalid/timing-unknown-field.json +7 -0
  180. package/src/auth-turn/fixtures/invalid/turn-id-snake-case.json +4 -0
  181. package/src/auth-turn/fixtures/invalid/unknown-top-level-field.json +5 -0
  182. package/src/auth-turn/fixtures/valid/abort.json +8 -0
  183. package/src/auth-turn/fixtures/valid/challenge.json +17 -0
  184. package/src/auth-turn/fixtures/valid/complete.json +13 -0
  185. package/src/auth-turn/fixtures/valid/form.json +14 -0
  186. package/src/auth-turn/fixtures/valid/message.json +13 -0
  187. package/src/auth-turn/fixtures/valid/multi_choice.json +15 -0
  188. package/src/auth-turn/fixtures/valid/pending.json +5 -0
  189. package/src/auth-turn/fixtures/valid/poll.json +9 -0
  190. package/src/auth-turn/fixtures/valid/redirect.json +16 -0
  191. package/src/auth-turn/fixtures/valid/retry.json +8 -0
  192. package/src/auth-turn/fixtures/valid/unknown-kind.json +7 -0
  193. package/src/auth-turn/index.ts +177 -0
  194. package/src/auth.ts +728 -0
  195. package/src/ceremonies/index.ts +33 -121
  196. package/src/cli/commands.ts +10 -0
  197. package/src/cli/create.ts +69 -99
  198. package/src/cli/prompt-assets.ts +865 -0
  199. package/src/cli/templates/provider/.agents/skills/fixtures-and-recording/SKILL.md.tpl +58 -0
  200. package/src/cli/templates/provider/.agents/skills/health-checks-and-fail-closed/SKILL.md.tpl +65 -0
  201. package/src/cli/templates/provider/.agents/skills/normalization-standards/SKILL.md.tpl +57 -0
  202. package/src/cli/templates/provider/.agents/skills/pagination-and-counts/SKILL.md.tpl +52 -0
  203. package/src/cli/templates/provider/.agents/skills/upstream-contract-verification/SKILL.md.tpl +45 -0
  204. package/src/cli/templates/provider/.agents/skills/upstream-notes/README.md.tpl +13 -0
  205. package/src/cli/templates/provider/AGENTS.md.tpl +96 -0
  206. package/src/cli/templates/provider/README.md.tpl +7 -4
  207. package/src/config/loader.ts +543 -208
  208. package/src/contract-serialization.ts +5 -11
  209. package/src/contract-types.ts +2 -2
  210. package/src/contract.ts +13 -28
  211. package/src/define.ts +397 -528
  212. package/src/dev.ts +4 -9
  213. package/src/errors.ts +58 -5
  214. package/src/i18n/catalog.ts +10 -32
  215. package/src/i18n/index.ts +2 -2
  216. package/src/i18n/keys.ts +5 -11
  217. package/src/index.ts +64 -41
  218. package/src/lint.ts +122 -159
  219. package/src/provider.ts +40 -9
  220. package/src/public-schema-field-lint.ts +7 -33
  221. package/src/recipes/gov-api.ts +2 -5
  222. package/src/runtime/auth-flow.ts +6 -6
  223. package/src/runtime/browser.ts +320 -151
  224. package/src/runtime/cache.ts +20 -67
  225. package/src/runtime/choice.ts +79 -132
  226. package/src/runtime/credential.ts +2 -2
  227. package/src/runtime/env.ts +1 -1
  228. package/src/runtime/executor.ts +23 -20
  229. package/src/runtime/http.ts +216 -539
  230. package/src/runtime/insights.ts +15 -53
  231. package/src/runtime/instrumentation.ts +78 -65
  232. package/src/runtime/keyring.ts +7 -19
  233. package/src/runtime/namespace.ts +2 -7
  234. package/src/runtime/otlp.ts +12 -23
  235. package/src/runtime/perf.ts +1 -1
  236. package/src/runtime/provider.ts +4 -9
  237. package/src/runtime/proxy-errors.ts +29 -42
  238. package/src/runtime/proxy-nodemaven.ts +178 -0
  239. package/src/runtime/proxy-retry-policy.ts +469 -0
  240. package/src/runtime/proxy-telemetry.ts +79 -77
  241. package/src/runtime/redis.ts +4 -12
  242. package/src/runtime/request-options.ts +4 -13
  243. package/src/runtime/secrets.ts +64 -0
  244. package/src/runtime/state.ts +41 -110
  245. package/src/runtime/stealth.ts +331 -369
  246. package/src/runtime/stt.ts +38 -94
  247. package/src/runtime/trace.ts +14 -44
  248. package/src/runtime/waterfall.ts +5 -18
  249. package/src/schema.ts +23 -84
  250. package/src/serve.ts +1 -1
  251. package/src/server/index.ts +44 -3
  252. package/src/server/self-test-input-tokens.ts +46 -0
  253. package/src/server/self-test-redaction.ts +97 -0
  254. package/src/server/self-test-token.ts +70 -0
  255. package/src/server/self-test.ts +1450 -0
  256. package/src/server/serve.ts +206 -216
  257. package/src/server/types.ts +7 -19
  258. package/src/stealth/profiles.ts +10 -26
  259. package/src/stream.ts +8 -19
  260. package/src/testing/index.ts +2 -2
  261. package/src/testing/run.ts +24 -64
  262. package/src/types.ts +274 -1
  263. package/src/user-input.ts +118 -0
@@ -3,61 +3,65 @@ import { join } from "node:path";
3
3
 
4
4
  import { Hono } from "hono";
5
5
  import { z } from "zod";
6
+ import { AuthAbortError, createAuthFlowHelpers } from "../auth.js";
6
7
  import {
7
8
  AuthError,
9
+ isProviderError,
10
+ isSessionExpiredError,
11
+ isTransportError,
8
12
  ProviderError,
9
- SessionExpiredError,
10
- TransportError,
11
- } from "../errors";
13
+ } from "../errors.js";
12
14
  import {
13
15
  loadProviderLocaleCatalogs,
14
16
  localizeAuthTurn,
15
17
  type ProviderLocaleCatalogMap,
16
- } from "../i18n/catalog";
17
- import type { ProviderLocale } from "../i18n/keys";
18
+ } from "../i18n/catalog.js";
19
+ import type { ProviderLocale } from "../i18n/keys.js";
18
20
  import {
19
21
  categoryForStatus,
20
22
  isRetryableCategory,
21
23
  PROVIDER_OBSERVABILITY_TAXONOMY_VERSION,
22
24
  type ProviderErrorCategory,
23
- } from "../observability";
24
- import { createScratchpad } from "../runtime/auth-flow";
25
- import { createBrowserClient } from "../runtime/browser";
26
- import { createProviderCache } from "../runtime/cache";
25
+ } from "../observability.js";
26
+ import { createScratchpad } from "../runtime/auth-flow.js";
27
+ import { createBrowserClient } from "../runtime/browser.js";
28
+ import { createProviderCache } from "../runtime/cache.js";
27
29
  import {
28
30
  createProviderChoiceContext,
29
31
  PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV,
30
- } from "../runtime/choice";
31
- import { createCredentialContext } from "../runtime/credential";
32
- import { createEnvContext } from "../runtime/env";
33
- import { executeOperation } from "../runtime/executor";
34
- import { createHttpClient } from "../runtime/http";
35
- import { wrapWithInstrumentation } from "../runtime/instrumentation";
36
- import { getProviderBaseUrl } from "../runtime/provider";
32
+ } from "../runtime/choice.js";
33
+ import { createCredentialContext } from "../runtime/credential.js";
34
+ import { createEnvContext } from "../runtime/env.js";
35
+ import { executeOperation } from "../runtime/executor.js";
36
+ import { createHttpClient } from "../runtime/http.js";
37
+ import { wrapWithInstrumentation } from "../runtime/instrumentation.js";
38
+ import { getProviderBaseUrl } from "../runtime/provider.js";
37
39
  import {
38
40
  PROXY_AUTH_IP_DENIED_CODE,
39
41
  PROXY_EDGE_AUTH_REJECTED_CODE,
40
42
  PROXY_POOL_EXHAUSTED_CODE,
41
- } from "../runtime/proxy-errors";
43
+ } from "../runtime/proxy-errors.js";
44
+ import { PROVIDER_TELEMETRY_HEADER, ProxyTelemetryCollector } from "../runtime/proxy-telemetry.js";
42
45
  import {
43
- PROVIDER_TELEMETRY_HEADER,
44
- ProxyTelemetryCollector,
45
- } from "../runtime/proxy-telemetry";
46
+ assertRequiredSecretsPresent,
47
+ listMissingRequiredSecrets,
48
+ MISSING_SECRET_CODE,
49
+ } from "../runtime/secrets.js";
46
50
  import {
47
51
  createProviderRuntimeStateFromEnv,
48
52
  createUnsupportedProviderRuntimeState,
49
- } from "../runtime/state";
50
- import { createStealthClient } from "../runtime/stealth";
51
- import { createSttClientFromEnv } from "../runtime/stt";
52
- import { createTraceContext } from "../runtime/trace";
53
- import { parseSchema } from "../schema";
54
- import { getStealthProfile } from "../stealth/profiles";
53
+ } from "../runtime/state.js";
54
+ import { createStealthClient } from "../runtime/stealth.js";
55
+ import { createSttClientFromEnv } from "../runtime/stt.js";
56
+ import { createTraceContext } from "../runtime/trace.js";
57
+ import { parseSchema } from "../schema.js";
58
+ import { getStealthProfile } from "../stealth/profiles.js";
55
59
  import {
56
60
  APIFUSE_STREAM_DONE_EVENT,
57
61
  APIFUSE_STREAM_ERROR_EVENT,
58
62
  encodeSseEvent,
59
63
  error as streamError,
60
- } from "../stream";
64
+ } from "../stream.js";
61
65
  import type {
62
66
  AuthContext,
63
67
  AuthTurn,
@@ -74,7 +78,14 @@ import type {
74
78
  ProviderStreamEvent,
75
79
  StealthClient,
76
80
  SttContext,
77
- } from "../types";
81
+ } from "../types.js";
82
+ import {
83
+ createSelfTestApp,
84
+ createSelfTestAuthFlowInvoke,
85
+ createSelfTestInvoke,
86
+ resolveSelfTestPort,
87
+ } from "./self-test.js";
88
+ import { resolveSelfTestMasterSecrets } from "./self-test-token.js";
78
89
  import {
79
90
  type AuthFlowRequest,
80
91
  AuthFlowRequestSchema,
@@ -85,7 +96,7 @@ import {
85
96
  OperationRequestSchema,
86
97
  type OperationResponse,
87
98
  type OperationSuccessResponse,
88
- } from "./types";
99
+ } from "./types.js";
89
100
 
90
101
  const DEFAULT_HOST = "0.0.0.0";
91
102
  const DEFAULT_PORT = 3000;
@@ -149,9 +160,7 @@ function createStealthStub(): StealthClient {
149
160
  };
150
161
  }
151
162
 
152
- function getProviderStealthBaseUrl(
153
- provider: ProviderDefinition,
154
- ): string | undefined {
163
+ function getProviderStealthBaseUrl(provider: ProviderDefinition): string | undefined {
155
164
  const baseUrl = getProviderBaseUrl(provider);
156
165
  if (baseUrl) {
157
166
  return baseUrl;
@@ -161,15 +170,10 @@ function getProviderStealthBaseUrl(
161
170
  }
162
171
 
163
172
  function getProviderStealthProfile(provider: ProviderDefinition) {
164
- return provider.stealth?.profile
165
- ? getStealthProfile(provider.stealth.profile)
166
- : undefined;
173
+ return provider.stealth?.profile ? getStealthProfile(provider.stealth.profile) : undefined;
167
174
  }
168
175
 
169
- function isProductionProviderBrowserMode(
170
- provider: ProviderDefinition,
171
- env = process.env,
172
- ): boolean {
176
+ function isProductionProviderBrowserMode(provider: ProviderDefinition, env = process.env): boolean {
173
177
  if (provider.runtime !== "browser") {
174
178
  return false;
175
179
  }
@@ -178,9 +182,7 @@ function isProductionProviderBrowserMode(
178
182
  return true;
179
183
  }
180
184
 
181
- return (
182
- env.NODE_ENV === "production" && env.APIFUSE__PROVIDER__ID === provider.id
183
- );
185
+ return env.NODE_ENV === "production" && env.APIFUSE__PROVIDER__ID === provider.id;
184
186
  }
185
187
 
186
188
  export function resolveProviderProxyAffinityKey(
@@ -188,18 +190,19 @@ export function resolveProviderProxyAffinityKey(
188
190
  request: OperationRequest,
189
191
  operationId: string,
190
192
  ): string {
191
- const connectionKey =
192
- request.connection?.id ?? request.connection?.externalRef;
193
+ const connectionKey = resolveOperationConnectionId(request) ?? request.connection?.externalRef;
193
194
  const affinity =
194
- typeof provider.proxy === "object"
195
- ? provider.proxy.session?.affinity
196
- : undefined;
195
+ typeof provider.proxy === "object" ? provider.proxy.session?.affinity : undefined;
197
196
  if (affinity === "operation") {
198
197
  return `${provider.id}/${operationId}`;
199
198
  }
200
199
  return connectionKey ?? provider.id;
201
200
  }
202
201
 
202
+ function resolveOperationConnectionId(request: OperationRequest): string | undefined {
203
+ return request.connection?.id ?? request.connectionId;
204
+ }
205
+
203
206
  function createProviderContext(
204
207
  provider: ProviderDefinition,
205
208
  request: OperationRequest,
@@ -213,11 +216,7 @@ function createProviderContext(
213
216
  const stealthProfile = getProviderStealthProfile(provider);
214
217
  const proxyClientOptions = {
215
218
  upstream: { proxy: provider.proxy },
216
- affinityKey: resolveProviderProxyAffinityKey(
217
- provider,
218
- request,
219
- operationId,
220
- ),
219
+ affinityKey: resolveProviderProxyAffinityKey(provider, request, operationId),
221
220
  telemetry: proxyTelemetry,
222
221
  };
223
222
  let wrappedContext: ProviderContext | undefined;
@@ -238,7 +237,7 @@ function createProviderContext(
238
237
  values: request.connection?.secrets,
239
238
  });
240
239
  const requestContext = {
241
- connectionId: request.connection?.id,
240
+ connectionId: resolveOperationConnectionId(request),
242
241
  headers: request.headers ?? {},
243
242
  };
244
243
  const context = wrapWithInstrumentation({
@@ -256,11 +255,7 @@ function createProviderContext(
256
255
  state,
257
256
  stealth: stealthBaseUrl
258
257
  ? stealthProfile
259
- ? createStealthClient(
260
- stealthBaseUrl,
261
- stealthProfile.name,
262
- stealthClientOptions,
263
- )
258
+ ? createStealthClient(stealthBaseUrl, stealthProfile.name, stealthClientOptions)
264
259
  : createStealthClient(stealthBaseUrl, stealthClientOptions)
265
260
  : createStealthStub(),
266
261
  browser:
@@ -329,6 +324,7 @@ function createAuthFlowContext(
329
324
  provider: ProviderDefinition,
330
325
  request: AuthFlowRequest,
331
326
  options: ProviderServerOptions = {},
327
+ signal?: AbortSignal,
332
328
  ): {
333
329
  context: FlowContext;
334
330
  getPatch: () => Record<string, unknown | null> | undefined;
@@ -372,17 +368,14 @@ function createAuthFlowContext(
372
368
  http: createHttpClient(baseUrl, proxyClientOptions),
373
369
  stealth: stealthBaseUrl
374
370
  ? stealthProfile
375
- ? createStealthClient(
376
- stealthBaseUrl,
377
- stealthProfile.name,
378
- stealthClientOptions,
379
- )
371
+ ? createStealthClient(stealthBaseUrl, stealthProfile.name, stealthClientOptions)
380
372
  : createStealthClient(stealthBaseUrl, stealthClientOptions)
381
373
  : createStealthStub(),
382
374
  env: createEnvContext(provider.secrets?.map((secret) => secret.name)),
383
375
  credential,
384
376
  context: flowContextStore.context,
385
377
  stt: options.stt ?? createSttClientFromEnv(provider.stt),
378
+ auth: createAuthFlowHelpers({ signal }),
386
379
  },
387
380
  getPatch: flowContextStore.getPatch,
388
381
  };
@@ -420,6 +413,12 @@ export type ProviderServerLogEvent =
420
413
  retryable?: boolean;
421
414
  issues?: Array<{ path: string; code: string; message: string }>;
422
415
  })
416
+ | {
417
+ level: "warn";
418
+ event: "provider_secrets_missing";
419
+ providerId: string;
420
+ missingSecrets: string[];
421
+ }
423
422
  | {
424
423
  level: "warn";
425
424
  event: "provider_cleanup_failed";
@@ -488,11 +487,8 @@ function zodDetails(error: z.ZodError): Array<{
488
487
  }));
489
488
  }
490
489
 
491
- function toErrorResponse(
492
- error: unknown,
493
- requestId?: string,
494
- ): OperationErrorResponse {
495
- if (error instanceof ProviderError) {
490
+ function toErrorResponse(error: unknown, requestId?: string): OperationErrorResponse {
491
+ if (isProviderError(error)) {
496
492
  const details = publicProviderErrorDetails(error);
497
493
  return {
498
494
  error: {
@@ -516,11 +512,22 @@ function toErrorResponse(
516
512
  };
517
513
  }
518
514
 
515
+ // A masked internal error MUST NOT be advertised as retryable: without an
516
+ // explicit retryable:false the hub (bori provider-backed engine) defaults 5xx
517
+ // to retryable:true, which turns a deterministic pre-upstream crash into an
518
+ // infinite START->CONTINUE->restart loop (2026-07-22 catchtable reserve RCA).
519
+ // We still refuse to leak message/stack — only the error class name (or the
520
+ // primitive type for non-Error throwables) is surfaced for ops triage.
519
521
  return {
520
522
  error: {
521
523
  code: "internal_error",
522
524
  message: "Internal error",
523
525
  ...(requestId ? { requestId } : {}),
526
+ details: {
527
+ retryable: false,
528
+ category: "internal_error",
529
+ errorClass: error instanceof Error ? error.name : typeof error,
530
+ },
524
531
  },
525
532
  };
526
533
  }
@@ -548,7 +555,12 @@ function isPlainRecord(value: unknown): value is Record<string, unknown> {
548
555
  return value !== null && typeof value === "object" && !Array.isArray(value);
549
556
  }
550
557
 
551
- function providerObservabilityDetails(error: ProviderError):
558
+ // Accepts `unknown` so the branded guards narrow cleanly from the top: the
559
+ // subtype error classes are structurally compatible with ProviderError, so
560
+ // narrowing from a ProviderError-typed value would collapse the negative branch
561
+ // to `never`. Narrowing from unknown avoids that while still recognizing errors
562
+ // from a duplicate SDK module instance.
563
+ function providerObservabilityDetails(error: unknown):
552
564
  | {
553
565
  category: ProviderErrorCategory;
554
566
  taxonomyVersion: string;
@@ -561,14 +573,26 @@ function providerObservabilityDetails(error: ProviderError):
561
573
  // operation (see design.md §4.3 D3). Without this branch the auth error would
562
574
  // serialize as a bare 401 with no retryable/category, losing the refresh
563
575
  // signal for exactly the retryOnAuthRefresh operations it is meant to enable.
564
- if (error instanceof SessionExpiredError) {
576
+ if (isSessionExpiredError(error)) {
565
577
  return {
566
578
  category: error.options?.category ?? "credential_expired",
567
579
  taxonomyVersion: PROVIDER_OBSERVABILITY_TAXONOMY_VERSION,
568
580
  retryable: error.options?.retryable ?? false,
569
581
  };
570
582
  }
571
- if (!(error instanceof TransportError)) {
583
+ // Missing-secret errors carry the canonical credential_unavailable category
584
+ // so Gateway/observability can attribute the failure to provisioning, not
585
+ // the upstream. Matched by code (not constructor) so both the SDK-owned
586
+ // runtime gate and any not-yet-migrated provider-thrown MISSING_SECRET
587
+ // serialize identically, including across duplicate SDK module instances.
588
+ if (isProviderError(error) && error.code === MISSING_SECRET_CODE) {
589
+ return {
590
+ category: error.options?.category ?? "credential_unavailable",
591
+ taxonomyVersion: PROVIDER_OBSERVABILITY_TAXONOMY_VERSION,
592
+ retryable: error.options?.retryable ?? false,
593
+ };
594
+ }
595
+ if (!isTransportError(error)) {
572
596
  return undefined;
573
597
  }
574
598
  const isProxyPoolCode =
@@ -601,7 +625,7 @@ function providerObservabilityDetails(error: ProviderError):
601
625
  }
602
626
 
603
627
  function publicProviderErrorMessage(error: ProviderError): string {
604
- if (error instanceof TransportError) {
628
+ if (isTransportError(error)) {
605
629
  if (error.code === PROXY_AUTH_IP_DENIED_CODE) {
606
630
  return error.message;
607
631
  }
@@ -624,22 +648,24 @@ function publicProviderErrorMessage(error: ProviderError): string {
624
648
  return error.message;
625
649
  }
626
650
 
627
- function toStatusCode(
628
- error: unknown,
629
- ): 400 | 401 | 404 | 429 | 500 | 502 | 503 | 504 {
651
+ function toStatusCode(error: unknown): 400 | 401 | 404 | 429 | 500 | 502 | 503 | 504 {
630
652
  if (error instanceof z.ZodError) {
631
653
  return 400;
632
654
  }
633
655
 
634
- if (error instanceof TransportError) {
656
+ if (isTransportError(error)) {
635
657
  return error.code === "transport_timeout" ? 504 : 502;
636
658
  }
637
659
 
638
- if (error instanceof ProviderError) {
660
+ if (isProviderError(error)) {
639
661
  switch (error.code) {
640
662
  case "AUTH_REQUIRED":
641
663
  case "reauth_required":
642
664
  return 401;
665
+ // Unprovisioned declared secret: a deployment/config defect, never an
666
+ // upstream failure — explicit 400 (was only reached via fallthrough).
667
+ case MISSING_SECRET_CODE:
668
+ return 400;
643
669
  case "NOT_FOUND":
644
670
  case "not_found":
645
671
  case "NO_DATA":
@@ -681,20 +707,15 @@ function logProviderError(
681
707
  status: number,
682
708
  cost: ProviderRequestCost,
683
709
  ): void {
684
- const code =
685
- error instanceof ProviderError
686
- ? (error.code ?? "provider_error")
687
- : error instanceof z.ZodError
688
- ? "invalid_request"
689
- : "internal_error";
710
+ const code = isProviderError(error)
711
+ ? (error.code ?? "provider_error")
712
+ : error instanceof z.ZodError
713
+ ? "invalid_request"
714
+ : "internal_error";
690
715
  const errorClass = error instanceof Error ? error.name : typeof error;
691
716
  const message = error instanceof Error ? error.message : String(error);
692
- const details =
693
- error instanceof ProviderError
694
- ? providerObservabilityDetails(error)
695
- : undefined;
696
- const emit =
697
- typeof logger === "function" ? logger : defaultProviderServerLogger;
717
+ const details = isProviderError(error) ? providerObservabilityDetails(error) : undefined;
718
+ const emit = typeof logger === "function" ? logger : defaultProviderServerLogger;
698
719
  emit({
699
720
  level: status >= 500 ? "error" : "warn",
700
721
  event: "provider_request_failed",
@@ -707,7 +728,7 @@ function logProviderError(
707
728
  code,
708
729
  errorClass,
709
730
  message,
710
- ...(error instanceof TransportError && error.upstreamStatus
731
+ ...(isTransportError(error) && error.upstreamStatus
711
732
  ? { upstreamStatus: error.upstreamStatus }
712
733
  : {}),
713
734
  ...(details
@@ -729,8 +750,7 @@ function logProviderCleanupError(
729
750
  resource: "browser" | "stealth",
730
751
  error: unknown,
731
752
  ): void {
732
- const emit =
733
- typeof logger === "function" ? logger : defaultProviderServerLogger;
753
+ const emit = typeof logger === "function" ? logger : defaultProviderServerLogger;
734
754
  const errorClass = error instanceof Error ? error.name : typeof error;
735
755
  const message = error instanceof Error ? error.message : String(error);
736
756
  emit({
@@ -755,8 +775,7 @@ function logProviderSuccess(
755
775
  status: number,
756
776
  cost: ProviderRequestCost,
757
777
  ): void {
758
- const emit =
759
- typeof logger === "function" ? logger : defaultProviderServerLogger;
778
+ const emit = typeof logger === "function" ? logger : defaultProviderServerLogger;
760
779
  emit({
761
780
  level: "info",
762
781
  event: "provider_request_completed",
@@ -802,18 +821,13 @@ function toJsonSuccessResponse(
802
821
  };
803
822
  }
804
823
 
805
- function isAsyncIterable<T = unknown>(
806
- value: unknown,
807
- ): value is AsyncIterable<T> {
824
+ function isAsyncIterable<T = unknown>(value: unknown): value is AsyncIterable<T> {
808
825
  if (!value || typeof value !== "object") return false;
809
826
  const iterator = Reflect.get(value, Symbol.asyncIterator);
810
827
  return typeof iterator === "function";
811
828
  }
812
829
 
813
- function responseWithCleanup(
814
- response: Response,
815
- cleanup: RequestCleanup,
816
- ): Response {
830
+ function responseWithCleanup(response: Response, cleanup: RequestCleanup): Response {
817
831
  if (!response.body) {
818
832
  void cleanup();
819
833
  return response;
@@ -862,10 +876,7 @@ async function validateSseEvent(
862
876
  const transport = getSseTransport(operation);
863
877
  const schema = transport?.events?.[event.event];
864
878
  if (!schema) {
865
- if (
866
- event.event === APIFUSE_STREAM_ERROR_EVENT ||
867
- event.event === APIFUSE_STREAM_DONE_EVENT
868
- ) {
879
+ if (event.event === APIFUSE_STREAM_ERROR_EVENT || event.event === APIFUSE_STREAM_DONE_EVENT) {
869
880
  return event;
870
881
  }
871
882
  throw new ProviderError(
@@ -878,11 +889,7 @@ async function validateSseEvent(
878
889
  },
879
890
  );
880
891
  }
881
- const data = await parseSchema(
882
- schema,
883
- event.data,
884
- `transport.events.${event.event}`,
885
- );
892
+ const data = await parseSchema(schema, event.data, `transport.events.${event.event}`);
886
893
  return { ...event, data };
887
894
  }
888
895
 
@@ -902,8 +909,7 @@ function assertStreamPayloadWithinLimit(
902
909
  throw new ProviderError(
903
910
  `Stream ${kind} exceeded declared byte limit (${actualBytes} > ${maxBytes}).`,
904
911
  {
905
- code:
906
- kind === "event" ? "STREAM_EVENT_TOO_LARGE" : "STREAM_CHUNK_TOO_LARGE",
912
+ code: kind === "event" ? "STREAM_EVENT_TOO_LARGE" : "STREAM_CHUNK_TOO_LARGE",
907
913
  retryable: false,
908
914
  category: "input_validation",
909
915
  fix:
@@ -948,15 +954,10 @@ function toSseResponse(
948
954
  const validated = await validateSseEvent(operation, next.value);
949
955
  const encodedEvent = encodeSseEvent(validated);
950
956
  const encodedBytes = encoder.encode(encodedEvent);
951
- assertStreamPayloadWithinLimit(
952
- encodedBytes.byteLength,
953
- transport?.maxEventBytes,
954
- "event",
955
- );
957
+ assertStreamPayloadWithinLimit(encodedBytes.byteLength, transport?.maxEventBytes, "event");
956
958
  controller.enqueue(encodedBytes);
957
959
  } catch (error) {
958
- const message =
959
- error instanceof Error ? error.message : "Stream failed";
960
+ const message = error instanceof Error ? error.message : "Stream failed";
960
961
  controller.enqueue(
961
962
  encoder.encode(
962
963
  encodeSseEvent(
@@ -1003,11 +1004,7 @@ function enforceStreamChunkLimit(
1003
1004
  return;
1004
1005
  }
1005
1006
  if (value) {
1006
- assertStreamPayloadWithinLimit(
1007
- byteLength(value),
1008
- maxChunkBytes,
1009
- "chunk",
1010
- );
1007
+ assertStreamPayloadWithinLimit(byteLength(value), maxChunkBytes, "chunk");
1011
1008
  controller.enqueue(value);
1012
1009
  }
1013
1010
  } catch (error) {
@@ -1027,10 +1024,7 @@ function toStreamingResponse(
1027
1024
  requestId?: string,
1028
1025
  ): Response {
1029
1026
  const transport = operation.transport?.kind ?? "json";
1030
- if (
1031
- transport === "sse" &&
1032
- (result instanceof Response || result instanceof ReadableStream)
1033
- ) {
1027
+ if (transport === "sse" && (result instanceof Response || result instanceof ReadableStream)) {
1034
1028
  void cleanup();
1035
1029
  throw new ProviderError(
1036
1030
  "SSE operations must return an AsyncIterable of typed stream.event(...) values.",
@@ -1044,20 +1038,13 @@ function toStreamingResponse(
1044
1038
  }
1045
1039
  if (result instanceof Response) {
1046
1040
  const httpTransport = getHttpStreamTransport(operation);
1047
- if (
1048
- httpTransport &&
1049
- result.body &&
1050
- httpTransport?.maxChunkBytes !== undefined
1051
- ) {
1041
+ if (httpTransport && result.body && httpTransport?.maxChunkBytes !== undefined) {
1052
1042
  return responseWithCleanup(
1053
- new Response(
1054
- enforceStreamChunkLimit(result.body, httpTransport.maxChunkBytes),
1055
- {
1056
- headers: result.headers,
1057
- status: result.status,
1058
- statusText: result.statusText,
1059
- },
1060
- ),
1043
+ new Response(enforceStreamChunkLimit(result.body, httpTransport.maxChunkBytes), {
1044
+ headers: result.headers,
1045
+ status: result.status,
1046
+ statusText: result.statusText,
1047
+ }),
1061
1048
  cleanup,
1062
1049
  );
1063
1050
  }
@@ -1077,8 +1064,7 @@ function toStreamingResponse(
1077
1064
  : {
1078
1065
  "Content-Type":
1079
1066
  operation.transport?.kind === "http-stream"
1080
- ? (operation.transport.contentType ??
1081
- "application/octet-stream")
1067
+ ? (operation.transport.contentType ?? "application/octet-stream")
1082
1068
  : "application/octet-stream",
1083
1069
  },
1084
1070
  }),
@@ -1098,18 +1084,14 @@ function toStreamingResponse(
1098
1084
  );
1099
1085
  }
1100
1086
 
1101
- function getSseTransport(
1102
- operation: OperationDefinition,
1103
- ): OperationSseTransport | undefined {
1087
+ function getSseTransport(operation: OperationDefinition): OperationSseTransport | undefined {
1104
1088
  return operation.transport?.kind === "sse" ? operation.transport : undefined;
1105
1089
  }
1106
1090
 
1107
1091
  function getHttpStreamTransport(
1108
1092
  operation: OperationDefinition,
1109
1093
  ): OperationHttpStreamTransport | undefined {
1110
- return operation.transport?.kind === "http-stream"
1111
- ? operation.transport
1112
- : undefined;
1094
+ return operation.transport?.kind === "http-stream" ? operation.transport : undefined;
1113
1095
  }
1114
1096
 
1115
1097
  function toAuthFlowResponse(
@@ -1130,9 +1112,7 @@ function toAuthFlowResponse(
1130
1112
  };
1131
1113
  }
1132
1114
 
1133
- function authFlowLocaleFromHeaders(
1134
- headers?: Record<string, string>,
1135
- ): ProviderLocale {
1115
+ function authFlowLocaleFromHeaders(headers?: Record<string, string>): ProviderLocale {
1136
1116
  const header = Object.entries(headers ?? {}).find(
1137
1117
  ([key]) => key.toLowerCase() === "accept-language",
1138
1118
  )?.[1];
@@ -1150,9 +1130,7 @@ function isAuthFlowLocale(value: string | undefined): value is ProviderLocale {
1150
1130
  }
1151
1131
 
1152
1132
  function isAuthTurn(value: unknown): value is AuthTurn {
1153
- return (
1154
- !!value && typeof value === "object" && "kind" in value && "turnId" in value
1155
- );
1133
+ return !!value && typeof value === "object" && "kind" in value && "turnId" in value;
1156
1134
  }
1157
1135
 
1158
1136
  function loadAuthFlowLocaleCatalogs(
@@ -1189,10 +1167,7 @@ function materializeAuthFlowTurn(
1189
1167
  });
1190
1168
  }
1191
1169
 
1192
- function withAuthRequestHeaders(
1193
- request: AuthFlowRequest,
1194
- headers: Headers,
1195
- ): AuthFlowRequest {
1170
+ function withAuthRequestHeaders(request: AuthFlowRequest, headers: Headers): AuthFlowRequest {
1196
1171
  return {
1197
1172
  ...request,
1198
1173
  headers: {
@@ -1210,17 +1185,9 @@ async function handleOperation(
1210
1185
  state: ProviderRuntimeState = createUnsupportedProviderRuntimeState(),
1211
1186
  proxyTelemetry?: ProxyTelemetryCollector,
1212
1187
  ): Promise<Response | OperationResponse> {
1213
- const ctx = createProviderContext(
1214
- provider,
1215
- request,
1216
- operationId,
1217
- options,
1218
- state,
1219
- proxyTelemetry,
1220
- );
1188
+ const ctx = createProviderContext(provider, request, operationId, options, state, proxyTelemetry);
1221
1189
  const operation = provider.operations[operationId];
1222
- const streaming =
1223
- operation?.transport?.kind && operation.transport.kind !== "json";
1190
+ const streaming = operation?.transport?.kind && operation.transport.kind !== "json";
1224
1191
  let cleanupCalled = false;
1225
1192
  const cleanup = async () => {
1226
1193
  if (cleanupCalled) return;
@@ -1251,12 +1218,7 @@ async function handleOperation(
1251
1218
  }
1252
1219
  };
1253
1220
  try {
1254
- const result = await executeOperation(
1255
- provider,
1256
- operationId,
1257
- ctx,
1258
- request.input,
1259
- );
1221
+ const result = await executeOperation(provider, operationId, ctx, request.input);
1260
1222
  if (streaming && operation) {
1261
1223
  return toStreamingResponse(operation, result, cleanup, request.requestId);
1262
1224
  }
@@ -1291,6 +1253,7 @@ async function handleAuthFlow(
1291
1253
  request: AuthFlowRequest,
1292
1254
  route: AuthRoute,
1293
1255
  options: ProviderServerOptions = {},
1256
+ signal?: AbortSignal,
1294
1257
  ): Promise<Response | AuthFlowResponse> {
1295
1258
  const flow = provider.auth?.flow;
1296
1259
  if (!flow) {
@@ -1299,12 +1262,16 @@ async function handleAuthFlow(
1299
1262
  });
1300
1263
  }
1301
1264
 
1302
- const { context, getPatch } = createAuthFlowContext(
1303
- provider,
1304
- request,
1305
- options,
1306
- );
1265
+ // Same SDK-owned gate as executeOperation: OAuth/credentials ceremonies
1266
+ // depend on declared secrets (client ids/secrets), so fail structured before
1267
+ // any flow code runs instead of at whatever point the ceremony first reads
1268
+ // the env. `abort` stays exempt: a user must always be able to cancel a
1269
+ // stranded flow even when provisioning is broken.
1270
+ const { context, getPatch } = createAuthFlowContext(provider, request, options, signal);
1307
1271
  try {
1272
+ if (route !== "abort") {
1273
+ assertRequiredSecretsPresent(provider, context.env);
1274
+ }
1308
1275
  const result =
1309
1276
  route === "start"
1310
1277
  ? await flow.start(context)
@@ -1336,6 +1303,11 @@ async function handleAuthFlow(
1336
1303
  ? materializeAuthFlowTurn(provider, request, result)
1337
1304
  : result;
1338
1305
  return toAuthFlowResponse(materializedResult, getPatch());
1306
+ } catch (error) {
1307
+ if (error instanceof AuthAbortError) {
1308
+ return toAuthFlowResponse(error.turn, getPatch());
1309
+ }
1310
+ throw error;
1339
1311
  } finally {
1340
1312
  context.stealth.close?.();
1341
1313
  }
@@ -1354,6 +1326,24 @@ export function createServerApp(
1354
1326
  allowMemoryFallback: options.allowMemoryStateFallback === true,
1355
1327
  });
1356
1328
 
1329
+ // Boot-time visibility for unprovisioned declared secrets: emit a structured
1330
+ // warn so deploy tooling/alerting sees the gap the moment the pod boots,
1331
+ // instead of discovering it request-by-request. Deliberately log-only — a
1332
+ // boot crash would trade a structured MISSING_SECRET signal for
1333
+ // CrashLoopBackOff. Requests still fail closed via the executeOperation gate.
1334
+ const missingSecretsAtBoot = listMissingRequiredSecrets(
1335
+ provider,
1336
+ createEnvContext(provider.secrets?.map((secret) => secret.name)),
1337
+ );
1338
+ if (missingSecretsAtBoot.length > 0) {
1339
+ logger({
1340
+ level: "warn",
1341
+ event: "provider_secrets_missing",
1342
+ providerId: provider.id,
1343
+ missingSecrets: missingSecretsAtBoot,
1344
+ });
1345
+ }
1346
+
1357
1347
  app.notFound((c) =>
1358
1348
  c.json(
1359
1349
  {
@@ -1446,11 +1436,8 @@ export function createServerApp(
1446
1436
  .clone()
1447
1437
  .json()
1448
1438
  .catch(() => undefined);
1449
- const body = withAuthRequestHeaders(
1450
- AuthFlowRequestSchema.parse(rawBody),
1451
- c.req.raw.headers,
1452
- );
1453
- const response = await handleAuthFlow(provider, body, "start", options);
1439
+ const body = withAuthRequestHeaders(AuthFlowRequestSchema.parse(rawBody), c.req.raw.headers);
1440
+ const response = await handleAuthFlow(provider, body, "start", options, c.req.raw.signal);
1454
1441
  logProviderSuccess(
1455
1442
  logger,
1456
1443
  provider,
@@ -1486,16 +1473,8 @@ export function createServerApp(
1486
1473
  .clone()
1487
1474
  .json()
1488
1475
  .catch(() => undefined);
1489
- const body = withAuthRequestHeaders(
1490
- AuthFlowRequestSchema.parse(rawBody),
1491
- c.req.raw.headers,
1492
- );
1493
- const response = await handleAuthFlow(
1494
- provider,
1495
- body,
1496
- "continue",
1497
- options,
1498
- );
1476
+ const body = withAuthRequestHeaders(AuthFlowRequestSchema.parse(rawBody), c.req.raw.headers);
1477
+ const response = await handleAuthFlow(provider, body, "continue", options, c.req.raw.signal);
1499
1478
  logProviderSuccess(
1500
1479
  logger,
1501
1480
  provider,
@@ -1531,11 +1510,8 @@ export function createServerApp(
1531
1510
  .clone()
1532
1511
  .json()
1533
1512
  .catch(() => undefined);
1534
- const body = withAuthRequestHeaders(
1535
- AuthFlowRequestSchema.parse(rawBody),
1536
- c.req.raw.headers,
1537
- );
1538
- const response = await handleAuthFlow(provider, body, "poll", options);
1513
+ const body = withAuthRequestHeaders(AuthFlowRequestSchema.parse(rawBody), c.req.raw.headers);
1514
+ const response = await handleAuthFlow(provider, body, "poll", options, c.req.raw.signal);
1539
1515
  logProviderSuccess(
1540
1516
  logger,
1541
1517
  provider,
@@ -1571,11 +1547,8 @@ export function createServerApp(
1571
1547
  .clone()
1572
1548
  .json()
1573
1549
  .catch(() => undefined);
1574
- const body = withAuthRequestHeaders(
1575
- AuthFlowRequestSchema.parse(rawBody),
1576
- c.req.raw.headers,
1577
- );
1578
- const response = await handleAuthFlow(provider, body, "refresh", options);
1550
+ const body = withAuthRequestHeaders(AuthFlowRequestSchema.parse(rawBody), c.req.raw.headers);
1551
+ const response = await handleAuthFlow(provider, body, "refresh", options, c.req.raw.signal);
1579
1552
  logProviderSuccess(
1580
1553
  logger,
1581
1554
  provider,
@@ -1611,11 +1584,8 @@ export function createServerApp(
1611
1584
  .clone()
1612
1585
  .json()
1613
1586
  .catch(() => undefined);
1614
- const body = withAuthRequestHeaders(
1615
- AuthFlowRequestSchema.parse(rawBody),
1616
- c.req.raw.headers,
1617
- );
1618
- const response = await handleAuthFlow(provider, body, "abort", options);
1587
+ const body = withAuthRequestHeaders(AuthFlowRequestSchema.parse(rawBody), c.req.raw.headers);
1588
+ const response = await handleAuthFlow(provider, body, "abort", options, c.req.raw.signal);
1619
1589
  logProviderSuccess(
1620
1590
  logger,
1621
1591
  provider,
@@ -1675,6 +1645,12 @@ function getBunServeRuntime(): BunServeRuntime | undefined {
1675
1645
  export interface ServeOptions extends ProviderServerOptions {
1676
1646
  host?: string;
1677
1647
  port?: number;
1648
+ /**
1649
+ * Port for the internal self-test listener (default 3001 or
1650
+ * APIFUSE__PROVIDER_RUNTIME__SELF_TEST_PORT). The listener only starts
1651
+ * when APIFUSE__PROVIDER_RUNTIME__SELF_TEST_MASTER_SECRET is present.
1652
+ */
1653
+ selfTestPort?: number;
1678
1654
  }
1679
1655
 
1680
1656
  export async function serve(
@@ -1684,12 +1660,9 @@ export async function serve(
1684
1660
  const bunRuntime = getBunServeRuntime();
1685
1661
 
1686
1662
  if (bunRuntime === undefined) {
1687
- throw new ProviderError(
1688
- "Bun runtime is required to start the provider server",
1689
- {
1690
- code: "RUNTIME_UNSUPPORTED",
1691
- },
1692
- );
1663
+ throw new ProviderError("Bun runtime is required to start the provider server", {
1664
+ code: "RUNTIME_UNSUPPORTED",
1665
+ });
1693
1666
  }
1694
1667
 
1695
1668
  const app = createServerApp(provider, {
@@ -1702,5 +1675,22 @@ export async function serve(
1702
1675
  hostname: options.host ?? DEFAULT_HOST,
1703
1676
  fetch: app.fetch,
1704
1677
  });
1678
+
1679
+ // Internal self-test listener (health dependency inversion): a SEPARATE
1680
+ // socket the tenant-facing gateway never dials. Off by default — it only
1681
+ // starts when the shared self-test master secret env is present.
1682
+ const selfTestSecrets = resolveSelfTestMasterSecrets();
1683
+ if (selfTestSecrets) {
1684
+ const selfTestApp = createSelfTestApp(provider, {
1685
+ secrets: selfTestSecrets,
1686
+ invoke: createSelfTestInvoke(app),
1687
+ authFlow: createSelfTestAuthFlowInvoke(app),
1688
+ });
1689
+ bunRuntime.serve({
1690
+ port: options.selfTestPort ?? resolveSelfTestPort(),
1691
+ hostname: options.host ?? DEFAULT_HOST,
1692
+ fetch: selfTestApp.fetch,
1693
+ });
1694
+ }
1705
1695
  await Promise.resolve();
1706
1696
  }