elanous 0.2.32 → 0.2.34

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 (633) hide show
  1. package/apps/pwa/out/404/index.html +1 -1
  2. package/apps/pwa/out/404.html +1 -1
  3. package/apps/pwa/out/_next/static/{IVKr3AlRUVLKsw2fh7eXF → L04y73dlwKeTW-GetaTF3}/_buildManifest.js +1 -1
  4. package/apps/pwa/out/_next/static/chunks/1162.c69abda138c7ce43.js +79 -0
  5. package/apps/pwa/out/_next/static/chunks/1449-1fede62af84c3e43.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/1598-13fc845935e816df.js +1 -0
  7. package/apps/pwa/out/_next/static/chunks/1639.62c5e16fa20d242f.js +94 -0
  8. package/apps/pwa/out/_next/static/chunks/{4174.92ed04922709ac9e.js → 1644.35dd25240ae24e4b.js} +2 -2
  9. package/apps/pwa/out/_next/static/chunks/1661-adb8b2babc9aaf8c.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/{2940-619bfd8ef337c611.js → 1895-60ed0e341c658bd4.js} +1 -1
  11. package/apps/pwa/out/_next/static/chunks/{5525.86debf231e181813.js → 2124.ed5bf480cea7f85b.js} +1 -1
  12. package/apps/pwa/out/_next/static/chunks/2147.107c0fc6146fb15a.js +23 -0
  13. package/apps/pwa/out/_next/static/chunks/2202-ef1c9b000c3074fc.js +1 -0
  14. package/apps/pwa/out/_next/static/chunks/2247.1e326638ee43e672.js +115 -0
  15. package/apps/pwa/out/_next/static/chunks/2268.56e1e3d58180abae.js +8 -0
  16. package/apps/pwa/out/_next/static/chunks/2469-606819c812414021.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/{6092.a7f99e68b188ca66.js → 2547.dd27303624cb4da8.js} +1 -1
  18. package/apps/pwa/out/_next/static/chunks/{2213.2ccbada92c33ea0f.js → 2554.3b5e200f5f5cfcd6.js} +16 -16
  19. package/apps/pwa/out/_next/static/chunks/2593.e53c1620c0bb3599.js +1 -0
  20. package/apps/pwa/out/_next/static/chunks/2690.e96671114222676a.js +1 -0
  21. package/apps/pwa/out/_next/static/chunks/2898-79ee1a5cc0cd9d13.js +1 -0
  22. package/apps/pwa/out/_next/static/chunks/298.26099a9f65e5c8ff.js +1 -0
  23. package/apps/pwa/out/_next/static/chunks/{5173-233188df2f42c626.js → 2987-30cf9c2eb7b8bdd5.js} +1 -1
  24. package/apps/pwa/out/_next/static/chunks/2992.51fc2a7295bd8139.js +1 -0
  25. package/apps/pwa/out/_next/static/chunks/{3541-ce3a5424ed5ad7f1.js → 3001-5a52788c813b6113.js} +1 -1
  26. package/apps/pwa/out/_next/static/chunks/3014-291245fba9df7b5b.js +1 -0
  27. package/apps/pwa/out/_next/static/chunks/3068.ee78201f0251a18d.js +1 -0
  28. package/apps/pwa/out/_next/static/chunks/3083.cd399a17e9365f78.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/3103.84470c8445962f04.js +34 -0
  30. package/apps/pwa/out/_next/static/chunks/{6433.fc573af22bbc9ab7.js → 3110.0a7065f25c841818.js} +17 -17
  31. package/apps/pwa/out/_next/static/chunks/3117-e1c474683fd1f824.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/3121.779fafe3e44612c5.js +1 -0
  33. package/apps/pwa/out/_next/static/chunks/3251-2c17bd5a91f8de2e.js +1 -0
  34. package/apps/pwa/out/_next/static/chunks/326.3fb32322bb2ddf6d.js +1 -0
  35. package/apps/pwa/out/_next/static/chunks/3411-f87314c6c66ebc73.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/{6157.0c9af021de4fb31b.js → 3414.172ad4ca2d9b08e8.js} +2 -2
  37. package/apps/pwa/out/_next/static/chunks/3421-7ff7fbaa156985a7.js +1 -0
  38. package/apps/pwa/out/_next/static/chunks/{4745.67d6f99c245af3f7.js → 3498.cf2a15a8a9e5b926.js} +3 -3
  39. package/apps/pwa/out/_next/static/chunks/381.f55dc63ea87735e6.js +1 -0
  40. package/apps/pwa/out/_next/static/chunks/3819.163d4777f0a597f6.js +32 -0
  41. package/apps/pwa/out/_next/static/chunks/{5543.a438b8ab5a13ea47.js → 3837.722e053fb9f321ad.js} +1 -1
  42. package/apps/pwa/out/_next/static/chunks/4129-ab0a6189f1778633.js +1 -0
  43. package/apps/pwa/out/_next/static/chunks/4361.93d14ba594b3a035.js +104 -0
  44. package/apps/pwa/out/_next/static/chunks/4364.7a7eb19cceb67d2f.js +286 -0
  45. package/apps/pwa/out/_next/static/chunks/4406.d71f506f7c3ee9d3.js +149 -0
  46. package/apps/pwa/out/_next/static/chunks/4461.2ad1514180ae7e29.js +148 -0
  47. package/apps/pwa/out/_next/static/chunks/4479.bd309cbe0e3842ee.js +64 -0
  48. package/apps/pwa/out/_next/static/chunks/4522-05966f777d66d13b.js +1 -0
  49. package/apps/pwa/out/_next/static/chunks/4543-9a3e9812e845de65.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/4557.f8f1d84a0d90d590.js +74 -0
  51. package/apps/pwa/out/_next/static/chunks/4589-79b11542179ef58c.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/4633-9f8f9f88ffbb8f5c.js +1 -0
  53. package/apps/pwa/out/_next/static/chunks/4831.23af4d10c0697b5e.js +1 -0
  54. package/apps/pwa/out/_next/static/chunks/4983.5951b85724f61f2a.js +1 -0
  55. package/apps/pwa/out/_next/static/chunks/{b313b242.cb1265eea908db61.js → 4fa33ebd.21c1bd5b294496e6.js} +1 -1
  56. package/apps/pwa/out/_next/static/chunks/{997ce8a5-2ad501927003b41b.js → 5014f970-22bef0b5210f7d60.js} +1 -1
  57. package/apps/pwa/out/_next/static/chunks/{2112-e4efc21a6505d5bc.js → 5028-f0722e6114c313e7.js} +1 -1
  58. package/apps/pwa/out/_next/static/chunks/{2898.fd781bc7cf01a7b7.js → 5249.96a848656b582a93.js} +1 -1
  59. package/apps/pwa/out/_next/static/chunks/5311.16e0ffbfa5468b80.js +62 -0
  60. package/apps/pwa/out/_next/static/chunks/{9407.e49124dedf0f5e72.js → 5446.edf141cff9ef647d.js} +1 -1
  61. package/apps/pwa/out/_next/static/chunks/{9002.e5658224e3dc1b6f.js → 5489.ec2dd1b5272a19dc.js} +1 -1
  62. package/apps/pwa/out/_next/static/chunks/{1335-6731f392c35216eb.js → 5576-82d3703636d5c4b9.js} +3 -3
  63. package/apps/pwa/out/_next/static/chunks/559.df87695df637920c.js +1 -0
  64. package/apps/pwa/out/_next/static/chunks/5618-5c88f2f70178c4ad.js +1 -0
  65. package/apps/pwa/out/_next/static/chunks/5764.05487bf0b73ddb6b.js +1 -0
  66. package/apps/pwa/out/_next/static/chunks/5930.e678826c3f831aab.js +1 -0
  67. package/apps/pwa/out/_next/static/chunks/5dfb57f7.f2e3305326202474.js +1 -0
  68. package/apps/pwa/out/_next/static/chunks/6053-d893974de92e6198.js +1 -0
  69. package/apps/pwa/out/_next/static/chunks/{2985.39ccdecd36848b12.js → 6086.913a69e6fa3e4a8b.js} +4 -4
  70. package/apps/pwa/out/_next/static/chunks/6200-cefd6f33f59d765e.js +1 -0
  71. package/apps/pwa/out/_next/static/chunks/6314-edeba650ae686ac9.js +1 -0
  72. package/apps/pwa/out/_next/static/chunks/6316-bc9a9b74d7842d79.js +1 -0
  73. package/apps/pwa/out/_next/static/chunks/6476-13b9779b73528008.js +1 -0
  74. package/apps/pwa/out/_next/static/chunks/666-04db3ac4c8e3c66f.js +1 -0
  75. package/apps/pwa/out/_next/static/chunks/670.682e55106ef915a6.js +77 -0
  76. package/apps/pwa/out/_next/static/chunks/{7862.30a8d6482bb238eb.js → 6740.43de73fc427c9fe2.js} +1 -1
  77. package/apps/pwa/out/_next/static/chunks/6816.b7ba9d0a1a4a46a7.js +1 -0
  78. package/apps/pwa/out/_next/static/chunks/690-fbbcc777f189af37.js +1 -0
  79. package/apps/pwa/out/_next/static/chunks/6989.0d3d4020e5cb3df6.js +90 -0
  80. package/apps/pwa/out/_next/static/chunks/{2871.0bca6137343b3f76.js → 7005.1623d2aea78eb85f.js} +1 -1
  81. package/apps/pwa/out/_next/static/chunks/7193-cf355cca5ba6c5d5.js +1 -0
  82. package/apps/pwa/out/_next/static/chunks/7217-d17393fe7af78fa6.js +1 -0
  83. package/apps/pwa/out/_next/static/chunks/7232.24edbd938a69c8d0.js +1 -0
  84. package/apps/pwa/out/_next/static/chunks/7295cd1e.b4b557908888eeee.js +1 -0
  85. package/apps/pwa/out/_next/static/chunks/7323.adb02de21991883c.js +1 -0
  86. package/apps/pwa/out/_next/static/chunks/7386.5d8e7e42e818dfb6.js +287 -0
  87. package/apps/pwa/out/_next/static/chunks/{7502.ccd5b8065545edd0.js → 7431.3051e9b8a4372d2d.js} +2 -2
  88. package/apps/pwa/out/_next/static/chunks/{3583.a05e88cc8d594628.js → 7448.478958ffbbba0ac0.js} +1 -1
  89. package/apps/pwa/out/_next/static/chunks/7537.ba2208c5c656b8a3.js +1 -0
  90. package/apps/pwa/out/_next/static/chunks/7562.948255cbc3e79d33.js +41 -0
  91. package/apps/pwa/out/_next/static/chunks/7596-962ae6db6cfc9c83.js +1 -0
  92. package/apps/pwa/out/_next/static/chunks/7671.cff51beac212a2ba.js +1 -0
  93. package/apps/pwa/out/_next/static/chunks/7883.3916734ee16d3e58.js +27 -0
  94. package/apps/pwa/out/_next/static/chunks/{9989.3694300e5f000b14.js → 7952.b192c31b6a0e00cc.js} +1 -1
  95. package/apps/pwa/out/_next/static/chunks/801.5b24e81eded22d1b.js +1 -0
  96. package/apps/pwa/out/_next/static/chunks/8176.8c9a8613f5a01ac1.js +36 -0
  97. package/apps/pwa/out/_next/static/chunks/819e78a6.fa3dd7f7da19bf61.js +1 -0
  98. package/apps/pwa/out/_next/static/chunks/8220-78978450c3183d8d.js +1 -0
  99. package/apps/pwa/out/_next/static/chunks/8221-eb60b5bc692a3361.js +1 -0
  100. package/apps/pwa/out/_next/static/chunks/826.e9a26064f474949e.js +1 -0
  101. package/apps/pwa/out/_next/static/chunks/{5307.60f2209e809bd627.js → 8368.014caa8de2f5af34.js} +1 -1
  102. package/apps/pwa/out/_next/static/chunks/8459-a351487a4480f584.js +1 -0
  103. package/apps/pwa/out/_next/static/chunks/8482-75823d513a4ce151.js +1 -0
  104. package/apps/pwa/out/_next/static/chunks/8488.8da515248cda3f3a.js +1 -0
  105. package/apps/pwa/out/_next/static/chunks/{9221.7d22eaf353a2d4e7.js → 8548.036c344973968847.js} +1 -1
  106. package/apps/pwa/out/_next/static/chunks/8584.388a1779d9b203af.js +1 -0
  107. package/apps/pwa/out/_next/static/chunks/8637.a55e098e61ffefb8.js +1 -0
  108. package/apps/pwa/out/_next/static/chunks/8817.61bb7ffbba502ecb.js +1 -0
  109. package/apps/pwa/out/_next/static/chunks/{9398-6aed28f0a96d0d6f.js → 9131-fce7256c4180c4a5.js} +1 -1
  110. package/apps/pwa/out/_next/static/chunks/9207-8e6a12db30373f32.js +1 -0
  111. package/apps/pwa/out/_next/static/chunks/9263-00ae932c62696a86.js +1 -0
  112. package/apps/pwa/out/_next/static/chunks/9318.caafc3a130e8769c.js +1 -0
  113. package/apps/pwa/out/_next/static/chunks/932.da3fb55951882562.js +93 -0
  114. package/apps/pwa/out/_next/static/chunks/9433.002f8426ac75b6b7.js +1 -0
  115. package/apps/pwa/out/_next/static/chunks/9464.dd631a5a14b1d660.js +24 -0
  116. package/apps/pwa/out/_next/static/chunks/{3278.2e5d15b53a4091c8.js → 9550.d5f76828f972058c.js} +2 -2
  117. package/apps/pwa/out/_next/static/chunks/{2534.e85ae21ad0dac829.js → 9739.8d5f36ac67a3b236.js} +1 -1
  118. package/apps/pwa/out/_next/static/chunks/9743.beeabb9d5f86abe4.js +1 -0
  119. package/apps/pwa/out/_next/static/chunks/9843-a23d3f32dc43f715.js +1 -0
  120. package/apps/pwa/out/_next/static/chunks/994-28211077c7c587d5.js +1 -0
  121. package/apps/pwa/out/_next/static/chunks/{7a1887c4.f36b88fba31da8fd.js → 9a8cb466.cc9fc9e6e46038aa.js} +1 -1
  122. package/apps/pwa/out/_next/static/chunks/{0bb0ff36.b8d418980951e5ba.js → 9b48c871.204d5d13e7809bd0.js} +7 -7
  123. package/apps/pwa/out/_next/static/chunks/{409f1e57.d0649fc47691ebfe.js → abb4b98b.163e0f56e84ae35d.js} +1 -1
  124. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-3b4c947f174e33d8.js +1 -0
  125. package/apps/pwa/out/_next/static/chunks/app/approvals/page-369d5ce99483f859.js +1 -0
  126. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-63c0ed0eb26e5fce.js +1 -0
  127. package/apps/pwa/out/_next/static/chunks/app/board/page-62802c6003bc85cc.js +1 -0
  128. package/apps/pwa/out/_next/static/chunks/app/botlab/page-ecb41560d4e77be9.js +1 -0
  129. package/apps/pwa/out/_next/static/chunks/app/bots/page-7b65ad8130c93e95.js +1 -0
  130. package/apps/pwa/out/_next/static/chunks/app/ceo/page-8755477c15a9060c.js +1 -0
  131. package/apps/pwa/out/_next/static/chunks/app/chat/page-5725447dd972c471.js +1 -0
  132. package/apps/pwa/out/_next/static/chunks/app/consult/page-b979de0591a69375.js +1 -0
  133. package/apps/pwa/out/_next/static/chunks/app/control/page-6d0796bf4633751a.js +1 -0
  134. package/apps/pwa/out/_next/static/chunks/app/decisions/page-59c31dc0394e67b4.js +1 -0
  135. package/apps/pwa/out/_next/static/chunks/app/design-check/page-12dfc95f6cc5c718.js +1 -0
  136. package/apps/pwa/out/_next/static/chunks/app/editor/page-25788244e66aa4dd.js +1 -0
  137. package/apps/pwa/out/_next/static/chunks/app/exec/page-a2555dc705466322.js +1 -0
  138. package/apps/pwa/out/_next/static/chunks/app/field/page-4fb31eb476dcbad3.js +1 -0
  139. package/apps/pwa/out/_next/static/chunks/app/healthz/route-3b4c947f174e33d8.js +1 -0
  140. package/apps/pwa/out/_next/static/chunks/app/inside/page-578729adbeaa40cb.js +1 -0
  141. package/apps/pwa/out/_next/static/chunks/app/intake/page-a1d20fffbec35205.js +1 -0
  142. package/apps/pwa/out/_next/static/chunks/app/layout-4a558f94516b8004.js +1 -0
  143. package/apps/pwa/out/_next/static/chunks/app/live/page-9e3834705b86145e.js +1 -0
  144. package/apps/pwa/out/_next/static/chunks/app/live-run/page-d7bff0f0905a662c.js +1 -0
  145. package/apps/pwa/out/_next/static/chunks/app/loops/page-cb1c5fe746ea4014.js +1 -0
  146. package/apps/pwa/out/_next/static/chunks/app/market/page-9ee320507356582a.js +1 -0
  147. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-66fd183b74835886.js +1 -0
  148. package/apps/pwa/out/_next/static/chunks/app/morning/page-3272a064a01a9927.js +1 -0
  149. package/apps/pwa/out/_next/static/chunks/app/not-found-f281b7a7f6ef2529.js +1 -0
  150. package/apps/pwa/out/_next/static/chunks/app/observatory/page-d45a65f8b7124fcd.js +1 -0
  151. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-e9c1fe684dd19a66.js +1 -0
  152. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-b8322932fc6e34de.js +1 -0
  153. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-82c66d9af2290575.js +1 -0
  154. package/apps/pwa/out/_next/static/chunks/app/outputs/page-63c6752009c97b05.js +1 -0
  155. package/apps/pwa/out/_next/static/chunks/app/page-bb33d42ebc358aec.js +1 -0
  156. package/apps/pwa/out/_next/static/chunks/app/reflection/page-7e4c9379197d7214.js +1 -0
  157. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-38bf1e9a7001732b.js +1 -0
  158. package/apps/pwa/out/_next/static/chunks/app/sessions/page-2e5b752012389d56.js +1 -0
  159. package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-8c66a3b24a745630.js → page-398c0faa173203f7.js} +1 -1
  160. package/apps/pwa/out/_next/static/chunks/app/settings/page-002f53a965b6445c.js +1 -0
  161. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-7f75997f61a26269.js +1 -0
  162. package/apps/pwa/out/_next/static/chunks/app/setup/layout-3b4c947f174e33d8.js +1 -0
  163. package/apps/pwa/out/_next/static/chunks/app/setup/page-05631753294f23fb.js +1 -0
  164. package/apps/pwa/out/_next/static/chunks/app/share/page-edd700fd2cc3d7f8.js +1 -0
  165. package/apps/pwa/out/_next/static/chunks/app/showroom/page-105f83ecb4594365.js +1 -0
  166. package/apps/pwa/out/_next/static/chunks/app/tasks/page-5823e87d2017f19c.js +1 -0
  167. package/apps/pwa/out/_next/static/chunks/app/term/{page-04d00ae9b0890349.js → page-df3445b207ceceab.js} +1 -1
  168. package/apps/pwa/out/_next/static/chunks/app/today/page-b51ca09566c03de7.js +1 -0
  169. package/apps/pwa/out/_next/static/chunks/app/trace/page-8aacd5ded5dbac60.js +1 -0
  170. package/apps/pwa/out/_next/static/chunks/app/vault/page-bae3365a0b3c6a8e.js +1 -0
  171. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-9ed52b48ff1aa7fd.js +1 -0
  172. package/apps/pwa/out/_next/static/chunks/app/workflows/page-b6f8cad35fe00946.js +1 -0
  173. package/apps/pwa/out/_next/static/chunks/app/workspace/page-6d25ceda500d2457.js +1 -0
  174. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-b6401a5f2c962fe8.js +1 -0
  175. package/apps/pwa/out/_next/static/chunks/ebf6cf5e.0d2f887b040e80fb.js +122 -0
  176. package/apps/pwa/out/_next/static/chunks/f09ec7d7-d29abe23edcbb193.js +1 -0
  177. package/apps/pwa/out/_next/static/chunks/{ebc7d829.264a0bcb6c750b50.js → ffdc3a04.b9c23c1b28dd4a96.js} +1 -1
  178. package/apps/pwa/out/_next/static/chunks/framework-9322cb8a56ac081c.js +1 -0
  179. package/apps/pwa/out/_next/static/chunks/main-2fbab294e49a1561.js +1 -0
  180. package/apps/pwa/out/_next/static/chunks/main-app-f36729f56621a8f3.js +1 -0
  181. package/apps/pwa/out/_next/static/chunks/pages/_app-2a62e450b47c6114.js +1 -0
  182. package/apps/pwa/out/_next/static/chunks/pages/_error-5e17f7b445f34d43.js +1 -0
  183. package/apps/pwa/out/_next/static/chunks/webpack-b4263e2190ea7adf.js +1 -0
  184. package/apps/pwa/out/approvals/index.html +1 -1
  185. package/apps/pwa/out/approvals/index.txt +22 -22
  186. package/apps/pwa/out/autopilot/index.html +1 -1
  187. package/apps/pwa/out/autopilot/index.txt +22 -22
  188. package/apps/pwa/out/board/index.html +1 -1
  189. package/apps/pwa/out/board/index.txt +22 -22
  190. package/apps/pwa/out/botlab/index.html +1 -1
  191. package/apps/pwa/out/botlab/index.txt +21 -21
  192. package/apps/pwa/out/bots/index.html +1 -1
  193. package/apps/pwa/out/bots/index.txt +21 -21
  194. package/apps/pwa/out/ceo/index.html +1 -1
  195. package/apps/pwa/out/ceo/index.txt +22 -22
  196. package/apps/pwa/out/chat/index.html +1 -1
  197. package/apps/pwa/out/chat/index.txt +21 -21
  198. package/apps/pwa/out/consult/index.html +1 -1
  199. package/apps/pwa/out/consult/index.txt +22 -22
  200. package/apps/pwa/out/control/index.html +1 -1
  201. package/apps/pwa/out/control/index.txt +22 -22
  202. package/apps/pwa/out/decisions/index.html +1 -1
  203. package/apps/pwa/out/decisions/index.txt +21 -21
  204. package/apps/pwa/out/design-check/index.html +1 -1
  205. package/apps/pwa/out/design-check/index.txt +22 -22
  206. package/apps/pwa/out/editor/index.html +1 -1
  207. package/apps/pwa/out/editor/index.txt +22 -22
  208. package/apps/pwa/out/exec/index.html +1 -1
  209. package/apps/pwa/out/exec/index.txt +22 -22
  210. package/apps/pwa/out/field/index.html +1 -1
  211. package/apps/pwa/out/field/index.txt +22 -22
  212. package/apps/pwa/out/healthz +1 -1
  213. package/apps/pwa/out/index.html +1 -1
  214. package/apps/pwa/out/index.txt +21 -21
  215. package/apps/pwa/out/inside/index.html +1 -1
  216. package/apps/pwa/out/inside/index.txt +21 -21
  217. package/apps/pwa/out/intake/index.html +1 -1
  218. package/apps/pwa/out/intake/index.txt +22 -22
  219. package/apps/pwa/out/live/index.html +1 -1
  220. package/apps/pwa/out/live/index.txt +22 -22
  221. package/apps/pwa/out/live-run/index.html +1 -1
  222. package/apps/pwa/out/live-run/index.txt +22 -22
  223. package/apps/pwa/out/loops/index.html +1 -1
  224. package/apps/pwa/out/loops/index.txt +21 -21
  225. package/apps/pwa/out/market/index.html +1 -1
  226. package/apps/pwa/out/market/index.txt +21 -21
  227. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  228. package/apps/pwa/out/missions/placeholder/index.txt +21 -21
  229. package/apps/pwa/out/morning/index.html +1 -1
  230. package/apps/pwa/out/morning/index.txt +22 -22
  231. package/apps/pwa/out/observatory/index.html +1 -1
  232. package/apps/pwa/out/observatory/index.txt +22 -22
  233. package/apps/pwa/out/ops/checklist/index.html +1 -1
  234. package/apps/pwa/out/ops/checklist/index.txt +21 -21
  235. package/apps/pwa/out/ops/release/index.html +1 -1
  236. package/apps/pwa/out/ops/release/index.txt +21 -21
  237. package/apps/pwa/out/ops/seats/index.html +1 -1
  238. package/apps/pwa/out/ops/seats/index.txt +21 -21
  239. package/apps/pwa/out/outputs/index.html +1 -1
  240. package/apps/pwa/out/outputs/index.txt +21 -21
  241. package/apps/pwa/out/reflection/index.html +1 -1
  242. package/apps/pwa/out/reflection/index.txt +22 -22
  243. package/apps/pwa/out/scheduler/index.html +1 -1
  244. package/apps/pwa/out/scheduler/index.txt +22 -22
  245. package/apps/pwa/out/sessions/index.html +1 -1
  246. package/apps/pwa/out/sessions/index.txt +21 -21
  247. package/apps/pwa/out/settings/devices/index.html +1 -1
  248. package/apps/pwa/out/settings/devices/index.txt +22 -22
  249. package/apps/pwa/out/settings/index.html +1 -1
  250. package/apps/pwa/out/settings/index.txt +22 -22
  251. package/apps/pwa/out/setup/done/index.html +1 -1
  252. package/apps/pwa/out/setup/done/index.txt +22 -22
  253. package/apps/pwa/out/setup/index.html +1 -1
  254. package/apps/pwa/out/setup/index.txt +22 -22
  255. package/apps/pwa/out/share/index.html +1 -1
  256. package/apps/pwa/out/share/index.txt +22 -22
  257. package/apps/pwa/out/showroom/index.html +1 -1
  258. package/apps/pwa/out/showroom/index.txt +21 -21
  259. package/apps/pwa/out/tasks/index.html +1 -1
  260. package/apps/pwa/out/tasks/index.txt +22 -22
  261. package/apps/pwa/out/term/index.html +1 -1
  262. package/apps/pwa/out/term/index.txt +22 -22
  263. package/apps/pwa/out/today/index.html +1 -1
  264. package/apps/pwa/out/today/index.txt +22 -22
  265. package/apps/pwa/out/trace/index.html +1 -1
  266. package/apps/pwa/out/trace/index.txt +22 -22
  267. package/apps/pwa/out/vault/index.html +1 -1
  268. package/apps/pwa/out/vault/index.txt +22 -22
  269. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  270. package/apps/pwa/out/workflows/chat-ui/index.txt +22 -22
  271. package/apps/pwa/out/workflows/index.html +1 -1
  272. package/apps/pwa/out/workflows/index.txt +22 -22
  273. package/apps/pwa/out/workspace/index.html +1 -1
  274. package/apps/pwa/out/workspace/index.txt +22 -22
  275. package/apps/pwa/out/worktrees/index.html +1 -1
  276. package/apps/pwa/out/worktrees/index.txt +22 -22
  277. package/bin/elanous.mjs +9 -2
  278. package/docs/goal-context/README.md +1 -0
  279. package/graphs/default-loop.yaml +20 -3
  280. package/graphs/document-loop.yaml +21 -2
  281. package/graphs/implement-loop.yaml +6 -0
  282. package/graphs/journey/SCENARIOS.md +33 -0
  283. package/graphs/journey/recipes.yaml +1 -0
  284. package/graphs/journey/ta-journey.yaml +84 -0
  285. package/graphs/journey/units/author/author.yaml +20 -0
  286. package/graphs/journey/units/author/recipes.yaml +2 -0
  287. package/graphs/journey/units/green/green.yaml +20 -0
  288. package/graphs/journey/units/green/recipes.yaml +2 -0
  289. package/graphs/journey/units/pr/pr.yaml +20 -0
  290. package/graphs/journey/units/pr/recipes.yaml +2 -0
  291. package/graphs/journey/units/remember/recipes.yaml +2 -0
  292. package/graphs/journey/units/remember/remember.yaml +20 -0
  293. package/graphs/operate-loop.yaml +21 -2
  294. package/graphs/overlays/stop-heal-ladder.yaml +1 -1
  295. package/graphs/release-rehearsal/recipes.yaml +1 -1
  296. package/graphs/release-rehearsal/release-rehearsal.yaml +2 -2
  297. package/graphs/research-loop.yaml +23 -2
  298. package/graphs/units/dispatch-pod/dispatch-pod.yaml +50 -0
  299. package/graphs/units/dispatch-pod/recipes.yaml +14 -0
  300. package/graphs/units/entry/entry.yaml +50 -0
  301. package/graphs/units/entry/recipes.yaml +14 -0
  302. package/graphs/units/harvest/harvest.yaml +25 -0
  303. package/graphs/units/harvest/recipes.yaml +4 -0
  304. package/graphs/units/land/land.yaml +45 -0
  305. package/graphs/units/land/recipes.yaml +3 -0
  306. package/graphs/units/recipes.yaml +7 -0
  307. package/graphs/units/recover.yaml +39 -0
  308. package/graphs/units/reflect/recipes.yaml +3 -0
  309. package/graphs/units/reflect/reflect.yaml +15 -0
  310. package/graphs/units/task-agent-recover-review.yaml +17 -0
  311. package/graphs/units/task-agent-recover-wait.yaml +17 -0
  312. package/graphs/units/verbs/make-doc.yaml +34 -0
  313. package/graphs/units/verbs/recipes.yaml +33 -0
  314. package/graphs/units/verbs/verify.yaml +55 -0
  315. package/graphs/work/recipes.yaml +2 -0
  316. package/graphs/work/research-report.yaml +28 -0
  317. package/package.json +1 -1
  318. package/scripts/ci-typecheck-changed.ts +69 -16
  319. package/scripts/docs/generate-graph-anatomy.ts +19 -2
  320. package/scripts/graph-harvest-unit.ts +22 -0
  321. package/scripts/graph-units/journey-stub.ts +6 -0
  322. package/scripts/graph-units/land-enqueue.ts +78 -0
  323. package/scripts/graph-units/recover-plan.ts +15 -0
  324. package/scripts/graph-units/reflect-node.ts +88 -0
  325. package/scripts/graph-units/ta-journey-observe.ts +109 -0
  326. package/scripts/graph-units/verbs/headless-goal-loop.ts +152 -0
  327. package/scripts/graph-units/verbs/verify-stage.ts +75 -0
  328. package/scripts/graph-units/verbs-envelope.ts +139 -0
  329. package/scripts/hq/standby-snapshot.ts +25 -0
  330. package/scripts/public-export.ts +43 -1
  331. package/scripts/ratchets/train-side-invariants.sh +45 -0
  332. package/scripts/release-loop/auto-approve-node.ts +30 -16
  333. package/scripts/release-loop/gate-closure.ts +5 -1
  334. package/scripts/release-loop/gate-diff.ts +1 -1
  335. package/scripts/release-loop/gate-node.ts +498 -31
  336. package/scripts/release-loop/gate-partial.ts +3 -3
  337. package/scripts/release-loop/gate-radius-shadow.ts +138 -0
  338. package/scripts/release-loop/gate-traffic-mode.ts +128 -0
  339. package/scripts/release-loop/gate-triage-node.ts +5 -2
  340. package/scripts/release-loop/known-issues-0.2.33-approved.json +194 -0
  341. package/scripts/release-loop/known-issues-node.ts +77 -8
  342. package/scripts/release-loop/preflight.ts +93 -7
  343. package/scripts/release-loop/publish-node.ts +60 -9
  344. package/scripts/release-loop/resume-cap.ts +56 -0
  345. package/scripts/release-loop/resume-release.ts +114 -15
  346. package/scripts/release-loop/resume.ts +24 -1
  347. package/scripts/release-loop/tui-sim-node.ts +8 -2
  348. package/scripts/release-loop/unattended-release.ts +8 -2
  349. package/src/acp/core-turn-bridge.ts +20 -5
  350. package/src/acp/reasoning-thought-buffer.ts +45 -0
  351. package/src/agent-substrate/acp-reviewer.ts +52 -12
  352. package/src/agent-substrate/review-finding-key.ts +53 -0
  353. package/src/agent-substrate/self-review-cli.ts +126 -5
  354. package/src/ask-user-question/pending-questions.ts +1 -1
  355. package/src/autopilot/pr-manager.ts +192 -43
  356. package/src/chat/tool-render/dispatch.ts +62 -10
  357. package/src/cli/control-cli.ts +24 -7
  358. package/src/cli/guardian-cli.ts +10 -0
  359. package/src/cli/light-entry-route.ts +35 -0
  360. package/src/cli/llm-detect-cli.ts +23 -1
  361. package/src/cli/pr-cli.ts +76 -12
  362. package/src/cli/pr-land-base-read.ts +2 -0
  363. package/src/cli/pr-land-overlap-evidence.ts +154 -16
  364. package/src/cli/pr-land-recovery.ts +64 -0
  365. package/src/cli/pty-takeover-cli.ts +6 -5
  366. package/src/cli/release-cli.ts +120 -33
  367. package/src/cli/release-run-if-ready.ts +29 -28
  368. package/src/cli/release-single-flight.ts +178 -0
  369. package/src/cli/seat-cli.ts +37 -0
  370. package/src/cli/setup-cli.ts +40 -1
  371. package/src/cli/tasks-cli.ts +149 -5
  372. package/src/control/control-status.ts +8 -3
  373. package/src/control-plane/ledger.ts +5 -3
  374. package/src/control-plane/server.ts +5 -3
  375. package/src/core-turn/short-question.ts +5 -1
  376. package/src/dashboard/index.ts +36 -67
  377. package/src/dashboard/input/chat-main-plain-turn-runtime.ts +3 -0
  378. package/src/dashboard/input/dashboard-plain-turn-stream.ts +4 -0
  379. package/src/dashboard/input/seat-autowake.ts +56 -3
  380. package/src/dashboard/seat-boot-preamble.ts +1 -1
  381. package/src/dashboard/slash-runtime/ask-clarification.ts +48 -0
  382. package/src/dashboard/slash-runtime/dashboard-delegated-slashes.ts +68 -0
  383. package/src/dashboard/slash-runtime/dashboard-handlers.ts +62 -322
  384. package/src/dashboard/slash-runtime/dashboard-log-slash.ts +208 -0
  385. package/src/dashboard/turn-preamble.ts +47 -1
  386. package/src/dashboard/turn-stream-formatter.ts +34 -1
  387. package/src/dashboard/turn-stream-runtime.ts +2 -0
  388. package/src/dashboard/turn-typeahead-prompt-zone.ts +67 -0
  389. package/src/domains/guardian-tick.ts +54 -2
  390. package/src/domains/outbound-alert.ts +3 -2
  391. package/src/extractors.ts +3 -1
  392. package/src/git-fs/retry.ts +4 -0
  393. package/src/git-fs/runner.ts +2 -0
  394. package/src/graph-runner/runner.ts +114 -44
  395. package/src/grok/credential.ts +5 -0
  396. package/src/harness/harness-cli-command.ts +39 -10
  397. package/src/harness/harness-incidents.ts +4 -0
  398. package/src/harness/harness-pod-dispatch.ts +38 -1
  399. package/src/harness/harness-salvage-cli.ts +14 -2
  400. package/src/index.ts +192 -8
  401. package/src/intake-plane/already-built-hint.ts +89 -0
  402. package/src/knowhow/pack.ts +199 -0
  403. package/src/light-cli.ts +95 -0
  404. package/src/llm.ts +55 -9
  405. package/src/loops/budget.ts +95 -0
  406. package/src/loops/msg-wake.ts +8 -2
  407. package/src/loops/orchestrator/traffic.ts +132 -1
  408. package/src/mss/logging/log-store.ts +4 -5
  409. package/src/nexus/api/graphs-api.ts +22 -3
  410. package/src/nexus/api/http-server.ts +1 -1
  411. package/src/nexus/api/ops-api.ts +12 -0
  412. package/src/oauth/claude-pty-maturity.ts +66 -0
  413. package/src/oauth/codex-access-mirror.ts +55 -0
  414. package/src/oauth/fallback-chain.ts +1 -1
  415. package/src/onboarding/first-run.ts +5 -3
  416. package/src/onboarding/non-interactive.ts +64 -13
  417. package/src/onboarding.ts +10 -2
  418. package/src/ops/overview-collect.ts +36 -1
  419. package/src/ops/overview.ts +11 -0
  420. package/src/pr-tool/census.ts +23 -5
  421. package/src/pr-tool/inflight-registry.ts +160 -7
  422. package/src/prompt-library/gpt6-family-addendum.ts +14 -1
  423. package/src/pty-shell/herdr-agent-report.ts +442 -0
  424. package/src/pty-shell/pty-control-ipc.ts +5 -5
  425. package/src/pty-shell/pty-write-outcome.ts +24 -6
  426. package/src/pty-shell/tui-run-state.ts +117 -3
  427. package/src/release-loop/checklist.ts +79 -5
  428. package/src/release-loop/feature-store.ts +19 -2
  429. package/src/release-loop/placement.ts +10 -3
  430. package/src/release-loop/rehearsal.ts +205 -25
  431. package/src/release-loop/rubric.ts +16 -2
  432. package/src/self-dev/draft-sweep.ts +52 -3
  433. package/src/self-dev/graph-journey-nodes.ts +10 -6
  434. package/src/self-dev/graph-journey-route.ts +60 -6
  435. package/src/self-dev/orchestrate.ts +79 -3
  436. package/src/self-dev/run-supervisor.ts +107 -12
  437. package/src/self-dev/run-types.ts +5 -1
  438. package/src/self-implement/gate-baseline.ts +49 -5
  439. package/src/self-implement/gate-remote.ts +9 -5
  440. package/src/self-implement/goal-author-identity.ts +66 -0
  441. package/src/self-implement/goal-author-preservation-obligation.ts +35 -0
  442. package/src/self-implement/goal-author.ts +4 -120
  443. package/src/self-implement/graph-overlay-yaml.ts +17 -2
  444. package/src/self-implement/graph-unit-flatten.ts +103 -0
  445. package/src/self-implement/graph-unit-resolver.ts +107 -0
  446. package/src/self-implement/graph-yaml.ts +151 -1
  447. package/src/self-implement/headless-elanous-driver.ts +1 -0
  448. package/src/self-implement/knowhow-pack.ts +103 -0
  449. package/src/self-implement/orchestrator.ts +8 -0
  450. package/src/self-implement/pod-warm-pool-profile.ts +45 -0
  451. package/src/self-implement/review-mustfix-class.ts +203 -0
  452. package/src/self-implement/run-stop.ts +8 -2
  453. package/src/self-implement/seams.ts +32 -1
  454. package/src/self-implement/stop-autoheal.ts +25 -1
  455. package/src/session/chat.ts +27 -0
  456. package/src/session-runtime/retry-policy.ts +2 -1
  457. package/src/skills/tools/schedule-wakeup.ts +66 -17
  458. package/src/steward/release-day.ts +7 -3
  459. package/src/steward/triage.ts +37 -4
  460. package/src/task-agent/harvest-unit.ts +141 -0
  461. package/src/task-agent/live-moves.ts +98 -12
  462. package/src/task-agent/parent-host.ts +93 -3
  463. package/src/task-agent/postland-card.ts +80 -1
  464. package/src/task-agent/rejudge-on-head.ts +112 -26
  465. package/src/task-agent/repair-branch-cas.ts +4 -1
  466. package/src/task-agent/repair-cycle.ts +16 -3
  467. package/src/task-agent/run-identity.ts +4 -3
  468. package/src/task-agent/shadow.ts +12 -2
  469. package/src/task-agent/task-hand.ts +54 -7
  470. package/src/task-agent/work-order-build.ts +22 -0
  471. package/src/task-orchestrator/surfaces/pod-account-broker.ts +39 -3
  472. package/src/task-orchestrator/surfaces/pod-command-job.ts +2 -1
  473. package/src/task-orchestrator/surfaces/pod-lifecycle-stages.ts +86 -8
  474. package/src/task-orchestrator/surfaces/pod-pool.ts +73 -17
  475. package/src/task-orchestrator/surfaces/pod-source-receive.ts +54 -4
  476. package/src/task-orchestrator/surfaces/pod-warm-worker-pod.ts +26 -3
  477. package/src/task-orchestrator/surfaces/pod-warm-worker-provider.ts +389 -0
  478. package/src/task-orchestrator/surfaces/pod-warm-worker-runtime.ts +2 -1
  479. package/src/task-orchestrator/surfaces/self-implement-pod.ts +223 -38
  480. package/src/task-orchestrator/surfaces/self-implement.ts +83 -2
  481. package/src/tool-runtime/truncation-store.ts +4 -0
  482. package/src/tui-client/dashboard-session.ts +10 -1
  483. package/src/user-config/coercion.ts +36 -0
  484. package/src/user-config.ts +18 -41
  485. package/src/ux-sim/scenario.ts +72 -13
  486. package/src/version/packed-revision.json +1 -1
  487. package/apps/pwa/out/_next/static/chunks/1034-8b639cee57b8e4a8.js +0 -1
  488. package/apps/pwa/out/_next/static/chunks/1085-6c2d35526f11f840.js +0 -1
  489. package/apps/pwa/out/_next/static/chunks/1220.1ff7d5cdc34b1ae5.js +0 -27
  490. package/apps/pwa/out/_next/static/chunks/1247.5884ec81b39fe2e7.js +0 -287
  491. package/apps/pwa/out/_next/static/chunks/1302.87681521a7a4661e.js +0 -62
  492. package/apps/pwa/out/_next/static/chunks/1324-59dae3002413623b.js +0 -1
  493. package/apps/pwa/out/_next/static/chunks/1343-f40b5870ac1eda5a.js +0 -1
  494. package/apps/pwa/out/_next/static/chunks/146-b6cd56be11dda017.js +0 -1
  495. package/apps/pwa/out/_next/static/chunks/1654.32de4b324981a188.js +0 -23
  496. package/apps/pwa/out/_next/static/chunks/1834-98b64172bc1efe77.js +0 -1
  497. package/apps/pwa/out/_next/static/chunks/1849.1ce38efa6c527d9a.js +0 -149
  498. package/apps/pwa/out/_next/static/chunks/1913.7cc6fb6055b1ed3f.js +0 -41
  499. package/apps/pwa/out/_next/static/chunks/2001.6c7e0fd02190886c.js +0 -1
  500. package/apps/pwa/out/_next/static/chunks/2030.41fae98c7e907304.js +0 -1
  501. package/apps/pwa/out/_next/static/chunks/2099-08b2f2dfba742c9b.js +0 -1
  502. package/apps/pwa/out/_next/static/chunks/2134.ecee03a550ee7250.js +0 -1
  503. package/apps/pwa/out/_next/static/chunks/2236.66061832181cb745.js +0 -1
  504. package/apps/pwa/out/_next/static/chunks/2422.b0bd272e7020d7e6.js +0 -1
  505. package/apps/pwa/out/_next/static/chunks/3040.92ff9ffc9f971cb9.js +0 -115
  506. package/apps/pwa/out/_next/static/chunks/3144.6861096c7b4edef7.js +0 -64
  507. package/apps/pwa/out/_next/static/chunks/3267c0e6.d34854e7b3984a6e.js +0 -1
  508. package/apps/pwa/out/_next/static/chunks/3771.e928edf2867d28c0.js +0 -1
  509. package/apps/pwa/out/_next/static/chunks/3902.e1e71aaac850a045.js +0 -34
  510. package/apps/pwa/out/_next/static/chunks/4063-03284b5de59965f3.js +0 -1
  511. package/apps/pwa/out/_next/static/chunks/4111.2bfedbb233ad206b.js +0 -1
  512. package/apps/pwa/out/_next/static/chunks/4300-c7ec4eeabbb5f3f7.js +0 -1
  513. package/apps/pwa/out/_next/static/chunks/4483.646d5065199e1b55.js +0 -36
  514. package/apps/pwa/out/_next/static/chunks/4519.28199ff26b2fd80d.js +0 -1
  515. package/apps/pwa/out/_next/static/chunks/4581.ef2443578054544f.js +0 -1
  516. package/apps/pwa/out/_next/static/chunks/4608-8d894e9cbe4f08bf.js +0 -1
  517. package/apps/pwa/out/_next/static/chunks/4639.ae48a01d1bb50a93.js +0 -286
  518. package/apps/pwa/out/_next/static/chunks/4739.08742ac668aada67.js +0 -77
  519. package/apps/pwa/out/_next/static/chunks/4882-16cef71d6a1a32be.js +0 -1
  520. package/apps/pwa/out/_next/static/chunks/4999.c064a4000c2a9e94.js +0 -79
  521. package/apps/pwa/out/_next/static/chunks/5260-69f35419bca263e7.js +0 -1
  522. package/apps/pwa/out/_next/static/chunks/5272.b92d1b72a15c25fe.js +0 -1
  523. package/apps/pwa/out/_next/static/chunks/5304.04d24756f242a094.js +0 -148
  524. package/apps/pwa/out/_next/static/chunks/5415-997980f828667736.js +0 -1
  525. package/apps/pwa/out/_next/static/chunks/5458-16cd7a2b51b74c8c.js +0 -1
  526. package/apps/pwa/out/_next/static/chunks/5572-42c9084f27ca761e.js +0 -1
  527. package/apps/pwa/out/_next/static/chunks/5661-80470aac963d9f36.js +0 -1
  528. package/apps/pwa/out/_next/static/chunks/5723.421338408b5c30c4.js +0 -74
  529. package/apps/pwa/out/_next/static/chunks/5809.64c3664267c50bce.js +0 -1
  530. package/apps/pwa/out/_next/static/chunks/6000.64bc21ac8627f2f5.js +0 -94
  531. package/apps/pwa/out/_next/static/chunks/6013.39459b9c68eabf5f.js +0 -1
  532. package/apps/pwa/out/_next/static/chunks/607-06466dff012b03ef.js +0 -1
  533. package/apps/pwa/out/_next/static/chunks/60fb96c9.360b01d8d4927b7b.js +0 -1
  534. package/apps/pwa/out/_next/static/chunks/6192.a190b989b81e9743.js +0 -1
  535. package/apps/pwa/out/_next/static/chunks/6323.8672b4817a306928.js +0 -1
  536. package/apps/pwa/out/_next/static/chunks/6444.7d1fdcb9b09ae71c.js +0 -1
  537. package/apps/pwa/out/_next/static/chunks/6517.4147878ac06d584d.js +0 -90
  538. package/apps/pwa/out/_next/static/chunks/6532.e34302010b2f0b68.js +0 -32
  539. package/apps/pwa/out/_next/static/chunks/6544cb48.a87fe20ddf137be2.js +0 -1
  540. package/apps/pwa/out/_next/static/chunks/6546.74bc4ed82f19016d.js +0 -104
  541. package/apps/pwa/out/_next/static/chunks/6684-e7523ce7c2fad075.js +0 -1
  542. package/apps/pwa/out/_next/static/chunks/6726.ebdc82feb161431e.js +0 -1
  543. package/apps/pwa/out/_next/static/chunks/6739.00fb6f892e5e392b.js +0 -1
  544. package/apps/pwa/out/_next/static/chunks/6745.934c4921bf15513a.js +0 -1
  545. package/apps/pwa/out/_next/static/chunks/6767.ea607cf0c20a25af.js +0 -24
  546. package/apps/pwa/out/_next/static/chunks/6843.67bfd020da0926c5.js +0 -93
  547. package/apps/pwa/out/_next/static/chunks/69-0a2c8209c6d4a158.js +0 -1
  548. package/apps/pwa/out/_next/static/chunks/6948.1062d4594652b5bf.js +0 -1
  549. package/apps/pwa/out/_next/static/chunks/7138-eaf841fc1dc03463.js +0 -1
  550. package/apps/pwa/out/_next/static/chunks/7174-cda29ee13d7790e7.js +0 -1
  551. package/apps/pwa/out/_next/static/chunks/7304-3c07583f4e405f4f.js +0 -1
  552. package/apps/pwa/out/_next/static/chunks/7371.80c05c653b08e907.js +0 -1
  553. package/apps/pwa/out/_next/static/chunks/7391.ebae168025c91b6a.js +0 -1
  554. package/apps/pwa/out/_next/static/chunks/7557.e325503a090a4310.js +0 -8
  555. package/apps/pwa/out/_next/static/chunks/7569-9ce9432c7f10989f.js +0 -1
  556. package/apps/pwa/out/_next/static/chunks/7886-55854585fadeded1.js +0 -1
  557. package/apps/pwa/out/_next/static/chunks/7947-d0759430f8042240.js +0 -1
  558. package/apps/pwa/out/_next/static/chunks/8018.a32663f04a6fc5f9.js +0 -1
  559. package/apps/pwa/out/_next/static/chunks/8097-9530c03bcc550f02.js +0 -1
  560. package/apps/pwa/out/_next/static/chunks/8215-1661812b6e9ad71f.js +0 -1
  561. package/apps/pwa/out/_next/static/chunks/8260-19c921a51ca8ec3e.js +0 -1
  562. package/apps/pwa/out/_next/static/chunks/8440.3a5cf5c85aa76db8.js +0 -1
  563. package/apps/pwa/out/_next/static/chunks/8519-4599be2249161e68.js +0 -1
  564. package/apps/pwa/out/_next/static/chunks/8547.b028b90c5c01ace6.js +0 -1
  565. package/apps/pwa/out/_next/static/chunks/8587-cbe02a953a7f4990.js +0 -1
  566. package/apps/pwa/out/_next/static/chunks/877-2b977259c4b15655.js +0 -1
  567. package/apps/pwa/out/_next/static/chunks/9019-0a4b40c50c7314fb.js +0 -1
  568. package/apps/pwa/out/_next/static/chunks/9138.b6e80031afb1d973.js +0 -1
  569. package/apps/pwa/out/_next/static/chunks/9188-d36e4fc869ddc166.js +0 -1
  570. package/apps/pwa/out/_next/static/chunks/9230.44f5039e796e4ab4.js +0 -1
  571. package/apps/pwa/out/_next/static/chunks/9279-d28c5b7754d010ae.js +0 -1
  572. package/apps/pwa/out/_next/static/chunks/939.336acce73e98482c.js +0 -1
  573. package/apps/pwa/out/_next/static/chunks/9610.c07cce4bfcc14ed7.js +0 -1
  574. package/apps/pwa/out/_next/static/chunks/9667-ce78c2ce91050ff9.js +0 -1
  575. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-339fabb3499da0e1.js +0 -1
  576. package/apps/pwa/out/_next/static/chunks/app/approvals/page-504204b58c82c253.js +0 -1
  577. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-a9717a67c2eecbaf.js +0 -1
  578. package/apps/pwa/out/_next/static/chunks/app/board/page-5c9ec30aee62dfbf.js +0 -1
  579. package/apps/pwa/out/_next/static/chunks/app/botlab/page-584191533721fa16.js +0 -1
  580. package/apps/pwa/out/_next/static/chunks/app/bots/page-73c4b4bb3ac7ab5f.js +0 -1
  581. package/apps/pwa/out/_next/static/chunks/app/ceo/page-c0f1ca371f710b06.js +0 -1
  582. package/apps/pwa/out/_next/static/chunks/app/chat/page-46430d45a9e04eac.js +0 -1
  583. package/apps/pwa/out/_next/static/chunks/app/consult/page-3b2ebf1dc64ff0be.js +0 -1
  584. package/apps/pwa/out/_next/static/chunks/app/control/page-a27b1e5a0d1b308f.js +0 -1
  585. package/apps/pwa/out/_next/static/chunks/app/decisions/page-25654b82094f7d22.js +0 -1
  586. package/apps/pwa/out/_next/static/chunks/app/design-check/page-8ea49a4c6dd2c6e7.js +0 -1
  587. package/apps/pwa/out/_next/static/chunks/app/editor/page-ce5113aefea0a8d1.js +0 -1
  588. package/apps/pwa/out/_next/static/chunks/app/exec/page-0e5a41fac1c75396.js +0 -1
  589. package/apps/pwa/out/_next/static/chunks/app/field/page-31aaf1ed37c6df9a.js +0 -1
  590. package/apps/pwa/out/_next/static/chunks/app/healthz/route-339fabb3499da0e1.js +0 -1
  591. package/apps/pwa/out/_next/static/chunks/app/inside/page-3dcc1fa411e1f4e0.js +0 -1
  592. package/apps/pwa/out/_next/static/chunks/app/intake/page-c69247e7134d329d.js +0 -1
  593. package/apps/pwa/out/_next/static/chunks/app/layout-66dc5cd3a0999abe.js +0 -1
  594. package/apps/pwa/out/_next/static/chunks/app/live/page-26ff7ff651a7217d.js +0 -1
  595. package/apps/pwa/out/_next/static/chunks/app/live-run/page-617b3976981efa96.js +0 -1
  596. package/apps/pwa/out/_next/static/chunks/app/loops/page-d256f5d5d9552823.js +0 -1
  597. package/apps/pwa/out/_next/static/chunks/app/market/page-ce8f1907b8a6101c.js +0 -1
  598. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-33e182a316263116.js +0 -1
  599. package/apps/pwa/out/_next/static/chunks/app/morning/page-32866485913d6a04.js +0 -1
  600. package/apps/pwa/out/_next/static/chunks/app/not-found-7607cca9b84950c6.js +0 -1
  601. package/apps/pwa/out/_next/static/chunks/app/observatory/page-eeab86176b03dc3b.js +0 -1
  602. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-5b93dbae3432b996.js +0 -1
  603. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-77a6cb90a9cf2b10.js +0 -1
  604. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-f47e768b5b242b5a.js +0 -1
  605. package/apps/pwa/out/_next/static/chunks/app/outputs/page-eb41aa3367fc2505.js +0 -1
  606. package/apps/pwa/out/_next/static/chunks/app/page-4d13ab0b8a267e3f.js +0 -1
  607. package/apps/pwa/out/_next/static/chunks/app/reflection/page-84dad32ff0fc0831.js +0 -1
  608. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-95306f892aa2d0ee.js +0 -1
  609. package/apps/pwa/out/_next/static/chunks/app/sessions/page-940fb6c217f6b470.js +0 -1
  610. package/apps/pwa/out/_next/static/chunks/app/settings/page-7b675dfd35c36fc9.js +0 -1
  611. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-8bd0d3cf7665bcfc.js +0 -1
  612. package/apps/pwa/out/_next/static/chunks/app/setup/layout-339fabb3499da0e1.js +0 -1
  613. package/apps/pwa/out/_next/static/chunks/app/setup/page-c219f2ed171131be.js +0 -1
  614. package/apps/pwa/out/_next/static/chunks/app/share/page-40f8fa21bdae955b.js +0 -1
  615. package/apps/pwa/out/_next/static/chunks/app/showroom/page-468f8313e4f1fbd9.js +0 -1
  616. package/apps/pwa/out/_next/static/chunks/app/tasks/page-7bbb8fa353a246c4.js +0 -1
  617. package/apps/pwa/out/_next/static/chunks/app/today/page-4214b855d9233ea8.js +0 -1
  618. package/apps/pwa/out/_next/static/chunks/app/trace/page-936752c7aa58762e.js +0 -1
  619. package/apps/pwa/out/_next/static/chunks/app/vault/page-ec052ad359df1797.js +0 -1
  620. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-8305570551091338.js +0 -1
  621. package/apps/pwa/out/_next/static/chunks/app/workflows/page-fad650e4793adb65.js +0 -1
  622. package/apps/pwa/out/_next/static/chunks/app/workspace/page-d90e64743826e1d8.js +0 -1
  623. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-a16bcfd49af86a32.js +0 -1
  624. package/apps/pwa/out/_next/static/chunks/e5012ffe.f727e23c231f4342.js +0 -122
  625. package/apps/pwa/out/_next/static/chunks/ea695e3c-1f1d70d740cf3031.js +0 -1
  626. package/apps/pwa/out/_next/static/chunks/framework-906af9bd029e166c.js +0 -1
  627. package/apps/pwa/out/_next/static/chunks/main-a24847f9f684d469.js +0 -1
  628. package/apps/pwa/out/_next/static/chunks/main-app-7bdcd8aa7c8397d3.js +0 -1
  629. package/apps/pwa/out/_next/static/chunks/pages/_app-6ba46a5066fc226d.js +0 -1
  630. package/apps/pwa/out/_next/static/chunks/pages/_error-a8dc6d07591f2a25.js +0 -1
  631. package/apps/pwa/out/_next/static/chunks/webpack-f13df28c15c55eee.js +0 -1
  632. package/src/cli/g6a-registration-probe.ts +0 -16
  633. /package/apps/pwa/out/_next/static/{IVKr3AlRUVLKsw2fh7eXF → L04y73dlwKeTW-GetaTF3}/_ssgManifest.js +0 -0
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env bun
2
2
  import { spawn, spawnSync } from 'node:child_process';
