@apifuse/provider-sdk 2.2.0-beta.7 → 2.2.0-beta.70

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 (478) hide show
  1. package/AUTHORING.md +943 -51
  2. package/CHANGELOG.md +273 -1
  3. package/LICENSE +21 -0
  4. package/README.md +117 -29
  5. package/SUBMISSION.md +3 -2
  6. package/bin/apifuse-check.ts +293 -13
  7. package/bin/apifuse-dev.ts +42 -19
  8. package/bin/apifuse-migrate-deployment.ts +200 -0
  9. package/bin/apifuse-migrate-operation-declaration.ts +55 -0
  10. package/bin/apifuse-migrate-shape.ts +202 -0
  11. package/bin/apifuse-pack-check.ts +19 -6
  12. package/bin/apifuse-pack-smoke.ts +57 -2
  13. package/bin/apifuse-pack-types.ts +736 -38
  14. package/bin/apifuse-perf.ts +59 -23
  15. package/bin/apifuse-record.ts +923 -94
  16. package/bin/apifuse-submit-check.ts +2313 -281
  17. package/bin/apifuse-test.ts +3 -1
  18. package/bin/submit-check-delimited-text.ts +50 -0
  19. package/dist/auth-turn/index.d.ts +3 -3
  20. package/dist/auth-turn/index.js +1 -1
  21. package/dist/auth.d.ts +14 -0
  22. package/dist/auth.js +67 -0
  23. package/dist/ceremonies/index.d.ts +16 -0
  24. package/dist/ceremonies/index.js +141 -36
  25. package/dist/cli/commands.d.ts +1 -1
  26. package/dist/cli/commands.js +30 -0
  27. package/dist/cli/create.d.ts +1 -1
  28. package/dist/cli/create.js +8 -2
  29. package/dist/cli/deployment-intent.d.ts +178 -0
  30. package/dist/cli/deployment-intent.js +277 -0
  31. package/dist/cli/local-connection.d.ts +111 -0
  32. package/dist/cli/local-connection.js +151 -0
  33. package/dist/cli/migrate-deployment-intent.d.ts +132 -0
  34. package/dist/cli/migrate-deployment-intent.js +1552 -0
  35. package/dist/cli/migrate-operation-declaration.d.ts +70 -0
  36. package/dist/cli/migrate-operation-declaration.js +2206 -0
  37. package/dist/cli/migrate-operation-shape.d.ts +44 -0
  38. package/dist/cli/migrate-operation-shape.js +113 -0
  39. package/dist/cli/migrate-provider-shape.d.ts +56 -0
  40. package/dist/cli/migrate-provider-shape.js +580 -0
  41. package/dist/cli/resolver-runtime.d.ts +21 -0
  42. package/dist/cli/resolver-runtime.js +31 -0
  43. package/dist/cli/templates/provider/.agents/skills/health-checks-and-fail-closed/SKILL.md.tpl +141 -23
  44. package/dist/cli/templates/provider/Dockerfile.tpl +1 -1
  45. package/dist/cli/templates/provider/README.md.tpl +41 -19
  46. package/dist/cli/templates/provider/index.ts.tpl +6 -3
  47. package/dist/cli/templates/provider/operations/ping.ts.tpl +4 -1
  48. package/dist/cli/templates/provider/provider.json.tpl +6 -0
  49. package/dist/config/loader.d.ts +202 -16
  50. package/dist/config/loader.js +491 -141
  51. package/dist/contract-serialization.js +4 -8
  52. package/dist/contract-types.d.ts +12 -5
  53. package/dist/contract.js +24 -10
  54. package/dist/declaration-validation.d.ts +34 -0
  55. package/dist/declaration-validation.js +305 -0
  56. package/dist/define.d.ts +71 -29
  57. package/dist/define.js +807 -69
  58. package/dist/dev.d.ts +5 -0
  59. package/dist/dev.js +8 -1
  60. package/dist/engine.d.ts +129 -0
  61. package/dist/engine.js +209 -0
  62. package/dist/error-observability.d.ts +7 -0
  63. package/dist/error-observability.js +61 -0
  64. package/dist/error-resolution.d.ts +18 -0
  65. package/dist/error-resolution.js +243 -0
  66. package/dist/errors.d.ts +74 -0
  67. package/dist/errors.js +99 -0
  68. package/dist/fixture-sanitization.d.ts +28 -0
  69. package/dist/fixture-sanitization.js +371 -0
  70. package/dist/handle-meta.d.ts +90 -0
  71. package/dist/handle-meta.js +73 -0
  72. package/dist/handle.d.ts +226 -0
  73. package/dist/handle.js +345 -0
  74. package/dist/health-scenario.d.ts +1901 -0
  75. package/dist/health-scenario.js +653 -0
  76. package/dist/i18n/catalog.js +21 -1
  77. package/dist/i18n/error-messages.d.ts +86 -0
  78. package/dist/i18n/error-messages.js +192 -0
  79. package/dist/i18n/index.d.ts +1 -0
  80. package/dist/i18n/index.js +1 -0
  81. package/dist/i18n/keys.d.ts +12 -0
  82. package/dist/i18n/keys.js +76 -5
  83. package/dist/i18n/operation-locale-namespace.d.ts +2 -0
  84. package/dist/i18n/operation-locale-namespace.js +7 -0
  85. package/dist/index.d.ts +40 -15
  86. package/dist/index.js +29 -11
  87. package/dist/lint.d.ts +43 -11
  88. package/dist/lint.js +2512 -177
  89. package/dist/native-address.d.ts +43 -0
  90. package/dist/native-address.js +281 -0
  91. package/dist/native-egress-policy.d.ts +31 -0
  92. package/dist/native-egress-policy.js +288 -0
  93. package/dist/observability.d.ts +5 -2
  94. package/dist/observability.js +48 -1
  95. package/dist/provider.d.ts +18 -8
  96. package/dist/provider.js +11 -6
  97. package/dist/runtime/auth-flow.d.ts +6 -1
  98. package/dist/runtime/auth-flow.js +8 -0
  99. package/dist/runtime/bounded-expiring-map.d.ts +21 -0
  100. package/dist/runtime/bounded-expiring-map.js +64 -0
  101. package/dist/runtime/browser-telemetry-binding.d.ts +5 -0
  102. package/dist/runtime/browser-telemetry-binding.js +83 -0
  103. package/dist/runtime/browser-telemetry.d.ts +76 -0
  104. package/dist/runtime/browser-telemetry.js +165 -0
  105. package/dist/runtime/browser.d.ts +4 -0
  106. package/dist/runtime/browser.js +688 -83
  107. package/dist/runtime/cache-telemetry.d.ts +71 -0
  108. package/dist/runtime/cache-telemetry.js +177 -0
  109. package/dist/runtime/cache.d.ts +3 -0
  110. package/dist/runtime/cache.js +245 -23
  111. package/dist/runtime/capability-response.d.ts +3 -0
  112. package/dist/runtime/capability-response.js +56 -0
  113. package/dist/runtime/capability-telemetry.d.ts +46 -0
  114. package/dist/runtime/capability-telemetry.js +68 -0
  115. package/dist/runtime/chrome149-header-order.d.ts +58 -0
  116. package/dist/runtime/chrome149-header-order.js +289 -0
  117. package/dist/runtime/diagnostic-env-values.d.ts +5 -0
  118. package/dist/runtime/diagnostic-env-values.js +57 -0
  119. package/dist/runtime/diagnostic-env.d.ts +14 -0
  120. package/dist/runtime/diagnostic-env.js +44 -0
  121. package/dist/runtime/diagnostic-redactor.d.ts +54 -0
  122. package/dist/runtime/diagnostic-redactor.js +571 -0
  123. package/dist/runtime/egress-lease.d.ts +65 -0
  124. package/dist/runtime/egress-lease.js +236 -0
  125. package/dist/runtime/engine-mode.d.ts +74 -0
  126. package/dist/runtime/engine-mode.js +152 -0
  127. package/dist/runtime/env.js +14 -1
  128. package/dist/runtime/executor.d.ts +3 -2
  129. package/dist/runtime/executor.js +23 -26
  130. package/dist/runtime/handle-wordlist.d.ts +43 -0
  131. package/dist/runtime/handle-wordlist.js +256 -0
  132. package/dist/runtime/handle.d.ts +71 -0
  133. package/dist/runtime/handle.js +908 -0
  134. package/dist/runtime/http-telemetry-binding.d.ts +10 -0
  135. package/dist/runtime/http-telemetry-binding.js +152 -0
  136. package/dist/runtime/http-telemetry-guard.d.ts +16 -0
  137. package/dist/runtime/http-telemetry-guard.js +202 -0
  138. package/dist/runtime/http-telemetry.d.ts +104 -0
  139. package/dist/runtime/http-telemetry.js +190 -0
  140. package/dist/runtime/http.d.ts +34 -0
  141. package/dist/runtime/http.js +680 -121
  142. package/dist/runtime/insights.js +2 -2
  143. package/dist/runtime/instrumentation.d.ts +2 -2
  144. package/dist/runtime/instrumentation.js +462 -19
  145. package/dist/runtime/native-network-errors.d.ts +33 -0
  146. package/dist/runtime/native-network-errors.js +69 -0
  147. package/dist/runtime/native-network.d.ts +105 -0
  148. package/dist/runtime/native-network.js +1433 -0
  149. package/dist/runtime/native-telemetry.d.ts +142 -0
  150. package/dist/runtime/native-telemetry.js +231 -0
  151. package/dist/runtime/ocr-telemetry.d.ts +82 -0
  152. package/dist/runtime/ocr-telemetry.js +192 -0
  153. package/dist/runtime/ocr.d.ts +29 -0
  154. package/dist/runtime/ocr.js +468 -0
  155. package/dist/runtime/otlp.d.ts +71 -2
  156. package/dist/runtime/otlp.js +398 -16
  157. package/dist/runtime/provider.d.ts +7 -0
  158. package/dist/runtime/provider.js +14 -0
  159. package/dist/runtime/proxy-errors.js +6 -2
  160. package/dist/runtime/proxy-nodemaven.d.ts +56 -0
  161. package/dist/runtime/proxy-nodemaven.js +147 -0
  162. package/dist/runtime/proxy-retry-policy.d.ts +4 -1
  163. package/dist/runtime/proxy-retry-policy.js +11 -2
  164. package/dist/runtime/proxy-telemetry.d.ts +133 -3
  165. package/dist/runtime/proxy-telemetry.js +218 -70
  166. package/dist/runtime/redirect-hop-headers.d.ts +19 -0
  167. package/dist/runtime/redirect-hop-headers.js +58 -0
  168. package/dist/runtime/redirects.d.ts +61 -0
  169. package/dist/runtime/redirects.js +149 -0
  170. package/dist/runtime/redis.d.ts +1 -1
  171. package/dist/runtime/redis.js +4 -2
  172. package/dist/runtime/request-options.d.ts +75 -1
  173. package/dist/runtime/request-options.js +588 -0
  174. package/dist/runtime/request-telemetry.d.ts +70 -0
  175. package/dist/runtime/request-telemetry.js +229 -0
  176. package/dist/runtime/resolver-config.d.ts +13 -0
  177. package/dist/runtime/resolver-config.js +14 -0
  178. package/dist/runtime/resolver-public.d.ts +3 -0
  179. package/dist/runtime/resolver-public.js +1 -0
  180. package/dist/runtime/resolver-runtime-options.d.ts +14 -0
  181. package/dist/runtime/resolver-runtime-options.js +33 -0
  182. package/dist/runtime/resolver-shared.d.ts +24 -0
  183. package/dist/runtime/resolver-shared.js +69 -0
  184. package/dist/runtime/resolver-span-attributes.d.ts +34 -0
  185. package/dist/runtime/resolver-span-attributes.js +93 -0
  186. package/dist/runtime/resolver-telemetry.d.ts +135 -0
  187. package/dist/runtime/resolver-telemetry.js +204 -0
  188. package/dist/runtime/resolver-usage.d.ts +32 -0
  189. package/dist/runtime/resolver-usage.js +54 -0
  190. package/dist/runtime/resolver-vendors/bindings.d.ts +53 -0
  191. package/dist/runtime/resolver-vendors/bindings.js +45 -0
  192. package/dist/runtime/resolver-vendors/browser.d.ts +24 -0
  193. package/dist/runtime/resolver-vendors/browser.js +423 -0
  194. package/dist/runtime/resolver-vendors/capsolver.d.ts +22 -0
  195. package/dist/runtime/resolver-vendors/capsolver.js +544 -0
  196. package/dist/runtime/resolver-vendors/hosts.d.ts +23 -0
  197. package/dist/runtime/resolver-vendors/hosts.js +68 -0
  198. package/dist/runtime/resolver-vendors/hypersolutions.d.ts +27 -0
  199. package/dist/runtime/resolver-vendors/hypersolutions.js +369 -0
  200. package/dist/runtime/resolver-vendors/twocaptcha.d.ts +24 -0
  201. package/dist/runtime/resolver-vendors/twocaptcha.js +425 -0
  202. package/dist/runtime/resolver-vendors/types.d.ts +125 -0
  203. package/dist/runtime/resolver-vendors/types.js +106 -0
  204. package/dist/runtime/resolver.d.ts +79 -0
  205. package/dist/runtime/resolver.js +1094 -0
  206. package/dist/runtime/state-telemetry.d.ts +79 -0
  207. package/dist/runtime/state-telemetry.js +194 -0
  208. package/dist/runtime/state.d.ts +10 -1
  209. package/dist/runtime/state.js +322 -82
  210. package/dist/runtime/stealth-akamai-sbsd.d.ts +54 -0
  211. package/dist/runtime/stealth-akamai-sbsd.js +142 -0
  212. package/dist/runtime/stealth-cookies.d.ts +24 -0
  213. package/dist/runtime/stealth-cookies.js +131 -0
  214. package/dist/runtime/stealth-owned-headers.d.ts +39 -0
  215. package/dist/runtime/stealth-owned-headers.js +57 -0
  216. package/dist/runtime/stealth-telemetry.d.ts +92 -0
  217. package/dist/runtime/stealth-telemetry.js +179 -0
  218. package/dist/runtime/stealth.d.ts +45 -6
  219. package/dist/runtime/stealth.js +1775 -320
  220. package/dist/runtime/stt-telemetry.d.ts +77 -0
  221. package/dist/runtime/stt-telemetry.js +176 -0
  222. package/dist/runtime/stt.js +18 -19
  223. package/dist/runtime/timeout.d.ts +5 -0
  224. package/dist/runtime/timeout.js +12 -0
  225. package/dist/runtime/trace-config.d.ts +12 -0
  226. package/dist/runtime/trace-config.js +62 -0
  227. package/dist/runtime/trace.d.ts +17 -0
  228. package/dist/runtime/trace.js +176 -25
  229. package/dist/runtime-boundary-lint.d.ts +137 -0
  230. package/dist/runtime-boundary-lint.js +590 -0
  231. package/dist/schema.d.ts +20 -0
  232. package/dist/schema.js +72 -2
  233. package/dist/serve.d.ts +1 -1
  234. package/dist/serve.js +1 -1
  235. package/dist/server/error-observability.d.ts +1 -0
  236. package/dist/server/error-observability.js +1 -0
  237. package/dist/server/error-stack-frames.d.ts +35 -0
  238. package/dist/server/error-stack-frames.js +219 -0
  239. package/dist/server/index.d.ts +21 -2
  240. package/dist/server/index.js +11 -2
  241. package/dist/server/runtime-diagnostics.d.ts +165 -0
  242. package/dist/server/runtime-diagnostics.js +360 -0
  243. package/dist/server/sdk-version.d.ts +3 -0
  244. package/dist/server/sdk-version.js +19 -0
  245. package/dist/server/self-test-input-tokens.d.ts +2 -1
  246. package/dist/server/self-test-input-tokens.js +18 -14
  247. package/dist/server/self-test-redaction.js +8 -33
  248. package/dist/server/self-test.d.ts +18 -3
  249. package/dist/server/self-test.js +139 -76
  250. package/dist/server/sensitive-values.d.ts +26 -0
  251. package/dist/server/sensitive-values.js +137 -0
  252. package/dist/server/serve-implementation.d.ts +285 -0
  253. package/dist/server/serve-implementation.js +3172 -0
  254. package/dist/server/serve.d.ts +1 -75
  255. package/dist/server/serve.js +1 -1170
  256. package/dist/server/state-backend-report.d.ts +70 -0
  257. package/dist/server/state-backend-report.js +113 -0
  258. package/dist/server/trace-output.d.ts +7 -0
  259. package/dist/server/trace-output.js +111 -0
  260. package/dist/server/types.d.ts +37 -5
  261. package/dist/server/types.js +34 -1
  262. package/dist/stateful/errors.d.ts +19 -0
  263. package/dist/stateful/errors.js +24 -0
  264. package/dist/stateful/http-provider-event-emitter.d.ts +40 -0
  265. package/dist/stateful/http-provider-event-emitter.js +237 -0
  266. package/dist/stateful/http-session-owner-registry.d.ts +44 -0
  267. package/dist/stateful/http-session-owner-registry.js +210 -0
  268. package/dist/stateful/index.d.ts +18 -0
  269. package/dist/stateful/index.js +18 -0
  270. package/dist/stateful/provider-event-delivery-failures.d.ts +32 -0
  271. package/dist/stateful/provider-event-delivery-failures.js +43 -0
  272. package/dist/stateful/provider-event-pipeline-metrics.d.ts +46 -0
  273. package/dist/stateful/provider-event-pipeline-metrics.js +48 -0
  274. package/dist/stateful/provider-event-pipeline.d.ts +50 -0
  275. package/dist/stateful/provider-event-pipeline.js +1 -0
  276. package/dist/stateful/provider-events.d.ts +101 -0
  277. package/dist/stateful/provider-events.js +289 -0
  278. package/dist/stateful/session-key.d.ts +15 -0
  279. package/dist/stateful/session-key.js +86 -0
  280. package/dist/stateful/stateful-provider-adapter-context.d.ts +5 -0
  281. package/dist/stateful/stateful-provider-adapter-context.js +42 -0
  282. package/dist/stateful/stateful-provider-adapter-metrics.d.ts +15 -0
  283. package/dist/stateful/stateful-provider-adapter-metrics.js +21 -0
  284. package/dist/stateful/stateful-provider-adapter.d.ts +98 -0
  285. package/dist/stateful/stateful-provider-adapter.js +287 -0
  286. package/dist/stateful/stateful-provider-observability.d.ts +62 -0
  287. package/dist/stateful/stateful-provider-observability.js +161 -0
  288. package/dist/stateful/stateful-provider-owner-forwarder.d.ts +41 -0
  289. package/dist/stateful/stateful-provider-owner-forwarder.js +237 -0
  290. package/dist/stateful/stateful-provider-runtime-context.d.ts +35 -0
  291. package/dist/stateful/stateful-provider-runtime-context.js +61 -0
  292. package/dist/stateful/stateful-provider-runtime-executor.d.ts +34 -0
  293. package/dist/stateful/stateful-provider-runtime-executor.js +52 -0
  294. package/dist/stateful/stateful-provider-session-routing.d.ts +67 -0
  295. package/dist/stateful/stateful-provider-session-routing.js +345 -0
  296. package/dist/stateful/stateful-provider-session-runtime.d.ts +98 -0
  297. package/dist/stateful/stateful-provider-session-runtime.js +245 -0
  298. package/dist/stateful-signing.d.ts +18 -0
  299. package/dist/stateful-signing.js +27 -0
  300. package/dist/stealth/profiles.d.ts +15 -4
  301. package/dist/stealth/profiles.js +169 -211
  302. package/dist/stream-evidence.d.ts +74 -0
  303. package/dist/stream-evidence.js +785 -0
  304. package/dist/stream.js +7 -1
  305. package/dist/testing/index.d.ts +2 -1
  306. package/dist/testing/index.js +2 -1
  307. package/dist/testing/run.d.ts +32 -2
  308. package/dist/testing/run.js +509 -25
  309. package/dist/trace-sanitization.d.ts +6 -0
  310. package/dist/trace-sanitization.js +68 -0
  311. package/dist/types.d.ts +899 -210
  312. package/dist/types.js +1 -0
  313. package/dist/typescript-module.d.ts +39 -0
  314. package/dist/typescript-module.js +72 -0
  315. package/package.json +52 -6
  316. package/src/auth-turn/index.ts +1 -1
  317. package/src/auth.ts +118 -0
  318. package/src/ceremonies/index.ts +189 -46
  319. package/src/cli/commands.ts +37 -0
  320. package/src/cli/create.ts +8 -2
  321. package/src/cli/deployment-intent.ts +364 -0
  322. package/src/cli/local-connection.ts +221 -0
  323. package/src/cli/migrate-deployment-intent.ts +1908 -0
  324. package/src/cli/migrate-operation-declaration.ts +3026 -0
  325. package/src/cli/migrate-operation-shape.ts +184 -0
  326. package/src/cli/migrate-provider-shape.ts +778 -0
  327. package/src/cli/resolver-runtime.ts +72 -0
  328. package/src/cli/templates/provider/.agents/skills/health-checks-and-fail-closed/SKILL.md.tpl +141 -23
  329. package/src/cli/templates/provider/Dockerfile.tpl +1 -1
  330. package/src/cli/templates/provider/README.md.tpl +41 -19
  331. package/src/cli/templates/provider/index.ts.tpl +6 -3
  332. package/src/cli/templates/provider/operations/ping.ts.tpl +4 -1
  333. package/src/cli/templates/provider/provider.json.tpl +6 -0
  334. package/src/config/loader.ts +774 -181
  335. package/src/contract-serialization.ts +5 -7
  336. package/src/contract-types.ts +12 -5
  337. package/src/contract.ts +24 -10
  338. package/src/declaration-validation.ts +376 -0
  339. package/src/define.ts +1105 -131
  340. package/src/dev.ts +13 -1
  341. package/src/engine.ts +390 -0
  342. package/src/error-observability.ts +64 -0
  343. package/src/error-resolution.ts +269 -0
  344. package/src/errors.ts +157 -0
  345. package/src/fixture-sanitization.ts +422 -0
  346. package/src/handle-meta.ts +143 -0
  347. package/src/handle.ts +694 -0
  348. package/src/health-scenario.ts +960 -0
  349. package/src/i18n/catalog.ts +23 -1
  350. package/src/i18n/error-messages.ts +255 -0
  351. package/src/i18n/index.ts +1 -0
  352. package/src/i18n/keys.ts +80 -6
  353. package/src/i18n/operation-locale-namespace.ts +10 -0
  354. package/src/index.ts +390 -23
  355. package/src/lint.ts +2975 -234
  356. package/src/native-address.ts +340 -0
  357. package/src/native-egress-policy.ts +358 -0
  358. package/src/observability.ts +51 -1
  359. package/src/provider.ts +207 -27
  360. package/src/runtime/auth-flow.ts +15 -0
  361. package/src/runtime/bounded-expiring-map.ts +67 -0
  362. package/src/runtime/browser-telemetry-binding.ts +92 -0
  363. package/src/runtime/browser-telemetry.ts +289 -0
  364. package/src/runtime/browser.ts +882 -99
  365. package/src/runtime/cache-telemetry.ts +247 -0
  366. package/src/runtime/cache.ts +258 -22
  367. package/src/runtime/capability-response.ts +53 -0
  368. package/src/runtime/capability-telemetry.ts +101 -0
  369. package/src/runtime/chrome149-header-order.ts +330 -0
  370. package/src/runtime/diagnostic-env-values.ts +56 -0
  371. package/src/runtime/diagnostic-env.ts +65 -0
  372. package/src/runtime/diagnostic-redactor.ts +668 -0
  373. package/src/runtime/egress-lease.ts +341 -0
  374. package/src/runtime/engine-mode.ts +233 -0
  375. package/src/runtime/env.ts +15 -1
  376. package/src/runtime/executor.ts +39 -32
  377. package/src/runtime/handle-wordlist.ts +266 -0
  378. package/src/runtime/handle.ts +1180 -0
  379. package/src/runtime/http-telemetry-binding.ts +162 -0
  380. package/src/runtime/http-telemetry-guard.ts +208 -0
  381. package/src/runtime/http-telemetry.ts +292 -0
  382. package/src/runtime/http.ts +896 -150
  383. package/src/runtime/insights.ts +2 -2
  384. package/src/runtime/instrumentation.ts +628 -28
  385. package/src/runtime/native-network-errors.ts +99 -0
  386. package/src/runtime/native-network.ts +1853 -0
  387. package/src/runtime/native-telemetry.ts +366 -0
  388. package/src/runtime/ocr-telemetry.ts +308 -0
  389. package/src/runtime/ocr.ts +564 -0
  390. package/src/runtime/otlp.ts +468 -21
  391. package/src/runtime/provider.ts +15 -0
  392. package/src/runtime/proxy-errors.ts +12 -4
  393. package/src/runtime/proxy-nodemaven.ts +222 -0
  394. package/src/runtime/proxy-retry-policy.ts +12 -1
  395. package/src/runtime/proxy-telemetry.ts +358 -103
  396. package/src/runtime/redirect-hop-headers.ts +71 -0
  397. package/src/runtime/redirects.ts +215 -0
  398. package/src/runtime/redis.ts +7 -2
  399. package/src/runtime/request-options.ts +728 -1
  400. package/src/runtime/request-telemetry.ts +375 -0
  401. package/src/runtime/resolver-config.ts +18 -0
  402. package/src/runtime/resolver-public.ts +44 -0
  403. package/src/runtime/resolver-runtime-options.ts +50 -0
  404. package/src/runtime/resolver-shared.ts +113 -0
  405. package/src/runtime/resolver-span-attributes.ts +139 -0
  406. package/src/runtime/resolver-telemetry.ts +362 -0
  407. package/src/runtime/resolver-usage.ts +99 -0
  408. package/src/runtime/resolver-vendors/bindings.ts +61 -0
  409. package/src/runtime/resolver-vendors/browser.ts +591 -0
  410. package/src/runtime/resolver-vendors/capsolver.ts +727 -0
  411. package/src/runtime/resolver-vendors/hosts.ts +81 -0
  412. package/src/runtime/resolver-vendors/hypersolutions.ts +480 -0
  413. package/src/runtime/resolver-vendors/twocaptcha.ts +555 -0
  414. package/src/runtime/resolver-vendors/types.ts +261 -0
  415. package/src/runtime/resolver.ts +1543 -0
  416. package/src/runtime/state-telemetry.ts +298 -0
  417. package/src/runtime/state.ts +435 -89
  418. package/src/runtime/stealth-akamai-sbsd.ts +207 -0
  419. package/src/runtime/stealth-cookies.ts +155 -0
  420. package/src/runtime/stealth-owned-headers.ts +67 -0
  421. package/src/runtime/stealth-telemetry.ts +309 -0
  422. package/src/runtime/stealth.ts +2275 -372
  423. package/src/runtime/stt-telemetry.ts +282 -0
  424. package/src/runtime/stt.ts +96 -91
  425. package/src/runtime/timeout.ts +18 -0
  426. package/src/runtime/trace-config.ts +78 -0
  427. package/src/runtime/trace.ts +257 -35
  428. package/src/runtime-boundary-lint.ts +707 -0
  429. package/src/schema.ts +80 -2
  430. package/src/serve.ts +6 -1
  431. package/src/server/error-observability.ts +1 -0
  432. package/src/server/error-stack-frames.ts +214 -0
  433. package/src/server/index.ts +184 -2
  434. package/src/server/runtime-diagnostics.ts +522 -0
  435. package/src/server/sdk-version.ts +22 -0
  436. package/src/server/self-test-input-tokens.ts +29 -14
  437. package/src/server/self-test-redaction.ts +10 -40
  438. package/src/server/self-test.ts +205 -89
  439. package/src/server/sensitive-values.ts +211 -0
  440. package/src/server/serve-implementation.ts +4596 -0
  441. package/src/server/serve.ts +1 -1685
  442. package/src/server/state-backend-report.ts +185 -0
  443. package/src/server/trace-output.ts +189 -0
  444. package/src/server/types.ts +34 -1
  445. package/src/stateful/README.md +146 -0
  446. package/src/stateful/errors.ts +35 -0
  447. package/src/stateful/http-provider-event-emitter.ts +314 -0
  448. package/src/stateful/http-session-owner-registry.ts +306 -0
  449. package/src/stateful/index.ts +18 -0
  450. package/src/stateful/provider-event-delivery-failures.ts +80 -0
  451. package/src/stateful/provider-event-pipeline-metrics.ts +95 -0
  452. package/src/stateful/provider-event-pipeline.ts +61 -0
  453. package/src/stateful/provider-events.ts +462 -0
  454. package/src/stateful/session-key.ts +111 -0
  455. package/src/stateful/stateful-provider-adapter-context.ts +59 -0
  456. package/src/stateful/stateful-provider-adapter-metrics.ts +48 -0
  457. package/src/stateful/stateful-provider-adapter.ts +562 -0
  458. package/src/stateful/stateful-provider-observability.ts +261 -0
  459. package/src/stateful/stateful-provider-owner-forwarder.ts +310 -0
  460. package/src/stateful/stateful-provider-runtime-context.ts +96 -0
  461. package/src/stateful/stateful-provider-runtime-executor.ts +96 -0
  462. package/src/stateful/stateful-provider-session-routing.ts +546 -0
  463. package/src/stateful/stateful-provider-session-runtime.ts +403 -0
  464. package/src/stateful-signing.ts +46 -0
  465. package/src/stealth/profiles.ts +212 -231
  466. package/src/stream-evidence.ts +988 -0
  467. package/src/stream.ts +8 -1
  468. package/src/testing/index.ts +10 -1
  469. package/src/testing/run.ts +685 -19
  470. package/src/trace-sanitization.ts +110 -0
  471. package/src/types.ts +1014 -273
  472. package/src/typescript-module.ts +100 -0
  473. package/dist/choice-token.d.ts +0 -24
  474. package/dist/choice-token.js +0 -74
  475. package/dist/runtime/choice.d.ts +0 -15
  476. package/dist/runtime/choice.js +0 -413
  477. package/src/choice-token.ts +0 -165
  478. package/src/runtime/choice.ts +0 -686
