@greatwait/contracts 0.1.0-alpha.3 → 0.1.0-alpha.5

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 (345) hide show
  1. package/README.md +12 -10
  2. package/dist/authority.d.ts +33 -27
  3. package/dist/authority.js +12 -2
  4. package/dist/billing.d.ts +1231 -0
  5. package/dist/billing.js +833 -0
  6. package/dist/callback-safety.d.ts +2 -2
  7. package/dist/callback-safety.js +15 -5
  8. package/dist/compatibility.d.ts +3 -3
  9. package/dist/compatibility.js +1 -1
  10. package/dist/configuration-bounds.d.ts +13 -0
  11. package/dist/configuration-bounds.js +13 -0
  12. package/dist/data-export.d.ts +67 -0
  13. package/dist/data-export.js +182 -0
  14. package/dist/effective-limits.d.ts +40 -0
  15. package/dist/effective-limits.js +42 -0
  16. package/dist/endpoints.d.ts +186 -245
  17. package/dist/endpoints.js +82 -86
  18. package/dist/events.d.ts +17 -17
  19. package/dist/events.js +1 -1
  20. package/dist/first-value-journey.d.ts +50 -56
  21. package/dist/first-value-journey.js +17 -20
  22. package/dist/forecast.d.ts +182 -92
  23. package/dist/forecast.js +88 -25
  24. package/dist/identifiers.d.ts +20 -20
  25. package/dist/identifiers.js +1 -1
  26. package/dist/identity.d.ts +41 -37
  27. package/dist/identity.js +5 -4
  28. package/dist/index.d.ts +12 -6
  29. package/dist/index.js +12 -6
  30. package/dist/mcp.d.ts +21 -19
  31. package/dist/mcp.js +2 -2
  32. package/dist/naming.d.ts +45 -0
  33. package/dist/naming.js +159 -0
  34. package/dist/oauth.d.ts +186 -172
  35. package/dist/oauth.js +1 -1
  36. package/dist/operation-definitions.d.ts +9 -0
  37. package/dist/operation-definitions.js +7 -0
  38. package/dist/operational-config.d.ts +101 -0
  39. package/dist/operational-config.js +341 -0
  40. package/dist/operations.d.ts +4742 -2170
  41. package/dist/operations.js +3064 -932
  42. package/dist/organization-lifecycle.d.ts +34 -0
  43. package/dist/organization-lifecycle.js +122 -0
  44. package/dist/partition.d.ts +43 -9
  45. package/dist/partition.js +3 -3
  46. package/dist/policy.d.ts +4 -4
  47. package/dist/policy.js +9 -5
  48. package/dist/problems.d.ts +57 -28
  49. package/dist/problems.js +55 -3
  50. package/dist/protocol.d.ts +209 -80
  51. package/dist/protocol.js +110 -37
  52. package/dist/scheduling-records.d.ts +349 -285
  53. package/dist/scheduling-records.js +1 -1
  54. package/dist/scheduling.d.ts +707 -361
  55. package/dist/scheduling.js +443 -61
  56. package/dist/secrets.d.ts +119 -105
  57. package/dist/secrets.js +1 -1
  58. package/dist/signatures.d.ts +74 -74
  59. package/dist/signatures.js +1 -1
  60. package/dist/tenancy.d.ts +142 -68
  61. package/dist/tenancy.js +70 -17
  62. package/dist/test-sink.d.ts +127 -127
  63. package/dist/test-sink.js +1 -1
  64. package/dist/time.d.ts +4 -4
  65. package/dist/time.js +1 -1
  66. package/dist/timing.d.ts +65 -0
  67. package/dist/timing.js +82 -0
  68. package/dist/usage.d.ts +103 -47
  69. package/dist/usage.js +183 -32
  70. package/dist/workspace.d.ts +34 -34
  71. package/dist/workspace.js +1 -1
  72. package/dist/zod-values.d.ts +3 -0
  73. package/dist/zod-values.js +2 -0
  74. package/dist/zod.d.ts +2 -0
  75. package/dist/zod.js +2 -0
  76. package/generated/v1/examples/attempts.list.response.json +6 -0
  77. package/generated/v1/examples/attempts.read.response.json +6 -0
  78. package/generated/v1/examples/billing.autotopup.request.json +5 -0
  79. package/generated/v1/examples/billing.autotopup.response.json +120 -0
  80. package/generated/v1/examples/billing.autotopuppreview.request.json +19 -0
  81. package/generated/v1/examples/billing.autotopuppreview.response.json +45 -0
  82. package/generated/v1/examples/billing.budgets.request.json +13 -0
  83. package/generated/v1/examples/billing.budgets.response.json +22 -0
  84. package/generated/v1/examples/billing.ceiling.request.json +8 -0
  85. package/generated/v1/examples/billing.ceiling.response.json +120 -0
  86. package/generated/v1/examples/billing.change.request.json +5 -0
  87. package/generated/v1/examples/billing.change.response.json +12 -0
  88. package/generated/v1/examples/billing.funding.response.json +120 -0
  89. package/generated/v1/examples/billing.fundingconfirm.response.json +120 -0
  90. package/generated/v1/examples/billing.fundingquote.request.json +7 -0
  91. package/generated/v1/examples/billing.fundingquote.response.json +13 -0
  92. package/generated/v1/examples/billing.invoicehistory.response.json +6 -0
  93. package/generated/v1/examples/billing.invoices.response.json +15 -0
  94. package/generated/v1/examples/billing.ledger.response.json +23 -0
  95. package/generated/v1/examples/billing.notices.response.json +41 -0
  96. package/generated/v1/examples/billing.notificationsettings.response.json +17 -0
  97. package/generated/v1/examples/billing.notificationsettingsupdate.request.json +9 -0
  98. package/generated/v1/examples/billing.notificationsettingsupdate.response.json +17 -0
  99. package/generated/v1/examples/billing.paymenthistory.response.json +6 -0
  100. package/generated/v1/examples/billing.paymentmethod.response.json +16 -0
  101. package/generated/v1/examples/billing.paymentmethodconfirm.request.json +3 -0
  102. package/generated/v1/examples/billing.paymentmethodconfirm.response.json +16 -0
  103. package/generated/v1/examples/billing.paymentmethodremove.request.json +3 -0
  104. package/generated/v1/examples/billing.paymentmethodremove.response.json +4 -0
  105. package/generated/v1/examples/billing.paymentmethodsetup.request.json +1 -0
  106. package/generated/v1/examples/billing.paymentmethodsetup.response.json +3 -0
  107. package/generated/v1/examples/billing.portal.response.json +3 -0
  108. package/generated/v1/examples/billing.projects.response.json +12 -0
  109. package/generated/v1/examples/billing.purchase.request.json +5 -0
  110. package/generated/v1/examples/billing.purchase.response.json +120 -0
  111. package/generated/v1/examples/billing.quote.request.json +4 -0
  112. package/generated/v1/examples/billing.quote.response.json +19 -0
  113. package/generated/v1/examples/billing.reconcile.response.json +73 -0
  114. package/generated/v1/examples/billing.refundrequest.request.json +10 -0
  115. package/generated/v1/examples/billing.refundrequest.response.json +11 -0
  116. package/generated/v1/examples/billing.refundresolution.request.json +4 -0
  117. package/generated/v1/examples/billing.refundresolution.response.json +120 -0
  118. package/generated/v1/examples/billing.summary.response.json +73 -0
  119. package/generated/v1/examples/endpoints.acknowledgeSigningKeyRotation.response.json +18 -0
  120. package/generated/v1/examples/endpoints.create.response.json +1 -1
  121. package/generated/v1/examples/endpoints.disable.response.json +0 -1
  122. package/generated/v1/examples/{endpoints.verify.response.json → endpoints.enable.response.json} +4 -5
  123. package/generated/v1/examples/endpoints.list.response.json +1 -2
  124. package/generated/v1/examples/endpoints.read.response.json +2 -2
  125. package/generated/v1/examples/endpoints.revealSigningSecret.response.json +7 -0
  126. package/generated/v1/examples/endpoints.rotateSigningKey.response.json +1 -2
  127. package/generated/v1/examples/endpoints.update.response.json +1 -1
  128. package/generated/v1/examples/executions.list.response.json +20 -0
  129. package/generated/v1/examples/exports.cancel.response.json +11 -0
  130. package/generated/v1/examples/exports.create.request.json +1 -0
  131. package/generated/v1/examples/exports.create.response.json +8 -0
  132. package/generated/v1/examples/exports.part.response.json +5 -0
  133. package/generated/v1/examples/exports.read.response.json +8 -0
  134. package/generated/v1/examples/limits.read.response.json +16 -0
  135. package/generated/v1/examples/occurrences.read.response.json +4 -0
  136. package/generated/v1/examples/occurrences.redeliver.response.json +2 -0
  137. package/generated/v1/examples/organizations.close.request.json +5 -0
  138. package/generated/v1/examples/organizations.close.response.json +10 -0
  139. package/generated/v1/examples/organizations.lifecycle.response.json +10 -0
  140. package/generated/v1/examples/organizations.restore.request.json +3 -0
  141. package/generated/v1/examples/organizations.restore.response.json +10 -0
  142. package/generated/v1/examples/projects.archive.request.json +3 -0
  143. package/generated/v1/examples/projects.archive.response.json +14 -0
  144. package/generated/v1/examples/projects.create.response.json +10 -1
  145. package/generated/v1/examples/projects.delete.response.json +9 -0
  146. package/generated/v1/examples/projects.list.response.json +12 -2
  147. package/generated/v1/examples/projects.rename.request.json +3 -0
  148. package/generated/v1/examples/projects.rename.response.json +14 -0
  149. package/generated/v1/examples/schedules.archive.response.json +3 -0
  150. package/generated/v1/examples/schedules.cancel.response.json +3 -0
  151. package/generated/v1/examples/schedules.create.request.json +4 -0
  152. package/generated/v1/examples/schedules.create.response.json +3 -0
  153. package/generated/v1/examples/schedules.list.response.json +3 -0
  154. package/generated/v1/examples/schedules.pause.response.json +3 -0
  155. package/generated/v1/examples/schedules.read.response.json +6 -0
  156. package/generated/v1/examples/schedules.replace.request.json +4 -0
  157. package/generated/v1/examples/schedules.replace.response.json +3 -0
  158. package/generated/v1/examples/schedules.resume.response.json +3 -0
  159. package/generated/v1/examples/timing.summary.response.json +35 -0
  160. package/generated/v1/examples/usage.read.response.json +1 -0
  161. package/generated/v1/first-value-journey.json +43 -18
  162. package/generated/v1/manifest.json +1287 -42
  163. package/generated/v1/openapi.json +36033 -5982
  164. package/generated/v1/policy-matrix.json +341 -47
  165. package/generated/v1/schemas/attempts.list.response.json +39 -1
  166. package/generated/v1/schemas/attempts.read.response.json +39 -1
  167. package/generated/v1/schemas/billing.autotopup.request-headers.json +13 -0
  168. package/generated/v1/schemas/billing.autotopup.request.json +22 -0
  169. package/generated/v1/schemas/billing.autotopup.response-headers.json +10 -0
  170. package/generated/v1/schemas/billing.autotopup.response.json +552 -0
  171. package/generated/v1/schemas/billing.autotopuppreview.request.json +77 -0
  172. package/generated/v1/schemas/billing.autotopuppreview.response.json +277 -0
  173. package/generated/v1/schemas/billing.budgets.request-headers.json +13 -0
  174. package/generated/v1/schemas/billing.budgets.request.json +66 -0
  175. package/generated/v1/schemas/billing.budgets.response-headers.json +10 -0
  176. package/generated/v1/schemas/billing.budgets.response.json +92 -0
  177. package/generated/v1/schemas/billing.ceiling.request-headers.json +13 -0
  178. package/generated/v1/schemas/billing.ceiling.request.json +40 -0
  179. package/generated/v1/schemas/billing.ceiling.response-headers.json +10 -0
  180. package/generated/v1/schemas/billing.ceiling.response.json +552 -0
  181. package/generated/v1/schemas/billing.change.request-headers.json +13 -0
  182. package/generated/v1/schemas/billing.change.request.json +22 -0
  183. package/generated/v1/schemas/billing.change.response.json +119 -0
  184. package/generated/v1/schemas/billing.funding.response.json +552 -0
  185. package/generated/v1/schemas/billing.fundingconfirm.response.json +552 -0
  186. package/generated/v1/schemas/billing.fundingquote.request.json +28 -0
  187. package/generated/v1/schemas/billing.fundingquote.response.json +61 -0
  188. package/generated/v1/schemas/billing.invoicehistory.query.json +16 -0
  189. package/generated/v1/schemas/billing.invoicehistory.response.json +88 -0
  190. package/generated/v1/schemas/billing.invoices.response.json +51 -0
  191. package/generated/v1/schemas/billing.ledger.query.json +16 -0
  192. package/generated/v1/schemas/billing.ledger.response.json +130 -0
  193. package/generated/v1/schemas/billing.notices.query.json +16 -0
  194. package/generated/v1/schemas/billing.notices.response.json +278 -0
  195. package/generated/v1/schemas/billing.notificationsettings.response.json +70 -0
  196. package/generated/v1/schemas/billing.notificationsettingsupdate.request-headers.json +13 -0
  197. package/generated/v1/schemas/billing.notificationsettingsupdate.request.json +43 -0
  198. package/generated/v1/schemas/billing.notificationsettingsupdate.response-headers.json +10 -0
  199. package/generated/v1/schemas/billing.notificationsettingsupdate.response.json +70 -0
  200. package/generated/v1/schemas/billing.paymenthistory.query.json +16 -0
  201. package/generated/v1/schemas/billing.paymenthistory.response.json +182 -0
  202. package/generated/v1/schemas/billing.paymentmethod.response.json +137 -0
  203. package/generated/v1/schemas/billing.paymentmethodconfirm.request.json +14 -0
  204. package/generated/v1/schemas/billing.paymentmethodconfirm.response.json +137 -0
  205. package/generated/v1/schemas/billing.paymentmethodremove.request.json +12 -0
  206. package/generated/v1/schemas/billing.paymentmethodremove.response.json +137 -0
  207. package/generated/v1/schemas/billing.paymentmethodsetup.request.json +12 -0
  208. package/generated/v1/schemas/billing.paymentmethodsetup.response.json +11 -0
  209. package/generated/v1/schemas/billing.portal.response.json +11 -0
  210. package/generated/v1/schemas/billing.projects.response.json +119 -0
  211. package/generated/v1/schemas/billing.purchase.request-headers.json +13 -0
  212. package/generated/v1/schemas/billing.purchase.request.json +26 -0
  213. package/generated/v1/schemas/billing.purchase.response-headers.json +10 -0
  214. package/generated/v1/schemas/billing.purchase.response.json +552 -0
  215. package/generated/v1/schemas/billing.quote.request.json +20 -0
  216. package/generated/v1/schemas/billing.quote.response.json +86 -0
  217. package/generated/v1/schemas/billing.reconcile.response.json +468 -0
  218. package/generated/v1/schemas/billing.refundrequest.request-headers.json +13 -0
  219. package/generated/v1/schemas/billing.refundrequest.request.json +42 -0
  220. package/generated/v1/schemas/billing.refundrequest.response-headers.json +10 -0
  221. package/generated/v1/schemas/billing.refundrequest.response.json +45 -0
  222. package/generated/v1/schemas/billing.refundresolution.request-headers.json +13 -0
  223. package/generated/v1/schemas/billing.refundresolution.request.json +61 -0
  224. package/generated/v1/schemas/billing.refundresolution.response-headers.json +10 -0
  225. package/generated/v1/schemas/billing.refundresolution.response.json +552 -0
  226. package/generated/v1/schemas/billing.summary.response.json +468 -0
  227. package/generated/v1/schemas/context.read.response.json +2 -0
  228. package/generated/v1/schemas/credentials.connection.response.json +2 -0
  229. package/generated/v1/schemas/credentials.create.request.json +2 -0
  230. package/generated/v1/schemas/credentials.create.response.json +2 -0
  231. package/generated/v1/schemas/credentials.list.response.json +2 -0
  232. package/generated/v1/schemas/credentials.revoke.response.json +2 -0
  233. package/generated/v1/schemas/credentials.rotate.response.json +4 -0
  234. package/generated/v1/schemas/{endpoints.verify.request-headers.json → endpoints.acknowledgeSigningKeyRotation.request-headers.json} +2 -1
  235. package/generated/v1/schemas/{endpoints.verify.response.json → endpoints.acknowledgeSigningKeyRotation.response.json} +8 -21
  236. package/generated/v1/schemas/endpoints.create.request.json +5 -0
  237. package/generated/v1/schemas/endpoints.create.response.json +8 -21
  238. package/generated/v1/schemas/endpoints.disable.response.json +8 -21
  239. package/generated/v1/schemas/endpoints.enable.response-headers.json +11 -0
  240. package/generated/v1/schemas/endpoints.enable.response.json +87 -0
  241. package/generated/v1/schemas/endpoints.list.response.json +8 -21
  242. package/generated/v1/schemas/endpoints.read.response.json +8 -21
  243. package/generated/v1/schemas/endpoints.revealSigningSecret.response.json +43 -0
  244. package/generated/v1/schemas/endpoints.rotateSigningKey.response.json +8 -21
  245. package/generated/v1/schemas/endpoints.update.request.json +5 -0
  246. package/generated/v1/schemas/endpoints.update.response.json +8 -21
  247. package/generated/v1/schemas/executions.list.query.json +46 -0
  248. package/generated/v1/schemas/executions.list.response.json +116 -0
  249. package/generated/v1/schemas/exports.cancel.response.json +48 -0
  250. package/generated/v1/schemas/exports.create.request.json +6 -0
  251. package/generated/v1/schemas/exports.create.response.json +38 -0
  252. package/generated/v1/schemas/exports.part.query.json +11 -0
  253. package/generated/v1/schemas/exports.part.response.json +22 -0
  254. package/generated/v1/schemas/exports.read.response.json +38 -0
  255. package/generated/v1/schemas/forecast.read.request-headers.json +10 -0
  256. package/generated/v1/schemas/forecast.read.response.json +148 -0
  257. package/generated/v1/schemas/grants.list.response.json +2 -0
  258. package/generated/v1/schemas/grants.revoke.response.json +2 -0
  259. package/generated/v1/schemas/limits.read.response.json +79 -0
  260. package/generated/v1/schemas/occurrences.list.query.json +5 -0
  261. package/generated/v1/schemas/occurrences.list.response.json +23 -0
  262. package/generated/v1/schemas/occurrences.read.response.json +23 -0
  263. package/generated/v1/schemas/occurrences.redeliver.response.json +39 -1
  264. package/generated/v1/schemas/organizations.close.request.json +20 -0
  265. package/generated/v1/schemas/organizations.close.response.json +68 -0
  266. package/generated/v1/schemas/organizations.lifecycle.response.json +68 -0
  267. package/generated/v1/schemas/organizations.restore.request.json +13 -0
  268. package/generated/v1/schemas/organizations.restore.response.json +68 -0
  269. package/generated/v1/schemas/projects.archive.request-headers.json +11 -0
  270. package/generated/v1/schemas/projects.archive.request.json +10 -0
  271. package/generated/v1/schemas/projects.archive.response-headers.json +11 -0
  272. package/generated/v1/schemas/projects.archive.response.json +79 -0
  273. package/generated/v1/schemas/projects.create.response.json +55 -1
  274. package/generated/v1/schemas/projects.delete.request-headers.json +11 -0
  275. package/generated/v1/schemas/projects.delete.response.json +37 -0
  276. package/generated/v1/schemas/projects.list.response.json +61 -2
  277. package/generated/v1/schemas/projects.rename.request-headers.json +11 -0
  278. package/generated/v1/schemas/projects.rename.request.json +12 -0
  279. package/generated/v1/schemas/projects.rename.response-headers.json +11 -0
  280. package/generated/v1/schemas/projects.rename.response.json +79 -0
  281. package/generated/v1/schemas/schedules.archive.response.json +90 -2
  282. package/generated/v1/schemas/schedules.cancel.response.json +90 -2
  283. package/generated/v1/schemas/schedules.create.request.json +24 -3
  284. package/generated/v1/schemas/schedules.create.response.json +90 -2
  285. package/generated/v1/schemas/schedules.list.query.json +13 -0
  286. package/generated/v1/schemas/schedules.list.response.json +67 -2
  287. package/generated/v1/schemas/schedules.pause.response.json +90 -2
  288. package/generated/v1/schemas/schedules.read.response.json +90 -2
  289. package/generated/v1/schemas/schedules.replace.request.json +24 -3
  290. package/generated/v1/schemas/schedules.replace.response.json +90 -2
  291. package/generated/v1/schemas/schedules.resume.response.json +90 -2
  292. package/generated/v1/schemas/timing.summary.response.json +191 -0
  293. package/generated/v1/schemas/usage.read.query.json +7 -0
  294. package/generated/v1/schemas/usage.read.response.json +36 -1
  295. package/generated/v1/snippets/billing.autotopup.json +6 -0
  296. package/generated/v1/snippets/billing.autotopuppreview.json +6 -0
  297. package/generated/v1/snippets/billing.budgets.json +6 -0
  298. package/generated/v1/snippets/billing.ceiling.json +6 -0
  299. package/generated/v1/snippets/billing.change.json +6 -0
  300. package/generated/v1/snippets/billing.funding.json +6 -0
  301. package/generated/v1/snippets/billing.fundingconfirm.json +6 -0
  302. package/generated/v1/snippets/billing.fundingquote.json +6 -0
  303. package/generated/v1/snippets/billing.invoicehistory.json +6 -0
  304. package/generated/v1/snippets/billing.invoices.json +6 -0
  305. package/generated/v1/snippets/billing.ledger.json +6 -0
  306. package/generated/v1/snippets/billing.notices.json +6 -0
  307. package/generated/v1/snippets/billing.notificationsettings.json +6 -0
  308. package/generated/v1/snippets/billing.notificationsettingsupdate.json +6 -0
  309. package/generated/v1/snippets/billing.paymenthistory.json +6 -0
  310. package/generated/v1/snippets/billing.paymentmethod.json +6 -0
  311. package/generated/v1/snippets/billing.paymentmethodconfirm.json +6 -0
  312. package/generated/v1/snippets/billing.paymentmethodremove.json +6 -0
  313. package/generated/v1/snippets/billing.paymentmethodsetup.json +6 -0
  314. package/generated/v1/snippets/billing.portal.json +6 -0
  315. package/generated/v1/snippets/billing.projects.json +6 -0
  316. package/generated/v1/snippets/billing.purchase.json +6 -0
  317. package/generated/v1/snippets/billing.quote.json +6 -0
  318. package/generated/v1/snippets/billing.reconcile.json +6 -0
  319. package/generated/v1/snippets/billing.refundrequest.json +6 -0
  320. package/generated/v1/snippets/billing.refundresolution.json +6 -0
  321. package/generated/v1/snippets/billing.summary.json +6 -0
  322. package/generated/v1/snippets/endpoints.acknowledgeSigningKeyRotation.json +6 -0
  323. package/generated/v1/snippets/{endpoints.verify.json → endpoints.enable.json} +3 -3
  324. package/generated/v1/snippets/endpoints.revealSigningSecret.json +6 -0
  325. package/generated/v1/snippets/executions.list.json +6 -0
  326. package/generated/v1/snippets/exports.cancel.json +6 -0
  327. package/generated/v1/snippets/exports.create.json +6 -0
  328. package/generated/v1/snippets/exports.part.json +6 -0
  329. package/generated/v1/snippets/exports.read.json +6 -0
  330. package/generated/v1/snippets/forecast.windows.json +1 -1
  331. package/generated/v1/snippets/limits.read.json +6 -0
  332. package/generated/v1/snippets/organizations.close.json +6 -0
  333. package/generated/v1/snippets/organizations.lifecycle.json +6 -0
  334. package/generated/v1/snippets/organizations.restore.json +6 -0
  335. package/generated/v1/snippets/projects.archive.json +6 -0
  336. package/generated/v1/snippets/projects.delete.json +6 -0
  337. package/generated/v1/snippets/projects.rename.json +6 -0
  338. package/generated/v1/snippets/schedules.create.json +3 -3
  339. package/generated/v1/snippets/schedules.replace.json +3 -3
  340. package/generated/v1/snippets/timing.summary.json +6 -0
  341. package/package.json +7 -7
  342. package/dist/callback-ownership.d.ts +0 -58
  343. package/dist/callback-ownership.js +0 -71
  344. package/generated/v1/callback-endpoint-verification.json +0 -59
  345. /package/generated/v1/schemas/{endpoints.verify.response-headers.json → endpoints.acknowledgeSigningKeyRotation.response-headers.json} +0 -0
