@apifuse/provider-sdk 2.2.0-beta.5 → 2.2.0-beta.51

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 (368) hide show
  1. package/AUTHORING.md +537 -33
  2. package/CHANGELOG.md +205 -1
  3. package/README.md +83 -22
  4. package/SUBMISSION.md +2 -2
  5. package/bin/apifuse-check.ts +165 -11
  6. package/bin/apifuse-dev.ts +57 -13
  7. package/bin/apifuse-migrate-operation-declaration.ts +55 -0
  8. package/bin/apifuse-migrate-shape.ts +202 -0
  9. package/bin/apifuse-pack-check.ts +22 -2
  10. package/bin/apifuse-pack-smoke.ts +57 -2
  11. package/bin/apifuse-pack-types.ts +357 -38
  12. package/bin/apifuse-perf.ts +14 -13
  13. package/bin/apifuse-record.ts +709 -72
  14. package/bin/apifuse-submit-check.ts +2344 -324
  15. package/bin/apifuse-sync-assets.ts +117 -0
  16. package/bin/submit-check-delimited-text.ts +50 -0
  17. package/dist/auth-turn/index.d.ts +3 -3
  18. package/dist/auth-turn/index.js +1 -1
  19. package/dist/auth.d.ts +14 -0
  20. package/dist/auth.js +67 -0
  21. package/dist/ceremonies/index.d.ts +16 -0
  22. package/dist/ceremonies/index.js +141 -36
  23. package/dist/cli/commands.d.ts +1 -1
  24. package/dist/cli/commands.js +27 -0
  25. package/dist/cli/create.d.ts +4 -1
  26. package/dist/cli/create.js +42 -37
  27. package/dist/cli/migrate-operation-declaration.d.ts +59 -0
  28. package/dist/cli/migrate-operation-declaration.js +1249 -0
  29. package/dist/cli/migrate-operation-shape.d.ts +44 -0
  30. package/dist/cli/migrate-operation-shape.js +113 -0
  31. package/dist/cli/migrate-provider-shape.d.ts +52 -0
  32. package/dist/cli/migrate-provider-shape.js +578 -0
  33. package/dist/cli/prompt-assets.d.ts +80 -0
  34. package/dist/cli/prompt-assets.js +743 -0
  35. package/dist/cli/templates/provider/AGENTS.md.tpl +17 -8
  36. package/dist/cli/templates/provider/Dockerfile.tpl +1 -1
  37. package/dist/cli/templates/provider/README.md.tpl +5 -5
  38. package/dist/cli/templates/provider/index.ts.tpl +6 -3
  39. package/dist/cli/templates/provider/operations/ping.ts.tpl +4 -1
  40. package/dist/cli/templates/provider/provider.json.tpl +6 -0
  41. package/dist/config/loader.d.ts +179 -16
  42. package/dist/config/loader.js +435 -127
  43. package/dist/contract-serialization.js +4 -8
  44. package/dist/contract-types.d.ts +12 -5
  45. package/dist/contract.js +24 -10
  46. package/dist/declaration-validation.d.ts +33 -0
  47. package/dist/declaration-validation.js +266 -0
  48. package/dist/define.d.ts +66 -29
  49. package/dist/define.js +778 -69
  50. package/dist/dev.d.ts +3 -0
  51. package/dist/dev.js +1 -1
  52. package/dist/engine.d.ts +78 -0
  53. package/dist/engine.js +133 -0
  54. package/dist/error-observability.d.ts +7 -0
  55. package/dist/error-observability.js +61 -0
  56. package/dist/error-resolution.d.ts +4 -0
  57. package/dist/error-resolution.js +122 -0
  58. package/dist/errors.d.ts +33 -0
  59. package/dist/errors.js +40 -0
  60. package/dist/fixture-sanitization.d.ts +28 -0
  61. package/dist/fixture-sanitization.js +227 -0
  62. package/dist/health-scenario.d.ts +1842 -0
  63. package/dist/health-scenario.js +624 -0
  64. package/dist/index.d.ts +21 -10
  65. package/dist/index.js +12 -7
  66. package/dist/lint.d.ts +23 -11
  67. package/dist/lint.js +572 -69
  68. package/dist/native-address.d.ts +43 -0
  69. package/dist/native-address.js +281 -0
  70. package/dist/native-egress-policy.d.ts +31 -0
  71. package/dist/native-egress-policy.js +288 -0
  72. package/dist/observability.d.ts +5 -2
  73. package/dist/observability.js +48 -1
  74. package/dist/provider.d.ts +10 -2
  75. package/dist/provider.js +4 -1
  76. package/dist/runtime/auth-flow.d.ts +5 -1
  77. package/dist/runtime/auth-flow.js +6 -0
  78. package/dist/runtime/browser.d.ts +1 -0
  79. package/dist/runtime/browser.js +492 -49
  80. package/dist/runtime/cache.d.ts +1 -0
  81. package/dist/runtime/cache.js +169 -15
  82. package/dist/runtime/choice-wordlist.d.ts +9 -0
  83. package/dist/runtime/choice-wordlist.js +138 -0
  84. package/dist/runtime/choice.d.ts +13 -1
  85. package/dist/runtime/choice.js +490 -102
  86. package/dist/runtime/chrome149-header-order.d.ts +58 -0
  87. package/dist/runtime/chrome149-header-order.js +289 -0
  88. package/dist/runtime/env.js +12 -0
  89. package/dist/runtime/executor.d.ts +3 -2
  90. package/dist/runtime/executor.js +26 -25
  91. package/dist/runtime/http.d.ts +1 -0
  92. package/dist/runtime/http.js +515 -53
  93. package/dist/runtime/insights.js +2 -2
  94. package/dist/runtime/instrumentation.d.ts +2 -2
  95. package/dist/runtime/instrumentation.js +366 -8
  96. package/dist/runtime/native-network-errors.d.ts +33 -0
  97. package/dist/runtime/native-network-errors.js +69 -0
  98. package/dist/runtime/native-network.d.ts +96 -0
  99. package/dist/runtime/native-network.js +1232 -0
  100. package/dist/runtime/ocr.d.ts +29 -0
  101. package/dist/runtime/ocr.js +440 -0
  102. package/dist/runtime/otlp.d.ts +71 -2
  103. package/dist/runtime/otlp.js +397 -16
  104. package/dist/runtime/proxy-errors.js +6 -2
  105. package/dist/runtime/proxy-nodemaven.d.ts +56 -0
  106. package/dist/runtime/proxy-nodemaven.js +146 -0
  107. package/dist/runtime/proxy-telemetry.d.ts +80 -1
  108. package/dist/runtime/proxy-telemetry.js +154 -47
  109. package/dist/runtime/redirects.d.ts +29 -0
  110. package/dist/runtime/redirects.js +36 -0
  111. package/dist/runtime/redis.d.ts +1 -1
  112. package/dist/runtime/redis.js +4 -2
  113. package/dist/runtime/request-options.d.ts +68 -1
  114. package/dist/runtime/request-options.js +548 -0
  115. package/dist/runtime/resolver-config.d.ts +6 -0
  116. package/dist/runtime/resolver-config.js +6 -0
  117. package/dist/runtime/resolver-public.d.ts +1 -0
  118. package/dist/runtime/resolver-public.js +1 -0
  119. package/dist/runtime/resolver-shared.d.ts +14 -0
  120. package/dist/runtime/resolver-shared.js +12 -0
  121. package/dist/runtime/resolver-vendors/bindings.d.ts +48 -0
  122. package/dist/runtime/resolver-vendors/bindings.js +40 -0
  123. package/dist/runtime/resolver-vendors/browser.d.ts +22 -0
  124. package/dist/runtime/resolver-vendors/browser.js +377 -0
  125. package/dist/runtime/resolver-vendors/capsolver.d.ts +22 -0
  126. package/dist/runtime/resolver-vendors/capsolver.js +531 -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 +408 -0
  131. package/dist/runtime/resolver-vendors/types.d.ts +94 -0
  132. package/dist/runtime/resolver-vendors/types.js +96 -0
  133. package/dist/runtime/resolver.d.ts +72 -0
  134. package/dist/runtime/resolver.js +771 -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 +3 -0
  138. package/dist/runtime/state.js +277 -71
  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 +41 -5
  142. package/dist/runtime/stealth.js +997 -291
  143. package/dist/runtime/stt.js +1 -12
  144. package/dist/runtime/timeout.d.ts +5 -0
  145. package/dist/runtime/timeout.js +12 -0
  146. package/dist/runtime/trace-config.d.ts +12 -0
  147. package/dist/runtime/trace-config.js +62 -0
  148. package/dist/runtime/trace.d.ts +5 -0
  149. package/dist/runtime/trace.js +43 -10
  150. package/dist/serve.d.ts +1 -1
  151. package/dist/serve.js +1 -1
  152. package/dist/server/error-observability.d.ts +1 -0
  153. package/dist/server/error-observability.js +1 -0
  154. package/dist/server/index.d.ts +5 -2
  155. package/dist/server/index.js +2 -2
  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.d.ts +14 -3
  159. package/dist/server/self-test.js +129 -58
  160. package/dist/server/serve-implementation.d.ts +230 -0
  161. package/dist/server/serve-implementation.js +2265 -0
  162. package/dist/server/serve.d.ts +1 -70
  163. package/dist/server/serve.js +1 -1131
  164. package/dist/server/trace-output.d.ts +6 -0
  165. package/dist/server/trace-output.js +79 -0
  166. package/dist/server/types.d.ts +30 -5
  167. package/dist/server/types.js +13 -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 +9 -4
  207. package/dist/stealth/profiles.js +160 -211
  208. package/dist/stream-evidence.d.ts +74 -0
  209. package/dist/stream-evidence.js +785 -0
  210. package/dist/stream.js +7 -1
  211. package/dist/testing/index.d.ts +2 -1
  212. package/dist/testing/index.js +2 -1
  213. package/dist/testing/run.d.ts +32 -2
  214. package/dist/testing/run.js +489 -21
  215. package/dist/trace-sanitization.d.ts +5 -0
  216. package/dist/trace-sanitization.js +45 -0
  217. package/dist/types.d.ts +673 -131
  218. package/dist/types.js +1 -0
  219. package/package.json +45 -5
  220. package/src/auth-turn/index.ts +1 -1
  221. package/src/auth.ts +118 -0
  222. package/src/ceremonies/index.ts +189 -46
  223. package/src/cli/__tests__/fixtures/migrate-operation-declaration/approval-override.ts.txt +6 -0
  224. package/src/cli/__tests__/fixtures/migrate-operation-declaration/codemod-syntax.ts.txt +3 -0
  225. package/src/cli/__tests__/fixtures/migrate-operation-declaration/connection-precedence.ts.txt +10 -0
  226. package/src/cli/__tests__/fixtures/migrate-operation-declaration/docs-conflict.ts.txt +8 -0
  227. package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-hoisted-array-spread.ts.txt +15 -0
  228. package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-imported-array-spread.ts.txt +9 -0
  229. package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-map.ts.txt +5 -0
  230. package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-mixed-array-spread.ts.txt +24 -0
  231. package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-operation.ts.txt +16 -0
  232. package/src/cli/__tests__/fixtures/migrate-operation-declaration/factory-map.ts.txt +3 -0
  233. package/src/cli/__tests__/fixtures/migrate-operation-declaration/hoist-all.ts.txt +31 -0
  234. package/src/cli/__tests__/fixtures/migrate-operation-declaration/hoisted-const.ts.txt +11 -0
  235. package/src/cli/__tests__/fixtures/migrate-operation-declaration/imported-spread.ts.txt +11 -0
  236. package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-map.ts.txt +11 -0
  237. package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-spread-cast-tail.ts.txt +21 -0
  238. package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-spread-ekitan.ts.txt +11 -0
  239. package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-spread-override.ts.txt +14 -0
  240. package/src/cli/__tests__/fixtures/migrate-operation-declaration/missing-english-locale.ts.txt +7 -0
  241. package/src/cli/__tests__/fixtures/migrate-operation-declaration/no-safety.ts.txt +6 -0
  242. package/src/cli/__tests__/fixtures/migrate-operation-declaration/non-literal.ts.txt +7 -0
  243. package/src/cli/__tests__/fixtures/migrate-operation-declaration/redundant-approval.ts.txt +6 -0
  244. package/src/cli/__tests__/fixtures/migrate-operation-declaration/safety-conflict.ts.txt +7 -0
  245. package/src/cli/__tests__/fixtures/migrate-operation-declaration/stream.ts.txt +7 -0
  246. package/src/cli/__tests__/fixtures/migrate-operation-declaration/tool-router-spread.ts.txt +15 -0
  247. package/src/cli/__tests__/fixtures/migrate-operation-declaration/unparseable.ts.txt +4 -0
  248. package/src/cli/__tests__/fixtures/migrate-operation-declaration/verbatim-template.ts.txt +12 -0
  249. package/src/cli/commands.ts +33 -0
  250. package/src/cli/create.ts +50 -37
  251. package/src/cli/migrate-operation-declaration.ts +1751 -0
  252. package/src/cli/migrate-operation-shape.ts +184 -0
  253. package/src/cli/migrate-provider-shape.ts +772 -0
  254. package/src/cli/prompt-assets.ts +865 -0
  255. package/src/cli/templates/provider/AGENTS.md.tpl +17 -8
  256. package/src/cli/templates/provider/Dockerfile.tpl +1 -1
  257. package/src/cli/templates/provider/README.md.tpl +5 -5
  258. package/src/cli/templates/provider/index.ts.tpl +6 -3
  259. package/src/cli/templates/provider/operations/ping.ts.tpl +4 -1
  260. package/src/cli/templates/provider/provider.json.tpl +6 -0
  261. package/src/config/loader.ts +690 -163
  262. package/src/contract-serialization.ts +5 -7
  263. package/src/contract-types.ts +12 -5
  264. package/src/contract.ts +24 -10
  265. package/src/declaration-validation.ts +330 -0
  266. package/src/define.ts +1048 -131
  267. package/src/dev.ts +4 -1
  268. package/src/engine.ts +279 -0
  269. package/src/error-observability.ts +64 -0
  270. package/src/error-resolution.ts +127 -0
  271. package/src/errors.ts +68 -0
  272. package/src/fixture-sanitization.ts +264 -0
  273. package/src/health-scenario.ts +875 -0
  274. package/src/index.ts +220 -13
  275. package/src/lint.ts +698 -90
  276. package/src/native-address.ts +340 -0
  277. package/src/native-egress-policy.ts +358 -0
  278. package/src/observability.ts +51 -1
  279. package/src/provider.ts +161 -3
  280. package/src/runtime/auth-flow.ts +12 -0
  281. package/src/runtime/browser.ts +661 -63
  282. package/src/runtime/cache.ts +189 -14
  283. package/src/runtime/choice-wordlist.ts +145 -0
  284. package/src/runtime/choice.ts +631 -120
  285. package/src/runtime/chrome149-header-order.ts +330 -0
  286. package/src/runtime/env.ts +13 -0
  287. package/src/runtime/executor.ts +43 -31
  288. package/src/runtime/http.ts +641 -61
  289. package/src/runtime/insights.ts +2 -2
  290. package/src/runtime/instrumentation.ts +520 -15
  291. package/src/runtime/native-network-errors.ts +99 -0
  292. package/src/runtime/native-network.ts +1605 -0
  293. package/src/runtime/ocr.ts +523 -0
  294. package/src/runtime/otlp.ts +467 -21
  295. package/src/runtime/proxy-errors.ts +12 -4
  296. package/src/runtime/proxy-nodemaven.ts +221 -0
  297. package/src/runtime/proxy-telemetry.ts +244 -75
  298. package/src/runtime/redirects.ts +66 -0
  299. package/src/runtime/redis.ts +7 -2
  300. package/src/runtime/request-options.ts +680 -1
  301. package/src/runtime/resolver-config.ts +6 -0
  302. package/src/runtime/resolver-public.ts +23 -0
  303. package/src/runtime/resolver-shared.ts +33 -0
  304. package/src/runtime/resolver-vendors/bindings.ts +56 -0
  305. package/src/runtime/resolver-vendors/browser.ts +533 -0
  306. package/src/runtime/resolver-vendors/capsolver.ts +707 -0
  307. package/src/runtime/resolver-vendors/hosts.ts +38 -0
  308. package/src/runtime/resolver-vendors/twocaptcha.ts +540 -0
  309. package/src/runtime/resolver-vendors/types.ts +212 -0
  310. package/src/runtime/resolver.ts +1157 -0
  311. package/src/runtime/secrets.ts +64 -0
  312. package/src/runtime/state.ts +394 -77
  313. package/src/runtime/stealth-cookies.ts +132 -0
  314. package/src/runtime/stealth.ts +1280 -336
  315. package/src/runtime/stt.ts +1 -19
  316. package/src/runtime/timeout.ts +18 -0
  317. package/src/runtime/trace-config.ts +78 -0
  318. package/src/runtime/trace.ts +57 -17
  319. package/src/serve.ts +6 -1
  320. package/src/server/error-observability.ts +1 -0
  321. package/src/server/index.ts +34 -2
  322. package/src/server/self-test-input-tokens.ts +29 -14
  323. package/src/server/self-test.ts +191 -68
  324. package/src/server/serve-implementation.ts +3378 -0
  325. package/src/server/serve.ts +1 -1632
  326. package/src/server/trace-output.ts +129 -0
  327. package/src/server/types.ts +13 -1
  328. package/src/stateful/README.md +146 -0
  329. package/src/stateful/errors.ts +35 -0
  330. package/src/stateful/http-provider-event-emitter.ts +314 -0
  331. package/src/stateful/http-session-owner-registry.ts +306 -0
  332. package/src/stateful/index.ts +18 -0
  333. package/src/stateful/provider-event-delivery-failures.ts +80 -0
  334. package/src/stateful/provider-event-pipeline-metrics.ts +95 -0
  335. package/src/stateful/provider-event-pipeline.ts +61 -0
  336. package/src/stateful/provider-events.ts +462 -0
  337. package/src/stateful/session-key.ts +111 -0
  338. package/src/stateful/stateful-provider-adapter-context.ts +59 -0
  339. package/src/stateful/stateful-provider-adapter-metrics.ts +48 -0
  340. package/src/stateful/stateful-provider-adapter.ts +562 -0
  341. package/src/stateful/stateful-provider-observability.ts +261 -0
  342. package/src/stateful/stateful-provider-owner-forwarder.ts +287 -0
  343. package/src/stateful/stateful-provider-runtime-context.ts +92 -0
  344. package/src/stateful/stateful-provider-runtime-executor.ts +96 -0
  345. package/src/stateful/stateful-provider-session-routing.ts +546 -0
  346. package/src/stateful/stateful-provider-session-runtime.ts +403 -0
  347. package/src/stateful-signing.ts +46 -0
  348. package/src/stealth/profiles.ts +202 -231
  349. package/src/stream-evidence.ts +988 -0
  350. package/src/stream.ts +8 -1
  351. package/src/testing/index.ts +10 -1
  352. package/src/testing/run.ts +658 -15
  353. package/src/trace-sanitization.ts +63 -0
  354. package/src/types.ts +788 -174
  355. package/dist/cli/templates/provider/CLAUDE.md.tpl +0 -1
  356. package/src/cli/templates/provider/CLAUDE.md.tpl +0 -1
  357. /package/dist/cli/templates/provider/{skills → .agents/skills}/fixtures-and-recording/SKILL.md.tpl +0 -0
  358. /package/dist/cli/templates/provider/{skills → .agents/skills}/health-checks-and-fail-closed/SKILL.md.tpl +0 -0
  359. /package/dist/cli/templates/provider/{skills → .agents/skills}/normalization-standards/SKILL.md.tpl +0 -0
  360. /package/dist/cli/templates/provider/{skills → .agents/skills}/pagination-and-counts/SKILL.md.tpl +0 -0
  361. /package/dist/cli/templates/provider/{skills → .agents/skills}/upstream-contract-verification/SKILL.md.tpl +0 -0
  362. /package/dist/cli/templates/provider/{skills → .agents/skills}/upstream-notes/README.md.tpl +0 -0
  363. /package/src/cli/templates/provider/{skills → .agents/skills}/fixtures-and-recording/SKILL.md.tpl +0 -0
  364. /package/src/cli/templates/provider/{skills → .agents/skills}/health-checks-and-fail-closed/SKILL.md.tpl +0 -0
  365. /package/src/cli/templates/provider/{skills → .agents/skills}/normalization-standards/SKILL.md.tpl +0 -0
  366. /package/src/cli/templates/provider/{skills → .agents/skills}/pagination-and-counts/SKILL.md.tpl +0 -0
  367. /package/src/cli/templates/provider/{skills → .agents/skills}/upstream-contract-verification/SKILL.md.tpl +0 -0
  368. /package/src/cli/templates/provider/{skills → .agents/skills}/upstream-notes/README.md.tpl +0 -0
