toolroll 0.5.0

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 (626) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1113 -0
  3. package/THIRD_PARTY_NOTICES.md +449 -0
  4. package/dist/accent-colors.d.ts +48 -0
  5. package/dist/accent-colors.js +122 -0
  6. package/dist/action-ledger.d.ts +26 -0
  7. package/dist/action-ledger.js +75 -0
  8. package/dist/agent-fence.d.ts +42 -0
  9. package/dist/agent-fence.js +183 -0
  10. package/dist/agentconfig.d.ts +216 -0
  11. package/dist/agentconfig.js +454 -0
  12. package/dist/api-tokens.d.ts +16 -0
  13. package/dist/api-tokens.js +27 -0
  14. package/dist/approval-policy.d.ts +69 -0
  15. package/dist/approval-policy.js +137 -0
  16. package/dist/approval-rules-ui.d.ts +25 -0
  17. package/dist/approval-rules-ui.js +39 -0
  18. package/dist/assignment-adapters.d.ts +180 -0
  19. package/dist/assignment-adapters.js +239 -0
  20. package/dist/assignment-brief.d.ts +71 -0
  21. package/dist/assignment-brief.js +129 -0
  22. package/dist/assignment-delivery.d.ts +56 -0
  23. package/dist/assignment-delivery.js +160 -0
  24. package/dist/assignment-presentation.d.ts +26 -0
  25. package/dist/assignment-presentation.js +80 -0
  26. package/dist/assignment-status.d.ts +62 -0
  27. package/dist/assignment-status.js +152 -0
  28. package/dist/assignment-ui.d.ts +71 -0
  29. package/dist/assignment-ui.js +103 -0
  30. package/dist/assignment.d.ts +222 -0
  31. package/dist/assignment.js +399 -0
  32. package/dist/attest.d.ts +56 -0
  33. package/dist/attest.js +153 -0
  34. package/dist/backend.d.ts +97 -0
  35. package/dist/backend.js +166 -0
  36. package/dist/backup-ui.d.ts +22 -0
  37. package/dist/backup-ui.js +59 -0
  38. package/dist/backup.d.ts +84 -0
  39. package/dist/backup.js +421 -0
  40. package/dist/beads.d.ts +34 -0
  41. package/dist/beads.js +135 -0
  42. package/dist/bin.d.ts +2 -0
  43. package/dist/bin.js +22 -0
  44. package/dist/board.d.ts +150 -0
  45. package/dist/board.js +210 -0
  46. package/dist/boot-identity.d.ts +63 -0
  47. package/dist/boot-identity.js +99 -0
  48. package/dist/browser/THIRD_PARTY_NOTICES.txt +2295 -0
  49. package/dist/browser/workspace.css +4 -0
  50. package/dist/browser/workspace.js +225 -0
  51. package/dist/browser-crew.d.ts +11 -0
  52. package/dist/browser-crew.js +50 -0
  53. package/dist/browser-shell.d.ts +8 -0
  54. package/dist/browser-shell.js +60 -0
  55. package/dist/browser-workspace.d.ts +780 -0
  56. package/dist/browser-workspace.js +47 -0
  57. package/dist/budget-alerts.d.ts +19 -0
  58. package/dist/budget-alerts.js +46 -0
  59. package/dist/builder.d.ts +370 -0
  60. package/dist/builder.js +3262 -0
  61. package/dist/capscan.d.ts +35 -0
  62. package/dist/capscan.js +113 -0
  63. package/dist/chat-acceptance.d.ts +71 -0
  64. package/dist/chat-acceptance.js +120 -0
  65. package/dist/chat-actions.d.ts +265 -0
  66. package/dist/chat-actions.js +1273 -0
  67. package/dist/chat-channel.d.ts +109 -0
  68. package/dist/chat-channel.js +652 -0
  69. package/dist/chat-continuity.d.ts +32 -0
  70. package/dist/chat-continuity.js +317 -0
  71. package/dist/chat-controls.d.ts +113 -0
  72. package/dist/chat-controls.js +53 -0
  73. package/dist/chat-delivery-state.d.ts +159 -0
  74. package/dist/chat-delivery-state.js +309 -0
  75. package/dist/chat-delivery.d.ts +38 -0
  76. package/dist/chat-delivery.js +693 -0
  77. package/dist/chat-display.d.ts +1 -0
  78. package/dist/chat-display.js +18 -0
  79. package/dist/chat-evidence.d.ts +123 -0
  80. package/dist/chat-evidence.js +207 -0
  81. package/dist/chat-flow.d.ts +44 -0
  82. package/dist/chat-flow.js +215 -0
  83. package/dist/chat-inbox.d.ts +57 -0
  84. package/dist/chat-inbox.js +123 -0
  85. package/dist/chat-polish.d.ts +16 -0
  86. package/dist/chat-polish.js +135 -0
  87. package/dist/chat-review.d.ts +46 -0
  88. package/dist/chat-review.js +98 -0
  89. package/dist/chat-rooms.d.ts +123 -0
  90. package/dist/chat-rooms.js +216 -0
  91. package/dist/chat-task-actions.d.ts +44 -0
  92. package/dist/chat-task-actions.js +79 -0
  93. package/dist/check-progress.d.ts +51 -0
  94. package/dist/check-progress.js +290 -0
  95. package/dist/child-database.d.ts +12 -0
  96. package/dist/child-database.js +30 -0
  97. package/dist/claim.d.ts +688 -0
  98. package/dist/claim.js +1740 -0
  99. package/dist/cli.d.ts +137 -0
  100. package/dist/cli.js +1461 -0
  101. package/dist/codex-limits.d.ts +15 -0
  102. package/dist/codex-limits.js +145 -0
  103. package/dist/coding-context.d.ts +55 -0
  104. package/dist/coding-context.js +92 -0
  105. package/dist/coding-handoff.d.ts +57 -0
  106. package/dist/coding-handoff.js +277 -0
  107. package/dist/coding-provider.d.ts +72 -0
  108. package/dist/coding-provider.js +424 -0
  109. package/dist/coding-shipping-ui.d.ts +4 -0
  110. package/dist/coding-shipping-ui.js +12 -0
  111. package/dist/coding-types.d.ts +62 -0
  112. package/dist/coding-types.js +1 -0
  113. package/dist/coding-ui.d.ts +17 -0
  114. package/dist/coding-ui.js +364 -0
  115. package/dist/coding-update.d.ts +15 -0
  116. package/dist/coding-update.js +149 -0
  117. package/dist/coding-workspace.d.ts +124 -0
  118. package/dist/coding-workspace.js +768 -0
  119. package/dist/container-state.d.ts +5 -0
  120. package/dist/container-state.js +62 -0
  121. package/dist/containment.d.ts +197 -0
  122. package/dist/containment.js +559 -0
  123. package/dist/contest.d.ts +318 -0
  124. package/dist/contest.js +753 -0
  125. package/dist/control-setup.d.ts +35 -0
  126. package/dist/control-setup.js +40 -0
  127. package/dist/control-ui.d.ts +29 -0
  128. package/dist/control-ui.js +53 -0
  129. package/dist/controller-service.d.ts +1 -0
  130. package/dist/controller-service.js +19 -0
  131. package/dist/controller-supervisor.d.ts +30 -0
  132. package/dist/controller-supervisor.js +104 -0
  133. package/dist/converse.d.ts +304 -0
  134. package/dist/converse.js +849 -0
  135. package/dist/coordinator-proposals.d.ts +29 -0
  136. package/dist/coordinator-proposals.js +137 -0
  137. package/dist/coordinator.d.ts +186 -0
  138. package/dist/coordinator.js +447 -0
  139. package/dist/credentials-ui.d.ts +25 -0
  140. package/dist/credentials-ui.js +48 -0
  141. package/dist/daemon.d.ts +209 -0
  142. package/dist/daemon.js +604 -0
  143. package/dist/decision.d.ts +120 -0
  144. package/dist/decision.js +388 -0
  145. package/dist/demo.d.ts +55 -0
  146. package/dist/demo.js +992 -0
  147. package/dist/desktop-access.d.ts +28 -0
  148. package/dist/desktop-access.js +88 -0
  149. package/dist/desktop-bundle.d.ts +16 -0
  150. package/dist/desktop-bundle.js +74 -0
  151. package/dist/desktop-host.d.ts +54 -0
  152. package/dist/desktop-host.js +508 -0
  153. package/dist/desktop-update-gate.d.ts +12 -0
  154. package/dist/desktop-update-gate.js +91 -0
  155. package/dist/desktop-update-recovery.d.ts +14 -0
  156. package/dist/desktop-update-recovery.js +243 -0
  157. package/dist/desktop-update.d.ts +110 -0
  158. package/dist/desktop-update.js +740 -0
  159. package/dist/discord-api.d.ts +20 -0
  160. package/dist/discord-api.js +138 -0
  161. package/dist/discord-chat.d.ts +16 -0
  162. package/dist/discord-chat.js +381 -0
  163. package/dist/discord-settings.d.ts +7 -0
  164. package/dist/discord-settings.js +75 -0
  165. package/dist/discord.d.ts +10 -0
  166. package/dist/discord.js +169 -0
  167. package/dist/discover.d.ts +75 -0
  168. package/dist/discover.js +150 -0
  169. package/dist/dispatch.d.ts +100 -0
  170. package/dist/dispatch.js +311 -0
  171. package/dist/dispose.d.ts +161 -0
  172. package/dist/dispose.js +803 -0
  173. package/dist/email-settings.d.ts +34 -0
  174. package/dist/email-settings.js +50 -0
  175. package/dist/envelope.d.ts +47 -0
  176. package/dist/envelope.js +105 -0
  177. package/dist/evidence-pack.d.ts +185 -0
  178. package/dist/evidence-pack.js +258 -0
  179. package/dist/evidence.d.ts +345 -0
  180. package/dist/evidence.js +782 -0
  181. package/dist/exec.d.ts +285 -0
  182. package/dist/exec.js +1841 -0
  183. package/dist/exhaustion.d.ts +85 -0
  184. package/dist/exhaustion.js +141 -0
  185. package/dist/export-ui.d.ts +4 -0
  186. package/dist/export-ui.js +17 -0
  187. package/dist/export.d.ts +48 -0
  188. package/dist/export.js +305 -0
  189. package/dist/flow-actions.d.ts +77 -0
  190. package/dist/flow-actions.js +203 -0
  191. package/dist/flow-code.d.ts +50 -0
  192. package/dist/flow-code.js +159 -0
  193. package/dist/flow-draft.d.ts +38 -0
  194. package/dist/flow-draft.js +80 -0
  195. package/dist/flow-engine.d.ts +66 -0
  196. package/dist/flow-engine.js +416 -0
  197. package/dist/flow-insights.d.ts +95 -0
  198. package/dist/flow-insights.js +149 -0
  199. package/dist/flow-live.d.ts +24 -0
  200. package/dist/flow-live.js +150 -0
  201. package/dist/flow-people.d.ts +24 -0
  202. package/dist/flow-people.js +91 -0
  203. package/dist/flow-replies.d.ts +48 -0
  204. package/dist/flow-replies.js +113 -0
  205. package/dist/flow-scripts.d.ts +38 -0
  206. package/dist/flow-scripts.js +73 -0
  207. package/dist/flow-secrets.d.ts +13 -0
  208. package/dist/flow-secrets.js +45 -0
  209. package/dist/flow-sort.d.ts +82 -0
  210. package/dist/flow-sort.js +153 -0
  211. package/dist/flow-steps.d.ts +40 -0
  212. package/dist/flow-steps.js +408 -0
  213. package/dist/flow-triggers.d.ts +244 -0
  214. package/dist/flow-triggers.js +959 -0
  215. package/dist/flows-ui.d.ts +20 -0
  216. package/dist/flows-ui.js +175 -0
  217. package/dist/flows.d.ts +229 -0
  218. package/dist/flows.js +968 -0
  219. package/dist/fonts.d.ts +19 -0
  220. package/dist/fonts.js +19 -0
  221. package/dist/gaps.d.ts +35 -0
  222. package/dist/gaps.js +101 -0
  223. package/dist/gate-failure.d.ts +40 -0
  224. package/dist/gate-failure.js +66 -0
  225. package/dist/git.d.ts +54 -0
  226. package/dist/git.js +94 -0
  227. package/dist/google-mail.d.ts +58 -0
  228. package/dist/google-mail.js +151 -0
  229. package/dist/grant.d.ts +136 -0
  230. package/dist/grant.js +238 -0
  231. package/dist/graph.d.ts +164 -0
  232. package/dist/graph.js +383 -0
  233. package/dist/guides.d.ts +22 -0
  234. package/dist/guides.js +363 -0
  235. package/dist/held.d.ts +150 -0
  236. package/dist/held.js +799 -0
  237. package/dist/invoke.d.ts +112 -0
  238. package/dist/invoke.js +780 -0
  239. package/dist/issues.d.ts +41 -0
  240. package/dist/issues.js +131 -0
  241. package/dist/job-object-helper.ps1 +252 -0
  242. package/dist/jsonl-discriminants.d.ts +11 -0
  243. package/dist/jsonl-discriminants.js +93 -0
  244. package/dist/keys.d.ts +104 -0
  245. package/dist/keys.js +229 -0
  246. package/dist/kits-ui.d.ts +17 -0
  247. package/dist/kits-ui.js +46 -0
  248. package/dist/kits.d.ts +86 -0
  249. package/dist/kits.js +215 -0
  250. package/dist/knowledge-cli.d.ts +101 -0
  251. package/dist/knowledge-cli.js +92 -0
  252. package/dist/knowledge-ui.d.ts +24 -0
  253. package/dist/knowledge-ui.js +40 -0
  254. package/dist/lead-context.d.ts +8 -0
  255. package/dist/lead-context.js +56 -0
  256. package/dist/lead-follow.d.ts +22 -0
  257. package/dist/lead-follow.js +167 -0
  258. package/dist/lead-status.d.ts +75 -0
  259. package/dist/lead-status.js +285 -0
  260. package/dist/ledger-chain.d.ts +46 -0
  261. package/dist/ledger-chain.js +187 -0
  262. package/dist/ledger-csv.d.ts +4 -0
  263. package/dist/ledger-csv.js +11 -0
  264. package/dist/ledger-view.d.ts +24 -0
  265. package/dist/ledger-view.js +58 -0
  266. package/dist/limits-ui.d.ts +26 -0
  267. package/dist/limits-ui.js +67 -0
  268. package/dist/link.d.ts +72 -0
  269. package/dist/link.js +217 -0
  270. package/dist/live.d.ts +96 -0
  271. package/dist/live.js +366 -0
  272. package/dist/liveness.d.ts +30 -0
  273. package/dist/liveness.js +42 -0
  274. package/dist/log.d.ts +7 -0
  275. package/dist/log.js +25 -0
  276. package/dist/mailbox.d.ts +57 -0
  277. package/dist/mailbox.js +150 -0
  278. package/dist/maintenance.d.ts +11 -0
  279. package/dist/maintenance.js +35 -0
  280. package/dist/mate-cli.d.ts +52 -0
  281. package/dist/mate-cli.js +345 -0
  282. package/dist/mate-contract.d.ts +10 -0
  283. package/dist/mate-contract.js +30 -0
  284. package/dist/mate-doors.d.ts +69 -0
  285. package/dist/mate-doors.js +548 -0
  286. package/dist/mate-progress.d.ts +29 -0
  287. package/dist/mate-progress.js +121 -0
  288. package/dist/mate-tools.d.ts +94 -0
  289. package/dist/mate-tools.js +1878 -0
  290. package/dist/mate.d.ts +92 -0
  291. package/dist/mate.js +435 -0
  292. package/dist/mcp-connect.d.ts +103 -0
  293. package/dist/mcp-connect.js +252 -0
  294. package/dist/mcp.d.ts +27 -0
  295. package/dist/mcp.js +651 -0
  296. package/dist/memory-cli.d.ts +466 -0
  297. package/dist/memory-cli.js +111 -0
  298. package/dist/memory-pass.d.ts +160 -0
  299. package/dist/memory-pass.js +409 -0
  300. package/dist/metrics.d.ts +3 -0
  301. package/dist/metrics.js +56 -0
  302. package/dist/mobile-viewport.d.ts +3 -0
  303. package/dist/mobile-viewport.js +37 -0
  304. package/dist/model-catalog.d.ts +118 -0
  305. package/dist/model-catalog.js +376 -0
  306. package/dist/models-cli.d.ts +102 -0
  307. package/dist/models-cli.js +66 -0
  308. package/dist/models-ui.d.ts +34 -0
  309. package/dist/models-ui.js +68 -0
  310. package/dist/modes.d.ts +80 -0
  311. package/dist/modes.js +182 -0
  312. package/dist/monitoring-settings.d.ts +44 -0
  313. package/dist/monitoring-settings.js +173 -0
  314. package/dist/monitoring-ui.d.ts +20 -0
  315. package/dist/monitoring-ui.js +51 -0
  316. package/dist/monitoring.d.ts +34 -0
  317. package/dist/monitoring.js +221 -0
  318. package/dist/names.d.ts +67 -0
  319. package/dist/names.js +102 -0
  320. package/dist/observations.d.ts +40 -0
  321. package/dist/observations.js +211 -0
  322. package/dist/oidc.d.ts +71 -0
  323. package/dist/oidc.js +182 -0
  324. package/dist/onboard.d.ts +108 -0
  325. package/dist/onboard.js +325 -0
  326. package/dist/openrouter-models.d.ts +30 -0
  327. package/dist/openrouter-models.js +120 -0
  328. package/dist/operate.d.ts +118 -0
  329. package/dist/operate.js +11321 -0
  330. package/dist/peek-cli.d.ts +97 -0
  331. package/dist/peek-cli.js +215 -0
  332. package/dist/peek.d.ts +134 -0
  333. package/dist/peek.js +578 -0
  334. package/dist/phase-routing.d.ts +307 -0
  335. package/dist/phase-routing.js +658 -0
  336. package/dist/plan-auto.d.ts +14 -0
  337. package/dist/plan-auto.js +92 -0
  338. package/dist/plan.d.ts +186 -0
  339. package/dist/plan.js +401 -0
  340. package/dist/planner-source.d.ts +251 -0
  341. package/dist/planner-source.js +460 -0
  342. package/dist/planner.d.ts +75 -0
  343. package/dist/planner.js +992 -0
  344. package/dist/policy-ui.d.ts +20 -0
  345. package/dist/policy-ui.js +41 -0
  346. package/dist/policy.d.ts +111 -0
  347. package/dist/policy.js +231 -0
  348. package/dist/prepared-evidence.d.ts +16 -0
  349. package/dist/prepared-evidence.js +96 -0
  350. package/dist/pricing.d.ts +45 -0
  351. package/dist/pricing.js +77 -0
  352. package/dist/principal.d.ts +42 -0
  353. package/dist/principal.js +82 -0
  354. package/dist/probe.d.ts +46 -0
  355. package/dist/probe.js +79 -0
  356. package/dist/process-custody.d.ts +17 -0
  357. package/dist/process-custody.js +91 -0
  358. package/dist/process-liveness.d.ts +10 -0
  359. package/dist/process-liveness.js +81 -0
  360. package/dist/process-recovery-anchor.d.ts +75 -0
  361. package/dist/process-recovery-anchor.js +188 -0
  362. package/dist/process-recovery-coalition.d.ts +25 -0
  363. package/dist/process-recovery-coalition.js +105 -0
  364. package/dist/process-recovery-eligibility.d.ts +178 -0
  365. package/dist/process-recovery-eligibility.js +170 -0
  366. package/dist/process-recovery-native.d.ts +172 -0
  367. package/dist/process-recovery-native.js +592 -0
  368. package/dist/process-recovery-provenance.d.ts +163 -0
  369. package/dist/process-recovery-provenance.js +292 -0
  370. package/dist/process-recovery-services.d.ts +56 -0
  371. package/dist/process-recovery-services.js +191 -0
  372. package/dist/process-recovery-settlement.d.ts +32 -0
  373. package/dist/process-recovery-settlement.js +132 -0
  374. package/dist/process-recovery.d.ts +41 -0
  375. package/dist/process-recovery.js +91 -0
  376. package/dist/process-tree.d.ts +41 -0
  377. package/dist/process-tree.js +264 -0
  378. package/dist/project-access.d.ts +14 -0
  379. package/dist/project-access.js +34 -0
  380. package/dist/project-cli.d.ts +18 -0
  381. package/dist/project-cli.js +104 -0
  382. package/dist/project-delete-ui.d.ts +22 -0
  383. package/dist/project-delete-ui.js +48 -0
  384. package/dist/project-delete.d.ts +55 -0
  385. package/dist/project-delete.js +413 -0
  386. package/dist/project-knowledge.d.ts +111 -0
  387. package/dist/project-knowledge.js +241 -0
  388. package/dist/project-learning.d.ts +100 -0
  389. package/dist/project-learning.js +438 -0
  390. package/dist/project-memory.d.ts +81 -0
  391. package/dist/project-memory.js +264 -0
  392. package/dist/project-skills.d.ts +152 -0
  393. package/dist/project-skills.js +660 -0
  394. package/dist/project-tools.d.ts +219 -0
  395. package/dist/project-tools.js +796 -0
  396. package/dist/project.d.ts +61 -0
  397. package/dist/project.js +125 -0
  398. package/dist/prompt.d.ts +22 -0
  399. package/dist/prompt.js +72 -0
  400. package/dist/proof.d.ts +513 -0
  401. package/dist/proof.js +1140 -0
  402. package/dist/proposal.d.ts +82 -0
  403. package/dist/proposal.js +210 -0
  404. package/dist/provider-connection.d.ts +22 -0
  405. package/dist/provider-connection.js +98 -0
  406. package/dist/provider-limits.d.ts +35 -0
  407. package/dist/provider-limits.js +95 -0
  408. package/dist/provider.d.ts +279 -0
  409. package/dist/provider.js +935 -0
  410. package/dist/publish.d.ts +107 -0
  411. package/dist/publish.js +650 -0
  412. package/dist/pulls.d.ts +118 -0
  413. package/dist/pulls.js +240 -0
  414. package/dist/push.d.ts +95 -0
  415. package/dist/push.js +353 -0
  416. package/dist/quality.d.ts +8 -0
  417. package/dist/quality.js +6 -0
  418. package/dist/recipe-ui.d.ts +18 -0
  419. package/dist/recipe-ui.js +152 -0
  420. package/dist/recipes.d.ts +81 -0
  421. package/dist/recipes.js +332 -0
  422. package/dist/remote.d.ts +67 -0
  423. package/dist/remote.js +122 -0
  424. package/dist/render.d.ts +64 -0
  425. package/dist/render.js +587 -0
  426. package/dist/report-summary.d.ts +13 -0
  427. package/dist/report-summary.js +15 -0
  428. package/dist/repos.d.ts +65 -0
  429. package/dist/repos.js +277 -0
  430. package/dist/repository-context-ui.d.ts +2 -0
  431. package/dist/repository-context-ui.js +10 -0
  432. package/dist/repository-context.d.ts +67 -0
  433. package/dist/repository-context.js +376 -0
  434. package/dist/restart-certification.d.ts +134 -0
  435. package/dist/restart-certification.js +226 -0
  436. package/dist/result-actions.d.ts +28 -0
  437. package/dist/result-actions.js +270 -0
  438. package/dist/result-completion.d.ts +18 -0
  439. package/dist/result-completion.js +66 -0
  440. package/dist/result-review.d.ts +213 -0
  441. package/dist/result-review.js +430 -0
  442. package/dist/retention-ui.d.ts +12 -0
  443. package/dist/retention-ui.js +39 -0
  444. package/dist/retention.d.ts +72 -0
  445. package/dist/retention.js +288 -0
  446. package/dist/review-context.d.ts +243 -0
  447. package/dist/review-context.js +1043 -0
  448. package/dist/review-evidence.d.ts +33 -0
  449. package/dist/review-evidence.js +56 -0
  450. package/dist/reviewer.d.ts +97 -0
  451. package/dist/reviewer.js +202 -0
  452. package/dist/routine.d.ts +228 -0
  453. package/dist/routine.js +764 -0
  454. package/dist/runner.d.ts +266 -0
  455. package/dist/runner.js +399 -0
  456. package/dist/scan.d.ts +32 -0
  457. package/dist/scan.js +92 -0
  458. package/dist/scope.d.ts +695 -0
  459. package/dist/scope.js +1176 -0
  460. package/dist/scout-report.d.ts +42 -0
  461. package/dist/scout-report.js +108 -0
  462. package/dist/scout.d.ts +69 -0
  463. package/dist/scout.js +351 -0
  464. package/dist/serve.d.ts +313 -0
  465. package/dist/serve.js +21776 -0
  466. package/dist/session-brief.d.ts +8 -0
  467. package/dist/session-brief.js +24 -0
  468. package/dist/session-cli.d.ts +13 -0
  469. package/dist/session-cli.js +284 -0
  470. package/dist/session-contract.d.ts +183 -0
  471. package/dist/session-contract.js +122 -0
  472. package/dist/session-http.d.ts +19 -0
  473. package/dist/session-http.js +154 -0
  474. package/dist/session-server.d.ts +12 -0
  475. package/dist/session-server.js +37 -0
  476. package/dist/session-service.d.ts +22 -0
  477. package/dist/session-service.js +157 -0
  478. package/dist/setup-guide.d.ts +39 -0
  479. package/dist/setup-guide.js +93 -0
  480. package/dist/sign-in-guard.d.ts +45 -0
  481. package/dist/sign-in-guard.js +74 -0
  482. package/dist/skills-ui.d.ts +11 -0
  483. package/dist/skills-ui.js +58 -0
  484. package/dist/skills.d.ts +97 -0
  485. package/dist/skills.js +276 -0
  486. package/dist/slack-api.d.ts +55 -0
  487. package/dist/slack-api.js +229 -0
  488. package/dist/slack-chat.d.ts +28 -0
  489. package/dist/slack-chat.js +413 -0
  490. package/dist/slack-settings.d.ts +7 -0
  491. package/dist/slack-settings.js +75 -0
  492. package/dist/slack-state.d.ts +13 -0
  493. package/dist/slack-state.js +9 -0
  494. package/dist/slack.d.ts +13 -0
  495. package/dist/slack.js +167 -0
  496. package/dist/spend-ui.d.ts +28 -0
  497. package/dist/spend-ui.js +95 -0
  498. package/dist/spend.d.ts +142 -0
  499. package/dist/spend.js +313 -0
  500. package/dist/sqlite-runtime.d.ts +3 -0
  501. package/dist/sqlite-runtime.js +6 -0
  502. package/dist/sso-settings.d.ts +33 -0
  503. package/dist/sso-settings.js +103 -0
  504. package/dist/sso-ui.d.ts +22 -0
  505. package/dist/sso-ui.js +39 -0
  506. package/dist/storage.d.ts +15 -0
  507. package/dist/storage.js +84 -0
  508. package/dist/store.d.ts +7754 -0
  509. package/dist/store.js +23279 -0
  510. package/dist/structured-output.d.ts +58 -0
  511. package/dist/structured-output.js +103 -0
  512. package/dist/style-asset.d.ts +7 -0
  513. package/dist/style-asset.js +41 -0
  514. package/dist/subscription-chat.d.ts +46 -0
  515. package/dist/subscription-chat.js +275 -0
  516. package/dist/summary.d.ts +33 -0
  517. package/dist/summary.js +71 -0
  518. package/dist/supervisor.mjs +297 -0
  519. package/dist/surface.d.ts +63 -0
  520. package/dist/surface.js +352 -0
  521. package/dist/sync.d.ts +94 -0
  522. package/dist/sync.js +279 -0
  523. package/dist/task-composer.d.ts +13 -0
  524. package/dist/task-composer.js +52 -0
  525. package/dist/task-control.d.ts +165 -0
  526. package/dist/task-control.js +214 -0
  527. package/dist/task-outcome-cli.d.ts +10 -0
  528. package/dist/task-outcome-cli.js +92 -0
  529. package/dist/task-text.d.ts +30 -0
  530. package/dist/task-text.js +41 -0
  531. package/dist/team-cli.d.ts +15 -0
  532. package/dist/team-cli.js +618 -0
  533. package/dist/team-contract.d.ts +92 -0
  534. package/dist/team-contract.js +1 -0
  535. package/dist/team-http.d.ts +17 -0
  536. package/dist/team-http.js +144 -0
  537. package/dist/team-leads.d.ts +81 -0
  538. package/dist/team-leads.js +565 -0
  539. package/dist/team-runtime.d.ts +27 -0
  540. package/dist/team-runtime.js +264 -0
  541. package/dist/team-ui.d.ts +3 -0
  542. package/dist/team-ui.js +9 -0
  543. package/dist/team-updates.d.ts +8 -0
  544. package/dist/team-updates.js +57 -0
  545. package/dist/teammate-admin.d.ts +53 -0
  546. package/dist/teammate-admin.js +111 -0
  547. package/dist/teammate-desk.d.ts +60 -0
  548. package/dist/teammate-desk.js +154 -0
  549. package/dist/teammate-memory.d.ts +64 -0
  550. package/dist/teammate-memory.js +196 -0
  551. package/dist/teammate-question.d.ts +59 -0
  552. package/dist/teammate-question.js +175 -0
  553. package/dist/teammate-tools.d.ts +108 -0
  554. package/dist/teammate-tools.js +271 -0
  555. package/dist/teammate-week.d.ts +68 -0
  556. package/dist/teammate-week.js +132 -0
  557. package/dist/teammate-work.d.ts +76 -0
  558. package/dist/teammate-work.js +280 -0
  559. package/dist/teammates-ui.d.ts +20 -0
  560. package/dist/teammates-ui.js +166 -0
  561. package/dist/teammates.d.ts +180 -0
  562. package/dist/teammates.js +253 -0
  563. package/dist/teams-api.d.ts +33 -0
  564. package/dist/teams-api.js +204 -0
  565. package/dist/teams-chat.d.ts +26 -0
  566. package/dist/teams-chat.js +215 -0
  567. package/dist/teams-settings.d.ts +12 -0
  568. package/dist/teams-settings.js +57 -0
  569. package/dist/teams.d.ts +21 -0
  570. package/dist/teams.js +124 -0
  571. package/dist/telegram-flow.d.ts +49 -0
  572. package/dist/telegram-flow.js +110 -0
  573. package/dist/telegram-mate.d.ts +137 -0
  574. package/dist/telegram-mate.js +707 -0
  575. package/dist/telegram-progress.d.ts +14 -0
  576. package/dist/telegram-progress.js +129 -0
  577. package/dist/telegram-settings.d.ts +9 -0
  578. package/dist/telegram-settings.js +36 -0
  579. package/dist/telegram-status.d.ts +75 -0
  580. package/dist/telegram-status.js +265 -0
  581. package/dist/telegram-team.d.ts +85 -0
  582. package/dist/telegram-team.js +359 -0
  583. package/dist/telegram.d.ts +230 -0
  584. package/dist/telegram.js +1931 -0
  585. package/dist/templates.d.ts +65 -0
  586. package/dist/templates.js +118 -0
  587. package/dist/tool-launcher.d.ts +1 -0
  588. package/dist/tool-launcher.js +101 -0
  589. package/dist/tools-ui.d.ts +30 -0
  590. package/dist/tools-ui.js +40 -0
  591. package/dist/transitions-recipes.d.ts +1 -0
  592. package/dist/transitions-recipes.js +203 -0
  593. package/dist/tree-proof.d.ts +34 -0
  594. package/dist/tree-proof.js +58 -0
  595. package/dist/verification-evidence.d.ts +44 -0
  596. package/dist/verification-evidence.js +238 -0
  597. package/dist/version.d.ts +2 -0
  598. package/dist/version.js +10 -0
  599. package/dist/webhooks.d.ts +91 -0
  600. package/dist/webhooks.js +283 -0
  601. package/dist/work-index.d.ts +68 -0
  602. package/dist/work-index.js +384 -0
  603. package/dist/work-summary.d.ts +57 -0
  604. package/dist/work-summary.js +116 -0
  605. package/dist/workspace-motion.d.ts +4 -0
  606. package/dist/workspace-motion.js +133 -0
  607. package/dist/workspace-revision.d.ts +19 -0
  608. package/dist/workspace-revision.js +92 -0
  609. package/dist/workspace-ui.d.ts +204 -0
  610. package/dist/workspace-ui.js +411 -0
  611. package/dist/worktree-notices.d.ts +13 -0
  612. package/dist/worktree-notices.js +17 -0
  613. package/dist/worktree.d.ts +249 -0
  614. package/dist/worktree.js +720 -0
  615. package/package.json +122 -0
  616. package/scripts/canary-assertions.mjs +42 -0
  617. package/scripts/crash-canary.mjs +253 -0
  618. package/scripts/fixtures/crash-process.mjs +74 -0
  619. package/scripts/fixtures/pilot-scenarios.mjs +85 -0
  620. package/scripts/fixtures/restart-service.mjs +27 -0
  621. package/scripts/launchd-certification.mjs +143 -0
  622. package/scripts/pilot.mjs +121 -0
  623. package/scripts/proof-preflight.mjs +140 -0
  624. package/scripts/provider-canary.mjs +379 -0
  625. package/scripts/recovery-canary.mjs +135 -0
  626. package/scripts/restart-certification.mjs +98 -0