@@ -1,7 +1,8 @@
1
- import { z } from "zod";
1
+ import { FIXED_INTERVAL_MIN_SECONDS, FIXED_INTERVAL_MAX_SECONDS, MAX_INLINE_PAYLOAD_BYTES, } from "./configuration-bounds.js";
2
+ import { z } from "./zod.js";
2
3
  import { attemptIdSchema, deliveryIdSchema, endpointIdSchema, occurrenceIdSchema, scheduleIdSchema, } from "./identifiers.js";
3
4
  import { CALLBACK_ATTEMPT_DIAGNOSTICS } from "./delivery.js";
4
- import { pageSchema, versionSchema } from "./protocol.js";
5
+ import { pageRequestSchema, pageSchema, versionSchema, } from "./protocol.js";
5
6
  import { callbackTriggerSchema, } from "./signatures.js";
6
7
  import { durationSecondsSchema, timeZoneSchema, timestampSchema } from "./time.js";
7
8
  /**
@@ -46,6 +47,38 @@ export const SCHEDULE_STATUSES = [
46
47
  "completed",
47
48
  ];
48
49
  export const scheduleStatusSchema = z.enum(SCHEDULE_STATUSES);
50
+ /**
51
+ * The statuses a schedule never leaves, and from which it never fires again on
52
+ * its own.
53
+ *
54
+ * **"Never fires again on its own" rather than "never delivers again", and the
55
+ * distinction is real.** A member can still press Redeliver on a completed
56
+ * occurrence, and the repository admits that for a `completed` schedule as well
57
+ * as an `active` one. What is terminal here is the schedule's own timetable, not
58
+ * every possible future delivery.
59
+ *
60
+ * Exported for the same reason as {@link TERMINAL_OCCURRENCE_STATES}: so a
61
+ * caller enumerates them rather than writing the list out and quietly missing
62
+ * one if another is ever added. Three places had already written it out — the
63
+ * `archivedAt` validator in `scheduling-records.ts`, the dashboard's schedule
64
+ * controls, and the dashboard's pending-action rules — and the third had missed
65
+ * it entirely, which is the defect this list was extracted for.
66
+ */
67
+ export const TERMINAL_SCHEDULE_STATUSES = [
68
+ "cancelled",
69
+ "completed",
70
+ ];
71
+ /**
72
+ * Whether this schedule has finished, either on its own or because somebody
73
+ * stopped it.
74
+ *
75
+ * `paused` is deliberately not terminal: it is reversible, so a paused schedule
76
+ * pointed at a broken destination is still a problem worth telling somebody
77
+ * about.
78
+ */
79
+ export function isTerminalScheduleStatus(status) {
80
+ return TERMINAL_SCHEDULE_STATUSES.includes(status);
81
+ }
49
82
  /**
50
83
  * How promptly an occurrence expects to be dispatched.
51
84
  *
@@ -55,29 +88,87 @@ export const scheduleStatusSchema = z.enum(SCHEDULE_STATUSES);
55
88
  */