package/src/lint.ts CHANGED
@@ -1,9 +1,53 @@
1
+ import { createRequire } from "node:module";
2
+
1
3
  import type { ZodType } from "zod";
2
4
 
5
+ import {
6
+ SDK_RUNTIME_OWNED_ERROR_CODES,
7
+ SDK_STATUS_MAPPED_PROVIDER_ERROR_CODES,
8
+ } from "./error-resolution.js";
3
9
  import { lintPublicSchemaFieldNames } from "./public-schema-field-lint.js";
4
10
  import { APIFUSE_DESCRIPTION_KEY_META_KEY, APIFUSE_SENSITIVE_META_KEY } from "./schema.js";
11
+ import type {
12
+ AuthMode,
13
+ OperationApprovalPolicy,
14
+ OperationRiskClass,
15
+ } from "./types.js";
16
+
17
+ const requireModule = createRequire(import.meta.url);
18
+ // `typeof import(...)` keeps the type without emitting a static import: the
19
+ // typescript package is a CLI-only dependency and src/lint.ts is production
20
+ // runtime, which the typescript-import-boundary test enforces.
21
+ let typeScriptModule: typeof import("typescript") | undefined;
22
+
23
+ function getTypeScript(): typeof import("typescript") {
24
+ typeScriptModule ??= requireModule("typescript") as typeof import("typescript");
25
+ return typeScriptModule;
26
+ }
27
+
28
+ type AuthModeLike =
29
+ | "none"
30
+ | "platform-managed"
31
+ | "credentials"
32
+ | "oauth2"
33
+ | "oauth2_proxied"
34
+ | "api-key";
35
+
36
+ const CREDENTIAL_BEARING_AUTH_MODES = [
37
+ "credentials",
38
+ "oauth2",
39
+ "oauth2_proxied",
40
+ ] satisfies readonly AuthMode[];
41
+
42
+ function isCredentialBearingAuthMode(mode: AuthModeLike | undefined): boolean {
43
+ return CREDENTIAL_BEARING_AUTH_MODES.some((credentialMode) => credentialMode === mode);
44
+ }
5
45
 