@@ -0,0 +1,764 @@
1
+ /**
2
+ * Routines: standing orders that fire on a schedule (Phase C).
3
+ *
4
+ * A routine is a pre-approved template — goal, exclusions, touches,
5
+ * requirements, schedule, budget — whose approval is what makes every
6
+ * instance legitimately automatic. The template's digest covers EVERY term
7
+ * that constrains the standing order, not just the scope trio: a schedule
8
+ * moved from weekly to hourly, or a budget quietly removed, changes what the
9
+ * operator agreed to exactly as much as a rewritten goal would.
10
+ *
11
+ * The schedule algebra lives here, pure, because its two failure modes are
12
+ * subtle enough to deserve names (Codex planning/routines review, finding
13
+ * 10): advancing one interval after downtime leaves the next firing in the
14
+ * past — a catch-up burst; advancing to `now + interval` drifts the cadence
15
+ * forever. The rule is ALIGNED advancement: the next occurrence strictly
16
+ * after now, anchored to the occurrence that just fired (for `every`) or to
17
+ * the clock itself (for `daily`). Missed slots while nothing was running
18
+ * fire ONCE — the overdue occurrence — and are never backfilled.
19
+ */
20
+ import { createHash } from "node:crypto";
21
+ import { hasForbiddenControls } from "./decision.js";
22
+ import { BUILT_IN, parseCapabilityKey } from "./store.js";
23
+ import { authenticateApprover, digestOf, profileDigestOf, parseAcceptanceCriteria, canonicalAcceptance, acceptanceWords } from "./scope.js";
24
+ import { reportsCost } from "./provider.js";
25
+ import { agentsSummary, legOf, postureWords, routeDigestOf, routeProblems } from "./phase-routing.js";
26
+ import { resolveRoutineAuthority } from "./agentconfig.js";
27
+ export const WEEKDAYS = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
28
+ export function validTimezone(value) {
29
+ if (value.trim() !== value)
30
+ return false;
31
+ if (!/^[A-Za-z0-9_+/-]{1,80}$/.test(value))
32
+ return false;
33
+ try {
34
+ new Intl.DateTimeFormat("en", { timeZone: value }).format(0);
35
+ return true;
36
+ }
37
+ catch {
38
+ return false;
39
+ }
40
+ }
41
+ /** Bounds a person would pick on purpose: 5 minutes to 7 days. */
42
+ export const MIN_EVERY_MINUTES = 5;
43
+ export const MAX_EVERY_MINUTES = 7 * 24 * 60;
44
+ /**
45
+ * Existing daily schedules remain UTC. Calendar schedules may name an IANA
46
+ * timezone, whose wall-clock time stays fixed across daylight saving changes.
47
+ * No cron expressions in v1 — a schedule the operator cannot read at a
48
+ * glance is a schedule they cannot honestly approve.
49
+ */
50
+ export function parseSchedule(text) {
51
+ if (text.trim() !== text)
52
+ return null;
53
+ const every = /^every:([0-9]{1,5})$/.exec(text);
54
+ if (every !== null) {
55
+ const minutes = Number(every[1]);
56
+ if (minutes < MIN_EVERY_MINUTES || minutes > MAX_EVERY_MINUTES)
57
+ return null;
58
+ return { kind: "every", minutes };
59
+ }
60
+ const calendar = /^(daily|weekdays|weekly:[0-6]):([01][0-9]|2[0-3]):([0-5][0-9])(?:@([A-Za-z0-9_+/-]{1,80}))?$/.exec(text);
61
+ if (calendar !== null) {
62
+ const timezone = calendar[4];
63
+ if (timezone !== undefined && !validTimezone(timezone))
64
+ return null;
65
+ const time = { hhmm: `${calendar[2]}:${calendar[3]}`, ...(timezone === undefined ? {} : { timezone }) };
66
+ return calendar[1] === "daily" ? { kind: "daily", ...time } : calendar[1] === "weekdays" ? { kind: "weekdays", ...time } : { kind: "weekly", day: Number(calendar[1].slice(-1)), ...time };
67
+ }
68
+ return null;
69
+ }
70
+ export function scheduleText(schedule) {
71
+ return schedule.kind === "every" ? `every:${schedule.minutes}` : `${schedule.kind === "weekly" ? `weekly:${schedule.day}` : schedule.kind}:${schedule.hhmm}${schedule.timezone === undefined ? "" : `@${schedule.timezone}`}`;
72
+ }
73
+ /** The schedule, in words an operator agrees to. */
74
+ export function describeSchedule(schedule) {
75
+ if (schedule.kind !== "every")
76
+ return `${schedule.kind === "daily" ? "daily" : schedule.kind === "weekdays" ? "weekdays" : `every ${WEEKDAYS[schedule.day]}`} at ${schedule.hhmm} ${schedule.timezone ?? "UTC"}`;
77
+ const { minutes } = schedule;
78
+ if (minutes % (24 * 60) === 0)
79
+ return `every ${minutes / (24 * 60)} day(s)`;
80
+ if (minutes % 60 === 0)
81
+ return `every ${minutes / 60} hour(s)`;
82
+ return `every ${minutes} minutes`;
83
+ }
84
+ /**
85
+ * The first occurrence after approval: the schedule starts counting from the
86
+ * yes, because "approved at 14:07, every 60 minutes" firing instantly would
87
+ * spend before the approver's hand left the keyboard.
88
+ */
89
+ export function firstFireAt(schedule, now) {
90
+ if (schedule.kind === "every") {
91
+ return new Date(now.getTime() + schedule.minutes * 60_000).toISOString();
92
+ }
93
+ return nextCalendar(schedule, now);
94
+ }
95
+ /**
96
+ * Aligned advancement: the smallest cadence occurrence STRICTLY after now.
97
+ * `anchor` is the occurrence that just fired (or was just skipped) — the
98
+ * cadence grid grows from it, so a pass that ran late does not tilt every
99
+ * later firing by its lateness.
100
+ */
101
+ export function nextFireAt(schedule, anchorIso, now) {
102
+ if (schedule.kind !== "every")
103
+ return nextCalendar(schedule, now);
104
+ const interval = schedule.minutes * 60_000;
105
+ const anchor = new Date(anchorIso).getTime();
106
+ const elapsed = now.getTime() - anchor;
107
+ // Strictly after now: an occurrence landing exactly on now already fired.
108
+ const steps = Math.max(1, Math.floor(elapsed / interval) + 1);
109
+ return new Date(anchor + steps * interval).toISOString();
110
+ }
111
+ function nextCalendar(schedule, now) {
112
+ const format = new Intl.DateTimeFormat("en-US", { timeZone: schedule.timezone ?? "UTC", year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", hourCycle: "h23" });
113
+ // Represent wall-clock components as a UTC number solely for arithmetic.
114
+ const wall = (instant) => {
115
+ const parts = Object.fromEntries(format.formatToParts(instant).map(part => [part.type, part.value]));
116
+ return Date.UTC(Number(parts.year), Number(parts.month) - 1, Number(parts.day), Number(parts.hour), Number(parts.minute));
117
+ };
118
+ const localNow = new Date(wall(now.getTime()));
119
+ const [hour, minute] = schedule.hhmm.split(":").map(Number);
120
+ const today = Date.UTC(localNow.getUTCFullYear(), localNow.getUTCMonth(), localNow.getUTCDate(), hour, minute);
121
+ for (let days = 0; days <= 15; days++) {
122
+ const target = today + days * 86_400_000;
123
+ if (schedule.kind === "weekly" && new Date(target).getUTCDay() !== schedule.day)
124
+ continue;
125
+ if (schedule.kind === "weekdays" && (new Date(target).getUTCDay() === 0 || new Date(target).getUTCDay() === 6))
126
+ continue;
127
+ // Nearby dates expose both offsets around a daylight-saving transition.
128
+ // A missing clock time skips its slot. An ambiguous time uses the first
129
+ // occurrence only, even if the scheduler advances during the repeated hour.
130
+ const offsets = [...new Set([-36, 0, 36].map(hours => { const sample = target + hours * 3_600_000; return wall(sample) - sample; }))];
131
+ const matches = offsets.map(offset => target - offset).filter(instant => wall(instant) === target);
132
+ if (matches.length > 0) {
133
+ const first = Math.min(...matches);
134
+ if (first > now.getTime())
135
+ return new Date(first).toISOString();
136
+ }
137
+ }
138
+ throw new Error("No calendar occurrence within the next fifteen days");
139
+ }
140
+ /**
141
+ * The exact standing order an operator agreed to, as 128 bits. Same shape
142
+ * and length as a scope digest, and for the same reason: approval binds to
143
+ * this value, and editing any term strands the old yes.
144
+ */
145
+ export function routineDigestOf(terms, profile, route) {
146
+ return createHash("sha256")
147
+ .update(JSON.stringify({
148
+ repo: terms.repo,
149
+ goal: terms.goal.trim(),
150
+ outOfScope: terms.outOfScope?.trim() ?? null,
151
+ touches: [...terms.touches].sort(),
152
+ // v39: the same absent/[] equivalence a scope digest gives its
153
+ // rubric — a routine created before this migration, or one whose
154
+ // stored acceptance somehow reads back empty, digests unchanged.
155
+ ...(terms.acceptance.length === 0 ? {} : { acceptance: canonicalAcceptance(terms.acceptance) }),
156
+ requirements: [...terms.requirements].sort(),
157
+ schedule: terms.schedule,
158
+ singleFlight: terms.singleFlight,
159
+ costCeilingUsd: terms.costCeilingUsd,
160
+ ...(terms.budgetPerRunMicrousd == null ? {} : { budgetPerRun: terms.budgetPerRunMicrousd }),
161
+ // v24: absent and null identical — legacy routine digests keep
162
+ // their bytes; profile-bearing routines bind their routing.
163
+ ...(profile == null ? {} : { profileDigest: profileDigestOf(profile) }),
164
+ // v48: the four-role agent route, under its own key — absent on a
165
+ // routine filed before routing froze (its digest bytes stand; it
166
+ // can no longer be approved or fired until filed again).
167
+ ...(route == null ? {} : { route: routeDigestOf(route) }),
168
+ }), "utf8")
169
+ .digest("hex")
170
+ .slice(0, 32);
171
+ }
172
+ /** Instance ids are `<name>-YYYYMMDD-HHMM`, so the name must be an id stem. */
173
+ export const ROUTINE_NAME = /^[a-z0-9][a-z0-9-]{0,40}$/;
174
+ /**
175
+ * Fail closed on every field at once, like plan parsing: an operator fixing
176
+ * a routine definition should learn everything wrong in one round.
177
+ */
178
+ export function validateRoutineTerms(terms, options = {}) {
179
+ const problems = [];
180
+ if (options.name !== undefined && !ROUTINE_NAME.test(options.name)) {
181
+ problems.push({ field: "name", problem: "a lowercase id stem — letters, digits, and dashes, at most 41 characters" });
182
+ }
183
+ if (terms.goal.trim() === "" || terms.goal.length > 2_000 || hasForbiddenControls(terms.goal)) {
184
+ problems.push({ field: "goal", problem: "required, at most 2000 characters, no control characters" });
185
+ }
186
+ if (terms.outOfScope !== null && (terms.outOfScope.length > 2_000 || hasForbiddenControls(terms.outOfScope))) {
187
+ problems.push({ field: "outOfScope", problem: "at most 2000 characters, no control characters" });
188
+ }
189
+ if (terms.touches.length > 50 || terms.touches.some(one => one.trim() === "" || one.length > 200 || hasForbiddenControls(one))) {
190
+ problems.push({ field: "touches", problem: "at most 50 paths, each non-empty and under 200 characters" });
191
+ }
192
+ if (terms.requirements.length > 20 || terms.requirements.some(one => parseCapabilityKey(one) === null)) {
193
+ problems.push({ field: "requirements", problem: "capability keys, `kind:name`, at most 20" });
194
+ }
195
+ // v39: this is the ONLY place a routine's rubric is authored — every
196
+ // firing copies it forward unchanged, never re-asks. A track that never
197
+ // gains one never gets past this validation — filed, migrated, or
198
+ // corrupted alike (final authority closure): an empty rubric is invalid
199
+ // wherever a stored row is read back, so a routine that predates
200
+ // rubrics (a migrated NULL acceptance_json) cannot refresh, take a yes,
201
+ // or fire `acceptance: []` until valid terms are filed again.
202
+ const acceptanceParse = parseAcceptanceCriteria(terms.acceptance);
203
+ if (acceptanceParse.problems.length > 0) {
204
+ problems.push({ field: "acceptance", problem: acceptanceParse.problems.map(p => p.message).join("; ") });
205
+ }
206
+ else if (acceptanceParse.criteria.length === 0) {
207
+ problems.push({ field: "acceptance", problem: "a standing order needs at least one signed acceptance criterion" });
208
+ }
209
+ if (parseSchedule(terms.schedule) === null) {
210
+ problems.push({
211
+ field: "schedule",
212
+ problem: `\`every:<minutes>\` (${MIN_EVERY_MINUTES}–${MAX_EVERY_MINUTES}), \`daily:<HH:MM>\`, or \`weekly:<0–6>:<HH:MM>\`, with an optional @IANA/timezone`,
213
+ });
214
+ }
215
+ if (terms.costCeilingUsd !== null && (!Number.isFinite(terms.costCeilingUsd) || terms.costCeilingUsd <= 0)) {
216
+ problems.push({ field: "costCeilingUsd", problem: "a positive dollar amount, or absent for no ceiling" });
217
+ }
218
+ if (terms.budgetPerRunMicrousd != null && (!Number.isSafeInteger(terms.budgetPerRunMicrousd) || terms.budgetPerRunMicrousd <= 0)) {
219
+ problems.push({ field: "budgetPerRunMicrousd", problem: "a positive whole micro-dollar amount, or absent for no per-run cap" });
220
+ }
221
+ // v1 is one-at-a-time, period: every approval surface SAYS so, and a
222
+ // stored false would make the ceremony describe behavior the firing does
223
+ // not have (Codex Phase C review, M4). The column stays for a future that
224
+ // designs the concurrent case; until then it is not accepted.
225
+ if (terms.singleFlight !== true) {
226
+ problems.push({ field: "singleFlight", problem: "v1 routines run one instance at a time — singleFlight must be true" });
227
+ }
228
+ return problems;
229
+ }
230
+ /** The stored row's terms, for re-proving the digest against what is actually there. */
231
+ export function termsOf(routine) {
232
+ return {
233
+ repo: routine.repo,
234
+ goal: routine.goal,
235
+ outOfScope: routine.outOfScope,
236
+ touches: routine.touches,
237
+ acceptance: routine.acceptance,
238
+ requirements: routine.requirements,
239
+ schedule: routine.schedule,
240
+ singleFlight: routine.singleFlight,
241
+ costCeilingUsd: routine.costCeilingUsd,
242
+ budgetPerRunMicrousd: routine.budgetPerRunMicrousd,
243
+ };
244
+ }
245
+ /** The instance task id: the routine's name stamped with its scheduled slot. */
246
+ export function instanceId(name, scheduledForIso) {
247
+ const stamp = scheduledForIso.replace(/[-:]/g, "").slice(0, 13).replace("T", "-");
248
+ return `${name}-${stamp}`;
249
+ }
250
+ /** The rolling budget window: seven days, and every surface says so. */
251
+ export const BUDGET_WINDOW_MS = 7 * 24 * 60 * 60_000;
252
+ /**
253
+ * A person agrees to the standing order — schedule, budget, and "each
254
+ * firing builds without asking" included, because the digest binds them.
255
+ * Same ceremony as a scope approval: credential re-proved inside the same
256
+ * transaction as the write, digest named rather than assumed.
257
+ */
258
+ export function approveRoutine(store, routineId, by, now, sawDigest, token) {
259
+ return store.transact(() => {
260
+ const authenticated = authenticateApprover(store, by, token, store.getRoutine(routineId)?.repo ?? null);
261
+ if (!authenticated.ok)
262
+ return authenticated;
263
+ const routine = store.getRoutine(routineId);
264
+ if (routine === null)
265
+ return { ok: false, reason: "no-such-routine" };
266
+ if (sawDigest !== routine.digest)
267
+ return { ok: false, reason: "changed" };
268
+ // ONE projection gates the yes (v48 authority repair): a routine that cannot say exactly
269
+ // what would run is unapprovable (v24, same rule as scopes) —
270
+ // restatement is the road. Since v48 that means all four roles: a
271
+ // routine with no frozen route (filed before routing froze, or under a
272
+ // configuration that could not make one exact) cannot be agreed to
273
+ // until it is filed again. The digest is re-derived from the stored
274
+ // terms inside the projection, never trusted as a column (Codex Phase
275
+ // C review, H1): a row whose digest does not match its own terms is
276
+ // not something a person can meaningfully agree to.
277
+ const integrity = routineIntegrity(routine);
278
+ if (!integrity.agents.approvable) {
279
+ return { ok: false, reason: integrity.agents.state === "unverified" ? "changed" : "profile-unresolved" };
280
+ }
281
+ const schedule = parseSchedule(routine.schedule);
282
+ if (schedule === null)
283
+ return { ok: false, reason: "changed" };
284
+ // v102: a project that refuses the requester refuses the standing order's maker too (each firing is their task).
285
+ if (store.approvalRules(routine.repo).notRequester && routine.createdBy !== null && routine.createdBy === by) {
286
+ return { ok: false, reason: "requester" };
287
+ }
288
+ // The schedule starts counting from the yes — never retroactively.
289
+ store.stampRoutineApproval(routineId, by, routine.digest, firstFireAt(schedule, now), now);
290
+ return { ok: true, routine: store.getRoutine(routineId) };
291
+ });
292
+ }
293
+ /**
294
+ * Fire one routine — or record, durably, why it did not.
295
+ *
296
+ * One store-owned transaction re-proves EVERYTHING before any write (Codex
297
+ * planning/routines review, finding 4): approval-digest equality, unpaused,
298
+ * due, single-flight, and budget are read inside the same BEGIN IMMEDIATE
299
+ * that creates the instance, so a template edited between an eligibility
300
+ * scan and this call simply refuses — the scan only nominates.
301
+ *
302
+ * The instance's scope is copied from the row proven approved HERE, and its
303
+ * approval identity is the template's: approvedBy/At come from the template
304
+ * stamp, and approvedDigest is the digest of the very terms copied in this
305
+ * transaction. No caller supplies approval fields; there is nothing to
306
+ * manufacture.
307
+ *
308
+ * A scheduled slot that cannot fire is SKIPPED ON THE LEDGER and the
309
+ * schedule advances past it (finding 10 — no backfill, no silent gap);
310
+ * blocked tracks page once per blocking fact (finding 9), and the episode
311
+ * resolves when a later firing succeeds. A manual run-now is different on
312
+ * exactly those points: it refuses to the person's face, records nothing,
313
+ * advances nothing, and pages nobody.
314
+ */
315
+ /** A firing that must not stand: thrown INSIDE the fire transaction so
316
+ * every row it wrote rolls back, caught at the edge and returned as the
317
+ * outcome it names. Nothing half-fired survives. */
318
+ class FiringRolledBack extends Error {
319
+ outcome;
320
+ page;
321
+ constructor(outcome, page) {
322
+ super(outcome.detail ?? outcome.reason);
323
+ this.outcome = outcome;
324
+ this.page = page;
325
+ }
326
+ }
327
+ export function fireRoutine(store, routineId, now, options = {}) {
328
+ const manual = options.manual === true;
329
+ try {
330
+ return fireRoutineInTransaction(store, routineId, now, manual);
331
+ }
332
+ catch (error) {
333
+ if (!(error instanceof FiringRolledBack))
334
+ throw error;
335
+ // The instance is gone with the transaction; the slot stays due. A
336
+ // scheduled firing pages once per blocking fact (its episode keys on
337
+ // the routine, so a later successful firing resolves it).
338
+ if (!manual && error.page !== null) {
339
+ store.enqueueRoutineEpisode(`routine-route:${routineId}`, { source: { project: store.getRoutine(routineId)?.repo ?? "" }, kind: "routine-blocked", pushClass: "attention", link: `/routines/${routineId}`, subject: error.page.subject, body: error.page.body }, now.toISOString(), now);
340
+ }
341
+ return error.outcome;
342
+ }
343
+ }
344
+ function fireRoutineInTransaction(store, routineId, now, manual) {
345
+ return store.transact(() => {
346
+ const routine = store.getRoutine(routineId);
347
+ if (routine === null)
348
+ return { ok: false, reason: "no-such-routine" };
349
+ // Approval is proved THREE ways: a stamp exists, it matches the stored
350
+ // digest, and — because a digest column can be written by any store
351
+ // caller — the digest is re-derived from the stored terms themselves
352
+ // (Codex Phase C review, H1). Terms edited under a reused digest fire
353
+ // nothing, whatever the columns claim. ONE projection answers (v48 authority repair),
354
+ // read here before anything is written.
355
+ const integrity = routineIntegrity(routine);
356
+ if (!integrity.approved) {
357
+ // Not approved is a plain read: nothing is written, nothing is
358
+ // paged — terms that cannot be read exactly (raw authority repair)
359
+ // refuse here too, leaving the routine, its slots, ledger, tasks,
360
+ // notifications, and next-fire time exactly as they were.
361
+ return {
362
+ ok: false,
363
+ reason: "not-approved",
364
+ detail: routine.termsProblem != null
365
+ ? `the stored terms cannot be read exactly (${routine.termsProblem}) — file this standing order again`
366
+ : integrity.agents.state === "unverified" && integrity.agents.problem !== null && integrity.agents.problem.startsWith("the stored terms are not ones this code files")
367
+ ? integrity.agents.problem
368
+ : routine.approvedAt === null
369
+ ? "nobody has agreed to this standing order"
370
+ : "the template was edited after approval — approve it again",
371
+ };
372
+ }
373
+ if (routine.paused)
374
+ return { ok: false, reason: "paused" };
375
+ const schedule = parseSchedule(routine.schedule);
376
+ if (schedule === null)
377
+ return { ok: false, reason: "bad-schedule" };
378
+ const occurredAt = manual ? now.toISOString() : routine.nextFireAt;
379
+ if (occurredAt === null || (!manual && occurredAt > now.toISOString())) {
380
+ return { ok: false, reason: "not-due" };
381
+ }
382
+ // THE FROZEN ROUTE (v48): the instance runs on exactly the four agents
383
+ // the approval sealed — planner, builder, repair, reviewer — copied
384
+ // from the approved snapshot, never recommended afresh. A routine whose
385
+ // approval predates routing (no sealed route) fires nothing: the slot
386
+ // stays due, the operator is paged once, and approving the standing
387
+ // order again — under agents it now names — is the road. No later
388
+ // `config set` can reach a firing.
389
+ // THE FROZEN SNAPSHOT IS RE-HASHED (v48, authority repair) by the same projection: the
390
+ // approved profile and the approved four-role route must read back,
391
+ // hash with the stored terms to the very digest the approver signed —
392
+ // a snapshot column rewritten after the yes fires nothing — carry no
393
+ // stated leg problem, agree with the sealed profile's build and
394
+ // repair pairs, and BE the working pair. Anything short of live
395
+ // refuses HERE — before the slot is read, before a stale pointer is
396
+ // healed, before a blocker is ledgered, before any instance, page, or
397
+ // next-fire time is written (v48 integrity): the refusal rides out of
398
+ // the transaction as a rollback, so the only thing a not-live firing
399
+ // leaves behind is the once-per-episode page at the edge. Two facts
400
+ // are said apart on the page: a snapshot never taken (approved before
401
+ // routing froze) and one whose bytes cannot be read (corrupt). The
402
+ // road is the same — refresh the agents, read them, approve again.
403
+ if (!integrity.live) {
404
+ const state = integrity.agents.state;
405
+ const subject = state === "unreadable"
406
+ ? `${routine.name} needs filing again: its frozen agents cannot be read`
407
+ : state === "unfrozen"
408
+ ? `${routine.name} needs approving again: its agents were never frozen`
409
+ : `${routine.name} needs approving again: its frozen agents do not verify`;
410
+ const body = state === "unreadable"
411
+ ? `The agents this standing order's approval froze cannot be read back. Open it, refresh its agents from today's configuration, read them, and approve it again; until then its firings wait.`
412
+ : state === "unfrozen"
413
+ ? `This standing order was approved before Toolroll froze which agents plan, build, repair, and review each firing. Open it, refresh its agents, read the agents it now names, and approve it again; until then its firings wait.`
414
+ : `The agents this standing order's approval froze do not verify (${integrity.liveProblem ?? "the frozen snapshot is not whole"}). Open it, refresh its agents from today's configuration, read them, and approve it again; until then its firings wait.`;
415
+ // A snapshot never taken (approved before routing froze) is a real
416
+ // state a person must act on, and it pages once at the edge. A
417
+ // snapshot whose bytes cannot be read or do not verify is CORRUPTION
418
+ // (atomic authority closure): scheduled or manual, it writes nothing
419
+ // at all — not a page — and the routine page says so in its words.
420
+ throw new FiringRolledBack({
421
+ ok: false,
422
+ reason: "route-unfrozen",
423
+ detail: state === "unreadable"
424
+ ? "the agents this standing order's approval froze cannot be read — refresh its agents and approve it again to fire it"
425
+ : state === "unfrozen"
426
+ ? "this standing order was approved before its agents were frozen — refresh its agents and approve it again to fire it"
427
+ : `${integrity.liveProblem ?? "the approved agents do not verify"} — refresh its agents and approve it again to fire it`,
428
+ }, state === "unfrozen" ? { subject, body } : null);
429
+ }
430
+ const frozenRoute = routine.approvedRoute;
431
+ const frozenProfile = routine.approvedProfile;
432
+ // A manual firing has its own ledger identity: it must never claim a
433
+ // scheduled slot's key, or a run-now landing exactly on the due instant
434
+ // would strand the schedule on that slot forever (Codex review, M1).
435
+ const slotKey = manual ? `manual:${occurredAt}` : occurredAt;
436
+ const scheduledFor = occurredAt;
437
+ // The slot check is a plain read because the whole function is one
438
+ // IMMEDIATE transaction — two passes finding the same due slot queue on
439
+ // the lock, and the second sees the first's row.
440
+ if (!store.routineSlotOpen(routineId, slotKey)) {
441
+ // A scheduled pointer aimed at an already-recorded slot is stale —
442
+ // heal it by advancing past the slot, or this refusal repeats forever.
443
+ if (!manual) {
444
+ store.setRoutineNextFire(routineId, nextFireAt(schedule, occurredAt, now), now);
445
+ }
446
+ return { ok: false, reason: "slot-taken" };
447
+ }
448
+ /** A due slot that cannot fire: ledger the skip, advance, page once. */
449
+ const skip = (reason, ledgerReason, page, detail) => {
450
+ if (manual)
451
+ return { ok: false, reason, detail };
452
+ store.recordRoutineFire({ routineId, scheduledFor: slotKey, outcome: "skipped", reason: ledgerReason, instanceTaskRef: null }, now);
453
+ store.setRoutineNextFire(routineId, nextFireAt(schedule, scheduledFor, now), now);
454
+ if (page !== null) {
455
+ // One page per blocking EPISODE: an open episode nags nobody twice,
456
+ // and a resolved one is history that must not suppress the next
457
+ // recurrence (Codex review, L1) — the slot stamp keys each afresh.
458
+ store.enqueueRoutineEpisode(page.prefix, { source: { project: routine.repo }, kind: "routine-blocked", subject: page.subject, body: page.body, pushClass: "attention", link: `/routines/${routineId}` }, scheduledFor, now);
459
+ }
460
+ return { ok: false, reason, detail };
461
+ };
462
+ // Single-flight is unconditional in v1 (Codex review, M4) — and a live
463
+ // claim blocks REGARDLESS of task state, so a state string written over
464
+ // a running build cannot conjure a twin beside it (H2).
465
+ const blocker = store.routineBlocker(routineId, now);
466
+ if (blocker !== null) {
467
+ return skip("single-flight", `single-flight:${blocker.taskId}`, {
468
+ prefix: `routine-singleflight:${routineId}:${blocker.taskId}`,
469
+ subject: `${routine.name} has stopped: ${blocker.taskId} is stuck`,
470
+ body: `The ${routine.name} track skipped its scheduled run because its last instance (${blocker.taskId}, ${blocker.state}) has not finished. The track stays stopped until that instance completes or is cancelled.`,
471
+ }, `instance ${blocker.taskId} (${blocker.state}) has not finished`);
472
+ }
473
+ const frozenBuild = legOf(frozenRoute, "build");
474
+ // The instance's agent IS the frozen build leg — never resolved from
475
+ // flags or configuration at fire time (Codex provider review, critical
476
+ // finding; v48 makes the whole route the pin).
477
+ const agentProvider = frozenBuild.provider;
478
+ // A ceiling against a provider that reports no dollars would fail
479
+ // closed on the SECOND firing anyway — refuse the first, say why, and
480
+ // skip the slot honestly (Codex provider review, Q6: re-proved at
481
+ // dispatch, not only at approval).
482
+ if (routine.costCeilingUsd !== null && !reportsCost(agentProvider)) {
483
+ return skip("unmeasured", `unmeasured-provider:${agentProvider}`, {
484
+ prefix: `routine-unmeasured:${routineId}`,
485
+ subject: `${routine.name} is blocked: ${agentProvider} cannot honor a cost ceiling`,
486
+ body: `The track's $${routine.costCeilingUsd.toFixed(2)} ceiling needs a provider that reports dollar cost, and ${agentProvider} does not. Drop the ceiling, or run the track on claude.`,
487
+ }, `${agentProvider} reports no dollar cost against a $${routine.costCeilingUsd.toFixed(2)} ceiling`);
488
+ }
489
+ if (routine.costCeilingUsd !== null) {
490
+ const spend = store.routineSpend(routineId, new Date(now.getTime() - BUDGET_WINDOW_MS).toISOString());
491
+ // Fail closed (finding 5): a paid run whose cost never landed is not
492
+ // headroom — it is an unknown liability, and it blocks the track.
493
+ if (spend.unmeasuredRuns > 0) {
494
+ return skip("unmeasured", "unmeasured", {
495
+ prefix: `routine-unmeasured:${routineId}`,
496
+ subject: `${routine.name} is blocked: spend is unmeasured`,
497
+ body: `${spend.unmeasuredRuns} paid run(s) in the last 7 days recorded no cost, so the $${routine.costCeilingUsd.toFixed(2)} ceiling cannot be honestly enforced. The track skips its firings until the window rolls past them.`,
498
+ }, `${spend.unmeasuredRuns} paid run(s) with no recorded cost`);
499
+ }
500
+ if (spend.costUsd >= routine.costCeilingUsd) {
501
+ return skip("budget", "budget", {
502
+ prefix: `routine-budget:${routineId}`,
503
+ subject: `${routine.name} hit its budget`,
504
+ body: `Instances spent $${spend.costUsd.toFixed(2)} of the $${routine.costCeilingUsd.toFixed(2)} ceiling in the last 7 days. Firings skip until the window rolls; raise the ceiling to resume sooner (that edit voids the approval, on purpose).`,
505
+ }, `$${spend.costUsd.toFixed(2)} spent of $${routine.costCeilingUsd.toFixed(2)}`);
506
+ }
507
+ }
508
+ // All proofs held: spawn the instance. Uniquified inside this same
509
+ // transaction; the ledger row makes a same-slot twin impossible anyway.
510
+ let taskId = instanceId(routine.name, scheduledFor);
511
+ for (let n = 2; store.getTask(taskId) !== null; n++) {
512
+ taskId = `${instanceId(routine.name, scheduledFor)}-${n}`;
513
+ }
514
+ const title = `${routine.name} · ${scheduledFor.slice(0, 16).replace("T", " ")} UTC`;
515
+ store.createTask({ id: taskId, title, filedBy: routine.createdBy === null ? { name: null, kind: "automation" } : { name: routine.createdBy, kind: "person" } }, now);
516
+ const ref = store.refFor(BUILT_IN, taskId, "ours");
517
+ // Placement BEFORE the scope exists — placeTask refuses to move scoped
518
+ // work, and this ordering is what keeps that guard out of the way here.
519
+ store.placeTask(ref.id, routine.repo);
520
+ store.linkRoutineInstance(ref.id, routine.id);
521
+ // The pin: this instance builds on exactly this agent, whatever flags a
522
+ // later pass carries — the routine's APPROVED profile when one was
523
+ // sealed (v47: the pin and the profile are one exact authority), else
524
+ // the resolved agent.
525
+ store.pinTaskAgent(ref.id, frozenProfile.provider, frozenProfile.model);
526
+ if (routine.requirements.length > 0) {
527
+ store.setRequirements(ref.id, routine.requirements);
528
+ }
529
+ // The instance scope: the template's terms, byte for byte, stamped
530
+ // approved by the person who approved the template (finding 4). The
531
+ // digest is computed from the copied terms IN this transaction, so the
532
+ // approval can only ever cover exactly what was proven approved above.
533
+ const draft = {
534
+ goal: routine.goal,
535
+ outOfScope: routine.outOfScope,
536
+ touches: [...routine.touches],
537
+ acceptance: [...routine.acceptance],
538
+ ...(routine.budgetPerRunMicrousd == null ? {} : { budgetMicrousd: routine.budgetPerRunMicrousd }),
539
+ };
540
+ // v24 (foundations 3d): the instance's profile is the routine's
541
+ // APPROVED snapshot — never fresh resolution. The instance digest
542
+ // binds it, the approval stamp covers it, and saveScope is handed the
543
+ // profile EXPLICITLY so its own resolution never runs here.
544
+ // v48: the frozen ROUTE rides beside it, verbatim — saveScope files it
545
+ // instead of recommending one from today's configuration, and the
546
+ // instance digest binds both, so the seal below covers exactly the
547
+ // four agents the routine's approver agreed to.
548
+ const instanceProfile = frozenProfile;
549
+ const instanceDigest = digestOf(draft, instanceProfile, frozenRoute);
550
+ store.saveScope({
551
+ taskId,
552
+ ...draft,
553
+ proposedAt: now.toISOString(),
554
+ digest: instanceDigest,
555
+ budgetMicrousd: routine.budgetPerRunMicrousd ?? null,
556
+ approvedAt: routine.approvedAt,
557
+ approvedBy: routine.approvedBy,
558
+ approvedDigest: instanceDigest,
559
+ }, {}, { profile: instanceProfile, route: frozenRoute });
560
+ // THE SEAL, OR NOTHING (v48): the instance's approval is real only once
561
+ // the seal copies the frozen route and profile into the approved
562
+ // snapshot and that snapshot reads back as the sealed route. A seal the
563
+ // store refuses (an unreadable route, a profile that disagrees) rolls
564
+ // the whole firing back — no instance, no ledger row, no advanced slot
565
+ // — and says why.
566
+ // v102: a firing is sealed by the standing order's approval, not a person voting now — protected work waits for two people.
567
+ const sealed = store.sealScopeApproval(taskId, routine.approvedBy ?? "routine", now, {}, undefined, "automation");
568
+ const sealedRoute = sealed ? store.sealedRouteOf(taskId) : null;
569
+ if (!sealed || sealedRoute === null || !sealedRoute.ok || routeDigestOf(sealedRoute.route) !== routeDigestOf(frozenRoute)) {
570
+ const why = !sealed ? "the store refused to seal the instance's approval" : sealedRoute !== null && !sealedRoute.ok ? sealedRoute.detail : "the sealed route is not the frozen route";
571
+ throw new FiringRolledBack({ ok: false, reason: "route-unfrozen", detail: `the instance could not be sealed under the frozen agents (${why}) — nothing fired; file the standing order again` }, { subject: `${routine.name} could not fire: its frozen agents did not seal`, body: `The firing was rolled back because ${why}. Open the standing order, refresh its agents, and approve it again; until then its firings wait.` });
572
+ }
573
+ store.recordRoutineFire({ routineId, scheduledFor: slotKey, outcome: "fired", reason: manual ? "manual" : null, instanceTaskRef: ref.id }, now);
574
+ if (!manual) {
575
+ store.setRoutineNextFire(routineId, nextFireAt(schedule, scheduledFor, now), now);
576
+ }
577
+ // Success is the proof the blockers are gone; their episodes resolve.
578
+ store.resolveRoutineEpisodes(routineId, now);
579
+ return { ok: true, taskId, scheduledFor };
580
+ });
581
+ }
582
+ export function routineIntegrity(routine) {
583
+ // THE RAW TERMS FIRST (raw authority repair): a row whose stored terms
584
+ // do not read back exactly — a touch or requirement that is not a
585
+ // string, a rubric entry with a key this code never writes or one that
586
+ // does not parse, a flag or ceiling that is not the shape it was written
587
+ // as — is NOT approved, not live, not approvable, and not refreshable,
588
+ // whatever its columns say: the filtered reading of it must never hash,
589
+ // fire, or be re-filed as if it were the terms the approver read.
590
+ if (routine.termsProblem != null) {
591
+ const problem = `the stored terms cannot be read exactly (${routine.termsProblem}) — file this standing order again`;
592
+ return { approved: false, live: false, liveProblem: null, agents: { state: "unverified", approvable: false, refresh: false, problem } };
593
+ }
594
+ const terms = termsOf(routine);
595
+ // THE SEMANTIC TERMS NEXT (atomic authority closure): terms that read
596
+ // back exactly can still say something this code never files — a
597
+ // single-flight flag of 0, a cost ceiling of −1, a schedule no parser
598
+ // holds, an empty rubric. Every filing door validates these; the
599
+ // projection validates them AGAIN so a rehashed row is never approvable
600
+ // or live on terms the filing door would have refused.
601
+ const semantic = validateRoutineTerms(terms, { name: routine.name });
602
+ if (semantic.length > 0) {
603
+ const problem = `the stored terms are not ones this code files (${semantic.map(one => `${one.field}: ${one.problem}`).join("; ")}) — file this standing order again`;
604
+ return { approved: false, live: false, liveProblem: null, agents: { state: "unverified", approvable: false, refresh: false, problem } };
605
+ }
606
+ const stamped = routine.approvedAt !== null && routine.approvedDigest !== null && routine.approvedDigest === routine.digest;
607
+ const workingRehashes = routineDigestOf(terms, routine.profile ?? null, routine.route ?? null) === routine.digest;
608
+ const approved = stamped && workingRehashes;
609
+ const closed = (state, problem, refresh = true) => ({
610
+ approved,
611
+ live: false,
612
+ liveProblem: approved ? problem : null,
613
+ agents: { state, approvable: false, refresh, problem },
614
+ });
615
+ if (approved) {
616
+ if (routine.approvedRouteUnreadable === true || routine.approvedProfileUnreadable === true) {
617
+ return closed("unreadable", "the agents this approval froze cannot be read back");
618
+ }
619
+ const route = routine.approvedRoute ?? null;
620
+ const profile = routine.approvedProfile ?? null;
621
+ if (route === null || profile === null)
622
+ return closed("unfrozen", "approved before agents were frozen");
623
+ if (routineDigestOf(terms, profile, route) !== routine.approvedDigest) {
624
+ return closed("unverified", "the approved agents do not hash to the approval this standing order carries");
625
+ }
626
+ // WORKING/APPROVED PARITY (v48 integrity): the approval stands on the
627
+ // stored digest, and that digest binds the WORKING route and profile —
628
+ // so the frozen snapshot must BE the working one, byte for byte. A
629
+ // frozen pair that hashes to the approval while the working pair says
630
+ // something else is a row two authorities wrote; nothing fires on it.
631
+ if (routine.routeUnreadable === true || routine.route == null || routine.profile == null) {
632
+ return closed("unverified", "the approved agents are frozen but the working agents cannot be read back");
633
+ }
634
+ if (routeDigestOf(routine.route) !== routeDigestOf(route) || profileDigestOf(routine.profile) !== profileDigestOf(profile)) {
635
+ return closed("unverified", "the approved agents are not the working agents this standing order files");
636
+ }
637
+ const problems = routeProblems(route);
638
+ if (problems.length > 0)
639
+ return closed("unresolved", problems.join("; "));
640
+ const build = legOf(route, "build");
641
+ const repair = legOf(route, "repair");
642
+ const repairModel = profile.repairModel === "inherit" ? profile.model : profile.repairModel;
643
+ if (build.provider !== profile.provider || build.model !== profile.model || repair.provider !== profile.provider || repair.model !== repairModel) {
644
+ return closed("unverified", `the approved agents (${profile.provider} · ${profile.model}, repair ${profile.provider} · ${repairModel}) disagree with the approved route (${build.provider} · ${build.model}, repair ${repair.provider} · ${repair.model})`);
645
+ }
646
+ return { approved: true, live: true, liveProblem: null, agents: { state: "frozen", approvable: false, refresh: false, problem: null } };
647
+ }
648
+ if (routine.routeUnreadable === true)
649
+ return closed("unreadable", "the filed agents cannot be read back");
650
+ const route = routine.route ?? null;
651
+ const profile = routine.profile ?? null;
652
+ if (route === null || profile === null)
653
+ return closed("unresolved", "this standing order does not name an exact agent for every role");
654
+ const problems = routeProblems(route);
655
+ if (problems.length > 0)
656
+ return closed("unresolved", problems.join("; "));
657
+ if (!workingRehashes)
658
+ return closed("unverified", "the filed terms do not hash to this standing order's reference — file it again");
659
+ // BUILD/REPAIR PARITY BEFORE THE YES (atomic authority closure): the
660
+ // pending pair must agree exactly as the approved pair must — a working
661
+ // profile of claude · sonnet beside a rehashed route that builds on
662
+ // codex is a row two authorities wrote, and no yes lands on it.
663
+ const build = legOf(route, "build");
664
+ const repair = legOf(route, "repair");
665
+ const repairModel = profile.repairModel === "inherit" ? profile.model : profile.repairModel;
666
+ if (build.provider !== profile.provider || build.model !== profile.model || repair.provider !== profile.provider || repair.model !== repairModel) {
667
+ return closed("unverified", `the filed agents (${profile.provider} · ${profile.model}, repair ${profile.provider} · ${repairModel}) disagree with the filed route (${build.provider} · ${build.model}, repair ${repair.provider} · ${repair.model}) — refresh the agents and file again`);
668
+ }
669
+ return { approved: false, live: false, liveProblem: null, agents: { state: "pending", approvable: true, refresh: false, problem: null } };
670
+ }
671
+ /** The agents word every surface uses — one reading of the projection. */
672
+ export function routineAgentsState(routine) {
673
+ return routineIntegrity(routine).agents;
674
+ }
675
+ /**
676
+ * THE RECOVERY ROAD (v48): re-resolve a standing order's agents — the
677
+ * four-role route and the profile that restates its build and repair legs
678
+ * — from today's configuration, and file them as the order's WORKING
679
+ * agents under a digest that binds them. Nothing is approved here: a
680
+ * refreshed order reads "edited — approve again", the approver reads the
681
+ * exact agents it now names and agrees to them with the password, and
682
+ * only that yes freezes the snapshot a firing copies. A routine whose
683
+ * terms already bind these exact agents is left byte-for-byte alone.
684
+ * A configuration that still cannot name an exact, runnable agent for
685
+ * every role answers with the words and changes nothing.
686
+ */
687
+ export function refreshRoutineAgents(store, routineId, now) {
688
+ return store.transact(() => {
689
+ const routine = store.getRoutine(routineId);
690
+ if (routine === null)
691
+ return { ok: false, reason: "no-such-routine", problem: "no such routine" };
692
+ // Terms that do not read back exactly are never re-filed from their
693
+ // filtered reading (raw authority repair): the refresh refuses in
694
+ // words and writes nothing.
695
+ if (routine.termsProblem != null) {
696
+ return { ok: false, reason: "unresolved", problem: `the stored terms cannot be read exactly (${routine.termsProblem}) — file this standing order again` };
697
+ }
698
+ // Terms this code never files (a single-flight flag of 0, a negative
699
+ // ceiling) are not re-filed either (atomic authority closure): the
700
+ // refresh cannot mend terms, and rehashing them would only launder
701
+ // the corruption into a digest a person could sign.
702
+ const semantic = validateRoutineTerms(termsOf(routine), { name: routine.name });
703
+ if (semantic.length > 0) {
704
+ return { ok: false, reason: "unresolved", problem: `the stored terms are not ones this code files (${semantic.map(one => `${one.field}: ${one.problem}`).join("; ")}) — file this standing order again` };
705
+ }
706
+ const authority = resolveRoutineAuthority(store, routine.repo, routine.acceptance, now);
707
+ if (!authority.ok)
708
+ return { ok: false, reason: "unresolved", problem: authority.problem };
709
+ const terms = termsOf(routine);
710
+ const digest = routineDigestOf(terms, authority.profile, authority.route);
711
+ // The projection is read BEFORE any write (v48 authority repair): an approval that is
712
+ // not live — never frozen, unreadable, or a snapshot that no longer
713
+ // verifies — is WITHDRAWN here, even when the working agents already
714
+ // bind these exact terms, so a corrupt frozen snapshot can never keep
715
+ // reading as approved. A live approval under unchanged terms is left
716
+ // byte-for-byte alone.
717
+ const integrity = routineIntegrity(routine);
718
+ const unchanged = digest === routine.digest && routine.route != null && routine.profile != null && !routine.routeUnreadable;
719
+ if (unchanged && (integrity.live || !integrity.approved) && integrity.agents.state !== "unverified") {
720
+ return { ok: true, routine, changed: false };
721
+ }
722
+ if (integrity.approved && !integrity.live)
723
+ store.withdrawRoutineApproval(routineId, now);
724
+ if (!unchanged || integrity.agents.state === "unverified") {
725
+ store.updateRoutineTerms(routineId, { ...terms, digest, profile: authority.profile, route: authority.route }, now);
726
+ }
727
+ return { ok: true, routine: store.getRoutine(routineId), changed: true };
728
+ });
729
+ }
730
+ /** The standing order, in the words an operator has to agree to. */
731
+ export function describeRoutine(routine) {
732
+ const schedule = parseSchedule(routine.schedule);
733
+ return [
734
+ ` goal ${routine.goal}`,
735
+ ...(routine.outOfScope === null ? [] : [` not this ${routine.outOfScope}`]),
736
+ ...(routine.touches.length === 0 ? [] : [` touches ${routine.touches.join(", ")}`]),
737
+ ...acceptanceWords(routine.acceptance),
738
+ ...(routine.requirements.length === 0 ? [] : [` needs ${routine.requirements.join(", ")}`]),
739
+ ` project ${routine.repo}`,
740
+ ...routineAgentsWords(routine),
741
+ ` schedule ${schedule === null ? routine.schedule : describeSchedule(schedule)}`,
742
+ ` budget ${routine.costCeilingUsd === null ? "no ceiling" : `$${routine.costCeilingUsd.toFixed(2)} per rolling 7 days`}`,
743
+ ` one at a time — a firing skips while the previous instance is unfinished`,
744
+ ` reference ${routine.digest}`,
745
+ ` approving this means EACH FIRING BUILDS WITHOUT ASKING, inside exactly these terms`,
746
+ ];
747
+ }
748
+ /** The agents a standing order's approval freezes, in the same words the
749
+ * task page and chat use — or why it cannot say. */
750
+ export function routineAgentsWords(routine) {
751
+ const approved = routine.approvedAt !== null && routine.approvedDigest === routine.digest;
752
+ const agents = routineAgentsState(routine);
753
+ const route = approved ? routine.approvedRoute ?? null : routine.route ?? null;
754
+ if (route === null || agents.state === "unreadable" || agents.state === "unresolved" || agents.state === "unverified") {
755
+ return [
756
+ ` agents ${agents.state === "unreadable" ? "cannot be read" : agents.state === "unfrozen" ? "not frozen" : agents.state === "unverified" ? "do not verify" : "not resolved"} — ${agents.problem ?? "this standing order does not name an exact agent for every role"}`,
757
+ ` refresh them with \`routine refresh <name>\`, read the agents it then names, and approve it again`,
758
+ ];
759
+ }
760
+ return [
761
+ ` agents ${agentsSummary(route)} — ${postureWords(route)}`,
762
+ ...(approved ? [` frozen by the approval: a configuration change cannot re-route a firing`] : [` frozen when you approve: a configuration change cannot re-route a firing`]),
763
+ ];
764
+ }