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

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 (365) hide show
  1. package/AUTHORING.md +537 -33
  2. package/CHANGELOG.md +201 -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 +1178 -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-map.ts.txt +5 -0
  228. package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-operation.ts.txt +16 -0
  229. package/src/cli/__tests__/fixtures/migrate-operation-declaration/factory-map.ts.txt +3 -0
  230. package/src/cli/__tests__/fixtures/migrate-operation-declaration/hoist-all.ts.txt +31 -0
  231. package/src/cli/__tests__/fixtures/migrate-operation-declaration/hoisted-const.ts.txt +11 -0
  232. package/src/cli/__tests__/fixtures/migrate-operation-declaration/imported-spread.ts.txt +11 -0
  233. package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-map.ts.txt +11 -0
  234. package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-spread-cast-tail.ts.txt +21 -0
  235. package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-spread-ekitan.ts.txt +11 -0
  236. package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-spread-override.ts.txt +14 -0
  237. package/src/cli/__tests__/fixtures/migrate-operation-declaration/missing-english-locale.ts.txt +7 -0
  238. package/src/cli/__tests__/fixtures/migrate-operation-declaration/no-safety.ts.txt +6 -0
  239. package/src/cli/__tests__/fixtures/migrate-operation-declaration/non-literal.ts.txt +7 -0
  240. package/src/cli/__tests__/fixtures/migrate-operation-declaration/redundant-approval.ts.txt +6 -0
  241. package/src/cli/__tests__/fixtures/migrate-operation-declaration/safety-conflict.ts.txt +7 -0
  242. package/src/cli/__tests__/fixtures/migrate-operation-declaration/stream.ts.txt +7 -0
  243. package/src/cli/__tests__/fixtures/migrate-operation-declaration/tool-router-spread.ts.txt +15 -0
  244. package/src/cli/__tests__/fixtures/migrate-operation-declaration/unparseable.ts.txt +4 -0
  245. package/src/cli/__tests__/fixtures/migrate-operation-declaration/verbatim-template.ts.txt +12 -0
  246. package/src/cli/commands.ts +33 -0
  247. package/src/cli/create.ts +50 -37
  248. package/src/cli/migrate-operation-declaration.ts +1654 -0
  249. package/src/cli/migrate-operation-shape.ts +184 -0
  250. package/src/cli/migrate-provider-shape.ts +772 -0
  251. package/src/cli/prompt-assets.ts +865 -0
  252. package/src/cli/templates/provider/AGENTS.md.tpl +17 -8
  253. package/src/cli/templates/provider/Dockerfile.tpl +1 -1
  254. package/src/cli/templates/provider/README.md.tpl +5 -5
  255. package/src/cli/templates/provider/index.ts.tpl +6 -3
  256. package/src/cli/templates/provider/operations/ping.ts.tpl +4 -1
  257. package/src/cli/templates/provider/provider.json.tpl +6 -0
  258. package/src/config/loader.ts +690 -163
  259. package/src/contract-serialization.ts +5 -7
  260. package/src/contract-types.ts +12 -5
  261. package/src/contract.ts +24 -10
  262. package/src/declaration-validation.ts +330 -0
  263. package/src/define.ts +1048 -131
  264. package/src/dev.ts +4 -1
  265. package/src/engine.ts +279 -0
  266. package/src/error-observability.ts +64 -0
  267. package/src/error-resolution.ts +127 -0
  268. package/src/errors.ts +68 -0
  269. package/src/fixture-sanitization.ts +264 -0
  270. package/src/health-scenario.ts +875 -0
  271. package/src/index.ts +220 -13
  272. package/src/lint.ts +698 -90
  273. package/src/native-address.ts +340 -0
  274. package/src/native-egress-policy.ts +358 -0
  275. package/src/observability.ts +51 -1
  276. package/src/provider.ts +161 -3
  277. package/src/runtime/auth-flow.ts +12 -0
  278. package/src/runtime/browser.ts +661 -63
  279. package/src/runtime/cache.ts +189 -14
  280. package/src/runtime/choice-wordlist.ts +145 -0
  281. package/src/runtime/choice.ts +631 -120
  282. package/src/runtime/chrome149-header-order.ts +330 -0
  283. package/src/runtime/env.ts +13 -0
  284. package/src/runtime/executor.ts +43 -31
  285. package/src/runtime/http.ts +641 -61
  286. package/src/runtime/insights.ts +2 -2
  287. package/src/runtime/instrumentation.ts +520 -15
  288. package/src/runtime/native-network-errors.ts +99 -0
  289. package/src/runtime/native-network.ts +1605 -0
  290. package/src/runtime/ocr.ts +523 -0
  291. package/src/runtime/otlp.ts +467 -21
  292. package/src/runtime/proxy-errors.ts +12 -4
  293. package/src/runtime/proxy-nodemaven.ts +221 -0
  294. package/src/runtime/proxy-telemetry.ts +244 -75
  295. package/src/runtime/redirects.ts +66 -0
  296. package/src/runtime/redis.ts +7 -2
  297. package/src/runtime/request-options.ts +680 -1
  298. package/src/runtime/resolver-config.ts +6 -0
  299. package/src/runtime/resolver-public.ts +23 -0
  300. package/src/runtime/resolver-shared.ts +33 -0
  301. package/src/runtime/resolver-vendors/bindings.ts +56 -0
  302. package/src/runtime/resolver-vendors/browser.ts +533 -0
  303. package/src/runtime/resolver-vendors/capsolver.ts +707 -0
  304. package/src/runtime/resolver-vendors/hosts.ts +38 -0
  305. package/src/runtime/resolver-vendors/twocaptcha.ts +540 -0
  306. package/src/runtime/resolver-vendors/types.ts +212 -0
  307. package/src/runtime/resolver.ts +1157 -0
  308. package/src/runtime/secrets.ts +64 -0
  309. package/src/runtime/state.ts +394 -77
  310. package/src/runtime/stealth-cookies.ts +132 -0
  311. package/src/runtime/stealth.ts +1280 -336
  312. package/src/runtime/stt.ts +1 -19
  313. package/src/runtime/timeout.ts +18 -0
  314. package/src/runtime/trace-config.ts +78 -0
  315. package/src/runtime/trace.ts +57 -17
  316. package/src/serve.ts +6 -1
  317. package/src/server/error-observability.ts +1 -0
  318. package/src/server/index.ts +34 -2
  319. package/src/server/self-test-input-tokens.ts +29 -14
  320. package/src/server/self-test.ts +191 -68
  321. package/src/server/serve-implementation.ts +3378 -0
  322. package/src/server/serve.ts +1 -1632
  323. package/src/server/trace-output.ts +129 -0
  324. package/src/server/types.ts +13 -1
  325. package/src/stateful/README.md +146 -0
  326. package/src/stateful/errors.ts +35 -0
  327. package/src/stateful/http-provider-event-emitter.ts +314 -0
  328. package/src/stateful/http-session-owner-registry.ts +306 -0
  329. package/src/stateful/index.ts +18 -0
  330. package/src/stateful/provider-event-delivery-failures.ts +80 -0
  331. package/src/stateful/provider-event-pipeline-metrics.ts +95 -0
  332. package/src/stateful/provider-event-pipeline.ts +61 -0
  333. package/src/stateful/provider-events.ts +462 -0
  334. package/src/stateful/session-key.ts +111 -0
  335. package/src/stateful/stateful-provider-adapter-context.ts +59 -0
  336. package/src/stateful/stateful-provider-adapter-metrics.ts +48 -0
  337. package/src/stateful/stateful-provider-adapter.ts +562 -0
  338. package/src/stateful/stateful-provider-observability.ts +261 -0
  339. package/src/stateful/stateful-provider-owner-forwarder.ts +287 -0
  340. package/src/stateful/stateful-provider-runtime-context.ts +92 -0
  341. package/src/stateful/stateful-provider-runtime-executor.ts +96 -0
  342. package/src/stateful/stateful-provider-session-routing.ts +546 -0
  343. package/src/stateful/stateful-provider-session-runtime.ts +403 -0
  344. package/src/stateful-signing.ts +46 -0
  345. package/src/stealth/profiles.ts +202 -231
  346. package/src/stream-evidence.ts +988 -0
  347. package/src/stream.ts +8 -1
  348. package/src/testing/index.ts +10 -1
  349. package/src/testing/run.ts +658 -15
  350. package/src/trace-sanitization.ts +63 -0
  351. package/src/types.ts +788 -174
  352. package/dist/cli/templates/provider/CLAUDE.md.tpl +0 -1
  353. package/src/cli/templates/provider/CLAUDE.md.tpl +0 -1
  354. /package/dist/cli/templates/provider/{skills → .agents/skills}/fixtures-and-recording/SKILL.md.tpl +0 -0
  355. /package/dist/cli/templates/provider/{skills → .agents/skills}/health-checks-and-fail-closed/SKILL.md.tpl +0 -0
  356. /package/dist/cli/templates/provider/{skills → .agents/skills}/normalization-standards/SKILL.md.tpl +0 -0
  357. /package/dist/cli/templates/provider/{skills → .agents/skills}/pagination-and-counts/SKILL.md.tpl +0 -0
  358. /package/dist/cli/templates/provider/{skills → .agents/skills}/upstream-contract-verification/SKILL.md.tpl +0 -0
  359. /package/dist/cli/templates/provider/{skills → .agents/skills}/upstream-notes/README.md.tpl +0 -0
  360. /package/src/cli/templates/provider/{skills → .agents/skills}/fixtures-and-recording/SKILL.md.tpl +0 -0
  361. /package/src/cli/templates/provider/{skills → .agents/skills}/health-checks-and-fail-closed/SKILL.md.tpl +0 -0
  362. /package/src/cli/templates/provider/{skills → .agents/skills}/normalization-standards/SKILL.md.tpl +0 -0
  363. /package/src/cli/templates/provider/{skills → .agents/skills}/pagination-and-counts/SKILL.md.tpl +0 -0
  364. /package/src/cli/templates/provider/{skills → .agents/skills}/upstream-contract-verification/SKILL.md.tpl +0 -0
  365. /package/src/cli/templates/provider/{skills → .agents/skills}/upstream-notes/README.md.tpl +0 -0
