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

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 (342) hide show
  1. package/AUTHORING.md +493 -5
  2. package/CHANGELOG.md +131 -1
  3. package/README.md +52 -6
  4. package/SUBMISSION.md +1 -1
  5. package/bin/apifuse-check.ts +106 -62
  6. package/bin/apifuse-create.ts +1 -1
  7. package/bin/apifuse-dev.ts +63 -55
  8. package/bin/apifuse-pack-check.ts +22 -2
  9. package/bin/apifuse-pack-smoke.ts +78 -82
  10. package/bin/apifuse-pack-types.ts +583 -0
  11. package/bin/apifuse-perf.ts +59 -140
  12. package/bin/apifuse-record.ts +698 -113
  13. package/bin/apifuse-submit-check.ts +517 -44
  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.ts +1 -1
  18. package/dist/auth-turn/index.d.ts +4 -4
  19. package/dist/auth-turn/index.js +1 -1
  20. package/dist/auth.d.ts +16 -2
  21. package/dist/auth.js +76 -18
  22. package/dist/ceremonies/index.d.ts +9 -1
  23. package/dist/ceremonies/index.js +117 -29
  24. package/dist/cli/commands.d.ts +1 -1
  25. package/dist/cli/commands.js +8 -0
  26. package/dist/cli/create.d.ts +3 -0
  27. package/dist/cli/create.js +34 -35
  28. package/dist/cli/prompt-assets.d.ts +80 -0
  29. package/dist/cli/prompt-assets.js +743 -0
  30. package/dist/cli/templates/provider/AGENTS.md.tpl +17 -8
  31. package/dist/cli/templates/provider/README.md.tpl +4 -4
  32. package/dist/config/loader.d.ts +176 -17
  33. package/dist/config/loader.js +434 -161
  34. package/dist/contract-serialization.d.ts +2 -2
  35. package/dist/contract-serialization.js +7 -14
  36. package/dist/contract-types.d.ts +3 -2
  37. package/dist/contract.d.ts +3 -3
  38. package/dist/contract.js +6 -6
  39. package/dist/declaration-validation.d.ts +23 -0
  40. package/dist/declaration-validation.js +159 -0
  41. package/dist/define.d.ts +13 -1
  42. package/dist/define.js +391 -122
  43. package/dist/dev.d.ts +1 -1
  44. package/dist/dev.js +1 -1
  45. package/dist/error-resolution.d.ts +4 -0
  46. package/dist/error-resolution.js +123 -0
  47. package/dist/errors.d.ts +19 -1
  48. package/dist/errors.js +41 -3
  49. package/dist/fixture-sanitization.d.ts +26 -0
  50. package/dist/fixture-sanitization.js +216 -0
  51. package/dist/i18n/catalog.d.ts +2 -2
  52. package/dist/i18n/catalog.js +4 -10
  53. package/dist/i18n/index.d.ts +2 -2
  54. package/dist/i18n/index.js +2 -2
  55. package/dist/i18n/keys.d.ts +2 -2
  56. package/dist/index.d.ts +50 -42
  57. package/dist/index.js +41 -37
  58. package/dist/lint.d.ts +6 -1
  59. package/dist/lint.js +370 -18
  60. package/dist/native-address.d.ts +43 -0
  61. package/dist/native-address.js +281 -0
  62. package/dist/native-egress-policy.d.ts +31 -0
  63. package/dist/native-egress-policy.js +288 -0
  64. package/dist/observability.d.ts +5 -2
  65. package/dist/observability.js +48 -1
  66. package/dist/provider.d.ts +13 -11
  67. package/dist/provider.js +10 -9
  68. package/dist/public-schema-field-lint.d.ts +1 -1
  69. package/dist/recipes/gov-api.js +1 -1
  70. package/dist/runtime/auth-flow.d.ts +3 -1
  71. package/dist/runtime/auth-flow.js +8 -3
  72. package/dist/runtime/browser.d.ts +1 -1
  73. package/dist/runtime/browser.js +138 -40
  74. package/dist/runtime/cache.d.ts +2 -1
  75. package/dist/runtime/cache.js +173 -23
  76. package/dist/runtime/choice-wordlist.d.ts +9 -0
  77. package/dist/runtime/choice-wordlist.js +138 -0
  78. package/dist/runtime/choice.d.ts +14 -1
  79. package/dist/runtime/choice.js +566 -101
  80. package/dist/runtime/credential.d.ts +1 -1
  81. package/dist/runtime/credential.js +1 -1
  82. package/dist/runtime/env.d.ts +1 -1
  83. package/dist/runtime/executor.d.ts +1 -1
  84. package/dist/runtime/executor.js +25 -3
  85. package/dist/runtime/http.d.ts +3 -2
  86. package/dist/runtime/http.js +517 -55
  87. package/dist/runtime/insights.d.ts +1 -1
  88. package/dist/runtime/insights.js +6 -13
  89. package/dist/runtime/instrumentation.d.ts +2 -2
  90. package/dist/runtime/instrumentation.js +371 -23
  91. package/dist/runtime/keyring.js +1 -1
  92. package/dist/runtime/namespace.js +1 -1
  93. package/dist/runtime/native-network-errors.d.ts +33 -0
  94. package/dist/runtime/native-network-errors.js +69 -0
  95. package/dist/runtime/native-network.d.ts +96 -0
  96. package/dist/runtime/native-network.js +1232 -0
  97. package/dist/runtime/ocr.d.ts +29 -0
  98. package/dist/runtime/ocr.js +440 -0
  99. package/dist/runtime/otlp.d.ts +1 -1
  100. package/dist/runtime/perf.d.ts +1 -1
  101. package/dist/runtime/provider.d.ts +1 -1
  102. package/dist/runtime/provider.js +1 -2
  103. package/dist/runtime/proxy-errors.d.ts +1 -1
  104. package/dist/runtime/proxy-errors.js +9 -7
  105. package/dist/runtime/proxy-nodemaven.d.ts +56 -0
  106. package/dist/runtime/proxy-nodemaven.js +146 -0
  107. package/dist/runtime/proxy-retry-policy.d.ts +2 -2
  108. package/dist/runtime/proxy-retry-policy.js +2 -2
  109. package/dist/runtime/proxy-telemetry.d.ts +2 -1
  110. package/dist/runtime/proxy-telemetry.js +58 -52
  111. package/dist/runtime/redirects.d.ts +29 -0
  112. package/dist/runtime/redirects.js +36 -0
  113. package/dist/runtime/redis.d.ts +1 -1
  114. package/dist/runtime/redis.js +5 -5
  115. package/dist/runtime/request-options.d.ts +68 -1
  116. package/dist/runtime/request-options.js +548 -0
  117. package/dist/runtime/resolver-config.d.ts +6 -0
  118. package/dist/runtime/resolver-config.js +6 -0
  119. package/dist/runtime/resolver-public.d.ts +1 -0
  120. package/dist/runtime/resolver-public.js +1 -0
  121. package/dist/runtime/resolver-shared.d.ts +3 -0
  122. package/dist/runtime/resolver-shared.js +12 -0
  123. package/dist/runtime/resolver-vendors/bindings.d.ts +48 -0
  124. package/dist/runtime/resolver-vendors/bindings.js +40 -0
  125. package/dist/runtime/resolver-vendors/browser.d.ts +20 -0
  126. package/dist/runtime/resolver-vendors/browser.js +282 -0
  127. package/dist/runtime/resolver-vendors/hosts.d.ts +2 -0
  128. package/dist/runtime/resolver-vendors/hosts.js +33 -0
  129. package/dist/runtime/resolver-vendors/twocaptcha.d.ts +24 -0
  130. package/dist/runtime/resolver-vendors/twocaptcha.js +368 -0
  131. package/dist/runtime/resolver-vendors/types.d.ts +83 -0
  132. package/dist/runtime/resolver-vendors/types.js +69 -0
  133. package/dist/runtime/resolver.d.ts +59 -0
  134. package/dist/runtime/resolver.js +705 -0
  135. package/dist/runtime/secrets.d.ts +27 -0
  136. package/dist/runtime/secrets.js +51 -0
  137. package/dist/runtime/state.d.ts +5 -2
  138. package/dist/runtime/state.js +280 -74
  139. package/dist/runtime/stealth-cookies.d.ts +20 -0
  140. package/dist/runtime/stealth-cookies.js +111 -0
  141. package/dist/runtime/stealth.d.ts +30 -5
  142. package/dist/runtime/stealth.js +523 -259
  143. package/dist/runtime/stt.d.ts +1 -1
  144. package/dist/runtime/stt.js +12 -27
  145. package/dist/runtime/timeout.d.ts +5 -0
  146. package/dist/runtime/timeout.js +12 -0
  147. package/dist/runtime/trace.d.ts +2 -2
  148. package/dist/runtime/trace.js +2 -4
  149. package/dist/runtime/waterfall.d.ts +1 -1
  150. package/dist/schema.d.ts +1 -1
  151. package/dist/schema.js +7 -15
  152. package/dist/serve.d.ts +1 -1
  153. package/dist/serve.js +1 -1
  154. package/dist/server/index.d.ts +7 -7
  155. package/dist/server/index.js +6 -6
  156. package/dist/server/self-test-input-tokens.d.ts +2 -1
  157. package/dist/server/self-test-input-tokens.js +18 -14
  158. package/dist/server/self-test-redaction.d.ts +1 -1
  159. package/dist/server/self-test-redaction.js +1 -1
  160. package/dist/server/self-test.d.ts +117 -3
  161. package/dist/server/self-test.js +787 -151
  162. package/dist/server/serve-implementation.d.ts +210 -0
  163. package/dist/server/serve-implementation.js +2078 -0
  164. package/dist/server/serve.d.ts +1 -70
  165. package/dist/server/serve.js +1 -1143
  166. package/dist/server/types.d.ts +34 -9
  167. package/dist/server/types.js +8 -1
  168. package/dist/stateful/errors.d.ts +19 -0
  169. package/dist/stateful/errors.js +24 -0
  170. package/dist/stateful/http-provider-event-emitter.d.ts +40 -0
  171. package/dist/stateful/http-provider-event-emitter.js +237 -0
  172. package/dist/stateful/http-session-owner-registry.d.ts +44 -0
  173. package/dist/stateful/http-session-owner-registry.js +210 -0
  174. package/dist/stateful/index.d.ts +18 -0
  175. package/dist/stateful/index.js +18 -0
  176. package/dist/stateful/provider-event-delivery-failures.d.ts +32 -0
  177. package/dist/stateful/provider-event-delivery-failures.js +43 -0
  178. package/dist/stateful/provider-event-pipeline-metrics.d.ts +46 -0
  179. package/dist/stateful/provider-event-pipeline-metrics.js +48 -0
  180. package/dist/stateful/provider-event-pipeline.d.ts +50 -0
  181. package/dist/stateful/provider-event-pipeline.js +1 -0
  182. package/dist/stateful/provider-events.d.ts +101 -0
  183. package/dist/stateful/provider-events.js +289 -0
  184. package/dist/stateful/session-key.d.ts +15 -0
  185. package/dist/stateful/session-key.js +86 -0
  186. package/dist/stateful/stateful-provider-adapter-context.d.ts +5 -0
  187. package/dist/stateful/stateful-provider-adapter-context.js +42 -0
  188. package/dist/stateful/stateful-provider-adapter-metrics.d.ts +15 -0
  189. package/dist/stateful/stateful-provider-adapter-metrics.js +21 -0
  190. package/dist/stateful/stateful-provider-adapter.d.ts +98 -0
  191. package/dist/stateful/stateful-provider-adapter.js +287 -0
  192. package/dist/stateful/stateful-provider-observability.d.ts +62 -0
  193. package/dist/stateful/stateful-provider-observability.js +161 -0
  194. package/dist/stateful/stateful-provider-owner-forwarder.d.ts +41 -0
  195. package/dist/stateful/stateful-provider-owner-forwarder.js +215 -0
  196. package/dist/stateful/stateful-provider-runtime-context.d.ts +32 -0
  197. package/dist/stateful/stateful-provider-runtime-context.js +60 -0
  198. package/dist/stateful/stateful-provider-runtime-executor.d.ts +34 -0
  199. package/dist/stateful/stateful-provider-runtime-executor.js +52 -0
  200. package/dist/stateful/stateful-provider-session-routing.d.ts +67 -0
  201. package/dist/stateful/stateful-provider-session-routing.js +345 -0
  202. package/dist/stateful/stateful-provider-session-runtime.d.ts +98 -0
  203. package/dist/stateful/stateful-provider-session-runtime.js +245 -0
  204. package/dist/stateful-signing.d.ts +18 -0
  205. package/dist/stateful-signing.js +27 -0
  206. package/dist/stealth/profiles.d.ts +1 -1
  207. package/dist/stealth/profiles.js +21 -21
  208. package/dist/stream-evidence.d.ts +74 -0
  209. package/dist/stream-evidence.js +785 -0
  210. package/dist/stream.d.ts +1 -1
  211. package/dist/stream.js +7 -1
  212. package/dist/testing/index.d.ts +3 -2
  213. package/dist/testing/index.js +3 -2
  214. package/dist/testing/run.d.ts +32 -2
  215. package/dist/testing/run.js +488 -28
  216. package/dist/types.d.ts +566 -19
  217. package/dist/types.js +1 -0
  218. package/dist/user-input.d.ts +30 -0
  219. package/dist/user-input.js +66 -0
  220. package/package.json +42 -7
  221. package/src/auth-turn/index.ts +2 -2
  222. package/src/auth.ts +146 -86
  223. package/src/ceremonies/index.ts +167 -92
  224. package/src/cli/commands.ts +10 -0
  225. package/src/cli/create.ts +42 -35
  226. package/src/cli/prompt-assets.ts +865 -0
  227. package/src/cli/templates/provider/AGENTS.md.tpl +17 -8
  228. package/src/cli/templates/provider/README.md.tpl +4 -4
  229. package/src/config/loader.ts +667 -289
  230. package/src/contract-serialization.ts +10 -18
  231. package/src/contract-types.ts +3 -2
  232. package/src/contract.ts +14 -28
  233. package/src/declaration-validation.ts +202 -0
  234. package/src/define.ts +631 -495
  235. package/src/dev.ts +4 -9
  236. package/src/error-resolution.ts +128 -0
  237. package/src/errors.ts +56 -11
  238. package/src/fixture-sanitization.ts +247 -0
  239. package/src/i18n/catalog.ts +10 -32
  240. package/src/i18n/index.ts +2 -2
  241. package/src/i18n/keys.ts +5 -11
  242. package/src/index.ts +158 -44
  243. package/src/lint.ts +488 -154
  244. package/src/native-address.ts +340 -0
  245. package/src/native-egress-policy.ts +358 -0
  246. package/src/observability.ts +51 -1
  247. package/src/provider.ts +66 -11
  248. package/src/public-schema-field-lint.ts +7 -33
  249. package/src/recipes/gov-api.ts +2 -5
  250. package/src/runtime/auth-flow.ts +13 -7
  251. package/src/runtime/browser.ts +252 -207
  252. package/src/runtime/cache.ts +209 -81
  253. package/src/runtime/choice-wordlist.ts +145 -0
  254. package/src/runtime/choice.ts +758 -197
  255. package/src/runtime/credential.ts +2 -2
  256. package/src/runtime/env.ts +1 -1
  257. package/src/runtime/executor.ts +37 -19
  258. package/src/runtime/http.ts +645 -65
  259. package/src/runtime/insights.ts +15 -53
  260. package/src/runtime/instrumentation.ts +530 -67
  261. package/src/runtime/keyring.ts +7 -19
  262. package/src/runtime/namespace.ts +2 -7
  263. package/src/runtime/native-network-errors.ts +99 -0
  264. package/src/runtime/native-network.ts +1605 -0
  265. package/src/runtime/ocr.ts +523 -0
  266. package/src/runtime/otlp.ts +12 -23
  267. package/src/runtime/perf.ts +1 -1
  268. package/src/runtime/provider.ts +4 -9
  269. package/src/runtime/proxy-errors.ts +29 -42
  270. package/src/runtime/proxy-nodemaven.ts +221 -0
  271. package/src/runtime/proxy-retry-policy.ts +3 -3
  272. package/src/runtime/proxy-telemetry.ts +84 -77
  273. package/src/runtime/redirects.ts +66 -0
  274. package/src/runtime/redis.ts +10 -13
  275. package/src/runtime/request-options.ts +679 -9
  276. package/src/runtime/resolver-config.ts +6 -0
  277. package/src/runtime/resolver-public.ts +18 -0
  278. package/src/runtime/resolver-shared.ts +17 -0
  279. package/src/runtime/resolver-vendors/bindings.ts +56 -0
  280. package/src/runtime/resolver-vendors/browser.ts +408 -0
  281. package/src/runtime/resolver-vendors/hosts.ts +38 -0
  282. package/src/runtime/resolver-vendors/twocaptcha.ts +500 -0
  283. package/src/runtime/resolver-vendors/types.ts +173 -0
  284. package/src/runtime/resolver.ts +1060 -0
  285. package/src/runtime/secrets.ts +64 -0
  286. package/src/runtime/state.ts +399 -161
  287. package/src/runtime/stealth-cookies.ts +132 -0
  288. package/src/runtime/stealth.ts +681 -295
  289. package/src/runtime/stt.ts +39 -113
  290. package/src/runtime/timeout.ts +18 -0
  291. package/src/runtime/trace.ts +14 -44
  292. package/src/runtime/waterfall.ts +5 -18
  293. package/src/schema.ts +23 -84
  294. package/src/serve.ts +6 -1
  295. package/src/server/index.ts +30 -7
  296. package/src/server/self-test-input-tokens.ts +29 -14
  297. package/src/server/self-test-redaction.ts +2 -2
  298. package/src/server/self-test.ts +1030 -180
  299. package/src/server/serve-implementation.ts +3062 -0
  300. package/src/server/serve.ts +1 -1781
  301. package/src/server/types.ts +12 -13
  302. package/src/stateful/README.md +146 -0
  303. package/src/stateful/errors.ts +35 -0
  304. package/src/stateful/http-provider-event-emitter.ts +314 -0
  305. package/src/stateful/http-session-owner-registry.ts +306 -0
  306. package/src/stateful/index.ts +18 -0
  307. package/src/stateful/provider-event-delivery-failures.ts +80 -0
  308. package/src/stateful/provider-event-pipeline-metrics.ts +95 -0
  309. package/src/stateful/provider-event-pipeline.ts +61 -0
  310. package/src/stateful/provider-events.ts +462 -0
  311. package/src/stateful/session-key.ts +111 -0
  312. package/src/stateful/stateful-provider-adapter-context.ts +59 -0
  313. package/src/stateful/stateful-provider-adapter-metrics.ts +48 -0
  314. package/src/stateful/stateful-provider-adapter.ts +562 -0
  315. package/src/stateful/stateful-provider-observability.ts +261 -0
  316. package/src/stateful/stateful-provider-owner-forwarder.ts +287 -0
  317. package/src/stateful/stateful-provider-runtime-context.ts +92 -0
  318. package/src/stateful/stateful-provider-runtime-executor.ts +96 -0
  319. package/src/stateful/stateful-provider-session-routing.ts +546 -0
  320. package/src/stateful/stateful-provider-session-runtime.ts +403 -0
  321. package/src/stateful-signing.ts +46 -0
  322. package/src/stealth/profiles.ts +27 -33
  323. package/src/stream-evidence.ts +988 -0
  324. package/src/stream.ts +16 -20
  325. package/src/testing/index.ts +11 -2
  326. package/src/testing/run.ts +668 -74
  327. package/src/types.ts +665 -35
  328. package/src/user-input.ts +118 -0
  329. package/dist/cli/templates/provider/CLAUDE.md.tpl +0 -1
  330. package/src/cli/templates/provider/CLAUDE.md.tpl +0 -1
  331. /package/dist/cli/templates/provider/{skills → .agents/skills}/fixtures-and-recording/SKILL.md.tpl +0 -0
  332. /package/dist/cli/templates/provider/{skills → .agents/skills}/health-checks-and-fail-closed/SKILL.md.tpl +0 -0
  333. /package/dist/cli/templates/provider/{skills → .agents/skills}/normalization-standards/SKILL.md.tpl +0 -0
  334. /package/dist/cli/templates/provider/{skills → .agents/skills}/pagination-and-counts/SKILL.md.tpl +0 -0
  335. /package/dist/cli/templates/provider/{skills → .agents/skills}/upstream-contract-verification/SKILL.md.tpl +0 -0
  336. /package/dist/cli/templates/provider/{skills → .agents/skills}/upstream-notes/README.md.tpl +0 -0
  337. /package/src/cli/templates/provider/{skills → .agents/skills}/fixtures-and-recording/SKILL.md.tpl +0 -0
  338. /package/src/cli/templates/provider/{skills → .agents/skills}/health-checks-and-fail-closed/SKILL.md.tpl +0 -0
  339. /package/src/cli/templates/provider/{skills → .agents/skills}/normalization-standards/SKILL.md.tpl +0 -0
  340. /package/src/cli/templates/provider/{skills → .agents/skills}/pagination-and-counts/SKILL.md.tpl +0 -0
  341. /package/src/cli/templates/provider/{skills → .agents/skills}/upstream-contract-verification/SKILL.md.tpl +0 -0
  342. /package/src/cli/templates/provider/{skills → .agents/skills}/upstream-notes/README.md.tpl +0 -0