package/AUTHORING.md CHANGED
@@ -41,9 +41,10 @@ rounds — weak models drop or corrupt it. Split the operation into a
41
41
 
42
42
  - The prepare operation is non-destructive. A start call takes only the
43
43
  scalar intent fields; every response returns a fresh `attempt_token`
44
- referencing a server-side record (`ctx.choice.issue` with
45
- `storage.mode: "server"`) that stores every settled decision. Continue
46
- calls take `attempt_token` plus only the NEW answers.
44
+ referencing a server-side record (a `ctx.handle` draft kind from
45
+ `defineDraft`, declared on the schema with `kind.field()`) that stores
46
+ every settled decision. Continue calls take `attempt_token` plus only the
47
+ NEW answers.
47
48
  - `needs_input` rounds list only the still-pending selections; settled
48
49
  decisions may ride along in a display-only field but are never re-sent.
49
50
  - When nothing is pending, the prepare operation returns `status: "ready"`
@@ -55,15 +56,14 @@ rounds — weak models drop or corrupt it. Split the operation into a
55
56
  for what the user picked.
56
57
  - Expired or foreign tokens fail factually (nothing happened; start a new
57
58
  attempt with the scalar fields) — no answer salvage from a dead token.
58
- - **The provider must enforce consumption itself.** `ctx.choice` server
59
- storage keeps tokens parseable until TTL — `parse` does not invalidate
60
- them, so a confirm handler that only parses can be replayed into a second
61
- booking or payment. After a successful execution, record the result under
62
- the token's digest in `ctx.state` and make replays idempotent: a repeated
63
- confirm returns the original created payload without touching upstream,
64
- and later prepare rounds on the consumed token fail factually with the
65
- existing reference. Record the result only after upstream success, so an
66
- interrupted confirm stays retryable.
59
+ - **Commit is one-shot with replay; providers no longer hand-roll dedup.**
60
+ `ctx.handle.read` and `ctx.handle.update` never consume a draft, so a
61
+ confirm handler must execute upstream work through `ctx.handle.commit`. It
62
+ runs the work once, stores the result under the handle, and a repeated
63
+ confirm returns `{ status: "replayed", result }` without touching upstream.
64
+ A failing upstream call restores the draft to `active`, so an interrupted
65
+ confirm stays retryable; later prepare rounds on a committed draft fail
66
+ factually with `HANDLE_COMMITTED`.
67
67
 
