looply-cli 0.0.0-stage → 0.3.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 (793) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +137 -3
  3. package/dist/.src-hash +1 -0
  4. package/dist/acceptance-observe.d.ts +108 -0
  5. package/dist/acceptance-observe.d.ts.map +1 -0
  6. package/dist/acceptance-observe.js +152 -0
  7. package/dist/acceptance-observe.js.map +1 -0
  8. package/dist/agent-cmd.d.ts +131 -0
  9. package/dist/agent-cmd.d.ts.map +1 -0
  10. package/dist/agent-cmd.js +154 -0
  11. package/dist/agent-cmd.js.map +1 -0
  12. package/dist/agent-contained.d.ts +114 -0
  13. package/dist/agent-contained.d.ts.map +1 -0
  14. package/dist/agent-contained.js +292 -0
  15. package/dist/agent-contained.js.map +1 -0
  16. package/dist/agent-driver.d.ts +130 -0
  17. package/dist/agent-driver.d.ts.map +1 -0
  18. package/dist/agent-driver.js +170 -0
  19. package/dist/agent-driver.js.map +1 -0
  20. package/dist/agent-envelope.d.ts +94 -0
  21. package/dist/agent-envelope.d.ts.map +1 -0
  22. package/dist/agent-envelope.js +214 -0
  23. package/dist/agent-envelope.js.map +1 -0
  24. package/dist/agent-exec.d.ts +182 -0
  25. package/dist/agent-exec.d.ts.map +1 -0
  26. package/dist/agent-exec.js +125 -0
  27. package/dist/agent-exec.js.map +1 -0
  28. package/dist/agent-runtime.d.ts +23 -0
  29. package/dist/agent-runtime.d.ts.map +1 -0
  30. package/dist/agent-runtime.js +143 -0
  31. package/dist/agent-runtime.js.map +1 -0
  32. package/dist/approval-gate-read.d.ts +66 -0
  33. package/dist/approval-gate-read.d.ts.map +1 -0
  34. package/dist/approval-gate-read.js +138 -0
  35. package/dist/approval-gate-read.js.map +1 -0
  36. package/dist/approval-resolution.d.ts +87 -0
  37. package/dist/approval-resolution.d.ts.map +1 -0
  38. package/dist/approval-resolution.js +100 -0
  39. package/dist/approval-resolution.js.map +1 -0
  40. package/dist/approval-resumes.d.ts +102 -0
  41. package/dist/approval-resumes.d.ts.map +1 -0
  42. package/dist/approval-resumes.js +167 -0
  43. package/dist/approval-resumes.js.map +1 -0
  44. package/dist/ask-brain.d.ts +167 -0
  45. package/dist/ask-brain.d.ts.map +1 -0
  46. package/dist/ask-brain.js +355 -0
  47. package/dist/ask-brain.js.map +1 -0
  48. package/dist/autonomy-authority.d.ts +80 -0
  49. package/dist/autonomy-authority.d.ts.map +1 -0
  50. package/dist/autonomy-authority.js +82 -0
  51. package/dist/autonomy-authority.js.map +1 -0
  52. package/dist/autonomy-controller.d.ts +321 -0
  53. package/dist/autonomy-controller.d.ts.map +1 -0
  54. package/dist/autonomy-controller.js +517 -0
  55. package/dist/autonomy-controller.js.map +1 -0
  56. package/dist/autonomy-store.d.ts +327 -0
  57. package/dist/autonomy-store.d.ts.map +1 -0
  58. package/dist/autonomy-store.js +581 -0
  59. package/dist/autonomy-store.js.map +1 -0
  60. package/dist/autonomy-tick.d.ts +257 -0
  61. package/dist/autonomy-tick.d.ts.map +1 -0
  62. package/dist/autonomy-tick.js +616 -0
  63. package/dist/autonomy-tick.js.map +1 -0
  64. package/dist/bin.d.ts +3 -0
  65. package/dist/bin.d.ts.map +1 -0
  66. package/dist/bin.js +2727 -0
  67. package/dist/bin.js.map +1 -0
  68. package/dist/brain-advice.d.ts +21 -0
  69. package/dist/brain-advice.d.ts.map +1 -0
  70. package/dist/brain-advice.js +51 -0
  71. package/dist/brain-advice.js.map +1 -0
  72. package/dist/candidate-replay-runtime.d.ts +125 -0
  73. package/dist/candidate-replay-runtime.d.ts.map +1 -0
  74. package/dist/candidate-replay-runtime.js +188 -0
  75. package/dist/candidate-replay-runtime.js.map +1 -0
  76. package/dist/checkout-carry.d.ts +76 -0
  77. package/dist/checkout-carry.d.ts.map +1 -0
  78. package/dist/checkout-carry.js +125 -0
  79. package/dist/checkout-carry.js.map +1 -0
  80. package/dist/citation-readers.d.ts +26 -0
  81. package/dist/citation-readers.d.ts.map +1 -0
  82. package/dist/citation-readers.js +102 -0
  83. package/dist/citation-readers.js.map +1 -0
  84. package/dist/command-receipts.d.ts +79 -0
  85. package/dist/command-receipts.d.ts.map +1 -0
  86. package/dist/command-receipts.js +143 -0
  87. package/dist/command-receipts.js.map +1 -0
  88. package/dist/command-runner.d.ts +32 -0
  89. package/dist/command-runner.d.ts.map +1 -0
  90. package/dist/command-runner.js +108 -0
  91. package/dist/command-runner.js.map +1 -0
  92. package/dist/compatible-sets.d.ts +67 -0
  93. package/dist/compatible-sets.d.ts.map +1 -0
  94. package/dist/compatible-sets.js +81 -0
  95. package/dist/compatible-sets.js.map +1 -0
  96. package/dist/compile-check.d.ts +31 -0
  97. package/dist/compile-check.d.ts.map +1 -0
  98. package/dist/compile-check.js +34 -0
  99. package/dist/compile-check.js.map +1 -0
  100. package/dist/compile-store.d.ts +16 -0
  101. package/dist/compile-store.d.ts.map +1 -0
  102. package/dist/compile-store.js +21 -0
  103. package/dist/compile-store.js.map +1 -0
  104. package/dist/completion.d.ts +106 -0
  105. package/dist/completion.d.ts.map +1 -0
  106. package/dist/completion.js +131 -0
  107. package/dist/completion.js.map +1 -0
  108. package/dist/config.d.ts +549 -0
  109. package/dist/config.d.ts.map +1 -0
  110. package/dist/config.js +763 -0
  111. package/dist/config.js.map +1 -0
  112. package/dist/control-file.d.ts +142 -0
  113. package/dist/control-file.d.ts.map +1 -0
  114. package/dist/control-file.js +144 -0
  115. package/dist/control-file.js.map +1 -0
  116. package/dist/coordinator-state.d.ts +131 -0
  117. package/dist/coordinator-state.d.ts.map +1 -0
  118. package/dist/coordinator-state.js +380 -0
  119. package/dist/coordinator-state.js.map +1 -0
  120. package/dist/coordinator.d.ts +95 -0
  121. package/dist/coordinator.d.ts.map +1 -0
  122. package/dist/coordinator.js +97 -0
  123. package/dist/coordinator.js.map +1 -0
  124. package/dist/cursor-store.d.ts +53 -0
  125. package/dist/cursor-store.d.ts.map +1 -0
  126. package/dist/cursor-store.js +98 -0
  127. package/dist/cursor-store.js.map +1 -0
  128. package/dist/daemon-cmd.d.ts +239 -0
  129. package/dist/daemon-cmd.d.ts.map +1 -0
  130. package/dist/daemon-cmd.js +1751 -0
  131. package/dist/daemon-cmd.js.map +1 -0
  132. package/dist/daemon-lock.d.ts +140 -0
  133. package/dist/daemon-lock.d.ts.map +1 -0
  134. package/dist/daemon-lock.js +352 -0
  135. package/dist/daemon-lock.js.map +1 -0
  136. package/dist/daemon-recovery.d.ts +78 -0
  137. package/dist/daemon-recovery.d.ts.map +1 -0
  138. package/dist/daemon-recovery.js +161 -0
  139. package/dist/daemon-recovery.js.map +1 -0
  140. package/dist/daemon.d.ts +368 -0
  141. package/dist/daemon.d.ts.map +1 -0
  142. package/dist/daemon.js +601 -0
  143. package/dist/daemon.js.map +1 -0
  144. package/dist/declared-response-shape.d.ts +93 -0
  145. package/dist/declared-response-shape.d.ts.map +1 -0
  146. package/dist/declared-response-shape.js +200 -0
  147. package/dist/declared-response-shape.js.map +1 -0
  148. package/dist/declared-surfaces.d.ts +16 -0
  149. package/dist/declared-surfaces.d.ts.map +1 -0
  150. package/dist/declared-surfaces.js +19 -0
  151. package/dist/declared-surfaces.js.map +1 -0
  152. package/dist/depends-review.d.ts +113 -0
  153. package/dist/depends-review.d.ts.map +1 -0
  154. package/dist/depends-review.js +110 -0
  155. package/dist/depends-review.js.map +1 -0
  156. package/dist/doctor.d.ts +91 -0
  157. package/dist/doctor.d.ts.map +1 -0
  158. package/dist/doctor.js +71 -0
  159. package/dist/doctor.js.map +1 -0
  160. package/dist/edge-approval.d.ts +105 -0
  161. package/dist/edge-approval.d.ts.map +1 -0
  162. package/dist/edge-approval.js +100 -0
  163. package/dist/edge-approval.js.map +1 -0
  164. package/dist/evidence-cache.d.ts +45 -0
  165. package/dist/evidence-cache.d.ts.map +1 -0
  166. package/dist/evidence-cache.js +90 -0
  167. package/dist/evidence-cache.js.map +1 -0
  168. package/dist/evidence-cmd.d.ts +133 -0
  169. package/dist/evidence-cmd.d.ts.map +1 -0
  170. package/dist/evidence-cmd.js +347 -0
  171. package/dist/evidence-cmd.js.map +1 -0
  172. package/dist/evidence-facts.d.ts +40 -0
  173. package/dist/evidence-facts.d.ts.map +1 -0
  174. package/dist/evidence-facts.js +114 -0
  175. package/dist/evidence-facts.js.map +1 -0
  176. package/dist/exclusive-lock.d.ts +52 -0
  177. package/dist/exclusive-lock.d.ts.map +1 -0
  178. package/dist/exclusive-lock.js +150 -0
  179. package/dist/exclusive-lock.js.map +1 -0
  180. package/dist/exec-domains.d.ts +140 -0
  181. package/dist/exec-domains.d.ts.map +1 -0
  182. package/dist/exec-domains.js +287 -0
  183. package/dist/exec-domains.js.map +1 -0
  184. package/dist/exemption-store.d.ts +197 -0
  185. package/dist/exemption-store.d.ts.map +1 -0
  186. package/dist/exemption-store.js +266 -0
  187. package/dist/exemption-store.js.map +1 -0
  188. package/dist/explicit-start-gate.d.ts +96 -0
  189. package/dist/explicit-start-gate.d.ts.map +1 -0
  190. package/dist/explicit-start-gate.js +103 -0
  191. package/dist/explicit-start-gate.js.map +1 -0
  192. package/dist/fs-trouble.d.ts +70 -0
  193. package/dist/fs-trouble.d.ts.map +1 -0
  194. package/dist/fs-trouble.js +98 -0
  195. package/dist/fs-trouble.js.map +1 -0
  196. package/dist/gate.d.ts +278 -0
  197. package/dist/gate.d.ts.map +1 -0
  198. package/dist/gate.js +278 -0
  199. package/dist/gate.js.map +1 -0
  200. package/dist/git-driver.d.ts +519 -0
  201. package/dist/git-driver.d.ts.map +1 -0
  202. package/dist/git-driver.js +900 -0
  203. package/dist/git-driver.js.map +1 -0
  204. package/dist/git-push-executor.d.ts +41 -0
  205. package/dist/git-push-executor.d.ts.map +1 -0
  206. package/dist/git-push-executor.js +142 -0
  207. package/dist/git-push-executor.js.map +1 -0
  208. package/dist/glob.d.ts +26 -0
  209. package/dist/glob.d.ts.map +1 -0
  210. package/dist/glob.js +93 -0
  211. package/dist/glob.js.map +1 -0
  212. package/dist/goal-adaptive.d.ts +295 -0
  213. package/dist/goal-adaptive.d.ts.map +1 -0
  214. package/dist/goal-adaptive.js +393 -0
  215. package/dist/goal-adaptive.js.map +1 -0
  216. package/dist/goal-compile.d.ts +342 -0
  217. package/dist/goal-compile.d.ts.map +1 -0
  218. package/dist/goal-compile.js +444 -0
  219. package/dist/goal-compile.js.map +1 -0
  220. package/dist/goal-depends-executor.d.ts +55 -0
  221. package/dist/goal-depends-executor.d.ts.map +1 -0
  222. package/dist/goal-depends-executor.js +202 -0
  223. package/dist/goal-depends-executor.js.map +1 -0
  224. package/dist/goal-execution-evidence.d.ts +135 -0
  225. package/dist/goal-execution-evidence.d.ts.map +1 -0
  226. package/dist/goal-execution-evidence.js +159 -0
  227. package/dist/goal-execution-evidence.js.map +1 -0
  228. package/dist/goal-gap.d.ts +140 -0
  229. package/dist/goal-gap.d.ts.map +1 -0
  230. package/dist/goal-gap.js +197 -0
  231. package/dist/goal-gap.js.map +1 -0
  232. package/dist/goal-met-target.d.ts +98 -0
  233. package/dist/goal-met-target.d.ts.map +1 -0
  234. package/dist/goal-met-target.js +92 -0
  235. package/dist/goal-met-target.js.map +1 -0
  236. package/dist/goal-ownership.d.ts +416 -0
  237. package/dist/goal-ownership.d.ts.map +1 -0
  238. package/dist/goal-ownership.js +576 -0
  239. package/dist/goal-ownership.js.map +1 -0
  240. package/dist/goal-runs.d.ts +82 -0
  241. package/dist/goal-runs.d.ts.map +1 -0
  242. package/dist/goal-runs.js +120 -0
  243. package/dist/goal-runs.js.map +1 -0
  244. package/dist/goal-store.d.ts +146 -0
  245. package/dist/goal-store.d.ts.map +1 -0
  246. package/dist/goal-store.js +294 -0
  247. package/dist/goal-store.js.map +1 -0
  248. package/dist/goal-view.d.ts +156 -0
  249. package/dist/goal-view.d.ts.map +1 -0
  250. package/dist/goal-view.js +499 -0
  251. package/dist/goal-view.js.map +1 -0
  252. package/dist/governed-approvals.d.ts +152 -0
  253. package/dist/governed-approvals.d.ts.map +1 -0
  254. package/dist/governed-approvals.js +246 -0
  255. package/dist/governed-approvals.js.map +1 -0
  256. package/dist/governed-broker.d.ts +169 -0
  257. package/dist/governed-broker.d.ts.map +1 -0
  258. package/dist/governed-broker.js +196 -0
  259. package/dist/governed-broker.js.map +1 -0
  260. package/dist/governed-run.d.ts +220 -0
  261. package/dist/governed-run.d.ts.map +1 -0
  262. package/dist/governed-run.js +406 -0
  263. package/dist/governed-run.js.map +1 -0
  264. package/dist/grant-reconcile.d.ts +63 -0
  265. package/dist/grant-reconcile.d.ts.map +1 -0
  266. package/dist/grant-reconcile.js +121 -0
  267. package/dist/grant-reconcile.js.map +1 -0
  268. package/dist/index-for-head.d.ts +56 -0
  269. package/dist/index-for-head.d.ts.map +1 -0
  270. package/dist/index-for-head.js +103 -0
  271. package/dist/index-for-head.js.map +1 -0
  272. package/dist/index.d.ts +39 -0
  273. package/dist/index.d.ts.map +1 -0
  274. package/dist/index.js +55 -0
  275. package/dist/index.js.map +1 -0
  276. package/dist/init-detect.d.ts +39 -0
  277. package/dist/init-detect.d.ts.map +1 -0
  278. package/dist/init-detect.js +188 -0
  279. package/dist/init-detect.js.map +1 -0
  280. package/dist/integration-deps.d.ts +62 -0
  281. package/dist/integration-deps.d.ts.map +1 -0
  282. package/dist/integration-deps.js +251 -0
  283. package/dist/integration-deps.js.map +1 -0
  284. package/dist/integration-runtime.d.ts +458 -0
  285. package/dist/integration-runtime.d.ts.map +1 -0
  286. package/dist/integration-runtime.js +681 -0
  287. package/dist/integration-runtime.js.map +1 -0
  288. package/dist/integration.d.ts +45 -0
  289. package/dist/integration.d.ts.map +1 -0
  290. package/dist/integration.js +87 -0
  291. package/dist/integration.js.map +1 -0
  292. package/dist/integrator.d.ts +84 -0
  293. package/dist/integrator.d.ts.map +1 -0
  294. package/dist/integrator.js +193 -0
  295. package/dist/integrator.js.map +1 -0
  296. package/dist/lane-claims.d.ts +174 -0
  297. package/dist/lane-claims.d.ts.map +1 -0
  298. package/dist/lane-claims.js +218 -0
  299. package/dist/lane-claims.js.map +1 -0
  300. package/dist/lane-coverage.d.ts +87 -0
  301. package/dist/lane-coverage.d.ts.map +1 -0
  302. package/dist/lane-coverage.js +129 -0
  303. package/dist/lane-coverage.js.map +1 -0
  304. package/dist/ledger-replay.d.ts +115 -0
  305. package/dist/ledger-replay.d.ts.map +1 -0
  306. package/dist/ledger-replay.js +155 -0
  307. package/dist/ledger-replay.js.map +1 -0
  308. package/dist/ledger-store.d.ts +68 -0
  309. package/dist/ledger-store.d.ts.map +1 -0
  310. package/dist/ledger-store.js +111 -0
  311. package/dist/ledger-store.js.map +1 -0
  312. package/dist/loop-safety-observer.d.ts +132 -0
  313. package/dist/loop-safety-observer.d.ts.map +1 -0
  314. package/dist/loop-safety-observer.js +76 -0
  315. package/dist/loop-safety-observer.js.map +1 -0
  316. package/dist/loop-safety-store.d.ts +339 -0
  317. package/dist/loop-safety-store.d.ts.map +1 -0
  318. package/dist/loop-safety-store.js +800 -0
  319. package/dist/loop-safety-store.js.map +1 -0
  320. package/dist/machine-store.d.ts +54 -0
  321. package/dist/machine-store.d.ts.map +1 -0
  322. package/dist/machine-store.js +149 -0
  323. package/dist/machine-store.js.map +1 -0
  324. package/dist/mcp-main.d.ts +2 -0
  325. package/dist/mcp-main.d.ts.map +1 -0
  326. package/dist/mcp-main.js +116 -0
  327. package/dist/mcp-main.js.map +1 -0
  328. package/dist/mcp-server.d.ts +72 -0
  329. package/dist/mcp-server.d.ts.map +1 -0
  330. package/dist/mcp-server.js +259 -0
  331. package/dist/mcp-server.js.map +1 -0
  332. package/dist/model-routing.d.ts +75 -0
  333. package/dist/model-routing.d.ts.map +1 -0
  334. package/dist/model-routing.js +108 -0
  335. package/dist/model-routing.js.map +1 -0
  336. package/dist/model-transport.d.ts +188 -0
  337. package/dist/model-transport.d.ts.map +1 -0
  338. package/dist/model-transport.js +625 -0
  339. package/dist/model-transport.js.map +1 -0
  340. package/dist/notify-cadence.d.ts +55 -0
  341. package/dist/notify-cadence.d.ts.map +1 -0
  342. package/dist/notify-cadence.js +68 -0
  343. package/dist/notify-cadence.js.map +1 -0
  344. package/dist/notify-channel.d.ts +69 -0
  345. package/dist/notify-channel.d.ts.map +1 -0
  346. package/dist/notify-channel.js +151 -0
  347. package/dist/notify-channel.js.map +1 -0
  348. package/dist/notify.d.ts +71 -0
  349. package/dist/notify.d.ts.map +1 -0
  350. package/dist/notify.js +125 -0
  351. package/dist/notify.js.map +1 -0
  352. package/dist/observation-inventory.d.ts +65 -0
  353. package/dist/observation-inventory.d.ts.map +1 -0
  354. package/dist/observation-inventory.js +139 -0
  355. package/dist/observation-inventory.js.map +1 -0
  356. package/dist/owned-children.d.ts +45 -0
  357. package/dist/owned-children.d.ts.map +1 -0
  358. package/dist/owned-children.js +64 -0
  359. package/dist/owned-children.js.map +1 -0
  360. package/dist/packet-for-goal.d.ts +19 -0
  361. package/dist/packet-for-goal.d.ts.map +1 -0
  362. package/dist/packet-for-goal.js +32 -0
  363. package/dist/packet-for-goal.js.map +1 -0
  364. package/dist/pair-cmd.d.ts +85 -0
  365. package/dist/pair-cmd.d.ts.map +1 -0
  366. package/dist/pair-cmd.js +205 -0
  367. package/dist/pair-cmd.js.map +1 -0
  368. package/dist/partial-asker.d.ts +96 -0
  369. package/dist/partial-asker.d.ts.map +1 -0
  370. package/dist/partial-asker.js +117 -0
  371. package/dist/partial-asker.js.map +1 -0
  372. package/dist/pdf-text-lines.d.ts +17 -0
  373. package/dist/pdf-text-lines.d.ts.map +1 -0
  374. package/dist/pdf-text-lines.js +70 -0
  375. package/dist/pdf-text-lines.js.map +1 -0
  376. package/dist/pdf-text-worker.d.ts +2 -0
  377. package/dist/pdf-text-worker.d.ts.map +1 -0
  378. package/dist/pdf-text-worker.js +81 -0
  379. package/dist/pdf-text-worker.js.map +1 -0
  380. package/dist/pdf-text.d.ts +56 -0
  381. package/dist/pdf-text.d.ts.map +1 -0
  382. package/dist/pdf-text.js +64 -0
  383. package/dist/pdf-text.js.map +1 -0
  384. package/dist/pending-action.d.ts +154 -0
  385. package/dist/pending-action.d.ts.map +1 -0
  386. package/dist/pending-action.js +148 -0
  387. package/dist/pending-action.js.map +1 -0
  388. package/dist/preemptor.d.ts +40 -0
  389. package/dist/preemptor.d.ts.map +1 -0
  390. package/dist/preemptor.js +49 -0
  391. package/dist/preemptor.js.map +1 -0
  392. package/dist/programme-mode.d.ts +29 -0
  393. package/dist/programme-mode.d.ts.map +1 -0
  394. package/dist/programme-mode.js +50 -0
  395. package/dist/programme-mode.js.map +1 -0
  396. package/dist/progress-view.d.ts +14 -0
  397. package/dist/progress-view.d.ts.map +1 -0
  398. package/dist/progress-view.js +114 -0
  399. package/dist/progress-view.js.map +1 -0
  400. package/dist/project-graph-builder.d.ts +81 -0
  401. package/dist/project-graph-builder.d.ts.map +1 -0
  402. package/dist/project-graph-builder.js +246 -0
  403. package/dist/project-graph-builder.js.map +1 -0
  404. package/dist/project-graph-runtime.d.ts +86 -0
  405. package/dist/project-graph-runtime.d.ts.map +1 -0
  406. package/dist/project-graph-runtime.js +112 -0
  407. package/dist/project-graph-runtime.js.map +1 -0
  408. package/dist/project-index-builder.d.ts +96 -0
  409. package/dist/project-index-builder.d.ts.map +1 -0
  410. package/dist/project-index-builder.js +298 -0
  411. package/dist/project-index-builder.js.map +1 -0
  412. package/dist/project-index-facts.d.ts +51 -0
  413. package/dist/project-index-facts.d.ts.map +1 -0
  414. package/dist/project-index-facts.js +117 -0
  415. package/dist/project-index-facts.js.map +1 -0
  416. package/dist/project-index-store.d.ts +97 -0
  417. package/dist/project-index-store.d.ts.map +1 -0
  418. package/dist/project-index-store.js +140 -0
  419. package/dist/project-index-store.js.map +1 -0
  420. package/dist/project-memory-store.d.ts +127 -0
  421. package/dist/project-memory-store.d.ts.map +1 -0
  422. package/dist/project-memory-store.js +302 -0
  423. package/dist/project-memory-store.js.map +1 -0
  424. package/dist/proposal-store.d.ts +14 -0
  425. package/dist/proposal-store.d.ts.map +1 -0
  426. package/dist/proposal-store.js +19 -0
  427. package/dist/proposal-store.js.map +1 -0
  428. package/dist/prover.d.ts +80 -0
  429. package/dist/prover.d.ts.map +1 -0
  430. package/dist/prover.js +137 -0
  431. package/dist/prover.js.map +1 -0
  432. package/dist/provider-identity-store.d.ts +34 -0
  433. package/dist/provider-identity-store.d.ts.map +1 -0
  434. package/dist/provider-identity-store.js +72 -0
  435. package/dist/provider-identity-store.js.map +1 -0
  436. package/dist/provider-identity.d.ts +104 -0
  437. package/dist/provider-identity.d.ts.map +1 -0
  438. package/dist/provider-identity.js +167 -0
  439. package/dist/provider-identity.js.map +1 -0
  440. package/dist/provider-limit.d.ts +79 -0
  441. package/dist/provider-limit.d.ts.map +1 -0
  442. package/dist/provider-limit.js +148 -0
  443. package/dist/provider-limit.js.map +1 -0
  444. package/dist/provider-preflight.d.ts +171 -0
  445. package/dist/provider-preflight.d.ts.map +1 -0
  446. package/dist/provider-preflight.js +395 -0
  447. package/dist/provider-preflight.js.map +1 -0
  448. package/dist/provider-state.d.ts +120 -0
  449. package/dist/provider-state.d.ts.map +1 -0
  450. package/dist/provider-state.js +182 -0
  451. package/dist/provider-state.js.map +1 -0
  452. package/dist/providers.d.ts +132 -0
  453. package/dist/providers.d.ts.map +1 -0
  454. package/dist/providers.js +161 -0
  455. package/dist/providers.js.map +1 -0
  456. package/dist/publication-recorder.d.ts +117 -0
  457. package/dist/publication-recorder.d.ts.map +1 -0
  458. package/dist/publication-recorder.js +124 -0
  459. package/dist/publication-recorder.js.map +1 -0
  460. package/dist/pursuit-delivery.d.ts +24 -0
  461. package/dist/pursuit-delivery.d.ts.map +1 -0
  462. package/dist/pursuit-delivery.js +38 -0
  463. package/dist/pursuit-delivery.js.map +1 -0
  464. package/dist/queue-store.d.ts +132 -0
  465. package/dist/queue-store.d.ts.map +1 -0
  466. package/dist/queue-store.js +185 -0
  467. package/dist/queue-store.js.map +1 -0
  468. package/dist/queue-summary.d.ts +14 -0
  469. package/dist/queue-summary.d.ts.map +1 -0
  470. package/dist/queue-summary.js +82 -0
  471. package/dist/queue-summary.js.map +1 -0
  472. package/dist/queue-view.d.ts +20 -0
  473. package/dist/queue-view.d.ts.map +1 -0
  474. package/dist/queue-view.js +163 -0
  475. package/dist/queue-view.js.map +1 -0
  476. package/dist/queue-write-lease.d.ts +106 -0
  477. package/dist/queue-write-lease.d.ts.map +1 -0
  478. package/dist/queue-write-lease.js +282 -0
  479. package/dist/queue-write-lease.js.map +1 -0
  480. package/dist/rails-home.d.ts +162 -0
  481. package/dist/rails-home.d.ts.map +1 -0
  482. package/dist/rails-home.js +452 -0
  483. package/dist/rails-home.js.map +1 -0
  484. package/dist/rails-migrations.d.ts +75 -0
  485. package/dist/rails-migrations.d.ts.map +1 -0
  486. package/dist/rails-migrations.js +199 -0
  487. package/dist/rails-migrations.js.map +1 -0
  488. package/dist/rails-stores.d.ts +49 -0
  489. package/dist/rails-stores.d.ts.map +1 -0
  490. package/dist/rails-stores.js +100 -0
  491. package/dist/rails-stores.js.map +1 -0
  492. package/dist/reaper.d.ts +69 -0
  493. package/dist/reaper.d.ts.map +1 -0
  494. package/dist/reaper.js +152 -0
  495. package/dist/reaper.js.map +1 -0
  496. package/dist/record-store.d.ts +36 -0
  497. package/dist/record-store.d.ts.map +1 -0
  498. package/dist/record-store.js +78 -0
  499. package/dist/record-store.js.map +1 -0
  500. package/dist/recovery-io.d.ts +50 -0
  501. package/dist/recovery-io.d.ts.map +1 -0
  502. package/dist/recovery-io.js +122 -0
  503. package/dist/recovery-io.js.map +1 -0
  504. package/dist/recovery.d.ts +146 -0
  505. package/dist/recovery.d.ts.map +1 -0
  506. package/dist/recovery.js +110 -0
  507. package/dist/recovery.js.map +1 -0
  508. package/dist/redraw.d.ts +34 -0
  509. package/dist/redraw.d.ts.map +1 -0
  510. package/dist/redraw.js +62 -0
  511. package/dist/redraw.js.map +1 -0
  512. package/dist/rejected-evidence.d.ts +255 -0
  513. package/dist/rejected-evidence.d.ts.map +1 -0
  514. package/dist/rejected-evidence.js +324 -0
  515. package/dist/rejected-evidence.js.map +1 -0
  516. package/dist/report-cmd.d.ts +32 -0
  517. package/dist/report-cmd.d.ts.map +1 -0
  518. package/dist/report-cmd.js +201 -0
  519. package/dist/report-cmd.js.map +1 -0
  520. package/dist/repos-cmd.d.ts +80 -0
  521. package/dist/repos-cmd.d.ts.map +1 -0
  522. package/dist/repos-cmd.js +240 -0
  523. package/dist/repos-cmd.js.map +1 -0
  524. package/dist/request-body-shape.d.ts +93 -0
  525. package/dist/request-body-shape.d.ts.map +1 -0
  526. package/dist/request-body-shape.js +407 -0
  527. package/dist/request-body-shape.js.map +1 -0
  528. package/dist/requirement-claims.d.ts +89 -0
  529. package/dist/requirement-claims.d.ts.map +1 -0
  530. package/dist/requirement-claims.js +92 -0
  531. package/dist/requirement-claims.js.map +1 -0
  532. package/dist/retention-apply-runtime.d.ts +105 -0
  533. package/dist/retention-apply-runtime.d.ts.map +1 -0
  534. package/dist/retention-apply-runtime.js +269 -0
  535. package/dist/retention-apply-runtime.js.map +1 -0
  536. package/dist/reverify.d.ts +41 -0
  537. package/dist/reverify.d.ts.map +1 -0
  538. package/dist/reverify.js +69 -0
  539. package/dist/reverify.js.map +1 -0
  540. package/dist/round-records.d.ts +65 -0
  541. package/dist/round-records.d.ts.map +1 -0
  542. package/dist/round-records.js +61 -0
  543. package/dist/round-records.js.map +1 -0
  544. package/dist/round-runner.d.ts +924 -0
  545. package/dist/round-runner.d.ts.map +1 -0
  546. package/dist/round-runner.js +1693 -0
  547. package/dist/round-runner.js.map +1 -0
  548. package/dist/run-approval.d.ts +180 -0
  549. package/dist/run-approval.d.ts.map +1 -0
  550. package/dist/run-approval.js +199 -0
  551. package/dist/run-approval.js.map +1 -0
  552. package/dist/run-cmd.d.ts +359 -0
  553. package/dist/run-cmd.d.ts.map +1 -0
  554. package/dist/run-cmd.js +4418 -0
  555. package/dist/run-cmd.js.map +1 -0
  556. package/dist/run-delivery.d.ts +71 -0
  557. package/dist/run-delivery.d.ts.map +1 -0
  558. package/dist/run-delivery.js +104 -0
  559. package/dist/run-delivery.js.map +1 -0
  560. package/dist/run-ended.d.ts +75 -0
  561. package/dist/run-ended.d.ts.map +1 -0
  562. package/dist/run-ended.js +27 -0
  563. package/dist/run-ended.js.map +1 -0
  564. package/dist/run-ending-locator.d.ts +125 -0
  565. package/dist/run-ending-locator.d.ts.map +1 -0
  566. package/dist/run-ending-locator.js +133 -0
  567. package/dist/run-ending-locator.js.map +1 -0
  568. package/dist/run-launcher.d.ts +459 -0
  569. package/dist/run-launcher.d.ts.map +1 -0
  570. package/dist/run-launcher.js +652 -0
  571. package/dist/run-launcher.js.map +1 -0
  572. package/dist/run-lease.d.ts +208 -0
  573. package/dist/run-lease.d.ts.map +1 -0
  574. package/dist/run-lease.js +301 -0
  575. package/dist/run-lease.js.map +1 -0
  576. package/dist/run-refusal-marks.d.ts +189 -0
  577. package/dist/run-refusal-marks.d.ts.map +1 -0
  578. package/dist/run-refusal-marks.js +249 -0
  579. package/dist/run-refusal-marks.js.map +1 -0
  580. package/dist/run-start-marks.d.ts +153 -0
  581. package/dist/run-start-marks.d.ts.map +1 -0
  582. package/dist/run-start-marks.js +269 -0
  583. package/dist/run-start-marks.js.map +1 -0
  584. package/dist/scan-cmd.d.ts +14 -0
  585. package/dist/scan-cmd.d.ts.map +1 -0
  586. package/dist/scan-cmd.js +270 -0
  587. package/dist/scan-cmd.js.map +1 -0
  588. package/dist/scan-events.d.ts +50 -0
  589. package/dist/scan-events.d.ts.map +1 -0
  590. package/dist/scan-events.js +91 -0
  591. package/dist/scan-events.js.map +1 -0
  592. package/dist/scan.d.ts +191 -0
  593. package/dist/scan.d.ts.map +1 -0
  594. package/dist/scan.js +417 -0
  595. package/dist/scan.js.map +1 -0
  596. package/dist/scope-rule.d.ts +148 -0
  597. package/dist/scope-rule.d.ts.map +1 -0
  598. package/dist/scope-rule.js +121 -0
  599. package/dist/scope-rule.js.map +1 -0
  600. package/dist/self-repo-id.d.ts +18 -0
  601. package/dist/self-repo-id.d.ts.map +1 -0
  602. package/dist/self-repo-id.js +70 -0
  603. package/dist/self-repo-id.js.map +1 -0
  604. package/dist/serve-cmd.d.ts +115 -0
  605. package/dist/serve-cmd.d.ts.map +1 -0
  606. package/dist/serve-cmd.js +505 -0
  607. package/dist/serve-cmd.js.map +1 -0
  608. package/dist/serve.d.ts +167 -0
  609. package/dist/serve.d.ts.map +1 -0
  610. package/dist/serve.js +336 -0
  611. package/dist/serve.js.map +1 -0
  612. package/dist/server-client.d.ts +766 -0
  613. package/dist/server-client.d.ts.map +1 -0
  614. package/dist/server-client.js +475 -0
  615. package/dist/server-client.js.map +1 -0
  616. package/dist/session-adapter-runtime.d.ts +195 -0
  617. package/dist/session-adapter-runtime.d.ts.map +1 -0
  618. package/dist/session-adapter-runtime.js +373 -0
  619. package/dist/session-adapter-runtime.js.map +1 -0
  620. package/dist/session-checkpoint-runtime.d.ts +62 -0
  621. package/dist/session-checkpoint-runtime.d.ts.map +1 -0
  622. package/dist/session-checkpoint-runtime.js +268 -0
  623. package/dist/session-checkpoint-runtime.js.map +1 -0
  624. package/dist/session-control-transport.d.ts +106 -0
  625. package/dist/session-control-transport.d.ts.map +1 -0
  626. package/dist/session-control-transport.js +232 -0
  627. package/dist/session-control-transport.js.map +1 -0
  628. package/dist/session-observation-store.d.ts +98 -0
  629. package/dist/session-observation-store.d.ts.map +1 -0
  630. package/dist/session-observation-store.js +155 -0
  631. package/dist/session-observation-store.js.map +1 -0
  632. package/dist/session-observe-runtime.d.ts +123 -0
  633. package/dist/session-observe-runtime.d.ts.map +1 -0
  634. package/dist/session-observe-runtime.js +317 -0
  635. package/dist/session-observe-runtime.js.map +1 -0
  636. package/dist/session-store.d.ts +98 -0
  637. package/dist/session-store.d.ts.map +1 -0
  638. package/dist/session-store.js +116 -0
  639. package/dist/session-store.js.map +1 -0
  640. package/dist/sessions-cmd.d.ts +202 -0
  641. package/dist/sessions-cmd.d.ts.map +1 -0
  642. package/dist/sessions-cmd.js +609 -0
  643. package/dist/sessions-cmd.js.map +1 -0
  644. package/dist/settings-overlay.d.ts +53 -0
  645. package/dist/settings-overlay.d.ts.map +1 -0
  646. package/dist/settings-overlay.js +175 -0
  647. package/dist/settings-overlay.js.map +1 -0
  648. package/dist/setup-prompt.d.ts +34 -0
  649. package/dist/setup-prompt.d.ts.map +1 -0
  650. package/dist/setup-prompt.js +92 -0
  651. package/dist/setup-prompt.js.map +1 -0
  652. package/dist/setup.d.ts +103 -0
  653. package/dist/setup.d.ts.map +1 -0
  654. package/dist/setup.js +283 -0
  655. package/dist/setup.js.map +1 -0
  656. package/dist/shared-grants.d.ts +164 -0
  657. package/dist/shared-grants.d.ts.map +1 -0
  658. package/dist/shared-grants.js +242 -0
  659. package/dist/shared-grants.js.map +1 -0
  660. package/dist/signals.d.ts +55 -0
  661. package/dist/signals.d.ts.map +1 -0
  662. package/dist/signals.js +85 -0
  663. package/dist/signals.js.map +1 -0
  664. package/dist/source-cmd.d.ts +201 -0
  665. package/dist/source-cmd.d.ts.map +1 -0
  666. package/dist/source-cmd.js +684 -0
  667. package/dist/source-cmd.js.map +1 -0
  668. package/dist/source-verdict.d.ts +99 -0
  669. package/dist/source-verdict.d.ts.map +1 -0
  670. package/dist/source-verdict.js +182 -0
  671. package/dist/source-verdict.js.map +1 -0
  672. package/dist/spend-ledger.d.ts +350 -0
  673. package/dist/spend-ledger.d.ts.map +1 -0
  674. package/dist/spend-ledger.js +546 -0
  675. package/dist/spend-ledger.js.map +1 -0
  676. package/dist/stale-run-sweep.d.ts +79 -0
  677. package/dist/stale-run-sweep.d.ts.map +1 -0
  678. package/dist/stale-run-sweep.js +116 -0
  679. package/dist/stale-run-sweep.js.map +1 -0
  680. package/dist/start-attempts.d.ts +385 -0
  681. package/dist/start-attempts.d.ts.map +1 -0
  682. package/dist/start-attempts.js +613 -0
  683. package/dist/start-attempts.js.map +1 -0
  684. package/dist/status-cmd.d.ts +28 -0
  685. package/dist/status-cmd.d.ts.map +1 -0
  686. package/dist/status-cmd.js +116 -0
  687. package/dist/status-cmd.js.map +1 -0
  688. package/dist/stop-follow-up.d.ts +107 -0
  689. package/dist/stop-follow-up.d.ts.map +1 -0
  690. package/dist/stop-follow-up.js +172 -0
  691. package/dist/stop-follow-up.js.map +1 -0
  692. package/dist/surface-lease.d.ts +118 -0
  693. package/dist/surface-lease.d.ts.map +1 -0
  694. package/dist/surface-lease.js +485 -0
  695. package/dist/surface-lease.js.map +1 -0
  696. package/dist/sync.d.ts +54 -0
  697. package/dist/sync.d.ts.map +1 -0
  698. package/dist/sync.js +93 -0
  699. package/dist/sync.js.map +1 -0
  700. package/dist/syncing-ledger.d.ts +45 -0
  701. package/dist/syncing-ledger.d.ts.map +1 -0
  702. package/dist/syncing-ledger.js +53 -0
  703. package/dist/syncing-ledger.js.map +1 -0
  704. package/dist/temp-fixtures.d.ts +68 -0
  705. package/dist/temp-fixtures.d.ts.map +1 -0
  706. package/dist/temp-fixtures.js +38 -0
  707. package/dist/temp-fixtures.js.map +1 -0
  708. package/dist/token-window.d.ts +84 -0
  709. package/dist/token-window.d.ts.map +1 -0
  710. package/dist/token-window.js +143 -0
  711. package/dist/token-window.js.map +1 -0
  712. package/dist/tool-bridge.d.ts +177 -0
  713. package/dist/tool-bridge.d.ts.map +1 -0
  714. package/dist/tool-bridge.js +329 -0
  715. package/dist/tool-bridge.js.map +1 -0
  716. package/dist/tool-call-record.d.ts +197 -0
  717. package/dist/tool-call-record.d.ts.map +1 -0
  718. package/dist/tool-call-record.js +276 -0
  719. package/dist/tool-call-record.js.map +1 -0
  720. package/dist/tool-protocol.d.ts +135 -0
  721. package/dist/tool-protocol.d.ts.map +1 -0
  722. package/dist/tool-protocol.js +104 -0
  723. package/dist/tool-protocol.js.map +1 -0
  724. package/dist/tool-worker.d.ts +3 -0
  725. package/dist/tool-worker.d.ts.map +1 -0
  726. package/dist/tool-worker.js +478 -0
  727. package/dist/tool-worker.js.map +1 -0
  728. package/dist/toolchain-mirror.d.ts +86 -0
  729. package/dist/toolchain-mirror.d.ts.map +1 -0
  730. package/dist/toolchain-mirror.js +307 -0
  731. package/dist/toolchain-mirror.js.map +1 -0
  732. package/dist/trusted-git.d.ts +243 -0
  733. package/dist/trusted-git.d.ts.map +1 -0
  734. package/dist/trusted-git.js +920 -0
  735. package/dist/trusted-git.js.map +1 -0
  736. package/dist/ui.d.ts +76 -0
  737. package/dist/ui.d.ts.map +1 -0
  738. package/dist/ui.js +118 -0
  739. package/dist/ui.js.map +1 -0
  740. package/dist/untrusted-exec.d.ts +275 -0
  741. package/dist/untrusted-exec.d.ts.map +1 -0
  742. package/dist/untrusted-exec.js +479 -0
  743. package/dist/untrusted-exec.js.map +1 -0
  744. package/dist/untrusted-gate.d.ts +73 -0
  745. package/dist/untrusted-gate.d.ts.map +1 -0
  746. package/dist/untrusted-gate.js +226 -0
  747. package/dist/untrusted-gate.js.map +1 -0
  748. package/dist/uploader.d.ts +153 -0
  749. package/dist/uploader.d.ts.map +1 -0
  750. package/dist/uploader.js +204 -0
  751. package/dist/uploader.js.map +1 -0
  752. package/dist/verifier.d.ts +66 -0
  753. package/dist/verifier.d.ts.map +1 -0
  754. package/dist/verifier.js +170 -0
  755. package/dist/verifier.js.map +1 -0
  756. package/dist/verify-apply.d.ts +26 -0
  757. package/dist/verify-apply.d.ts.map +1 -0
  758. package/dist/verify-apply.js +36 -0
  759. package/dist/verify-apply.js.map +1 -0
  760. package/dist/verify-cmd.d.ts +44 -0
  761. package/dist/verify-cmd.d.ts.map +1 -0
  762. package/dist/verify-cmd.js +125 -0
  763. package/dist/verify-cmd.js.map +1 -0
  764. package/dist/verify-pass.d.ts +73 -0
  765. package/dist/verify-pass.d.ts.map +1 -0
  766. package/dist/verify-pass.js +103 -0
  767. package/dist/verify-pass.js.map +1 -0
  768. package/dist/welcome.d.ts +43 -0
  769. package/dist/welcome.d.ts.map +1 -0
  770. package/dist/welcome.js +116 -0
  771. package/dist/welcome.js.map +1 -0
  772. package/dist/workspace-provisioner.d.ts +65 -0
  773. package/dist/workspace-provisioner.d.ts.map +1 -0
  774. package/dist/workspace-provisioner.js +66 -0
  775. package/dist/workspace-provisioner.js.map +1 -0
  776. package/dist/workspace-quarantine.d.ts +64 -0
  777. package/dist/workspace-quarantine.d.ts.map +1 -0
  778. package/dist/workspace-quarantine.js +156 -0
  779. package/dist/workspace-quarantine.js.map +1 -0
  780. package/dist/workspace-residue-sweep.d.ts +113 -0
  781. package/dist/workspace-residue-sweep.d.ts.map +1 -0
  782. package/dist/workspace-residue-sweep.js +158 -0
  783. package/dist/workspace-residue-sweep.js.map +1 -0
  784. package/dist/workspace.d.ts +639 -0
  785. package/dist/workspace.d.ts.map +1 -0
  786. package/dist/workspace.js +2026 -0
  787. package/dist/workspace.js.map +1 -0
  788. package/package.json +52 -4
  789. package/win/CredProbe.cs +64 -0
  790. package/win/FakeClaude.cs +56 -0
  791. package/win/HandleProbe.cs +116 -0
  792. package/win/TokenProbe.cs +53 -0
  793. package/win/appcontainer.ps1 +500 -0
