@telora/daemon 0.23.0 → 0.23.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (317) hide show
  1. package/build-info.json +6 -3
  2. package/dist/completion/review-exit/review-census-record.d.ts.map +1 -1
  3. package/dist/completion/review-exit/review-census-record.js +1 -0
  4. package/dist/completion/review-exit/review-census-record.js.map +1 -1
  5. package/dist/completion/review-exit/review-exit-deps.d.ts +2 -1
  6. package/dist/completion/review-exit/review-exit-deps.d.ts.map +1 -1
  7. package/dist/completion/review-exit/review-exit-phase.d.ts +9 -0
  8. package/dist/completion/review-exit/review-exit-phase.d.ts.map +1 -1
  9. package/dist/completion/review-exit/review-exit-phase.js +5 -4
  10. package/dist/completion/review-exit/review-exit-phase.js.map +1 -1
  11. package/dist/completion/review-exit-holds/commit-verdict-release.d.ts.map +1 -1
  12. package/dist/completion/review-exit-holds/commit-verdict-release.js +34 -12
  13. package/dist/completion/review-exit-holds/commit-verdict-release.js.map +1 -1
  14. package/dist/completion/review-exit-holds/review-exit-verdict-gate.d.ts +35 -2
  15. package/dist/completion/review-exit-holds/review-exit-verdict-gate.d.ts.map +1 -1
  16. package/dist/completion/review-exit-holds/review-exit-verdict-gate.js +38 -6
  17. package/dist/completion/review-exit-holds/review-exit-verdict-gate.js.map +1 -1
  18. package/dist/completion/state-cascade/review-approved-head-gate.d.ts +6 -0
  19. package/dist/completion/state-cascade/review-approved-head-gate.d.ts.map +1 -1
  20. package/dist/completion/state-cascade/review-approved-head-gate.js +2 -1
  21. package/dist/completion/state-cascade/review-approved-head-gate.js.map +1 -1
  22. package/dist/completion/state-cascade/review-next-step.d.ts.map +1 -1
  23. package/dist/completion/state-cascade/review-next-step.js +15 -8
  24. package/dist/completion/state-cascade/review-next-step.js.map +1 -1
  25. package/dist/completion/state-cascade/standing-approval-convergence.d.ts.map +1 -1
  26. package/dist/completion/state-cascade/standing-approval-convergence.js +24 -12
  27. package/dist/completion/state-cascade/standing-approval-convergence.js.map +1 -1
  28. package/dist/completion/team-exit/status-advance-phase.d.ts.map +1 -1
  29. package/dist/completion/team-exit/status-advance-phase.js +6 -6
  30. package/dist/completion/team-exit/status-advance-phase.js.map +1 -1
  31. package/dist/completion/team-liveness/supervision-firing-record.d.ts +6 -0
  32. package/dist/completion/team-liveness/supervision-firing-record.d.ts.map +1 -1
  33. package/dist/completion/team-liveness/supervision-firing-record.js +10 -1
  34. package/dist/completion/team-liveness/supervision-firing-record.js.map +1 -1
  35. package/dist/completion/team-outcome/apply-advance.d.ts.map +1 -1
  36. package/dist/completion/team-outcome/apply-advance.js +3 -2
  37. package/dist/completion/team-outcome/apply-advance.js.map +1 -1
  38. package/dist/completion/team-outcome/decide-close-rows.d.ts +9 -0
  39. package/dist/completion/team-outcome/decide-close-rows.d.ts.map +1 -1
  40. package/dist/completion/team-outcome/decide-close-rows.js +18 -4
  41. package/dist/completion/team-outcome/decide-close-rows.js.map +1 -1
  42. package/dist/completion/team-outcome/delivery-advance-types.d.ts +19 -2
  43. package/dist/completion/team-outcome/delivery-advance-types.d.ts.map +1 -1
  44. package/dist/composition/completion-event-context.js +2 -2
  45. package/dist/composition/completion-event-context.js.map +1 -1
  46. package/dist/composition/daemon-contexts.d.ts.map +1 -1
  47. package/dist/composition/daemon-contexts.js +5 -1
  48. package/dist/composition/daemon-contexts.js.map +1 -1
  49. package/dist/composition/engine-start-context.d.ts +3 -0
  50. package/dist/composition/engine-start-context.d.ts.map +1 -0
  51. package/dist/composition/engine-start-context.js +24 -0
  52. package/dist/composition/engine-start-context.js.map +1 -0
  53. package/dist/composition/execution-spawn-context.d.ts.map +1 -1
  54. package/dist/composition/execution-spawn-context.js +13 -1
  55. package/dist/composition/execution-spawn-context.js.map +1 -1
  56. package/dist/composition/runtime-delivery-advance-context.js +3 -3
  57. package/dist/composition/runtime-delivery-advance-context.js.map +1 -1
  58. package/dist/composition/runtime-heartbeat-context.d.ts.map +1 -1
  59. package/dist/composition/runtime-heartbeat-context.js +3 -0
  60. package/dist/composition/runtime-heartbeat-context.js.map +1 -1
  61. package/dist/composition/runtime-poll-context.d.ts +29 -2
  62. package/dist/composition/runtime-poll-context.d.ts.map +1 -1
  63. package/dist/composition/runtime-poll-context.js +51 -4
  64. package/dist/composition/runtime-poll-context.js.map +1 -1
  65. package/dist/composition/runtime-prd-context.d.ts.map +1 -1
  66. package/dist/composition/runtime-prd-context.js +25 -1
  67. package/dist/composition/runtime-prd-context.js.map +1 -1
  68. package/dist/composition/runtime-spawn-pass-context.js +1 -1
  69. package/dist/composition/runtime-spawn-pass-context.js.map +1 -1
  70. package/dist/composition/spawn-pass-deps.d.ts +1 -1
  71. package/dist/composition/spawn-pass-deps.d.ts.map +1 -1
  72. package/dist/composition/spawn-pass-deps.js +3 -0
  73. package/dist/composition/spawn-pass-deps.js.map +1 -1
  74. package/dist/composition/standing-approval-context.d.ts.map +1 -1
  75. package/dist/composition/standing-approval-context.js +2 -1
  76. package/dist/composition/standing-approval-context.js.map +1 -1
  77. package/dist/composition/state-cascade-context.js +1 -1
  78. package/dist/composition/state-cascade-context.js.map +1 -1
  79. package/dist/composition/tower-pass-context.d.ts +9 -0
  80. package/dist/composition/tower-pass-context.d.ts.map +1 -0
  81. package/dist/composition/tower-pass-context.js +53 -0
  82. package/dist/composition/tower-pass-context.js.map +1 -0
  83. package/dist/composition/verify-rework-context.d.ts +26 -2
  84. package/dist/composition/verify-rework-context.d.ts.map +1 -1
  85. package/dist/composition/verify-rework-context.js +34 -10
  86. package/dist/composition/verify-rework-context.js.map +1 -1
  87. package/dist/config/live-daemon-config.d.ts.map +1 -1
  88. package/dist/config/live-daemon-config.js +1 -19
  89. package/dist/config/live-daemon-config.js.map +1 -1
  90. package/dist/daemon-state/awaiting-slot.d.ts.map +1 -1
  91. package/dist/daemon-state/awaiting-slot.js +2 -1
  92. package/dist/daemon-state/awaiting-slot.js.map +1 -1
  93. package/dist/daemon-state/focus-team-state.js +1 -1
  94. package/dist/daemon-state/intake-control.d.ts.map +1 -1
  95. package/dist/daemon-state/intake-control.js +2 -1
  96. package/dist/daemon-state/intake-control.js.map +1 -1
  97. package/dist/delivery/recorded-concerns.d.ts +19 -2
  98. package/dist/delivery/recorded-concerns.d.ts.map +1 -1
  99. package/dist/delivery/recorded-concerns.js +32 -6
  100. package/dist/delivery/recorded-concerns.js.map +1 -1
  101. package/dist/directive/directive-queue.js +1 -1
  102. package/dist/focus/closed-review-window.d.ts +12 -0
  103. package/dist/focus/closed-review-window.d.ts.map +1 -1
  104. package/dist/focus/closed-review-window.js +18 -0
  105. package/dist/focus/closed-review-window.js.map +1 -1
  106. package/dist/focus/control-state.d.ts +30 -26
  107. package/dist/focus/control-state.d.ts.map +1 -1
  108. package/dist/focus/control-state.js +60 -46
  109. package/dist/focus/control-state.js.map +1 -1
  110. package/dist/focus/daemon-contexts.d.ts +23 -0
  111. package/dist/focus/daemon-contexts.d.ts.map +1 -1
  112. package/dist/focus/daemon-contexts.js +1 -1
  113. package/dist/focus/daemon-contexts.js.map +1 -1
  114. package/dist/focus/engine-ready-pass.d.ts +11 -0
  115. package/dist/focus/engine-ready-pass.d.ts.map +1 -0
  116. package/dist/focus/engine-ready-pass.js +25 -0
  117. package/dist/focus/engine-ready-pass.js.map +1 -0
  118. package/dist/focus/failed-head-wedge.d.ts +95 -0
  119. package/dist/focus/failed-head-wedge.d.ts.map +1 -0
  120. package/dist/focus/failed-head-wedge.js +85 -0
  121. package/dist/focus/failed-head-wedge.js.map +1 -0
  122. package/dist/focus/focus-engine.d.ts.map +1 -1
  123. package/dist/focus/focus-engine.js +20 -20
  124. package/dist/focus/focus-engine.js.map +1 -1
  125. package/dist/focus/focus-loop-registry.d.ts.map +1 -1
  126. package/dist/focus/focus-loop-registry.js +2 -1
  127. package/dist/focus/focus-loop-registry.js.map +1 -1
  128. package/dist/focus/focus-poller-auto-advance.d.ts.map +1 -1
  129. package/dist/focus/focus-poller-auto-advance.js +4 -4
  130. package/dist/focus/focus-poller-auto-advance.js.map +1 -1
  131. package/dist/focus/focus-poller-spawn.d.ts +42 -49
  132. package/dist/focus/focus-poller-spawn.d.ts.map +1 -1
  133. package/dist/focus/focus-poller-spawn.js +129 -83
  134. package/dist/focus/focus-poller-spawn.js.map +1 -1
  135. package/dist/focus/focus-poller-state.d.ts.map +1 -1
  136. package/dist/focus/focus-poller-state.js +1 -19
  137. package/dist/focus/focus-poller-state.js.map +1 -1
  138. package/dist/focus/focus-poller.d.ts.map +1 -1
  139. package/dist/focus/focus-poller.js +2 -1
  140. package/dist/focus/focus-poller.js.map +1 -1
  141. package/dist/focus/focus-respawn-guard.d.ts +7 -0
  142. package/dist/focus/focus-respawn-guard.d.ts.map +1 -1
  143. package/dist/focus/focus-respawn-guard.js +20 -1
  144. package/dist/focus/focus-respawn-guard.js.map +1 -1
  145. package/dist/focus/focus-spawn-decision.d.ts +11 -3
  146. package/dist/focus/focus-spawn-decision.d.ts.map +1 -1
  147. package/dist/focus/focus-spawn-decision.js +3 -1
  148. package/dist/focus/focus-spawn-decision.js.map +1 -1
  149. package/dist/focus/heartbeat-metadata.d.ts +3 -0
  150. package/dist/focus/heartbeat-metadata.d.ts.map +1 -1
  151. package/dist/focus/heartbeat-metadata.js +6 -1
  152. package/dist/focus/heartbeat-metadata.js.map +1 -1
  153. package/dist/focus/heartbeat.d.ts.map +1 -1
  154. package/dist/focus/heartbeat.js +2 -0
  155. package/dist/focus/heartbeat.js.map +1 -1
  156. package/dist/focus/pending-directive-gates.d.ts +47 -0
  157. package/dist/focus/pending-directive-gates.d.ts.map +1 -0
  158. package/dist/focus/pending-directive-gates.js +76 -0
  159. package/dist/focus/pending-directive-gates.js.map +1 -0
  160. package/dist/focus/pending-review-approved-head.d.ts +2 -1
  161. package/dist/focus/pending-review-approved-head.d.ts.map +1 -1
  162. package/dist/focus/pending-review-approved-head.js +3 -4
  163. package/dist/focus/pending-review-approved-head.js.map +1 -1
  164. package/dist/focus/pending-spawn-pass-deps.d.ts +5 -0
  165. package/dist/focus/pending-spawn-pass-deps.d.ts.map +1 -1
  166. package/dist/focus/pending-spawn-pass-deps.js +4 -1
  167. package/dist/focus/pending-spawn-pass-deps.js.map +1 -1
  168. package/dist/focus/pending-spawn-verify-hold.d.ts +13 -0
  169. package/dist/focus/pending-spawn-verify-hold.d.ts.map +1 -0
  170. package/dist/focus/pending-spawn-verify-hold.js +30 -0
  171. package/dist/focus/pending-spawn-verify-hold.js.map +1 -0
  172. package/dist/focus/ready-focus-spawn-action.d.ts +15 -2
  173. package/dist/focus/ready-focus-spawn-action.d.ts.map +1 -1
  174. package/dist/focus/ready-focus-spawn-action.js +25 -8
  175. package/dist/focus/ready-focus-spawn-action.js.map +1 -1
  176. package/dist/focus/spawn-demand-probe.d.ts +45 -0
  177. package/dist/focus/spawn-demand-probe.d.ts.map +1 -0
  178. package/dist/focus/spawn-demand-probe.js +74 -0
  179. package/dist/focus/spawn-demand-probe.js.map +1 -0
  180. package/dist/focus/stranded-review-reroute.d.ts.map +1 -1
  181. package/dist/focus/stranded-review-reroute.js +5 -3
  182. package/dist/focus/stranded-review-reroute.js.map +1 -1
  183. package/dist/focus/verify-routing-passes.d.ts +8 -3
  184. package/dist/focus/verify-routing-passes.d.ts.map +1 -1
  185. package/dist/focus/verify-routing-passes.js +1 -0
  186. package/dist/focus/verify-routing-passes.js.map +1 -1
  187. package/dist/git/focus-branch-containment.d.ts +39 -0
  188. package/dist/git/focus-branch-containment.d.ts.map +1 -0
  189. package/dist/git/focus-branch-containment.js +47 -0
  190. package/dist/git/focus-branch-containment.js.map +1 -0
  191. package/dist/git/types.d.ts.map +1 -1
  192. package/dist/git/types.js +1 -0
  193. package/dist/git/types.js.map +1 -1
  194. package/dist/poll-trigger.js +1 -1
  195. package/dist/poll-trigger.js.map +1 -1
  196. package/dist/prd/arm-readiness.d.ts.map +1 -1
  197. package/dist/prd/arm-readiness.js +2 -1
  198. package/dist/prd/arm-readiness.js.map +1 -1
  199. package/dist/prd/controller-shared.d.ts +32 -8
  200. package/dist/prd/controller-shared.d.ts.map +1 -1
  201. package/dist/prd/controller-shared.js +34 -18
  202. package/dist/prd/controller-shared.js.map +1 -1
  203. package/dist/prd/controller.d.ts.map +1 -1
  204. package/dist/prd/controller.js +3 -8
  205. package/dist/prd/controller.js.map +1 -1
  206. package/dist/prd/done-sweep.d.ts.map +1 -1
  207. package/dist/prd/done-sweep.js +2 -7
  208. package/dist/prd/done-sweep.js.map +1 -1
  209. package/dist/prd/frontier-arming.d.ts +13 -1
  210. package/dist/prd/frontier-arming.d.ts.map +1 -1
  211. package/dist/prd/frontier-arming.js +50 -2
  212. package/dist/prd/frontier-arming.js.map +1 -1
  213. package/dist/prd/index.d.ts.map +1 -1
  214. package/dist/prd/index.js +2 -1
  215. package/dist/prd/index.js.map +1 -1
  216. package/dist/queries/api-client/shared.d.ts +1 -0
  217. package/dist/queries/api-client/shared.d.ts.map +1 -1
  218. package/dist/queries/api-client/shared.js +16 -1
  219. package/dist/queries/api-client/shared.js.map +1 -1
  220. package/dist/queries/deliveries/issues.d.ts.map +1 -1
  221. package/dist/queries/deliveries/issues.js +3 -0
  222. package/dist/queries/deliveries/issues.js.map +1 -1
  223. package/dist/queries/focuses/focus-discovery.d.ts +15 -0
  224. package/dist/queries/focuses/focus-discovery.d.ts.map +1 -1
  225. package/dist/queries/focuses/focus-discovery.js +10 -1
  226. package/dist/queries/focuses/focus-discovery.js.map +1 -1
  227. package/dist/queries/prd/prd.d.ts.map +1 -1
  228. package/dist/queries/prd/prd.js +2 -1
  229. package/dist/queries/prd/prd.js.map +1 -1
  230. package/dist/queries/tower/tower-state.d.ts +56 -0
  231. package/dist/queries/tower/tower-state.d.ts.map +1 -0
  232. package/dist/queries/tower/tower-state.js +76 -0
  233. package/dist/queries/tower/tower-state.js.map +1 -0
  234. package/dist/queries/wire-schemas/schemas.d.ts +12 -0
  235. package/dist/queries/wire-schemas/schemas.d.ts.map +1 -1
  236. package/dist/queries/wire-schemas/schemas.js +11 -0
  237. package/dist/queries/wire-schemas/schemas.js.map +1 -1
  238. package/dist/spawn/lead-continuation.d.ts +9 -1
  239. package/dist/spawn/lead-continuation.d.ts.map +1 -1
  240. package/dist/spawn/lead-continuation.js +37 -11
  241. package/dist/spawn/lead-continuation.js.map +1 -1
  242. package/dist/spawn/spawn-decision.d.ts.map +1 -1
  243. package/dist/spawn/spawn-decision.js +4 -1
  244. package/dist/spawn/spawn-decision.js.map +1 -1
  245. package/dist/supervision/spawn-cooldown.js +1 -1
  246. package/dist/supervision/stop-rows-team-session.d.ts.map +1 -1
  247. package/dist/supervision/stop-rows-team-session.js +11 -10
  248. package/dist/supervision/stop-rows-team-session.js.map +1 -1
  249. package/dist/supervision/team-work.d.ts.map +1 -1
  250. package/dist/supervision/team-work.js +3 -1
  251. package/dist/supervision/team-work.js.map +1 -1
  252. package/dist/testing/fixtures/api-client.js +1 -1
  253. package/dist/testing/fixtures/approved-head-inert-deps.js +1 -1
  254. package/dist/testing/fixtures/approved-head-inert-deps.js.map +1 -1
  255. package/dist/testing/fixtures/index.js +1 -1
  256. package/dist/testing/fixtures/pending-spawn-world.d.ts.map +1 -1
  257. package/dist/testing/fixtures/pending-spawn-world.js +3 -1
  258. package/dist/testing/fixtures/pending-spawn-world.js.map +1 -1
  259. package/dist/testing/fixtures/review-exit-deps.js +1 -1
  260. package/dist/testing/fixtures/review-exit-deps.js.map +1 -1
  261. package/dist/testing/fixtures/verification-feedback-deps.d.ts.map +1 -1
  262. package/dist/testing/fixtures/verification-feedback-deps.js +2 -0
  263. package/dist/testing/fixtures/verification-feedback-deps.js.map +1 -1
  264. package/dist/tower/focus-landed.d.ts +4 -0
  265. package/dist/tower/focus-landed.d.ts.map +1 -0
  266. package/dist/tower/focus-landed.js +45 -0
  267. package/dist/tower/focus-landed.js.map +1 -0
  268. package/dist/tower/tower-admission.d.ts +91 -0
  269. package/dist/tower/tower-admission.d.ts.map +1 -0
  270. package/dist/tower/tower-admission.js +137 -0
  271. package/dist/tower/tower-admission.js.map +1 -0
  272. package/dist/tower/tower-graph.d.ts +23 -0
  273. package/dist/tower/tower-graph.d.ts.map +1 -0
  274. package/dist/tower/tower-graph.js +53 -0
  275. package/dist/tower/tower-graph.js.map +1 -0
  276. package/dist/tower/tower-heartbeat-status.d.ts +28 -0
  277. package/dist/tower/tower-heartbeat-status.d.ts.map +1 -0
  278. package/dist/tower/tower-heartbeat-status.js +11 -0
  279. package/dist/tower/tower-heartbeat-status.js.map +1 -0
  280. package/dist/tower/tower-mode.d.ts +9 -0
  281. package/dist/tower/tower-mode.d.ts.map +1 -0
  282. package/dist/tower/tower-mode.js +16 -0
  283. package/dist/tower/tower-mode.js.map +1 -0
  284. package/dist/tower/tower-pass-standing.d.ts +8 -0
  285. package/dist/tower/tower-pass-standing.d.ts.map +1 -0
  286. package/dist/tower/tower-pass-standing.js +50 -0
  287. package/dist/tower/tower-pass-standing.js.map +1 -0
  288. package/dist/tower/tower-pass.d.ts +55 -0
  289. package/dist/tower/tower-pass.d.ts.map +1 -0
  290. package/dist/tower/tower-pass.js +59 -0
  291. package/dist/tower/tower-pass.js.map +1 -0
  292. package/dist/tower/tower-production-pass.d.ts +3 -0
  293. package/dist/tower/tower-production-pass.d.ts.map +1 -0
  294. package/dist/tower/tower-production-pass.js +24 -0
  295. package/dist/tower/tower-production-pass.js.map +1 -0
  296. package/dist/tower/tower-spawn-intent.d.ts +72 -0
  297. package/dist/tower/tower-spawn-intent.d.ts.map +1 -0
  298. package/dist/tower/tower-spawn-intent.js +138 -0
  299. package/dist/tower/tower-spawn-intent.js.map +1 -0
  300. package/dist/tower/tower-verdicts.d.ts +81 -0
  301. package/dist/tower/tower-verdicts.d.ts.map +1 -0
  302. package/dist/tower/tower-verdicts.js +139 -0
  303. package/dist/tower/tower-verdicts.js.map +1 -0
  304. package/dist/types/index.js +1 -1
  305. package/dist/types/issue-status.d.ts +14 -0
  306. package/dist/types/issue-status.d.ts.map +1 -1
  307. package/dist/types/issue-status.js +14 -0
  308. package/dist/types/issue-status.js.map +1 -1
  309. package/dist/verify/gate-verdicts/commit-verdict-record.js +1 -1
  310. package/dist/verify/outcome-routing/verification-rework-issue.d.ts +44 -5
  311. package/dist/verify/outcome-routing/verification-rework-issue.d.ts.map +1 -1
  312. package/dist/verify/outcome-routing/verification-rework-issue.js +85 -27
  313. package/dist/verify/outcome-routing/verification-rework-issue.js.map +1 -1
  314. package/dist/worktree/focus-worktree-state.d.ts.map +1 -1
  315. package/dist/worktree/focus-worktree-state.js +1 -0
  316. package/dist/worktree/focus-worktree-state.js.map +1 -1
  317. package/package.json +3 -3