68
68
  The invariant behind all of it: complex flow state is the system's job, not
69
69
  the model's. The model carries exactly one opaque key between calls.
@@ -73,38 +73,303 @@ payment state tokens in the platform monorepo.
73
73
 
74
74
  ### Description template
75
75
 
76
- Every operation `description` MUST be at least 150 characters and follow this structure:
76
+ Every operation declares a `descriptionKey`. Its English locale value should
77
+ follow this structure:
77
78
 
78
79
  ```
79
80
  <What the tool does in one sentence>. Use when <specific scenarios>. Do NOT use for <counter-scenarios; point to alternatives>. Returns <key output fields>. <Important caveats: rate limits, auth, freshness>.
80
81
  ```
81
82
 
82
- Example:
83
+ Example declaration and locale entry:
83
84
  ```ts
84
- description:
85
- "Retrieves KMA ultra-short-term weather observation for a given grid coordinate in South Korea, " +
86
- "including temperature, humidity, wind speed, precipitation, and sky condition. " +
87
- "Use when the user asks about current or hourly weather at a specific Korean location. " +
88
- "Do NOT use for forecasts beyond 2 days — use kma_mid_forecast instead. " +
89
- "Returns hourly data in KST timezone; null values indicate data unavailable. " +
90
- "Rate-limited to 1000 calls/day on the free tier.",
85
+ descriptionKey: "operations.realtimeWeather.description",
86
+ ```
87
+
88
+ ```json
89
+ {
90
+ "operations": {
91
+ "realtimeWeather": {
92
+ "description": "Retrieves KMA ultra-short-term weather observations for a South Korean grid coordinate. Use when the user asks about current or hourly weather. Do NOT use for forecasts beyond two days; use the mid-range forecast operation instead. Returns hourly KST data; null values mean unavailable data."
93
+ }
94
+ }
95
+ }
91
96
  ```
92
97
 
93
98
  ### Language policy
94
99
 
95
- - **Structural text**: English (operation `description`, Zod `.describe()`, `whenToUse`, `whenNotToUse`, `derivations`, `inputExamples.scenario/rationale`).
96
- - **Values only**: native language (fixtures payloads, `inputExamples[].input` values like "대방동", "KRW-BTC", entity catalog entries).
100
+ - **Structural text**: locale keys (`descriptionKey`, the other `*Key`/`*Keys`
101
+ fields, schema `.describeKey()`, and `examples[].scenarioKey`/`rationaleKey`).
102
+ - **Values only**: native language (fixtures payloads and `examples[].input`
103
+ values like "대방동", "KRW-BTC", entity catalog entries).
97
104
 
98
105
  ### Required per operation
99
106
 
100
- - `description` — 150+ chars English (error-level rule)
101
- - Every Zod field in input AND output has `.describe()` including nested objects + array items (error-level rule)
107
+ - `riskClass` — authored as `read`, `write`, `destructive`, or `external-send`
108
+ - `descriptionKey` — backed by every required provider locale catalog
109
+ - `connectionMode` — explicit for `credentials`, `oauth2`, and `oauth2_proxied` providers
110
+ - Every Zod field in input AND output has `.describeKey()` including nested objects + array items (error-level rule)
102
111
  - `fixtures.request` + `fixtures.response` both present (error-level rule)
103
112
  - Exactly one of `healthCheck`, `healthCheckUnsupported`, or `healthJourneys[].coversOperations` coverage per operation. Prefer `healthCheck` for safe read-only upstream probes; use `healthCheckUnsupported` only with a specific reason for destructive, paid, credential-sensitive, flaky, or otherwise unsafe probes. Use a provider-level health journey when a destructive or credential-sensitive flow can be proven safely only as a multi-step boundary test, such as stopping at a payment WebView URL.
