@overdeck/core 0.50.3 → 0.50.4

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 (208) hide show
  1. package/dist/agents-DNMRPC8n.js.map +1 -1
  2. package/dist/{build-info-BJuEVWOh.js → build-info-ioRdOmqT.js} +3 -3
  3. package/dist/{build-info-BJuEVWOh.js.map → build-info-ioRdOmqT.js.map} +1 -1
  4. package/dist/build-info-kMMitDOP.js +2 -0
  5. package/dist/cli/index.js +4 -4
  6. package/dist/conversation-compaction-GFfO5UZL.js.map +1 -1
  7. package/dist/dashboard/{advancing-selfheal-Bq8V0QUa.js → advancing-selfheal-Dp5_Gai3.js} +3 -3
  8. package/dist/dashboard/{advancing-selfheal-Bq8V0QUa.js.map → advancing-selfheal-Dp5_Gai3.js.map} +1 -1
  9. package/dist/dashboard/{agent-gc-Coz8zQt_.js → agent-gc-JzoSs5Pi.js} +3 -3
  10. package/dist/dashboard/{agent-gc-Coz8zQt_.js.map → agent-gc-JzoSs5Pi.js.map} +1 -1
  11. package/dist/dashboard/{agent-idle-CAwB-K6M.js → agent-idle-D-Qy5BN_.js} +2 -2
  12. package/dist/dashboard/{agent-idle-CAwB-K6M.js.map → agent-idle-D-Qy5BN_.js.map} +1 -1
  13. package/dist/dashboard/{agent-state-Ci6PhjOE.js → agent-state-B196fj1h.js} +38 -16
  14. package/dist/dashboard/agent-state-B196fj1h.js.map +1 -0
  15. package/dist/dashboard/{agent-state-CbUEwUEQ.js → agent-state-IpWnw8lv.js} +1 -1
  16. package/dist/dashboard/{agent-state-service-D6IVrX3B.js → agent-state-service-CCo5-9DR.js} +3 -3
  17. package/dist/dashboard/{agent-state-service-D6IVrX3B.js.map → agent-state-service-CCo5-9DR.js.map} +1 -1
  18. package/dist/dashboard/{agent-state-service-DcN1j6eK.js → agent-state-service-D_F3VZ23.js} +1 -1
  19. package/dist/dashboard/{agents-BVvL4o_4.js → agents-CkGhnAmu.js} +1 -1
  20. package/dist/dashboard/{backlog-auto-trigger-BuSBqG2V.js → backlog-auto-trigger-B5Z2zBtC.js} +2 -2
  21. package/dist/dashboard/{backlog-auto-trigger-BuSBqG2V.js.map → backlog-auto-trigger-B5Z2zBtC.js.map} +1 -1
  22. package/dist/dashboard/build-info-BTjOB0Xr.js +2 -0
  23. package/dist/dashboard/{build-info-Cj3Y0K87.js → build-info-D0EJNj6C.js} +3 -3
  24. package/dist/dashboard/{build-info-Cj3Y0K87.js.map → build-info-D0EJNj6C.js.map} +1 -1
  25. package/dist/dashboard/cli-CIznFrcE.js +2 -0
  26. package/dist/dashboard/{cli-DIhQieif.js → cli-DCbVrSsg.js} +2 -2
  27. package/dist/dashboard/{cli-DIhQieif.js.map → cli-DCbVrSsg.js.map} +1 -1
  28. package/dist/dashboard/{close-issue-CRlmrE5g.js → close-issue-Bmu9aOIx.js} +2 -2
  29. package/dist/dashboard/{close-issue-CRlmrE5g.js.map → close-issue-Bmu9aOIx.js.map} +1 -1
  30. package/dist/dashboard/{closed-issue-reaper-DrHyYgJR.js → closed-issue-reaper-COxfTtLz.js} +5 -5
  31. package/dist/dashboard/{closed-issue-reaper-DrHyYgJR.js.map → closed-issue-reaper-COxfTtLz.js.map} +1 -1
  32. package/dist/dashboard/{closed-issue-reaper-DWlw2EtL.js → closed-issue-reaper-DwMwG_Pz.js} +1 -1
  33. package/dist/dashboard/{compaction-continuation-DhYgsXmF.js → compaction-continuation-Bqc212Zm.js} +2 -2
  34. package/dist/dashboard/{compaction-continuation-DhYgsXmF.js.map → compaction-continuation-Bqc212Zm.js.map} +1 -1
  35. package/dist/dashboard/{conflict-gate-vczgdVTa.js → conflict-gate-9mPVg7-5.js} +1 -1
  36. package/dist/dashboard/{conflict-gate-Bt-lLgYf.js → conflict-gate-CGQTcR0I.js} +3 -3
  37. package/dist/dashboard/{conflict-gate-Bt-lLgYf.js.map → conflict-gate-CGQTcR0I.js.map} +1 -1
  38. package/dist/dashboard/{conversation-compaction-CsdCbcQB.js → conversation-compaction-C7P6V8Iw.js} +5 -5
  39. package/dist/dashboard/{conversation-compaction-CsdCbcQB.js.map → conversation-compaction-C7P6V8Iw.js.map} +1 -1
  40. package/dist/dashboard/{conversation-compaction-D0zgWM1f.js → conversation-compaction-Cck5oi-V.js} +1 -1
  41. package/dist/dashboard/{conversation-service-6LaymUV-.js → conversation-service-CsOpwI9Y.js} +37 -5
  42. package/dist/dashboard/{conversation-service-6LaymUV-.js.map → conversation-service-CsOpwI9Y.js.map} +1 -1
  43. package/dist/dashboard/conversation-service-plOFkoMo.js +3 -0
  44. package/dist/dashboard/dashboard-db-worker.js +1 -1
  45. package/dist/dashboard/{deacon-CU47dU7U.js → deacon-B8oMMIgN.js} +62 -62
  46. package/dist/dashboard/{deacon-CU47dU7U.js.map → deacon-B8oMMIgN.js.map} +1 -1
  47. package/dist/dashboard/{deacon-DYsOa_ss.js → deacon-b7J57Svt.js} +2 -2
  48. package/dist/dashboard/{deacon-pending-lifecycle-ijGX8wLb.js → deacon-pending-lifecycle-3NIJ7ew5.js} +2 -2
  49. package/dist/dashboard/{deacon-pending-lifecycle-ijGX8wLb.js.map → deacon-pending-lifecycle-3NIJ7ew5.js.map} +1 -1
  50. package/dist/dashboard/deacon.js +5 -5
  51. package/dist/dashboard/{delivery-DMYxIE0J.js → delivery-lnI-gQKo.js} +1 -1
  52. package/dist/dashboard/{deploy-patrol-C4eXco1S.js → deploy-patrol-u3v2krg9.js} +2 -2
  53. package/dist/dashboard/{deploy-patrol-C4eXco1S.js.map → deploy-patrol-u3v2krg9.js.map} +1 -1
  54. package/dist/dashboard/{feedback-stuck-retirement-Dd5XNrMr.js → feedback-stuck-retirement-CoFNqjCc.js} +2 -2
  55. package/dist/dashboard/{feedback-stuck-retirement-Dd5XNrMr.js.map → feedback-stuck-retirement-CoFNqjCc.js.map} +1 -1
  56. package/dist/dashboard/{feedback-target-BzdJ2wIR.js → feedback-target-BBCh3RRq.js} +1 -1
  57. package/dist/dashboard/{feedback-target-BcsA_GRV.js → feedback-target-CeCq6EhW.js} +5 -5
  58. package/dist/dashboard/{feedback-target-BcsA_GRV.js.map → feedback-target-CeCq6EhW.js.map} +1 -1
  59. package/dist/dashboard/{flywheel-Is3w2lcT.js → flywheel-DKldI63J.js} +7 -7
  60. package/dist/dashboard/{flywheel-Is3w2lcT.js.map → flywheel-DKldI63J.js.map} +1 -1
  61. package/dist/dashboard/{flywheel-D0guNqkh.js → flywheel-DpRPLErh.js} +7 -7
  62. package/dist/dashboard/{flywheel-D0guNqkh.js.map → flywheel-DpRPLErh.js.map} +1 -1
  63. package/dist/dashboard/{flywheel-B34cdfbd.js → flywheel-jeZFYvm-.js} +1 -1
  64. package/dist/dashboard/{flywheel-merge-order-6HiPR2WG.js → flywheel-merge-order-0OnoosaO.js} +1 -1
  65. package/dist/dashboard/{flywheel-merge-order-BsNPMu0S.js → flywheel-merge-order-PG5op4j7.js} +2 -2
  66. package/dist/dashboard/{flywheel-merge-order-BsNPMu0S.js.map → flywheel-merge-order-PG5op4j7.js.map} +1 -1
  67. package/dist/dashboard/{harness-resolve-CzTFVZ5l.js → harness-resolve-C0HWj3H9.js} +2 -2
  68. package/dist/dashboard/{harness-resolve-CzTFVZ5l.js.map → harness-resolve-C0HWj3H9.js.map} +1 -1
  69. package/dist/dashboard/{identity-Dc7rQLln.js → identity-Cot1Hg4G.js} +2 -2
  70. package/dist/dashboard/{identity-Dc7rQLln.js.map → identity-Cot1Hg4G.js.map} +1 -1
  71. package/dist/dashboard/{inspect-agent-CP7Bu47O.js → inspect-agent-BDmLWbGx.js} +3 -3
  72. package/dist/dashboard/{inspect-agent-CP7Bu47O.js.map → inspect-agent-BDmLWbGx.js.map} +1 -1
  73. package/dist/dashboard/inspect-agent-BcOkA-hC.js +2 -0
  74. package/dist/dashboard/{issue-lifecycle-BPbczKyy.js → issue-lifecycle-1B_v8Rf5.js} +6 -6
  75. package/dist/dashboard/{issue-lifecycle-BPbczKyy.js.map → issue-lifecycle-1B_v8Rf5.js.map} +1 -1
  76. package/dist/dashboard/{issue-lifecycle-FaugTi39.js → issue-lifecycle-CIrfbIbj.js} +1 -1
  77. package/dist/dashboard/{issue-service-singleton-Dsad9NTt.js → issue-service-singleton-BVwPzjwF.js} +1 -1
  78. package/dist/dashboard/{issue-service-singleton-BrZQJLRI.js → issue-service-singleton-KcRrOiFD.js} +2 -2
  79. package/dist/dashboard/{issue-service-singleton-BrZQJLRI.js.map → issue-service-singleton-KcRrOiFD.js.map} +1 -1
  80. package/dist/dashboard/{jsonl-resolver-DQEe0DHM.js → jsonl-resolver-jxbif7MD.js} +3 -3
  81. package/dist/dashboard/{jsonl-resolver-DQEe0DHM.js.map → jsonl-resolver-jxbif7MD.js.map} +1 -1
  82. package/dist/dashboard/{kimi-code-Cx5YUJpS.js → kimi-code-BZh3NgcN.js} +1 -1
  83. package/dist/dashboard/{lifecycle-DZFHmpGI.js → lifecycle-CvcZ4_fA.js} +4 -4
  84. package/dist/dashboard/{lifecycle-DZFHmpGI.js.map → lifecycle-CvcZ4_fA.js.map} +1 -1
  85. package/dist/dashboard/{linear-mcp-auth-DylPJABZ.js → linear-mcp-auth-CZoj_-d7.js} +1 -1
  86. package/dist/dashboard/{linear-mcp-auth-BFlpMvWh.js → linear-mcp-auth-Dx74yCAY.js} +2 -2
  87. package/dist/dashboard/{linear-mcp-auth-BFlpMvWh.js.map → linear-mcp-auth-Dx74yCAY.js.map} +1 -1
  88. package/dist/dashboard/{memory-governor-B7uDWRo9.js → memory-governor-Bv3LAqk3.js} +5 -5
  89. package/dist/dashboard/{memory-governor-B7uDWRo9.js.map → memory-governor-Bv3LAqk3.js.map} +1 -1
  90. package/dist/dashboard/{merge-agent-ddEcZDZD.js → merge-agent-1zVLl6k_.js} +1 -1
  91. package/dist/dashboard/{merge-agent-ttRY053V.js → merge-agent-8zrQu48t.js} +8 -8
  92. package/dist/dashboard/{merge-agent-ttRY053V.js.map → merge-agent-8zrQu48t.js.map} +1 -1
  93. package/dist/dashboard/merge-ops-CFJaMPLu.js +2 -0
  94. package/dist/dashboard/{merge-train-f_un38UJ.js → merge-train-BYH_cF7G.js} +2 -2
  95. package/dist/dashboard/{merge-train-f_un38UJ.js.map → merge-train-BYH_cF7G.js.map} +1 -1
  96. package/dist/dashboard/{merge-train-deps-GPlHOjK6.js → merge-train-deps-BTfXM5AR.js} +2 -2
  97. package/dist/dashboard/{merge-train-deps-GPlHOjK6.js.map → merge-train-deps-BTfXM5AR.js.map} +1 -1
  98. package/dist/dashboard/{messaging-BasbftDu.js → messaging-Bk6vGsMw.js} +1 -1
  99. package/dist/dashboard/{orphan-proposed-reconciler-DFHdQSEg.js → orphan-proposed-reconciler-B898fk26.js} +6 -6
  100. package/dist/dashboard/{orphan-proposed-reconciler-DFHdQSEg.js.map → orphan-proposed-reconciler-B898fk26.js.map} +1 -1
  101. package/dist/dashboard/{orphan-proposed-reconciler-DzAxrM99.js → orphan-proposed-reconciler-Bx_aWS4B.js} +1 -1
  102. package/dist/dashboard/{parked-residue-DMtbxJml.js → parked-residue-q2kzrYSm.js} +9 -9
  103. package/dist/dashboard/{parked-residue-DMtbxJml.js.map → parked-residue-q2kzrYSm.js.map} +1 -1
  104. package/dist/dashboard/{post-merge-lifecycle-worker-jzN2g_EY.js → post-merge-lifecycle-worker-Cl7-uf4g.js} +2 -2
  105. package/dist/dashboard/{post-merge-lifecycle-worker-jzN2g_EY.js.map → post-merge-lifecycle-worker-Cl7-uf4g.js.map} +1 -1
  106. package/dist/dashboard/{project-resource-refresh-queue-cqOeJ0fB.js → project-resource-refresh-queue-O_dRfPHn.js} +1 -1
  107. package/dist/dashboard/{project-resource-refresh-queue-DebwjPPA.js → project-resource-refresh-queue-se3T9IYP.js} +4 -4
  108. package/dist/dashboard/{project-resource-refresh-queue-DebwjPPA.js.map → project-resource-refresh-queue-se3T9IYP.js.map} +1 -1
  109. package/dist/dashboard/{reconstruct-cache-Bb2Jg2DZ.js → reconstruct-cache-XpWC9L0F.js} +2 -2
  110. package/dist/dashboard/{reconstruct-cache-Bb2Jg2DZ.js.map → reconstruct-cache-XpWC9L0F.js.map} +1 -1
  111. package/dist/dashboard/{release-engine-78iCajIc.js → release-engine-BuzxOamf.js} +2 -2
  112. package/dist/dashboard/{release-engine-78iCajIc.js.map → release-engine-BuzxOamf.js.map} +1 -1
  113. package/dist/dashboard/{remote-completion-D5wzZCte.js → remote-completion-BCZgeKPS.js} +3 -3
  114. package/dist/dashboard/{remote-completion-D5wzZCte.js.map → remote-completion-BCZgeKPS.js.map} +1 -1
  115. package/dist/dashboard/{resolver-BGZh3lXA.js → resolver-BeNrqNou.js} +5 -5
  116. package/dist/dashboard/{resolver-BGZh3lXA.js.map → resolver-BeNrqNou.js.map} +1 -1
  117. package/dist/dashboard/{resource-pressure-patrol-Bg23rPV-.js → resource-pressure-patrol-Ccg_aCgO.js} +3 -3
  118. package/dist/dashboard/{resource-pressure-patrol-Bg23rPV-.js.map → resource-pressure-patrol-Ccg_aCgO.js.map} +1 -1
  119. package/dist/dashboard/{resume-BKyB4hA1.js → resume-KGCrn0DV.js} +1 -1
  120. package/dist/dashboard/{review-agent-ZdP-HmwY.js → review-agent-BmM6YMZW.js} +1 -1
  121. package/dist/dashboard/{review-agent-DHs-JngN.js → review-agent-DRWVU8fP.js} +13 -13
  122. package/dist/dashboard/{review-agent-DHs-JngN.js.map → review-agent-DRWVU8fP.js.map} +1 -1
  123. package/dist/dashboard/{review-convoy-liveness-BTMvU4CA.js → review-convoy-liveness-EvCDv8Q_.js} +3 -3
  124. package/dist/dashboard/{review-convoy-liveness-BTMvU4CA.js.map → review-convoy-liveness-EvCDv8Q_.js.map} +1 -1
  125. package/dist/dashboard/{review-run-recovery-BYalQzbh.js → review-run-recovery-Ctm48Snt.js} +1 -1
  126. package/dist/dashboard/{review-run-recovery-BvVG3oUr.js → review-run-recovery-DNDI58cM.js} +2 -2
  127. package/dist/dashboard/{review-run-recovery-BvVG3oUr.js.map → review-run-recovery-DNDI58cM.js.map} +1 -1
  128. package/dist/dashboard/{review-status-DXTFzCmR.js → review-status-FnlvQuPI.js} +1 -1
  129. package/dist/dashboard/{review-status-emit-C9YW6SQL.js → review-status-emit-CeMS31DX.js} +2 -2
  130. package/dist/dashboard/{review-status-emit-C9YW6SQL.js.map → review-status-emit-CeMS31DX.js.map} +1 -1
  131. package/dist/dashboard/{review-status-BegAr86G.js → review-status-yY5qt7Rl.js} +5 -5
  132. package/dist/dashboard/{review-status-BegAr86G.js.map → review-status-yY5qt7Rl.js.map} +1 -1
  133. package/dist/dashboard/{review-verdict-feedback-qlVBi19-.js → review-verdict-feedback-BNgRj1e3.js} +4 -4
  134. package/dist/dashboard/{review-verdict-feedback-qlVBi19-.js.map → review-verdict-feedback-BNgRj1e3.js.map} +1 -1
  135. package/dist/dashboard/{review-verdict-feedback-Dp139Lmv.js → review-verdict-feedback-D39cuSV1.js} +1 -1
  136. package/dist/dashboard/{review-verdict-writer-CJr_Ob0Y.js → review-verdict-writer-DvFfKAn-.js} +2 -2
  137. package/dist/dashboard/{review-verdict-writer-CJr_Ob0Y.js.map → review-verdict-writer-DvFfKAn-.js.map} +1 -1
  138. package/dist/dashboard/{review-verdict-writer-BVco3WoI.js → review-verdict-writer-PcGBkvJ4.js} +1 -1
  139. package/dist/dashboard/{sequence-io-iljWlslY.js → sequence-io-DZyschsf.js} +2 -2
  140. package/dist/dashboard/{sequence-io-iljWlslY.js.map → sequence-io-DZyschsf.js.map} +1 -1
  141. package/dist/dashboard/{sequencer-agent-BGGVKxtw.js → sequencer-agent-BXDw5cNx.js} +5 -5
  142. package/dist/dashboard/{sequencer-agent-BGGVKxtw.js.map → sequencer-agent-BXDw5cNx.js.map} +1 -1
  143. package/dist/dashboard/server.js +79 -72
  144. package/dist/dashboard/server.js.map +1 -1
  145. package/dist/dashboard/{service-kzhN0DLg.js → service-DP50Omvn.js} +33 -33
  146. package/dist/dashboard/{service-kzhN0DLg.js.map → service-DP50Omvn.js.map} +1 -1
  147. package/dist/dashboard/{smart-compaction-owKHJDbL.js → smart-compaction-944GL8PN.js} +1 -1
  148. package/dist/dashboard/{smart-compaction-C96F2aFQ.js → smart-compaction-BzDfF6yb.js} +2 -2
  149. package/dist/dashboard/{smart-compaction-C96F2aFQ.js.map → smart-compaction-BzDfF6yb.js.map} +1 -1
  150. package/dist/dashboard/{spawn-planning-session-CiJdOHTD.js → spawn-planning-session-BcVnHJjm.js} +1 -1
  151. package/dist/dashboard/{spawn-planning-session-FmJa6PCS.js → spawn-planning-session-CH8IYli7.js} +3 -3
  152. package/dist/dashboard/{spawn-planning-session-FmJa6PCS.js.map → spawn-planning-session-CH8IYli7.js.map} +1 -1
  153. package/dist/dashboard/{specialist-context-_ootmzn2.js → specialist-context-DhDTXVht.js} +3 -3
  154. package/dist/dashboard/{specialist-context-_ootmzn2.js.map → specialist-context-DhDTXVht.js.map} +1 -1
  155. package/dist/dashboard/{specialist-logs-BbAX221-.js → specialist-logs-C1bcnZ-G.js} +2 -2
  156. package/dist/dashboard/{specialist-logs-BbAX221-.js.map → specialist-logs-C1bcnZ-G.js.map} +1 -1
  157. package/dist/dashboard/{specialists-COZoYaiz.js → specialists-BKmReEWT.js} +53 -53
  158. package/dist/dashboard/{specialists-COZoYaiz.js.map → specialists-BKmReEWT.js.map} +1 -1
  159. package/dist/dashboard/{specialists-RPLJnYc1.js → specialists-Bvrvt0br.js} +12 -12
  160. package/dist/dashboard/{specialists-RPLJnYc1.js.map → specialists-Bvrvt0br.js.map} +1 -1
  161. package/dist/dashboard/{specialists-DTAOVZpU.js → specialists-CD6GEKXk.js} +1 -1
  162. package/dist/dashboard/{specialists-D8sYZnPb.js → specialists-eFWpJRBc.js} +1 -1
  163. package/dist/dashboard/{stall-sweeper-9bRBZhrQ.js → stall-sweeper-D3fZvg7O.js} +3 -3
  164. package/dist/dashboard/{stall-sweeper-9bRBZhrQ.js.map → stall-sweeper-D3fZvg7O.js.map} +1 -1
  165. package/dist/dashboard/{state-migrate-J3tFJnbd.js → state-migrate-KZ2goscQ.js} +2 -2
  166. package/dist/dashboard/{state-migrate-J3tFJnbd.js.map → state-migrate-KZ2goscQ.js.map} +1 -1
  167. package/dist/dashboard/{stuck-remediation-BvTK_NYN.js → stuck-remediation-Cb2V1roj.js} +1 -1
  168. package/dist/dashboard/{stuck-remediation-4Rf_xMEb.js → stuck-remediation-CnoV0kcv.js} +5 -5
  169. package/dist/dashboard/{stuck-remediation-4Rf_xMEb.js.map → stuck-remediation-CnoV0kcv.js.map} +1 -1
  170. package/dist/dashboard/{summary-fork-PLcckTRE.js → summary-fork-CxuNBM0F.js} +2 -2
  171. package/dist/dashboard/{summary-fork-D-cylknJ.js → summary-fork-lC2vMXly.js} +4 -4
  172. package/dist/dashboard/{summary-fork-D-cylknJ.js.map → summary-fork-lC2vMXly.js.map} +1 -1
  173. package/dist/dashboard/{teardown-workspace-CTL16rnG.js → teardown-workspace-C-9murBw.js} +4 -4
  174. package/dist/dashboard/{teardown-workspace-CTL16rnG.js.map → teardown-workspace-C-9murBw.js.map} +1 -1
  175. package/dist/dashboard/{test-agent-queue-CMgYZiR_.js → test-agent-queue-YCBRzaAB.js} +3 -3
  176. package/dist/dashboard/{test-agent-queue-CMgYZiR_.js.map → test-agent-queue-YCBRzaAB.js.map} +1 -1
  177. package/dist/dashboard/{uat-failure-feedback-DS6W9X3I.js → uat-failure-feedback-B-1mL8Zp.js} +3 -3
  178. package/dist/dashboard/{uat-failure-feedback-DS6W9X3I.js.map → uat-failure-feedback-B-1mL8Zp.js.map} +1 -1
  179. package/dist/dashboard/{uat-promote-verification-CRsIHE_q.js → uat-promote-verification-BEBPcpPg.js} +2 -2
  180. package/dist/dashboard/{uat-promote-verification-CRsIHE_q.js.map → uat-promote-verification-BEBPcpPg.js.map} +1 -1
  181. package/dist/dashboard/{uat-train-BoA9r6_I.js → uat-train-BFo3OQUB.js} +1 -1
  182. package/dist/dashboard/{uat-train-Cd8CJeNe.js → uat-train-COgoJomz.js} +8 -8
  183. package/dist/dashboard/{uat-train-Cd8CJeNe.js.map → uat-train-COgoJomz.js.map} +1 -1
  184. package/dist/dashboard/{validate-BdmvTIym.js → validate-069_N1e2.js} +2 -2
  185. package/dist/dashboard/{validate-BdmvTIym.js.map → validate-069_N1e2.js.map} +1 -1
  186. package/dist/dashboard/{verification-runner-BO0K3xNA.js → verification-runner-CQnZyC5A.js} +1 -1
  187. package/dist/dashboard/{verification-runner-fEY1Qj-l.js → verification-runner-fy3wNrWn.js} +4 -4
  188. package/dist/dashboard/{verification-runner-fEY1Qj-l.js.map → verification-runner-fy3wNrWn.js.map} +1 -1
  189. package/dist/dashboard/workflows-Bk6Kt0ZQ.js +2 -0
  190. package/dist/dashboard/{workflows-CxVnygYU.js → workflows-N4QMkDzN.js} +10 -10
  191. package/dist/dashboard/{workflows-CxVnygYU.js.map → workflows-N4QMkDzN.js.map} +1 -1
  192. package/dist/dashboard/{workspaces-Cpz0xJWQ.js → workspaces-DmoyfHoU.js} +33 -33
  193. package/dist/dashboard/{workspaces-Cpz0xJWQ.js.map → workspaces-DmoyfHoU.js.map} +1 -1
  194. package/dist/{lifecycle-C6k0HWEO.js → lifecycle-DCFcjg7y.js} +1 -1
  195. package/dist/{lifecycle-D5oRpvvB.js → lifecycle-GkaHQJNK.js} +2 -2
  196. package/dist/{lifecycle-D5oRpvvB.js.map → lifecycle-GkaHQJNK.js.map} +1 -1
  197. package/dist/{merge-agent-Drufc7tU.js → merge-agent-CogNFasY.js} +2 -2
  198. package/dist/{merge-agent-Drufc7tU.js.map → merge-agent-CogNFasY.js.map} +1 -1
  199. package/dist/{merge-agent-CBOVY_eC.js → merge-agent-eteKQ9Rb.js} +1 -1
  200. package/package.json +1 -1
  201. package/dist/build-info-BgrwLUy6.js +0 -2
  202. package/dist/dashboard/agent-state-Ci6PhjOE.js.map +0 -1
  203. package/dist/dashboard/build-info-CYlxvZSQ.js +0 -2
  204. package/dist/dashboard/cli-ZAS502uC.js +0 -2
  205. package/dist/dashboard/conversation-service-Dlks_hSS.js +0 -3
  206. package/dist/dashboard/inspect-agent-DTuSg95i.js +0 -2
  207. package/dist/dashboard/merge-ops-Bgc-EScO.js +0 -2
  208. package/dist/dashboard/workflows-CgGNyl1z.js +0 -2
@@ -1 +1 @@
1
- {"version":3,"file":"uat-train-Cd8CJeNe.js","names":["basename","dirname","conflictingWith","execFileAsync","resolve","relative","isAbsolute","rm","execFileAsync","defaultDeps","execFileAsync","dirname","join","basename","join","nodeServicesLayer"],"sources":["../../src/lib/cloister/uat-candidate-name.ts","../../src/lib/cloister/uat-union-lint.ts","../../src/lib/cloister/uat-generation-engine.ts","../../src/lib/cloister/uat-generation-deps.ts","../../src/lib/cloister/uat-polyrepo-engine.ts","../../src/lib/cloister/uat-conflict-agent.ts","../../src/lib/cloister/uat-reconciler.ts","../../src/lib/cloister/uat-stack.ts","../../src/lib/cloister/uat-promote.ts","../../src/lib/cloister/uat-promote-notify.ts","../../src/dashboard/server/services/uat-train.ts"],"sourcesContent":["/**\n * UAT candidate branch naming (PAN-1691) — codename + short date.\n *\n * One candidate branch is assembled per label/day and force-reset on rebuild.\n * Format: `uat/<label>-<codename>-<MMDD>`, e.g. `uat/pan-otter-0609`. Codenames\n * are short, memorable words you can say out loud (\"ship the otter candidate\").\n *\n * The core is pure and fully injectable (wordlist / index pick / collision\n * date) so it's deterministically testable. The default codename index is a\n * stable hash of label + day so rebuilds for the same day reuse the same branch.\n */\n\nexport const UAT_CODENAMES: readonly string[] = [\n 'otter', 'falcon', 'cedar', 'quartz', 'ember', 'marlin', 'willow', 'cobalt',\n 'sable', 'heron', 'onyx', 'larch', 'perch', 'flint', 'dune', 'reef', 'vale',\n 'crow', 'moss', 'thorn', 'birch', 'koi', 'slate', 'wren',\n];\n\nfunction mmdd(dateIso: string): string {\n const [, month, day] = dateIso.slice(0, 10).split('-');\n return `${month ?? '00'}${day ?? '00'}`;\n}\n\nexport interface UatNameDeps {\n /** Project label, e.g. 'pan'. */\n label: string;\n /** ISO date string; only the YYYY-MM-DD prefix is used (→ MMDD). */\n dateIso: string;\n /** Injectable wordlist (default UAT_CODENAMES). */\n codenames?: readonly string[];\n /** Returns an index in [0, n) (default: stable hash of label + MMDD). */\n pick?: (n: number) => number;\n}\n\n/**\n * Build the deterministic `uat/<label>-<codename>-<MMDD>` branch name. Pure.\n */\nexport function makeUatCandidateName(deps: UatNameDeps): string {\n const day = mmdd(deps.dateIso);\n const words = deps.codenames ?? UAT_CODENAMES;\n const pick = deps.pick ?? ((n: number) => stableIndex(`${deps.label}-${day}`, n));\n const index = words.length > 0 ? pick(words.length) % words.length : 0;\n const word = words[index] ?? 'candidate';\n return `uat/${deps.label}-${word}-${day}`;\n}\n\n/** Keep the deterministic first name, then mint -2, -3, … around durable collisions. */\nexport function makeUniqueUatCandidateName(deps: UatNameDeps, takenNames: Iterable<string>): string {\n const base = makeUatCandidateName(deps);\n const taken = new Set(takenNames);\n if (!taken.has(base)) return base;\n let suffix = 2;\n while (taken.has(`${base}-${suffix}`)) suffix += 1;\n return `${base}-${suffix}`;\n}\n\nfunction stableIndex(seed: string, modulo: number): number {\n if (modulo <= 0) return 0;\n let hash = 2166136261;\n for (let i = 0; i < seed.length; i++) {\n hash ^= seed.charCodeAt(i);\n hash = Math.imul(hash, 16777619);\n }\n return (hash >>> 0) % modulo;\n}\n","/**\n * Union lint for UAT generation assembly (PAN-3166).\n *\n * Git merges branches by *content*, so two branches that each add a differently\n * named file never conflict — even when the union of those files is invalid.\n * Flyway migrations are the canonical case: `V256__Kaia_session_task_binding.sql`\n * and `V256__Add_author_type_to_task_comment.sql` merge cleanly, each branch's\n * own CI is green because each branch alone has exactly one V256, and the\n * assembled batch then refuses to boot with\n * `FlywayException: Found more than one migration with version 256`.\n *\n * **Colliding is the normal case, not bad luck.** Sequential `V<n>` naming means\n * every branch cut from main picks the next free number, so any two concurrent\n * schema branches collide with certainty. MYN sat at V255 with three separate\n * branches all claiming V256, touching three disjoint tables. Holding a member\n * out for that would cost throughput on every batch carrying two schema changes\n * and buy no safety at all.\n *\n * So the lint dispositions a collision by what the migrations actually touch:\n *\n * - **Provably independent** → deterministically renumber by merge order: the\n * earlier member keeps `V256`, the later takes the next version unused\n * anywhere in the union. The UAT branch content is exactly what promotes to\n * main, so a rename in the union is what lands — the same category of\n * assembly-time resolution as a conflict the assembly agent fixes on branch.\n * - **Anything else** → hold out, and let a human decide the order.\n *\n * **Independence must be proven, and the proof is deliberately strict.** A\n * distinct primary table proves nothing on its own: a child table's `REFERENCES`\n * names a parent, a view or trigger body reads other tables, and a data\n * migration's `INSERT … SELECT` couples two tables that never appear in each\n * other's DDL. So `migrationObjects` collects EVERY object a migration names —\n * targets, `REFERENCES` parents, and DML sources — and any overlap at all holds\n * out. A statement the extractor does not confidently recognize also holds out:\n * unrecognized SQL is unknown coupling, and the safe default is the human.\n *\n * Detection is deliberately PRE-merge — it reads the candidate branch's file\n * list — so a hold-out costs one `git ls-tree` and no rollback. Renames are\n * applied post-merge, when the files exist on the generation branch.\n */\n\n/** A Flyway migration file: `V<version>__<description>.sql`. */\nconst MIGRATION_FILENAME = /^V(\\d+(?:[._]\\d+)*)__(.+\\.sql)$/i;\n\n/** The basename of a path, using forward slashes (git's own separator). */\nfunction basename(path: string): string {\n const slash = path.lastIndexOf('/');\n return slash >= 0 ? path.slice(slash + 1) : path;\n}\n\nfunction dirname(path: string): string {\n const slash = path.lastIndexOf('/');\n return slash >= 0 ? path.slice(0, slash + 1) : '';\n}\n\n/**\n * The Flyway version a path declares, normalized so `V1_1__x.sql` and\n * `V1.1__x.sql` — which Flyway treats as the SAME version — collide here too.\n * Returns null for anything that is not a versioned migration (repeatable `R__`\n * migrations carry no version and cannot collide).\n */\nexport function migrationVersionOf(path: string): string | null {\n const match = MIGRATION_FILENAME.exec(basename(path));\n if (!match?.[1]) return null;\n return match[1].replace(/_/g, '.');\n}\n\n/**\n * The Flyway history a migration belongs to. **One repo is not one version\n * space**: a multi-service repo legitimately runs several independent Flyway\n * histories, each restarting at V1, and two services' `V1__init.sql` are not in\n * conflict. The migration's own directory is that boundary, so every version\n * comparison, reservation, and allocation is scoped to it.\n */\nexport function migrationRootOf(path: string): string {\n return dirname(path);\n}\n\n/** One versioned migration, located in its own Flyway history. */\nexport interface MigrationEntry {\n /** Migration directory — the version namespace this file lives in. */\n root: string;\n version: string;\n path: string;\n}\n\n/** `<root>` and `<version>` joined into one map key. */\nfunction versionKey(root: string, version: string): string {\n return `${root}\u0000${version}`;\n}\n\n/** Every versioned migration in a file list. */\nexport function migrationEntries(files: readonly string[]): MigrationEntry[] {\n const entries: MigrationEntry[] = [];\n for (const path of files) {\n const version = migrationVersionOf(path);\n if (!version) continue;\n entries.push({ root: migrationRootOf(path), version, path });\n }\n return entries;\n}\n\n/**\n * The FIRST migration claiming each `(root, version)` in a file list. A branch\n * listing two files at one version inside one root is that branch's own bug —\n * its own CI sees it — so only the first is considered here.\n */\nfunction firstByVersionKey(files: readonly string[]): Map<string, MigrationEntry> {\n const byKey = new Map<string, MigrationEntry>();\n for (const entry of migrationEntries(files)) {\n const key = versionKey(entry.root, entry.version);\n if (!byKey.has(key)) byKey.set(key, entry);\n }\n return byKey;\n}\n\n// ── Object extraction ────────────────────────────────────────────────────────\n\n/**\n * Statement shapes the extractor understands. Each entry names the leading\n * keywords it matches and every capture group that yields an object name — the\n * statement's target AND anything it couples to. A statement matching none of\n * these is unrecognized, which fails closed.\n */\nconst STATEMENT_RULES: Array<{ pattern: RegExp; objects: number[] }> = [\n { pattern: /^create\\s+(?:unlogged\\s+|temporary\\s+|temp\\s+)?table\\s+(?:if\\s+not\\s+exists\\s+)?([\\w.\"`]+)/i, objects: [1] },\n { pattern: /^alter\\s+table\\s+(?:if\\s+exists\\s+)?(?:only\\s+)?([\\w.\"`]+)/i, objects: [1] },\n { pattern: /^drop\\s+table\\s+(?:if\\s+exists\\s+)?([\\w.\"`]+)/i, objects: [1] },\n { pattern: /^create\\s+(?:unique\\s+)?index\\s+(?:concurrently\\s+)?(?:if\\s+not\\s+exists\\s+)?(?:[\\w.\"`]+\\s+)?on\\s+([\\w.\"`]+)/i, objects: [1] },\n { pattern: /^drop\\s+index\\s+(?:concurrently\\s+)?(?:if\\s+exists\\s+)?([\\w.\"`]+)/i, objects: [1] },\n { pattern: /^create\\s+type\\s+([\\w.\"`]+)/i, objects: [1] },\n { pattern: /^alter\\s+type\\s+([\\w.\"`]+)/i, objects: [1] },\n { pattern: /^drop\\s+type\\s+(?:if\\s+exists\\s+)?([\\w.\"`]+)/i, objects: [1] },\n { pattern: /^insert\\s+into\\s+([\\w.\"`]+)/i, objects: [1] },\n { pattern: /^update\\s+([\\w.\"`]+)\\s+set\\b/i, objects: [1] },\n { pattern: /^delete\\s+from\\s+([\\w.\"`]+)/i, objects: [1] },\n { pattern: /^comment\\s+on\\s+\\w+\\s+([\\w.\"`]+)/i, objects: [1] },\n // Statements with no object at all — recognized, contribute nothing.\n { pattern: /^(?:begin|commit|rollback|start\\s+transaction|set\\s+\\w+|analyze|vacuum)\\b/i, objects: [] },\n];\n\n/**\n * Couplings that can appear ANYWHERE inside a recognized statement and name an\n * object other than its target: a foreign key's parent, and the tables a data\n * migration reads. Missing one of these is exactly the failure mode a\n * primary-table-only check has, so they are collected from the whole body.\n */\nconst COUPLING_PATTERNS: RegExp[] = [\n /\\breferences\\s+([\\w.\"`]+)/gi,\n /\\bfrom\\s+([\\w.\"`]+)/gi,\n /\\bjoin\\s+([\\w.\"`]+)/gi,\n];\n\n/** Strip SQL comments so a commented-out statement never counts as a touch. */\nfunction stripSqlComments(sql: string): string {\n return sql.replace(/\\/\\*[\\s\\S]*?\\*\\//g, ' ').replace(/--[^\\n]*/g, ' ');\n}\n\n/** `\"public\".\"Task_Comment\"` → `task_comment`. */\nfunction normalizeObjectName(raw: string): string {\n const unquoted = raw.replace(/[\"`]/g, '').toLowerCase();\n const parts = unquoted.split('.').filter(Boolean);\n // Any schema qualifier is dropped, so `app.task` and `task` read as the same\n // object. Erring toward \"these might be the same\" only ever holds out.\n return parts[parts.length - 1] ?? unquoted;\n}\n\n/** Statements, split on `;`, with `$$ … $$` bodies kept intact. */\nfunction splitStatements(sql: string): string[] {\n const withoutDollarBodies = sql.replace(/\\$\\$[\\s\\S]*?\\$\\$/g, ' $DOLLARBODY$ ');\n return withoutDollarBodies\n .split(';')\n .map((statement) => statement.replace(/\\s+/g, ' ').trim())\n .filter(Boolean);\n}\n\nexport interface MigrationAnalysis {\n /** Every object the migration names: targets, FK parents, DML sources. */\n objects: Set<string>;\n /**\n * Statements the extractor could not classify. Non-empty means the migration\n * cannot be reasoned about, so its collisions must hold out.\n */\n unrecognized: string[];\n}\n\n/**\n * Everything a migration touches, plus whatever could not be understood.\n *\n * A function or trigger body (`$$ … $$`) is opaque to this extractor and is\n * deliberately reported unrecognized rather than skimmed: its body can read any\n * table in the schema.\n */\nexport function analyzeMigration(sql: string): MigrationAnalysis {\n const body = stripSqlComments(sql);\n const objects = new Set<string>();\n const unrecognized: string[] = [];\n\n for (const statement of splitStatements(body)) {\n if (statement.includes('$DOLLARBODY$')) {\n unrecognized.push(statement.slice(0, 120));\n continue;\n }\n const rule = STATEMENT_RULES.find((candidate) => candidate.pattern.test(statement));\n if (!rule) {\n unrecognized.push(statement.slice(0, 120));\n continue;\n }\n const match = rule.pattern.exec(statement);\n for (const group of rule.objects) {\n const name = normalizeObjectName(match?.[group] ?? '');\n if (name) objects.add(name);\n }\n for (const coupling of COUPLING_PATTERNS) {\n coupling.lastIndex = 0;\n for (const found of statement.matchAll(coupling)) {\n const name = normalizeObjectName(found[1] ?? '');\n if (name) objects.add(name);\n }\n }\n }\n\n return { objects, unrecognized };\n}\n\n// ── Ledger + dispositions ────────────────────────────────────────────────────\n\n/** Who owns a migration version in the assembled union. */\nexport interface MigrationVersionOwner {\n /** Path as it currently stands on the generation branch. */\n path: string;\n /** Null for a migration the batch base already carried. */\n issueId: string | null;\n}\n\n/** One version, within one migration root, claimed by two different files. */\nexport interface MigrationCollision {\n version: string;\n /** Migration directory both files live in — the version namespace. */\n root: string;\n incoming: { path: string; issueId: string };\n existing: MigrationVersionOwner;\n}\n\n/** A migration renamed on the generation branch to break a version collision. */\nexport interface MigrationRename {\n from: string;\n to: string;\n fromVersion: string;\n toVersion: string;\n}\n\nexport type UnionDisposition =\n | { kind: 'ok' }\n | { kind: 'renumber'; renames: MigrationRename[]; note: string }\n | { kind: 'hold-out'; reason: string };\n\n/**\n * The migration versions the assembled union carries, and who contributed each.\n *\n * Two distinct sets, because they answer different questions:\n *\n * - **owners** — versions actually ON the generation branch, which is what a\n * collision is measured against.\n * - **reserved** — every version that exists ANYWHERE in the union universe:\n * the base, plus every candidate branch's migrations, held out or not, plus\n * every number already handed out by a rename. A new number is safe only if\n * it is free in all of them; allocating blindly upward would happily pick a\n * V257 that main already carries or that the same member is about to add.\n */\nexport class MigrationVersionLedger {\n private readonly owners = new Map<string, MigrationVersionOwner>();\n private readonly reserved = new Set<string>();\n\n constructor(baseFiles: readonly string[] = []) {\n this.record(baseFiles, null);\n }\n\n /**\n * Declare versions that exist in the union universe without putting them on\n * the branch — every candidate branch's migrations, before assembly starts.\n */\n reserve(files: readonly string[]): void {\n for (const entry of migrationEntries(files)) this.reserved.add(versionKey(entry.root, entry.version));\n }\n\n /**\n * `(root, version)` pairs this member's files claim that a DIFFERENT file\n * already owns. Same version in a different migration root is a different\n * Flyway history and never collides.\n */\n collisions(files: readonly string[], issueId: string): MigrationCollision[] {\n const found: MigrationCollision[] = [];\n for (const [key, incoming] of firstByVersionKey(files)) {\n // Every branch also re-lists the base migrations — same path, same\n // owner, not a collision. The union lint owns only what the union creates.\n const owner = this.owners.get(key);\n if (!owner || basename(owner.path) === basename(incoming.path)) continue;\n found.push({\n version: incoming.version,\n root: incoming.root,\n incoming: { path: incoming.path, issueId },\n existing: owner,\n });\n }\n return found;\n }\n\n /** Attribute this member's migrations to it. Call only once it is accepted. */\n record(files: readonly string[], issueId: string | null): void {\n for (const [key, entry] of firstByVersionKey(files)) {\n this.reserved.add(key);\n if (this.owners.has(key)) continue;\n this.owners.set(key, { path: entry.path, issueId });\n }\n }\n\n /** Is this version claimed anywhere in the union, within this root? */\n isTaken(root: string, version: string): boolean {\n const key = versionKey(root, version);\n return this.owners.has(key) || this.reserved.has(key);\n }\n\n /**\n * The next plain-integer version free across the WHOLE union *for one\n * migration root*, counting up from a collision. Allocation is immediately\n * reserved, so two renames in one assembly can never land on the same number\n * even if the first is later rolled back — a skipped number costs nothing, a\n * reused one is the bug.\n */\n allocateVersion(root: string, from: number): number {\n let candidate = from + 1;\n while (this.isTaken(root, String(candidate))) candidate += 1;\n this.reserved.add(versionKey(root, String(candidate)));\n return candidate;\n }\n}\n\n/** Plain-integer versions are the only ones safe to renumber sequentially. */\nfunction integerVersion(version: string): number | null {\n return /^\\d+$/.test(version) ? Number(version) : null;\n}\n\n/** Rewrite `…/V256__Kaia.sql` to `…/V257__Kaia.sql`, keeping the description. */\nfunction renamedTo(path: string, toVersion: number): string {\n const match = MIGRATION_FILENAME.exec(basename(path));\n if (!match?.[2]) return path;\n return `${dirname(path)}V${toVersion}__${match[2]}`;\n}\n\nexport interface ResolveUnionArgs {\n ledger: MigrationVersionLedger;\n /** Migration paths the candidate branch carries. */\n files: readonly string[];\n issueId: string;\n /**\n * Read one migration's SQL. `path` is repo-relative; `owner` distinguishes\n * the candidate's own files (still only on its branch) from the ones already\n * on the generation branch.\n */\n readMigration: (path: string, owner: 'incoming' | 'union') => Promise<string>;\n}\n\n/**\n * Decide what to do about this member's version collisions.\n *\n * Renumbering is chosen ONLY when every collision is provably independent:\n * plain-integer versions, both migrations fully recognized, and no shared\n * object between them of any kind. Everything else holds the member out.\n */\nexport async function resolveUnionCollisions(args: ResolveUnionArgs): Promise<UnionDisposition> {\n const collisions = args.ledger.collisions(args.files, args.issueId);\n if (collisions.length === 0) return { kind: 'ok' };\n\n const renames: MigrationRename[] = [];\n for (const collision of collisions) {\n const version = integerVersion(collision.version);\n if (version === null) {\n return {\n kind: 'hold-out',\n reason: holdOutReason(collision, 'its version is not a plain integer, so its ordering is explicit and must not be renumbered'),\n };\n }\n\n let incomingSql: string;\n let existingSql: string;\n try {\n incomingSql = await args.readMigration(collision.incoming.path, 'incoming');\n existingSql = await args.readMigration(collision.existing.path, 'union');\n } catch {\n return { kind: 'hold-out', reason: holdOutReason(collision, 'one of the migrations could not be read, so they cannot be proven independent') };\n }\n\n const incoming = analyzeMigration(incomingSql);\n const existing = analyzeMigration(existingSql);\n const unrecognized = [...incoming.unrecognized, ...existing.unrecognized];\n if (unrecognized.length > 0) {\n return {\n kind: 'hold-out',\n reason: holdOutReason(collision, `SQL the union lint does not recognize (${unrecognized[0]}) could couple them, so independence cannot be proven`),\n };\n }\n if (incoming.objects.size === 0 || existing.objects.size === 0) {\n return { kind: 'hold-out', reason: holdOutReason(collision, 'no database object could be identified in one of them, so they cannot be proven independent') };\n }\n\n const shared = [...incoming.objects].filter((object) => existing.objects.has(object));\n if (shared.length > 0) {\n return {\n kind: 'hold-out',\n reason: holdOutReason(collision, `both touch ${shared.sort().join(', ')} — the order matters and only a human can decide it`),\n };\n }\n\n const toVersion = args.ledger.allocateVersion(collision.root, version);\n renames.push({\n from: collision.incoming.path,\n to: renamedTo(collision.incoming.path, toVersion),\n fromVersion: collision.version,\n toVersion: String(toVersion),\n });\n }\n\n return {\n kind: 'renumber',\n renames,\n note: renames.map((r) => `${basename(r.from)} → ${basename(r.to)}`).join('; '),\n };\n}\n\nfunction holdOutReason(collision: MigrationCollision, why: string): string {\n const existing = collision.existing.issueId\n ? `${basename(collision.existing.path)} (${collision.existing.issueId})`\n : `${basename(collision.existing.path)} (already on the batch base)`;\n return (\n `migration version V${collision.version} collides: ` +\n `${basename(collision.incoming.path)} (${collision.incoming.issueId}) vs ${existing} — ${why}`\n );\n}\n\n/** The file list a member effectively contributes once its renames are applied. */\nexport function applyRenames(files: readonly string[], renames: readonly MigrationRename[]): string[] {\n const byFrom = new Map(renames.map((r) => [r.from, r.to]));\n return files.map((file) => byFrom.get(file) ?? file);\n}\n\n// ── Engine-facing helpers ────────────────────────────────────────────────────\n\n/**\n * The git operations the union lint needs, as a structural subset of\n * `GenerationGitDeps`. All optional: deps that omit `listMigrationFiles` skip\n * the lint entirely, which is the pre-PAN-3166 behaviour.\n */\nexport interface UnionLintPort {\n listMigrationFiles?(ref: string): Promise<string[]>;\n readMigrationFile?(ref: string, path: string): Promise<string>;\n renameMigrations?(renames: ReadonlyArray<{ from: string; to: string }>, message: string): Promise<string>;\n}\n\nexport interface UnionLintPlan {\n /** Migration paths the candidate branch carries, before any renaming. */\n files: string[];\n disposition: UnionDisposition;\n}\n\nconst OK_PLAN: UnionLintPlan = { files: [], disposition: { kind: 'ok' } };\n\n/**\n * Seed a ledger for one repo: the versions already on the generation branch as\n * cut from its target, plus a reservation covering every candidate branch's\n * migrations, so an allocated number can never collide with one a later member\n * (or an unchanged migration on main) already holds.\n */\nexport async function seedUnionLedger(\n git: UnionLintPort,\n generationBranch: string,\n candidateBranches: readonly string[],\n): Promise<MigrationVersionLedger | null> {\n if (!git.listMigrationFiles) return null;\n const list = git.listMigrationFiles.bind(git);\n // The generation branch read is UNGUARDED on purpose (PAN-3166). Swallowing a\n // git error into an empty list would silently disable the lint and let\n // assembly publish another non-bootable batch while reporting success — the\n // exact failure this exists to prevent. Empty-because-unread must never look\n // like empty-because-no-migrations, so this failure fails the assembly.\n const ledger = new MigrationVersionLedger(await list(generationBranch));\n for (const branch of candidateBranches) {\n // A candidate that cannot be read costs only itself: `planUnionLint` hits\n // the same failure and holds that member out, so its versions never enter\n // the union and a missing reservation cannot mis-allocate around it. One\n // broken branch must not fail the whole batch.\n ledger.reserve(await list(branch).catch(() => []));\n }\n return ledger;\n}\n\n/**\n * Lint one candidate branch against the union so far, PRE-merge. Renumbering\n * additionally needs `readMigrationFile` (to prove independence) and\n * `renameMigrations` (to apply it); without either, a collision can only be\n * held out.\n */\nexport async function planUnionLint(args: {\n git: UnionLintPort;\n ledger: MigrationVersionLedger | null;\n /** Generation branch the union lives on — where already-merged files are read from. */\n generationBranch: string;\n /** Candidate branch being considered. */\n featureBranch: string;\n issueId: string;\n}): Promise<UnionLintPlan> {\n const { git, ledger } = args;\n if (!ledger || !git.listMigrationFiles) return OK_PLAN;\n\n // A read failure holds the member out rather than degrading to \"no\n // migrations\" — a guard that disables itself on error is worse than no guard,\n // because it still reports success (PAN-3166).\n let files: string[];\n try {\n files = await git.listMigrationFiles(args.featureBranch);\n } catch (err) {\n return {\n files: [],\n disposition: {\n kind: 'hold-out',\n reason:\n `could not list ${args.issueId}'s migrations on ${args.featureBranch} ` +\n `(${err instanceof Error ? err.message.split('\\n')[0] : String(err)}) — ` +\n `the union lint cannot rule out a migration version collision`,\n },\n };\n }\n if (files.length === 0) return { files, disposition: { kind: 'ok' } };\n\n const read = git.readMigrationFile?.bind(git);\n const canRenumber = Boolean(read && git.renameMigrations);\n const disposition = await resolveUnionCollisions({\n ledger,\n files,\n issueId: args.issueId,\n readMigration: async (path, owner) => {\n if (!canRenumber || !read) throw new Error('renumbering unavailable');\n return read(owner === 'incoming' ? args.featureBranch : args.generationBranch, path);\n },\n });\n return { files, disposition };\n}\n\n/**\n * Apply a renumbering on the generation branch, POST-merge (the files only\n * exist there once the merge landed). Returns the commit SHA, or null when the\n * plan needed no rename.\n */\nexport async function applyUnionLintPlan(\n git: UnionLintPort,\n plan: UnionLintPlan,\n issueId: string,\n): Promise<{ commitSha: string; files: string[]; note: string } | null> {\n if (plan.disposition.kind !== 'renumber' || !git.renameMigrations) return null;\n const { renames, note } = plan.disposition;\n const commitSha = await git.renameMigrations(\n renames.map((r) => ({ from: r.from, to: r.to })),\n `chore(uat): renumber ${issueId} migrations to break a version collision\\n\\n${note}`,\n );\n return { commitSha, files: renames.map((r) => r.to), note };\n}\n\n/** The migration paths a member contributes once its plan has been applied. */\nexport function plannedFiles(plan: UnionLintPlan): string[] {\n return plan.disposition.kind === 'renumber'\n ? applyRenames(plan.files, plan.disposition.renames)\n : [...plan.files];\n}\n","/**\n * UAT generation assembly engine (PAN-1737: UAT batch trains).\n *\n * Builds one *generation* — a deterministic-per-day `uat/<label>-<codename>-<MMDD>` branch\n * off current main containing as many ready features as possible, merged in\n * queue order, with cross-feature conflicts resolved on the branch by the\n * injected `resolveConflict` hook (the assembly agent). A feature whose\n * conflict cannot be resolved is *held out* with a human-readable reason and\n * assembly continues — a single bad merge never blocks the batch.\n *\n * Each rebuild for the same label/day force-resets the same branch + persistent\n * worktree under `<projectRoot>/workspaces/`, keeping one authoritative current\n * UAT candidate instead of proliferating stale branches.\n *\n * Pure orchestrator: all git and store I/O is injected (see\n * uat-generation-deps.ts for the real wiring), so every path — happy,\n * conflict-resolved, held-out, failed — is unit-testable with fake deps.\n */\nimport { makeUniqueUatCandidateName } from './uat-candidate-name.js';\nimport {\n applyUnionLintPlan,\n planUnionLint,\n plannedFiles,\n seedUnionLedger,\n} from './uat-union-lint.js';\nimport type { PolyrepoRepoContribution } from '../flywheel-merge-order.js';\nimport type {\n UatGeneration,\n UatGenerationMember,\n UatGenerationRepo,\n UatGenerationStatus,\n} from '../overdeck/merge-sync.js';\n\n/** A feature eligible for bundling, in merge-queue order. */\nexport interface ReadyFeature {\n issueId: string;\n title: string;\n /** Feature branch, e.g. feature/pan-1704. */\n branch: string;\n pr?: number;\n prUrl?: string;\n /** Issue ids this feature's changed files overlap with (from computeMergeQueue). */\n conflictsWith?: readonly string[];\n /**\n * Member repos this feature actually has a branch in (polyrepo only; the\n * monorepo ready set omits it). Populated by\n * computePolyrepoMergeQueueFromCandidates — the polyrepo engine merges only\n * these repos and skips the rest.\n */\n repoContributions?: readonly PolyrepoRepoContribution[];\n}\n\n/** Context handed to the conflict-resolution hook while the merge is mid-conflict. */\nexport interface ConflictContext {\n feature: ReadyFeature;\n /** Members already merged onto the branch, in order. */\n mergedIssueIds: readonly string[];\n /** The already-merged members this feature is known to overlap with. */\n conflictingIssueIds: readonly string[];\n branchName: string;\n worktreePath: string;\n /**\n * Member repo this conflict occurred in (polyrepo assembly only). Absent for\n * monorepo assembly. The hook already works purely on `worktreePath`, so\n * per-repo worktrees scope it automatically — this only names the repo in the\n * prompt and logs so an operator can tell which one blocked.\n */\n repoKey?: string;\n}\n\n/** Successful resolution: the hook committed the merge on the branch. */\nexport interface ConflictResolutionResult {\n files: string[];\n commitSha: string;\n}\n\nexport interface GenerationGitDeps {\n /** `git fetch origin main` and return the origin/main head SHA. */\n fetchMain(): Promise<string>;\n /** Create or reset the generation worktree branch off origin/main. */\n createWorktree(branchName: string, worktreePath: string): Promise<void>;\n /** Head SHA of a feature branch (origin-first). */\n branchHeadSha(branch: string): Promise<string>;\n /** Whether a UAT branch tip is already contained in the target branch. */\n isBranchContainedInMain?(branch: string): Promise<boolean>;\n /**\n * Merge a feature branch into the generation worktree. On conflict the\n * worktree is left MID-CONFLICT (the hook needs that state); the engine\n * decides whether to resolve or abort.\n */\n mergeBranch(featureBranch: string): Promise<\n { ok: true } | { ok: false; conflict: boolean; reason: string }\n >;\n /** `git merge --abort` in the generation worktree. */\n abortMerge(): Promise<void>;\n /** Push the generation branch to origin. */\n push(branchName: string): Promise<void>;\n /**\n * Files tracked at a git ref that the union lint cares about — today, Flyway\n * migrations (PAN-3166). Optional: deps that omit it skip the union lint\n * entirely, which is exactly the pre-PAN-3166 behaviour.\n */\n listMigrationFiles?(ref: string): Promise<string[]>;\n /** One migration's SQL at a ref, for deciding which objects it touches. */\n readMigrationFile?(ref: string, path: string): Promise<string>;\n /**\n * Rename migrations on the generation branch and commit, breaking a version\n * collision between migrations that touch disjoint objects. Returns the\n * commit SHA so the renumbering is auditable as a resolution.\n */\n renameMigrations?(renames: ReadonlyArray<{ from: string; to: string }>, message: string): Promise<string>;\n}\n\nexport interface GenerationStorePort {\n /** Insert or reset the generation row at assembly start. */\n insert(gen: Omit<UatGeneration, 'createdAt' | 'updatedAt'>): void;\n update(\n name: string,\n // 'repos' is written only by the polyrepo engine; the monorepo path never\n // sets it, so its generations keep reading back as a synthesized N=1 entry.\n patch: Partial<Pick<UatGeneration, 'status' | 'baseSha' | 'members' | 'heldOut' | 'resolutions' | 'cleanedAt' | 'repos'>>,\n ): void;\n /** Existing generation names. */\n listNames(): string[];\n listChain(projectRoot: string, statuses?: readonly UatGenerationStatus[]): UatGeneration[];\n}\n\nexport interface AssembleGenerationInput {\n projectRoot: string;\n /** Project label for the codename, e.g. 'pan'. */\n label: string;\n /** ISO date; MMDD goes into the branch name. */\n dateIso: string;\n /** Ready features in merge-queue order. */\n features: readonly ReadyFeature[];\n /** Branch names (beyond the store) already present, e.g. live git refs. */\n takenBranchNames?: readonly string[];\n}\n\nexport interface AssembleGenerationDeps {\n git: GenerationGitDeps;\n store: GenerationStorePort;\n /**\n * The assembly agent. Called mid-conflict; must either conclude the merge\n * with a commit (returning what it resolved) or return null to give up.\n * Absent hook = every conflict is held out.\n */\n resolveConflict?: (ctx: ConflictContext) => Promise<ConflictResolutionResult | null>;\n log?: (msg: string) => void;\n}\n\n/** Branch name → worktree folder name (slashes collapse to dashes). */\nexport function generationFolderName(branchName: string): string {\n return branchName.replace(/[^a-zA-Z0-9._-]+/g, '-');\n}\n\n/**\n * Assemble the next generation from the given ready set. Always returns the\n * generation row (status 'ready' or 'failed'); throws only on store failures.\n * On success, older 'ready' generations for the project flip to 'superseded'.\n */\nexport async function assembleUatGeneration(\n input: AssembleGenerationInput,\n deps: AssembleGenerationDeps,\n): Promise<UatGeneration> {\n const log = deps.log ?? (() => {});\n\n const baseSha = await deps.git.fetchMain();\n const name = makeUniqueUatCandidateName({\n label: input.label,\n dateIso: input.dateIso,\n }, [...deps.store.listNames(), ...(input.takenBranchNames ?? [])]);\n const worktreePath = `${input.projectRoot}/workspaces/${generationFolderName(name)}`;\n\n deps.store.insert({\n name,\n worktreePath,\n projectRoot: input.projectRoot,\n baseSha,\n status: 'assembling',\n members: [],\n heldOut: [],\n resolutions: [],\n stackStartedAt: null,\n cleanedAt: null,\n });\n\n const members: UatGeneration['members'] = [];\n const heldOut: UatGeneration['heldOut'] = [];\n const resolutions: UatGeneration['resolutions'] = [];\n const finish = (status: UatGenerationStatus): UatGeneration => {\n deps.store.update(name, { status, members, heldOut, resolutions });\n return {\n name, worktreePath, projectRoot: input.projectRoot, baseSha,\n status, members, heldOut, resolutions,\n stackStartedAt: null, cleanedAt: null, createdAt: '', updatedAt: '',\n };\n };\n\n try {\n await deps.git.createWorktree(name, worktreePath);\n } catch (err) {\n log(`[uat-generation] ${name}: worktree creation failed: ${err instanceof Error ? err.message : String(err)}`);\n return finish('failed');\n }\n\n // Union lint (PAN-3166): owners come from the branch as just cut from the\n // target, so a member colliding with a migration already on main is caught\n // too; reservations cover every candidate branch, so an allocated version\n // cannot collide with one a later member already holds. A read failure here\n // fails the generation — a lint that quietly disables itself is worse than\n // none, because assembly would still report ready.\n let ledger: Awaited<ReturnType<typeof seedUnionLedger>>;\n try {\n ledger = await seedUnionLedger(deps.git, name, input.features.map((f) => f.branch));\n } catch (err) {\n log(`[uat-generation] ${name}: union lint could not read migrations: ${err instanceof Error ? err.message : String(err)} — marking failed`);\n return finish('failed');\n }\n\n for (const feature of input.features) {\n const mergedIssueIds = members.map((m) => m.issueId);\n const attemptedHeadSha = async () => deps.git.branchHeadSha(feature.branch).catch(() => 'unknown');\n const recordMember = async (): Promise<UatGenerationMember> => ({\n issueId: feature.issueId,\n title: feature.title,\n branch: feature.branch,\n headSha: await attemptedHeadSha(),\n mergeOrder: members.length + 1,\n ...(feature.pr !== undefined ? { pr: feature.pr } : {}),\n ...(feature.prUrl !== undefined ? { prUrl: feature.prUrl } : {}),\n });\n const holdOut = async (reason: string): Promise<void> => {\n heldOut.push({ issueId: feature.issueId, branch: feature.branch, headSha: await attemptedHeadSha(), reason });\n };\n\n // Union lint runs BEFORE the merge: git would take this branch cleanly\n // (the colliding migrations are different filenames), and the batch would\n // only fail at Flyway startup, long after assembly reported ready. A\n // collision between migrations touching disjoint objects is renumbered\n // after the merge lands; only a same-object collision holds the member out.\n const plan = await planUnionLint({\n git: deps.git,\n ledger,\n generationBranch: name,\n featureBranch: feature.branch,\n issueId: feature.issueId,\n });\n if (plan.disposition.kind === 'hold-out') {\n log(`[uat-generation] ${name}: holding ${feature.issueId} out — ${plan.disposition.reason}`);\n await holdOut(plan.disposition.reason);\n deps.store.update(name, { members, heldOut, resolutions });\n continue;\n }\n\n /**\n * Land any renumbering and record the member's (possibly renamed) files.\n * Returns false when a planned rename did not land: the branch would then\n * carry the very collision the lint exists to prevent, and this engine has\n * no post-merge rollback, so the caller abandons the generation rather than\n * publish a union known not to boot.\n */\n const settleUnionLint = async (): Promise<boolean> => {\n let applied: Awaited<ReturnType<typeof applyUnionLintPlan>>;\n try {\n applied = await applyUnionLintPlan(deps.git, plan, feature.issueId);\n } catch (err) {\n log(`[uat-generation] ${name}: renumbering ${feature.issueId} failed: ${err instanceof Error ? err.message : String(err)}`);\n return false;\n }\n if (applied) {\n log(`[uat-generation] ${name}: renumbered ${feature.issueId} migrations — ${applied.note}`);\n resolutions.push({\n issueIds: [feature.issueId],\n files: applied.files,\n commitSha: applied.commitSha,\n kind: 'migration-renumber',\n note: applied.note,\n });\n } else if (plan.disposition.kind === 'renumber') {\n log(`[uat-generation] ${name}: ${feature.issueId} needed renumbering but no rename primitive is wired`);\n return false;\n }\n ledger?.record(plannedFiles(plan), feature.issueId);\n return true;\n };\n\n let result: Awaited<ReturnType<GenerationGitDeps['mergeBranch']>>;\n try {\n result = await deps.git.mergeBranch(feature.branch);\n } catch (err) {\n await deps.git.abortMerge().catch(() => {});\n await holdOut(`merge failed: ${err instanceof Error ? err.message.split('\\n')[0] : String(err)}`);\n deps.store.update(name, { members, heldOut, resolutions });\n continue;\n }\n\n if (result.ok) {\n if (!(await settleUnionLint())) return finish('failed');\n members.push(await recordMember());\n deps.store.update(name, { members, heldOut, resolutions });\n continue;\n }\n\n if (!result.conflict || !deps.resolveConflict) {\n await deps.git.abortMerge().catch(() => {});\n await holdOut(result.conflict\n ? `conflicts with ${conflictingWith(feature, mergedIssueIds).join(', ') || 'an earlier member'} — no assembly agent available`\n : result.reason);\n deps.store.update(name, { members, heldOut, resolutions });\n continue;\n }\n\n const conflictingIssueIds = conflictingWith(feature, mergedIssueIds);\n log(`[uat-generation] ${name}: resolving conflict ${feature.issueId} <-> ${conflictingIssueIds.join(', ') || '(unknown member)'}`);\n let resolution: ConflictResolutionResult | null = null;\n try {\n resolution = await deps.resolveConflict({\n feature,\n mergedIssueIds,\n conflictingIssueIds,\n branchName: name,\n worktreePath,\n });\n } catch (err) {\n log(`[uat-generation] ${name}: conflict agent threw: ${err instanceof Error ? err.message : String(err)}`);\n resolution = null;\n }\n\n if (resolution) {\n if (!(await settleUnionLint())) return finish('failed');\n members.push(await recordMember());\n resolutions.push({\n issueIds: [feature.issueId, ...conflictingIssueIds],\n files: resolution.files,\n commitSha: resolution.commitSha,\n kind: 'conflict',\n });\n } else {\n await deps.git.abortMerge().catch(() => {});\n await holdOut(`conflict with ${conflictingIssueIds.join(', ') || 'an earlier member'} could not be auto-resolved — waits for the next generation`);\n }\n deps.store.update(name, { members, heldOut, resolutions });\n }\n\n if (members.length === 0) {\n log(`[uat-generation] ${name}: nothing merged (${heldOut.length} held out) — marking failed`);\n return finish('failed');\n }\n\n try {\n await deps.git.push(name);\n } catch (err) {\n log(`[uat-generation] ${name}: push failed: ${err instanceof Error ? err.message : String(err)}`);\n return finish('failed');\n }\n\n // This generation is now the current one; older ready generations remain\n // testable but are no longer the freshest — flip them to superseded.\n for (const older of deps.store.listChain(input.projectRoot, ['ready'])) {\n if (older.name !== name) deps.store.update(older.name, { status: 'superseded' });\n }\n\n log(`[uat-generation] ${name}: ready — ${members.length} member(s), ${resolutions.length} resolution(s), ${heldOut.length} held out`);\n return finish('ready');\n}\n\nfunction conflictingWith(feature: ReadyFeature, mergedIssueIds: readonly string[]): string[] {\n const merged = new Set(mergedIssueIds.map((id) => id.toUpperCase()));\n return (feature.conflictsWith ?? []).filter((id) => merged.has(id.toUpperCase()));\n}\n\nexport interface GenerationCleanupDeps {\n store: GenerationStorePort;\n /** Remove the generation's worktree directory (idempotent). */\n removeWorktree(worktreePath: string): Promise<void>;\n /** Delete the generation branch locally and on origin (idempotent). */\n deleteBranch(branchName: string): Promise<void>;\n /** Tear down the generation's live stack if one is running. */\n teardownStack?: (generation: UatGeneration) => Promise<void>;\n /**\n * Remove ONE member repo's worktree and branch (local + remote), for a\n * polyrepo generation (PAN-3093). Its PRESENCE selects per-repo teardown:\n * every generation reads back with at least one `repos` entry (monorepo rows\n * synthesize one), so a repo count cannot distinguish the two, and a polyrepo\n * generation with a single contributing repo still needs per-repo handling.\n * Omit it and cleanup behaves exactly as it did before.\n */\n removeRepoArtifacts?: (repo: UatGenerationRepo) => Promise<void>;\n /**\n * Everything left once the recorded repos are clean: the wrapper folder that\n * held the per-repo worktrees, plus the generation branch in any member repo\n * the generation stopped using (a hold-out can drop a repo after its branch\n * was already created locally — that branch is never pushed, but it lingers).\n */\n removeGenerationResidue?: (\n generation: UatGeneration,\n /** Repos the generation recorded — already cleaned by removeRepoArtifacts. */\n recordedRepoKeys?: readonly string[],\n ) => Promise<void>;\n log?: (msg: string) => void;\n}\n\n/**\n * Reap dead generations' branches/worktrees, and trim the live chain to the\n * newest `keep` (default 3). Trimmed superseded generations flip to\n * 'invalidated' (their branch is gone — no longer testable). Rows are never\n * deleted: the chain is the audit trail. Promoted generations keep their row\n * but lose branch/worktree/stack — their content lives on main now.\n */\nexport async function cleanupUatGenerations(\n projectRoot: string,\n deps: GenerationCleanupDeps,\n options: { keep?: number } = {},\n): Promise<void> {\n const log = deps.log ?? (() => {});\n const keep = options.keep ?? 3;\n\n const live = deps.store.listChain(projectRoot, ['ready', 'superseded']);\n const trimmed = live.slice(keep);\n const dead = deps.store.listChain(projectRoot, ['invalidated', 'promoted', 'failed'])\n .filter((gen) => !gen.cleanedAt);\n\n for (const gen of [...trimmed, ...dead]) {\n let cleaned = true;\n if (gen.stackStartedAt && deps.teardownStack) {\n await deps.teardownStack(gen).catch((err) => {\n cleaned = false;\n log(`[uat-generation] cleanup ${gen.name}: stack teardown failed: ${err instanceof Error ? err.message : String(err)}`);\n });\n }\n if (deps.removeRepoArtifacts) {\n // Polyrepo: one worktree and one branch per member repo, then the wrapper\n // folder. Every repo is attempted even after one fails, so a single bad\n // repo cannot strand the others' artifacts; cleanedAt still stays unset.\n for (const repo of gen.repos ?? []) {\n await deps.removeRepoArtifacts(repo).catch((err) => {\n cleaned = false;\n log(`[uat-generation] cleanup ${gen.name}: ${repo.repoKey} artifact removal failed: ${err instanceof Error ? err.message : String(err)}`);\n });\n }\n await deps.removeGenerationResidue?.(gen, (gen.repos ?? []).map((r) => r.repoKey)).catch((err) => {\n cleaned = false;\n log(`[uat-generation] cleanup ${gen.name}: generation folder removal failed: ${err instanceof Error ? err.message : String(err)}`);\n });\n } else {\n await deps.removeWorktree(gen.worktreePath).catch((err) => {\n cleaned = false;\n log(`[uat-generation] cleanup ${gen.name}: worktree removal failed: ${err instanceof Error ? err.message : String(err)}`);\n });\n await deps.deleteBranch(gen.name).catch((err) => {\n cleaned = false;\n log(`[uat-generation] cleanup ${gen.name}: branch deletion failed: ${err instanceof Error ? err.message : String(err)}`);\n });\n }\n if (!cleaned) continue;\n const cleanedAt = new Date().toISOString();\n if (gen.status === 'ready' || gen.status === 'superseded') {\n deps.store.update(gen.name, { status: 'invalidated', cleanedAt });\n log(`[uat-generation] cleanup: trimmed ${gen.name} (beyond newest ${keep})`);\n } else {\n deps.store.update(gen.name, { cleanedAt });\n }\n }\n}\n","/**\n * Real git + store wiring for the UAT generation engine (PAN-1737).\n *\n * Generation worktrees are PERSISTENT under `<projectRoot>/workspaces/` — the\n * live UAT stack serves from them, and the deterministic daily folder name yields the Traefik\n * host (`uat-<label>-<codename>-<mmdd>.overdeck.localhost`) via the standard\n * FEATURE_FOLDER devcontainer template.\n *\n * Pure I/O — exercised live; the orchestrator (uat-generation-engine.ts) holds\n * the tested logic.\n */\nimport { execFile } from 'child_process';\nimport { rm } from 'fs/promises';\nimport { isAbsolute, relative, resolve } from 'path';\nimport { promisify } from 'util';\nimport {\n insertUatGenerationSync,\n listUatGenerationNamesSync,\n listUatGenerationsSync,\n updateUatGenerationSync,\n} from '../overdeck/merge-sync.js';\nimport type { GenerationGitDeps, GenerationStorePort } from './uat-generation-engine.js';\nimport type { PolyrepoRepoGit } from './uat-polyrepo-engine.js';\nimport type { ResolvedProjectRepo } from '../project-repos.js';\n\nconst execFileAsync = promisify(execFile);\n\nconst runGit = (args: string[], cwd: string) =>\n execFileAsync('git', args, { cwd, maxBuffer: 16 * 1024 * 1024 });\n\n/**\n * Validate a branch name against its expected namespace.\n *\n * `feature` accepts a CONFIGURED prefix, not a hardcoded `feature/`: a member\n * repo may set `branch_prefix: feat/`, and rejecting that made every one of its\n * contributions throw and get held out. The character class is unchanged, so\n * shell metacharacters and path traversal are still refused.\n */\nexport function safeBranchName(\n branchName: string,\n prefix: 'feature' | 'uat',\n featurePrefix = 'feature/',\n): string {\n const namespace = prefix === 'uat' ? 'uat/' : featurePrefix;\n const escaped = namespace.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n const pattern = new RegExp(`^${escaped}[A-Za-z0-9][A-Za-z0-9._-]*$`);\n if (!pattern.test(branchName)) {\n throw new Error(`unsafe ${prefix} branch name: ${branchName}`);\n }\n return branchName;\n}\n\nexport function safeGenerationWorktreePath(projectRoot: string, worktreePath: string): string {\n const workspacesRoot = resolve(projectRoot, 'workspaces');\n const target = resolve(worktreePath);\n const rel = relative(workspacesRoot, target);\n if (rel === '' || rel.startsWith('..') || isAbsolute(rel)) {\n throw new Error(`unsafe UAT worktree path outside project workspaces: ${worktreePath}`);\n }\n return target;\n}\n\n/** Store port backed by uat-generations-db. */\nexport function buildUatGenerationStore(): GenerationStorePort {\n return {\n insert: (gen) => { insertUatGenerationSync(gen); },\n update: (name, patch) => { updateUatGenerationSync(name, patch); },\n listNames: () => listUatGenerationNamesSync(),\n listChain: (projectRoot, statuses) =>\n listUatGenerationsSync({ projectRoot, ...(statuses ? { statuses } : {}) }),\n };\n}\n\nexport interface UatGenerationGitDepsOptions {\n /**\n * Root whose `workspaces/` directory may hold the worktree. Defaults to\n * `projectRoot`. A polyrepo member repo runs git in its own git root but\n * places its worktree under the WRAPPER project's `workspaces/`, so the two\n * roots differ and the path check has to be told which one governs.\n */\n workspacesRoot?: string;\n /** Branch the generation is cut from and merged back into. Defaults to `main`. */\n targetBranch?: string;\n /**\n * Namespace this repo's FEATURE branches live in, e.g. `feat/`. Defaults to\n * `feature/`. Comes from the repo's configured `branch_prefix`; a mismatch\n * makes every contribution fail validation and get held out.\n */\n featureBranchPrefix?: string;\n}\n\n/**\n * Git deps for one assembly run. The worktree path is bound on createWorktree\n * and reused by the merge/push operations that follow.\n */\nexport function buildUatGenerationGitDeps(\n projectRoot: string,\n options: UatGenerationGitDepsOptions = {},\n): GenerationGitDeps {\n let worktreePath = '';\n const workspacesRoot = options.workspacesRoot ?? projectRoot;\n const targetBranch = options.targetBranch ?? 'main';\n const originTarget = `origin/${targetBranch}`;\n const featurePrefix = options.featureBranchPrefix ?? 'feature/';\n\n return {\n fetchMain: async () => {\n await runGit(['fetch', 'origin', targetBranch], projectRoot);\n const { stdout } = await runGit(['rev-parse', originTarget], projectRoot);\n return stdout.trim();\n },\n\n createWorktree: async (branchName, path) => {\n const safeBranch = safeBranchName(branchName, 'uat');\n worktreePath = safeGenerationWorktreePath(workspacesRoot, path);\n // A leftover worktree at this path means a previous assembly of the SAME\n // name crashed mid-build (names are collision-checked) — reclaim it.\n await runGit(['worktree', 'remove', '--force', worktreePath], projectRoot).catch(() => {});\n await runGit(['worktree', 'prune'], projectRoot).catch(() => {});\n await runGit(['worktree', 'add', '-B', safeBranch, worktreePath, originTarget], projectRoot);\n },\n\n branchHeadSha: async (branch) => {\n const safeBranch = safeBranchName(branch, 'feature', featurePrefix);\n const tryRef = async (ref: string) => (await runGit(['rev-parse', ref], projectRoot)).stdout.trim();\n return tryRef(`origin/${safeBranch}`).catch(() => tryRef(safeBranch));\n },\n\n isBranchContainedInMain: async (branch) => {\n const safeBranch = safeBranchName(branch, 'uat');\n const ref = await runGit(['rev-parse', '--verify', `origin/${safeBranch}`], projectRoot)\n .then(() => `origin/${safeBranch}`)\n .catch(() => safeBranch);\n return runGit(['merge-base', '--is-ancestor', ref, originTarget], projectRoot)\n .then(() => true)\n .catch(() => false);\n },\n\n mergeBranch: async (featureBranch) => {\n // Prefer the origin ref — feature branches are pushed by work agents and\n // the local ref may lag.\n const safeFeatureBranch = safeBranchName(featureBranch, 'feature', featurePrefix);\n const originRef = `origin/${safeFeatureBranch}`;\n const ref = await runGit(['rev-parse', '--verify', originRef], worktreePath)\n .then(() => originRef)\n .catch(() => safeFeatureBranch);\n try {\n await runGit(['merge', '--no-edit', ref], worktreePath);\n return { ok: true as const };\n } catch (err) {\n const { stdout } = await runGit(['ls-files', '-u'], worktreePath).catch(() => ({ stdout: '' }));\n const conflict = stdout.trim().length > 0;\n // Leave the worktree mid-conflict — the engine decides whether the\n // assembly agent gets a shot before aborting.\n return {\n ok: false as const,\n conflict,\n reason: err instanceof Error ? (err.message.split('\\n')[0] ?? 'merge failed') : String(err),\n };\n }\n },\n\n abortMerge: async () => {\n await runGit(['merge', '--abort'], worktreePath).catch(() => {});\n },\n\n push: async (branchName) => {\n await runGit(['push', '-u', '--force-with-lease', 'origin', safeBranchName(branchName, 'uat')], worktreePath);\n },\n\n /**\n * Flyway migrations tracked at a ref, for the union lint (PAN-3166). Reads\n * the ref rather than a worktree so a candidate can be linted before it is\n * merged.\n *\n * Feature branches resolve origin-first for the same reason mergeBranch\n * prefers it — work agents push, and the local ref may lag. The GENERATION\n * branch resolves locally only: its name is deterministic per day and\n * force-pushed, so `origin/uat/…` can still hold yesterday's tree while the\n * local ref is the one assembly just cut.\n */\n listMigrationFiles: async (ref) => {\n const safeRef = safeRefName(ref);\n const resolved = safeRef.startsWith('uat/')\n ? safeRef\n : await runGit(['rev-parse', '--verify', `origin/${safeRef}`], projectRoot)\n .then(() => `origin/${safeRef}`)\n .catch(() => safeRef);\n const { stdout } = await runGit(['ls-tree', '-r', '--name-only', resolved], projectRoot);\n return stdout\n .split('\\n')\n .map((line) => line.trim())\n .filter((path) => /(^|\\/)V\\d[\\w.]*__.+\\.sql$/i.test(path));\n },\n\n /** One migration's SQL at a ref, so the lint can see what it touches. */\n readMigrationFile: async (ref, path) => {\n const safeRef = safeRefName(ref);\n const resolved = safeRef.startsWith('uat/')\n ? safeRef\n : await runGit(['rev-parse', '--verify', `origin/${safeRef}`], projectRoot)\n .then(() => `origin/${safeRef}`)\n .catch(() => safeRef);\n const { stdout } = await runGit(['show', `${resolved}:${safeRepoPath(path)}`], projectRoot);\n return stdout;\n },\n\n /**\n * Renumber migrations on the generation branch and commit. `git mv` keeps\n * the rename visible in history, and the commit lands on the branch that\n * promotes to main — so what the operator UATs is exactly what ships.\n */\n renameMigrations: async (renames, message) => {\n if (!worktreePath) throw new Error('renameMigrations before createWorktree');\n for (const { from, to } of renames) {\n await runGit(['mv', safeRepoPath(from), safeRepoPath(to)], worktreePath);\n }\n await runGit(['commit', '--no-verify', '-m', message], worktreePath);\n const { stdout } = await runGit(['rev-parse', 'HEAD'], worktreePath);\n return stdout.trim();\n },\n };\n}\n\n/**\n * A repo-relative path safe to hand to git: no leading dash (git would read it\n * as a flag), no absolute path, no traversal out of the repo.\n */\nfunction safeRepoPath(path: string): string {\n if (!/^[A-Za-z0-9_][A-Za-z0-9._/-]*$/.test(path) || path.includes('..')) {\n throw new Error(`unsafe repo path: ${path}`);\n }\n return path;\n}\n\n/**\n * A ref safe to pass as a git argument: no leading dash (which git would read\n * as a flag), no path traversal, no shell metacharacters. Accepts both `uat/…`\n * and any configured feature namespace, so the union lint can read either.\n */\nfunction safeRefName(ref: string): string {\n if (!/^[A-Za-z0-9][A-Za-z0-9._/-]*$/.test(ref) || ref.includes('..')) {\n throw new Error(`unsafe git ref: ${ref}`);\n }\n return ref;\n}\n\n/**\n * The namespace part of a configured source branch: `feat/min-1` -> `feat/`.\n *\n * Exported because EVERY construction of the feature-branch adapter must use\n * the same namespace — assembly and the reconciler's anchor path both build one,\n * and a mismatch makes the reconciler reject a branch assembly just accepted.\n */\nexport function featureNamespaceOf(sourceBranch: string): string {\n const slash = sourceBranch.lastIndexOf('/');\n return slash >= 0 ? sourceBranch.slice(0, slash + 1) : 'feature/';\n}\n\n/**\n * Per-repo git deps for a polyrepo assembly, keyed by repoKey (PAN-3093).\n * Each repo runs git in its own root but writes its worktree under the wrapper\n * project's `workspaces/`, and cuts from its own configured target branch.\n */\nexport function buildPolyrepoGitDeps(\n repos: readonly ResolvedProjectRepo[],\n /** Containment-only callers include read-only repos recorded on old generations. */\n options: { includeReadOnly?: boolean } = {},\n): Map<string, PolyrepoRepoGit> {\n return new Map(\n repos\n // A repo configured `readonly: true` (required === false) is never a\n // write target. Assembly uses the default filter; terminal containment\n // opts in because historical generations can record read-only repos.\n .filter((repo) => repo.required || options.includeReadOnly)\n .map((repo) => {\n let worktreePath = '';\n let generationBranch = '';\n const base = buildUatGenerationGitDeps(repo.repoPath, {\n workspacesRoot: repo.projectPath,\n targetBranch: repo.targetBranch,\n // From this repo's own configured branch_prefix.\n featureBranchPrefix: featureNamespaceOf(repo.sourceBranch),\n });\n\n const git: PolyrepoRepoGit = {\n ...base,\n createWorktree: async (branchName, path) => {\n await base.createWorktree(branchName, path);\n generationBranch = safeBranchName(branchName, 'uat');\n worktreePath = safeGenerationWorktreePath(repo.projectPath, path);\n },\n generationHeadSha: async () =>\n (await runGit(['rev-parse', 'HEAD'], worktreePath)).stdout.trim(),\n /**\n * Undo the merges applied since `sha` by re-pointing the generation\n * branch at it. `git reset --hard` is forbidden repo-wide, and\n * `checkout -B` reaches the same state: the branch moves, the\n * worktree follows, and nothing outside this throwaway branch moves.\n */\n resetGenerationTo: async (sha) => {\n if (!/^[0-9a-f]{7,40}$/i.test(sha)) throw new Error(`unsafe rollback sha: ${sha}`);\n if (!generationBranch) throw new Error('rollback before createWorktree');\n await runGit(['checkout', '-B', generationBranch, sha], worktreePath);\n },\n };\n return [repo.repoKey, git] as const;\n }),\n );\n}\n\n/**\n * Union of uat/* branches across every member repo. A generation name collides\n * if ANY member repo already has it, since the name is shared across repos.\n */\nexport async function listRemoteUatBranchesMulti(\n repos: readonly ResolvedProjectRepo[],\n): Promise<string[]> {\n const perRepo = await Promise.all(repos.map((repo) => listRemoteUatBranches(repo.repoPath)));\n return [...new Set(perRepo.flat())];\n}\n\n/** Branch names currently present on origin under uat/* — naming collision input. */\nexport async function listRemoteUatBranches(projectRoot: string): Promise<string[]> {\n const { stdout } = await runGit(['ls-remote', '--heads', 'origin', 'uat/*'], projectRoot).catch(() => ({ stdout: '' }));\n return stdout\n .split('\\n')\n .map((line) => line.split('\\t')[1] ?? '')\n .filter((ref) => ref.startsWith('refs/heads/'))\n .map((ref) => ref.slice('refs/heads/'.length));\n}\n\n/**\n * Polyrepo cleanup deps (PAN-3093). Each member repo owns its own worktree and\n * branch, and the wrapper folder holds them all.\n *\n * `removeGenerationResidue` also deletes the generation branch from EVERY\n * configured member repo, not just the ones the generation recorded: a global\n * hold-out can drop a repo after its branch was already created locally. That\n * branch is never pushed, but it would linger. Deleting a branch that does not\n * exist is a no-op, so the sweep is safe.\n */\nexport function buildPolyrepoCleanupGit(\n repos: readonly ResolvedProjectRepo[],\n projectPath: string,\n): {\n removeWorktree(worktreePath: string): Promise<void>;\n deleteBranch(branchName: string): Promise<void>;\n removeRepoArtifacts(repo: { repoKey: string; repoPath: string; branch: string; worktreePath: string }): Promise<void>;\n removeGenerationResidue(\n generation: { name: string; worktreePath: string },\n recordedRepoKeys?: readonly string[],\n ): Promise<void>;\n} {\n // Cleanup deletes branches locally AND on the remote, so a read-only repo must\n // be excluded here for the same reason it is excluded from assembly.\n const writable = repos.filter((repo) => repo.required);\n const byKey = new Map(writable.map((r) => [r.repoKey, r]));\n\n const deleteBranchIn = async (repoPath: string, branchName: string) => {\n const safeBranch = safeBranchName(branchName, 'uat');\n await runGit(['branch', '-D', safeBranch], repoPath).catch(() => {});\n await runGit(['push', 'origin', '--delete', safeBranch], repoPath).catch(() => {});\n };\n\n return {\n // Unused on the polyrepo path (removeRepoArtifacts supersedes them) but the\n // deps contract still requires them.\n removeWorktree: async () => {},\n deleteBranch: async () => {},\n\n removeRepoArtifacts: async (repo) => {\n // Unknown key here means the repo is not in the writable set — refuse\n // rather than fall back to the caller-supplied path.\n const configured = byKey.get(repo.repoKey);\n if (!configured) {\n throw new Error(`[uat-cleanup] refusing to mutate non-writable or unknown repo: ${repo.repoKey}`);\n }\n const repoPath = configured.repoPath;\n const safePath = safeGenerationWorktreePath(projectPath, repo.worktreePath);\n await runGit(['worktree', 'remove', '--force', safePath], repoPath).catch(() => {});\n await runGit(['worktree', 'prune'], repoPath).catch(() => {});\n await deleteBranchIn(repoPath, repo.branch);\n },\n\n removeGenerationResidue: async (generation, recordedRepoKeys = []) => {\n // Only repos the generation did NOT record: removeRepoArtifacts already\n // deleted the branch for every recorded repo, and repeating it costs a\n // full remote handshake per repo before its expected failure.\n const recorded = new Set(recordedRepoKeys);\n for (const repo of writable) {\n if (recorded.has(repo.repoKey)) continue;\n await deleteBranchIn(repo.repoPath, generation.name);\n await runGit(['worktree', 'prune'], repo.repoPath).catch(() => {});\n }\n // Validate before deleting: this is a recursive removal, and the check\n // refuses anything outside <projectPath>/workspaces.\n const safeFolder = safeGenerationWorktreePath(projectPath, generation.worktreePath);\n await rm(safeFolder, { recursive: true, force: true });\n },\n };\n}\n\n/** Cleanup deps: remove a generation worktree and delete its branch everywhere. */\nexport function buildUatGenerationCleanupGit(projectRoot: string): {\n removeWorktree(worktreePath: string): Promise<void>;\n deleteBranch(branchName: string): Promise<void>;\n} {\n return {\n removeWorktree: async (path) => {\n const safePath = safeGenerationWorktreePath(projectRoot, path);\n await runGit(['worktree', 'remove', '--force', safePath], projectRoot).catch(() => {});\n await runGit(['worktree', 'prune'], projectRoot).catch(() => {});\n },\n deleteBranch: async (branchName) => {\n const safeBranch = safeBranchName(branchName, 'uat');\n await runGit(['branch', '-D', safeBranch], projectRoot).catch(() => {});\n await runGit(['push', 'origin', '--delete', safeBranch], projectRoot).catch(() => {});\n },\n };\n}\n","/**\n * Polyrepo UAT generation assembly (PAN-3093).\n *\n * The single-repo engine (uat-generation-engine.ts) builds one branch off one\n * main in one worktree. A polyrepo project's generation is an N-tuple: one\n * `uat/<label>-<codename>-<MMDD>` branch per member repo that some ready\n * feature actually contributes to, each cut from that repo's own target head.\n * This module orchestrates that; the single-repo engine is untouched and its\n * `GenerationGitDeps` contract is reused verbatim, one instance per repo.\n *\n * Two rules distinguish it from N independent assemblies:\n *\n * - **Global hold-out.** A feature that cannot be merged and resolved in ANY\n * repo is held out of the WHOLE generation. Testers must never see a\n * feature half-applied across repos.\n * - **Atomic per feature, with rollback.** A feature applies to every repo it\n * contributes to or to none. Each target repo's head is captured first, and\n * a repo that already merged the feature is re-pointed at that head when a\n * later repo rejects it. Accepted features are never rebuilt, so the work\n * is linear in repos x features.\n *\n * Pure orchestrator: all git and store I/O is injected, so every path — happy,\n * conflict-resolved, held-out, push-failed — is unit-testable with fake deps.\n */\n\nimport { makeUniqueUatCandidateName } from './uat-candidate-name.js';\nimport { generationFolderName } from './uat-generation-engine.js';\nimport {\n applyUnionLintPlan,\n planUnionLint,\n plannedFiles,\n seedUnionLedger,\n type MigrationVersionLedger,\n type UnionLintPlan,\n} from './uat-union-lint.js';\nimport type {\n ConflictContext,\n ConflictResolutionResult,\n GenerationGitDeps,\n GenerationStorePort,\n ReadyFeature,\n} from './uat-generation-engine.js';\nimport type {\n UatGeneration,\n UatGenerationHeldOut,\n UatGenerationMember,\n UatGenerationMemberRepo,\n UatGenerationRepo,\n UatGenerationResolution,\n UatGenerationStatus,\n} from '../overdeck/merge-sync.js';\nimport type { ResolvedProjectRepo } from '../project-repos.js';\n\nexport interface PolyrepoAssembleInput {\n /** Wrapper project path. Not a git repo; it owns `workspaces/`. */\n projectRoot: string;\n /** Project label for the codename, e.g. 'min'. */\n label: string;\n /** ISO date; MMDD goes into the branch name. */\n dateIso: string;\n /**\n * Ready features in merge-queue order. Each carries `repoContributions`\n * naming the member repos it has a branch in (see\n * computePolyrepoMergeQueueFromCandidates).\n */\n features: readonly ReadyFeature[];\n /** Every configured member repo for the project. */\n repos: readonly ResolvedProjectRepo[];\n takenBranchNames?: readonly string[];\n}\n\n/**\n * One member repo's git, plus the two primitives per-feature atomicity needs.\n *\n * Without them the only way to un-apply a feature that failed in a later repo\n * is to rebuild every repo branch from base and replay the survivors — O(R×F²)\n * heavyweight git work, which for a 20-feature batch across 3 repos is hundreds\n * of merges and can hold the project's single-flight reconcile slot for hours.\n */\nexport interface PolyrepoRepoGit extends GenerationGitDeps {\n /** Current head of this repo's generation branch. */\n generationHeadSha(): Promise<string>;\n /** Move this repo's generation branch back to a previously captured head. */\n resetGenerationTo(sha: string): Promise<void>;\n}\n\nexport interface PolyrepoAssembleDeps {\n /** One PolyrepoRepoGit per repoKey — see buildPolyrepoGitDeps. */\n repoGit: ReadonlyMap<string, PolyrepoRepoGit>;\n store: GenerationStorePort;\n /**\n * The assembly agent, called mid-conflict with the conflicting repo named in\n * `repoKey`. Absent hook = every conflict is held out.\n */\n resolveConflict?: (ctx: ConflictContext) => Promise<ConflictResolutionResult | null>;\n log?: (msg: string) => void;\n}\n\n/** A feature's merge outcome inside one repo during a single assembly pass. */\ninterface RepoMergeOutcome {\n repoKey: string;\n headSha: string;\n mergeOrderInRepo: number;\n resolution?: ConflictResolutionResult;\n /** Union-lint renumbering applied on this repo's branch (PAN-3166). */\n renumber?: { files: string[]; commitSha: string; note: string };\n conflictingIssueIds: string[];\n}\n\n/** A feature that could not be applied everywhere and is held out globally. */\ninterface BlockedFeature {\n issueId: string;\n /** The repo that rejected it. */\n repoKey: string;\n branch: string;\n reason: string;\n}\n\n/** Everything the assembly pass produced. */\ninterface AssemblyPass {\n /** Per-repo base SHA and worktree, in merge order. */\n repos: UatGenerationRepo[];\n /** issueId → the repos it merged cleanly into. */\n mergedByIssue: Map<string, RepoMergeOutcome[]>;\n /** A repo-level failure (worktree creation, failed rollback) aborts the generation. */\n fatal: string | null;\n}\n\nconst SHORT_SHA_LENGTH = 7;\n\n/**\n * `fe@abc1234 api@def5678` — the one anchor format for every per-repo SHA\n * tuple. Base anchors, member anchors, and the reconciler's live anchors are\n * all built here: the reconciler decides staleness by STRING EQUALITY against\n * what assembly stored, so any two producers that disagree on ordering or SHA\n * length would make every generation look permanently stale and reassemble\n * forever.\n */\nexport function compositeAnchor(\n entries: ReadonlyArray<{ repoKey: string; sha: string; mergeOrder: number }>,\n): string {\n return [...entries]\n .sort((a, b) => a.mergeOrder - b.mergeOrder || a.repoKey.localeCompare(b.repoKey))\n .map((e) => `${e.repoKey}@${e.sha.slice(0, SHORT_SHA_LENGTH)}`)\n .join(' ');\n}\n\n/** The generation's base anchor, across the repos it branched. */\nexport function compositeBaseAnchor(repos: readonly UatGenerationRepo[]): string {\n return compositeAnchor(repos.map((r) => ({ repoKey: r.repoKey, sha: r.baseSha, mergeOrder: r.mergeOrder })));\n}\n\n/**\n * One member's anchor, across the repos it contributed to, ordered by repoKey.\n *\n * Deliberately NOT ordered by merge order: assembly knows `mergeOrderInRepo`\n * (position within one repo's own sequence) while the reconciler only has the\n * repo's configured order, and those are different numbers. Sorting on a field\n * only one side can compute would silently yield two different strings for the\n * same state, so every generation would read stale and reassemble forever.\n * repoKey is available to both and is stable.\n */\nexport function compositeMemberAnchor(\n repos: ReadonlyArray<{ repoKey: string; headSha: string }>,\n): string {\n return [...repos]\n .sort((a, b) => a.repoKey.localeCompare(b.repoKey))\n .map((r) => `${r.repoKey}@${r.headSha.slice(0, SHORT_SHA_LENGTH)}`)\n .join(' ');\n}\n\n/**\n * Composite anchor over every repo a held-out feature contributes to, in the\n * same shape a merged member gets, so the reconciler's per-feature anchor can\n * be compared against it by string equality.\n */\nasync function heldOutAnchor(\n feature: ReadyFeature | undefined,\n deps: PolyrepoAssembleDeps,\n): Promise<string> {\n const contributions = feature?.repoContributions ?? [];\n if (contributions.length === 0) return 'unknown';\n\n const entries = await Promise.all(\n contributions.map(async (c) => ({\n repoKey: c.repoKey,\n headSha: await deps.repoGit.get(c.repoKey)?.branchHeadSha(c.branch).catch(() => 'unknown') ?? 'unknown',\n })),\n );\n return compositeMemberAnchor(entries);\n}\n\n/** Repos some feature contributes to, in configured merge order. */\nfunction contributingRepos(\n features: readonly ReadyFeature[],\n repos: readonly ResolvedProjectRepo[],\n): ResolvedProjectRepo[] {\n const touched = new Set<string>();\n for (const feature of features) {\n for (const contribution of feature.repoContributions ?? []) touched.add(contribution.repoKey);\n }\n return repos\n .filter((repo) => touched.has(repo.repoKey))\n .sort((a, b) => a.mergeOrder - b.mergeOrder || a.repoKey.localeCompare(b.repoKey));\n}\n\nfunction contributionFor(feature: ReadyFeature, repoKey: string) {\n return (feature.repoContributions ?? []).find((c) => c.repoKey === repoKey);\n}\n\nfunction conflictingWith(feature: ReadyFeature, mergedIssueIds: readonly string[]): string[] {\n const merged = new Set(mergedIssueIds.map((id) => id.toUpperCase()));\n return (feature.conflictsWith ?? []).filter((id) => merged.has(id.toUpperCase()));\n}\n\n/**\n * Assemble one polyrepo generation. Always returns the generation row (status\n * 'ready' or 'failed'); throws only on store failures. On success, older\n * 'ready' generations for the project flip to 'superseded'.\n */\nexport async function assemblePolyrepoUatGeneration(\n input: PolyrepoAssembleInput,\n deps: PolyrepoAssembleDeps,\n): Promise<UatGeneration> {\n const log = deps.log ?? (() => {});\n const name = makeUniqueUatCandidateName(\n { label: input.label, dateIso: input.dateIso },\n [...deps.store.listNames(), ...(input.takenBranchNames ?? [])],\n );\n const worktreePath = `${input.projectRoot}/workspaces/${generationFolderName(name)}`;\n const repoWorktree = (repoKey: string) => `${worktreePath}/${repoKey}`;\n\n deps.store.insert({\n name,\n worktreePath,\n projectRoot: input.projectRoot,\n baseSha: '',\n status: 'assembling',\n repos: [],\n members: [],\n heldOut: [],\n resolutions: [],\n stackStartedAt: null,\n cleanedAt: null,\n });\n\n const heldOut: UatGenerationHeldOut[] = [];\n const heldOutIds = new Set<string>();\n\n /**\n * Base SHA per repo, accumulated across passes. The generation's base anchor\n * covers every repo the INPUT features contribute to, not just the repos the\n * final pass branched: a hold-out can drop a repo, and if the anchor shrank\n * with it the reconciler — which derives its anchor from the ready set —\n * could never match, so it would rebuild the same generation every tick.\n */\n const baseShaByRepo = new Map<string, string>();\n\n const finish = (\n status: UatGenerationStatus,\n repos: UatGenerationRepo[],\n members: UatGenerationMember[],\n resolutions: UatGenerationResolution[],\n anchorRepos?: readonly ResolvedProjectRepo[],\n ): UatGeneration => {\n const baseSha = anchorRepos\n ? compositeAnchor(\n anchorRepos\n .filter((r) => baseShaByRepo.has(r.repoKey))\n .map((r) => ({ repoKey: r.repoKey, sha: baseShaByRepo.get(r.repoKey)!, mergeOrder: r.mergeOrder })),\n )\n : compositeBaseAnchor(repos);\n deps.store.update(name, { status, baseSha, repos, members, heldOut, resolutions });\n return {\n name, worktreePath, projectRoot: input.projectRoot, baseSha,\n status, repos, members, heldOut, resolutions,\n stackStartedAt: null, cleanedAt: null, createdAt: '', updatedAt: '',\n };\n };\n\n const allContributing = contributingRepos(input.features, input.repos);\n if (allContributing.length === 0) {\n log(`[uat-polyrepo] ${name}: no member repo has a contributing feature branch — marking failed`);\n return finish('failed', [], [], []);\n }\n log(`[uat-polyrepo] ${name}: assembling across ${allContributing.map((r) => r.repoKey).join(', ')}`);\n\n const missingDeps = allContributing.filter((repo) => !deps.repoGit.has(repo.repoKey));\n if (missingDeps.length > 0) {\n log(`[uat-polyrepo] ${name}: no git deps for repo(s) ${missingDeps.map((r) => r.repoKey).join(', ')} — marking failed`);\n return finish('failed', [], [], []);\n }\n\n // ONE pass, feature-atomic. Each feature is merged into every repo it\n // contributes to; if any repo rejects it, the repos that already took it are\n // rolled back to the head they had before that feature, and assembly moves on.\n // Earlier accepted features are never rebuilt, so the work is O(R x F) rather\n // than the O(R x F^2) a rebuild-and-replay design costs.\n const pass = await runAssemblyPass(name, input.features, allContributing, repoWorktree, deps, log, {\n onHeldOut: async (blocked) => {\n heldOutIds.add(blocked.issueId);\n // The reconciler compares a held-out feature's stored headSha against a\n // composite anchor over ALL its contributions, so storing the single\n // blocking repo's SHA would never match and would churn the generation\n // every tick. Record the same composite shape members use.\n const blockedFeature = input.features.find((f) => f.issueId === blocked.issueId);\n heldOut.push({\n issueId: blocked.issueId,\n // The LOGICAL feature branch, not the blocking repo's contribution\n // branch: the reconciler keys its head anchors by ReadyFeature.branch,\n // and a repo with a custom branch_prefix (feat/ vs feature/) would make\n // that lookup miss forever and rebuild the generation every tick. The\n // blocking repo is already named in the reason.\n branch: blockedFeature?.branch ?? blocked.branch,\n headSha: await heldOutAnchor(blockedFeature, deps),\n reason: blocked.reason,\n });\n deps.store.update(name, { heldOut });\n log(`[uat-polyrepo] ${name}: holding ${blocked.issueId} out of the whole generation (${blocked.repoKey})`);\n },\n });\n for (const repo of pass.repos) baseShaByRepo.set(repo.repoKey, repo.baseSha);\n\n if (pass.fatal) {\n log(`[uat-polyrepo] ${name}: ${pass.fatal}`);\n return finish('failed', pass.repos, [], []);\n }\n\n // A repo every one of whose features was held out carries nothing. Its\n // worktree and branch exist locally but are never pushed; cleanup reaps them\n // with the generation folder.\n const carrying = new Set([...pass.mergedByIssue.values()].flat().map((o) => o.repoKey));\n const abandoned = pass.repos.filter((r) => !carrying.has(r.repoKey));\n if (abandoned.length > 0) {\n log(`[uat-polyrepo] ${name}: repo(s) ${abandoned.map((r) => r.repoKey).join(', ')} lost every contribution to a hold-out — no branch published; local worktree reaped with the generation folder`);\n }\n const publishedRepos = pass.repos.filter((r) => carrying.has(r.repoKey));\n\n const { members, resolutions } = collectMembers(input.features, heldOutIds, pass.mergedByIssue);\n\n if (members.length === 0) {\n // Anchor on the full candidate set even though nothing published: the\n // reconciler's desired anchor covers every ready-set contribution, and\n // FAILED_RETRY_BACKOFF_MS only suppresses a repeat when the two signatures\n // match exactly. Without it an all-held-out batch reassembles every tick.\n log(`[uat-polyrepo] ${name}: nothing merged (${heldOut.length} held out) — marking failed`);\n return finish('failed', publishedRepos, [], [], allContributing);\n }\n\n // Push every repo. A partial push is recoverable — cleanup reaps the branches\n // that did land — but the generation is not testable, so it is failed.\n for (const repo of publishedRepos) {\n try {\n await deps.repoGit.get(repo.repoKey)!.push(name);\n } catch (err) {\n log(`[uat-polyrepo] ${name}: push failed in ${repo.repoKey}: ${err instanceof Error ? err.message : String(err)}`);\n return finish('failed', publishedRepos, members, resolutions);\n }\n }\n\n for (const older of deps.store.listChain(input.projectRoot, ['ready'])) {\n if (older.name !== name) deps.store.update(older.name, { status: 'superseded' });\n }\n\n log(`[uat-polyrepo] ${name}: ready — ${members.length} member(s) across ${publishedRepos.length} repo(s), ${resolutions.length} resolution(s), ${heldOut.length} held out`);\n return finish('ready', publishedRepos, members, resolutions, allContributing);\n}\n\n/**\n * Build every repo's branch from base, then apply features one at a time.\n *\n * A feature is applied to every repo it contributes to or to none: if a later\n * repo rejects it, the repos that already merged it are reset to the head they\n * had before, and it is reported held out. Accepted features are never\n * disturbed, so total git work is linear in (repos x features).\n */\nasync function runAssemblyPass(\n name: string,\n features: readonly ReadyFeature[],\n active: readonly ResolvedProjectRepo[],\n repoWorktree: (repoKey: string) => string,\n deps: PolyrepoAssembleDeps,\n log: (msg: string) => void,\n hooks: { onHeldOut: (blocked: BlockedFeature) => Promise<void> },\n): Promise<AssemblyPass> {\n const repos: UatGenerationRepo[] = [];\n const mergedByIssue = new Map<string, RepoMergeOutcome[]>();\n /** Merge count per repo, which is that repo's mergeOrderInRepo counter. */\n const mergedInRepo = new Map<string, string[]>();\n /** Union lint state per repo (PAN-3166) — absent when the repo's deps omit it. */\n const ledgers = new Map<string, MigrationVersionLedger>();\n\n for (const repo of active) {\n const git = deps.repoGit.get(repo.repoKey)!;\n const path = repoWorktree(repo.repoKey);\n\n let baseSha: string;\n try {\n baseSha = await git.fetchMain();\n await git.createWorktree(name, path);\n } catch (err) {\n return {\n repos,\n mergedByIssue,\n fatal: `worktree creation failed in ${repo.repoKey}: ${err instanceof Error ? err.message : String(err)}`,\n };\n }\n\n repos.push({\n repoKey: repo.repoKey,\n repoPath: repo.repoPath,\n branch: name,\n baseSha,\n // Persisted so promote merges back into the branch this was cut from,\n // rather than assuming main long after assembly.\n targetBranch: repo.targetBranch,\n worktreePath: path,\n mergeOrder: repo.mergeOrder,\n promotedAt: null,\n mergeSha: null,\n });\n mergedInRepo.set(repo.repoKey, []);\n\n // Union lint (PAN-3166), per repo: owners from this repo's freshly cut\n // branch, reservations from every contributing candidate branch in THIS\n // repo. A read failure is fatal to the assembly rather than silently\n // disabling the lint.\n try {\n const candidates = features\n .map((feature) => contributionFor(feature, repo.repoKey)?.branch)\n .filter((branch): branch is string => Boolean(branch));\n const ledger = await seedUnionLedger(git, name, candidates);\n if (ledger) ledgers.set(repo.repoKey, ledger);\n } catch (err) {\n return {\n repos,\n mergedByIssue,\n fatal: `union lint could not read migrations in ${repo.repoKey}: ${err instanceof Error ? err.message : String(err)}`,\n };\n }\n }\n\n for (const feature of features) {\n const targets = active.filter((repo) => contributionFor(feature, repo.repoKey));\n if (targets.length === 0) continue;\n\n // Union lint (PAN-3166) — planned for every repo BEFORE any merge, so a\n // hold-out costs one `git ls-tree` per repo and never needs the rollback\n // machinery. A hold-out is global here, exactly like a merge conflict: one\n // repo's colliding migration keeps the whole feature out of the generation.\n const plans = new Map<string, UnionLintPlan>();\n let unionCollision: BlockedFeature | null = null;\n for (const repo of targets) {\n const contribution = contributionFor(feature, repo.repoKey)!;\n const plan = await planUnionLint({\n git: deps.repoGit.get(repo.repoKey)!,\n ledger: ledgers.get(repo.repoKey) ?? null,\n generationBranch: name,\n featureBranch: contribution.branch,\n issueId: feature.issueId,\n });\n plans.set(repo.repoKey, plan);\n if (plan.disposition.kind === 'hold-out') {\n unionCollision = {\n issueId: feature.issueId,\n repoKey: repo.repoKey,\n branch: contribution.branch,\n reason: `${plan.disposition.reason} (in ${repo.repoKey})`,\n };\n break;\n }\n }\n if (unionCollision) {\n log(`[uat-polyrepo] ${name}: holding ${feature.issueId} out — ${unionCollision.reason}`);\n await hooks.onHeldOut(unionCollision);\n continue;\n }\n\n // Capture each target's head BEFORE this feature, so a partial application\n // can be undone without rebuilding the branch from base.\n const snapshots = new Map<string, string>();\n let snapshotFailure: string | null = null;\n for (const repo of targets) {\n try {\n snapshots.set(repo.repoKey, await deps.repoGit.get(repo.repoKey)!.generationHeadSha());\n } catch (err) {\n snapshotFailure = `could not read ${repo.repoKey} head: ${err instanceof Error ? err.message : String(err)}`;\n break;\n }\n }\n if (snapshotFailure) {\n await hooks.onHeldOut({\n issueId: feature.issueId,\n repoKey: targets[0]!.repoKey,\n branch: contributionFor(feature, targets[0]!.repoKey)?.branch ?? feature.branch,\n reason: snapshotFailure,\n });\n continue;\n }\n\n const applied: RepoMergeOutcome[] = [];\n let blocked: BlockedFeature | null = null;\n\n for (const repo of targets) {\n const contribution = contributionFor(feature, repo.repoKey)!;\n const git = deps.repoGit.get(repo.repoKey)!;\n const outcome = await mergeOneFeature({\n name,\n feature,\n featureBranch: contribution.branch,\n repoKey: repo.repoKey,\n worktreePath: repoWorktree(repo.repoKey),\n mergedIssueIds: mergedInRepo.get(repo.repoKey) ?? [],\n mergeOrderInRepo: (mergedInRepo.get(repo.repoKey)?.length ?? 0) + 1,\n git,\n }, deps, log);\n\n if ('reason' in outcome) {\n blocked = {\n issueId: feature.issueId,\n repoKey: repo.repoKey,\n branch: contribution.branch,\n reason: `${outcome.reason} (in ${repo.repoKey})`,\n };\n break;\n }\n applied.push(outcome);\n\n // The renumbering can only land once the merge brought the files onto\n // this repo's branch. It is committed ABOVE the snapshot taken before\n // this feature, so the rollback below removes it with everything else —\n // a rolled-back feature never leaves an orphaned rename behind.\n const plan = plans.get(repo.repoKey);\n if (plan) {\n try {\n const renumber = await applyUnionLintPlan(git, plan, feature.issueId);\n if (renumber) {\n outcome.renumber = renumber;\n log(`[uat-polyrepo] ${name}: renumbered ${feature.issueId} migrations in ${repo.repoKey} — ${renumber.note}`);\n } else if (plan.disposition.kind === 'renumber') {\n blocked = {\n issueId: feature.issueId,\n repoKey: repo.repoKey,\n branch: contribution.branch,\n reason: `migration renumbering is required but no rename primitive is wired (in ${repo.repoKey})`,\n };\n break;\n }\n } catch (err) {\n blocked = {\n issueId: feature.issueId,\n repoKey: repo.repoKey,\n branch: contribution.branch,\n reason: `migration renumbering failed: ${err instanceof Error ? err.message.split('\\n')[0] : String(err)} (in ${repo.repoKey})`,\n };\n break;\n }\n }\n }\n\n if (blocked) {\n // Roll the repos that took it back to their pre-feature head. A failure\n // here cannot be ignored: the branch would carry a feature recorded as\n // held out, so the generation is abandoned rather than shipped wrong.\n for (const outcome of applied) {\n const snapshot = snapshots.get(outcome.repoKey)!;\n try {\n await deps.repoGit.get(outcome.repoKey)!.resetGenerationTo(snapshot);\n } catch (err) {\n return {\n repos,\n mergedByIssue,\n fatal:\n `could not roll ${outcome.repoKey} back to ${snapshot.slice(0, 9)} after holding out ` +\n `${feature.issueId}: ${err instanceof Error ? err.message : String(err)}`,\n };\n }\n }\n await hooks.onHeldOut(blocked);\n continue;\n }\n\n // Only now, with the feature accepted in EVERY repo it contributes to, is\n // it attributed in the ledgers — and by its POST-rename paths. Recording\n // per repo as the loop went would leave a half-recorded rename behind when\n // a later repo rejected the feature and rolled the earlier ones back.\n for (const outcome of applied) {\n mergedInRepo.get(outcome.repoKey)!.push(feature.issueId);\n const plan = plans.get(outcome.repoKey);\n if (plan) ledgers.get(outcome.repoKey)?.record(plannedFiles(plan), feature.issueId);\n }\n mergedByIssue.set(feature.issueId, applied);\n }\n\n return { repos, mergedByIssue, fatal: null };\n}\n\ninterface MergeOneFeatureArgs {\n name: string;\n feature: ReadyFeature;\n featureBranch: string;\n repoKey: string;\n /** This repo's worktree — the mid-conflict state the hook edits. */\n worktreePath: string;\n mergedIssueIds: readonly string[];\n mergeOrderInRepo: number;\n git: GenerationGitDeps;\n}\n\n/** Merge one feature into one repo's generation branch. */\nasync function mergeOneFeature(\n args: MergeOneFeatureArgs,\n deps: PolyrepoAssembleDeps,\n log: (msg: string) => void,\n): Promise<RepoMergeOutcome | { reason: string; headSha: string }> {\n const { name, feature, featureBranch, repoKey, worktreePath, mergedIssueIds, mergeOrderInRepo, git } = args;\n const headSha = await git.branchHeadSha(featureBranch).catch(() => 'unknown');\n const conflictingIssueIds = conflictingWith(feature, mergedIssueIds);\n\n let result: Awaited<ReturnType<GenerationGitDeps['mergeBranch']>>;\n try {\n result = await git.mergeBranch(featureBranch);\n } catch (err) {\n await git.abortMerge().catch(() => {});\n return { headSha, reason: `merge failed: ${err instanceof Error ? (err.message.split('\\n')[0] ?? 'merge failed') : String(err)}` };\n }\n\n if (result.ok) {\n return { repoKey, headSha, mergeOrderInRepo, conflictingIssueIds };\n }\n\n if (!result.conflict || !deps.resolveConflict) {\n await git.abortMerge().catch(() => {});\n return {\n headSha,\n reason: result.conflict\n ? `conflicts with ${conflictingIssueIds.join(', ') || 'an earlier member'} — no assembly agent available`\n : result.reason,\n };\n }\n\n log(`[uat-polyrepo] ${name}: resolving conflict in ${repoKey} ${feature.issueId} <-> ${conflictingIssueIds.join(', ') || '(unknown member)'}`);\n let resolution: ConflictResolutionResult | null = null;\n try {\n resolution = await deps.resolveConflict({\n feature,\n mergedIssueIds,\n conflictingIssueIds,\n branchName: name,\n worktreePath,\n repoKey,\n });\n } catch (err) {\n log(`[uat-polyrepo] ${name}: conflict agent threw in ${repoKey}: ${err instanceof Error ? err.message : String(err)}`);\n resolution = null;\n }\n\n if (!resolution) {\n await git.abortMerge().catch(() => {});\n return {\n headSha,\n reason: `conflict with ${conflictingIssueIds.join(', ') || 'an earlier member'} could not be auto-resolved — waits for the next generation`,\n };\n }\n\n return { repoKey, headSha, mergeOrderInRepo, resolution, conflictingIssueIds };\n}\n\n/**\n * Fold the accepted pass into store-shaped members and resolutions, preserving\n * the ready-set order so mergeOrder reads the same as the monorepo path.\n */\nfunction collectMembers(\n features: readonly ReadyFeature[],\n heldOutIds: ReadonlySet<string>,\n mergedByIssue: ReadonlyMap<string, RepoMergeOutcome[]>,\n): { members: UatGenerationMember[]; resolutions: UatGenerationResolution[] } {\n const members: UatGenerationMember[] = [];\n const resolutions: UatGenerationResolution[] = [];\n\n for (const feature of features) {\n if (heldOutIds.has(feature.issueId)) continue;\n const outcomes = mergedByIssue.get(feature.issueId);\n if (!outcomes || outcomes.length === 0) continue;\n\n const repos: UatGenerationMemberRepo[] = outcomes.map((o) => ({\n repoKey: o.repoKey,\n branch: contributionFor(feature, o.repoKey)?.branch ?? feature.branch,\n headSha: o.headSha,\n mergeOrderInRepo: o.mergeOrderInRepo,\n }));\n\n members.push({\n issueId: feature.issueId,\n title: feature.title,\n branch: feature.branch,\n // The generation row keeps ONE headSha per member, which the reconciler\n // compares by string equality to decide staleness — so for a polyrepo\n // member it must be the composite anchor over every repo it touched, not\n // one repo's SHA. Per-repo SHAs are also kept individually in `repos`.\n headSha: compositeMemberAnchor(repos),\n mergeOrder: members.length + 1,\n ...(feature.pr !== undefined ? { pr: feature.pr } : {}),\n ...(feature.prUrl !== undefined ? { prUrl: feature.prUrl } : {}),\n repos,\n });\n\n for (const outcome of outcomes) {\n if (outcome.resolution) {\n resolutions.push({\n issueIds: [feature.issueId, ...outcome.conflictingIssueIds],\n files: outcome.resolution.files,\n commitSha: outcome.resolution.commitSha,\n kind: 'conflict',\n });\n }\n if (outcome.renumber) {\n resolutions.push({\n issueIds: [feature.issueId],\n files: outcome.renumber.files,\n commitSha: outcome.renumber.commitSha,\n kind: 'migration-renumber',\n note: `${outcome.renumber.note} (in ${outcome.repoKey})`,\n });\n }\n }\n }\n\n return { members, resolutions };\n}\n","/**\n * Assembly-agent conflict resolution (PAN-1737: UAT batch trains).\n *\n * Implements the engine's `resolveConflict` hook: when merging a feature onto\n * a UAT generation branch conflicts, a TIMEBOXED headless agent run is given\n * the mid-conflict worktree with one mission — resolve THIS merge's conflict\n * markers, changing nothing beyond the resolution.\n *\n * Division of labor, deliberately strict:\n * - The agent only EDITS FILES (headless `claude -p` with acceptEdits; no\n * shell access). It cannot stage, commit, push, or wander.\n * - This module verifies the result in code (no leftover conflict markers,\n * no unmerged index entries), stages, and concludes the merge commit.\n * - Any failure — agent missing, timeout, markers left, commit rejected —\n * returns null; the engine then aborts the merge and holds the feature\n * out. The assembly NEVER wedges on a conflict.\n *\n * The merge commit subject keeps git's standard `Merge branch ...` form (it\n * survives commitlint's default-ignores when this history is later promoted\n * to main); the auditable `uat-assembly:` marker lives in the body.\n */\nimport { execFile } from 'child_process';\nimport { promisify } from 'util';\nimport type { ConflictContext, ConflictResolutionResult } from './uat-generation-engine.js';\n\nconst execFileAsync = promisify(execFile);\n\nexport const DEFAULT_CONFLICT_TIMEOUT_MS = 5 * 60 * 1000;\n\n/** Injectable I/O so the hook's decision logic is unit-testable. */\nexport interface ConflictAgentDeps {\n /** Paths with unmerged index entries (`git diff --name-only --diff-filter=U`). */\n listConflictedFiles(cwd: string): Promise<string[]>;\n /** Staged paths before/after the agent run (`git diff --cached --name-only`). */\n stagedFiles(cwd: string): Promise<string[]>;\n /** Run the headless resolution agent; throws on spawn failure or timeout. */\n runAgent(args: { prompt: string; cwd: string; timeoutMs: number }): Promise<void>;\n /** Subset of `files` still containing conflict markers after the agent ran. */\n filesWithConflictMarkers(cwd: string, files: readonly string[]): Promise<string[]>;\n /** Unstaged or untracked paths outside the conflicted-file allowlist. */\n unsafeChangedFiles(cwd: string, allowedFiles: readonly string[]): Promise<string[]>;\n /** Discard untrusted worktree/untracked changes outside the allowlist. */\n discardFiles(cwd: string, files: readonly string[]): Promise<void>;\n /** Stage only the conflicted files that the agent was allowed to edit. */\n stageFiles(cwd: string, files: readonly string[]): Promise<void>;\n /** True if unmerged index entries remain after staging. */\n hasUnmergedEntries(cwd: string): Promise<boolean>;\n /** Conclude the in-progress merge with the given message. */\n commitMerge(cwd: string, message: string): Promise<void>;\n headSha(cwd: string): Promise<string>;\n log?: (msg: string) => void;\n}\n\nexport interface ConflictAgentOptions {\n timeoutMs?: number;\n deps?: Partial<ConflictAgentDeps>;\n}\n\nfunction splitNul(stdout: string): string[] {\n return stdout.split('\\0').filter((p) => p.length > 0);\n}\n\nfunction allowlist(files: readonly string[]): ReadonlySet<string> {\n return new Set(files.map((f) => f.replace(/\\\\/g, '/')));\n}\n\nexport function buildConflictResolutionPrompt(ctx: ConflictContext, conflictedFiles: readonly string[]): string {\n const counterparts = ctx.conflictingIssueIds.length > 0\n ? ctx.conflictingIssueIds.join(', ')\n : 'an earlier batch member';\n return [\n ctx.repoKey\n ? `You are resolving a git merge conflict inside the ${ctx.repoKey} repo of a UAT batch assembly worktree.`\n : `You are resolving a git merge conflict inside a UAT batch assembly worktree.`,\n ``,\n `The branch ${ctx.branchName} bundles already-reviewed features for human testing.`,\n `Merging ${ctx.feature.branch} (${ctx.feature.issueId}: ${ctx.feature.title}) conflicted with ${counterparts}.`,\n ``,\n `Conflicted files:`,\n ...conflictedFiles.map((f) => ` - ${f}`),\n ``,\n `Your ONLY job: edit these files to resolve every conflict marker (<<<<<<<, =======, >>>>>>>)`,\n `so that BOTH features' intended behavior is preserved. Both sides passed review —`,\n `combine them; do not pick one side wholesale unless the changes are genuinely identical in intent.`,\n ``,\n `Rules:`,\n `- Change NOTHING beyond what resolving the conflict requires.`,\n `- Do not refactor, reformat, or \"improve\" surrounding code.`,\n `- Do not run git commands; the caller stages and commits.`,\n `- If the conflict cannot be safely resolved, leave the markers in place and say so.`,\n ].join('\\n');\n}\n\nfunction buildMergeCommitMessage(ctx: ConflictContext, files: readonly string[]): string {\n const pair = [ctx.feature.issueId, ...ctx.conflictingIssueIds].join(' <-> ');\n return [\n `Merge branch '${ctx.feature.branch}' into ${ctx.branchName}`,\n ``,\n `uat-assembly: resolve ${pair}`,\n `files: ${files.join(', ')}`,\n ].join('\\n');\n}\n\nfunction defaultDeps(): ConflictAgentDeps {\n const run = (cmd: string, args: string[], cwd: string, timeoutMs?: number) =>\n execFileAsync(cmd, args, { cwd, maxBuffer: 16 * 1024 * 1024, ...(timeoutMs ? { timeout: timeoutMs } : {}) });\n\n return {\n listConflictedFiles: async (cwd) => {\n const { stdout } = await run('git', ['diff', '--name-only', '--diff-filter=U'], cwd);\n return stdout.split('\\n').map((l) => l.trim()).filter(Boolean);\n },\n stagedFiles: async (cwd) => {\n const { stdout } = await run('git', ['diff', '--cached', '--name-only', '-z'], cwd);\n return splitNul(stdout);\n },\n runAgent: async ({ prompt, cwd, timeoutMs }) => {\n await run('claude', ['-p', prompt, '--permission-mode', 'acceptEdits'], cwd, timeoutMs);\n },\n filesWithConflictMarkers: async (cwd, files) => {\n if (files.length === 0) return [];\n // -l: names only; -E for the alternation. grep exits 1 on no matches.\n const { stdout } = await execFileAsync(\n 'grep', ['-lE', '^(<<<<<<<|=======$|>>>>>>>)', '--', ...files],\n { cwd, maxBuffer: 16 * 1024 * 1024 },\n ).catch((err: { code?: number; stdout?: string }) => {\n if (err && err.code === 1) return { stdout: '' };\n throw err;\n });\n return stdout.split('\\n').map((l) => l.trim()).filter(Boolean);\n },\n unsafeChangedFiles: async (cwd, files) => {\n const allowed = allowlist(files);\n const [{ stdout: unstaged }, { stdout: untracked }] = await Promise.all([\n run('git', ['diff', '--name-only', '-z'], cwd),\n run('git', ['ls-files', '--others', '--exclude-standard', '-z'], cwd),\n ]);\n return [...new Set([...splitNul(unstaged), ...splitNul(untracked)])]\n .filter((file) => !allowed.has(file.replace(/\\\\/g, '/')));\n },\n discardFiles: async (cwd, files) => {\n if (files.length === 0) return;\n await run('git', ['restore', '--staged', '--worktree', '--', ...files], cwd).catch(() => {});\n await run('git', ['clean', '-fd', '--', ...files], cwd).catch(() => {});\n },\n stageFiles: async (cwd, files) => {\n if (files.length === 0) return;\n await run('git', ['add', '--', ...files], cwd);\n },\n hasUnmergedEntries: async (cwd) => {\n const { stdout } = await run('git', ['ls-files', '-u'], cwd);\n return stdout.trim().length > 0;\n },\n commitMerge: async (cwd, message) => {\n await run('git', ['commit', '-m', message], cwd);\n },\n headSha: async (cwd) => (await run('git', ['rev-parse', 'HEAD'], cwd)).stdout.trim(),\n };\n}\n\n/**\n * Build the engine's `resolveConflict` hook. Returns the resolution on\n * success; null on ANY failure (the engine aborts the merge and holds the\n * feature out — never throws into the assembly loop).\n */\nexport function buildConflictAgentHook(\n options: ConflictAgentOptions = {},\n): (ctx: ConflictContext) => Promise<ConflictResolutionResult | null> {\n const deps: ConflictAgentDeps = { ...defaultDeps(), ...(options.deps ?? {}) };\n const timeoutMs = options.timeoutMs ?? DEFAULT_CONFLICT_TIMEOUT_MS;\n const log = deps.log ?? (() => {});\n\n return async (ctx) => {\n // Polyrepo assembly runs this hook once per repo, so the repo key has to be\n // in the tag or two repos' log lines are indistinguishable.\n const tag = ctx.repoKey\n ? `[uat-conflict-agent] ${ctx.branchName} ${ctx.repoKey} ${ctx.feature.issueId}`\n : `[uat-conflict-agent] ${ctx.branchName} ${ctx.feature.issueId}`;\n let files: string[];\n let stagedBefore: string[];\n try {\n files = await deps.listConflictedFiles(ctx.worktreePath);\n stagedBefore = await deps.stagedFiles(ctx.worktreePath);\n } catch (err) {\n log(`${tag}: could not inspect conflicted state: ${err instanceof Error ? err.message : String(err)}`);\n return null;\n }\n if (files.length === 0) {\n log(`${tag}: merge failed without unmerged paths — not a content conflict, giving up`);\n return null;\n }\n\n try {\n await deps.runAgent({\n prompt: buildConflictResolutionPrompt(ctx, files),\n cwd: ctx.worktreePath,\n timeoutMs,\n });\n } catch (err) {\n log(`${tag}: agent run failed (${err instanceof Error ? err.message.split('\\n')[0] : String(err)})`);\n return null;\n }\n\n try {\n const unsafe = await deps.unsafeChangedFiles(ctx.worktreePath, files);\n if (unsafe.length > 0) {\n log(`${tag}: agent touched non-conflicted path(s): ${unsafe.join(', ')}`);\n await deps.discardFiles(ctx.worktreePath, unsafe).catch((err) => {\n log(`${tag}: cleanup of non-conflicted path(s) failed: ${err instanceof Error ? err.message : String(err)}`);\n });\n return null;\n }\n\n const unresolved = await deps.filesWithConflictMarkers(ctx.worktreePath, files);\n if (unresolved.length > 0) {\n log(`${tag}: conflict markers remain in ${unresolved.join(', ')}`);\n return null;\n }\n await deps.stageFiles(ctx.worktreePath, files);\n if (await deps.hasUnmergedEntries(ctx.worktreePath)) {\n log(`${tag}: unmerged index entries remain after staging`);\n return null;\n }\n\n const stagedBeforeSet = new Set(stagedBefore);\n const allowed = allowlist(files);\n const stagedAfter = await deps.stagedFiles(ctx.worktreePath);\n const newlyStagedOutsideAllowlist = stagedAfter.filter((file) =>\n !stagedBeforeSet.has(file) && !allowed.has(file.replace(/\\\\/g, '/')),\n );\n if (newlyStagedOutsideAllowlist.length > 0) {\n log(`${tag}: cached diff includes non-conflicted path(s): ${newlyStagedOutsideAllowlist.join(', ')}`);\n return null;\n }\n\n await deps.commitMerge(ctx.worktreePath, buildMergeCommitMessage(ctx, files));\n const commitSha = await deps.headSha(ctx.worktreePath);\n log(`${tag}: resolved ${files.length} file(s) at ${commitSha.slice(0, 9)}`);\n return { files, commitSha };\n } catch (err) {\n log(`${tag}: verification/commit failed: ${err instanceof Error ? err.message.split('\\n')[0] : String(err)}`);\n return null;\n }\n };\n}\n","/**\n * UAT generation reconciler (PAN-1737: UAT batch trains).\n *\n * The loop that keeps \"always one batch ready\" true with zero human action.\n * Each tick (interval-driven from the dashboard server, or forced via the\n * API's rebuild action):\n *\n * 1. No-op unless the merge train is enabled and a flywheel run is active.\n * 2. Invalidate live generations that went stale — main advanced past their\n * baseSha, a member left the ready set, or a member branch gained\n * commits. Invalidation tears down the generation's live stack.\n * (Older SUBSET generations are NOT stale: a smaller batch off current\n * main remains perfectly testable and promotable.)\n * 3. If no live generation matches the current desired set, assemble the\n * next generation in the background. Single-flight per project; a failed\n * assembly for the SAME desired signature backs off before retrying.\n * 4. Trim/reap the chain (cleanup hook).\n *\n * Pure orchestration with injected deps — interval wiring and real data\n * sources live in the dashboard server service.\n */\nimport type { ReadyFeature, GenerationStorePort } from './uat-generation-engine.js';\nimport type { UatGeneration } from '../overdeck/merge-sync.js';\n\n/**\n * An 'assembling' row with NO PROGRESS for this long is marked failed (train\n * un-wedge). Keyed on updated_at — the engine patches the row after every\n * member step — so a big batch grinding through many timeboxed conflict\n * resolutions is never killed for total age (the first live run killed a\n * slow-but-alive 5-member assembly at a flat 30min of created_at age; with\n * many developers driving hundreds of agents, assemblies will only get\n * bigger). A crashed engine stops updating the row and trips this within the\n * hour. Cooperative cancellation of the in-flight engine is PAN-1755.\n */\nexport const STUCK_ASSEMBLING_MS = 60 * 60 * 1000;\n/** Minimum age before re-attempting an assembly that failed for the same input. */\nexport const FAILED_RETRY_BACKOFF_MS = 10 * 60 * 1000;\n\nexport interface UatReconcilerDeps {\n /** Gate: flywheel.merge_train_enabled. */\n isEnabled(): boolean;\n /**\n * Current ready set in merge order, or null when it cannot be computed\n * (no active flywheel run) — null means \"do nothing\", not \"empty\".\n */\n getReadySet(): Promise<readonly ReadyFeature[] | null>;\n /** Current origin/main head SHA (fetching first). */\n getMainHeadSha(): Promise<string>;\n /** Current head SHA per feature branch (origin-first). */\n getBranchHeadSha(branch: string): Promise<string>;\n /**\n * Polyrepo base anchor across every member repo (`fe@abc1234 api@def5678`),\n * replacing the single main SHA. Omit for monorepo — the reconciler then\n * behaves exactly as before, using getMainHeadSha.\n *\n * Staleness is decided by STRING EQUALITY against what assembly stored, so\n * an anchor here must be produced by the same helpers the polyrepo engine\n * uses (compositeBaseAnchor / compositeMemberAnchor).\n */\n getBaseAnchor?(): Promise<string>;\n /**\n * Polyrepo per-feature anchor across the repos it contributes to, replacing\n * the single branch head SHA. Omit for monorepo.\n */\n getFeatureAnchor?(feature: ReadyFeature): Promise<string>;\n /** Positive merge evidence for a generation branch; omitted when unavailable. */\n isGenerationContainedInMain?(generation: UatGeneration): Promise<boolean>;\n store: GenerationStorePort;\n /** Assemble the next generation from the desired set (the engine). */\n assemble(features: readonly ReadyFeature[]): Promise<UatGeneration>;\n /** Tear down a generation's live stack (no-op when none is running). */\n teardownStack(generation: UatGeneration): Promise<void>;\n /** Chain trim/reap (cleanupUatGenerations wiring). */\n cleanup(): Promise<void>;\n now?: () => number;\n log?: (msg: string) => void;\n}\n\nexport interface ReconcileResult {\n action: 'disabled' | 'no-queue' | 'idle' | 'assembled' | 'assembly-failed' | 'backoff' | 'in-flight';\n invalidated: string[];\n generation?: UatGeneration;\n}\n\n/**\n * Single-flight per project: a tick that arrives while one is running gets\n * 'in-flight' immediately instead of queuing (assemblies are minutes-long;\n * the interval would pile up).\n */\nconst inFlightReconciles = new Map<string, Promise<ReconcileResult>>();\n\n/** Stable signature of \"what we want assembled\" — backoff bookkeeping. */\nfunction desiredSignature(features: readonly ReadyFeature[], headShas: ReadonlyMap<string, string>, mainSha: string): string {\n const parts = [...features]\n .map((f) => `${f.issueId.toUpperCase()}@${headShas.get(f.branch) ?? 'unknown'}`)\n .sort();\n return `${mainSha}|${parts.join(',')}`;\n}\n\nfunction generationSignature(gen: UatGeneration): string {\n const parts = [\n ...gen.members.map((m) => `${m.issueId.toUpperCase()}@${m.headSha}`),\n ...gen.heldOut.map((h) => `${h.issueId.toUpperCase()}@${h.headSha ?? 'held'}`),\n ].sort();\n return `${gen.baseSha}|${parts.join(',')}`;\n}\n\nfunction liveSignatureMatches(\n gen: UatGeneration,\n features: readonly ReadyFeature[],\n headShas: ReadonlyMap<string, string>,\n mainSha: string,\n): boolean {\n if (gen.baseSha !== mainSha) return false;\n const desiredIds = new Set(features.map((f) => f.issueId.toUpperCase()));\n const genIds = new Set([\n ...gen.members.map((m) => m.issueId.toUpperCase()),\n ...gen.heldOut.map((h) => h.issueId.toUpperCase()),\n ]);\n if (desiredIds.size !== genIds.size) return false;\n for (const id of desiredIds) if (!genIds.has(id)) return false;\n for (const member of gen.members) {\n const current = headShas.get(member.branch);\n if (current !== member.headSha) return false;\n }\n for (const held of gen.heldOut) {\n if (!held.branch || !held.headSha) return false;\n const current = headShas.get(held.branch);\n if (current !== held.headSha) return false;\n }\n return true;\n}\n\n/**\n * A live generation is INVALIDATED only when its own CONTENT can no longer be\n * trusted for promotion: a bundled member left the ready queue (its work must\n * not merge) or a member's branch gained commits (the tested tree is outdated).\n *\n * Deliberately NOT invalidation triggers (the first live run, 2026-06-10,\n * churned every ready batch to death within minutes):\n * - main advancing past baseSha — an active flywheel run lands strike commits\n * on main continuously; the batch tree is still perfectly testable. Base\n * movement makes liveSignatureMatches() fail, so a FRESH superset assembles\n * and supersedes this one when ready; promotion safety against a moved base\n * is enforced at promote time (member-file overlap check + clean merge).\n * - held-out drift (departed / branch moved / metadata missing) — held-out\n * features are not in the tree; their changes also flow through\n * liveSignatureMatches() into a fresh assembly, never destruction of a\n * testable batch.\n */\nfunction isStale(\n gen: UatGeneration,\n desiredIds: ReadonlySet<string>,\n headShas: ReadonlyMap<string, string>,\n): string | null {\n for (const member of gen.members) {\n if (!desiredIds.has(member.issueId.toUpperCase())) {\n return `${member.issueId} left the ready queue`;\n }\n const current = headShas.get(member.branch);\n if (current !== member.headSha) {\n return `${member.issueId} branch gained commits`;\n }\n }\n return null;\n}\n\nexport async function reconcileUatGenerations(\n projectRoot: string,\n deps: UatReconcilerDeps,\n options: { force?: boolean } = {},\n): Promise<ReconcileResult> {\n const log = deps.log ?? (() => {});\n const now = deps.now ?? Date.now;\n const invalidated: string[] = [];\n\n if (!deps.isEnabled()) return { action: 'disabled', invalidated };\n\n if (inFlightReconciles.has(projectRoot)) return { action: 'in-flight', invalidated };\n\n const tick = async (): Promise<ReconcileResult> => {\n const readySet = await deps.getReadySet();\n if (readySet === null) return { action: 'no-queue', invalidated };\n\n // Polyrepo supplies composite anchors in place of single SHAs; both are\n // opaque strings to everything downstream, so the comparison logic is\n // identical either way.\n const mainSha = deps.getBaseAnchor\n ? await deps.getBaseAnchor()\n : await deps.getMainHeadSha();\n const headShas = new Map<string, string>();\n for (const f of readySet) {\n const anchor = deps.getFeatureAnchor\n ? await deps.getFeatureAnchor(f).catch(() => 'unknown')\n : await deps.getBranchHeadSha(f.branch).catch(() => 'unknown');\n headShas.set(f.branch, anchor);\n }\n const desiredIds = new Set(readySet.map((f) => f.issueId.toUpperCase()));\n\n // Promoted is terminal: stamp positive merge evidence before stale checks,\n // and never include promoted rows in the live list passed to isStale.\n for (const gen of deps.store.listChain(projectRoot, ['ready', 'superseded'])) {\n if (await deps.isGenerationContainedInMain?.(gen).catch(() => false)) {\n log(`[uat-reconciler] stamping ${gen.name} promoted — branch is contained in main`);\n deps.store.update(gen.name, { status: 'promoted' });\n continue;\n }\n const staleReason = isStale(gen, desiredIds, headShas);\n if (!staleReason) continue;\n log(`[uat-reconciler] invalidating ${gen.name}: ${staleReason}`);\n deps.store.update(gen.name, { status: 'invalidated' });\n invalidated.push(gen.name);\n await deps.teardownStack(gen).catch((err) => {\n log(`[uat-reconciler] stack teardown for ${gen.name} failed: ${err instanceof Error ? err.message : String(err)}`);\n });\n }\n\n // 2. Un-wedge crashed assemblies; respect a live one. \"Stuck\" means NO\n // PROGRESS (updated_at stale) — the engine patches the row after every\n // member step, so total age is the wrong signal: a big batch grinding\n // through many timeboxed conflict resolutions is alive, just slow.\n const assembling = deps.store.listChain(projectRoot, ['assembling']);\n for (const gen of assembling) {\n const lastProgress = Date.parse(gen.updatedAt || gen.createdAt || '');\n // Unparseable timestamps: freshness can't be proven — treat as stuck.\n const sinceProgress = Number.isFinite(lastProgress) ? now() - lastProgress : Number.POSITIVE_INFINITY;\n if (sinceProgress > STUCK_ASSEMBLING_MS) {\n log(`[uat-reconciler] marking stuck assembling generation ${gen.name} failed (no progress for ${Math.round(sinceProgress / 60000)}m)`);\n deps.store.update(gen.name, { status: 'failed' });\n }\n }\n if (!options.force && deps.store.listChain(projectRoot, ['assembling']).length > 0) {\n return { action: 'in-flight', invalidated };\n }\n\n if (readySet.length === 0) {\n await deps.cleanup().catch(() => {});\n return { action: 'idle', invalidated };\n }\n\n // 3. Assemble when nothing live answers the current desired set.\n const desired = desiredSignature(readySet, headShas, mainSha);\n if (!options.force) {\n const live = deps.store.listChain(projectRoot, ['ready', 'superseded']);\n if (live.some((gen) => liveSignatureMatches(gen, readySet, headShas, mainSha))) {\n await deps.cleanup().catch(() => {});\n return { action: 'idle', invalidated };\n }\n const failed = deps.store.listChain(projectRoot, ['failed']);\n const recentFailure = failed.find((gen) =>\n generationSignature(gen) === desired &&\n now() - Date.parse(gen.updatedAt || gen.createdAt || '') < FAILED_RETRY_BACKOFF_MS,\n );\n if (recentFailure) {\n log(`[uat-reconciler] backing off — ${recentFailure.name} failed recently for the same input`);\n return { action: 'backoff', invalidated };\n }\n }\n\n log(`[uat-reconciler] assembling next generation for ${readySet.length} ready feature(s)`);\n const generation = await deps.assemble(readySet);\n await deps.cleanup().catch(() => {});\n return {\n action: generation.status === 'ready' ? 'assembled' : 'assembly-failed',\n invalidated,\n generation,\n };\n };\n\n const pending = tick().finally(() => inFlightReconciles.delete(projectRoot));\n inFlightReconciles.set(projectRoot, pending);\n return pending;\n}\n","/**\n * Live UAT stack lifecycle for batch generations (PAN-1737, absorbs PAN-1738).\n *\n * Each ready generation can serve a real dashboard stack from its persistent\n * worktree: the devcontainer renders via the standard FEATURE_FOLDER template,\n * so the folder name (`uat-<label>-<codename>-<mmdd>`) yields the Traefik host\n * `uat-<label>-<codename>-<mmdd>.overdeck.localhost` with zero new infra.\n *\n * HARD INVARIANT — max 2 UAT stacks run concurrently. Docker's default\n * address pool fits ~31 bridge networks; accumulating UAT stacks would\n * eventually block ALL workspace creation (\"all predefined address pools\n * have been fully subnetted\"). Starting a third stack tears down the oldest\n * first, and invalidation/promotion always tear the generation's stack down.\n *\n * All process exec is async (server-reachable code — never execSync).\n */\nimport { existsSync } from 'fs';\nimport { join, dirname, basename } from 'path';\nimport { readFile } from 'node:fs/promises';\nimport { execFile } from 'child_process';\nimport { promisify } from 'util';\nimport { ensureDevcontainerSync } from '../workspace/ensure-devcontainer.js';\nimport {\n listUatGenerationsWithStacksSync,\n setUatGenerationStackStartedAtSync,\n type UatGeneration,\n} from '../overdeck/merge-sync.js';\nimport { findProjectByPathSync } from '../projects.js';\n\nconst execFileAsync = promisify(execFile);\n\nexport const MAX_UAT_STACKS = 2;\n\nconst COMPOSE_FILES = [\n 'docker-compose.devcontainer.yml',\n 'docker-compose.yml',\n 'docker-compose.yaml',\n 'compose.yml',\n 'compose.yaml',\n];\n\nconst stackMutationLocks = new Map<string, Promise<void>>();\n\nasync function withStackMutationLock<T>(projectRoot: string, fn: () => Promise<T>): Promise<T> {\n const previous = stackMutationLocks.get(projectRoot) ?? Promise.resolve();\n let release!: () => void;\n const current = new Promise<void>((resolve) => { release = resolve; });\n const tail = previous.catch(() => {}).then(() => current);\n stackMutationLocks.set(projectRoot, tail);\n await previous.catch(() => {});\n try {\n return await fn();\n } finally {\n release();\n if (stackMutationLocks.get(projectRoot) === tail) stackMutationLocks.delete(projectRoot);\n }\n}\n\nexport interface UatStackDeps {\n /** Render `.devcontainer/` if missing; issueId only resolves project config. */\n ensureDevcontainer(workspacePath: string, issueId: string): { ok: boolean; error?: string };\n composeUp(composeFile: string, projectName: string): Promise<void>;\n composeDown(composeFile: string, projectName: string): Promise<void>;\n /** Every container the compose project has, running or exited. */\n composePs(composeFile: string, projectName: string): Promise<ComposeServiceState[]>;\n /** Service names the compose file declares under the active profiles. */\n composeServices(composeFile: string, projectName: string): Promise<string[]>;\n /** Tail of one service's logs, for the exit reason. */\n composeServiceLogs(composeFile: string, projectName: string, service: string): Promise<string>;\n findComposeFile(workspacePath: string): string | null;\n readComposeFile(composeFile: string): Promise<string>;\n store: {\n setStack(name: string, startedAt: string | null): void;\n listWithStacks(): UatGeneration[];\n };\n log?: (msg: string) => void;\n}\n\nfunction defaultDeps(): UatStackDeps {\n const compose = (args: string[], cwd: string) =>\n execFileAsync('docker', ['compose', ...args], {\n cwd,\n encoding: 'utf-8',\n timeout: 300_000,\n maxBuffer: 10 * 1024 * 1024,\n });\n\n return {\n ensureDevcontainer: (workspacePath, issueId) => {\n const result = ensureDevcontainerSync({ workspacePath, issueId });\n return result.step.success\n ? { ok: true }\n : { ok: false, error: result.step.error ?? 'devcontainer render failed' };\n },\n composeUp: async (composeFile, projectName) => {\n await compose(['-f', composeFile, '-p', projectName, 'up', '-d', '--build'], dirname(composeFile));\n },\n composeDown: async (composeFile, projectName) => {\n await compose(['-f', composeFile, '-p', projectName, 'down', '-v', '--remove-orphans'], dirname(composeFile));\n },\n composePs: async (composeFile, projectName) => {\n const { stdout } = await compose(\n ['-f', composeFile, '-p', projectName, 'ps', '--all', '--format', 'json'],\n dirname(composeFile),\n );\n return parseComposePs(stdout);\n },\n composeServices: async (composeFile, projectName) => {\n const { stdout } = await compose(['-f', composeFile, '-p', projectName, 'config', '--services'], dirname(composeFile));\n return stdout.split('\\n').map((l) => l.trim()).filter(Boolean);\n },\n composeServiceLogs: async (composeFile, projectName, service) => {\n const { stdout } = await compose(\n ['-f', composeFile, '-p', projectName, 'logs', '--no-color', '--no-log-prefix', '--tail', '200', service],\n dirname(composeFile),\n );\n return stdout;\n },\n findComposeFile: (workspacePath) => {\n const devcontainerDir = join(workspacePath, '.devcontainer');\n for (const file of COMPOSE_FILES) {\n const fullPath = join(devcontainerDir, file);\n if (existsSync(fullPath)) return fullPath;\n }\n return null;\n },\n readComposeFile: (composeFile) => readFile(composeFile, 'utf-8'),\n store: {\n setStack: (name, startedAt) => setUatGenerationStackStartedAtSync(name, startedAt),\n listWithStacks: () => listUatGenerationsWithStacksSync(),\n },\n };\n}\n\nexport function uatStackFolderName(gen: UatGeneration): string {\n return basename(gen.worktreePath);\n}\n\nfunction composeProjectName(gen: UatGeneration): string {\n return `overdeck-${uatStackFolderName(gen)}`;\n}\n\n/**\n * The frontend URL the generation's stack serves (or will serve). Prefers the\n * Host(`…`) Traefik label in the rendered compose file; falls back to the\n * FEATURE_FOLDER convention using the project's DNS domain (PAN-1696, D8, FR-10).\n */\nexport async function uatFrontendUrl(gen: UatGeneration, deps: Partial<UatStackDeps> = {}): Promise<string> {\n const d = { ...defaultDeps(), ...deps };\n const folder = uatStackFolderName(gen);\n const composeFile = d.findComposeFile(gen.worktreePath);\n if (composeFile) {\n try {\n const content = await d.readComposeFile(composeFile);\n const hostMatch = content.match(new RegExp('Host\\\\(`(' + folder.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&') + '\\\\.[^`]+)`\\\\)'));\n if (hostMatch?.[1]) return `https://${hostMatch[1]}`;\n } catch { /* fall through to convention */ }\n }\n // Resolve project config to get DNS domain (PAN-1696). Keep the fallback\n // neutral so a missing project domain cannot fabricate a branded dead link.\n const project = findProjectByPathSync(gen.projectRoot);\n const domain = project?.workspace?.dns?.domain ?? 'localhost';\n return `https://${folder}.${domain}`;\n}\n\nexport type UatStackStatus = 'running' | 'degraded' | 'unknown' | 'absent';\n\nexport interface UatStackProbe {\n status: UatStackStatus;\n frontendUrl: string;\n /** Declared services that are not serving — exited, missing, or unhealthy. */\n downServices?: string[];\n /** service → last error line from its logs, best-effort (degraded only). */\n serviceErrors?: Record<string, string>;\n /** Why the probe could not tell (unknown only). */\n probeError?: string;\n}\n\n/**\n * Probe a generation's stack. Self-corrects stale stackStartedAt records.\n *\n * PAN-3166: counting running containers is not a health signal. A stack whose\n * api container died at startup still has three healthy containers, so a count\n * reports `running` and the UI offers \"Open UAT frontend\" for a gateway\n * timeout. The probe compares what the compose file DECLARES against what is\n * actually up, and pulls the last error line from anything that is down.\n *\n * The four outcomes are deliberately distinct, because `absent` is the only one\n * that CLEARS the stack record — and clearing it destroys the evidence:\n *\n * - **zero containers** → `absent`, and the stale record self-heals. Nothing\n * ran, so there is nothing to explain.\n * - **containers present, something down or unhealthy** → `degraded`. The\n * record is preserved so the exited service's logs stay reachable; this is\n * the path that used to be misreported as `running` (api dead) and, once the\n * containers finally all stopped, silently self-healed to `absent` — which is\n * why the Flyway error was unreachable from the UI at all.\n * - **the probe itself failed** → `unknown`, record preserved. A failed\n * `docker compose ps` is not proof of absence, and must never clear the row.\n */\nexport async function probeUatStack(gen: UatGeneration, deps: Partial<UatStackDeps> = {}): Promise<UatStackProbe> {\n const d = { ...defaultDeps(), ...deps };\n const frontendUrl = await uatFrontendUrl(gen, d);\n const composeFile = d.findComposeFile(gen.worktreePath);\n if (!composeFile || !gen.stackStartedAt) return { status: 'absent', frontendUrl };\n\n const project = composeProjectName(gen);\n let containers: ComposeServiceState[];\n try {\n containers = await d.composePs(composeFile, project);\n } catch (err) {\n const probeError = err instanceof Error ? (err.message.split('\\n')[0] ?? 'compose ps failed') : String(err);\n d.log?.(`[uat-stack] ${gen.name}: probe failed (${probeError}) — leaving the stack record intact`);\n return { status: 'unknown', frontendUrl, probeError };\n }\n\n if (containers.length === 0) {\n // Nothing was ever created for this project — the record is stale, heal it.\n try { d.store.setStack(gen.name, null); } catch { /* row may be gone */ }\n return { status: 'absent', frontendUrl };\n }\n\n let declared: string[];\n try {\n declared = await d.composeServices(composeFile, project);\n } catch (err) {\n const probeError = err instanceof Error ? (err.message.split('\\n')[0] ?? 'compose config failed') : String(err);\n d.log?.(`[uat-stack] ${gen.name}: could not read declared services (${probeError})`);\n return { status: 'unknown', frontendUrl, probeError };\n }\n\n const down = downServices(declared, containers);\n if (down.length === 0) return { status: 'running', frontendUrl };\n\n const serviceErrors: Record<string, string> = {};\n for (const service of down) {\n const logs = await d.composeServiceLogs(composeFile, project, service).catch(() => '');\n const line = lastErrorLine(logs);\n if (line) serviceErrors[service] = line;\n }\n d.log?.(`[uat-stack] ${gen.name}: degraded — ${down.join(', ')} not serving`);\n return {\n status: 'degraded',\n frontendUrl,\n downServices: down,\n ...(Object.keys(serviceErrors).length > 0 ? { serviceErrors } : {}),\n };\n}\n\n/** One compose service's container state, as `docker compose ps --all` reports it. */\nexport interface ComposeServiceState {\n service: string;\n running: boolean;\n /** Human status, e.g. `Exited (0) 7 minutes ago`. */\n status: string;\n exitCode: number | null;\n /** Compose healthcheck verdict when the service declares one. */\n health?: string;\n}\n\ninterface ComposePsJson {\n Service?: string;\n State?: string;\n Status?: string;\n ExitCode?: number;\n Health?: string;\n}\n\n/**\n * `docker compose ps --format json` emits either one JSON array or one object\n * per line, depending on the Compose version — parse both.\n */\nexport function parseComposePs(stdout: string): ComposeServiceState[] {\n const rows: ComposePsJson[] = [];\n const trimmed = stdout.trim();\n if (!trimmed) return [];\n if (trimmed.startsWith('[')) {\n try { rows.push(...(JSON.parse(trimmed) as ComposePsJson[])); } catch { /* fall through to per-line */ }\n }\n if (rows.length === 0) {\n for (const line of trimmed.split('\\n')) {\n if (!line.trim().startsWith('{')) continue;\n try { rows.push(JSON.parse(line) as ComposePsJson); } catch { /* skip malformed row */ }\n }\n }\n return rows\n .filter((row): row is ComposePsJson & { Service: string } => Boolean(row.Service))\n .map((row) => ({\n service: row.Service,\n running: (row.State ?? '').toLowerCase() === 'running',\n status: row.Status ?? row.State ?? '',\n exitCode: typeof row.ExitCode === 'number' ? row.ExitCode : null,\n // Compose reports Health only for services declaring a healthcheck; some\n // versions fold it into Status as `Up 2 minutes (unhealthy)` instead.\n ...(row.Health\n ? { health: row.Health.toLowerCase() }\n : /\\(unhealthy\\)/i.test(row.Status ?? '')\n ? { health: 'unhealthy' }\n : {}),\n }));\n}\n\n/**\n * Init containers exit by design — the same convention the workspace stack\n * health check uses (`src/lib/workspace/stack-health.ts`). Everything else the\n * compose file declares is expected to stay up.\n */\nfunction isOneShotService(service: string): boolean {\n return /(^|[-_])(?:init|init-perms|test-unit)($|[-_])/.test(service.toLowerCase());\n}\n\n/**\n * Declared services that are not serving — the honest reading of \"is this\n * stack usable?\". Three ways to fail:\n *\n * - no container at all, or one that is not running;\n * - running, but Compose reports its healthcheck `unhealthy` (a crash-looping\n * or wedged api is as unusable as a dead one);\n * - a one-shot that exited non-zero — a failed init means whatever depended on\n * it never came up either. A one-shot exiting cleanly is by design.\n */\nexport function downServices(\n declared: readonly string[],\n containers: readonly ComposeServiceState[],\n): string[] {\n const byService = new Map(containers.map((c) => [c.service, c]));\n const down: string[] = [];\n for (const service of declared) {\n const container = byService.get(service);\n if (isOneShotService(service)) {\n if (container && !container.running && (container.exitCode ?? 0) !== 0) down.push(service);\n continue;\n }\n if (!container || !container.running || container.health === 'unhealthy') down.push(service);\n }\n return down;\n}\n\nconst ERROR_LINE = /(caused by:|exception|error|fatal|panic:|\\bfailed\\b)/i;\n\n/** The most informative line from a dead service's log tail. */\nexport function lastErrorLine(logs: string): string | null {\n const lines = logs.split('\\n').map((l) => l.trimEnd()).filter((l) => l.trim().length > 0);\n if (lines.length === 0) return null;\n // `Caused by:` is the root cause in a JVM stack trace and is what a Flyway\n // version collision surfaces as — prefer it over the generic wrapper above it.\n const caused = [...lines].reverse().find((l) => /caused by:/i.test(l));\n const errorish = caused ?? [...lines].reverse().find((l) => ERROR_LINE.test(l));\n const line = errorish ?? lines[lines.length - 1]!;\n return line.trim().slice(0, 300);\n}\n\nexport interface EnsureUatStackResult {\n success: boolean;\n error?: string;\n frontendUrl?: string;\n /** Generations whose stacks were torn down to respect MAX_UAT_STACKS. */\n evicted: string[];\n}\n\n/**\n * Bring up the live stack for a generation (idempotent — compose up on a\n * running project is a no-op). Enforces MAX_UAT_STACKS by tearing down the\n * oldest running UAT stack(s) first.\n */\nexport async function ensureUatStack(gen: UatGeneration, deps: Partial<UatStackDeps> = {}): Promise<EnsureUatStackResult> {\n return withStackMutationLock(gen.projectRoot, async () => {\n const d = { ...defaultDeps(), ...deps };\n const log = d.log ?? (() => {});\n const evicted: string[] = [];\n\n const member = gen.members[0];\n if (!member) return { success: false, error: 'generation has no members', evicted };\n\n // Cap enforcement happens inside the lock and re-reads the live stack set so\n // concurrent start requests cannot both observe the same stale pre-start set.\n const others = d.store.listWithStacks().filter((g) => g.name !== gen.name);\n while (others.length > 0 && others.length >= MAX_UAT_STACKS) {\n const oldest = others.shift()!;\n log(`[uat-stack] cap ${MAX_UAT_STACKS} reached — tearing down oldest stack ${oldest.name}`);\n try {\n await teardownUatStackUnlocked(oldest, d);\n } catch (err) {\n return {\n success: false,\n error: err instanceof Error ? (err.message.split('\\n')[0] ?? 'stack teardown failed') : String(err),\n evicted,\n };\n }\n evicted.push(oldest.name);\n }\n\n const rendered = d.ensureDevcontainer(gen.worktreePath, member.issueId);\n if (!rendered.ok) return { success: false, error: rendered.error ?? 'devcontainer render failed', evicted };\n\n const composeFile = d.findComposeFile(gen.worktreePath);\n if (!composeFile) {\n return { success: false, error: `no compose file under ${gen.worktreePath}/.devcontainer`, evicted };\n }\n\n try {\n await d.composeUp(composeFile, composeProjectName(gen));\n } catch (err) {\n return {\n success: false,\n error: err instanceof Error ? (err.message.split('\\n')[0] ?? 'compose up failed') : String(err),\n evicted,\n };\n }\n\n d.store.setStack(gen.name, new Date().toISOString());\n const frontendUrl = await uatFrontendUrl(gen, d);\n log(`[uat-stack] ${gen.name}: stack up at ${frontendUrl}`);\n return { success: true, frontendUrl, evicted };\n });\n}\n\nasync function teardownUatStackUnlocked(gen: UatGeneration, d: UatStackDeps): Promise<void> {\n const composeFile = d.findComposeFile(gen.worktreePath);\n if (composeFile) {\n try {\n await d.composeDown(composeFile, composeProjectName(gen));\n } catch (err) {\n d.log?.(`[uat-stack] ${gen.name}: compose down failed: ${err instanceof Error ? err.message.split('\\n')[0] : String(err)}`);\n throw err;\n }\n }\n try { d.store.setStack(gen.name, null); } catch { /* row may be gone */ }\n}\n\n/**\n * Tear the generation's stack down (idempotent) and clear its stack record.\n * MUST be called on invalidation, promotion, and generation cleanup —\n * orphaned uat networks eventually block all workspace creation.\n */\nexport async function teardownUatStack(gen: UatGeneration, deps: Partial<UatStackDeps> = {}): Promise<void> {\n await withStackMutationLock(gen.projectRoot, async () => {\n const d = { ...defaultDeps(), ...deps };\n await teardownUatStackUnlocked(gen, d);\n });\n}\n","/**\n * Batch promotion (PAN-1737: UAT batch trains) — \"merge what you tested\".\n *\n * Promoting a generation merges the uat/* branch itself into main (one no-ff\n * merge), so main receives EXACTLY the tree the operator exercised — including\n * the assembly agent's conflict resolutions. GitHub automatically marks each\n * feature PR merged because its head becomes reachable from main, and every\n * member issue then flows through the standard per-issue post-merge lifecycle\n * exactly once, behind the PAN-328\n * in-flight guard.\n *\n * Hard precondition: the generation's baseSha must still equal origin/main —\n * promoting a stale tree would silently drop commits that landed since\n * assembly. Stale promotes are rejected with reassemble guidance (the\n * reconciler rebuilds automatically anyway).\n *\n * Pure orchestration with injected deps; real git in buildUatPromoteGitDeps.\n */\nimport { join } from 'path';\nimport { tmpdir } from 'os';\nimport { promisify } from 'util';\nimport { execFile } from 'child_process';\nimport type { UatGeneration, UatGenerationRepo, UatGenerationStatus } from '../overdeck/merge-sync.js';\nimport type { VerifiedMergedRepo } from './merge-verification.js';\nimport type { GenerationStorePort } from './uat-generation-engine.js';\n\nconst execFileAsync = promisify(execFile);\n\nfunction safeUatBranchName(branchName: string): string {\n if (!/^uat\\/[A-Za-z0-9][A-Za-z0-9._-]*$/.test(branchName)) {\n throw new Error(`unsafe UAT branch name: ${branchName}`);\n }\n return branchName;\n}\n\nexport interface UatPromoteGitDeps {\n /** `git fetch origin main` and return the origin/main head SHA. */\n fetchMain(): Promise<string>;\n /**\n * Merge the generation branch into main with the given message (no-ff, in a\n * throwaway worktree — NEVER the primary checkout) and push. Returns the\n * merge commit SHA.\n */\n mergeIntoMain(branchName: string, message: string): Promise<string>;\n /** Files changed on origin/main since the given SHA (base-movement footprint). */\n changedFilesSince(baseSha: string): Promise<string[]>;\n /** Files the batch branch changes vs its merge-base with origin/main. */\n batchChangedFiles(branchName: string): Promise<string[]>;\n}\n\n/**\n * A merge prepared in a throwaway worktree but NOT pushed. Phase A produces one\n * per repo; phase B publishes them. The opaque `handle` is whatever the git\n * implementation needs to find the prepared tree again (a worktree path in the\n * real wiring).\n */\nexport interface PreparedRepoMerge {\n repoKey: string;\n handle: string;\n mergeSha: string;\n}\n\n/** Per-repo promote operations for one member repo of a polyrepo project. */\nexport interface PolyrepoRepoPromoteGit {\n /** Fetch the repo's target branch and return its head SHA. */\n targetHeadSha(): Promise<string>;\n /** Files changed on the target branch since the given SHA. */\n changedFilesSince(baseSha: string): Promise<string[]>;\n /** Files this repo's uat branch changes vs its merge-base with the target. */\n batchChangedFiles(branchName: string): Promise<string[]>;\n /** Merge the uat branch into the target locally. MUST NOT push. */\n trialMerge(branchName: string, message: string): Promise<PreparedRepoMerge>;\n /** Push an already-prepared merge to the target branch. */\n publishPrepared(prepared: PreparedRepoMerge): Promise<void>;\n /** Drop a prepared merge's worktree. Must be safe to call twice. */\n discardPrepared(prepared: PreparedRepoMerge): Promise<void>;\n /**\n * If this repo's uat branch is ALREADY contained in its target branch, the\n * merge commit that brought it in; otherwise null.\n *\n * Publishing and stamping are two operations against two systems. A process\n * death, a thrown state write, or a dropped connection after the remote\n * accepted the push leaves a repo genuinely landed with `promoted_at` null.\n * Retry would then re-classify it as pending and — because the target has\n * moved over the batch's own files — reject the whole promote as stale-base,\n * wedging a half-landed batch permanently. This lets recovery prove the truth\n * from git instead of trusting local state.\n */\n findLandedMerge(branchName: string): Promise<string | null>;\n}\n\nexport interface UatPromoteDeps {\n git: UatPromoteGitDeps;\n /**\n * Per-repo git for a polyrepo project, keyed by repoKey. Its PRESENCE — not\n * a repo count — selects the two-phase path: a polyrepo project with a single\n * contributing repo still has no git repo at its own project root, so the\n * monorepo path would run git against a non-repo and fail.\n */\n polyrepoGit?: ReadonlyMap<string, PolyrepoRepoPromoteGit>;\n /**\n * Record that one repo's merge landed, so a retry after a partial publish\n * skips it. Required whenever polyrepoGit is supplied.\n */\n markRepoPromoted?: (name: string, repoKey: string, promotedAt: string, mergeSha?: string) => void;\n now?: () => Date;\n store: GenerationStorePort & { get(name: string): UatGeneration | null };\n teardownStack(generation: UatGeneration): Promise<void>;\n /**\n * Kick off the per-issue post-merge lifecycle (through the PAN-328 guard).\n * Returns false when a run for that issue is already in flight.\n */\n firePostMerge(\n issueId: string,\n options?: {\n sourceBranch?: string;\n verifiedMergedRef?: string;\n /** Per-repo merge evidence for a polyrepo batch (PAN-3093). */\n verifiedMergedRepos?: readonly VerifiedMergedRepo[];\n },\n ): boolean;\n /**\n * Authoritative per-member merge eligibility (PAN-1759), checked at promote\n * time as defense-in-depth: batch membership is computed from orchestrator\n * verbs at assembly time, and a member's pipeline state can change (or have\n * been wrong) between assembly and the operator clicking Merge. RUN-20\n * assembled a batch containing a mid-review issue; this gate refuses to land\n * it on main.\n */\n memberEligibility(issueId: string): { eligible: boolean; reason?: string };\n recordVerification?: (generation: UatGeneration, mergeSha: string) => void;\n runShip?: (\n generation: UatGeneration,\n shipVersion: string | undefined,\n promotedRepos: readonly UatGenerationRepo[],\n ) => Promise<void>;\n log?: (msg: string) => void;\n}\n\nexport type PromoteFailureReason = 'not-found' | 'wrong-status' | 'member-not-ready' | 'stale-base' | 'merge-failed';\n\nexport type PromoteResult =\n | {\n success: true;\n generation: string;\n mergeSha: string;\n members: string[];\n /** Members whose post-merge lifecycle started (false = already in flight). */\n postMergeStarted: string[];\n invalidated: string[];\n }\n | { success: false; reason: PromoteFailureReason; message: string };\n\nconst PROMOTABLE: readonly UatGenerationStatus[] = ['ready', 'superseded'];\n\nexport function buildPromoteMergeMessage(gen: UatGeneration): string {\n const ids = gen.members.map((m) => m.issueId).join(', ');\n const resolutionNote = gen.resolutions.length > 0\n ? `\\n\\nIncludes ${gen.resolutions.length} uat-assembly conflict resolution(s): ` +\n gen.resolutions.map((r) => r.issueIds.join(' <-> ')).join('; ')\n : '';\n return `Merge UAT batch ${gen.name} (${ids})${resolutionNote}`;\n}\n\nexport async function promoteUatGeneration(\n name: string,\n projectRoot: string,\n deps: UatPromoteDeps,\n options: { shipVersion?: string } = {},\n): Promise<PromoteResult> {\n const log = deps.log ?? (() => {});\n\n const gen = deps.store.get(name);\n if (!gen || gen.projectRoot !== projectRoot) {\n return { success: false, reason: 'not-found', message: `No UAT generation named ${name}` };\n }\n if (!PROMOTABLE.includes(gen.status)) {\n return {\n success: false,\n reason: 'wrong-status',\n message: `${name} is ${gen.status} — only a ready or superseded batch can be merged to main`,\n };\n }\n\n const notReady = gen.members\n .map((m) => ({ issueId: m.issueId, gate: deps.memberEligibility(m.issueId) }))\n .filter(({ gate }) => !gate.eligible);\n if (notReady.length > 0) {\n const detail = notReady.map(({ issueId, gate }) => `${issueId} (${gate.reason ?? 'not eligible'})`).join(', ');\n return {\n success: false,\n reason: 'member-not-ready',\n message:\n `${name} contains member(s) the pipeline has not cleared to merge: ${detail} — ` +\n `wait for them to pass review+test, or for the reconciler to rebuild the batch without them.`,\n };\n }\n\n if (deps.polyrepoGit) {\n return promotePolyrepo(gen, projectRoot, deps, deps.polyrepoGit, log, options.shipVersion);\n }\n\n const mainSha = await deps.git.fetchMain();\n if (gen.baseSha !== mainSha) {\n // Main moved since assembly. An active flywheel run lands commits on main\n // continuously, so exact base equality would make ready batches almost\n // never promotable (first live run, 2026-06-10). Safety is preserved by a\n // narrower check: reject only when main's NEW commits touch files the\n // batch also touches — then the tested behavior genuinely may not match\n // what lands. Disjoint movement (docs, unrelated fixes) proceeds; the\n // no-ff merge itself still hard-fails on any textual conflict.\n const [mainChanged, batchChanged] = await Promise.all([\n deps.git.changedFilesSince(gen.baseSha),\n deps.git.batchChangedFiles(gen.name),\n ]);\n const batchSet = new Set(batchChanged);\n const overlap = mainChanged.filter((f) => batchSet.has(f));\n if (overlap.length > 0) {\n return {\n success: false,\n reason: 'stale-base',\n message:\n `${name} was assembled off ${gen.baseSha.slice(0, 9)} but main is now at ${mainSha.slice(0, 9)}, ` +\n `and the new commits touch ${overlap.length} file(s) this batch also changes ` +\n `(${overlap.slice(0, 3).join(', ')}${overlap.length > 3 ? ', …' : ''}) — ` +\n `the tree you tested may not match what would land. A fresh batch reassembles automatically; re-test before merging.`,\n };\n }\n log(`[uat-promote] ${name}: base moved ${gen.baseSha.slice(0, 9)} → ${mainSha.slice(0, 9)} with no member-file overlap — proceeding`);\n }\n\n let mergeSha: string;\n try {\n mergeSha = await deps.git.mergeIntoMain(gen.name, buildPromoteMergeMessage(gen));\n } catch (err) {\n return {\n success: false,\n reason: 'merge-failed',\n message: err instanceof Error ? (err.message.split('\\n')[0] ?? 'merge failed') : String(err),\n };\n }\n log(`[uat-promote] ${name}: merged to main at ${mergeSha.slice(0, 9)} (${gen.members.length} member(s))`);\n\n // The batch is on main: this generation is done, every other live\n // generation is stale by definition (main moved).\n return finishPromote(gen, projectRoot, deps, mergeSha, log, false, options.shipVersion);\n}\n\n/**\n * Composite merge reference for a polyrepo generation, rebuilt from the stored\n * per-repo merge SHAs rather than an in-memory list, so it survives a restart\n * between the last publish and finalization.\n */\n/**\n * A merge that is live on its remote but has no durable record of it.\n *\n * Not rolled back — main is never rewound — and deliberately NOT finalized:\n * marking the generation promoted here would fire post-merge lifecycles against\n * `uat_generation_repos` rows that still read pending, and would end the\n * generation's promotable life, so the retry that is supposed to recover the\n * stamp could never run. Staying promotable is what makes recovery possible.\n */\nfunction unstampableFailure(\n gen: UatGeneration,\n unstampable: ReadonlyArray<{ repoKey: string; mergeSha: string; error: unknown }>,\n log: (msg: string) => void,\n): PromoteResult {\n const detail = unstampable\n .map((u) => `${u.repoKey}@${u.mergeSha.slice(0, 9)} (${u.error instanceof Error ? u.error.message.split('\\n')[0] : String(u.error)})`)\n .join('; ');\n log(`[uat-promote] ${gen.name}: merges are live but unrecorded — not finalizing: ${detail}`);\n return {\n success: false,\n reason: 'merge-failed',\n message:\n `${gen.name}: ${unstampable.length} repo merge(s) are LIVE on their target but could not be recorded: ${detail}. ` +\n `Nothing is rolled back — a landed merge is never rewound. The batch stays promotable: fix the state store and ` +\n `retry, and the already-landed repos are detected and skipped rather than republished.`,\n };\n}\n\n/**\n * A repo row that claims a promote stamp but has no merge SHA and no merge\n * findable on its target.\n *\n * Deliberately non-terminal. `promotedAt` on its own is enough to keep the row\n * out of the pending set, so letting it through would finalize a generation\n * that can never produce complete per-repo merge evidence: post-merge\n * verification would refuse every member forever, while the terminal `promoted`\n * status would block the retry that is the only way to recover the missing SHA.\n * Failing here keeps the batch promotable so a later attempt — once the remote\n * is reachable, or the row is repaired — can finish it properly.\n */\nfunction unprovenStampFailure(\n gen: UatGeneration,\n unproven: ReadonlyArray<{ repoKey: string; why: string }>,\n log: (msg: string) => void,\n): PromoteResult {\n const detail = unproven.map((u) => `${u.repoKey} (${u.why})`).join('; ');\n log(`[uat-promote] ${gen.name}: stamped repo(s) cannot be proven merged — not finalizing: ${detail}`);\n return {\n success: false,\n reason: 'merge-failed',\n message:\n `${gen.name}: ${unproven.length} repo(s) are recorded as promoted but their merge cannot be proven: ${detail}. ` +\n `Nothing was published on this attempt. A batch is only finalized once every repo carries both a promote ` +\n `timestamp and the merge commit it landed, because post-merge verification checks each repo's merge against ` +\n `its own target branch and refuses on missing evidence. The batch stays promotable: retry once the remote is ` +\n `reachable, or correct the repo row, and repos that genuinely landed are detected and skipped rather than ` +\n `republished.`,\n };\n}\n\n/** The generation with its repo rows replaced by recovered/updated ones. */\nfunction withRepos(gen: UatGeneration, repos: readonly UatGenerationRepo[]): UatGeneration {\n return { ...gen, repos: [...repos] };\n}\n\nfunction landedMergeRef(repos: readonly UatGenerationRepo[]): string {\n return [...repos]\n .sort((a, b) => a.mergeOrder - b.mergeOrder)\n .map((r) => `${r.repoKey}@${(r.mergeSha ?? 'landed').slice(0, 7)}`)\n .join(' ');\n}\n\n/**\n * Two-phase polyrepo promote.\n *\n * Phase A validates and trial-merges EVERY repo without pushing anything, so\n * the all-or-nothing decision is made while nothing is published: a failure in\n * repo 3 of 3 leaves repos 1 and 2 untouched on their remotes.\n *\n * Phase B publishes the prepared merges in merge order. A failure partway is\n * NOT rolled back — undoing a landed merge means force-pushing a member repo's\n * main, a one-way door. It is instead resumable: each landed repo is stamped\n * with promoted_at, the result names landed vs pending, the generation stays\n * promotable, and a retry skips whatever already landed.\n */\nasync function promotePolyrepo(\n gen: UatGeneration,\n projectRoot: string,\n deps: UatPromoteDeps,\n repoGit: ReadonlyMap<string, PolyrepoRepoPromoteGit>,\n log: (msg: string) => void,\n shipVersion?: string,\n): Promise<PromoteResult> {\n const allRepos = [...(gen.repos ?? [])].sort((a, b) => a.mergeOrder - b.mergeOrder);\n if (allRepos.length === 0) {\n return { success: false, reason: 'merge-failed', message: `${gen.name} records no member repos to promote` };\n }\n\n const now = deps.now ?? (() => new Date());\n /** Repos whose merge is live on the remote but whose durable stamp failed. */\n const unstampable: Array<{ repoKey: string; mergeSha: string; error: unknown }> = [];\n /** Repos that claim a promote stamp but can produce no merge SHA to prove it. */\n const unproven: Array<{ repoKey: string; why: string }> = [];\n\n // Reconcile local state against the remotes BEFORE classifying anything as\n // pending. A repo whose push succeeded but whose stamp never landed is\n // genuinely published; treating it as pending would re-trial-merge it and\n // then reject the promote as stale-base, because the target has moved over\n // this batch's own files.\n const recovered: UatGenerationRepo[] = [];\n for (const repo of allRepos) {\n // A stamped row with no merge SHA still needs remote recovery: evidence is\n // all-or-nothing, so one such row withholds it for the whole generation and\n // post-merge verification refuses forever. Recovering the SHA is the only\n // way out, and it is exactly what findLandedMerge answers.\n if (repo.promotedAt && repo.mergeSha) { recovered.push(repo); continue; }\n const git = repoGit.get(repo.repoKey);\n if (!git) {\n // An unstamped row without deps is simply pending, and the `missing` check\n // below rejects it by name. A STAMPED one is the dangerous case: the stamp\n // alone keeps it out of `pending`, so with no way to look up its merge it\n // would sail through to finalization unproven.\n if (repo.promotedAt) {\n unproven.push({ repoKey: repo.repoKey, why: 'there are no promote git deps for it, so its target cannot be inspected' });\n }\n recovered.push(repo);\n continue;\n }\n\n let landedSha: string | null = null;\n try {\n landedSha = await git.findLandedMerge(repo.branch);\n } catch (err) {\n // Unknown is not \"pending\". Treating it as pending would trial-merge and\n // possibly republish a merge that is already live.\n return {\n success: false,\n reason: 'merge-failed',\n message:\n `${gen.name}: could not establish whether ${repo.repoKey} has already landed ` +\n `(${err instanceof Error ? err.message.split('\\n')[0] : String(err)}). ` +\n `Nothing was published; retry once the remote is reachable.`,\n };\n }\n if (!landedSha) {\n // No stamp and no landed merge is the ordinary pending case. A stamp with\n // no merge SHA and no merge on the target is a row that CLAIMS to have\n // been promoted and cannot back it up — the probe just came back empty, so\n // there is no evidence left to find on this attempt.\n if (repo.promotedAt) {\n unproven.push({\n repoKey: repo.repoKey,\n why: `it is stamped promoted at ${repo.promotedAt} with no recorded merge sha, and ${repo.branch} is not contained in ${repo.targetBranch ?? 'main'}`,\n });\n }\n recovered.push(repo);\n continue;\n }\n\n log(\n `[uat-promote] ${gen.name}: ${repo.repoKey} is contained in its target (${landedSha.slice(0, 9)}) ` +\n `${repo.promotedAt ? 'but has no recorded merge sha' : 'but was never stamped'} — recovering`,\n );\n const promotedAt = repo.promotedAt ?? now().toISOString();\n try {\n deps.markRepoPromoted?.(gen.name, repo.repoKey, promotedAt, landedSha);\n } catch (err) {\n // Do NOT fabricate promoted state in memory. The canonical row still says\n // pending, so finalizing here would mark the generation terminal, fire\n // post-merge, and destroy the very state the \"next attempt recovers\" story\n // depends on — there would be no next attempt.\n unstampable.push({ repoKey: repo.repoKey, mergeSha: landedSha, error: err });\n log(`[uat-promote] ${gen.name}: recovery stamp failed for ${repo.repoKey}: ${err instanceof Error ? err.message : String(err)}`);\n recovered.push(repo);\n continue;\n }\n recovered.push({ ...repo, promotedAt, mergeSha: landedSha });\n }\n\n if (unstampable.length > 0) {\n return unstampableFailure(gen, unstampable, log);\n }\n if (unproven.length > 0) {\n return unprovenStampFailure(gen, unproven, log);\n }\n\n const alreadyLanded = recovered.filter((r) => r.promotedAt);\n const pending = recovered.filter((r) => !r.promotedAt);\n if (alreadyLanded.length > 0) {\n log(`[uat-promote] ${gen.name}: resuming — ${alreadyLanded.map((r) => r.repoKey).join(', ')} already landed`);\n }\n if (pending.length === 0) {\n // Every repo already landed. This is the crash window between the last\n // publish stamp and finalization, and retry is the documented recovery —\n // so it is the idempotent completion case, not an error. Rejecting it\n // would leave a fully-published batch permanently unfinalizable: never\n // promoted, no verdicts recorded, no stack teardown, no post-merge.\n log(`[uat-promote] ${gen.name}: every repo already published — finalizing`);\n return finishPromote(\n withRepos(gen, recovered),\n projectRoot,\n deps,\n landedMergeRef(recovered),\n log,\n true,\n shipVersion,\n );\n }\n\n const missing = pending.filter((r) => !repoGit.has(r.repoKey));\n if (missing.length > 0) {\n return {\n success: false,\n reason: 'merge-failed',\n message: `${gen.name}: no promote git deps for repo(s) ${missing.map((r) => r.repoKey).join(', ')}`,\n };\n }\n\n const message = buildPromoteMergeMessage(gen);\n const prepared: PreparedRepoMerge[] = [];\n const discardAll = async () => {\n for (const p of prepared) {\n await repoGit.get(p.repoKey)!.discardPrepared(p).catch((err) => {\n log(`[uat-promote] ${gen.name}: discarding ${p.repoKey} trial merge failed: ${err instanceof Error ? err.message : String(err)}`);\n });\n }\n };\n\n // ── Phase A: validate + trial-merge everything, push nothing ──────────────\n for (const repo of pending) {\n const git = repoGit.get(repo.repoKey)!;\n\n let targetHead: string;\n try {\n targetHead = await git.targetHeadSha();\n } catch (err) {\n await discardAll();\n return {\n success: false,\n reason: 'merge-failed',\n message: `${gen.name}: could not read ${repo.repoKey} target head: ${err instanceof Error ? err.message : String(err)}`,\n };\n }\n\n if (repo.baseSha !== targetHead) {\n // Same disjoint-movement rule as the monorepo path, per repo: reject only\n // when this repo's target gained commits touching files this repo's uat\n // branch also changes.\n const [targetChanged, batchChanged] = await Promise.all([\n git.changedFilesSince(repo.baseSha),\n git.batchChangedFiles(repo.branch),\n ]);\n const batchSet = new Set(batchChanged);\n const overlap = targetChanged.filter((f) => batchSet.has(f));\n if (overlap.length > 0) {\n await discardAll();\n return {\n success: false,\n reason: 'stale-base',\n message:\n `${gen.name} was assembled off ${repo.repoKey}@${repo.baseSha.slice(0, 9)} but that repo is now at ` +\n `${targetHead.slice(0, 9)}, and the new commits touch ${overlap.length} file(s) this batch also changes ` +\n `(${overlap.slice(0, 3).join(', ')}${overlap.length > 3 ? ', …' : ''}) — ` +\n `the tree you tested may not match what would land. Nothing was published. A fresh batch reassembles automatically; re-test before merging.`,\n };\n }\n log(`[uat-promote] ${gen.name}: ${repo.repoKey} base moved ${repo.baseSha.slice(0, 9)} → ${targetHead.slice(0, 9)} with no member-file overlap — proceeding`);\n }\n\n try {\n prepared.push(await git.trialMerge(repo.branch, message));\n } catch (err) {\n await discardAll();\n return {\n success: false,\n reason: 'merge-failed',\n message:\n `${gen.name}: trial merge failed in ${repo.repoKey} — ` +\n `${err instanceof Error ? (err.message.split('\\n')[0] ?? 'merge failed') : String(err)}. ` +\n `Nothing was published; the batch is unchanged and still promotable.`,\n };\n }\n }\n\n log(`[uat-promote] ${gen.name}: all ${prepared.length} repo(s) trial-merged cleanly — publishing`);\n\n // ── Phase B: publish; a failure here is resumable, never rolled back ──────\n const landed: Array<{ repoKey: string; mergeSha: string }> = [];\n for (const p of prepared) {\n try {\n await repoGit.get(p.repoKey)!.publishPrepared(p);\n } catch (err) {\n const stillPending = prepared.slice(prepared.indexOf(p)).map((x) => x.repoKey);\n await discardAll();\n return {\n success: false,\n reason: 'merge-failed',\n message:\n `${gen.name}: published ${landed.length > 0 ? landed.map((l) => l.repoKey).join(', ') : 'no repos'} ` +\n `but failed on ${p.repoKey} (${err instanceof Error ? (err.message.split('\\n')[0] ?? 'push failed') : String(err)}). ` +\n `Still pending: ${stillPending.join(', ')}. Nothing is rolled back — main is never rewound — ` +\n `retry the merge and the repos that already landed are skipped.`,\n };\n }\n landed.push({ repoKey: p.repoKey, mergeSha: p.mergeSha });\n try {\n deps.markRepoPromoted?.(gen.name, p.repoKey, now().toISOString(), p.mergeSha);\n } catch (err) {\n // The push succeeded, so the merge is live and must never be rolled back —\n // but without the durable stamp the canonical row still says pending.\n // Finalizing anyway would mark the generation terminal on state that does\n // not exist, so stop short of finalizing and stay promotable.\n unstampable.push({ repoKey: p.repoKey, mergeSha: p.mergeSha, error: err });\n log(`[uat-promote] ${gen.name}: publish stamp failed for ${p.repoKey} (merge IS live at ${p.mergeSha.slice(0, 9)}): ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n if (unstampable.length > 0) {\n await discardAll();\n return unstampableFailure(gen, unstampable, log);\n }\n\n await discardAll();\n\n // Built from stored SHAs plus this run's, so a resumed promote reports the\n // same reference a single-pass promote would.\n const landedByKey = new Map(landed.map((l) => [l.repoKey, l.mergeSha]));\n const mergeSha = landedMergeRef(\n recovered.map((r) => ({ ...r, mergeSha: landedByKey.get(r.repoKey) ?? r.mergeSha ?? null })),\n );\n log(`[uat-promote] ${gen.name}: merged to ${landed.length} repo target(s) — ${mergeSha}`);\n\n // finishPromote needs the per-repo merge evidence for post-merge verification,\n // and the in-memory gen predates this run's publishes.\n const promotedGen: UatGeneration = {\n ...gen,\n repos: recovered.map((r) => ({\n ...r,\n mergeSha: landedByKey.get(r.repoKey) ?? r.mergeSha ?? null,\n promotedAt: r.promotedAt ?? (landedByKey.has(r.repoKey) ? now().toISOString() : null),\n })),\n };\n\n return finishPromote(promotedGen, projectRoot, deps, mergeSha, log, true, shipVersion);\n}\n\n/**\n * Everything after the merge itself lands: status, verification verdicts, stack\n * teardown, invalidating the rest of the chain, and the per-issue post-merge\n * handoff. Shared so the monorepo and polyrepo paths cannot drift.\n */\nasync function finishPromote(\n gen: UatGeneration,\n projectRoot: string,\n deps: UatPromoteDeps,\n mergeSha: string,\n log: (msg: string) => void,\n isPolyrepoGeneration = false,\n shipVersion?: string,\n): Promise<PromoteResult> {\n // The one place a generation becomes terminal, so the completeness invariant\n // is enforced here rather than at each caller: a polyrepo batch is finalized\n // only when EVERY repo carries both a promote stamp and the merge commit it\n // landed. Anything less cannot supply the per-repo evidence post-merge\n // verification needs, and `promoted` would foreclose the retry that could\n // still recover it.\n if (isPolyrepoGeneration) {\n const incomplete = (gen.repos ?? []).filter((r) => !r.promotedAt || !r.mergeSha);\n if (incomplete.length > 0) {\n return unprovenStampFailure(\n gen,\n incomplete.map((r) => ({\n repoKey: r.repoKey,\n why: r.promotedAt ? 'no merge sha was recorded for it' : 'it was never stamped as promoted',\n })),\n log,\n );\n }\n }\n\n const promotedAt = (deps.now?.() ?? new Date()).toISOString();\n const promotedRepos: UatGenerationRepo[] = isPolyrepoGeneration\n ? (gen.repos ?? [])\n : [{\n ...(gen.repos?.[0] ?? {\n repoKey: projectRoot.replace(/\\/+$/, '').split('/').pop() || projectRoot,\n repoPath: projectRoot,\n branch: gen.name,\n baseSha: gen.baseSha,\n targetBranch: 'main',\n worktreePath: gen.worktreePath,\n mergeOrder: 0,\n }),\n repoPath: projectRoot,\n targetBranch: 'main',\n promotedAt,\n mergeSha,\n }];\n const promotedGeneration: UatGeneration = { ...gen, status: 'promoted', repos: promotedRepos };\n\n // Persist the exact landed refs before any ship work. Deferred ship must root\n // its worktrees at these commits rather than the ambient primary checkout.\n deps.store.update(gen.name, { status: 'promoted', repos: promotedRepos });\n try {\n deps.recordVerification?.(promotedGeneration, mergeSha);\n } catch (err) {\n log(`[uat-promote] ${gen.name}: verification verdict recording failed after merge: ${err instanceof Error ? err.message : String(err)}`);\n }\n try {\n await deps.runShip?.(promotedGeneration, shipVersion, promotedRepos);\n } catch (err) {\n log(`[uat-promote] ${gen.name}: version ship settlement failed after merge: ${err instanceof Error ? err.message : String(err)}`);\n }\n await deps.teardownStack(promotedGeneration).catch((err) => {\n log(`[uat-promote] ${gen.name}: stack teardown failed: ${err instanceof Error ? err.message : String(err)}`);\n });\n\n const invalidated: string[] = [];\n for (const other of deps.store.listChain(projectRoot, ['ready', 'superseded'])) {\n if (other.name === gen.name) continue;\n deps.store.update(other.name, { status: 'invalidated' });\n invalidated.push(other.name);\n await deps.teardownStack(other).catch(() => {});\n }\n\n // Standard per-issue post-merge handoff, exactly once each (PAN-328 guard).\n // For polyrepo this runs only after EVERY repo published, so a member is\n // never handed off while part of its work is still unlanded.\n //\n // A polyrepo member's headSha is a composite anchor (`fe@abc api@def`), not a\n // git ref, and the wrapper project path is not a git repo — passing it as\n // verifiedMergedRef guarantees the ancestry check fails and the lifecycle\n // refuses. Per-repo merge commits are handed over instead, each verified in\n // its own repo against its own target branch.\n //\n // The completeness guard above already refused anything short of a merge SHA\n // per repo, so this evidence set covers every repo the batch touched — never\n // a subset, which would advance every member's lifecycle having proven only\n // some of the repos their work landed in.\n const verifiedMergedRepos = isPolyrepoGeneration\n ? (promotedGeneration.repos ?? []).map((r) => ({\n repoKey: r.repoKey,\n repoPath: r.repoPath,\n mergeSha: r.mergeSha!,\n targetBranch: r.targetBranch ?? 'main',\n }))\n : [];\n\n const postMergeStarted: string[] = [];\n for (const member of gen.members) {\n const options = verifiedMergedRepos.length > 0\n ? { sourceBranch: member.branch, verifiedMergedRepos }\n : { sourceBranch: member.branch, verifiedMergedRef: member.headSha || member.branch };\n if (deps.firePostMerge(member.issueId, options)) postMergeStarted.push(member.issueId);\n else log(`[uat-promote] ${gen.name}: post-merge for ${member.issueId} already in flight — skipped`);\n }\n\n return {\n success: true,\n generation: gen.name,\n mergeSha,\n members: gen.members.map((m) => m.issueId),\n postMergeStarted,\n invalidated,\n };\n}\n\n/**\n * Real per-repo promote wiring for a polyrepo project (PAN-3093), keyed by\n * repoKey. Each repo merges its own uat branch into its own target branch in a\n * throwaway detached worktree.\n *\n * The split that makes phase A safe: trialMerge does everything EXCEPT the\n * push, and leaves the worktree in place so publishPrepared only has to push\n * it. Nothing reaches a remote until every repo has produced a merge commit\n * locally.\n */\nexport function buildPolyrepoUatPromoteGitDeps(\n repos: ReadonlyArray<{ repoKey: string; repoPath: string; targetBranch?: string }>,\n): Map<string, PolyrepoRepoPromoteGit> {\n const runGit = (args: string[], cwd: string) =>\n execFileAsync('git', args, { cwd, maxBuffer: 16 * 1024 * 1024 });\n\n return new Map(\n repos.map((repo) => {\n const target = repo.targetBranch ?? 'main';\n const originTarget = `origin/${target}`;\n const worktreeFor = (branchName: string) =>\n join(tmpdir(), `uat-promote-${repo.repoKey}-${branchName.replace(/[^a-z0-9]/gi, '-')}`);\n\n const git: PolyrepoRepoPromoteGit = {\n targetHeadSha: async () => {\n await runGit(['fetch', 'origin', target], repo.repoPath);\n return (await runGit(['rev-parse', originTarget], repo.repoPath)).stdout.trim();\n },\n\n changedFilesSince: async (baseSha) => {\n if (!/^[0-9a-f]{7,40}$/i.test(baseSha)) throw new Error(`unsafe base sha: ${baseSha}`);\n const { stdout } = await runGit(['diff', '--name-only', `${baseSha}..${originTarget}`], repo.repoPath);\n return stdout.split('\\n').map((l) => l.trim()).filter(Boolean);\n },\n\n batchChangedFiles: async (branchName) => {\n const safeBranch = safeUatBranchName(branchName);\n const ref = await runGit(['rev-parse', '--verify', `origin/${safeBranch}`], repo.repoPath)\n .then(() => `origin/${safeBranch}`)\n .catch(() => safeBranch);\n const mergeBase = (await runGit(['merge-base', originTarget, ref], repo.repoPath)).stdout.trim();\n const { stdout } = await runGit(['diff', '--name-only', `${mergeBase}..${ref}`], repo.repoPath);\n return stdout.split('\\n').map((l) => l.trim()).filter(Boolean);\n },\n\n trialMerge: async (branchName, message) => {\n const safeBranch = safeUatBranchName(branchName);\n const worktreePath = worktreeFor(safeBranch);\n await runGit(['worktree', 'remove', '--force', worktreePath], repo.repoPath).catch(() => {});\n await runGit(['worktree', 'prune'], repo.repoPath).catch(() => {});\n await runGit(['worktree', 'add', '--detach', worktreePath, originTarget], repo.repoPath);\n try {\n const originRef = `origin/${safeBranch}`;\n const ref = await runGit(['rev-parse', '--verify', originRef], worktreePath)\n .then(() => originRef)\n .catch(() => safeBranch);\n await runGit(['merge', '--no-ff', ref, '-m', message], worktreePath);\n const mergeSha = (await runGit(['rev-parse', 'HEAD'], worktreePath)).stdout.trim();\n return { repoKey: repo.repoKey, handle: worktreePath, mergeSha };\n } catch (err) {\n // Clean up our own worktree before propagating; the caller discards\n // the ones that already succeeded.\n await runGit(['worktree', 'remove', '--force', worktreePath], repo.repoPath).catch(() => {});\n await runGit(['worktree', 'prune'], repo.repoPath).catch(() => {});\n throw err;\n }\n },\n\n publishPrepared: async (prepared) => {\n await runGit(['push', 'origin', `HEAD:${target}`], prepared.handle);\n },\n\n discardPrepared: async (prepared) => {\n await runGit(['worktree', 'remove', '--force', prepared.handle], repo.repoPath).catch(() => {});\n await runGit(['worktree', 'prune'], repo.repoPath).catch(() => {});\n },\n\n findLandedMerge: async (branchName) => {\n const safeBranch = safeUatBranchName(branchName);\n // This call establishes REMOTE truth after an ambiguous publish, so a\n // failed fetch must not be answered from a cached target ref: that\n // could report \"not landed\" for a merge that is live, and republish it.\n await runGit(['fetch', 'origin', target], repo.repoPath).catch((err) => {\n throw new Error(\n `cannot determine whether ${repo.repoKey} already landed: fetching origin/${target} failed ` +\n `(${err instanceof Error ? err.message.split('\\n')[0] : String(err)})`,\n );\n });\n // Fetch the UAT ref as well: a fresh checkout has no tracking ref for\n // it, and resolving only from local refs would misclassify a landed\n // unstamped repo as pending. Absence here is normal (the branch may\n // already be reaped), so this one is best-effort.\n await runGit(\n ['fetch', 'origin', `+refs/heads/${safeBranch}:refs/remotes/origin/${safeBranch}`],\n repo.repoPath,\n ).catch(() => {});\n\n const ref = await runGit(['rev-parse', '--verify', `origin/${safeBranch}`], repo.repoPath)\n .then(() => `origin/${safeBranch}`)\n .catch(() => safeBranch);\n\n const tip = await runGit(['rev-parse', ref], repo.repoPath)\n .then(({ stdout }) => stdout.trim())\n .catch(() => '');\n if (!tip) return null;\n\n // Contained in the target? If not, this repo genuinely has not landed.\n const contained = await runGit(['merge-base', '--is-ancestor', tip, originTarget], repo.repoPath)\n .then(() => true)\n .catch(() => false);\n if (!contained) return null;\n\n // The merge commit that brought it in: the oldest merge on the\n // ancestry path from the batch tip to the target head.\n const { stdout } = await runGit(\n ['rev-list', '--ancestry-path', '--merges', '--reverse', `${tip}..${originTarget}`],\n repo.repoPath,\n ).catch(() => ({ stdout: '' }));\n const mergeCommit = stdout.split('\\n').map((l) => l.trim()).filter(Boolean)[0];\n // A fast-forwarded target has no merge commit; the tip itself is what landed.\n return mergeCommit ?? tip;\n },\n };\n\n return [repo.repoKey, git] as const;\n }),\n );\n}\n\n/** Real git wiring: throwaway detached worktree off origin/main, no-ff merge, push. */\nexport function buildUatPromoteGitDeps(projectRoot: string): UatPromoteGitDeps {\n const runGit = (args: string[], cwd: string) =>\n execFileAsync('git', args, { cwd, maxBuffer: 16 * 1024 * 1024 });\n\n return {\n fetchMain: async () => {\n await runGit(['fetch', 'origin', 'main'], projectRoot);\n return (await runGit(['rev-parse', 'origin/main'], projectRoot)).stdout.trim();\n },\n mergeIntoMain: async (branchName, message) => {\n const safeBranch = safeUatBranchName(branchName);\n const worktreePath = join(tmpdir(), `uat-promote-${safeBranch.replace(/[^a-z0-9]/gi, '-')}`);\n await runGit(['worktree', 'remove', '--force', worktreePath], projectRoot).catch(() => {});\n await runGit(['worktree', 'prune'], projectRoot).catch(() => {});\n await runGit(['worktree', 'add', '--detach', worktreePath, 'origin/main'], projectRoot);\n try {\n const originRef = `origin/${safeBranch}`;\n const ref = await runGit(['rev-parse', '--verify', originRef], worktreePath)\n .then(() => originRef)\n .catch(() => safeBranch);\n await runGit(['merge', '--no-ff', ref, '-m', message], worktreePath);\n await runGit(['push', 'origin', 'HEAD:main'], worktreePath);\n return (await runGit(['rev-parse', 'HEAD'], worktreePath)).stdout.trim();\n } finally {\n await runGit(['worktree', 'remove', '--force', worktreePath], projectRoot).catch(() => {});\n await runGit(['worktree', 'prune'], projectRoot).catch(() => {});\n }\n },\n changedFilesSince: async (baseSha) => {\n if (!/^[0-9a-f]{7,40}$/i.test(baseSha)) throw new Error(`unsafe base sha: ${baseSha}`);\n const { stdout } = await runGit(['diff', '--name-only', `${baseSha}..origin/main`], projectRoot);\n return stdout.split('\\n').map((l) => l.trim()).filter(Boolean);\n },\n batchChangedFiles: async (branchName) => {\n const safeBranch = safeUatBranchName(branchName);\n const ref = await runGit(['rev-parse', '--verify', `origin/${safeBranch}`], projectRoot)\n .then(() => `origin/${safeBranch}`)\n .catch(() => safeBranch);\n const mergeBase = (await runGit(['merge-base', 'origin/main', ref], projectRoot)).stdout.trim();\n const { stdout } = await runGit(['diff', '--name-only', `${mergeBase}..${ref}`], projectRoot);\n return stdout.split('\\n').map((l) => l.trim()).filter(Boolean);\n },\n };\n}\n","import { Effect } from 'effect';\nimport { messageAgent } from '../agents.js';\nimport { getFlywheelActiveRunId, isFlywheelGloballyPaused } from '../overdeck/control-settings.js';\nimport { sessionExists } from '../tmux.js';\nimport { recordDeaconNudge } from './deacon-nudge-log.js';\nimport { FLYWHEEL_ORCHESTRATOR_AGENT_ID } from './flywheel.js';\nimport type { PromoteResult } from './uat-promote.js';\n\ntype SessionExists = (name: string) => boolean | Promise<boolean>;\n\nexport interface NotifyDeps {\n getActiveRunId?: typeof getFlywheelActiveRunId;\n isPaused?: typeof isFlywheelGloballyPaused;\n sessionExists?: SessionExists;\n message?: typeof messageAgent;\n recordNudge?: typeof recordDeaconNudge;\n}\n\nfunction buildPromoteNudge(result: Extract<PromoteResult, { success: true }>): string {\n const members = result.members.length > 0 ? result.members.join(', ') : '(no members recorded)';\n return (\n `The operator just promoted UAT generation ${result.generation} to main at ${result.mergeSha}. ` +\n `Run a fresh Observe->Act loop NOW. Fetch GET /api/registered-projects, then for every returned ` +\n `project key call GET /api/pipeline/membership?project=<URL-encoded-project-key>. A bare array is a ` +\n `successful answer: combine arrays and re-derive activePipeline from only rows where inPipeline === true; ` +\n `clean_terminal rows are audit-only and excluded. An object with status:'unavailable' is a typed blind spot: ` +\n `emit an investigate suggestion naming projectKey, reason, and message, and NEVER derive membership from ` +\n `tracker, agent, tmux, workspace, or review-status state. membershipQueryable:false from the registered-projects ` +\n `response is an upfront missing_issue_prefix hint, not permission to skip the read door. EXCLUDE ` +\n `the merged member(s): ${members}. Re-assemble a clean UAT batch with only members that are currently ` +\n `review+test passed, close out the promoted issue(s), emit a fresh status snapshot, and re-arm the next ` +\n `tick. Do NOT ask the operator a question, do NOT pause, and do NOT reuse the stale pre-promote ready set.`\n );\n}\n\nexport async function notifyFlywheelOfUatPromote(result: PromoteResult, deps: NotifyDeps = {}): Promise<void> {\n try {\n if (result.success !== true) return;\n\n const getActiveRunId = deps.getActiveRunId ?? getFlywheelActiveRunId;\n const isPaused = deps.isPaused ?? isFlywheelGloballyPaused;\n const sessionExistsDep = deps.sessionExists ?? ((name: string) => Effect.runPromise(sessionExists(name)));\n const message = deps.message ?? messageAgent;\n const recordNudge = deps.recordNudge ?? recordDeaconNudge;\n\n if (!getActiveRunId()) return;\n if (isPaused()) return;\n\n const delivered = await sessionExistsDep(FLYWHEEL_ORCHESTRATOR_AGENT_ID);\n if (delivered) {\n try {\n await message(FLYWHEEL_ORCHESTRATOR_AGENT_ID, buildPromoteNudge(result), 'uat-promote-notify');\n } catch {\n /* best-effort — the nudge log below still records that the session was present */\n }\n }\n\n recordNudge({\n patrol: 'uat-promote-notify',\n issueId: result.members[0] ?? result.generation,\n action: 'notified flywheel-orchestrator to re-derive ready set after UAT promote',\n reason:\n 'operator promoted a UAT batch; flywheel must immediately rebuild to drop merged + regressed members before its next tick',\n state: {\n generation: result.generation,\n members: result.members,\n mergeSha: result.mergeSha,\n delivered,\n },\n });\n } catch {\n /* best-effort — promote success must never be converted into a notification failure */\n }\n}\n","/**\n * UAT batch train service (PAN-1737) — real wiring for the reconciler,\n * assembly engine, stacks, and promote path, plus the payload builders the\n * /api/merge-train/* routes expose.\n *\n * The reconciler interval is the heartbeat of \"always one batch ready\": every\n * 60s it walks every tracked project whose effective merge-train flag is on,\n * comparing that project's ready set against its generation chain and\n * assembling/invalidating as needed. PAN-1696 removed the active-flywheel-run\n * requirement — the ready set comes from review-status records, so batches\n * assemble with no run at all. Assemblies run minutes; the reconciler is\n * single-flight per project, so ticks never pile up.\n */\nimport { stat } from 'node:fs/promises';\nimport { resolve } from 'node:path';\nimport { Effect } from 'effect';\nimport { layer as nodeServicesLayer } from '@effect/platform-node/NodeServices';\nimport {\n assembleUatGeneration,\n cleanupUatGenerations,\n type ReadyFeature,\n} from '../../../lib/cloister/uat-generation-engine.js';\nimport {\n buildPolyrepoCleanupGit,\n buildPolyrepoGitDeps,\n buildUatGenerationGitDeps,\n featureNamespaceOf,\n buildUatGenerationStore,\n buildUatGenerationCleanupGit,\n listRemoteUatBranches,\n listRemoteUatBranchesMulti,\n} from '../../../lib/cloister/uat-generation-deps.js';\nimport {\n assemblePolyrepoUatGeneration,\n compositeAnchor,\n compositeMemberAnchor,\n} from '../../../lib/cloister/uat-polyrepo-engine.js';\nimport { buildConflictAgentHook } from '../../../lib/cloister/uat-conflict-agent.js';\nimport { reconcileUatGenerations, type ReconcileResult } from '../../../lib/cloister/uat-reconciler.js';\nimport { ensureUatStack, probeUatStack, teardownUatStack } from '../../../lib/cloister/uat-stack.js';\nimport {\n promoteUatGeneration,\n buildPolyrepoUatPromoteGitDeps,\n buildUatPromoteGitDeps,\n type PromoteResult,\n type UatPromoteDeps,\n} from '../../../lib/cloister/uat-promote.js';\nimport { notifyFlywheelOfUatPromote } from '../../../lib/cloister/uat-promote-notify.js';\nimport {\n getUatGenerationSync,\n hasUncleanedTerminalUatGenerationSync,\n isMergeTrainEnabled,\n listUatGenerationsSync,\n markUatGenerationRepoPromotedSync,\n type UatGeneration,\n type UatGenerationRepo,\n} from '../../../lib/overdeck/merge-sync.js';\nimport { listEligibleCandidatesByProject } from '../../../lib/flywheel-merge-order.js';\nimport { extractACFromDocument } from '../../../lib/xbrief/acceptance-criteria.js';\nimport { findXBriefByIssue, readXBriefDocument } from '../../../lib/xbrief/xbrief-index.js';\nimport { findProjectByPathSync, listProjectsSync, resolveProjectFromIssueSync } from '../../../lib/projects.js';\nimport type { PanIssueShipRecord } from '../../../lib/pan-dir/record.js';\nimport {\n executeVersionShipForGeneration,\n persistPendingShipRecords,\n persistShipRecords,\n withGenerationShipLock,\n} from '../../../lib/cloister/ship-record.js';\nimport {\n aggregateGenerationShipStatus,\n loadShipRecords,\n publicShipStatus,\n} from '../../../lib/cloister/ship-status.js';\nimport {\n resolveConfiguredReposSync,\n resolveProjectReposFromResolvedIssueSync,\n type ResolvedProjectRepo,\n} from '../../../lib/project-repos.js';\nimport { getDashboardIdentity } from '../identity.js';\n\nconst RECONCILE_INTERVAL_MS = 60_000;\nconst CHAIN_PAYLOAD_LIMIT = 10;\nconst ACCEPTANCE_CRITERIA_READ_CONCURRENCY = 4;\n\ntype AcceptanceCriteriaSummary = Array<{ title: string; status: string }>;\n\n/**\n * PAN-3165: an unresolvable plan is not a plan with no criteria. Collapsing the\n * two let a resolver bug render as the confident claim \"No UAT steps in plan\",\n * silently deleting the operator's checklist, so the lookup outcome travels\n * alongside the criteria all the way to the UI.\n */\ninterface AcceptanceCriteriaLookup {\n planResolved: boolean;\n criteria: AcceptanceCriteriaSummary;\n}\n\ninterface AcceptanceCriteriaCacheEntry {\n path: string;\n mtimeMs: number;\n criteria: AcceptanceCriteriaSummary;\n}\n\nconst acceptanceCriteriaByIssue = new Map<string, AcceptanceCriteriaCacheEntry>();\n\nexport function resolveUatProjectRoot(cwdPath = process.cwd()): string {\n const registeredProject = findProjectByPathSync(cwdPath);\n if (registeredProject) return resolve(registeredProject.path);\n\n const normalized = resolve(cwdPath);\n const workspaceMatch = normalized.match(/^(.*)\\/workspaces\\/feature-[^/]+(?:\\/.*)?$/i);\n return workspaceMatch?.[1] ? resolve(workspaceMatch[1]) : normalized;\n}\n\nfunction projectRoot(): string {\n return resolveUatProjectRoot();\n}\n\nexport function canStartUatTrainReconciler(): boolean {\n const identity = getDashboardIdentity();\n return identity.mode === 'primary' && resolve(identity.repoRoot) === projectRoot();\n}\n\nfunction codenameLabel(features: readonly ReadyFeature[]): string {\n const prefix = features[0]?.issueId.split('-')[0];\n return (prefix ?? 'uat').toLowerCase();\n}\n\n/**\n * PAN-1696 reconciler-decouple: Per-project cleanup function factory.\n */\nfunction makeCleanupForProject(projectPath: string): () => Promise<void> {\n return async () => {\n // A polyrepo generation's artifacts live in N repos plus a wrapper folder,\n // none of them reachable from the wrapper path the monorepo cleanup uses.\n const projectConfig = findProjectByPathSync(projectPath);\n const cleanupGit = projectConfig?.workspace?.type === 'polyrepo'\n ? buildPolyrepoCleanupGit(await resolveProjectRepos(projectPath), projectPath)\n : buildUatGenerationCleanupGit(projectPath);\n\n await cleanupUatGenerations(projectPath, {\n store: buildUatGenerationStore(),\n ...cleanupGit,\n teardownStack: (gen) => teardownUatStack(gen),\n log: (msg) => console.log(msg),\n });\n };\n}\n\n/** One reconciler pass. `force` rebuilds even when a live generation matches. */\n/**\n * PAN-1696 reconciler-decouple: Internal per-project reconciler.\n * Runs the UAT train reconciliation for a specific project.\n */\nasync function runUatTrainReconcileForProject(\n projectPath: string,\n options: { force?: boolean } = {},\n): Promise<ReconcileResult> {\n const { isMergeTrainEnabledForProject } = await import('../../../lib/overdeck/merge-sync.js');\n const projectConfig = findProjectByPathSync(projectPath);\n\n // Fail closed: if project cannot be resolved, don't proceed\n if (!projectConfig) {\n console.log(`[uat-train] project at ${projectPath} not found in config — skipping`);\n return { action: 'no-queue', invalidated: [] };\n }\n\n // Check if enabled before building git deps (fail closed on null config)\n if (!isMergeTrainEnabledForProject(projectConfig)) {\n return { action: 'disabled', invalidated: [] };\n }\n\n // Early exit if no candidates and no live generations — skip git operations\n const candidates = await listEligibleCandidatesByProject(projectPath);\n const liveGenerations = listUatGenerationsSync({\n projectRoot: projectPath,\n statuses: ['assembling', 'ready', 'superseded'],\n limit: 1,\n });\n if (candidates.length === 0 && liveGenerations.length === 0) {\n // Terminal generations still own branches, worktrees, and a wrapper folder.\n // Promoting the LAST ready batch lands exactly here — no candidates, no\n // live rows — so returning early without cleanup leaks every artifact the\n // batch created, permanently. Cleanup is idempotent and skips rows already\n // marked cleaned, so running it on an otherwise idle tick is cheap.\n // An existence check, not a load: generation rows are retained as an audit\n // trail, so hydrating every terminal row and its four child tables to answer\n // a yes/no question would make the idle minute cost grow with history.\n if (hasUncleanedTerminalUatGenerationSync(projectPath)) {\n await makeCleanupForProject(projectPath)().catch((err) => {\n console.log(`[uat-train] terminal cleanup failed for ${projectPath}: ${err instanceof Error ? err.message : String(err)}`);\n });\n }\n return { action: 'idle', invalidated: [] };\n }\n\n // A polyrepo project has no git repo at its own path, so every git dep must\n // be per member repo and staleness must compare composite anchors.\n if (projectConfig.workspace?.type === 'polyrepo') {\n // The base anchor must cover exactly the ready set's contributing repos, so\n // both callbacks read one memoized ready set — recomputing it would also\n // re-run every git probe.\n let readySetOnce: Promise<ReadyFeature[] | null> | undefined;\n const readySet = () => (readySetOnce ??= getPolyrepoReadySetForProject(projectPath));\n\n return reconcileUatGenerations(projectPath, {\n isEnabled: () => true, // Already checked above\n getReadySet: readySet,\n // Unused on this path — getBaseAnchor/getFeatureAnchor take over — but\n // the deps contract still requires them.\n getMainHeadSha: async () => '',\n getBranchHeadSha: async () => '',\n getBaseAnchor: async () => getPolyrepoBaseAnchor(projectPath, await readySet()),\n getFeatureAnchor: (feature) => getPolyrepoFeatureAnchor(feature),\n isGenerationContainedInMain: async (generation) => {\n const repoGit = buildPolyrepoGitDeps(await resolveProjectRepos(projectPath), { includeReadOnly: true });\n const repos = generation.repos ?? [];\n return repos.length > 0 && Promise.all(repos.map((repo) =>\n repoGit.get(repo.repoKey)?.isBranchContainedInMain?.(repo.branch) ?? Promise.resolve(false)))\n .then((contained) => contained.every(Boolean));\n },\n store: buildUatGenerationStore(),\n assemble: (features) => assemblePolyrepoFromReadySetForProject(projectPath, features),\n teardownStack: (gen) => teardownUatStack(gen),\n cleanup: makeCleanupForProject(projectPath),\n log: (msg) => console.log(msg),\n }, options);\n }\n\n const gitDeps = buildUatGenerationGitDeps(projectPath);\n\n return reconcileUatGenerations(projectPath, {\n isEnabled: () => true, // Already checked above\n getReadySet: () => getReadySetForProject(projectPath),\n getMainHeadSha: () => gitDeps.fetchMain(),\n getBranchHeadSha: (branch) => gitDeps.branchHeadSha(branch),\n isGenerationContainedInMain: (generation) => gitDeps.isBranchContainedInMain?.(generation.name) ?? Promise.resolve(false),\n store: buildUatGenerationStore(),\n assemble: (features) => assembleFromReadySetForProject(projectPath, features),\n teardownStack: (gen) => teardownUatStack(gen),\n cleanup: makeCleanupForProject(projectPath),\n log: (msg) => console.log(msg),\n }, options);\n}\n\n/**\n * Get ready set for a specific project path.\n */\nasync function getReadySetForProject(projectPath: string): Promise<ReadyFeature[] | null> {\n const { computeMergeQueueFromCandidates, listEligibleCandidatesByProject, resolveMergeQueuePrUrl } = await import('../../../lib/flywheel-merge-order.js');\n\n const candidates = await listEligibleCandidatesByProject(projectPath);\n if (candidates.length === 0) return [];\n\n const queue = await Effect.runPromise(\n computeMergeQueueFromCandidates(candidates, projectPath, {\n getPrUrl: resolveMergeQueuePrUrl,\n }).pipe(\n Effect.provide(nodeServicesLayer),\n ),\n );\n\n return queue.map((item) => ({\n issueId: item.issueId,\n title: item.title,\n branch: item.branchName,\n ...(item.pr !== undefined ? { pr: item.pr } : {}),\n ...(item.prUrl !== undefined ? { prUrl: item.prUrl } : {}),\n conflictsWith: item.conflictsWith,\n }));\n}\n\n/**\n * Member repos configured for an issue in a polyrepo project. Resolution goes\n * through the canonical repo resolver, which collapses monorepo to one\n * synthetic entry — so this is the same door the rest of the pipeline uses.\n */\nfunction resolveReposForIssue(issueId: string): ResolvedProjectRepo[] {\n const resolvedProject = resolveProjectFromIssueSync(issueId);\n if (!resolvedProject) return [];\n return resolveProjectReposFromResolvedIssueSync(issueId, resolvedProject) ?? [];\n}\n\n/**\n * The project's configured member repos, resolved through the issue door using\n * any eligible candidate. Every issue in a project resolves the same repo set;\n * only the per-issue branch names differ, and callers here use the branches\n * from each feature's own contributions rather than these.\n */\nasync function resolveProjectRepos(projectPath: string, preferredIssueId?: string): Promise<ResolvedProjectRepo[]> {\n const issueId = preferredIssueId ?? (await listEligibleCandidatesByProject(projectPath))[0]?.issueId;\n if (issueId) return resolveReposForIssue(issueId);\n\n // No candidate to resolve through — the exact state cleanup runs in after the\n // last batch is promoted. Resolve the configured repo set straight from\n // project config instead; the per-issue branch names it derives are unused\n // here, only the repo paths and targets matter.\n const resolved = findProjectByPathSync(projectPath);\n if (!resolved) return [];\n const entry = listProjectsSync().find(({ config }) => resolve(config.path) === resolve(projectPath));\n if (!entry) return [];\n return resolveConfiguredReposSync(entry.key, projectPath, entry.config, `${entry.key}-cleanup`);\n}\n\n/** Polyrepo ready set: per-candidate contributions across member repos. */\nasync function getPolyrepoReadySetForProject(projectPath: string): Promise<ReadyFeature[] | null> {\n const { computePolyrepoMergeQueueFromCandidates, listEligibleCandidatesByProject, resolveMergeQueuePrUrl } =\n await import('../../../lib/flywheel-merge-order.js');\n\n const candidates = await listEligibleCandidatesByProject(projectPath);\n if (candidates.length === 0) return [];\n\n const reposByIssue = new Map(candidates.map((c) => [c.issueId, resolveReposForIssue(c.issueId)]));\n\n // An outage is \"unavailable\", never \"empty\". The reconciler treats an empty\n // ready set as authoritative and will invalidate the live generation and tear\n // down its stack; null means \"do nothing this tick\".\n let unavailableRepos: readonly string[] = [];\n\n const queue = await Effect.runPromise(\n computePolyrepoMergeQueueFromCandidates(candidates, reposByIssue, projectPath, {\n getPrUrl: resolveMergeQueuePrUrl,\n onExcluded: (issueId, reason) =>\n console.log(`[uat-train] ${issueId} excluded from the polyrepo ready set: ${reason}`),\n onRefreshUnavailable: (repoPaths) => { unavailableRepos = repoPaths; },\n }).pipe(Effect.provide(nodeServicesLayer)),\n );\n\n if (unavailableRepos.length > 0) {\n console.log(\n `[uat-train] polyrepo ready set unavailable for ${projectPath}: could not refresh refs in ` +\n `${unavailableRepos.join(', ')} — skipping this tick rather than invalidating the live batch`,\n );\n return null;\n }\n\n return queue.map((item) => ({\n issueId: item.issueId,\n title: item.title,\n branch: item.branchName,\n ...(item.pr !== undefined ? { pr: item.pr } : {}),\n ...(item.prUrl !== undefined ? { prUrl: item.prUrl } : {}),\n conflictsWith: item.conflictsWith,\n repoContributions: item.repoContributions,\n }));\n}\n\n/**\n * Composite base anchor over exactly the repos the ready set contributes to.\n *\n * NOT every configured repo: assembly anchors on the contributing set, and\n * staleness is string equality, so including an untouched repo would make the\n * two anchors differ forever and rebuild the same generation every tick.\n */\nasync function getPolyrepoBaseAnchor(\n projectPath: string,\n readySet: readonly ReadyFeature[] | null,\n): Promise<string> {\n const contributingKeys = new Set(\n (readySet ?? []).flatMap((f) => (f.repoContributions ?? []).map((c) => c.repoKey)),\n );\n const repos = (await resolveProjectRepos(projectPath)).filter((r) => contributingKeys.has(r.repoKey));\n if (repos.length === 0) return '';\n const gitByRepo = buildPolyrepoGitDeps(repos);\n\n const entries = await Promise.all(\n repos.map(async (repo) => ({\n repoKey: repo.repoKey,\n sha: await gitByRepo.get(repo.repoKey)!.fetchMain().catch(() => 'unknown'),\n mergeOrder: repo.mergeOrder,\n })),\n );\n return compositeAnchor(entries);\n}\n\n/** Composite anchor over the repos one feature currently contributes to. */\nasync function getPolyrepoFeatureAnchor(feature: ReadyFeature): Promise<string> {\n const contributions = feature.repoContributions ?? [];\n if (contributions.length === 0) return 'unknown';\n\n const entries = await Promise.all(\n contributions.map(async (c) => {\n const git = buildUatGenerationGitDeps(c.repoPath, {\n targetBranch: c.targetBranch,\n // The SAME configured namespace assembly uses. Defaulting to `feature/`\n // here would reject a valid `feat/…` branch, yield an `unknown` anchor,\n // and invalidate the generation assembly had just built.\n featureBranchPrefix: featureNamespaceOf(c.branch),\n });\n return { repoKey: c.repoKey, headSha: await git.branchHeadSha(c.branch).catch(() => 'unknown') };\n }),\n );\n return compositeMemberAnchor(entries);\n}\n\n/** Assemble a polyrepo UAT generation for a project's ready set. */\nasync function assemblePolyrepoFromReadySetForProject(\n projectPath: string,\n features: readonly ReadyFeature[],\n): Promise<UatGeneration> {\n const repos = await resolveProjectRepos(projectPath, features[0]?.issueId);\n if (repos.length === 0) {\n throw new Error(`[uat-train] no member repos resolved for the polyrepo project at ${projectPath}`);\n }\n\n return assemblePolyrepoUatGeneration(\n {\n projectRoot: projectPath,\n label: codenameLabel(features),\n dateIso: new Date().toISOString(),\n features,\n repos,\n takenBranchNames: await listRemoteUatBranchesMulti(repos),\n },\n {\n repoGit: buildPolyrepoGitDeps(repos),\n store: buildUatGenerationStore(),\n resolveConflict: buildConflictAgentHook(),\n log: (msg) => console.log(msg),\n },\n );\n}\n\n/**\n * Assemble UAT generation for a specific project's ready set.\n */\nasync function assembleFromReadySetForProject(\n projectPath: string,\n features: readonly ReadyFeature[],\n): Promise<UatGeneration> {\n const store = buildUatGenerationStore();\n return assembleUatGeneration(\n {\n projectRoot: projectPath,\n label: codenameLabel(features),\n dateIso: new Date().toISOString(),\n features,\n takenBranchNames: await listRemoteUatBranches(projectPath),\n },\n {\n git: buildUatGenerationGitDeps(projectPath),\n store,\n resolveConflict: buildConflictAgentHook(),\n log: (msg) => console.log(msg),\n },\n );\n}\n\n/**\n * PAN-1696 reconciler-decouple: Single-project reconciliation (used by forced-rebuild endpoint).\n * When called from /api/merge-train/assemble with force:true and no project, reconciles every enabled project; with { project } or an explicit projectRoot, just that one.\n */\nexport async function runUatTrainReconcile(\n options: { force?: boolean; projectRoot?: string } = {},\n): Promise<ReconcileResult> {\n const projectPath = options.projectRoot ? resolve(options.projectRoot) : projectRoot();\n return runUatTrainReconcileForProject(projectPath, { ...(options.force !== undefined ? { force: options.force } : {}) });\n}\n\n/**\n * PAN-1696 merge-train-routes: reconcile every tracked project whose effective\n * merge-train flag is on, isolating failures so one project's git error cannot\n * abort the others. Returns one result per attempted project so the caller can\n * report which projects moved and which errored.\n */\nexport async function runUatTrainReconcileAllProjects(\n options: { force?: boolean } = {},\n): Promise<Array<{ projectKey: string; result?: ReconcileResult; error?: string }>> {\n const { listProjectsSync } = await import('../../../lib/projects.js');\n const { isMergeTrainEnabledForProject } = await import('../../../lib/overdeck/merge-sync.js');\n\n const enabled = listProjectsSync().filter(({ config }) => isMergeTrainEnabledForProject(config));\n return Promise.all(\n enabled.map(async ({ key, config }) => {\n const projectPath = resolve(config.path);\n try {\n const result = await runUatTrainReconcileForProject(projectPath, options);\n return { projectKey: key, result };\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`[uat-train] project ${projectPath} failed to reconcile:`, msg);\n return { projectKey: key, error: msg };\n }\n }),\n );\n}\n\nlet reconcilerTimer: ReturnType<typeof setInterval> | null = null;\n\nexport function startUatTrainReconciler(): boolean {\n if (!canStartUatTrainReconciler()) return false;\n if (reconcilerTimer) return true;\n reconcilerTimer = setInterval(() => {\n void runUatTrainReconcileAllProjects().catch((err) => {\n console.warn('[uat-train] reconcile tick failed:', err instanceof Error ? err.message : err);\n });\n }, RECONCILE_INTERVAL_MS);\n reconcilerTimer.unref?.();\n void runUatTrainReconcileAllProjects().catch((err) => {\n console.warn('[uat-train] initial reconcile failed:', err instanceof Error ? err.message : err);\n });\n return true;\n}\n\nexport function stopUatTrainReconciler(): void {\n if (reconcilerTimer) {\n clearInterval(reconcilerTimer);\n reconcilerTimer = null;\n }\n}\n\n// ─── Route payloads ───────────────────────────────────────────────────────────\n\nexport interface UatGenerationMemberPayload {\n issueId: string;\n title: string;\n branch: string;\n pr?: number;\n prUrl?: string;\n mergeOrder: number;\n /** What-to-UAT checklist from the issue's xBRIEF spec (shared extractor). */\n acceptanceCriteria: Array<{ title: string; status: string }>;\n /**\n * False when the issue's spec could not be resolved or read at all (PAN-3165).\n * An empty `acceptanceCriteria` then means \"we don't know\", not \"the plan\n * specified nothing\" — the UI must say so rather than assert the latter.\n */\n planResolved: boolean;\n}\n\n/** Per-repo detail safe to return over HTTP — no host filesystem paths. */\nexport interface UatGenerationRepoPayload {\n repoKey: string;\n branch: string;\n baseSha: string;\n targetBranch: string;\n mergeOrder: number;\n promotedAt?: string | null;\n mergeSha?: string | null;\n}\n\nexport interface UatGenerationPayload {\n name: string;\n status: UatGeneration['status'];\n baseSha: string;\n createdAt: string;\n updatedAt: string;\n members: UatGenerationMemberPayload[];\n heldOut: UatGeneration['heldOut'];\n resolutions: UatGeneration['resolutions'];\n versionSyncConfigured: boolean;\n /** Public aggregate omits operational error/reason detail. */\n shipStatus: Omit<PanIssueShipRecord, 'error' | 'reason'> | null;\n /**\n * Per-repo generation detail, additive (PAN-3093). Always present and\n * non-empty: a monorepo generation projects the single synthesized entry, so\n * consumers read one shape. Rendering it is deliberately a follow-up; the\n * data ships now so that follow-up has something to render.\n *\n * A deliberate public DTO, not the internal model: `repoPath` and\n * `worktreePath` are absolute server paths, and a response has no reason to\n * disclose host filesystem topology.\n */\n repos: UatGenerationRepoPayload[];\n /**\n * Live stack state (PAN-3166). `degraded` means containers are up but a\n * service the compose file declares is not serving — `downServices` names\n * them and `serviceErrors` carries each one's last error line, so the panel\n * can say why instead of offering a link into a gateway timeout. `unknown`\n * means the probe itself failed, which is not proof the stack is gone.\n */\n stack: {\n status: 'running' | 'degraded' | 'unknown' | 'absent';\n frontendUrl: string;\n downServices?: string[];\n serviceErrors?: Record<string, string>;\n probeError?: string;\n };\n}\n\nasync function mapBounded<T>(items: readonly T[], concurrency: number, worker: (item: T) => Promise<void>): Promise<void> {\n let next = 0;\n const workers = Array.from({ length: Math.min(concurrency, items.length) }, async () => {\n while (next < items.length) {\n const item = items[next++]!;\n await worker(item);\n }\n });\n await Promise.all(workers);\n}\n\n/**\n * PAN-1696: `root` is the project the issues belong to, NOT the dashboard's own\n * repo. Once the generations payload started serving every tracked project, a\n * dashboard-rooted xBRIEF lookup silently returned no acceptance criteria for\n * every non-PAN generation, so those batches showed an empty \"What to UAT\" list.\n * Issue ids are prefix-distinct across projects, so the module-level mtime cache\n * stays safe to key by issue id alone.\n */\nasync function loadAcceptanceCriteriaCache(\n issueIds: ReadonlySet<string>,\n root: string,\n): Promise<Map<string, AcceptanceCriteriaLookup>> {\n const cache = new Map<string, AcceptanceCriteriaLookup>();\n if (issueIds.size === 0) return cache;\n\n await mapBounded([...issueIds], ACCEPTANCE_CRITERIA_READ_CONCURRENCY, async (issueId) => {\n const upperIssueId = issueId.toUpperCase();\n const existing = acceptanceCriteriaByIssue.get(upperIssueId);\n if (existing) {\n try {\n const { mtimeMs } = await stat(existing.path);\n if (existing.mtimeMs === mtimeMs) {\n cache.set(upperIssueId, { planResolved: true, criteria: existing.criteria });\n return;\n }\n } catch {\n acceptanceCriteriaByIssue.delete(upperIssueId);\n }\n }\n\n try {\n const found = await Effect.runPromise(findXBriefByIssue(root, upperIssueId));\n if (!found) {\n acceptanceCriteriaByIssue.delete(upperIssueId);\n console.warn(`[uat-train] no xBRIEF spec resolved for ${upperIssueId} under ${root} — UAT checklist unavailable`);\n cache.set(upperIssueId, { planResolved: false, criteria: [] });\n return;\n }\n const { mtimeMs } = await stat(found.path);\n const document = await Effect.runPromise(readXBriefDocument(found.path));\n const criteria = extractACFromDocument(document).map((ac) => ({ title: ac.title, status: ac.status }));\n acceptanceCriteriaByIssue.set(upperIssueId, { path: found.path, mtimeMs, criteria });\n cache.set(upperIssueId, { planResolved: true, criteria });\n } catch (err) {\n console.warn(\n `[uat-train] failed to read xBRIEF spec for ${upperIssueId}:`,\n err instanceof Error ? err.message : err,\n );\n cache.set(upperIssueId, { planResolved: false, criteria: [] });\n }\n });\n return cache;\n}\n\n/** The generation chain, newest first, enriched for the UAT batches card. */\nexport async function getUatGenerationsPayload(projectRootOverride?: string): Promise<UatGenerationPayload[]> {\n // PAN-1696: no flywheel-run requirement — list generations directly from store.\n // projectRootOverride lets the aggregate /api/merge-train/generations route read\n // each tracked project's chain instead of only the dashboard's own repo.\n const root = projectRootOverride ? resolve(projectRootOverride) : projectRoot();\n const project = findProjectByPathSync(root);\n const versionSyncConfigured = Boolean(project?.version_sync);\n const chain = listUatGenerationsSync({ projectRoot: root, limit: CHAIN_PAYLOAD_LIMIT });\n if (chain.length === 0) return [];\n const memberIssueIds = new Set(chain.flatMap((gen) => gen.members.map((member) => member.issueId.toUpperCase())));\n // Resolve each member's xBRIEF in ITS OWN project, not the dashboard's repo.\n const [acCache, shipRecords] = await Promise.all([\n loadAcceptanceCriteriaCache(memberIssueIds, root),\n project && versionSyncConfigured ? loadShipRecords(project, chain) : Promise.resolve(new Map()),\n ]);\n const payload: UatGenerationPayload[] = [];\n for (const gen of chain) {\n const probe = await probeUatStack(gen);\n const members: UatGenerationMemberPayload[] = [];\n for (const member of gen.members) {\n const lookup = acCache.get(member.issueId.toUpperCase());\n members.push({\n issueId: member.issueId,\n title: member.title,\n branch: member.branch,\n ...(member.pr !== undefined ? { pr: member.pr } : {}),\n ...(member.prUrl !== undefined ? { prUrl: member.prUrl } : {}),\n mergeOrder: member.mergeOrder,\n acceptanceCriteria: lookup?.criteria ?? [],\n planResolved: lookup?.planResolved ?? false,\n });\n }\n payload.push({\n name: gen.name,\n status: gen.status,\n baseSha: gen.baseSha,\n createdAt: gen.createdAt,\n updatedAt: gen.updatedAt,\n members,\n heldOut: gen.heldOut,\n resolutions: gen.resolutions,\n versionSyncConfigured,\n shipStatus: publicShipStatus(\n versionSyncConfigured ? aggregateGenerationShipStatus(gen, shipRecords) : null,\n ),\n repos: (gen.repos ?? []).map((r) => ({\n repoKey: r.repoKey,\n branch: r.branch,\n baseSha: r.baseSha,\n targetBranch: r.targetBranch,\n mergeOrder: r.mergeOrder,\n promotedAt: r.promotedAt ?? null,\n mergeSha: r.mergeSha ?? null,\n })),\n stack: {\n status: probe.status,\n frontendUrl: probe.frontendUrl,\n ...(probe.downServices ? { downServices: probe.downServices } : {}),\n ...(probe.serviceErrors ? { serviceErrors: probe.serviceErrors } : {}),\n ...(probe.probeError ? { probeError: probe.probeError } : {}),\n },\n });\n }\n return payload;\n}\n\nexport interface UatCandidatePayload {\n branchName: string;\n bundled: string[];\n status: 'ready';\n}\n\n/** The authoritative active UAT candidate, if one is ready to test/ship. */\nexport async function getUatCandidatePayload(): Promise<UatCandidatePayload | null> {\n const [candidate] = listUatGenerationsSync({\n projectRoot: projectRoot(),\n statuses: ['ready'],\n limit: 1,\n });\n if (!candidate) return null;\n return {\n branchName: candidate.name,\n bundled: candidate.members.map((member) => member.issueId),\n status: 'ready',\n };\n}\n\nexport async function postUatGenerationStackPayload(name: string): Promise<\n { ok: true; frontendUrl: string; evicted: string[] } | { ok: false; error: string; status: number }\n> {\n const gen = getUatGenerationSync(name);\n if (!gen) return { ok: false, error: `No UAT generation named ${name}`, status: 404 };\n if (gen.status !== 'ready' && gen.status !== 'superseded') {\n return { ok: false, error: `${name} is ${gen.status} — only live batches can serve a stack`, status: 409 };\n }\n const result = await ensureUatStack(gen);\n if (!result.success) return { ok: false, error: result.error ?? 'stack start failed', status: 500 };\n return { ok: true, frontendUrl: result.frontendUrl!, evicted: result.evicted };\n}\n\nexport async function postUatGenerationPromotePayload(\n name: string,\n // Typed as the full promote contract, not `(issueId) => boolean`: the narrow\n // signature is what let the per-repo evidence be dropped by the forwarding\n // layer without a type error.\n firePostMerge: UatPromoteDeps['firePostMerge'],\n shipVersion?: string,\n): Promise<PromoteResult> {\n // PAN-1696: promote into the generation's OWN project repo. Generation rows carry\n // project_root, so a MIN generation must not be merged against the Overdeck repo.\n // For a generation belonging to this repo this resolves to the same path as before.\n const root = resolve(getUatGenerationSync(name)?.projectRoot ?? projectRoot());\n const [\n { reviewRecordEligibility },\n { recordUatPromotionVerdicts },\n ] = await Promise.all([\n import('../../../lib/flywheel-merge-order.js'),\n import('../../../lib/cloister/uat-promote-verification.js'),\n ]);\n // A polyrepo generation merges into each member repo's own target branch, so\n // it needs per-repo promote git. Supplying it is what selects the two-phase\n // (trial-merge everything, then publish) path.\n const projectConfig = findProjectByPathSync(root);\n const polyrepo = projectConfig?.workspace?.type === 'polyrepo';\n const storedRepos = polyrepo ? (getUatGenerationSync(name)?.repos ?? []) : [];\n\n // Re-check writability against CURRENT config, not the config that was live\n // at assembly: a repo can be flipped to readonly between assembly and merge,\n // and promote pushes to it. Fail closed rather than silently skipping a repo,\n // which would publish a partial batch.\n const writableKeys = polyrepo\n ? new Set((await resolveProjectRepos(root)).filter((r) => r.required).map((r) => r.repoKey))\n : new Set<string>();\n const nowReadOnly = polyrepo ? storedRepos.filter((r) => !writableKeys.has(r.repoKey)) : [];\n if (nowReadOnly.length > 0) {\n const detail = nowReadOnly.map((r) => r.repoKey).join(', ');\n const result: PromoteResult = {\n success: false,\n reason: 'merge-failed',\n message:\n `${name} includes repo(s) that are no longer writable in project config: ${detail}. ` +\n `Nothing was published. Restore write access or let the reconciler rebuild the batch without them.`,\n };\n await notifyFlywheelOfUatPromote(result).catch(() => {});\n return result;\n }\n const generationRepos = storedRepos;\n\n const result = await promoteUatGeneration(name, root, {\n git: buildUatPromoteGitDeps(root),\n ...(polyrepo && generationRepos.length > 0\n ? {\n // generationRepos carry their own persisted targetBranch, so a repo\n // configured for `develop` is fetched, trial-merged, and published\n // there rather than silently defaulting to main.\n polyrepoGit: buildPolyrepoUatPromoteGitDeps(generationRepos),\n markRepoPromoted: (genName, repoKey, at, mergeSha) =>\n markUatGenerationRepoPromotedSync(genName, repoKey, at, mergeSha),\n }\n : {}),\n store: { ...buildUatGenerationStore(), get: (n) => getUatGenerationSync(n) },\n teardownStack: (gen) => teardownUatStack(gen),\n firePostMerge,\n memberEligibility: reviewRecordEligibility,\n recordVerification: (generation, mergeSha) => recordUatPromotionVerdicts(generation, mergeSha),\n ...(projectConfig?.version_sync\n ? {\n runShip: (generation: UatGeneration, requestedVersion: string | undefined) => withGenerationShipLock(\n generation.name,\n async () => {\n // Establish durable batch membership before any command, worktree, or\n // Git operation can fail. Deferred recovery and the DoD gate then\n // remain blocking even when propagation never starts.\n await persistPendingShipRecords(\n generation,\n requestedVersion ? 'version ship in progress' : 'no version supplied at promote time',\n );\n if (!requestedVersion) return;\n\n const report = await executeVersionShipForGeneration({\n generation,\n project: projectConfig,\n version: requestedVersion,\n });\n await persistShipRecords(generation, report);\n },\n ),\n }\n : {}),\n log: (msg) => console.log(msg),\n }, { shipVersion });\n await notifyFlywheelOfUatPromote(result).catch(() => {});\n return result;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"uat-train-COgoJomz.js","names":["basename","dirname","conflictingWith","execFileAsync","resolve","relative","isAbsolute","rm","execFileAsync","defaultDeps","execFileAsync","dirname","join","basename","join","nodeServicesLayer"],"sources":["../../src/lib/cloister/uat-candidate-name.ts","../../src/lib/cloister/uat-union-lint.ts","../../src/lib/cloister/uat-generation-engine.ts","../../src/lib/cloister/uat-generation-deps.ts","../../src/lib/cloister/uat-polyrepo-engine.ts","../../src/lib/cloister/uat-conflict-agent.ts","../../src/lib/cloister/uat-reconciler.ts","../../src/lib/cloister/uat-stack.ts","../../src/lib/cloister/uat-promote.ts","../../src/lib/cloister/uat-promote-notify.ts","../../src/dashboard/server/services/uat-train.ts"],"sourcesContent":["/**\n * UAT candidate branch naming (PAN-1691) — codename + short date.\n *\n * One candidate branch is assembled per label/day and force-reset on rebuild.\n * Format: `uat/<label>-<codename>-<MMDD>`, e.g. `uat/pan-otter-0609`. Codenames\n * are short, memorable words you can say out loud (\"ship the otter candidate\").\n *\n * The core is pure and fully injectable (wordlist / index pick / collision\n * date) so it's deterministically testable. The default codename index is a\n * stable hash of label + day so rebuilds for the same day reuse the same branch.\n */\n\nexport const UAT_CODENAMES: readonly string[] = [\n 'otter', 'falcon', 'cedar', 'quartz', 'ember', 'marlin', 'willow', 'cobalt',\n 'sable', 'heron', 'onyx', 'larch', 'perch', 'flint', 'dune', 'reef', 'vale',\n 'crow', 'moss', 'thorn', 'birch', 'koi', 'slate', 'wren',\n];\n\nfunction mmdd(dateIso: string): string {\n const [, month, day] = dateIso.slice(0, 10).split('-');\n return `${month ?? '00'}${day ?? '00'}`;\n}\n\nexport interface UatNameDeps {\n /** Project label, e.g. 'pan'. */\n label: string;\n /** ISO date string; only the YYYY-MM-DD prefix is used (→ MMDD). */\n dateIso: string;\n /** Injectable wordlist (default UAT_CODENAMES). */\n codenames?: readonly string[];\n /** Returns an index in [0, n) (default: stable hash of label + MMDD). */\n pick?: (n: number) => number;\n}\n\n/**\n * Build the deterministic `uat/<label>-<codename>-<MMDD>` branch name. Pure.\n */\nexport function makeUatCandidateName(deps: UatNameDeps): string {\n const day = mmdd(deps.dateIso);\n const words = deps.codenames ?? UAT_CODENAMES;\n const pick = deps.pick ?? ((n: number) => stableIndex(`${deps.label}-${day}`, n));\n const index = words.length > 0 ? pick(words.length) % words.length : 0;\n const word = words[index] ?? 'candidate';\n return `uat/${deps.label}-${word}-${day}`;\n}\n\n/** Keep the deterministic first name, then mint -2, -3, … around durable collisions. */\nexport function makeUniqueUatCandidateName(deps: UatNameDeps, takenNames: Iterable<string>): string {\n const base = makeUatCandidateName(deps);\n const taken = new Set(takenNames);\n if (!taken.has(base)) return base;\n let suffix = 2;\n while (taken.has(`${base}-${suffix}`)) suffix += 1;\n return `${base}-${suffix}`;\n}\n\nfunction stableIndex(seed: string, modulo: number): number {\n if (modulo <= 0) return 0;\n let hash = 2166136261;\n for (let i = 0; i < seed.length; i++) {\n hash ^= seed.charCodeAt(i);\n hash = Math.imul(hash, 16777619);\n }\n return (hash >>> 0) % modulo;\n}\n","/**\n * Union lint for UAT generation assembly (PAN-3166).\n *\n * Git merges branches by *content*, so two branches that each add a differently\n * named file never conflict — even when the union of those files is invalid.\n * Flyway migrations are the canonical case: `V256__Kaia_session_task_binding.sql`\n * and `V256__Add_author_type_to_task_comment.sql` merge cleanly, each branch's\n * own CI is green because each branch alone has exactly one V256, and the\n * assembled batch then refuses to boot with\n * `FlywayException: Found more than one migration with version 256`.\n *\n * **Colliding is the normal case, not bad luck.** Sequential `V<n>` naming means\n * every branch cut from main picks the next free number, so any two concurrent\n * schema branches collide with certainty. MYN sat at V255 with three separate\n * branches all claiming V256, touching three disjoint tables. Holding a member\n * out for that would cost throughput on every batch carrying two schema changes\n * and buy no safety at all.\n *\n * So the lint dispositions a collision by what the migrations actually touch:\n *\n * - **Provably independent** → deterministically renumber by merge order: the\n * earlier member keeps `V256`, the later takes the next version unused\n * anywhere in the union. The UAT branch content is exactly what promotes to\n * main, so a rename in the union is what lands — the same category of\n * assembly-time resolution as a conflict the assembly agent fixes on branch.\n * - **Anything else** → hold out, and let a human decide the order.\n *\n * **Independence must be proven, and the proof is deliberately strict.** A\n * distinct primary table proves nothing on its own: a child table's `REFERENCES`\n * names a parent, a view or trigger body reads other tables, and a data\n * migration's `INSERT … SELECT` couples two tables that never appear in each\n * other's DDL. So `migrationObjects` collects EVERY object a migration names —\n * targets, `REFERENCES` parents, and DML sources — and any overlap at all holds\n * out. A statement the extractor does not confidently recognize also holds out:\n * unrecognized SQL is unknown coupling, and the safe default is the human.\n *\n * Detection is deliberately PRE-merge — it reads the candidate branch's file\n * list — so a hold-out costs one `git ls-tree` and no rollback. Renames are\n * applied post-merge, when the files exist on the generation branch.\n */\n\n/** A Flyway migration file: `V<version>__<description>.sql`. */\nconst MIGRATION_FILENAME = /^V(\\d+(?:[._]\\d+)*)__(.+\\.sql)$/i;\n\n/** The basename of a path, using forward slashes (git's own separator). */\nfunction basename(path: string): string {\n const slash = path.lastIndexOf('/');\n return slash >= 0 ? path.slice(slash + 1) : path;\n}\n\nfunction dirname(path: string): string {\n const slash = path.lastIndexOf('/');\n return slash >= 0 ? path.slice(0, slash + 1) : '';\n}\n\n/**\n * The Flyway version a path declares, normalized so `V1_1__x.sql` and\n * `V1.1__x.sql` — which Flyway treats as the SAME version — collide here too.\n * Returns null for anything that is not a versioned migration (repeatable `R__`\n * migrations carry no version and cannot collide).\n */\nexport function migrationVersionOf(path: string): string | null {\n const match = MIGRATION_FILENAME.exec(basename(path));\n if (!match?.[1]) return null;\n return match[1].replace(/_/g, '.');\n}\n\n/**\n * The Flyway history a migration belongs to. **One repo is not one version\n * space**: a multi-service repo legitimately runs several independent Flyway\n * histories, each restarting at V1, and two services' `V1__init.sql` are not in\n * conflict. The migration's own directory is that boundary, so every version\n * comparison, reservation, and allocation is scoped to it.\n */\nexport function migrationRootOf(path: string): string {\n return dirname(path);\n}\n\n/** One versioned migration, located in its own Flyway history. */\nexport interface MigrationEntry {\n /** Migration directory — the version namespace this file lives in. */\n root: string;\n version: string;\n path: string;\n}\n\n/** `<root>` and `<version>` joined into one map key. */\nfunction versionKey(root: string, version: string): string {\n return `${root}\u0000${version}`;\n}\n\n/** Every versioned migration in a file list. */\nexport function migrationEntries(files: readonly string[]): MigrationEntry[] {\n const entries: MigrationEntry[] = [];\n for (const path of files) {\n const version = migrationVersionOf(path);\n if (!version) continue;\n entries.push({ root: migrationRootOf(path), version, path });\n }\n return entries;\n}\n\n/**\n * The FIRST migration claiming each `(root, version)` in a file list. A branch\n * listing two files at one version inside one root is that branch's own bug —\n * its own CI sees it — so only the first is considered here.\n */\nfunction firstByVersionKey(files: readonly string[]): Map<string, MigrationEntry> {\n const byKey = new Map<string, MigrationEntry>();\n for (const entry of migrationEntries(files)) {\n const key = versionKey(entry.root, entry.version);\n if (!byKey.has(key)) byKey.set(key, entry);\n }\n return byKey;\n}\n\n// ── Object extraction ────────────────────────────────────────────────────────\n\n/**\n * Statement shapes the extractor understands. Each entry names the leading\n * keywords it matches and every capture group that yields an object name — the\n * statement's target AND anything it couples to. A statement matching none of\n * these is unrecognized, which fails closed.\n */\nconst STATEMENT_RULES: Array<{ pattern: RegExp; objects: number[] }> = [\n { pattern: /^create\\s+(?:unlogged\\s+|temporary\\s+|temp\\s+)?table\\s+(?:if\\s+not\\s+exists\\s+)?([\\w.\"`]+)/i, objects: [1] },\n { pattern: /^alter\\s+table\\s+(?:if\\s+exists\\s+)?(?:only\\s+)?([\\w.\"`]+)/i, objects: [1] },\n { pattern: /^drop\\s+table\\s+(?:if\\s+exists\\s+)?([\\w.\"`]+)/i, objects: [1] },\n { pattern: /^create\\s+(?:unique\\s+)?index\\s+(?:concurrently\\s+)?(?:if\\s+not\\s+exists\\s+)?(?:[\\w.\"`]+\\s+)?on\\s+([\\w.\"`]+)/i, objects: [1] },\n { pattern: /^drop\\s+index\\s+(?:concurrently\\s+)?(?:if\\s+exists\\s+)?([\\w.\"`]+)/i, objects: [1] },\n { pattern: /^create\\s+type\\s+([\\w.\"`]+)/i, objects: [1] },\n { pattern: /^alter\\s+type\\s+([\\w.\"`]+)/i, objects: [1] },\n { pattern: /^drop\\s+type\\s+(?:if\\s+exists\\s+)?([\\w.\"`]+)/i, objects: [1] },\n { pattern: /^insert\\s+into\\s+([\\w.\"`]+)/i, objects: [1] },\n { pattern: /^update\\s+([\\w.\"`]+)\\s+set\\b/i, objects: [1] },\n { pattern: /^delete\\s+from\\s+([\\w.\"`]+)/i, objects: [1] },\n { pattern: /^comment\\s+on\\s+\\w+\\s+([\\w.\"`]+)/i, objects: [1] },\n // Statements with no object at all — recognized, contribute nothing.\n { pattern: /^(?:begin|commit|rollback|start\\s+transaction|set\\s+\\w+|analyze|vacuum)\\b/i, objects: [] },\n];\n\n/**\n * Couplings that can appear ANYWHERE inside a recognized statement and name an\n * object other than its target: a foreign key's parent, and the tables a data\n * migration reads. Missing one of these is exactly the failure mode a\n * primary-table-only check has, so they are collected from the whole body.\n */\nconst COUPLING_PATTERNS: RegExp[] = [\n /\\breferences\\s+([\\w.\"`]+)/gi,\n /\\bfrom\\s+([\\w.\"`]+)/gi,\n /\\bjoin\\s+([\\w.\"`]+)/gi,\n];\n\n/** Strip SQL comments so a commented-out statement never counts as a touch. */\nfunction stripSqlComments(sql: string): string {\n return sql.replace(/\\/\\*[\\s\\S]*?\\*\\//g, ' ').replace(/--[^\\n]*/g, ' ');\n}\n\n/** `\"public\".\"Task_Comment\"` → `task_comment`. */\nfunction normalizeObjectName(raw: string): string {\n const unquoted = raw.replace(/[\"`]/g, '').toLowerCase();\n const parts = unquoted.split('.').filter(Boolean);\n // Any schema qualifier is dropped, so `app.task` and `task` read as the same\n // object. Erring toward \"these might be the same\" only ever holds out.\n return parts[parts.length - 1] ?? unquoted;\n}\n\n/** Statements, split on `;`, with `$$ … $$` bodies kept intact. */\nfunction splitStatements(sql: string): string[] {\n const withoutDollarBodies = sql.replace(/\\$\\$[\\s\\S]*?\\$\\$/g, ' $DOLLARBODY$ ');\n return withoutDollarBodies\n .split(';')\n .map((statement) => statement.replace(/\\s+/g, ' ').trim())\n .filter(Boolean);\n}\n\nexport interface MigrationAnalysis {\n /** Every object the migration names: targets, FK parents, DML sources. */\n objects: Set<string>;\n /**\n * Statements the extractor could not classify. Non-empty means the migration\n * cannot be reasoned about, so its collisions must hold out.\n */\n unrecognized: string[];\n}\n\n/**\n * Everything a migration touches, plus whatever could not be understood.\n *\n * A function or trigger body (`$$ … $$`) is opaque to this extractor and is\n * deliberately reported unrecognized rather than skimmed: its body can read any\n * table in the schema.\n */\nexport function analyzeMigration(sql: string): MigrationAnalysis {\n const body = stripSqlComments(sql);\n const objects = new Set<string>();\n const unrecognized: string[] = [];\n\n for (const statement of splitStatements(body)) {\n if (statement.includes('$DOLLARBODY$')) {\n unrecognized.push(statement.slice(0, 120));\n continue;\n }\n const rule = STATEMENT_RULES.find((candidate) => candidate.pattern.test(statement));\n if (!rule) {\n unrecognized.push(statement.slice(0, 120));\n continue;\n }\n const match = rule.pattern.exec(statement);\n for (const group of rule.objects) {\n const name = normalizeObjectName(match?.[group] ?? '');\n if (name) objects.add(name);\n }\n for (const coupling of COUPLING_PATTERNS) {\n coupling.lastIndex = 0;\n for (const found of statement.matchAll(coupling)) {\n const name = normalizeObjectName(found[1] ?? '');\n if (name) objects.add(name);\n }\n }\n }\n\n return { objects, unrecognized };\n}\n\n// ── Ledger + dispositions ────────────────────────────────────────────────────\n\n/** Who owns a migration version in the assembled union. */\nexport interface MigrationVersionOwner {\n /** Path as it currently stands on the generation branch. */\n path: string;\n /** Null for a migration the batch base already carried. */\n issueId: string | null;\n}\n\n/** One version, within one migration root, claimed by two different files. */\nexport interface MigrationCollision {\n version: string;\n /** Migration directory both files live in — the version namespace. */\n root: string;\n incoming: { path: string; issueId: string };\n existing: MigrationVersionOwner;\n}\n\n/** A migration renamed on the generation branch to break a version collision. */\nexport interface MigrationRename {\n from: string;\n to: string;\n fromVersion: string;\n toVersion: string;\n}\n\nexport type UnionDisposition =\n | { kind: 'ok' }\n | { kind: 'renumber'; renames: MigrationRename[]; note: string }\n | { kind: 'hold-out'; reason: string };\n\n/**\n * The migration versions the assembled union carries, and who contributed each.\n *\n * Two distinct sets, because they answer different questions:\n *\n * - **owners** — versions actually ON the generation branch, which is what a\n * collision is measured against.\n * - **reserved** — every version that exists ANYWHERE in the union universe:\n * the base, plus every candidate branch's migrations, held out or not, plus\n * every number already handed out by a rename. A new number is safe only if\n * it is free in all of them; allocating blindly upward would happily pick a\n * V257 that main already carries or that the same member is about to add.\n */\nexport class MigrationVersionLedger {\n private readonly owners = new Map<string, MigrationVersionOwner>();\n private readonly reserved = new Set<string>();\n\n constructor(baseFiles: readonly string[] = []) {\n this.record(baseFiles, null);\n }\n\n /**\n * Declare versions that exist in the union universe without putting them on\n * the branch — every candidate branch's migrations, before assembly starts.\n */\n reserve(files: readonly string[]): void {\n for (const entry of migrationEntries(files)) this.reserved.add(versionKey(entry.root, entry.version));\n }\n\n /**\n * `(root, version)` pairs this member's files claim that a DIFFERENT file\n * already owns. Same version in a different migration root is a different\n * Flyway history and never collides.\n */\n collisions(files: readonly string[], issueId: string): MigrationCollision[] {\n const found: MigrationCollision[] = [];\n for (const [key, incoming] of firstByVersionKey(files)) {\n // Every branch also re-lists the base migrations — same path, same\n // owner, not a collision. The union lint owns only what the union creates.\n const owner = this.owners.get(key);\n if (!owner || basename(owner.path) === basename(incoming.path)) continue;\n found.push({\n version: incoming.version,\n root: incoming.root,\n incoming: { path: incoming.path, issueId },\n existing: owner,\n });\n }\n return found;\n }\n\n /** Attribute this member's migrations to it. Call only once it is accepted. */\n record(files: readonly string[], issueId: string | null): void {\n for (const [key, entry] of firstByVersionKey(files)) {\n this.reserved.add(key);\n if (this.owners.has(key)) continue;\n this.owners.set(key, { path: entry.path, issueId });\n }\n }\n\n /** Is this version claimed anywhere in the union, within this root? */\n isTaken(root: string, version: string): boolean {\n const key = versionKey(root, version);\n return this.owners.has(key) || this.reserved.has(key);\n }\n\n /**\n * The next plain-integer version free across the WHOLE union *for one\n * migration root*, counting up from a collision. Allocation is immediately\n * reserved, so two renames in one assembly can never land on the same number\n * even if the first is later rolled back — a skipped number costs nothing, a\n * reused one is the bug.\n */\n allocateVersion(root: string, from: number): number {\n let candidate = from + 1;\n while (this.isTaken(root, String(candidate))) candidate += 1;\n this.reserved.add(versionKey(root, String(candidate)));\n return candidate;\n }\n}\n\n/** Plain-integer versions are the only ones safe to renumber sequentially. */\nfunction integerVersion(version: string): number | null {\n return /^\\d+$/.test(version) ? Number(version) : null;\n}\n\n/** Rewrite `…/V256__Kaia.sql` to `…/V257__Kaia.sql`, keeping the description. */\nfunction renamedTo(path: string, toVersion: number): string {\n const match = MIGRATION_FILENAME.exec(basename(path));\n if (!match?.[2]) return path;\n return `${dirname(path)}V${toVersion}__${match[2]}`;\n}\n\nexport interface ResolveUnionArgs {\n ledger: MigrationVersionLedger;\n /** Migration paths the candidate branch carries. */\n files: readonly string[];\n issueId: string;\n /**\n * Read one migration's SQL. `path` is repo-relative; `owner` distinguishes\n * the candidate's own files (still only on its branch) from the ones already\n * on the generation branch.\n */\n readMigration: (path: string, owner: 'incoming' | 'union') => Promise<string>;\n}\n\n/**\n * Decide what to do about this member's version collisions.\n *\n * Renumbering is chosen ONLY when every collision is provably independent:\n * plain-integer versions, both migrations fully recognized, and no shared\n * object between them of any kind. Everything else holds the member out.\n */\nexport async function resolveUnionCollisions(args: ResolveUnionArgs): Promise<UnionDisposition> {\n const collisions = args.ledger.collisions(args.files, args.issueId);\n if (collisions.length === 0) return { kind: 'ok' };\n\n const renames: MigrationRename[] = [];\n for (const collision of collisions) {\n const version = integerVersion(collision.version);\n if (version === null) {\n return {\n kind: 'hold-out',\n reason: holdOutReason(collision, 'its version is not a plain integer, so its ordering is explicit and must not be renumbered'),\n };\n }\n\n let incomingSql: string;\n let existingSql: string;\n try {\n incomingSql = await args.readMigration(collision.incoming.path, 'incoming');\n existingSql = await args.readMigration(collision.existing.path, 'union');\n } catch {\n return { kind: 'hold-out', reason: holdOutReason(collision, 'one of the migrations could not be read, so they cannot be proven independent') };\n }\n\n const incoming = analyzeMigration(incomingSql);\n const existing = analyzeMigration(existingSql);\n const unrecognized = [...incoming.unrecognized, ...existing.unrecognized];\n if (unrecognized.length > 0) {\n return {\n kind: 'hold-out',\n reason: holdOutReason(collision, `SQL the union lint does not recognize (${unrecognized[0]}) could couple them, so independence cannot be proven`),\n };\n }\n if (incoming.objects.size === 0 || existing.objects.size === 0) {\n return { kind: 'hold-out', reason: holdOutReason(collision, 'no database object could be identified in one of them, so they cannot be proven independent') };\n }\n\n const shared = [...incoming.objects].filter((object) => existing.objects.has(object));\n if (shared.length > 0) {\n return {\n kind: 'hold-out',\n reason: holdOutReason(collision, `both touch ${shared.sort().join(', ')} — the order matters and only a human can decide it`),\n };\n }\n\n const toVersion = args.ledger.allocateVersion(collision.root, version);\n renames.push({\n from: collision.incoming.path,\n to: renamedTo(collision.incoming.path, toVersion),\n fromVersion: collision.version,\n toVersion: String(toVersion),\n });\n }\n\n return {\n kind: 'renumber',\n renames,\n note: renames.map((r) => `${basename(r.from)} → ${basename(r.to)}`).join('; '),\n };\n}\n\nfunction holdOutReason(collision: MigrationCollision, why: string): string {\n const existing = collision.existing.issueId\n ? `${basename(collision.existing.path)} (${collision.existing.issueId})`\n : `${basename(collision.existing.path)} (already on the batch base)`;\n return (\n `migration version V${collision.version} collides: ` +\n `${basename(collision.incoming.path)} (${collision.incoming.issueId}) vs ${existing} — ${why}`\n );\n}\n\n/** The file list a member effectively contributes once its renames are applied. */\nexport function applyRenames(files: readonly string[], renames: readonly MigrationRename[]): string[] {\n const byFrom = new Map(renames.map((r) => [r.from, r.to]));\n return files.map((file) => byFrom.get(file) ?? file);\n}\n\n// ── Engine-facing helpers ────────────────────────────────────────────────────\n\n/**\n * The git operations the union lint needs, as a structural subset of\n * `GenerationGitDeps`. All optional: deps that omit `listMigrationFiles` skip\n * the lint entirely, which is the pre-PAN-3166 behaviour.\n */\nexport interface UnionLintPort {\n listMigrationFiles?(ref: string): Promise<string[]>;\n readMigrationFile?(ref: string, path: string): Promise<string>;\n renameMigrations?(renames: ReadonlyArray<{ from: string; to: string }>, message: string): Promise<string>;\n}\n\nexport interface UnionLintPlan {\n /** Migration paths the candidate branch carries, before any renaming. */\n files: string[];\n disposition: UnionDisposition;\n}\n\nconst OK_PLAN: UnionLintPlan = { files: [], disposition: { kind: 'ok' } };\n\n/**\n * Seed a ledger for one repo: the versions already on the generation branch as\n * cut from its target, plus a reservation covering every candidate branch's\n * migrations, so an allocated number can never collide with one a later member\n * (or an unchanged migration on main) already holds.\n */\nexport async function seedUnionLedger(\n git: UnionLintPort,\n generationBranch: string,\n candidateBranches: readonly string[],\n): Promise<MigrationVersionLedger | null> {\n if (!git.listMigrationFiles) return null;\n const list = git.listMigrationFiles.bind(git);\n // The generation branch read is UNGUARDED on purpose (PAN-3166). Swallowing a\n // git error into an empty list would silently disable the lint and let\n // assembly publish another non-bootable batch while reporting success — the\n // exact failure this exists to prevent. Empty-because-unread must never look\n // like empty-because-no-migrations, so this failure fails the assembly.\n const ledger = new MigrationVersionLedger(await list(generationBranch));\n for (const branch of candidateBranches) {\n // A candidate that cannot be read costs only itself: `planUnionLint` hits\n // the same failure and holds that member out, so its versions never enter\n // the union and a missing reservation cannot mis-allocate around it. One\n // broken branch must not fail the whole batch.\n ledger.reserve(await list(branch).catch(() => []));\n }\n return ledger;\n}\n\n/**\n * Lint one candidate branch against the union so far, PRE-merge. Renumbering\n * additionally needs `readMigrationFile` (to prove independence) and\n * `renameMigrations` (to apply it); without either, a collision can only be\n * held out.\n */\nexport async function planUnionLint(args: {\n git: UnionLintPort;\n ledger: MigrationVersionLedger | null;\n /** Generation branch the union lives on — where already-merged files are read from. */\n generationBranch: string;\n /** Candidate branch being considered. */\n featureBranch: string;\n issueId: string;\n}): Promise<UnionLintPlan> {\n const { git, ledger } = args;\n if (!ledger || !git.listMigrationFiles) return OK_PLAN;\n\n // A read failure holds the member out rather than degrading to \"no\n // migrations\" — a guard that disables itself on error is worse than no guard,\n // because it still reports success (PAN-3166).\n let files: string[];\n try {\n files = await git.listMigrationFiles(args.featureBranch);\n } catch (err) {\n return {\n files: [],\n disposition: {\n kind: 'hold-out',\n reason:\n `could not list ${args.issueId}'s migrations on ${args.featureBranch} ` +\n `(${err instanceof Error ? err.message.split('\\n')[0] : String(err)}) — ` +\n `the union lint cannot rule out a migration version collision`,\n },\n };\n }\n if (files.length === 0) return { files, disposition: { kind: 'ok' } };\n\n const read = git.readMigrationFile?.bind(git);\n const canRenumber = Boolean(read && git.renameMigrations);\n const disposition = await resolveUnionCollisions({\n ledger,\n files,\n issueId: args.issueId,\n readMigration: async (path, owner) => {\n if (!canRenumber || !read) throw new Error('renumbering unavailable');\n return read(owner === 'incoming' ? args.featureBranch : args.generationBranch, path);\n },\n });\n return { files, disposition };\n}\n\n/**\n * Apply a renumbering on the generation branch, POST-merge (the files only\n * exist there once the merge landed). Returns the commit SHA, or null when the\n * plan needed no rename.\n */\nexport async function applyUnionLintPlan(\n git: UnionLintPort,\n plan: UnionLintPlan,\n issueId: string,\n): Promise<{ commitSha: string; files: string[]; note: string } | null> {\n if (plan.disposition.kind !== 'renumber' || !git.renameMigrations) return null;\n const { renames, note } = plan.disposition;\n const commitSha = await git.renameMigrations(\n renames.map((r) => ({ from: r.from, to: r.to })),\n `chore(uat): renumber ${issueId} migrations to break a version collision\\n\\n${note}`,\n );\n return { commitSha, files: renames.map((r) => r.to), note };\n}\n\n/** The migration paths a member contributes once its plan has been applied. */\nexport function plannedFiles(plan: UnionLintPlan): string[] {\n return plan.disposition.kind === 'renumber'\n ? applyRenames(plan.files, plan.disposition.renames)\n : [...plan.files];\n}\n","/**\n * UAT generation assembly engine (PAN-1737: UAT batch trains).\n *\n * Builds one *generation* — a deterministic-per-day `uat/<label>-<codename>-<MMDD>` branch\n * off current main containing as many ready features as possible, merged in\n * queue order, with cross-feature conflicts resolved on the branch by the\n * injected `resolveConflict` hook (the assembly agent). A feature whose\n * conflict cannot be resolved is *held out* with a human-readable reason and\n * assembly continues — a single bad merge never blocks the batch.\n *\n * Each rebuild for the same label/day force-resets the same branch + persistent\n * worktree under `<projectRoot>/workspaces/`, keeping one authoritative current\n * UAT candidate instead of proliferating stale branches.\n *\n * Pure orchestrator: all git and store I/O is injected (see\n * uat-generation-deps.ts for the real wiring), so every path — happy,\n * conflict-resolved, held-out, failed — is unit-testable with fake deps.\n */\nimport { makeUniqueUatCandidateName } from './uat-candidate-name.js';\nimport {\n applyUnionLintPlan,\n planUnionLint,\n plannedFiles,\n seedUnionLedger,\n} from './uat-union-lint.js';\nimport type { PolyrepoRepoContribution } from '../flywheel-merge-order.js';\nimport type {\n UatGeneration,\n UatGenerationMember,\n UatGenerationRepo,\n UatGenerationStatus,\n} from '../overdeck/merge-sync.js';\n\n/** A feature eligible for bundling, in merge-queue order. */\nexport interface ReadyFeature {\n issueId: string;\n title: string;\n /** Feature branch, e.g. feature/pan-1704. */\n branch: string;\n pr?: number;\n prUrl?: string;\n /** Issue ids this feature's changed files overlap with (from computeMergeQueue). */\n conflictsWith?: readonly string[];\n /**\n * Member repos this feature actually has a branch in (polyrepo only; the\n * monorepo ready set omits it). Populated by\n * computePolyrepoMergeQueueFromCandidates — the polyrepo engine merges only\n * these repos and skips the rest.\n */\n repoContributions?: readonly PolyrepoRepoContribution[];\n}\n\n/** Context handed to the conflict-resolution hook while the merge is mid-conflict. */\nexport interface ConflictContext {\n feature: ReadyFeature;\n /** Members already merged onto the branch, in order. */\n mergedIssueIds: readonly string[];\n /** The already-merged members this feature is known to overlap with. */\n conflictingIssueIds: readonly string[];\n branchName: string;\n worktreePath: string;\n /**\n * Member repo this conflict occurred in (polyrepo assembly only). Absent for\n * monorepo assembly. The hook already works purely on `worktreePath`, so\n * per-repo worktrees scope it automatically — this only names the repo in the\n * prompt and logs so an operator can tell which one blocked.\n */\n repoKey?: string;\n}\n\n/** Successful resolution: the hook committed the merge on the branch. */\nexport interface ConflictResolutionResult {\n files: string[];\n commitSha: string;\n}\n\nexport interface GenerationGitDeps {\n /** `git fetch origin main` and return the origin/main head SHA. */\n fetchMain(): Promise<string>;\n /** Create or reset the generation worktree branch off origin/main. */\n createWorktree(branchName: string, worktreePath: string): Promise<void>;\n /** Head SHA of a feature branch (origin-first). */\n branchHeadSha(branch: string): Promise<string>;\n /** Whether a UAT branch tip is already contained in the target branch. */\n isBranchContainedInMain?(branch: string): Promise<boolean>;\n /**\n * Merge a feature branch into the generation worktree. On conflict the\n * worktree is left MID-CONFLICT (the hook needs that state); the engine\n * decides whether to resolve or abort.\n */\n mergeBranch(featureBranch: string): Promise<\n { ok: true } | { ok: false; conflict: boolean; reason: string }\n >;\n /** `git merge --abort` in the generation worktree. */\n abortMerge(): Promise<void>;\n /** Push the generation branch to origin. */\n push(branchName: string): Promise<void>;\n /**\n * Files tracked at a git ref that the union lint cares about — today, Flyway\n * migrations (PAN-3166). Optional: deps that omit it skip the union lint\n * entirely, which is exactly the pre-PAN-3166 behaviour.\n */\n listMigrationFiles?(ref: string): Promise<string[]>;\n /** One migration's SQL at a ref, for deciding which objects it touches. */\n readMigrationFile?(ref: string, path: string): Promise<string>;\n /**\n * Rename migrations on the generation branch and commit, breaking a version\n * collision between migrations that touch disjoint objects. Returns the\n * commit SHA so the renumbering is auditable as a resolution.\n */\n renameMigrations?(renames: ReadonlyArray<{ from: string; to: string }>, message: string): Promise<string>;\n}\n\nexport interface GenerationStorePort {\n /** Insert or reset the generation row at assembly start. */\n insert(gen: Omit<UatGeneration, 'createdAt' | 'updatedAt'>): void;\n update(\n name: string,\n // 'repos' is written only by the polyrepo engine; the monorepo path never\n // sets it, so its generations keep reading back as a synthesized N=1 entry.\n patch: Partial<Pick<UatGeneration, 'status' | 'baseSha' | 'members' | 'heldOut' | 'resolutions' | 'cleanedAt' | 'repos'>>,\n ): void;\n /** Existing generation names. */\n listNames(): string[];\n listChain(projectRoot: string, statuses?: readonly UatGenerationStatus[]): UatGeneration[];\n}\n\nexport interface AssembleGenerationInput {\n projectRoot: string;\n /** Project label for the codename, e.g. 'pan'. */\n label: string;\n /** ISO date; MMDD goes into the branch name. */\n dateIso: string;\n /** Ready features in merge-queue order. */\n features: readonly ReadyFeature[];\n /** Branch names (beyond the store) already present, e.g. live git refs. */\n takenBranchNames?: readonly string[];\n}\n\nexport interface AssembleGenerationDeps {\n git: GenerationGitDeps;\n store: GenerationStorePort;\n /**\n * The assembly agent. Called mid-conflict; must either conclude the merge\n * with a commit (returning what it resolved) or return null to give up.\n * Absent hook = every conflict is held out.\n */\n resolveConflict?: (ctx: ConflictContext) => Promise<ConflictResolutionResult | null>;\n log?: (msg: string) => void;\n}\n\n/** Branch name → worktree folder name (slashes collapse to dashes). */\nexport function generationFolderName(branchName: string): string {\n return branchName.replace(/[^a-zA-Z0-9._-]+/g, '-');\n}\n\n/**\n * Assemble the next generation from the given ready set. Always returns the\n * generation row (status 'ready' or 'failed'); throws only on store failures.\n * On success, older 'ready' generations for the project flip to 'superseded'.\n */\nexport async function assembleUatGeneration(\n input: AssembleGenerationInput,\n deps: AssembleGenerationDeps,\n): Promise<UatGeneration> {\n const log = deps.log ?? (() => {});\n\n const baseSha = await deps.git.fetchMain();\n const name = makeUniqueUatCandidateName({\n label: input.label,\n dateIso: input.dateIso,\n }, [...deps.store.listNames(), ...(input.takenBranchNames ?? [])]);\n const worktreePath = `${input.projectRoot}/workspaces/${generationFolderName(name)}`;\n\n deps.store.insert({\n name,\n worktreePath,\n projectRoot: input.projectRoot,\n baseSha,\n status: 'assembling',\n members: [],\n heldOut: [],\n resolutions: [],\n stackStartedAt: null,\n cleanedAt: null,\n });\n\n const members: UatGeneration['members'] = [];\n const heldOut: UatGeneration['heldOut'] = [];\n const resolutions: UatGeneration['resolutions'] = [];\n const finish = (status: UatGenerationStatus): UatGeneration => {\n deps.store.update(name, { status, members, heldOut, resolutions });\n return {\n name, worktreePath, projectRoot: input.projectRoot, baseSha,\n status, members, heldOut, resolutions,\n stackStartedAt: null, cleanedAt: null, createdAt: '', updatedAt: '',\n };\n };\n\n try {\n await deps.git.createWorktree(name, worktreePath);\n } catch (err) {\n log(`[uat-generation] ${name}: worktree creation failed: ${err instanceof Error ? err.message : String(err)}`);\n return finish('failed');\n }\n\n // Union lint (PAN-3166): owners come from the branch as just cut from the\n // target, so a member colliding with a migration already on main is caught\n // too; reservations cover every candidate branch, so an allocated version\n // cannot collide with one a later member already holds. A read failure here\n // fails the generation — a lint that quietly disables itself is worse than\n // none, because assembly would still report ready.\n let ledger: Awaited<ReturnType<typeof seedUnionLedger>>;\n try {\n ledger = await seedUnionLedger(deps.git, name, input.features.map((f) => f.branch));\n } catch (err) {\n log(`[uat-generation] ${name}: union lint could not read migrations: ${err instanceof Error ? err.message : String(err)} — marking failed`);\n return finish('failed');\n }\n\n for (const feature of input.features) {\n const mergedIssueIds = members.map((m) => m.issueId);\n const attemptedHeadSha = async () => deps.git.branchHeadSha(feature.branch).catch(() => 'unknown');\n const recordMember = async (): Promise<UatGenerationMember> => ({\n issueId: feature.issueId,\n title: feature.title,\n branch: feature.branch,\n headSha: await attemptedHeadSha(),\n mergeOrder: members.length + 1,\n ...(feature.pr !== undefined ? { pr: feature.pr } : {}),\n ...(feature.prUrl !== undefined ? { prUrl: feature.prUrl } : {}),\n });\n const holdOut = async (reason: string): Promise<void> => {\n heldOut.push({ issueId: feature.issueId, branch: feature.branch, headSha: await attemptedHeadSha(), reason });\n };\n\n // Union lint runs BEFORE the merge: git would take this branch cleanly\n // (the colliding migrations are different filenames), and the batch would\n // only fail at Flyway startup, long after assembly reported ready. A\n // collision between migrations touching disjoint objects is renumbered\n // after the merge lands; only a same-object collision holds the member out.\n const plan = await planUnionLint({\n git: deps.git,\n ledger,\n generationBranch: name,\n featureBranch: feature.branch,\n issueId: feature.issueId,\n });\n if (plan.disposition.kind === 'hold-out') {\n log(`[uat-generation] ${name}: holding ${feature.issueId} out — ${plan.disposition.reason}`);\n await holdOut(plan.disposition.reason);\n deps.store.update(name, { members, heldOut, resolutions });\n continue;\n }\n\n /**\n * Land any renumbering and record the member's (possibly renamed) files.\n * Returns false when a planned rename did not land: the branch would then\n * carry the very collision the lint exists to prevent, and this engine has\n * no post-merge rollback, so the caller abandons the generation rather than\n * publish a union known not to boot.\n */\n const settleUnionLint = async (): Promise<boolean> => {\n let applied: Awaited<ReturnType<typeof applyUnionLintPlan>>;\n try {\n applied = await applyUnionLintPlan(deps.git, plan, feature.issueId);\n } catch (err) {\n log(`[uat-generation] ${name}: renumbering ${feature.issueId} failed: ${err instanceof Error ? err.message : String(err)}`);\n return false;\n }\n if (applied) {\n log(`[uat-generation] ${name}: renumbered ${feature.issueId} migrations — ${applied.note}`);\n resolutions.push({\n issueIds: [feature.issueId],\n files: applied.files,\n commitSha: applied.commitSha,\n kind: 'migration-renumber',\n note: applied.note,\n });\n } else if (plan.disposition.kind === 'renumber') {\n log(`[uat-generation] ${name}: ${feature.issueId} needed renumbering but no rename primitive is wired`);\n return false;\n }\n ledger?.record(plannedFiles(plan), feature.issueId);\n return true;\n };\n\n let result: Awaited<ReturnType<GenerationGitDeps['mergeBranch']>>;\n try {\n result = await deps.git.mergeBranch(feature.branch);\n } catch (err) {\n await deps.git.abortMerge().catch(() => {});\n await holdOut(`merge failed: ${err instanceof Error ? err.message.split('\\n')[0] : String(err)}`);\n deps.store.update(name, { members, heldOut, resolutions });\n continue;\n }\n\n if (result.ok) {\n if (!(await settleUnionLint())) return finish('failed');\n members.push(await recordMember());\n deps.store.update(name, { members, heldOut, resolutions });\n continue;\n }\n\n if (!result.conflict || !deps.resolveConflict) {\n await deps.git.abortMerge().catch(() => {});\n await holdOut(result.conflict\n ? `conflicts with ${conflictingWith(feature, mergedIssueIds).join(', ') || 'an earlier member'} — no assembly agent available`\n : result.reason);\n deps.store.update(name, { members, heldOut, resolutions });\n continue;\n }\n\n const conflictingIssueIds = conflictingWith(feature, mergedIssueIds);\n log(`[uat-generation] ${name}: resolving conflict ${feature.issueId} <-> ${conflictingIssueIds.join(', ') || '(unknown member)'}`);\n let resolution: ConflictResolutionResult | null = null;\n try {\n resolution = await deps.resolveConflict({\n feature,\n mergedIssueIds,\n conflictingIssueIds,\n branchName: name,\n worktreePath,\n });\n } catch (err) {\n log(`[uat-generation] ${name}: conflict agent threw: ${err instanceof Error ? err.message : String(err)}`);\n resolution = null;\n }\n\n if (resolution) {\n if (!(await settleUnionLint())) return finish('failed');\n members.push(await recordMember());\n resolutions.push({\n issueIds: [feature.issueId, ...conflictingIssueIds],\n files: resolution.files,\n commitSha: resolution.commitSha,\n kind: 'conflict',\n });\n } else {\n await deps.git.abortMerge().catch(() => {});\n await holdOut(`conflict with ${conflictingIssueIds.join(', ') || 'an earlier member'} could not be auto-resolved — waits for the next generation`);\n }\n deps.store.update(name, { members, heldOut, resolutions });\n }\n\n if (members.length === 0) {\n log(`[uat-generation] ${name}: nothing merged (${heldOut.length} held out) — marking failed`);\n return finish('failed');\n }\n\n try {\n await deps.git.push(name);\n } catch (err) {\n log(`[uat-generation] ${name}: push failed: ${err instanceof Error ? err.message : String(err)}`);\n return finish('failed');\n }\n\n // This generation is now the current one; older ready generations remain\n // testable but are no longer the freshest — flip them to superseded.\n for (const older of deps.store.listChain(input.projectRoot, ['ready'])) {\n if (older.name !== name) deps.store.update(older.name, { status: 'superseded' });\n }\n\n log(`[uat-generation] ${name}: ready — ${members.length} member(s), ${resolutions.length} resolution(s), ${heldOut.length} held out`);\n return finish('ready');\n}\n\nfunction conflictingWith(feature: ReadyFeature, mergedIssueIds: readonly string[]): string[] {\n const merged = new Set(mergedIssueIds.map((id) => id.toUpperCase()));\n return (feature.conflictsWith ?? []).filter((id) => merged.has(id.toUpperCase()));\n}\n\nexport interface GenerationCleanupDeps {\n store: GenerationStorePort;\n /** Remove the generation's worktree directory (idempotent). */\n removeWorktree(worktreePath: string): Promise<void>;\n /** Delete the generation branch locally and on origin (idempotent). */\n deleteBranch(branchName: string): Promise<void>;\n /** Tear down the generation's live stack if one is running. */\n teardownStack?: (generation: UatGeneration) => Promise<void>;\n /**\n * Remove ONE member repo's worktree and branch (local + remote), for a\n * polyrepo generation (PAN-3093). Its PRESENCE selects per-repo teardown:\n * every generation reads back with at least one `repos` entry (monorepo rows\n * synthesize one), so a repo count cannot distinguish the two, and a polyrepo\n * generation with a single contributing repo still needs per-repo handling.\n * Omit it and cleanup behaves exactly as it did before.\n */\n removeRepoArtifacts?: (repo: UatGenerationRepo) => Promise<void>;\n /**\n * Everything left once the recorded repos are clean: the wrapper folder that\n * held the per-repo worktrees, plus the generation branch in any member repo\n * the generation stopped using (a hold-out can drop a repo after its branch\n * was already created locally — that branch is never pushed, but it lingers).\n */\n removeGenerationResidue?: (\n generation: UatGeneration,\n /** Repos the generation recorded — already cleaned by removeRepoArtifacts. */\n recordedRepoKeys?: readonly string[],\n ) => Promise<void>;\n log?: (msg: string) => void;\n}\n\n/**\n * Reap dead generations' branches/worktrees, and trim the live chain to the\n * newest `keep` (default 3). Trimmed superseded generations flip to\n * 'invalidated' (their branch is gone — no longer testable). Rows are never\n * deleted: the chain is the audit trail. Promoted generations keep their row\n * but lose branch/worktree/stack — their content lives on main now.\n */\nexport async function cleanupUatGenerations(\n projectRoot: string,\n deps: GenerationCleanupDeps,\n options: { keep?: number } = {},\n): Promise<void> {\n const log = deps.log ?? (() => {});\n const keep = options.keep ?? 3;\n\n const live = deps.store.listChain(projectRoot, ['ready', 'superseded']);\n const trimmed = live.slice(keep);\n const dead = deps.store.listChain(projectRoot, ['invalidated', 'promoted', 'failed'])\n .filter((gen) => !gen.cleanedAt);\n\n for (const gen of [...trimmed, ...dead]) {\n let cleaned = true;\n if (gen.stackStartedAt && deps.teardownStack) {\n await deps.teardownStack(gen).catch((err) => {\n cleaned = false;\n log(`[uat-generation] cleanup ${gen.name}: stack teardown failed: ${err instanceof Error ? err.message : String(err)}`);\n });\n }\n if (deps.removeRepoArtifacts) {\n // Polyrepo: one worktree and one branch per member repo, then the wrapper\n // folder. Every repo is attempted even after one fails, so a single bad\n // repo cannot strand the others' artifacts; cleanedAt still stays unset.\n for (const repo of gen.repos ?? []) {\n await deps.removeRepoArtifacts(repo).catch((err) => {\n cleaned = false;\n log(`[uat-generation] cleanup ${gen.name}: ${repo.repoKey} artifact removal failed: ${err instanceof Error ? err.message : String(err)}`);\n });\n }\n await deps.removeGenerationResidue?.(gen, (gen.repos ?? []).map((r) => r.repoKey)).catch((err) => {\n cleaned = false;\n log(`[uat-generation] cleanup ${gen.name}: generation folder removal failed: ${err instanceof Error ? err.message : String(err)}`);\n });\n } else {\n await deps.removeWorktree(gen.worktreePath).catch((err) => {\n cleaned = false;\n log(`[uat-generation] cleanup ${gen.name}: worktree removal failed: ${err instanceof Error ? err.message : String(err)}`);\n });\n await deps.deleteBranch(gen.name).catch((err) => {\n cleaned = false;\n log(`[uat-generation] cleanup ${gen.name}: branch deletion failed: ${err instanceof Error ? err.message : String(err)}`);\n });\n }\n if (!cleaned) continue;\n const cleanedAt = new Date().toISOString();\n if (gen.status === 'ready' || gen.status === 'superseded') {\n deps.store.update(gen.name, { status: 'invalidated', cleanedAt });\n log(`[uat-generation] cleanup: trimmed ${gen.name} (beyond newest ${keep})`);\n } else {\n deps.store.update(gen.name, { cleanedAt });\n }\n }\n}\n","/**\n * Real git + store wiring for the UAT generation engine (PAN-1737).\n *\n * Generation worktrees are PERSISTENT under `<projectRoot>/workspaces/` — the\n * live UAT stack serves from them, and the deterministic daily folder name yields the Traefik\n * host (`uat-<label>-<codename>-<mmdd>.overdeck.localhost`) via the standard\n * FEATURE_FOLDER devcontainer template.\n *\n * Pure I/O — exercised live; the orchestrator (uat-generation-engine.ts) holds\n * the tested logic.\n */\nimport { execFile } from 'child_process';\nimport { rm } from 'fs/promises';\nimport { isAbsolute, relative, resolve } from 'path';\nimport { promisify } from 'util';\nimport {\n insertUatGenerationSync,\n listUatGenerationNamesSync,\n listUatGenerationsSync,\n updateUatGenerationSync,\n} from '../overdeck/merge-sync.js';\nimport type { GenerationGitDeps, GenerationStorePort } from './uat-generation-engine.js';\nimport type { PolyrepoRepoGit } from './uat-polyrepo-engine.js';\nimport type { ResolvedProjectRepo } from '../project-repos.js';\n\nconst execFileAsync = promisify(execFile);\n\nconst runGit = (args: string[], cwd: string) =>\n execFileAsync('git', args, { cwd, maxBuffer: 16 * 1024 * 1024 });\n\n/**\n * Validate a branch name against its expected namespace.\n *\n * `feature` accepts a CONFIGURED prefix, not a hardcoded `feature/`: a member\n * repo may set `branch_prefix: feat/`, and rejecting that made every one of its\n * contributions throw and get held out. The character class is unchanged, so\n * shell metacharacters and path traversal are still refused.\n */\nexport function safeBranchName(\n branchName: string,\n prefix: 'feature' | 'uat',\n featurePrefix = 'feature/',\n): string {\n const namespace = prefix === 'uat' ? 'uat/' : featurePrefix;\n const escaped = namespace.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n const pattern = new RegExp(`^${escaped}[A-Za-z0-9][A-Za-z0-9._-]*$`);\n if (!pattern.test(branchName)) {\n throw new Error(`unsafe ${prefix} branch name: ${branchName}`);\n }\n return branchName;\n}\n\nexport function safeGenerationWorktreePath(projectRoot: string, worktreePath: string): string {\n const workspacesRoot = resolve(projectRoot, 'workspaces');\n const target = resolve(worktreePath);\n const rel = relative(workspacesRoot, target);\n if (rel === '' || rel.startsWith('..') || isAbsolute(rel)) {\n throw new Error(`unsafe UAT worktree path outside project workspaces: ${worktreePath}`);\n }\n return target;\n}\n\n/** Store port backed by uat-generations-db. */\nexport function buildUatGenerationStore(): GenerationStorePort {\n return {\n insert: (gen) => { insertUatGenerationSync(gen); },\n update: (name, patch) => { updateUatGenerationSync(name, patch); },\n listNames: () => listUatGenerationNamesSync(),\n listChain: (projectRoot, statuses) =>\n listUatGenerationsSync({ projectRoot, ...(statuses ? { statuses } : {}) }),\n };\n}\n\nexport interface UatGenerationGitDepsOptions {\n /**\n * Root whose `workspaces/` directory may hold the worktree. Defaults to\n * `projectRoot`. A polyrepo member repo runs git in its own git root but\n * places its worktree under the WRAPPER project's `workspaces/`, so the two\n * roots differ and the path check has to be told which one governs.\n */\n workspacesRoot?: string;\n /** Branch the generation is cut from and merged back into. Defaults to `main`. */\n targetBranch?: string;\n /**\n * Namespace this repo's FEATURE branches live in, e.g. `feat/`. Defaults to\n * `feature/`. Comes from the repo's configured `branch_prefix`; a mismatch\n * makes every contribution fail validation and get held out.\n */\n featureBranchPrefix?: string;\n}\n\n/**\n * Git deps for one assembly run. The worktree path is bound on createWorktree\n * and reused by the merge/push operations that follow.\n */\nexport function buildUatGenerationGitDeps(\n projectRoot: string,\n options: UatGenerationGitDepsOptions = {},\n): GenerationGitDeps {\n let worktreePath = '';\n const workspacesRoot = options.workspacesRoot ?? projectRoot;\n const targetBranch = options.targetBranch ?? 'main';\n const originTarget = `origin/${targetBranch}`;\n const featurePrefix = options.featureBranchPrefix ?? 'feature/';\n\n return {\n fetchMain: async () => {\n await runGit(['fetch', 'origin', targetBranch], projectRoot);\n const { stdout } = await runGit(['rev-parse', originTarget], projectRoot);\n return stdout.trim();\n },\n\n createWorktree: async (branchName, path) => {\n const safeBranch = safeBranchName(branchName, 'uat');\n worktreePath = safeGenerationWorktreePath(workspacesRoot, path);\n // A leftover worktree at this path means a previous assembly of the SAME\n // name crashed mid-build (names are collision-checked) — reclaim it.\n await runGit(['worktree', 'remove', '--force', worktreePath], projectRoot).catch(() => {});\n await runGit(['worktree', 'prune'], projectRoot).catch(() => {});\n await runGit(['worktree', 'add', '-B', safeBranch, worktreePath, originTarget], projectRoot);\n },\n\n branchHeadSha: async (branch) => {\n const safeBranch = safeBranchName(branch, 'feature', featurePrefix);\n const tryRef = async (ref: string) => (await runGit(['rev-parse', ref], projectRoot)).stdout.trim();\n return tryRef(`origin/${safeBranch}`).catch(() => tryRef(safeBranch));\n },\n\n isBranchContainedInMain: async (branch) => {\n const safeBranch = safeBranchName(branch, 'uat');\n const ref = await runGit(['rev-parse', '--verify', `origin/${safeBranch}`], projectRoot)\n .then(() => `origin/${safeBranch}`)\n .catch(() => safeBranch);\n return runGit(['merge-base', '--is-ancestor', ref, originTarget], projectRoot)\n .then(() => true)\n .catch(() => false);\n },\n\n mergeBranch: async (featureBranch) => {\n // Prefer the origin ref — feature branches are pushed by work agents and\n // the local ref may lag.\n const safeFeatureBranch = safeBranchName(featureBranch, 'feature', featurePrefix);\n const originRef = `origin/${safeFeatureBranch}`;\n const ref = await runGit(['rev-parse', '--verify', originRef], worktreePath)\n .then(() => originRef)\n .catch(() => safeFeatureBranch);\n try {\n await runGit(['merge', '--no-edit', ref], worktreePath);\n return { ok: true as const };\n } catch (err) {\n const { stdout } = await runGit(['ls-files', '-u'], worktreePath).catch(() => ({ stdout: '' }));\n const conflict = stdout.trim().length > 0;\n // Leave the worktree mid-conflict — the engine decides whether the\n // assembly agent gets a shot before aborting.\n return {\n ok: false as const,\n conflict,\n reason: err instanceof Error ? (err.message.split('\\n')[0] ?? 'merge failed') : String(err),\n };\n }\n },\n\n abortMerge: async () => {\n await runGit(['merge', '--abort'], worktreePath).catch(() => {});\n },\n\n push: async (branchName) => {\n await runGit(['push', '-u', '--force-with-lease', 'origin', safeBranchName(branchName, 'uat')], worktreePath);\n },\n\n /**\n * Flyway migrations tracked at a ref, for the union lint (PAN-3166). Reads\n * the ref rather than a worktree so a candidate can be linted before it is\n * merged.\n *\n * Feature branches resolve origin-first for the same reason mergeBranch\n * prefers it — work agents push, and the local ref may lag. The GENERATION\n * branch resolves locally only: its name is deterministic per day and\n * force-pushed, so `origin/uat/…` can still hold yesterday's tree while the\n * local ref is the one assembly just cut.\n */\n listMigrationFiles: async (ref) => {\n const safeRef = safeRefName(ref);\n const resolved = safeRef.startsWith('uat/')\n ? safeRef\n : await runGit(['rev-parse', '--verify', `origin/${safeRef}`], projectRoot)\n .then(() => `origin/${safeRef}`)\n .catch(() => safeRef);\n const { stdout } = await runGit(['ls-tree', '-r', '--name-only', resolved], projectRoot);\n return stdout\n .split('\\n')\n .map((line) => line.trim())\n .filter((path) => /(^|\\/)V\\d[\\w.]*__.+\\.sql$/i.test(path));\n },\n\n /** One migration's SQL at a ref, so the lint can see what it touches. */\n readMigrationFile: async (ref, path) => {\n const safeRef = safeRefName(ref);\n const resolved = safeRef.startsWith('uat/')\n ? safeRef\n : await runGit(['rev-parse', '--verify', `origin/${safeRef}`], projectRoot)\n .then(() => `origin/${safeRef}`)\n .catch(() => safeRef);\n const { stdout } = await runGit(['show', `${resolved}:${safeRepoPath(path)}`], projectRoot);\n return stdout;\n },\n\n /**\n * Renumber migrations on the generation branch and commit. `git mv` keeps\n * the rename visible in history, and the commit lands on the branch that\n * promotes to main — so what the operator UATs is exactly what ships.\n */\n renameMigrations: async (renames, message) => {\n if (!worktreePath) throw new Error('renameMigrations before createWorktree');\n for (const { from, to } of renames) {\n await runGit(['mv', safeRepoPath(from), safeRepoPath(to)], worktreePath);\n }\n await runGit(['commit', '--no-verify', '-m', message], worktreePath);\n const { stdout } = await runGit(['rev-parse', 'HEAD'], worktreePath);\n return stdout.trim();\n },\n };\n}\n\n/**\n * A repo-relative path safe to hand to git: no leading dash (git would read it\n * as a flag), no absolute path, no traversal out of the repo.\n */\nfunction safeRepoPath(path: string): string {\n if (!/^[A-Za-z0-9_][A-Za-z0-9._/-]*$/.test(path) || path.includes('..')) {\n throw new Error(`unsafe repo path: ${path}`);\n }\n return path;\n}\n\n/**\n * A ref safe to pass as a git argument: no leading dash (which git would read\n * as a flag), no path traversal, no shell metacharacters. Accepts both `uat/…`\n * and any configured feature namespace, so the union lint can read either.\n */\nfunction safeRefName(ref: string): string {\n if (!/^[A-Za-z0-9][A-Za-z0-9._/-]*$/.test(ref) || ref.includes('..')) {\n throw new Error(`unsafe git ref: ${ref}`);\n }\n return ref;\n}\n\n/**\n * The namespace part of a configured source branch: `feat/min-1` -> `feat/`.\n *\n * Exported because EVERY construction of the feature-branch adapter must use\n * the same namespace — assembly and the reconciler's anchor path both build one,\n * and a mismatch makes the reconciler reject a branch assembly just accepted.\n */\nexport function featureNamespaceOf(sourceBranch: string): string {\n const slash = sourceBranch.lastIndexOf('/');\n return slash >= 0 ? sourceBranch.slice(0, slash + 1) : 'feature/';\n}\n\n/**\n * Per-repo git deps for a polyrepo assembly, keyed by repoKey (PAN-3093).\n * Each repo runs git in its own root but writes its worktree under the wrapper\n * project's `workspaces/`, and cuts from its own configured target branch.\n */\nexport function buildPolyrepoGitDeps(\n repos: readonly ResolvedProjectRepo[],\n /** Containment-only callers include read-only repos recorded on old generations. */\n options: { includeReadOnly?: boolean } = {},\n): Map<string, PolyrepoRepoGit> {\n return new Map(\n repos\n // A repo configured `readonly: true` (required === false) is never a\n // write target. Assembly uses the default filter; terminal containment\n // opts in because historical generations can record read-only repos.\n .filter((repo) => repo.required || options.includeReadOnly)\n .map((repo) => {\n let worktreePath = '';\n let generationBranch = '';\n const base = buildUatGenerationGitDeps(repo.repoPath, {\n workspacesRoot: repo.projectPath,\n targetBranch: repo.targetBranch,\n // From this repo's own configured branch_prefix.\n featureBranchPrefix: featureNamespaceOf(repo.sourceBranch),\n });\n\n const git: PolyrepoRepoGit = {\n ...base,\n createWorktree: async (branchName, path) => {\n await base.createWorktree(branchName, path);\n generationBranch = safeBranchName(branchName, 'uat');\n worktreePath = safeGenerationWorktreePath(repo.projectPath, path);\n },\n generationHeadSha: async () =>\n (await runGit(['rev-parse', 'HEAD'], worktreePath)).stdout.trim(),\n /**\n * Undo the merges applied since `sha` by re-pointing the generation\n * branch at it. `git reset --hard` is forbidden repo-wide, and\n * `checkout -B` reaches the same state: the branch moves, the\n * worktree follows, and nothing outside this throwaway branch moves.\n */\n resetGenerationTo: async (sha) => {\n if (!/^[0-9a-f]{7,40}$/i.test(sha)) throw new Error(`unsafe rollback sha: ${sha}`);\n if (!generationBranch) throw new Error('rollback before createWorktree');\n await runGit(['checkout', '-B', generationBranch, sha], worktreePath);\n },\n };\n return [repo.repoKey, git] as const;\n }),\n );\n}\n\n/**\n * Union of uat/* branches across every member repo. A generation name collides\n * if ANY member repo already has it, since the name is shared across repos.\n */\nexport async function listRemoteUatBranchesMulti(\n repos: readonly ResolvedProjectRepo[],\n): Promise<string[]> {\n const perRepo = await Promise.all(repos.map((repo) => listRemoteUatBranches(repo.repoPath)));\n return [...new Set(perRepo.flat())];\n}\n\n/** Branch names currently present on origin under uat/* — naming collision input. */\nexport async function listRemoteUatBranches(projectRoot: string): Promise<string[]> {\n const { stdout } = await runGit(['ls-remote', '--heads', 'origin', 'uat/*'], projectRoot).catch(() => ({ stdout: '' }));\n return stdout\n .split('\\n')\n .map((line) => line.split('\\t')[1] ?? '')\n .filter((ref) => ref.startsWith('refs/heads/'))\n .map((ref) => ref.slice('refs/heads/'.length));\n}\n\n/**\n * Polyrepo cleanup deps (PAN-3093). Each member repo owns its own worktree and\n * branch, and the wrapper folder holds them all.\n *\n * `removeGenerationResidue` also deletes the generation branch from EVERY\n * configured member repo, not just the ones the generation recorded: a global\n * hold-out can drop a repo after its branch was already created locally. That\n * branch is never pushed, but it would linger. Deleting a branch that does not\n * exist is a no-op, so the sweep is safe.\n */\nexport function buildPolyrepoCleanupGit(\n repos: readonly ResolvedProjectRepo[],\n projectPath: string,\n): {\n removeWorktree(worktreePath: string): Promise<void>;\n deleteBranch(branchName: string): Promise<void>;\n removeRepoArtifacts(repo: { repoKey: string; repoPath: string; branch: string; worktreePath: string }): Promise<void>;\n removeGenerationResidue(\n generation: { name: string; worktreePath: string },\n recordedRepoKeys?: readonly string[],\n ): Promise<void>;\n} {\n // Cleanup deletes branches locally AND on the remote, so a read-only repo must\n // be excluded here for the same reason it is excluded from assembly.\n const writable = repos.filter((repo) => repo.required);\n const byKey = new Map(writable.map((r) => [r.repoKey, r]));\n\n const deleteBranchIn = async (repoPath: string, branchName: string) => {\n const safeBranch = safeBranchName(branchName, 'uat');\n await runGit(['branch', '-D', safeBranch], repoPath).catch(() => {});\n await runGit(['push', 'origin', '--delete', safeBranch], repoPath).catch(() => {});\n };\n\n return {\n // Unused on the polyrepo path (removeRepoArtifacts supersedes them) but the\n // deps contract still requires them.\n removeWorktree: async () => {},\n deleteBranch: async () => {},\n\n removeRepoArtifacts: async (repo) => {\n // Unknown key here means the repo is not in the writable set — refuse\n // rather than fall back to the caller-supplied path.\n const configured = byKey.get(repo.repoKey);\n if (!configured) {\n throw new Error(`[uat-cleanup] refusing to mutate non-writable or unknown repo: ${repo.repoKey}`);\n }\n const repoPath = configured.repoPath;\n const safePath = safeGenerationWorktreePath(projectPath, repo.worktreePath);\n await runGit(['worktree', 'remove', '--force', safePath], repoPath).catch(() => {});\n await runGit(['worktree', 'prune'], repoPath).catch(() => {});\n await deleteBranchIn(repoPath, repo.branch);\n },\n\n removeGenerationResidue: async (generation, recordedRepoKeys = []) => {\n // Only repos the generation did NOT record: removeRepoArtifacts already\n // deleted the branch for every recorded repo, and repeating it costs a\n // full remote handshake per repo before its expected failure.\n const recorded = new Set(recordedRepoKeys);\n for (const repo of writable) {\n if (recorded.has(repo.repoKey)) continue;\n await deleteBranchIn(repo.repoPath, generation.name);\n await runGit(['worktree', 'prune'], repo.repoPath).catch(() => {});\n }\n // Validate before deleting: this is a recursive removal, and the check\n // refuses anything outside <projectPath>/workspaces.\n const safeFolder = safeGenerationWorktreePath(projectPath, generation.worktreePath);\n await rm(safeFolder, { recursive: true, force: true });\n },\n };\n}\n\n/** Cleanup deps: remove a generation worktree and delete its branch everywhere. */\nexport function buildUatGenerationCleanupGit(projectRoot: string): {\n removeWorktree(worktreePath: string): Promise<void>;\n deleteBranch(branchName: string): Promise<void>;\n} {\n return {\n removeWorktree: async (path) => {\n const safePath = safeGenerationWorktreePath(projectRoot, path);\n await runGit(['worktree', 'remove', '--force', safePath], projectRoot).catch(() => {});\n await runGit(['worktree', 'prune'], projectRoot).catch(() => {});\n },\n deleteBranch: async (branchName) => {\n const safeBranch = safeBranchName(branchName, 'uat');\n await runGit(['branch', '-D', safeBranch], projectRoot).catch(() => {});\n await runGit(['push', 'origin', '--delete', safeBranch], projectRoot).catch(() => {});\n },\n };\n}\n","/**\n * Polyrepo UAT generation assembly (PAN-3093).\n *\n * The single-repo engine (uat-generation-engine.ts) builds one branch off one\n * main in one worktree. A polyrepo project's generation is an N-tuple: one\n * `uat/<label>-<codename>-<MMDD>` branch per member repo that some ready\n * feature actually contributes to, each cut from that repo's own target head.\n * This module orchestrates that; the single-repo engine is untouched and its\n * `GenerationGitDeps` contract is reused verbatim, one instance per repo.\n *\n * Two rules distinguish it from N independent assemblies:\n *\n * - **Global hold-out.** A feature that cannot be merged and resolved in ANY\n * repo is held out of the WHOLE generation. Testers must never see a\n * feature half-applied across repos.\n * - **Atomic per feature, with rollback.** A feature applies to every repo it\n * contributes to or to none. Each target repo's head is captured first, and\n * a repo that already merged the feature is re-pointed at that head when a\n * later repo rejects it. Accepted features are never rebuilt, so the work\n * is linear in repos x features.\n *\n * Pure orchestrator: all git and store I/O is injected, so every path — happy,\n * conflict-resolved, held-out, push-failed — is unit-testable with fake deps.\n */\n\nimport { makeUniqueUatCandidateName } from './uat-candidate-name.js';\nimport { generationFolderName } from './uat-generation-engine.js';\nimport {\n applyUnionLintPlan,\n planUnionLint,\n plannedFiles,\n seedUnionLedger,\n type MigrationVersionLedger,\n type UnionLintPlan,\n} from './uat-union-lint.js';\nimport type {\n ConflictContext,\n ConflictResolutionResult,\n GenerationGitDeps,\n GenerationStorePort,\n ReadyFeature,\n} from './uat-generation-engine.js';\nimport type {\n UatGeneration,\n UatGenerationHeldOut,\n UatGenerationMember,\n UatGenerationMemberRepo,\n UatGenerationRepo,\n UatGenerationResolution,\n UatGenerationStatus,\n} from '../overdeck/merge-sync.js';\nimport type { ResolvedProjectRepo } from '../project-repos.js';\n\nexport interface PolyrepoAssembleInput {\n /** Wrapper project path. Not a git repo; it owns `workspaces/`. */\n projectRoot: string;\n /** Project label for the codename, e.g. 'min'. */\n label: string;\n /** ISO date; MMDD goes into the branch name. */\n dateIso: string;\n /**\n * Ready features in merge-queue order. Each carries `repoContributions`\n * naming the member repos it has a branch in (see\n * computePolyrepoMergeQueueFromCandidates).\n */\n features: readonly ReadyFeature[];\n /** Every configured member repo for the project. */\n repos: readonly ResolvedProjectRepo[];\n takenBranchNames?: readonly string[];\n}\n\n/**\n * One member repo's git, plus the two primitives per-feature atomicity needs.\n *\n * Without them the only way to un-apply a feature that failed in a later repo\n * is to rebuild every repo branch from base and replay the survivors — O(R×F²)\n * heavyweight git work, which for a 20-feature batch across 3 repos is hundreds\n * of merges and can hold the project's single-flight reconcile slot for hours.\n */\nexport interface PolyrepoRepoGit extends GenerationGitDeps {\n /** Current head of this repo's generation branch. */\n generationHeadSha(): Promise<string>;\n /** Move this repo's generation branch back to a previously captured head. */\n resetGenerationTo(sha: string): Promise<void>;\n}\n\nexport interface PolyrepoAssembleDeps {\n /** One PolyrepoRepoGit per repoKey — see buildPolyrepoGitDeps. */\n repoGit: ReadonlyMap<string, PolyrepoRepoGit>;\n store: GenerationStorePort;\n /**\n * The assembly agent, called mid-conflict with the conflicting repo named in\n * `repoKey`. Absent hook = every conflict is held out.\n */\n resolveConflict?: (ctx: ConflictContext) => Promise<ConflictResolutionResult | null>;\n log?: (msg: string) => void;\n}\n\n/** A feature's merge outcome inside one repo during a single assembly pass. */\ninterface RepoMergeOutcome {\n repoKey: string;\n headSha: string;\n mergeOrderInRepo: number;\n resolution?: ConflictResolutionResult;\n /** Union-lint renumbering applied on this repo's branch (PAN-3166). */\n renumber?: { files: string[]; commitSha: string; note: string };\n conflictingIssueIds: string[];\n}\n\n/** A feature that could not be applied everywhere and is held out globally. */\ninterface BlockedFeature {\n issueId: string;\n /** The repo that rejected it. */\n repoKey: string;\n branch: string;\n reason: string;\n}\n\n/** Everything the assembly pass produced. */\ninterface AssemblyPass {\n /** Per-repo base SHA and worktree, in merge order. */\n repos: UatGenerationRepo[];\n /** issueId → the repos it merged cleanly into. */\n mergedByIssue: Map<string, RepoMergeOutcome[]>;\n /** A repo-level failure (worktree creation, failed rollback) aborts the generation. */\n fatal: string | null;\n}\n\nconst SHORT_SHA_LENGTH = 7;\n\n/**\n * `fe@abc1234 api@def5678` — the one anchor format for every per-repo SHA\n * tuple. Base anchors, member anchors, and the reconciler's live anchors are\n * all built here: the reconciler decides staleness by STRING EQUALITY against\n * what assembly stored, so any two producers that disagree on ordering or SHA\n * length would make every generation look permanently stale and reassemble\n * forever.\n */\nexport function compositeAnchor(\n entries: ReadonlyArray<{ repoKey: string; sha: string; mergeOrder: number }>,\n): string {\n return [...entries]\n .sort((a, b) => a.mergeOrder - b.mergeOrder || a.repoKey.localeCompare(b.repoKey))\n .map((e) => `${e.repoKey}@${e.sha.slice(0, SHORT_SHA_LENGTH)}`)\n .join(' ');\n}\n\n/** The generation's base anchor, across the repos it branched. */\nexport function compositeBaseAnchor(repos: readonly UatGenerationRepo[]): string {\n return compositeAnchor(repos.map((r) => ({ repoKey: r.repoKey, sha: r.baseSha, mergeOrder: r.mergeOrder })));\n}\n\n/**\n * One member's anchor, across the repos it contributed to, ordered by repoKey.\n *\n * Deliberately NOT ordered by merge order: assembly knows `mergeOrderInRepo`\n * (position within one repo's own sequence) while the reconciler only has the\n * repo's configured order, and those are different numbers. Sorting on a field\n * only one side can compute would silently yield two different strings for the\n * same state, so every generation would read stale and reassemble forever.\n * repoKey is available to both and is stable.\n */\nexport function compositeMemberAnchor(\n repos: ReadonlyArray<{ repoKey: string; headSha: string }>,\n): string {\n return [...repos]\n .sort((a, b) => a.repoKey.localeCompare(b.repoKey))\n .map((r) => `${r.repoKey}@${r.headSha.slice(0, SHORT_SHA_LENGTH)}`)\n .join(' ');\n}\n\n/**\n * Composite anchor over every repo a held-out feature contributes to, in the\n * same shape a merged member gets, so the reconciler's per-feature anchor can\n * be compared against it by string equality.\n */\nasync function heldOutAnchor(\n feature: ReadyFeature | undefined,\n deps: PolyrepoAssembleDeps,\n): Promise<string> {\n const contributions = feature?.repoContributions ?? [];\n if (contributions.length === 0) return 'unknown';\n\n const entries = await Promise.all(\n contributions.map(async (c) => ({\n repoKey: c.repoKey,\n headSha: await deps.repoGit.get(c.repoKey)?.branchHeadSha(c.branch).catch(() => 'unknown') ?? 'unknown',\n })),\n );\n return compositeMemberAnchor(entries);\n}\n\n/** Repos some feature contributes to, in configured merge order. */\nfunction contributingRepos(\n features: readonly ReadyFeature[],\n repos: readonly ResolvedProjectRepo[],\n): ResolvedProjectRepo[] {\n const touched = new Set<string>();\n for (const feature of features) {\n for (const contribution of feature.repoContributions ?? []) touched.add(contribution.repoKey);\n }\n return repos\n .filter((repo) => touched.has(repo.repoKey))\n .sort((a, b) => a.mergeOrder - b.mergeOrder || a.repoKey.localeCompare(b.repoKey));\n}\n\nfunction contributionFor(feature: ReadyFeature, repoKey: string) {\n return (feature.repoContributions ?? []).find((c) => c.repoKey === repoKey);\n}\n\nfunction conflictingWith(feature: ReadyFeature, mergedIssueIds: readonly string[]): string[] {\n const merged = new Set(mergedIssueIds.map((id) => id.toUpperCase()));\n return (feature.conflictsWith ?? []).filter((id) => merged.has(id.toUpperCase()));\n}\n\n/**\n * Assemble one polyrepo generation. Always returns the generation row (status\n * 'ready' or 'failed'); throws only on store failures. On success, older\n * 'ready' generations for the project flip to 'superseded'.\n */\nexport async function assemblePolyrepoUatGeneration(\n input: PolyrepoAssembleInput,\n deps: PolyrepoAssembleDeps,\n): Promise<UatGeneration> {\n const log = deps.log ?? (() => {});\n const name = makeUniqueUatCandidateName(\n { label: input.label, dateIso: input.dateIso },\n [...deps.store.listNames(), ...(input.takenBranchNames ?? [])],\n );\n const worktreePath = `${input.projectRoot}/workspaces/${generationFolderName(name)}`;\n const repoWorktree = (repoKey: string) => `${worktreePath}/${repoKey}`;\n\n deps.store.insert({\n name,\n worktreePath,\n projectRoot: input.projectRoot,\n baseSha: '',\n status: 'assembling',\n repos: [],\n members: [],\n heldOut: [],\n resolutions: [],\n stackStartedAt: null,\n cleanedAt: null,\n });\n\n const heldOut: UatGenerationHeldOut[] = [];\n const heldOutIds = new Set<string>();\n\n /**\n * Base SHA per repo, accumulated across passes. The generation's base anchor\n * covers every repo the INPUT features contribute to, not just the repos the\n * final pass branched: a hold-out can drop a repo, and if the anchor shrank\n * with it the reconciler — which derives its anchor from the ready set —\n * could never match, so it would rebuild the same generation every tick.\n */\n const baseShaByRepo = new Map<string, string>();\n\n const finish = (\n status: UatGenerationStatus,\n repos: UatGenerationRepo[],\n members: UatGenerationMember[],\n resolutions: UatGenerationResolution[],\n anchorRepos?: readonly ResolvedProjectRepo[],\n ): UatGeneration => {\n const baseSha = anchorRepos\n ? compositeAnchor(\n anchorRepos\n .filter((r) => baseShaByRepo.has(r.repoKey))\n .map((r) => ({ repoKey: r.repoKey, sha: baseShaByRepo.get(r.repoKey)!, mergeOrder: r.mergeOrder })),\n )\n : compositeBaseAnchor(repos);\n deps.store.update(name, { status, baseSha, repos, members, heldOut, resolutions });\n return {\n name, worktreePath, projectRoot: input.projectRoot, baseSha,\n status, repos, members, heldOut, resolutions,\n stackStartedAt: null, cleanedAt: null, createdAt: '', updatedAt: '',\n };\n };\n\n const allContributing = contributingRepos(input.features, input.repos);\n if (allContributing.length === 0) {\n log(`[uat-polyrepo] ${name}: no member repo has a contributing feature branch — marking failed`);\n return finish('failed', [], [], []);\n }\n log(`[uat-polyrepo] ${name}: assembling across ${allContributing.map((r) => r.repoKey).join(', ')}`);\n\n const missingDeps = allContributing.filter((repo) => !deps.repoGit.has(repo.repoKey));\n if (missingDeps.length > 0) {\n log(`[uat-polyrepo] ${name}: no git deps for repo(s) ${missingDeps.map((r) => r.repoKey).join(', ')} — marking failed`);\n return finish('failed', [], [], []);\n }\n\n // ONE pass, feature-atomic. Each feature is merged into every repo it\n // contributes to; if any repo rejects it, the repos that already took it are\n // rolled back to the head they had before that feature, and assembly moves on.\n // Earlier accepted features are never rebuilt, so the work is O(R x F) rather\n // than the O(R x F^2) a rebuild-and-replay design costs.\n const pass = await runAssemblyPass(name, input.features, allContributing, repoWorktree, deps, log, {\n onHeldOut: async (blocked) => {\n heldOutIds.add(blocked.issueId);\n // The reconciler compares a held-out feature's stored headSha against a\n // composite anchor over ALL its contributions, so storing the single\n // blocking repo's SHA would never match and would churn the generation\n // every tick. Record the same composite shape members use.\n const blockedFeature = input.features.find((f) => f.issueId === blocked.issueId);\n heldOut.push({\n issueId: blocked.issueId,\n // The LOGICAL feature branch, not the blocking repo's contribution\n // branch: the reconciler keys its head anchors by ReadyFeature.branch,\n // and a repo with a custom branch_prefix (feat/ vs feature/) would make\n // that lookup miss forever and rebuild the generation every tick. The\n // blocking repo is already named in the reason.\n branch: blockedFeature?.branch ?? blocked.branch,\n headSha: await heldOutAnchor(blockedFeature, deps),\n reason: blocked.reason,\n });\n deps.store.update(name, { heldOut });\n log(`[uat-polyrepo] ${name}: holding ${blocked.issueId} out of the whole generation (${blocked.repoKey})`);\n },\n });\n for (const repo of pass.repos) baseShaByRepo.set(repo.repoKey, repo.baseSha);\n\n if (pass.fatal) {\n log(`[uat-polyrepo] ${name}: ${pass.fatal}`);\n return finish('failed', pass.repos, [], []);\n }\n\n // A repo every one of whose features was held out carries nothing. Its\n // worktree and branch exist locally but are never pushed; cleanup reaps them\n // with the generation folder.\n const carrying = new Set([...pass.mergedByIssue.values()].flat().map((o) => o.repoKey));\n const abandoned = pass.repos.filter((r) => !carrying.has(r.repoKey));\n if (abandoned.length > 0) {\n log(`[uat-polyrepo] ${name}: repo(s) ${abandoned.map((r) => r.repoKey).join(', ')} lost every contribution to a hold-out — no branch published; local worktree reaped with the generation folder`);\n }\n const publishedRepos = pass.repos.filter((r) => carrying.has(r.repoKey));\n\n const { members, resolutions } = collectMembers(input.features, heldOutIds, pass.mergedByIssue);\n\n if (members.length === 0) {\n // Anchor on the full candidate set even though nothing published: the\n // reconciler's desired anchor covers every ready-set contribution, and\n // FAILED_RETRY_BACKOFF_MS only suppresses a repeat when the two signatures\n // match exactly. Without it an all-held-out batch reassembles every tick.\n log(`[uat-polyrepo] ${name}: nothing merged (${heldOut.length} held out) — marking failed`);\n return finish('failed', publishedRepos, [], [], allContributing);\n }\n\n // Push every repo. A partial push is recoverable — cleanup reaps the branches\n // that did land — but the generation is not testable, so it is failed.\n for (const repo of publishedRepos) {\n try {\n await deps.repoGit.get(repo.repoKey)!.push(name);\n } catch (err) {\n log(`[uat-polyrepo] ${name}: push failed in ${repo.repoKey}: ${err instanceof Error ? err.message : String(err)}`);\n return finish('failed', publishedRepos, members, resolutions);\n }\n }\n\n for (const older of deps.store.listChain(input.projectRoot, ['ready'])) {\n if (older.name !== name) deps.store.update(older.name, { status: 'superseded' });\n }\n\n log(`[uat-polyrepo] ${name}: ready — ${members.length} member(s) across ${publishedRepos.length} repo(s), ${resolutions.length} resolution(s), ${heldOut.length} held out`);\n return finish('ready', publishedRepos, members, resolutions, allContributing);\n}\n\n/**\n * Build every repo's branch from base, then apply features one at a time.\n *\n * A feature is applied to every repo it contributes to or to none: if a later\n * repo rejects it, the repos that already merged it are reset to the head they\n * had before, and it is reported held out. Accepted features are never\n * disturbed, so total git work is linear in (repos x features).\n */\nasync function runAssemblyPass(\n name: string,\n features: readonly ReadyFeature[],\n active: readonly ResolvedProjectRepo[],\n repoWorktree: (repoKey: string) => string,\n deps: PolyrepoAssembleDeps,\n log: (msg: string) => void,\n hooks: { onHeldOut: (blocked: BlockedFeature) => Promise<void> },\n): Promise<AssemblyPass> {\n const repos: UatGenerationRepo[] = [];\n const mergedByIssue = new Map<string, RepoMergeOutcome[]>();\n /** Merge count per repo, which is that repo's mergeOrderInRepo counter. */\n const mergedInRepo = new Map<string, string[]>();\n /** Union lint state per repo (PAN-3166) — absent when the repo's deps omit it. */\n const ledgers = new Map<string, MigrationVersionLedger>();\n\n for (const repo of active) {\n const git = deps.repoGit.get(repo.repoKey)!;\n const path = repoWorktree(repo.repoKey);\n\n let baseSha: string;\n try {\n baseSha = await git.fetchMain();\n await git.createWorktree(name, path);\n } catch (err) {\n return {\n repos,\n mergedByIssue,\n fatal: `worktree creation failed in ${repo.repoKey}: ${err instanceof Error ? err.message : String(err)}`,\n };\n }\n\n repos.push({\n repoKey: repo.repoKey,\n repoPath: repo.repoPath,\n branch: name,\n baseSha,\n // Persisted so promote merges back into the branch this was cut from,\n // rather than assuming main long after assembly.\n targetBranch: repo.targetBranch,\n worktreePath: path,\n mergeOrder: repo.mergeOrder,\n promotedAt: null,\n mergeSha: null,\n });\n mergedInRepo.set(repo.repoKey, []);\n\n // Union lint (PAN-3166), per repo: owners from this repo's freshly cut\n // branch, reservations from every contributing candidate branch in THIS\n // repo. A read failure is fatal to the assembly rather than silently\n // disabling the lint.\n try {\n const candidates = features\n .map((feature) => contributionFor(feature, repo.repoKey)?.branch)\n .filter((branch): branch is string => Boolean(branch));\n const ledger = await seedUnionLedger(git, name, candidates);\n if (ledger) ledgers.set(repo.repoKey, ledger);\n } catch (err) {\n return {\n repos,\n mergedByIssue,\n fatal: `union lint could not read migrations in ${repo.repoKey}: ${err instanceof Error ? err.message : String(err)}`,\n };\n }\n }\n\n for (const feature of features) {\n const targets = active.filter((repo) => contributionFor(feature, repo.repoKey));\n if (targets.length === 0) continue;\n\n // Union lint (PAN-3166) — planned for every repo BEFORE any merge, so a\n // hold-out costs one `git ls-tree` per repo and never needs the rollback\n // machinery. A hold-out is global here, exactly like a merge conflict: one\n // repo's colliding migration keeps the whole feature out of the generation.\n const plans = new Map<string, UnionLintPlan>();\n let unionCollision: BlockedFeature | null = null;\n for (const repo of targets) {\n const contribution = contributionFor(feature, repo.repoKey)!;\n const plan = await planUnionLint({\n git: deps.repoGit.get(repo.repoKey)!,\n ledger: ledgers.get(repo.repoKey) ?? null,\n generationBranch: name,\n featureBranch: contribution.branch,\n issueId: feature.issueId,\n });\n plans.set(repo.repoKey, plan);\n if (plan.disposition.kind === 'hold-out') {\n unionCollision = {\n issueId: feature.issueId,\n repoKey: repo.repoKey,\n branch: contribution.branch,\n reason: `${plan.disposition.reason} (in ${repo.repoKey})`,\n };\n break;\n }\n }\n if (unionCollision) {\n log(`[uat-polyrepo] ${name}: holding ${feature.issueId} out — ${unionCollision.reason}`);\n await hooks.onHeldOut(unionCollision);\n continue;\n }\n\n // Capture each target's head BEFORE this feature, so a partial application\n // can be undone without rebuilding the branch from base.\n const snapshots = new Map<string, string>();\n let snapshotFailure: string | null = null;\n for (const repo of targets) {\n try {\n snapshots.set(repo.repoKey, await deps.repoGit.get(repo.repoKey)!.generationHeadSha());\n } catch (err) {\n snapshotFailure = `could not read ${repo.repoKey} head: ${err instanceof Error ? err.message : String(err)}`;\n break;\n }\n }\n if (snapshotFailure) {\n await hooks.onHeldOut({\n issueId: feature.issueId,\n repoKey: targets[0]!.repoKey,\n branch: contributionFor(feature, targets[0]!.repoKey)?.branch ?? feature.branch,\n reason: snapshotFailure,\n });\n continue;\n }\n\n const applied: RepoMergeOutcome[] = [];\n let blocked: BlockedFeature | null = null;\n\n for (const repo of targets) {\n const contribution = contributionFor(feature, repo.repoKey)!;\n const git = deps.repoGit.get(repo.repoKey)!;\n const outcome = await mergeOneFeature({\n name,\n feature,\n featureBranch: contribution.branch,\n repoKey: repo.repoKey,\n worktreePath: repoWorktree(repo.repoKey),\n mergedIssueIds: mergedInRepo.get(repo.repoKey) ?? [],\n mergeOrderInRepo: (mergedInRepo.get(repo.repoKey)?.length ?? 0) + 1,\n git,\n }, deps, log);\n\n if ('reason' in outcome) {\n blocked = {\n issueId: feature.issueId,\n repoKey: repo.repoKey,\n branch: contribution.branch,\n reason: `${outcome.reason} (in ${repo.repoKey})`,\n };\n break;\n }\n applied.push(outcome);\n\n // The renumbering can only land once the merge brought the files onto\n // this repo's branch. It is committed ABOVE the snapshot taken before\n // this feature, so the rollback below removes it with everything else —\n // a rolled-back feature never leaves an orphaned rename behind.\n const plan = plans.get(repo.repoKey);\n if (plan) {\n try {\n const renumber = await applyUnionLintPlan(git, plan, feature.issueId);\n if (renumber) {\n outcome.renumber = renumber;\n log(`[uat-polyrepo] ${name}: renumbered ${feature.issueId} migrations in ${repo.repoKey} — ${renumber.note}`);\n } else if (plan.disposition.kind === 'renumber') {\n blocked = {\n issueId: feature.issueId,\n repoKey: repo.repoKey,\n branch: contribution.branch,\n reason: `migration renumbering is required but no rename primitive is wired (in ${repo.repoKey})`,\n };\n break;\n }\n } catch (err) {\n blocked = {\n issueId: feature.issueId,\n repoKey: repo.repoKey,\n branch: contribution.branch,\n reason: `migration renumbering failed: ${err instanceof Error ? err.message.split('\\n')[0] : String(err)} (in ${repo.repoKey})`,\n };\n break;\n }\n }\n }\n\n if (blocked) {\n // Roll the repos that took it back to their pre-feature head. A failure\n // here cannot be ignored: the branch would carry a feature recorded as\n // held out, so the generation is abandoned rather than shipped wrong.\n for (const outcome of applied) {\n const snapshot = snapshots.get(outcome.repoKey)!;\n try {\n await deps.repoGit.get(outcome.repoKey)!.resetGenerationTo(snapshot);\n } catch (err) {\n return {\n repos,\n mergedByIssue,\n fatal:\n `could not roll ${outcome.repoKey} back to ${snapshot.slice(0, 9)} after holding out ` +\n `${feature.issueId}: ${err instanceof Error ? err.message : String(err)}`,\n };\n }\n }\n await hooks.onHeldOut(blocked);\n continue;\n }\n\n // Only now, with the feature accepted in EVERY repo it contributes to, is\n // it attributed in the ledgers — and by its POST-rename paths. Recording\n // per repo as the loop went would leave a half-recorded rename behind when\n // a later repo rejected the feature and rolled the earlier ones back.\n for (const outcome of applied) {\n mergedInRepo.get(outcome.repoKey)!.push(feature.issueId);\n const plan = plans.get(outcome.repoKey);\n if (plan) ledgers.get(outcome.repoKey)?.record(plannedFiles(plan), feature.issueId);\n }\n mergedByIssue.set(feature.issueId, applied);\n }\n\n return { repos, mergedByIssue, fatal: null };\n}\n\ninterface MergeOneFeatureArgs {\n name: string;\n feature: ReadyFeature;\n featureBranch: string;\n repoKey: string;\n /** This repo's worktree — the mid-conflict state the hook edits. */\n worktreePath: string;\n mergedIssueIds: readonly string[];\n mergeOrderInRepo: number;\n git: GenerationGitDeps;\n}\n\n/** Merge one feature into one repo's generation branch. */\nasync function mergeOneFeature(\n args: MergeOneFeatureArgs,\n deps: PolyrepoAssembleDeps,\n log: (msg: string) => void,\n): Promise<RepoMergeOutcome | { reason: string; headSha: string }> {\n const { name, feature, featureBranch, repoKey, worktreePath, mergedIssueIds, mergeOrderInRepo, git } = args;\n const headSha = await git.branchHeadSha(featureBranch).catch(() => 'unknown');\n const conflictingIssueIds = conflictingWith(feature, mergedIssueIds);\n\n let result: Awaited<ReturnType<GenerationGitDeps['mergeBranch']>>;\n try {\n result = await git.mergeBranch(featureBranch);\n } catch (err) {\n await git.abortMerge().catch(() => {});\n return { headSha, reason: `merge failed: ${err instanceof Error ? (err.message.split('\\n')[0] ?? 'merge failed') : String(err)}` };\n }\n\n if (result.ok) {\n return { repoKey, headSha, mergeOrderInRepo, conflictingIssueIds };\n }\n\n if (!result.conflict || !deps.resolveConflict) {\n await git.abortMerge().catch(() => {});\n return {\n headSha,\n reason: result.conflict\n ? `conflicts with ${conflictingIssueIds.join(', ') || 'an earlier member'} — no assembly agent available`\n : result.reason,\n };\n }\n\n log(`[uat-polyrepo] ${name}: resolving conflict in ${repoKey} ${feature.issueId} <-> ${conflictingIssueIds.join(', ') || '(unknown member)'}`);\n let resolution: ConflictResolutionResult | null = null;\n try {\n resolution = await deps.resolveConflict({\n feature,\n mergedIssueIds,\n conflictingIssueIds,\n branchName: name,\n worktreePath,\n repoKey,\n });\n } catch (err) {\n log(`[uat-polyrepo] ${name}: conflict agent threw in ${repoKey}: ${err instanceof Error ? err.message : String(err)}`);\n resolution = null;\n }\n\n if (!resolution) {\n await git.abortMerge().catch(() => {});\n return {\n headSha,\n reason: `conflict with ${conflictingIssueIds.join(', ') || 'an earlier member'} could not be auto-resolved — waits for the next generation`,\n };\n }\n\n return { repoKey, headSha, mergeOrderInRepo, resolution, conflictingIssueIds };\n}\n\n/**\n * Fold the accepted pass into store-shaped members and resolutions, preserving\n * the ready-set order so mergeOrder reads the same as the monorepo path.\n */\nfunction collectMembers(\n features: readonly ReadyFeature[],\n heldOutIds: ReadonlySet<string>,\n mergedByIssue: ReadonlyMap<string, RepoMergeOutcome[]>,\n): { members: UatGenerationMember[]; resolutions: UatGenerationResolution[] } {\n const members: UatGenerationMember[] = [];\n const resolutions: UatGenerationResolution[] = [];\n\n for (const feature of features) {\n if (heldOutIds.has(feature.issueId)) continue;\n const outcomes = mergedByIssue.get(feature.issueId);\n if (!outcomes || outcomes.length === 0) continue;\n\n const repos: UatGenerationMemberRepo[] = outcomes.map((o) => ({\n repoKey: o.repoKey,\n branch: contributionFor(feature, o.repoKey)?.branch ?? feature.branch,\n headSha: o.headSha,\n mergeOrderInRepo: o.mergeOrderInRepo,\n }));\n\n members.push({\n issueId: feature.issueId,\n title: feature.title,\n branch: feature.branch,\n // The generation row keeps ONE headSha per member, which the reconciler\n // compares by string equality to decide staleness — so for a polyrepo\n // member it must be the composite anchor over every repo it touched, not\n // one repo's SHA. Per-repo SHAs are also kept individually in `repos`.\n headSha: compositeMemberAnchor(repos),\n mergeOrder: members.length + 1,\n ...(feature.pr !== undefined ? { pr: feature.pr } : {}),\n ...(feature.prUrl !== undefined ? { prUrl: feature.prUrl } : {}),\n repos,\n });\n\n for (const outcome of outcomes) {\n if (outcome.resolution) {\n resolutions.push({\n issueIds: [feature.issueId, ...outcome.conflictingIssueIds],\n files: outcome.resolution.files,\n commitSha: outcome.resolution.commitSha,\n kind: 'conflict',\n });\n }\n if (outcome.renumber) {\n resolutions.push({\n issueIds: [feature.issueId],\n files: outcome.renumber.files,\n commitSha: outcome.renumber.commitSha,\n kind: 'migration-renumber',\n note: `${outcome.renumber.note} (in ${outcome.repoKey})`,\n });\n }\n }\n }\n\n return { members, resolutions };\n}\n","/**\n * Assembly-agent conflict resolution (PAN-1737: UAT batch trains).\n *\n * Implements the engine's `resolveConflict` hook: when merging a feature onto\n * a UAT generation branch conflicts, a TIMEBOXED headless agent run is given\n * the mid-conflict worktree with one mission — resolve THIS merge's conflict\n * markers, changing nothing beyond the resolution.\n *\n * Division of labor, deliberately strict:\n * - The agent only EDITS FILES (headless `claude -p` with acceptEdits; no\n * shell access). It cannot stage, commit, push, or wander.\n * - This module verifies the result in code (no leftover conflict markers,\n * no unmerged index entries), stages, and concludes the merge commit.\n * - Any failure — agent missing, timeout, markers left, commit rejected —\n * returns null; the engine then aborts the merge and holds the feature\n * out. The assembly NEVER wedges on a conflict.\n *\n * The merge commit subject keeps git's standard `Merge branch ...` form (it\n * survives commitlint's default-ignores when this history is later promoted\n * to main); the auditable `uat-assembly:` marker lives in the body.\n */\nimport { execFile } from 'child_process';\nimport { promisify } from 'util';\nimport type { ConflictContext, ConflictResolutionResult } from './uat-generation-engine.js';\n\nconst execFileAsync = promisify(execFile);\n\nexport const DEFAULT_CONFLICT_TIMEOUT_MS = 5 * 60 * 1000;\n\n/** Injectable I/O so the hook's decision logic is unit-testable. */\nexport interface ConflictAgentDeps {\n /** Paths with unmerged index entries (`git diff --name-only --diff-filter=U`). */\n listConflictedFiles(cwd: string): Promise<string[]>;\n /** Staged paths before/after the agent run (`git diff --cached --name-only`). */\n stagedFiles(cwd: string): Promise<string[]>;\n /** Run the headless resolution agent; throws on spawn failure or timeout. */\n runAgent(args: { prompt: string; cwd: string; timeoutMs: number }): Promise<void>;\n /** Subset of `files` still containing conflict markers after the agent ran. */\n filesWithConflictMarkers(cwd: string, files: readonly string[]): Promise<string[]>;\n /** Unstaged or untracked paths outside the conflicted-file allowlist. */\n unsafeChangedFiles(cwd: string, allowedFiles: readonly string[]): Promise<string[]>;\n /** Discard untrusted worktree/untracked changes outside the allowlist. */\n discardFiles(cwd: string, files: readonly string[]): Promise<void>;\n /** Stage only the conflicted files that the agent was allowed to edit. */\n stageFiles(cwd: string, files: readonly string[]): Promise<void>;\n /** True if unmerged index entries remain after staging. */\n hasUnmergedEntries(cwd: string): Promise<boolean>;\n /** Conclude the in-progress merge with the given message. */\n commitMerge(cwd: string, message: string): Promise<void>;\n headSha(cwd: string): Promise<string>;\n log?: (msg: string) => void;\n}\n\nexport interface ConflictAgentOptions {\n timeoutMs?: number;\n deps?: Partial<ConflictAgentDeps>;\n}\n\nfunction splitNul(stdout: string): string[] {\n return stdout.split('\\0').filter((p) => p.length > 0);\n}\n\nfunction allowlist(files: readonly string[]): ReadonlySet<string> {\n return new Set(files.map((f) => f.replace(/\\\\/g, '/')));\n}\n\nexport function buildConflictResolutionPrompt(ctx: ConflictContext, conflictedFiles: readonly string[]): string {\n const counterparts = ctx.conflictingIssueIds.length > 0\n ? ctx.conflictingIssueIds.join(', ')\n : 'an earlier batch member';\n return [\n ctx.repoKey\n ? `You are resolving a git merge conflict inside the ${ctx.repoKey} repo of a UAT batch assembly worktree.`\n : `You are resolving a git merge conflict inside a UAT batch assembly worktree.`,\n ``,\n `The branch ${ctx.branchName} bundles already-reviewed features for human testing.`,\n `Merging ${ctx.feature.branch} (${ctx.feature.issueId}: ${ctx.feature.title}) conflicted with ${counterparts}.`,\n ``,\n `Conflicted files:`,\n ...conflictedFiles.map((f) => ` - ${f}`),\n ``,\n `Your ONLY job: edit these files to resolve every conflict marker (<<<<<<<, =======, >>>>>>>)`,\n `so that BOTH features' intended behavior is preserved. Both sides passed review —`,\n `combine them; do not pick one side wholesale unless the changes are genuinely identical in intent.`,\n ``,\n `Rules:`,\n `- Change NOTHING beyond what resolving the conflict requires.`,\n `- Do not refactor, reformat, or \"improve\" surrounding code.`,\n `- Do not run git commands; the caller stages and commits.`,\n `- If the conflict cannot be safely resolved, leave the markers in place and say so.`,\n ].join('\\n');\n}\n\nfunction buildMergeCommitMessage(ctx: ConflictContext, files: readonly string[]): string {\n const pair = [ctx.feature.issueId, ...ctx.conflictingIssueIds].join(' <-> ');\n return [\n `Merge branch '${ctx.feature.branch}' into ${ctx.branchName}`,\n ``,\n `uat-assembly: resolve ${pair}`,\n `files: ${files.join(', ')}`,\n ].join('\\n');\n}\n\nfunction defaultDeps(): ConflictAgentDeps {\n const run = (cmd: string, args: string[], cwd: string, timeoutMs?: number) =>\n execFileAsync(cmd, args, { cwd, maxBuffer: 16 * 1024 * 1024, ...(timeoutMs ? { timeout: timeoutMs } : {}) });\n\n return {\n listConflictedFiles: async (cwd) => {\n const { stdout } = await run('git', ['diff', '--name-only', '--diff-filter=U'], cwd);\n return stdout.split('\\n').map((l) => l.trim()).filter(Boolean);\n },\n stagedFiles: async (cwd) => {\n const { stdout } = await run('git', ['diff', '--cached', '--name-only', '-z'], cwd);\n return splitNul(stdout);\n },\n runAgent: async ({ prompt, cwd, timeoutMs }) => {\n await run('claude', ['-p', prompt, '--permission-mode', 'acceptEdits'], cwd, timeoutMs);\n },\n filesWithConflictMarkers: async (cwd, files) => {\n if (files.length === 0) return [];\n // -l: names only; -E for the alternation. grep exits 1 on no matches.\n const { stdout } = await execFileAsync(\n 'grep', ['-lE', '^(<<<<<<<|=======$|>>>>>>>)', '--', ...files],\n { cwd, maxBuffer: 16 * 1024 * 1024 },\n ).catch((err: { code?: number; stdout?: string }) => {\n if (err && err.code === 1) return { stdout: '' };\n throw err;\n });\n return stdout.split('\\n').map((l) => l.trim()).filter(Boolean);\n },\n unsafeChangedFiles: async (cwd, files) => {\n const allowed = allowlist(files);\n const [{ stdout: unstaged }, { stdout: untracked }] = await Promise.all([\n run('git', ['diff', '--name-only', '-z'], cwd),\n run('git', ['ls-files', '--others', '--exclude-standard', '-z'], cwd),\n ]);\n return [...new Set([...splitNul(unstaged), ...splitNul(untracked)])]\n .filter((file) => !allowed.has(file.replace(/\\\\/g, '/')));\n },\n discardFiles: async (cwd, files) => {\n if (files.length === 0) return;\n await run('git', ['restore', '--staged', '--worktree', '--', ...files], cwd).catch(() => {});\n await run('git', ['clean', '-fd', '--', ...files], cwd).catch(() => {});\n },\n stageFiles: async (cwd, files) => {\n if (files.length === 0) return;\n await run('git', ['add', '--', ...files], cwd);\n },\n hasUnmergedEntries: async (cwd) => {\n const { stdout } = await run('git', ['ls-files', '-u'], cwd);\n return stdout.trim().length > 0;\n },\n commitMerge: async (cwd, message) => {\n await run('git', ['commit', '-m', message], cwd);\n },\n headSha: async (cwd) => (await run('git', ['rev-parse', 'HEAD'], cwd)).stdout.trim(),\n };\n}\n\n/**\n * Build the engine's `resolveConflict` hook. Returns the resolution on\n * success; null on ANY failure (the engine aborts the merge and holds the\n * feature out — never throws into the assembly loop).\n */\nexport function buildConflictAgentHook(\n options: ConflictAgentOptions = {},\n): (ctx: ConflictContext) => Promise<ConflictResolutionResult | null> {\n const deps: ConflictAgentDeps = { ...defaultDeps(), ...(options.deps ?? {}) };\n const timeoutMs = options.timeoutMs ?? DEFAULT_CONFLICT_TIMEOUT_MS;\n const log = deps.log ?? (() => {});\n\n return async (ctx) => {\n // Polyrepo assembly runs this hook once per repo, so the repo key has to be\n // in the tag or two repos' log lines are indistinguishable.\n const tag = ctx.repoKey\n ? `[uat-conflict-agent] ${ctx.branchName} ${ctx.repoKey} ${ctx.feature.issueId}`\n : `[uat-conflict-agent] ${ctx.branchName} ${ctx.feature.issueId}`;\n let files: string[];\n let stagedBefore: string[];\n try {\n files = await deps.listConflictedFiles(ctx.worktreePath);\n stagedBefore = await deps.stagedFiles(ctx.worktreePath);\n } catch (err) {\n log(`${tag}: could not inspect conflicted state: ${err instanceof Error ? err.message : String(err)}`);\n return null;\n }\n if (files.length === 0) {\n log(`${tag}: merge failed without unmerged paths — not a content conflict, giving up`);\n return null;\n }\n\n try {\n await deps.runAgent({\n prompt: buildConflictResolutionPrompt(ctx, files),\n cwd: ctx.worktreePath,\n timeoutMs,\n });\n } catch (err) {\n log(`${tag}: agent run failed (${err instanceof Error ? err.message.split('\\n')[0] : String(err)})`);\n return null;\n }\n\n try {\n const unsafe = await deps.unsafeChangedFiles(ctx.worktreePath, files);\n if (unsafe.length > 0) {\n log(`${tag}: agent touched non-conflicted path(s): ${unsafe.join(', ')}`);\n await deps.discardFiles(ctx.worktreePath, unsafe).catch((err) => {\n log(`${tag}: cleanup of non-conflicted path(s) failed: ${err instanceof Error ? err.message : String(err)}`);\n });\n return null;\n }\n\n const unresolved = await deps.filesWithConflictMarkers(ctx.worktreePath, files);\n if (unresolved.length > 0) {\n log(`${tag}: conflict markers remain in ${unresolved.join(', ')}`);\n return null;\n }\n await deps.stageFiles(ctx.worktreePath, files);\n if (await deps.hasUnmergedEntries(ctx.worktreePath)) {\n log(`${tag}: unmerged index entries remain after staging`);\n return null;\n }\n\n const stagedBeforeSet = new Set(stagedBefore);\n const allowed = allowlist(files);\n const stagedAfter = await deps.stagedFiles(ctx.worktreePath);\n const newlyStagedOutsideAllowlist = stagedAfter.filter((file) =>\n !stagedBeforeSet.has(file) && !allowed.has(file.replace(/\\\\/g, '/')),\n );\n if (newlyStagedOutsideAllowlist.length > 0) {\n log(`${tag}: cached diff includes non-conflicted path(s): ${newlyStagedOutsideAllowlist.join(', ')}`);\n return null;\n }\n\n await deps.commitMerge(ctx.worktreePath, buildMergeCommitMessage(ctx, files));\n const commitSha = await deps.headSha(ctx.worktreePath);\n log(`${tag}: resolved ${files.length} file(s) at ${commitSha.slice(0, 9)}`);\n return { files, commitSha };\n } catch (err) {\n log(`${tag}: verification/commit failed: ${err instanceof Error ? err.message.split('\\n')[0] : String(err)}`);\n return null;\n }\n };\n}\n","/**\n * UAT generation reconciler (PAN-1737: UAT batch trains).\n *\n * The loop that keeps \"always one batch ready\" true with zero human action.\n * Each tick (interval-driven from the dashboard server, or forced via the\n * API's rebuild action):\n *\n * 1. No-op unless the merge train is enabled and a flywheel run is active.\n * 2. Invalidate live generations that went stale — main advanced past their\n * baseSha, a member left the ready set, or a member branch gained\n * commits. Invalidation tears down the generation's live stack.\n * (Older SUBSET generations are NOT stale: a smaller batch off current\n * main remains perfectly testable and promotable.)\n * 3. If no live generation matches the current desired set, assemble the\n * next generation in the background. Single-flight per project; a failed\n * assembly for the SAME desired signature backs off before retrying.\n * 4. Trim/reap the chain (cleanup hook).\n *\n * Pure orchestration with injected deps — interval wiring and real data\n * sources live in the dashboard server service.\n */\nimport type { ReadyFeature, GenerationStorePort } from './uat-generation-engine.js';\nimport type { UatGeneration } from '../overdeck/merge-sync.js';\n\n/**\n * An 'assembling' row with NO PROGRESS for this long is marked failed (train\n * un-wedge). Keyed on updated_at — the engine patches the row after every\n * member step — so a big batch grinding through many timeboxed conflict\n * resolutions is never killed for total age (the first live run killed a\n * slow-but-alive 5-member assembly at a flat 30min of created_at age; with\n * many developers driving hundreds of agents, assemblies will only get\n * bigger). A crashed engine stops updating the row and trips this within the\n * hour. Cooperative cancellation of the in-flight engine is PAN-1755.\n */\nexport const STUCK_ASSEMBLING_MS = 60 * 60 * 1000;\n/** Minimum age before re-attempting an assembly that failed for the same input. */\nexport const FAILED_RETRY_BACKOFF_MS = 10 * 60 * 1000;\n\nexport interface UatReconcilerDeps {\n /** Gate: flywheel.merge_train_enabled. */\n isEnabled(): boolean;\n /**\n * Current ready set in merge order, or null when it cannot be computed\n * (no active flywheel run) — null means \"do nothing\", not \"empty\".\n */\n getReadySet(): Promise<readonly ReadyFeature[] | null>;\n /** Current origin/main head SHA (fetching first). */\n getMainHeadSha(): Promise<string>;\n /** Current head SHA per feature branch (origin-first). */\n getBranchHeadSha(branch: string): Promise<string>;\n /**\n * Polyrepo base anchor across every member repo (`fe@abc1234 api@def5678`),\n * replacing the single main SHA. Omit for monorepo — the reconciler then\n * behaves exactly as before, using getMainHeadSha.\n *\n * Staleness is decided by STRING EQUALITY against what assembly stored, so\n * an anchor here must be produced by the same helpers the polyrepo engine\n * uses (compositeBaseAnchor / compositeMemberAnchor).\n */\n getBaseAnchor?(): Promise<string>;\n /**\n * Polyrepo per-feature anchor across the repos it contributes to, replacing\n * the single branch head SHA. Omit for monorepo.\n */\n getFeatureAnchor?(feature: ReadyFeature): Promise<string>;\n /** Positive merge evidence for a generation branch; omitted when unavailable. */\n isGenerationContainedInMain?(generation: UatGeneration): Promise<boolean>;\n store: GenerationStorePort;\n /** Assemble the next generation from the desired set (the engine). */\n assemble(features: readonly ReadyFeature[]): Promise<UatGeneration>;\n /** Tear down a generation's live stack (no-op when none is running). */\n teardownStack(generation: UatGeneration): Promise<void>;\n /** Chain trim/reap (cleanupUatGenerations wiring). */\n cleanup(): Promise<void>;\n now?: () => number;\n log?: (msg: string) => void;\n}\n\nexport interface ReconcileResult {\n action: 'disabled' | 'no-queue' | 'idle' | 'assembled' | 'assembly-failed' | 'backoff' | 'in-flight';\n invalidated: string[];\n generation?: UatGeneration;\n}\n\n/**\n * Single-flight per project: a tick that arrives while one is running gets\n * 'in-flight' immediately instead of queuing (assemblies are minutes-long;\n * the interval would pile up).\n */\nconst inFlightReconciles = new Map<string, Promise<ReconcileResult>>();\n\n/** Stable signature of \"what we want assembled\" — backoff bookkeeping. */\nfunction desiredSignature(features: readonly ReadyFeature[], headShas: ReadonlyMap<string, string>, mainSha: string): string {\n const parts = [...features]\n .map((f) => `${f.issueId.toUpperCase()}@${headShas.get(f.branch) ?? 'unknown'}`)\n .sort();\n return `${mainSha}|${parts.join(',')}`;\n}\n\nfunction generationSignature(gen: UatGeneration): string {\n const parts = [\n ...gen.members.map((m) => `${m.issueId.toUpperCase()}@${m.headSha}`),\n ...gen.heldOut.map((h) => `${h.issueId.toUpperCase()}@${h.headSha ?? 'held'}`),\n ].sort();\n return `${gen.baseSha}|${parts.join(',')}`;\n}\n\nfunction liveSignatureMatches(\n gen: UatGeneration,\n features: readonly ReadyFeature[],\n headShas: ReadonlyMap<string, string>,\n mainSha: string,\n): boolean {\n if (gen.baseSha !== mainSha) return false;\n const desiredIds = new Set(features.map((f) => f.issueId.toUpperCase()));\n const genIds = new Set([\n ...gen.members.map((m) => m.issueId.toUpperCase()),\n ...gen.heldOut.map((h) => h.issueId.toUpperCase()),\n ]);\n if (desiredIds.size !== genIds.size) return false;\n for (const id of desiredIds) if (!genIds.has(id)) return false;\n for (const member of gen.members) {\n const current = headShas.get(member.branch);\n if (current !== member.headSha) return false;\n }\n for (const held of gen.heldOut) {\n if (!held.branch || !held.headSha) return false;\n const current = headShas.get(held.branch);\n if (current !== held.headSha) return false;\n }\n return true;\n}\n\n/**\n * A live generation is INVALIDATED only when its own CONTENT can no longer be\n * trusted for promotion: a bundled member left the ready queue (its work must\n * not merge) or a member's branch gained commits (the tested tree is outdated).\n *\n * Deliberately NOT invalidation triggers (the first live run, 2026-06-10,\n * churned every ready batch to death within minutes):\n * - main advancing past baseSha — an active flywheel run lands strike commits\n * on main continuously; the batch tree is still perfectly testable. Base\n * movement makes liveSignatureMatches() fail, so a FRESH superset assembles\n * and supersedes this one when ready; promotion safety against a moved base\n * is enforced at promote time (member-file overlap check + clean merge).\n * - held-out drift (departed / branch moved / metadata missing) — held-out\n * features are not in the tree; their changes also flow through\n * liveSignatureMatches() into a fresh assembly, never destruction of a\n * testable batch.\n */\nfunction isStale(\n gen: UatGeneration,\n desiredIds: ReadonlySet<string>,\n headShas: ReadonlyMap<string, string>,\n): string | null {\n for (const member of gen.members) {\n if (!desiredIds.has(member.issueId.toUpperCase())) {\n return `${member.issueId} left the ready queue`;\n }\n const current = headShas.get(member.branch);\n if (current !== member.headSha) {\n return `${member.issueId} branch gained commits`;\n }\n }\n return null;\n}\n\nexport async function reconcileUatGenerations(\n projectRoot: string,\n deps: UatReconcilerDeps,\n options: { force?: boolean } = {},\n): Promise<ReconcileResult> {\n const log = deps.log ?? (() => {});\n const now = deps.now ?? Date.now;\n const invalidated: string[] = [];\n\n if (!deps.isEnabled()) return { action: 'disabled', invalidated };\n\n if (inFlightReconciles.has(projectRoot)) return { action: 'in-flight', invalidated };\n\n const tick = async (): Promise<ReconcileResult> => {\n const readySet = await deps.getReadySet();\n if (readySet === null) return { action: 'no-queue', invalidated };\n\n // Polyrepo supplies composite anchors in place of single SHAs; both are\n // opaque strings to everything downstream, so the comparison logic is\n // identical either way.\n const mainSha = deps.getBaseAnchor\n ? await deps.getBaseAnchor()\n : await deps.getMainHeadSha();\n const headShas = new Map<string, string>();\n for (const f of readySet) {\n const anchor = deps.getFeatureAnchor\n ? await deps.getFeatureAnchor(f).catch(() => 'unknown')\n : await deps.getBranchHeadSha(f.branch).catch(() => 'unknown');\n headShas.set(f.branch, anchor);\n }\n const desiredIds = new Set(readySet.map((f) => f.issueId.toUpperCase()));\n\n // Promoted is terminal: stamp positive merge evidence before stale checks,\n // and never include promoted rows in the live list passed to isStale.\n for (const gen of deps.store.listChain(projectRoot, ['ready', 'superseded'])) {\n if (await deps.isGenerationContainedInMain?.(gen).catch(() => false)) {\n log(`[uat-reconciler] stamping ${gen.name} promoted — branch is contained in main`);\n deps.store.update(gen.name, { status: 'promoted' });\n continue;\n }\n const staleReason = isStale(gen, desiredIds, headShas);\n if (!staleReason) continue;\n log(`[uat-reconciler] invalidating ${gen.name}: ${staleReason}`);\n deps.store.update(gen.name, { status: 'invalidated' });\n invalidated.push(gen.name);\n await deps.teardownStack(gen).catch((err) => {\n log(`[uat-reconciler] stack teardown for ${gen.name} failed: ${err instanceof Error ? err.message : String(err)}`);\n });\n }\n\n // 2. Un-wedge crashed assemblies; respect a live one. \"Stuck\" means NO\n // PROGRESS (updated_at stale) — the engine patches the row after every\n // member step, so total age is the wrong signal: a big batch grinding\n // through many timeboxed conflict resolutions is alive, just slow.\n const assembling = deps.store.listChain(projectRoot, ['assembling']);\n for (const gen of assembling) {\n const lastProgress = Date.parse(gen.updatedAt || gen.createdAt || '');\n // Unparseable timestamps: freshness can't be proven — treat as stuck.\n const sinceProgress = Number.isFinite(lastProgress) ? now() - lastProgress : Number.POSITIVE_INFINITY;\n if (sinceProgress > STUCK_ASSEMBLING_MS) {\n log(`[uat-reconciler] marking stuck assembling generation ${gen.name} failed (no progress for ${Math.round(sinceProgress / 60000)}m)`);\n deps.store.update(gen.name, { status: 'failed' });\n }\n }\n if (!options.force && deps.store.listChain(projectRoot, ['assembling']).length > 0) {\n return { action: 'in-flight', invalidated };\n }\n\n if (readySet.length === 0) {\n await deps.cleanup().catch(() => {});\n return { action: 'idle', invalidated };\n }\n\n // 3. Assemble when nothing live answers the current desired set.\n const desired = desiredSignature(readySet, headShas, mainSha);\n if (!options.force) {\n const live = deps.store.listChain(projectRoot, ['ready', 'superseded']);\n if (live.some((gen) => liveSignatureMatches(gen, readySet, headShas, mainSha))) {\n await deps.cleanup().catch(() => {});\n return { action: 'idle', invalidated };\n }\n const failed = deps.store.listChain(projectRoot, ['failed']);\n const recentFailure = failed.find((gen) =>\n generationSignature(gen) === desired &&\n now() - Date.parse(gen.updatedAt || gen.createdAt || '') < FAILED_RETRY_BACKOFF_MS,\n );\n if (recentFailure) {\n log(`[uat-reconciler] backing off — ${recentFailure.name} failed recently for the same input`);\n return { action: 'backoff', invalidated };\n }\n }\n\n log(`[uat-reconciler] assembling next generation for ${readySet.length} ready feature(s)`);\n const generation = await deps.assemble(readySet);\n await deps.cleanup().catch(() => {});\n return {\n action: generation.status === 'ready' ? 'assembled' : 'assembly-failed',\n invalidated,\n generation,\n };\n };\n\n const pending = tick().finally(() => inFlightReconciles.delete(projectRoot));\n inFlightReconciles.set(projectRoot, pending);\n return pending;\n}\n","/**\n * Live UAT stack lifecycle for batch generations (PAN-1737, absorbs PAN-1738).\n *\n * Each ready generation can serve a real dashboard stack from its persistent\n * worktree: the devcontainer renders via the standard FEATURE_FOLDER template,\n * so the folder name (`uat-<label>-<codename>-<mmdd>`) yields the Traefik host\n * `uat-<label>-<codename>-<mmdd>.overdeck.localhost` with zero new infra.\n *\n * HARD INVARIANT — max 2 UAT stacks run concurrently. Docker's default\n * address pool fits ~31 bridge networks; accumulating UAT stacks would\n * eventually block ALL workspace creation (\"all predefined address pools\n * have been fully subnetted\"). Starting a third stack tears down the oldest\n * first, and invalidation/promotion always tear the generation's stack down.\n *\n * All process exec is async (server-reachable code — never execSync).\n */\nimport { existsSync } from 'fs';\nimport { join, dirname, basename } from 'path';\nimport { readFile } from 'node:fs/promises';\nimport { execFile } from 'child_process';\nimport { promisify } from 'util';\nimport { ensureDevcontainerSync } from '../workspace/ensure-devcontainer.js';\nimport {\n listUatGenerationsWithStacksSync,\n setUatGenerationStackStartedAtSync,\n type UatGeneration,\n} from '../overdeck/merge-sync.js';\nimport { findProjectByPathSync } from '../projects.js';\n\nconst execFileAsync = promisify(execFile);\n\nexport const MAX_UAT_STACKS = 2;\n\nconst COMPOSE_FILES = [\n 'docker-compose.devcontainer.yml',\n 'docker-compose.yml',\n 'docker-compose.yaml',\n 'compose.yml',\n 'compose.yaml',\n];\n\nconst stackMutationLocks = new Map<string, Promise<void>>();\n\nasync function withStackMutationLock<T>(projectRoot: string, fn: () => Promise<T>): Promise<T> {\n const previous = stackMutationLocks.get(projectRoot) ?? Promise.resolve();\n let release!: () => void;\n const current = new Promise<void>((resolve) => { release = resolve; });\n const tail = previous.catch(() => {}).then(() => current);\n stackMutationLocks.set(projectRoot, tail);\n await previous.catch(() => {});\n try {\n return await fn();\n } finally {\n release();\n if (stackMutationLocks.get(projectRoot) === tail) stackMutationLocks.delete(projectRoot);\n }\n}\n\nexport interface UatStackDeps {\n /** Render `.devcontainer/` if missing; issueId only resolves project config. */\n ensureDevcontainer(workspacePath: string, issueId: string): { ok: boolean; error?: string };\n composeUp(composeFile: string, projectName: string): Promise<void>;\n composeDown(composeFile: string, projectName: string): Promise<void>;\n /** Every container the compose project has, running or exited. */\n composePs(composeFile: string, projectName: string): Promise<ComposeServiceState[]>;\n /** Service names the compose file declares under the active profiles. */\n composeServices(composeFile: string, projectName: string): Promise<string[]>;\n /** Tail of one service's logs, for the exit reason. */\n composeServiceLogs(composeFile: string, projectName: string, service: string): Promise<string>;\n findComposeFile(workspacePath: string): string | null;\n readComposeFile(composeFile: string): Promise<string>;\n store: {\n setStack(name: string, startedAt: string | null): void;\n listWithStacks(): UatGeneration[];\n };\n log?: (msg: string) => void;\n}\n\nfunction defaultDeps(): UatStackDeps {\n const compose = (args: string[], cwd: string) =>\n execFileAsync('docker', ['compose', ...args], {\n cwd,\n encoding: 'utf-8',\n timeout: 300_000,\n maxBuffer: 10 * 1024 * 1024,\n });\n\n return {\n ensureDevcontainer: (workspacePath, issueId) => {\n const result = ensureDevcontainerSync({ workspacePath, issueId });\n return result.step.success\n ? { ok: true }\n : { ok: false, error: result.step.error ?? 'devcontainer render failed' };\n },\n composeUp: async (composeFile, projectName) => {\n await compose(['-f', composeFile, '-p', projectName, 'up', '-d', '--build'], dirname(composeFile));\n },\n composeDown: async (composeFile, projectName) => {\n await compose(['-f', composeFile, '-p', projectName, 'down', '-v', '--remove-orphans'], dirname(composeFile));\n },\n composePs: async (composeFile, projectName) => {\n const { stdout } = await compose(\n ['-f', composeFile, '-p', projectName, 'ps', '--all', '--format', 'json'],\n dirname(composeFile),\n );\n return parseComposePs(stdout);\n },\n composeServices: async (composeFile, projectName) => {\n const { stdout } = await compose(['-f', composeFile, '-p', projectName, 'config', '--services'], dirname(composeFile));\n return stdout.split('\\n').map((l) => l.trim()).filter(Boolean);\n },\n composeServiceLogs: async (composeFile, projectName, service) => {\n const { stdout } = await compose(\n ['-f', composeFile, '-p', projectName, 'logs', '--no-color', '--no-log-prefix', '--tail', '200', service],\n dirname(composeFile),\n );\n return stdout;\n },\n findComposeFile: (workspacePath) => {\n const devcontainerDir = join(workspacePath, '.devcontainer');\n for (const file of COMPOSE_FILES) {\n const fullPath = join(devcontainerDir, file);\n if (existsSync(fullPath)) return fullPath;\n }\n return null;\n },\n readComposeFile: (composeFile) => readFile(composeFile, 'utf-8'),\n store: {\n setStack: (name, startedAt) => setUatGenerationStackStartedAtSync(name, startedAt),\n listWithStacks: () => listUatGenerationsWithStacksSync(),\n },\n };\n}\n\nexport function uatStackFolderName(gen: UatGeneration): string {\n return basename(gen.worktreePath);\n}\n\nfunction composeProjectName(gen: UatGeneration): string {\n return `overdeck-${uatStackFolderName(gen)}`;\n}\n\n/**\n * The frontend URL the generation's stack serves (or will serve). Prefers the\n * Host(`…`) Traefik label in the rendered compose file; falls back to the\n * FEATURE_FOLDER convention using the project's DNS domain (PAN-1696, D8, FR-10).\n */\nexport async function uatFrontendUrl(gen: UatGeneration, deps: Partial<UatStackDeps> = {}): Promise<string> {\n const d = { ...defaultDeps(), ...deps };\n const folder = uatStackFolderName(gen);\n const composeFile = d.findComposeFile(gen.worktreePath);\n if (composeFile) {\n try {\n const content = await d.readComposeFile(composeFile);\n const hostMatch = content.match(new RegExp('Host\\\\(`(' + folder.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&') + '\\\\.[^`]+)`\\\\)'));\n if (hostMatch?.[1]) return `https://${hostMatch[1]}`;\n } catch { /* fall through to convention */ }\n }\n // Resolve project config to get DNS domain (PAN-1696). Keep the fallback\n // neutral so a missing project domain cannot fabricate a branded dead link.\n const project = findProjectByPathSync(gen.projectRoot);\n const domain = project?.workspace?.dns?.domain ?? 'localhost';\n return `https://${folder}.${domain}`;\n}\n\nexport type UatStackStatus = 'running' | 'degraded' | 'unknown' | 'absent';\n\nexport interface UatStackProbe {\n status: UatStackStatus;\n frontendUrl: string;\n /** Declared services that are not serving — exited, missing, or unhealthy. */\n downServices?: string[];\n /** service → last error line from its logs, best-effort (degraded only). */\n serviceErrors?: Record<string, string>;\n /** Why the probe could not tell (unknown only). */\n probeError?: string;\n}\n\n/**\n * Probe a generation's stack. Self-corrects stale stackStartedAt records.\n *\n * PAN-3166: counting running containers is not a health signal. A stack whose\n * api container died at startup still has three healthy containers, so a count\n * reports `running` and the UI offers \"Open UAT frontend\" for a gateway\n * timeout. The probe compares what the compose file DECLARES against what is\n * actually up, and pulls the last error line from anything that is down.\n *\n * The four outcomes are deliberately distinct, because `absent` is the only one\n * that CLEARS the stack record — and clearing it destroys the evidence:\n *\n * - **zero containers** → `absent`, and the stale record self-heals. Nothing\n * ran, so there is nothing to explain.\n * - **containers present, something down or unhealthy** → `degraded`. The\n * record is preserved so the exited service's logs stay reachable; this is\n * the path that used to be misreported as `running` (api dead) and, once the\n * containers finally all stopped, silently self-healed to `absent` — which is\n * why the Flyway error was unreachable from the UI at all.\n * - **the probe itself failed** → `unknown`, record preserved. A failed\n * `docker compose ps` is not proof of absence, and must never clear the row.\n */\nexport async function probeUatStack(gen: UatGeneration, deps: Partial<UatStackDeps> = {}): Promise<UatStackProbe> {\n const d = { ...defaultDeps(), ...deps };\n const frontendUrl = await uatFrontendUrl(gen, d);\n const composeFile = d.findComposeFile(gen.worktreePath);\n if (!composeFile || !gen.stackStartedAt) return { status: 'absent', frontendUrl };\n\n const project = composeProjectName(gen);\n let containers: ComposeServiceState[];\n try {\n containers = await d.composePs(composeFile, project);\n } catch (err) {\n const probeError = err instanceof Error ? (err.message.split('\\n')[0] ?? 'compose ps failed') : String(err);\n d.log?.(`[uat-stack] ${gen.name}: probe failed (${probeError}) — leaving the stack record intact`);\n return { status: 'unknown', frontendUrl, probeError };\n }\n\n if (containers.length === 0) {\n // Nothing was ever created for this project — the record is stale, heal it.\n try { d.store.setStack(gen.name, null); } catch { /* row may be gone */ }\n return { status: 'absent', frontendUrl };\n }\n\n let declared: string[];\n try {\n declared = await d.composeServices(composeFile, project);\n } catch (err) {\n const probeError = err instanceof Error ? (err.message.split('\\n')[0] ?? 'compose config failed') : String(err);\n d.log?.(`[uat-stack] ${gen.name}: could not read declared services (${probeError})`);\n return { status: 'unknown', frontendUrl, probeError };\n }\n\n const down = downServices(declared, containers);\n if (down.length === 0) return { status: 'running', frontendUrl };\n\n const serviceErrors: Record<string, string> = {};\n for (const service of down) {\n const logs = await d.composeServiceLogs(composeFile, project, service).catch(() => '');\n const line = lastErrorLine(logs);\n if (line) serviceErrors[service] = line;\n }\n d.log?.(`[uat-stack] ${gen.name}: degraded — ${down.join(', ')} not serving`);\n return {\n status: 'degraded',\n frontendUrl,\n downServices: down,\n ...(Object.keys(serviceErrors).length > 0 ? { serviceErrors } : {}),\n };\n}\n\n/** One compose service's container state, as `docker compose ps --all` reports it. */\nexport interface ComposeServiceState {\n service: string;\n running: boolean;\n /** Human status, e.g. `Exited (0) 7 minutes ago`. */\n status: string;\n exitCode: number | null;\n /** Compose healthcheck verdict when the service declares one. */\n health?: string;\n}\n\ninterface ComposePsJson {\n Service?: string;\n State?: string;\n Status?: string;\n ExitCode?: number;\n Health?: string;\n}\n\n/**\n * `docker compose ps --format json` emits either one JSON array or one object\n * per line, depending on the Compose version — parse both.\n */\nexport function parseComposePs(stdout: string): ComposeServiceState[] {\n const rows: ComposePsJson[] = [];\n const trimmed = stdout.trim();\n if (!trimmed) return [];\n if (trimmed.startsWith('[')) {\n try { rows.push(...(JSON.parse(trimmed) as ComposePsJson[])); } catch { /* fall through to per-line */ }\n }\n if (rows.length === 0) {\n for (const line of trimmed.split('\\n')) {\n if (!line.trim().startsWith('{')) continue;\n try { rows.push(JSON.parse(line) as ComposePsJson); } catch { /* skip malformed row */ }\n }\n }\n return rows\n .filter((row): row is ComposePsJson & { Service: string } => Boolean(row.Service))\n .map((row) => ({\n service: row.Service,\n running: (row.State ?? '').toLowerCase() === 'running',\n status: row.Status ?? row.State ?? '',\n exitCode: typeof row.ExitCode === 'number' ? row.ExitCode : null,\n // Compose reports Health only for services declaring a healthcheck; some\n // versions fold it into Status as `Up 2 minutes (unhealthy)` instead.\n ...(row.Health\n ? { health: row.Health.toLowerCase() }\n : /\\(unhealthy\\)/i.test(row.Status ?? '')\n ? { health: 'unhealthy' }\n : {}),\n }));\n}\n\n/**\n * Init containers exit by design — the same convention the workspace stack\n * health check uses (`src/lib/workspace/stack-health.ts`). Everything else the\n * compose file declares is expected to stay up.\n */\nfunction isOneShotService(service: string): boolean {\n return /(^|[-_])(?:init|init-perms|test-unit)($|[-_])/.test(service.toLowerCase());\n}\n\n/**\n * Declared services that are not serving — the honest reading of \"is this\n * stack usable?\". Three ways to fail:\n *\n * - no container at all, or one that is not running;\n * - running, but Compose reports its healthcheck `unhealthy` (a crash-looping\n * or wedged api is as unusable as a dead one);\n * - a one-shot that exited non-zero — a failed init means whatever depended on\n * it never came up either. A one-shot exiting cleanly is by design.\n */\nexport function downServices(\n declared: readonly string[],\n containers: readonly ComposeServiceState[],\n): string[] {\n const byService = new Map(containers.map((c) => [c.service, c]));\n const down: string[] = [];\n for (const service of declared) {\n const container = byService.get(service);\n if (isOneShotService(service)) {\n if (container && !container.running && (container.exitCode ?? 0) !== 0) down.push(service);\n continue;\n }\n if (!container || !container.running || container.health === 'unhealthy') down.push(service);\n }\n return down;\n}\n\nconst ERROR_LINE = /(caused by:|exception|error|fatal|panic:|\\bfailed\\b)/i;\n\n/** The most informative line from a dead service's log tail. */\nexport function lastErrorLine(logs: string): string | null {\n const lines = logs.split('\\n').map((l) => l.trimEnd()).filter((l) => l.trim().length > 0);\n if (lines.length === 0) return null;\n // `Caused by:` is the root cause in a JVM stack trace and is what a Flyway\n // version collision surfaces as — prefer it over the generic wrapper above it.\n const caused = [...lines].reverse().find((l) => /caused by:/i.test(l));\n const errorish = caused ?? [...lines].reverse().find((l) => ERROR_LINE.test(l));\n const line = errorish ?? lines[lines.length - 1]!;\n return line.trim().slice(0, 300);\n}\n\nexport interface EnsureUatStackResult {\n success: boolean;\n error?: string;\n frontendUrl?: string;\n /** Generations whose stacks were torn down to respect MAX_UAT_STACKS. */\n evicted: string[];\n}\n\n/**\n * Bring up the live stack for a generation (idempotent — compose up on a\n * running project is a no-op). Enforces MAX_UAT_STACKS by tearing down the\n * oldest running UAT stack(s) first.\n */\nexport async function ensureUatStack(gen: UatGeneration, deps: Partial<UatStackDeps> = {}): Promise<EnsureUatStackResult> {\n return withStackMutationLock(gen.projectRoot, async () => {\n const d = { ...defaultDeps(), ...deps };\n const log = d.log ?? (() => {});\n const evicted: string[] = [];\n\n const member = gen.members[0];\n if (!member) return { success: false, error: 'generation has no members', evicted };\n\n // Cap enforcement happens inside the lock and re-reads the live stack set so\n // concurrent start requests cannot both observe the same stale pre-start set.\n const others = d.store.listWithStacks().filter((g) => g.name !== gen.name);\n while (others.length > 0 && others.length >= MAX_UAT_STACKS) {\n const oldest = others.shift()!;\n log(`[uat-stack] cap ${MAX_UAT_STACKS} reached — tearing down oldest stack ${oldest.name}`);\n try {\n await teardownUatStackUnlocked(oldest, d);\n } catch (err) {\n return {\n success: false,\n error: err instanceof Error ? (err.message.split('\\n')[0] ?? 'stack teardown failed') : String(err),\n evicted,\n };\n }\n evicted.push(oldest.name);\n }\n\n const rendered = d.ensureDevcontainer(gen.worktreePath, member.issueId);\n if (!rendered.ok) return { success: false, error: rendered.error ?? 'devcontainer render failed', evicted };\n\n const composeFile = d.findComposeFile(gen.worktreePath);\n if (!composeFile) {\n return { success: false, error: `no compose file under ${gen.worktreePath}/.devcontainer`, evicted };\n }\n\n try {\n await d.composeUp(composeFile, composeProjectName(gen));\n } catch (err) {\n return {\n success: false,\n error: err instanceof Error ? (err.message.split('\\n')[0] ?? 'compose up failed') : String(err),\n evicted,\n };\n }\n\n d.store.setStack(gen.name, new Date().toISOString());\n const frontendUrl = await uatFrontendUrl(gen, d);\n log(`[uat-stack] ${gen.name}: stack up at ${frontendUrl}`);\n return { success: true, frontendUrl, evicted };\n });\n}\n\nasync function teardownUatStackUnlocked(gen: UatGeneration, d: UatStackDeps): Promise<void> {\n const composeFile = d.findComposeFile(gen.worktreePath);\n if (composeFile) {\n try {\n await d.composeDown(composeFile, composeProjectName(gen));\n } catch (err) {\n d.log?.(`[uat-stack] ${gen.name}: compose down failed: ${err instanceof Error ? err.message.split('\\n')[0] : String(err)}`);\n throw err;\n }\n }\n try { d.store.setStack(gen.name, null); } catch { /* row may be gone */ }\n}\n\n/**\n * Tear the generation's stack down (idempotent) and clear its stack record.\n * MUST be called on invalidation, promotion, and generation cleanup —\n * orphaned uat networks eventually block all workspace creation.\n */\nexport async function teardownUatStack(gen: UatGeneration, deps: Partial<UatStackDeps> = {}): Promise<void> {\n await withStackMutationLock(gen.projectRoot, async () => {\n const d = { ...defaultDeps(), ...deps };\n await teardownUatStackUnlocked(gen, d);\n });\n}\n","/**\n * Batch promotion (PAN-1737: UAT batch trains) — \"merge what you tested\".\n *\n * Promoting a generation merges the uat/* branch itself into main (one no-ff\n * merge), so main receives EXACTLY the tree the operator exercised — including\n * the assembly agent's conflict resolutions. GitHub automatically marks each\n * feature PR merged because its head becomes reachable from main, and every\n * member issue then flows through the standard per-issue post-merge lifecycle\n * exactly once, behind the PAN-328\n * in-flight guard.\n *\n * Hard precondition: the generation's baseSha must still equal origin/main —\n * promoting a stale tree would silently drop commits that landed since\n * assembly. Stale promotes are rejected with reassemble guidance (the\n * reconciler rebuilds automatically anyway).\n *\n * Pure orchestration with injected deps; real git in buildUatPromoteGitDeps.\n */\nimport { join } from 'path';\nimport { tmpdir } from 'os';\nimport { promisify } from 'util';\nimport { execFile } from 'child_process';\nimport type { UatGeneration, UatGenerationRepo, UatGenerationStatus } from '../overdeck/merge-sync.js';\nimport type { VerifiedMergedRepo } from './merge-verification.js';\nimport type { GenerationStorePort } from './uat-generation-engine.js';\n\nconst execFileAsync = promisify(execFile);\n\nfunction safeUatBranchName(branchName: string): string {\n if (!/^uat\\/[A-Za-z0-9][A-Za-z0-9._-]*$/.test(branchName)) {\n throw new Error(`unsafe UAT branch name: ${branchName}`);\n }\n return branchName;\n}\n\nexport interface UatPromoteGitDeps {\n /** `git fetch origin main` and return the origin/main head SHA. */\n fetchMain(): Promise<string>;\n /**\n * Merge the generation branch into main with the given message (no-ff, in a\n * throwaway worktree — NEVER the primary checkout) and push. Returns the\n * merge commit SHA.\n */\n mergeIntoMain(branchName: string, message: string): Promise<string>;\n /** Files changed on origin/main since the given SHA (base-movement footprint). */\n changedFilesSince(baseSha: string): Promise<string[]>;\n /** Files the batch branch changes vs its merge-base with origin/main. */\n batchChangedFiles(branchName: string): Promise<string[]>;\n}\n\n/**\n * A merge prepared in a throwaway worktree but NOT pushed. Phase A produces one\n * per repo; phase B publishes them. The opaque `handle` is whatever the git\n * implementation needs to find the prepared tree again (a worktree path in the\n * real wiring).\n */\nexport interface PreparedRepoMerge {\n repoKey: string;\n handle: string;\n mergeSha: string;\n}\n\n/** Per-repo promote operations for one member repo of a polyrepo project. */\nexport interface PolyrepoRepoPromoteGit {\n /** Fetch the repo's target branch and return its head SHA. */\n targetHeadSha(): Promise<string>;\n /** Files changed on the target branch since the given SHA. */\n changedFilesSince(baseSha: string): Promise<string[]>;\n /** Files this repo's uat branch changes vs its merge-base with the target. */\n batchChangedFiles(branchName: string): Promise<string[]>;\n /** Merge the uat branch into the target locally. MUST NOT push. */\n trialMerge(branchName: string, message: string): Promise<PreparedRepoMerge>;\n /** Push an already-prepared merge to the target branch. */\n publishPrepared(prepared: PreparedRepoMerge): Promise<void>;\n /** Drop a prepared merge's worktree. Must be safe to call twice. */\n discardPrepared(prepared: PreparedRepoMerge): Promise<void>;\n /**\n * If this repo's uat branch is ALREADY contained in its target branch, the\n * merge commit that brought it in; otherwise null.\n *\n * Publishing and stamping are two operations against two systems. A process\n * death, a thrown state write, or a dropped connection after the remote\n * accepted the push leaves a repo genuinely landed with `promoted_at` null.\n * Retry would then re-classify it as pending and — because the target has\n * moved over the batch's own files — reject the whole promote as stale-base,\n * wedging a half-landed batch permanently. This lets recovery prove the truth\n * from git instead of trusting local state.\n */\n findLandedMerge(branchName: string): Promise<string | null>;\n}\n\nexport interface UatPromoteDeps {\n git: UatPromoteGitDeps;\n /**\n * Per-repo git for a polyrepo project, keyed by repoKey. Its PRESENCE — not\n * a repo count — selects the two-phase path: a polyrepo project with a single\n * contributing repo still has no git repo at its own project root, so the\n * monorepo path would run git against a non-repo and fail.\n */\n polyrepoGit?: ReadonlyMap<string, PolyrepoRepoPromoteGit>;\n /**\n * Record that one repo's merge landed, so a retry after a partial publish\n * skips it. Required whenever polyrepoGit is supplied.\n */\n markRepoPromoted?: (name: string, repoKey: string, promotedAt: string, mergeSha?: string) => void;\n now?: () => Date;\n store: GenerationStorePort & { get(name: string): UatGeneration | null };\n teardownStack(generation: UatGeneration): Promise<void>;\n /**\n * Kick off the per-issue post-merge lifecycle (through the PAN-328 guard).\n * Returns false when a run for that issue is already in flight.\n */\n firePostMerge(\n issueId: string,\n options?: {\n sourceBranch?: string;\n verifiedMergedRef?: string;\n /** Per-repo merge evidence for a polyrepo batch (PAN-3093). */\n verifiedMergedRepos?: readonly VerifiedMergedRepo[];\n },\n ): boolean;\n /**\n * Authoritative per-member merge eligibility (PAN-1759), checked at promote\n * time as defense-in-depth: batch membership is computed from orchestrator\n * verbs at assembly time, and a member's pipeline state can change (or have\n * been wrong) between assembly and the operator clicking Merge. RUN-20\n * assembled a batch containing a mid-review issue; this gate refuses to land\n * it on main.\n */\n memberEligibility(issueId: string): { eligible: boolean; reason?: string };\n recordVerification?: (generation: UatGeneration, mergeSha: string) => void;\n runShip?: (\n generation: UatGeneration,\n shipVersion: string | undefined,\n promotedRepos: readonly UatGenerationRepo[],\n ) => Promise<void>;\n log?: (msg: string) => void;\n}\n\nexport type PromoteFailureReason = 'not-found' | 'wrong-status' | 'member-not-ready' | 'stale-base' | 'merge-failed';\n\nexport type PromoteResult =\n | {\n success: true;\n generation: string;\n mergeSha: string;\n members: string[];\n /** Members whose post-merge lifecycle started (false = already in flight). */\n postMergeStarted: string[];\n invalidated: string[];\n }\n | { success: false; reason: PromoteFailureReason; message: string };\n\nconst PROMOTABLE: readonly UatGenerationStatus[] = ['ready', 'superseded'];\n\nexport function buildPromoteMergeMessage(gen: UatGeneration): string {\n const ids = gen.members.map((m) => m.issueId).join(', ');\n const resolutionNote = gen.resolutions.length > 0\n ? `\\n\\nIncludes ${gen.resolutions.length} uat-assembly conflict resolution(s): ` +\n gen.resolutions.map((r) => r.issueIds.join(' <-> ')).join('; ')\n : '';\n return `Merge UAT batch ${gen.name} (${ids})${resolutionNote}`;\n}\n\nexport async function promoteUatGeneration(\n name: string,\n projectRoot: string,\n deps: UatPromoteDeps,\n options: { shipVersion?: string } = {},\n): Promise<PromoteResult> {\n const log = deps.log ?? (() => {});\n\n const gen = deps.store.get(name);\n if (!gen || gen.projectRoot !== projectRoot) {\n return { success: false, reason: 'not-found', message: `No UAT generation named ${name}` };\n }\n if (!PROMOTABLE.includes(gen.status)) {\n return {\n success: false,\n reason: 'wrong-status',\n message: `${name} is ${gen.status} — only a ready or superseded batch can be merged to main`,\n };\n }\n\n const notReady = gen.members\n .map((m) => ({ issueId: m.issueId, gate: deps.memberEligibility(m.issueId) }))\n .filter(({ gate }) => !gate.eligible);\n if (notReady.length > 0) {\n const detail = notReady.map(({ issueId, gate }) => `${issueId} (${gate.reason ?? 'not eligible'})`).join(', ');\n return {\n success: false,\n reason: 'member-not-ready',\n message:\n `${name} contains member(s) the pipeline has not cleared to merge: ${detail} — ` +\n `wait for them to pass review+test, or for the reconciler to rebuild the batch without them.`,\n };\n }\n\n if (deps.polyrepoGit) {\n return promotePolyrepo(gen, projectRoot, deps, deps.polyrepoGit, log, options.shipVersion);\n }\n\n const mainSha = await deps.git.fetchMain();\n if (gen.baseSha !== mainSha) {\n // Main moved since assembly. An active flywheel run lands commits on main\n // continuously, so exact base equality would make ready batches almost\n // never promotable (first live run, 2026-06-10). Safety is preserved by a\n // narrower check: reject only when main's NEW commits touch files the\n // batch also touches — then the tested behavior genuinely may not match\n // what lands. Disjoint movement (docs, unrelated fixes) proceeds; the\n // no-ff merge itself still hard-fails on any textual conflict.\n const [mainChanged, batchChanged] = await Promise.all([\n deps.git.changedFilesSince(gen.baseSha),\n deps.git.batchChangedFiles(gen.name),\n ]);\n const batchSet = new Set(batchChanged);\n const overlap = mainChanged.filter((f) => batchSet.has(f));\n if (overlap.length > 0) {\n return {\n success: false,\n reason: 'stale-base',\n message:\n `${name} was assembled off ${gen.baseSha.slice(0, 9)} but main is now at ${mainSha.slice(0, 9)}, ` +\n `and the new commits touch ${overlap.length} file(s) this batch also changes ` +\n `(${overlap.slice(0, 3).join(', ')}${overlap.length > 3 ? ', …' : ''}) — ` +\n `the tree you tested may not match what would land. A fresh batch reassembles automatically; re-test before merging.`,\n };\n }\n log(`[uat-promote] ${name}: base moved ${gen.baseSha.slice(0, 9)} → ${mainSha.slice(0, 9)} with no member-file overlap — proceeding`);\n }\n\n let mergeSha: string;\n try {\n mergeSha = await deps.git.mergeIntoMain(gen.name, buildPromoteMergeMessage(gen));\n } catch (err) {\n return {\n success: false,\n reason: 'merge-failed',\n message: err instanceof Error ? (err.message.split('\\n')[0] ?? 'merge failed') : String(err),\n };\n }\n log(`[uat-promote] ${name}: merged to main at ${mergeSha.slice(0, 9)} (${gen.members.length} member(s))`);\n\n // The batch is on main: this generation is done, every other live\n // generation is stale by definition (main moved).\n return finishPromote(gen, projectRoot, deps, mergeSha, log, false, options.shipVersion);\n}\n\n/**\n * Composite merge reference for a polyrepo generation, rebuilt from the stored\n * per-repo merge SHAs rather than an in-memory list, so it survives a restart\n * between the last publish and finalization.\n */\n/**\n * A merge that is live on its remote but has no durable record of it.\n *\n * Not rolled back — main is never rewound — and deliberately NOT finalized:\n * marking the generation promoted here would fire post-merge lifecycles against\n * `uat_generation_repos` rows that still read pending, and would end the\n * generation's promotable life, so the retry that is supposed to recover the\n * stamp could never run. Staying promotable is what makes recovery possible.\n */\nfunction unstampableFailure(\n gen: UatGeneration,\n unstampable: ReadonlyArray<{ repoKey: string; mergeSha: string; error: unknown }>,\n log: (msg: string) => void,\n): PromoteResult {\n const detail = unstampable\n .map((u) => `${u.repoKey}@${u.mergeSha.slice(0, 9)} (${u.error instanceof Error ? u.error.message.split('\\n')[0] : String(u.error)})`)\n .join('; ');\n log(`[uat-promote] ${gen.name}: merges are live but unrecorded — not finalizing: ${detail}`);\n return {\n success: false,\n reason: 'merge-failed',\n message:\n `${gen.name}: ${unstampable.length} repo merge(s) are LIVE on their target but could not be recorded: ${detail}. ` +\n `Nothing is rolled back — a landed merge is never rewound. The batch stays promotable: fix the state store and ` +\n `retry, and the already-landed repos are detected and skipped rather than republished.`,\n };\n}\n\n/**\n * A repo row that claims a promote stamp but has no merge SHA and no merge\n * findable on its target.\n *\n * Deliberately non-terminal. `promotedAt` on its own is enough to keep the row\n * out of the pending set, so letting it through would finalize a generation\n * that can never produce complete per-repo merge evidence: post-merge\n * verification would refuse every member forever, while the terminal `promoted`\n * status would block the retry that is the only way to recover the missing SHA.\n * Failing here keeps the batch promotable so a later attempt — once the remote\n * is reachable, or the row is repaired — can finish it properly.\n */\nfunction unprovenStampFailure(\n gen: UatGeneration,\n unproven: ReadonlyArray<{ repoKey: string; why: string }>,\n log: (msg: string) => void,\n): PromoteResult {\n const detail = unproven.map((u) => `${u.repoKey} (${u.why})`).join('; ');\n log(`[uat-promote] ${gen.name}: stamped repo(s) cannot be proven merged — not finalizing: ${detail}`);\n return {\n success: false,\n reason: 'merge-failed',\n message:\n `${gen.name}: ${unproven.length} repo(s) are recorded as promoted but their merge cannot be proven: ${detail}. ` +\n `Nothing was published on this attempt. A batch is only finalized once every repo carries both a promote ` +\n `timestamp and the merge commit it landed, because post-merge verification checks each repo's merge against ` +\n `its own target branch and refuses on missing evidence. The batch stays promotable: retry once the remote is ` +\n `reachable, or correct the repo row, and repos that genuinely landed are detected and skipped rather than ` +\n `republished.`,\n };\n}\n\n/** The generation with its repo rows replaced by recovered/updated ones. */\nfunction withRepos(gen: UatGeneration, repos: readonly UatGenerationRepo[]): UatGeneration {\n return { ...gen, repos: [...repos] };\n}\n\nfunction landedMergeRef(repos: readonly UatGenerationRepo[]): string {\n return [...repos]\n .sort((a, b) => a.mergeOrder - b.mergeOrder)\n .map((r) => `${r.repoKey}@${(r.mergeSha ?? 'landed').slice(0, 7)}`)\n .join(' ');\n}\n\n/**\n * Two-phase polyrepo promote.\n *\n * Phase A validates and trial-merges EVERY repo without pushing anything, so\n * the all-or-nothing decision is made while nothing is published: a failure in\n * repo 3 of 3 leaves repos 1 and 2 untouched on their remotes.\n *\n * Phase B publishes the prepared merges in merge order. A failure partway is\n * NOT rolled back — undoing a landed merge means force-pushing a member repo's\n * main, a one-way door. It is instead resumable: each landed repo is stamped\n * with promoted_at, the result names landed vs pending, the generation stays\n * promotable, and a retry skips whatever already landed.\n */\nasync function promotePolyrepo(\n gen: UatGeneration,\n projectRoot: string,\n deps: UatPromoteDeps,\n repoGit: ReadonlyMap<string, PolyrepoRepoPromoteGit>,\n log: (msg: string) => void,\n shipVersion?: string,\n): Promise<PromoteResult> {\n const allRepos = [...(gen.repos ?? [])].sort((a, b) => a.mergeOrder - b.mergeOrder);\n if (allRepos.length === 0) {\n return { success: false, reason: 'merge-failed', message: `${gen.name} records no member repos to promote` };\n }\n\n const now = deps.now ?? (() => new Date());\n /** Repos whose merge is live on the remote but whose durable stamp failed. */\n const unstampable: Array<{ repoKey: string; mergeSha: string; error: unknown }> = [];\n /** Repos that claim a promote stamp but can produce no merge SHA to prove it. */\n const unproven: Array<{ repoKey: string; why: string }> = [];\n\n // Reconcile local state against the remotes BEFORE classifying anything as\n // pending. A repo whose push succeeded but whose stamp never landed is\n // genuinely published; treating it as pending would re-trial-merge it and\n // then reject the promote as stale-base, because the target has moved over\n // this batch's own files.\n const recovered: UatGenerationRepo[] = [];\n for (const repo of allRepos) {\n // A stamped row with no merge SHA still needs remote recovery: evidence is\n // all-or-nothing, so one such row withholds it for the whole generation and\n // post-merge verification refuses forever. Recovering the SHA is the only\n // way out, and it is exactly what findLandedMerge answers.\n if (repo.promotedAt && repo.mergeSha) { recovered.push(repo); continue; }\n const git = repoGit.get(repo.repoKey);\n if (!git) {\n // An unstamped row without deps is simply pending, and the `missing` check\n // below rejects it by name. A STAMPED one is the dangerous case: the stamp\n // alone keeps it out of `pending`, so with no way to look up its merge it\n // would sail through to finalization unproven.\n if (repo.promotedAt) {\n unproven.push({ repoKey: repo.repoKey, why: 'there are no promote git deps for it, so its target cannot be inspected' });\n }\n recovered.push(repo);\n continue;\n }\n\n let landedSha: string | null = null;\n try {\n landedSha = await git.findLandedMerge(repo.branch);\n } catch (err) {\n // Unknown is not \"pending\". Treating it as pending would trial-merge and\n // possibly republish a merge that is already live.\n return {\n success: false,\n reason: 'merge-failed',\n message:\n `${gen.name}: could not establish whether ${repo.repoKey} has already landed ` +\n `(${err instanceof Error ? err.message.split('\\n')[0] : String(err)}). ` +\n `Nothing was published; retry once the remote is reachable.`,\n };\n }\n if (!landedSha) {\n // No stamp and no landed merge is the ordinary pending case. A stamp with\n // no merge SHA and no merge on the target is a row that CLAIMS to have\n // been promoted and cannot back it up — the probe just came back empty, so\n // there is no evidence left to find on this attempt.\n if (repo.promotedAt) {\n unproven.push({\n repoKey: repo.repoKey,\n why: `it is stamped promoted at ${repo.promotedAt} with no recorded merge sha, and ${repo.branch} is not contained in ${repo.targetBranch ?? 'main'}`,\n });\n }\n recovered.push(repo);\n continue;\n }\n\n log(\n `[uat-promote] ${gen.name}: ${repo.repoKey} is contained in its target (${landedSha.slice(0, 9)}) ` +\n `${repo.promotedAt ? 'but has no recorded merge sha' : 'but was never stamped'} — recovering`,\n );\n const promotedAt = repo.promotedAt ?? now().toISOString();\n try {\n deps.markRepoPromoted?.(gen.name, repo.repoKey, promotedAt, landedSha);\n } catch (err) {\n // Do NOT fabricate promoted state in memory. The canonical row still says\n // pending, so finalizing here would mark the generation terminal, fire\n // post-merge, and destroy the very state the \"next attempt recovers\" story\n // depends on — there would be no next attempt.\n unstampable.push({ repoKey: repo.repoKey, mergeSha: landedSha, error: err });\n log(`[uat-promote] ${gen.name}: recovery stamp failed for ${repo.repoKey}: ${err instanceof Error ? err.message : String(err)}`);\n recovered.push(repo);\n continue;\n }\n recovered.push({ ...repo, promotedAt, mergeSha: landedSha });\n }\n\n if (unstampable.length > 0) {\n return unstampableFailure(gen, unstampable, log);\n }\n if (unproven.length > 0) {\n return unprovenStampFailure(gen, unproven, log);\n }\n\n const alreadyLanded = recovered.filter((r) => r.promotedAt);\n const pending = recovered.filter((r) => !r.promotedAt);\n if (alreadyLanded.length > 0) {\n log(`[uat-promote] ${gen.name}: resuming — ${alreadyLanded.map((r) => r.repoKey).join(', ')} already landed`);\n }\n if (pending.length === 0) {\n // Every repo already landed. This is the crash window between the last\n // publish stamp and finalization, and retry is the documented recovery —\n // so it is the idempotent completion case, not an error. Rejecting it\n // would leave a fully-published batch permanently unfinalizable: never\n // promoted, no verdicts recorded, no stack teardown, no post-merge.\n log(`[uat-promote] ${gen.name}: every repo already published — finalizing`);\n return finishPromote(\n withRepos(gen, recovered),\n projectRoot,\n deps,\n landedMergeRef(recovered),\n log,\n true,\n shipVersion,\n );\n }\n\n const missing = pending.filter((r) => !repoGit.has(r.repoKey));\n if (missing.length > 0) {\n return {\n success: false,\n reason: 'merge-failed',\n message: `${gen.name}: no promote git deps for repo(s) ${missing.map((r) => r.repoKey).join(', ')}`,\n };\n }\n\n const message = buildPromoteMergeMessage(gen);\n const prepared: PreparedRepoMerge[] = [];\n const discardAll = async () => {\n for (const p of prepared) {\n await repoGit.get(p.repoKey)!.discardPrepared(p).catch((err) => {\n log(`[uat-promote] ${gen.name}: discarding ${p.repoKey} trial merge failed: ${err instanceof Error ? err.message : String(err)}`);\n });\n }\n };\n\n // ── Phase A: validate + trial-merge everything, push nothing ──────────────\n for (const repo of pending) {\n const git = repoGit.get(repo.repoKey)!;\n\n let targetHead: string;\n try {\n targetHead = await git.targetHeadSha();\n } catch (err) {\n await discardAll();\n return {\n success: false,\n reason: 'merge-failed',\n message: `${gen.name}: could not read ${repo.repoKey} target head: ${err instanceof Error ? err.message : String(err)}`,\n };\n }\n\n if (repo.baseSha !== targetHead) {\n // Same disjoint-movement rule as the monorepo path, per repo: reject only\n // when this repo's target gained commits touching files this repo's uat\n // branch also changes.\n const [targetChanged, batchChanged] = await Promise.all([\n git.changedFilesSince(repo.baseSha),\n git.batchChangedFiles(repo.branch),\n ]);\n const batchSet = new Set(batchChanged);\n const overlap = targetChanged.filter((f) => batchSet.has(f));\n if (overlap.length > 0) {\n await discardAll();\n return {\n success: false,\n reason: 'stale-base',\n message:\n `${gen.name} was assembled off ${repo.repoKey}@${repo.baseSha.slice(0, 9)} but that repo is now at ` +\n `${targetHead.slice(0, 9)}, and the new commits touch ${overlap.length} file(s) this batch also changes ` +\n `(${overlap.slice(0, 3).join(', ')}${overlap.length > 3 ? ', …' : ''}) — ` +\n `the tree you tested may not match what would land. Nothing was published. A fresh batch reassembles automatically; re-test before merging.`,\n };\n }\n log(`[uat-promote] ${gen.name}: ${repo.repoKey} base moved ${repo.baseSha.slice(0, 9)} → ${targetHead.slice(0, 9)} with no member-file overlap — proceeding`);\n }\n\n try {\n prepared.push(await git.trialMerge(repo.branch, message));\n } catch (err) {\n await discardAll();\n return {\n success: false,\n reason: 'merge-failed',\n message:\n `${gen.name}: trial merge failed in ${repo.repoKey} — ` +\n `${err instanceof Error ? (err.message.split('\\n')[0] ?? 'merge failed') : String(err)}. ` +\n `Nothing was published; the batch is unchanged and still promotable.`,\n };\n }\n }\n\n log(`[uat-promote] ${gen.name}: all ${prepared.length} repo(s) trial-merged cleanly — publishing`);\n\n // ── Phase B: publish; a failure here is resumable, never rolled back ──────\n const landed: Array<{ repoKey: string; mergeSha: string }> = [];\n for (const p of prepared) {\n try {\n await repoGit.get(p.repoKey)!.publishPrepared(p);\n } catch (err) {\n const stillPending = prepared.slice(prepared.indexOf(p)).map((x) => x.repoKey);\n await discardAll();\n return {\n success: false,\n reason: 'merge-failed',\n message:\n `${gen.name}: published ${landed.length > 0 ? landed.map((l) => l.repoKey).join(', ') : 'no repos'} ` +\n `but failed on ${p.repoKey} (${err instanceof Error ? (err.message.split('\\n')[0] ?? 'push failed') : String(err)}). ` +\n `Still pending: ${stillPending.join(', ')}. Nothing is rolled back — main is never rewound — ` +\n `retry the merge and the repos that already landed are skipped.`,\n };\n }\n landed.push({ repoKey: p.repoKey, mergeSha: p.mergeSha });\n try {\n deps.markRepoPromoted?.(gen.name, p.repoKey, now().toISOString(), p.mergeSha);\n } catch (err) {\n // The push succeeded, so the merge is live and must never be rolled back —\n // but without the durable stamp the canonical row still says pending.\n // Finalizing anyway would mark the generation terminal on state that does\n // not exist, so stop short of finalizing and stay promotable.\n unstampable.push({ repoKey: p.repoKey, mergeSha: p.mergeSha, error: err });\n log(`[uat-promote] ${gen.name}: publish stamp failed for ${p.repoKey} (merge IS live at ${p.mergeSha.slice(0, 9)}): ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n if (unstampable.length > 0) {\n await discardAll();\n return unstampableFailure(gen, unstampable, log);\n }\n\n await discardAll();\n\n // Built from stored SHAs plus this run's, so a resumed promote reports the\n // same reference a single-pass promote would.\n const landedByKey = new Map(landed.map((l) => [l.repoKey, l.mergeSha]));\n const mergeSha = landedMergeRef(\n recovered.map((r) => ({ ...r, mergeSha: landedByKey.get(r.repoKey) ?? r.mergeSha ?? null })),\n );\n log(`[uat-promote] ${gen.name}: merged to ${landed.length} repo target(s) — ${mergeSha}`);\n\n // finishPromote needs the per-repo merge evidence for post-merge verification,\n // and the in-memory gen predates this run's publishes.\n const promotedGen: UatGeneration = {\n ...gen,\n repos: recovered.map((r) => ({\n ...r,\n mergeSha: landedByKey.get(r.repoKey) ?? r.mergeSha ?? null,\n promotedAt: r.promotedAt ?? (landedByKey.has(r.repoKey) ? now().toISOString() : null),\n })),\n };\n\n return finishPromote(promotedGen, projectRoot, deps, mergeSha, log, true, shipVersion);\n}\n\n/**\n * Everything after the merge itself lands: status, verification verdicts, stack\n * teardown, invalidating the rest of the chain, and the per-issue post-merge\n * handoff. Shared so the monorepo and polyrepo paths cannot drift.\n */\nasync function finishPromote(\n gen: UatGeneration,\n projectRoot: string,\n deps: UatPromoteDeps,\n mergeSha: string,\n log: (msg: string) => void,\n isPolyrepoGeneration = false,\n shipVersion?: string,\n): Promise<PromoteResult> {\n // The one place a generation becomes terminal, so the completeness invariant\n // is enforced here rather than at each caller: a polyrepo batch is finalized\n // only when EVERY repo carries both a promote stamp and the merge commit it\n // landed. Anything less cannot supply the per-repo evidence post-merge\n // verification needs, and `promoted` would foreclose the retry that could\n // still recover it.\n if (isPolyrepoGeneration) {\n const incomplete = (gen.repos ?? []).filter((r) => !r.promotedAt || !r.mergeSha);\n if (incomplete.length > 0) {\n return unprovenStampFailure(\n gen,\n incomplete.map((r) => ({\n repoKey: r.repoKey,\n why: r.promotedAt ? 'no merge sha was recorded for it' : 'it was never stamped as promoted',\n })),\n log,\n );\n }\n }\n\n const promotedAt = (deps.now?.() ?? new Date()).toISOString();\n const promotedRepos: UatGenerationRepo[] = isPolyrepoGeneration\n ? (gen.repos ?? [])\n : [{\n ...(gen.repos?.[0] ?? {\n repoKey: projectRoot.replace(/\\/+$/, '').split('/').pop() || projectRoot,\n repoPath: projectRoot,\n branch: gen.name,\n baseSha: gen.baseSha,\n targetBranch: 'main',\n worktreePath: gen.worktreePath,\n mergeOrder: 0,\n }),\n repoPath: projectRoot,\n targetBranch: 'main',\n promotedAt,\n mergeSha,\n }];\n const promotedGeneration: UatGeneration = { ...gen, status: 'promoted', repos: promotedRepos };\n\n // Persist the exact landed refs before any ship work. Deferred ship must root\n // its worktrees at these commits rather than the ambient primary checkout.\n deps.store.update(gen.name, { status: 'promoted', repos: promotedRepos });\n try {\n deps.recordVerification?.(promotedGeneration, mergeSha);\n } catch (err) {\n log(`[uat-promote] ${gen.name}: verification verdict recording failed after merge: ${err instanceof Error ? err.message : String(err)}`);\n }\n try {\n await deps.runShip?.(promotedGeneration, shipVersion, promotedRepos);\n } catch (err) {\n log(`[uat-promote] ${gen.name}: version ship settlement failed after merge: ${err instanceof Error ? err.message : String(err)}`);\n }\n await deps.teardownStack(promotedGeneration).catch((err) => {\n log(`[uat-promote] ${gen.name}: stack teardown failed: ${err instanceof Error ? err.message : String(err)}`);\n });\n\n const invalidated: string[] = [];\n for (const other of deps.store.listChain(projectRoot, ['ready', 'superseded'])) {\n if (other.name === gen.name) continue;\n deps.store.update(other.name, { status: 'invalidated' });\n invalidated.push(other.name);\n await deps.teardownStack(other).catch(() => {});\n }\n\n // Standard per-issue post-merge handoff, exactly once each (PAN-328 guard).\n // For polyrepo this runs only after EVERY repo published, so a member is\n // never handed off while part of its work is still unlanded.\n //\n // A polyrepo member's headSha is a composite anchor (`fe@abc api@def`), not a\n // git ref, and the wrapper project path is not a git repo — passing it as\n // verifiedMergedRef guarantees the ancestry check fails and the lifecycle\n // refuses. Per-repo merge commits are handed over instead, each verified in\n // its own repo against its own target branch.\n //\n // The completeness guard above already refused anything short of a merge SHA\n // per repo, so this evidence set covers every repo the batch touched — never\n // a subset, which would advance every member's lifecycle having proven only\n // some of the repos their work landed in.\n const verifiedMergedRepos = isPolyrepoGeneration\n ? (promotedGeneration.repos ?? []).map((r) => ({\n repoKey: r.repoKey,\n repoPath: r.repoPath,\n mergeSha: r.mergeSha!,\n targetBranch: r.targetBranch ?? 'main',\n }))\n : [];\n\n const postMergeStarted: string[] = [];\n for (const member of gen.members) {\n const options = verifiedMergedRepos.length > 0\n ? { sourceBranch: member.branch, verifiedMergedRepos }\n : { sourceBranch: member.branch, verifiedMergedRef: member.headSha || member.branch };\n if (deps.firePostMerge(member.issueId, options)) postMergeStarted.push(member.issueId);\n else log(`[uat-promote] ${gen.name}: post-merge for ${member.issueId} already in flight — skipped`);\n }\n\n return {\n success: true,\n generation: gen.name,\n mergeSha,\n members: gen.members.map((m) => m.issueId),\n postMergeStarted,\n invalidated,\n };\n}\n\n/**\n * Real per-repo promote wiring for a polyrepo project (PAN-3093), keyed by\n * repoKey. Each repo merges its own uat branch into its own target branch in a\n * throwaway detached worktree.\n *\n * The split that makes phase A safe: trialMerge does everything EXCEPT the\n * push, and leaves the worktree in place so publishPrepared only has to push\n * it. Nothing reaches a remote until every repo has produced a merge commit\n * locally.\n */\nexport function buildPolyrepoUatPromoteGitDeps(\n repos: ReadonlyArray<{ repoKey: string; repoPath: string; targetBranch?: string }>,\n): Map<string, PolyrepoRepoPromoteGit> {\n const runGit = (args: string[], cwd: string) =>\n execFileAsync('git', args, { cwd, maxBuffer: 16 * 1024 * 1024 });\n\n return new Map(\n repos.map((repo) => {\n const target = repo.targetBranch ?? 'main';\n const originTarget = `origin/${target}`;\n const worktreeFor = (branchName: string) =>\n join(tmpdir(), `uat-promote-${repo.repoKey}-${branchName.replace(/[^a-z0-9]/gi, '-')}`);\n\n const git: PolyrepoRepoPromoteGit = {\n targetHeadSha: async () => {\n await runGit(['fetch', 'origin', target], repo.repoPath);\n return (await runGit(['rev-parse', originTarget], repo.repoPath)).stdout.trim();\n },\n\n changedFilesSince: async (baseSha) => {\n if (!/^[0-9a-f]{7,40}$/i.test(baseSha)) throw new Error(`unsafe base sha: ${baseSha}`);\n const { stdout } = await runGit(['diff', '--name-only', `${baseSha}..${originTarget}`], repo.repoPath);\n return stdout.split('\\n').map((l) => l.trim()).filter(Boolean);\n },\n\n batchChangedFiles: async (branchName) => {\n const safeBranch = safeUatBranchName(branchName);\n const ref = await runGit(['rev-parse', '--verify', `origin/${safeBranch}`], repo.repoPath)\n .then(() => `origin/${safeBranch}`)\n .catch(() => safeBranch);\n const mergeBase = (await runGit(['merge-base', originTarget, ref], repo.repoPath)).stdout.trim();\n const { stdout } = await runGit(['diff', '--name-only', `${mergeBase}..${ref}`], repo.repoPath);\n return stdout.split('\\n').map((l) => l.trim()).filter(Boolean);\n },\n\n trialMerge: async (branchName, message) => {\n const safeBranch = safeUatBranchName(branchName);\n const worktreePath = worktreeFor(safeBranch);\n await runGit(['worktree', 'remove', '--force', worktreePath], repo.repoPath).catch(() => {});\n await runGit(['worktree', 'prune'], repo.repoPath).catch(() => {});\n await runGit(['worktree', 'add', '--detach', worktreePath, originTarget], repo.repoPath);\n try {\n const originRef = `origin/${safeBranch}`;\n const ref = await runGit(['rev-parse', '--verify', originRef], worktreePath)\n .then(() => originRef)\n .catch(() => safeBranch);\n await runGit(['merge', '--no-ff', ref, '-m', message], worktreePath);\n const mergeSha = (await runGit(['rev-parse', 'HEAD'], worktreePath)).stdout.trim();\n return { repoKey: repo.repoKey, handle: worktreePath, mergeSha };\n } catch (err) {\n // Clean up our own worktree before propagating; the caller discards\n // the ones that already succeeded.\n await runGit(['worktree', 'remove', '--force', worktreePath], repo.repoPath).catch(() => {});\n await runGit(['worktree', 'prune'], repo.repoPath).catch(() => {});\n throw err;\n }\n },\n\n publishPrepared: async (prepared) => {\n await runGit(['push', 'origin', `HEAD:${target}`], prepared.handle);\n },\n\n discardPrepared: async (prepared) => {\n await runGit(['worktree', 'remove', '--force', prepared.handle], repo.repoPath).catch(() => {});\n await runGit(['worktree', 'prune'], repo.repoPath).catch(() => {});\n },\n\n findLandedMerge: async (branchName) => {\n const safeBranch = safeUatBranchName(branchName);\n // This call establishes REMOTE truth after an ambiguous publish, so a\n // failed fetch must not be answered from a cached target ref: that\n // could report \"not landed\" for a merge that is live, and republish it.\n await runGit(['fetch', 'origin', target], repo.repoPath).catch((err) => {\n throw new Error(\n `cannot determine whether ${repo.repoKey} already landed: fetching origin/${target} failed ` +\n `(${err instanceof Error ? err.message.split('\\n')[0] : String(err)})`,\n );\n });\n // Fetch the UAT ref as well: a fresh checkout has no tracking ref for\n // it, and resolving only from local refs would misclassify a landed\n // unstamped repo as pending. Absence here is normal (the branch may\n // already be reaped), so this one is best-effort.\n await runGit(\n ['fetch', 'origin', `+refs/heads/${safeBranch}:refs/remotes/origin/${safeBranch}`],\n repo.repoPath,\n ).catch(() => {});\n\n const ref = await runGit(['rev-parse', '--verify', `origin/${safeBranch}`], repo.repoPath)\n .then(() => `origin/${safeBranch}`)\n .catch(() => safeBranch);\n\n const tip = await runGit(['rev-parse', ref], repo.repoPath)\n .then(({ stdout }) => stdout.trim())\n .catch(() => '');\n if (!tip) return null;\n\n // Contained in the target? If not, this repo genuinely has not landed.\n const contained = await runGit(['merge-base', '--is-ancestor', tip, originTarget], repo.repoPath)\n .then(() => true)\n .catch(() => false);\n if (!contained) return null;\n\n // The merge commit that brought it in: the oldest merge on the\n // ancestry path from the batch tip to the target head.\n const { stdout } = await runGit(\n ['rev-list', '--ancestry-path', '--merges', '--reverse', `${tip}..${originTarget}`],\n repo.repoPath,\n ).catch(() => ({ stdout: '' }));\n const mergeCommit = stdout.split('\\n').map((l) => l.trim()).filter(Boolean)[0];\n // A fast-forwarded target has no merge commit; the tip itself is what landed.\n return mergeCommit ?? tip;\n },\n };\n\n return [repo.repoKey, git] as const;\n }),\n );\n}\n\n/** Real git wiring: throwaway detached worktree off origin/main, no-ff merge, push. */\nexport function buildUatPromoteGitDeps(projectRoot: string): UatPromoteGitDeps {\n const runGit = (args: string[], cwd: string) =>\n execFileAsync('git', args, { cwd, maxBuffer: 16 * 1024 * 1024 });\n\n return {\n fetchMain: async () => {\n await runGit(['fetch', 'origin', 'main'], projectRoot);\n return (await runGit(['rev-parse', 'origin/main'], projectRoot)).stdout.trim();\n },\n mergeIntoMain: async (branchName, message) => {\n const safeBranch = safeUatBranchName(branchName);\n const worktreePath = join(tmpdir(), `uat-promote-${safeBranch.replace(/[^a-z0-9]/gi, '-')}`);\n await runGit(['worktree', 'remove', '--force', worktreePath], projectRoot).catch(() => {});\n await runGit(['worktree', 'prune'], projectRoot).catch(() => {});\n await runGit(['worktree', 'add', '--detach', worktreePath, 'origin/main'], projectRoot);\n try {\n const originRef = `origin/${safeBranch}`;\n const ref = await runGit(['rev-parse', '--verify', originRef], worktreePath)\n .then(() => originRef)\n .catch(() => safeBranch);\n await runGit(['merge', '--no-ff', ref, '-m', message], worktreePath);\n await runGit(['push', 'origin', 'HEAD:main'], worktreePath);\n return (await runGit(['rev-parse', 'HEAD'], worktreePath)).stdout.trim();\n } finally {\n await runGit(['worktree', 'remove', '--force', worktreePath], projectRoot).catch(() => {});\n await runGit(['worktree', 'prune'], projectRoot).catch(() => {});\n }\n },\n changedFilesSince: async (baseSha) => {\n if (!/^[0-9a-f]{7,40}$/i.test(baseSha)) throw new Error(`unsafe base sha: ${baseSha}`);\n const { stdout } = await runGit(['diff', '--name-only', `${baseSha}..origin/main`], projectRoot);\n return stdout.split('\\n').map((l) => l.trim()).filter(Boolean);\n },\n batchChangedFiles: async (branchName) => {\n const safeBranch = safeUatBranchName(branchName);\n const ref = await runGit(['rev-parse', '--verify', `origin/${safeBranch}`], projectRoot)\n .then(() => `origin/${safeBranch}`)\n .catch(() => safeBranch);\n const mergeBase = (await runGit(['merge-base', 'origin/main', ref], projectRoot)).stdout.trim();\n const { stdout } = await runGit(['diff', '--name-only', `${mergeBase}..${ref}`], projectRoot);\n return stdout.split('\\n').map((l) => l.trim()).filter(Boolean);\n },\n };\n}\n","import { Effect } from 'effect';\nimport { messageAgent } from '../agents.js';\nimport { getFlywheelActiveRunId, isFlywheelGloballyPaused } from '../overdeck/control-settings.js';\nimport { sessionExists } from '../tmux.js';\nimport { recordDeaconNudge } from './deacon-nudge-log.js';\nimport { FLYWHEEL_ORCHESTRATOR_AGENT_ID } from './flywheel.js';\nimport type { PromoteResult } from './uat-promote.js';\n\ntype SessionExists = (name: string) => boolean | Promise<boolean>;\n\nexport interface NotifyDeps {\n getActiveRunId?: typeof getFlywheelActiveRunId;\n isPaused?: typeof isFlywheelGloballyPaused;\n sessionExists?: SessionExists;\n message?: typeof messageAgent;\n recordNudge?: typeof recordDeaconNudge;\n}\n\nfunction buildPromoteNudge(result: Extract<PromoteResult, { success: true }>): string {\n const members = result.members.length > 0 ? result.members.join(', ') : '(no members recorded)';\n return (\n `The operator just promoted UAT generation ${result.generation} to main at ${result.mergeSha}. ` +\n `Run a fresh Observe->Act loop NOW. Fetch GET /api/registered-projects, then for every returned ` +\n `project key call GET /api/pipeline/membership?project=<URL-encoded-project-key>. A bare array is a ` +\n `successful answer: combine arrays and re-derive activePipeline from only rows where inPipeline === true; ` +\n `clean_terminal rows are audit-only and excluded. An object with status:'unavailable' is a typed blind spot: ` +\n `emit an investigate suggestion naming projectKey, reason, and message, and NEVER derive membership from ` +\n `tracker, agent, tmux, workspace, or review-status state. membershipQueryable:false from the registered-projects ` +\n `response is an upfront missing_issue_prefix hint, not permission to skip the read door. EXCLUDE ` +\n `the merged member(s): ${members}. Re-assemble a clean UAT batch with only members that are currently ` +\n `review+test passed, close out the promoted issue(s), emit a fresh status snapshot, and re-arm the next ` +\n `tick. Do NOT ask the operator a question, do NOT pause, and do NOT reuse the stale pre-promote ready set.`\n );\n}\n\nexport async function notifyFlywheelOfUatPromote(result: PromoteResult, deps: NotifyDeps = {}): Promise<void> {\n try {\n if (result.success !== true) return;\n\n const getActiveRunId = deps.getActiveRunId ?? getFlywheelActiveRunId;\n const isPaused = deps.isPaused ?? isFlywheelGloballyPaused;\n const sessionExistsDep = deps.sessionExists ?? ((name: string) => Effect.runPromise(sessionExists(name)));\n const message = deps.message ?? messageAgent;\n const recordNudge = deps.recordNudge ?? recordDeaconNudge;\n\n if (!getActiveRunId()) return;\n if (isPaused()) return;\n\n const delivered = await sessionExistsDep(FLYWHEEL_ORCHESTRATOR_AGENT_ID);\n if (delivered) {\n try {\n await message(FLYWHEEL_ORCHESTRATOR_AGENT_ID, buildPromoteNudge(result), 'uat-promote-notify');\n } catch {\n /* best-effort — the nudge log below still records that the session was present */\n }\n }\n\n recordNudge({\n patrol: 'uat-promote-notify',\n issueId: result.members[0] ?? result.generation,\n action: 'notified flywheel-orchestrator to re-derive ready set after UAT promote',\n reason:\n 'operator promoted a UAT batch; flywheel must immediately rebuild to drop merged + regressed members before its next tick',\n state: {\n generation: result.generation,\n members: result.members,\n mergeSha: result.mergeSha,\n delivered,\n },\n });\n } catch {\n /* best-effort — promote success must never be converted into a notification failure */\n }\n}\n","/**\n * UAT batch train service (PAN-1737) — real wiring for the reconciler,\n * assembly engine, stacks, and promote path, plus the payload builders the\n * /api/merge-train/* routes expose.\n *\n * The reconciler interval is the heartbeat of \"always one batch ready\": every\n * 60s it walks every tracked project whose effective merge-train flag is on,\n * comparing that project's ready set against its generation chain and\n * assembling/invalidating as needed. PAN-1696 removed the active-flywheel-run\n * requirement — the ready set comes from review-status records, so batches\n * assemble with no run at all. Assemblies run minutes; the reconciler is\n * single-flight per project, so ticks never pile up.\n */\nimport { stat } from 'node:fs/promises';\nimport { resolve } from 'node:path';\nimport { Effect } from 'effect';\nimport { layer as nodeServicesLayer } from '@effect/platform-node/NodeServices';\nimport {\n assembleUatGeneration,\n cleanupUatGenerations,\n type ReadyFeature,\n} from '../../../lib/cloister/uat-generation-engine.js';\nimport {\n buildPolyrepoCleanupGit,\n buildPolyrepoGitDeps,\n buildUatGenerationGitDeps,\n featureNamespaceOf,\n buildUatGenerationStore,\n buildUatGenerationCleanupGit,\n listRemoteUatBranches,\n listRemoteUatBranchesMulti,\n} from '../../../lib/cloister/uat-generation-deps.js';\nimport {\n assemblePolyrepoUatGeneration,\n compositeAnchor,\n compositeMemberAnchor,\n} from '../../../lib/cloister/uat-polyrepo-engine.js';\nimport { buildConflictAgentHook } from '../../../lib/cloister/uat-conflict-agent.js';\nimport { reconcileUatGenerations, type ReconcileResult } from '../../../lib/cloister/uat-reconciler.js';\nimport { ensureUatStack, probeUatStack, teardownUatStack } from '../../../lib/cloister/uat-stack.js';\nimport {\n promoteUatGeneration,\n buildPolyrepoUatPromoteGitDeps,\n buildUatPromoteGitDeps,\n type PromoteResult,\n type UatPromoteDeps,\n} from '../../../lib/cloister/uat-promote.js';\nimport { notifyFlywheelOfUatPromote } from '../../../lib/cloister/uat-promote-notify.js';\nimport {\n getUatGenerationSync,\n hasUncleanedTerminalUatGenerationSync,\n isMergeTrainEnabled,\n listUatGenerationsSync,\n markUatGenerationRepoPromotedSync,\n type UatGeneration,\n type UatGenerationRepo,\n} from '../../../lib/overdeck/merge-sync.js';\nimport { listEligibleCandidatesByProject } from '../../../lib/flywheel-merge-order.js';\nimport { extractACFromDocument } from '../../../lib/xbrief/acceptance-criteria.js';\nimport { findXBriefByIssue, readXBriefDocument } from '../../../lib/xbrief/xbrief-index.js';\nimport { findProjectByPathSync, listProjectsSync, resolveProjectFromIssueSync } from '../../../lib/projects.js';\nimport type { PanIssueShipRecord } from '../../../lib/pan-dir/record.js';\nimport {\n executeVersionShipForGeneration,\n persistPendingShipRecords,\n persistShipRecords,\n withGenerationShipLock,\n} from '../../../lib/cloister/ship-record.js';\nimport {\n aggregateGenerationShipStatus,\n loadShipRecords,\n publicShipStatus,\n} from '../../../lib/cloister/ship-status.js';\nimport {\n resolveConfiguredReposSync,\n resolveProjectReposFromResolvedIssueSync,\n type ResolvedProjectRepo,\n} from '../../../lib/project-repos.js';\nimport { getDashboardIdentity } from '../identity.js';\n\nconst RECONCILE_INTERVAL_MS = 60_000;\nconst CHAIN_PAYLOAD_LIMIT = 10;\nconst ACCEPTANCE_CRITERIA_READ_CONCURRENCY = 4;\n\ntype AcceptanceCriteriaSummary = Array<{ title: string; status: string }>;\n\n/**\n * PAN-3165: an unresolvable plan is not a plan with no criteria. Collapsing the\n * two let a resolver bug render as the confident claim \"No UAT steps in plan\",\n * silently deleting the operator's checklist, so the lookup outcome travels\n * alongside the criteria all the way to the UI.\n */\ninterface AcceptanceCriteriaLookup {\n planResolved: boolean;\n criteria: AcceptanceCriteriaSummary;\n}\n\ninterface AcceptanceCriteriaCacheEntry {\n path: string;\n mtimeMs: number;\n criteria: AcceptanceCriteriaSummary;\n}\n\nconst acceptanceCriteriaByIssue = new Map<string, AcceptanceCriteriaCacheEntry>();\n\nexport function resolveUatProjectRoot(cwdPath = process.cwd()): string {\n const registeredProject = findProjectByPathSync(cwdPath);\n if (registeredProject) return resolve(registeredProject.path);\n\n const normalized = resolve(cwdPath);\n const workspaceMatch = normalized.match(/^(.*)\\/workspaces\\/feature-[^/]+(?:\\/.*)?$/i);\n return workspaceMatch?.[1] ? resolve(workspaceMatch[1]) : normalized;\n}\n\nfunction projectRoot(): string {\n return resolveUatProjectRoot();\n}\n\nexport function canStartUatTrainReconciler(): boolean {\n const identity = getDashboardIdentity();\n return identity.mode === 'primary' && resolve(identity.repoRoot) === projectRoot();\n}\n\nfunction codenameLabel(features: readonly ReadyFeature[]): string {\n const prefix = features[0]?.issueId.split('-')[0];\n return (prefix ?? 'uat').toLowerCase();\n}\n\n/**\n * PAN-1696 reconciler-decouple: Per-project cleanup function factory.\n */\nfunction makeCleanupForProject(projectPath: string): () => Promise<void> {\n return async () => {\n // A polyrepo generation's artifacts live in N repos plus a wrapper folder,\n // none of them reachable from the wrapper path the monorepo cleanup uses.\n const projectConfig = findProjectByPathSync(projectPath);\n const cleanupGit = projectConfig?.workspace?.type === 'polyrepo'\n ? buildPolyrepoCleanupGit(await resolveProjectRepos(projectPath), projectPath)\n : buildUatGenerationCleanupGit(projectPath);\n\n await cleanupUatGenerations(projectPath, {\n store: buildUatGenerationStore(),\n ...cleanupGit,\n teardownStack: (gen) => teardownUatStack(gen),\n log: (msg) => console.log(msg),\n });\n };\n}\n\n/** One reconciler pass. `force` rebuilds even when a live generation matches. */\n/**\n * PAN-1696 reconciler-decouple: Internal per-project reconciler.\n * Runs the UAT train reconciliation for a specific project.\n */\nasync function runUatTrainReconcileForProject(\n projectPath: string,\n options: { force?: boolean } = {},\n): Promise<ReconcileResult> {\n const { isMergeTrainEnabledForProject } = await import('../../../lib/overdeck/merge-sync.js');\n const projectConfig = findProjectByPathSync(projectPath);\n\n // Fail closed: if project cannot be resolved, don't proceed\n if (!projectConfig) {\n console.log(`[uat-train] project at ${projectPath} not found in config — skipping`);\n return { action: 'no-queue', invalidated: [] };\n }\n\n // Check if enabled before building git deps (fail closed on null config)\n if (!isMergeTrainEnabledForProject(projectConfig)) {\n return { action: 'disabled', invalidated: [] };\n }\n\n // Early exit if no candidates and no live generations — skip git operations\n const candidates = await listEligibleCandidatesByProject(projectPath);\n const liveGenerations = listUatGenerationsSync({\n projectRoot: projectPath,\n statuses: ['assembling', 'ready', 'superseded'],\n limit: 1,\n });\n if (candidates.length === 0 && liveGenerations.length === 0) {\n // Terminal generations still own branches, worktrees, and a wrapper folder.\n // Promoting the LAST ready batch lands exactly here — no candidates, no\n // live rows — so returning early without cleanup leaks every artifact the\n // batch created, permanently. Cleanup is idempotent and skips rows already\n // marked cleaned, so running it on an otherwise idle tick is cheap.\n // An existence check, not a load: generation rows are retained as an audit\n // trail, so hydrating every terminal row and its four child tables to answer\n // a yes/no question would make the idle minute cost grow with history.\n if (hasUncleanedTerminalUatGenerationSync(projectPath)) {\n await makeCleanupForProject(projectPath)().catch((err) => {\n console.log(`[uat-train] terminal cleanup failed for ${projectPath}: ${err instanceof Error ? err.message : String(err)}`);\n });\n }\n return { action: 'idle', invalidated: [] };\n }\n\n // A polyrepo project has no git repo at its own path, so every git dep must\n // be per member repo and staleness must compare composite anchors.\n if (projectConfig.workspace?.type === 'polyrepo') {\n // The base anchor must cover exactly the ready set's contributing repos, so\n // both callbacks read one memoized ready set — recomputing it would also\n // re-run every git probe.\n let readySetOnce: Promise<ReadyFeature[] | null> | undefined;\n const readySet = () => (readySetOnce ??= getPolyrepoReadySetForProject(projectPath));\n\n return reconcileUatGenerations(projectPath, {\n isEnabled: () => true, // Already checked above\n getReadySet: readySet,\n // Unused on this path — getBaseAnchor/getFeatureAnchor take over — but\n // the deps contract still requires them.\n getMainHeadSha: async () => '',\n getBranchHeadSha: async () => '',\n getBaseAnchor: async () => getPolyrepoBaseAnchor(projectPath, await readySet()),\n getFeatureAnchor: (feature) => getPolyrepoFeatureAnchor(feature),\n isGenerationContainedInMain: async (generation) => {\n const repoGit = buildPolyrepoGitDeps(await resolveProjectRepos(projectPath), { includeReadOnly: true });\n const repos = generation.repos ?? [];\n return repos.length > 0 && Promise.all(repos.map((repo) =>\n repoGit.get(repo.repoKey)?.isBranchContainedInMain?.(repo.branch) ?? Promise.resolve(false)))\n .then((contained) => contained.every(Boolean));\n },\n store: buildUatGenerationStore(),\n assemble: (features) => assemblePolyrepoFromReadySetForProject(projectPath, features),\n teardownStack: (gen) => teardownUatStack(gen),\n cleanup: makeCleanupForProject(projectPath),\n log: (msg) => console.log(msg),\n }, options);\n }\n\n const gitDeps = buildUatGenerationGitDeps(projectPath);\n\n return reconcileUatGenerations(projectPath, {\n isEnabled: () => true, // Already checked above\n getReadySet: () => getReadySetForProject(projectPath),\n getMainHeadSha: () => gitDeps.fetchMain(),\n getBranchHeadSha: (branch) => gitDeps.branchHeadSha(branch),\n isGenerationContainedInMain: (generation) => gitDeps.isBranchContainedInMain?.(generation.name) ?? Promise.resolve(false),\n store: buildUatGenerationStore(),\n assemble: (features) => assembleFromReadySetForProject(projectPath, features),\n teardownStack: (gen) => teardownUatStack(gen),\n cleanup: makeCleanupForProject(projectPath),\n log: (msg) => console.log(msg),\n }, options);\n}\n\n/**\n * Get ready set for a specific project path.\n */\nasync function getReadySetForProject(projectPath: string): Promise<ReadyFeature[] | null> {\n const { computeMergeQueueFromCandidates, listEligibleCandidatesByProject, resolveMergeQueuePrUrl } = await import('../../../lib/flywheel-merge-order.js');\n\n const candidates = await listEligibleCandidatesByProject(projectPath);\n if (candidates.length === 0) return [];\n\n const queue = await Effect.runPromise(\n computeMergeQueueFromCandidates(candidates, projectPath, {\n getPrUrl: resolveMergeQueuePrUrl,\n }).pipe(\n Effect.provide(nodeServicesLayer),\n ),\n );\n\n return queue.map((item) => ({\n issueId: item.issueId,\n title: item.title,\n branch: item.branchName,\n ...(item.pr !== undefined ? { pr: item.pr } : {}),\n ...(item.prUrl !== undefined ? { prUrl: item.prUrl } : {}),\n conflictsWith: item.conflictsWith,\n }));\n}\n\n/**\n * Member repos configured for an issue in a polyrepo project. Resolution goes\n * through the canonical repo resolver, which collapses monorepo to one\n * synthetic entry — so this is the same door the rest of the pipeline uses.\n */\nfunction resolveReposForIssue(issueId: string): ResolvedProjectRepo[] {\n const resolvedProject = resolveProjectFromIssueSync(issueId);\n if (!resolvedProject) return [];\n return resolveProjectReposFromResolvedIssueSync(issueId, resolvedProject) ?? [];\n}\n\n/**\n * The project's configured member repos, resolved through the issue door using\n * any eligible candidate. Every issue in a project resolves the same repo set;\n * only the per-issue branch names differ, and callers here use the branches\n * from each feature's own contributions rather than these.\n */\nasync function resolveProjectRepos(projectPath: string, preferredIssueId?: string): Promise<ResolvedProjectRepo[]> {\n const issueId = preferredIssueId ?? (await listEligibleCandidatesByProject(projectPath))[0]?.issueId;\n if (issueId) return resolveReposForIssue(issueId);\n\n // No candidate to resolve through — the exact state cleanup runs in after the\n // last batch is promoted. Resolve the configured repo set straight from\n // project config instead; the per-issue branch names it derives are unused\n // here, only the repo paths and targets matter.\n const resolved = findProjectByPathSync(projectPath);\n if (!resolved) return [];\n const entry = listProjectsSync().find(({ config }) => resolve(config.path) === resolve(projectPath));\n if (!entry) return [];\n return resolveConfiguredReposSync(entry.key, projectPath, entry.config, `${entry.key}-cleanup`);\n}\n\n/** Polyrepo ready set: per-candidate contributions across member repos. */\nasync function getPolyrepoReadySetForProject(projectPath: string): Promise<ReadyFeature[] | null> {\n const { computePolyrepoMergeQueueFromCandidates, listEligibleCandidatesByProject, resolveMergeQueuePrUrl } =\n await import('../../../lib/flywheel-merge-order.js');\n\n const candidates = await listEligibleCandidatesByProject(projectPath);\n if (candidates.length === 0) return [];\n\n const reposByIssue = new Map(candidates.map((c) => [c.issueId, resolveReposForIssue(c.issueId)]));\n\n // An outage is \"unavailable\", never \"empty\". The reconciler treats an empty\n // ready set as authoritative and will invalidate the live generation and tear\n // down its stack; null means \"do nothing this tick\".\n let unavailableRepos: readonly string[] = [];\n\n const queue = await Effect.runPromise(\n computePolyrepoMergeQueueFromCandidates(candidates, reposByIssue, projectPath, {\n getPrUrl: resolveMergeQueuePrUrl,\n onExcluded: (issueId, reason) =>\n console.log(`[uat-train] ${issueId} excluded from the polyrepo ready set: ${reason}`),\n onRefreshUnavailable: (repoPaths) => { unavailableRepos = repoPaths; },\n }).pipe(Effect.provide(nodeServicesLayer)),\n );\n\n if (unavailableRepos.length > 0) {\n console.log(\n `[uat-train] polyrepo ready set unavailable for ${projectPath}: could not refresh refs in ` +\n `${unavailableRepos.join(', ')} — skipping this tick rather than invalidating the live batch`,\n );\n return null;\n }\n\n return queue.map((item) => ({\n issueId: item.issueId,\n title: item.title,\n branch: item.branchName,\n ...(item.pr !== undefined ? { pr: item.pr } : {}),\n ...(item.prUrl !== undefined ? { prUrl: item.prUrl } : {}),\n conflictsWith: item.conflictsWith,\n repoContributions: item.repoContributions,\n }));\n}\n\n/**\n * Composite base anchor over exactly the repos the ready set contributes to.\n *\n * NOT every configured repo: assembly anchors on the contributing set, and\n * staleness is string equality, so including an untouched repo would make the\n * two anchors differ forever and rebuild the same generation every tick.\n */\nasync function getPolyrepoBaseAnchor(\n projectPath: string,\n readySet: readonly ReadyFeature[] | null,\n): Promise<string> {\n const contributingKeys = new Set(\n (readySet ?? []).flatMap((f) => (f.repoContributions ?? []).map((c) => c.repoKey)),\n );\n const repos = (await resolveProjectRepos(projectPath)).filter((r) => contributingKeys.has(r.repoKey));\n if (repos.length === 0) return '';\n const gitByRepo = buildPolyrepoGitDeps(repos);\n\n const entries = await Promise.all(\n repos.map(async (repo) => ({\n repoKey: repo.repoKey,\n sha: await gitByRepo.get(repo.repoKey)!.fetchMain().catch(() => 'unknown'),\n mergeOrder: repo.mergeOrder,\n })),\n );\n return compositeAnchor(entries);\n}\n\n/** Composite anchor over the repos one feature currently contributes to. */\nasync function getPolyrepoFeatureAnchor(feature: ReadyFeature): Promise<string> {\n const contributions = feature.repoContributions ?? [];\n if (contributions.length === 0) return 'unknown';\n\n const entries = await Promise.all(\n contributions.map(async (c) => {\n const git = buildUatGenerationGitDeps(c.repoPath, {\n targetBranch: c.targetBranch,\n // The SAME configured namespace assembly uses. Defaulting to `feature/`\n // here would reject a valid `feat/…` branch, yield an `unknown` anchor,\n // and invalidate the generation assembly had just built.\n featureBranchPrefix: featureNamespaceOf(c.branch),\n });\n return { repoKey: c.repoKey, headSha: await git.branchHeadSha(c.branch).catch(() => 'unknown') };\n }),\n );\n return compositeMemberAnchor(entries);\n}\n\n/** Assemble a polyrepo UAT generation for a project's ready set. */\nasync function assemblePolyrepoFromReadySetForProject(\n projectPath: string,\n features: readonly ReadyFeature[],\n): Promise<UatGeneration> {\n const repos = await resolveProjectRepos(projectPath, features[0]?.issueId);\n if (repos.length === 0) {\n throw new Error(`[uat-train] no member repos resolved for the polyrepo project at ${projectPath}`);\n }\n\n return assemblePolyrepoUatGeneration(\n {\n projectRoot: projectPath,\n label: codenameLabel(features),\n dateIso: new Date().toISOString(),\n features,\n repos,\n takenBranchNames: await listRemoteUatBranchesMulti(repos),\n },\n {\n repoGit: buildPolyrepoGitDeps(repos),\n store: buildUatGenerationStore(),\n resolveConflict: buildConflictAgentHook(),\n log: (msg) => console.log(msg),\n },\n );\n}\n\n/**\n * Assemble UAT generation for a specific project's ready set.\n */\nasync function assembleFromReadySetForProject(\n projectPath: string,\n features: readonly ReadyFeature[],\n): Promise<UatGeneration> {\n const store = buildUatGenerationStore();\n return assembleUatGeneration(\n {\n projectRoot: projectPath,\n label: codenameLabel(features),\n dateIso: new Date().toISOString(),\n features,\n takenBranchNames: await listRemoteUatBranches(projectPath),\n },\n {\n git: buildUatGenerationGitDeps(projectPath),\n store,\n resolveConflict: buildConflictAgentHook(),\n log: (msg) => console.log(msg),\n },\n );\n}\n\n/**\n * PAN-1696 reconciler-decouple: Single-project reconciliation (used by forced-rebuild endpoint).\n * When called from /api/merge-train/assemble with force:true and no project, reconciles every enabled project; with { project } or an explicit projectRoot, just that one.\n */\nexport async function runUatTrainReconcile(\n options: { force?: boolean; projectRoot?: string } = {},\n): Promise<ReconcileResult> {\n const projectPath = options.projectRoot ? resolve(options.projectRoot) : projectRoot();\n return runUatTrainReconcileForProject(projectPath, { ...(options.force !== undefined ? { force: options.force } : {}) });\n}\n\n/**\n * PAN-1696 merge-train-routes: reconcile every tracked project whose effective\n * merge-train flag is on, isolating failures so one project's git error cannot\n * abort the others. Returns one result per attempted project so the caller can\n * report which projects moved and which errored.\n */\nexport async function runUatTrainReconcileAllProjects(\n options: { force?: boolean } = {},\n): Promise<Array<{ projectKey: string; result?: ReconcileResult; error?: string }>> {\n const { listProjectsSync } = await import('../../../lib/projects.js');\n const { isMergeTrainEnabledForProject } = await import('../../../lib/overdeck/merge-sync.js');\n\n const enabled = listProjectsSync().filter(({ config }) => isMergeTrainEnabledForProject(config));\n return Promise.all(\n enabled.map(async ({ key, config }) => {\n const projectPath = resolve(config.path);\n try {\n const result = await runUatTrainReconcileForProject(projectPath, options);\n return { projectKey: key, result };\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`[uat-train] project ${projectPath} failed to reconcile:`, msg);\n return { projectKey: key, error: msg };\n }\n }),\n );\n}\n\nlet reconcilerTimer: ReturnType<typeof setInterval> | null = null;\n\nexport function startUatTrainReconciler(): boolean {\n if (!canStartUatTrainReconciler()) return false;\n if (reconcilerTimer) return true;\n reconcilerTimer = setInterval(() => {\n void runUatTrainReconcileAllProjects().catch((err) => {\n console.warn('[uat-train] reconcile tick failed:', err instanceof Error ? err.message : err);\n });\n }, RECONCILE_INTERVAL_MS);\n reconcilerTimer.unref?.();\n void runUatTrainReconcileAllProjects().catch((err) => {\n console.warn('[uat-train] initial reconcile failed:', err instanceof Error ? err.message : err);\n });\n return true;\n}\n\nexport function stopUatTrainReconciler(): void {\n if (reconcilerTimer) {\n clearInterval(reconcilerTimer);\n reconcilerTimer = null;\n }\n}\n\n// ─── Route payloads ───────────────────────────────────────────────────────────\n\nexport interface UatGenerationMemberPayload {\n issueId: string;\n title: string;\n branch: string;\n pr?: number;\n prUrl?: string;\n mergeOrder: number;\n /** What-to-UAT checklist from the issue's xBRIEF spec (shared extractor). */\n acceptanceCriteria: Array<{ title: string; status: string }>;\n /**\n * False when the issue's spec could not be resolved or read at all (PAN-3165).\n * An empty `acceptanceCriteria` then means \"we don't know\", not \"the plan\n * specified nothing\" — the UI must say so rather than assert the latter.\n */\n planResolved: boolean;\n}\n\n/** Per-repo detail safe to return over HTTP — no host filesystem paths. */\nexport interface UatGenerationRepoPayload {\n repoKey: string;\n branch: string;\n baseSha: string;\n targetBranch: string;\n mergeOrder: number;\n promotedAt?: string | null;\n mergeSha?: string | null;\n}\n\nexport interface UatGenerationPayload {\n name: string;\n status: UatGeneration['status'];\n baseSha: string;\n createdAt: string;\n updatedAt: string;\n members: UatGenerationMemberPayload[];\n heldOut: UatGeneration['heldOut'];\n resolutions: UatGeneration['resolutions'];\n versionSyncConfigured: boolean;\n /** Public aggregate omits operational error/reason detail. */\n shipStatus: Omit<PanIssueShipRecord, 'error' | 'reason'> | null;\n /**\n * Per-repo generation detail, additive (PAN-3093). Always present and\n * non-empty: a monorepo generation projects the single synthesized entry, so\n * consumers read one shape. Rendering it is deliberately a follow-up; the\n * data ships now so that follow-up has something to render.\n *\n * A deliberate public DTO, not the internal model: `repoPath` and\n * `worktreePath` are absolute server paths, and a response has no reason to\n * disclose host filesystem topology.\n */\n repos: UatGenerationRepoPayload[];\n /**\n * Live stack state (PAN-3166). `degraded` means containers are up but a\n * service the compose file declares is not serving — `downServices` names\n * them and `serviceErrors` carries each one's last error line, so the panel\n * can say why instead of offering a link into a gateway timeout. `unknown`\n * means the probe itself failed, which is not proof the stack is gone.\n */\n stack: {\n status: 'running' | 'degraded' | 'unknown' | 'absent';\n frontendUrl: string;\n downServices?: string[];\n serviceErrors?: Record<string, string>;\n probeError?: string;\n };\n}\n\nasync function mapBounded<T>(items: readonly T[], concurrency: number, worker: (item: T) => Promise<void>): Promise<void> {\n let next = 0;\n const workers = Array.from({ length: Math.min(concurrency, items.length) }, async () => {\n while (next < items.length) {\n const item = items[next++]!;\n await worker(item);\n }\n });\n await Promise.all(workers);\n}\n\n/**\n * PAN-1696: `root` is the project the issues belong to, NOT the dashboard's own\n * repo. Once the generations payload started serving every tracked project, a\n * dashboard-rooted xBRIEF lookup silently returned no acceptance criteria for\n * every non-PAN generation, so those batches showed an empty \"What to UAT\" list.\n * Issue ids are prefix-distinct across projects, so the module-level mtime cache\n * stays safe to key by issue id alone.\n */\nasync function loadAcceptanceCriteriaCache(\n issueIds: ReadonlySet<string>,\n root: string,\n): Promise<Map<string, AcceptanceCriteriaLookup>> {\n const cache = new Map<string, AcceptanceCriteriaLookup>();\n if (issueIds.size === 0) return cache;\n\n await mapBounded([...issueIds], ACCEPTANCE_CRITERIA_READ_CONCURRENCY, async (issueId) => {\n const upperIssueId = issueId.toUpperCase();\n const existing = acceptanceCriteriaByIssue.get(upperIssueId);\n if (existing) {\n try {\n const { mtimeMs } = await stat(existing.path);\n if (existing.mtimeMs === mtimeMs) {\n cache.set(upperIssueId, { planResolved: true, criteria: existing.criteria });\n return;\n }\n } catch {\n acceptanceCriteriaByIssue.delete(upperIssueId);\n }\n }\n\n try {\n const found = await Effect.runPromise(findXBriefByIssue(root, upperIssueId));\n if (!found) {\n acceptanceCriteriaByIssue.delete(upperIssueId);\n console.warn(`[uat-train] no xBRIEF spec resolved for ${upperIssueId} under ${root} — UAT checklist unavailable`);\n cache.set(upperIssueId, { planResolved: false, criteria: [] });\n return;\n }\n const { mtimeMs } = await stat(found.path);\n const document = await Effect.runPromise(readXBriefDocument(found.path));\n const criteria = extractACFromDocument(document).map((ac) => ({ title: ac.title, status: ac.status }));\n acceptanceCriteriaByIssue.set(upperIssueId, { path: found.path, mtimeMs, criteria });\n cache.set(upperIssueId, { planResolved: true, criteria });\n } catch (err) {\n console.warn(\n `[uat-train] failed to read xBRIEF spec for ${upperIssueId}:`,\n err instanceof Error ? err.message : err,\n );\n cache.set(upperIssueId, { planResolved: false, criteria: [] });\n }\n });\n return cache;\n}\n\n/** The generation chain, newest first, enriched for the UAT batches card. */\nexport async function getUatGenerationsPayload(projectRootOverride?: string): Promise<UatGenerationPayload[]> {\n // PAN-1696: no flywheel-run requirement — list generations directly from store.\n // projectRootOverride lets the aggregate /api/merge-train/generations route read\n // each tracked project's chain instead of only the dashboard's own repo.\n const root = projectRootOverride ? resolve(projectRootOverride) : projectRoot();\n const project = findProjectByPathSync(root);\n const versionSyncConfigured = Boolean(project?.version_sync);\n const chain = listUatGenerationsSync({ projectRoot: root, limit: CHAIN_PAYLOAD_LIMIT });\n if (chain.length === 0) return [];\n const memberIssueIds = new Set(chain.flatMap((gen) => gen.members.map((member) => member.issueId.toUpperCase())));\n // Resolve each member's xBRIEF in ITS OWN project, not the dashboard's repo.\n const [acCache, shipRecords] = await Promise.all([\n loadAcceptanceCriteriaCache(memberIssueIds, root),\n project && versionSyncConfigured ? loadShipRecords(project, chain) : Promise.resolve(new Map()),\n ]);\n const payload: UatGenerationPayload[] = [];\n for (const gen of chain) {\n const probe = await probeUatStack(gen);\n const members: UatGenerationMemberPayload[] = [];\n for (const member of gen.members) {\n const lookup = acCache.get(member.issueId.toUpperCase());\n members.push({\n issueId: member.issueId,\n title: member.title,\n branch: member.branch,\n ...(member.pr !== undefined ? { pr: member.pr } : {}),\n ...(member.prUrl !== undefined ? { prUrl: member.prUrl } : {}),\n mergeOrder: member.mergeOrder,\n acceptanceCriteria: lookup?.criteria ?? [],\n planResolved: lookup?.planResolved ?? false,\n });\n }\n payload.push({\n name: gen.name,\n status: gen.status,\n baseSha: gen.baseSha,\n createdAt: gen.createdAt,\n updatedAt: gen.updatedAt,\n members,\n heldOut: gen.heldOut,\n resolutions: gen.resolutions,\n versionSyncConfigured,\n shipStatus: publicShipStatus(\n versionSyncConfigured ? aggregateGenerationShipStatus(gen, shipRecords) : null,\n ),\n repos: (gen.repos ?? []).map((r) => ({\n repoKey: r.repoKey,\n branch: r.branch,\n baseSha: r.baseSha,\n targetBranch: r.targetBranch,\n mergeOrder: r.mergeOrder,\n promotedAt: r.promotedAt ?? null,\n mergeSha: r.mergeSha ?? null,\n })),\n stack: {\n status: probe.status,\n frontendUrl: probe.frontendUrl,\n ...(probe.downServices ? { downServices: probe.downServices } : {}),\n ...(probe.serviceErrors ? { serviceErrors: probe.serviceErrors } : {}),\n ...(probe.probeError ? { probeError: probe.probeError } : {}),\n },\n });\n }\n return payload;\n}\n\nexport interface UatCandidatePayload {\n branchName: string;\n bundled: string[];\n status: 'ready';\n}\n\n/** The authoritative active UAT candidate, if one is ready to test/ship. */\nexport async function getUatCandidatePayload(): Promise<UatCandidatePayload | null> {\n const [candidate] = listUatGenerationsSync({\n projectRoot: projectRoot(),\n statuses: ['ready'],\n limit: 1,\n });\n if (!candidate) return null;\n return {\n branchName: candidate.name,\n bundled: candidate.members.map((member) => member.issueId),\n status: 'ready',\n };\n}\n\nexport async function postUatGenerationStackPayload(name: string): Promise<\n { ok: true; frontendUrl: string; evicted: string[] } | { ok: false; error: string; status: number }\n> {\n const gen = getUatGenerationSync(name);\n if (!gen) return { ok: false, error: `No UAT generation named ${name}`, status: 404 };\n if (gen.status !== 'ready' && gen.status !== 'superseded') {\n return { ok: false, error: `${name} is ${gen.status} — only live batches can serve a stack`, status: 409 };\n }\n const result = await ensureUatStack(gen);\n if (!result.success) return { ok: false, error: result.error ?? 'stack start failed', status: 500 };\n return { ok: true, frontendUrl: result.frontendUrl!, evicted: result.evicted };\n}\n\nexport async function postUatGenerationPromotePayload(\n name: string,\n // Typed as the full promote contract, not `(issueId) => boolean`: the narrow\n // signature is what let the per-repo evidence be dropped by the forwarding\n // layer without a type error.\n firePostMerge: UatPromoteDeps['firePostMerge'],\n shipVersion?: string,\n): Promise<PromoteResult> {\n // PAN-1696: promote into the generation's OWN project repo. Generation rows carry\n // project_root, so a MIN generation must not be merged against the Overdeck repo.\n // For a generation belonging to this repo this resolves to the same path as before.\n const root = resolve(getUatGenerationSync(name)?.projectRoot ?? projectRoot());\n const [\n { reviewRecordEligibility },\n { recordUatPromotionVerdicts },\n ] = await Promise.all([\n import('../../../lib/flywheel-merge-order.js'),\n import('../../../lib/cloister/uat-promote-verification.js'),\n ]);\n // A polyrepo generation merges into each member repo's own target branch, so\n // it needs per-repo promote git. Supplying it is what selects the two-phase\n // (trial-merge everything, then publish) path.\n const projectConfig = findProjectByPathSync(root);\n const polyrepo = projectConfig?.workspace?.type === 'polyrepo';\n const storedRepos = polyrepo ? (getUatGenerationSync(name)?.repos ?? []) : [];\n\n // Re-check writability against CURRENT config, not the config that was live\n // at assembly: a repo can be flipped to readonly between assembly and merge,\n // and promote pushes to it. Fail closed rather than silently skipping a repo,\n // which would publish a partial batch.\n const writableKeys = polyrepo\n ? new Set((await resolveProjectRepos(root)).filter((r) => r.required).map((r) => r.repoKey))\n : new Set<string>();\n const nowReadOnly = polyrepo ? storedRepos.filter((r) => !writableKeys.has(r.repoKey)) : [];\n if (nowReadOnly.length > 0) {\n const detail = nowReadOnly.map((r) => r.repoKey).join(', ');\n const result: PromoteResult = {\n success: false,\n reason: 'merge-failed',\n message:\n `${name} includes repo(s) that are no longer writable in project config: ${detail}. ` +\n `Nothing was published. Restore write access or let the reconciler rebuild the batch without them.`,\n };\n await notifyFlywheelOfUatPromote(result).catch(() => {});\n return result;\n }\n const generationRepos = storedRepos;\n\n const result = await promoteUatGeneration(name, root, {\n git: buildUatPromoteGitDeps(root),\n ...(polyrepo && generationRepos.length > 0\n ? {\n // generationRepos carry their own persisted targetBranch, so a repo\n // configured for `develop` is fetched, trial-merged, and published\n // there rather than silently defaulting to main.\n polyrepoGit: buildPolyrepoUatPromoteGitDeps(generationRepos),\n markRepoPromoted: (genName, repoKey, at, mergeSha) =>\n markUatGenerationRepoPromotedSync(genName, repoKey, at, mergeSha),\n }\n : {}),\n store: { ...buildUatGenerationStore(), get: (n) => getUatGenerationSync(n) },\n teardownStack: (gen) => teardownUatStack(gen),\n firePostMerge,\n memberEligibility: reviewRecordEligibility,\n recordVerification: (generation, mergeSha) => recordUatPromotionVerdicts(generation, mergeSha),\n ...(projectConfig?.version_sync\n ? {\n runShip: (generation: UatGeneration, requestedVersion: string | undefined) => withGenerationShipLock(\n generation.name,\n async () => {\n // Establish durable batch membership before any command, worktree, or\n // Git operation can fail. Deferred recovery and the DoD gate then\n // remain blocking even when propagation never starts.\n await persistPendingShipRecords(\n generation,\n requestedVersion ? 'version ship in progress' : 'no version supplied at promote time',\n );\n if (!requestedVersion) return;\n\n const report = await executeVersionShipForGeneration({\n generation,\n project: projectConfig,\n version: requestedVersion,\n });\n await persistShipRecords(generation, report);\n },\n ),\n }\n : {}),\n log: (msg) => console.log(msg),\n }, { shipVersion });\n await notifyFlywheelOfUatPromote(result).catch(() => {});\n return result;\n}\n"],"mappings":"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"}