56
89
  export const LATENCY_CLASSES = ["realtime", "bulk"];
57
90
  export const latencyClassSchema = z.enum(LATENCY_CLASSES);
91
+ /**
92
+ * What happens when an occurrence reaches its first attempt after its planned
93
+ * instant.
94
+ *
95
+ * `skipAhead` never carries a recurring first arrival across its next planned
96
+ * instant (and a shorter custom window may retire it earlier). `catchUp`
97
+ * admits one first arrival while it is still inside the schedule's staleness
98
+ * window, even after later recurrence instants passed. A one-shot has no next
99
+ * recurrence, so either explicit policy uses its staleness window and its
100
+ * default is `catchUp`. Neither choice applies to an occurrence already on its
101
+ * retry ladder: that work arrived, failed, and keeps the retry/give-up policy
102
+ * it started under.
103
+ */
104
+ export const LATENESS_POLICIES = ["skipAhead", "catchUp"];
105
+ export const latenessPolicySchema = z.enum(LATENESS_POLICIES);
106
+ /** The inherited one-shot staleness window: twenty-four hours. */
107
+ export const DEFAULT_ONE_SHOT_MAX_STALENESS_SECONDS = 86_400;
108
+ /**
109
+ * The per-schedule lateness choices a caller may state.
110
+ *
111
+ * Both members are optional so a caller can override only the part whose
112
+ * default does not fit. The policy defaults from recurrence kind. An omitted
113
+ * staleness bound uses the recurrence's own default: twenty-four hours for a
114
+ * one-shot, and one recurrence interval for a recurring schedule.
115
+ */
116
+ export const scheduleLatenessRequestSchema = z
117
+ .object({
118
+ maxStalenessSeconds: durationSecondsSchema
119
+ .positive("must be at least 1 second")
120
+ .max(Number.MAX_SAFE_INTEGER, "must be a safely representable duration")
121
+ .optional(),
122
+ policy: latenessPolicySchema.optional(),
123
+ })
124
+ .strict();
125
+ /**
126
+ * Lateness settings as a schedule reads back.
127
+ *
128
+ * The policy is always effective and therefore required. The numeric member
129
+ * remains optional because it records a per-schedule override, not a duplicated
130
+ * copy of a default that can be derived from the recurrence.
131
+ */
132
+ export const scheduleLatenessSchema = scheduleLatenessRequestSchema.extend({
133
+ policy: latenessPolicySchema,
134
+ });
58
135
  /* -------------------------------------------------------------------------- */