104
113
 
114
+ ### Capability declarations
115
+
116
+ Declare each capability used by provider operations. Capabilities without
117
+ configuration use a bare object, for example `http: {}`, `state: {}`, or
118
+ `cache: {}`. `handle` is the exception: it takes an array of kinds
119
+ (`handle: [WaitingDraft]`) and requires `state: {}` alongside it. `trace` is
120
+ ambient and must not be declared: every operation
121
+ context receives it. `allowedHosts`, `proxy`, `secrets`, and `context` declare
122
+ policy or metadata only; they do not create context members and are not
123
+ capability bindings.
124
+
125
+ Declare `runtimeTarget: "vanilla"` for portable provider business logic. Use
126
+ `runtimeTarget: "engine"` only for an approved session-bearing provider that
127
+ must remain engine-resident. A vanilla target cannot declare `native`.
128
+
129
+ Proxy vendor application keys, usernames, and passwords are engine-owned. Do
130
+ not list `APIFUSE__PROXY__SMARTPROXY_APP_KEY`,
131
+ `APIFUSE__PROXY__NODEMAVEN_USERNAME`, or
132
+ `APIFUSE__PROXY__NODEMAVEN_PASSWORD` in provider `secrets`; `proxy` contains
133
+ policy intent only.
134
+
135
+ Hosted runtime settings are engine-owned as well: every `APIFUSE__CDP_POOL__*`
136
+ variable, the Cloudflare OCR/STT tokens and account identifier,
137
+ `APIFUSE__OCR__API_KEY`, and `APIFUSE__CACHE__KEY_PEPPER`. `defineProvider`
138
+ rejects them in `secrets`; the engine reads them from its own environment.
139
+ Provider code must not probe those names either — to learn whether OCR is
140
+ configured, read `ctx.ocr.available` (see the OCR section below).
141
+
142
+ ### Engine mode
143
+
144
+ Provider processes attach their capability bindings through an engine. Today the
145
+ default is the in-process engine; ADR-0011 replaces it with the authenticated
146
+ remote engine in a phased migration, and `serve` logs every boot as
147
+ `provider_engine_mode` (`deprecated: true` for in-process). You do not need to
148
+ change anything now.
149
+
150
+ A deployment names the mode with the engine-owned `APIFUSE__ENGINE__MODE`
151
+ variable (`in-process` or `remote`; trimmed and case-folded, blank means unset);
152
+ hosts may pass `engineMode` to `serve`/`createServerApp`/`startDevServer`
153
+ instead. The manifest wins when the two disagree, so provider code cannot
154
+ silently downgrade what the deployment asked for; the disagreement is reported in
155
+ the boot event's `warnings`. An unrecognized value is warned about and ignored —
156
+ it never crashes the process. `APIFUSE__ENGINE__MODE` cannot be declared in
157
+ `secrets` (`APIFUSE__ENGINE__*` is engine-owned).
158
+
159
+ `remote` has no client in this release: the server attaches an engine that fails
160
+ every request with `PROVIDER_ENGINE_MODE_UNSUPPORTED` and reports `503` on
161
+ `/readyz`. It never falls back to in-process. `apifuse dev` and `apifuse record`
162
+ refuse to start instead.
163
+
164
+ `GET /health` is liveness only and stays engine-blind; `GET /readyz` reports the
165
+ resolved engine attachment and is what readiness/startup probes should use — but
166
+ only for a provider already pinned to an SDK release that serves the route.
167
+ Earlier pins answer `404`, and a failing readiness/startup probe is not
168
+ self-healing, so manifests move to `/readyz` after the pin wave, never before.
169
+
170
+ `GET /__apifuse/diagnostics/runtime` is the internal runtime diagnostics route on
171
+ the same listener. It answers one JSON report (`schemaVersion: 1`) sampled over a
172
+ short window (`?windowMs=`, default 250, clamped to 50..5000): `cpu.window`
173
+ (`process.cpuUsage()` delta as cores, user/system split), `cpu.lifetime`,
174
+ `memory`, `eventLoop` (`performance.eventLoopUtilization`), `activeResources`
175
+ (`process.getActiveResourcesInfo()` counts by type) and, on Linux, `procfs`
176
+ with per-thread CPU ticks, context switches, wait channel and file-descriptor
177
+ counts by kind from `/proc/self`. Bun 1.2 and 1.3 stub the two Node APIs, so
178
+ their `reported` flag is `false` there; `cpu.window` and `procfs.threads`
179
+ (sorted hottest first, named `bun`, `HTTP Client`, `HeapHelper`, ...) are the
180
+ signals that tell an idle process that still burns CPU apart from a busy one.
181
+ The route is read-only and carries no secrets. It is internal by the same
182
+ boundary as `/health` and `/readyz`: the generated provider NetworkPolicy admits
183
+ only the gateway on this port and the gateway dials canonical operation paths
184
+ only, so operators reach it with `kubectl port-forward` and the tenant-facing
185
+ API never does.
186
+
187
+ ### Deployment intent
188
+
189
+ Deployment intent (runtime profile, resources, HPA, Redis, extra TCP ports) has
190
+ exactly one authored home: the optional `deployment` key inside
191
+ `defineProvider({...})`. The SDK passes it through verbatim; the APIFuse
192
+ registry resolves omitted fields from the runtime profile and rejects anything
193
+ it cannot resolve.
194
+
195
+ - Declare only what differs from the profile. Profile defaults:
196
+ `runtime: "shared"`, `language: "typescript"`, `replicas: 1`,
197
+ `hpa: { enabled: true, minReplicas: 1, maxReplicas: 1, targetCPUUtilizationPercentage: 70 }`,
198
+ resources `25m/128Mi` (shared) or `200m/256Mi` (browser), `buildContext: "."`.
199
+ Most providers therefore declare no `deployment` key at all.
200
+ - The deployment `runtime` axis (`shared | dedicated | browser`) is distinct
201
+ from the execution `runtime` (`standard | shared | browser`) declared next to
202
+ it.
203
+ - Do not author a standalone `deploy.ts`, a `deployment` extra on a spread
204
+ default export (`export default { ...provider, deployment }`), or a
205
+ re-exported config module. A repository that still carries `deploy.ts`
206
+ migrates with `apifuse migrate-deployment .`: it hoists the file's non-default
207
+ values into the declaration, proves the result resolves identically, and
208
+ deletes the file. The `/deploy.ts` CODEOWNERS entry stays until the platform
209
+ stops honoring a re-added legacy file (`--drop-codeowners-lock` then). A
210
+ refusal names the manual step.
211
+ - `apifuse check` warns during the migration window
212
+ (`deployment/legacy-deploy-file`, `deployment/spread-export`,
213
+ `deployment/redundant-default`); the rules become errors once no repository
214
+ carries the legacy file.
215
+
216
+ ```ts
217
+ const buildProvider = defineProvider({
218
+ id: "example",
219
+ version: "1.0.0",
220
+ runtime: "standard",
221
+ deployment: { cache: { redis: { enabled: true } } },
222
+ // ...
223
+ });
224
+ ```
225
+
105
226
  ### Factored operations
106
227
 
107
- Use `defineOperation()` when an operation is large enough to live beside helper functions or in a separate module. It preserves the same type inference as inline `defineProvider()` operations and can be placed directly in the provider `operations` map. `defineProvider()` accepts Zod and Standard Schema v1-compatible schemas. If config validation fails, the SDK names the field to fix, for example `runtime`, `auth.mode`, `operations.<id>.handler`, or `operations.<id>.fixtures.response`.
228
+ `defineProvider(declaration)` returns the builder that accepts `operations`, so
229
+ inline handlers are typed only after capability declarations are fixed. Export
230
+ one declaration-derived context alias from the provider entry point:
231
+
232
+ ```ts
233
+ import {
234
+ defineProvider,
235
+ type ProviderContext,
236
+ type ProviderDeclaration,
237
+ } from "@apifuse/provider-sdk/provider";
238
+
239
+ const declaration = {
240
+ // ...id, version, runtime, meta
241
+ http: {},
242
+ } as const satisfies ProviderDeclaration;
243
+
244
+ export type Ctx = ProviderContext<typeof declaration>;
245
+ const buildProvider = defineProvider(declaration);
246
+ ```
247
+
248
+ Separate operation files need one provider-local type import and no generic
249
+ handler signature:
250
+
251
+ ```ts
252
+ import { defineOperation } from "@apifuse/provider-sdk/provider";
253
+ import type { Ctx } from "../index.js";
254
+
255
+ export const search = defineOperation<Ctx>()({
256
+ // ...input, output, fixtures, health check
257
+ async handler(ctx, input) {
258
+ return fetchSearch(ctx.http, input);
259
+ },
260
+ });
261
+ ```
262
+
263
+ `ProviderContextOf<typeof buildProvider>` remains an equivalent convenience
264
+ alias. Helpers should accept the one SDK client they use rather than the
265
+ provider context. Zod and Standard Schema v1 schemas retain input/output
266
+ inference. Invalid configs name the offending field, such as `auth.mode` or
267
+ `operations.<id>.fixtures.response`.
268
+
269
+ ### Replay-safe fixtures
270
+
271
+ Keep public operation schemas strict: date fields should accept absolute dates,
272
+ not relative tokens. Inside `fixtures.request` only, the SDK resolves `+Nd` and
273
+ `+Nd:YYYYMMDD` (1–365 days ahead) before import-time schema validation and
274
+ stores the resolved request in provider metadata. Health-check case inputs use
275
+ the same resolver when a probe runs. The default calendar is **KST**, including
276
+ the 15:00–23:59 UTC window when KST is already on the next day.
277
+
278
+ `fixtures.recordedAt` is the KST `YYYY-MM-DD` date when the response evidence
279
+ was captured. It must be a real, non-future calendar date. Response date fields
280
+ are expected to align with `recordedAt`, not with the newly resolved request;
281
+ this permits stable recorded evidence alongside a replay-safe request.
282
+
283
+ ```ts
284
+ const FlightInput = z.object({
285
+ departureDate: z.string().date(), // public calls remain absolute-date only
286
+ });
287
+
288
+ const searchFlights = {
289
+ input: FlightInput,
290
+ output: FlightOutput,
291
+ async handler(ctx, input) {
292
+ return fetchAndNormalizeFlights(ctx, input);
293
+ },
294
+ fixtures: {
295
+ request: { departureDate: "+45d" },
296
+ response: recordedFlightResponse, // dates reflect the capture below
297
+ recordedAt: "2026-07-15",
298
+ },
299
+ healthCheckUnsupported: { reason: "Upstream search is cost-bearing." },
300
+ };
301
+ ```
302
+
303
+ For code that explicitly calls the shared resolver, omit the third argument to
304
+ use KST or pass `"UTC"` deliberately:
305
+
306
+ ```ts
307
+ import { resolveHealthCheckInputDateTokens } from "@apifuse/provider-sdk/server";
308
+
309
+ const kstInput = resolveHealthCheckInputDateTokens({ date: "+45d" });
310
+ const utcInput = resolveHealthCheckInputDateTokens({ date: "+45d" }, new Date(), "UTC");
311
+ ```
312
+
313
+ Do not re-resolve a health assertion's dates in UTC when its case input used the
314
+ default KST calendar.
315
+
316
+ ### Sensitive vs public fields
317
+
318
+ The platform fixture-privacy ratchet flags name-, phone-, and address-shaped keys
319
+ unless the schema at that path declares them one way or the other. Declare every hit:
320
+
321
+ - `sensitive(schema, kind)` / `fields.*()` — anything about a person: user, profile,
322
+ passenger, driver, or member names, emails, member ids, personal phone numbers.
323
+ Emits `x-apifuse-sensitive: true`; the value is redacted in published artifacts.
324
+ - `publicField(schema)` — reviewed public data: business, shop, product, artist, or
325
+ facility names, hospital/hotel/station phones, timezone names. Emits
326
+ `x-apifuse-sensitive: false`; nothing is redacted. It is a review verdict, not a mute.
327
+ Credential-shaped keys (token, password, apiKey, …) cannot be declared public.
328
+
329
+ Apply either helper to the leaf, before `.optional()`/`.nullable()`/`.default()`;
330
+ `publicField()` over a wrapper that hides a `sensitive()` leaf throws at definition time.
331
+
332
+ ### Real-handler E2E in standard tests
333
+
334
+ `runStandardTests(provider)` validates declarations and fixtures but reports a
335
+ per-operation warning because it has no handler E2E coverage. Opt in with an
336
+ `upstreamStub`: the runner calls each fixture-backed real handler with its
337
+ already-resolved fixture request, routes ProviderContext upstream transports to
338
+ the stub, and validates the result against the output schema. It never compares
339
+ the result to the recorded response because that evidence belongs to
340
+ `recordedAt`.
341
+
342
+ ```ts
343
+ import { runStandardTests } from "@apifuse/provider-sdk/testing";
344
+ import provider from "../index.js";
345
+
346
+ runStandardTests(provider, {
347
+ upstreamStub: ({ transport, method, url }) => {
348
+ if (
349
+ transport === "http" &&
350
+ method === "GET" &&
351
+ url === "https://api.example.test/flights"
352
+ ) {
353
+ return Response.json({ flights: [{ id: "fixture-flight" }] });
354
+ }
355
+ return undefined; // fails the test: live-network passthrough is forbidden
356
+ },
357
+ });
358
+ ```
359
+
360
+ The stub also identifies `stealth`, `browser`, and `native` interactions. Return
361
+ a Web `Response` or `{ status, headers, body }`; an unmatched call fails with
362
+ the operation, transport, and method named in the error. Browser handlers expose
363
+ method-level calls such as `goto`, `evaluate`, and `locator.click`, so provide a
364
+ canned result for each method the handler uses. Native connections similarly
365
+ identify `connectTcp`/`connectTls` and subsequent `write` calls. The offline
366
+ `ctx.stealth` rejects a stealth-owned request header (`User-Agent`,
367
+ `Sec-Fetch-*`, `sec-ch-ua*`, ...) with `STEALTH_HEADER_OVERRIDE_UNSUPPORTED`
368
+ before calling the stub, through the same check the production transport runs,
369
+ so a header spread from a shared constants module or read from a variable fails
370
+ the test instead of the first production request. Direct global
371
+ `fetch` or socket usage is outside this ProviderContext seam and should not be
372
+ used by provider handlers.
108
373
 