3
- import { appendFileSync, chmodSync, existsSync, mkdirSync, mkdtempSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
3
+ import { appendFileSync, chmodSync, copyFileSync, existsSync, mkdirSync, mkdtempSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
4
4
  import { hostname, tmpdir } from 'node:os';
5
5
  import { createHash, randomUUID } from 'node:crypto';
6
6
  import { dirname, join, resolve } from 'node:path';
@@ -14,6 +14,7 @@ import { loadEnvKnownFailures, splitKnownEnv } from './env-known-failures';
14
14
  import { loadGateBatchSafe } from './gate-batch-safe';
15
15
  import { baselineFromCutLogs } from './baseline-from-logs';
16
16
  import { mergePartialFailures, readPartialPlan, testFileOfId, verifyDocsOnlyCarry } from './gate-partial';
17
+ import { planGateRadiusShadow, shadowActual, writeGateRadiusShadow, type GateRadiusShadowPlan } from './gate-radius-shadow';
17
18
  import { logPlanWalltime, planDurations, readFileMemory } from './shard-plan';
18
19
  import { defaultGateHostMirrorReaders, resolveGateHostMirror, type GateHostMirror, type GateHostMirrorReaders } from './gate-host-mirror';
19
20
  import { isGateSpawnLaneFile, planGateSpawnSeparatedShards } from './spawn-lane';
@@ -28,15 +29,48 @@ import { POD_BUN_CACHE_HOST_PATH, parseInstallSeconds, podBunCacheVolume } from
28
29
  import { PodPoolScheduler, parsePodPool, checkPodPool, defaultRemoteRun, type PodPoolMember } from '../../src/task-orchestrator/surfaces/pod-pool.js';
29
30
  import { POD_INSTALL_SLOTS_DEFAULT, POD_INSTALL_SLOTS_HOST_PATH, installSlotScript } from '../../src/task-orchestrator/surfaces/pod-install-slots.js';
30
31
  import { gateDepsInstallScript, parseGateDeps } from '../../src/task-orchestrator/surfaces/pod-gate-deps.js';
31
- import { writeWarmJobManifest } from './gate-warm-artifacts.js';
32
+ import { warmReferenceFromJobArtifacts, writeWarmJobManifest } from './gate-warm-artifacts.js';
33
+ import { assessWarmWorkerShadow, parseWarmWorkerReply, type WarmWorkerCommand, type WarmWorkerFileResult, type WarmWorkerKey } from '../../src/task-orchestrator/surfaces/pod-warm-worker-protocol.js';
34
+ import type { WarmPodTransportResult } from '../../src/task-orchestrator/surfaces/pod-warm-worker-pod.js';
35
+ import { requestWarmPodReplay, finalizeWarmPodReplay, quarantineWarmPodReplay, type WarmReplayResult } from '../../src/task-orchestrator/surfaces/pod-warm-worker-provider.js';
36
+ import { resolveWarmPoolProfile } from '../../src/self-implement/pod-warm-pool-profile.js';
37
+ import { decideWarmReuse, type WarmReuseSwitchConfig, type WarmReuseEvidence } from '../../src/task-orchestrator/surfaces/pod-warm-switch.js';
38
+ import { getUserConfig } from '../../src/user-config.js';
32
39
  import { gateShardsPath, recordGateShardsVerdict, writeGateShards, type GateShard } from '../../src/release-loop/gate-shards.js';
33
40
  import { defaultKubectl } from '../../src/task-orchestrator/surfaces/self-implement-pod.js';
34
41
  import { executeTuiNodeCheck, type ExecutedTuiCheck } from './tui-node-checks.js';
42
+ import { runWithReleaseGateTraffic } from './gate-traffic-mode.js';
35
43
  import { emitPodLifecycle, gatePodLifecycleEvent, gatePodOutcome } from '../../src/task-orchestrator/surfaces/pod-lifecycle-stages.js';
36
44
  import { gateHostReservationRoot, reserveReleaseGateHosts } from '../../src/release-loop/gate-host-reservation.js';
37
45
  import { normRoot } from '../../src/instance/resolve.js';
38
46
 
47
+ export interface WarmShadowObservation {
48
+ files: string[];
49
+ attemptId: string;
50
+ reason: string;
51
+ status: 'observed' | 'fallback' | 'unmeasured';
52
+ quarantined: 'yes' | 'no' | 'failed' | 'unavailable';
53
+ finalized?: boolean;
54
+ /** Exact producer identity; absent on an old verdict means the sample cannot enable reuse. */
55
+ cutSha?: string;
56
+ baselineSha?: string;
57
+ workerKey?: WarmWorkerKey;
58
+ }
59
+
60
+ /** A provider may fill only lease identity; it cannot rewrite the Job attempt being compared. */
61
+ export function warmCommandMatchesRequest(command: WarmWorkerCommand, request: Omit<WarmWorkerCommand, 'leaseId' | 'generation' | 'workerId' | 'key'>): boolean {
62
+ return command.protocol === request.protocol && command.runId === request.runId
63
+ && command.attemptId === request.attemptId && command.cutSha === request.cutSha
64
+ && command.baselineSha === request.baselineSha && command.deadlineAt === request.deadlineAt
65
+ && command.envProfile === request.envProfile && command.files.length === request.files.length
66
+ && command.files.every((file, index) => file === request.files[index]);
67
+ }
68
+
39
69
  interface CommandResult { rc: number; output: string; passedIds?: string[]; stalledEnv?: Array<{ file: string; reason: 'no-output' }>; timedOut?: boolean;
70
+ /** Complete, source-bound one-file Job result for shadow comparison; absent means unmeasured. */
71
+ warmReference?: WarmWorkerFileResult | null;
72
+ warmAttemptId?: string;
73
+ warmShadow?: WarmShadowObservation;
40
74
  /** GATE-POD-STALL-FAILFAST ① — shard Pods whose tests the mid-run watch aborted in this sweep. */
41
75
  abortedShards?: number;
42
76
  /** Each contributing Pod leaf already passed failuresOf; repeated printed ids cannot add new failures. */
@@ -76,6 +110,22 @@ export interface GateOptions {
76
110
  recheckFileLimit?: number;
77
111
  /** freshShards — GATE-SHARD-RESUME off: every shard starts a Pod even when this logDir already holds its result. */
78
112
  pod?: { pool: string; shards?: number; shardTimeoutSeconds?: number; durationSource?: string; bunCache?: string; freshShards?: boolean;
113
+ /** Opt-in gate shadow: provider fills the fenced lease identity. The Job remains authoritative. */
114
+ warmReplay?: (input: Omit<WarmWorkerCommand, 'leaseId' | 'generation' | 'workerId' | 'key'>) => Promise<{ command: WarmWorkerCommand | null; transport: WarmPodTransportResult }>;
115
+ warmQuarantine?: (command: WarmWorkerCommand, reason: string) => Promise<void>;
116
+ warmFinalize?: (command: WarmWorkerCommand) => Promise<void>;
117
+ /** Set only on the cut recheck; baseline Jobs never consume the cut worker. */
118
+ warmCutSha?: string;
119
+ warmBaselineSha?: string;
120
+ warmReuseConfig?: WarmReuseSwitchConfig;
121
+ /** At most three cut isolation files may request a shadow replay per verdict. */
122
+ warmEligible?: boolean;
123
+ /** An explicitly excluded file cannot race for the shared replay budget. */
124
+ warmReplayOwner?: boolean;
125
+ /** Internal budget shared by every recheck in one gate verdict, including timeout retries. */
126
+ warmShadowScope?: { requested: boolean; attempts?: number };
127
+ /** Internal shadow wait cap; a stuck provider cannot hold the Job verdict (tests may lower it). */
128
+ warmShadowTimeoutMs?: number;
79
129
  /** Maximum concurrent file-isolation fallback Jobs across the entire sweep; omitted = 4. */
80
130
  isolationConcurrency?: number;
81
131
  /** GATE-SKIP-KNOWN-RETRY — failure ids the previous release already had (and env-known ones): a shard that fails only
@@ -124,6 +174,16 @@ export interface GateOptions {
124
174
  /** GATE-WARM-BOOT-SCAN — observation only: the sweep's test selection (list · filter · duration plan · shard plan) time. */
125
175
  onSelectionTimed?: (timed: { ms: number; files: number }) => void };
126
176
  }
177
+
178
+ /** Gate-profile opt-in wires the producer only for the process entry, never a test runner seam. */
179
+ export function withWarmGateHooks(opts: GateOptions, config: unknown, injectedRunner = false): GateOptions {
180
+ if (injectedRunner || !opts.pod || resolveWarmPoolProfile(config) !== 'gate') return opts;
181
+ const warmReuseConfig = config && typeof config === 'object' && 'pod' in config
182
+ && config.pod && typeof config.pod === 'object' && 'warmReuse' in config.pod
183
+ ? config.pod.warmReuse as WarmReuseSwitchConfig : undefined;
184
+ return { ...opts, pod: { ...opts.pod, warmReplay: requestWarmPodReplay,
185
+ warmFinalize: finalizeWarmPodReplay, warmQuarantine: quarantineWarmPodReplay, warmReuseConfig } };
186
+ }
127
187
  export interface GateResult {
128
188
  outcome: 'ok' | 'regression' | 'error';
129
189
  commit: string;
@@ -137,6 +197,8 @@ export interface GateResult {
137
197
  rcNonzeroNoFailures: Array<{ file: string; rc: number; pass: number; baseline: 'same' | 'clean' | 'missing' }>;
138
198
  /** Files left unmeasured after isolated recheck; these cannot seed a reusable baseline. */
139
199
  unmeasuredRecheckFiles?: string[];
200
+ /** Shadow only: Job verdict is unchanged even on a worker mismatch or failed quarantine. */
201
+ warmShadow?: WarmShadowObservation[];
140
202
  /** GATE-POD-STALL-FAILFAST ① — cut sweep shard Pods ended by the mid-run abort (their files went the timeout way). */
141
203
  abortedShards?: number;
142
204
  baselineSource?: 'ledger' | 'instance' | 'cut-logs' | 'swept';
@@ -149,6 +211,10 @@ export interface GateResult {
149
211
  selectionMs?: number;
150
212
  error?: string;
151
213
  stalledShards?: Array<{ shard: number; files: string[]; reason: 'incomplete' | 'no-output' | 'job-failed' | 'exit-code' | 'unattributed' | 'timeout'; lastFile?: string; summaryFailures?: number; namedFailures?: number; detail?: string;
214
+ /** Pool member that ran the final unmeasured attempt; enables a bounded cross-host tail remeasure. */
215
+ lastHost?: string;
216
+ /** Failure of the optional cross-host tail attempt; original stall fields remain authoritative. */
217
+ tailRemeasure?: { reason: string; host?: string; detail?: string };
152
218
  /** GATE-POD-STALL-FAILFAST — the shard (and its one fresh retry) printed no test output within the stall window: unmeasured. */
153
219
  stall?: { seconds: number; sinceCheckoutMs: number } }>;
154
220
  partialSummary?: { pass: number; fail: number; errors: number; ran: number; files: number };
@@ -169,6 +235,124 @@ const versionPattern = /^\d+\.\d+\.\d+(?:-(?:rc|alpha|beta)\.\d+)?$/;
169
235
  const failureFile = (root: string, version: string) => join(root, 'release', version, 'gate-failures.json');
170
236
  /** Judged identities live beside the machine release ledger; gate-failures.json keeps its raw baseline schema. */
171
237
  const verdictFile = (root: string, version: string) => join(root, 'release', version, 'gate-verdict.json');
238
+ const WARM_VERDICT_HISTORY_LIMIT = 5;
239
+ const WARM_REPLAY_BUDGET = 3;
240
+ interface WarmVerdictSample { version: string; commit: string; baselineCommit: string; warmShadow: WarmShadowObservation[] }
241
+ const recordValue = (value: unknown): Record<string, unknown> | null =>
242
+ value !== null && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : null;
243
+ /** The verdict is atomically replaced; carry a bounded prior-verdict window in that same SSOT. */
244
+ function readWarmVerdictHistory(path: string): WarmVerdictSample[] | undefined {
245
+ if (!existsSync(path)) return [];
246
+ try {
247
+ const current = recordValue(JSON.parse(readFileSync(path, 'utf8')));
248
+ if (!current || !Array.isArray(current.warmHistory)) return undefined;
249
+ const history = [...current.warmHistory, { version: current.version, commit: current.commit,
250
+ baselineCommit: current.baselineCommit, warmShadow: current.warmShadow }].slice(-WARM_VERDICT_HISTORY_LIMIT);
251
+ if (history.some((entry) => { const row = recordValue(entry);
252
+ return !row || typeof row.version !== 'string' || typeof row.commit !== 'string'
253
+ || typeof row.baselineCommit !== 'string' || !Array.isArray(row.warmShadow); })) return undefined;
254
+ return history as WarmVerdictSample[];
255
+ } catch { return undefined; }
256
+ }
257
+ /** Every counted match has a measured, finalized worker with this exact cut, baseline and lock. */
258
+ export function warmEvidenceFromVerdicts(history: readonly WarmVerdictSample[] | undefined,
259
+ version: string, head: string, baseline: string, lock: string): WarmReuseEvidence | undefined {
260
+ if (!history?.length || !lock) return undefined;
261
+ const rows = history.flatMap((verdict) => {
262
+ if (verdict.version !== version || verdict.commit !== head || verdict.baselineCommit !== baseline
263
+ || !Array.isArray(verdict.warmShadow)) return [null];
264
+ return verdict.warmShadow.length ? verdict.warmShadow : [null];
265
+ });
266
+ const attempts = new Set<string>();
267
+ const key = rows.at(-1)?.workerKey;
268
+ if (rows.some((entry) => {
269
+ if (entry === null || !recordValue(entry) || !Array.isArray(entry.files) || !recordValue(entry.workerKey)) return true;
270
+ if (typeof entry.attemptId !== 'string' || !entry.attemptId || attempts.has(entry.attemptId)
271
+ || entry.files.length !== 1 || typeof entry.files[0] !== 'string'
272
+ || !/^(?:[\w.-]+\/)*[\w.-]+\.test\.[cm]?[jt]sx?$/.test(entry.files[0])
273
+ || entry.files[0].split('/').some((part) => part === '.' || part === '..')) return true;
274
+ attempts.add(entry.attemptId);
275
+ return entry.cutSha !== head || entry.baselineSha !== baseline || entry.workerKey?.lockHash !== lock
276
+ || typeof entry.workerKey.context !== 'string' || !entry.workerKey.context
277
+ || typeof entry.workerKey.imageDigest !== 'string' || !entry.workerKey.imageDigest
278
+ || typeof entry.workerKey.architecture !== 'string' || !entry.workerKey.architecture
279
+ || entry.workerKey.context !== key?.context || entry.workerKey.imageDigest !== key?.imageDigest
280
+ || entry.workerKey.architecture !== key?.architecture
281
+ || (entry.reason === 'match' && (entry.status !== 'observed' || entry.finalized !== true || entry.quarantined !== 'no'));
282
+ })) return undefined;
283
+ const recentMismatchCount = rows.filter((entry) => entry!.reason !== 'match').length;
284
+ let consecutiveMatchSamples = 0;
285
+ for (const row of [...rows].reverse()) {
286
+ if (row!.status !== 'observed' || row!.reason !== 'match' || row!.finalized !== true) break;
287
+ consecutiveMatchSamples++;
288
+ }
289
+ const latest = rows.at(-1)!;
290
+ return { consecutiveMatchSamples, recentMismatchCount,
291
+ leaseValid: latest!.finalized === true,
292
+ canaryPassed: latest!.status === 'observed' && latest!.reason === 'match' && latest!.finalized === true,
293
+ // A release cut is the pinned main commit; baselineSha is checked separately above.
294
+ expected: { head, main: head, lock }, worker: { head: latest!.cutSha, main: latest!.cutSha, lock: latest!.workerKey!.lockHash } };
295
+ }
296
+ /** Live reuse is serial and opt-in. Any uncertain lease, reply, or cleanup returns to the fresh Job. */
297
+ async function tryWarmGateAdoption(file: string, cutSha: string, baselineSha: string, expectedKey: WarmWorkerKey,
298
+ pod: NonNullable<GateOptions['pod']>): Promise<CommandResult | undefined> {
299
+ if (!pod.warmReplay || !pod.warmFinalize || !pod.warmQuarantine || !pod.warmShadowScope
300
+ || (pod.warmShadowScope.attempts ?? 0) >= WARM_REPLAY_BUDGET) return undefined;
301
+ pod.warmShadowScope.attempts = (pod.warmShadowScope.attempts ?? 0) + 1;
302
+ pod.warmShadowScope.requested = true;
303
+ const waitMs = pod.warmShadowTimeoutMs ?? 150_000;
304
+ const deadline = Date.now() + waitMs;
305
+ const request = { protocol: 1 as const, runId: randomUUID(), attemptId: randomUUID(), cutSha,
306
+ baselineSha, deadlineAt: deadline - 1_000, files: [file], envProfile: 'gate-v1' };
307
+ let command: WarmWorkerCommand | null = null;
308
+ let timedOut = false;
309
+ const bounded = async <T>(operation: () => Promise<T>): Promise<T> => {
310
+ const remainingMs = deadline - Date.now();
311
+ if (remainingMs <= 0) throw new Error('warm adoption timeout');
312
+ let timer: ReturnType<typeof setTimeout> | undefined;
313
+ try { return await Promise.race([Promise.resolve().then(operation), new Promise<never>((_, reject) => {
314
+ timer = setTimeout(() => reject(new Error('warm adoption timeout')), remainingMs);
315
+ })]); } finally { if (timer) clearTimeout(timer); }
316
+ };
317
+ try {
318
+ const pending = Promise.resolve().then(() => pod.warmReplay!(request));
319
+ void pending.then((late) => {
320
+ if (timedOut && late.command) void pod.warmQuarantine!(late.command, 'late-adoption-after-timeout')
321
+ .catch((error) => debug.log('release-loop.gate', 'warm-adoption-late-quarantine-failed', { file, error: String(error) }));
322
+ }, () => undefined);
323
+ let replay: WarmReplayResult;
324
+ try { replay = await bounded(() => pending); }
325
+ catch (error) { timedOut = true; throw error; }
326
+ command = replay.command;
327
+ if (!command || replay.transport.status !== 'observed' || replay.transport.reason !== 'complete'
328
+ || !warmCommandMatchesRequest(command, request) || command.key.context !== expectedKey.context
329
+ || command.key.imageDigest !== expectedKey.imageDigest || command.key.lockHash !== expectedKey.lockHash
330
+ || command.key.architecture !== expectedKey.architecture) throw new Error('warm adoption lease or identity');
331
+ const reply = parseWarmWorkerReply(replay.transport.reply);
332
+ const part = reply?.files[0];
333
+ if (!reply || !part || reply.files.length !== 1 || part.file !== file || part.rc !== 0 || part.junitComplete !== true
334
+ || part.failureIds.length !== 0 || part.errorIds.length !== 0
335
+ || reply.runId !== command.runId || reply.attemptId !== command.attemptId || reply.cutSha !== cutSha
336
+ || reply.baselineSha !== baselineSha || reply.leaseId !== command.leaseId || reply.generation !== command.generation
337
+ || reply.workerId !== command.workerId || reply.envProfile !== command.envProfile
338
+ || reply.key.context !== command.key.context || reply.key.imageDigest !== command.key.imageDigest
339
+ || reply.key.lockHash !== command.key.lockHash || reply.key.architecture !== command.key.architecture
340
+ || reply.finishedAt < reply.startedAt || reply.finishedAt > command.deadlineAt) throw new Error('warm adoption result');
341
+ await bounded(() => pod.warmFinalize!(command!));
342
+ debug.log('release-loop.gate', 'warm-adopted-recheck', { file, cutSha, attemptId: command.attemptId });
343
+ return { rc: 0, output: `${file}:\n(pass) warm isolated recheck\n1 pass\n0 fail\nRan 1 test across 1 file.\n` };
344
+ } catch (error) {
345
+ if (command) {
346
+ let quarantineTimer: ReturnType<typeof setTimeout> | undefined;
347
+ try { await Promise.race([pod.warmQuarantine!(command, 'adoption-fallback'),
348
+ new Promise<never>((_, reject) => { quarantineTimer = setTimeout(() => reject(new Error('warm quarantine timeout')), 10_000); })]); }
349
+ catch (fenceError) { debug.log('release-loop.gate', 'warm-adoption-quarantine-failed', { file, error: String(fenceError) }); }
350
+ finally { if (quarantineTimer) clearTimeout(quarantineTimer); }
351
+ }
352
+ debug.log('release-loop.gate', 'warm-adoption-fresh-job', { file, reason: String(error) });
353
+ return undefined;
354
+ }
355
+ }
172
356
  const quote = (s: string) => `'${s.replaceAll("'", "'\\''")}'`;
173
357
  const defaultRemoteMirror = '~/mirror/elanous-agent.git';
174
358
 
@@ -958,6 +1142,16 @@ class StalledPodShards extends Error {
958
1142
  }
959
1143
  }
960
1144
 
1145
+ class WarmRecheckIncomplete extends Error {
1146
+ constructor(message: string, readonly file: string, readonly shadow: WarmShadowObservation) { super(message); }
1147
+ }
1148
+
1149
+ class SettledRecheckFailures extends Error {
1150
+ constructor(readonly failures: unknown[]) {
1151
+ super(failures[0] instanceof Error ? failures[0].message : String(failures[0]));
1152
+ }
1153
+ }
1154
+
961
1155
  /** Isolated re-runs (host stall re-run · cut and baseline re-checks) are time-limited; sweeps and installs keep their full time. */
962
1156
  export const GATE_ISOLATED_TIMEOUT_MS = 300_000;
963
1157
  /** Default final recheck window size; overflow continues in later windows. */
@@ -1212,6 +1406,7 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
1212
1406
  let abortedShards = 0;
1213
1407
  // GATE-MULTI-HOST: members found unreachable during this gate — skipped for placement while any other member is left.
1214
1408
  const downMembers = new Set<string>();
1409
+ let tailPlacedHost: string | undefined;
1215
1410
  const multiHost = poolScheduler.members.length > 1;
1216
1411
  const probeMember = pod.probeMember ?? (poolOverride ? undefined : defaultGateMemberProbe);
1217
1412
  const memberUnreadableSeconds = pod.memberUnreadableSeconds ?? GATE_MEMBER_UNREADABLE_SECONDS_DEFAULT;
@@ -1287,12 +1482,13 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
1287
1482
  /** The gate's scheduler, seen through one shard: a slot wait and the Pod start become row states. */
1288
1483
  // GATE-MEM-ADMIT: `placed` releases the shard's admission once a member is chosen — from then on the Job is
1289
1484
  // Pending/Running in the cluster and the next admission reading counts it, so holding the lease would count it twice.
1290
- const trackedPool = (shard: number, retry: boolean, placed: (member: NonNullable<Awaited<ReturnType<PodPoolScheduler['tryAcquire']>>>) => void = () => {}): PodPoolScheduler => ({
1485
+ const trackedPool = (shard: number, retry: boolean, placed: (member: NonNullable<Awaited<ReturnType<PodPoolScheduler['tryAcquire']>>>) => void = () => {}, skipMembers: ReadonlySet<string> = new Set()): PodPoolScheduler => ({
1291
1486
  members: poolScheduler.members,
1292
1487
  tryAcquire: async (...[skip, onEmpty, mode]: Parameters<PodPoolScheduler['tryAcquire']>) => {
1293
1488
  // GATE-MULTI-HOST: a lost member is skipped while another member is left (all lost = the old behaviour).
1294
1489
  const anyLeft = poolScheduler.members.some((m) => !downMembers.has(m.context));
1295
- const member = await poolScheduler.tryAcquire(downMembers.size && anyLeft ? new Set([...(skip ?? []), ...downMembers]) : skip, onEmpty, mode);
1490
+ const excluded = new Set([...(skip ?? []), ...skipMembers, ...(downMembers.size && anyLeft ? downMembers : [])]);
1491
+ const member = await poolScheduler.tryAcquire(excluded.size ? excluded : undefined, onEmpty, mode);
1296
1492
  if (member) placed(member);
1297
1493
  if (member) markShard(shard, { state: retry ? 'retry' : 'running', startedAt: board[shard]?.startedAt ?? new Date().toISOString(), host: member.context }, ['waitReason']);
1298
1494
  else markShard(shard, { waitReason: 'Pod 자리 대기' });
@@ -1300,7 +1496,7 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
1300
1496
  },
1301
1497
  release: (member: Parameters<PodPoolScheduler['release']>[0]) => poolScheduler.release(member),
1302
1498
  }) as unknown as PodPoolScheduler;
1303
- const runShard = async (paths: string[], shard: number, depth = 0, branch = '', retriedNoOutput = false, fresh = pod.freshShards === true, parentAttempt?: string, hostRetries = 0, batchIsolation = false): Promise<{ runs: ShardRun[]; stalled: NonNullable<GateResult['stalledShards']> }> => {
1499
+ const runShard = async (paths: string[], shard: number, depth = 0, branch = '', retriedNoOutput = false, fresh = pod.freshShards === true, parentAttempt?: string, hostRetries = 0, batchIsolation = false, skipMembers: ReadonlySet<string> = new Set(), singleShotTail = false): Promise<{ runs: ShardRun[]; stalled: NonNullable<GateResult['stalledShards']> }> => {
1304
1500
  // 파일별 소요는 junit 으로 남긴다 — 콘솔 요약(판정 원천)은 그대로이고, 느린 시험 목록(K10 D4)·계층 분리(D2)의 자가 된다.
1305
1501
  // GATE-TIMEOUT-HEAL: parts (each with its own junit) under a soft deadline inside this shard's own Job deadline.
1306
1502
  const shardDeadline = shardDeadlineSeconds(batchIsolation ? Math.max(...paths.map((file) => estimated([file]))) * Math.ceil(paths.length / batchWidth) : estimated(paths), deadlineSeconds);
@@ -1356,7 +1552,7 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
1356
1552
  if (descent === 'retry') {
1357
1553
  // «descend» is only the way down; «resumed» comes from each leaf whose log reads cleanly (review r4).
1358
1554
  debug.log('release-loop.gate', 'pod-shard-descend', { shard, branch, files: paths.length, via: 'retry' });
1359
- return runShard(paths, shard, depth, `${branch}-retry`, true, undefined, attemptId, hostRetries, batchIsolation);
1555
+ return runShard(paths, shard, depth, `${branch}-retry`, true, undefined, attemptId, hostRetries, batchIsolation, skipMembers);
1360
1556
  }
1361
1557
  if (descent === 'split') {
1362
1558
  output = undefined;
@@ -1397,7 +1593,10 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
1397
1593
  } : {}) }) : undefined;
1398
1594
  const placed = admitted.release;
1399
1595
  const job = await podCommand({
1400
- pool: pod.pool, poolScheduler: trackedPool(shard, depth > 0 || branch !== '', (member) => { stamp('poolPlaced'); jobContext = member.context; placedOn = member.context; placed(); }), clone: true, source: { kind: 'commit', sha: commit }, deadlineSeconds: shardDeadline,
1596
+ pool: pod.pool, poolScheduler: trackedPool(shard, depth > 0 || branch !== '', (member) => {
1597
+ stamp('poolPlaced'); jobContext = member.context; placedOn = member.context; placed();
1598
+ if (branch === '-tail') tailPlacedHost = member.context;
1599
+ }, skipMembers), clone: true, source: { kind: 'commit', sha: commit }, deadlineSeconds: shardDeadline,
1401
1600
  ...gateHostMirror(pod),
1402
1601
  ...(multiHost ? { statusUnreadableMs: memberUnreadableSeconds * 1000 } : {}),
1403
1602
  ...(bunCache ? { bunCache } : {}),
@@ -1560,14 +1759,14 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
1560
1759
  // A reused result that the current tool cannot read cleanly is not a verdict: run that shard for real.
1561
1760
  if (reused && reason) {
1562
1761
  debug.log('release-loop.gate', 'pod-shard-resume-miss', { shard, branch, reason });
1563
- return runShard(paths, shard, depth, branch, retriedNoOutput, true, parentAttempt, hostRetries, batchIsolation);
1762
+ return runShard(paths, shard, depth, branch, retriedNoOutput, true, parentAttempt, hostRetries, batchIsolation, skipMembers, singleShotTail);
1564
1763
  }
1565
1764
  // GATE-MULTI-HOST: the Job failed because its member is gone (launch failed · status unreadable) — not the shard's
1566
1765
  // fault, so no split: the same files run again on the members left. Bounded by the roster size.
1567
- if (jobFailed && placedOn && hostRetries < poolScheduler.members.length && memberLost(placedOn, shard, jobError)
1766
+ if (!singleShotTail && jobFailed && placedOn && hostRetries < poolScheduler.members.length && memberLost(placedOn, shard, jobError)
1568
1767
  && poolScheduler.members.some((m) => !downMembers.has(m.context))) {
1569
1768
  debug.log('release-loop.gate', 'pod-shard-host-retry', { shard, branch, files: paths.length, lostMember: placedOn, attempt: hostRetries + 1 }, { level: 'warn' });
1570
- return runShard(paths, shard, depth, `${branch}-h${hostRetries}`, retriedNoOutput, undefined, attemptId, hostRetries + 1, batchIsolation);
1769
+ return runShard(paths, shard, depth, `${branch}-h${hostRetries}`, retriedNoOutput, undefined, attemptId, hostRetries + 1, batchIsolation, skipMembers, singleShotTail);
1571
1770
  }
1572
1771
  // GATE-TIMEOUT-HEAL ④: a Pod that died before any test ran (no outbox · jobExitCode≠0 — 0.2.19: isolated retries
1573
1772
  // ending with exit 5, the source checkout) gets the same one fresh retry as «no output» instead of stalling the sweep.
@@ -1575,13 +1774,18 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
1575
1774
  const diedBeforeTests = reason === 'job-failed' && output === undefined && !jobFailed && jobExitCode !== undefined && jobExitCode !== 0
1576
1775
  && jobExitCode !== 137 && (paths.length === 1 || jobExitCode === 5);
1577
1776
  if ((reason === 'no-output' || diedBeforeTests) && !retriedNoOutput) {
1578
- const retry = await runShard(paths, shard, depth, `${branch}-retry`, true, undefined, attemptId, hostRetries, batchIsolation);
1777
+ const retry = await runShard(paths, shard, depth, `${branch}-retry`, true, undefined, attemptId, hostRetries, batchIsolation, skipMembers);
1579
1778
  debug.log('release-loop.gate', 'pod-shard-retry', { shard, files: paths, reason, outcome: retry.stalled[0]?.reason ?? 'ok' });
1580
1779
  return retry;
1581
1780
  }
1582
1781
  // GATE-POD-STALL-FAILFAST: a stall after the fresh retry is unmeasured — no split, no isolation: smaller Pods on the
1583
1782
  // same stalled node only re-measure the stall (10-10: retries and splits repeated it for 2h+).
1584
- if (stall && reason === 'no-output') return { runs: [], stalled: [{ shard, files: paths, reason, stall }] };
1783
+ if (stall && reason === 'no-output') return { runs: [], stalled: [{ shard, files: paths, reason, stall, ...(placedOn ? { lastHost: placedOn } : {}) }] };
1784
+ // GATE-STALLED-TAIL-REMEASURE: the alternate-host probe is a single observation. Do not split, isolate, or
1785
+ // retry its failed result; the sweep retains the original stall and records this attempt separately.
1786
+ if (singleShotTail && reason) return { runs: [], stalled: [{ shard, files: paths, reason,
1787
+ ...(lastFile ? { lastFile } : {}), ...(placedOn ? { lastHost: placedOn } : {}),
1788
+ ...(reason === 'unattributed' ? { summaryFailures: fails, namedFailures, ...(unattributedDetail ? { detail: unattributedDetail } : {}) } : {}) }] };
1585
1789
  // A shared Job may finish some file parts before another part loses its JUnit/log/rc. Keep every complete part's
1586
1790
  // original verdict, including a failure, and rerun only parts without the full artifact trio.
1587
1791
  if (batchIsolation && !batchArtifactsComplete && batchArtifactDir && reason) {
@@ -1714,8 +1918,43 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
1714
1918
  const rejected = settled.find((result): result is PromiseRejectedResult => result.status === 'rejected');
1715
1919
  if (rejected) throw rejected.reason;
1716
1920
  const results = settled.flatMap((result) => result.status === 'fulfilled' ? [result.value] : []);
1717
- const stalledShards = results.flatMap((result) => result.stalled);
1921
+ let stalledShards = results.flatMap((result) => result.stalled);
1718
1922
  const measured = results.flatMap((result) => result.runs);
1923
+ // GATE-STALLED-TAIL-REMEASURE: after the sweep, give only a one-file no-output leaf one fresh attempt on a different
1924
+ // pool member. Keep the original unmeasured evidence if no alternate is available or the remeasure is incomplete.
1925
+ const remainingStalls: typeof stalledShards = [];
1926
+ for (const stalled of stalledShards) {
1927
+ const alternatives = stalled.lastHost
1928
+ ? poolScheduler.members.filter((member) => member.context !== stalled.lastHost && !downMembers.has(member.context))
1929
+ : [];
1930
+ if (!stalled.stall || stalled.reason !== 'no-output' || stalled.files.length !== 1 || !stalled.lastHost || !alternatives.length) {
1931
+ remainingStalls.push(stalled);
1932
+ continue;
1933
+ }
1934
+ debug.log('release-loop.gate', 'pod-stalled-tail-remeasure-start', { shard: stalled.shard, file: stalled.files[0], fromHost: stalled.lastHost,
1935
+ availableHosts: alternatives.map((member) => member.context) });
1936
+ try {
1937
+ tailPlacedHost = undefined;
1938
+ const tail = await runShard(stalled.files, stalled.shard, 0, '-tail', true, true, undefined, 0, false, new Set([stalled.lastHost]), true);
1939
+ if (tail.stalled.length) {
1940
+ const unresolved = tail.stalled[0]!;
1941
+ remainingStalls.push({ ...stalled, tailRemeasure: { reason: unresolved.reason,
1942
+ ...(unresolved.lastHost ?? tailPlacedHost ? { host: unresolved.lastHost ?? tailPlacedHost } : {}),
1943
+ ...(unresolved.detail ? { detail: unresolved.detail } : {}) } });
1944
+ debug.log('release-loop.gate', 'pod-stalled-tail-remeasure-incomplete', { shard: stalled.shard, file: stalled.files[0],
1945
+ fromHost: stalled.lastHost, toHost: unresolved.lastHost ?? null, reason: unresolved.reason }, { level: 'warn' });
1946
+ } else {
1947
+ measured.push(...tail.runs);
1948
+ debug.log('release-loop.gate', 'pod-stalled-tail-remeasure-complete', { shard: stalled.shard, file: stalled.files[0],
1949
+ fromHost: stalled.lastHost, toHost: tailPlacedHost ?? null });
1950
+ }
1951
+ } catch (error) {
1952
+ remainingStalls.push({ ...stalled, tailRemeasure: { reason: 'error', ...(tailPlacedHost ? { host: tailPlacedHost } : {}), detail: String(error).slice(0, 500) } });
1953
+ debug.log('release-loop.gate', 'pod-stalled-tail-remeasure-error', { shard: stalled.shard, file: stalled.files[0],
1954
+ fromHost: stalled.lastHost, error: String(error).slice(0, 500) }, { level: 'warn' });
1955
+ }
1956
+ }
1957
+ stalledShards = remainingStalls;
1719
1958
  const total = measured.reduce((sum, run) => ({ pass: sum.pass + run.pass, fail: sum.fail + run.fail,
1720
1959
  errors: sum.errors + run.errors, ran: sum.ran + run.ran, files: sum.files + run.files }),
1721
1960
  { pass: 0, fail: 0, errors: 0, ran: 0, files: 0 });
@@ -1733,6 +1972,21 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
1733
1972
  ...(abortedShards ? { abortedShards } : {}) };
1734
1973
  };
1735
1974
  const runPodRecheckBatch = async (files: readonly string[], commit: string, pod: NonNullable<GateOptions['pod']>, timeoutMs: number, relaxed = false): Promise<Map<string, CommandResult>> => {
1975
+ const warmShadowScope = pod.warmShadowScope ?? { requested: false };
1976
+ const shadowTimeoutMs = pod.warmShadowTimeoutMs ?? 150_000;
1977
+ if (!Number.isSafeInteger(shadowTimeoutMs) || shadowTimeoutMs < 1 || shadowTimeoutMs > 150_000)
1978
+ throw new Error('invalid warm shadow timeout');
1979
+ let shadowDeadline = 0;
1980
+ const boundedShadow = async <T>(phase: string, operation: () => Promise<T>): Promise<T> => {
1981
+ const remainingMs = shadowDeadline - Date.now();
1982
+ if (remainingMs <= 0) throw new Error(`warm ${phase} timeout after ${shadowTimeoutMs}ms`);
1983
+ let timer: ReturnType<typeof setTimeout> | undefined;
1984
+ try {
1985
+ return await Promise.race([Promise.resolve().then(operation), new Promise<never>((_resolve, reject) => {
1986
+ timer = setTimeout(() => reject(new Error(`warm ${phase} timeout after ${shadowTimeoutMs}ms`)), remainingMs);
1987
+ })]);
1988
+ } finally { if (timer) clearTimeout(timer); }
1989
+ };
1736
1990
  if (!files.length || files.length > GATE_RECHECK_POD_BATCH_FILES || new Set(files).size !== files.length
1737
1991
  || files.some((file) => !/^(?:[\w.-]+\/)*[\w.-]+\.test\.[cm]?[jt]sx?$/.test(file)
1738
1992
  || file.split('/').some((part) => part === '.' || part === '..')))
@@ -1741,11 +1995,12 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
1741
1995
  const installSlots = pod.installSlots ?? POD_INSTALL_SLOTS_DEFAULT;
1742
1996
  if (!Number.isSafeInteger(installSlots) || installSlots < 0) throw new Error('invalid pod recheck install slots');
1743
1997
  const bunCache = pod.bunCache?.trim() || process.env[POD_BUN_CACHE_HOST_PATH]?.trim();
1998
+ const attemptId = randomUUID();
1744
1999
  const partLimitSeconds = Math.ceil(timeoutMs / 1000);
1745
2000
  const deadlineSeconds = partLimitSeconds * Math.ceil(files.length / 2) + POD_SHARD_EMIT_RESERVE_SECONDS + 60;
1746
2001
  const shell = gateShardShell({ parts: files.map((file) => [file]), cdpPatterns: [], softSeconds: softDeadlineSeconds(deadlineSeconds),
1747
2002
  cachePrefix: bunCache ? podBunCacheVolume(bunCache).shellPrefix : '', installSlots, parallelParts: files.length > 1 ? 2 : 1,
1748
- partLimitSeconds, ...(relaxed ? { testTimeoutMs: GATE_TIMEOUT_RECHECK_MS } : {}) });
2003
+ partLimitSeconds, sourceBinding: { cutSha: commit, attemptId }, ...(relaxed ? { testTimeoutMs: GATE_TIMEOUT_RECHECK_MS } : {}) });
1749
2004
  const job = await podCommand({ pool: pod.pool, ...(poolOverride ? { poolScheduler: poolOverride } : {}), clone: true,
1750
2005
  source: { kind: 'commit', sha: commit }, deadlineSeconds, ...(bunCache ? { bunCache } : {}), ...gateHostMirror(pod),
1751
2006
  ...(installSlots > 0 ? { installSlots: POD_INSTALL_SLOTS_HOST_PATH } : {}), ...(pod.cpu ? { cpu: pod.cpu } : {}),
@@ -1755,8 +2010,13 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
1755
2010
  return existsSync(path) ? readFileSync(path, 'utf8') : undefined;
1756
2011
  };
1757
2012
  const installed = artifact('install.rc')?.trim() === '0';
2013
+ const warmEligible = files.length === 1 && pod.warmEligible === true && !!pod.warmReplay && !!pod.warmQuarantine && !!pod.warmFinalize
2014
+ && pod.warmCutSha === commit;
2015
+ const incomplete = (message: string, file: string): Error => warmEligible
2016
+ ? new WarmRecheckIncomplete(message, file, { files: [file], attemptId, status: 'unmeasured', reason: 'reference-unmeasured', quarantined: 'no' })
2017
+ : new Error(message);
1758
2018
  if (!installed || job.exitCode !== 0)
1759
- throw new Error(`pod isolated recheck setup incomplete: ${commit.slice(0, 9)} job=${job.job} exit=${job.exitCode} install=${installed}`);
2019
+ throw incomplete(`pod isolated recheck setup incomplete: ${commit.slice(0, 9)} job=${job.job} exit=${job.exitCode} install=${installed}`, files[0]!);
1760
2020
  const timedOut = artifact('shard.timeout') !== undefined;
1761
2021
  const results = new Map<string, CommandResult>();
1762
2022
  for (const [index, file] of files.entries()) {
@@ -1765,11 +2025,105 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
1765
2025
  const rc = rcText && /^\d+$/.test(rcText) ? Number(rcText) : undefined;
1766
2026
  const partTimedOut = artifact(`part-${index}.timeout`) !== undefined;
1767
2027
  if (rc === undefined || output === undefined || partTimedOut) {
1768
- if (!timedOut && !partTimedOut) throw new Error(`pod isolated recheck incomplete: ${file} @${commit.slice(0, 9)} job=${job.job} rc=${rc ?? 'missing'}`);
2028
+ if (!timedOut && !partTimedOut) throw incomplete(`pod isolated recheck incomplete: ${file} @${commit.slice(0, 9)} job=${job.job} rc=${rc ?? 'missing'}`, file);
1769
2029
  results.set(file, { rc: 124, output: output ?? '', timedOut: true });
1770
- } else results.set(file, { rc, output });
2030
+ } else {
2031
+ // The comparator only accepts a complete JUnit/log pair tied to this exact Job attempt.
2032
+ // A missing or damaged artifact leaves the existing Job verdict authoritative.
2033
+ let warmReference: WarmWorkerFileResult | null = null;
2034
+ if (warmEligible) {
2035
+ try {
2036
+ const prefix = join(job.artifactsDir, `recheck-${index}`);
2037
+ const part = (suffix: string) => join(job.artifactsDir, `part-${index}.${suffix}`);
2038
+ for (const suffix of ['log', 'rc', 'junit.xml']) if (existsSync(part(suffix))) copyFileSync(part(suffix), `${prefix}.${suffix}`);
2039
+ writeFileSync(`${prefix}.json`, JSON.stringify({ commit, attemptId, files: [file], rc,
2040
+ jobFailed: false, jobExitCode: job.exitCode }) + '\n', { mode: 0o600 });
2041
+ if (writeWarmJobManifest(prefix, file, commit, attemptId))
2042
+ warmReference = warmReferenceFromJobArtifacts(prefix, file, { cutSha: commit, attemptId });
2043
+ } catch (error) {
2044
+ debug.log('release-loop.gate', 'warm-reference-unmeasured', { file, attemptId, error: String(error) });
2045
+ }
2046
+ }
2047
+ results.set(file, { rc, output, warmReference, warmAttemptId: attemptId });
2048
+ }
1771
2049
  debug.log('release-loop.gate', 'pod-isolated-recheck', { file, commit, job: job.job, rc: rc ?? 124, relaxed });
1772
2050
  }
2051
+ if (warmEligible && pod.warmReplay && pod.warmQuarantine && pod.warmFinalize) {
2052
+ shadowDeadline = Date.now() + shadowTimeoutMs;
2053
+ const first = results.get(files[0]!)!;
2054
+ const reference = first.warmReference;
2055
+ if (!reference) {
2056
+ first.warmShadow = { files: [...files], attemptId, status: 'unmeasured', reason: 'reference-unmeasured', quarantined: 'no' };
2057
+ debug.log('release-loop.gate', 'warm-shadow-recheck', first.warmShadow);
2058
+ } else if (pod.warmReplayOwner !== false && (warmShadowScope.attempts ?? (warmShadowScope.requested ? 1 : 0)) < WARM_REPLAY_BUDGET) {
2059
+ warmShadowScope.attempts = (warmShadowScope.attempts ?? (warmShadowScope.requested ? 1 : 0)) + 1;
2060
+ warmShadowScope.requested = true;
2061
+ let observation: WarmShadowObservation;
2062
+ try {
2063
+ const request = { protocol: 1 as const, runId: randomUUID(), attemptId, cutSha: commit,
2064
+ ...(pod.warmBaselineSha ? { baselineSha: pod.warmBaselineSha } : {}),
2065
+ deadlineAt: Math.min(Date.now() + timeoutMs * files.length + 60_000, shadowDeadline - 1_000),
2066
+ files, envProfile: 'gate-v1' };
2067
+ // The producer honors deadlineAt. An injected/slow producer may still settle after our wait cap:
2068
+ // fence any late lease before it can be mistaken for a reusable worker.
2069
+ let replayTimedOut = false;
2070
+ const pendingReplay = Promise.resolve().then(() => pod.warmReplay!(request));
2071
+ void pendingReplay.then((late) => {
2072
+ if (replayTimedOut && late.command) void Promise.resolve().then(() => pod.warmQuarantine!(late.command!, 'late-replay-after-timeout'))
2073
+ .catch((error) => debug.log('release-loop.gate', 'warm-shadow-late-quarantine-failed', { attemptId, error: String(error) }));
2074
+ }, (error) => {
2075
+ if (replayTimedOut) debug.log('release-loop.gate', 'warm-shadow-late-replay-failed', { attemptId, error: String(error) });
2076
+ });
2077
+ let replay: WarmReplayResult;
2078
+ try { replay = await boundedShadow('replay', () => pendingReplay); }
2079
+ catch (error) { replayTimedOut = true; throw error; }
2080
+ if (!replay.command) {
2081
+ observation = { files: [...files], attemptId, status: 'fallback', reason: replay.transport.reason, quarantined: 'no' };
2082
+ } else if (replay.transport.status !== 'observed') {
2083
+ let quarantined: WarmShadowObservation['quarantined'] = 'yes';
2084
+ try { await boundedShadow('quarantine', () => pod.warmQuarantine!(replay.command!, `transport-${replay.transport.reason}`)); }
2085
+ catch (error) {
2086
+ quarantined = 'failed';
2087
+ debug.log('release-loop.gate', 'warm-shadow-quarantine-failed', { attemptId,
2088
+ reason: replay.transport.reason, error: String(error) });
2089
+ }
2090
+ observation = { files: [...files], attemptId, status: 'fallback', reason: replay.transport.reason, quarantined };
2091
+ } else {
2092
+ const bound = warmCommandMatchesRequest(replay.command, request);
2093
+ const decision = bound ? assessWarmWorkerShadow(replay.command, [reference], replay.transport.reply)
2094
+ : { reason: 'identity' as const, quarantineWorker: true };
2095
+ let reason: string = decision.reason;
2096
+ let status: WarmShadowObservation['status'] = 'observed';
2097
+ let quarantined: WarmShadowObservation['quarantined'] = 'no';
2098
+ let finalized = false;
2099
+ if (decision.reason === 'match') {
2100
+ try { await boundedShadow('finalize', () => pod.warmFinalize!(replay.command!)); finalized = true; }
2101
+ catch (error) {
2102
+ reason = 'finalize-failed';
2103
+ status = 'fallback';
2104
+ debug.log('release-loop.gate', 'warm-shadow-finalize-failed', { attemptId, error: String(error) });
2105
+ }
2106
+ }
2107
+ if (!finalized) {
2108
+ try { await boundedShadow('quarantine', () => pod.warmQuarantine!(replay.command!, reason)); quarantined = 'yes'; }
2109
+ catch (error) {
2110
+ quarantined = 'failed';
2111
+ debug.log('release-loop.gate', 'warm-shadow-quarantine-failed', { attemptId, reason, error: String(error) });
2112
+ }
2113
+ }
2114
+ observation = { files: [...files], attemptId, status, reason, quarantined, finalized,
2115
+ cutSha: replay.command.cutSha, baselineSha: replay.command.baselineSha, workerKey: replay.command.key };
2116
+ }
2117
+ } catch (error) {
2118
+ observation = { files: [...files], attemptId, status: 'unmeasured', reason: String(error), quarantined: 'unavailable' };
2119
+ }
2120
+ first.warmShadow = observation;
2121
+ debug.log('release-loop.gate', 'warm-shadow-recheck', observation);
2122
+ } else {
2123
+ first.warmShadow = { files: [...files], attemptId, status: 'fallback', reason: 'replay-budget-exhausted', quarantined: 'no' };
2124
+ debug.log('release-loop.gate', 'warm-shadow-recheck', first.warmShadow);
2125
+ }
2126
+ }
1773
2127
  return results;
1774
2128
  };
1775
2129
  return {
@@ -1943,6 +2297,8 @@ export function saveBaselineFileCache(ledger: string, commit: string, rows: Base
1943
2297
 
1944
2298
  export async function judgeGate(opts: GateOptions, runner: GateRunner = createGateRunner(opts.repo ?? process.cwd(), opts.remote)): Promise<GateResult> {
1945
2299
  const start = Date.now();
2300
+ if (opts.pod?.warmReplay) opts = { ...opts, pod: { ...opts.pod,
2301
+ warmShadowScope: { requested: false, attempts: 0 } } };
1946
2302
  const explicitConfig = getElanousConfigDirOverride();
1947
2303
  const root = explicitConfig ? effectiveInstanceRoot() : (opts.instanceRoot ?? effectiveInstanceRoot());
1948
2304
  // The freeze is read from the same root the gate runs in (a config-dir override must not move it elsewhere).
@@ -1969,17 +2325,26 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
1969
2325
  };
1970
2326
  const repo = resolve(opts.repo ?? process.cwd());
1971
2327
  const result: GateResult = { outcome: 'error', commit: opts.commit, introduced: [], preexisting: 0, fixed: 0, knownEnv: 0, knownEnvCleared: [], stalledEnv: [], rcNonzeroNoFailures: [], durationMs: 0 };
2328
+ let baselineCommitForVerdict = opts.baselineCommit ?? '';
1972
2329
  const preexistingIds: string[] = [];
1973
2330
  let knownEnvIds: string[] = [];
1974
2331
  let work: string | undefined;
1975
2332
  let baseSnapshot = false;
1976
2333
  let cutFailures: SweepFailures | undefined;
2334
+ let cutSweepUsable = false;
2335
+ let cutSweepRc: number | null = null;
2336
+ let radiusShadow: GateRadiusShadowPlan | undefined;
1977
2337
  const trees: string[] = [];
1978
2338
  try {
1979
2339
  if (!sha.test(opts.commit) || !versionPattern.test(opts.version)
1980
2340
  || (opts.baselineVersion && !versionPattern.test(opts.baselineVersion))
1981
2341
  || (opts.baselineCommit && !sha.test(opts.baselineCommit))) throw new Error('invalid commit or version');
1982
2342
  if (!opts.baselineVersion) throw new Error('previous release version required (--baseline-version)');
2343
+ // Observation only. Failure to plan cannot change the gate selector or verdict.
2344
+ try { radiusShadow = planGateRadiusShadow({ repo, ledgerRoot: ledger, version: opts.version,
2345
+ baselineVersion: opts.baselineVersion, forCommit: opts.commit, instanceRoot: root,
2346
+ expectedBaselineCommit: opts.baselineCommit }); }
2347
+ catch (error) { debug.log('release-loop.gate', 'radius-shadow-unavailable', { reason: String(error) }); }
1983
2348
  if (!Number.isSafeInteger(opts.recheckFileLimit ?? GATE_HOST_RECHECK_FILE_LIMIT) || (opts.recheckFileLimit ?? GATE_HOST_RECHECK_FILE_LIMIT) < 1)
1984
2349
  throw new Error('invalid gate recheck file limit');
1985
2350
  if (opts.pod && (!opts.pod.pool || !Number.isSafeInteger(opts.pod.shards ?? 24) || (opts.pod.shards ?? 24) < 1
@@ -2061,6 +2426,9 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
2061
2426
  // --ledger-root (tests) keeps it in that ledger.
2062
2427
  shardsFile: { path: opts.ledgerRoot ? gateShardsPath(opts.version, opts.ledgerRoot) : gateShardsPath(opts.version), version: opts.version } } : undefined,
2063
2428
  partial?.files);
2429
+ cutSweepRc = cutRun?.rc ?? null;
2430
+ cutSweepUsable = !!cutRun && (cutRun.rc === 0 || cutRun.rc === 1) && !cutRun.timedOut
2431
+ && !cutRun.abortedShards && !cutRun.stalledEnv?.length;
2064
2432
  if (cutRun?.abortedShards) result.abortedShards = cutRun.abortedShards;
2065
2433
  const localStalled = async (run: CommandResult, tree: string, commit: string, label: string) => {
2066
2434
  const stalled = run.stalledEnv ?? [];
@@ -2145,6 +2513,7 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
2145
2513
  if (partial) result.partial = { priorCommit: partial.priorCommit, files: partial.files.length };
2146
2514
  cutFailures = cut;
2147
2515
  const baseSha = opts.baselineCommit ?? baselineReleaseCommit(opts.baselineVersion);
2516
+ baselineCommitForVerdict = baseSha;
2148
2517
  const cached = cachedBaseline(opts.baselineVersion, baseSha);
2149
2518
  const recorded = cached ? undefined : baselineFromCutLogs(join(ledger, 'release', opts.baselineVersion, 'gate-logs', 'cut'), baseSha);
2150
2519
  const trusted = cached?.saved ?? (recorded?.complete ? recorded : undefined);
@@ -2221,6 +2590,29 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
2221
2590
  catch (error) { debug.log('release.gate', 'baseline-cache-write-failed', { commit: row.commit, file: row.file, error: String(error) }); }
2222
2591
  };
2223
2592
  const recheckFiles = [...newFailureFiles].sort();
2593
+ const eligible = recheckFiles.filter((file) => !cutHostFailed.has(file) && !prefetchedCut.has(file));
2594
+ const warmEligibleFiles = new Set(opts.pod?.warmReplay && opts.pod.warmQuarantine && opts.pod.warmFinalize
2595
+ ? eligible.slice(0, WARM_REPLAY_BUDGET) : []);
2596
+ // The source-bound lock comes from the cut commit itself, never from the producer's claim.
2597
+ const history = readWarmVerdictHistory(verdictFile(ledger, opts.version));
2598
+ let warmLockHash = '';
2599
+ if (opts.pod?.warmReuseConfig?.gateRecheck && history?.length) {
2600
+ try {
2601
+ const lock = await runner.localCommand('git', ['show', `${opts.commit}:bun.lock`], repo);
2602
+ if (lock.rc === 0 && lock.output) warmLockHash = createHash('sha256').update(lock.output).digest('hex');
2603
+ } catch { /* Unmeasured evidence keeps reuse off. */ }
2604
+ }
2605
+ const warmEvidence = warmEvidenceFromVerdicts(history, opts.version, opts.commit, baseSha, warmLockHash);
2606
+ const warmDecision = decideWarmReuse('gate-recheck', opts.pod?.warmReuseConfig,
2607
+ warmEvidence ? { 'gate-recheck': warmEvidence } : undefined);
2608
+ const verifiedWorkerKey = history?.at(-1)?.warmShadow.at(-1)?.workerKey;
2609
+ if (warmDecision.live && verifiedWorkerKey && opts.pod && runner.recheckPod) {
2610
+ // Only the first two files may adopt; replay them serially before scheduling fresh fallbacks.
2611
+ for (const file of eligible.slice(0, 2)) {
2612
+ const adopted = await tryWarmGateAdoption(file, opts.commit, baseSha, verifiedWorkerKey, opts.pod);
2613
+ if (adopted) prefetchedCut.set(file, adopted);
2614
+ }
2615
+ }
2224
2616
  const recheckLimit = opts.recheckFileLimit ?? GATE_HOST_RECHECK_FILE_LIMIT;
2225
2617
  const recheckConcurrency = opts.pod?.isolationConcurrency ?? 4;
2226
2618
  if (opts.pod && (!Number.isSafeInteger(recheckConcurrency) || recheckConcurrency < 1)) throw new Error('invalid pod recheck concurrency');
@@ -2230,13 +2622,19 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
2230
2622
  concurrency: opts.pod && runner.recheckPod ? recheckConcurrency : 1, executor: opts.pod && runner.recheckPod ? 'pod' : 'host',
2231
2623
  remote: opts.remote ?? null, podBatchFiles: opts.pod && runner.recheckPod && runner.recheckPodBatch ? GATE_RECHECK_POD_BATCH_FILES : 1 });
2232
2624
  const recheckStartedAt = Date.now();
2625
+ const recordWarmShadow = (shadow?: WarmShadowObservation) => {
2626
+ if (shadow && !result.warmShadow?.some((entry) => entry.attemptId === shadow.attemptId))
2627
+ (result.warmShadow ??= []).push(shadow);
2628
+ };
2233
2629
  const recheckFile = async (file: string, index: number) => {
2234
2630
  debug.log('release-loop.gate', 'isolated-recheck-progress',
2235
2631
  { index: index + 1, total: recheckFiles.length, file, elapsedSec: Math.round((Date.now() - recheckStartedAt) / 1000) });
2236
2632
  // 0.2.15: the cut re-check of a new-failure file (InsidePage.test.tsx · headless Chrome) hung for good — limit it like the host re-run.
2237
2633
  const cutCheck = cutHostFailed.has(file) ? undefined : prefetchedCut.get(file) ?? (opts.pod && runner.recheckPod
2238
- ? await runner.recheckPod(file, opts.commit, opts.pod, GATE_ISOLATED_TIMEOUT_MS)
2634
+ ? await runner.recheckPod(file, opts.commit, { ...opts.pod, warmCutSha: opts.commit, warmBaselineSha: baseSha,
2635
+ warmEligible: warmEligibleFiles.has(file) }, GATE_ISOLATED_TIMEOUT_MS)
2239
2636
  : await runner.command('bun', ['run', 'test:deterministic', asPath(file)], cutTree, GATE_ISOLATED_TIMEOUT_MS));
2637
+ recordWarmShadow(cutCheck?.warmShadow);
2240
2638
  if (cutCheck?.timedOut) {
2241
2639
  debug.log('release-loop.gate', 'isolated-timeout', { file, label: 'cut isolated', limitMs: GATE_ISOLATED_TIMEOUT_MS });
2242
2640
  result.stalledEnv.push({ file, reason: 'isolated-timeout', local: 'timeout' });
@@ -2258,8 +2656,10 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
2258
2656
  const shaped = candidates.filter((id) => timedOut.has(id) && isolatedCut.failures.includes(id));
2259
2657
  if (shaped.length) {
2260
2658
  const relaxed = opts.pod && runner.recheckPod
2261
- ? await runner.recheckPod(file, opts.commit, opts.pod, GATE_ISOLATED_TIMEOUT_MS, true)
2659
+ ? await runner.recheckPod(file, opts.commit, { ...opts.pod, warmCutSha: opts.commit, warmBaselineSha: baseSha,
2660
+ warmEligible: warmEligibleFiles.has(file) }, GATE_ISOLATED_TIMEOUT_MS, true)
2262
2661
  : await runner.command('bun', ['run', 'test:deterministic', asPath(file), '--timeout', String(GATE_TIMEOUT_RECHECK_MS)], cutTree, GATE_ISOLATED_TIMEOUT_MS);
2662
+ recordWarmShadow(relaxed.warmShadow);
2263
2663
  let flaky: string[] = [];
2264
2664
  let recheck: 'read' | 'timeout' | 'unreadable' = 'read';
2265
2665
  if (relaxed.timedOut) recheck = 'timeout';
@@ -2329,20 +2729,51 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
2329
2729
  const cap = opts.pod && runner.recheckPod ? recheckConcurrency : 1;
2330
2730
  const settleStarted = async <T>(jobs: Promise<T>[]): Promise<T[]> => {
2331
2731
  const settled = await Promise.allSettled(jobs);
2332
- const rejected = settled.find((item): item is PromiseRejectedResult => item.status === 'rejected');
2333
- if (rejected) throw rejected.reason;
2732
+ const rejected = settled.filter((item): item is PromiseRejectedResult => item.status === 'rejected');
2733
+ if (rejected.length) throw new SettledRecheckFailures(rejected.map((item) => item.reason));
2334
2734
  return settled.map((item) => (item as PromiseFulfilledResult<T>).value);
2335
2735
  };
2736
+ // Always attempt both eligible references before a failed Job can end the verdict. A cap of one
2737
+ // still runs the second Job, and successes retain their shadow if a peer throws.
2738
+ if (warmEligibleFiles.size && opts.pod && runner.recheckPod) {
2739
+ const completed = new Map<string, CommandResult>();
2740
+ const failures: unknown[] = [];
2741
+ const warmFiles = [...warmEligibleFiles].filter((file) => !prefetchedCut.has(file));
2742
+ for (let start = 0; start < warmFiles.length; start += cap) {
2743
+ const settled = await Promise.allSettled(warmFiles.slice(start, start + cap).map(async (file) => {
2744
+ const pod = { ...opts.pod!, warmCutSha: opts.commit, warmBaselineSha: baseSha, warmEligible: true,
2745
+ warmReplayOwner: true };
2746
+ return runner.recheckPodBatch
2747
+ ? (await runner.recheckPodBatch([file], opts.commit, pod, GATE_ISOLATED_TIMEOUT_MS)).get(file)
2748
+ : await runner.recheckPod!(file, opts.commit, pod, GATE_ISOLATED_TIMEOUT_MS);
2749
+ }));
2750
+ for (const [index, item] of settled.entries()) {
2751
+ if (item.status === 'rejected') failures.push(item.reason);
2752
+ else if (item.value) completed.set(warmFiles[start + index]!, item.value);
2753
+ else failures.push(new Error(`pod isolated recheck missing result: ${warmFiles[start + index]}`));
2754
+ }
2755
+ }
2756
+ for (const [file, run] of completed) {
2757
+ recordWarmShadow(run.warmShadow);
2758
+ prefetchedCut.set(file, run);
2759
+ }
2760
+ if (failures.length) throw new SettledRecheckFailures(failures);
2761
+ }
2336
2762
  for (let start = 0; start < recheckFiles.length; start += recheckLimit) {
2337
2763
  const window = recheckFiles.slice(start, start + recheckLimit);
2338
2764
  debug.log('release-loop.gate', 'isolated-recheck-window', { start, files: window.length, total: recheckFiles.length });
2339
2765
  if (opts.pod && runner.recheckPod && runner.recheckPodBatch) {
2340
2766
  const pending = window.filter((file) => !cutHostFailed.has(file) && !prefetchedCut.has(file));
2341
- const groups = Array.from({ length: Math.ceil(pending.length / GATE_RECHECK_POD_BATCH_FILES) }, (_, index) =>
2342
- pending.slice(index * GATE_RECHECK_POD_BATCH_FILES, (index + 1) * GATE_RECHECK_POD_BATCH_FILES));
2767
+ const warmSingles = pending.filter((file) => warmEligibleFiles.has(file)).length;
2768
+ const groups = [
2769
+ ...pending.slice(0, warmSingles).map((file) => [file]),
2770
+ ...Array.from({ length: Math.ceil((pending.length - warmSingles) / GATE_RECHECK_POD_BATCH_FILES) }, (_, index) =>
2771
+ pending.slice(warmSingles + index * GATE_RECHECK_POD_BATCH_FILES, warmSingles + (index + 1) * GATE_RECHECK_POD_BATCH_FILES)),
2772
+ ];
2343
2773
  for (let next = 0; next < groups.length; next += cap) {
2344
2774
  const batches = await settleStarted(groups.slice(next, next + cap).map((files) =>
2345
- runner.recheckPodBatch!(files, opts.commit, opts.pod!, GATE_ISOLATED_TIMEOUT_MS)));
2775
+ runner.recheckPodBatch!(files, opts.commit, { ...opts.pod!, warmCutSha: opts.commit, warmBaselineSha: baseSha,
2776
+ warmEligible: files.length === 1 && warmEligibleFiles.has(files[0]!) }, GATE_ISOLATED_TIMEOUT_MS)));
2346
2777
  for (const batch of batches) for (const [file, run] of batch) prefetchedCut.set(file, run);
2347
2778
  }
2348
2779
  }
@@ -2375,6 +2806,13 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
2375
2806
  }
2376
2807
  } catch (error) {
2377
2808
  result.error = error instanceof Error ? error.message : String(error);
2809
+ const warmFailures = (error instanceof SettledRecheckFailures ? error.failures : [error])
2810
+ .filter((failure): failure is WarmRecheckIncomplete => failure instanceof WarmRecheckIncomplete);
2811
+ if (warmFailures.length) {
2812
+ (result.warmShadow ??= []).push(...warmFailures.map((failure) => failure.shadow));
2813
+ result.unmeasuredRecheckFiles = [...new Set([...(result.unmeasuredRecheckFiles ?? []),
2814
+ ...warmFailures.map((failure) => failure.file)])].sort();
2815
+ }
2378
2816
  if (error instanceof StalledPodShards) {
2379
2817
  result.stalledShards = error.stalledShards;
2380
2818
  result.partialSummary = error.partialSummary;
@@ -2416,6 +2854,7 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
2416
2854
  }
2417
2855
  }
2418
2856
  {
2857
+ const baselineCommit = baselineCommitForVerdict;
2419
2858
  const ids = {
2420
2859
  introduced: [...result.introduced].sort(),
2421
2860
  knownEnv: [...knownEnvIds].sort(),
@@ -2425,7 +2864,8 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
2425
2864
  ...(result.unmeasuredErrors?.length ? { unmeasuredErrors: result.unmeasuredErrors } : {}),
2426
2865
  ...(result.interferenceErrors?.length ? { interferenceErrors: [...result.interferenceErrors].sort() } : {}),
2427
2866
  };
2428
- const observed = result.selectionMs !== undefined ? { selectionMs: result.selectionMs } : {};
2867
+ const observed = { ...(result.selectionMs !== undefined ? { selectionMs: result.selectionMs } : {}),
2868
+ ...(result.warmShadow?.length ? { warmShadow: result.warmShadow } : {}) };
2429
2869
  try {
2430
2870
  for (const location of roots) {
2431
2871
  const path = verdictFile(location, opts.version);
@@ -2436,7 +2876,10 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
2436
2876
  mkdirSync(dirname(path), { recursive: true });
2437
2877
  const temp = join(dirname(path), `.gate-verdict-${process.pid}-${randomUUID()}.tmp`);
2438
2878
  try {
2439
- writeFileSync(temp, JSON.stringify({ version: opts.version, commit: opts.commit, outcome: result.outcome, ...ids, ...observed }, null, 2) + '\n', { mode: 0o600 });
2879
+ const warmHistory = readWarmVerdictHistory(path) ?? [];
2880
+ writeFileSync(temp, JSON.stringify({ version: opts.version, commit: opts.commit,
2881
+ outcome: result.outcome, ...ids, ...observed,
2882
+ ...(opts.pod?.warmReplay ? { baselineCommit, warmHistory } : {}) }, null, 2) + '\n', { mode: 0o600 });
2440
2883
  chmodSync(temp, 0o600);
2441
2884
  renameSync(temp, path);
2442
2885
  } finally { if (existsSync(temp)) rmSync(temp); }
@@ -2449,7 +2892,10 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
2449
2892
  mkdirSync(dirname(path), { recursive: true });
2450
2893
  const temp = join(dirname(path), `.gate-verdict-${process.pid}-${randomUUID()}.tmp`);
2451
2894
  try {
2452
- writeFileSync(temp, JSON.stringify({ version: opts.version, commit: opts.commit, outcome: result.outcome, ...ids, ...observed }, null, 2) + '\n', { mode: 0o600 });
2895
+ const warmHistory = readWarmVerdictHistory(path) ?? [];
2896
+ writeFileSync(temp, JSON.stringify({ version: opts.version, commit: opts.commit,
2897
+ outcome: result.outcome, ...ids, ...observed,
2898
+ ...(opts.pod?.warmReplay ? { baselineCommit, warmHistory } : {}) }, null, 2) + '\n', { mode: 0o600 });
2453
2899
  chmodSync(temp, 0o600);
2454
2900
  renameSync(temp, path);
2455
2901
  } finally { if (existsSync(temp)) rmSync(temp); }
@@ -2465,6 +2911,20 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
2465
2911
  ...(result.outcome === 'error' ? { error: result.error } : {}),
2466
2912
  });
2467
2913
  }
2914
+ if (radiusShadow) {
2915
+ try {
2916
+ const record = { plan: radiusShadow, recordedAt: new Date().toISOString(),
2917
+ actual: shadowActual({ plan: radiusShadow, outcome: result.outcome,
2918
+ introduced: result.introduced, cutFailures: cutFailures?.failures ?? [], cutErrors: cutFailures?.errors ?? [], cutRc: cutSweepRc,
2919
+ fullSweep: cutSweepUsable && !result.partial && !!cutFailures && result.outcome !== 'error'
2920
+ && !result.rcNonzeroNoFailures.length && !result.stalledShards?.length
2921
+ && !result.stalledEnv.length && !result.unmeasuredRecheckFiles?.length
2922
+ && !result.unmeasuredErrors?.length }) };
2923
+ const path = writeGateRadiusShadow(ledger, record);
2924
+ debug.log('release-loop.gate', 'radius-shadow-recorded', { path, proposed: radiusShadow.proposed,
2925
+ missedIntroduced: record.actual.missedIntroduced.length });
2926
+ } catch (error) { debug.log('release-loop.gate', 'radius-shadow-unavailable', { reason: String(error) }); }
2927
+ }
2468
2928
  // GATE-LIVE-OBS-RC-WORDING ⓒ — the shards board says «판정 전» until this lands; then «새 실패 N» only (UX · gate-shards.ts).
2469
2929
  // Same ledger as the board's own writes (sweep's shardsFile). A write failure is logged and never changes the verdict.
2470
2930
  if (result.outcome !== 'error' && opts.pod) {
@@ -2606,13 +3066,20 @@ export function parseOptions(args: string[], env: NodeJS.ProcessEnv): GateOption
2606
3066
 
2607
3067
  /** The node's run: judge, emit the graph result line, and return the process exit code (ok 0 · regression 1 · else 2).
2608
3068
  * `runner` is a seam for tests; the process entry passes none. */
2609
- export async function runGateNode(argv: string[], env: NodeJS.ProcessEnv, runner?: GateRunner): Promise<number> {
3069
+ export async function runGateNode(argv: string[], env: NodeJS.ProcessEnv, runner?: GateRunner,
3070
+ judge: (opts: GateOptions, runner?: GateRunner) => Promise<GateResult> = judgeGate): Promise<number> {
2610
3071
  const start = Date.now();
2611
3072
  let result: GateResult;
2612
3073
  let version = '';
2613
3074
  let placement: { executor: string | null; host: string | null } | undefined;
2614
3075
  try {
2615
- const opts = parseOptions(argv, env);
3076
+ const parsed = parseOptions(argv, env);
3077
+ let config: unknown = null;
3078
+ if (parsed !== 'help' && !runner && parsed.pod) {
3079
+ try { config = getUserConfig(); }
3080
+ catch (error) { debug.log('release-loop.gate', 'warm-profile-unavailable', { error: String(error) }); }
3081
+ }
3082
+ const opts = parsed === 'help' ? parsed : withWarmGateHooks(parsed, config, !!runner);
2616
3083
  if (opts !== 'help') {
2617
3084
  placement = gateExecutorOf(opts);
2618
3085
  debug.log('release-loop.gate', 'executor', { version: opts.version, ...placement, requested: opts.executor ?? null });
@@ -2631,7 +3098,7 @@ export async function runGateNode(argv: string[], env: NodeJS.ProcessEnv, runner
2631
3098
  throw new Error('release gate host reservation requires graph runId and pinned state root');
2632
3099
  const releaseHosts = context && hosts.length
2633
3100
  ? reserveReleaseGateHosts(reservationRoot!, context.runId!, hosts) : () => undefined;
2634
- try { result = await (runner ? judgeGate(opts, runner) : judgeGate(opts)); }
3101
+ try { result = await judge(opts, runner); }
2635
3102
  finally { releaseHosts(); }
2636
3103
  } catch (error) {
2637
3104
  result = { outcome: 'error', commit: '', introduced: [], preexisting: 0, fixed: 0, knownEnv: 0, knownEnvCleared: [], stalledEnv: [], rcNonzeroNoFailures: [], durationMs: Date.now() - start, error: String(error) };
@@ -2647,5 +3114,5 @@ if (import.meta.main) {
2647
3114
  // The graph spawns this file as its own process, which inherits no daemon sink — without this every
2648
3115
  // `release-loop.gate`/`release.gate`/`pod.pool` event stayed in the file trail and `elanous logs` read 0 rows.
2649
3116
  await registerStandaloneLogSink('release-loop');
2650
- process.exitCode = await runGateNode(process.argv.slice(2), process.env);
3117
+ process.exitCode = await runWithReleaseGateTraffic(process.env, () => runGateNode(process.argv.slice(2), process.env));
2651
3118
  }