elanous 0.2.40 → 0.2.42

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 (685) hide show
  1. package/README.md +7 -7
  2. package/apps/pwa/out/404/index.html +1 -1
  3. package/apps/pwa/out/404.html +1 -1
  4. package/apps/pwa/out/_next/static/{HjeK7zONHfR7op1hKXp4k → X1ucLw10Cds_h614NoEnh}/_buildManifest.js +1 -1
  5. package/apps/pwa/out/_next/static/chunks/0244e5b5.6fc7670f8223d82d.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/{4769.9fd582071301f382.js → 1063.2fb8a9dc55c731f2.js} +1 -1
  7. package/apps/pwa/out/_next/static/chunks/1121.13ba7a8aff19df81.js +36 -0
  8. package/apps/pwa/out/_next/static/chunks/1147-5196d19c222051b3.js +1 -0
  9. package/apps/pwa/out/_next/static/chunks/{470.51a72a2348306455.js → 1262.e804feb8fd1acb8c.js} +2 -2
  10. package/apps/pwa/out/_next/static/chunks/1319.b9b815ae0f181e06.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/1345-31427b833438f521.js +1 -0
  12. package/apps/pwa/out/_next/static/chunks/{7219-15bdd9a80661af8e.js → 1455-530ca16c7e71baff.js} +1 -1
  13. package/apps/pwa/out/_next/static/chunks/{1281.d81877e35b9068f4.js → 1487.2f67b79444062da6.js} +1 -1
  14. package/apps/pwa/out/_next/static/chunks/1574.878c18ecc97b1bd4.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/1586.fafc33a9a5835cb7.js +32 -0
  16. package/apps/pwa/out/_next/static/chunks/1744.855fd60ae4c4a8c8.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/193.21a47a3e07ef6860.js +1 -0
  18. package/apps/pwa/out/_next/static/chunks/1945.7b54990d578c119b.js +1 -0
  19. package/apps/pwa/out/_next/static/chunks/2004.e839fda7ea5a738a.js +1 -0
  20. package/apps/pwa/out/_next/static/chunks/2044-dd681a5c08779ff8.js +1 -0
  21. package/apps/pwa/out/_next/static/chunks/2101.2248decb38829b03.js +286 -0
  22. package/apps/pwa/out/_next/static/chunks/2160.a097e7b138ae1c55.js +23 -0
  23. package/apps/pwa/out/_next/static/chunks/2300.41ea70ac1bef1446.js +1 -0
  24. package/apps/pwa/out/_next/static/chunks/2357-07b5eeb5fb12628d.js +1 -0
  25. package/apps/pwa/out/_next/static/chunks/2364.28652020695a6db3.js +34 -0
  26. package/apps/pwa/out/_next/static/chunks/{1350.59f40e39e02d91b1.js → 2456.b6de0e1c05f28843.js} +1 -1
  27. package/apps/pwa/out/_next/static/chunks/2526.c3e50ac1fec064bd.js +1 -0
  28. package/apps/pwa/out/_next/static/chunks/2549-cda50b716ff4250c.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/2550-5f1e88f615568045.js +1 -0
  30. package/apps/pwa/out/_next/static/chunks/261-319078c954e40d66.js +1 -0
  31. package/apps/pwa/out/_next/static/chunks/2690.0521c738b7749931.js +115 -0
  32. package/apps/pwa/out/_next/static/chunks/{5436.fdf6fa8ae3e9588d.js → 2974.595d1e29d040cb6a.js} +1 -1
  33. package/apps/pwa/out/_next/static/chunks/{5383.bdfa5d886e39a826.js → 3207.a756c95e9ec8c3c8.js} +1 -1
  34. package/apps/pwa/out/_next/static/chunks/3285.89ee2f298682f5e5.js +93 -0
  35. package/apps/pwa/out/_next/static/chunks/{2177.df8547f9d7215c4e.js → 3347.f846266b5e5f3ede.js} +3 -3
  36. package/apps/pwa/out/_next/static/chunks/3447.06edc7b092021417.js +41 -0
  37. package/apps/pwa/out/_next/static/chunks/{9184.45afbf44fec87574.js → 3482.a9ae9d2ad6a5bd97.js} +1 -1
  38. package/apps/pwa/out/_next/static/chunks/3507.97e1977bc4d5fb0a.js +2 -0
  39. package/apps/pwa/out/_next/static/chunks/3510-9ca392698bfe4391.js +1 -0
  40. package/apps/pwa/out/_next/static/chunks/3541.772e5ce425aaa611.js +24 -0
  41. package/apps/pwa/out/_next/static/chunks/{8099.1cccf31c1baa7e1c.js → 3741.9cf76acaa2ee5298.js} +1 -1
  42. package/apps/pwa/out/_next/static/chunks/3837.a210d7d0db8d8abd.js +1 -0
  43. package/apps/pwa/out/_next/static/chunks/3898-955c896f394217a6.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/3947-ef57acea76eaa8d2.js +1 -0
  45. package/apps/pwa/out/_next/static/chunks/{4864a2ca.1c1c280a7772c5e5.js → 3d0154af.5331fec737ffe8c3.js} +1 -1
  46. package/apps/pwa/out/_next/static/chunks/4037-3b82ebc7d78afdc7.js +1 -0
  47. package/apps/pwa/out/_next/static/chunks/4043.6b91203436c9c86b.js +309 -0
  48. package/apps/pwa/out/_next/static/chunks/405.9022d4a478202b67.js +77 -0
  49. package/apps/pwa/out/_next/static/chunks/417-fb2cc9a9910e5968.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/{3330.7443183b7e6fdbdf.js → 4276.336d76d858fcdf1b.js} +1 -1
  51. package/apps/pwa/out/_next/static/chunks/4302.6ced9aa4535de947.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/4320.583cbab64db6be02.js +1 -0
  53. package/apps/pwa/out/_next/static/chunks/4768-8eac183fd291ca53.js +1 -0
  54. package/apps/pwa/out/_next/static/chunks/4770.769c9cd9c7034eba.js +148 -0
  55. package/apps/pwa/out/_next/static/chunks/4895.65605ec3e6504e89.js +74 -0
  56. package/apps/pwa/out/_next/static/chunks/{3b355b3d.6f73c98bf6f9c749.js → 50061469.0326382241ef3b2d.js} +1 -1
  57. package/apps/pwa/out/_next/static/chunks/5112.4b9f300124fab7b3.js +149 -0
  58. package/apps/pwa/out/_next/static/chunks/5335.375ee922bcd5bc83.js +1 -0
  59. package/apps/pwa/out/_next/static/chunks/5342.023a2e729689b320.js +27 -0
  60. package/apps/pwa/out/_next/static/chunks/{8d173e38.18a2f7b4e1848bdb.js → 53c73f84.373b6160adfa89a4.js} +1 -1
  61. package/apps/pwa/out/_next/static/chunks/{1537-6bb97b5214928ea9.js → 5431-72a1dbde59400691.js} +1 -1
  62. package/apps/pwa/out/_next/static/chunks/5440-4236de26c5ea5856.js +1 -0
  63. package/apps/pwa/out/_next/static/chunks/5486-b318c165f645d8ba.js +1 -0
  64. package/apps/pwa/out/_next/static/chunks/5506-c7d2ec25c62a7c94.js +1 -0
  65. package/apps/pwa/out/_next/static/chunks/5570-c66ecbea26b4e3b1.js +1 -0
  66. package/apps/pwa/out/_next/static/chunks/5696.ffca5aa259d8451e.js +1 -0
  67. package/apps/pwa/out/_next/static/chunks/5793-8c584aea2aa87560.js +1 -0
  68. package/apps/pwa/out/_next/static/chunks/5848-6de6b8c173351a90.js +1 -0
  69. package/apps/pwa/out/_next/static/chunks/5885.0a3e52c8faccec98.js +287 -0
  70. package/apps/pwa/out/_next/static/chunks/5999.94e3c83040200f31.js +1 -0
  71. package/apps/pwa/out/_next/static/chunks/5a7901d6-2d8d9b5587cf9042.js +1 -0
  72. package/apps/pwa/out/_next/static/chunks/{261.0d471e8aded799f3.js → 6131.348b7d6bdcd267ce.js} +3 -3
  73. package/apps/pwa/out/_next/static/chunks/6164.86133a0172611184.js +90 -0
  74. package/apps/pwa/out/_next/static/chunks/{9175.2de5209a5ce6f68a.js → 6377.bc7df239edbff244.js} +1 -1
  75. package/apps/pwa/out/_next/static/chunks/6515.e07e8b2b8a707a87.js +1 -0
  76. package/apps/pwa/out/_next/static/chunks/6522-c005514f87d18982.js +1 -0
  77. package/apps/pwa/out/_next/static/chunks/{2226.2cf61ec781827219.js → 6632.0eac1c554f8f2ef3.js} +2 -2
  78. package/apps/pwa/out/_next/static/chunks/6642.648adb1767f39ecf.js +1 -0
  79. package/apps/pwa/out/_next/static/chunks/6848.138805a09d001d09.js +1 -0
  80. package/apps/pwa/out/_next/static/chunks/7049-c09e2b7a253a24f3.js +1 -0
  81. package/apps/pwa/out/_next/static/chunks/7072.1e00447d9fa39972.js +104 -0
  82. package/apps/pwa/out/_next/static/chunks/7106.64201c539c5e220c.js +94 -0
  83. package/apps/pwa/out/_next/static/chunks/7151-1f742b455986530e.js +1 -0
  84. package/apps/pwa/out/_next/static/chunks/7199.18d6b25e001154ce.js +1 -0
  85. package/apps/pwa/out/_next/static/chunks/7279-c199ea83f34338fa.js +1 -0
  86. package/apps/pwa/out/_next/static/chunks/728b938d.ffaa7ad583d60e58.js +1 -0
  87. package/apps/pwa/out/_next/static/chunks/7309.171c3de54e0322f1.js +1 -0
  88. package/apps/pwa/out/_next/static/chunks/733.7a1ab3342e03306f.js +1 -0
  89. package/apps/pwa/out/_next/static/chunks/{b84cad00.594027ebe0531fe2.js → 73d2eb0e.4966ad55293c801a.js} +3 -3
  90. package/apps/pwa/out/_next/static/chunks/{6257.c0037b8949239f5e.js → 7411.9b4b8b747dc7b802.js} +2 -2
  91. package/apps/pwa/out/_next/static/chunks/7504.d5cf1e7cd89b1b08.js +24 -0
  92. package/apps/pwa/out/_next/static/chunks/7573-0fd4af62d65d4598.js +1 -0
  93. package/apps/pwa/out/_next/static/chunks/759.6f8267df4689c401.js +1 -0
  94. package/apps/pwa/out/_next/static/chunks/7825-668b046f72f14483.js +1 -0
  95. package/apps/pwa/out/_next/static/chunks/7852-1538768b6eb19388.js +1 -0
  96. package/apps/pwa/out/_next/static/chunks/7868.cff810430bb63c91.js +1 -0
  97. package/apps/pwa/out/_next/static/chunks/{9189505a.980427ac048c8c60.js → 7a98bc80.fa935989e829f9c2.js} +1 -1
  98. package/apps/pwa/out/_next/static/chunks/8084-dfcc796874ab4e07.js +1 -0
  99. package/apps/pwa/out/_next/static/chunks/8138.6c10b2a356d52fca.js +1 -0
  100. package/apps/pwa/out/_next/static/chunks/{2566.beaffc3a5e1fcd2e.js → 8262.ddc03e06b733f184.js} +1 -1
  101. package/apps/pwa/out/_next/static/chunks/8266.6bf9b131b2584678.js +1 -0
  102. package/apps/pwa/out/_next/static/chunks/8403.7b81d5e64074a725.js +8 -0
  103. package/apps/pwa/out/_next/static/chunks/8470.1998cbd0866a7eac.js +1 -0
  104. package/apps/pwa/out/_next/static/chunks/8505.fd803c6c33b54cc3.js +79 -0
  105. package/apps/pwa/out/_next/static/chunks/{8272-025653884f9645d2.js → 8546-6f03f8ea34dd61d9.js} +1 -1
  106. package/apps/pwa/out/_next/static/chunks/{6051-0ae955eedb10adb9.js → 8673-bab689a55f7e13b9.js} +3 -3
  107. package/apps/pwa/out/_next/static/chunks/8687-246fda8433d3477c.js +1 -0
  108. package/apps/pwa/out/_next/static/chunks/898-280022fb082936bc.js +1 -0
  109. package/apps/pwa/out/_next/static/chunks/9098-50eefc1844212e3d.js +1 -0
  110. package/apps/pwa/out/_next/static/chunks/9211-330b6736a63d4cb3.js +1 -0
  111. package/apps/pwa/out/_next/static/chunks/{4351.0f37a6db2b34d50b.js → 9249.976f1e69a9d80754.js} +1 -1
  112. package/apps/pwa/out/_next/static/chunks/9255-cf88fceda5666645.js +1 -0
  113. package/apps/pwa/out/_next/static/chunks/9306-e523ba58bb8adc42.js +1 -0
  114. package/apps/pwa/out/_next/static/chunks/9324.a9866d717ba62cb6.js +1 -0
  115. package/apps/pwa/out/_next/static/chunks/9325.a8510e8aa87c8143.js +1 -0
  116. package/apps/pwa/out/_next/static/chunks/9353-12bfff0c130104cf.js +1 -0
  117. package/apps/pwa/out/_next/static/chunks/9415.69e4a21c60c7d2db.js +1 -0
  118. package/apps/pwa/out/_next/static/chunks/{6749.0744ce6eb08d40e9.js → 9427.96aa912ddb5801e0.js} +16 -16
  119. package/apps/pwa/out/_next/static/chunks/9479-154a697d4b6e4e98.js +1 -0
  120. package/apps/pwa/out/_next/static/chunks/9540-65aa6ccc31b3fd78.js +1 -0
  121. package/apps/pwa/out/_next/static/chunks/{7611.85f045bf232053ac.js → 9633.ce5bddc56784da72.js} +1 -1
  122. package/apps/pwa/out/_next/static/chunks/{3746-f80a78f220bf37b5.js → 9656-374232558fd528d0.js} +1 -1
  123. package/apps/pwa/out/_next/static/chunks/9832.f4a98c878ecf2c0a.js +62 -0
  124. package/apps/pwa/out/_next/static/chunks/9865.f094440645f02d98.js +1 -0
  125. package/apps/pwa/out/_next/static/chunks/9a852a62-6192a6ea9346ced3.js +1 -0
  126. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-e772b4710df5d936.js +1 -0
  127. package/apps/pwa/out/_next/static/chunks/app/approvals/page-4361368e6a76b967.js +1 -0
  128. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-802f349fdd65248d.js +1 -0
  129. package/apps/pwa/out/_next/static/chunks/app/board/page-7f35513988a2a495.js +1 -0
  130. package/apps/pwa/out/_next/static/chunks/app/botlab/page-798121656540e33e.js +1 -0
  131. package/apps/pwa/out/_next/static/chunks/app/bots/page-20a089490affe98c.js +1 -0
  132. package/apps/pwa/out/_next/static/chunks/app/ceo/page-e5e4ec786d70e604.js +1 -0
  133. package/apps/pwa/out/_next/static/chunks/app/chat/page-d12fe5d0801430e4.js +1 -0
  134. package/apps/pwa/out/_next/static/chunks/app/consult/page-d8cf21022fecc026.js +1 -0
  135. package/apps/pwa/out/_next/static/chunks/app/control/page-21ce565c9c57ee97.js +1 -0
  136. package/apps/pwa/out/_next/static/chunks/app/decisions/page-b56563025f619c9b.js +1 -0
  137. package/apps/pwa/out/_next/static/chunks/app/design-check/page-52f14dd133da8348.js +1 -0
  138. package/apps/pwa/out/_next/static/chunks/app/editor/page-c0ed7fdcdba76633.js +1 -0
  139. package/apps/pwa/out/_next/static/chunks/app/exec/page-af8c3020cb520f15.js +1 -0
  140. package/apps/pwa/out/_next/static/chunks/app/field/page-72eda67af9f5e35c.js +1 -0
  141. package/apps/pwa/out/_next/static/chunks/app/healthz/route-e772b4710df5d936.js +1 -0
  142. package/apps/pwa/out/_next/static/chunks/app/inside/page-67a6620d31f0a46f.js +1 -0
  143. package/apps/pwa/out/_next/static/chunks/app/intake/page-f059363aa0880ec2.js +1 -0
  144. package/apps/pwa/out/_next/static/chunks/app/layout-f9f917d6a57f69fb.js +1 -0
  145. package/apps/pwa/out/_next/static/chunks/app/live/page-d3ae55eee2f0826c.js +1 -0
  146. package/apps/pwa/out/_next/static/chunks/app/live-run/{page-83d27757e2b8060a.js → page-0e3d7b9421b79dac.js} +1 -1
  147. package/apps/pwa/out/_next/static/chunks/app/loops/page-ba738f7e233eb211.js +1 -0
  148. package/apps/pwa/out/_next/static/chunks/app/market/page-33438b98c74fd599.js +1 -0
  149. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-0b34d5fd990d793a.js +1 -0
  150. package/apps/pwa/out/_next/static/chunks/app/morning/page-0f291d3c8fd55a3a.js +1 -0
  151. package/apps/pwa/out/_next/static/chunks/app/not-found-3fced192be5279c1.js +1 -0
  152. package/apps/pwa/out/_next/static/chunks/app/observatory/page-04cb3cf182a7c1ea.js +1 -0
  153. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-6ccbd2ab23a21542.js +1 -0
  154. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-aa0518c48a5f4e56.js +1 -0
  155. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-4b32226d341ad7d7.js +1 -0
  156. package/apps/pwa/out/_next/static/chunks/app/outputs/page-3224fd8f6383b61f.js +1 -0
  157. package/apps/pwa/out/_next/static/chunks/app/page-efbc97459f2914e2.js +1 -0
  158. package/apps/pwa/out/_next/static/chunks/app/reflection/page-2fe487d88cd5d97c.js +1 -0
  159. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-48861dd1a858bab7.js +1 -0
  160. package/apps/pwa/out/_next/static/chunks/app/sessions/page-197d5d4d21d228ea.js +1 -0
  161. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-a763ba89fbdd9c27.js +1 -0
  162. package/apps/pwa/out/_next/static/chunks/app/settings/page-7edb253510b1c136.js +1 -0
  163. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-0c875702c61965ea.js +1 -0
  164. package/apps/pwa/out/_next/static/chunks/app/setup/layout-e772b4710df5d936.js +1 -0
  165. package/apps/pwa/out/_next/static/chunks/app/setup/page-e425c951151095f7.js +1 -0
  166. package/apps/pwa/out/_next/static/chunks/app/share/page-9c90e0aec4e27f77.js +1 -0
  167. package/apps/pwa/out/_next/static/chunks/app/showroom/page-733e09a02e727d42.js +1 -0
  168. package/apps/pwa/out/_next/static/chunks/app/tasks/page-4f899e14c34d77a1.js +1 -0
  169. package/apps/pwa/out/_next/static/chunks/app/term/{page-139d722de1dcd6c1.js → page-bf067887b3307423.js} +1 -1
  170. package/apps/pwa/out/_next/static/chunks/app/today/page-7af10868ff87d04e.js +1 -0
  171. package/apps/pwa/out/_next/static/chunks/app/trace/page-efe5304057aa11c7.js +1 -0
  172. package/apps/pwa/out/_next/static/chunks/app/vault/page-76bcd29aaa8a26c3.js +1 -0
  173. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-4cfd88e2273ce50f.js +1 -0
  174. package/apps/pwa/out/_next/static/chunks/app/workflows/page-0a1ddb47efba0a32.js +1 -0
  175. package/apps/pwa/out/_next/static/chunks/app/workspace/page-2a176c707152c892.js +1 -0
  176. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-efe03031b8797fbf.js +1 -0
  177. package/apps/pwa/out/_next/static/chunks/{409c787b.e48c5bec8ec10456.js → b450776b.3db34d8d965f82d3.js} +1 -1
  178. package/apps/pwa/out/_next/static/chunks/{ca3702f8.2913182e73889fa2.js → e8543907.5b040a5f22a0b0e5.js} +7 -7
  179. package/apps/pwa/out/_next/static/chunks/framework-d0d2d673bfd8a8a9.js +1 -0
  180. package/apps/pwa/out/_next/static/chunks/main-4c9fceb2baa09cb7.js +1 -0
  181. package/apps/pwa/out/_next/static/chunks/main-app-b0ab8d8fc9a97556.js +1 -0
  182. package/apps/pwa/out/_next/static/chunks/pages/_app-7e2cc62a2e5cec96.js +1 -0
  183. package/apps/pwa/out/_next/static/chunks/pages/_error-22324935f753ec5e.js +1 -0
  184. package/apps/pwa/out/_next/static/chunks/webpack-9dc1bfa25334998a.js +1 -0
  185. package/apps/pwa/out/_next/static/css/{b9d8151e48998417.css → 62c858ad53854f47.css} +1 -1
  186. package/apps/pwa/out/approvals/index.html +1 -1
  187. package/apps/pwa/out/approvals/index.txt +22 -22
  188. package/apps/pwa/out/autopilot/index.html +1 -1
  189. package/apps/pwa/out/autopilot/index.txt +22 -22
  190. package/apps/pwa/out/board/index.html +1 -1
  191. package/apps/pwa/out/board/index.txt +22 -22
  192. package/apps/pwa/out/botlab/index.html +1 -1
  193. package/apps/pwa/out/botlab/index.txt +21 -21
  194. package/apps/pwa/out/bots/index.html +1 -1
  195. package/apps/pwa/out/bots/index.txt +21 -21
  196. package/apps/pwa/out/ceo/index.html +1 -1
  197. package/apps/pwa/out/ceo/index.txt +22 -22
  198. package/apps/pwa/out/chat/index.html +1 -1
  199. package/apps/pwa/out/chat/index.txt +21 -21
  200. package/apps/pwa/out/consult/index.html +1 -1
  201. package/apps/pwa/out/consult/index.txt +22 -22
  202. package/apps/pwa/out/control/index.html +1 -1
  203. package/apps/pwa/out/control/index.txt +22 -22
  204. package/apps/pwa/out/decisions/index.html +1 -1
  205. package/apps/pwa/out/decisions/index.txt +21 -21
  206. package/apps/pwa/out/design-check/index.html +1 -1
  207. package/apps/pwa/out/design-check/index.txt +22 -22
  208. package/apps/pwa/out/editor/index.html +1 -1
  209. package/apps/pwa/out/editor/index.txt +23 -24
  210. package/apps/pwa/out/exec/index.html +1 -1
  211. package/apps/pwa/out/exec/index.txt +22 -22
  212. package/apps/pwa/out/field/index.html +1 -1
  213. package/apps/pwa/out/field/index.txt +22 -22
  214. package/apps/pwa/out/healthz +1 -1
  215. package/apps/pwa/out/index.html +1 -1
  216. package/apps/pwa/out/index.txt +21 -21
  217. package/apps/pwa/out/inside/index.html +1 -1
  218. package/apps/pwa/out/inside/index.txt +22 -22
  219. package/apps/pwa/out/intake/index.html +1 -1
  220. package/apps/pwa/out/intake/index.txt +22 -22
  221. package/apps/pwa/out/live/index.html +1 -1
  222. package/apps/pwa/out/live/index.txt +23 -24
  223. package/apps/pwa/out/live-run/index.html +1 -1
  224. package/apps/pwa/out/live-run/index.txt +23 -24
  225. package/apps/pwa/out/loops/index.html +1 -1
  226. package/apps/pwa/out/loops/index.txt +21 -21
  227. package/apps/pwa/out/market/index.html +1 -1
  228. package/apps/pwa/out/market/index.txt +21 -21
  229. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  230. package/apps/pwa/out/missions/placeholder/index.txt +21 -21
  231. package/apps/pwa/out/morning/index.html +1 -1
  232. package/apps/pwa/out/morning/index.txt +22 -22
  233. package/apps/pwa/out/observatory/index.html +1 -1
  234. package/apps/pwa/out/observatory/index.txt +22 -22
  235. package/apps/pwa/out/ops/checklist/index.html +1 -1
  236. package/apps/pwa/out/ops/checklist/index.txt +21 -21
  237. package/apps/pwa/out/ops/release/index.html +1 -1
  238. package/apps/pwa/out/ops/release/index.txt +21 -21
  239. package/apps/pwa/out/ops/seats/index.html +1 -1
  240. package/apps/pwa/out/ops/seats/index.txt +21 -21
  241. package/apps/pwa/out/outputs/index.html +1 -1
  242. package/apps/pwa/out/outputs/index.txt +21 -21
  243. package/apps/pwa/out/reflection/index.html +1 -1
  244. package/apps/pwa/out/reflection/index.txt +22 -22
  245. package/apps/pwa/out/scheduler/index.html +1 -1
  246. package/apps/pwa/out/scheduler/index.txt +22 -22
  247. package/apps/pwa/out/sessions/index.html +1 -1
  248. package/apps/pwa/out/sessions/index.txt +21 -21
  249. package/apps/pwa/out/settings/devices/index.html +1 -1
  250. package/apps/pwa/out/settings/devices/index.txt +22 -22
  251. package/apps/pwa/out/settings/index.html +1 -1
  252. package/apps/pwa/out/settings/index.txt +22 -22
  253. package/apps/pwa/out/setup/done/index.html +1 -1
  254. package/apps/pwa/out/setup/done/index.txt +22 -22
  255. package/apps/pwa/out/setup/index.html +1 -1
  256. package/apps/pwa/out/setup/index.txt +22 -22
  257. package/apps/pwa/out/share/index.html +1 -1
  258. package/apps/pwa/out/share/index.txt +22 -22
  259. package/apps/pwa/out/showroom/index.html +1 -1
  260. package/apps/pwa/out/showroom/index.txt +21 -21
  261. package/apps/pwa/out/tasks/index.html +1 -1
  262. package/apps/pwa/out/tasks/index.txt +22 -22
  263. package/apps/pwa/out/term/index.html +1 -1
  264. package/apps/pwa/out/term/index.txt +22 -22
  265. package/apps/pwa/out/today/index.html +1 -1
  266. package/apps/pwa/out/today/index.txt +22 -22
  267. package/apps/pwa/out/trace/index.html +1 -1
  268. package/apps/pwa/out/trace/index.txt +22 -22
  269. package/apps/pwa/out/vault/index.html +1 -1
  270. package/apps/pwa/out/vault/index.txt +22 -22
  271. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  272. package/apps/pwa/out/workflows/chat-ui/index.txt +22 -22
  273. package/apps/pwa/out/workflows/index.html +1 -1
  274. package/apps/pwa/out/workflows/index.txt +22 -22
  275. package/apps/pwa/out/workspace/index.html +1 -1
  276. package/apps/pwa/out/workspace/index.txt +22 -22
  277. package/apps/pwa/out/worktrees/index.html +1 -1
  278. package/apps/pwa/out/worktrees/index.txt +22 -22
  279. package/bin/elanous.mjs +6 -1
  280. package/catalog/models/openai/gpt-6.1-sol.yaml +16 -0
  281. package/catalog/models/openai/gpt-6.1-terra.yaml +16 -0
  282. package/docs/goal-context/04-mock-scenarios.md +13 -0
  283. package/graphs/catalog/catalog.yaml +1 -1
  284. package/graphs/demo/persona-teacher/lesson-pack.yaml +16 -0
  285. package/graphs/demo/persona-teacher/recipes.yaml +17 -0
  286. package/graphs/demo/persona-teacher/weekly-lesson.yaml +36 -0
  287. package/graphs/heal/heal-loop.yaml +1 -0
  288. package/graphs/overlays/stop-heal-ladder.yaml +136 -2
  289. package/graphs/release/release-loop.yaml +3 -1
  290. package/graphs/units/gate/gate.yaml +20 -0
  291. package/graphs/units/git/git.yaml +26 -0
  292. package/graphs/units/implement/implement.yaml +19 -0
  293. package/graphs/units/landing/landing.yaml +18 -0
  294. package/graphs/units/review/review.yaml +20 -0
  295. package/graphs/units/verbs/recipes.yaml +13 -10
  296. package/graphs/units/verbs/research.yaml +20 -0
  297. package/graphs/work/research-report.yaml +21 -3
  298. package/package.json +1 -1
  299. package/scripts/disk-steward.ts +46 -0
  300. package/scripts/fleet/node-setup.sh +50 -0
  301. package/scripts/graph-units/verbs/research.ts +55 -0
  302. package/scripts/heal/run-role.ts +99 -5
  303. package/scripts/land-global-checks.ts +6 -1
  304. package/scripts/lib/decompose-ab.ts +1 -0
  305. package/scripts/lib/fixtures/tui-regress/help-insufficient-commands.txt +15 -0
  306. package/scripts/lib/fixtures/tui-regress/help-live.txt +42 -0
  307. package/scripts/lib/fixtures/tui-regress/help-missing-keys.txt +21 -0
  308. package/scripts/lib/fixtures/tui-regress/help-original.txt +51 -0
  309. package/scripts/lib/fixtures/tui-regress/help-shifted-input.txt +26 -0
  310. package/scripts/lib/tui-regress-checks.ts +33 -13
  311. package/scripts/llm-cost-ledger.ts +179 -8
  312. package/scripts/measure-nl-routing-corpus.ts +10 -2
  313. package/scripts/persona-teacher/weekly-lesson-node.ts +172 -0
  314. package/scripts/public-copy-lint.ts +163 -19
  315. package/scripts/ratchets/train-side-invariants.sh +0 -1
  316. package/scripts/release-loop/gate-autoheal.ts +117 -0
  317. package/scripts/release-loop/gate-baseline-receipt.ts +55 -10
  318. package/scripts/release-loop/gate-diff.ts +22 -3
  319. package/scripts/release-loop/gate-image-pin.ts +81 -0
  320. package/scripts/release-loop/gate-node-sim.ts +207 -0
  321. package/scripts/release-loop/gate-node.ts +489 -62
  322. package/scripts/release-loop/gate-pod-image-sticky-plan.ts +99 -0
  323. package/scripts/release-loop/gate-radius-live.ts +97 -13
  324. package/scripts/release-loop/gate-shard-dispatch.ts +43 -0
  325. package/scripts/release-loop/gate-shard-load-flaky.ts +256 -0
  326. package/scripts/release-loop/gate-train-receipts.ts +122 -0
  327. package/scripts/release-loop/gate-triage-node.ts +70 -10
  328. package/scripts/release-loop/resume-release.ts +330 -23
  329. package/scripts/release-loop/traffic-node.ts +6 -3
  330. package/scripts/release-loop/unattended-release.ts +4 -2
  331. package/scripts/shadow-authoring-probe.ts +20 -0
  332. package/scripts/ux-sim.ts +37 -3
  333. package/scripts/warm-pool-provider-config.ts +263 -0
  334. package/skills/omni-crawl/SKILL.md +5 -2
  335. package/skills/omni-crawl/scripts/main.ts +84 -23
  336. package/src/acp/core-turn-bridge.ts +99 -4
  337. package/src/agent/registry.ts +2 -0
  338. package/src/agent/task-notification.ts +25 -5
  339. package/src/agent/types.ts +6 -0
  340. package/src/agent-mission/mission-cli.ts +24 -0
  341. package/src/agent-mission/mission-out.ts +595 -0
  342. package/src/agent-mission/tool-install.ts +88 -2
  343. package/src/agent-substrate/context-strategy.ts +4 -4
  344. package/src/agent-substrate/self-review-cli.ts +267 -40
  345. package/src/auto-research/research-turn.ts +133 -51
  346. package/src/autopilot/discovery/wiki-claim-proposer.ts +22 -1
  347. package/src/autopilot/pr-manager.ts +38 -7
  348. package/src/autopilot/pty-control-loop.ts +2 -0
  349. package/src/boot/daemon-tools/self-implement.ts +46 -9
  350. package/src/budget/shadow-arms.ts +52 -0
  351. package/src/capture/tui-self-report.ts +22 -6
  352. package/src/chat/index.ts +1 -1
  353. package/src/chat/pickers/state.ts +7 -1
  354. package/src/cli/fixtures/review-loop-breaker/opwaiver-32646-1694ccc7a170.json +1 -0
  355. package/src/cli/fixtures/review-loop-breaker/opwaiver-32853-bd8f73595c83.json +1 -0
  356. package/src/cli/fixtures/review-loop-breaker/opwaiver-33097-1cc85900413c.json +1 -0
  357. package/src/cli/fixtures/review-loop-breaker/opwaiver-33106-972f31e4a3ca.json +1 -0
  358. package/src/cli/fixtures/review-loop-breaker/opwaiver-33113-3ab51f05968f.json +1 -0
  359. package/src/cli/fixtures/review-loop-breaker/pr-33097-1cc85900413c.json +1 -0
  360. package/src/cli/log-event-names.ts +5 -0
  361. package/src/cli/mcp-login.ts +8 -18
  362. package/src/cli/msg-cli.ts +57 -1
  363. package/src/cli/pr-land-heavy-remote.ts +47 -9
  364. package/src/cli/pr-land-overlap-evidence.ts +100 -1
  365. package/src/cli/pr-land-pod-shadow-job.ts +60 -0
  366. package/src/cli/release-checklist-read-cli.ts +20 -10
  367. package/src/cli/release-cli.ts +27 -1
  368. package/src/cli/research-cli.ts +4 -5
  369. package/src/cli/sim-cli.ts +7 -1
  370. package/src/cli/tasks-cli.ts +12 -4
  371. package/src/codex/models.ts +30 -15
  372. package/src/compact/auto.ts +8 -1
  373. package/src/control-plane/ledger.ts +52 -2
  374. package/src/control-plane/server.ts +16 -2
  375. package/src/core-turn/run-core-turn.ts +313 -89
  376. package/src/core-turn/run-goal-loop.ts +106 -2
  377. package/src/core-turn/types.ts +2 -0
  378. package/src/dashboard/acp-boot.ts +72 -3
  379. package/src/dashboard/classic-sidetab-mission-job.ts +58 -0
  380. package/src/dashboard/classic-sidetab-mission.ts +185 -0
  381. package/src/dashboard/index.ts +155 -15
  382. package/src/dashboard/input/agent-wake.ts +23 -1
  383. package/src/dashboard/input/chat-main-plain-turn-runtime.ts +9 -0
  384. package/src/dashboard/mission-cli-consumer.ts +153 -0
  385. package/src/dashboard/seat-boot-preamble.ts +171 -1
  386. package/src/dashboard/side-basic-runtime.ts +54 -1
  387. package/src/dashboard/sidetab-runtime.ts +90 -1
  388. package/src/dashboard/skill-catalog-preamble.ts +101 -0
  389. package/src/dashboard/skill-detection-runtime.ts +55 -15
  390. package/src/dashboard/slash-runtime/dashboard-handlers.ts +15 -0
  391. package/src/dashboard/turn-preamble.ts +65 -16
  392. package/src/dashboard/turn-prompt-runtime.ts +4 -2
  393. package/src/disk-steward/judge.ts +73 -0
  394. package/src/disk-steward/ledger.ts +122 -0
  395. package/src/disk-steward/steward.ts +373 -0
  396. package/src/domains/guardian-tick.ts +191 -2
  397. package/src/graph-runner/graph-cli.ts +44 -7
  398. package/src/graph-runner/graph-tick.ts +10 -2
  399. package/src/graph-runner/in-process-registry.ts +37 -0
  400. package/src/graph-runner/runner.ts +34 -4
  401. package/src/grok/agent-search.ts +14 -4
  402. package/src/grok/credential.ts +28 -11
  403. package/src/grounding/ref-index.ts +19 -0
  404. package/src/grounding/sources-cli.ts +9 -1
  405. package/src/harness/harness-cli-command.ts +267 -14
  406. package/src/harness/harness-incidents.ts +312 -7
  407. package/src/harness/harness-pod-dispatch.ts +81 -15
  408. package/src/harness/harness-space.ts +15 -1
  409. package/src/harness/harness-stop.ts +155 -11
  410. package/src/harness/progress-worker-entry.ts +59 -0
  411. package/src/harness/progress-worker-runtime.ts +11 -0
  412. package/src/index.ts +52 -7
  413. package/src/instance/dev-project.ts +25 -0
  414. package/src/instance/spawn-elanous.ts +6 -4
  415. package/src/intelligence-map/model-alias.ts +6 -5
  416. package/src/intelligence-map/model-catalog.ts +59 -2
  417. package/src/llm/model-defaults.ts +4 -4
  418. package/src/llm.ts +208 -19
  419. package/src/loops/orchestrator/coordinator-shadow.ts +226 -0
  420. package/src/loops/orchestrator/tick.ts +61 -0
  421. package/src/maturity/feature-maturity.ts +1 -1
  422. package/src/mcp/mcp-oauth.ts +3 -1
  423. package/src/model-tier/llm-tier-map.ts +9 -9
  424. package/src/model-tier/turn-tier.ts +1 -1
  425. package/src/msg/msg-mirror-github.ts +80 -0
  426. package/src/msg/msg-mirror-policy.ts +61 -0
  427. package/src/msg/msg-mirror-runner.ts +219 -0
  428. package/src/msg/msg-mirror-state.ts +33 -0
  429. package/src/msg/msg-store.ts +11 -0
  430. package/src/native-tool-catalog.ts +2 -2
  431. package/src/nexus/api/harness-traversal.ts +11 -0
  432. package/src/output-truncation.ts +41 -12
  433. package/src/pr-tool/census.ts +3 -2
  434. package/src/project/project-store.ts +48 -1
  435. package/src/pty-shell/tui-run-state.ts +188 -2
  436. package/src/release-loop/checklist-target-path-hint.ts +13 -0
  437. package/src/release-loop/checklist.ts +9 -1
  438. package/src/release-loop/gate-host-reservation.ts +144 -0
  439. package/src/release-loop/release-schedule.ts +90 -1
  440. package/src/repl/index.ts +43 -2
  441. package/src/research/axis-fanout.ts +121 -16
  442. package/src/research/bloom-exemplars.ts +137 -0
  443. package/src/research/gap-followup.ts +13 -1
  444. package/src/research/plugin-discovery.ts +36 -14
  445. package/src/research-bridge/invoke.ts +22 -5
  446. package/src/research-bridge/types.ts +2 -0
  447. package/src/self-dev/ask-launch-flow.ts +39 -25
  448. package/src/self-dev/decompose.ts +119 -5
  449. package/src/self-dev/dev-cli.ts +17 -17
  450. package/src/self-dev/graph-front-nodes.ts +23 -0
  451. package/src/self-dev/launch-preflight-text.ts +5 -6
  452. package/src/self-dev/launch-preflight.ts +9 -4
  453. package/src/self-dev/orchestrate.ts +27 -2
  454. package/src/self-dev/review-provider-fallback.ts +9 -2
  455. package/src/self-dev/run-supervisor.ts +226 -6
  456. package/src/self-dev/run-types.ts +7 -0
  457. package/src/self-implement/doctor-visit.ts +88 -0
  458. package/src/self-implement/gate-artifacts.ts +13 -0
  459. package/src/self-implement/gate-cli.ts +54 -24
  460. package/src/self-implement/gate-remote.ts +23 -5
  461. package/src/self-implement/goal-author-clarification.ts +31 -12
  462. package/src/self-implement/goal-author.ts +403 -8
  463. package/src/self-implement/graph-overlay-yaml.ts +7 -0
  464. package/src/self-implement/graph-stage-units.ts +172 -0
  465. package/src/self-implement/graph-templates.ts +162 -1
  466. package/src/self-implement/graph-variant.ts +37 -4
  467. package/src/self-implement/headless-elanous-driver.ts +31 -2
  468. package/src/self-implement/heal-intake.ts +112 -4
  469. package/src/self-implement/heal-triage.ts +20 -0
  470. package/src/self-implement/host-regate.ts +31 -8
  471. package/src/self-implement/knowhow-pack.ts +38 -0
  472. package/src/self-implement/merge-train.ts +5 -0
  473. package/src/self-implement/orchestrator.ts +359 -30
  474. package/src/self-implement/regate-detail.ts +38 -1
  475. package/src/self-implement/rework-child-provider.ts +25 -0
  476. package/src/self-implement/run-chart.ts +119 -0
  477. package/src/self-implement/run-ledger.ts +176 -17
  478. package/src/self-implement/run-stop.ts +208 -5
  479. package/src/self-implement/running-runs.ts +35 -12
  480. package/src/self-implement/self-implement-cli.ts +36 -4
  481. package/src/self-implement/stop-autoheal-operational.ts +43 -0
  482. package/src/self-implement/stop-autoheal.ts +28 -6
  483. package/src/session/chat.ts +69 -58
  484. package/src/session/request-size-guard.ts +123 -0
  485. package/src/session-runtime/index.ts +98 -4
  486. package/src/session-runtime/tier-flip.ts +3 -3
  487. package/src/sidetab-core/model.ts +150 -0
  488. package/src/sidetab-core/visible-agent-mission.ts +186 -0
  489. package/src/skills/name-match.ts +20 -0
  490. package/src/skills/tools/agent.ts +30 -2
  491. package/src/skills/tools/omni-search.ts +67 -5
  492. package/src/task-agent/parent-host.ts +14 -1
  493. package/src/task-agent/pod-distributor-entry.ts +3 -0
  494. package/src/task-agent/task-hand.ts +31 -1
  495. package/src/task-orchestrator/surfaces/pod-distributor.ts +48 -0
  496. package/src/task-orchestrator/surfaces/pod-failure-reason.ts +43 -0
  497. package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +38 -1
  498. package/src/task-orchestrator/surfaces/pod-ledger-events.ts +29 -4
  499. package/src/task-orchestrator/surfaces/pod-si-warm-entry.ts +33 -6
  500. package/src/task-orchestrator/surfaces/pod-source-bundle.ts +45 -9
  501. package/src/task-orchestrator/surfaces/pod-source-receive.ts +163 -13
  502. package/src/task-orchestrator/surfaces/pod-source-spec.ts +3 -3
  503. package/src/task-orchestrator/surfaces/pod-warm-worker-provider.ts +209 -39
  504. package/src/task-orchestrator/surfaces/pod-warm-worker-runtime.ts +80 -3
  505. package/src/task-orchestrator/surfaces/self-implement-pod.ts +280 -60
  506. package/src/task-orchestrator/surfaces/self-implement.ts +18 -0
  507. package/src/tokens.ts +35 -2
  508. package/src/tool-runtime/agent-runtime.ts +1 -0
  509. package/src/tool-runtime/elanous-skills-list-runtime.ts +5 -1
  510. package/src/tool-runtime/skill-exec-runtime.ts +33 -13
  511. package/src/tool-runtime/types.ts +1 -0
  512. package/src/tui/entry.ts +24 -7
  513. package/src/tui/sidetab/attach.ts +47 -0
  514. package/src/tui/sidetab/list.ts +45 -0
  515. package/src/tui/sidetab/nl-tool.ts +2 -136
  516. package/src/tui/sidetab/peek.ts +52 -0
  517. package/src/tui-client/dashboard-session.ts +3 -0
  518. package/src/tui-client/local-chat-turn.ts +26 -13
  519. package/src/tui.ts +22 -0
  520. package/src/user-config.ts +11 -5
  521. package/src/ux-sim/fake-kubectl.ts +37 -3
  522. package/src/ux-sim/fault-needs-human-reason-repair.ts +107 -0
  523. package/src/ux-sim/fault-pod-child-result.ts +42 -0
  524. package/src/ux-sim/fault-pool-cap.ts +11 -6
  525. package/src/ux-sim/mock-scenario-catalog.json +41 -0
  526. package/src/ux-sim/mock-scenario-catalog.ts +156 -0
  527. package/src/ux-sim/release-sim-rig.ts +262 -0
  528. package/src/ux-sim/scenario.ts +116 -23
  529. package/src/ux-sim/seminar-rehearsal.ts +109 -0
  530. package/src/ux-sim/subagent-tools-device.ts +2 -2
  531. package/src/version/packed-revision.json +1 -1
  532. package/src/voice/voice-tts-singleton.ts +18 -3
  533. package/src/web-search/grok.ts +5 -9
  534. package/apps/pwa/out/_next/static/chunks/1060.014f1f01a17aec26.js +0 -115
  535. package/apps/pwa/out/_next/static/chunks/1415.79821ec7ae99acdb.js +0 -1
  536. package/apps/pwa/out/_next/static/chunks/1452.162bb2d9d699e82a.js +0 -1
  537. package/apps/pwa/out/_next/static/chunks/147.f92f63bbc5e85fa5.js +0 -286
  538. package/apps/pwa/out/_next/static/chunks/1661.fb425a1d8a065b72.js +0 -149
  539. package/apps/pwa/out/_next/static/chunks/1671-1c22b40b50b208f2.js +0 -1
  540. package/apps/pwa/out/_next/static/chunks/1758.0204d3f48783c49d.js +0 -90
  541. package/apps/pwa/out/_next/static/chunks/1806.027bf78b6a94d340.js +0 -104
  542. package/apps/pwa/out/_next/static/chunks/2047.8f23e210995d4548.js +0 -2
  543. package/apps/pwa/out/_next/static/chunks/2061-7f2bbc53ae96c88b.js +0 -1
  544. package/apps/pwa/out/_next/static/chunks/2066.a77257c8c350770a.js +0 -23
  545. package/apps/pwa/out/_next/static/chunks/2096-a938496bc996dcbd.js +0 -1
  546. package/apps/pwa/out/_next/static/chunks/2200-9a453b8f99687d49.js +0 -1
  547. package/apps/pwa/out/_next/static/chunks/2281-b8cede695c6c650e.js +0 -1
  548. package/apps/pwa/out/_next/static/chunks/2499.02fbfd18126c10c0.js +0 -1
  549. package/apps/pwa/out/_next/static/chunks/2522.f2766b76d22c8bdf.js +0 -34
  550. package/apps/pwa/out/_next/static/chunks/2615.3c9738153de3ef3f.js +0 -1
  551. package/apps/pwa/out/_next/static/chunks/2727.e703d99ac414916d.js +0 -1
  552. package/apps/pwa/out/_next/static/chunks/2752.7904b8df0254d0ae.js +0 -27
  553. package/apps/pwa/out/_next/static/chunks/2762-5051a862dfa40e99.js +0 -1
  554. package/apps/pwa/out/_next/static/chunks/2767-31784bfedf8e245d.js +0 -1
  555. package/apps/pwa/out/_next/static/chunks/2774-719820e3f5b2a3eb.js +0 -1
  556. package/apps/pwa/out/_next/static/chunks/2812-f016aa0dda149287.js +0 -1
  557. package/apps/pwa/out/_next/static/chunks/2829-734fe8bf2eee7347.js +0 -1
  558. package/apps/pwa/out/_next/static/chunks/311-32dcac79970985e8.js +0 -1
  559. package/apps/pwa/out/_next/static/chunks/3175-f93f7495cd28253c.js +0 -1
  560. package/apps/pwa/out/_next/static/chunks/3184.8da993f5502d2140.js +0 -1
  561. package/apps/pwa/out/_next/static/chunks/3231-d7c981db0ff8bdcc.js +0 -1
  562. package/apps/pwa/out/_next/static/chunks/3250.151309c5176d6b4b.js +0 -1
  563. package/apps/pwa/out/_next/static/chunks/3517.a5aa1797a9dbf2ee.js +0 -1
  564. package/apps/pwa/out/_next/static/chunks/3567-45f676877ed9e8a5.js +0 -1
  565. package/apps/pwa/out/_next/static/chunks/3819.8a1de79b2e8c7298.js +0 -24
  566. package/apps/pwa/out/_next/static/chunks/39.9b37b0be4c128a83.js +0 -77
  567. package/apps/pwa/out/_next/static/chunks/4023.a19045970a9ce16a.js +0 -36
  568. package/apps/pwa/out/_next/static/chunks/413-24c8284bda08241d.js +0 -1
  569. package/apps/pwa/out/_next/static/chunks/4135.e45d6ebc29e20647.js +0 -1
  570. package/apps/pwa/out/_next/static/chunks/4283.65502f5d0e6ff6c0.js +0 -287
  571. package/apps/pwa/out/_next/static/chunks/4305-4e28fa92a7b34a5b.js +0 -1
  572. package/apps/pwa/out/_next/static/chunks/4335-50efa61fd7613142.js +0 -1
  573. package/apps/pwa/out/_next/static/chunks/4720.ecbd1693fa151068.js +0 -1
  574. package/apps/pwa/out/_next/static/chunks/483.725e4a61be922788.js +0 -1
  575. package/apps/pwa/out/_next/static/chunks/4969-4b209d7307410c00.js +0 -1
  576. package/apps/pwa/out/_next/static/chunks/5068-c7f53862f83c0917.js +0 -1
  577. package/apps/pwa/out/_next/static/chunks/5315.bc96711c24dfefe2.js +0 -74
  578. package/apps/pwa/out/_next/static/chunks/5542.efbf14f16baed4dd.js +0 -1
  579. package/apps/pwa/out/_next/static/chunks/5622.94893f5aea7185d0.js +0 -1
  580. package/apps/pwa/out/_next/static/chunks/5644.8f401a25601d05fc.js +0 -1
  581. package/apps/pwa/out/_next/static/chunks/5658.35f70eb9871a54bf.js +0 -1
  582. package/apps/pwa/out/_next/static/chunks/5721.7bdc0e904c76ab51.js +0 -8
  583. package/apps/pwa/out/_next/static/chunks/5790-eef0ca20884a6bdf.js +0 -1
  584. package/apps/pwa/out/_next/static/chunks/5802.ef0d8598ca68fe84.js +0 -62
  585. package/apps/pwa/out/_next/static/chunks/5890.fb974729788dcb71.js +0 -1
  586. package/apps/pwa/out/_next/static/chunks/5924.5ef25cddd36d5dbf.js +0 -1
  587. package/apps/pwa/out/_next/static/chunks/5979-e59c099ebf8d1f35.js +0 -1
  588. package/apps/pwa/out/_next/static/chunks/6020.714cfeada2315e2e.js +0 -94
  589. package/apps/pwa/out/_next/static/chunks/6027-dfe0349d8c50f698.js +0 -1
  590. package/apps/pwa/out/_next/static/chunks/6178.bc4730c86cf74536.js +0 -1
  591. package/apps/pwa/out/_next/static/chunks/6273.d808b0dcb3c2b155.js +0 -1
  592. package/apps/pwa/out/_next/static/chunks/6292.6ef1df64fa6a4c8a.js +0 -1
  593. package/apps/pwa/out/_next/static/chunks/647-af8dd920ba46533c.js +0 -1
  594. package/apps/pwa/out/_next/static/chunks/6541-170dc5c59e135c0c.js +0 -1
  595. package/apps/pwa/out/_next/static/chunks/6624.981ef7caeb680560.js +0 -32
  596. package/apps/pwa/out/_next/static/chunks/6742-a1ad4a94ba7bc37a.js +0 -1
  597. package/apps/pwa/out/_next/static/chunks/6836-493dc55c39187949.js +0 -1
  598. package/apps/pwa/out/_next/static/chunks/6856-53ac421c06039a18.js +0 -1
  599. package/apps/pwa/out/_next/static/chunks/6908.06008c67f6380ea9.js +0 -148
  600. package/apps/pwa/out/_next/static/chunks/6915.096931864206f522.js +0 -1
  601. package/apps/pwa/out/_next/static/chunks/6925.5e181fb7dcf90193.js +0 -1
  602. package/apps/pwa/out/_next/static/chunks/6cc1d1d6.9a98dd4622f1afc6.js +0 -1
  603. package/apps/pwa/out/_next/static/chunks/7203-800e11037adc41eb.js +0 -1
  604. package/apps/pwa/out/_next/static/chunks/7279.6170f591105936bb.js +0 -93
  605. package/apps/pwa/out/_next/static/chunks/7349-373165c7fbdcc3df.js +0 -1
  606. package/apps/pwa/out/_next/static/chunks/7514-a533ba0ca1cae4f4.js +0 -1
  607. package/apps/pwa/out/_next/static/chunks/7515.1ad6efe157fa4448.js +0 -1
  608. package/apps/pwa/out/_next/static/chunks/7521.d919cc1fab80c1d9.js +0 -24
  609. package/apps/pwa/out/_next/static/chunks/7538.90e97bbdf9178cff.js +0 -1
  610. package/apps/pwa/out/_next/static/chunks/7825-39203196a12579b2.js +0 -1
  611. package/apps/pwa/out/_next/static/chunks/7869.5e1a9a12fd70d6c7.js +0 -1
  612. package/apps/pwa/out/_next/static/chunks/8086.1ab9103c41be884f.js +0 -1
  613. package/apps/pwa/out/_next/static/chunks/8179-e61fffbdb70f1152.js +0 -1
  614. package/apps/pwa/out/_next/static/chunks/8265.bdeb991b34db6a06.js +0 -1
  615. package/apps/pwa/out/_next/static/chunks/8357.1aaa713b052a78b1.js +0 -309
  616. package/apps/pwa/out/_next/static/chunks/8638-e42615f2166165b5.js +0 -1
  617. package/apps/pwa/out/_next/static/chunks/8639-92c11d069792429e.js +0 -1
  618. package/apps/pwa/out/_next/static/chunks/8691.54e22831d5e8ccf8.js +0 -1
  619. package/apps/pwa/out/_next/static/chunks/9069.657590a84afb8fac.js +0 -41
  620. package/apps/pwa/out/_next/static/chunks/9125-ae588d88a0293b93.js +0 -1
  621. package/apps/pwa/out/_next/static/chunks/9311.f9f641a46a508611.js +0 -1
  622. package/apps/pwa/out/_next/static/chunks/9371-a6fee8378ebfa3ee.js +0 -1
  623. package/apps/pwa/out/_next/static/chunks/9421-3aa9ff4fa7f63d8a.js +0 -1
  624. package/apps/pwa/out/_next/static/chunks/9992.07ce73c10bd5e6a1.js +0 -79
  625. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-76394dc618d322c8.js +0 -1
  626. package/apps/pwa/out/_next/static/chunks/app/approvals/page-1b948637c37b3abd.js +0 -1
  627. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-c63400f1bcf3e47c.js +0 -1
  628. package/apps/pwa/out/_next/static/chunks/app/board/page-95d0957267819586.js +0 -1
  629. package/apps/pwa/out/_next/static/chunks/app/botlab/page-ba957ff875a026a0.js +0 -1
  630. package/apps/pwa/out/_next/static/chunks/app/bots/page-bb6fd0714879cb87.js +0 -1
  631. package/apps/pwa/out/_next/static/chunks/app/ceo/page-8f9038520d2acbef.js +0 -1
  632. package/apps/pwa/out/_next/static/chunks/app/chat/page-d4b48397e05fce2c.js +0 -1
  633. package/apps/pwa/out/_next/static/chunks/app/consult/page-72d5744245dc0c27.js +0 -1
  634. package/apps/pwa/out/_next/static/chunks/app/control/page-37625dc8300fa43a.js +0 -1
  635. package/apps/pwa/out/_next/static/chunks/app/decisions/page-fbef9d160272dbc1.js +0 -1
  636. package/apps/pwa/out/_next/static/chunks/app/design-check/page-58424168df84928e.js +0 -1
  637. package/apps/pwa/out/_next/static/chunks/app/editor/page-72c12ba430ac57c0.js +0 -1
  638. package/apps/pwa/out/_next/static/chunks/app/exec/page-d53f3e9ed806d7e0.js +0 -1
  639. package/apps/pwa/out/_next/static/chunks/app/field/page-4d2fbd45feee0829.js +0 -1
  640. package/apps/pwa/out/_next/static/chunks/app/healthz/route-76394dc618d322c8.js +0 -1
  641. package/apps/pwa/out/_next/static/chunks/app/inside/page-8783dc0ed7590a2c.js +0 -1
  642. package/apps/pwa/out/_next/static/chunks/app/intake/page-3706cb390606e930.js +0 -1
  643. package/apps/pwa/out/_next/static/chunks/app/layout-9b0a86598e0111fd.js +0 -1
  644. package/apps/pwa/out/_next/static/chunks/app/live/page-4edb8df90f4537ce.js +0 -1
  645. package/apps/pwa/out/_next/static/chunks/app/loops/page-47398b9ba1cc5e1b.js +0 -1
  646. package/apps/pwa/out/_next/static/chunks/app/market/page-fb86f55cfe2e681d.js +0 -1
  647. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-e22345fc8fd47b1b.js +0 -1
  648. package/apps/pwa/out/_next/static/chunks/app/morning/page-fca2e59a81744d9e.js +0 -1
  649. package/apps/pwa/out/_next/static/chunks/app/not-found-6e96bda63100b31e.js +0 -1
  650. package/apps/pwa/out/_next/static/chunks/app/observatory/page-4d17ad8e0db8cea4.js +0 -1
  651. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-121f92766c33beaa.js +0 -1
  652. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-43f14212c545f92d.js +0 -1
  653. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-ccec2023e7a974ec.js +0 -1
  654. package/apps/pwa/out/_next/static/chunks/app/outputs/page-b04f33f548b89a94.js +0 -1
  655. package/apps/pwa/out/_next/static/chunks/app/page-c8792823c0917021.js +0 -1
  656. package/apps/pwa/out/_next/static/chunks/app/reflection/page-8c0e32782ceef4cb.js +0 -1
  657. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-94bd3922eb230ce8.js +0 -1
  658. package/apps/pwa/out/_next/static/chunks/app/sessions/page-bca565887bafa03c.js +0 -1
  659. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-ac1dffafe649fc6c.js +0 -1
  660. package/apps/pwa/out/_next/static/chunks/app/settings/page-52374edb5e23e455.js +0 -1
  661. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-ad4ebca120d43ca7.js +0 -1
  662. package/apps/pwa/out/_next/static/chunks/app/setup/layout-76394dc618d322c8.js +0 -1
  663. package/apps/pwa/out/_next/static/chunks/app/setup/page-718b89ce4b4ae4e4.js +0 -1
  664. package/apps/pwa/out/_next/static/chunks/app/share/page-204eca33eeeaddec.js +0 -1
  665. package/apps/pwa/out/_next/static/chunks/app/showroom/page-e58ba9bcdb9ac0f9.js +0 -1
  666. package/apps/pwa/out/_next/static/chunks/app/tasks/page-98f6dd50bafd7734.js +0 -1
  667. package/apps/pwa/out/_next/static/chunks/app/today/page-5a4bdd72bff27057.js +0 -1
  668. package/apps/pwa/out/_next/static/chunks/app/trace/page-6f6ab17d44f984ce.js +0 -1
  669. package/apps/pwa/out/_next/static/chunks/app/vault/page-aabb7bd15fb72da9.js +0 -1
  670. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-e4322319164a4436.js +0 -1
  671. package/apps/pwa/out/_next/static/chunks/app/workflows/page-e9efc0c8f32e2371.js +0 -1
  672. package/apps/pwa/out/_next/static/chunks/app/workspace/page-ccde99f07aaf71f7.js +0 -1
  673. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-80464aced9614390.js +0 -1
  674. package/apps/pwa/out/_next/static/chunks/c696986b.5dc8bb1143af16d1.js +0 -1
  675. package/apps/pwa/out/_next/static/chunks/d5d3cefc-373c4eb722ed848b.js +0 -1
  676. package/apps/pwa/out/_next/static/chunks/d8de0de2-dd0f4ac8e5ab1832.js +0 -1
  677. package/apps/pwa/out/_next/static/chunks/framework-002b472a66935d31.js +0 -1
  678. package/apps/pwa/out/_next/static/chunks/main-49b88d47292c0476.js +0 -1
  679. package/apps/pwa/out/_next/static/chunks/main-app-fff2549cb10a133f.js +0 -1
  680. package/apps/pwa/out/_next/static/chunks/pages/_app-32abbbba3f3b76b7.js +0 -1
  681. package/apps/pwa/out/_next/static/chunks/pages/_error-9d9cfef60c60dd7a.js +0 -1
  682. package/apps/pwa/out/_next/static/chunks/webpack-cc7f483fa294034a.js +0 -1
  683. package/apps/pwa/out/_next/static/css/d0a48dc099ed86cf.css +0 -1
  684. package/scripts/ratchets/next-tui-entry-budget.ts +0 -124
  685. /package/apps/pwa/out/_next/static/{HjeK7zONHfR7op1hKXp4k → X1ucLw10Cds_h614NoEnh}/_ssgManifest.js +0 -0