@@ -0,0 +1,55 @@
1
+ /**
2
+ * The tower pass: one pass per configured product, the SOLE writer of
3
+ * product_focuses.tower_verdict.
4
+ *
5
+ * It runs at the head of every ready-focus pass (so on daemon start and every
6
+ * poll), reads the product's limit and armed foci, computes every verdict
7
+ * (tower-verdicts.ts) from the armed set, the limit, focus rank, team presence
8
+ * and the spawn passes' last record of demand, and writes only what changed --
9
+ * or what is aging toward the bounded freshness age. So any change to those
10
+ * inputs is reflected on the next poll, and a verdict the pass keeps confirming
11
+ * never goes stale under the spawn pass.
12
+ *
13
+ * ISOLATION. A pass that throws for one product is logged and recorded
14
+ * (tower-pass-standing.ts): admission fails open on that product's foci at
15
+ * once, whatever verdicts they still carry, until a pass for it succeeds. It
16
+ * never stops another product's pass. Mode `off` runs no pass at all.
17
+ *
18
+ * The tower never arms or un-arms: nothing here writes assigned_agent_role_id.
19
+ */
20
+ import type { TowerMode } from '@telora/daemon-core';
21
+ import type { TowerProductState } from '../queries/tower/tower-state.js';
22
+ import { type TowerVerdictWrite } from './tower-verdicts.js';
23
+ import type { TowerGraphInputs } from './tower-graph.js';
24
+ export interface TowerPassDeps {
25
+ products: ReadonlyArray<{
26
+ id: string;
27
+ }>;
28
+ mode: TowerMode;
29
+ readState: (productId: string) => Promise<TowerProductState>;
30
+ writeVerdicts: (productId: string, writes: readonly TowerVerdictWrite[]) => Promise<void>;
31
+ /** Foci holding a live coding or review team now. */
32
+ liveTeamFocusIds: () => ReadonlySet<string>;
33
+ /** Foci the spawn passes last found wanting a team (null = not yet known). */
34
+ wantingFocusIds: () => ReadonlySet<string> | null;
35
+ /**
36
+ * The product's graph inputs (depends_on / excludes / landed), judged from the
37
+ * state read (tower/tower-graph.ts over the product's git). Throwing fails the
38
+ * product's pass, which fails open.
39
+ */
40
+ judgeGraph: (productId: string, state: TowerProductState) => TowerGraphInputs;
41
+ now: () => Date;
42
+ }
43
+ /** The pass's deps over the running daemon, or null before it has a live config (composition/tower-pass-context.ts). */
44
+ export type TowerPassDepsSource = () => TowerPassDeps | null;
45
+ export interface TowerProductOutcome {
46
+ productId: string;
47
+ ok: boolean;
48
+ writes: number;
49
+ error?: string;
50
+ }
51
+ /** Run one product's pass. Throws on a read/compute/write failure (the caller isolates it). */
52
+ export declare function runTowerProductPass(productId: string, deps: TowerPassDeps): Promise<number>;
53
+ /** Run every product's pass; one product's failure never stops another's. */
54
+ export declare function runTowerPass(deps: TowerPassDeps): Promise<TowerProductOutcome[]>;
55
+ //# sourceMappingURL=tower-pass.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tower-pass.d.ts","sourceRoot":"","sources":["../../src/tower/tower-pass.ts"],"names":[],"mappings":"AAOA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AAEzE,OAAO,EAIL,KAAK,iBAAiB,EACvB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAMzD,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,aAAa,CAAC;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACxC,IAAI,EAAE,SAAS,CAAC;IAChB,SAAS,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,iBAAiB,CAAC,CAAC;IAC7D,aAAa,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,iBAAiB,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1F,qDAAqD;IACrD,gBAAgB,EAAE,MAAM,WAAW,CAAC,MAAM,CAAC,CAAC;IAC5C,8EAA8E;IAC9E,eAAe,EAAE,MAAM,WAAW,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC;IAClD;;;;OAIG;IACH,UAAU,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,iBAAiB,KAAK,gBAAgB,CAAC;IAC9E,GAAG,EAAE,MAAM,IAAI,CAAC;CACjB;AAED,wHAAwH;AACxH,MAAM,MAAM,mBAAmB,GAAG,MAAM,aAAa,GAAG,IAAI,CAAC;AAE7D,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,+FAA+F;AAC/F,wBAAsB,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CAwBjG;AAED,6EAA6E;AAC7E,wBAAsB,YAAY,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,mBAAmB,EAAE,CAAC,CAmBtF"}
@@ -0,0 +1,59 @@
1
+ // PINNED-BY-BEGIN (generated by gen:pinned-by -- do not hand-edit; checked by lint:pinned-by)
2
+ // Pinned by: packages/daemon/src/tower/tower-entrypoints.test.ts,
3
+ // packages/daemon/src/tower/tower-pass.test.ts, packages/daemon/src/tower/tower-prd-arm.test.ts,
4
+ // packages/daemon/src/tower/tower-spawn-modes.test.ts,
5
+ // packages/daemon/src/tower/tower-state-ingestion.test.ts
6
+ // PINNED-BY-END
7
+ import { createLogger } from '../log/daemon-log.js';
8
+ import { computeTowerVerdicts, planTowerWrites, } from './tower-verdicts.js';
9
+ import { recordTowerPassOutcome } from './tower-pass-standing.js';
10
+ const log = createLogger({ module: 'tower' });
11
+ /** Run one product's pass. Throws on a read/compute/write failure (the caller isolates it). */
12
+ export async function runTowerProductPass(productId, deps) {
13
+ if (deps.mode === 'off')
14
+ return 0;
15
+ const state = await deps.readState(productId);
16
+ const foci = state.foci.map((f) => ({
17
+ focusId: f.focusId,
18
+ focusName: f.focusName,
19
+ priorityRank: f.priorityRank,
20
+ armedAt: f.agentAssignedAt ?? null,
21
+ previousVerdict: f.towerVerdict ?? null,
22
+ }));
23
+ const graph = deps.judgeGraph(productId, state);
24
+ const now = deps.now();
25
+ const computed = computeTowerVerdicts({
26
+ foci,
27
+ limit: state.concurrencyLimit ?? null,
28
+ liveTeamFocusIds: deps.liveTeamFocusIds(),
29
+ wantingFocusIds: deps.wantingFocusIds(),
30
+ ...graph,
31
+ mode: deps.mode,
32
+ now: now.toISOString(),
33
+ });
34
+ const writes = planTowerWrites(foci, computed, state.unarmedWithVerdictFocusIds ?? [], now.getTime());
35
+ await deps.writeVerdicts(productId, writes);
36
+ return writes.length;
37
+ }
38
+ /** Run every product's pass; one product's failure never stops another's. */
39
+ export async function runTowerPass(deps) {
40
+ if (deps.mode === 'off')
41
+ return [];
42
+ const outcomes = [];
43
+ for (const product of deps.products) {
44
+ try {
45
+ const writes = await runTowerProductPass(product.id, deps);
46
+ recordTowerPassOutcome(product.id, true, deps.now().getTime());
47
+ outcomes.push({ productId: product.id, ok: true, writes });
48
+ }
49
+ catch (err) {
50
+ const error = err instanceof Error ? err.message : String(err);
51
+ recordTowerPassOutcome(product.id, false, deps.now().getTime());
52
+ log.warn(`[tower] pass failed for product ${product.id.slice(0, 8)}: ${error} -- admission fails open on its foci ` +
53
+ `until a pass for it succeeds (tower-pass-standing.ts)`);
54
+ outcomes.push({ productId: product.id, ok: false, writes: 0, error });
55
+ }
56
+ }
57
+ return outcomes;
58
+ }
59
+ //# sourceMappingURL=tower-pass.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tower-pass.js","sourceRoot":"","sources":["../../src/tower/tower-pass.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import { type TowerPassDepsSource } from './tower-pass.js';
2
+ export declare function runTowerPassSafely(source: TowerPassDepsSource): Promise<void>;
3
+ //# sourceMappingURL=tower-production-pass.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tower-production-pass.d.ts","sourceRoot":"","sources":["../../src/tower/tower-production-pass.ts"],"names":[],"mappings":"AAaA,OAAO,EAAgB,KAAK,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAIzE,wBAAsB,kBAAkB,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAOnF"}
@@ -0,0 +1,24 @@
1
+ // PINNED-BY-BEGIN (generated by gen:pinned-by -- do not hand-edit; checked by lint:pinned-by)
2
+ // Pinned by: packages/daemon/src/tower/tower-production-pass.test.ts,
3
+ // packages/daemon/src/tower/tower-spawn-modes.test.ts
4
+ // PINNED-BY-END
5
+ /**
6
+ * The tower pass at the head of every production ready-focus pass
7
+ * (focus/focus-poller-spawn.ts runProductionReadyFocusPass), over the deps the
8
+ * composition root binds (composition/tower-pass-context.ts). Never throws into
9
+ * the spawn pass it heads: a failed tower costs no spawn.
10
+ */
11
+ import { createLogger } from '../log/daemon-log.js';
12
+ import { runTowerPass } from './tower-pass.js';
13
+ const log = createLogger({ module: 'tower' });
14
+ export async function runTowerPassSafely(source) {
15
+ try {
16
+ const deps = source();
17
+ if (deps)
18
+ await runTowerPass(deps);
19
+ }
20
+ catch (err) {
21
+ log.warn(`[tower] pass failed: ${err instanceof Error ? err.message : String(err)} -- admission fails open`);
22
+ }
23
+ }
24
+ //# sourceMappingURL=tower-production-pass.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tower-production-pass.js","sourceRoot":"","sources":["../../src/tower/tower-production-pass.ts"],"names":[],"mappings":"AAAA,8FAA8F;AAC9F,sEAAsE;AACtE,wDAAwD;AACxD,gBAAgB;AAEhB;;;;;GAKG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,EAAE,YAAY,EAA4B,MAAM,iBAAiB,CAAC;AAEzE,MAAM,GAAG,GAAG,YAAY,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC;AAE9C,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,MAA2B;IAClE,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC;QACtB,IAAI,IAAI;YAAE,MAAM,YAAY,CAAC,IAAI,CAAC,CAAC;IACrC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,GAAG,CAAC,IAAI,CAAC,wBAAwB,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAC;IAC/G,CAAC;AACH,CAAC"}
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Which armed foci the spawn passes last found WANTING a team -- the tower's
3
+ * memory of demand, so a free slot goes to a focus that would use it.
4
+ *
5
+ * A spawn pass records a focus as wanting only when the focus reached the
6
+ * tower's admission and was stopped there by the tower or the host cap --
7
+ * demand still OPEN. A focus that spawned has had its demand met (its live team
8
+ * holds a slot by the slot rule, not by wanting), and a focus a pass stopped
9
+ * for any other reason (no team work, a verify hold, an escalation, an operator
10
+ * hold, the review-cycle cap, a cooldown) cannot run, so it does not want.
11
+ *
12
+ * Two rules keep the record CURRENT rather than a snapshot of an older pass:
13
+ * - Each spawn pass is a unit (beginSpawnIntentPass .. commitSpawnIntentPass):
14
+ * every focus the pass OFFERED and did not record as wanting is committed as
15
+ * not wanting, whichever earlier gate stopped it -- so a queued directive
16
+ * parked behind a verify hold or an escalation reserves no product slot.
17
+ * - Before the tower allocates slots, the ready pass reconciles the record
18
+ * against what could run NOW (reconcileSurfacedSpawnIntent, run by
19
+ * focus/focus-poller-spawn.ts runReadyFocusPassThroughTower ahead of the
20
+ * tower pass): every surfaced focus is probed through its spawn pass's gates
21
+ * bar the tower and the host cap (focus/spawn-demand-probe.ts), and wants a
22
+ * slot exactly when they are open -- the first time it surfaces, and the
23
+ * pass its gate clears. A focus no longer surfaced (not ready, no queued
24
+ * directive) wants nothing.
25
+ *
26
+ * Before any pass or reconcile has recorded anything the set is UNKNOWN (null)
27
+ * and the tower treats every focus as wanting -- the safe reading on daemon
28
+ * start.
29
+ *
30
+ * A leaf: in-process state only, imported by the spawn passes (focus/), the
31
+ * admission (tower-admission.ts) and the tower pass (tower-pass.ts).
32
+ */
33
+ /** A record older than this no longer counts (two missed 30s polls plus margin). */
34
+ export declare const SPAWN_INTENT_TTL_MS = 90000;
35
+ /** The spawn pass a record comes from (each runs as its own unit). */
36
+ export type SpawnIntentSource = 'ready' | 'pending';
37
+ /**
38
+ * Open a spawn pass over the foci it offers. Until it commits, what the pass
39
+ * records stays staged: the tower reads the last COMMITTED record, never a
40
+ * half-finished pass.
41
+ */
42
+ export declare function beginSpawnIntentPass(source: SpawnIntentSource, offeredFocusIds: Iterable<string>): void;
43
+ /** Record what a spawn pass found for one focus (staged while that pass is open). */
44
+ export declare function recordSpawnIntent(focusId: string, wants: boolean, nowMs?: number, source?: SpawnIntentSource): void;
45
+ /**
46
+ * Close a spawn pass: every focus it offered is committed -- wanting when the
47
+ * pass recorded it so, not wanting otherwise. No-op when no pass is open (a pass
48
+ * that returned before offering anything changes nothing).
49
+ */
50
+ export declare function commitSpawnIntentPass(source: SpawnIntentSource, nowMs?: number): void;
51
+ /** Drop an open pass uncommitted (the pass threw): the last committed record stands. */
52
+ export declare function abandonSpawnIntentPass(source: SpawnIntentSource): void;
53
+ /**
54
+ * Reconcile demand with what could run NOW. `demand` holds EVERY surfaced focus
55
+ * (ready, or holding a queued directive) with whether its spawn pass's gates,
56
+ * bar the tower and the host cap, are open on this pass
57
+ * (focus/spawn-demand-probe.ts), or null when its probe could not answer:
58
+ * - a focus not in `demand` is no longer surfaced: its record is dropped, so it
59
+ * reads as not wanting;
60
+ * - a probed focus's record becomes the probe's answer, whatever an earlier
61
+ * pass recorded -- a gate that has cleared since makes it want, a gate that
62
+ * holds it (even on the first pass that surfaces it) makes it not want;
63
+ * - a focus whose probe could not answer keeps a fresh record; with none (the
64
+ * first pass that surfaces it) or an expired one it WANTS: an unreadable gate
65
+ * fails open, so a read error never silently demotes a higher-ranked focus.
66
+ */
67
+ export declare function reconcileSurfacedSpawnIntent(demand: ReadonlyMap<string, boolean | null>, nowMs?: number): void;
68
+ /** The foci last found wanting a team, or null before any pass has recorded. */
69
+ export declare function wantingFocusIds(nowMs?: number): ReadonlySet<string> | null;
70
+ /** Test-only reset. */
71
+ export declare function resetSpawnIntent(): void;
72
+ //# sourceMappingURL=tower-spawn-intent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tower-spawn-intent.d.ts","sourceRoot":"","sources":["../../src/tower/tower-spawn-intent.ts"],"names":[],"mappings":"AAOA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,oFAAoF;AACpF,eAAO,MAAM,mBAAmB,QAAS,CAAC;AAE1C,sEAAsE;AACtE,MAAM,MAAM,iBAAiB,GAAG,OAAO,GAAG,SAAS,CAAC;AAapD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,iBAAiB,EAAE,eAAe,EAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,IAAI,CAEvG;AAED,qFAAqF;AACrF,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,GAAE,MAAmB,EAAE,MAAM,GAAE,iBAA2B,GAAG,IAAI,CASxI;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,iBAAiB,EAAE,KAAK,GAAE,MAAmB,GAAG,IAAI,CAMjG;AAED,wFAAwF;AACxF,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,iBAAiB,GAAG,IAAI,CAEtE;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,4BAA4B,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CAAC,EAAE,KAAK,GAAE,MAAmB,GAAG,IAAI,CAe1H;AAED,gFAAgF;AAChF,wBAAgB,eAAe,CAAC,KAAK,GAAE,MAAmB,GAAG,WAAW,CAAC,MAAM,CAAC,GAAG,IAAI,CAWtF;AAED,uBAAuB;AACvB,wBAAgB,gBAAgB,IAAI,IAAI,CAIvC"}
@@ -0,0 +1,138 @@
1
+ // PINNED-BY-BEGIN (generated by gen:pinned-by -- do not hand-edit; checked by lint:pinned-by)
2
+ // Pinned by: packages/daemon/src/tower/tower-entrypoints.test.ts,
3
+ // packages/daemon/src/tower/tower-prd-arm.test.ts,
4
+ // packages/daemon/src/tower/tower-ready-row-ingestion.test.ts,
5
+ // packages/daemon/src/tower/tower-spawn-modes.test.ts
6
+ // PINNED-BY-END
7
+ /**
8
+ * Which armed foci the spawn passes last found WANTING a team -- the tower's
9
+ * memory of demand, so a free slot goes to a focus that would use it.
10
+ *
11
+ * A spawn pass records a focus as wanting only when the focus reached the
12
+ * tower's admission and was stopped there by the tower or the host cap --
13
+ * demand still OPEN. A focus that spawned has had its demand met (its live team
14
+ * holds a slot by the slot rule, not by wanting), and a focus a pass stopped
15
+ * for any other reason (no team work, a verify hold, an escalation, an operator
16
+ * hold, the review-cycle cap, a cooldown) cannot run, so it does not want.
17
+ *
18
+ * Two rules keep the record CURRENT rather than a snapshot of an older pass:
19
+ * - Each spawn pass is a unit (beginSpawnIntentPass .. commitSpawnIntentPass):
20
+ * every focus the pass OFFERED and did not record as wanting is committed as
21
+ * not wanting, whichever earlier gate stopped it -- so a queued directive
22
+ * parked behind a verify hold or an escalation reserves no product slot.
23
+ * - Before the tower allocates slots, the ready pass reconciles the record
24
+ * against what could run NOW (reconcileSurfacedSpawnIntent, run by
25
+ * focus/focus-poller-spawn.ts runReadyFocusPassThroughTower ahead of the
26
+ * tower pass): every surfaced focus is probed through its spawn pass's gates
27
+ * bar the tower and the host cap (focus/spawn-demand-probe.ts), and wants a
28
+ * slot exactly when they are open -- the first time it surfaces, and the
29
+ * pass its gate clears. A focus no longer surfaced (not ready, no queued
30
+ * directive) wants nothing.
31
+ *
32
+ * Before any pass or reconcile has recorded anything the set is UNKNOWN (null)
33
+ * and the tower treats every focus as wanting -- the safe reading on daemon
34
+ * start.
35
+ *
36
+ * A leaf: in-process state only, imported by the spawn passes (focus/), the
37
+ * admission (tower-admission.ts) and the tower pass (tower-pass.ts).
38
+ */
39
+ /** A record older than this no longer counts (two missed 30s polls plus margin). */
40
+ export const SPAWN_INTENT_TTL_MS = 90_000;
41
+ const records = new Map();
42
+ let everRecorded = false;
43
+ /** A spawn pass in progress: the foci it offered and what it recorded for them so far. */
44
+ const openPasses = new Map();
45
+ /**
46
+ * Open a spawn pass over the foci it offers. Until it commits, what the pass
47
+ * records stays staged: the tower reads the last COMMITTED record, never a
48
+ * half-finished pass.
49
+ */
50
+ export function beginSpawnIntentPass(source, offeredFocusIds) {
51
+ openPasses.set(source, { offered: new Set(offeredFocusIds), wanting: new Set() });
52
+ }
53
+ /** Record what a spawn pass found for one focus (staged while that pass is open). */
54
+ export function recordSpawnIntent(focusId, wants, nowMs = Date.now(), source = 'ready') {
55
+ const open = openPasses.get(source);
56
+ if (open && open.offered.has(focusId)) {
57
+ if (wants)
58
+ open.wanting.add(focusId);
59
+ else
60
+ open.wanting.delete(focusId);
61
+ return;
62
+ }
63
+ records.set(focusId, { wants, atMs: nowMs });
64
+ everRecorded = true;
65
+ }
66
+ /**
67
+ * Close a spawn pass: every focus it offered is committed -- wanting when the
68
+ * pass recorded it so, not wanting otherwise. No-op when no pass is open (a pass
69
+ * that returned before offering anything changes nothing).
70
+ */
71
+ export function commitSpawnIntentPass(source, nowMs = Date.now()) {
72
+ const open = openPasses.get(source);
73
+ if (!open)
74
+ return;
75
+ openPasses.delete(source);
76
+ for (const focusId of open.offered)
77
+ records.set(focusId, { wants: open.wanting.has(focusId), atMs: nowMs });
78
+ if (open.offered.size > 0)
79
+ everRecorded = true;
80
+ }
81
+ /** Drop an open pass uncommitted (the pass threw): the last committed record stands. */
82
+ export function abandonSpawnIntentPass(source) {
83
+ openPasses.delete(source);
84
+ }
85
+ /**
86
+ * Reconcile demand with what could run NOW. `demand` holds EVERY surfaced focus
87
+ * (ready, or holding a queued directive) with whether its spawn pass's gates,
88
+ * bar the tower and the host cap, are open on this pass
89
+ * (focus/spawn-demand-probe.ts), or null when its probe could not answer:
90
+ * - a focus not in `demand` is no longer surfaced: its record is dropped, so it
91
+ * reads as not wanting;
92
+ * - a probed focus's record becomes the probe's answer, whatever an earlier
93
+ * pass recorded -- a gate that has cleared since makes it want, a gate that
94
+ * holds it (even on the first pass that surfaces it) makes it not want;
95
+ * - a focus whose probe could not answer keeps a fresh record; with none (the
96
+ * first pass that surfaces it) or an expired one it WANTS: an unreadable gate
97
+ * fails open, so a read error never silently demotes a higher-ranked focus.
98
+ */
99
+ export function reconcileSurfacedSpawnIntent(demand, nowMs = Date.now()) {
100
+ for (const focusId of [...records.keys()]) {
101
+ if (!demand.has(focusId))
102
+ records.delete(focusId);
103
+ }
104
+ for (const [focusId, wants] of demand) {
105
+ if (wants !== null) {
106
+ records.set(focusId, { wants, atMs: nowMs });
107
+ continue;
108
+ }
109
+ // Unknown: a fresh record stands; with none (first surface) or an expired one, fail OPEN --
110
+ // the focus is ranked with the waiters, never silently demoted behind them by a read error.
111
+ const r = records.get(focusId);
112
+ if (!r || nowMs - r.atMs > SPAWN_INTENT_TTL_MS)
113
+ records.set(focusId, { wants: true, atMs: nowMs });
114
+ }
115
+ everRecorded = true;
116
+ }
117
+ /** The foci last found wanting a team, or null before any pass has recorded. */
118
+ export function wantingFocusIds(nowMs = Date.now()) {
119
+ if (!everRecorded)
120
+ return null;
121
+ const out = new Set();
122
+ for (const [focusId, r] of records) {
123
+ if (nowMs - r.atMs > SPAWN_INTENT_TTL_MS) {
124
+ records.delete(focusId);
125
+ continue;
126
+ }
127
+ if (r.wants)
128
+ out.add(focusId);
129
+ }
130
+ return out;
131
+ }
132
+ /** Test-only reset. */
133
+ export function resetSpawnIntent() {
134
+ records.clear();
135
+ openPasses.clear();
136
+ everRecorded = false;
137
+ }
138
+ //# sourceMappingURL=tower-spawn-intent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tower-spawn-intent.js","sourceRoot":"","sources":["../../src/tower/tower-spawn-intent.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,81 @@
1
+ /**
2
+ * The tower's verdict computation for ONE product -- pure (inputs in, verdicts
3
+ * out), so the slot rule, the rank order and the reason precedence are tested
4
+ * directly. The pass that feeds it and writes the result is tower-pass.ts.
5
+ *
6
+ * THE SLOT RULE. A focus occupies one of the product's slots only while it has a
7
+ * live coding or review team. A live focus is always cleared: the tower never
8
+ * stops a running team. The free slots (limit minus live foci; unbounded when
9
+ * the product has no limit) are cleared to the other foci in RANK order (lowest
10
+ * priority_rank first), foci the spawn pass last found WANTING a team ahead of
11
+ * foci it found idle -- so a focus that is armed but has nothing to run (waiting
12
+ * on CI, a verify hold, an escalation) never takes the slot from one that does.
13
+ * Every focus beyond the free slots holds with `product_limit_reached`.
14
+ *
15
+ * THE REASONS, in the precedence daemon-core TOWER_HOLD_REASONS declares:
16
+ * waits_on -- a depends_on predecessor has not landed;
17
+ * excluded_by -- an excludes edge joins it to a focus that has a live
18
+ * team or was cleared ahead of it in this pass (so two
19
+ * excluded foci are never cleared together);
20
+ * product_limit_reached -- the free slots went to foci ahead of it.
21
+ * A focus held for a graph reason takes no slot.
22
+ *
23
+ * A focus whose inputs could not be judged (UNAVAILABLE) gets no verdict (null):
24
+ * the spawn pass fails open on it rather than holding it.
25
+ */
26
+ import { type TowerMode, type TowerVerdict } from '@telora/daemon-core';
27
+ /** One armed, active focus of the product. */
28
+ export interface TowerFocusInput {
29
+ focusId: string;
30
+ focusName: string;
31
+ priorityRank: number;
32
+ /** product_focuses.agent_assigned_at: the instant of the current arm. */
33
+ armedAt: string | null;
34
+ /** The stored product_focuses.tower_verdict (raw JSONB). */
35
+ previousVerdict: unknown;
36
+ }
37
+ /** An excludes edge seen from one side. */
38
+ export interface TowerExclusion {
39
+ otherFocusId: string;
40
+ otherFocusName: string;
41
+ evidence: string;
42
+ }
43
+ /** The first depends_on predecessor that has not landed. */
44
+ export interface TowerUnlandedPredecessor {
45
+ focusId: string;
46
+ focusName: string;
47
+ }
48
+ export interface TowerComputeInput {
49
+ foci: readonly TowerFocusInput[];
50
+ /** products.tower_concurrency_limit; null = no product limit. */
51
+ limit: number | null;
52
+ /** Foci holding a live coding or review team. */
53
+ liveTeamFocusIds: ReadonlySet<string>;
54
+ /** Foci the spawn pass last found wanting a team; null = not yet known (all want). */
55
+ wantingFocusIds: ReadonlySet<string> | null;
56
+ /** depends_on: focus -> its first unlanded predecessor. Absent = none. */
57
+ waitsOn?: ReadonlyMap<string, TowerUnlandedPredecessor>;
58
+ /** excludes: focus -> the foci an excludes edge joins it to. Absent = none. */
59
+ excludes?: ReadonlyMap<string, readonly TowerExclusion[]>;
60
+ /** Foci whose graph inputs could not be judged (e.g. a landed check failed). */
61
+ unavailableFocusIds?: ReadonlySet<string>;
62
+ mode: Exclude<TowerMode, 'off'>;
63
+ now: string;
64
+ }
65
+ /** focusId -> the verdict to hold, or null for "no verdict" (fail open). */
66
+ export type TowerComputed = Map<string, TowerVerdict | null>;
67
+ /** Compute every armed focus's verdict for one product. */
68
+ export declare function computeTowerVerdicts(input: TowerComputeInput): TowerComputed;
69
+ /** One verdict write: the verdict, or null to clear it. */
70
+ export interface TowerVerdictWrite {
71
+ focusId: string;
72
+ verdict: TowerVerdict | null;
73
+ }
74
+ /**
75
+ * Which computed verdicts need writing: a changed verdict, a cleared-to-null
76
+ * one, or an unchanged one whose last confirmation is past HALF the bounded age
77
+ * (so a running tower's verdicts never go stale under the spawn pass). Plus a
78
+ * clear for every un-armed focus still carrying a verdict.
79
+ */
80
+ export declare function planTowerWrites(foci: readonly TowerFocusInput[], computed: TowerComputed, unarmedWithVerdictFocusIds: readonly string[], nowMs: number, maxAgeMs?: number): TowerVerdictWrite[];
81
+ //# sourceMappingURL=tower-verdicts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tower-verdicts.d.ts","sourceRoot":"","sources":["../../src/tower/tower-verdicts.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,EAML,KAAK,SAAS,EACd,KAAK,YAAY,EAElB,MAAM,qBAAqB,CAAC;AAE7B,8CAA8C;AAC9C,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,yEAAyE;IACzE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,4DAA4D;IAC5D,eAAe,EAAE,OAAO,CAAC;CAC1B;AAED,2CAA2C;AAC3C,MAAM,WAAW,cAAc;IAC7B,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,4DAA4D;AAC5D,MAAM,WAAW,wBAAwB;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,SAAS,eAAe,EAAE,CAAC;IACjC,iEAAiE;IACjE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,iDAAiD;IACjD,gBAAgB,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACtC,sFAAsF;IACtF,eAAe,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC;IAC5C,0EAA0E;IAC1E,OAAO,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,wBAAwB,CAAC,CAAC;IACxD,+EAA+E;IAC/E,QAAQ,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,SAAS,cAAc,EAAE,CAAC,CAAC;IAC1D,gFAAgF;IAChF,mBAAmB,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC1C,IAAI,EAAE,OAAO,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;IAChC,GAAG,EAAE,MAAM,CAAC;CACb;AAED,4EAA4E;AAC5E,MAAM,MAAM,aAAa,GAAG,GAAG,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI,CAAC,CAAC;AAyC7D,2DAA2D;AAC3D,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,iBAAiB,GAAG,aAAa,CAkD5E;AAED,2DAA2D;AAC3D,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,YAAY,GAAG,IAAI,CAAC;CAC9B;AAOD;;;;;GAKG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,SAAS,eAAe,EAAE,EAChC,QAAQ,EAAE,aAAa,EACvB,0BAA0B,EAAE,SAAS,MAAM,EAAE,EAC7C,KAAK,EAAE,MAAM,EACb,QAAQ,GAAE,MAAiC,GAC1C,iBAAiB,EAAE,CAgBrB"}
@@ -0,0 +1,139 @@
1
+ // PINNED-BY-BEGIN (generated by gen:pinned-by -- do not hand-edit; checked by lint:pinned-by)
2
+ // Pinned by: packages/daemon/src/tower/tower-pass.test.ts, packages/daemon/src/tower/tower-verdicts.test.ts
3
+ // PINNED-BY-END
4
+ /**
5
+ * The tower's verdict computation for ONE product -- pure (inputs in, verdicts
6
+ * out), so the slot rule, the rank order and the reason precedence are tested
7
+ * directly. The pass that feeds it and writes the result is tower-pass.ts.
8
+ *
9
+ * THE SLOT RULE. A focus occupies one of the product's slots only while it has a
10
+ * live coding or review team. A live focus is always cleared: the tower never
11
+ * stops a running team. The free slots (limit minus live foci; unbounded when
12
+ * the product has no limit) are cleared to the other foci in RANK order (lowest
13
+ * priority_rank first), foci the spawn pass last found WANTING a team ahead of
14
+ * foci it found idle -- so a focus that is armed but has nothing to run (waiting
15
+ * on CI, a verify hold, an escalation) never takes the slot from one that does.
16
+ * Every focus beyond the free slots holds with `product_limit_reached`.
17
+ *
18
+ * THE REASONS, in the precedence daemon-core TOWER_HOLD_REASONS declares:
19
+ * waits_on -- a depends_on predecessor has not landed;
20
+ * excluded_by -- an excludes edge joins it to a focus that has a live
21
+ * team or was cleared ahead of it in this pass (so two
22
+ * excluded foci are never cleared together);
23
+ * product_limit_reached -- the free slots went to foci ahead of it.
24
+ * A focus held for a graph reason takes no slot.
25
+ *
26
+ * A focus whose inputs could not be judged (UNAVAILABLE) gets no verdict (null):
27
+ * the spawn pass fails open on it rather than holding it.
28
+ */
29
+ import { TOWER_HOLD_REASON, TOWER_VERDICT_MAX_AGE_MS, TOWER_VERDICT_STATE, parseTowerVerdict, } from '@telora/daemon-core';
30
+ function byRank(a, b) {
31
+ return a.priorityRank - b.priorityRank || (a.focusId < b.focusId ? -1 : a.focusId > b.focusId ? 1 : 0);
32
+ }
33
+ /** Keep `since` when the focus already held this exact state + reason (+ other focus) under this arm. */
34
+ function sinceFor(focus, state, reason, detail, now) {
35
+ const prev = parseTowerVerdict(focus.previousVerdict);
36
+ if (!prev)
37
+ return now;
38
+ const sameArm = prev.armedAt === focus.armedAt
39
+ || (prev.armedAt !== null && focus.armedAt !== null && Date.parse(prev.armedAt) === Date.parse(focus.armedAt));
40
+ if (!sameArm || prev.state !== state || prev.reason !== reason)
41
+ return now;
42
+ if ((prev.detail.focusId ?? null) !== (detail.focusId ?? null))
43
+ return now;
44
+ return prev.since;
45
+ }
46
+ function verdict(focus, input, reason, detail = {}) {
47
+ const state = reason === null ? TOWER_VERDICT_STATE.CLEARED : TOWER_VERDICT_STATE.HOLDING;
48
+ return {
49
+ state,
50
+ reason,
51
+ since: sinceFor(focus, state, reason, detail, input.now),
52
+ computedAt: input.now,
53
+ armedAt: focus.armedAt,
54
+ mode: input.mode,
55
+ detail,
56
+ };
57
+ }
58
+ /** Compute every armed focus's verdict for one product. */
59
+ export function computeTowerVerdicts(input) {
60
+ const out = new Map();
61
+ const ordered = [...input.foci].sort(byRank);
62
+ const live = ordered.filter((f) => input.liveTeamFocusIds.has(f.focusId));
63
+ const cleared = new Set();
64
+ for (const f of live) {
65
+ out.set(f.focusId, verdict(f, input, null));
66
+ cleared.add(f.focusId);
67
+ }
68
+ // Wanting foci first, then idle ones, each in rank order.
69
+ const waiting = ordered.filter((f) => !input.liveTeamFocusIds.has(f.focusId));
70
+ const wants = (f) => input.wantingFocusIds === null || input.wantingFocusIds.has(f.focusId);
71
+ const queue = [...waiting.filter(wants), ...waiting.filter((f) => !wants(f))];
72
+ let free = input.limit === null ? Number.POSITIVE_INFINITY : Math.max(0, input.limit - live.length);
73
+ for (const f of queue) {
74
+ if (input.unavailableFocusIds?.has(f.focusId)) {
75
+ out.set(f.focusId, null);
76
+ continue;
77
+ }
78
+ const predecessor = input.waitsOn?.get(f.focusId);
79
+ if (predecessor) {
80
+ out.set(f.focusId, verdict(f, input, TOWER_HOLD_REASON.WAITS_ON, {
81
+ focusId: predecessor.focusId,
82
+ focusName: predecessor.focusName,
83
+ }));
84
+ continue;
85
+ }
86
+ const blocker = input.excludes?.get(f.focusId)?.find((e) => cleared.has(e.otherFocusId));
87
+ if (blocker) {
88
+ out.set(f.focusId, verdict(f, input, TOWER_HOLD_REASON.EXCLUDED_BY, {
89
+ focusId: blocker.otherFocusId,
90
+ focusName: blocker.otherFocusName,
91
+ evidence: blocker.evidence,
92
+ }));
93
+ continue;
94
+ }
95
+ if (free <= 0) {
96
+ out.set(f.focusId, verdict(f, input, TOWER_HOLD_REASON.PRODUCT_LIMIT_REACHED, {
97
+ ...(input.limit !== null ? { limit: input.limit } : {}),
98
+ occupied: live.length,
99
+ }));
100
+ continue;
101
+ }
102
+ free -= 1;
103
+ cleared.add(f.focusId);
104
+ out.set(f.focusId, verdict(f, input, null));
105
+ }
106
+ return out;
107
+ }
108
+ /** The facts that make two verdicts the same verdict (computedAt aside). */
109
+ function verdictKey(v) {
110
+ return JSON.stringify([v.state, v.reason, v.since, v.armedAt, v.mode, v.detail]);
111
+ }
112
+ /**
113
+ * Which computed verdicts need writing: a changed verdict, a cleared-to-null
114
+ * one, or an unchanged one whose last confirmation is past HALF the bounded age
115
+ * (so a running tower's verdicts never go stale under the spawn pass). Plus a
116
+ * clear for every un-armed focus still carrying a verdict.
117
+ */
118
+ export function planTowerWrites(foci, computed, unarmedWithVerdictFocusIds, nowMs, maxAgeMs = TOWER_VERDICT_MAX_AGE_MS) {
119
+ const writes = [];
120
+ for (const f of foci) {
121
+ if (!computed.has(f.focusId))
122
+ continue;
123
+ const next = computed.get(f.focusId) ?? null;
124
+ const prev = parseTowerVerdict(f.previousVerdict);
125
+ if (next === null) {
126
+ if (f.previousVerdict !== null && f.previousVerdict !== undefined)
127
+ writes.push({ focusId: f.focusId, verdict: null });
128
+ continue;
129
+ }
130
+ const changed = !prev || verdictKey(prev) !== verdictKey(next);
131
+ const aging = prev !== null && nowMs - Date.parse(prev.computedAt) > maxAgeMs / 2;
132
+ if (changed || aging)
133
+ writes.push({ focusId: f.focusId, verdict: next });
134
+ }
135
+ for (const focusId of unarmedWithVerdictFocusIds)
136
+ writes.push({ focusId, verdict: null });
137
+ return writes;
138
+ }
139
+ //# sourceMappingURL=tower-verdicts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tower-verdicts.js","sourceRoot":"","sources":["../../src/tower/tower-verdicts.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  // PINNED-BY-BEGIN (generated by gen:pinned-by -- do not hand-edit; checked by lint:pinned-by)
2
- // Pinned by: 253 tests -- run: npm run gen:pinned-by -- --list packages/daemon/src/types/index.ts
2
+ // Pinned by: 256 tests -- run: npm run gen:pinned-by -- --list packages/daemon/src/types/index.ts
3
3
  // PINNED-BY-END
4
4
  /**
5
5
  * Domain-specific type re-exports.