@@ -0,0 +1,261 @@
1
+ import { createHash } from "node:crypto";
2
+
3
+ export const STATEFUL_PROVIDER_METRIC_NAMES = [
4
+ "apifuse_stateful_provider_session_owner_acquire_total",
5
+ "apifuse_stateful_provider_session_owner_renew_total",
6
+ "apifuse_stateful_provider_session_owner_release_total",
7
+ "apifuse_stateful_provider_session_owner_conflict_total",
8
+ "apifuse_stateful_provider_session_owner_fencing_stale_total",
9
+ "apifuse_stateful_provider_active_sessions",
10
+ "apifuse_stateful_provider_lru_evictions_total",
11
+ "apifuse_stateful_provider_session_invalidations_total",
12
+ "apifuse_stateful_provider_session_operation_queue_depth",
13
+ "apifuse_stateful_provider_session_operation_queue_wait_ms",
14
+ "apifuse_stateful_provider_session_operation_duration_ms",
15
+ "apifuse_stateful_provider_routing_forward_total",
16
+ "apifuse_stateful_provider_routing_local_total",
17
+ "apifuse_stateful_provider_routing_reacquire_total",
18
+ "apifuse_stateful_provider_routing_deadline_expired_total",
19
+ "apifuse_stateful_provider_event_append_total",
20
+ "apifuse_stateful_provider_event_dedupe_total",
21
+ "apifuse_stateful_provider_event_drop_total",
22
+ "apifuse_stateful_provider_event_lag_ms",
23
+ "apifuse_stateful_provider_webhook_enqueue_total",
24
+ "apifuse_stateful_provider_webhook_fanout_recovered_total",
25
+ "apifuse_stateful_provider_webhook_delivered_total",
26
+ "apifuse_stateful_provider_webhook_retry_total",
27
+ "apifuse_stateful_provider_webhook_dead_letter_total",
28
+ "apifuse_stateful_provider_webhook_canceled_total",
29
+ "apifuse_stateful_provider_webhook_replay_total",
30
+ "apifuse_stateful_provider_webhook_latency_ms",
31
+ "apifuse_stateful_provider_write_ledger_ambiguous_total",
32
+ "apifuse_stateful_provider_write_ledger_reconciled_total",
33
+ "apifuse_stateful_provider_write_ledger_failed_total",
34
+ ] as const;
35
+
36
+ export type StatefulProviderMetricName = (typeof STATEFUL_PROVIDER_METRIC_NAMES)[number];
37
+
38
+ export const STATEFUL_PROVIDER_LOG_FIELDS = [
39
+ "requestId",
40
+ "eventId",
41
+ "deliveryId",
42
+ "providerId",
43
+ "connectionId",
44
+ "serviceAccountId",
45
+ "operationId",
46
+ "sessionKeyHash",
47
+ "ownerPodId",
48
+ "generation",
49
+ "leaseExpiresAt",
50
+ "capacityClass",
51
+ "queueDepth",
52
+ "eventType",
53
+ "subjectKind",
54
+ "subjectId",
55
+ "retryCount",
56
+ "ledgerState",
57
+ "healthStatus",
58
+ "redactedErrorCode",
59
+ ] as const;
60
+
61
+ export type StatefulProviderLogField = (typeof STATEFUL_PROVIDER_LOG_FIELDS)[number];
62
+
63
+ export const STATEFUL_PROVIDER_AUTOSCALING_SIGNALS = [
64
+ "active_sessions",
65
+ "session_queue_depth",
66
+ "session_queue_wait_ms",
67
+ "reconnect_rate",
68
+ "provider_event_lag_ms",
69
+ "webhook_outbox_depth",
70
+ "webhook_due_retry_depth",
71
+ "memory_pressure",
72
+ "file_descriptor_pressure",
73
+ "socket_pressure",
74
+ ] as const;
75
+
76
+ export type StatefulProviderAutoscalingSignal =
77
+ (typeof STATEFUL_PROVIDER_AUTOSCALING_SIGNALS)[number];
78
+
79
+ export const STATEFUL_PROVIDER_HEALTH_DIMENSIONS = [
80
+ "registry_connectivity",
81
+ "owner_lease_freshness",
82
+ "pool_capacity",
83
+ "upstream_auth_validity",
84
+ "push_loop_lag",
85
+ "event_append_lag",
86
+ "webhook_backlog",
87
+ ] as const;
88
+
89
+ export type StatefulProviderHealthDimension = (typeof STATEFUL_PROVIDER_HEALTH_DIMENSIONS)[number];
90
+
91
+ export const STATEFUL_PROVIDER_ROLLOUT_STATES = [
92
+ "disabled",
93
+ "tenant_allowlist",
94
+ "connection_allowlist",
95
+ "shadow",
96
+ "canary",
97
+ "production",
98
+ "rollback",
99
+ ] as const;
100
+
101
+ export type StatefulProviderRolloutState = (typeof STATEFUL_PROVIDER_ROLLOUT_STATES)[number];
102
+
103
+ export interface StatefulProviderMetricLabelInput {
104
+ readonly requestId?: string;
105
+ readonly eventId?: string;
106
+ readonly deliveryId?: string;
107
+ readonly providerId?: string;
108
+ readonly connectionId?: string;
109
+ readonly serviceAccountId?: string;
110
+ readonly operationId?: string;
111
+ readonly sessionKey?: string;
112
+ readonly sessionKeyHash?: string;
113
+ readonly ownerPodId?: string;
114
+ readonly generation?: number | string;
115
+ readonly leaseExpiresAt?: string;
116
+ readonly capacityClass?: string;
117
+ readonly queueDepth?: number | string;
118
+ readonly eventType?: string;
119
+ readonly subjectKind?: string;
120
+ readonly subjectId?: string;
121
+ readonly retryCount?: number | string;
122
+ readonly ledgerState?: string;
123
+ readonly healthStatus?: string;
124
+ readonly redactedErrorCode?: string;
125
+ }
126
+
127
+ export type StatefulProviderMetricLabels = Partial<Record<StatefulProviderLogField, string>>;
128
+
129
+ export interface StatefulProviderMetricEmitter {
130
+ increment(
131
+ name: StatefulProviderMetricName,
132
+ labels?: StatefulProviderMetricLabelInput,
133
+ value?: number,
134
+ ): void;
135
+ gauge(
136
+ name: StatefulProviderMetricName,
137
+ labels: StatefulProviderMetricLabelInput,
138
+ value: number,
139
+ ): void;
140
+ observe(
141
+ name: StatefulProviderMetricName,
142
+ labels: StatefulProviderMetricLabelInput,
143
+ value: number,
144
+ ): void;
145
+ }
146
+
147
+ export type StatefulProviderRecordedMetric = {
148
+ readonly kind: "increment" | "gauge" | "observe";
149
+ readonly name: StatefulProviderMetricName;
150
+ readonly labels: StatefulProviderMetricLabels;
151
+ readonly value: number;
152
+ };
153
+
154
+ const METRIC_NAME_SET = new Set<string>(STATEFUL_PROVIDER_METRIC_NAMES);
155
+ const LOG_FIELD_SET = new Set<string>(STATEFUL_PROVIDER_LOG_FIELDS);
156
+ const AUTOSCALING_SIGNAL_SET = new Set<string>(STATEFUL_PROVIDER_AUTOSCALING_SIGNALS);
157
+
158
+ export const NOOP_STATEFUL_PROVIDER_METRIC_EMITTER = {
159
+ increment() {},
160
+ gauge() {},
161
+ observe() {},
162
+ } satisfies StatefulProviderMetricEmitter;
163
+
164
+ export class RecordingStatefulProviderMetricEmitter implements StatefulProviderMetricEmitter {
165
+ readonly metrics: StatefulProviderRecordedMetric[] = [];
166
+
167
+ increment(
168
+ name: StatefulProviderMetricName,
169
+ labels: StatefulProviderMetricLabelInput = {},
170
+ value = 1,
171
+ ): void {
172
+ this.metrics.push({
173
+ kind: "increment",
174
+ name,
175
+ labels: statefulProviderMetricLabels(labels),
176
+ value,
177
+ });
178
+ }
179
+
180
+ gauge(
181
+ name: StatefulProviderMetricName,
182
+ labels: StatefulProviderMetricLabelInput,
183
+ value: number,
184
+ ): void {
185
+ this.metrics.push({
186
+ kind: "gauge",
187
+ name,
188
+ labels: statefulProviderMetricLabels(labels),
189
+ value,
190
+ });
191
+ }
192
+
193
+ observe(
194
+ name: StatefulProviderMetricName,
195
+ labels: StatefulProviderMetricLabelInput,
196
+ value: number,
197
+ ): void {
198
+ this.metrics.push({
199
+ kind: "observe",
200
+ name,
201
+ labels: statefulProviderMetricLabels(labels),
202
+ value,
203
+ });
204
+ }
205
+ }
206
+
207
+ export function isStatefulProviderMetricName(value: unknown): value is StatefulProviderMetricName {
208
+ return typeof value === "string" && METRIC_NAME_SET.has(value);
209
+ }
210
+
211
+ export function isStatefulProviderLogField(value: unknown): value is StatefulProviderLogField {
212
+ return typeof value === "string" && LOG_FIELD_SET.has(value);
213
+ }
214
+
215
+ export function isStatefulProviderAutoscalingSignal(
216
+ value: unknown,
217
+ ): value is StatefulProviderAutoscalingSignal {
218
+ return typeof value === "string" && AUTOSCALING_SIGNAL_SET.has(value);
219
+ }
220
+
221
+ export function sanitizeSessionKeyForLog(sessionKey: string): string {
222
+ return `session_sha256:${createHash("sha256").update(sessionKey).digest("hex")}`;
223
+ }
224
+
225
+ export function statefulProviderMetricLabels(
226
+ input: StatefulProviderMetricLabelInput,
227
+ ): StatefulProviderMetricLabels {
228
+ const labels: StatefulProviderMetricLabels = {};
229
+ for (const field of STATEFUL_PROVIDER_LOG_FIELDS) {
230
+ if (field === "sessionKeyHash") continue;
231
+ const value = input[field];
232
+ if (value !== undefined) {
233
+ labels[field] = String(value);
234
+ }
235
+ }
236
+
237
+ const sessionKeyHash =
238
+ input.sessionKeyHash ??
239
+ (input.sessionKey ? sanitizeSessionKeyForLog(input.sessionKey) : undefined);
240
+ if (sessionKeyHash) {
241
+ labels.sessionKeyHash = sessionKeyHash;
242
+ }
243
+
244
+ if (input.sessionKey) {
245
+ assertNoRawSessionKeyInLabels(labels, input.sessionKey);
246
+ }
247
+
248
+ return labels;
249
+ }
250
+
251
+ export function assertNoRawSessionKeyInLabels(
252
+ labels: Record<string, string | number | boolean | null | undefined>,
253
+ rawSessionKey: string,
254
+ ): void {
255
+ if (!rawSessionKey) return;
256
+ for (const [key, value] of Object.entries(labels)) {
257
+ if (String(value ?? "").includes(rawSessionKey)) {
258
+ throw new Error(`Stateful provider metric label "${key}" contains a raw session key.`);
259
+ }
260
+ }
261
+ }
@@ -0,0 +1,287 @@
1
+ import { z } from "zod";
2
+ import {
3
+ type OperationConnection,
4
+ OperationErrorResponseSchema,
5
+ OperationSuccessResponseSchema,
6
+ } from "../server/index.js";
7
+ import type { ProviderServerStatefulForwardEnvelope } from "../server/serve.js";
8
+ import { signStatefulRequestBody, statefulSignedHeaders } from "../stateful-signing.js";
9
+ import type { SessionKey } from "./session-key.js";
10
+ import type {
11
+ StatefulOperationRequest,
12
+ StatefulOperationResult,
13
+ StatefulOwnerForwarder,
14
+ } from "./stateful-provider-session-routing.js";
15
+ import { forwardingContextFromStatefulRuntimeContext } from "./stateful-provider-session-routing.js";
16
+ import type {
17
+ SessionOwnerRecord,
18
+ SessionOwnerRegistry,
19
+ } from "./stateful-provider-session-runtime.js";
20
+
21
+ export const STATEFUL_INTERNAL_OPERATIONS_ROUTE = "/__apifuse/stateful/operations";
22
+ export const STATEFUL_FORWARDING_SIGNATURE_HEADER = "x-apifuse-stateful-signature";
23
+ export const STATEFUL_FORWARDING_TIMESTAMP_HEADER = "x-apifuse-stateful-timestamp";
24
+ export const STATEFUL_FORWARDING_NONCE_HEADER = "x-apifuse-stateful-nonce";
25
+ export const STATEFUL_FORWARDING_SOURCE_POD_HEADER = "x-apifuse-stateful-source-pod";
26
+
27
+ export interface StatefulOwnerForwarderOptions {
28
+ readonly currentPodId: string;
29
+ readonly secret: string;
30
+ readonly fetch?: FetchTransport;
31
+ readonly clock?: () => Date;
32
+ }
33
+
34
+ type FetchTransport = (url: string | URL | Request, init?: RequestInit) => Promise<Response>;
35
+
36
+ const MAX_FORWARDED_HEADERS = 32;
37
+ const MAX_FORWARDED_HEADER_BYTES = 8 * 1024;
38
+ // Inbound compatibility boundary for rolling deploys: older owner pods omit
39
+ // top-level retryable. Emitted responses remain strict via
40
+ // OperationErrorResponseSchema.
41
+ const ForwardedOperationErrorResponseSchema = OperationErrorResponseSchema.extend({
42
+ error: OperationErrorResponseSchema.shape.error.extend({
43
+ retryable: z.boolean().optional().default(false),
44
+ }),
45
+ });
46
+ const SENSITIVE_HEADER_NAMES = new Set([
47
+ "authorization",
48
+ "cookie",
49
+ "set-cookie",
50
+ STATEFUL_FORWARDING_SIGNATURE_HEADER,
51
+ STATEFUL_FORWARDING_TIMESTAMP_HEADER,
52
+ STATEFUL_FORWARDING_NONCE_HEADER,
53
+ STATEFUL_FORWARDING_SOURCE_POD_HEADER,
54
+ ]);
55
+
56
+ /** Builds the standard fail-closed owner-currentness check for serve(). */
57
+ export function createStatefulOwnerFenceValidator(registry: SessionOwnerRegistry) {
58
+ return async (
59
+ fence: Pick<ProviderServerStatefulForwardEnvelope, "sessionKey" | "ownerPodId" | "generation">,
60
+ signal: AbortSignal,
61
+ ): Promise<boolean> => {
62
+ const current = await registry.resolve(fence.sessionKey as SessionKey, undefined, signal);
63
+ return (
64
+ current !== null &&
65
+ current.sessionKey === fence.sessionKey &&
66
+ current.ownerPodId === fence.ownerPodId &&
67
+ current.generation === fence.generation
68
+ );
69
+ };
70
+ }
71
+
72
+ export class StatefulOwnerForwardingError extends Error {
73
+ readonly code: string;
74
+ readonly status?: number;
75
+
76
+ constructor(input: {
77
+ readonly code: string;
78
+ readonly message: string;
79
+ readonly status?: number;
80
+ readonly cause?: unknown;
81
+ }) {
82
+ super(input.message, input.cause === undefined ? undefined : { cause: input.cause });
83
+ this.name = "StatefulOwnerForwardingError";
84
+ this.code = input.code;
85
+ this.status = input.status;
86
+ }
87
+ }
88
+
89
+ export class HttpStatefulOwnerForwarder implements StatefulOwnerForwarder {
90
+ readonly #currentPodId: string;
91
+ readonly #secret: string;
92
+ readonly #fetch: FetchTransport;
93
+ readonly #clock: () => Date;
94
+
95
+ constructor(options: StatefulOwnerForwarderOptions) {
96
+ if (options.secret.trim().length === 0) {
97
+ throw new Error("Stateful owner forwarding secret is required.");
98
+ }
99
+ this.#currentPodId = options.currentPodId;
100
+ this.#secret = options.secret;
101
+ this.#fetch = options.fetch ?? globalThis.fetch;
102
+ this.#clock = options.clock ?? (() => new Date());
103
+ }
104
+
105
+ async forward(
106
+ owner: SessionOwnerRecord,
107
+ request: StatefulOperationRequest,
108
+ signal: AbortSignal,
109
+ ): Promise<StatefulOperationResult> {
110
+ assertNoRequestScopedFiles(request.input);
111
+ const forwardedAt = this.#clock().toISOString();
112
+ const envelope = buildForwardingEnvelope({
113
+ owner,
114
+ request,
115
+ sourcePodId: this.#currentPodId,
116
+ forwardedAt,
117
+ });
118
+ const rawBody = JSON.stringify(envelope);
119
+ let response: Response;
120
+ try {
121
+ const target = new URL(
122
+ `${owner.ownerEndpoint.replace(/\/+$/, "")}${STATEFUL_INTERNAL_OPERATIONS_ROUTE}`,
123
+ );
124
+ response = await this.#fetch(target, {
125
+ method: "POST",
126
+ headers: {
127
+ "content-type": "application/json",
128
+ ...statefulSignedHeaders({
129
+ secret: this.#secret,
130
+ timestamp: forwardedAt,
131
+ rawBody,
132
+ method: "POST",
133
+ path: STATEFUL_INTERNAL_OPERATIONS_ROUTE,
134
+ }),
135
+ [STATEFUL_FORWARDING_SOURCE_POD_HEADER]: this.#currentPodId,
136
+ },
137
+ body: rawBody,
138
+ signal,
139
+ });
140
+ } catch (cause) {
141
+ throw new StatefulOwnerForwardingError({
142
+ code: "STATEFUL_FORWARDING_REQUEST_FAILED",
143
+ message: "Stateful owner forwarding request failed before a response was received.",
144
+ cause,
145
+ });
146
+ }
147
+ return parseForwardedResponse(response);
148
+ }
149
+ }
150
+
151
+ export function signStatefulForwardingBody(input: {
152
+ readonly secret: string;
153
+ readonly timestamp: string;
154
+ readonly rawBody: string;
155
+ readonly method: string;
156
+ readonly path: string;
157
+ readonly nonce: string;
158
+ }): string {
159
+ return signStatefulRequestBody(input);
160
+ }
161
+
162
+ function assertNoRequestScopedFiles(input: unknown): void {
163
+ if (!containsRequestScopedFile(input)) return;
164
+ throw new StatefulOwnerForwardingError({
165
+ code: "STATEFUL_FILE_FORWARDING_UNSUPPORTED",
166
+ message:
167
+ "Request-scoped files cannot be forwarded to a remote stateful owner. Use staged upload once available so the forwarding envelope carries only durable file_ref values, or route the request to the owner-local provider process.",
168
+ });
169
+ }
170
+
171
+ function containsRequestScopedFile(value: unknown): boolean {
172
+ if (isRequestScopedFileRef(value)) return true;
173
+ if (Array.isArray(value)) return value.some((item) => containsRequestScopedFile(item));
174
+ if (!value || typeof value !== "object") return false;
175
+ return Object.values(value).some((item) => containsRequestScopedFile(item));
176
+ }
177
+
178
+ function isRequestScopedFileRef(value: unknown): boolean {
179
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
180
+ const record = Object.fromEntries(Object.entries(value));
181
+ return record.type === "request_file" && typeof record.id === "string";
182
+ }
183
+
184
+ function buildForwardingEnvelope(input: {
185
+ readonly owner: SessionOwnerRecord;
186
+ readonly request: StatefulOperationRequest;
187
+ readonly sourcePodId: string;
188
+ readonly forwardedAt: string;
189
+ }): ProviderServerStatefulForwardEnvelope {
190
+ const metadata = forwardedMetadata(input.request);
191
+ return {
192
+ requestId: input.request.requestId,
193
+ providerId: input.request.providerId,
194
+ operationId: input.request.operationId,
195
+ sessionKey: input.request.sessionKey,
196
+ connectionId: input.request.connectionId,
197
+ serviceAccountId: input.request.serviceAccountId,
198
+ ...(input.request.idempotencyKey ? { idempotencyKey: input.request.idempotencyKey } : {}),
199
+ ...(input.request.deadlineAt !== undefined ? { deadlineAt: input.request.deadlineAt } : {}),
200
+ sourcePodId: input.sourcePodId,
201
+ forwardedAt: input.forwardedAt,
202
+ ownerPodId: input.owner.ownerPodId,
203
+ generation: input.owner.generation,
204
+ operationRequest: {
205
+ requestId: input.request.requestId,
206
+ input: input.request.input as Record<string, unknown>,
207
+ connection: metadata.connection,
208
+ headers: sanitizeForwardedHeaders(metadata.headers),
209
+ ...(metadata.trace ? { trace: metadata.trace } : {}),
210
+ },
211
+ };
212
+ }
213
+
214
+ function forwardedMetadata(request: StatefulOperationRequest): {
215
+ readonly connection: OperationConnection;
216
+ readonly headers?: Record<string, string>;
217
+ readonly trace?: Record<string, string>;
218
+ } {
219
+ const forwardingContext = forwardingContextFromStatefulRuntimeContext(request.runtimeContext);
220
+ if (!forwardingContext) {
221
+ throw new StatefulOwnerForwardingError({
222
+ code: "STATEFUL_FORWARDING_CONTEXT_MISSING",
223
+ message: "Stateful operation forwarding requires request context metadata.",
224
+ });
225
+ }
226
+ const operationRequest = forwardingContext.operationRequest;
227
+ const connection = OperationConnectionSchema.parse(operationRequest?.connection);
228
+ return {
229
+ connection,
230
+ ...(operationRequest?.headers ? { headers: operationRequest.headers } : {}),
231
+ ...(operationRequest?.trace ? { trace: operationRequest.trace } : {}),
232
+ };
233
+ }
234
+
235
+ function sanitizeForwardedHeaders(
236
+ headers: Record<string, string> | undefined,
237
+ ): Record<string, string> {
238
+ if (!headers) return {};
239
+ const forwarded: Record<string, string> = {};
240
+ let totalBytes = 0;
241
+ for (const [name, value] of Object.entries(headers)) {
242
+ const normalized = name.toLowerCase();
243
+ if (SENSITIVE_HEADER_NAMES.has(normalized)) continue;
244
+ const nextBytes = Buffer.byteLength(name) + Buffer.byteLength(value);
245
+ if (
246
+ Object.keys(forwarded).length >= MAX_FORWARDED_HEADERS ||
247
+ totalBytes + nextBytes > MAX_FORWARDED_HEADER_BYTES
248
+ ) {
249
+ break;
250
+ }
251
+ forwarded[name] = value;
252
+ totalBytes += nextBytes;
253
+ }
254
+ return forwarded;
255
+ }
256
+
257
+ async function parseForwardedResponse(response: Response): Promise<StatefulOperationResult> {
258
+ const body = await response.json().catch(() => undefined);
259
+ const success = OperationSuccessResponseSchema.safeParse(body);
260
+ if (response.ok && success.success) {
261
+ return { output: success.data.data };
262
+ }
263
+
264
+ const error = ForwardedOperationErrorResponseSchema.safeParse(body);
265
+ if (error.success) {
266
+ throw new StatefulOwnerForwardingError({
267
+ code: error.data.error.code,
268
+ message: error.data.error.message,
269
+ status: response.status,
270
+ });
271
+ }
272
+
273
+ throw new StatefulOwnerForwardingError({
274
+ code: "STATEFUL_FORWARDING_BAD_RESPONSE",
275
+ message: `Stateful owner returned an invalid response with status ${response.status}.`,
276
+ status: response.status,
277
+ });
278
+ }
279
+
280
+ const OperationConnectionSchema = z.object({
281
+ id: z.string(),
282
+ mode: z.enum(["oauth2", "credentials", "platform-managed", "none"]),
283
+ secrets: z.record(z.string(), z.string()),
284
+ scopes: z.array(z.string()).optional(),
285
+ metadata: z.record(z.string(), z.unknown()),
286
+ externalRef: z.string(),
287
+ });
@@ -0,0 +1,92 @@
1
+ export interface StatefulLocalRuntimeContext {
2
+ readonly providerContext?: unknown;
3
+ }
4
+
5
+ export interface StatefulForwardingRuntimeContext {
6
+ readonly operationRequest?: {
7
+ readonly requestId: string;
8
+ readonly connection?: unknown;
9
+ readonly headers?: Record<string, string>;
10
+ readonly trace?: Record<string, string>;
11
+ };
12
+ }
13
+
14
+ export interface StatefulOperationRuntimeContext {
15
+ readonly local?: StatefulLocalRuntimeContext;
16
+ readonly forwarding?: StatefulForwardingRuntimeContext;
17
+ }
18
+
19
+ /**
20
+ * Attaches provider-process-only context for local execution. This value must
21
+ * never be serialized into owner-forwarding envelopes.
22
+ */
23
+ export function withStatefulLocalProviderContext(
24
+ providerContext: unknown,
25
+ forwarding?: StatefulForwardingRuntimeContext,
26
+ ): StatefulOperationRuntimeContext {
27
+ return {
28
+ local: { providerContext },
29
+ ...(forwarding ? { forwarding } : {}),
30
+ };
31
+ }
32
+
33
+ /**
34
+ * Copies only operation metadata that is safe to serialize across owner pods.
35
+ */
36
+ export function statefulForwardingContextFromProviderRequest(request: {
37
+ readonly requestId: string;
38
+ readonly connection?: unknown;
39
+ readonly headers?: Record<string, string>;
40
+ readonly trace?: Record<string, string>;
41
+ }): StatefulForwardingRuntimeContext {
42
+ return {
43
+ operationRequest: {
44
+ requestId: request.requestId,
45
+ ...(request.connection !== undefined ? { connection: request.connection } : {}),
46
+ ...(request.headers ? { headers: request.headers } : {}),
47
+ ...(request.trace ? { trace: request.trace } : {}),
48
+ },
49
+ };
50
+ }
51
+
52
+ export function isStatefulOperationRuntimeContext(
53
+ value: unknown,
54
+ ): value is StatefulOperationRuntimeContext {
55
+ if (value === null || typeof value !== "object") return false;
56
+ return (
57
+ "local" in value ||
58
+ "forwarding" in value ||
59
+ "operationRequest" in value ||
60
+ "providerContext" in value
61
+ );
62
+ }
63
+
64
+ export function providerContextFromStatefulRuntimeContext(value: unknown): unknown {
65
+ if (!isStatefulOperationRuntimeContext(value)) return undefined;
66
+ if ("local" in value) return value.local?.providerContext;
67
+ // Backward-compatible read for pre-split runtimeContext objects. New code
68
+ // should always use `withStatefulLocalProviderContext` instead.
69
+ if ("providerContext" in value) return value.providerContext;
70
+ return undefined;
71
+ }
72
+
73
+ export function forwardingContextFromStatefulRuntimeContext(
74
+ value: unknown,
75
+ ): StatefulForwardingRuntimeContext | undefined {
76
+ if (!isStatefulOperationRuntimeContext(value)) return undefined;
77
+ if ("forwarding" in value) return value.forwarding;
78
+ if ("operationRequest" in value) {
79
+ const operationRequest = value.operationRequest;
80
+ if (isForwardingOperationRequest(operationRequest)) {
81
+ return { operationRequest };
82
+ }
83
+ }
84
+ return undefined;
85
+ }
86
+
87
+ function isForwardingOperationRequest(
88
+ value: unknown,
89
+ ): value is StatefulForwardingRuntimeContext["operationRequest"] {
90
+ if (value === null || typeof value !== "object") return false;
91
+ return "requestId" in value && typeof value.requestId === "string";
92
+ }