59
136
  /* Delivery duration */
60
137
  /* -------------------------------------------------------------------------- */
61
138
  /**
62
- * How long a receiver may take to answer, as a ladder rather than a number.
63
- *
64
- * **A ladder because every rung is a different product.** The first two are the
65
- * ordinary path: a delivery is carried inside the partition's own dispatch and
66
- * holds one of six in-flight slots for its whole duration, so the cost of a slow
67
- * receiver is paid by every other schedule in that
68
- * `(organization, project, environment)`. The rungs above are carried elsewhere
69
- * and priced, because that is the only honest way to sell them.
70
- *
71
- * **Deliberately not a free-form integer.** A caller that could ask for 47
72
- * seconds would be asking a question this product has no answer to: 47 is past
73
- * the point where a delivery may occupy a partition slot and short of the point
74
- * where it is worth carrying separately. The ladder is what makes "which side of
75
- * the line is this" a decision somebody took rather than a comparison somebody
76
- * wrote.
139
+ * The durations this product used to sell, one rung at a time.
140
+ *
141
+ * **Kept, and no longer the whole of what is accepted** (FXO-2258). The ladder
142
+ * was the argument that 47 seconds is a question with no answer — past the
143
+ * point where a delivery may occupy a partition slot, short of the point where
144
+ * it is worth carrying separately. The operator overruled it after the walk:
145
+ * "This can have a completely user definable timeout, they don't cost us hardly
146
+ * anything, but we should cap at something anyway, 24h is still fine."
147
+ *
148
+ * What the rungs were actually carrying survives without them. The line that
149
+ * matters commercially is {@link FREE_DELIVERY_DURATION_SECONDS} — above thirty
150
+ * seconds a delivery is carried outside the partition and needs a plan that
151
+ * allows it — and that line is a comparison, not a membership test. The line
152
+ * that matters technically is {@link RESPONSE_DELIVERY_MAX_DURATION_SECONDS},
153
+ * which is also a comparison. Neither ever needed eight fixed values to be
154
+ * expressible; the ladder was how they were *sold*, and per-rung pricing is
155
+ * what the operator withdrew.
156
+ *
157
+ * The list stays because a schedule saved on a rung must keep working, because
158
+ * the delivery-class rules are still exercised against exactly these values,
159
+ * and because the dashboard offers them as the shortcuts most people want.
77
160
  */