@@ -9,7 +9,7 @@ import { registerStandaloneLogSink } from '../../src/domains/standalone-log-sink
9
9
  import { landingFreezeMessage, readLandingFreeze } from '../../src/release-loop/landing-freeze.js';
10
10
  import { effectiveInstanceRoot, releaseLedgerRoot } from '../../src/instance/resolve.js';
11
11
  import { getElanousConfigDirOverride } from '../../src/elanous-config-dir.js';
12
- import { diffFailures, junitFailures, parseFailures, parseTimedOutFailures } from './gate-diff';
12
+ import { collapseGateFailureAttempts, diffFailures, GateFailureAttributionMismatch, junitFailures, parseFailureAttempts, parseFailures, parseTimedOutFailures } from './gate-diff';
13
13
  import { loadEnvKnownFailures, splitKnownEnv } from './env-known-failures';
14
14
  import { loadGateBatchSafe } from './gate-batch-safe';
15
15
  import { observeGatePrewarmCutShadow, type GatePrewarmInput } from './gate-prewarm-plan.js';
@@ -17,9 +17,13 @@ import { gateHistory } from './gate-history.js';
17
17
  import { baselineFromCutLogs } from './baseline-from-logs';
18
18
  import { mergePartialFailures, readPartialPlanResult, testFileOfId, verifyDocsOnlyCarry } from './gate-partial';
