chainlesschain 0.163.4 → 0.163.6

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 (200) hide show
  1. package/completions/_cc +1 -1
  2. package/completions/cc.bash +1 -1
  3. package/completions/cc.fish +1 -1
  4. package/completions/cc.ps1 +1 -1
  5. package/package.json +1 -1
  6. package/src/assets/web-panel/assets/{AIOps-DB5X7qqV.js → AIOps-DDIUofA8.js} +1 -1
  7. package/src/assets/web-panel/assets/{ActionButton-DxHRtKLk.js → ActionButton-Bm-5xYRn.js} +1 -1
  8. package/src/assets/web-panel/assets/{Analytics-DIH5QrxT.js → Analytics-D5UH4ljI.js} +3 -3
  9. package/src/assets/web-panel/assets/{AppLayout-uu0oxVz7.js → AppLayout-D_ifjOOP.js} +5 -5
  10. package/src/assets/web-panel/assets/{Artifacts-BNSMiW9Y.js → Artifacts-BY1mXdC7.js} +1 -1
  11. package/src/assets/web-panel/assets/{Audit-DcAzB4_x.js → Audit-Bujr4Ml3.js} +1 -1
  12. package/src/assets/web-panel/assets/{BackgroundAgents-ClSsvjIc.js → BackgroundAgents-sioKSTcC.js} +1 -1
  13. package/src/assets/web-panel/assets/{Backup-BMciY0js.js → Backup-DDOAK8RK.js} +1 -1
  14. package/src/assets/web-panel/assets/{BaseInput-Hn3b57Sh.js → BaseInput-CtCsVVke.js} +1 -1
  15. package/src/assets/web-panel/assets/{Chat-CoSlC53V.js → Chat-DpNj0vnd.js} +4 -4
  16. package/src/assets/web-panel/assets/{ChatBubbleRenderer-CFevJ3So.js → ChatBubbleRenderer-CZta3Ftr.js} +1 -1
  17. package/src/assets/web-panel/assets/{Checkbox-B4L4Eu4w.js → Checkbox-DoHVNSXq.js} +1 -1
  18. package/src/assets/web-panel/assets/{Codegen-C77FgRxF.js → Codegen-DlVl7Qmv.js} +1 -1
  19. package/src/assets/web-panel/assets/{Col-BrLtJLH3.js → Col-GIqoPYty.js} +1 -1
  20. package/src/assets/web-panel/assets/{Community-Be7pXXhA.js → Community-CIAPEVji.js} +1 -1
  21. package/src/assets/web-panel/assets/{Compact-DuGPecjo.js → Compact-BN7JiI_h.js} +1 -1
  22. package/src/assets/web-panel/assets/{Compliance-CzS66mjP.js → Compliance-CqUWCM9B.js} +1 -1
  23. package/src/assets/web-panel/assets/{Cowork-CEhjCoyK.js → Cowork-A4iPp7A4.js} +3 -3
  24. package/src/assets/web-panel/assets/{Cron-D7ir8Vja.js → Cron-B2zeXXWb.js} +2 -2
  25. package/src/assets/web-panel/assets/{Crosschain-BcXmuLxf.js → Crosschain-BR2xmWr8.js} +1 -1
  26. package/src/assets/web-panel/assets/{DID-Cto1dtBy.js → DID-BzFDjrIv.js} +2 -2
  27. package/src/assets/web-panel/assets/{Dashboard-B7KjgwOX.js → Dashboard-DKtDo3yA.js} +2 -2
  28. package/src/assets/web-panel/assets/{Dropdown-COMqxijg.js → Dropdown-RNx43_YG.js} +1 -1
  29. package/src/assets/web-panel/assets/{EmailListRenderer-BZrya-nf.js → EmailListRenderer-SwMt4Rcy.js} +1 -1
  30. package/src/assets/web-panel/assets/{FamilyGuardDashboard-AmVeMq0J.js → FamilyGuardDashboard-CsYebZ4X.js} +1 -1
  31. package/src/assets/web-panel/assets/{Federation-kZU8c-27.js → Federation-BmRKvBTy.js} +1 -1
  32. package/src/assets/web-panel/assets/{FormItemContext-sOV22v2-.js → FormItemContext-CVxGTA-0.js} +1 -1
  33. package/src/assets/web-panel/assets/{GenericCardRenderer-BUuLtGCz.js → GenericCardRenderer-x2iXpyB7.js} +1 -1
  34. package/src/assets/web-panel/assets/{Git-K-ZxsLsH.js → Git-DfqbkiOP.js} +2 -2
  35. package/src/assets/web-panel/assets/{Governance-D3OEnawI.js → Governance-RuHyyj5O.js} +1 -1
  36. package/src/assets/web-panel/assets/{Inference-Bll0gRmP.js → Inference-Bn7uZ7rI.js} +1 -1
  37. package/src/assets/web-panel/assets/{KnowledgeGraph-DBXwjr53.js → KnowledgeGraph-DlP7XefZ.js} +1 -1
  38. package/src/assets/web-panel/assets/{Logs-CEzPwEfP.js → Logs-BYNdOiCd.js} +2 -2
  39. package/src/assets/web-panel/assets/MarkdownRenderer-aMtgN-R6.js +1 -0
  40. package/src/assets/web-panel/assets/{Marketplace-BYyRyOPz.js → Marketplace-JQYmDqM8.js} +1 -1
  41. package/src/assets/web-panel/assets/{McpTools-DVu8w_rM.js → McpTools-Cyx4yZ-9.js} +4 -4
  42. package/src/assets/web-panel/assets/{Memory-tenuZ_hO.js → Memory-VDuGZTN2.js} +2 -2
  43. package/src/assets/web-panel/assets/{MobileBridge-Bxgy4ozS.js → MobileBridge-_yKo97gi.js} +1 -1
  44. package/src/assets/web-panel/assets/{MobileProjects-Dgx1D6sc.js → MobileProjects-CXRXW3q7.js} +1 -1
  45. package/src/assets/web-panel/assets/{Mtc-CRfJ8r8j.js → Mtc-acTGtfLH.js} +5 -5
  46. package/src/assets/web-panel/assets/{MtcAudit-BBJq4Aiz.js → MtcAudit-ClpjXfkY.js} +4 -4
  47. package/src/assets/web-panel/assets/{Multisig-B3ii9lai.js → Multisig-_6Tn3o96.js} +3 -3
  48. package/src/assets/web-panel/assets/{NLProgramming-FIi-Cubv.js → NLProgramming-CK7pbZzP.js} +1 -1
  49. package/src/assets/web-panel/assets/{Notes-DIq8MPf1.js → Notes-Czy6V_P8.js} +3 -3
  50. package/src/assets/web-panel/assets/{NotificationSettings-BFOD9eGd.js → NotificationSettings-Cqx-UVv3.js} +1 -1
  51. package/src/assets/web-panel/assets/{OrderTableRenderer--41u7vre.js → OrderTableRenderer-C69WDxGy.js} +1 -1
  52. package/src/assets/web-panel/assets/{Organization-B_0AWztW.js → Organization-DFnA9vLQ.js} +4 -4
  53. package/src/assets/web-panel/assets/{Overflow-B6I7TuXv.js → Overflow-ReZkk2yT.js} +1 -1
  54. package/src/assets/web-panel/assets/{P2P-Cdf6qens.js → P2P-DzjSI0aI.js} +2 -2
  55. package/src/assets/web-panel/assets/{PdhVaultBrowser-B_0FcQJY.js → PdhVaultBrowser-DKTf5xIP.js} +3 -3
  56. package/src/assets/web-panel/assets/{Permissions-6nFBjsQp.js → Permissions-DhQ5klhR.js} +3 -3
  57. package/src/assets/web-panel/assets/{PersonalDataHub-nQYV7xpn.js → PersonalDataHub-CUL1iHrW.js} +3 -3
  58. package/src/assets/web-panel/assets/{Pipeline-CBVod5jm.js → Pipeline-DU88iu4N.js} +1 -1
  59. package/src/assets/web-panel/assets/{Privacy-D52jW1Nt.js → Privacy-Bw-sYfyK.js} +1 -1
  60. package/src/assets/web-panel/assets/{ProjectInit-Bdx3BYhf.js → ProjectInit-Bq3Q5y0z.js} +2 -2
  61. package/src/assets/web-panel/assets/{ProjectSettings-b7xr4DUa.js → ProjectSettings-C8spzpho.js} +2 -2
  62. package/src/assets/web-panel/assets/{Projects-DhfifCKx.js → Projects-Dd-xuPIc.js} +1 -1
  63. package/src/assets/web-panel/assets/Providers-Bf4eTP9W.js +1 -0
  64. package/src/assets/web-panel/assets/{QrScannerModal-FQKSmc3e.js → QrScannerModal-Dme9BDU1.js} +1 -1
  65. package/src/assets/web-panel/assets/{QuickAsk-CSTrhzcF.js → QuickAsk-CIXfv1rE.js} +1 -1
  66. package/src/assets/web-panel/assets/{Recommend-CxMABOUh.js → Recommend-CIxvdeZ-.js} +1 -1
  67. package/src/assets/web-panel/assets/{RemoteSession-Cv2T8yRa.js → RemoteSession-C6k7MExI.js} +2 -2
  68. package/src/assets/web-panel/assets/{Reputation-CE1k4Vvu.js → Reputation-CISDz2bl.js} +1 -1
  69. package/src/assets/web-panel/assets/{Row-BNgNcAV_.js → Row-CdnDxXyf.js} +1 -1
  70. package/src/assets/web-panel/assets/{RssFeed-5ZiBZ3ph.js → RssFeed-C-QpRHsl.js} +3 -3
  71. package/src/assets/web-panel/assets/{Search-mDOIF1UV.js → Search-DBjaGzSn.js} +1 -1
  72. package/src/assets/web-panel/assets/{Security-CtuCTC-e.js → Security-CQKukOeE.js} +3 -3
  73. package/src/assets/web-panel/assets/{Services-DxFO2Zg7.js → Services-DZz86jWb.js} +2 -2
  74. package/src/assets/web-panel/assets/{Skeleton-CKmKkH9y.js → Skeleton-jb8RMK0d.js} +1 -1
  75. package/src/assets/web-panel/assets/{Skills-Bmzl9qkk.js → Skills-CdY76fZG.js} +1 -1
  76. package/src/assets/web-panel/assets/{Sla-9RqaWHOC.js → Sla-C_CSiZX9.js} +1 -1
  77. package/src/assets/web-panel/assets/SpeechSettings-C7tT9iad.js +1 -0
  78. package/src/assets/web-panel/assets/{SyncSettings-CvNuCeBT.js → SyncSettings-qvimGO0O.js} +2 -2
  79. package/src/assets/web-panel/assets/{Tasks-B_sLdcnw.js → Tasks-Dng5LPJC.js} +1 -1
  80. package/src/assets/web-panel/assets/{Templates-D9_YijdK.js → Templates-Bwqq5Wll.js} +1 -1
  81. package/src/assets/web-panel/assets/{Tenant-Cuc7BUSP.js → Tenant-DNWuwP7Z.js} +1 -1
  82. package/src/assets/web-panel/assets/{Terminal-6VYRnJgd.js → Terminal-B-6Othpp.js} +2 -2
  83. package/src/assets/web-panel/assets/{TimelineRenderer-GtK8xd8Y.js → TimelineRenderer-DKpKZfzx.js} +1 -1
  84. package/src/assets/web-panel/assets/{Tokens-BZ94Lt5W.js → Tokens-DzfGMd2J.js} +1 -1
  85. package/src/assets/web-panel/assets/{Trigger-DEcZgDaX.js → Trigger-BXwUi74J.js} +1 -1
  86. package/src/assets/web-panel/assets/{Trust-CC3z9THe.js → Trust-BNzUq4ui.js} +1 -1
  87. package/src/assets/web-panel/assets/{UkeySign-Cvtw8cV3.js → UkeySign-BYugxww4.js} +1 -1
  88. package/src/assets/web-panel/assets/{VideoEditing-CL4e6Bn3.js → VideoEditing-DdauhtSJ.js} +1 -1
  89. package/src/assets/web-panel/assets/{Wallet-ycEemP27.js → Wallet-CSC9bCe_.js} +4 -4
  90. package/src/assets/web-panel/assets/{WebAuthn-DgZ4X_aw.js → WebAuthn-Q8AN3bTO.js} +5 -5
  91. package/src/assets/web-panel/assets/{WorkflowEditor-D0qMhSv7.js → WorkflowEditor-d-LBGCuA.js} +1 -1
  92. package/src/assets/web-panel/assets/{chat-DsAg01Zn.js → chat-BLVDCgsZ.js} +1 -1
  93. package/src/assets/web-panel/assets/{colors-B0oqAhrQ.js → colors-BZlPSEHl.js} +1 -1
  94. package/src/assets/web-panel/assets/{compact-item-CmX0Rabz.js → compact-item-IWseP6R6.js} +1 -1
  95. package/src/assets/web-panel/assets/{createContext-Be4cn1Ub.js → createContext-DnVRV3Ao.js} +1 -1
  96. package/src/assets/web-panel/assets/devWarning-Jzu62CLC.js +1 -0
  97. package/src/assets/web-panel/assets/{hasIn-D5v12kVq.js → hasIn-DLQZA_Wn.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-BA4oulaP.js → index-1jeAudHd.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-DM724NgU.js → index-BEu2lMBy.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-5LGY0JbG.js → index-BOZJBTWZ.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-DikJ0Qqe.js → index-BSjilZ9L.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-BqltWojQ.js → index-BZs5bBnk.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-7VKDn-t1.js → index-B_N4-Gq9.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-BXGSbelg.js → index-Bbj2NS_I.js} +3 -3
  105. package/src/assets/web-panel/assets/{index-CGiIyTZw.js → index-BhA3QEni.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-BKmsS2jj.js → index-Bxp7uDD8.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-B3mUHuVc.js → index-C1ifZtXo.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-BBvyHguy.js → index-C2FqfMvU.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-q_fQ94SE.js → index-C5dgnilC.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-D5YVopyr.js → index-CINJQEj6.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-0eWLkQKT.js → index-COev0W2g.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-BoAIAZ5C.js → index-CT9vVN-X.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-CqoCZ5vR.js → index-CZDekj7T.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-301ogenv.js → index-CarMTIyQ.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-Dn2dSdXx.js → index-CbQNL0ue.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-DKUBj36_.js → index-CpBaJ3TD.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-BBREnn1V.js → index-DB0MJgF_.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-BRIqLy1m.js → index-DOoQpuQY.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-jK3ax6Z6.js → index-DTbxp4UH.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-B1YGjOWC.js → index-DUq0c7gq.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-CyyGTMKd.js → index-DZpYeqrh.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-_JjRLSD0.js → index-D_LQodeJ.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-DyUaFWEk.js → index-DalA0TKC.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-BaE0Ui07.js → index-DjWnONdy.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-CyWBir7N.js → index-Dkwsi1Kn.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-qMuZ-NRR.js → index-DocEhinm.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-Ckrk3pIV.js → index-DpVrK6fr.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-DQRsQen0.js → index-Dy0R5ZED.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-frSojpHj.js → index-M7wxfAbm.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-D0BSkxYQ.js → index-N_GoWZX4.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-8WvYGnON.js → index-YSw_gxlZ.js} +1 -1
  132. package/src/assets/web-panel/assets/index-Zb2MeDSy.js +1 -0
  133. package/src/assets/web-panel/assets/index-fikqml4k.js +1 -0
  134. package/src/assets/web-panel/assets/{index-Hfe_dEGx.js → index-gt8bNefW.js} +1 -1
  135. package/src/assets/web-panel/assets/{index-CZ2wUEgr.js → index-hwVDeoC0.js} +1 -1
  136. package/src/assets/web-panel/assets/{index-CZpfzcuy.js → index-jWb70ZgZ.js} +1 -1
  137. package/src/assets/web-panel/assets/{initDefaultProps-D4VdhJ9_.js → initDefaultProps-Co67Kqmb.js} +1 -1
  138. package/src/assets/web-panel/assets/{motion-CZJA0MQW.js → motion-BMOxQSF_.js} +1 -1
  139. package/src/assets/web-panel/assets/{move-BqEs7Sb1.js → move-C5fWDw-N.js} +1 -1
  140. package/src/assets/web-panel/assets/{mtc-parser-BpHEtsu1.js → mtc-parser-BJevpL0e.js} +1 -1
  141. package/src/assets/web-panel/assets/{omit-BW94tY9W.js → omit-BCzNU8s6.js} +1 -1
  142. package/src/assets/web-panel/assets/{pickAttrs-BnBjPWP9.js → pickAttrs-CybWvDPk.js} +1 -1
  143. package/src/assets/web-panel/assets/{placementArrow-uUkjQ_hu.js → placementArrow-BMWS9HnW.js} +1 -1
  144. package/src/assets/web-panel/assets/{responsiveObserve-B-klUqXK.js → responsiveObserve-BoL2UnGY.js} +1 -1
  145. package/src/assets/web-panel/assets/{slide-CvY1cNAs.js → slide-DWNfXraU.js} +1 -1
  146. package/src/assets/web-panel/assets/{statusUtils-Ix5sEDT2.js → statusUtils-BquTM3tX.js} +1 -1
  147. package/src/assets/web-panel/assets/{styleChecker-C8qcXOne.js → styleChecker-BEkCleim.js} +1 -1
  148. package/src/assets/web-panel/assets/{useFlexGapSupport-KNF6Kj8f.js → useFlexGapSupport-Oecnq7EA.js} +1 -1
  149. package/src/assets/web-panel/assets/{useFs-DGJr345n.js → useFs-CkDffJ4F.js} +1 -1
  150. package/src/assets/web-panel/assets/{usePersonalDataHub-BboN99rI.js → usePersonalDataHub-qbvKXesB.js} +1 -1
  151. package/src/assets/web-panel/assets/{vnode-Bgt8Vgte.js → vnode-C6v8n6mA.js} +1 -1
  152. package/src/assets/web-panel/assets/{zoom-BhJtErgt.js → zoom-4CTeFILV.js} +1 -1
  153. package/src/assets/web-panel/index.html +1 -1
  154. package/src/command-help-index.json +3 -3
  155. package/src/command-manifest.json +1 -1
  156. package/src/commands/agenda.js +125 -7
  157. package/src/commands/automation.js +462 -3
  158. package/src/commands/background-session.js +102 -1
  159. package/src/commands/cowork.js +30 -13
  160. package/src/commands/loop.js +167 -36
  161. package/src/commands/routine.js +50 -13
  162. package/src/commands/scheduler-daemon.js +450 -0
  163. package/src/data/changelog.json +27 -1
  164. package/src/lib/agent-schedule-store.js +509 -36
  165. package/src/lib/automation-center-routines.js +363 -0
  166. package/src/lib/automation-center.js +435 -0
  167. package/src/lib/automation-engine.js +74 -19
  168. package/src/lib/automation-execution-authority.js +736 -0
  169. package/src/lib/background-agent-supervisor.js +78 -0
  170. package/src/lib/background-needs-input-incident.js +221 -0
  171. package/src/lib/cowork-cron.js +555 -39
  172. package/src/lib/cowork-observe.js +5 -1
  173. package/src/lib/loop.js +37 -2
  174. package/src/lib/micro-compact.js +11 -5
  175. package/src/lib/permission-engine.js +14 -7
  176. package/src/lib/process-execution-broker/index.js +14 -3
  177. package/src/lib/public-ide-capability-manifest.js +2 -0
  178. package/src/lib/routine-store.js +242 -68
  179. package/src/lib/scheduler-kernel/agenda-adapter.js +812 -0
  180. package/src/lib/scheduler-kernel/authority-resolver.js +163 -0
  181. package/src/lib/scheduler-kernel/automation-adapter.js +592 -0
  182. package/src/lib/scheduler-kernel/automation-event-adapter.js +721 -0
  183. package/src/lib/scheduler-kernel/contract.js +11 -3
  184. package/src/lib/scheduler-kernel/cowork-cron-adapter.js +820 -0
  185. package/src/lib/scheduler-kernel/loop-adapter.js +378 -0
  186. package/src/lib/scheduler-kernel/routine-adapter.js +812 -0
  187. package/src/lib/scheduler-kernel/runtime.js +527 -0
  188. package/src/lib/scheduler-kernel/service.js +328 -0
  189. package/src/lib/scheduler-kernel/store.js +940 -98
  190. package/src/lib/session-message-provenance.js +4 -1
  191. package/src/repl/agent-repl.js +24 -5
  192. package/src/runtime/agent-core.js +38 -17
  193. package/src/runtime/coding-agent-contract-shared.cjs +5 -0
  194. package/src/workers/background-agent-worker.js +91 -28
  195. package/src/assets/web-panel/assets/MarkdownRenderer-CLmCbHx6.js +0 -1
  196. package/src/assets/web-panel/assets/Providers-Dy_PgDMr.js +0 -1
  197. package/src/assets/web-panel/assets/SpeechSettings-CGdzFIYw.js +0 -1
  198. package/src/assets/web-panel/assets/devWarning-CP2qzG6D.js +0 -1
  199. package/src/assets/web-panel/assets/index-CKB0vbJU.js +0 -1
  200. package/src/assets/web-panel/assets/index-DgoXmwqB.js +0 -1