78
161
  export const DELIVERY_DURATION_TIERS = [
79
162
  5, 30, 300, 900, 1_800, 3_600, 43_200, 86_400,
80
163
  ];
164
+ /**
165
+ * The longest duration any delivery may be given, in seconds.
166
+ *
167
+ * Twenty-four hours, which is the ladder's own top rung — so widening the field
168
+ * to a free-form integer admits nothing that was not already for sale. "24h is
169
+ * still fine."
170
+ */
171
+ export const MAX_DELIVERY_DURATION_SECONDS = 86_400;
81
172
  /**
82
173
  * What a schedule gets without asking, in seconds.
83
174
  *
@@ -134,8 +225,26 @@ export const FREE_DELIVERY_DURATION_SECONDS = 30;
134
225
  * than thirty minutes has to do is answer immediately and call us back.
135
226
  */
136
227
  export const RESPONSE_DELIVERY_MAX_DURATION_SECONDS = 1_800;
228
+ /**
229
+ * How long the receiver may take to answer, in whole seconds.
230
+ *
231
+ * **A bounded integer since FXO-2258, and every value the enum accepted is
232
+ * still accepted.** A widening is the one shape of contract change that cannot
233
+ * break a caller: a request that was valid before is valid now, and a stored
234
+ * schedule reads back and re-saves unchanged.
235
+ *
236
+ * The two bounds that are real are still enforced, and neither of them was ever
237
+ * the enum's doing: `maxDurationSeconds` above
238
+ * {@link RESPONSE_DELIVERY_MAX_DURATION_SECONDS} is refused unless `completion`
239
+ * is `callback` (checked in `scheduleRequestSchema`'s refinement, because it is
240
+ * a relation between two fields), and above
241
+ * {@link FREE_DELIVERY_DURATION_SECONDS} it needs a plan that allows it.
242
+ */
137
243
  export const deliveryDurationSecondsSchema = z
138
- .literal(DELIVERY_DURATION_TIERS)
244
+ .number()
245
+ .int("must be a whole number of seconds")
246
+ .positive("must be at least 1 second")
247
+ .max(MAX_DELIVERY_DURATION_SECONDS, `must be at most ${String(MAX_DELIVERY_DURATION_SECONDS)} seconds`)
139
248
  .describe("How long the receiver may take to answer, in seconds");
140
249
  /** Whether a duration is past the point where it needs a plan that allows it. */