@@ -0,0 +1,1751 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SKIP_REFUSED_REASON = exports.PLACEMENT_WAIT_MS = exports.CLI_VERSION = void 0;
4
+ exports.pairCommand = pairCommand;
5
+ exports.resolveProjectId = resolveProjectId;
6
+ exports.resolveProjectIdWithConsent = resolveProjectIdWithConsent;
7
+ exports.isYes = isYes;
8
+ exports.renderRegisterAsk = renderRegisterAsk;
9
+ exports.nodeRegisterAsk = nodeRegisterAsk;
10
+ exports.reviewRefusedReason = reviewRefusedReason;
11
+ exports.makeCommandHandler = makeCommandHandler;
12
+ exports.daemonCommand = daemonCommand;
13
+ exports.grantOf = grantOf;
14
+ const pursuit_delivery_1 = require("./pursuit-delivery");
15
+ const node_crypto_1 = require("node:crypto");
16
+ const node_path_1 = require("node:path");
17
+ const machine_store_1 = require("./machine-store");
18
+ const server_client_1 = require("./server-client");
19
+ const daemon_1 = require("./daemon");
20
+ const signals_1 = require("./signals");
21
+ const pair_cmd_1 = require("./pair-cmd");
22
+ const redraw_1 = require("./redraw");
23
+ const goal_ownership_1 = require("./goal-ownership");
24
+ const run_launcher_1 = require("./run-launcher");
25
+ const control_file_1 = require("./control-file");
26
+ const command_receipts_1 = require("./command-receipts");
27
+ const ledger_store_1 = require("./ledger-store");
28
+ const stale_run_sweep_1 = require("./stale-run-sweep");
29
+ const run_ending_locator_1 = require("./run-ending-locator");
30
+ const run_lease_1 = require("./run-lease");
31
+ const workspace_1 = require("./workspace");
32
+ const cursor_store_1 = require("./cursor-store");
33
+ const ledger_replay_1 = require("./ledger-replay");
34
+ const goal_runs_1 = require("./goal-runs");
35
+ const goal_store_1 = require("./goal-store");
36
+ const core_1 = require("@looply/core");
37
+ const autonomy_store_1 = require("./autonomy-store");
38
+ const start_attempts_1 = require("./start-attempts");
39
+ const autonomy_tick_1 = require("./autonomy-tick");
40
+ const grant_reconcile_1 = require("./grant-reconcile");
41
+ const run_start_marks_1 = require("./run-start-marks");
42
+ const run_refusal_marks_1 = require("./run-refusal-marks");
43
+ const approval_gate_read_1 = require("./approval-gate-read");
44
+ const explicit_start_gate_1 = require("./explicit-start-gate");
45
+ const approval_resumes_1 = require("./approval-resumes");
46
+ const governed_approvals_1 = require("./governed-approvals");
47
+ const core_2 = require("@looply/core");
48
+ const git_driver_1 = require("./git-driver");
49
+ const setup_prompt_1 = require("./setup-prompt");
50
+ const ui_1 = require("./ui");
51
+ const rails_stores_1 = require("./rails-stores");
52
+ const daemon_lock_1 = require("./daemon-lock");
53
+ const coordinator_1 = require("./coordinator");
54
+ const coordinator_state_1 = require("./coordinator-state");
55
+ const ask_brain_1 = require("./ask-brain");
56
+ const depends_review_1 = require("./depends-review");
57
+ const brain_advice_1 = require("./brain-advice");
58
+ const index_for_head_1 = require("./index-for-head");
59
+ const packet_for_goal_1 = require("./packet-for-goal");
60
+ const project_index_facts_1 = require("./project-index-facts");
61
+ const project_memory_store_1 = require("./project-memory-store");
62
+ const workspace_provisioner_1 = require("./workspace-provisioner");
63
+ const preemptor_1 = require("./preemptor");
64
+ const stop_follow_up_1 = require("./stop-follow-up");
65
+ const programme_mode_1 = require("./programme-mode");
66
+ const reaper_1 = require("./reaper");
67
+ const integrator_1 = require("./integrator");
68
+ const integration_deps_1 = require("./integration-deps");
69
+ const completion_1 = require("./completion");
70
+ const lane_coverage_1 = require("./lane-coverage");
71
+ /**
72
+ * Composition roots for `looply pair` and `looply daemon`.
73
+ *
74
+ * Both need the same three things — a machine identity on disk, a server client
75
+ * carrying its token, and a project id for this repo — so they are assembled
76
+ * here rather than twice.
77
+ */
78
+ exports.CLI_VERSION = '0.1.0';
79
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
80
+ const sha256 = (s) => (0, node_crypto_1.createHash)('sha256').update(s, 'utf8').digest('hex');
81
+ async function pairCommand(opts) {
82
+ const store = new machine_store_1.MachineStore((0, machine_store_1.machineFilePath)());
83
+ const client = new server_client_1.ServerClient({ baseUrl: opts.serverUrl });
84
+ // A repo checked out at a filesystem root has an empty basename, which would
85
+ // send a blank machine name to the server and show as a blank row in the app.
86
+ const machineName = opts.machineName ?? ((0, node_path_1.basename)(opts.cwd) || 'this machine');
87
+ opts.log(`server : ${opts.serverUrl}`);
88
+ opts.log(`machine : ${machineName}`);
89
+ const redrawer = new redraw_1.Redrawer((0, redraw_1.nodeRedrawSink)());
90
+ const result = await (0, pair_cmd_1.runPair)({
91
+ serverUrl: opts.serverUrl,
92
+ machineName,
93
+ store,
94
+ client,
95
+ log: opts.log,
96
+ // Drawn in place. A reissue replaces the box instead of printing a second
97
+ // one below it, so the only code on screen is the one that still works.
98
+ render: (info, secondsLeft) => redrawer.draw((0, pair_cmd_1.renderPairCode)(info, secondsLeft)),
99
+ sleep,
100
+ now: () => Date.now(),
101
+ });
102
+ if (!result.paired) {
103
+ opts.log(`pair : gave up — ${result.reason ?? 'unknown'}`);
104
+ return 1;
105
+ }
106
+ opts.log(' next: `looply daemon` to keep this machine connected.');
107
+ return 0;
108
+ }
109
+ /**
110
+ * Resolve this repo to a server-side project, registering it the first time.
111
+ *
112
+ * The mapping is cached on the machine so a second run does not create a second
113
+ * project row for the same directory, and the server keys on
114
+ * `(machineId, repoPathHash)` so even a lost cache converges rather than
115
+ * duplicating.
116
+ */
117
+ async function resolveProjectId(store, client, machineId, cwd, name) {
118
+ const hash = (0, machine_store_1.repoPathHash)(cwd, sha256);
119
+ const cached = store.projectFor(hash);
120
+ if (cached)
121
+ return cached;
122
+ const r = await client.registerProject({ machineId, name, repoPathHash: hash });
123
+ store.rememberProject(hash, r.project.id);
124
+ return r.project.id;
125
+ }
126
+ /**
127
+ * Resolve this repo to a project, asking before creating one that does not exist.
128
+ *
129
+ * Registration is the one irreversible thing the daemon does on startup, and it
130
+ * used to happen silently: `looply daemon` in the wrong folder — a home
131
+ * directory, a scratch clone, a tarball someone unpacked — put a permanent row
132
+ * on the server that nobody asked for and, until recently, nothing could remove.
133
+ * A directory this machine already knows is never asked about, so the ordinary
134
+ * case is unchanged and the question only appears when something new is about to
135
+ * be created.
136
+ */
137
+ async function resolveProjectIdWithConsent(store, client, machineId, cwd, name, gate) {
138
+ const cached = store.projectFor((0, machine_store_1.repoPathHash)(cwd, sha256));
139
+ if (cached)
140
+ return { kind: 'known', projectId: cached };
141
+ if (!gate.assumeYes) {
142
+ if (!gate.ask)
143
+ return { kind: 'refused', reason: 'no-terminal' };
144
+ if (!(await gate.ask({ cwd, name })))
145
+ return { kind: 'refused', reason: 'declined' };
146
+ }
147
+ return { kind: 'registered', projectId: await resolveProjectId(store, client, machineId, cwd, name) };
148
+ }
149
+ /** Blank means no. The safe answer to "shall I create something permanent" is the default one. */
150
+ function isYes(raw) {
151
+ return /^(y|yes)$/i.test(raw.trim());
152
+ }
153
+ function renderRegisterAsk(info) {
154
+ return `\n${(0, ui_1.box)([
155
+ 'No looply project is registered for this directory:',
156
+ '',
157
+ ` ${(0, ui_1.bold)(info.cwd)}`,
158
+ '',
159
+ (0, ui_1.grey)('Saying yes creates one on the server, called'),
160
+ ` ${(0, ui_1.bold)(info.name)}`,
161
+ (0, ui_1.grey)('and it stays there until somebody deletes it.'),
162
+ ], { title: 'register this directory?', tone: ui_1.volt })}\n`;
163
+ }
164
+ /** The terminal half: print the box, read one answer, put the prompt away. */
165
+ function nodeRegisterAsk(write) {
166
+ return async (info) => {
167
+ write(renderRegisterAsk(info));
168
+ const asker = (0, setup_prompt_1.nodeAsker)();
169
+ try {
170
+ return isYes(await asker.question(` ${(0, ui_1.volt)('›')} ${(0, ui_1.dim)('[y/N]')} `));
171
+ }
172
+ finally {
173
+ asker.close();
174
+ }
175
+ };
176
+ }
177
+ /** How long a person's Start waits for the coordinator to place its goal. */
178
+ exports.PLACEMENT_WAIT_MS = 60_000;
179
+ /**
180
+ * M6-17: WHAT THE DAEMON SAYS WHEN THE PHONE ASKS FOR A VERB THIS BUILD DOES
181
+ * NOT CARRY OUT. The refusal was a bare `refused` — the phone saw the word and
182
+ * nothing else (the audit's I-06), while the review it was tapping on writes
183
+ * one row on the server and reaches no machine (J-03). The kit's plan says the
184
+ * wiring is a later, governed cycle and that the honest first step is the
185
+ * truth on both surfaces: the reason travels on the wire the way M6-10's
186
+ * ceiling refusal does, stored with the receipt, replayed on a redelivery.
187
+ */
188
+ exports.SKIP_REFUSED_REASON = 'skip is not carried out by this build — a run picks its own next item; stop it if the current one should not go on';
189
+ function reviewRefusedReason(kind) {
190
+ return (`${kind} is not carried out by this machine — a review is recorded on the server and reaches no machine yet; ` +
191
+ 'nothing has been pushed or reverted');
192
+ }
193
+ function makeCommandHandler(log, starter, control,
194
+ /**
195
+ * What this machine has already finished with.
196
+ *
197
+ * Optional so every existing caller and test keeps working without one; when
198
+ * present it is what stops a redelivered command being carried out twice.
199
+ */
200
+ receipts,
201
+ /** The project this daemon is for, when it knows. Commands for others are not ours. */
202
+ projectId,
203
+ /**
204
+ * How this machine lets go of a goal's work.
205
+ *
206
+ * Optional like `starter`, and for the same reason: a caller that cannot
207
+ * carry a release out must leave it queued rather than ack something it
208
+ * never did.
209
+ */
210
+ releaser,
211
+ /**
212
+ * Where a grant of autonomy is written down.
213
+ *
214
+ * Optional like the rest, and for the same reason: a daemon with no store to
215
+ * put it in must leave the command queued rather than ack a permission it
216
+ * did not record. A grant that was acked and not stored is the worst of both
217
+ * -- the phone shows it was received, and nothing on this machine knows.
218
+ */
219
+ autonomy,
220
+ /**
221
+ * THE LAST AUTHORITY BOUNDARY BEFORE A RUN EXISTS.
222
+ *
223
+ * Asked immediately before the Start seam, and not a moment earlier, because
224
+ * everything between the question and the spawn is a window in which a person
225
+ * can be asked something. A gate read at the top of `dispatch` would answer
226
+ * about a world that had already moved.
227
+ *
228
+ * Optional like everything beside it: a daemon that cannot ask has no
229
+ * approvals to be held by, and every existing caller and test keeps working
230
+ * without one.
231
+ */
232
+ approvals,
233
+ /**
234
+ * M6-9: WHERE THE COORDINATOR PLACED A GOAL THIS CADENCE, when it has.
235
+ *
236
+ * A person's Start on a placed goal goes to that checkout — the same answer
237
+ * the autonomy tick hands the launcher — and that checkout's lease is the
238
+ * explicit-start carrier. Found live (M6-8): without this the person's run
239
+ * landed in the project checkout, found an empty queue, compiled through the
240
+ * agent and ended `aborted`, while the work it was resumed for sat in the
241
+ * checkout the machine had made for it. Optional so every existing caller is
242
+ * unchanged; absent, or a goal placed nowhere, is today's path.
243
+ */
244
+ workspaceFor,
245
+ /**
246
+ * M6-10: the owner's Start ceiling per grant (`machine.programme.limits.starts_per_grant`).
247
+ * The daemon's own ceiling was core's MAX; a declared one lower than that is
248
+ * honoured here — a grant asking for more is refused, naming the limit,
249
+ * never clamped. Absent, core's MAX as before.
250
+ */
251
+ startCeiling,
252
+ /**
253
+ * M6-14: whether the coordinator is still placing a goal (see
254
+ * {@link PlacementInProgress}). Optional so every existing caller is
255
+ * unchanged; absent, a goal placed nowhere goes to the project checkout as
256
+ * before.
257
+ */
258
+ placing) {
259
+ // When each waiting Start was first seen, by command id — so the bound is
260
+ // measured from the first poll that found the goal being placed.
261
+ const waitingSince = new Map();
262
+ return async (command) => {
263
+ // A machine can host several projects, and `pop` scopes only to the
264
+ // machine. Without this a daemon running in project A would faithfully
265
+ // carry out a start-run meant for project B — spawning a run in the wrong
266
+ // repository. Left for the daemon that owns it rather than refused.
267
+ if (projectId !== undefined && command.projectId !== projectId) {
268
+ log(`daemon : ${command.kind} is for another project here — leaving it queued.`);
269
+ return 'retry';
270
+ }
271
+ // Already finished with. The lease cannot answer this: it stops existing
272
+ // when the run ends, so a start-run redelivered after its run completed
273
+ // would find a free tree and start a second one.
274
+ const priorReceipt = receipts?.seen(command.id);
275
+ if (priorReceipt) {
276
+ log(`daemon : ${command.kind} was already carried out here (${priorReceipt.outcome}).`);
277
+ // The SAME answer as the first time, reason included. A redelivery is a
278
+ // repeated question, not a new one, and answering it with a barer version
279
+ // of the original would make a lost ack quietly cost the explanation.
280
+ return priorReceipt.outcome === 'refused' && priorReceipt.reason !== undefined
281
+ ? { disposition: 'refused', reason: priorReceipt.reason }
282
+ : priorReceipt.outcome;
283
+ }
284
+ // An unreadable receipts file means we cannot prove a consequential command
285
+ // has not already run. Refused rather than retried — retrying will not make
286
+ // the file readable — and terminal, so it does not repeat every three
287
+ // seconds for the redelivery window. Deleting the file clears it.
288
+ if (receipts?.status() === 'unreadable' && !(0, command_receipts_1.isRepeatable)(command.kind)) {
289
+ log(`daemon : cannot tell whether ${command.kind} already ran here — refusing it.`);
290
+ log(' .looply/state/commands.json is unreadable; delete it to reset.');
291
+ return 'refused';
292
+ }
293
+ const decision = await dispatch(command);
294
+ const disposition = typeof decision === 'string' ? decision : decision.disposition;
295
+ const reason = typeof decision === 'string' ? undefined : decision.reason;
296
+ // Recorded before the ack, so a crash between the two leaves the receipt
297
+ // rather than losing it: the redelivery that follows is then recognised
298
+ // instead of re-executed. The reason goes down with it for the same
299
+ // reason — a receipt that outlives the ack has to be able to answer as
300
+ // fully as the ack would have.
301
+ if (receipts && disposition !== 'retry') {
302
+ await receipts.record(command.id, command.kind, disposition, reason);
303
+ }
304
+ return decision;
305
+ };
306
+ async function dispatch(command) {
307
+ switch (command.kind) {
308
+ case 'start-run': {
309
+ if (!starter) {
310
+ log('daemon : start-run is not carried out here — leaving it queued.');
311
+ return 'retry';
312
+ }
313
+ // Read defensively: the payload is `unknown` all the way from the
314
+ // phone, stored verbatim as Json, handed back through Redis, and this
315
+ // build may be older or newer than the one that wrote the row.
316
+ const read = (0, run_launcher_1.readStartPayload)(command.payload);
317
+ if (!read.ok) {
318
+ // Refused rather than run, because the other option is starting an
319
+ // UNBOUND run over the whole queue while the phone waits for a goal
320
+ // that will never bind — the failure this reader exists to prevent.
321
+ // Terminal: a durable row will not read any better in three seconds.
322
+ log(`daemon : start-run ${read.problem} — refusing it.`);
323
+ return 'refused';
324
+ }
325
+ /**
326
+ * ASKED HERE, WITH NOTHING BETWEEN THIS AND THE SPAWN.
327
+ *
328
+ * A person tapping Start while a question of theirs is still open would
329
+ * buy a run that meets the same boundary and files nothing new — the
330
+ * request key is derived from the run — so it would spend a Start to
331
+ * arrive exactly where it began. And a person tapping Start after an
332
+ * answer is the ONE thing that resumes a pursuit, so this is also where
333
+ * that answer is picked up.
334
+ *
335
+ * `undefined` when this daemon cannot ask; the run itself still refuses
336
+ * to file anything it cannot get an approval for, so the boundary is
337
+ * never crossed silently.
338
+ */
339
+ // M6-9: a goal the coordinator has placed is started where it was placed.
340
+ const placed = read.start.goalId === undefined ? undefined : workspaceFor?.(read.start.goalId);
341
+ // M6-14: a goal the coordinator is still placing — its checkout asked
342
+ // for or made, the placement not yet recorded — is not a goal placed
343
+ // nowhere. The Start waits for the placement rather than running in
344
+ // the project checkout; past the bound it is refused, with the reason.
345
+ if (placed === undefined && read.start.goalId !== undefined && placing?.placing(read.start.goalId) === true) {
346
+ const now = placing.now?.() ?? Date.now();
347
+ const since = waitingSince.get(command.id) ?? now;
348
+ waitingSince.set(command.id, since);
349
+ const waitMs = placing.waitMs ?? exports.PLACEMENT_WAIT_MS;
350
+ if (now - since < waitMs) {
351
+ log(`daemon : start-run for ${read.start.goalId} waits for the coordinator to place it`);
352
+ return 'retry';
353
+ }
354
+ waitingSince.delete(command.id);
355
+ const reason = `the coordinator did not place ${read.start.goalId} within ${Math.round(waitMs / 1000)} s — ` +
356
+ 'try again, or turn the coordinator off to start it here';
357
+ log(`daemon : start-run refused — ${reason}`);
358
+ return { disposition: 'refused', reason };
359
+ }
360
+ waitingSince.delete(command.id);
361
+ if (placed !== undefined) {
362
+ log(`daemon : start-run for ${read.start.goalId} goes to the checkout the coordinator placed it in — ${placed}`);
363
+ }
364
+ const decision = approvals === undefined ? {} : await approvals(read.start, command.id, placed);
365
+ if (decision.refuse) {
366
+ log(`daemon : start-run refused — ${decision.refuse.reason}`);
367
+ // Terminal for a definite answer, queued for one nobody obtained. A
368
+ // person whose approval is still open taps Start again when they have
369
+ // decided; a machine that could not reach the server may reach it on
370
+ // the next poll.
371
+ return decision.refuse.terminal
372
+ ? { disposition: 'refused', reason: decision.refuse.reason }
373
+ : 'retry';
374
+ }
375
+ // The command IS the request here, and says so. `requestId` is what
376
+ // every durable record is keyed by; `origin` is what the machine
377
+ // consults when it needs to know whether a person is actually behind
378
+ // this Start. For a human command the two carry the same string, which
379
+ // is exactly what keeps every existing lease, mark and receipt readable.
380
+ const started = await starter.start({
381
+ ...read.start,
382
+ requestId: command.id,
383
+ origin: { kind: 'human-command', commandId: command.id },
384
+ ...(placed === undefined ? {} : { workspace: placed }),
385
+ ...(decision.governedApprovalId === undefined
386
+ ? {}
387
+ : { governedApprovalId: decision.governedApprovalId }),
388
+ ...(decision.resumedFromApprovalId === undefined
389
+ ? {}
390
+ : { resumedFromApprovalId: decision.resumedFromApprovalId }),
391
+ });
392
+ if (!started.started) {
393
+ // A refusal that retrying cannot fix — something is already working
394
+ // here, or we cannot prove otherwise. Left queued it would be offered
395
+ // again every three seconds for the whole redelivery window, which is
396
+ // noise rather than resilience.
397
+ if ('terminal' in started) {
398
+ log(`daemon : start-run refused — ${started.reason}`);
399
+ // The run's own words, carried through. This is the line the phase
400
+ // exists for: without the reason, "this goal's work is done" and
401
+ // "nothing here can move without you" both reached the phone as a
402
+ // bare `refused`, which is the same as saying nothing.
403
+ return { disposition: 'refused', reason: started.reason };
404
+ }
405
+ if ('waiting' in started) {
406
+ // A child is going and has not proven a Run yet. Left queued on
407
+ // purpose: acking here is precisely the lie this phase removed, and
408
+ // the launcher will not spawn a second child for the same request.
409
+ log(`daemon : ${started.reason} — waiting for it to prove it before acking.`);
410
+ return 'retry';
411
+ }
412
+ log(`daemon : start-run failed — ${started.reason}. Leaving it queued.`);
413
+ return 'retry';
414
+ }
415
+ // Reached only with durable proof that this command's run crossed the
416
+ // start boundary. The run id comes from that proof, so it names the run
417
+ // the phone's tap actually produced rather than one we assume.
418
+ log(`daemon : run confirmed started${started.runId === undefined ? '' : ` (${started.runId})`}${started.pid === undefined ? '' : ` (pid ${started.pid})`}${read.start.goalId === undefined ? '' : ` for goal ${read.start.goalId}`}${read.start.hours === undefined ? '' : `, ${read.start.hours}h budget`}${started.already ? ' — already in progress here' : ''}.`);
419
+ return 'done';
420
+ }
421
+ case 'release-goal-items': {
422
+ if (!releaser) {
423
+ log('daemon : release-goal-items is not carried out here — leaving it queued.');
424
+ return 'retry';
425
+ }
426
+ const named = releaseOf(command.payload);
427
+ if (named === null) {
428
+ // The server builds this payload itself and puts exactly one field in
429
+ // it, so an unreadable one is a build that disagrees with the server
430
+ // rather than a transient fault. Terminal.
431
+ log('daemon : release-goal-items names a goal this build cannot read — refusing it.');
432
+ return 'refused';
433
+ }
434
+ const out = await releaser.release(named.goalId, named.goalVersion);
435
+ if ('busy' in out) {
436
+ // Left queued on purpose: the queue file is shared with a live run,
437
+ // and this is the one thing that must not race it.
438
+ log(`daemon : ${out.busy}.`);
439
+ return 'retry';
440
+ }
441
+ log(`daemon : goal ${named.goalId} let go of ${out.released} item(s)` +
442
+ (out.heldByARun ? `, ${out.heldByARun} still being worked on` : '') +
443
+ (out.keptAsRecord ? `, ${out.keptAsRecord} finished and kept` : '') +
444
+ '.');
445
+ return 'done';
446
+ }
447
+ case 'authorize-goal-autonomy': {
448
+ // WRITTEN DOWN, AND NOTHING ELSE.
449
+ //
450
+ // Not a Start. `start-run` authorizes one run and this authorizes
451
+ // several, but the acts are separate on purpose: turning a grant into
452
+ // a Start here would make the grant indistinguishable from the tap
453
+ // that begins work, and the whole point of a bounded permission is
454
+ // that the machine decides WHEN to spend it, later, under its own
455
+ // budget. Nothing in 6D1R spends one; there is no controller yet.
456
+ if (!autonomy) {
457
+ log('daemon : authorize-goal-autonomy is not carried out here — leaving it queued.');
458
+ return 'retry';
459
+ }
460
+ const ceiling = startCeiling ?? core_1.MAX_AUTONOMOUS_STARTS;
461
+ const asked = askedStarts(command.payload);
462
+ if (asked !== null && asked > ceiling) {
463
+ log(`daemon : the grant asks for ${asked} Start(s) and this machine's programme limit is ${ceiling} — refusing it.`);
464
+ return {
465
+ disposition: 'refused',
466
+ reason: `this machine's programme limit is ${ceiling} Start(s) per grant; the grant asked for ${asked}`,
467
+ };
468
+ }
469
+ const grant = grantOf(command.payload, ceiling);
470
+ if (grant === null) {
471
+ // The server builds this payload itself, so an unreadable one is a
472
+ // build that disagrees with the server rather than a transient
473
+ // fault. Terminal, with a reason the phone can show.
474
+ log('daemon : the grant is in a shape this build cannot read — refusing it.');
475
+ return {
476
+ disposition: 'refused',
477
+ reason: 'this machine could not read the terms of that permission',
478
+ };
479
+ }
480
+ try {
481
+ // Idempotent by controllerId. A redelivery of the same grant writes
482
+ // nothing at all; a controllerId already held whose TERMS have moved
483
+ // is refused rather than adopted, because from 6D1R-B one
484
+ // controllerId is one act of permission forever and a second
485
+ // authorization arrives as an id this machine has never seen.
486
+ await autonomy.adopt(grant);
487
+ }
488
+ catch (e) {
489
+ // NOT EVERY FAILURE IS AN ANSWER. A refusal is durable and the person
490
+ // has to notice it and authorize again; a locked file is neither
491
+ // their fault nor a lasting fact. Adopt is idempotent by
492
+ // controllerId, so leaving it queued costs nothing and the redelivery
493
+ // writes nothing if the first one did land.
494
+ if (e.transient === true) {
495
+ log('daemon : the grant could not be recorded just now — leaving it queued.');
496
+ return 'retry';
497
+ }
498
+ log(`daemon : the grant could not be recorded — refusing it.`);
499
+ return { disposition: 'refused', reason: e.message };
500
+ }
501
+ log(`daemon : autonomy recorded for goal ${grant.goalId} at version ` +
502
+ `${grant.goalVersion} — up to ${grant.maxStarts} Start(s), until ${grant.expiresAt}. ` +
503
+ 'Nothing is started by this.');
504
+ return 'done';
505
+ }
506
+ case 'pause':
507
+ case 'resume':
508
+ case 'stop': {
509
+ if (!control) {
510
+ log(`daemon : ${command.kind} is not carried out here — leaving it queued.`);
511
+ return 'retry';
512
+ }
513
+ // The run is a separate, detached process (see run-launcher.ts), so the
514
+ // request goes on disk and the runner picks it up between rounds. It is
515
+ // written even when no run is in progress: the phone's intent outlives
516
+ // the moment, and a pause that only lands if a round happens to be
517
+ // running is a pause you cannot trust.
518
+ await control.request(command.kind, command.actor);
519
+ log(command.kind === 'pause'
520
+ ? 'daemon : pause written — the current round finishes, then the run holds.'
521
+ : command.kind === 'resume'
522
+ ? 'daemon : resume written — a holding run picks up its next round.'
523
+ : 'daemon : stop written — the run ends after the current round.');
524
+ return 'done';
525
+ }
526
+ case 'scan': {
527
+ if (!starter?.scan) {
528
+ log('daemon : scan is not carried out here — leaving it queued.');
529
+ return 'retry';
530
+ }
531
+ const scan = await starter.scan();
532
+ if (!scan.started) {
533
+ log(`daemon : scan failed — ${scan.reason}. Leaving it queued.`);
534
+ return 'retry';
535
+ }
536
+ log(scan.already
537
+ ? 'daemon : scan received, but a scan is already running here.'
538
+ : `daemon : scan started${scan.pid === undefined ? '' : ` (pid ${scan.pid})`} — its passes stream to your phone as they finish.`);
539
+ return 'done';
540
+ }
541
+ // The three verbs this build does not implement. `refused` rather than
542
+ // `retry`, and the distinction is now load-bearing: commands are really
543
+ // redelivered until they reach a terminal state, so leaving these unacked
544
+ // would have the server offer them again every three seconds for the life
545
+ // of the machine. Refusing ends them, and says why.
546
+ case 'skip':
547
+ log(`daemon : skip is not carried out by this version — refusing it: ${exports.SKIP_REFUSED_REASON}.`);
548
+ return { disposition: 'refused', reason: exports.SKIP_REFUSED_REASON };
549
+ case 'approve':
550
+ case 'undo': {
551
+ // M6-17: the reason on the wire, not only in this log.
552
+ const reason = reviewRefusedReason(command.kind);
553
+ log(`daemon : ${command.kind} is not carried out by this version — refusing it: ${reason}.`);
554
+ return { disposition: 'refused', reason };
555
+ }
556
+ default:
557
+ // A newer phone talking to an older daemon. Refused rather than left
558
+ // queued: the tap will be stale long before anyone upgrades the CLI,
559
+ // and an instruction nothing here can dispatch is better ended with a
560
+ // reason than offered forever.
561
+ log(`daemon : unrecognised command ${String(command.kind)} — refusing it.`);
562
+ return 'refused';
563
+ }
564
+ }
565
+ }
566
+ async function daemonCommand(opts) {
567
+ const store = new machine_store_1.MachineStore((0, machine_store_1.machineFilePath)());
568
+ const identity = store.load();
569
+ if (!identity) {
570
+ opts.log('daemon : this machine has not paired. Run `looply pair` first.');
571
+ return 1;
572
+ }
573
+ const client = new server_client_1.ServerClient({ baseUrl: identity.serverUrl }).withToken(identity.token);
574
+ let projectId;
575
+ try {
576
+ const resolved = await resolveProjectIdWithConsent(store, client, identity.machineId, opts.cwd, opts.config.project.name, opts.gate ?? {});
577
+ if (resolved.kind === 'refused') {
578
+ opts.log(resolved.reason === 'declined'
579
+ ? 'daemon : nothing registered, nothing started. Run it inside the project you meant.'
580
+ : 'daemon : this directory is not a registered project and there is no terminal to ask on. `looply daemon --yes` registers it.');
581
+ return 1;
582
+ }
583
+ projectId = resolved.projectId;
584
+ }
585
+ catch (e) {
586
+ if (e instanceof server_client_1.ServerError && e.isAuth) {
587
+ opts.log('daemon : this machine\'s access was revoked. Run `looply pair` again.');
588
+ return 1;
589
+ }
590
+ if (e instanceof server_client_1.ServerError && e.isOffline) {
591
+ // Registration is the one thing that genuinely needs the server, so this
592
+ // is a refusal rather than a degraded start — a daemon with no project id
593
+ // has nothing to attach its events to.
594
+ opts.log(`daemon : cannot reach ${identity.serverUrl} to register this project.`);
595
+ return 1;
596
+ }
597
+ throw e;
598
+ }
599
+ opts.log(`machine : ${identity.machineId}`);
600
+ opts.log(`project : ${projectId}`);
601
+ opts.log(`server : ${identity.serverUrl}`);
602
+ opts.log(`mode : ${(0, programme_mode_1.describeProgramme)(opts.config)}`);
603
+ /**
604
+ * 🔴 BOTH CALLBACKS, BECAUSE NEITHER WAS EVER WIRED HERE.
605
+ *
606
+ * The RUN wires both correctly and says what it is doing. The daemon
607
+ * constructed its watcher with neither, so a first Ctrl-C printed NOTHING —
608
+ * a person saw a daemon that appeared to ignore them, pressed again, saw
609
+ * nothing again, and needed a third press with no explanation for any of it.
610
+ *
611
+ * ⚠️ AND THE FIRST PRESS IS STILL GRACEFUL. The daemon holds delivery state
612
+ * — cursors, receipts, the outbound buffer — and killing it mid-flush loses
613
+ * the tail of a night's ledger. The distinction between the two presses is
614
+ * the whole reason to say anything at all.
615
+ */
616
+ const signals = new signals_1.SignalWatcher({
617
+ proc: (0, signals_1.nodeSignalSource)(),
618
+ onGraceful: () => {
619
+ opts.log('');
620
+ opts.log('daemon : stopping after this tick. Press Ctrl-C again to stop now.');
621
+ },
622
+ onForce: () => {
623
+ opts.log('daemon : stopping now — anything not yet shipped stays on disk for the next start.');
624
+ },
625
+ });
626
+ signals.install();
627
+ const receipts = new command_receipts_1.CommandReceipts((0, node_path_1.join)(opts.cwd, git_driver_1.STATE_DIR, 'state'), ledger_store_1.nodeLedgerFs);
628
+ if ((await receipts.open()) === 'unreadable') {
629
+ opts.log('daemon : .looply/state/commands.json is unreadable — commands that');
630
+ opts.log(' spawn work will be refused until it is deleted.');
631
+ }
632
+ // The standing daemon is the one process that owns delivery state. Its
633
+ // usage line has always said it ships the ledger and until now nothing fed
634
+ // it: no producer was wired, so it flushed an empty buffer forever while a
635
+ // crashed run's tail sat on disk with nothing that would ever look at it.
636
+ const cursors = new cursor_store_1.CursorStore((0, node_path_1.join)(opts.cwd, git_driver_1.STATE_DIR, 'state'), ledger_store_1.nodeLedgerFs);
637
+ if ((await cursors.open()) === 'unreadable') {
638
+ opts.log('daemon : .looply/state/cursor.json is unreadable — replaying from the start.');
639
+ opts.log(' Duplicates are discarded by the server, so this is safe, only slower.');
640
+ }
641
+ // What a previous run left behind, settled before the loop starts.
642
+ //
643
+ // After `projectId` is resolved and before the daemon runs, and both halves
644
+ // of that are load-bearing rather than tidy. After, because a recovery event
645
+ // stamped with anything but the id the server registered is dropped by ingest
646
+ // on arrival — the exact defect `run-identity.spec.ts` guards. Before, so the
647
+ // first replay pass ships what this appends, rather than it waiting a tick.
648
+ //
649
+ // The append goes through the ledger rather than straight into the daemon's
650
+ // buffer on purpose: it is what rewrites `status.json`, so `looply status`
651
+ // locally stops reading `live`, and it survives a daemon that dies before it
652
+ // ever flushes. An `enqueue` would do neither.
653
+ /**
654
+ * Built once and called BOTH at startup and on the loop's sweep.
655
+ *
656
+ * Startup alone was enough while nothing waited on a run: if the last run had
657
+ * been killed, the next daemon start tidied up. It stops being enough the
658
+ * moment something waits for `run.ended` — a SIGKILLed run never writes one,
659
+ * so on a daemon that stays up for a week that run stays un-ended for a week,
660
+ * and whatever is waiting waits for ever.
661
+ *
662
+ * Idempotent by contract: it answers `idle` when there is no lease, when the
663
+ * run is live, or when the ending is already written.
664
+ */
665
+ /**
666
+ * 🔴 EVERY CHECKOUT THIS PROJECT EXECUTES IN, NOT ONLY THIS ONE. The sweep
667
+ * was built over `join(opts.cwd, STATE_DIR, 'state')` alone, so a run the
668
+ * coordinator placed in a linked worktree was never given its ending when it
669
+ * died — see `stale-run-sweep.ts`. The same factory drives the end-to-end
670
+ * proof, so what is proven is what runs here.
671
+ */
672
+ const sweep = (0, stale_run_sweep_1.makeStaleRunSweep)({ cwd: opts.cwd, projectId, log: opts.log });
673
+ /**
674
+ * M6-5 — THE FOLLOW-UP ON A RUN THAT IGNORES ITS STOP (STATE §5 item 16).
675
+ *
676
+ * Same cadence as the sweep, after it: the sweep closes runs that died, this
677
+ * names runs that were asked to stop and began a round anyway — once, in the
678
+ * log and as ONE `alert` on the run's own ledger, which ships to the phone
679
+ * on the next flush. It never ends a run; that stays a person's act.
680
+ */
681
+ const followUpOnStops = (0, stop_follow_up_1.nodeStopFollowUp)({ projectState: (0, git_driver_1.projectStateDir)(opts.cwd), projectId, log: opts.log });
682
+ const recoverStaleRun = async () => {
683
+ await sweep();
684
+ await followUpOnStops();
685
+ };
686
+ await recoverStaleRun();
687
+ // The server's goals, mirrored locally. Loaded before the loop so a daemon
688
+ // that starts offline still has whatever the last one saw, rather than an
689
+ // empty mirror it cannot distinguish from "this project has no goals".
690
+ //
691
+ // Read-only, and read by nothing yet: this phase writes the file and stops
692
+ // there. A goal is not consulted to decide what work to do until a later one.
693
+ const goals = new goal_store_1.GoalStore((0, git_driver_1.projectStateDir)(opts.cwd), ledger_store_1.nodeLedgerFs, opts.log);
694
+ await goals.load();
695
+ // ───────────────────────────────────────────────────────────────────────────
696
+ // THE AUTONOMY GRAPH, BUILT ONCE.
697
+ //
698
+ // Every authority the controller consults is the real one: the grant store,
699
+ // the attempt claims, the durable start and refusal marks, the ledger's
700
+ // run-ended reader, and the machine-authenticated server. Nothing here is a
701
+ // stub and nothing defaults to "authorized" — a controller that could not
702
+ // reach the server must not be able to spend a person's Start on the strength
703
+ // of a fallback.
704
+ /**
705
+ * 🔴 THE AUTHORITY'S WRITER AND ITS READERS DISAGREED ABOUT WHERE IT IS.
706
+ *
707
+ * This was `join(opts.cwd, STATE_DIR, 'state')` — a path built from the
708
+ * literal working directory — while `bin.ts`, the run and the control store
709
+ * all use `projectStateDir`, which resolves the REPOSITORY. The two agree
710
+ * only when the daemon is started from the repository root of a main
711
+ * worktree, which is why every measurement of it agreed.
712
+ *
713
+ * Start the daemon from `packages/cli`, or from a linked worktree, and the
714
+ * grants, the attempt claims and the start marks are written somewhere no
715
+ * reader looks: a controller re-spends a Start it already spent, and the run
716
+ * that did happen is invisible to the daemon that launched it.
717
+ */
718
+ const stateDir = (0, git_driver_1.projectStateDir)(opts.cwd);
719
+ const readRunEnded_ = (0, run_ending_locator_1.makeRunEndedReader)({ cwd: opts.cwd });
720
+ // ONE control file, read by the autonomy tick and written by the command
721
+ // handler. The same path `looply run` reads: the daemon, the run and the
722
+ // controller are three processes over one repo, and this file is all they
723
+ // share about whether a person has asked the machine to hold.
724
+ // PROJECT-SCOPED: see `projectStateDir`. The run reads the same path.
725
+ // PROJECT-SCOPED: see `projectStateDir`. The run reads the same path.
726
+ const projectState = (0, git_driver_1.projectStateDir)(opts.cwd);
727
+ const controlFile = new control_file_1.ControlStore(projectState, ledger_store_1.nodeLedgerFs);
728
+ /**
729
+ * 🔴 THE PHONE'S BRAKE REACHED ONE RUN IN N, and this is the path that matters
730
+ * most — nobody is standing at the terminal to notice that two of three runs
731
+ * kept going.
732
+ *
733
+ * Writing one shared `control.json` is not enough once there are several
734
+ * executions: the FIRST to reach a round boundary consumes it by writing
735
+ * `resume` over it, and every other one then reads `resume` and carries on.
736
+ * `looply stop` had already been taught to write an addressed request per live
737
+ * execution; this had not, and had a different code path besides.
738
+ *
739
+ * ⚠️ WRITING ONLY. Reading stays on `controlFile` above, because the autonomy
740
+ * tick asks a different question — "has a person asked this machine to hold" —
741
+ * of the one file that answers for the project.
742
+ */
743
+ const controlWriter = {
744
+ request: async (kind, actor) => {
745
+ const live = new workspace_1.RunRegistry(projectState).active();
746
+ const out = await (0, workspace_1.requestControlEverywhere)(projectState, live, kind, actor ?? 'phone', ledger_store_1.nodeLedgerFs, opts.cwd);
747
+ /**
748
+ * ⚠️ AND SAY WHICH RUNS IT MISSED. The reach report was discarded, so a
749
+ * `stop` that could not be written into one execution's state directory
750
+ * looked exactly like one that reached them all — on the path where
751
+ * nobody is at a terminal to notice.
752
+ */
753
+ if (out.unreachable.length > 0) {
754
+ opts.log(`daemon : could not reach ${out.unreachable.join(', ')} — their state could not be written.`);
755
+ }
756
+ return { kind, requestedAt: new Date().toISOString(), ...(actor === undefined ? {} : { actor }) };
757
+ },
758
+ };
759
+ // Advances once per autonomy cadence so the scan does not always begin at
760
+ // the same grant. Fairness only: it decides who is ASKED first, never what
761
+ // the answer is.
762
+ //
763
+ // SEEDED, not zero. The scan looks at a bounded window, and a window that
764
+ // always opened at the same place would leave a machine holding more grants
765
+ // than the window is wide with a far controller it never reached — one
766
+ // starvation traded for another. Advancing solves that for a daemon that
767
+ // stays up; seeding solves it for one that does not, because two runs of a
768
+ // crash-looping daemon then open on different grants instead of the same one
769
+ // for ever.
770
+ //
771
+ // The clock is a fine source here precisely because this decides nothing:
772
+ // correctness does not depend on the order, only on every grant eventually
773
+ // getting a turn.
774
+ let autonomyTurn = Date.now();
775
+ // The same, for which accepted dependency edge a cadence raises first (`fileReviewed`).
776
+ let edgeTurn = Date.now();
777
+ const autonomyStore = new autonomy_store_1.AutonomyStore(stateDir, autonomy_store_1.nodeAutonomyFs);
778
+ const attempts = new start_attempts_1.StartAttempts(stateDir, start_attempts_1.nodeAttemptFs);
779
+ /**
780
+ * 🔴 PROJECT-SCOPED, BECAUSE THE RUN THAT WRITES THEM MAY BE IN ANOTHER
781
+ * CHECKOUT — AND THE CONTROLLER READS THESE, not the launcher's copy.
782
+ *
783
+ * `startMarkOf` is what `autonomy-controller` uses to decide whether a Start
784
+ * BEGAN. Reading it from the daemon's own execution state meant a
785
+ * coordinator-placed run's mark was never found, so three cadences later
786
+ * every placed goal was escalated with
787
+ *
788
+ * Start 1 was attempted 3 times and never began
789
+ *
790
+ * while its runs had started, worked and finished perfectly. The launcher's
791
+ * `provenStart` had the identical bug and was fixed with it; both readers
792
+ * have to agree with the writer, and the writer is a run that may be
793
+ * anywhere in this project.
794
+ */
795
+ const marksDir = (0, git_driver_1.projectStateDir)(opts.cwd);
796
+ const startMarks = new run_start_marks_1.RunStartMarks(marksDir, ledger_store_1.nodeLedgerFs);
797
+ const refusalMarks = new run_refusal_marks_1.RunRefusalMarks(marksDir, ledger_store_1.nodeLedgerFs);
798
+ // Which of a person's answers have already been handed to a run. Read by the
799
+ // approval gate below and written at the Start boundary, by exclusive create,
800
+ // so two daemons over one project cannot both give out the same answer.
801
+ const resumes = new approval_resumes_1.ApprovalResumes(stateDir, start_attempts_1.nodeAttemptFs);
802
+ // Which approved action each Start took responsibility for. Written here, at
803
+ // the Start boundary, and read by the run to derive the claim key it must
804
+ // present — including after a crash, which is why it is durable at all.
805
+ const governed = new governed_approvals_1.GovernedApprovals(stateDir, start_attempts_1.nodeAttemptFs);
806
+ /**
807
+ * The safety history. Constructed once; RE-OPENED on every consultation.
808
+ *
809
+ * The observations are written by the RUN process, so a snapshot taken at
810
+ * startup is a fact about a moment that has passed — the same reason the
811
+ * approval gate is read from the server every tick rather than from anything
812
+ * this machine wrote down.
813
+ */
814
+ // MACHINE-SCOPED, and it must be the SAME directory the run raises into,
815
+ // or every hold this gate reads is one no run ever wrote. Both go through
816
+ // `railsSafetyStore`, which is what keeps that true by construction.
817
+ /**
818
+ * THE COORDINATOR LOCK, ONCE IT HAS BEEN TAKEN.
819
+ *
820
+ * 🔴 IT WAS TAKEN ONCE AND NEVER LOOKED AT AGAIN. The daemon claimed it
821
+ * before `run()` and nothing consulted it afterwards, so if the file was
822
+ * removed — a person tidying `.looply`, a disk that filled and was cleared,
823
+ * a second daemon sweeping what it judged litter — this daemon carried on
824
+ * scheduling with no lock at all, for the rest of its life, silently. The
825
+ * whole point of the lock is that exactly one coordinator schedules.
826
+ *
827
+ * Null until it is held, so a tick before the claim schedules nothing.
828
+ */
829
+ let coordinatorLock = null;
830
+ /**
831
+ * WHERE THE COORDINATOR PLACED EACH GOAL THIS CADENCE, and how many starts it
832
+ * decided were free. Both are filled by `prefer` and read by the two deps
833
+ * below it, within one tick.
834
+ *
835
+ * ⚠️ PROCESS-LOCAL AND DELIBERATELY NOT DURABLE. They describe ONE cadence's
836
+ * decision; a restarted daemon recomputes from the registry and the leases,
837
+ * which are the durable facts. Anything that outlived the cadence would be a
838
+ * second opinion about capacity, and the registry is the first.
839
+ */
840
+ const placement = new Map();
841
+ /** F28 (corr-3): each goal's controller's last decision, for the next cadence's scheduler. */
842
+ const lastDecision = new Map();
843
+ let freeCapacity = 1;
844
+ /**
845
+ * M4-1 — THE MACHINE MAKES ITS OWN CHECKOUT, when the project asked.
846
+ *
847
+ * `machine.provision_workspaces` is off by default; then `provisioner` is
848
+ * null and the gatherer tells the scheduler so, and a `provision` action
849
+ * cannot even be emitted — every wait is the one it always was. On, the tick
850
+ * may ask for ONE checkout per cadence; it is made after the tick returns
851
+ * (the tick is synchronous by contract), with the same code a person uses,
852
+ * and the next cadence finds it idle and places the goal.
853
+ */
854
+ const provisioner = opts.config.machine?.provisionWorkspaces === true ? (0, workspace_provisioner_1.nodeWorkspaceProvisioner)(opts.cwd) : null;
855
+ let toProvision = null;
856
+ /**
857
+ * M6-14: THE GOAL A CHECKOUT IS BEING MADE FOR, and the checkouts made but
858
+ * not yet placed. `provisioningFor` is set beside `toProvision` for the
859
+ * cadence the tick asks; `provisionedFor` holds a goal from the moment its
860
+ * checkout exists until the tick places it (or a minute passes). Between the
861
+ * two the placement map is empty for that goal, and that is the window a
862
+ * person's Start fell into (M6-13).
863
+ */
864
+ let provisioningFor = null;
865
+ const provisionedFor = new Map();
866
+ /**
867
+ * M6-22: IS THIS MACHINE PURSUING THIS GOAL?
868
+ *
869
+ * Asked of the live grants — the same records the coordinator's own gatherer
870
+ * reads — and only while the coordinator is on, because with it off nothing
871
+ * places anything and a person's Start belongs in the project checkout, as it
872
+ * always did. `authorized` is the one state that means "the machine may spend
873
+ * Starts against this", and an expired grant is not live however it is
874
+ * labelled.
875
+ *
876
+ * ⚠️ IT ANSWERS "IS IT BEING PLACED", NOT "WHERE". The placement itself is
877
+ * still `workspaceFor`'s to answer, freshly, every poll; this only decides
878
+ * whether a Start waits for that answer or goes without one.
879
+ */
880
+ const pursuing = (goalId) => {
881
+ if (opts.config.machine?.coordinator !== true)
882
+ return false;
883
+ const now = Date.now();
884
+ return autonomyStore
885
+ .all()
886
+ .some((g) => g.goalId === goalId && g.state === 'authorized' && Date.parse(g.expiresAt) > now);
887
+ };
888
+ /**
889
+ * M4-2 — THE MACHINE ASKS ONE OF ITS OWN RUNS TO STOP, when the project asked.
890
+ *
891
+ * `machine.preemption` is off by default; then `preemptor` is null and a
892
+ * `preempt` decision stays a line in `schedule.jsonl` that nothing acts on,
893
+ * as it always was. On, the first preempt of a tick is answered after the
894
+ * tick with the run's own graceful `stop` request — the one `looply stop`
895
+ * writes — never a kill; only a run the machine started can be named,
896
+ * because the gatherer reports a person's as not preemptible.
897
+ */
898
+ const preemptor = opts.config.machine?.preemption === true ? (0, preemptor_1.nodePreemptor)(projectState, ledger_store_1.nodeLedgerFs) : null;
899
+ let toPreempt = null;
900
+ /**
901
+ * M4-4 — THE MACHINE RETIRES WHAT IT MADE, when the project asked.
902
+ *
903
+ * `machine.reap_provisioned` is off by default; then `reaper` is null and
904
+ * nothing is inspected or removed. On, after the tick and after any stop
905
+ * request, at most ONE checkout per cadence that the machine itself made,
906
+ * idle beyond `idle_checkout_minutes`, referenced by no run and holding no
907
+ * residue, is retired through the manual command's own reaping. A checkout
908
+ * a person cut is never touched.
909
+ */
910
+ const reaper = opts.config.machine?.reapProvisioned === true
911
+ ? (0, reaper_1.nodeReaper)(opts.cwd, projectState, { idleMinutes: opts.config.machine?.idleCheckoutMinutes ?? 60, log: opts.log })
912
+ : null;
913
+ /** M4-4: what the tick placed last cadence — a launch is not an arrival, and the reaper must not race it. */
914
+ let placedBefore = [];
915
+ /**
916
+ * M5-1 — THE MACHINE OFFERS A FINISHED RUN'S BRANCH TO THE LANE, when the project asked.
917
+ *
918
+ * `machine.integrate` is off by default; then `integrator` is null and
919
+ * nothing is listed or offered. On — and only with the lane's own gate,
920
+ * `run.integration`, on — one branch per cadence, after the reaper, goes
921
+ * through the very `integrationTick` the `looply integrate` command calls,
922
+ * with the command's own deps. A fast-forward or a refusal is the lane's
923
+ * decision, on record either way.
924
+ */
925
+ /**
926
+ * RULE #2's READER FOR THE LANE (F18): the goal as the server holds it at the
927
+ * moment a candidate is judged, so the lane can regenerate the integrated
928
+ * receipt (`laneGoalReader`).
929
+ *
930
+ * ⚠️ PROGRAMME MODE ONLY. Absent `currency` is the lane's whole simple-mode
931
+ * guarantee — no receipt computed, no attempt field written — and a simple
932
+ * project keeps it.
933
+ */
934
+ const goalNow =
935
+ // F31 (soun-1/corr-2): the SAME predicate the worker defers its coverage on — slice 1
936
+ // (`machine.programme: true`) included — so no goal closes with coverage judged by nobody.
937
+ !(0, programme_mode_1.programmeIsOn)(opts.config) ? undefined : (0, integration_deps_1.laneGoalReader)(() => client.projectGoals(projectId));
938
+ const integrator = opts.config.machine?.integrate === true ? (0, integrator_1.nodeIntegrator)(opts.cwd, projectState, opts.config, opts.log, goalNow) : null;
939
+ /**
940
+ * M5-2: publication as the THIRD KEY. Asked only when this daemon integrates
941
+ * for itself AND the lane is on — a hold on a lane nobody runs would never
942
+ * lift. Otherwise the tick is handed nothing, and a met goal closes its grant
943
+ * exactly as it did before this existed.
944
+ */
945
+ /**
946
+ * ⚠️ IN PROGRAMME MODE THE THIRD KEY ALSO CARRIES STRICT COVERAGE, judged on the
947
+ * integration tip (F25, `withLaneCoverage`). The worker defers its fourth key to it.
948
+ */
949
+ const thirdKey = integrator !== null && opts.config.run.integration === true
950
+ ? goalNow === undefined
951
+ ? (0, completion_1.nodePublication)(opts.cwd)
952
+ : (0, lane_coverage_1.withLaneCoverage)(
953
+ // F27: a programme's Starts judge the tree the lane holds, so a hold a Start can move buys one.
954
+ (0, completion_1.nodePublication)(opts.cwd, { rejudge: true }), (0, lane_coverage_1.nodeLaneCoverageDeps)(opts.cwd, new git_driver_1.GitDriver((0, git_driver_1.nodeGitExec)(opts.cwd)), client, projectId, goalNow))
955
+ : null;
956
+ const safety = (0, rails_stores_1.railsSafetyStore)(opts.cwd);
957
+ if ((await safety.open()) === 'unreadable') {
958
+ opts.log('daemon : the loop-safety history could not be read — autonomy will hold');
959
+ }
960
+ /**
961
+ * WHETHER A PERSON'S START MAY PROCEED, ASKED OF THE SERVER.
962
+ *
963
+ * The same derivation the controller holds on, at the other boundary. One
964
+ * reading answers both halves: whether to turn this Start away, and which
965
+ * answer — if any — it has just been given the right to carry.
966
+ *
967
+ * A START WITH NO PURSUIT IS NOT GATED. An approval belongs to a goal at a
968
+ * version, so a run that names none can have nothing outstanding; asking
969
+ * would be a request that could only ever come back empty.
970
+ *
971
+ * THE CLAIM IS TAKEN HERE, before the run exists, because that is where two
972
+ * Starts race. Losing it is not a refusal: the other Start has the answer and
973
+ * is about to hold the working tree, so this one carries on as an ordinary
974
+ * Start and meets the lease like any other.
975
+ */
976
+ /**
977
+ * THE DECISION LIVES IN `explicit-start-gate.ts`, and this is the wiring.
978
+ *
979
+ * It was a closure here, over these same five values, which meant it had no
980
+ * export and no test seam — and that is a large part of why it could hand an
981
+ * approval id to `approved` only, while `refusesExplicitStart` admitted three
982
+ * more kinds so that something could go and look. Nothing looked, and the
983
+ * pursuit wedged. Injecting the collaborators costs nothing and makes the
984
+ * wiring answerable to a spec.
985
+ */
986
+ const explicitStartGate = async (start, requestId, workspace) => {
987
+ // The pursuit is resolved HERE and nowhere further in. `decideExplicitStart`
988
+ // takes a gate rather than a way to read one, because the decision does not
989
+ // need to know what a goal is — and `goal-wiring.spec.ts` enforces exactly
990
+ // that confinement across the whole tree.
991
+ if (start.goalId === undefined || start.goalVersion === undefined)
992
+ return {};
993
+ /**
994
+ * A PERSON'S EXPLICIT START IS THE ESCAPE HATCH FROM A SAFETY HOLD, and it
995
+ * is spent HERE — before a run exists, in the same place the governed
996
+ * reservation is taken, because this is where two Starts race.
997
+ *
998
+ * ⚠️ IT IS ONE RETRY, NOT "SAFETY OFF". The incident is marked spent and the
999
+ * observation history is left exactly where it is, so a Run that also
1000
+ * achieves nothing raises a NEW incident. That is the difference between a
1001
+ * person saying "try once more" and a person disabling a brake, and there
1002
+ * is deliberately no flag anywhere that does the second.
1003
+ *
1004
+ * The refusal is RETRYABLE rather than terminal: losing this race means
1005
+ * another Start took the retry, and that Start may yet fail to produce a
1006
+ * run.
1007
+ */
1008
+ if ((await safety.open()) === 'ok') {
1009
+ const incident = safety.activeIncident(start.goalId, start.goalVersion);
1010
+ if (incident !== null) {
1011
+ const spent = await safety.override(incident.id, requestId);
1012
+ if (!spent.ok)
1013
+ return { refuse: { reason: spent.because, terminal: false } };
1014
+ opts.log(`daemon : a person is trying again after ${incident.kind} (${incident.id})`);
1015
+ }
1016
+ }
1017
+ const gate = await (0, approval_gate_read_1.readApprovalGate)(client, {
1018
+ projectId,
1019
+ goalId: start.goalId,
1020
+ goalVersion: start.goalVersion,
1021
+ now: Date.now(),
1022
+ consumed: (ids) => resumes.consumed(ids),
1023
+ });
1024
+ return await (0, explicit_start_gate_1.decideExplicitStart)({
1025
+ governed,
1026
+ resumes,
1027
+ // The SAME durable lease the startup recovery gates on, not a second
1028
+ // notion of liveness. `RunLauncher.isRunning()` would not do: it knows
1029
+ // only about children THIS daemon spawned, so a run left by a previous
1030
+ // one would read as nobody.
1031
+ // M6-9: the placed checkout's lease when the goal was placed, the project's otherwise.
1032
+ carrier: () => new run_lease_1.RunLeaseStore((0, node_path_1.join)(workspace ?? opts.cwd, git_driver_1.STATE_DIR, 'state'), run_lease_1.nodeLeaseFs).inspect(),
1033
+ log: opts.log,
1034
+ }, gate, requestId);
1035
+ };
1036
+ const runStarter = (0, run_launcher_1.nodeRunLauncher)(opts.cwd, stateDir);
1037
+ /**
1038
+ * The brain's tools, built only when the project switched it on (M3-1).
1039
+ * Off — the default — costs nothing: no store opened, no transport built.
1040
+ */
1041
+ const brain = opts.config.run.brain === true ? await (0, brain_advice_1.brainDepsFor)(opts.cwd, opts.config, opts.log) : null;
1042
+ const daemon = new daemon_1.Daemon({
1043
+ client,
1044
+ machineId: identity.machineId,
1045
+ projectId,
1046
+ cliVersion: exports.CLI_VERSION,
1047
+ cursors,
1048
+ goals,
1049
+ recoverStaleRun,
1050
+ /**
1051
+ * One controller transition per cadence.
1052
+ *
1053
+ * The tick itself is bounded: it reads durable facts, asks the server once,
1054
+ * and at most launches ONE detached child before returning. It does not
1055
+ * wait for a run — that would hold the heartbeat for hours.
1056
+ */
1057
+ autonomyTick: async () => {
1058
+ /**
1059
+ * 🔴 THE HEARTBEAT BELONGS TO THE CADENCE, NOT TO THE SCHEDULER —
1060
+ * AND IT WAS REACHABLE ONLY THROUGH A BRANCH THAT IS OFF BY DEFAULT.
1061
+ *
1062
+ * `refresh()` had exactly one call site in the tree: inside `prefer()`,
1063
+ * AFTER `if (coordinator !== true) return []`. Every daemon takes the
1064
+ * lock; only a coordinator daemon ever said it was still there. MEASURED:
1065
+ * a default daemon holds its claim, never refreshes it, and ten minutes
1066
+ * later `heartbeatStale` judges it dead — so a second `looply daemon`
1067
+ * over the same repository takes the lock and both run, which is the one
1068
+ * outcome this lock exists to prevent.
1069
+ *
1070
+ * ⚠️ AND LOSING IT STOPS THE TICK, NOT ONLY THE ORDERING. The hazard is
1071
+ * two processes spending the same Start; refusing to schedule while
1072
+ * continuing to launch would not address it. A daemon that no longer
1073
+ * holds its claim does nothing autonomous until it is restarted.
1074
+ */
1075
+ if (coordinatorLock !== null && !coordinatorLock.refresh()) {
1076
+ opts.log('daemon : this daemon no longer holds this repository’s lock — not acting autonomously.');
1077
+ placement.clear();
1078
+ freeCapacity = 1;
1079
+ return;
1080
+ }
1081
+ const gatherState = () => (0, coordinator_state_1.gatherScheduleState)(opts.cwd, {
1082
+ goals,
1083
+ autonomy: autonomyStore,
1084
+ registry: new workspace_1.RunRegistry(projectState),
1085
+ safety,
1086
+ /**
1087
+ * 🔴 THE MINIMUM OF EVERY CEILING, NOT THE COORDINATOR'S OWN.
1088
+ *
1089
+ * MEASURED by the end-to-end proof: with `max_workspaces: 2` the
1090
+ * scheduler correctly placed two goals in two checkouts and
1091
+ * exactly ONE run ever registered, because
1092
+ * `run.max_concurrent_runs` defaults to 1 and the second was
1093
+ * refused by the slot bound AFTER a workspace was cut, a goal
1094
+ * placed and a process spawned. Planning past a ceiling somebody
1095
+ * else enforces wastes exactly that much work every cadence.
1096
+ */
1097
+ capacity: {
1098
+ maxWorkspaces: (0, core_2.effectiveConcurrency)({
1099
+ workspaces: opts.config.machine?.maxWorkspaces,
1100
+ runs: opts.config.run.maxConcurrentRuns,
1101
+ }),
1102
+ maxAgentsInFlight: (0, core_2.effectiveConcurrency)({
1103
+ agents: opts.config.machine?.maxAgentsInFlight,
1104
+ runs: opts.config.run.maxConcurrentRuns,
1105
+ }),
1106
+ provision: provisioner !== null,
1107
+ },
1108
+ /**
1109
+ * 🔴 WHETHER THIS PROGRAMME INTEGRATES, AND THEREFORE WHETHER
1110
+ * FINISHING LOCALLY IS FINISHING.
1111
+ *
1112
+ * Both halves, the same pair `describeProgramme` reports as off:
1113
+ * the machine must run the integrator AND the run must be allowed to
1114
+ * integrate. With either off there is no lane, nothing is ever
1115
+ * published, and reading "not integrated" as "not satisfied" would
1116
+ * stop every dependent for ever.
1117
+ */
1118
+ integrates: opts.config.machine?.integrate === true && opts.config.run.integration === true,
1119
+ // F28 (corr-3): a grant whose controller is only waiting or holding takes no slot.
1120
+ held: new Set([...lastDecision].filter(([, kind]) => kind === 'wait' || kind === 'hold').map(([goalId]) => goalId)),
1121
+ // F34: in a programme whose brain proposes dependencies, a goal waits for its answer —
1122
+ // F36: an answer that STANDS (accepted); a refused one filed no edge, so it is not advice —
1123
+ // F36c: until the version has had its bounded number of answers (`advisedVersions`).
1124
+ ...(brain !== null && (0, programme_mode_1.programmeIsOn)(opts.config) && opts.config.machine?.coordinator === true
1125
+ ? { advised: (0, ask_brain_1.advisedVersions)(brain.store.all()) }
1126
+ : {}),
1127
+ now: () => Date.now(),
1128
+ });
1129
+ /**
1130
+ * M3-1 — THE BRAIN'S ADVICE FOR THIS CADENCE, ASKED BEFORE THE TICK.
1131
+ *
1132
+ * `run.brain` is off by default, and then `brain` is null and nothing
1133
+ * here runs: the coordinator receives no proposals, exactly as before.
1134
+ * On, every live authorised goal the mirror holds is asked about at most
1135
+ * once per (goal, version, tree, prompt digest) — the record on disk
1136
+ * answers a repeat — and at most ONE new question is put per cadence, so
1137
+ * the heartbeat waits for one model call, never for a fleet of them.
1138
+ * What comes back is advice: `decideSchedule` writes it into a start's
1139
+ * `because` and starts nothing it would not have started without it.
1140
+ * Asked BEFORE the tick because `prefer` is synchronous by contract and
1141
+ * a model is not.
1142
+ */
1143
+ let advice;
1144
+ /**
1145
+ * 🔴 PROPOSED EDGES ARE CARRIED, NOT APPLIED. `dependsVerdict` is the
1146
+ * fence and it needs facts this cadence does not assemble — goal
1147
+ * versions, source versions, the graph. Holding them here rather than
1148
+ * dropping them is the difference between a suggestion that goes nowhere
1149
+ * and one that has somewhere to go next; applying one from a daemon tick
1150
+ * would be a provider's suggestion reaching the graph without passing a
1151
+ * person, which is what the fence and the approval path exist to prevent.
1152
+ */
1153
+ let proposedEdges = [];
1154
+ if (brain !== null && opts.config.machine?.coordinator === true && coordinatorLock !== null) {
1155
+ try {
1156
+ const head = await (0, index_for_head_1.indexForHead)(opts.cwd);
1157
+ if (!head.ok) {
1158
+ opts.log(`brain : not asked this cadence — ${head.because}`);
1159
+ }
1160
+ else {
1161
+ const tick = await (0, ask_brain_1.proposalsForTick)(gatherState().goals, {
1162
+ projectId,
1163
+ treeSha: head.treeSha,
1164
+ facts: (0, project_index_facts_1.factsFromIndex)(head.snapshot, (0, project_memory_store_1.projectKeyFor)(opts.cwd, sha256)).map((d) => `${d.subject}: ${d.content}`),
1165
+ goalFor: (id) => goals.get(id),
1166
+ packetFor: async (goal) => (await (0, packet_for_goal_1.packetForGoal)(goal, (id) => client.sourceDetail(projectId, id),
1167
+ // F19: what the programme's other goals own, so a proposal is made knowing it.
1168
+ (0, programme_mode_1.programmeIsOn)(opts.config) ? goals.all() : [])).packet,
1169
+ }, brain, opts.log, undefined,
1170
+ // F34: in a programme each goal version is asked once.
1171
+ (0, programme_mode_1.programmeIsOn)(opts.config));
1172
+ advice = tick.advice;
1173
+ proposedEdges = tick.edges;
1174
+ }
1175
+ }
1176
+ catch (e) {
1177
+ // The brain is optional; a failure to ask must not take the cadence with it.
1178
+ opts.log(`brain : not asked this cadence — ${e.message}`);
1179
+ }
1180
+ }
1181
+ /**
1182
+ * 🔴 EVERY PROPOSED EDGE IS PUT TO THE FENCE, AND NOTHING IS APPLIED.
1183
+ * `reviewEdges` answers `accepted` when every check a machine can make
1184
+ * passes; the edge is still not in the graph and will not be until
1185
+ * somebody puts it there. Writing one from a cadence would be a
1186
+ * provider's suggestion reaching the graph without passing a person.
1187
+ *
1188
+ * ⚠️ THE READS ARE PAID FOR ONLY WHEN THERE IS SOMETHING TO JUDGE.
1189
+ * `reviewEdges` short-circuits on an empty list before it touches either
1190
+ * reader, so a cadence that produced no edges makes no extra server call
1191
+ * — which is why it is safe to hang a network read off a heartbeat.
1192
+ */
1193
+ if (proposedEdges.length > 0) {
1194
+ try {
1195
+ const review = await (0, depends_review_1.reviewEdges)(proposedEdges, {
1196
+ projectId,
1197
+ goals: async () => goals.all().map((g) => ({ id: g.id, version: g.version, dependsOn: g.dependsOn ?? [], projectId: g.projectId })),
1198
+ sourceShas: async () => new Map((await client.sources(projectId)).sources.map((s) => [s.id, s.sha256])),
1199
+ });
1200
+ (0, depends_review_1.reportReview)(review, opts.log);
1201
+ /**
1202
+ * 🔴 AND THE ACCEPTED ONES ARE PUT TO A PERSON. This is the two-key
1203
+ * rule's first key and nothing more: a machine raises the request,
1204
+ * and a person turns the second. `fileReviewed` files only an
1205
+ * `accepted` verdict — a stale or refused edge must never reach
1206
+ * somebody's phone, because asking about those spends the one thing
1207
+ * this system cannot replace, which is somebody's willingness to read
1208
+ * the NEXT question carefully.
1209
+ *
1210
+ * ⚠️ ONE PER HEARTBEAT, and the rest said out loud. See
1211
+ * `MAX_EDGE_FILINGS_PER_TICK` for why that bound is not hypothetical.
1212
+ */
1213
+ const filing = await (0, depends_review_1.fileReviewed)(review,
1214
+ /**
1215
+ * 🔴 `'system'`, NOT A PERSON AND NOT A RUN ROLE. `Actor` offers
1216
+ * `runner`, `prover`, `keeper`, `system` and `human:<id>`; a cadence
1217
+ * is none of the three run roles, and writing a `human:` here would
1218
+ * be a machine signing somebody's name on the thing they are about
1219
+ * to be asked to approve.
1220
+ */
1221
+ { projectId, requestedBy: 'system' }, { createApproval: (p, input) => client.createApproval(p, input) },
1222
+ // F34: in a programme every accepted edge is filed the cadence it is proposed — a goal must not
1223
+ // start before the edge that holds it exists, and asking once per version keeps it to one per goal.
1224
+ (0, programme_mode_1.programmeIsOn)(opts.config) ? Number.MAX_SAFE_INTEGER : depends_review_1.MAX_EDGE_FILINGS_PER_TICK,
1225
+ // WHOSE TURN IT IS, so an edge already waiting for a decision
1226
+ // cannot hold the one filing a cadence allows for ever. Seeded
1227
+ // and process-local for the reasons `autonomyTurn` is.
1228
+ (edgeTurn += 1));
1229
+ (0, depends_review_1.reportFiling)(filing, opts.log);
1230
+ }
1231
+ catch (e) {
1232
+ /**
1233
+ * ⚠️ A FAILURE HERE IS NOT A FAILURE OF THE CADENCE. The edges stay
1234
+ * on record and the next cadence judges them against a newer world,
1235
+ * which is the right answer for a proposal whose whole verdict
1236
+ * depends on when it is asked.
1237
+ */
1238
+ opts.log(`depends : not judged this cadence — ${e.message}`);
1239
+ }
1240
+ }
1241
+ await (0, autonomy_tick_1.autonomyTick)({
1242
+ // WHOSE TURN IT IS FIRST. Process-local and deliberately not
1243
+ // durable: the ORDER controllers are served in is not a fact about
1244
+ // any of them, so a restarted daemon beginning again costs nothing.
1245
+ // It was never wired at all, which left the scan starting from the
1246
+ // same grant every cadence for the life of the process.
1247
+ rotation: () => (autonomyTurn += 1),
1248
+ /**
1249
+ * 🔴 AND THIS IS WHERE THE DOLLAR CEILING BECAME REAL. `withinEnvelope`
1250
+ * shipped fully specced with no production caller, no config key that
1251
+ * could construct an `Envelope`, and a spend ledger nothing ever read.
1252
+ * `machine.envelope` produces one; the tick enforces it at the moment
1253
+ * the machine is about to spend without asking.
1254
+ *
1255
+ * ⚠️ ABSENT MEANS NO CEILING, which is what every existing project has.
1256
+ */
1257
+ ...(opts.config.machine?.envelope === undefined
1258
+ ? {}
1259
+ : {
1260
+ envelope: () => opts.config.machine?.envelope,
1261
+ estimateUsd: () => opts.config.machine?.envelope?.estimateUsdPerStart,
1262
+ runsInFlight: () => new workspace_1.RunRegistry(projectState).active().length,
1263
+ }),
1264
+ /**
1265
+ * 🔴 AND THIS IS WHERE THE SCHEDULER BECAME REACHABLE.
1266
+ *
1267
+ * `decideSchedule` and `coordinatorTick` were written, specced, and
1268
+ * imported by nothing that ships. Cycles, holds, dependency order and
1269
+ * the anti-starvation age boost all existed and could not affect one
1270
+ * overnight decision, while `coordinator.spec.ts` opened by describing
1271
+ * "the thing wired into the daemon".
1272
+ *
1273
+ * ⚠️ OFF UNLESS THE PROJECT ASKS — see `machine.coordinator`. With it
1274
+ * off this returns nothing and the tick rotates exactly as before.
1275
+ *
1276
+ * ⚠️ AND A TICK THAT REFUSES ORDERS NOTHING. `coordinatorTick` refuses
1277
+ * without the coordinator lock, and a refusal must not be read as "no
1278
+ * goal is worth doing": returning `[]` falls back to the old order,
1279
+ * which is a worse order rather than a wrong one.
1280
+ */
1281
+ prefer: () => {
1282
+ /**
1283
+ * ⚠️ THE PLACEMENT IS CLEARED WHENEVER THIS RETURNS NOTHING, and a
1284
+ * refusal that left it standing was a defect of its own: `prefer`
1285
+ * returned `[]` while `workspaceFor` went on answering last
1286
+ * cadence's checkout and `launchesPerTick` last cadence's number, so
1287
+ * a daemon that had stopped deciding kept acting on a decision it no
1288
+ * longer stood behind.
1289
+ */
1290
+ if (opts.config.machine?.coordinator !== true) {
1291
+ placement.clear();
1292
+ provisionedFor.clear();
1293
+ provisioningFor = null;
1294
+ freeCapacity = 1;
1295
+ return [];
1296
+ }
1297
+ // The lock is refreshed once per cadence in `autonomyTick` above,
1298
+ // which reaches a daemon in every mode; by here it is known held.
1299
+ if (coordinatorLock === null) {
1300
+ placement.clear();
1301
+ provisionedFor.clear();
1302
+ provisioningFor = null;
1303
+ freeCapacity = 1;
1304
+ return [];
1305
+ }
1306
+ try {
1307
+ const gathered = gatherState();
1308
+ // The advice rides on the same input the gatherer built; with the brain off it is the same object.
1309
+ const input = advice === undefined ? gathered : { ...gathered, proposals: advice };
1310
+ const tick = (0, coordinator_1.coordinatorTick)(opts.cwd, input);
1311
+ if (!tick.ok) {
1312
+ opts.log(`daemon : the coordinator did not decide — ${tick.because}.`);
1313
+ placement.clear();
1314
+ provisionedFor.clear();
1315
+ provisioningFor = null;
1316
+ freeCapacity = 1;
1317
+ return [];
1318
+ }
1319
+ /**
1320
+ * 🔴 ONE DECISION, THREE ANSWERS. The tick needs to know WHO to ask
1321
+ * first, WHERE each one runs, and HOW MANY it may launch — and all
1322
+ * three come from the same `decideSchedule` call. Computing them
1323
+ * separately would let the three disagree within one cadence, which
1324
+ * is how a goal gets placed in a workspace another goal was just
1325
+ * given.
1326
+ */
1327
+ // M4-1: the checkout this tick asked for, if any — made after the tick. The
1328
+ // provisioner knows nothing about goals; whom it is for is said here.
1329
+ const askedFor = tick.actions.find((a) => a.kind === 'provision');
1330
+ toProvision = askedFor === undefined ? null : { because: `for ${askedFor.goalId} (${askedFor.because})` };
1331
+ provisioningFor = askedFor === undefined ? null : askedFor.goalId;
1332
+ // M4-2: the run this tick would stop, if any — asked after the tick. The
1333
+ // preemptor knows nothing about goals; whose run it is is said here.
1334
+ const victim = tick.actions.find((a) => a.kind === 'preempt');
1335
+ toPreempt =
1336
+ victim === undefined
1337
+ ? null
1338
+ : { runId: victim.runId, who: `${victim.runId} (${victim.goalId}, started by the machine)`, because: victim.because };
1339
+ const starts = tick.actions.filter((a) => a.kind === 'start');
1340
+ placement.clear();
1341
+ for (const a of starts) {
1342
+ placement.set(a.goalId, a.observed.workspace);
1343
+ provisionedFor.delete(a.goalId);
1344
+ }
1345
+ // A checkout made a minute ago for a goal the tick has still not
1346
+ // placed is not a placement in progress any more (the grant may be
1347
+ // gone); a Start on that goal is then a goal placed nowhere.
1348
+ for (const [g, made] of [...provisionedFor]) {
1349
+ if (Date.now() - made.at > exports.PLACEMENT_WAIT_MS)
1350
+ provisionedFor.delete(g);
1351
+ }
1352
+ freeCapacity = starts.length;
1353
+ return starts.map((a) => a.goalId);
1354
+ }
1355
+ catch (e) {
1356
+ // A gatherer that throws must not take the night's daemon with it.
1357
+ opts.log(`daemon : the coordinator could not read this project — ${e.message}.`);
1358
+ placement.clear();
1359
+ provisionedFor.clear();
1360
+ provisioningFor = null;
1361
+ freeCapacity = 1;
1362
+ return [];
1363
+ }
1364
+ },
1365
+ /**
1366
+ * 🔴 THE SCHEDULER ALREADY CHOSE A CHECKOUT FOR EVERY START, and
1367
+ * nothing carried it. `ObservedPremise.workspace` has been on the start
1368
+ * action since Phase 3; the launcher keyed its live map on the literal
1369
+ * string `'run'` and peeked the project's own lease, so a second launch
1370
+ * was refused by the first however many workspaces were idle.
1371
+ */
1372
+ workspaceFor: (goalId) => placement.get(goalId),
1373
+ onDecision: (goalId, kind) => {
1374
+ lastDecision.set(goalId, kind);
1375
+ },
1376
+ /**
1377
+ * 🔴 AND HOW MANY MAY GO AT ONCE. `decideSchedule` already bounds its
1378
+ * own answer by `maxWorkspaces` and `maxAgentsInFlight` minus what is
1379
+ * in flight, so the number of `start` actions IS the free capacity. The
1380
+ * tick used to launch one per cadence whatever it was told, which fills
1381
+ * N slots in N cadences and calls that a fleet.
1382
+ */
1383
+ launchesPerTick: () => (opts.config.machine?.coordinator === true ? freeCapacity : 1),
1384
+ autonomy: autonomyStore,
1385
+ attempts,
1386
+ starter: runStarter,
1387
+ // REAL reconciliation. The server is the authority on whether a person
1388
+ // still permits this, and an unreachable server is `unreachable` — a
1389
+ // reason to wait — never `active` and never an ending.
1390
+ reconcile: async (grant) => {
1391
+ try {
1392
+ // ONE read, answering both halves: is this grant still authorized,
1393
+ // and is the wording it was authorized for still the wording.
1394
+ // Asking separately would act on two answers from two moments, with
1395
+ // an edit able to land in between.
1396
+ const { autonomy, goal } = await client.goalAutonomy(projectId, grant.goalId);
1397
+ const mine = autonomy.find((a) => a.controllerId === grant.controllerId) ?? null;
1398
+ return (0, grant_reconcile_1.reconcileGrant)(grant, mine, goal, Date.now());
1399
+ }
1400
+ catch (e) {
1401
+ return { kind: 'unreachable', because: e.message };
1402
+ }
1403
+ },
1404
+ projectTerminal: async (grant, state, because) => {
1405
+ const settlable = (0, grant_reconcile_1.settlableState)(state);
1406
+ // Nothing to report. `cancelled` is the case: a person's word, which
1407
+ // this machine may observe and stop on but may not write.
1408
+ if (settlable === null)
1409
+ return { ok: true };
1410
+ try {
1411
+ const { autonomy } = await client.goalAutonomy(projectId, grant.goalId);
1412
+ const mine = autonomy.find((a) => a.controllerId === grant.controllerId);
1413
+ if (!mine)
1414
+ return { ok: false, because: 'the server has no record of this grant' };
1415
+ // L4b: a met goal is settled WITH what key 2 confirmed, when the run wrote it.
1416
+ let delivery = null;
1417
+ if (settlable === 'satisfied') {
1418
+ await refusalMarks.load();
1419
+ delivery = (0, pursuit_delivery_1.deliveryForPursuit)((id) => refusalMarks.find(id), grant.controllerId);
1420
+ }
1421
+ await client.settleGoalAutonomy(projectId, grant.controllerId, {
1422
+ version: mine.version,
1423
+ state: settlable,
1424
+ reason: because,
1425
+ ...(delivery === null ? {} : { delivery }),
1426
+ });
1427
+ return { ok: true };
1428
+ }
1429
+ catch (e) {
1430
+ return { ok: false, because: e.message };
1431
+ }
1432
+ },
1433
+ // A PERSON'S TAP, read straight off the control file the daemon
1434
+ // already writes when a pause/stop/resume command arrives.
1435
+ //
1436
+ // An unreadable control file reports a STOP rather than nothing. Not
1437
+ // being able to see whether somebody asked the machine to wait is not
1438
+ // the same as nobody having asked, and only one of those two mistakes
1439
+ // costs a person their evening.
1440
+ humanHold: async () => {
1441
+ // `readOrTrouble`, not `read`. This catch used to be unreachable:
1442
+ // ControlStore.read swallows the failure and answers null, so an
1443
+ // unreadable control file read as "nobody asked" and the machine
1444
+ // carried on — a fail-OPEN default under a comment claiming the
1445
+ // opposite.
1446
+ const out = await controlFile.readOrTrouble();
1447
+ if (!out.ok) {
1448
+ return { kind: 'stop', actor: 'this machine could not read the control file' };
1449
+ }
1450
+ const req = out.request;
1451
+ if (req === null || req.kind === 'resume')
1452
+ return null;
1453
+ return { kind: req.kind, ...(req.actor === undefined ? {} : { actor: req.actor }) };
1454
+ },
1455
+ /**
1456
+ * 🔴 READ FROM THE RUN'S OWN LEDGER, WHEREVER THE RUN IS. This was
1457
+ * `readRunEnded(stateDir, …)` — the PROJECT's ledger — and a run the
1458
+ * coordinator placed in another checkout writes its ending to that
1459
+ * checkout, so its ordinal never closed. The reader locates the run
1460
+ * through the registry record and the start mark and validates the
1461
+ * checkout before reading; an ordinary checkout takes the exact path
1462
+ * it always did. See `run-ending-locator.ts`.
1463
+ */
1464
+ // PROGRAMME MODE, slice 1 (M6-7): off by default. On, the controller
1465
+ // continues from an `approved` decision and the Start carries it,
1466
+ // reserved once through the person's own explicit-start decision —
1467
+ // WITHOUT the person-only safety override that sits in front of it
1468
+ // for a tap (a loop-safety hold still holds in the controller).
1469
+ programme: opts.config.machine?.programme === true,
1470
+ continueFrom: (gate, requestId, workspace) => (0, explicit_start_gate_1.decideExplicitStart)({
1471
+ governed,
1472
+ resumes,
1473
+ carrier: () => new run_lease_1.RunLeaseStore((0, node_path_1.join)(workspace ?? opts.cwd, git_driver_1.STATE_DIR, 'state'), run_lease_1.nodeLeaseFs).inspect(),
1474
+ log: opts.log,
1475
+ }, gate, requestId),
1476
+ runEnded: async (runId) => readRunEnded_(runId),
1477
+ /**
1478
+ * WHAT THE SERVER SAYS THE PURSUIT IS WAITING FOR.
1479
+ *
1480
+ * Deliberately placed AFTER `runEnded`, and not because of ordering in
1481
+ * the object: `autonomy-wiring.spec.ts` reads the source text between
1482
+ * `reconcile:` and `runEnded:` to prove the reconciliation callback
1483
+ * never turns a caught error into an answer. A second callback with its
1484
+ * own catch inside that slice would fail a guard for a reason that has
1485
+ * nothing to do with it.
1486
+ *
1487
+ * FAILS CLOSED, in the two ways it can. Without a server there is
1488
+ * nobody to have asked, so a machine that cannot pair reports `unknown`
1489
+ * rather than `none` — the same choice `humanHold` above makes about an
1490
+ * unreadable control file, and for the same reason: not being able to
1491
+ * see whether somebody was asked is not the same as nobody having been.
1492
+ *
1493
+ * A CLAIM ALREADY GIVEN OUT IS NOT A BLOCKER, so the resumes are read
1494
+ * here and folded in. An answer is a message to one run, once; without
1495
+ * this the first refusal a goal ever received would hold it for the
1496
+ * rest of its life.
1497
+ */
1498
+ approvalGate: (grant) => (0, approval_gate_read_1.readApprovalGate)(client, {
1499
+ projectId,
1500
+ goalId: grant.goalId,
1501
+ goalVersion: grant.goalVersion,
1502
+ now: Date.now(),
1503
+ // Asked with the ids that came back. `unknown` on trouble, never an
1504
+ // empty set: a claim that could not be read is not a claim that is
1505
+ // not there, and reading it as absent hands one answer to two runs.
1506
+ consumed: (ids) => resumes.consumed(ids),
1507
+ }),
1508
+ /**
1509
+ * Whether this machine has already decided that repeating is useless.
1510
+ *
1511
+ * RE-OPENED EVERY TICK, exactly like `startMarkOf` below and for the
1512
+ * identical reason: the observations are written by the RUN process, so
1513
+ * a snapshot taken when the daemon booted is of the moment before the
1514
+ * thing being looked for happened.
1515
+ *
1516
+ * FAILS CLOSED. A history that cannot be read HOLDS — a machine that
1517
+ * cannot see its own safety record must not spend a night on the
1518
+ * assumption that the record is empty, which is the same choice
1519
+ * `approvalGate` makes about an unreachable server.
1520
+ */
1521
+ loopSafety: async (grant) => {
1522
+ if ((await safety.open()) !== 'ok') {
1523
+ return { held: true, because: 'the loop-safety history could not be read' };
1524
+ }
1525
+ const incident = safety.activeIncident(grant.goalId, grant.goalVersion);
1526
+ return incident === null ? { held: false } : { held: true, because: incident.because };
1527
+ },
1528
+ startMarkOf: async (requestId) => {
1529
+ // Re-read every time: the file is written by the RUN process, so a
1530
+ // snapshot taken here would be of the moment before the thing being
1531
+ // looked for happened.
1532
+ await startMarks.load();
1533
+ return startMarks.find(requestId);
1534
+ },
1535
+ refusalOf: async (requestId) => {
1536
+ await refusalMarks.load();
1537
+ const mark = refusalMarks.find(requestId);
1538
+ return mark === null
1539
+ ? null
1540
+ : {
1541
+ reason: mark.reason,
1542
+ ...(mark.code === undefined ? {} : { code: mark.code }),
1543
+ ...(mark.branch === undefined ? {} : { branch: mark.branch }),
1544
+ };
1545
+ },
1546
+ ...(thirdKey === null ? {} : { publicationOf: thirdKey }),
1547
+ now: () => Date.now(),
1548
+ log: opts.log,
1549
+ });
1550
+ // M4-1: one checkout per cadence, after the tick, only when it asked for one.
1551
+ const asked = toProvision;
1552
+ toProvision = null;
1553
+ const made = await (0, workspace_provisioner_1.provisionOnce)(asked, provisioner, opts.log);
1554
+ if (made.path !== null && provisioningFor !== null)
1555
+ provisionedFor.set(provisioningFor, { path: made.path, at: Date.now() });
1556
+ provisioningFor = null;
1557
+ // M4-2: one stop request per cadence, after the tick, only when it decided one.
1558
+ const stopAsk = toPreempt;
1559
+ toPreempt = null;
1560
+ await (0, preemptor_1.preemptOnce)(stopAsk, preemptor, opts.log);
1561
+ // M4-4: one checkout the machine made, retired per cadence, only with the lock held — never one
1562
+ // placed this cadence or last, never the one just provisioned.
1563
+ const placedNow = [...placement.values()];
1564
+ if (reaper !== null && coordinatorLock !== null)
1565
+ await reaper([...placedNow, ...placedBefore, ...(made.path === null ? [] : [made.path])]);
1566
+ placedBefore = placedNow;
1567
+ // M5-1: one branch offered to the integration lane per cadence, only with the lock held.
1568
+ if (integrator !== null && coordinatorLock !== null)
1569
+ await integrator();
1570
+ },
1571
+ // M5-6: every ledger this machine holds — the project's, the reaper's retired records, each registered
1572
+ // run's, each checkout's — the same list `looply goal` reads, re-listed every pass; one cursor per run as before.
1573
+ replay: (0, ledger_replay_1.makeLedgersReplay)(() => (0, goal_runs_1.ledgersOn)(stateDir, new workspace_1.RunRegistry(projectState), opts.cwd), ledger_store_1.nodeLedgerFs, { log: opts.log }),
1574
+ onCommand: makeCommandHandler(opts.log,
1575
+ // Given the state dir so it can read the run lease before spawning. The
1576
+ // daemon's own memory of what it started does not survive its restart;
1577
+ // the lease does, and that gap is the duplicate-runner window.
1578
+ // Given the state dir so it can read the run lease before spawning, AND
1579
+ // the durable start marks — the only thing that may report a run as
1580
+ // started, and therefore the only thing that may make a start-run
1581
+ // terminally done.
1582
+ (0, run_launcher_1.nodeRunLauncher)(opts.cwd, (0, node_path_1.join)(opts.cwd, git_driver_1.STATE_DIR, 'state')),
1583
+ // The same path `looply run` reads (see run-cmd.ts). The daemon and the
1584
+ // run are different processes over one repo, and this file is all they
1585
+ // share.
1586
+ // The FANNING writer, not the bare store: a phone Stop has to reach every
1587
+ // live execution, not just the first one to look. See `controlWriter`.
1588
+ controlWriter,
1589
+ // What this machine has already finished with. The lease covers "is a run
1590
+ // happening now"; this covers "did I already do this", which the lease
1591
+ // cannot answer because it stops existing when the run ends.
1592
+ receipts,
1593
+ // This daemon is for one project. `pop` scopes only to the machine, and a
1594
+ // machine can host several.
1595
+ projectId,
1596
+ // How this machine lets go of a goal's work. Given the lease store as
1597
+ // well as the queue, because it must not write `queue.json` while a run
1598
+ // is doing the same.
1599
+ (0, goal_ownership_1.nodeGoalReleaser)((0, node_path_1.join)(opts.cwd, git_driver_1.STATE_DIR, 'state'), ledger_store_1.nodeLedgerFs, new run_lease_1.RunLeaseStore((0, node_path_1.join)(opts.cwd, git_driver_1.STATE_DIR, 'state'), run_lease_1.nodeLeaseFs), exports.CLI_VERSION,
1600
+ // The same mirror the daemon pulls into, so carrying out a release also
1601
+ // records WHY, and a Start still in flight for the old version cannot
1602
+ // adopt work back into a goal that has been given up on.
1603
+ goals),
1604
+ // Where a grant of autonomy is written down. Its OWN directory of
1605
+ // receipts, not a field on any file two processes already share.
1606
+ // The same instance the controller reads. Two stores over one file would
1607
+ // be two caches of one fact.
1608
+ autonomyStore, explicitStartGate,
1609
+ // M6-9: the same placement the tick hands the launcher, for a person's Start too.
1610
+ (goalId) => placement.get(goalId),
1611
+ // M6-10: the owner's Start ceiling per grant, when declared.
1612
+ opts.config.machine?.programmeMode?.limits.startsPerGrant,
1613
+ /**
1614
+ * M6-14, widened in M6-22: while this machine is still deciding WHERE a
1615
+ * goal runs, a person's Start waits for it rather than falling to the
1616
+ * project checkout.
1617
+ *
1618
+ * Three ways to be in that state, and the third is the one M6-21 found:
1619
+ * a checkout is being made for it, one was made and the goal is not yet
1620
+ * placed in it, or this machine simply holds a live grant for the goal —
1621
+ * it is pursuing it, and the tick that says where has not run yet.
1622
+ */
1623
+ {
1624
+ placing: (goalId) => provisioningFor === goalId || provisionedFor.has(goalId) || pursuing(goalId),
1625
+ }),
1626
+ log: opts.log,
1627
+ sleep,
1628
+ now: () => Date.now(),
1629
+ stopRequested: () => signals.requested(),
1630
+ });
1631
+ if (opts.once) {
1632
+ const beat = await daemon.beat();
1633
+ opts.log(`daemon : heartbeat ${beat ? 'ok' : 'failed'}`);
1634
+ signals.uninstall();
1635
+ return beat ? 0 : 1;
1636
+ }
1637
+ /**
1638
+ * 🔴 THE COORDINATOR LOCK, TAKEN AT LAST.
1639
+ *
1640
+ * `daemon-lock.ts` was written, specced, and never called by anything that
1641
+ * ships. So `coordinatorTick` — whose first act is to refuse without the lock
1642
+ * — could never return anything but a refusal, and, far more immediately, two
1643
+ * `looply daemon` processes over one repository both ran: two autonomy ticks
1644
+ * reading the same grants, each free to spend the same Start, with nothing in
1645
+ * either log mentioning the other. The file existed; the hazard it describes
1646
+ * was live.
1647
+ *
1648
+ * ⚠️ AFTER THE `--once` BRANCH, DELIBERATELY. A heartbeat check is not a
1649
+ * coordinator and must keep working beside a running daemon — refusing it
1650
+ * would break `looply daemon --once` on exactly the machines that are
1651
+ * working properly.
1652
+ *
1653
+ * ⚠️ AND IT IS A REFUSAL, NOT A WAIT. A second daemon that queued for the
1654
+ * lock would sit invisible until the first one stopped, which is the shape a
1655
+ * person reads as "it started fine".
1656
+ */
1657
+ const taken = (0, daemon_lock_1.takeDaemonLock)(opts.cwd);
1658
+ if (!taken.ok) {
1659
+ opts.log(`daemon : ${taken.because}.`);
1660
+ signals.uninstall();
1661
+ return 1;
1662
+ }
1663
+ // Visible to the tick, which refreshes it and refuses to schedule without it.
1664
+ coordinatorLock = taken.lock;
1665
+ opts.log('daemon : connected. Ctrl-C to stop.');
1666
+ try {
1667
+ const final = await daemon.run();
1668
+ opts.log(`daemon : stopped — ${final.delivered} events delivered, ${final.buffered} still buffered`);
1669
+ return 0;
1670
+ }
1671
+ finally {
1672
+ coordinatorLock = null;
1673
+ taken.lock.release();
1674
+ signals.uninstall();
1675
+ }
1676
+ }
1677
+ /**
1678
+ * The terms of a grant, off an `unknown` payload.
1679
+ *
1680
+ * Read defensively for the same reason `readStartPayload` is: the payload
1681
+ * travels from the server as Json and this build may be older or newer than
1682
+ * the one that wrote it. Every field is required -- there is no sensible
1683
+ * default for any of them, and a grant missing its ceiling or its expiry is
1684
+ * not a grant with a gap in it, it is not a grant.
1685
+ */
1686
+ /** The Starts a grant payload asks for, when it says. Read before `grantOf` so a refusal can name the ceiling. */
1687
+ function askedStarts(payload) {
1688
+ if (typeof payload !== 'object' || payload === null)
1689
+ return null;
1690
+ const v = payload['maxStarts'];
1691
+ return typeof v === 'number' && Number.isInteger(v) ? v : null;
1692
+ }
1693
+ function grantOf(payload, ceiling = core_1.MAX_AUTONOMOUS_STARTS) {
1694
+ if (typeof payload !== 'object' || payload === null)
1695
+ return null;
1696
+ const p = payload;
1697
+ const { controllerId, goalId, goalVersion, maxStarts, expiresAt } = p;
1698
+ if (typeof controllerId !== 'string' || controllerId.length === 0)
1699
+ return null;
1700
+ if (typeof goalId !== 'string' || goalId.length === 0)
1701
+ return null;
1702
+ if (typeof goalVersion !== 'number' || !Number.isInteger(goalVersion) || goalVersion < 0) {
1703
+ return null;
1704
+ }
1705
+ // A ceiling of zero authorizes nothing and would be recorded as a live grant
1706
+ // that can never spend; a negative or fractional one is a build disagreeing
1707
+ // with the server. Both are refusals, not values to clamp.
1708
+ // THE MACHINE HOLDS ITS OWN CEILING.
1709
+ //
1710
+ // MAX_AUTONOMOUS_STARTS was enforced at exactly one site — the server,
1711
+ // where the row is minted — and the machine copied whatever arrived. So the
1712
+ // bound lived entirely on the far side of the wire: a bad migration, a
1713
+ // restored backup or a server bug carrying maxStarts: 100000 would have been
1714
+ // adopted verbatim, and reconciliation would have agreed with itself because
1715
+ // both numbers came off the same wrong row.
1716
+ //
1717
+ // A person's evening is spent HERE, so the bound that protects it belongs
1718
+ // here too. Refused rather than clamped: a grant whose terms this machine
1719
+ // does not recognise is not one to quietly reinterpret.
1720
+ if (typeof maxStarts !== 'number' || !Number.isInteger(maxStarts) || maxStarts < 1)
1721
+ return null;
1722
+ if (maxStarts > ceiling)
1723
+ return null;
1724
+ // PARSEABLE, not merely a string. An expiry that reads as NaN compares false
1725
+ // against every clock, so `Date.parse(expiresAt) <= now` would be false
1726
+ // forever and the grant would never run out of time.
1727
+ if (typeof expiresAt !== 'string' || Number.isNaN(Date.parse(expiresAt)))
1728
+ return null;
1729
+ return { controllerId, goalId, goalVersion, maxStarts, expiresAt };
1730
+ }
1731
+ /**
1732
+ * The goal and version a release names, off an `unknown` payload.
1733
+ *
1734
+ * `null` rather than `undefined` for an unreadable one: there is no "no goal"
1735
+ * case here — a release with no goal names nothing and can only be a mistake,
1736
+ * so both the missing and the malformed reading are the same refusal.
1737
+ */
1738
+ function releaseOf(payload) {
1739
+ if (typeof payload !== 'object' || payload === null)
1740
+ return null;
1741
+ const p = payload;
1742
+ if (typeof p.goalId !== 'string' || p.goalId.length === 0)
1743
+ return null;
1744
+ // Required, not optional. The server stamps it on every release, and a
1745
+ // release without one could only record `abandoned` against whatever version
1746
+ // this machine happened to hold — a row no server ever wrote.
1747
+ if (typeof p.goalVersion !== 'number' || !Number.isInteger(p.goalVersion) || p.goalVersion < 0)
1748
+ return null;
1749
+ return { goalId: p.goalId, goalVersion: p.goalVersion };
1750
+ }
1751
+ //# sourceMappingURL=daemon-cmd.js.map