@@ -0,0 +1,875 @@
1
+ import { z } from "zod";
2
+ import type { JsonPrimitive, JsonValue } from "./contract-json.js";
3
+ export type NonEmpty<T> = readonly [T, ...T[]];
4
+
5
+ const finiteInt = (min: number, max?: number) => {
6
+ const schema = z.number().finite().int().min(min);
7
+ return max === undefined ? schema : schema.max(max);
8
+ };
9
+
10
+ const nonEmptyArray = <T>(schema: z.ZodType<T>) =>
11
+ z
12
+ .array(schema)
13
+ .min(1)
14
+ .transform((value) => value as [T, ...T[]]);
15
+
16
+ const jsonValueSchema: z.ZodType<JsonValue> = z.lazy(() =>
17
+ z.union([
18
+ z.null(),
19
+ z.boolean(),
20
+ z.number().finite(),
21
+ z.string(),
22
+ z.array(jsonValueSchema),
23
+ z.record(z.string(), jsonValueSchema),
24
+ ]),
25
+ );
26
+
27
+ const pathPartSchema = z.union([z.string().min(1), finiteInt(0, 10_000)]);
28
+
29
+ const propertyNameSchema = z
30
+ .string()
31
+ .min(1)
32
+ .superRefine((value, ctx) => {
33
+ if (new TextEncoder().encode(value).byteLength > 128)
34
+ ctx.addIssue({ code: "custom", message: "property name exceeds 128 UTF-8 bytes" });
35
+ });
36
+
37
+ const pathSegmentSchema = z.union([
38
+ z.object({ kind: z.literal("property"), name: propertyNameSchema }).strict(),
39
+ z.object({ kind: z.literal("index"), index: finiteInt(0, 10_000) }).strict(),
40
+ ]);
41
+
42
+ export const BoundedJsonPathSchema = z
43
+ .object({ root: z.literal("$"), segments: z.array(pathSegmentSchema).max(12) })
44
+ .strict();
45
+ export type BoundedJsonPath = z.infer<typeof BoundedJsonPathSchema>;
46
+
47
+ const valueTypeSchema = z.enum([
48
+ "null",
49
+ "boolean",
50
+ "number",
51
+ "string",
52
+ "object",
53
+ "array",
54
+ "json",
55
+ "established_connection",
56
+ ]);
57
+ export const ValueTypeSchema = valueTypeSchema;
58
+ export type ValueType = z.infer<typeof valueTypeSchema>;
59
+
60
+ const stepReferenceSchema = z
61
+ .object({
62
+ namespace: z.literal("steps"),
63
+ binding: z.string().min(1),
64
+ path: z.array(pathPartSchema).min(1).max(12),
65
+ })
66
+ .strict();
67
+ export const StepReferenceSchema = stepReferenceSchema;
68
+ export type StepReference = z.infer<typeof stepReferenceSchema>;
69
+
70
+ const credentialReferenceSchema = z
71
+ .object({
72
+ namespace: z.literal("credentials"),
73
+ alias: z.string().min(1),
74
+ field: z.literal("connection"),
75
+ })
76
+ .strict();
77
+ const attemptReferenceSchema = z
78
+ .object({
79
+ namespace: z.literal("attempt"),
80
+ field: z.enum([
81
+ "id",
82
+ "external_ref",
83
+ "provider_id",
84
+ "scenario_id",
85
+ "started_at",
86
+ "deadline_at",
87
+ ]),
88
+ })
89
+ .strict();
90
+ const candidateReferenceSchema = z
91
+ .object({
92
+ namespace: z.literal("candidate"),
93
+ binding: z.string().min(1),
94
+ path: z.union([
95
+ z.tuple([z.literal("item")]).rest(pathPartSchema),
96
+ z.tuple([z.literal("result")]).rest(pathPartSchema),
97
+ ]),
98
+ })
99
+ .strict();
100
+
101
+ export type CredentialReference = z.infer<typeof credentialReferenceSchema>;
102
+ export type EstablishedConnectionReference = CredentialReference & { field: "connection" };
103
+ export type AttemptReference = z.infer<typeof attemptReferenceSchema>;
104
+ export type CandidateReference = z.infer<typeof candidateReferenceSchema>;
105
+ export type Reference =
106
+ | StepReference
107
+ | CredentialReference
108
+ | AttemptReference
109
+ | CandidateReference;
110
+ const referenceSchema = z.discriminatedUnion("namespace", [
111
+ stepReferenceSchema,
112
+ credentialReferenceSchema,
113
+ attemptReferenceSchema,
114
+ candidateReferenceSchema,
115
+ ]);
116
+ export const ReferenceSchema = referenceSchema;
117
+
118
+ const relativeDateNodeSchema = z
119
+ .object({
120
+ relativeDate: z
121
+ .object({
122
+ anchor: z.literal("operation_started_at"),
123
+ offsetDays: finiteInt(1, 365),
124
+ timeZone: z.literal("Asia/Seoul"),
125
+ format: z.enum(["YYYY-MM-DD", "YYYYMMDD"]),
126
+ })
127
+ .strict(),
128
+ })
129
+ .strict();
130
+ export type RelativeDateNode = z.infer<typeof relativeDateNodeSchema>;
131
+ export const RelativeDateNodeSchema = relativeDateNodeSchema;
132
+ const referenceNodeSchema = z.object({ ref: referenceSchema }).strict();
133
+
134
+ export type ReferenceNode = { ref: Reference };
135
+ export type JsonTemplate =
136
+ | JsonPrimitive
137
+ | ReferenceNode
138
+ | RelativeDateNode
139
+ | JsonTemplate[]
140
+ | { [key: string]: JsonTemplate };
141
+ // Reserved template keys may only occur in their exact, single-key node forms.
142
+ const templateRecordSchema = z
143
+ .record(
144
+ z.string(),
145
+ z.lazy(() => jsonTemplateSchema),
146
+ )
147
+ .superRefine((value, ctx) => {
148
+ if (Object.hasOwn(value, "ref") || Object.hasOwn(value, "relativeDate"))
149
+ ctx.addIssue({ code: "custom", message: "reserved template keys require a sole-key node" });
150
+ });
151
+ const jsonTemplateSchema: z.ZodType<JsonTemplate> = z.lazy(() =>
152
+ z.union([
153
+ z.null(),
154
+ z.boolean(),
155
+ z.number().finite(),
156
+ z.string(),
157
+ referenceNodeSchema,
158
+ relativeDateNodeSchema,
159
+ z.array(jsonTemplateSchema),
160
+ templateRecordSchema,
161
+ ]),
162
+ );
163
+ export const JsonTemplateSchema = jsonTemplateSchema;
164
+
165
+ export type SafeRegex = {
166
+ readonly engine: "re2";
167
+ readonly pattern: string;
168
+ readonly flags: "" | "i";
169
+ };
170
+ function containsNestedRegexQuantifier(pattern: string): boolean {
171
+ const groups: boolean[] = [];
172
+ let escaped = false;
173
+ let inClass = false;
174
+ let closedQuantified = false;
175
+ for (let i = 0; i < pattern.length; i += 1) {
176
+ const c = pattern[i];
177
+ if (escaped) {
178
+ escaped = false;
179
+ closedQuantified = false;
180
+ continue;
181
+ }
182
+ if (c === "\\") {
183
+ escaped = true;
184
+ closedQuantified = false;
185
+ continue;
186
+ }
187
+ if (c === "[" && !inClass) {
188
+ inClass = true;
189
+ continue;
190
+ }
191
+ if (c === "]" && inClass) {
192
+ inClass = false;
193
+ continue;
194
+ }
195
+ if (inClass) continue;
196
+ if (c === "(") {
197
+ groups.push(false);
198
+ closedQuantified = false;
199
+ continue;
200
+ }
201
+ if (c === ")") {
202
+ closedQuantified = groups.pop() ?? false;
203
+ if (closedQuantified && groups.length > 0) groups[groups.length - 1] = true;
204
+ continue;
205
+ }
206
+ const brace = c === "{" && /^\{\d+(?:,\d*)?\}/.test(pattern.slice(i));
207
+ if (c === "*" || c === "+" || brace || (c === "?" && pattern[i - 1] !== "(")) {
208
+ if (closedQuantified) return true;
209
+ if (groups.length > 0) groups[groups.length - 1] = true;
210
+ }
211
+ closedQuantified = false;
212
+ }
213
+ return false;
214
+ }
215
+ export const SafeRegexSchema = z
216
+ .object({ engine: z.literal("re2"), pattern: z.string().max(256), flags: z.enum(["", "i"]) })
217
+ .strict()
218
+ .superRefine((value, ctx) => {
219
+ if (/\\[1-9kg]/.test(value.pattern))
220
+ ctx.addIssue({ code: "custom", path: ["pattern"], message: "backreferences are not supported by RE2" });
221
+ if (/\(\?/.test(value.pattern))
222
+ ctx.addIssue({ code: "custom", path: ["pattern"], message: "extended groups are not supported by RE2" });
223
+ if (containsNestedRegexQuantifier(value.pattern))
224
+ ctx.addIssue({ code: "custom", path: ["pattern"], message: "nested regex quantifiers are not permitted" });
225
+ try {
226
+ new RegExp(value.pattern, value.flags);
227
+ } catch {
228
+ ctx.addIssue({ code: "custom", path: ["pattern"], message: "pattern must be valid regular-expression syntax" });
229
+ }
230
+ });
231
+
232
+ const operandLiteralSchema = jsonValueSchema.superRefine((value, ctx) => {
233
+ if (
234
+ value !== null &&
235
+ typeof value === "object" &&
236
+ !Array.isArray(value) &&
237
+ Object.hasOwn(value, "ref")
238
+ )
239
+ ctx.addIssue({
240
+ code: "custom",
241
+ message: 'the object key "ref" is reserved for a validated reference',
242
+ });
243
+ });
244
+ const operandSchema = z.union([operandLiteralSchema, referenceNodeSchema]);
245
+ export const OperandSchema = operandSchema;
246
+ export type Operand = JsonValue | ReferenceNode;
247
+
248
+ const predicateSchema = z.union([
249
+ z
250
+ .object({
251
+ kind: z.literal("predicate"),
252
+ operator: z.enum(["exists", "not_exists", "non_empty", "is_true"]),
253
+ actual: operandSchema,
254
+ })
255
+ .strict(),
256
+ z
257
+ .object({
258
+ kind: z.literal("predicate"),
259
+ operator: z.enum(["equals", "not_equals", "contains"]),
260
+ actual: operandSchema,
261
+ expected: operandSchema,
262
+ })
263
+ .strict(),
264
+ z
265
+ .object({
266
+ kind: z.literal("predicate"),
267
+ operator: z.literal("matches"),
268
+ actual: operandSchema,
269
+ pattern: SafeRegexSchema,
270
+ })
271
+ .strict(),
272
+ z
273
+ .object({
274
+ kind: z.literal("predicate"),
275
+ operator: z.enum(["number_gt", "number_gte", "number_lt", "number_lte"]),
276
+ actual: operandSchema,
277
+ expected: operandSchema,
278
+ })
279
+ .strict(),
280
+ z
281
+ .object({
282
+ kind: z.literal("predicate"),
283
+ operator: z.enum(["array_length_eq", "array_length_gte", "array_length_lte"]),
284
+ actual: operandSchema,
285
+ expected: z.number().finite().int().nonnegative(),
286
+ })
287
+ .strict(),
288
+ z
289
+ .object({
290
+ kind: z.literal("predicate"),
291
+ operator: z.literal("status_2xx"),
292
+ actual: operandSchema,
293
+ })
294
+ .strict(),
295
+ z
296
+ .object({
297
+ kind: z.literal("predicate"),
298
+ operator: z.literal("type_is"),
299
+ actual: operandSchema,
300
+ expected: z.enum(["null", "boolean", "number", "string", "object", "array"]),
301
+ })
302
+ .strict(),
303
+ ]);
304
+ export const AssertionPredicateSchema = predicateSchema;
305
+ export type AssertionPredicate = z.infer<typeof predicateSchema>;
306
+
307
+ const scopedItemReferenceSchema = z
308
+ .object({
309
+ namespace: z.literal("item"),
310
+ binding: z.string().min(1),
311
+ path: z.array(pathPartSchema).max(12),
312
+ })
313
+ .strict();
314
+ export const ScopedItemReferenceSchema = scopedItemReferenceSchema;
315
+ export type ScopedItemReference = z.infer<typeof scopedItemReferenceSchema>;
316
+
317
+ const scopedOperandSchema = z.union([
318
+ operandLiteralSchema,
319
+ z.object({ ref: scopedItemReferenceSchema }).strict(),
320
+ referenceNodeSchema,
321
+ ]);
322
+ export const ScopedOperandSchema = scopedOperandSchema;
323
+ export type ScopedOperand = z.infer<typeof scopedOperandSchema>;
324
+
325
+ const scopedPredicateSchema = z.union([
326
+ z
327
+ .object({
328
+ kind: z.literal("predicate"),
329
+ operator: z.enum(["exists", "not_exists", "non_empty", "is_true"]),
330
+ actual: scopedOperandSchema,
331
+ })
332
+ .strict(),
333
+ z
334
+ .object({
335
+ kind: z.literal("predicate"),
336
+ operator: z.enum(["equals", "not_equals", "contains"]),
337
+ actual: scopedOperandSchema,
338
+ expected: scopedOperandSchema,
339
+ })
340
+ .strict(),
341
+ z
342
+ .object({
343
+ kind: z.literal("predicate"),
344
+ operator: z.literal("matches"),
345
+ actual: scopedOperandSchema,
346
+ pattern: SafeRegexSchema,
347
+ })
348
+ .strict(),
349
+ z
350
+ .object({
351
+ kind: z.literal("predicate"),
352
+ operator: z.enum(["number_gt", "number_gte", "number_lt", "number_lte"]),
353
+ actual: scopedOperandSchema,
354
+ expected: scopedOperandSchema,
355
+ })
356
+ .strict(),
357
+ z
358
+ .object({
359
+ kind: z.literal("predicate"),
360
+ operator: z.enum(["array_length_eq", "array_length_gte", "array_length_lte"]),
361
+ actual: scopedOperandSchema,
362
+ expected: z.number().finite().int().nonnegative(),
363
+ })
364
+ .strict(),
365
+ z
366
+ .object({
367
+ kind: z.literal("predicate"),
368
+ operator: z.literal("status_2xx"),
369
+ actual: scopedOperandSchema,
370
+ })
371
+ .strict(),
372
+ z
373
+ .object({
374
+ kind: z.literal("predicate"),
375
+ operator: z.literal("type_is"),
376
+ actual: scopedOperandSchema,
377
+ expected: z.enum(["null", "boolean", "number", "string", "object", "array"]),
378
+ })
379
+ .strict(),
380
+ ]);
381
+ export const ScopedAssertionPredicateSchema = scopedPredicateSchema;
382
+ export type ScopedAssertionPredicate = z.infer<typeof scopedPredicateSchema>;
383
+
384
+ export type ScopedAssertionExpression =
385
+ | { kind: "all" | "any"; clauses: NonEmpty<ScopedAssertionExpression> }
386
+ | { kind: "not"; clause: ScopedAssertionExpression }
387
+ | ScopedAssertionPredicate;
388
+ const scopedExpressionSchema: z.ZodType<ScopedAssertionExpression> = z.lazy(() =>
389
+ z.union([
390
+ z.object({ kind: z.literal("all"), clauses: nonEmptyArray(scopedExpressionSchema) }).strict(),
391
+ z.object({ kind: z.literal("any"), clauses: nonEmptyArray(scopedExpressionSchema) }).strict(),
392
+ z.object({ kind: z.literal("not"), clause: scopedExpressionSchema }).strict(),
393
+ scopedPredicateSchema,
394
+ ]),
395
+ );
396
+ export const ScopedAssertionExpressionSchema = scopedExpressionSchema;
397
+
398
+ export type Quantifier = {
399
+ kind: "quantifier";
400
+ quantifier: "every" | "any";
401
+ items: { ref: StepReference | CandidateReference };
402
+ itemBinding: string;
403
+ maxItems: number;
404
+ clause: ScopedAssertionExpression;
405
+ };
406
+ const quantifierSchema: z.ZodType<Quantifier> = z
407
+ .object({
408
+ kind: z.literal("quantifier"),
409
+ quantifier: z.enum(["every", "any"]),
410
+ items: z.object({ ref: z.union([stepReferenceSchema, candidateReferenceSchema]) }).strict(),
411
+ itemBinding: z.string().min(1),
412
+ maxItems: finiteInt(1, 100),
413
+ clause: scopedExpressionSchema,
414
+ })
415
+ .strict();
416
+ export const QuantifierSchema = quantifierSchema;
417
+
418
+ export type AssertionExpression =
419
+ | { kind: "all" | "any"; clauses: NonEmpty<AssertionExpression> }
420
+ | { kind: "not"; clause: AssertionExpression }
421
+ | AssertionPredicate
422
+ | Quantifier;
423
+ const expressionSchema: z.ZodType<AssertionExpression> = z.lazy(() =>
424
+ z.union([
425
+ z.object({ kind: z.literal("all"), clauses: nonEmptyArray(expressionSchema) }).strict(),
426
+ z.object({ kind: z.literal("any"), clauses: nonEmptyArray(expressionSchema) }).strict(),
427
+ z.object({ kind: z.literal("not"), clause: expressionSchema }).strict(),
428
+ predicateSchema,
429
+ quantifierSchema,
430
+ ]),
431
+ );
432
+ export const AssertionExpressionSchema = expressionSchema;
433
+
434
+ export type RetryPolicy = {
435
+ maxAttempts: number;
436
+ retryOn: NonEmpty<"transport_error" | "timeout" | "http_429" | "http_5xx">;
437
+ backoff:
438
+ | { kind: "fixed"; delayMs: number }
439
+ | { kind: "exponential"; initialDelayMs: number; maxDelayMs: number };
440
+ attemptTimeoutMs?: number;
441
+ };
442
+ const retryPolicySchema: z.ZodType<RetryPolicy> = z
443
+ .object({
444
+ maxAttempts: finiteInt(1, 3),
445
+ retryOn: nonEmptyArray(z.enum(["transport_error", "timeout", "http_429", "http_5xx"])),
446
+ backoff: z.union([
447
+ z
448
+ .object({ kind: z.literal("fixed"), delayMs: z.number().finite().int().nonnegative() })
449
+ .strict(),
450
+ z
451
+ .object({
452
+ kind: z.literal("exponential"),
453
+ initialDelayMs: z.number().finite().int().nonnegative(),
454
+ maxDelayMs: z.number().finite().int().nonnegative(),
455
+ })
456
+ .strict(),
457
+ ]),
458
+ attemptTimeoutMs: finiteInt(1, 600_000).optional(),
459
+ })
460
+ .strict();
461
+ export const RetryPolicySchema = retryPolicySchema;
462
+
463
+ export type CandidatePolicy = {
464
+ items: StepReference;
465
+ itemBinding: string;
466
+ itemType: "string" | "number" | "object";
467
+ maxAttempts: number;
468
+ accept: AssertionExpression;
469
+ };
470
+ const candidatePolicySchema: z.ZodType<CandidatePolicy> = z
471
+ .object({
472
+ items: stepReferenceSchema,
473
+ itemBinding: z.string().min(1),
474
+ itemType: z.enum(["string", "number", "object"]),
475
+ maxAttempts: finiteInt(1, 10),
476
+ accept: expressionSchema,
477
+ })
478
+ .strict();
479
+ export const CandidatePolicySchema = candidatePolicySchema;
480
+
481
+ export type CandidateBlock = {
482
+ scope: "step_block";
483
+ items: StepReference;
484
+ itemBinding: string;
485
+ itemType: "string" | "number" | "object";
486
+ members: readonly [string, string, ...string[]];
487
+ maxAttempts: number;
488
+ accept: AssertionExpression;
489
+ };
490
+ const candidateBlockSchema: z.ZodType<CandidateBlock> = z
491
+ .object({
492
+ scope: z.literal("step_block"),
493
+ items: stepReferenceSchema,
494
+ itemBinding: z.string().min(1),
495
+ itemType: z.enum(["string", "number", "object"]),
496
+ members: z
497
+ .array(z.string().min(1))
498
+ .min(2)
499
+ .max(16)
500
+ .transform((value) => value as [string, string, ...string[]]),
501
+ maxAttempts: finiteInt(1, 10),
502
+ accept: expressionSchema,
503
+ })
504
+ .strict();
505
+ export const CandidateBlockSchema = candidateBlockSchema;
506
+
507
+ export type JournalPolicy = {
508
+ kind: "side_effect_barrier";
509
+ version: 1;
510
+ key: Reference;
511
+ before: "required";
512
+ after: "required";
513
+ replay: "deny_after_started";
514
+ };
515
+ const journalPolicySchema: z.ZodType<JournalPolicy> = z
516
+ .object({
517
+ kind: z.literal("side_effect_barrier"),
518
+ version: z.literal(1),
519
+ key: referenceSchema,
520
+ before: z.literal("required"),
521
+ after: z.literal("required"),
522
+ replay: z.literal("deny_after_started"),
523
+ })
524
+ .strict();
525
+ export const JournalPolicySchema = journalPolicySchema;
526
+
527
+ export type StepBase = { id: string; result: string; timeoutMs?: number };
528
+ const stepBaseSchema = z
529
+ .object({
530
+ id: z.string().min(1),
531
+ result: z.string().min(1),
532
+ timeoutMs: finiteInt(1, 600_000).optional(),
533
+ })
534
+ .strict();
535
+ export type GuardReasonCode = "expected_absence";
536
+ export type GuardAttribution = {
537
+ operationId: string;
538
+ status: "degraded";
539
+ reasonCode: GuardReasonCode;
540
+ reasonKey: string;
541
+ };
542
+ export type OperationResult = {
543
+ kind: "operation_result";
544
+ status_code: number;
545
+ data: JsonValue;
546
+ request_id: string;
547
+ duration_ms: number;
548
+ candidate?: { attempts: number; selected_index: number | null };
549
+ };
550
+ export type ExtractResult<T extends JsonValue = JsonValue> = {
551
+ kind: "extract_result";
552
+ found: boolean;
553
+ value: T | null;
554
+ };
555
+ export type AssertResult = {
556
+ kind: "assert_result";
557
+ passed: boolean;
558
+ failed_clause_paths: string[];
559
+ };
560
+ export type GuardResult = { kind: "guard_result"; passed: true };
561
+
562
+ const attributionSchema = z
563
+ .object({
564
+ operationId: z.string().min(1),
565
+ status: z.literal("degraded"),
566
+ reasonCode: z.literal("expected_absence"),
567
+ reasonKey: z.string().min(1),
568
+ })
569
+ .strict();
570
+
571
+ export type FindFirst = {
572
+ kind: "find_first";
573
+ itemBinding: string;
574
+ predicate: ScopedAssertionExpression;
575
+ maxScan: number;
576
+ };
577
+ const findFirstSchema: z.ZodType<FindFirst> = z
578
+ .object({
579
+ kind: z.literal("find_first"),
580
+ itemBinding: z.string().min(1),
581
+ predicate: scopedExpressionSchema,
582
+ maxScan: finiteInt(1, 100),
583
+ })
584
+ .strict();
585
+ export const FindFirstSchema = findFirstSchema;
586
+
587
+ export type OperationStep = StepBase & {
588
+ kind: "operation";
589
+ operationId: string;
590
+ inputTemplate: JsonTemplate;
591
+ connection?: CredentialReference;
592
+ retry?: RetryPolicy;
593
+ candidate?: CandidatePolicy | CandidateBlock;
594
+ journal?: JournalPolicy;
595
+ };
596
+ export type ExtractStep = StepBase & {
597
+ kind: "extract";
598
+ from: StepReference;
599
+ selector: BoundedJsonPath | FindFirst;
600
+ valueType: ValueType;
601
+ required: boolean;
602
+ };
603
+ export type AssertStep = StepBase & {
604
+ kind: "assert";
605
+ coversOperations: NonEmpty<string>;
606
+ expression: AssertionExpression;
607
+ };
608
+ export type GuardStep = StepBase & {
609
+ kind: "guard";
610
+ condition: AssertionExpression;
611
+ onFail: { attribute: NonEmpty<GuardAttribution>; stop: "scenario" };
612
+ };
613
+ export type HealthStep = OperationStep | ExtractStep | AssertStep | GuardStep;
614
+
615
+ const operationStepSchema = stepBaseSchema
616
+ .extend({
617
+ kind: z.literal("operation"),
618
+ operationId: z.string().min(1),
619
+ inputTemplate: jsonTemplateSchema,
620
+ connection: credentialReferenceSchema.optional(),
621
+ retry: retryPolicySchema.optional(),
622
+ candidate: z.union([candidatePolicySchema, candidateBlockSchema]).optional(),
623
+ journal: journalPolicySchema.optional(),
624
+ })
625
+ .strict();
626
+ const extractStepSchema = stepBaseSchema
627
+ .extend({
628
+ kind: z.literal("extract"),
629
+ from: stepReferenceSchema,
630
+ selector: z.union([BoundedJsonPathSchema, findFirstSchema]),
631
+ valueType: valueTypeSchema,
632
+ required: z.boolean(),
633
+ })
634
+ .strict();
635
+ const assertStepSchema = stepBaseSchema
636
+ .extend({
637
+ kind: z.literal("assert"),
638
+ coversOperations: nonEmptyArray(z.string().min(1)),
639
+ expression: expressionSchema,
640
+ })
641
+ .strict();
642
+ const guardStepSchema = stepBaseSchema
643
+ .extend({
644
+ kind: z.literal("guard"),
645
+ condition: expressionSchema,
646
+ onFail: z
647
+ .object({ attribute: nonEmptyArray(attributionSchema), stop: z.literal("scenario") })
648
+ .strict(),
649
+ })
650
+ .strict();
651
+ export const HealthStepSchema = z.discriminatedUnion("kind", [
652
+ operationStepSchema,
653
+ extractStepSchema,
654
+ assertStepSchema,
655
+ guardStepSchema,
656
+ ]);
657
+ export const OperationStepSchema = operationStepSchema;
658
+ export const ExtractStepSchema = extractStepSchema;
659
+ export const AssertStepSchema = assertStepSchema;
660
+ export const GuardStepSchema = guardStepSchema;
661
+
662
+ export type ManualTriggerPolicy =
663
+ | { enabled: false; reasonKey: string }
664
+ | {
665
+ enabled: true;
666
+ requiresAcknowledgement: boolean;
667
+ risk: "read_only" | "writes_external_state";
668
+ minManualIntervalMs: number;
669
+ publicRationaleKey: string;
670
+ };
671
+ const manualTriggerSchema: z.ZodType<ManualTriggerPolicy> = z.union([
672
+ z.object({ enabled: z.literal(false), reasonKey: z.string().min(1) }).strict(),
673
+ z
674
+ .object({
675
+ enabled: z.literal(true),
676
+ requiresAcknowledgement: z.boolean(),
677
+ risk: z.enum(["read_only", "writes_external_state"]),
678
+ minManualIntervalMs: finiteInt(1000, 86_400_000),
679
+ publicRationaleKey: z.string().min(1),
680
+ })
681
+ .strict(),
682
+ ]);
683
+ export const ManualTriggerPolicySchema = manualTriggerSchema;
684
+
685
+ export type CredentialRefDeclaration = { alias: string; kind: "connection" };
686
+ const credentialRefDeclarationSchema = z
687
+ .object({ alias: z.string().min(1), kind: z.literal("connection") })
688
+ .strict();
689
+ export const CredentialRefDeclarationSchema = credentialRefDeclarationSchema;
690
+
691
+ export type HealthScenario = {
692
+ scenarioVersion: 2;
693
+ id: string;
694
+ display: { titleKey: string; descriptionKey?: string };
695
+ schedule: { kind: "interval"; intervalMs: number; jitterMs: number };
696
+ timeoutMs: number;
697
+ cooldownMs?: number;
698
+ manualTrigger?: ManualTriggerPolicy;
699
+ coversOperations: NonEmpty<string>;
700
+ credentialRefs: CredentialRefDeclaration[];
701
+ steps: NonEmpty<HealthStep>;
702
+ };
703
+
704
+ export const HealthScenarioSchema: z.ZodType<HealthScenario> = z
705
+ .object({
706
+ scenarioVersion: z.literal(2),
707
+ id: z.string().min(1),
708
+ display: z
709
+ .object({ titleKey: z.string().min(1), descriptionKey: z.string().min(1).optional() })
710
+ .strict(),
711
+ schedule: z
712
+ .object({
713
+ kind: z.literal("interval"),
714
+ intervalMs: finiteInt(30_000, 604_800_000),
715
+ jitterMs: finiteInt(0),
716
+ })
717
+ .strict(),
718
+ timeoutMs: finiteInt(1000, 600_000),
719
+ cooldownMs: finiteInt(0, 86_400_000).optional(),
720
+ manualTrigger: manualTriggerSchema.optional(),
721
+ coversOperations: nonEmptyArray(z.string().min(1)),
722
+ credentialRefs: z.array(credentialRefDeclarationSchema),
723
+ steps: z
724
+ .array(HealthStepSchema)
725
+ .min(1)
726
+ .max(64)
727
+ .transform((value) => value as [HealthStep, ...HealthStep[]]),
728
+ })
729
+ .strict()
730
+ .superRefine((value, ctx) => {
731
+ if (value.schedule.jitterMs > value.schedule.intervalMs)
732
+ ctx.addIssue({
733
+ code: "custom",
734
+ path: ["schedule", "jitterMs"],
735
+ message: "jitterMs must not exceed intervalMs",
736
+ });
737
+ if (value.timeoutMs > value.schedule.intervalMs)
738
+ ctx.addIssue({
739
+ code: "custom",
740
+ path: ["timeoutMs"],
741
+ message: "timeoutMs must not exceed intervalMs",
742
+ });
743
+ const ids = new Set<string>();
744
+ const bindings = new Set<string>();
745
+ for (const [index, step] of value.steps.entries()) {
746
+ if (ids.has(step.id))
747
+ ctx.addIssue({
748
+ code: "custom",
749
+ path: ["steps", index, "id"],
750
+ message: `duplicate step ${step.id}`,
751
+ });
752
+ ids.add(step.id);
753
+ if (bindings.has(step.result))
754
+ ctx.addIssue({
755
+ code: "custom",
756
+ path: ["steps", index, "result"],
757
+ message: `duplicate result binding ${step.result}`,
758
+ });
759
+ bindings.add(step.result);
760
+ if (step.timeoutMs !== undefined && step.timeoutMs > value.timeoutMs)
761
+ ctx.addIssue({
762
+ code: "custom",
763
+ path: ["steps", index, "timeoutMs"],
764
+ message: "step timeout must not exceed the scenario timeout",
765
+ });
766
+ if (
767
+ step.kind === "operation" &&
768
+ step.retry?.retryOn.includes("timeout") &&
769
+ step.retry.attemptTimeoutMs === undefined
770
+ )
771
+ ctx.addIssue({
772
+ code: "custom",
773
+ path: ["steps", index, "retry", "attemptTimeoutMs"],
774
+ message: "timeout retry requires attemptTimeoutMs",
775
+ });
776
+
777
+ const expression =
778
+ step.kind === "assert"
779
+ ? step.expression
780
+ : step.kind === "guard"
781
+ ? step.condition
782
+ : step.kind === "operation" && step.candidate
783
+ ? step.candidate.accept
784
+ : undefined;
785
+ if (expression) walkExpression(expression, ctx, ["steps", index], 0, { count: 0 }, true);
786
+ if (step.kind === "extract" && "kind" in step.selector && step.selector.kind === "find_first")
787
+ walkExpression(
788
+ step.selector.predicate,
789
+ ctx,
790
+ ["steps", index, "selector", "predicate"],
791
+ 0,
792
+ { count: 0 },
793
+ false,
794
+ step.selector.itemBinding,
795
+ );
796
+ }
797
+ try {
798
+ const serialized = JSON.stringify(value);
799
+ if (new TextEncoder().encode(serialized).byteLength > 128 * 1024)
800
+ ctx.addIssue({ code: "custom", path: [], message: "serialized scenario exceeds 128 KiB" });
801
+ } catch {
802
+ ctx.addIssue({ code: "custom", path: [], message: "scenario must be JSON-serializable" });
803
+ }
804
+ });
805
+
806
+ function walkExpression(
807
+ expression: AssertionExpression | ScopedAssertionExpression,
808
+ ctx: z.RefinementCtx,
809
+ path: PropertyKey[],
810
+ depth: number,
811
+ leaves: { count: number },
812
+ allowQuantifier: boolean,
813
+ scopedBinding?: string,
814
+ ): void {
815
+ if (depth > 8) {
816
+ ctx.addIssue({ code: "custom", path, message: "expression depth exceeds 8" });
817
+ return;
818
+ }
819
+ if (expression.kind === "quantifier") {
820
+ if (!allowQuantifier) {
821
+ ctx.addIssue({ code: "custom", path, message: "quantifiers may not be nested" });
822
+ return;
823
+ }
824
+ if (expression.itemBinding === scopedBinding) {
825
+ ctx.addIssue({
826
+ code: "custom",
827
+ path: [...path, "itemBinding"],
828
+ message: "item binding is invalid or shadows an enclosing binding",
829
+ });
830
+ return;
831
+ }
832
+ walkExpression(
833
+ expression.clause,
834
+ ctx,
835
+ [...path, "clause"],
836
+ depth + 1,
837
+ leaves,
838
+ false,
839
+ expression.itemBinding,
840
+ );
841
+ return;
842
+ }
843
+ if (expression.kind === "all" || expression.kind === "any") {
844
+ for (const [index, clause] of expression.clauses.entries())
845
+ walkExpression(
846
+ clause,
847
+ ctx,
848
+ [...path, "clauses", index],
849
+ depth + 1,
850
+ leaves,
851
+ allowQuantifier,
852
+ scopedBinding,
853
+ );
854
+ return;
855
+ }
856
+ if (expression.kind === "not") {
857
+ walkExpression(
858
+ expression.clause,
859
+ ctx,
860
+ [...path, "clause"],
861
+ depth + 1,
862
+ leaves,
863
+ allowQuantifier,
864
+ scopedBinding,
865
+ );
866
+ return;
867
+ }
868
+ leaves.count += 1;
869
+ if (leaves.count > 64)
870
+ ctx.addIssue({ code: "custom", path, message: "expression contains more than 64 leaves" });
871
+ }
872
+
873
+ export function defineHealthScenario(input: unknown): HealthScenario {
874
+ return HealthScenarioSchema.parse(input);
875
+ }