141
250
  export function isLongRunningDuration(seconds) {
@@ -247,6 +356,89 @@ export const OCCURRENCE_STATES = [
247
356
  "skipped_stale",
248
357
  ];
249
358
  export const occurrenceStateSchema = z.enum(OCCURRENCE_STATES);
359
+ /**
360
+ * One project-wide walk over retained occurrences, newest first.
361
+ *
362
+ * There is deliberately no order parameter: the Executions page answers what
363
+ * happened most recently, and making that fixed lets its index and cursor say
364
+ * the same thing. The date range is half-open, like every other time window in
365
+ * this contract, so adjacent requests neither overlap nor leave a gap.
366
+ */
367
+ export const projectOccurrenceListRequestSchema = pageRequestSchema
368
+ .extend({
369
+ endpoint: endpointIdSchema.optional(),
370
+ from: timestampSchema.optional(),
371
+ outcome: occurrenceStateSchema.optional(),
372
+ schedule: scheduleIdSchema.optional(),
373
+ to: timestampSchema.optional(),
374
+ })
375
+ .refine((request) => request.from === undefined ||
376
+ request.to === undefined ||
377
+ Date.parse(request.from) < Date.parse(request.to), {
378
+ message: "must be later than from",
379
+ path: ["to"],
380
+ });
381
+ const PROJECT_OCCURRENCE_LIST_PARAMETERS = [
382
+ "cursor",
383
+ "limit",
384
+ "endpoint",
385
+ "from",
386
+ "outcome",
387
+ "schedule",
388
+ "to",
389
+ ];
390
+ /** Read the project occurrence filters exactly as REST and MCP send them. */
391
+ export function parseProjectOccurrenceListRequest(query) {
392
+ const errors = [];
393
+ const raw = {};
394
+ for (const field of PROJECT_OCCURRENCE_LIST_PARAMETERS) {
395
+ const values = query.getAll(field);
396
+ if (values.length > 1) {
397
+ errors.push({
398
+ pointer: `/${field}`,
399
+ reason: "must be given at most once",
400
+ });
401
+ continue;
402
+ }
403
+ const value = values[0];
404
+ if (value === undefined)
405
+ continue;
406
+ raw[field] =
407
+ field === "limit" && /^-?\d+$/u.test(value) ? Number(value) : value;
408
+ }
409
+ if (errors.length > 0)
410
+ return { errors, ok: false };
411
+ const parsed = projectOccurrenceListRequestSchema.safeParse(raw);
412
+ if (parsed.success)
413
+ return { ok: true, page: parsed.data };
414
+ return {
415
+ errors: parsed.error.issues.map((issue) => ({
416
+ pointer: `/${issue.path.join("/")}`,
417
+ reason: issue.message,
418
+ })),
419
+ ok: false,
420
+ };
421
+ }
422
+ /**
423
+ * The exact operation and filters a project occurrence cursor belongs to.
424
+ *
425
+ * Cursor and limit do not affect which ordered collection is being walked, so
426
+ * neither belongs in the binding. Every actual filter does. Optional values
427
+ * stay absent rather than becoming empty strings, which are invalid inputs and
428
+ * must never collide with an unfiltered request.
429
+ */
430
+ export function projectOccurrenceCursorBinding(request) {
431
+ return {
432
+ operation: "executions.list",
433
+ parameters: {
434
+ ...(request.endpoint === undefined ? {} : { endpoint: request.endpoint }),
435
+ ...(request.from === undefined ? {} : { from: request.from }),
436
+ ...(request.outcome === undefined ? {} : { outcome: request.outcome }),
437
+ ...(request.schedule === undefined ? {} : { schedule: request.schedule }),
438
+ ...(request.to === undefined ? {} : { to: request.to }),
439
+ },
440
+ };
441
+ }
250
442
  /**
251
443
  * The states an occurrence never leaves.
252
444
  *
@@ -296,7 +488,7 @@ export const ATTEMPT_REMEDIATIONS = [
296
488
  "none",
297
489
  "review_retry_policy",
298
490
  "inspect_receiver",
299
- "verify_endpoint",
491
+ "review_endpoint",
300
492
  "replace_payload",
301
493
  "contact_support",
302
494
  "review_before_redelivery",
@@ -320,7 +512,7 @@ export function remediationForAttempt(diagnostic) {
320
512
  return "none";
321
513
  case "endpoint_unavailable":
322
514
  case "endpoint_unsafe":
323
- return "verify_endpoint";
515
+ return "review_endpoint";
324
516
  case "http_refused":
325
517
  case "http_terminal":
326
518
  // The same advice for two different futures, deliberately. Both mean the
@@ -409,17 +601,37 @@ export const SCHEDULING_BOUNDS = {
409
601
  * Attempts made against one occurrence before it is terminal, including the
410
602
  * first. Bounded so a permanently broken receiver cannot make one occurrence
411
603
  * cost an unbounded amount of work.
604
+ *
605
+ * **Eight, and the number is load-bearing against a second limit rather than
606
+ * on its own.** `MAX_BUDGETED_DELIVERY_FAILURES` stops an occurrence after
607
+ * five failures the *receiver* caused; a rate limit and this product's own
608
+ * infrastructure faults are exempt from that count. The gap between the two
609
+ * is what the exemption spends — an occurrence throttled twice still has
610
+ * five receiver failures left. Lowering this to five was considered during
611
+ * the W4-10 walk and rejected once the pair was understood: it would make
612
+ * both limits fire on the same attempt and leave the exemption buying
613
+ * nothing.
412
614
  */
413
615
  attemptsPerOccurrence: 8,
414
616
  /**
415
617
  * The longest fixed interval a recurring schedule may ask for, in seconds.
416
618
  *
417
- * Thirty days, which is the copied scheduler's `MAX_INTERVAL_SECONDS` kept
418
- * rather than a new number invented beside it, and its reason is the
419
- * platform's rather than the product's: a Durable Object alarm is not
420
- * arbitrarily far in the future.
619
+ * One year. **The thirty days this replaces had no reason of its own** — it
620
+ * was the copied scheduler's `MAX_INTERVAL_SECONDS`, carried over on the
621
+ * belief that "a Durable Object alarm is not arbitrarily far in the future".
622
+ * Cloudflare documents no maximum distance for `setAlarm()`, and the belief
623
+ * predates the PostgreSQL authority besides: the durable fact is a row, and
624
+ * the alarm is only what wakes a bucket to read it.
625
+ *
626
+ * What makes a distant alarm safe is not the alarm. `TimerBucketRepairer`
627
+ * asks PostgreSQL for occurrences whose instant has passed and re-signals
628
+ * their bucket, on committed truth, never asking whether an alarm was armed
629
+ * — so an alarm that is somehow lost costs lateness rather than the run.
630
+ *
631
+ * Widening a bound is additive by `classifyChange` and narrowing it is
632
+ * breaking, so this direction is the safe one to move in.
421
633
  */
422
- everySecondsMax: 30 * 24 * 60 * 60,
634
+ everySecondsMax: FIXED_INTERVAL_MAX_SECONDS,
423
635
  /**
424
636
  * The shortest fixed interval a recurring schedule may ask for, in seconds.
425
637
  *
@@ -431,7 +643,7 @@ export const SCHEDULING_BOUNDS = {
431
643
  * `classifyChange` and narrowing it is breaking, which is the direction to be
432
644
  * conservative in.
433
645
  */
434
- everySecondsMin: 60,
646
+ everySecondsMin: FIXED_INTERVAL_MIN_SECONDS,
435
647
  /**
436
648
  * How long an idempotency receipt is honoured. A client retrying a create for
437
649
  * longer than a day is not retrying, it is making a new request; keeping
@@ -483,7 +695,7 @@ export const SCHEDULING_BOUNDS = {
483
695
  * command-shaped payloads this product is for, and refuses the ones that are
484
696
  * really a file transfer.
485
697
  */
486
- payloadBytes: 65_536,
698
+ payloadBytes: MAX_INLINE_PAYLOAD_BYTES,
487
699
  /**
488
700
  * How soon a one-shot has to be for `realtime` to be the class it gets by
489
701
  * default, in seconds.
@@ -573,6 +785,10 @@ export const recurrenceSchema = z.discriminatedUnion("kind", [
573
785
  timeZone: timeZoneSchema,
574
786
  }),
575
787
  ]);
788
+ /** The customer-safe default for a schedule's recurrence kind. */
789
+ export function defaultLatenessPolicyForRecurrence(kind) {
790
+ return kind === "at" ? "catchUp" : "skipAhead";
791
+ }
576
792
  /* -------------------------------------------------------------------------- */
577
793
  /* Public resources */
578
794
  /* -------------------------------------------------------------------------- */
@@ -584,6 +800,23 @@ export const recurrenceSchema = z.discriminatedUnion("kind", [
584
800
  * endpoint id is useful definition metadata; the encrypted payload is content
585
801
  * and stays only in the authoritative partition.
586
802
  */
803
+ /**
804
+ * A schedule's name: for people, never an identifier, and free to collide.
805
+ *
806
+ * Generated on save when nobody types one (W4-14, FXO-2255) — "Every 60s →
807
+ * flowxo.com" — and stored as any other name, so editing the recurrence
808
+ * later rewrites nothing. Two schedules may share a name: two on the same
809
+ * cadence against the same host both generate the same one, and the operator
810
+ * chose to let them collide, with the other columns and the identifier
811
+ * telling them apart. Endpoints get the opposite rule.
812
+ */
813
+ /** The most a schedule's name may hold; the generated one is cut to fit it. */
814
+ export const SCHEDULE_NAME_MAX_LENGTH = 120;
815
+ export const scheduleNameSchema = z
816
+ .string()
817
+ .trim()
818
+ .min(1, "must not be blank")
819
+ .max(SCHEDULE_NAME_MAX_LENGTH, `must be at most ${String(SCHEDULE_NAME_MAX_LENGTH)} characters`);
587
820
  export const scheduleSchema = z.object({
588
821
  /**
589
822
  * When the schedule was filed away, and absent until it is.
@@ -596,6 +829,20 @@ export const scheduleSchema = z.object({
596
829
  * terminal status — archiving is filing, not stopping.
597
830
  */
598
831
  archivedAt: timestampSchema.optional(),
832
+ /**
833
+ * The payload the schedule carries, as it was given.
834
+ *
835
+ * Present on a read when the schedule has one; left off listings, which are
836
+ * served from a projection that holds a summary. **Readable by anyone who
837
+ * can read the schedule** — the dashboard, the API, the SDK and MCP alike —
838
+ * since FXO-2243, chosen with the cost stated: a payload somebody put a
839
+ * secret in is readable by anything holding a read key, and appears in an
840
+ * agent's tool history. The write-only custody this replaces answered that
841
+ * with a blanket rule over the whole body — "I've never used a platform
842
+ * where my configured payload was hidden from me" — and W4-13 answers it
843
+ * per field instead, with a header value that is a secret marked as one.
844
+ */
845
+ data: z.json().optional(),
599
846
  createdAt: timestampSchema,
600
847
  /**
601
848
  * The delivery settings in force, with the defaults filled in.
@@ -626,9 +873,33 @@ export const scheduleSchema = z.object({
626
873
  .optional(),
627
874
  /** The verified reusable callback endpoint this schedule delivers to. */
628
875
  endpoint: endpointIdSchema.optional(),
876
+ /**
877
+ * Where the schedule delivers, as an address (W4-14, FXO-2255).
878
+ *
879
+ * Beside the identifier rather than instead of it, on the listing and the
880
+ * read alike, so every caller — dashboard, SDK, agent — sees a readable
881
+ * destination without a second request. Present when the schedule has an
882
+ * endpoint the service could name; absent on a timer, and on a row whose
883
+ * endpoint is no longer readable.
884
+ */
885
+ endpointUrl: z.string().url().optional(),
629
886
  generation: generationSchema,
630
887
  id: scheduleIdSchema,
631
888
  latencyClass: latencyClassSchema,
889
+ /**
890
+ * What this schedule does with an occurrence that reaches its first attempt
891
+ * late. Present on every schedule so a caller never has to duplicate the
892
+ * recurrence-sensitive policy default. `maxStalenessSeconds` is present only
893
+ * when this schedule overrides its recurrence's window.
894
+ */
895
+ lateness: scheduleLatenessSchema,
896
+ /**
897
+ * The schedule's name. Every schedule the service has written since W4-14
898
+ * has one — generated on save when none was given — and it is optional on
899
+ * the wire only so a row from before the column, or a reader holding a
900
+ * projection the migration has not reached, parses rather than fails.
901
+ */
902
+ name: scheduleNameSchema.optional(),
632
903
  partitionKey: partitionKeySchema.optional(),
633
904
  /**
634
905
  * How this schedule recurs, in the vocabulary it will keep being read in.
@@ -643,6 +914,24 @@ export const scheduleSchema = z.object({
643
914
  * was converted to an instant once, at acceptance, and reporting the delay
644
915
  * back would describe a schedule whose next fire moves every time it is read.
645
916
  */
917
+ /**
918
+ * When this schedule next fires, and absent when nothing is on the books.
919
+ *
920
+ * **The earliest unsettled occurrence, which is a fact rather than a
921
+ * projection** (FXO-2283). It is absent for a schedule that will not fire
922
+ * again — cancelled, archived, or a one-shot that has run — and absent is the
923
+ * truth about those rather than a value withheld: there is no instant to
924
+ * report, and reporting the last one would read as a plan.
925
+ *
926
+ * **This is the one field on a listing row that does not come from the
927
+ * projection.** The page comment below says a listing holds a route and a
928
+ * summary rather than every partition's live occurrence, and that is still
929
+ * true of the *detail* an occurrence carries — its state, its attempts, its
930
+ * identifier. This is one instant, taken by a lateral against a partial index
931
+ * built for it, and it is here because "when does this next run" is the
932
+ * question a list of schedules is read to answer.
933
+ */
934
+ nextDueAt: timestampSchema.optional(),
646
935
  recurrence: recurrenceSchema,
647
936
  status: scheduleStatusSchema,
648
937
  updatedAt: timestampSchema,
@@ -652,13 +941,39 @@ export const scheduleSchema = z.object({
652
941
  * A page of schedules.
653
942
  *
654
943
  * Items are schedules rather than schedule *views*. A cross-partition listing is
655
- * served from the D1 projection W2-06 built, and that projection holds a route
944
+ * served from the control-plane projection W2-06 built, and that projection holds a route
656
945
  * and a summary rather than every partition's live occurrence — so a page that
657
946
  * promised an occurrence would either lie about its freshness or force the
658
947
  * listing back through every partition it spans. Detail is where the occurrence
659
948
  * is authoritative, and detail is one call away.
660
949
  */
661
950
  export const schedulePageSchema = pageSchema(scheduleSchema);
951
+ function hasCompleteTimingEvidence(value) {
952
+ return ((value.firstAttemptStartedAt === undefined) ===
953
+ (value.firstAttemptLatenessMs === undefined) &&
954
+ (value.firstAttemptStartedAt === undefined) ===
955
+ (value.timingPolicyVersion === undefined));
956
+ }
957
+ function hasConsistentTimingEligibility(value) {
958
+ return value.timingEligibility === undefined
959
+ ? value.timingEligibilityReason === undefined
960
+ : value.timingEligibility === "eligible"
961
+ ? value.timingEligibilityReason === undefined
962
+ : value.timingEligibilityReason !== undefined;
963
+ }
964
+ const timingEvidenceShape = {
965
+ /** Signed difference from `plannedFor`; a negative value is an early send. */
966
+ firstAttemptLatenessMs: z.int().optional(),
967
+ /** Earliest trusted outbound start across this occurrence's attempts. */
968
+ firstAttemptStartedAt: timestampSchema.optional(),
969
+ /** Absent on records that predate timing evidence, which means unknown. */
970
+ timingEligibility: z
971
+ .enum(["eligible", "customer_blocked", "platform_blocked"])
972
+ .optional(),
973
+ timingEligibilityReason: z.string().min(1).max(128).optional(),
974
+ /** Policy pinned when trusted first-attempt timing evidence is recorded. */
975
+ timingPolicyVersion: z.string().min(1).max(64).optional(),
976
+ };
662
977
  /**
663
978
  * One concrete occurrence of a schedule.
664
979
  *
@@ -670,6 +985,7 @@ export const schedulePageSchema = pageSchema(scheduleSchema);
670
985
  export const occurrenceSchema = z
671
986
  .object({
672
987
  attemptCount: z.number().int().nonnegative(),
988
+ ...timingEvidenceShape,
673
989
  generation: generationSchema,
674
990
  id: occurrenceIdSchema,
675
991
  /** When the occurrence was planned to fire. Never earlier than this. */
@@ -689,6 +1005,14 @@ export const occurrenceSchema = z
689
1005
  (occurrence.settledAt !== undefined), {
690
1006
  message: "settledAt must be present for a terminal state and absent otherwise",
691
1007
  path: ["settledAt"],
1008
+ })
1009
+ .refine(hasCompleteTimingEvidence, {
1010
+ message: "firstAttemptStartedAt, firstAttemptLatenessMs and timingPolicyVersion must be present together",
1011
+ path: ["firstAttemptStartedAt"],
1012
+ })
1013
+ .refine(hasConsistentTimingEligibility, {
1014
+ message: "timingEligibilityReason is required for a blocked population and absent otherwise",
1015
+ path: ["timingEligibilityReason"],
692
1016
  });
693
1017
  /**
694
1018
  * One delivery attempt against one occurrence.
@@ -746,6 +1070,12 @@ export const attemptSchema = z
746
1070
  delivery: deliveryIdSchema,
747
1071
  /** Safe classifier code; absent when durable evidence predates diagnostics. */
748
1072
  diagnostic: attemptDiagnosticSchema.optional(),
1073
+ /**
1074
+ * Instrumented re-drives of the same dispatching occurrence under a new
1075
+ * dispatch epoch. Includes deliberate admission deferrals; this is not a
1076
+ * count of database failures or callback sends. Absent on older responses.
1077
+ */
1078
+ dispatchRecoveries: z.int().nonnegative().optional(),
749
1079
  /** Executor-observed elapsed time; absent when no durable timing survived. */
750
1080
  durationMs: z
751
1081
  .number()
@@ -753,8 +1083,26 @@ export const attemptSchema = z
753
1083
  .nonnegative()
754
1084
  .max(ATTEMPT_DURATION_MAX_MS)
755
1085
  .optional(),
1086
+ ...timingEvidenceShape,
756
1087
  id: attemptIdSchema,
1088
+ /**
1089
+ * Instrumented send authorizations granted after a later local alarm
1090
+ * replayed the original wake receipt. Preserves the attempt and delivery
1091
+ * identity; excludes synchronous receipt hedges and repeated authorization
1092
+ * acknowledgements. A grant does not prove a callback left. Absent on
1093
+ * older responses.
1094
+ */
1095
+ localReceiptRecoveries: z.int().nonnegative().optional(),
757
1096
  occurrence: occurrenceIdSchema,
1097
+ /**
1098
+ * How many times the watchdog took this attempt's send back and sent it
1099
+ * again: the send was authorized, and its outcome was never recorded
1100
+ * before its lease expired. Each re-send uses this attempt's `delivery` id,
1101
+ * so the receiver may have got the attempt more than once. `state` is the
1102
+ * outcome that was recorded. The timing evidence is the earliest send
1103
+ * known to have left. Absent when nothing was re-sent.
1104
+ */
1105
+ rescues: z.int().positive().optional(),
758
1106
  /** Stable next-action code paired with a diagnostic when one is known. */
759
1107
  remediation: attemptRemediationSchema.optional(),
760
1108
  /**
@@ -802,11 +1150,24 @@ export const attemptSchema = z
802
1150
  attempt.responseStatus === undefined), {
803
1151
  message: "classification metadata must be absent while an attempt is in flight",
804
1152
  path: ["state"],
1153
+ })
1154
+ .refine(hasCompleteTimingEvidence, {
1155
+ message: "firstAttemptStartedAt, firstAttemptLatenessMs and timingPolicyVersion must be present together",
1156
+ path: ["firstAttemptStartedAt"],
1157
+ })
1158
+ .refine(hasConsistentTimingEligibility, {
1159
+ message: "timingEligibilityReason is required for a blocked population and absent otherwise",
1160
+ path: ["timingEligibilityReason"],
805
1161
  });
806
1162
  /** An authoritative page over one schedule's retained occurrences. */
807
1163
  export const occurrencePageSchema = pageSchema(occurrenceSchema).extend({
808
1164
  freshness: z.object({ kind: z.literal("authoritative") }),
809
1165
  });
1166
+ /** A project-wide occurrence page and its plan's normal history boundary. */
1167
+ export const projectOccurrencePageSchema = pageSchema(occurrenceSchema).extend({
1168
+ freshness: z.object({ kind: z.literal("authoritative") }),
1169
+ retention: z.object({ from: timestampSchema }),
1170
+ });
810
1171
  /** An authoritative occurrence detail response. */
811
1172
  export const occurrenceViewSchema = z.object({
812
1173
  freshness: z.object({ kind: z.literal("authoritative") }),
@@ -877,8 +1238,8 @@ export const scheduleRequestSchema = z
877
1238
  /**
878
1239
  * Customer data placed in the signed callback envelope.
879
1240
  *
880
- * Any JSON value is accepted, including `null`, arrays and scalars. It is
881
- * never returned by schedule reads or listings. The accepting adapter
1241
+ * Any JSON value is accepted, including `null`, arrays and scalars. A
1242
+ * schedule read returns it; listings leave it off. The accepting adapter
882
1243
  * serializes it once, enforces `SCHEDULING_BOUNDS.payloadBytes` against
883
1244
  * those UTF-8 bytes, then encrypts those same bytes before persistence.
884
1245
  */
@@ -904,16 +1265,15 @@ export const scheduleRequestSchema = z
904
1265
  *
905
1266
  * **Grouped rather than three sibling fields**, because two of the three are
906
1267
  * meaningless without the first. `onOverlap` only answers a question a long
907
- * run can ask, and a caller who set it beside a five-second delivery would
908
- * be configuring something that cannot happen. Nesting makes the dependency
909
- * visible in the shape instead of only in a refusal message.
1268
+ * run can ask; beside a five-second delivery it is stored and never
1269
+ * consulted. Nesting makes the dependency visible in the shape.
910
1270
  */
911
1271
  delivery: z
912
1272
  .object({
913
1273
  /**
914
1274
  * What to do when this schedule comes due and its previous occurrence
915
- * has not finished. Only meaningful on a recurring, long-running
916
- * schedule; refused elsewhere rather than ignored.
1275
+ * has not finished. Consulted only on a recurring schedule whose runs
1276
+ * can outlast their interval; accepted and ignored elsewhere.
917
1277
  */
918
1278
  onOverlap: deliveryOverlapPolicySchema.optional(),
919
1279
  /**
@@ -922,8 +1282,11 @@ export const scheduleRequestSchema = z
922
1282
  */
923
1283
  completion: deliveryCompletionModeSchema.optional(),
924
1284
  /**
925
- * How long the receiver may take to answer, in seconds. One of
926
- * `DELIVERY_DURATION_TIERS`.
1285
+ * How long the receiver may take to answer, in whole seconds, up to
1286
+ * `MAX_DELIVERY_DURATION_SECONDS`. Above
1287
+ * `RESPONSE_DELIVERY_MAX_DURATION_SECONDS` the receiver has to answer
1288
+ * immediately and call the completion URL instead, which is what
1289
+ * `completion: "callback"` says.
927
1290
  */
928
1291
  maxDurationSeconds: deliveryDurationSecondsSchema.optional(),
929
1292
  })
@@ -965,6 +1328,17 @@ export const scheduleRequestSchema = z
965
1328
  * than obtained more of the service.
966
1329
  */
967
1330
  latencyClass: latencyClassSchema.optional(),
1331
+ /**
1332
+ * What to do when an occurrence reaches its first attempt late.
1333
+ *
1334
+ * Omitted policy defaults to `catchUp` for `at` and `delaySeconds`, and to
1335
+ * `skipAhead` for `everySeconds` and `cron`. `maxStalenessSeconds` overrides
1336
+ * the schedule-derived window with a positive whole number of seconds.
1337
+ * Retries are exempt: once an occurrence has made an attempt, retry and
1338
+ * give-up policy decide its outcome instead of measuring again from the
1339
+ * original planned instant.
1340
+ */
1341
+ lateness: scheduleLatenessRequestSchema.optional(),
968
1342
  /**
969
1343
  * A fairness lane within the caller's own project and environment.
970
1344
  *
@@ -983,6 +1357,15 @@ export const scheduleRequestSchema = z
983
1357
  * somebody asks otherwise.
984
1358
  */
985
1359
  partitionKey: partitionKeySchema.optional(),
1360
+ /**
1361
+ * A name for people (W4-14, FXO-2255). On a create, omitted means the
1362
+ * service generates one from the type, period and target domain —
1363
+ * "Every 60s → flowxo.com" — and stores it. On a replace, omitted means
1364
+ * the stored name stays as it is: a generated name is recomputed on
1365
+ * nothing, so editing the recurrence does not silently rewrite a name
1366
+ * the person kept or changed. Names may collide.
1367
+ */
1368
+ name: scheduleNameSchema.optional(),
986
1369
  /**
987
1370
  * The IANA timezone `cron` is read against. `UTC` when omitted.
988
1371
  *
@@ -1024,30 +1407,15 @@ export const scheduleRequestSchema = z
1024
1407
  path: ["timeZone"],
1025
1408
  });
1026
1409
  }
1027
- if (request.delivery?.onOverlap !== undefined) {
1028
- // A one-shot has nothing to overlap with. Refused rather than ignored, for
1029
- // the reason `timeZone` is: an ignored field is a misunderstanding the
1030
- // customer cannot see, and this one would look like a guarantee.
1031
- if (request.cron === undefined && request.everySeconds === undefined) {
1032
- context.addIssue({
1033
- code: "custom",
1034
- message: "`onOverlap` is only meaningful with `cron` or `everySeconds`",
1035
- path: ["delivery", "onOverlap"],
1036
- });
1037
- }
1038
- // Only checked when the duration is in *this* request. A replace may carry
1039
- // `onOverlap` while the stored schedule holds the duration, exactly as it
1040
- // may carry `data` while the stored schedule holds the endpoint, and the
1041
- // partition is where a request is resolved against what is stored.
1042
- const duration = request.delivery.maxDurationSeconds;
1043
- if (duration !== undefined && !isLongRunningDuration(duration)) {
1044
- context.addIssue({
1045
- code: "custom",
1046
- message: `\`onOverlap\` is only meaningful above ${String(FREE_DELIVERY_DURATION_SECONDS)} seconds, where a run can still be going when the next one is due`,
1047
- path: ["delivery", "onOverlap"],
1048
- });
1049
- }
1050
- }
1410
+ // **`onOverlap` is accepted wherever it is given, and ignored where it
1411
+ // cannot apply** — a one-shot, or a run too short to outlast its own
1412
+ // interval. It used to be refused in both cases, and the refusal became a
1413
+ // trap: every stored schedule carries the default policy, the dashboard's
1414
+ // editor sent it back on every save, and a five-second delivery was
1415
+ // refused for a field nobody had touched, under a collapsed section. The
1416
+ // operator's ruling on the W4-10 walk: "so what if it's not meaningful —
1417
+ // then we ignore it." A policy that cannot fire is not a guarantee anyone
1418
+ // can rely on, and the schedule's read reports what is stored.
1051
1419
  // Held open past the point a held-open request survives. Checked against the
1052
1420
  // *derived* pair rather than against what the body names, because
1053
1421
  // `completion` defaults to `response`: a caller who asks for twelve hours and
@@ -1067,6 +1435,20 @@ export const scheduleRequestSchema = z
1067
1435
  });
1068
1436
  }
1069
1437
  });
1438
+ /** Resolve the lateness settings a create or replacement request runs under. */
1439
+ export function deriveLatenessSettings(request) {
1440
+ const recurrenceKind = request.cron !== undefined
1441
+ ? "cron"
1442
+ : request.everySeconds !== undefined
1443
+ ? "every"
1444
+ : "at";
1445
+ const policy = request.lateness?.policy ??
1446
+ defaultLatenessPolicyForRecurrence(recurrenceKind);
1447
+ const maxStalenessSeconds = request.lateness?.maxStalenessSeconds;
1448
+ return maxStalenessSeconds === undefined
1449
+ ? { policy }
1450
+ : { maxStalenessSeconds, policy };
1451
+ }
1070
1452
  /**
1071
1453
  * The delivery settings a request runs under: the caller's, or the defaults.
1072
1454
  *