6
- type AuthModeLike = "none" | "platform-managed" | "credentials" | "oauth2" | "api-key";
46
+ function defaultApprovalPolicy(riskClass: OperationRiskClass): OperationApprovalPolicy {
47
+ if (riskClass === "read") return "never";
48
+ if (riskClass === "write") return "risk-based";
49
+ return "always";
50
+ }
7
51
 
8
52
  type ProviderAuthLike = {
9
53
  mode?: AuthModeLike;
@@ -17,11 +61,103 @@ type ProviderAuthLike = {
17
61
  exchange?: unknown;
18
62
  };
19
63
 
64
+ // Operations that perform an auth-lifecycle action belong on the single
65
+ // `auth.flow` interface, never on a provider operation:
66
+ // - entry (login / signin / authenticate) => auth.flow.start/continue
67
+ // - exit (logout / signout / disconnect) => auth.flow.abort
68
+ //
69
+ // Matching works on `-`/`_` separated segments rather than a raw substring or a
70
+ // leading anchor, so `shop-logout`, `shop_logout` and `user-sign-out-everywhere`
71
+ // are all recognised: a domain prefix does not make the operation any less of an
72
+ // auth-lifecycle action, and operation ids may use either separator.
73
+ //
74
+ // Vocabulary is split into two tiers because auth words collide with ordinary
75
+ // domain verbs. Measured against the live fleet plus synthetic domain ids:
76
+ // - `authorize-payment`, `revoke-invitation`, `unlink-record`,
77
+ // `disconnect-device` are domain actions that never touch the connection
78
+ // credential, so these verbs are NOT matched as segments;
79
+ // - the same verbs as a complete operation id (`authorize`, `revoke`) do
80
+ // refer to the credential itself, so they are matched only in that form.
81
+ // `exchange`, `callback`, `connect`, `session`, `token`, `credential`,
82
+ // `password` and `otp` stay out entirely for the same reason.
83
+ const AUTH_LIFECYCLE_SEGMENT_WORDS = new Set([
84
+ "login",
85
+ "logout",
86
+ "signin",
87
+ "signout",
88
+ "signup",
89
+ "authenticate",
90
+ "reauth",
91
+ "auth",
92
+ ]);
93
+
94
+ // Ambiguous as a prefix, unambiguous when they are the whole operation id.
95
+ const AUTH_LIFECYCLE_WHOLE_ID_WORDS = new Set([
96
+ "authorize",
97
+ "revoke",
98
+ "unlink",
99
+ "disconnect",
100
+ ]);
101
+
102
+ // A verb stem followed by a direction word across two segments: `sign-out`,
103
+ // `user_sign_up_flow`, and the spelled-out `log-in` / `shop-log-out` forms
104
+ // (their fused equivalents `login`/`logout` live in the segment set above).
105
+ // `sign` pairs match anywhere; `log` pairs match only at the END of the id,
106
+ // because mid-id `log` is the noun in domain phrases measured against real
107
+ // fleets (`audit-log-in-range`, `change-log-out-of-band` are reads of a log,
108
+ // while `shop-log-out` is a logout).
109
+ const AUTH_DIRECTION_PAIRS: ReadonlyMap<
110
+ string,
111
+ { directions: ReadonlySet<string>; endOnly: boolean }
112
+ > = new Map([
113
+ ["sign", { directions: new Set(["in", "out", "up"]), endOnly: false }],
114
+ ["log", { directions: new Set(["in", "out"]), endOnly: true }],
115
+ ]);
116
+
117
+ // Legacy anchored form kept for token-plumbing words whose bare use is only
118
+ // auth-related when it leads the operation id (`exchange-code`, `refresh`).
20
119
  const AUTH_OPERATION_ID_PATTERN =
21
120
  /^(?:auth[-_])?(?:login|exchange|continue|refresh|callback)(?:[-_]|$)/i;
22
121
 
122
+ function isAuthLifecycleOperationId(operationId: string, authMode: string): boolean {
123
+ const segments = operationId.toLowerCase().split(/[-_]+/).filter(Boolean);
124
+
125
+ if (segments.some((segment) => AUTH_LIFECYCLE_SEGMENT_WORDS.has(segment))) return true;
126
+
127
+ // A verb stem + direction spread across two segments (`sign-out`,
128
+ // `sign_up`, `shop-log-out`); see AUTH_DIRECTION_PAIRS for positioning.
129
+ if (
130
+ segments.some((segment, index) => {
131
+ const pair = AUTH_DIRECTION_PAIRS.get(segment);
132
+ if (pair === undefined || index + 1 >= segments.length) return false;
133
+ if (!pair.directions.has(segments[index + 1] as string)) return false;
134
+ return pair.endOnly ? index + 2 === segments.length : true;
135
+ })
136
+ ) {
137
+ return true;
138
+ }
139
+
140
+ if (segments.length === 1 && AUTH_LIFECYCLE_WHOLE_ID_WORDS.has(segments[0] as string)) {
141
+ return true;
142
+ }
143
+
144
+ // The legacy anchored pattern keeps its original scope. It matches ordinary
145
+ // domain ids such as `exchange-rates` and `refresh-catalog`, so extending it
146
+ // to `oauth2_proxied` would spread that behavior to providers it never
147
+ // applied to; proxied providers are covered by the segment tiers above.
148
+ if (authMode === "credentials" || authMode === "oauth2") {
149
+ return AUTH_OPERATION_ID_PATTERN.test(operationId);
150
+ }
151
+
152
+ return false;
153
+ }
154
+
23
155
  type ProviderContractMetaLike = {
24
156
  publicSchemaFieldNames?: "normalized";
157
+ pinnedWireFieldPaths?: readonly {
158
+ readonly path: string;
159
+ readonly reason: string;
160
+ }[];
25
161
  };
26
162
 
27
163
  type SchemaLike = ZodType & {
@@ -55,6 +191,17 @@ type ProviderLintOptions = {
55
191
  mode?: ProviderLintMode;
56
192
  };
57
193
 
194
+ export interface ProviderLintInformation {
195
+ rule: string;
196
+ message: string;
197
+ field?: string;
198
+ }
199
+
200
+ export interface ProviderLintResult {
201
+ diagnostics: LintDiagnostic[];
202
+ information: ProviderLintInformation[];
203
+ }
204
+
58
205
  type ProviderSourceLike = {
59
206
  authFlowSource?: string;
60
207
  providerSourceFiles?: Record<string, string>;
@@ -573,21 +720,6 @@ function collectSchemaDescriptionKeyDiagnostics(
573
720
  return diagnostics;
574
721
  }
575
722
 
576
- function isComplexSchema(schema: unknown, seen = new Set<SchemaLike>()): boolean {
577
- if (!isSchema(schema) || seen.has(schema)) {
578
- return false;
579
- }
580
-
581
- seen.add(schema);
582
- const children = getChildSchemas(schema);
583
- const hasNestedComposite = children.some(({ schema: child }) => {
584
- const childChildren = getChildSchemas(child);
585
- return childChildren.length > 0;
586
- });
587
-
588
- return hasNestedComposite || children.some(({ schema: child }) => isComplexSchema(child, seen));
589
- }
590
-
591
723
  function hasBidirectionalFixtures(fixtures: unknown): boolean {
592
724
  if (!fixtures || typeof fixtures !== "object") {
593
725
  return true;
@@ -793,69 +925,460 @@ function lintSelfHostedBrowserPatterns(
793
925
  return diagnostics;
794
926
  }
795
927
 
928
+ const THROWN_ERROR_CONSTRUCTION_PATTERN = /new\s+(?:ProviderError|ValidationError)\s*\(/g;
929
+
930
+ const TEST_SOURCE_FILE_PATTERN = /(?:^|\/)(?:__tests__|__mocks__)\/|\.(?:test|spec)\.[cm]?[jt]sx?$/;
931
+ const RECORDED_FIXTURE_SOURCE_FILE_PATTERN =
932
+ /(?:^|\/)__fixtures__(?:\/|$)|(?:^|\/)__tests__\/fixtures(?:\/|$)/;
933
+ const JAVASCRIPT_SOURCE_FILE_PATTERN = /\.[cm]?[jt]sx?$/;
934
+ const VERSIONED_PROFILE_LITERAL_PATTERN =
935
+ /\b(?:chrome|chromium|firefox|safari|edge|opera|ios[-_]safari)[-_]\d+(?:[._-]\d+)*(?=$|[^A-Za-z0-9])/i;
936
+ const VERSIONED_USER_AGENT_PATTERN = /\b(?:Chrome|CriOS|Firefox|FxiOS|EdgA?|OPR)\/\d+(?:\.\d+)*/i;
937
+ const VERSIONED_SAFARI_USER_AGENT_PATTERN = /\bVersion\/(\d+(?:\.\d+)*)(?=[\s\S]*\bSafari\/\d)/i;
938
+ const VERSIONED_CLIENT_HINT_PATTERN = /(?:^|[;,\s])v\s*=\s*["']?\d+/i;
939
+
940
+ type BrowserVersionLiteralKind = "profile" | "user-agent" | "sec-ch-ua";
941
+
942
+ type BrowserVersionLiteralFinding = {
943
+ kind: BrowserVersionLiteralKind;
944
+ literal: string;
945
+ position: number;
946
+ };
947
+
948
+ function staticStringText(node: import("typescript").Node | undefined): string | undefined {
949
+ if (!node) return undefined;
950
+ const ts = getTypeScript();
951
+ if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) return node.text;
952
+ return undefined;
953
+ }
954
+
955
+ function staticPropertyName(node: import("typescript").PropertyName): string | undefined {
956
+ const ts = getTypeScript();
957
+ if (ts.isIdentifier(node) || ts.isStringLiteral(node) || ts.isNumericLiteral(node)) {
958
+ return node.text;
959
+ }
960
+ if (ts.isComputedPropertyName(node)) return staticStringText(node.expression);
961
+ return undefined;
962
+ }
963
+
964
+ function isSecChUaHeaderName(value: string | undefined): boolean {
965
+ return value?.toLowerCase() === "sec-ch-ua";
966
+ }
967
+
968
+ function collectBrowserVersionLiteralFindings(source: string): BrowserVersionLiteralFinding[] {
969
+ const ts = getTypeScript();
970
+ const sourceFile = ts.createSourceFile(
971
+ "provider-source.ts",
972
+ source,
973
+ ts.ScriptTarget.Latest,
974
+ true,
975
+ ts.ScriptKind.TSX,
976
+ );
977
+ const findings: BrowserVersionLiteralFinding[] = [];
978
+ const seen = new Set<string>();
979
+
980
+ const addFinding = (kind: BrowserVersionLiteralKind, literal: string, position: number) => {
981
+ const key = `${kind}:${position}:${literal}`;
982
+ if (seen.has(key)) return;
983
+ seen.add(key);
984
+ findings.push({ kind, literal, position });
985
+ };
986
+
987
+ const inspectLiteral = (text: string, position: number) => {
988
+ const profile = text.match(VERSIONED_PROFILE_LITERAL_PATTERN)?.[0];
989
+ if (profile) addFinding("profile", profile, position);
990
+
991
+ const userAgent =
992
+ text.match(VERSIONED_USER_AGENT_PATTERN)?.[0] ??
993
+ text.match(VERSIONED_SAFARI_USER_AGENT_PATTERN)?.[0];
994
+ if (userAgent) addFinding("user-agent", userAgent, position);
995
+ };
996
+
997
+ const inspectSecChUaValue = (node: import("typescript").Node | undefined) => {
998
+ const text = staticStringText(node);
999
+ if (text && VERSIONED_CLIENT_HINT_PATTERN.test(text)) {
1000
+ addFinding("sec-ch-ua", "sec-ch-ua", node!.getStart(sourceFile));
1001
+ }
1002
+ };
1003
+
1004
+ const visit = (node: import("typescript").Node) => {
1005
+ const text = staticStringText(node);
1006
+ if (text !== undefined) inspectLiteral(text, node.getStart(sourceFile));
1007
+
1008
+ if (ts.isPropertyAssignment(node) && isSecChUaHeaderName(staticPropertyName(node.name))) {
1009
+ inspectSecChUaValue(node.initializer);
1010
+ }
1011
+
1012
+ if (ts.isCallExpression(node) && isSecChUaHeaderName(staticStringText(node.arguments[0]))) {
1013
+ inspectSecChUaValue(node.arguments[1]);
1014
+ }
1015
+
1016
+ if (
1017
+ ts.isArrayLiteralExpression(node) &&
1018
+ isSecChUaHeaderName(staticStringText(node.elements[0]))
1019
+ ) {
1020
+ inspectSecChUaValue(node.elements[1]);
1021
+ }
1022
+
1023
+ ts.forEachChild(node, visit);
1024
+ };
1025
+
1026
+ visit(sourceFile);
1027
+ return findings.sort((left, right) => left.position - right.position);
1028
+ }
1029
+
1030
+ function browserVersionLiteralMessage(finding: BrowserVersionLiteralFinding): string {
1031
+ switch (finding.kind) {
1032
+ case "profile":
1033
+ return `Hardcoded stealth profile "${finding.literal}" pins a browser version and will rot. Select the browser and OS structurally, for example stealth: { browser: "chrome", os: "macos" }.`;
1034
+ case "user-agent":
1035
+ return `Hardcoded User-Agent browser version "${finding.literal}" can disagree with the stealth TLS fingerprint. Remove the literal and derive it from the structured profile, for example getStealthProfile({ browser: "chrome", os: "macos" }).userAgent.`;
1036
+ case "sec-ch-ua":
1037
+ return 'Hardcoded sec-ch-ua versions can disagree with the stealth TLS fingerprint. Remove the literal and let ctx.stealth generate client hints from stealth: { browser: "chrome", os: "macos" }; derive any explicit User-Agent with getStealthProfile({ browser: "chrome", os: "macos" }).userAgent.';
1038
+ }
1039
+ }
1040
+
1041
+ function lintBrowserVersionLiterals(provider: ProviderSourceLike): LintDiagnostic[] {
1042
+ const sources: Array<{ field: string; source: string }> = [];
1043
+ const sourceFiles = Object.entries(provider.providerSourceFiles ?? {}).filter(
1044
+ ([filePath]) =>
1045
+ JAVASCRIPT_SOURCE_FILE_PATTERN.test(filePath) &&
1046
+ !TEST_SOURCE_FILE_PATTERN.test(filePath) &&
1047
+ !RECORDED_FIXTURE_SOURCE_FILE_PATTERN.test(filePath),
1048
+ );
1049
+ if (sourceFiles.length > 0) {
1050
+ for (const [filePath, source] of sourceFiles) {
1051
+ sources.push({ field: `sourceFiles.${filePath}`, source });
1052
+ }
1053
+ } else {
1054
+ if (provider.authFlowSource)
1055
+ sources.push({ field: "auth.flow", source: provider.authFlowSource });
1056
+ for (const [operationKey, operation] of Object.entries(provider.operations ?? {})) {
1057
+ const source = getOperationSource(operation);
1058
+ if (source) sources.push({ field: `operations.${operationKey}.handler`, source });
1059
+ }
1060
+ }
1061
+
1062
+ return sources.flatMap(({ field, source }) =>
1063
+ collectBrowserVersionLiteralFindings(source).map((finding) => ({
1064
+ rule: "browser-version-literal",
1065
+ level: "error" as const,
1066
+ field,
1067
+ message: browserVersionLiteralMessage(finding),
1068
+ })),
1069
+ );
1070
+ }
1071
+
1072
+ /**
1073
+ * Skips a string literal starting at `startIndex` (which must point at the
1074
+ * opening quote). Returns the index of the closing quote, or -1 when the
1075
+ * literal is unterminated. Template literals handle nested `${...}`
1076
+ * expressions, including strings inside them.
1077
+ */
1078
+ function skipStringLiteral(source: string, startIndex: number): number {
1079
+ const quote = source[startIndex];
1080
+ for (let index = startIndex + 1; index < source.length; index++) {
1081
+ const char = source[index];
1082
+ if (char === "\\") {
1083
+ index++;
1084
+ continue;
1085
+ }
1086
+ if (quote === "`" && char === "$" && source[index + 1] === "{") {
1087
+ index = skipTemplateExpression(source, index + 2);
1088
+ if (index < 0) {
1089
+ return -1;
1090
+ }
1091
+ continue;
1092
+ }
1093
+ if (char === quote) {
1094
+ return index;
1095
+ }
1096
+ if (quote !== "`" && char === "\n") {
1097
+ return -1;
1098
+ }
1099
+ }
1100
+ return -1;
1101
+ }
1102
+
1103
+ function skipTemplateExpression(source: string, startIndex: number): number {
1104
+ let depth = 1;
1105
+ for (let index = startIndex; index < source.length; index++) {
1106
+ const char = source[index];
1107
+ if (char === '"' || char === "'" || char === "`") {
1108
+ index = skipStringLiteral(source, index);
1109
+ if (index < 0) {
1110
+ return -1;
1111
+ }
1112
+ continue;
1113
+ }
1114
+ if (char === "{") {
1115
+ depth++;
1116
+ } else if (char === "}") {
1117
+ depth--;
1118
+ if (depth === 0) {
1119
+ return index;
1120
+ }
1121
+ }
1122
+ }
1123
+ return -1;
1124
+ }
1125
+
1126
+ /**
1127
+ * Extracts the argument text of a call whose opening paren has already been
1128
+ * consumed (`startIndex` points just past it). Returns undefined when the
1129
+ * call never closes in this source, which the caller treats as "skip
1130
+ * silently" — this scanner is conservative by design.
1131
+ */
1132
+ function extractBalancedCallArguments(source: string, startIndex: number): string | undefined {
1133
+ let depth = 1;
1134
+ for (let index = startIndex; index < source.length; index++) {
1135
+ const char = source[index];
1136
+ if (char === '"' || char === "'" || char === "`") {
1137
+ index = skipStringLiteral(source, index);
1138
+ if (index < 0) {
1139
+ return undefined;
1140
+ }
1141
+ continue;
1142
+ }
1143
+ if (char === "/" && source[index + 1] === "/") {
1144
+ const newline = source.indexOf("\n", index);
1145
+ if (newline === -1) {
1146
+ return undefined;
1147
+ }
1148
+ index = newline;
1149
+ continue;
1150
+ }
1151
+ if (char === "/" && source[index + 1] === "*") {
1152
+ const end = source.indexOf("*/", index + 2);
1153
+ if (end === -1) {
1154
+ return undefined;
1155
+ }
1156
+ index = end + 1;
1157
+ continue;
1158
+ }
1159
+ if (char === "(") {
1160
+ depth++;
1161
+ } else if (char === ")") {
1162
+ depth--;
1163
+ if (depth === 0) {
1164
+ return source.slice(startIndex, index);
1165
+ }
1166
+ }
1167
+ }
1168
+ return undefined;
1169
+ }
1170
+
1171
+ /**
1172
+ * Collects literal string values of top-level `code:` properties inside a
1173
+ * ProviderError/ValidationError options object. Only plain `"..."` / `'...'`
1174
+ * literals at options-object depth count; computed codes (identifiers,
1175
+ * ternaries, template substitutions, concatenations, escapes) are skipped
1176
+ * silently so the rule never guesses.
1177
+ */
1178
+ function collectLiteralErrorCodeValues(args: string): string[] {
1179
+ const codes: string[] = [];
1180
+ let braceDepth = 0;
1181
+ let parenDepth = 0;
1182
+ let bracketDepth = 0;
1183
+ let previousSignificantChar = "";
1184
+ for (let index = 0; index < args.length; index++) {
1185
+ const char = args[index] ?? "";
1186
+ if (char === '"' || char === "'" || char === "`") {
1187
+ const end = skipStringLiteral(args, index);
1188
+ if (end < 0) {
1189
+ return codes;
1190
+ }
1191
+ index = end;
1192
+ previousSignificantChar = char;
1193
+ continue;
1194
+ }
1195
+ if (char === "/" && args[index + 1] === "/") {
1196
+ const newline = args.indexOf("\n", index);
1197
+ if (newline === -1) {
1198
+ return codes;
1199
+ }
1200
+ index = newline;
1201
+ continue;
1202
+ }
1203
+ if (char === "/" && args[index + 1] === "*") {
1204
+ const end = args.indexOf("*/", index + 2);
1205
+ if (end === -1) {
1206
+ return codes;
1207
+ }
1208
+ index = end + 1;
1209
+ continue;
1210
+ }
1211
+ if (/\s/.test(char)) {
1212
+ continue;
1213
+ }
1214
+ if (char === "{") {
1215
+ braceDepth++;
1216
+ } else if (char === "}") {
1217
+ braceDepth--;
1218
+ } else if (char === "(") {
1219
+ parenDepth++;
1220
+ } else if (char === ")") {
1221
+ parenDepth--;
1222
+ } else if (char === "[") {
1223
+ bracketDepth++;
1224
+ } else if (char === "]") {
1225
+ bracketDepth--;
1226
+ } else if (
1227
+ braceDepth === 1 &&
1228
+ parenDepth === 0 &&
1229
+ bracketDepth === 0 &&
1230
+ (previousSignificantChar === "{" || previousSignificantChar === ",") &&
1231
+ args.startsWith("code", index)
1232
+ ) {
1233
+ let cursor = index + "code".length;
1234
+ while (cursor < args.length && /\s/.test(args[cursor] ?? "")) {
1235
+ cursor++;
1236
+ }
1237
+ if (args[cursor] === ":") {
1238
+ cursor++;
1239
+ while (cursor < args.length && /\s/.test(args[cursor] ?? "")) {
1240
+ cursor++;
1241
+ }
1242
+ const quote = args[cursor];
1243
+ if (quote === '"' || quote === "'") {
1244
+ const end = skipStringLiteral(args, cursor);
1245
+ if (end > cursor) {
1246
+ const value = args.slice(cursor + 1, end);
1247
+ let after = end + 1;
1248
+ while (after < args.length && /\s/.test(args[after] ?? "")) {
1249
+ after++;
1250
+ }
1251
+ const nextChar = after < args.length ? (args[after] ?? "") : "";
1252
+ if (!value.includes("\\") && (nextChar === "," || nextChar === "}" || nextChar === "")) {
1253
+ codes.push(value);
1254
+ }
1255
+ index = end;
1256
+ previousSignificantChar = quote;
1257
+ continue;
1258
+ }
1259
+ return codes;
1260
+ }
1261
+ }
1262
+ }
1263
+ previousSignificantChar = char;
1264
+ }
1265
+ return codes;
1266
+ }
1267
+
1268
+ function collectLiteralThrownErrorCodes(source: string): string[] {
1269
+ const codes: string[] = [];
1270
+ THROWN_ERROR_CONSTRUCTION_PATTERN.lastIndex = 0;
1271
+ for (
1272
+ let match = THROWN_ERROR_CONSTRUCTION_PATTERN.exec(source);
1273
+ match;
1274
+ match = THROWN_ERROR_CONSTRUCTION_PATTERN.exec(source)
1275
+ ) {
1276
+ const argsStart = match.index + match[0].length;
1277
+ const args = extractBalancedCallArguments(source, argsStart);
1278
+ if (args !== undefined) {
1279
+ codes.push(...collectLiteralErrorCodeValues(args));
1280
+ }
1281
+ THROWN_ERROR_CONSTRUCTION_PATTERN.lastIndex = argsStart;
1282
+ }
1283
+ return codes;
1284
+ }
1285
+
1286
+ /**
1287
+ * Static counterpart of the runtime `unregistered_provider_error_code`
1288
+ * signal (honest-provider-error-contract Phase 3.5.5): flags
1289
+ * `new ProviderError(...)` / `new ValidationError(...)` constructions whose
1290
+ * literal `code` is neither SDK-registered (SDK_RUNTIME_OWNED_ERROR_CODES
1291
+ * plus the canonical status-mapped codes shared with serve.ts toStatusCode)
1292
+ * nor declared in any operation's errorCodes. At runtime such a code
1293
+ * serves HTTP 500 and emits the signal; this rule surfaces it at check time.
1294
+ *
1295
+ * A throw site cannot be attributed to a specific operation statically —
1296
+ * providers routinely throw from helpers shared across operations — so this
1297
+ * rule matches against the provider-level union of declared codes. That is
1298
+ * the honest scope: it will not catch a code declared only on the "wrong"
1299
+ * operation, and it never claims per-operation attribution it cannot prove.
1300
+ * Only literal string codes are checked; computed/dynamic codes and test
1301
+ * sources are skipped silently. Warning level: the long tail of existing
1302
+ * providers converges gradually, so this must not fail `apifuse check`.
1303
+ */
1304
+ function lintUndeclaredThrownErrorCodes(provider: {
1305
+ authFlowSource?: string;
1306
+ providerSourceFiles?: Record<string, string>;
1307
+ operations?: Record<
1308
+ string,
1309
+ {
1310
+ handler?: unknown;
1311
+ source?: string;
1312
+ errorCodes?: ReadonlyArray<{ code: string }>;
1313
+ }
1314
+ >;
1315
+ }): LintDiagnostic[] {
1316
+ const knownCodes = new Set<string>([
1317
+ ...SDK_RUNTIME_OWNED_ERROR_CODES,
1318
+ ...SDK_STATUS_MAPPED_PROVIDER_ERROR_CODES.keys(),
1319
+ ]);
1320
+ for (const operation of Object.values(provider.operations ?? {})) {
1321
+ for (const entry of operation.errorCodes ?? []) {
1322
+ if (typeof entry?.code === "string") {
1323
+ knownCodes.add(entry.code);
1324
+ }
1325
+ }
1326
+ }
1327
+
1328
+ const sources: Array<{ field: string; source: string }> = [];
1329
+ const sourceFiles = Object.entries(provider.providerSourceFiles ?? {}).filter(
1330
+ ([filePath]) => !TEST_SOURCE_FILE_PATTERN.test(filePath),
1331
+ );
1332
+ if (sourceFiles.length > 0) {
1333
+ for (const [filePath, source] of sourceFiles) {
1334
+ sources.push({ field: `sourceFiles.${filePath}`, source });
1335
+ }
1336
+ } else {
1337
+ if (provider.authFlowSource) {
1338
+ sources.push({ field: "auth.flow", source: provider.authFlowSource });
1339
+ }
1340
+ for (const [operationKey, operation] of Object.entries(provider.operations ?? {})) {
1341
+ const source = getOperationSource(operation);
1342
+ if (source) {
1343
+ sources.push({ field: `operations.${operationKey}.handler`, source });
1344
+ }
1345
+ }
1346
+ }
1347
+
1348
+ const diagnostics: LintDiagnostic[] = [];
1349
+ for (const { field, source } of sources) {
1350
+ const undeclaredCodes = new Set(
1351
+ collectLiteralThrownErrorCodes(source).filter((code) => !knownCodes.has(code)),
1352
+ );
1353
+ for (const code of undeclaredCodes) {
1354
+ diagnostics.push({
1355
+ rule: "thrown-error-code-undeclared",
1356
+ level: "warn",
1357
+ field,
1358
+ message: `Thrown error code "${code}" (${field}) is neither SDK-registered nor declared in any operation's errorCodes; at runtime it serves HTTP 500 and emits the unregistered_provider_error_code signal. Declare it in the owning operation's errorCodes with status and retryable.`,
1359
+ });
1360
+ }
1361
+ }
1362
+ return diagnostics;
1363
+ }
1364
+
796
1365
  export function lintOperation(op: {
797
- description?: string;
798
1366
  descriptionKey?: string;
799
- whenToUse?: readonly string[];
800
1367
  whenToUseKeys?: readonly string[];
801
- whenNotToUse?: readonly string[];
802
1368
  whenNotToUseKeys?: readonly string[];
803
1369
  input: unknown;
804
1370
  output: unknown;
805
1371
  fixtures?: unknown;
806
- inputExamples?: readonly unknown[];
807
- derivations?: Record<string, string>;
808
1372
  }): LintDiagnostic[] {
809
1373
  const diagnostics: LintDiagnostic[] = [];
810
- const description = op.description ?? "";
811
1374
  const hasDescriptionKey = typeof op.descriptionKey === "string" && op.descriptionKey.length > 0;
812
1375
 
813
- if (description.trim().length > 0 && !hasDescriptionKey) {
814
- diagnostics.push({
815
- rule: "operation-description-raw-prose",
816
- level: "error",
817
- field: "description",
818
- message: "Operation description must use descriptionKey instead of raw static prose.",
819
- });
820
- }
821
-
822
- if (!hasDescriptionKey && description.length < 150) {
823
- diagnostics.push({
824
- rule: "description-min-length",
825
- level: "error",
826
- field: "description",
827
- message: "Operation description must be at least 150 characters.",
828
- });
829
- }
830
-
831
- if ((op.whenToUse?.length ?? 0) > 0 && !(op.whenToUseKeys?.length ?? 0)) {
832
- diagnostics.push({
833
- rule: "operation-when-to-use-raw-prose",
834
- level: "error",
835
- field: "whenToUse",
836
- message: "Operation whenToUse must use whenToUseKeys instead of raw static prose.",
837
- });
838
- }
839
-
840
- if ((op.whenNotToUse?.length ?? 0) > 0 && !(op.whenNotToUseKeys?.length ?? 0)) {
1376
+ if (!hasDescriptionKey) {
841
1377
  diagnostics.push({
842
- rule: "operation-when-not-to-use-raw-prose",
1378
+ rule: "description-key-required",
843
1379
  level: "error",
844
- field: "whenNotToUse",
845
- message: "Operation whenNotToUse must use whenNotToUseKeys instead of raw static prose.",
846
- });
847
- }
848
-
849
- const lowerDescription = description.toLowerCase();
850
- if (
851
- !hasDescriptionKey &&
852
- !(lowerDescription.includes("use") && lowerDescription.includes("when"))
853
- ) {
854
- diagnostics.push({
855
- rule: "description-has-when-clause",
856
- level: "warn",
857
- field: "description",
858
- message: 'Operation description should include both "use" and "when".',
1380
+ field: "descriptionKey",
1381
+ message: "Operation must declare a locale-backed descriptionKey.",
859
1382
  });
860
1383
  }
861
1384
 
@@ -873,15 +1396,6 @@ export function lintOperation(op: {
873
1396
  });
874
1397
  }
875
1398
 
876
- if (isComplexSchema(op.input) && (op.inputExamples?.length ?? 0) < 2) {
877
- diagnostics.push({
878
- rule: "complex-input-has-examples",
879
- level: "warn",
880
- field: "inputExamples",
881
- message: "Complex input schemas should provide at least 2 input examples.",
882
- });
883
- }
884
-
885
1399
  for (const field of uniqueFields(collectUnmarkedSensitiveFields(op.input, "input"))) {
886
1400
  diagnostics.push({
887
1401
  rule: "sensitive-field-unmarked",
@@ -903,6 +1417,67 @@ export function lintOperation(op: {
903
1417
  return diagnostics;
904
1418
  }
905
1419
 
1420
+ function declaredPinnedWireFieldPaths(provider: {
1421
+ meta?: { contract?: ProviderContractMetaLike };
1422
+ }): readonly { readonly path: string; readonly reason: string }[] {
1423
+ const pins = provider.meta?.contract?.pinnedWireFieldPaths;
1424
+ if (!Array.isArray(pins)) return [];
1425
+ return pins.filter(
1426
+ (pin) =>
1427
+ pin !== null &&
1428
+ typeof pin === "object" &&
1429
+ typeof pin.path === "string" &&
1430
+ pin.path.trim().length > 0 &&
1431
+ typeof pin.reason === "string" &&
1432
+ pin.reason.trim().length > 0,
1433
+ );
1434
+ }
1435
+
1436
+ function applyPinnedWireFieldPaths(
1437
+ provider: { meta?: { contract?: ProviderContractMetaLike } },
1438
+ diagnostics: readonly LintDiagnostic[],
1439
+ ): ProviderLintResult {
1440
+ const pins = declaredPinnedWireFieldPaths(provider);
1441
+ if (pins.length === 0) {
1442
+ return { diagnostics: [...diagnostics], information: [] };
1443
+ }
1444
+
1445
+ const pinsByPath = new Map(pins.map((pin) => [pin.path, pin]));
1446
+ const matchedPaths = new Set<string>();
1447
+ const remainingDiagnostics = diagnostics.filter((diagnostic) => {
1448
+ if (
1449
+ diagnostic.rule !== "public-schema-upstream-field" ||
1450
+ diagnostic.field === undefined ||
1451
+ !pinsByPath.has(diagnostic.field)
1452
+ ) {
1453
+ return true;
1454
+ }
1455
+ matchedPaths.add(diagnostic.field);
1456
+ return false;
1457
+ });
1458
+
1459
+ const information: ProviderLintInformation[] = [];
1460
+ for (const pin of pins) {
1461
+ if (matchedPaths.has(pin.path)) {
1462
+ information.push({
1463
+ rule: "public-schema-pinned-wire-field",
1464
+ field: pin.path,
1465
+ message: `Suppressed exact public-schema-upstream-field diagnostic. Reason: ${pin.reason}`,
1466
+ });
1467
+ continue;
1468
+ }
1469
+
1470
+ remainingDiagnostics.push({
1471
+ rule: "public-schema-pinned-wire-field-stale",
1472
+ level: "error",
1473
+ field: pin.path,
1474
+ message: `Pinned wire field path ${JSON.stringify(pin.path)} matches no current public-schema-upstream-field diagnostic; remove the stale declaration.`,
1475
+ });
1476
+ }
1477
+
1478
+ return { diagnostics: remainingDiagnostics, information };
1479
+ }
1480
+
906
1481
  export function lintProvider(
907
1482
  provider: {
908
1483
  id?: string;
@@ -922,19 +1497,18 @@ export function lintProvider(
922
1497
  operations?: Record<
923
1498
  string,
924
1499
  {
925
- description?: string;
926
1500
  descriptionKey?: string;
927
- whenToUse?: readonly string[];
928
1501
  whenToUseKeys?: readonly string[];
929
- whenNotToUse?: readonly string[];
930
1502
  whenNotToUseKeys?: readonly string[];
1503
+ connectionMode?: "none" | "optional" | "required";
1504
+ riskClass?: OperationRiskClass;
1505
+ approval?: OperationApprovalPolicy;
931
1506
  input: unknown;
932
1507
  output: unknown;
933
1508
  fixtures?: unknown;
934
- inputExamples?: readonly unknown[];
935
- derivations?: Record<string, string>;
936
1509
  handler?: unknown;
937
1510
  source?: string;
1511
+ errorCodes?: ReadonlyArray<{ code: string }>;
938
1512
  }
939
1513
  >;
940
1514
  meta?: {
@@ -944,6 +1518,14 @@ export function lintProvider(
944
1518
  },
945
1519
  options: ProviderLintOptions = {},
946
1520
  ): LintDiagnostic[] {
1521
+ return lintProviderWithInformation(provider, options).diagnostics;
1522
+ }
1523
+
1524
+ /** Internal detailed result used by the CLI to render suppression audits. */
1525
+ export function lintProviderWithInformation(
1526
+ provider: Parameters<typeof lintProvider>[0],
1527
+ options: ProviderLintOptions = {},
1528
+ ): ProviderLintResult {
947
1529
  const diagnostics: LintDiagnostic[] = [
948
1530
  ...lintAllowedHosts(provider.id, provider.allowedHosts),
949
1531
  ...lintReviewed(provider.id, provider.reviewed),
@@ -952,18 +1534,49 @@ export function lintProvider(
952
1534
  ...lintCredentialWriteUsage(provider),
953
1535
  ...lintPlaywrightDirectImports(provider),
954
1536
  ...lintSelfHostedBrowserPatterns(provider, options),
1537
+ ...lintBrowserVersionLiterals(provider),
1538
+ ...lintUndeclaredThrownErrorCodes(provider),
955
1539
  ];
956
1540
 
957
1541
  if (provider.operations) {
958
1542
  const authMode = provider.auth?.mode;
959
- if (authMode === "credentials" || authMode === "oauth2") {
1543
+ for (const [operationKey, operation] of Object.entries(provider.operations)) {
1544
+ if (isCredentialBearingAuthMode(authMode) && operation.connectionMode === undefined) {
1545
+ diagnostics.push({
1546
+ rule: "mixed-auth-connection-mode-required",
1547
+ level: "error",
1548
+ field: `operations.${operationKey}.connectionMode`,
1549
+ message: `Provider "${provider.id ?? "unknown"}" uses credential-bearing auth.mode "${authMode}"; operation "${operationKey}" must declare connectionMode explicitly.`,
1550
+ });
1551
+ }
1552
+ if (
1553
+ operation.riskClass !== undefined &&
1554
+ operation.approval !== undefined &&
1555
+ operation.approval === defaultApprovalPolicy(operation.riskClass)
1556
+ ) {
1557
+ diagnostics.push({
1558
+ rule: "redundant-approval",
1559
+ level: "error",
1560
+ field: `operations.${operationKey}.approval`,
1561
+ message: `Operation "${operationKey}" approval "${operation.approval}" repeats the default for riskClass "${operation.riskClass}"; omit approval unless it is a deliberate override.`,
1562
+ });
1563
+ }
1564
+ }
1565
+ // Every authenticated mode owns an auth.flow; `oauth2_proxied` was
1566
+ // previously exempt, which let auth-lifecycle operations ship on
1567
+ // proxied providers unchecked.
1568
+ if (
1569
+ authMode === "credentials" ||
1570
+ authMode === "oauth2" ||
1571
+ authMode === "oauth2_proxied"
1572
+ ) {
960
1573
  for (const operationKey of Object.keys(provider.operations)) {
961
- if (AUTH_OPERATION_ID_PATTERN.test(operationKey)) {
1574
+ if (isAuthLifecycleOperationId(operationKey, authMode)) {
962
1575
  diagnostics.push({
963
1576
  rule: "auth-operation-unsupported",
964
1577
  level: "error",
965
1578
  field: `operations.${operationKey}`,
966
- message: `Provider "${provider.id ?? "unknown"}" operation "${operationKey}" looks like a login/token/session exchange endpoint. Authenticated providers must expose login through the single auth.flow interface because Gateway persists only auth.flow complete turn data.credential as the connection credential. Move this logic into auth.flow.continue instead of a provider operation.`,
1579
+ message: `Provider "${provider.id ?? "unknown"}" operation "${operationKey}" performs an auth-lifecycle action (login, logout, token exchange or similar). Authenticated providers must expose the whole credential lifecycle through the single auth.flow interface because Gateway persists only auth.flow complete turn data.credential as the connection credential, and an operation that mutates the session outside that interface leaves the stored connection stale. Move sign-in logic into auth.flow.start/continue and sign-out/disconnect logic into auth.flow.abort (served by POST /auth/disconnect) instead of a provider operation.`,
967
1580
  });
968
1581
  }
969
1582
  }
@@ -971,24 +1584,19 @@ export function lintProvider(
971
1584
  }
972
1585
 
973
1586
  if (!provider.operations) {
974
- return diagnostics;
1587
+ return applyPinnedWireFieldPaths(provider, diagnostics);
975
1588
  }
976
1589
 
977
1590
  diagnostics.push(
978
1591
  ...Object.entries(provider.operations).flatMap(([operationKey, operation]) =>
979
1592
  [
980
1593
  ...lintOperation({
981
- description: operation.description ?? "",
982
1594
  descriptionKey: operation.descriptionKey,
983
- whenToUse: operation.whenToUse,
984
1595
  whenToUseKeys: operation.whenToUseKeys,
985
- whenNotToUse: operation.whenNotToUse,
986
1596
  whenNotToUseKeys: operation.whenNotToUseKeys,
987
1597
  input: operation.input,
988
1598
  output: operation.output,
989
1599
  fixtures: operation.fixtures,
990
- inputExamples: operation.inputExamples,
991
- derivations: operation.derivations,
992
1600
  }),
993
1601
  ...lintPublicSchemaFieldNames(
994
1602
  provider.id,
@@ -1007,5 +1615,5 @@ export function lintProvider(
1007
1615
  ),
1008
1616
  );
1009
1617
 
1010
- return diagnostics;
1618
+ return applyPinnedWireFieldPaths(provider, diagnostics);
1011
1619
  }