@@ -0,0 +1,736 @@
1
+ import { createHash } from "node:crypto";
2
+ import {
3
+ checkPermission,
4
+ ensurePermissionTables,
5
+ } from "./permission-engine.js";
6
+ import {
7
+ SchedulerKernelError,
8
+ canonicalJson,
9
+ normalizeEpochMs,
10
+ normalizeIdentifier,
11
+ } from "./scheduler-kernel/contract.js";
12
+
13
+ export const AUTOMATION_EXECUTE_PERMISSION = "automation:execute";
14
+ export const AUTOMATION_MIN_BUDGET_WINDOW_MS = 60_000;
15
+ export const AUTOMATION_MAX_BUDGET_WINDOW_MS = 31 * 24 * 60 * 60 * 1000;
16
+ export const AUTOMATION_MAX_BUDGET_VALUE = 1_000_000;
17
+
18
+ const CONNECTOR_PATTERN = /^[a-z0-9][a-z0-9_-]{0,63}$/u;
19
+ const DIGEST_PATTERN = /^[0-9a-f]{64}$/u;
20
+
21
+ function authorityError(code, message, details = undefined, cause = undefined) {
22
+ const error = new SchedulerKernelError(
23
+ code,
24
+ message,
25
+ details,
26
+ cause ? { cause } : undefined,
27
+ );
28
+ error.retryable = false;
29
+ return error;
30
+ }
31
+
32
+ function positiveInteger(value, field, maximum = Number.MAX_SAFE_INTEGER) {
33
+ const normalized = Number(value);
34
+ if (
35
+ !Number.isSafeInteger(normalized) ||
36
+ normalized <= 0 ||
37
+ normalized > maximum
38
+ ) {
39
+ throw authorityError(
40
+ "AUTOMATION_EXECUTION_BUDGET_INVALID",
41
+ `${field} must be a positive integer no greater than ${maximum}`,
42
+ );
43
+ }
44
+ return normalized;
45
+ }
46
+
47
+ function nonnegativeInteger(value, field) {
48
+ const normalized = Number(value);
49
+ if (!Number.isSafeInteger(normalized) || normalized < 0) {
50
+ throw authorityError(
51
+ "AUTOMATION_EXECUTION_BUDGET_STATE_INVALID",
52
+ `${field} must be a non-negative safe integer`,
53
+ );
54
+ }
55
+ return normalized;
56
+ }
57
+
58
+ function normalizeConnector(value, field) {
59
+ const connector = normalizeIdentifier(value, field, { maxLength: 64 });
60
+ if (!CONNECTOR_PATTERN.test(connector)) {
61
+ throw authorityError(
62
+ "AUTOMATION_EXECUTION_CONNECTOR_INVALID",
63
+ `${field} must be a lowercase connector identifier`,
64
+ );
65
+ }
66
+ return connector;
67
+ }
68
+
69
+ function normalizeDigest(value, field) {
70
+ if (typeof value !== "string" || !DIGEST_PATTERN.test(value)) {
71
+ throw authorityError(
72
+ "AUTOMATION_EXECUTION_AUTHORITY_INVALID",
73
+ `${field} must be a SHA-256 digest`,
74
+ );
75
+ }
76
+ return value;
77
+ }
78
+
79
+ function normalizeFlowAuthorityInputs(flow) {
80
+ if (!flow || typeof flow !== "object" || Array.isArray(flow)) {
81
+ throw authorityError(
82
+ "AUTOMATION_EXECUTION_FLOW_INVALID",
83
+ "automation execution authority requires a flow",
84
+ );
85
+ }
86
+ const flowId = normalizeIdentifier(flow.id, "automationFlow.id");
87
+ if (
88
+ typeof flow.createdBy !== "string" ||
89
+ flow.createdBy.trim().length === 0
90
+ ) {
91
+ throw authorityError(
92
+ "AUTOMATION_EXECUTION_PRINCIPAL_REQUIRED",
93
+ `automation flow requires createdBy before unattended execution: ${flowId}`,
94
+ { flowId },
95
+ );
96
+ }
97
+ const principalId = normalizeIdentifier(
98
+ flow.createdBy,
99
+ "automationFlow.createdBy",
100
+ );
101
+ if (!Array.isArray(flow.nodes)) {
102
+ throw authorityError(
103
+ "AUTOMATION_EXECUTION_FLOW_INVALID",
104
+ `automation flow nodes are malformed: ${flowId}`,
105
+ { flowId },
106
+ );
107
+ }
108
+ const connectors = [];
109
+ let actionSteps = 0;
110
+ for (let index = 0; index < flow.nodes.length; index += 1) {
111
+ const node = flow.nodes[index];
112
+ if (!node || typeof node !== "object" || Array.isArray(node)) {
113
+ throw authorityError(
114
+ "AUTOMATION_EXECUTION_FLOW_INVALID",
115
+ `automation flow node is malformed: ${flowId}`,
116
+ { flowId, index },
117
+ );
118
+ }
119
+ if ((node.type || "action") !== "action") continue;
120
+ actionSteps += 1;
121
+ connectors.push(
122
+ normalizeConnector(
123
+ node.connector,
124
+ `automationFlow.nodes[${index}].connector`,
125
+ ),
126
+ );
127
+ }
128
+ return {
129
+ flowId,
130
+ principalId,
131
+ connectors: [...new Set(connectors)].sort(),
132
+ actionSteps,
133
+ };
134
+ }
135
+
136
+ export function automationConnectorPermission(connector) {
137
+ return `automation:connector:${normalizeConnector(connector, "connector")}`;
138
+ }
139
+
140
+ export function automationRequiredPermissions(flow) {
141
+ const inputs = normalizeFlowAuthorityInputs(flow);
142
+ return [
143
+ AUTOMATION_EXECUTE_PERMISSION,
144
+ ...inputs.connectors.map(automationConnectorPermission),
145
+ ];
146
+ }
147
+
148
+ export function ensureAutomationExecutionAuthorityTables(db) {
149
+ if (
150
+ !db ||
151
+ typeof db.exec !== "function" ||
152
+ typeof db.prepare !== "function"
153
+ ) {
154
+ throw authorityError(
155
+ "AUTOMATION_EXECUTION_DATABASE_REQUIRED",
156
+ "automation execution authority requires a compatible database",
157
+ );
158
+ }
159
+ ensurePermissionTables(db);
160
+ db.exec(`
161
+ CREATE TABLE IF NOT EXISTS auto_execution_budgets (
162
+ flow_id TEXT PRIMARY KEY,
163
+ revision INTEGER NOT NULL,
164
+ window_ms INTEGER NOT NULL,
165
+ max_runs INTEGER NOT NULL,
166
+ max_action_steps INTEGER NOT NULL,
167
+ created_at_ms INTEGER NOT NULL,
168
+ updated_at_ms INTEGER NOT NULL
169
+ )
170
+ `);
171
+ db.exec(`
172
+ CREATE TABLE IF NOT EXISTS auto_execution_budget_usage (
173
+ flow_id TEXT NOT NULL,
174
+ budget_revision INTEGER NOT NULL,
175
+ window_started_at_ms INTEGER NOT NULL,
176
+ runs INTEGER NOT NULL,
177
+ action_steps INTEGER NOT NULL,
178
+ updated_at_ms INTEGER NOT NULL,
179
+ PRIMARY KEY (flow_id, budget_revision, window_started_at_ms)
180
+ )
181
+ `);
182
+ db.exec(`
183
+ CREATE TABLE IF NOT EXISTS auto_execution_budget_reservations (
184
+ occurrence_id TEXT PRIMARY KEY,
185
+ flow_id TEXT NOT NULL,
186
+ budget_revision INTEGER NOT NULL,
187
+ window_started_at_ms INTEGER NOT NULL,
188
+ principal_id TEXT NOT NULL,
189
+ authority_digest TEXT NOT NULL,
190
+ action_steps INTEGER NOT NULL,
191
+ created_at_ms INTEGER NOT NULL
192
+ )
193
+ `);
194
+ db.exec(`
195
+ CREATE INDEX IF NOT EXISTS auto_execution_budget_reservations_window_idx
196
+ ON auto_execution_budget_reservations
197
+ (flow_id, budget_revision, window_started_at_ms)
198
+ `);
199
+ }
200
+
201
+ function rowToBudget(row) {
202
+ if (!row) return null;
203
+ return {
204
+ flowId: row.flow_id,
205
+ revision: row.revision,
206
+ windowMs: row.window_ms,
207
+ maxRuns: row.max_runs,
208
+ maxActionSteps: row.max_action_steps,
209
+ createdAtMs: row.created_at_ms,
210
+ updatedAtMs: row.updated_at_ms,
211
+ };
212
+ }
213
+
214
+ export function normalizeAutomationExecutionBudget(value) {
215
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
216
+ throw authorityError(
217
+ "AUTOMATION_EXECUTION_BUDGET_INVALID",
218
+ "automation execution budget must be an object",
219
+ );
220
+ }
221
+ const windowMs = positiveInteger(
222
+ value.windowMs,
223
+ "automationBudget.windowMs",
224
+ AUTOMATION_MAX_BUDGET_WINDOW_MS,
225
+ );
226
+ if (windowMs < AUTOMATION_MIN_BUDGET_WINDOW_MS) {
227
+ throw authorityError(
228
+ "AUTOMATION_EXECUTION_BUDGET_INVALID",
229
+ `automationBudget.windowMs must be at least ${AUTOMATION_MIN_BUDGET_WINDOW_MS}`,
230
+ );
231
+ }
232
+ return {
233
+ flowId: normalizeIdentifier(value.flowId, "automationBudget.flowId"),
234
+ revision: positiveInteger(
235
+ value.revision,
236
+ "automationBudget.revision",
237
+ Number.MAX_SAFE_INTEGER,
238
+ ),
239
+ windowMs,
240
+ maxRuns: positiveInteger(
241
+ value.maxRuns,
242
+ "automationBudget.maxRuns",
243
+ AUTOMATION_MAX_BUDGET_VALUE,
244
+ ),
245
+ maxActionSteps: positiveInteger(
246
+ value.maxActionSteps,
247
+ "automationBudget.maxActionSteps",
248
+ AUTOMATION_MAX_BUDGET_VALUE,
249
+ ),
250
+ };
251
+ }
252
+
253
+ export function getAutomationExecutionBudget(db, flowId) {
254
+ ensureAutomationExecutionAuthorityTables(db);
255
+ const id = normalizeIdentifier(flowId, "flowId");
256
+ return rowToBudget(
257
+ db
258
+ .prepare("SELECT * FROM auto_execution_budgets WHERE flow_id = ?")
259
+ .get(id),
260
+ );
261
+ }
262
+
263
+ export function setAutomationExecutionBudget(
264
+ db,
265
+ flowId,
266
+ { windowMs, maxRuns, maxActionSteps } = {},
267
+ { now = Date.now } = {},
268
+ ) {
269
+ ensureAutomationExecutionAuthorityTables(db);
270
+ const id = normalizeIdentifier(flowId, "flowId");
271
+ const flow = db.prepare("SELECT id FROM auto_flows WHERE id = ?").get(id);
272
+ if (!flow) {
273
+ throw authorityError(
274
+ "AUTOMATION_EXECUTION_FLOW_NOT_FOUND",
275
+ `automation flow not found: ${id}`,
276
+ );
277
+ }
278
+ const normalizedWindow = positiveInteger(
279
+ windowMs,
280
+ "windowMs",
281
+ AUTOMATION_MAX_BUDGET_WINDOW_MS,
282
+ );
283
+ if (normalizedWindow < AUTOMATION_MIN_BUDGET_WINDOW_MS) {
284
+ throw authorityError(
285
+ "AUTOMATION_EXECUTION_BUDGET_INVALID",
286
+ `windowMs must be at least ${AUTOMATION_MIN_BUDGET_WINDOW_MS}`,
287
+ );
288
+ }
289
+ const normalizedRuns = positiveInteger(
290
+ maxRuns,
291
+ "maxRuns",
292
+ AUTOMATION_MAX_BUDGET_VALUE,
293
+ );
294
+ const normalizedSteps = positiveInteger(
295
+ maxActionSteps,
296
+ "maxActionSteps",
297
+ AUTOMATION_MAX_BUDGET_VALUE,
298
+ );
299
+ const timestamp = normalizeEpochMs(Number(now()), "now");
300
+ if (typeof db.transaction !== "function") {
301
+ throw authorityError(
302
+ "AUTOMATION_EXECUTION_TRANSACTION_REQUIRED",
303
+ "automation execution budget requires transactional database support",
304
+ );
305
+ }
306
+ const write = db.transaction(() => {
307
+ const current = rowToBudget(
308
+ db
309
+ .prepare("SELECT * FROM auto_execution_budgets WHERE flow_id = ?")
310
+ .get(id),
311
+ );
312
+ const revision = (current?.revision || 0) + 1;
313
+ db.prepare(
314
+ `INSERT INTO auto_execution_budgets
315
+ (flow_id, revision, window_ms, max_runs, max_action_steps, created_at_ms, updated_at_ms)
316
+ VALUES (?, ?, ?, ?, ?, ?, ?)
317
+ ON CONFLICT(flow_id) DO UPDATE SET
318
+ revision = excluded.revision,
319
+ window_ms = excluded.window_ms,
320
+ max_runs = excluded.max_runs,
321
+ max_action_steps = excluded.max_action_steps,
322
+ updated_at_ms = excluded.updated_at_ms`,
323
+ ).run(
324
+ id,
325
+ revision,
326
+ normalizedWindow,
327
+ normalizedRuns,
328
+ normalizedSteps,
329
+ current?.createdAtMs || timestamp,
330
+ timestamp,
331
+ );
332
+ return rowToBudget(
333
+ db
334
+ .prepare("SELECT * FROM auto_execution_budgets WHERE flow_id = ?")
335
+ .get(id),
336
+ );
337
+ });
338
+ return write.immediate();
339
+ }
340
+
341
+ export function automationExecutionAuthoritySnapshot(db, flow) {
342
+ const inputs = normalizeFlowAuthorityInputs(flow);
343
+ const budget = getAutomationExecutionBudget(db, inputs.flowId);
344
+ if (!budget) {
345
+ throw authorityError(
346
+ "AUTOMATION_EXECUTION_BUDGET_REQUIRED",
347
+ `automation flow requires an execution budget: ${inputs.flowId}`,
348
+ { flowId: inputs.flowId },
349
+ );
350
+ }
351
+ return {
352
+ schemaVersion: 1,
353
+ flowId: inputs.flowId,
354
+ principalId: inputs.principalId,
355
+ requiredPermissions: [
356
+ AUTOMATION_EXECUTE_PERMISSION,
357
+ ...inputs.connectors.map(automationConnectorPermission),
358
+ ],
359
+ connectors: inputs.connectors,
360
+ actionSteps: inputs.actionSteps,
361
+ budget: normalizeAutomationExecutionBudget(budget),
362
+ };
363
+ }
364
+
365
+ export function normalizeAutomationExecutionAuthoritySnapshot(value, flow) {
366
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
367
+ throw authorityError(
368
+ "AUTOMATION_EXECUTION_AUTHORITY_INVALID",
369
+ "automation execution authority snapshot is required",
370
+ );
371
+ }
372
+ const inputs = normalizeFlowAuthorityInputs(flow);
373
+ const budget = normalizeAutomationExecutionBudget(value.budget);
374
+ const requiredPermissions = automationRequiredPermissions(flow);
375
+ const normalized = {
376
+ schemaVersion: value.schemaVersion,
377
+ flowId: normalizeIdentifier(value.flowId, "executionAuthority.flowId"),
378
+ principalId: normalizeIdentifier(
379
+ value.principalId,
380
+ "executionAuthority.principalId",
381
+ ),
382
+ requiredPermissions: Array.isArray(value.requiredPermissions)
383
+ ? value.requiredPermissions.map((permission, index) =>
384
+ normalizeIdentifier(
385
+ permission,
386
+ `executionAuthority.requiredPermissions[${index}]`,
387
+ ),
388
+ )
389
+ : [],
390
+ connectors: Array.isArray(value.connectors)
391
+ ? value.connectors.map((connector, index) =>
392
+ normalizeConnector(
393
+ connector,
394
+ `executionAuthority.connectors[${index}]`,
395
+ ),
396
+ )
397
+ : [],
398
+ actionSteps: Number(value.actionSteps),
399
+ budget,
400
+ };
401
+ const valid =
402
+ normalized.schemaVersion === 1 &&
403
+ normalized.flowId === inputs.flowId &&
404
+ normalized.principalId === inputs.principalId &&
405
+ canonicalJson(
406
+ normalized.requiredPermissions,
407
+ "executionAuthority.requiredPermissions",
408
+ ) === canonicalJson(requiredPermissions, "requiredPermissions") &&
409
+ canonicalJson(normalized.connectors, "executionAuthority.connectors") ===
410
+ canonicalJson(inputs.connectors, "connectors") &&
411
+ normalized.actionSteps === inputs.actionSteps &&
412
+ budget.flowId === inputs.flowId;
413
+ if (!valid) {
414
+ throw authorityError(
415
+ "AUTOMATION_EXECUTION_AUTHORITY_INVALID",
416
+ `automation execution authority does not match flow: ${inputs.flowId}`,
417
+ );
418
+ }
419
+ return normalized;
420
+ }
421
+
422
+ export function automationExecutionAuthorityDigest(snapshot, flow) {
423
+ const normalized = normalizeAutomationExecutionAuthoritySnapshot(
424
+ snapshot,
425
+ flow,
426
+ );
427
+ return createHash("sha256")
428
+ .update("chainlesschain.automation-execution-authority.v1\0", "utf8")
429
+ .update(canonicalJson(normalized, "automationExecutionAuthority"), "utf8")
430
+ .digest("hex");
431
+ }
432
+
433
+ export function automationSchedulerAuthority(snapshot, flow, capability) {
434
+ const normalized = normalizeAutomationExecutionAuthoritySnapshot(
435
+ snapshot,
436
+ flow,
437
+ );
438
+ const digest = automationExecutionAuthorityDigest(normalized, flow);
439
+ return {
440
+ schemaVersion: 1,
441
+ principal: { type: "user", id: normalized.principalId },
442
+ tenantId: null,
443
+ workspaceId: null,
444
+ requestedCapabilities: [
445
+ normalizeIdentifier(capability, "capability"),
446
+ ...normalized.connectors.map(
447
+ (connector) => `automation.connector.${connector}`,
448
+ ),
449
+ ].sort(),
450
+ authorizationRefs: {
451
+ decisionId: null,
452
+ policyRevision: `automation-budget:${normalized.budget.revision}:${digest.slice(0, 16)}`,
453
+ grantIds: [],
454
+ approvalIds: [],
455
+ delegationIds: [],
456
+ },
457
+ };
458
+ }
459
+
460
+ function currentWindow(budget, now) {
461
+ return Math.floor(now / budget.windowMs) * budget.windowMs;
462
+ }
463
+
464
+ function usageRow(db, flowId, revision, windowStartedAtMs) {
465
+ const row = db
466
+ .prepare(
467
+ `SELECT runs, action_steps
468
+ FROM auto_execution_budget_usage
469
+ WHERE flow_id = ? AND budget_revision = ? AND window_started_at_ms = ?`,
470
+ )
471
+ .get(flowId, revision, windowStartedAtMs);
472
+ return {
473
+ runs: row ? nonnegativeInteger(row.runs, "budgetUsage.runs") : 0,
474
+ actionSteps: row
475
+ ? nonnegativeInteger(row.action_steps, "budgetUsage.actionSteps")
476
+ : 0,
477
+ };
478
+ }
479
+
480
+ function reservationUsage(db, flowId, revision, windowStartedAtMs) {
481
+ const row = db
482
+ .prepare(
483
+ `SELECT COUNT(*) AS runs, COALESCE(SUM(action_steps), 0) AS action_steps
484
+ FROM auto_execution_budget_reservations
485
+ WHERE flow_id = ? AND budget_revision = ? AND window_started_at_ms = ?`,
486
+ )
487
+ .get(flowId, revision, windowStartedAtMs);
488
+ return {
489
+ runs: nonnegativeInteger(row?.runs ?? 0, "budgetReservations.runs"),
490
+ actionSteps: nonnegativeInteger(
491
+ row?.action_steps ?? 0,
492
+ "budgetReservations.actionSteps",
493
+ ),
494
+ };
495
+ }
496
+
497
+ function assertUsageMatchesReservations(usage, reservations, flowId) {
498
+ if (
499
+ usage.runs !== reservations.runs ||
500
+ usage.actionSteps !== reservations.actionSteps
501
+ ) {
502
+ throw authorityError(
503
+ "AUTOMATION_EXECUTION_BUDGET_STATE_INVALID",
504
+ `automation budget usage does not match reservations: ${flowId}`,
505
+ { usage, reservations },
506
+ );
507
+ }
508
+ }
509
+
510
+ export function inspectAutomationExecutionAuthority(
511
+ db,
512
+ flow,
513
+ { now = Date.now } = {},
514
+ ) {
515
+ const snapshot = automationExecutionAuthoritySnapshot(db, flow);
516
+ const timestamp = normalizeEpochMs(Number(now()), "now");
517
+ const permissions = snapshot.requiredPermissions.map((permission) => ({
518
+ permission,
519
+ allowed: checkPermission(db, snapshot.principalId, permission, {
520
+ nowMs: timestamp,
521
+ }),
522
+ }));
523
+ const windowStartedAtMs = currentWindow(snapshot.budget, timestamp);
524
+ const usage = usageRow(
525
+ db,
526
+ snapshot.flowId,
527
+ snapshot.budget.revision,
528
+ windowStartedAtMs,
529
+ );
530
+ assertUsageMatchesReservations(
531
+ usage,
532
+ reservationUsage(
533
+ db,
534
+ snapshot.flowId,
535
+ snapshot.budget.revision,
536
+ windowStartedAtMs,
537
+ ),
538
+ snapshot.flowId,
539
+ );
540
+ return {
541
+ ready:
542
+ permissions.every((entry) => entry.allowed) &&
543
+ usage.runs + 1 <= snapshot.budget.maxRuns &&
544
+ usage.actionSteps + snapshot.actionSteps <=
545
+ snapshot.budget.maxActionSteps,
546
+ snapshot,
547
+ digest: automationExecutionAuthorityDigest(snapshot, flow),
548
+ permissions,
549
+ window: {
550
+ startedAtMs: windowStartedAtMs,
551
+ endsAtMs: windowStartedAtMs + snapshot.budget.windowMs,
552
+ usedRuns: usage.runs,
553
+ usedActionSteps: usage.actionSteps,
554
+ remainingRuns: Math.max(0, snapshot.budget.maxRuns - usage.runs),
555
+ remainingActionSteps: Math.max(
556
+ 0,
557
+ snapshot.budget.maxActionSteps - usage.actionSteps,
558
+ ),
559
+ },
560
+ };
561
+ }
562
+
563
+ function assertSameReservation(row, expected) {
564
+ const matches =
565
+ row.flow_id === expected.flowId &&
566
+ row.budget_revision === expected.budgetRevision &&
567
+ row.principal_id === expected.principalId &&
568
+ row.authority_digest === expected.authorityDigest &&
569
+ row.action_steps === expected.actionSteps;
570
+ if (!matches) {
571
+ throw authorityError(
572
+ "AUTOMATION_EXECUTION_RESERVATION_MISMATCH",
573
+ `automation budget reservation identity is mismatched: ${expected.occurrenceId}`,
574
+ );
575
+ }
576
+ }
577
+
578
+ export function reserveAutomationExecutionAuthority(
579
+ db,
580
+ flow,
581
+ occurrenceId,
582
+ snapshot,
583
+ snapshotDigest,
584
+ { now = Date.now } = {},
585
+ ) {
586
+ ensureAutomationExecutionAuthorityTables(db);
587
+ if (typeof db.transaction !== "function") {
588
+ throw authorityError(
589
+ "AUTOMATION_EXECUTION_TRANSACTION_REQUIRED",
590
+ "automation execution budget requires transactional database support",
591
+ );
592
+ }
593
+ const occurrence = normalizeIdentifier(occurrenceId, "occurrenceId");
594
+ const normalized = normalizeAutomationExecutionAuthoritySnapshot(
595
+ snapshot,
596
+ flow,
597
+ );
598
+ const expectedDigest = automationExecutionAuthorityDigest(normalized, flow);
599
+ if (
600
+ normalizeDigest(snapshotDigest, "executionAuthorityDigest") !==
601
+ expectedDigest
602
+ ) {
603
+ throw authorityError(
604
+ "AUTOMATION_EXECUTION_AUTHORITY_INVALID",
605
+ `automation execution authority digest is invalid: ${normalized.flowId}`,
606
+ );
607
+ }
608
+ const timestamp = normalizeEpochMs(Number(now()), "now");
609
+ const reserve = db.transaction(() => {
610
+ const current = automationExecutionAuthoritySnapshot(db, flow);
611
+ const currentDigest = automationExecutionAuthorityDigest(current, flow);
612
+ if (currentDigest !== expectedDigest) {
613
+ throw authorityError(
614
+ "AUTOMATION_EXECUTION_AUTHORITY_STALE",
615
+ `automation execution authority changed after enqueue: ${normalized.flowId}`,
616
+ );
617
+ }
618
+ const denied = normalized.requiredPermissions.filter(
619
+ (permission) =>
620
+ !checkPermission(db, normalized.principalId, permission, {
621
+ nowMs: timestamp,
622
+ }),
623
+ );
624
+ if (denied.length > 0) {
625
+ throw authorityError(
626
+ "AUTOMATION_EXECUTION_PERMISSION_DENIED",
627
+ `automation execution permission denied: ${normalized.flowId}`,
628
+ { principalId: normalized.principalId, permissions: denied },
629
+ );
630
+ }
631
+ const expected = {
632
+ occurrenceId: occurrence,
633
+ flowId: normalized.flowId,
634
+ budgetRevision: normalized.budget.revision,
635
+ principalId: normalized.principalId,
636
+ authorityDigest: expectedDigest,
637
+ actionSteps: normalized.actionSteps,
638
+ };
639
+ const existing = db
640
+ .prepare(
641
+ "SELECT * FROM auto_execution_budget_reservations WHERE occurrence_id = ?",
642
+ )
643
+ .get(occurrence);
644
+ if (existing) {
645
+ assertSameReservation(existing, expected);
646
+ const usage = usageRow(
647
+ db,
648
+ normalized.flowId,
649
+ normalized.budget.revision,
650
+ existing.window_started_at_ms,
651
+ );
652
+ assertUsageMatchesReservations(
653
+ usage,
654
+ reservationUsage(
655
+ db,
656
+ normalized.flowId,
657
+ normalized.budget.revision,
658
+ existing.window_started_at_ms,
659
+ ),
660
+ normalized.flowId,
661
+ );
662
+ return {
663
+ ...expected,
664
+ windowStartedAtMs: existing.window_started_at_ms,
665
+ deduplicated: true,
666
+ };
667
+ }
668
+ const windowStartedAtMs = currentWindow(normalized.budget, timestamp);
669
+ const usage = usageRow(
670
+ db,
671
+ normalized.flowId,
672
+ normalized.budget.revision,
673
+ windowStartedAtMs,
674
+ );
675
+ assertUsageMatchesReservations(
676
+ usage,
677
+ reservationUsage(
678
+ db,
679
+ normalized.flowId,
680
+ normalized.budget.revision,
681
+ windowStartedAtMs,
682
+ ),
683
+ normalized.flowId,
684
+ );
685
+ const nextRuns = usage.runs + 1;
686
+ const nextActionSteps = usage.actionSteps + normalized.actionSteps;
687
+ if (
688
+ nextRuns > normalized.budget.maxRuns ||
689
+ nextActionSteps > normalized.budget.maxActionSteps
690
+ ) {
691
+ throw authorityError(
692
+ "AUTOMATION_EXECUTION_BUDGET_EXHAUSTED",
693
+ `automation execution budget exhausted: ${normalized.flowId}`,
694
+ {
695
+ maxRuns: normalized.budget.maxRuns,
696
+ maxActionSteps: normalized.budget.maxActionSteps,
697
+ nextRuns,
698
+ nextActionSteps,
699
+ windowStartedAtMs,
700
+ },
701
+ );
702
+ }
703
+ db.prepare(
704
+ `INSERT INTO auto_execution_budget_usage
705
+ (flow_id, budget_revision, window_started_at_ms, runs, action_steps, updated_at_ms)
706
+ VALUES (?, ?, ?, ?, ?, ?)
707
+ ON CONFLICT(flow_id, budget_revision, window_started_at_ms) DO UPDATE SET
708
+ runs = excluded.runs,
709
+ action_steps = excluded.action_steps,
710
+ updated_at_ms = excluded.updated_at_ms`,
711
+ ).run(
712
+ normalized.flowId,
713
+ normalized.budget.revision,
714
+ windowStartedAtMs,
715
+ nextRuns,
716
+ nextActionSteps,
717
+ timestamp,
718
+ );
719
+ db.prepare(
720
+ `INSERT INTO auto_execution_budget_reservations
721
+ (occurrence_id, flow_id, budget_revision, window_started_at_ms, principal_id, authority_digest, action_steps, created_at_ms)
722
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
723
+ ).run(
724
+ occurrence,
725
+ normalized.flowId,
726
+ normalized.budget.revision,
727
+ windowStartedAtMs,
728
+ normalized.principalId,
729
+ expectedDigest,
730
+ normalized.actionSteps,
731
+ timestamp,
732
+ );
733
+ return { ...expected, windowStartedAtMs, deduplicated: false };
734
+ });
735
+ return reserve.immediate();
736
+ }