109
374
  ### Health assertion context
110
375
 
@@ -130,15 +395,11 @@ healthCheck: {
130
395
  ```
131
396
  <!-- @magic-end:sample -->
132
397
 
133
- ### Strongly recommended (warn-level rules)
134
-
135
- - `description` includes "use" AND "when" phrasing
136
- - `inputExamples` with 2+ scenarios for complex input (nested objects, enums, format-sensitive strings)
137
- - `derivations` for parameters not directly visible in the user query (e.g., `gridX` derived from geocoding)
138
-
139
398
  ### Optional but valuable
140
399
 
141
- - `annotations`: `{ readOnly, destructive, idempotent, openWorld, rateLimit }` — agentic safety signals
400
+ - `examples`: usage examples with locale-keyed `scenarioKey`, an `input`, and optional `rationaleKey`
401
+ - `approval`: only when it deliberately differs from the `riskClass` default (`read → never`, `write → risk-based`, otherwise `always`)
402
+ - `titleKey`, `summaryKey`, `markdownKey`, `whenToUseKeys`, `whenNotToUseKeys`, and `normalizationNotesKeys`: locale-keyed operation prose
142
403
  - `tags`: operation-level semantic tags for retrieval (e.g., `["weather", "korea", "realtime"]`)
143
404
  - `relatedOperations`: `{ alternatives?: string[] }` — links to fallback/sibling operations
144
405
 
@@ -152,10 +413,12 @@ level, then call `ctx.stt` from operation handlers or auth-flow handlers.
152
413
  ```ts
153
414
  export default defineProvider({
154
415
  id: "example-provider",
155
- // ...metadata, auth, operations, allowedHosts
416
+ // ...metadata, auth, allowedHosts
156
417
  stt: { mode: "required" },
418
+ })({
157
419
  operations: {
158
420
  verifyAudioOtp: {
421
+ riskClass: "read",
159
422
  input: z.object({
160
423
  audioBase64: z.string().describe("Base64-encoded short OTP audio"),
161
424
  mediaType: z.string().optional().describe("Audio MIME type"),
@@ -216,6 +479,40 @@ Best-practice rules:
216
479
  - Submission checks and health checks must not invoke live STT by default.
217
480
  Provide explicit smoke evidence when a provider depends on audio OTP behavior.
218
481
 
482
+ ### OCR runtime capability: availability is the SDK's verdict
483
+
484
+ Declare `ocr: { mode: "required" | "optional" }` and call `ctx.ocr` from
485
+ operation handlers or auth-flow handlers. Whether a working backend is wired
486
+ in is decided by the SDK, never by provider code: `ctx.ocr.available` is
487
+ `true` when `createOcrClientFromEnv` found a configured backend and `false`
488
+ for the unsupported/error client that undeclared or misconfigured providers
489
+ receive. Branch on that flag for behavior that depends on OCR being present
490
+ (for example, whether a CAPTCHA field is required from the user), and keep the
491
+ `OCR_UNAVAILABLE` / `UNSUPPORTED_OCR_BACKEND` error fallback for the call
492
+ itself: `available: true` promises a configured backend, not a successful
493
+ upstream call.
494
+
495
+ ```ts
496
+ async start(ctx) {
497
+ const image = await fetchCaptcha(ctx);
498
+ return ctx.auth.nextForm({
499
+ turnId: "credentials",
500
+ fields: {
501
+ captchaText: { type: "string", required: !ctx.ocr.available, /* ... */ },
502
+ },
503
+ data: { captchaImage: image, ocrConfigured: ctx.ocr.available },
504
+ });
505
+ }
506
+ ```
507
+
508
+ Do not re-implement the predicate by reading `APIFUSE__OCR__*` or
509
+ `APIFUSE__CLOUDFLARE__ACCOUNT_ID`: those names are engine-owned, the
510
+ runtime-boundary lint reports the read, and a drift between the copy and the
511
+ SDK's own selection logic downgrades every login to the manual path silently.
512
+
513
+ A custom `OcrContext` override (`serve({ ocr })`, `createFlowContext({ ocr })`,
514
+ or a hand-written test double) must set `available` explicitly as a plain data
515
+ property; the SDK's own clients do, and `bindOcrTelemetry` preserves it.
219
516
 
220
517
  ### Health journey DX for SMS/payment flows
221
518
 
@@ -273,15 +570,17 @@ const paymentWebviewJourney = defineHealthJourney({
273
570
  ],
274
571
  });
275
572
 
276
- export default defineProvider({
573
+ const buildProvider = defineProvider({
277
574
  id: "example-provider",
278
- // ...metadata, auth, operations, allowedHosts
575
+ // ...metadata, auth, allowedHosts
279
576
  healthJourneys: [paymentWebviewJourney],
280
577
  });
578
+
579
+ export default buildProvider({ operations });
281
580
  ```
282
581
  <!-- @magic-end:sample -->
283
582
 
284
- The journey runner supplies `ctx.gateway`, `ctx.sms.waitForOtp()`, `ctx.journal.sideEffect()`, `ctx.state`, and `ctx.event.operation()` to the optional journey `run` function. Provider authors should keep `run` small: call the covered operations in step order, stop at the declared safe boundary, and let the generated health metadata carry schedule, timeout, required secret, and SMS matcher information to the health monitor.
583
+ The journey runner supplies `ctx.gateway`, `ctx.sms.waitForOtp()`, `ctx.journal.sideEffect()`, `ctx.state`, and `ctx.event.operation()` to the required journey `run` function. Provider authors should keep `run` small: call the covered operations in step order, stop at the declared safe boundary, and let the generated health metadata carry schedule, timeout, required secret, and SMS matcher information to the health monitor.
285
584
 
286
585
  For authenticated journeys, open a fresh connection inside `run` with `ctx.gateway.connect({ input: { ... } })`, execute covered operations with the returned `connectionId`, and disconnect in a `finally` block. Do not require or store long-lived `HEALTH_MONITOR_*_CONNECTION_ID` secrets; those stale connection IDs can hide broken login ceremonies.
287
586
 
@@ -309,6 +608,287 @@ External contributors are expected to submit standalone Provider source plus:
309
608
  Maintainers own monorepo import under `providers/<id>/`, registry generation,
310
609
  deployment projection checks, and release workflows.
311
610
 
611
+ ### Error responses
612
+
613
+ Provider-server failures use a stable public envelope:
614
+
615
+ ```json
616
+ {
617
+ "error": {
618
+ "code": "UPSTREAM_ERROR",
619
+ "message": "The upstream service failed",
620
+ "requestId": "req_123",
621
+ "retryable": true,
622
+ "details": { "providerReason": "temporarily_unavailable" }
623
+ }
624
+ }
625
+ ```
626
+
627
+ `retryable` is always present on responses emitted by the current SDK. Set
628
+ `retryable` in the `ProviderError` options when the provider knows the answer;
629
+ an explicit `true` or `false` wins over the matching operation declaration and
630
+ SDK derivation. When it is omitted, `operations.<id>.errorCodes[].retryable`
631
+ is used for a matching provider-owned code, followed by SDK derivation (which
632
+ defaults ordinary `ProviderError` values to `false`). During stateful rolling
633
+ upgrades, the forwarding client also accepts an older owner response that omits
634
+ `retryable` and treats it as `false` without loosening the emitted response
635
+ contract. Existing optional `fix` guidance is also preserved when a
636
+ `ProviderError` supplies it.
637
+
638
+ `details` belongs exclusively to the provider. The server passes
639
+ `ProviderError.options.details` through verbatim, including strings and arrays,
640
+ and never merges, overwrites, or wraps it. Do not put SDK taxonomy fields there.
641
+ SDK-owned validation and masked-internal-error paths retain their own diagnostic
642
+ details.
643
+
644
+ SDK observability is emitted separately in the
645
+ `X-ApiFuse-Error-Observability` response header as compact, single-line JSON:
646
+
647
+ ```json
648
+ {"category":"upstream_http","taxonomyVersion":"2026-05-26","retryable":true,"upstreamStatus":502}
649
+ ```
650
+
651
+ Treat this header as telemetry, not as provider-controlled public error detail.
652
+ It is SDK-owned even when a handler returns a raw `Response`: a caller-set
653
+ header is dropped, and a raw status of 400 or above gets a status-derived one
654
+ (retryable only for 408, 429, and 5xx) while the body is left untouched.
655
+ Its category, taxonomy version, retryability, and optional upstream status match
656
+ the structured `provider_request_failed` log event.
657
+
658
+ Declare provider-owned operation failures directly on the operation. The
659
+ server builds a lookup once at startup and applies it to failures from that
660
+ operation:
661
+
662
+ ```ts
663
+ errorCodes: [{
664
+ code: "ITEM_SOLD_OUT",
665
+ status: 409,
666
+ retryable: false,
667
+ description: "The upstream sold the item before checkout completed.",
668
+ }],
669
+ handler: async () => {
670
+ throw new ProviderError("Item sold out", { code: "ITEM_SOLD_OUT" });
671
+ },
672
+ ```
673
+
674
+ Declare only codes the SDK does not already register. `UPSTREAM_SCHEMA_ERROR`,
675
+ `UPSTREAM_AUTH_ERROR`, and `INVALID_REQUEST` are registered below, so throwing
676
+ them needs no declaration at all.
677
+
678
+ `defineProvider` accepts only statuses the server can emit: 400, 401, 404, 429,
679
+ 500, 502, 503, and 504. Invalid declared statuses fail provider definition,
680
+ not a live request. Status selection uses this order:
681
+
682
+ 1. SDK-owned errors retain SDK status semantics. Operation declarations cannot
683
+ override SDK-owned codes, stateful-forwarding failures, Zod/deadline errors,
684
+ or `TransportError` values.
685
+ 2. A matching operation `errorCodes` entry with `status` supplies the status.
686
+ This slot applies to `ValidationError` as well as ordinary `ProviderError`.
687
+ 3. The registered mappings below apply.
688
+ 4. Existing fallbacks apply: `TransportError` 502/504, unregistered input
689
+ `ValidationError` 400 (output validation 500), and other unregistered
690
+ `ProviderError` values 500.
691
+
692
+ The registered mappings are:
693
+
694
+ | Error code or fallback | HTTP status |
695
+ | --- | ---: |
696
+ | `AUTH_REQUIRED`, `reauth_required` | 401 |
697
+ | `MISSING_SECRET`, `UPSTREAM_AUTH_ERROR`, `INVALID_REQUEST` | 400 |
698
+ | `NOT_FOUND`, `not_found`, `NO_DATA` | 404 |
699
+ | `RATE_LIMITED`, `UPSTREAM_RATE_LIMIT`, `LIMITED_NUMBER_OF_SERVICE_REQUESTS_EXCEEDS_ERROR` | 429 |
700
+ | `UPSTREAM_ERROR`, `BLOCKED`, `UPSTREAM_SCHEMA_ERROR` | 502 |
701
+ | `STT_UNAVAILABLE`, `UNSUPPORTED_STT_BACKEND`, `STATEFUL_FORWARDING_REPLAY_CACHE_FULL` | 503 |
702
+ | Unregistered input `ValidationError` code | 400 |
703
+ | Other unregistered `ProviderError` code | 500 |
704
+
705
+ A registered code that names an upstream outcome also carries a category. When
706
+ a `ProviderError` (or any subclass, `ValidationError` included) with one of
707
+ these codes sets no `category`, the server reports the code's category in
708
+ `X-ApiFuse-Error-Observability`, and the public `source` follows from it:
709
+
710
+ | Error code | Category when unset | `source` |
711
+ | --- | --- | --- |
712
+ | `NOT_FOUND`, `not_found`, `NO_DATA` | `upstream_http` | `upstream_failure` |
713
+ | `RATE_LIMITED`, `UPSTREAM_RATE_LIMIT`, `LIMITED_NUMBER_OF_SERVICE_REQUESTS_EXCEEDS_ERROR` | `upstream_rate_limited` | `upstream_rule` |
714
+ | `UPSTREAM_REJECTED` | `upstream_rejected` | `upstream_rule` |
715
+ | `UPSTREAM_ERROR` | `upstream_http` | `upstream_failure` |
716
+ | `BLOCKED` | `anti_bot_blocked` | `upstream_failure` |
717
+ | `UPSTREAM_AUTH_ERROR` | `upstream_auth` | `upstream_failure` |
718
+ | `UPSTREAM_SCHEMA_ERROR` | `upstream_schema_drift` | `upstream_failure` |
719
+
720
+ So `throw new ProviderError("…", { code: "UPSTREAM_SCHEMA_ERROR" })` is enough
721
+ for the health monitor to grade a drifted upstream `down`; without the
722
+ category it would be served as `source: "apifuse"` and graded unknown. An
723
+ explicit `category` always wins, an operation-declared rejection status
724
+ (409/410/422) still classifies as `upstream_rejected`, and retryability does
725
+ not follow the category: it stays the instance option, then the declaration,
726
+ then `false`. Every other code, including `AUTH_REQUIRED`, `INVALID_REQUEST`,
727
+ and your own codes, is classified as before (`provider_error` unless you set a
728
+ category).
729
+
730
+ An unregistered non-validation `ProviderError` code returns HTTP 500 and emits
731
+ the greppable `unregistered_provider_error_code` signal with the code in the
732
+ structured failure log. A matching operation declaration, including one that
733
+ omits `status`, makes the code registered for this signal and may independently
734
+ supply `retryable`. The HTTP 400 `ValidationError` behavior is only the fallback
735
+ when neither an operation status nor a registered mapping applies.
736
+
737
+ Throw the domain `ProviderError` directly. Subclassing or wrapping it as a
738
+ `TransportError` solely to preserve a 5xx response is obsolete; declare the
739
+ domain code's `status` instead. Genuine `TransportError` values remain
740
+ SDK-owned and keep their 502/504 mapping.
741
+
742
+ #### Three fleet-consensus codes are registered
743
+
744
+ `UPSTREAM_AUTH_ERROR` (400), `UPSTREAM_SCHEMA_ERROR` (502), and
745
+ `INVALID_REQUEST` (400) are registered mappings. All three are non-retryable.
746
+
747
+ - `UPSTREAM_AUTH_ERROR` is 400, not 401 or 502, because it means the upstream
748
+ refused a *platform-managed* service key. The caller holds no credential, so
749
+ 401 ("re-authenticate") asks for something the caller cannot do, and 502
750
+ ("the upstream is sick") promises a recovery that will never arrive. It is a
751
+ deployment/config defect, exactly like `MISSING_SECRET`. The status is about
752
+ what the caller can do; the attribution is still the upstream's, since the
753
+ upstream is what refused the key, so the category is `upstream_auth`.
754
+ - `UPSTREAM_SCHEMA_ERROR` is non-retryable: a retry returns the same payload
755
+ the provider already could not normalize.
756
+ - `INVALID_REQUEST` is the spelling for caller-side bad input. `INVALID_INPUT`,
757
+ `VALIDATION_ERROR`, and `BAD_REQUEST` are not registered.
758
+
759
+ Like `UPSTREAM_ERROR` and `BLOCKED`, these are thrown by provider code rather
760
+ than by the SDK, so they are registered mappings and not SDK-owned codes: an
761
+ existing operation declaration still wins at runtime (step 2 above). Registering
762
+ them therefore never rewrites a status you already declared. What it does do is
763
+ let you delete the declaration — and when your declaration disagrees with the
764
+ registered mapping, `apifuse check` reports it rather than leaving the fleet
765
+ answering one code two ways:
766
+
767
+ - `error-code-status-conflicts-sdk` — declared `status` differs from the
768
+ registered status.
769
+ - `error-code-retryable-conflicts-sdk` — declared `retryable` differs from the
770
+ registered retryability.
771
+
772
+ Both are warnings. Drop the conflicting field so the SDK supplies the value, or,
773
+ if your operation genuinely means something else, throw a distinct code that
774
+ says so.
775
+
776
+ ### Localized error messages (`messageKey` / `fixKey`)
777
+
778
+ The positional `ProviderError` message stays **English**. It is what
779
+ `error.message`, cause frames, the `provider_request_failed` log, OTLP
780
+ attributes and tests see. Only two fields of the response body are localized:
781
+ `error.message` and `error.fix`. Nothing else — not `code`, not `details`, not
782
+ `errorCodes[].description`, not the `X-ApiFuse-Error-Observability` header.
783
+
784
+ Attach locale keys instead of parsing `Accept-Language` yourself:
785
+
786
+ ```ts
787
+ // locales/en.json
788
+ { "errors": { "upstreamSchema": {
789
+ "message": "{provider} returned an unexpected response.",
790
+ "fix": "Retry in a few minutes."
791
+ } } }
792
+ ```
793
+
794
+ ```ts
795
+ throw new ProviderError("The culture data service returned an unexpected response.", {
796
+ code: "UPSTREAM_SCHEMA_ERROR",
797
+ messageKey: "errors.upstreamSchema.message",
798
+ fixKey: "errors.upstreamSchema.fix",
799
+ params: { provider: "Culture Data" },
800
+ });
801
+ ```
802
+
803
+ Keys can also live on the declaration, which localizes **every** site that
804
+ throws that code without touching the throw sites:
805
+
806
+ ```ts
807
+ errorCodes: [{
808
+ code: "UPSTREAM_SCHEMA_ERROR",
809
+ description: "The upstream response no longer matches its schema.",
810
+ messageKey: "errors.upstreamSchema.message",
811
+ fixKey: "errors.upstreamSchema.fix",
812
+ }],
813
+ ```
814
+
815
+ `status` and `retryable` are omitted because `UPSTREAM_SCHEMA_ERROR` is a
816
+ registered code: the SDK already supplies 502 and non-retryable, and declaring
817
+ a different value trips `error-code-status-conflicts-sdk` /
818
+ `error-code-retryable-conflicts-sdk`. A declaration that carries only keys is
819
+ still worth writing — it localizes every site that throws the code.
820
+
821
+ Resolution order per field, evaluated at serve time:
822
+
823
+ 1. the throw site's `messageKey` / `fixKey`;
824
+ 2. the matching `errorCodes[]` entry's `messageKey` / `fixKey`;
825
+ 3. the derived `errors.<code>.message` / `errors.<code>.fix` (the code is used
826
+ verbatim as a catalog segment, so `errors.UPSTREAM_SCHEMA_ERROR.message`
827
+ works with no declaration at all);
828
+ 4. the English literal passed to the constructor.
829
+
830
+ Each candidate is resolved in the caller's locale first and then in `en`, so a
831
+ more specific key always beats a more specific locale. A missing key, a
832
+ malformed key, a non-string catalog value or an absent catalog is a miss, never
833
+ a throw: the caller always gets text. Steps 2 and 3 do not apply to SDK-owned
834
+ failures (transport, Zod, stateful deadline, SDK runtime codes) — a provider
835
+ catalog must not be able to relabel `Request timed out`.
836
+
837
+ **Key grammar.** A locale key is dot-separated segments. Every segment is
838
+ camelCase (`upstreamSchema`, `whenToUse`) or an array index (`0`), and the
839
+ first segment is always a camelCase namespace. The one exception is the segment
840
+ directly under `errors`, which may also be the error code spelled the way it is
841
+ thrown — `errors.UPSTREAM_SCHEMA_ERROR.message`, `errors.BLOCKED.fix`,
842
+ `errors.reauth_required.message`. Both spellings work, so
843
+ `errors.upstreamSchema.message` and `errors.UPSTREAM_SCHEMA_ERROR.message` are
844
+ equally legal keys; write the code form when you want the key to match the
845
+ derived path in step 3. Nothing else loosens: a space, a dot inside a segment,
846
+ an empty segment, a mixed-case or hyphenated code, and a leading digit are all
847
+ still malformed.
848
+
849
+ Locale negotiation reads `Accept-Language` from the request envelope's
850
+ `headers` map first (the gateway stamps the flow's locale there) and then from
851
+ the HTTP header, honours quality values, matches on the primary subtag, and
852
+ supports `en`, `ko`, `ja` with `en` as the default. A request with no
853
+ `Accept-Language` therefore keeps serving the `en` catalog value, so existing
854
+ contract fixtures do not churn.
855
+
856
+ **Interpolation.** `{name}` placeholders, `{{` and `}}` for literal braces.
857
+ `params` accepts strings and finite numbers only. A placeholder with no usable
858
+ param is left verbatim so the authoring gap is visible rather than silently
859
+ deleting text.
860
+
861
+ **Params are untrusted.** Upstream error text routinely ends up in `params`, so
862
+ every string value is scrubbed before substitution: secrets and e-mail
863
+ addresses redacted, control/bidi/newline characters encoded or collapsed,
864
+ `<`, `>` and backticks removed, HTML-entity ampersands (`&lt;`) neutralized
865
+ while ordinary ampersands survive, and the value capped at 200 characters.
866
+ Substituted text is never rescanned, so a value containing `{other}` cannot
867
+ trigger a second interpolation round. Prefer `details` when you need to relay
868
+ an upstream body verbatim; `params` is for short, bounded values.
869
+
870
+ **Catalog loading.** Catalogs are read once when the server is built (from the
871
+ provider directory's `locales/{en,ko,ja}.json`, loading only the locales that
872
+ exist). Pass `serve(provider, { localeCatalogs })` to bundle them instead. A
873
+ catalog that exists but cannot be parsed logs one
874
+ `provider_locale_catalogs_unavailable` warn at boot and every field falls back
875
+ to its English literal.
876
+
877
+ **Lint.** `apifuse check` reports:
878
+
879
+ - `error-locale-key-missing` / `error-locale-key-malformed` (**error**) — a
880
+ literal `messageKey`/`fixKey`, on a throw site or on an `errorCodes[]` entry,
881
+ that `locales/en.json` does not resolve to non-empty text, or that is not a
882
+ legal locale dot path (see **Key grammar** above).
883
+ - `thrown-error-message-not-localized` (**warn**) — a throw site whose declared
884
+ code has no key anywhere and no `errors.<code>.message` in `en`, so it is
885
+ served untranslated. Warning during the migration; promoted to error per
886
+ provider as each wave lands.
887
+
888
+ `ko`/`ja` coverage needs no new rule: `en` is the baseline, so the existing
889
+ catalog parity and placeholder checks already fail an `errors.*` key that is
890
+ missing or untranslated in `ko`/`ja`.
891
+
312
892
  ### Declared secrets are SDK-enforced
313
893
 
314
894
  Environment/secret presence validation is single-sourced in the SDK. Declare
@@ -327,12 +907,12 @@ secrets: [
327
907
  The runtime validates every `required: true` declaration before any operation
328
908
  handler or auth-flow handler (except `abort`) runs. When a required secret is
329
909
  unset or whitespace-only, the invocation fails with the canonical structured
330
- error — code `MISSING_SECRET`, HTTP 400, `details.category:
331
- "credential_unavailable"`, `retryable: false`, and a `fix` naming every missing
332
- secret — across `/v1/{operation}`, self-test probes, `apifuse perf`, and
333
- `apifuse record`. The server also emits a `provider_secrets_missing` warn log
334
- at boot so unprovisioned deployments are visible immediately without crashing
335
- the pod.
910
+ error — code `MISSING_SECRET`, HTTP 400, top-level `retryable: false`, and a
911
+ `fix` naming every missing secret — across `/v1/{operation}`, self-test probes,
912
+ `apifuse perf`, and `apifuse record`. Its error-observability header carries the
913
+ `credential_unavailable` category. The server also emits a
914
+ `provider_secrets_missing` warn log at boot so unprovisioned deployments are
915
+ visible immediately without crashing the pod.
336
916
 
337
917
  Provider-local presence re-validation is **deprecated**: do not write
338
918
  `requireServiceKey`/`requireApiKey`-style guards that re-check `ctx.env.get()`
@@ -362,6 +942,157 @@ Note the asymmetry: the gate treats whitespace-only values as missing, but
362
942
  `ctx.env.get()` still returns the raw value to handlers — trim at the point of
363
943
  use if the upstream is whitespace-sensitive.
364
944
 
945
+ ### Runtime boundary: no `process.env`, `node:fs`, `child_process`, or raw `fetch()`
946
+
947
+ Provider runtime code reaches the outside world through the context only:
948
+ `ctx.http` / `ctx.stealth` for HTTP, `ctx.browser` for browser work,
949
+ `ctx.env.get()` / `ctx.credential` for configuration and secrets, `ctx.files` /
950
+ `ctx.cache` / `ctx.state` for data that must outlive a request. Ambient Node
951
+ and Bun globals bypass `allowedHosts`, proxy policy, retries, redaction, the
952
+ secret presence gate, and telemetry. `apifuse check` reports them at **error**
953
+ level — a finding fails `apifuse check` and `apifuse submit-check`, in the
954
+ official and the bounty `standalone` mode alike (the level was `warn` for one
955
+ release as a migration window; the window closed once the fleet's request-path
956
+ hits were fixed or acknowledged — a provider that still carries one sees
957
+ `apifuse check` fail on its next SDK bump and fixes or acknowledges it in that
958
+ same PR):
959
+
960
+ - `process-env-direct-read` — `process.env.X`, `process.env["X"]`, a bare
961
+ `process.env` object use, `Bun.env`. The `APIFUSE__RUNTIME__*` bootstrap
962
+ family (`APIFUSE__RUNTIME__PORT`, `…POD_ID`, `…POD_ENDPOINT`) is exempt
963
+ everywhere; every other value is declared in `defineProvider` under
964
+ `secrets: [{ name, required }]` and read with `ctx.env.get(name)`. The names
965
+ visible to `ctx.env.get` are exactly the declared `secrets[].name` (minus
966
+ engine-owned names; an `oauth2_proxied` auth flow additionally sees
967
+ `APIFUSE__AUTH_PROXY__URL`). `env: true` (or `env: {}`) is the capability
968
+ declaration that turns the `ctx.env` binding on — a type *and* runtime gate,
969
+ since touching `ctx.env` without it throws `PROVIDER_CAPABILITY_UNDECLARED`
970
+ — and does not widen the readable names. Test/E2E toggles are not secrets
971
+ and cannot move to `ctx.env`: replace them with test doubles (inject
972
+ `ctx.env` / `ctx.http`) or recorded fixtures.
973
+ - `node-runtime-module-import` — value imports of `fs`, `fs/promises`, `net`,
974
+ `tls`, `dgram`, `http`, `https`, `http2`, `child_process` (with or without
975
+ `node:`), and `Bun.spawn` / `Bun.spawnSync` / ``Bun.$` ` `` / `Bun.file` /
976
+ `Bun.write`. Type-only imports (`import type`, or every binding inline `type`) are ignored; `node:path`, `node:crypto`,
977
+ `node:url` are fine.
978
+ - `direct-fetch-call` — the global `fetch()` (also `globalThis.fetch`).
979
+ `ctx.stealth.fetch()` never matches, nor does a call to a `fetch` binding
980
+ declared in an enclosing scope (parameter, variable, import, catch clause).
981
+ The check is lexical: a wrapper that takes `fetch` as a parameter does not
982
+ hide a global `fetch()` in another function of the same file.
983
+
984
+ Scope is runtime source only: tests, recorded fixtures, `.d.ts`, the root
985
+ `dev.ts` / `start.ts` / `deploy.ts` entrypoints, and the `scripts/`, `tools/`,
986
+ `bin/` directories at the provider root are not scanned. Keep fixture
987
+ recorders, smoke scripts, and other operator tooling under `scripts/`, and do
988
+ not import from there in request-path modules. `apifuse check` also leaves out
989
+ any directory that is itself a git checkout (a linked worktree such as
990
+ `.worktree/<branch>/`, a nested clone, an initialised submodule): its files are
991
+ another revision, checked in that checkout, not this provider's runtime source.
992
+
993
+ Acknowledge a deliberate exception on the finding line or the comment-only
994
+ line above it with `// @apifuse-allow <rule>: <reason>`; the finding then
995
+ appears as an `INFO` audit line instead of an error, and the check passes.
996
+
997
+ ```ts
998
+ // Before: request-path module reads the ambient environment and the disk
999
+ import { mkdir, writeFile } from "node:fs/promises";
1000
+ const serviceKey = process.env.EXAMPLE_SERVICE_KEY;
1001
+ if (process.env.EXAMPLE_DIAG_TAP_DIR) {
1002
+ await writeFile(`${process.env.EXAMPLE_DIAG_TAP_DIR}/booking.json`, JSON.stringify(response));
1003
+ }
1004
+
1005
+ // After: the credential is a declared secret read through the context —
1006
+ // defineProvider({
1007
+ // env: true, // turns the ctx.env capability on (type + runtime gate)
1008
+ // secrets: [{ name: "APIFUSE__PROVIDER__EXAMPLE__SERVICE_KEY", required: true }], // the readable names
1009
+ // })
1010
+ // — and the ad-hoc diagnostic tap is deleted: a toggle that is not a secret has
1011
+ // no place in ctx.env; observability goes through ctx.trace instead.
1012
+ const serviceKey = ctx.env.get("APIFUSE__PROVIDER__EXAMPLE__SERVICE_KEY");
1013
+ const response = await ctx.trace.span("booking.confirm", () => confirmBooking(ctx, serviceKey));
1014
+ ```
1015
+
1016
+ ### Credentials forced into query parameters
1017
+
1018
+ Prefer an authorization header or request body whenever the upstream supports
1019
+ one. When the upstream requires a credential in the URL query (for example
1020
+ `serviceKey`, `confmKey`, or `crtfc_key`), use `sensitiveParams`:
1021
+
1022
+ ```ts
1023
+ const response = await ctx.http.get("/openapi/lookup", {
1024
+ params: { pageNo: 1, numOfRows: 100 },
1025
+ sensitiveParams: {
1026
+ serviceKey: ctx.env.get("APIFUSE__PROVIDER__EXAMPLE__SERVICE_KEY")!,
1027
+ },
1028
+ });
1029
+ ```
1030
+
1031
+ `sensitiveParams` is merged into the outgoing query like `params`, while its
1032
+ values are redacted from SDK transport errors, traces, and `apifuse record`
1033
+ fixtures. Do not put query credentials in `params`, and do not hand-build a URL
1034
+ containing a key; those paths cannot declare which query values are secret.
1035
+
1036
+ #### Residual risks
1037
+
1038
+ Redaction is unconditional in structural positions (declared query keys and
1039
+ exact scalar fixture/error fields) for values of every length. In unstructured
1040
+ free text, values of four or more characters are replaced as substrings; shorter
1041
+ values are replaced only at token boundaries to avoid corrupting unrelated text
1042
+ (for example, a secret `api` must not rewrite `rapid`). The residual risk is that
1043
+ a sub-four-character secret embedded directly inside a larger alphanumeric token
1044
+ can remain in free text. Prefer a higher-entropy credential, or a header/body
1045
+ credential channel, whenever the upstream permits it. An empty
1046
+ `sensitiveParams: {}` is treated exactly as if the option were omitted.
1047
+
1048
+ For `session.redirects.run()`, returned hop URLs are diagnostic metadata and
1049
+ therefore keep declared query values and common response-only credential keys
1050
+ redacted. If a login flow must consume a rotated credential from `Location`,
1051
+ inspect it inside `stopWhen`; that callback receives the real hop while callback
1052
+ failures are sanitized before propagation.
1053
+
1054
+ ### Request-bound proof headers
1055
+
1056
+ When an upstream requires a single-use, request-bound header (DPoP proofs,
1057
+ HTTP message signatures, HMAC nonces), pass `headers` as a factory instead of a
1058
+ record. `ctx.http` calls it once per issued attempt with the resolved URL and
1059
+ method, so a managed retry sends a fresh proof rather than replaying the first
1060
+ one, and the request keeps proxy failover and the transient retry taxonomy:
1061
+
1062
+ ```ts
1063
+ const response = await ctx.http.get(requestUrl, {
1064
+ headers: async ({ url, method }) => {
1065
+ // RFC 9449 §4.2: `htu` is the target URI without query and fragment.
1066
+ const htu = new URL(url);
1067
+ htu.search = "";
1068
+ htu.hash = "";
1069
+ return {
1070
+ ...commonHeaders,
1071
+ dpop: await createDpopProof(htu.toString(), method),
1072
+ };
1073
+ },
1074
+ });
1075
+ ```
1076
+
1077
+ `url` is the exact URL the attempt is issued against, including `params` and
1078
+ `sensitiveParams`. Bind to it instead of a URL you rebuild, but strip the query
1079
+ and fragment for fields that are defined without them (DPoP `htu`), and never
1080
+ log it or copy it verbatim into a signed field a verifier logs.
1081
+
1082
+ Keep the factory fast and local. It runs after proxy resolution, so an already
1083
+ allocated egress endpoint and its lease are held while it is awaited; a remote
1084
+ signing round trip there can outlive the lease TTL. Mint locally, and set
1085
+ `timeout` on the call so a hung signer cannot hold the endpoint indefinitely.
1086
+
1087
+ The factory is not invoked for redirect hops (they reuse the attempt's
1088
+ headers), for skipped duplicate proxy offsets, or for an attempt whose proxy
1089
+ allocation failed — those build no request, so `attempt` counts only the
1090
+ requests that are actually issued. A factory that throws fails the request with
1091
+ the non-retryable `TransportError` code `http_header_factory_failed`, which is
1092
+ classified as a provider-side fault (`provider_error`), not an upstream
1093
+ failure. `ctx.stealth` has its own `headers` option and does not accept a
1094
+ factory.
1095
+
365
1096
  ### Public local debugging checklist
366
1097
 
367
1098
  - Operation smoke requests use the provider server envelope:
@@ -379,7 +1110,7 @@ use if the upstream is whitespace-sensitive.
379
1110
  HTML, or upstream `Error` objects.
380
1111
 
381
1112
  ```ts
382
- export default defineProvider({
1113
+ const buildProvider = defineProvider({
383
1114
  id: "example-provider",
384
1115
  version: "1.0.0",
385
1116
  runtime: "standard",
@@ -413,8 +1144,10 @@ export default defineProvider({
413
1144
  },
414
1145
  },
415
1146
  credential: { keys: ["cookie"] },
416
- // ...metadata and operations
1147
+ // ...metadata
417
1148
  });
1149
+
1150
+ export default buildProvider({ operations });
418
1151
  ```
419
1152
 
420
1153
  - Credentials auth providers should use `defineCredentialsAuth()` instead of
@@ -441,15 +1174,17 @@ const credentialsAuth = defineCredentialsAuth({
441
1174
  },
442
1175
  });
443
1176
 
444
- export default defineProvider({
1177
+ const buildProvider = defineProvider({
445
1178
  id: "example-provider",
446
1179
  version: "1.0.0",
447
1180
  runtime: "standard",
448
1181
  auth: credentialsAuth.auth,
449
1182
  credential: credentialsAuth.credential,
450
1183
  context: credentialsAuth.context,
451
- // ...metadata and operations
1184
+ // ...metadata
452
1185
  });
1186
+
1187
+ export default buildProvider({ operations });
453
1188
  ```
454
1189
 
455
1190
  For OTP, MFA, CAPTCHA handoff, or user-approved login, return a challenge from
@@ -509,14 +1244,171 @@ const credentialsAuth = defineCredentialsAuth({
509
1244
  request's `context`.
510
1245
  - Stealth/browser providers may require local runtime setup outside Provider code:
511
1246
  keep access-sensitive operations on `ctx.stealth.fetch()` with an SDK stealth
512
- `profile`; the TypeScript runtime uses `impit` behind that interface, so do
1247
+ `profile`; the TypeScript runtime uses `wreq-js` behind that interface, so do
513
1248
  not add per-operation JA3, HTTP/2 SETTINGS, or pseudo-header tuning. `ctx.stealth`
514
- supports Chrome/Firefox-style profiles; use `browser.engine:
515
- "playwright-stealth"` for Safari-specific or real browser Providers
516
- (`nodriver` is Python-runtime only); install local browser assets with
1249
+ supports Chrome, Firefox, and Safari profiles; use `ctx.browser` when a
1250
+ Provider needs real browser execution. TypeScript browser Providers use
1251
+ `browser.engine: "playwright-stealth"` (`nodriver` is Python-runtime only);
1252
+ install local browser assets with
517
1253
  `bunx playwright install chromium`, or set
518
1254
  `APIFUSE__CDP_POOL__URL` for remote browser debugging.
519
1255
 
1256
+ ### Native gateway adapter migration
1257
+
1258
+ Native proxy resolution supports both HTTP CONNECT and SOCKS5 without
1259
+ terminating origin TLS. The built-in vendor order follows `proxy.providers`
1260
+ exactly: `smartproxy` means the `api.smartproxy.org` allocation vendor (raw
1261
+ `ip:port` endpoints), while `nodemaven` is the credentialed gateway. It is not
1262
+ the company formerly called Smartproxy; that separate company is represented
1263
+ by the deprecated `decodo` name.
1264
+
1265
+ Custom `gatewaySynthesizers` must now accept the selected `protocol` and the
1266
+ injected `credentials` resolver on `NativeGatewayProxySynthesisInput`.
1267
+ Synthesizers may be async because allocation vendors perform network I/O. They
1268
+ may return a proxy, `undefined` when they do not implement the offered vendor,
1269
+ or `{ kind: "skipped", reason }` so an exhausted required chain can explain an
1270
+ absent credential, unsupported protocol, or allocation failure. Accordingly,
1271
+ `resolveNativeGatewayProxy(...)` must now be awaited.
1272
+
1273
+ Callers that do not supply custom synthesizers or credentials keep env-backed
1274
+ behavior. Hosts that already have an allowlisted `EnvContext` can inject it
1275
+ explicitly, and vault-backed or per-tenant hosts can supply their own resolver:
1276
+
1277
+ ```ts
1278
+ import {
1279
+ createEnvVendorCredentialResolver,
1280
+ createNativeNetworkClient,
1281
+ } from "@apifuse/provider-sdk";
1282
+
1283
+ const network = createNativeNetworkClient({
1284
+ proxyPolicy: { mode: "required", providers: ["smartproxy", "nodemaven"] },
1285
+ credentials: createEnvVendorCredentialResolver(ctx.env),
1286
+ // Optional advanced override; omit for each vendor's default.
1287
+ proxyProtocol: "socks5",
1288
+ });
1289
+ ```
1290
+
1291
+ Never include credential values in adapter skip messages or thrown errors. The
1292
+ SDK redacts built-in proxy URL userinfo, CONNECT authentication, and allocator
1293
+ causes, but a custom adapter remains responsible for not publishing secrets in
1294
+ its own diagnostics.
1295
+
1296
+ ### Limiting stealth response bodies
1297
+
1298
+ Set `maxBodyBytes` on `ctx.stealth.fetch()` or `session.redirects.run()` when an
1299
+ upstream response has a known safe maximum. The limit is opt-in and counts
1300
+ decoded bytes as `wreq-js` streams them. It applies to every redirect hop, uses a
1301
+ parseable `Content-Length` for an early rejection, and still enforces the limit
1302
+ incrementally when the header is absent or inaccurate. Exceeding the limit
1303
+ aborts the response and throws a non-retryable `TransportError` with code
1304
+ `response_too_large`.
1305
+
1306
+ Pass the limit to the transport instead of checking `Content-Length` in provider
1307
+ code:
1308
+
1309
+ ```ts
1310
+ const response = await ctx.stealth.fetch("/api/search", {
1311
+ params: { query: input.query },
1312
+ maxBodyBytes: 2 * 1024 * 1024,
1313
+ })
1314
+ ```
1315
+
1316
+ ### Persisting stealth session cookies
1317
+
1318
+ Persist `session.cookies.serialize()` as JSON when an authenticated session must
1319
+ survive a restart or move to another replica. The returned
1320
+ `StealthCookieStoreV1` has an explicit version and retains every cookie together
1321
+ with its Domain, Path, Secure, expiry, host-only, and other cookie attributes.
1322
+ Restore it with `session.cookies.deserialize()`. Unsupported future versions
1323
+ fail explicitly instead of being accepted as a partial cookie jar.
1324
+
1325
+ Credential values are strings, so stringify the store at the credential
1326
+ boundary and parse it when rebuilding the session:
1327
+
1328
+ ```ts
1329
+ // After login (including any redirects across sibling hosts):
1330
+ const result = await session.redirects.run({ url: loginUrl });
1331
+ return {
1332
+ credential: {
1333
+ cookieStore: JSON.stringify(result.cookieStore),
1334
+ },
1335
+ };
1336
+
1337
+ // In a later operation or replica:
1338
+ const persisted = ctx.credential.get("cookieStore");
1339
+ if (persisted) {
1340
+ session.cookies.deserialize(JSON.parse(persisted));
1341
+ }
1342
+ ```
1343
+
1344
+ `snapshot()` and `restore()` remain only for backward compatibility with flat
1345
+ `Record<string, string>` credentials. `snapshot()` enumerates cookies across all
1346
+ hosts and paths, but the flat shape is inherently lossy: duplicate names
1347
+ collapse and Domain, Path, Secure, expiry, and host-only attributes cannot be
1348
+ represented. `restore()` therefore recreates host-only `Path=/` cookies on the
1349
+ session base origin. Do not use the flat form for new persistence code. Cookie
1350
+ headers remain origin-filtered: use `toHeader(url)` for a particular request and
1351
+ never build a request header from serialized or snapshotted persistence data.
1352
+
1353
+ ### Recording and replaying streaming responses
1354
+
1355
+ `apifuse record` passes responses returned by `ctx.http.stream()` directly to the operation
1356
+ handler while incrementally capturing a bounded preview. If the handler returns or cancels its
1357
+ reader before EOF, the recorder drains the retained upstream reader before writing a JSON evidence
1358
+ record to `__fixtures__/raw.json`. The record contains the status, success
1359
+ flag, `content-type`/`content-length`/`content-disposition` headers when present, the
1360
+ full body SHA-256 and byte count, and a base64 preview up to the configured stream preview limit.
1361
+ Textual previews are decoded and passed through the fixture sanitizer before base64 encoding.
1362
+ Classification uses both the declared content type and the preview bytes, so missing or incorrect
1363
+ content-type headers do not bypass sanitization. PEM private-key blocks and long high-entropy
1364
+ tokens in otherwise unstructured text are redacted as well. If the full preview is not valid UTF-8,
1365
+ the entire lossy-decoded preview is scanned and matching decodable byte windows are sanitized. Only a
1366
+ magic-number-confirmed binary preview with no textual-secret pattern anywhere in the preview bypasses
1367
+ sanitization; other undecodable data fails closed.
1368
+ Sanitized previews carry `preview_sanitized: true`, plus a
1369
+ `preview_redaction_reason` when capture had to fail closed. The original hash and byte count always
1370
+ describe upstream bytes, not a sanitized preview.
1371
+
1372
+ Each record includes query-free request provenance (`method`, `path`, and a one-based stream call
1373
+ ordinal). Provenance never stores the origin, URL userinfo, query, or fragment. Every retained path
1374
+ segment is scrubbed before persistence: credential-key segments, values following those keys,
1375
+ known token shapes, and long high-entropy opaque segments become `[REDACTED]`. If an operation
1376
+ opens multiple streams, the recorder finalizes every retained reader and
1377
+ writes all evidence records in stream call order. Stream invocations use a tagged capture envelope
1378
+ whose items distinguish stream evidence from ordinary JSON responses. Evidence-only snapshot replay
1379
+ consumes that exact call order and fails immediately when evidence is exhausted or a call kind is
1380
+ reordered. When request provenance is present, replay also rejects method or path changes (relative
1381
+ URLs are resolved against the recorded path prefix); ordinals remain diagnostic and are not matched.
1382
+ Appended fixtures replay a stream envelope only when it is the latest invocation. SSE
1383
+ recording remains unsupported and fails explicitly instead of retaining an unrelated earlier
1384
+ response.
1385
+
1386
+ #### Residual risks
1387
+
1388
+ - Credential-path sanitization decodes each URL path segment once. Double-encoded separators or
1389
+ values such as `%252F` are not decoded recursively, so they can conceal a credential-shaped
1390
+ segment from the recorder. This single-pass policy keeps path handling deterministic and avoids
1391
+ interpreting ambiguous or intentionally layered encodings differently from the upstream. Never
1392
+ place credentials in URL paths, and review recorded provenance before committing fixtures.
1393
+ - Primitive strings embedded in prose are redacted only when they match the current PEM,
1394
+ credential-assignment, known-token, or entropy heuristics. Other secret formats can remain because
1395
+ blanket redaction of ordinary strings would destroy useful fixture content and create broad false
1396
+ positives. Keep secrets under credential-named structured fields where possible and manually
1397
+ inspect sanitized fixture text before committing it.
1398
+
1399
+ Stream fixture replay in `runStandardTests(..., { snapshot: true })` is evidence-only:
1400
+ `ctx.http.stream()` returns a usable stream containing exactly the recorded preview,
1401
+ not a fabricated full body. The replay response also carries runtime metadata
1402
+ `evidence_only: true`, `body_sha256`, `body_bytes`, and the optional preview sanitization fields
1403
+ for assertions about the original capture. Do not assert that the replay body hashes to
1404
+ `body_sha256` when `body_bytes`
1405
+ exceeds the decoded preview length or `preview_sanitized` is present; use the metadata for
1406
+ full-body integrity and limit body-content assertions to the preview.
1407
+
1408
+ Golden snapshot suites can set `requireSnapshot: true` so a missing committed snapshot fails instead
1409
+ of being created implicitly. Regenerate intentional changes with
1410
+ `bun test --update-snapshots`; review and commit the resulting `transform.snap.json` file.
1411
+
520
1412
  ### Running the pre-submission report
521
1413
 
522
1414
  ```bash