19
19
  import { planGateRadiusShadow, shadowActual, writeGateRadiusShadow, type GateRadiusShadowPlan } from './gate-radius-shadow';
20
- import { planHardRehearsalRadius, partialGateImagesMatch, preflightGateImageDigest } from './gate-radius-live';
21
- import { actualGatePodImageDigest, gateBaselineReceipt, gateEnvFingerprint, gateGraphPins, gateSourcePins,
20
+ import { planHardRehearsalRadius, partialGateImagesMatch, preflightGateExecutionIdentity, preflightGateImageDigest } from './gate-radius-live';
21
+ import { actualGatePodImageDigest, gateBaselineReceipt, gateEnvFingerprint, gateGraphPins, gateSourcePins, parseGatePodRuntimeIdentity,
22
22
  POD_SWEEP_INTEGRATION_ONLY } from './gate-baseline-receipt';
23
+ import { gateImagePinPath, readGateImagePin, resolveGateImagePin, writeGateImagePin } from './gate-image-pin.js';
24
+ import { gateReceiptRegistryPath, planGateTrainReceiptCarry } from './gate-train-receipts.js';
25
+ import { gateEnvFingerprint as gateReceiptEnvFingerprint, gateLockfileHash } from '../../src/self-implement/gate-receipt-registry.js';
26
+ import { SPAWN_SYNC_MAX_BUFFER } from '../../src/util/spawn-sync-output.js';
23
27
  export { POD_SWEEP_INTEGRATION_ONLY } from './gate-baseline-receipt';
24
28
  import { logPlanWalltime, planDurations, readFileMemory } from './shard-plan';
25
29
  import { defaultGateHostMirrorReaders, resolveGateHostMirror, type GateHostMirror, type GateHostMirrorReaders } from './gate-host-mirror';
@@ -30,7 +34,7 @@ import { readJunitFileSeconds } from './gate-method';
30
34
  import { deriveCdpTestPatterns } from '../test-deterministic';
31
35
  import { emitNodeResult, readGraphContext } from './node-verdict.js';
32
36
  import { isGateExecutor, type GateExecutor } from './gate-executor.js';
33
- import { runPodCommand, parsePodCpu, podCpuMillis, resolvePodCpu, type RunPodCommandOptions, type PodCommandResult, type PodCpu } from '../../src/task-orchestrator/surfaces/pod-command-job.js';
37
+ import { runPodCommand, parsePodCpu, podCpuMillis, resolvePodCpu, podCommandImageFor, type RunPodCommandOptions, type PodCommandResult, type PodCpu } from '../../src/task-orchestrator/surfaces/pod-command-job.js';
34
38
  import { POD_BUN_CACHE_HOST_PATH, parseInstallSeconds, podBunCacheVolume } from '../../src/task-orchestrator/surfaces/pod-bun-cache.js';
35
39
  import { PodPoolScheduler, parsePodPool, checkPodPool, defaultRemoteRun, snapshotWarmPoolMembers, type PodPoolMember } from '../../src/task-orchestrator/surfaces/pod-pool.js';
36
40
  import { POD_INSTALL_SLOTS_DEFAULT, POD_INSTALL_SLOTS_HOST_PATH, installSlotScript } from '../../src/task-orchestrator/surfaces/pod-install-slots.js';
@@ -45,13 +49,15 @@ import { getUserConfig } from '../../src/user-config.js';
45
49
  import { beginGateShardAttempt, finalizeTerminalGateShards, gateShardsPath, recordGateShardsVerdict, writeGateShards, type GateJobTerminal, type GateShard } from '../../src/release-loop/gate-shards.js';
46
50
  import { appendGateShardLedger, gateShardLedgerPath, type GateShardLedgerRow } from '../../src/release-loop/gate-shard-ledger.js';
47
51
  import { markGateShardUnmeasured, nextGateShardAttempt, readGateShardManifest, readGateShardResult, saveGateShardResult } from './gate-shard-receipts.js';
48
- import { planGateShardDispatch, type GateShardReceipt } from './gate-shard-dispatch.js';
52
+ import { planGateShardDispatch, summarizeGateProgress, type GateProgressGroup, type GateShardReceipt } from './gate-shard-dispatch.js';
49
53
  import { defaultKubectl } from '../../src/task-orchestrator/surfaces/self-implement-pod.js';
50
54
  import { executeTuiNodeCheck, type ExecutedTuiCheck } from './tui-node-checks.js';
51
55
  import { runWithReleaseGateTraffic } from './gate-traffic-mode.js';
52
56
  import { emitPodLifecycle, gatePodLifecycleEvent, gatePodOutcome } from '../../src/task-orchestrator/surfaces/pod-lifecycle-stages.js';
53
57
  import { gateHostReservationRoot, reserveReleaseGateHosts } from '../../src/release-loop/gate-host-reservation.js';
54
58
  import { normRoot } from '../../src/instance/resolve.js';
59
+ import { cleanRetryVerdict, junitFailedCaseIds, junitTestcaseCount, junitTestcaseFiles } from './gate-shard-load-flaky.js';
60
+ import { decideAndRecordGateAutoheal, gateAutohealCause, type GateAutohealDecision } from './gate-autoheal.js';
55
61
 
56
62
  export interface WarmShadowObservation {
57
63
  files: string[];
@@ -76,6 +82,10 @@ export function warmCommandMatchesRequest(command: WarmWorkerCommand, request: O
76
82
  }
77
83
 
78
84
  interface CommandResult { rc: number; output: string; passedIds?: string[]; stalledEnv?: Array<{ file: string; reason: 'no-output' }>; timedOut?: boolean;
85
+ /** Same-Pod per-file retry passed with complete log/JUnit evidence; these original ids are excluded from introduced. */
86
+ loadFlaky?: string[];
87
+ /** Files already given their one same-Pod retry; prevent the later timeout-only path from retrying them again. */
88
+ loadFlakyAttemptedFiles?: string[];
79
89
  /** Identity and complete selected inventory of this Pod sweep; only the producer may issue it. */
80
90
  podSweep?: { id: string; shards: number; files: string[] };
81
91
  /** Complete, source-bound one-file Job result for shadow comparison; absent means unmeasured. */
@@ -107,12 +117,28 @@ export interface GateTools {
107
117
  podCommand?: (options: RunPodCommandOptions) => Promise<PodCommandResult>;
108
118
  /** Kubernetes client (pool readiness · Pod lifecycle · Job log tail · failure evidence); absent = `defaultKubectl`. */
109
119
  kubectl?: typeof defaultKubectl;
120
+ /** Mock-only imageID observation seam; production reads the completed Job's child imageID from Kubernetes. */
121
+ podImageDigest?: (input: { job: PodCommandResult; host: string }) => string | null;
110
122
  /** SSH transport behind {@link GateRunner.command} on a remote host; absent = a real `ssh` child. */
111
123
  ssh?: (host: string, script: string, limitMs?: number) => { rc: number; output: string; timedOut?: boolean };
112
124
  /** SSH reachability probe of a multi-host roster; absent = `defaultRemoteRun`. */
113
125
  remoteRun?: typeof defaultRemoteRun;
114
126
  /** Pool admission a shard waits on (leases ⊕ a cluster reading); absent = the gate's own pool admission. */
115
127
  admission?: GateAdmission;
128
+ /** The live progress heartbeat scheduler; tests inject a deterministic tick, production uses an unref'd interval. */
129
+ progressScheduler?: GateProgressScheduler;
130
+ }
131
+
132
+ export type GateProgressScheduler = (tick: () => void, intervalMs: number) => () => void;
133
+ const scheduleGateProgress: GateProgressScheduler = (tick, intervalMs) => {
134
+ const timer = setInterval(tick, intervalMs);
135
+ timer.unref();
136
+ return () => clearInterval(timer);
137
+ };
138
+
139
+ export interface GateSweepProgress {
140
+ executor: 'host' | 'pod';
141
+ groups: readonly GateProgressGroup[];
116
142
  }
117
143
  export interface GateRunner {
118
144
  command(cmd: string, args: string[], cwd: string, limitMs?: number): Promise<CommandResult>;
@@ -122,7 +148,7 @@ export interface GateRunner {
122
148
  /** One checkout/install for a small independent file group; each file retains its own rc and log. */
123
149
  recheckPodBatch?(files: readonly string[], commit: string, pod: NonNullable<GateOptions['pod']>, timeoutMs: number): Promise<Map<string, CommandResult>>;
124
150
  /** `only` (GATE-PARTIAL) restricts the sweep to these test files; absent = every test file. */
125
- sweep(tree: string, logDir?: string, pod?: GateOptions['pod'], only?: readonly string[]): Promise<CommandResult>;
151
+ sweep(tree: string, logDir?: string, pod?: GateOptions['pod'], only?: readonly string[], onProgress?: (progress: GateSweepProgress) => void): Promise<CommandResult>;
126
152
  add(tree: string, commit: string): Promise<void>;
127
153
  remove(tree: string): Promise<void>;
128
154
  snapshot(tree: string, commit: string): Promise<void>;
@@ -131,6 +157,7 @@ export interface GateRunner {
131
157
  remoteMirror?: string;
132
158
  /** GATE-FLOW-SIM — the clock this runner measures with (the injected fake clock in a scenario); absent = `Date.now`. */
133
159
  now?(): number;
160
+ progressScheduler?: GateProgressScheduler;
134
161
  }
135
162
 
136
163
  /** One watchdog tick. Batch Kubernetes reads per host and trust only the current attempt's result marker. */
@@ -205,6 +232,8 @@ export interface GateOptions {
205
232
  /** Only a direct hard rehearsal may pin graph bytes from its measured Git commit. */
206
233
  rehearsalBaseline?: boolean;
207
234
  forceFreeze?: boolean;
235
+ /** Force all release tests to run, bypassing every reusable partial plan. */
236
+ full?: boolean;
208
237
  version: string;
209
238
  baselineVersion?: string;
210
239
  baselineCommit?: string;
@@ -221,6 +250,9 @@ export interface GateOptions {
221
250
  recheckFileLimit?: number;
222
251
  /** freshShards — GATE-SHARD-RESUME off: every shard starts a Pod even when this logDir already holds its result. */
223
252
  pod?: { pool: string; shards?: number; shardTimeoutSeconds?: number; durationSource?: string; bunCache?: string; freshShards?: boolean;
253
+ /** One immutable container image identity for this gate graph run and all its resumes. */
254
+ pinnedImageDigest?: string;
255
+ pinnedRunId?: string;
224
256
  /** Remeasure one named shard, or all incomplete shards with auto. Complete siblings keep their receipts. */
225
257
  rerouteShard?: string | 'auto';
226
258
  /** Pool context for the rerouted shard; the planner otherwise distributes missing shards over pool contexts. */
@@ -331,13 +363,16 @@ export interface GateResult {
331
363
  baselineSource?: 'ledger' | 'instance' | 'cut-logs' | 'swept';
332
364
  /** GATE-BASELINE-CACHE — isolated baseline files answered from the ledger (hits) or run now (misses). */
333
365
  baselineCache?: { hits: number; misses: number };
366
+ /** A residual summary/name mismatch triggers an explicit baseline rerun before the gate stays fail-closed. */
367
+ attributionRecheck?: { summary: number; observed: number; identified: number; collapsed: number;
368
+ targetFiles: string[]; baselineResult: 'completed' | 'failed' | 'timeout' };
334
369
  /** GATE-SPEED A4 — a swept baseline ran only the cut's failing files that exist at the previous release. */
335
370
  baselineDeferred?: { cutFiles: number; swept: number };
336
371
  durationMs: number;
337
372
  /** GATE-WARM-BOOT-SCAN — observation only: the cut sweep's test selection time (pod sweep · absent otherwise). */
338
373
  selectionMs?: number;
339
374
  error?: string;
340
- stalledShards?: Array<{ shard: number; files: string[]; reason: 'incomplete' | 'no-output' | 'job-failed' | 'exit-code' | 'unattributed' | 'timeout' | 'admission-timeout' | 'admission-error'; lastFile?: string; summaryFailures?: number; namedFailures?: number; detail?: string;
375
+ stalledShards?: Array<{ shard: number; files: string[]; reason: 'incomplete' | 'no-output' | 'job-failed' | 'exit-code' | 'unattributed' | 'timeout' | 'admission-timeout' | 'admission-error' | 'image-pin-mismatch'; lastFile?: string; summaryFailures?: number; namedFailures?: number; detail?: string;
341
376
  /** Pool member that ran the final unmeasured attempt; enables a bounded cross-host tail remeasure. */
342
377
  lastHost?: string;
343
378
  /** Failure of the optional cross-host tail attempt; original stall fields remain authoritative. */
@@ -351,14 +386,17 @@ export interface GateResult {
351
386
  partialFailures?: SweepFailures;
352
387
  /** GATE-PARTIAL — this verdict re-ran only these files and carried the prior gate's results for the rest. */
353
388
  partial?: { priorCommit: string; files: number };
354
- /** GATE-INTRO-RECHECK — new failures that were only the «timed out» shape and passed one isolated re-run with a
355
- * {@link GATE_TIMEOUT_RECHECK_MS} per-test limit: a warning (flaky under load), not an introduced regression. */
389
+ trainReceiptReuse?: { carried: number; total: number; receipts: number; reasons: string[] };
390
+ /** A failure that passed an evidence-checked isolated re-run: warning (flaky under load), not an introduced regression. */
356
391
  loadFlaky?: string[];
357
392
  /** File-level unhandled errors that reproduced or could not be measured in isolation. They keep the gate red. */
358
393
  unmeasuredErrors?: Array<{ file: string; reason: 'reproduced' | 'timeout' | 'unreadable'; ids: string[] }>;
359
394
  /** Shard-only unhandled errors whose file-level replay completed cleanly. */
360
395
  interferenceErrors?: string[];
361
396
  rehearsalTui?: ExecutedTuiCheck;
397
+ /** Recovery recommendation is persisted with the failed graph node so the resume coordinator can act on it. */
398
+ autoheal?: GateAutohealDecision;
399
+ autoheals?: GateAutohealDecision[];
362
400
  }
363
401
 
364
402
  const sha = /^[0-9a-f]{7,40}$/i;
@@ -768,7 +806,8 @@ export function gateShardShell(o: { parts: readonly (readonly string[])[]; cdpPa
768
806
  // run_part body changes the shell hash once for this feature; keeping the
769
807
  // multi-file invocation unchanged avoids another cache-key change.
770
808
  const warmFile = part.length === 1 ? `WARM_REFERENCE_FILE=${quote(part[0]!)} ` : '';
771
- return `${warmFile}run_part ${index} ${[...runner, ...(o.testTimeoutMs !== undefined ? ['--timeout', String(o.testTimeoutMs)] : spawnParts.includes(index) ? ['--timeout=120000'] : []), ...ignores, ...part.map(asPath)].map(quote).join(' ')}`;
809
+ const manifest = `printf '%s\\n' ${part.map(quote).join(' ')} > "$O/part-${index}.files"; `;
810
+ return `${manifest}${warmFile}run_part ${index} ${[...runner, ...(o.testTimeoutMs !== undefined ? ['--timeout', String(o.testTimeoutMs)] : spawnParts.includes(index) ? ['--timeout=120000'] : []), ...ignores, ...part.map(asPath)].map(quote).join(' ')}`;
772
811
  });
773
812
  // Only file-isolation batches use this path. A bounded group shares one clone/install, but every test process gets
774
813
  // its own HOME, state and tmp directory; part logs/JUnit remain separate and the existing artifact reader judges them.
@@ -795,6 +834,7 @@ export function gateShardShell(o: { parts: readonly (readonly string[])[]; cdpPa
795
834
  ...(o.beatSeconds ? gateBeatScript(o.beatSeconds, o.installSlots > 0) : []),
796
835
  ...(o.cachePrefix ? [o.cachePrefix] : []),
797
836
  gateEnvBaselineLine(),
837
+ `bun -e ${quote("const {createHash}=require('node:crypto');const keys=['ELANOUS_TEST_FAKE_LLM','LANG','LC_ALL','TZ'];const e=Object.fromEntries(keys.sort().map(k=>[k,process.env[k]??'']));const envFingerprint=createHash('sha256').update(JSON.stringify(e)).digest('hex');console.log(JSON.stringify({bunVersion:Bun.version,platform:process.platform,architecture:process.arch,envFingerprint}));")} > "$O/runtime-identity.json" 2> "$O/runtime-identity.err" || rm -f "$O/runtime-identity.json"`,
798
838
  ...(o.rulesBaseSha ? [`export ELANOUS_GATE_BASE_SHA=${quote(o.rulesBaseSha)}`] : []),
799
839
  // Seconds since this container's PID 1 started (the clone ran before this shell) — 0 when /proc cannot tell.
800
840
  `pod_age=$(awk -v t="$(cut -d' ' -f22 /proc/1/stat 2>/dev/null)" -v hz="$(getconf CLK_TCK 2>/dev/null)" '{ if (t > 0 && hz > 0) print int($1 - t / hz); else print 0 }' /proc/uptime 2>/dev/null)`,
@@ -826,6 +866,24 @@ export function gateShardShell(o: { parts: readonly (readonly string[])[]; cdpPa
826
866
  ...(spawnParts.length ? [' local test_start; test_start=$(date +%s)'] : []),
827
867
  ` { if [ -n "\${WARM_REFERENCE_FILE:-}" ]; then printf "[warm-worker] file=%s\\n" "$WARM_REFERENCE_FILE"; ${o.sourceBinding ? `printf "[warm-worker] cut=%s attempt=%s\\n" ${quote(o.sourceBinding.cutSha)} ${quote(o.sourceBinding.attemptId)}; ` : ''}fi; "\${run_env[@]}" timeout -k 10 "$left" "$@" --reporter=junit --reporter-outfile="$O/part-$i.junit.xml"; } > "$O/part-$i.log" 2>&1`,
828
868
  ' local prc=$?',
869
+ ' if [ -s "$O/part-$i.junit.xml" ]; then',
870
+ ' if bun run scripts/release-loop/gate-shard-load-flaky.ts "$O/part-$i.junit.xml" "$O/part-$i.retry-files" "$O/part-$i.files"; then',
871
+ ' local retry_index=0 retry_left retry_rc retry_file',
872
+ ' while IFS= read -r retry_file; do',
873
+ ' [ -n "$retry_file" ] || continue',
874
+ ' retry_left=$(( gate_end - $(date +%s) ))',
875
+ ' [ "$retry_left" -gt 0 ] || break',
876
+ ' [ "$retry_left" -le 120 ] || retry_left=120',
877
+ ' stage "part-$i-retry-$retry_index-start"',
878
+ ' : > "$O/part-$i.retry-$retry_index.started"',
879
+ ' timeout -k 10 "$retry_left" "${run_env[@]}" bun run scripts/test-deterministic.ts "$retry_file" --timeout 120000 --reporter=junit --reporter-outfile="$O/part-$i.retry-$retry_index.junit.xml" > "$O/part-$i.retry-$retry_index.log" 2>&1',
880
+ ' retry_rc=$?',
881
+ ' echo "$retry_rc" > "$O/part-$i.retry-$retry_index.rc"',
882
+ ' stage "part-$i-retry-$retry_index-end"',
883
+ ' retry_index=$(( retry_index + 1 ))',
884
+ ' done < "$O/part-$i.retry-files"',
885
+ ' fi',
886
+ ' fi',
829
887
  ' if [ -n "${WARM_REFERENCE_FILE:-}" ] && [ -f "$O/part-$i.junit.xml" ]; then',
830
888
  ' local junit_sha; junit_sha=$(sha256sum "$O/part-$i.junit.xml" 2>/dev/null | awk \'{print $1}\')',
831
889
  ' if [ -z "$junit_sha" ]; then junit_sha=$(shasum -a 256 "$O/part-$i.junit.xml" 2>/dev/null | awk \'{print $1}\'); fi',
@@ -1136,26 +1194,40 @@ export interface ShardArtifacts {
1136
1194
  finished: string[];
1137
1195
  /** Raw logs of the parts that did not finish (diagnostics · last started file). */
1138
1196
  unfinishedLog?: string;
1197
+ runtimeIdentity?: ReturnType<typeof parseGatePodRuntimeIdentity>;
1198
+ /** Original failure ids whose one same-Pod standalone retry completed cleanly. */
1199
+ loadFlaky?: string[];
1200
+ /** Files for which a same-Pod retry completed, even if it remained failing. */
1201
+ loadFlakyAttemptedFiles?: string[];
1139
1202
  }
1140
1203
  /** Reads a shard Pod's outbox: the part layout ({@link gateShardShell}) or the older single-run one (shard.log · shard.rc · junit.xml). */
1141
1204
  export function readShardArtifacts(dir: string, parts: readonly (readonly string[])[]): ShardArtifacts {
1142
1205
  const read = (name: string) => { const path = join(dir, name); return existsSync(path) ? readFileSync(path, 'utf8') : undefined; };
1206
+ const identityText = read('runtime-identity.json');
1207
+ const runtimeIdentity = identityText === undefined ? undefined : parseGatePodRuntimeIdentity(identityText) ?? undefined;
1143
1208
  const rcOf = (text: string | undefined) => { const value = text?.trim() ?? ''; return /^\d+$/.test(value) && Number.isSafeInteger(Number(value)) ? Number(value) : undefined; };
1144
1209
  const installRc = rcOf(read('install.rc'));
1145
1210
  const install = read('install.log');
1146
1211
  if (installRc === undefined && install === undefined) {
1147
1212
  const junit = read('junit.xml');
1148
1213
  const rc = rcOf(read('shard.rc'));
1149
- return { output: read('shard.log'), ...(junit !== undefined ? { junit } : {}), ...(rc !== undefined ? { rc } : {}), timedOut: false, finished: [] };
1214
+ return { output: read('shard.log'), ...(junit !== undefined ? { junit } : {}), ...(rc !== undefined ? { rc } : {}),
1215
+ ...(runtimeIdentity ? { runtimeIdentity } : {}), timedOut: false, finished: [] };
1150
1216
  }
1151
1217
  const timedOut = read('shard.timeout') !== undefined;
1152
- if (installRc !== 0) return { output: install, ...(installRc !== undefined ? { rc: installRc } : {}), timedOut, finished: [] };
1153
- const good: Array<{ log: string; junit?: string; rc: 0 | 1; files: readonly string[] }> = [];
1218
+ if (installRc !== 0) return { output: install, ...(installRc !== undefined ? { rc: installRc } : {}),
1219
+ ...(runtimeIdentity ? { runtimeIdentity } : {}), timedOut, finished: [] };
1220
+ const good: Array<{ log: string; junit?: string; rc: 0 | 1; files: readonly string[]; loadFlaky?: string[]; loadFlakyAttemptedFiles?: string[] }> = [];
1154
1221
  const bad: string[] = [];
1155
1222
  const exitAnomaly: string[] = [];
1223
+ const attemptedFiles = new Set<string>();
1156
1224
  let otherBad = false;
1157
1225
  let badRc: number | undefined;
1158
1226
  parts.forEach((files, index) => {
1227
+ const retryManifest = (read(`part-${index}.retry-files`) ?? '').split(/\r?\n/).filter(Boolean);
1228
+ retryManifest.forEach((file, retryIndex) => {
1229
+ if (files.includes(file) && read(`part-${index}.retry-${retryIndex}.started`) !== undefined) attemptedFiles.add(file);
1230
+ });
1159
1231
  const log = read(`part-${index}.log`);
1160
1232
  const rc = rcOf(read(`part-${index}.rc`));
1161
1233
  const clean = log?.replace(/\x1b\[[0-9;]*[A-Za-z]/g, '');
@@ -1166,9 +1238,32 @@ export function readShardArtifacts(dir: string, parts: readonly (readonly string
1166
1238
  otherBad = true;
1167
1239
  return;
1168
1240
  }
1169
- if (clean !== undefined && (rc === 0 || rc === 1) && lastMatch(clean, /Ran (\d+) tests? across (\d+) files?/)) {
1241
+ const summary = clean === undefined ? undefined : lastMatch(clean, /Ran (\d+) tests? across (\d+) files?/);
1242
+ const summaryTests = Number(summary?.[1] ?? NaN);
1243
+ const summaryPass = clean === undefined ? NaN : summaryCount(clean, 'pass');
1244
+ const summaryFail = clean === undefined ? NaN : summaryCount(clean, 'fail');
1245
+ const summaryErrors = clean === undefined ? NaN : summaryCount(clean, 'error');
1246
+ const partJunit = read(`part-${index}.junit.xml`) ?? '';
1247
+ const testcaseFiles = junitTestcaseFiles(partJunit);
1248
+ const expectedFiles = [...files].sort();
1249
+ const timedOutComplete = rc === 124 && summary !== undefined && summary !== null && Number(summary[2]) === files.length
1250
+ && summaryPass + summaryFail + (Number.isNaN(summaryErrors) ? 0 : summaryErrors) === summaryTests
1251
+ && junitTestcaseCount(partJunit) === summaryTests
1252
+ && testcaseFiles !== null && testcaseFiles.length === expectedFiles.length && expectedFiles.every((file, fileIndex) => testcaseFiles[fileIndex] === file);
1253
+ if (clean !== undefined && ((rc === 0 || rc === 1) || timedOutComplete) && summary) {
1170
1254
  const junit = read(`part-${index}.junit.xml`);
1171
- good.push({ log: clean, rc, files, ...(junit !== undefined ? { junit } : {}) });
1255
+ const retryFiles = retryManifest;
1256
+ const loadFlaky = retryFiles.flatMap((file, retryIndex) => {
1257
+ if (!files.includes(file)) return [];
1258
+ const retryRc = rcOf(read(`part-${index}.retry-${retryIndex}.rc`));
1259
+ const retryLog = read(`part-${index}.retry-${retryIndex}.log`);
1260
+ const retryJunit = read(`part-${index}.retry-${retryIndex}.junit.xml`);
1261
+ const originalFailures = (junitFailedCaseIds(junit ?? '') ?? []).filter((id) => fileOf(id) === file);
1262
+ const verdict = cleanRetryVerdict(retryRc, retryLog, retryJunit, file, originalFailures);
1263
+ if (!verdict.clean) return [];
1264
+ return originalFailures;
1265
+ });
1266
+ good.push({ log: clean, rc: rc === 0 ? 0 : 1, files, ...(junit !== undefined ? { junit } : {}), ...(loadFlaky.length ? { loadFlaky } : {}) });
1172
1267
  return;
1173
1268
  }
1174
1269
  if (log !== undefined) bad.push(`# part ${index} (${rc === undefined ? 'unfinished' : `rc=${rc}`})\n${log}`);
@@ -1194,8 +1289,12 @@ export function readShardArtifacts(dir: string, parts: readonly (readonly string
1194
1289
  ...(rc !== undefined ? { rc } : {}),
1195
1290
  timedOut: !complete && timedOut && badRc === undefined,
1196
1291
  finished: good.flatMap((part) => [...part.files]),
1292
+ ...([ ...new Set(good.flatMap((part) => part.loadFlaky ?? [])) ].length
1293
+ ? { loadFlaky: [...new Set(good.flatMap((part) => part.loadFlaky ?? []))].sort() } : {}),
1294
+ ...(attemptedFiles.size ? { loadFlakyAttemptedFiles: [...attemptedFiles].sort() } : {}),
1197
1295
  ...(bad.length ? { unfinishedLog: bad.join('\n') } : {}),
1198
1296
  ...(exitAnomaly.length && !otherBad ? { exitCodeOnly: exitAnomaly } : {}),
1297
+ ...(runtimeIdentity ? { runtimeIdentity } : {}),
1199
1298
  };
1200
1299
  }
1201
1300
 
@@ -1290,7 +1389,6 @@ function failuresOf(run: CommandResult, label: string): SweepFailures {
1290
1389
  const errorCount = subruns?.error ?? (Number.isNaN(reportedErrors) ? 0 : reportedErrors);
1291
1390
  const incomplete = (reason: string) => new Error(`${label} incomplete (rc=${run.rc}; Ran=${ran ? ran[0] : 'missing'}; errors=${subruns || !Number.isNaN(reportedErrors) ? errorCount : 'missing'}; ${reason})\n${tail40(output)}`);
1292
1391
  const count = subruns?.fail ?? summaryCount(output, 'fail');
1293
- const failures = parseFailures(output);
1294
1392
  // Bun repeats failed test names under "N tests failed:". A validated Pod sweep joins
1295
1393
  // separately checked shards, so a summary in one shard can precede real failures
1296
1394
  // from later shards. Remove each repeated block, not everything after the last one.
@@ -1301,6 +1399,11 @@ function failuresOf(run: CommandResult, label: string): SweepFailures {
1301
1399
  ? output.replace(/^\d+ tests? failed:\r?\n(?:\(fail\)[^\r\n]*(?:\r?\n|$))*(?:[ \t]*\r?\n)*/gm, '')
1302
1400
  : failureSummary ? output.slice(0, failureSummary.index) : output;
1303
1401
  const occurrences = [...executionOutput.matchAll(/^[ \t]*\(fail\)[ \t]+.+?(?:[ \t]+\[[\d.]+(?:ms|s)\])?[ \t]*\r?$/gm)].length;
1402
+ const collapsed = collapseGateFailureAttempts(parseFailureAttempts(executionOutput));
1403
+ const failures = collapsed.failures;
1404
+ if (collapsed.collapsed > 0) debug.log('release-loop.gate', 'failure-attribution-deduplicated', {
1405
+ label, summary: count, observed: occurrences, identified: collapsed.failures.length, collapsed: collapsed.collapsed,
1406
+ });
1304
1407
  // A test that leaves process.exitCode set leaks a non-zero exit into a run whose own summary is complete and clean
1305
1408
  // (0.2.7 gate: agent-cli at v0.2.6 · 12 pass · 0 fail · rc 2). The summary is the verdict; read that exit as clean.
1306
1409
  const leakedExit = !!ran && run.rc !== 0 && run.rc !== 1 && count === 0 && errorCount === 0 && occurrences === 0
@@ -1319,7 +1422,11 @@ function failuresOf(run: CommandResult, label: string): SweepFailures {
1319
1422
  }
1320
1423
  }
1321
1424
  if (errors.length !== errorCount) throw incomplete(`error attribution identified=${errors.length}`);
1322
- if (!Number.isFinite(count) || (count !== occurrences && !(run.validatedShards && occurrences > count && failures.length === count)) || (subruns && failures.length !== count) || (count > 0 && failures.length === 0)
1425
+ const retryDuplicatesExplainCount = occurrences > count && collapsed.observed === occurrences && failures.length === count;
1426
+ if (Number.isFinite(count) && (failures.length !== count || (occurrences !== count && !retryDuplicatesExplainCount))) {
1427
+ throw new GateFailureAttributionMismatch(label, count, occurrences, failures.length, collapsed.collapsed, failures);
1428
+ }
1429
+ if (!Number.isFinite(count) || (count > 0 && failures.length === 0)
1323
1430
  || (rc === 1 && count === 0 && errorCount === 0) || (rc === 0 && count + errorCount > 0)) {
1324
1431
  throw incomplete(`failure attribution incomplete: summary=${count}, identified=${failures.length}`);
1325
1432
  }
@@ -1374,6 +1481,7 @@ class StalledPodShards extends Error {
1374
1481
  const exitCode = stalledShards.filter((item) => item.reason === 'exit-code');
1375
1482
  super(`pod sweep incomplete: stalled shards ${stalledShards.map(({ shard }) => shard).join(', ')}`
1376
1483
  + (unmeasured.length ? ` (unmeasured: no test output within ${unmeasured[0]!.stall!.seconds}s of checkout — shards ${[...new Set(unmeasured.map(({ shard }) => shard))].join(', ')})` : '')
1484
+ + (stalledShards.some((item) => item.reason === 'image-pin-mismatch') ? ` (image pin mismatch: ${stalledShards.filter((item) => item.reason === 'image-pin-mismatch').map((item) => item.detail ?? `shard ${item.shard}`).join('; ')})` : '')
1377
1485
  + (exitCode.length ? ` (exit-code anomaly: 0 fail but exit ≠ 0/1 — ${[...new Set(exitCode.flatMap(({ files }) => files))].join(', ')})` : '')
1378
1486
  + (abortedShards ? ` (중단 조각 ${abortedShards}: mid-run-stall)` : ''));
1379
1487
  }
@@ -1408,6 +1516,7 @@ class SettledRecheckFailures extends Error {
1408
1516
 
1409
1517
  /** Isolated re-runs (host stall re-run · cut and baseline re-checks) are time-limited; sweeps and installs keep their full time. */
1410
1518
  export const GATE_ISOLATED_TIMEOUT_MS = 300_000;
1519
+ export const GATE_ATTRIBUTION_RECHECK_FILE_LIMIT = 64;
1411
1520
  /** Default final recheck window size; overflow continues in later windows. */
1412
1521
  export const GATE_HOST_RECHECK_FILE_LIMIT = 32;
1413
1522
  /** Two isolated test processes in each of four Pods = eight at most, with one checkout/install per eight files. */
@@ -1602,7 +1711,7 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
1602
1711
  if (!/^[0-9a-f]{40}$/.test(base)) throw new Error(`rules gate base invalid for ${commit.slice(0, 12)}: ${base}`);
1603
1712
  return base;
1604
1713
  };
1605
- const podSweep = async (tree: string, logDir: string | undefined, pod: NonNullable<GateOptions['pod']>, only?: readonly string[]): Promise<CommandResult> => {
1714
+ const podSweep = async (tree: string, logDir: string | undefined, pod: NonNullable<GateOptions['pod']>, only?: readonly string[], onProgress?: (progress: GateSweepProgress) => void): Promise<CommandResult> => {
1606
1715
  const sweepId = randomUUID();
1607
1716
  const shardCount = pod.shards ?? 24;
1608
1717
  const isolationConcurrency = pod.isolationConcurrency ?? 4;
@@ -1832,7 +1941,7 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
1832
1941
  return { ...(podName ? { podName } : {}), ...(termination ? { termination } : {}), ...(logs?.status === 0 ? { logTail: logs.stdout } : {}),
1833
1942
  ...(errors.length ? { error: `수거 실패 · ${errors.join(' · ')}` } : {}) };
1834
1943
  });
1835
- type ShardRun = { output: string; rc: number; pass: number; fail: number; errors: number; ran: number; files: number; passedIds: string[] };
1944
+ type ShardRun = { output: string; rc: number; pass: number; fail: number; errors: number; ran: number; files: number; passedIds: string[]; loadFlaky?: string[]; loadFlakyAttemptedFiles?: string[] };
1836
1945
  type ShardReason = NonNullable<GateResult['stalledShards']>[number]['reason'];
1837
1946
  // 깊이 끝 OOM 조각은 «조각 전체»를 파일 하나씩 격리한다 — 상한 40 이면 나머지가 못 잰 채 남았다(09-30 ① 7번 조각 110 파일).
1838
1947
  const isolationRemaining = shards.map((item) => item.files.length);
@@ -1862,6 +1971,13 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
1862
1971
  // GATE-LIVE-OBS — root rows stay stable; each recursive split/retry and tail gets its own row.
1863
1972
  // Otherwise a finished root Job can hide a still-running child (or tail) behind 80/80 done.
1864
1973
  const board: GateShard[] = shards.map((item, index) => ({ id: `pod-${index}`, state: 'pending', plannedMin: Math.round(item.plannedSeconds / 6) / 10 }));
1974
+ const reportProgress = () => onProgress?.({ executor: 'pod', groups: board.map((row) => ({
1975
+ id: row.id, state: row.state === 'retry' ? 'running' : row.state,
1976
+ ...(row.host ? { host: row.host } : {}),
1977
+ ...(row.startedAt ? { startedAtMs: Date.parse(row.startedAt) } : {}),
1978
+ ...(row.endedAt ? { endedAtMs: Date.parse(row.endedAt) } : {}),
1979
+ })) });
1980
+ reportProgress();
1865
1981
  const filesById = new Map(shards.map((item, index) => [`pod-${index}`, [...item.files]]));
1866
1982
  const detailsById = new Map<string, Partial<GateShardLedgerRow>>();
1867
1983
  const observeShard = (row: GateShard) => {
@@ -1898,6 +2014,7 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
1898
2014
  for (const key of clear) delete next[key];
1899
2015
  if (JSON.stringify(next) === JSON.stringify(row)) return;
1900
2016
  board[index] = next;
2017
+ reportProgress();
1901
2018
  observeShard(next);
1902
2019
  flushBoard();
1903
2020
  };
@@ -2001,15 +2118,19 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
2001
2118
  let podStages: Record<string, number> | undefined;
2002
2119
  let podLifecycle: Record<string, string> | undefined;
2003
2120
  let imageDigest: string | null = null;
2121
+ let runtimeIdentity: ReturnType<typeof parseGatePodRuntimeIdentity> | undefined;
2004
2122
  let podJobName: string | undefined;
2005
2123
  let batchArtifactsComplete = true;
2006
2124
  let junit: string | undefined;
2007
2125
  let rc: number | undefined;
2126
+ let shardLoadFlaky: string[] = [];
2127
+ let shardLoadFlakyAttemptedFiles: string[] = [];
2008
2128
  let jobExitCode: number | undefined;
2009
2129
  let batchArtifactDir: string | undefined;
2010
2130
  let lastFile: string | undefined;
2011
2131
  let jobFailed = false;
2012
2132
  let jobError = '';
2133
+ let imagePinMismatch = false;
2013
2134
  let failureCapture: ReturnType<NonNullable<NonNullable<GateOptions['pod']>['failureEvidence']>> | undefined;
2014
2135
  let jobNameForCapture: string | undefined;
2015
2136
  let jobContextForCapture: string | undefined;
@@ -2122,7 +2243,9 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
2122
2243
  // poolOverride is the injected scheduler used by isolated tests; it has no authenticated kubectl
2123
2244
  // context. Those sweeps intentionally cannot mint a valid baseline receipt (missing observed digest).
2124
2245
  // Production pool sweeps always use this Kubernetes lookup; lookup failure is receipt-invalid only.
2125
- if (!poolOverride && placedOn) {
2246
+ if (tools.podImageDigest) {
2247
+ imageDigest = tools.podImageDigest({ job, host: placedOn ?? 'unobserved' });
2248
+ } else if (!poolOverride && placedOn) {
2126
2249
  try {
2127
2250
  const got = kubectl(['--context', placedOn, '-n', 'elanous-test', 'get', 'pods', '-l', `job-name=${job.job}`, '-o', 'json']);
2128
2251
  if (got.status === 0) {
@@ -2131,10 +2254,43 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
2131
2254
  }
2132
2255
  } catch { /* timings never change the verdict */ }
2133
2256
  }
2257
+ if (pod.pinnedImageDigest && imageDigest !== pod.pinnedImageDigest) {
2258
+ imagePinMismatch = true;
2259
+ debug.log('release-loop.gate', 'image-pin-mismatch', { runId: pod.pinnedRunId ?? null,
2260
+ shard, branch, host: placedOn ?? null, pinnedDigest: pod.pinnedImageDigest, actualDigest: imageDigest,
2261
+ decision: 'reject-job' }, { level: 'error' });
2262
+ throw new Error(`gate image pin mismatch: expected ${pod.pinnedImageDigest}, got ${imageDigest ?? 'unmeasured'}`);
2263
+ }
2134
2264
  jobExitCode = job.exitCode;
2135
2265
  if (job.exitCode !== 0) captureFailure();
2136
2266
  batchArtifactDir = job.artifactsDir;
2137
2267
  const read = readShardArtifacts(job.artifactsDir, parts);
2268
+ runtimeIdentity = read.runtimeIdentity;
2269
+ if (logDir) {
2270
+ mkdirSync(logDir, { recursive: true });
2271
+ let retryArtifactIndex = 0;
2272
+ const retryEvidence = parts.flatMap((files, partIndex) => {
2273
+ const path = join(job.artifactsDir, `part-${partIndex}.retry-files`);
2274
+ if (!existsSync(path)) return [];
2275
+ const retryFiles = readFileSync(path, 'utf8').split(/\r?\n/).filter((file) => file && files.includes(file));
2276
+ return retryFiles.map((file, retryIndex) => {
2277
+ const prefix = `part-${partIndex}.retry-${retryIndex}`;
2278
+ const persisted: Record<string, unknown> = { file, started: existsSync(join(job.artifactsDir, `${prefix}.started`)), rc: existsSync(join(job.artifactsDir, `${prefix}.rc`))
2279
+ ? readFileSync(join(job.artifactsDir, `${prefix}.rc`), 'utf8').trim() : null };
2280
+ for (const suffix of ['log', 'junit.xml'] as const) {
2281
+ const source = join(job.artifactsDir, `${prefix}.${suffix}`);
2282
+ if (!existsSync(source)) continue;
2283
+ const targetName = `pod-${shard}${branch}.retry-${retryArtifactIndex}.${suffix}`;
2284
+ copyFileSync(source, join(logDir, targetName)); chmodSync(join(logDir, targetName), 0o600);
2285
+ persisted[suffix === 'log' ? 'log' : 'junit'] = targetName;
2286
+ }
2287
+ retryArtifactIndex++;
2288
+ return persisted;
2289
+ });
2290
+ });
2291
+ if (retryEvidence.length) writeFileSync(join(logDir, `pod-${shard}${branch}.retries.json`), JSON.stringify(retryEvidence, null, 2) + '\n', { mode: 0o600 });
2292
+ else rmSync(join(logDir, `pod-${shard}${branch}.retries.json`), { force: true });
2293
+ }
2138
2294
  batchArtifactsComplete = !batchIsolation || parts.every((_, index) =>
2139
2295
  ['rc', 'log', 'junit.xml'].every((suffix) => existsSync(join(job.artifactsDir, `part-${index}.${suffix}`))));
2140
2296
  const stagesPath = join(job.artifactsDir, 'stages.tsv');
@@ -2149,6 +2305,8 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
2149
2305
  }
2150
2306
  }
2151
2307
  stamp('artifactsRead');
2308
+ shardLoadFlaky = read.loadFlaky ?? [];
2309
+ shardLoadFlakyAttemptedFiles = read.loadFlakyAttemptedFiles ?? [];
2152
2310
  ({ output, junit, rc, timedOut, finished, unfinishedLog, exitCodeOnly } = read);
2153
2311
  // A stall counts only when the Job also left no verdict (a failed stop can let a late shard finish after all).
2154
2312
  const stalledAt = watch?.stalled();
@@ -2201,6 +2359,8 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
2201
2359
  if (unfinishedLog !== undefined) writeFileSync(unfinishedDestination, unfinishedLog, { mode: 0o600 });
2202
2360
  else rmSync(unfinishedDestination, { force: true });
2203
2361
  writeFileSync(join(logDir, `pod-${shard}${branch}.json`), JSON.stringify({ completed: true, jobName: jobNameForCapture, durationMs, rc: rc ?? null,
2362
+ ...(shardLoadFlakyAttemptedFiles.length ? { samePodRetryFiles: shardLoadFlakyAttemptedFiles } : {}),
2363
+ ...(shardLoadFlaky.length ? { loadFlaky: shardLoadFlaky } : {}),
2204
2364
  ...(stall ? { reason: 'stall', finishedFiles: 0, stallSeconds: stall.seconds, sinceCheckoutMs: stall.sinceCheckoutMs } : timedOut ? { reason: 'timeout', finishedFiles: finished.length } : {}), files: paths, plannedSeconds: estimated(paths), deadlineSeconds: shardDeadline, shardCount: shards.length, sweepId, commit, runKey, attemptId, ...(parentAttempt ? { parentAttempt } : {}), jobExitCode: jobExitCode ?? null, jobFailed, ...(placedOn ? { host: placedOn } : {}),
2205
2365
  ...(Object.keys(phaseTimes).length ? { phaseTimes } : {}), ...(podStages ? { podStages } : {}),
2206
2366
  ...(podLifecycle && Object.keys(podLifecycle).length ? { podLifecycle } : {}),
@@ -2214,7 +2374,7 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
2214
2374
  ...(failureCapture.logTail !== undefined ? { logTail: redactedGateLogTail(failureCapture.logTail) } : {}),
2215
2375
  ...(failureCapture.error ? { error: redactSecretText(failureCapture.error) } : {}),
2216
2376
  } } : {}),
2217
- ...(imageDigest ? { imageDigest } : {}),
2377
+ ...(imageDigest ? { imageDigest } : {}), ...(runtimeIdentity ? { runtimeIdentity } : {}),
2218
2378
  ...(fileSeconds !== undefined ? { fileSeconds, testSeconds } : {}), ...(testWallSeconds !== undefined ? { testWallSeconds } : {}),
2219
2379
  ...(podOverheadSeconds !== undefined ? { podOverheadSeconds } : {}),
2220
2380
  ...(unfinishedLogBytes !== undefined ? { unfinishedLogBytes } : {}),
@@ -2248,6 +2408,7 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
2248
2408
  let namedFailures: number | undefined;
2249
2409
  let unattributedDetail: string | undefined;
2250
2410
  if (descent === 'split') reason = 'incomplete';
2411
+ else if (imagePinMismatch) reason = 'image-pin-mismatch';
2251
2412
  // GATE-POD-STALL-FAILFAST: stopped with no test output — the existing «no output» reason (one fresh retry).
2252
2413
  else if (stall) reason = 'no-output';
2253
2414
  else if (timedOut) reason = 'timeout';
@@ -2366,6 +2527,8 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
2366
2527
  }
2367
2528
  }
2368
2529
  if (reason) {
2530
+ if (reason === 'image-pin-mismatch') return { runs: [], stalled: [{ shard, files: paths, reason,
2531
+ detail: jobError.slice(0, 300), ...(placedOn ? { lastHost: placedOn } : {}) }] };
2369
2532
  // GATE-SKIP-KNOWN-RETRY — a shard whose summary is complete but whose failures could not be read back (unattributed)
2370
2533
  // and whose every named failure is one the previous release already had is not re-run: re-running only re-measures
2371
2534
  // known failures (0.2.18 cut: 109 retry Pods · 1,840 shard-minutes, 4x the first pass). Those failures go to the
@@ -2448,7 +2611,9 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
2448
2611
  if (reused) debug.log('release-loop.gate', 'pod-shard-resumed', { shard, branch, files: paths.length, rc, durationMs: reused.durationMs ?? null });
2449
2612
  return { runs: [{ output: clean!, rc: rc!, pass: passes, fail: fails,
2450
2613
  errors: Number.isNaN(errors) ? 0 : errors,
2451
- ran: Number(ran![1]), files: Number(ran![2]), passedIds: junit ? junitPassedIds(junit) : [] }], stalled: [] };
2614
+ ran: Number(ran![1]), files: Number(ran![2]), passedIds: junit ? junitPassedIds(junit) : [],
2615
+ ...(shardLoadFlaky.length ? { loadFlaky: shardLoadFlaky } : {}),
2616
+ ...(shardLoadFlakyAttemptedFiles.length ? { loadFlakyAttemptedFiles: shardLoadFlakyAttemptedFiles } : {}) }], stalled: [] };
2452
2617
  };
2453
2618
  const runShard = async (...args: Parameters<typeof runShardImpl>): ReturnType<typeof runShardImpl> => {
2454
2619
  const [paths, shard, depth = 0, branch = '', , , parentAttempt, hostRetries = 0] = args;
@@ -2516,7 +2681,10 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
2516
2681
  // has no base/environment/image binding and cannot authorize this cross-host measurement.
2517
2682
  // The sixth runShardImpl argument is `fresh`: a missing receipt must launch a new Job.
2518
2683
  const freshForReceipt = !!receiptRoot || pod.freshShards === true;
2519
- const done = await withRootSlot(() => runShard(item.files, shard, 0, '', false, freshForReceipt));
2684
+ const done = await withRootSlot(() => {
2685
+ markShard(shard, { state: 'running', startedAt: new Date().toISOString() }, ['waitReason']);
2686
+ return runShard(item.files, shard, 0, '', false, freshForReceipt);
2687
+ });
2520
2688
  shardDurations[shard] = clock() - shardStartedAt;
2521
2689
  const state = !done.stalled.length ? 'done' as const : done.stalled.some((stall) => stall.reason === 'timeout') ? 'timeout' as const : 'failed' as const;
2522
2690
  markShard(shard, { state, rc: done.stalled.length || done.runs.some((run) => run.fail || run.errors) ? 1 : 0, endedAt: new Date().toISOString() }, ['waitReason']);
@@ -2618,6 +2786,10 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
2618
2786
  return { rc: total.fail + total.errors ? 1 : 0, output: outputs.join('\n') + `\n${total.pass} pass\n${total.fail} fail\n${total.errors} errors\nRan ${total.ran} tests across ${total.files} files.\n`, validatedShards: true,
2619
2787
  podSweep: { id: sweepId, shards: shards.length, files: assignable },
2620
2788
  passedIds: [...new Set(measured.flatMap((run) => run.passedIds))].sort(),
2789
+ ...(measured.some((run) => run.loadFlaky?.length)
2790
+ ? { loadFlaky: [...new Set(measured.flatMap((run) => run.loadFlaky ?? []))].sort() } : {}),
2791
+ ...(measured.some((run) => run.loadFlakyAttemptedFiles?.length)
2792
+ ? { loadFlakyAttemptedFiles: [...new Set(measured.flatMap((run) => run.loadFlakyAttemptedFiles ?? []))].sort() } : {}),
2621
2793
  stalledEnv: stalledShards.flatMap((item) => item.files.map((file) => ({ file, reason: 'no-output' as const }))),
2622
2794
  ...(abortedShards ? { abortedShards } : {}),
2623
2795
  ...(riskLane ? { riskLane } : {}) };
@@ -2704,6 +2876,33 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
2704
2876
  await quarantine('gate-job-failed');
2705
2877
  throw error;
2706
2878
  }
2879
+ if (pod.pinnedImageDigest) {
2880
+ let actualDigest: string | null = null;
2881
+ if (tools.podImageDigest) {
2882
+ actualDigest = tools.podImageDigest({ job, host: producer?.context ?? 'unobserved' });
2883
+ } else if (!poolOverride) {
2884
+ // Rechecks launch through the pool scheduler too; find the Job in its configured
2885
+ // contexts so a recheck cannot silently escape the run-level image pin.
2886
+ const contexts = [...new Set(parsePodPool(pod.pool).map((member) => member.context))];
2887
+ const observed: string[] = [];
2888
+ for (const context of contexts) {
2889
+ try {
2890
+ const got = kubectl(['--context', context, '-n', 'elanous-test', 'get', 'pods', '-l', `job-name=${job.job}`, '-o', 'json']);
2891
+ if (got.status !== 0) continue;
2892
+ const digest = actualGatePodImageDigest(got.stdout);
2893
+ if (digest) observed.push(digest);
2894
+ } catch { /* inability to observe is rejected below */ }
2895
+ }
2896
+ if (new Set(observed).size === 1) actualDigest = observed[0]!;
2897
+ }
2898
+ if (actualDigest !== pod.pinnedImageDigest) {
2899
+ debug.log('release-loop.gate', 'image-pin-mismatch', { runId: pod.pinnedRunId ?? null,
2900
+ job: job.job, phase: 'recheck', host: producer?.context ?? null,
2901
+ pinnedDigest: pod.pinnedImageDigest, actualDigest, decision: 'reject-job' }, { level: 'error' });
2902
+ await quarantine('gate-image-pin-mismatch');
2903
+ throw new Error(`gate image pin mismatch: expected ${pod.pinnedImageDigest}, got ${actualDigest ?? 'unmeasured'} (recheck)`);
2904
+ }
2905
+ }
2707
2906
  try {
2708
2907
  const artifact = (name: string) => {
2709
2908
  const path = join(job.artifactsDir, name);
@@ -2848,8 +3047,9 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
2848
3047
  recheckPodBatch: (files, commit, pod, timeoutMs) => runPodRecheckBatch(files, commit, pod, timeoutMs),
2849
3048
  get remoteMirror() { return remoteMirror; },
2850
3049
  set remoteMirror(path) { remoteMirror = path; },
2851
- async sweep(tree, logDir, pod, only) {
2852
- if (pod) return podSweep(tree, logDir, pod, only);
3050
+ progressScheduler: tools.progressScheduler ?? scheduleGateProgress,
3051
+ async sweep(tree, logDir, pod, only, onProgress) {
3052
+ if (pod) return podSweep(tree, logDir, pod, only, onProgress);
2853
3053
  const listed = await command('git', ['ls-files', '*.test.*'], tree);
2854
3054
  check(listed, 'git ls-files tests');
2855
3055
  const allFiles = listed.output.split(/\r?\n/).filter((file) => /\.test\.(?:tsx?|jsx?|mts|cts)$/.test(file));
@@ -2871,13 +3071,26 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
2871
3071
  } else cdpPatterns = deriveCdpTestPatterns({ cwd: tree });
2872
3072
  const outputs: string[] = [];
2873
3073
  let total = { pass: 0, fail: 0, errors: 0, ran: 0, files: 0 };
2874
- for (const group of groups) {
2875
- if (!group.paths.length || !files.some((f) => group.paths.some((p) => f === p || f.startsWith(`${p}/`)))) continue;
3074
+ const selectedGroups = groups.filter((group) => group.paths.length && files.some((f) => group.paths.some((p) => f === p || f.startsWith(`${p}/`))));
3075
+ const progressGroups: GateProgressGroup[] = selectedGroups.map((group) => ({ id: group.name, state: 'pending', ...(remote ? { host: remote } : {}) }));
3076
+ onProgress?.({ executor: 'host', groups: progressGroups });
3077
+ for (const group of selectedGroups) {
3078
+ const progress = progressGroups.find((item) => item.id === group.name)!;
3079
+ progress.state = 'running'; progress.startedAtMs = clock();
3080
+ onProgress?.({ executor: 'host', groups: progressGroups });
2876
3081
  const start = Date.now();
2877
3082
  const groupIgnores = [...new Set(cdpPatterns)]
2878
3083
  .filter((pattern) => files.includes(pattern) && group.paths.some((p) => pattern === p || pattern.startsWith(`${p}/`)))
2879
3084
  .flatMap((pattern) => ['--path-ignore-patterns', pattern]);
2880
- const run = await command('bun', ['run', 'test:deterministic', ...groupIgnores, ...group.paths.map(asPath)], tree);
3085
+ let run: CommandResult;
3086
+ try { run = await command('bun', ['run', 'test:deterministic', ...groupIgnores, ...group.paths.map(asPath)], tree); }
3087
+ catch (error) {
3088
+ progress.state = 'failed'; progress.endedAtMs = clock();
3089
+ onProgress?.({ executor: 'host', groups: progressGroups });
3090
+ throw error;
3091
+ }
3092
+ progress.state = run.timedOut ? 'timeout' : run.rc === 0 ? 'done' : 'failed'; progress.endedAtMs = clock();
3093
+ onProgress?.({ executor: 'host', groups: progressGroups });
2881
3094
  if (logDir) {
2882
3095
  mkdirSync(logDir, { recursive: true });
2883
3096
  writeFileSync(join(logDir, `${group.name}.log`), run.output, { mode: 0o600 });
@@ -3009,7 +3222,8 @@ export function saveBaselineFileCache(ledger: string, commit: string, rows: Base
3009
3222
  }
3010
3223
 
3011
3224
  export async function judgeGate(opts: GateOptions, runner: GateRunner = createGateRunner(opts.repo ?? process.cwd(), opts.remote),
3012
- imageProbe: typeof preflightGateImageDigest = preflightGateImageDigest): Promise<GateResult> {
3225
+ imageProbe: typeof preflightGateImageDigest = preflightGateImageDigest,
3226
+ identityProbe: typeof preflightGateExecutionIdentity = preflightGateExecutionIdentity): Promise<GateResult> {
3013
3227
  if (opts.pod?.warmReplay) opts = { ...opts, pod: { ...opts.pod,
3014
3228
  warmShadowScope: { requested: false, attempts: 0 } } };
3015
3229
  const explicitConfig = getElanousConfigDirOverride();
@@ -3056,18 +3270,88 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
3056
3270
  if (!sha.test(opts.commit) || !versionPattern.test(opts.version)
3057
3271
  || (opts.baselineVersion && !versionPattern.test(opts.baselineVersion))
3058
3272
  || (opts.baselineCommit && !sha.test(opts.baselineCommit))) throw new Error('invalid commit or version');
3059
- if (!opts.baselineVersion) throw new Error('previous release version required (--baseline-version)');
3273
+ const baselineVersion = opts.baselineVersion;
3274
+ if (!baselineVersion) throw new Error('previous release version required (--baseline-version)');
3060
3275
  if (opts.rehearsalBaseline && (opts.graphRunId || !opts.pod || !/-rc\.\d+$/.test(opts.version)))
3061
3276
  throw new Error('rehearsal baseline requires a direct Pod gate with an isolated rc version');
3062
3277
  // Observation only. Failure to plan cannot change the gate selector or verdict.
3063
3278
  try { radiusShadow = planGateRadiusShadow({ repo, ledgerRoot: ledger, version: opts.version,
3064
- baselineVersion: opts.baselineVersion, forCommit: opts.commit, instanceRoot: root,
3279
+ baselineVersion, forCommit: opts.commit, instanceRoot: root,
3065
3280
  expectedBaselineCommit: opts.baselineCommit }); }
3066
3281
  catch (error) { debug.log('release-loop.gate', 'radius-shadow-unavailable', { reason: String(error) }); }
3067
3282
  if (!Number.isSafeInteger(opts.recheckFileLimit ?? GATE_HOST_RECHECK_FILE_LIMIT) || (opts.recheckFileLimit ?? GATE_HOST_RECHECK_FILE_LIMIT) < 1)
3068
3283
  throw new Error('invalid gate recheck file limit');
3069
3284
  if (opts.pod && (!opts.pod.pool || !Number.isSafeInteger(opts.pod.shards ?? 24) || (opts.pod.shards ?? 24) < 1
3070
3285
  || !Number.isSafeInteger(opts.pod.shardTimeoutSeconds ?? 1200) || (opts.pod.shardTimeoutSeconds ?? 1200) < 1)) throw new Error('invalid pod sweep options');
3286
+ if (opts.pod && opts.graphRunId) {
3287
+ const components = opts.pod.pool.split(',').map((part) => part.trim()).filter(Boolean);
3288
+ const members = parsePodPool(opts.pod.pool);
3289
+ const memberSpecs = members.map((member) => {
3290
+ const spec = components.find((part) => part.split(/[@:#]/, 1)[0] === member.context);
3291
+ if (!spec) throw new Error(`gate image pin cannot map pool member ${member.context}`);
3292
+ return { context: member.context, spec };
3293
+ });
3294
+ const poolImages = new Map<string, readonly string[]>();
3295
+ const currentImages: Record<string, string | null> = {};
3296
+ for (const member of memberSpecs) {
3297
+ const digest = imageProbe(member.spec, opts.commit);
3298
+ currentImages[member.context] = digest;
3299
+ poolImages.set(member.context, digest ? [digest] : []);
3300
+ }
3301
+ const pinRunId = opts.graphRunId;
3302
+ const pinPath = gateImagePinPath(root, opts.version, pinRunId);
3303
+ const gateImageTag = `${podCommandImageFor([], true).split(':')[0]}:${opts.commit.slice(0, 12)}`;
3304
+ let savedPin: ReturnType<typeof readGateImagePin>;
3305
+ try { savedPin = existsSync(pinPath) ? readGateImagePin(pinPath, pinRunId, gateImageTag, opts.commit) : null; }
3306
+ catch (error) {
3307
+ debug.log('release-loop.gate', 'image-pin', { runId: pinRunId, currentImages, decision: 'fail-closed',
3308
+ reason: 'saved-image-pin-invalid', error: String(error).slice(0, 300) }, { level: 'error' });
3309
+ throw new Error('gate resume refused: saved image digest pin is invalid');
3310
+ }
3311
+ const savedDigest = savedPin?.digest ?? null;
3312
+ let previouslyExecuted = false;
3313
+ if (opts.graphRunId) {
3314
+ // A gate's immutable pin is written before any Pod work. The graph state is only a legacy-run detector:
3315
+ // if it already records an executed gate but this pin does not exist, resume cannot safely invent one.
3316
+ const runPath = join(root, 'graph-runs', 'release-loop', `${opts.graphRunId}.json`);
3317
+ if (existsSync(runPath)) {
3318
+ try {
3319
+ const prior = JSON.parse(readFileSync(runPath, 'utf8')) as { nodes?: Array<{ nodeId?: unknown; executed?: unknown }> };
3320
+ previouslyExecuted = prior.nodes?.some((node) => node.nodeId === 'gate' && node.executed === true) ?? false;
3321
+ } catch { previouslyExecuted = true; }
3322
+ }
3323
+ }
3324
+ if (!savedDigest && previouslyExecuted) {
3325
+ debug.log('release-loop.gate', 'image-pin', { runId: opts.graphRunId, savedDigest: null, currentImages,
3326
+ decision: 'fail-closed', reason: 'resume-pin-missing' }, { level: 'error' });
3327
+ throw new Error('gate resume refused: saved image digest pin is missing');
3328
+ }
3329
+ const resolution = resolveGateImagePin({ ...(savedDigest ? { savedDigest } : {}),
3330
+ ...(!savedDigest ? { initialDigest: [...poolImages.values()].flat().find(Boolean) ?? null } : {}), poolImages });
3331
+ if (!resolution.ok) {
3332
+ debug.log('release-loop.gate', 'image-pin', { runId: opts.graphRunId ?? null, savedDigest, currentImages,
3333
+ decision: 'fail-closed', reason: resolution.reason }, { level: 'error' });
3334
+ throw new Error(`gate image pin refused: ${resolution.reason}`);
3335
+ }
3336
+ const selected = memberSpecs.filter((member) => resolution.eligiblePools.includes(member.context));
3337
+ if (!selected.length) throw new Error('gate image pin refused: no eligible pool');
3338
+ if (!savedDigest) {
3339
+ try { writeGateImagePin(pinPath, pinRunId, resolution.digest, gateImageTag, opts.commit); }
3340
+ catch (error) {
3341
+ debug.log('release-loop.gate', 'image-pin', { runId: pinRunId, pinnedDigest: resolution.digest,
3342
+ currentImages, decision: 'fail-closed', reason: 'pin-write-failed', error: String(error).slice(0, 300) }, { level: 'error' });
3343
+ throw error;
3344
+ }
3345
+ }
3346
+ opts = { ...opts, pod: { ...opts.pod, pool: selected.map((member) => member.spec).join(','),
3347
+ pinnedImageDigest: resolution.digest, pinnedRunId: pinRunId } };
3348
+ debug.log('release-loop.gate', 'image-pin', { runId: opts.graphRunId ?? null, pinnedDigest: resolution.digest,
3349
+ currentImages, eligiblePools: resolution.eligiblePools, excludedPools: members.map((member) => member.context)
3350
+ .filter((context) => !resolution.eligiblePools.includes(context)), decision: savedDigest ? 'resume-pinned' : 'pin-created' });
3351
+ } else if (opts.pod) {
3352
+ debug.log('release-loop.gate', 'image-pin-unscoped', { runId: null, decision: 'not-applicable',
3353
+ reason: 'direct-pod-gate-without-graph-run-id' });
3354
+ }
3071
3355
  if (opts.remote && !/^(?:[\w.-]+@)?[\w.-]+$/.test(opts.remote)) throw new Error('invalid ssh host');
3072
3356
  if (opts.remote) {
3073
3357
  runner.remoteMirror = undefined;
@@ -3078,8 +3362,8 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
3078
3362
  const homePath = home?.output.split(/\r?\n/).find((line) => line.startsWith('/'));
3079
3363
  if (home && (!homePath || !/^\/[^\r\n]*$/.test(homePath))) throw new Error('invalid remote home');
3080
3364
  const mirror = configured.startsWith('~/') ? join(homePath!, configured.slice(2)) : configured;
3081
- const baseSha = opts.baselineCommit ?? baselineReleaseCommit(opts.baselineVersion);
3082
- cachedBaseline(opts.baselineVersion, baseSha);
3365
+ const baseSha = opts.baselineCommit ?? baselineReleaseCommit(baselineVersion);
3366
+ cachedBaseline(baselineVersion, baseSha);
3083
3367
  const present = await runner.command('test', ['-d', mirror], '/tmp');
3084
3368
  if (present.rc === 1) {
3085
3369
  check(await runner.command('mkdir', ['-p', dirname(mirror)], '/tmp'), 'remote mirror parent');
@@ -3108,26 +3392,57 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
3108
3392
  // GATE-PARTIAL: a plan made by `release resume --from gate --partial` for exactly this commit re-runs only its files.
3109
3393
  // Its logs go to cut-partial/ — the full cut/ logs stay whole (the next release reads them as a baseline source).
3110
3394
  const partialRead = readPartialPlanResult(roots, opts.version, opts.commit);
3111
- const explicitPartial = partialRead.plan;
3395
+ const explicitPartial = opts.full ? null : partialRead.plan;
3112
3396
  const rehearsalStampPresent = ['hard-5h', 'hard-mid'].some((step) =>
3113
3397
  existsSync(join(root, 'release', opts.version, 'rehearsal', `${step}.json`)));
3114
- const currentImage = !explicitPartial && opts.graphRunId && opts.pod && rehearsalStampPresent
3115
- ? imageProbe(opts.pod.pool) : null;
3398
+ const currentIdentity = !opts.full && !explicitPartial && opts.graphRunId && opts.pod && rehearsalStampPresent
3399
+ ? identityProbe(opts.pod.pool, process.env, opts.commit) : null;
3400
+ const currentImage = currentIdentity?.imageDigest ?? (!opts.full && !explicitPartial
3401
+ && opts.graphRunId && opts.pod && rehearsalStampPresent ? imageProbe(opts.pod.pool, opts.commit) : null);
3116
3402
  const receiptImage = currentImage ?? (opts.graphRunId && opts.pod && process.env.ELANOUS_GRAPH_CONTEXT
3117
- ? imageProbe(opts.pod.pool) : null);
3118
- const receiptBase = opts.baselineCommit ?? (opts.baselineVersion && roots.some((location) =>
3119
- existsSync(join(location, 'release', opts.baselineVersion!, 'release.json')))
3120
- ? baselineReleaseCommit(opts.baselineVersion) : undefined);
3121
- const radius = !explicitPartial && opts.graphRunId && opts.pod && rehearsalStampPresent
3403
+ ? imageProbe(opts.pod.pool, opts.commit) : null);
3404
+ const receiptBase = opts.baselineCommit ?? (roots.some((location) =>
3405
+ existsSync(join(location, 'release', baselineVersion, 'release.json')))
3406
+ ? baselineReleaseCommit(baselineVersion) : undefined);
3407
+ const radius = !opts.full && !explicitPartial && opts.graphRunId && opts.pod && rehearsalStampPresent
3122
3408
  ? planHardRehearsalRadius({ root, repo, version: opts.version, forCommit: opts.commit,
3123
- graphPins: gateGraphPins(root, opts.graphRunId), envFingerprint: gateEnvFingerprint(process.env), imageDigest: currentImage })
3409
+ graphPins: gateGraphPins(root, opts.graphRunId), envFingerprint: gateEnvFingerprint(process.env), imageDigest: currentImage,
3410
+ executionIdentity: currentIdentity })
3124
3411
  : null;
3125
3412
  if (radius) debug.log('release-loop.gate', 'radius-live-plan', { version: opts.version, reason: radius.reason,
3126
3413
  files: radius.plan?.files.length ?? null, rehearsalRunId: radius.rehearsalRunId ?? null });
3127
- let partial = explicitPartial ?? radius?.plan ?? null;
3414
+ let partial = opts.full ? null : explicitPartial ?? radius?.plan ?? null;
3415
+ let trainReceiptPlan: ReturnType<typeof planGateTrainReceiptCarry> = null;
3416
+ if (!opts.full && !partial && opts.graphRunId && opts.pod) {
3417
+ const receiptPath = gateReceiptRegistryPath(repo);
3418
+ const bun = spawnSync('bun', ['--version'], { cwd: repo, encoding: 'utf8', maxBuffer: SPAWN_SYNC_MAX_BUFFER });
3419
+ if (receiptPath && bun.status === 0 && !bun.error) {
3420
+ try {
3421
+ trainReceiptPlan = planGateTrainReceiptCarry({ repo, commit: opts.commit, receiptPath,
3422
+ bunVersion: bun.stdout.trim(), lockfileHash: gateLockfileHash(repo), envFingerprint: gateReceiptEnvFingerprint(process.env) });
3423
+ if (trainReceiptPlan?.carriedFiles.length) partial = { version: opts.version, priorCommit: trainReceiptPlan.priorCommit,
3424
+ forCommit: opts.commit, files: trainReceiptPlan.files, priorFailures: [], priorErrors: [] };
3425
+ } catch (error) {
3426
+ debug.log('release-loop.gate', 'train-receipt-plan-failed', { version: opts.version, reason: String(error).slice(0, 240) });
3427
+ }
3428
+ }
3429
+ if (trainReceiptPlan) {
3430
+ result.trainReceiptReuse = { carried: trainReceiptPlan.carriedFiles.length, total: trainReceiptPlan.totalFiles,
3431
+ receipts: trainReceiptPlan.receipts.length, reasons: trainReceiptPlan.reasons };
3432
+ debug.log('release-loop.gate', 'train-receipt-plan', { version: opts.version, commit: opts.commit,
3433
+ carried: trainReceiptPlan.carriedFiles.length, total: trainReceiptPlan.totalFiles,
3434
+ ratio: trainReceiptPlan.carriedFiles.length / trainReceiptPlan.totalFiles,
3435
+ receipts: trainReceiptPlan.receipts, selectedFiles: trainReceiptPlan.files.length,
3436
+ reasons: trainReceiptPlan.reasons, action: trainReceiptPlan.carriedFiles.length ? 'run-remaining-tests' : 'full-sweep' });
3437
+ } else if (!opts.full && !explicitPartial && !radius?.plan) {
3438
+ debug.log('release-loop.gate', 'train-receipt-plan', { version: opts.version, commit: opts.commit,
3439
+ carried: 0, total: null, ratio: 0, receipts: [], selectedFiles: null,
3440
+ reasons: ['no safe receipt coverage'], action: 'full-sweep' });
3441
+ }
3442
+ }
3128
3443
  const explicitPodPlan = !!explicitPartial && explicitPartial.kind !== 'recheck' && !!opts.pod;
3129
3444
  const explicitPodDigest = explicitPodPlan ? explicitPartial!.shardImageDigests?.[0] : undefined;
3130
- const probedPodDigest = explicitPodDigest ? imageProbe(opts.pod!.pool) : null;
3445
+ const probedPodDigest = explicitPodDigest ? imageProbe(opts.pod!.pool, opts.commit) : null;
3131
3446
  if (explicitPodPlan && (!explicitPodDigest || probedPodDigest !== explicitPodDigest)) {
3132
3447
  debug.log('release-loop.gate', 'partial-image-preflight-fallback', { version: opts.version,
3133
3448
  reason: !explicitPodDigest || !probedPodDigest ? 'executed-pod-image-digest-missing' : 'executed-pod-image-digest-mismatch' }, { level: 'warn' });
@@ -3163,15 +3478,33 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
3163
3478
  let knownFailures: string[] = [];
3164
3479
  if (opts.pod) {
3165
3480
  try {
3166
- const knownSha = opts.baselineCommit ?? baselineReleaseCommit(opts.baselineVersion);
3167
- const saved = cachedBaseline(opts.baselineVersion, knownSha)?.saved;
3168
- const recorded = saved ? undefined : baselineFromCutLogs(join(ledger, 'release', opts.baselineVersion, 'gate-logs', 'cut'), knownSha);
3481
+ const knownSha = opts.baselineCommit ?? baselineReleaseCommit(baselineVersion);
3482
+ const saved = cachedBaseline(baselineVersion, knownSha)?.saved;
3483
+ const recorded = saved ? undefined : baselineFromCutLogs(join(ledger, 'release', baselineVersion, 'gate-logs', 'cut'), knownSha);
3169
3484
  const trustedKnown = saved ?? (recorded?.complete ? recorded : undefined);
3170
3485
  if (trustedKnown) knownFailures = [...trustedKnown.failures, ...(trustedKnown.errors ?? []), ...loadEnvKnownFailures(repo)];
3171
3486
  } catch (error) { debug.log('release-loop.gate', 'known-failures-unavailable', { error: String(error) }); }
3172
3487
  }
3173
- const durationSource = join(ledger, 'release', opts.baselineVersion, 'gate-logs', 'cut');
3174
- const runCut = (selected: typeof partial) => selected?.kind === 'carry' ? undefined : runner.sweep(cutTree,
3488
+ const durationSource = join(ledger, 'release', baselineVersion, 'gate-logs', 'cut');
3489
+ const runCut = async (selected: typeof partial) => {
3490
+ if (selected?.kind === 'carry') return undefined;
3491
+ const progressFile = opts.graphRunId && /^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(opts.graphRunId)
3492
+ ? join(ledger, 'graph-runs', 'release-loop', `${opts.graphRunId}.json.contexts`, 'gate-progress.jsonl') : undefined;
3493
+ let latest: GateSweepProgress | undefined;
3494
+ const emitProgress = () => {
3495
+ if (!latest) return;
3496
+ const nowMs = runner.now?.() ?? Date.now();
3497
+ const event = { version: opts.version, runId: opts.graphRunId ?? null, at: new Date(nowMs).toISOString(),
3498
+ ...summarizeGateProgress({ executor: latest.executor, groups: latest.groups, nowMs }) };
3499
+ debug.log('release-loop.gate', 'progress', event);
3500
+ if (progressFile) {
3501
+ try { mkdirSync(dirname(progressFile), { recursive: true }); appendFileSync(progressFile, `${JSON.stringify(event)}\n`, { mode: 0o600 }); }
3502
+ catch (error) { debug.log('release-loop.gate', 'progress-log-write-failed', { path: progressFile, error: String(error) }, { level: 'warn' }); }
3503
+ }
3504
+ };
3505
+ const cancel = (runner.progressScheduler ?? scheduleGateProgress)(emitProgress, 60_000);
3506
+ try {
3507
+ const sweepResult = await runner.sweep(cutTree,
3175
3508
  join(root, 'release', opts.version, 'gate-logs', selected ? 'cut-partial' : 'cut'),
3176
3509
  opts.pod ? { ...opts.pod, durationSource, ...(knownFailures.length ? { knownFailures } : {}), onSelectionTimed: ({ ms }) => { result.selectionMs = ms; },
3177
3510
  // GATE-RISK-LANE — the watch list is read from this gate's own ledgers (the instance root ⊕ the machine ledger);
@@ -3184,7 +3517,11 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
3184
3517
  receiptRoot: join(ledger, 'release', opts.version, 'gate-logs', 'cut', 'receipts'),
3185
3518
  base40: receiptBase,
3186
3519
  expectedImageDigest: receiptImage } } : undefined,
3187
- selected?.files);
3520
+ selected?.files, (progress) => { latest = progress; emitProgress(); });
3521
+ emitProgress();
3522
+ return sweepResult;
3523
+ } finally { cancel?.(); }
3524
+ };
3188
3525
  let cutRun = await runCut(partial);
3189
3526
  const radiusSelectionMatched = partial?.files && cutRun?.podSweep?.files
3190
3527
  && JSON.stringify([...partial.files].sort()) === JSON.stringify([...cutRun.podSweep.files].sort());
@@ -3248,7 +3585,45 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
3248
3585
  debug.log('release-loop.gate', 'pod-shard-stalled-env', { files: stalled.map(({ file, reason }) => ({ file, reason })), local });
3249
3586
  return measured;
3250
3587
  };
3251
- const measuredCut = cutRun ? await localStalled(cutRun, cutTree, opts.commit, 'cut') : { failures: [], errors: [] };
3588
+ let measuredCut: SweepFailures;
3589
+ try {
3590
+ measuredCut = cutRun ? await localStalled(cutRun, cutTree, opts.commit, 'cut') : { failures: [], errors: [] };
3591
+ } catch (error) {
3592
+ if (!(error instanceof GateFailureAttributionMismatch) || error.label !== 'cut sweep') throw error;
3593
+ const targetFiles = [...new Set(error.failures.map(fileOf))].sort();
3594
+ const attribution = { summary: error.summary, observed: error.observed, identified: error.identified,
3595
+ collapsed: error.collapsed, targetFiles,
3596
+ baselineResult: 'failed' as 'completed' | 'failed' | 'timeout' };
3597
+ result.attributionRecheck = attribution;
3598
+ debug.log('release-loop.gate', 'failure-attribution-baseline-recheck', { ...attribution, phase: 'started' });
3599
+ try {
3600
+ if (!targetFiles.length || targetFiles.length > GATE_ATTRIBUTION_RECHECK_FILE_LIMIT)
3601
+ throw new Error(`baseline recheck target count ${targetFiles.length} outside safe bound 1..${GATE_ATTRIBUTION_RECHECK_FILE_LIMIT}`);
3602
+ if (!work) throw new Error('gate workspace missing before baseline attribution recheck');
3603
+ const recheckTree = join(work, 'baseline-attribution-recheck');
3604
+ trees.push(recheckTree);
3605
+ const baseShaForRecheck = opts.baselineCommit ?? baselineReleaseCommit(baselineVersion);
3606
+ await runner.add(recheckTree, baseShaForRecheck);
3607
+ for (const dir of [recheckTree, join(recheckTree, 'apps/pwa')])
3608
+ check(await runner.command('bun', ['install'], dir), `bun install ${dir}`);
3609
+ const rechecked = await runner.sweep(recheckTree,
3610
+ join(root, 'release', opts.version, 'gate-logs', 'baseline-attribution-recheck'),
3611
+ opts.pod ? { ...opts.pod, durationSource } : undefined,
3612
+ targetFiles);
3613
+ if (rechecked.timedOut || rechecked.abortedShards || rechecked.stalledEnv?.length) {
3614
+ attribution.baselineResult = 'timeout';
3615
+ throw new Error('baseline sweep timed out or stalled');
3616
+ }
3617
+ failuresOf(rechecked, 'baseline attribution recheck');
3618
+ attribution.baselineResult = 'completed';
3619
+ } catch (recheckError) {
3620
+ debug.log('release-loop.gate', 'failure-attribution-baseline-recheck', { ...attribution, phase: 'failed', error: String(recheckError) });
3621
+ throw new Error(`baseline recheck ${attribution.baselineResult}; cut attribution unresolved (summary=${attribution.summary}, identified=${attribution.identified})`);
3622
+ }
3623
+ debug.log('release-loop.gate', 'failure-attribution-baseline-recheck', { ...attribution, phase: 'completed',
3624
+ disposition: 'cut names remain unresolved; baseline success cannot identify unnamed cut failures; fail closed' });
3625
+ throw new Error(`baseline recheck completed; cut attribution still unresolved (summary=${attribution.summary}, identified=${attribution.identified})`);
3626
+ }
3252
3627
  // A between-tests error proves only that the shared process failed. Re-run each affected file by itself using the
3253
3628
  // same isolated path as ordinary new-failure rechecks, even when the error was present in the baseline ledger.
3254
3629
  // Replace that file's shard observations with its isolated observations before the existing regression comparison.
@@ -3289,15 +3664,30 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
3289
3664
  }
3290
3665
  }
3291
3666
  const cut = partial ? mergePartialFailures(partial, measuredCut) : measuredCut;
3667
+ const samePodFlaky = [...new Set(cutRun?.loadFlaky ?? [])].sort();
3668
+ if (samePodFlaky.length) {
3669
+ const flaky = new Set(samePodFlaky);
3670
+ cut.failures = cut.failures.filter((id) => !flaky.has(id));
3671
+ cut.errors = cut.errors.filter((id) => !flaky.has(id));
3672
+ result.loadFlaky = [...new Set([...(result.loadFlaky ?? []), ...samePodFlaky])].sort();
3673
+ debug.log('release-loop.gate', 'same-pod-load-flaky', { ids: samePodFlaky, files: [...new Set(samePodFlaky.map(fileOf))].sort() });
3674
+ try {
3675
+ const path = loadFlakyCensusPath(ledger);
3676
+ mkdirSync(dirname(path), { recursive: true });
3677
+ const at = new Date().toISOString();
3678
+ appendFileSync(path, samePodFlaky.map((id) => JSON.stringify({ at, version: opts.version, commit: opts.commit, id,
3679
+ host: opts.pod ? `pod:${opts.pod.pool}` : opts.remote ?? 'local', retry: 'same-pod-standalone' }) + '\n').join(''), { mode: 0o600 });
3680
+ } catch (error) { debug.log('release-loop.gate', 'load-flaky-census-write-failed', { ids: samePodFlaky, error: String(error) }); }
3681
+ }
3292
3682
  if (partial) result.partial = { priorCommit: partial.priorCommit, files: partial.files.length };
3293
3683
  cutFailures = cut;
3294
- const baseSha = opts.baselineCommit ?? baselineReleaseCommit(opts.baselineVersion);
3684
+ const baseSha = opts.baselineCommit ?? baselineReleaseCommit(baselineVersion);
3295
3685
  baselineCommitForVerdict = baseSha;
3296
- const cached = cachedBaseline(opts.baselineVersion, baseSha);
3297
- const recorded = cached ? undefined : baselineFromCutLogs(join(ledger, 'release', opts.baselineVersion, 'gate-logs', 'cut'), baseSha);
3686
+ const cached = cachedBaseline(baselineVersion, baseSha);
3687
+ const recorded = cached ? undefined : baselineFromCutLogs(join(ledger, 'release', baselineVersion, 'gate-logs', 'cut'), baseSha);
3298
3688
  const trusted = cached?.saved ?? (recorded?.complete ? recorded : undefined);
3299
3689
  result.baselineSource = cached ? cached.source : recorded?.complete ? 'cut-logs' : 'swept';
3300
- debug.log('release-loop.gate', 'baseline', { version: opts.baselineVersion, source: result.baselineSource, commit: baseSha });
3690
+ debug.log('release-loop.gate', 'baseline', { version: baselineVersion, source: result.baselineSource, commit: baseSha });
3301
3691
  let baseTree: string | undefined;
3302
3692
  const getBaseTree = async () => {
3303
3693
  if (!baseTree) {
@@ -3328,7 +3718,7 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
3328
3718
  present = cutFiles.filter((file) => known.has(file));
3329
3719
  }
3330
3720
  result.baselineDeferred = { cutFiles: cutFiles.length, swept: present.length };
3331
- debug.log('release-loop.gate', 'baseline-deferred', { version: opts.baselineVersion, commit: baseSha, cutFiles: cutFiles.length, swept: present.length });
3721
+ debug.log('release-loop.gate', 'baseline-deferred', { version: baselineVersion, commit: baseSha, cutFiles: cutFiles.length, swept: present.length });
3332
3722
  if (present.length) {
3333
3723
  // K9b — the baseline is swept on the same Pod pool as the cut, not on this host (0.2.6: an hour of local baseline
3334
3724
  // after a 23-minute Pod cut).
@@ -3432,7 +3822,8 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
3432
3822
  // per-test limit, on the same runner (node-b when the gate is remote). Passing there = flaky under load (warning);
3433
3823
  // an assertion failure, a failure again, or a run that cannot be read stays introduced.
3434
3824
  const timedOut = cutCheck === undefined ? new Set<string>() : new Set(parseTimedOutFailures(stripPodRepoRoot(cutCheck.output)));
3435
- const shaped = candidates.filter((id) => timedOut.has(id) && isolatedCut.failures.includes(id));
3825
+ const samePodRetried = new Set(cutRun?.loadFlakyAttemptedFiles ?? []);
3826
+ const shaped = candidates.filter((id) => timedOut.has(id) && isolatedCut.failures.includes(id) && !samePodRetried.has(fileOf(id)));
3436
3827
  if (shaped.length) {
3437
3828
  const relaxed = opts.pod && runner.recheckPod
3438
3829
  ? await runner.recheckPod(file, opts.commit, { ...opts.pod, warmCutSha: opts.commit, warmBaselineSha: baseSha,
@@ -3651,11 +4042,13 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
3651
4042
  ...(result.interferenceErrors?.length ? { interferenceErrors: [...result.interferenceErrors].sort() } : {}),
3652
4043
  };
3653
4044
  const observed = { ...(result.selectionMs !== undefined ? { selectionMs: result.selectionMs } : {}),
3654
- ...(result.warmShadow?.length ? { warmShadow: result.warmShadow } : {}) };
4045
+ ...(result.warmShadow?.length ? { warmShadow: result.warmShadow } : {}),
4046
+ ...(result.attributionRecheck ? { attributionRecheck: result.attributionRecheck } : {}) };
3655
4047
  // Receipt is advisory for a later radius gate. Missing identity makes reuse invalid, never this gate's verdict.
3656
4048
  const baselineReceipt = gateBaselineReceipt({ root, version: opts.version, commit: opts.commit,
3657
4049
  graphPins: gateGraphPins(root, opts.graphRunId) ?? (opts.rehearsalBaseline ? gateSourcePins(repo, opts.commit) : null),
3658
4050
  envFingerprint: gateEnvFingerprint(process.env),
4051
+ ...(result.attributionRecheck ? { attributionRecheck: result.attributionRecheck } : {}),
3659
4052
  fullSweep: result.outcome === 'ok' && cutSweepUsable && !result.partial && !!cutFailures
3660
4053
  && !result.rcNonzeroNoFailures.length && !result.stalledShards?.length && !result.stalledEnv.length
3661
4054
  && !result.unmeasuredRecheckFiles?.length && !result.unmeasuredErrors?.length });
@@ -3736,7 +4129,7 @@ export function graphGateResult(result: GateResult, graph: boolean) {
3736
4129
  summary: (result.partial ? `부분 재검 ${result.partial.files}파일(앞 게이트 ${result.partial.priorCommit.slice(0, 9)} 결과 이음) · ` : '')
3737
4130
  + (result.outcome === 'ok' ? `새 회귀 ${result.introduced.length} · 기존 ${result.preexisting} · 고침 ${result.fixed ?? '못 셈(기준선 미루기)'}` : `게이트 ${result.outcome}: ${result.error ?? result.introduced.length + ' new regressions'}`) + ` · 환경 알려진 실패 ${result.knownEnv}`
3738
4131
  + (result.rcNonzeroNoFailures.length ? ` · ⚠ 종료코드만 비영 ${result.rcNonzeroNoFailures.length}` : '')
3739
- + (result.loadFlaky?.length ? ` · ⚠ 부하 흔들림 ${result.loadFlaky.length}(시간 초과 꼴 · ${GATE_TIMEOUT_RECHECK_MS / 1000}s 재실행 통과 · 막지 않음)` : '')
4132
+ + (result.loadFlaky?.length ? ` · ⚠ 부하 흔들림 ${result.loadFlaky.length}(독립 재실행 통과 · 막지 않음)` : '')
3740
4133
  + (result.stalledEnv.length ? ` · 환경 멈춤 ${result.stalledEnv.length} (Pod 밖 통과 ${result.stalledEnv.filter((item) => item.local === 'passed').length})` : '')
3741
4134
  + (result.abortedShards ? ` · 중단 조각 ${result.abortedShards}(중간 매달림 · 시험만 끊고 재확인)` : '') };
3742
4135
  }
@@ -3827,6 +4220,7 @@ export function parseOptions(args: string[], env: NodeJS.ProcessEnv): GateOption
3827
4220
  const arg = args[i];
3828
4221
  if (arg === '--help') return 'help';
3829
4222
  if (arg === '--json') continue;
4223
+ if (arg === '--full') { opts.full = true; continue; }
3830
4224
  if (arg === '--rehearsal-baseline') { opts.rehearsalBaseline = true; continue; }
3831
4225
  // Applied after the loop and only to a Pod gate — on its own it must not turn a local gate into a Pod one (review r5).
3832
4226
  if (arg === '--fresh-shards') { freshShards = true; continue; }
@@ -3889,10 +4283,13 @@ export function parseOptions(args: string[], env: NodeJS.ProcessEnv): GateOption
3889
4283
  /** The node's run: judge, emit the graph result line, and return the process exit code (ok 0 · regression 1 · else 2).
3890
4284
  * `runner` is a seam for tests; the process entry passes none. */
3891
4285
  export async function runGateNode(argv: string[], env: NodeJS.ProcessEnv, runner?: GateRunner,
3892
- judge: (opts: GateOptions, runner?: GateRunner) => Promise<GateResult> = judgeGate): Promise<number> {
4286
+ judge: (opts: GateOptions, runner?: GateRunner, imageProbe?: typeof preflightGateImageDigest) => Promise<GateResult> = judgeGate,
4287
+ imageProbe: typeof preflightGateImageDigest = preflightGateImageDigest): Promise<number> {
3893
4288
  const start = Date.now();
3894
4289
  let result: GateResult;
3895
4290
  let version = '';
4291
+ let graphRunId: string | null = null;
4292
+ let autohealStoreRoot = '';
3896
4293
  let placement: { executor: string | null; host: string | null } | undefined;
3897
4294
  try {
3898
4295
  const parsed = parseOptions(argv, env);
@@ -3907,12 +4304,14 @@ export async function runGateNode(argv: string[], env: NodeJS.ProcessEnv, runner
3907
4304
  debug.log('release-loop.gate', 'executor', { version: opts.version, ...placement, requested: opts.executor ?? null });
3908
4305
  }
3909
4306
  if (opts === 'help') {
3910
- console.log('Usage: bun scripts/release-loop/gate-node.ts --commit <sha> --version <v> [--baseline-version <prev>] [--baseline-commit <sha>] [--remote <ssh-host>] [--remote-mirror <path>] [--pod-pool <pool>] [--pod-shards <n>] [--pod-shard-timeout-seconds <n>] [--recheck-file-limit <n>] [--fresh-shards] [--reroute-shard <pod-N|auto>] [--shard-host <pool-context>] [--executor <pod|host>] [--rehearsal-baseline] [--json]\nWithout flags, input.commit, input.version and input.previousVersion come from the JSON file at ELANOUS_GRAPH_CONTEXT.\n--executor host (input.gateExecutor) ignores every pod option and sweeps on --remote (input.gateRemote, required).\n--rehearsal-baseline pins source graph bytes only for a direct Pod gate on an isolated rc version.');
4307
+ console.log('Usage: bun scripts/release-loop/gate-node.ts --commit <sha> --version <v> [--baseline-version <prev>] [--baseline-commit <sha>] [--remote <ssh-host>] [--remote-mirror <path>] [--pod-pool <pool>] [--pod-shards <n>] [--pod-shard-timeout-seconds <n>] [--recheck-file-limit <n>] [--fresh-shards] [--reroute-shard <pod-N|auto>] [--shard-host <pool-context>] [--executor <pod|host>] [--rehearsal-baseline] [--full] [--json]\nWithout flags, input.commit, input.version and input.previousVersion come from the JSON file at ELANOUS_GRAPH_CONTEXT.\n--executor host (input.gateExecutor) ignores every pod option and sweeps on --remote (input.gateRemote, required).\n--rehearsal-baseline pins source graph bytes only for a direct Pod gate on an isolated rc version.\n--full disables receipt and partial reuse and runs the complete test inventory.');
3911
4308
  return 0;
3912
4309
  }
3913
4310
  version = opts.version;
4311
+ autohealStoreRoot = opts.ledgerRoot ?? releaseLedgerRoot();
3914
4312
  // The graph's existing gate-node execution path delegates verdict classification and persistence here.
3915
4313
  const context = usesGraphContext(argv, env) ? readGraphContext(env) : undefined;
4314
+ graphRunId = context?.runId ?? null;
3916
4315
  // Let the gate's existing SSH host validation report an invalid host before any reservation is published.
3917
4316
  const hosts = opts.remote && !/^(?:[\w.-]+@)?[\w.-]+$/.test(opts.remote) ? [] : reservedGateHosts(opts);
3918
4317
  const reservationRoot = context ? gateHostReservationRoot() : undefined;
@@ -3920,12 +4319,40 @@ export async function runGateNode(argv: string[], env: NodeJS.ProcessEnv, runner
3920
4319
  throw new Error('release gate host reservation requires graph runId and pinned state root');
3921
4320
  const releaseHosts = context && hosts.length
3922
4321
  ? reserveReleaseGateHosts(reservationRoot!, context.runId!, hosts) : () => undefined;
3923
- try { result = await judge(context?.runId ? { ...opts, graphRunId: context.runId } : opts, runner); }
4322
+ try { result = await judge(context?.runId ? { ...opts, graphRunId: context.runId } : opts, runner, imageProbe); }
3924
4323
  finally { releaseHosts(); }
3925
4324
  } catch (error) {
3926
4325
  result = { outcome: 'error', commit: '', introduced: [], preexisting: 0, fixed: 0, knownEnv: 0, knownEnvCleared: [], stalledEnv: [], rcNonzeroNoFailures: [], durationMs: Date.now() - start, error: String(error) };
3927
4326
  placement ??= requestedGateExecutor(argv, env);
3928
4327
  }
4328
+ if (versionPattern.test(version) && result.stalledShards?.length) {
4329
+ result.autoheals = result.stalledShards.map((shard, index) => {
4330
+ const shardKey = `pod-${shard.shard}`;
4331
+ const signature = createHash('sha256').update(JSON.stringify({ shard: shard.shard,
4332
+ files: [...shard.files].sort(), reason: shard.reason })).digest('hex');
4333
+ const cause = gateAutohealCause({ outcome: result.outcome, introduced: result.introduced, durationMs: result.durationMs,
4334
+ error: result.error, stalledShards: [shard], interferenceErrors: result.interferenceErrors, loadFlaky: result.loadFlaky }) ?? 'environment';
4335
+ const decision = decideAndRecordGateAutoheal({ cause, signature, version, runId: graphRunId ?? 'direct', shardKey,
4336
+ shardKeys: [shardKey], shardElapsedMs: result.durationMs,
4337
+ overallElapsedMs: index === 0 ? result.durationMs : 0,
4338
+ storeRoot: autohealStoreRoot || releaseLedgerRoot() });
4339
+ debug.log('release-loop.gate-autoheal', 'decision', { runId: graphRunId, version, ...decision });
4340
+ return decision;
4341
+ });
4342
+ result.autoheal = result.autoheals.find((decision) => decision.action === 'hitl') ?? result.autoheals[0];
4343
+ } else if (versionPattern.test(version)) {
4344
+ // Some failures (for example an unreachable gate host or a measured regression) have no
4345
+ // stalled shard. Keep them in the same durable policy ledger instead of silently skipping autoheal.
4346
+ const cause = gateAutohealCause(result);
4347
+ if (cause) {
4348
+ const signature = createHash('sha256').update(JSON.stringify({ cause,
4349
+ error: result.error ?? null, introduced: [...(result.introduced ?? [])].sort() })).digest('hex');
4350
+ result.autoheal = decideAndRecordGateAutoheal({ cause, signature, version, runId: graphRunId ?? 'direct',
4351
+ shardKey: 'gate', shardKeys: ['gate'], shardElapsedMs: result.durationMs, overallElapsedMs: result.durationMs,
4352
+ storeRoot: autohealStoreRoot || releaseLedgerRoot() });
4353
+ debug.log('release-loop.gate-autoheal', 'decision', { runId: graphRunId, version, ...result.autoheal });
4354
+ }
4355
+ }
3929
4356
  if (result.error) console.error(result.error);
3930
4357
  debug.log('release-loop.gate', 'result', { version, outcome: result.outcome, ...(placement ?? {}) });
3931
4358
  emitNodeResult({ ...graphGateResult(result, !!env.ELANOUS_GRAPH_CONTEXT), ...(placement ?? {}) });