elanous 0.2.41 → 0.2.43

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 (796) 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/{GiOLWmt4MqDZePubBjA4Q → Om5xS9jpjSXJCeTNGYEi4}/_buildManifest.js +1 -1
  4. package/apps/pwa/out/_next/static/chunks/{2fe2c302.41b517f88d6fe502.js → 0966f8a1.745fcd44f64a87ea.js} +1 -1
  5. package/apps/pwa/out/_next/static/chunks/{385e4574.e9886ab32759eda3.js → 0ad6802e.ae7afb63fdcb4491.js} +1 -1
  6. package/apps/pwa/out/_next/static/chunks/114.2b90424d1a3ce84c.js +1 -0
  7. package/apps/pwa/out/_next/static/chunks/1151-5c2bb8f002ca2dd6.js +1 -0
  8. package/apps/pwa/out/_next/static/chunks/1200.6a37c202f9f17e9a.js +226 -0
  9. package/apps/pwa/out/_next/static/chunks/1261-a616d5728e02e624.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/1313-19f737e0222c4444.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/1357.3bbef3976c37f94a.js +1 -0
  12. package/apps/pwa/out/_next/static/chunks/1362-20adddfaf662b0fa.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/{6874.d5cf7eb8edb2cabe.js → 1406.a1dc2ab6f8449374.js} +2 -2
  14. package/apps/pwa/out/_next/static/chunks/1737-9491fc6ab29b9ec6.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/1803-4f91a8e7dcaf042c.js +1 -0
  16. package/apps/pwa/out/_next/static/chunks/{5744.b9415e18ad623100.js → 1961.a81b12872c3e93a9.js} +1 -1
  17. package/apps/pwa/out/_next/static/chunks/1970cf59.2d4d8ce5d6c81853.js +1 -0
  18. package/apps/pwa/out/_next/static/chunks/1971-8890f3962ba7ee0a.js +1 -0
  19. package/apps/pwa/out/_next/static/chunks/2073-ba274e00e4f9efb4.js +1 -0
  20. package/apps/pwa/out/_next/static/chunks/{5471.1e611302ed3a7c53.js → 2144.25b292396712196d.js} +2 -2
  21. package/apps/pwa/out/_next/static/chunks/{3230-49298c3b73ef2268.js → 2268-53861f339e0b65a2.js} +1 -1
  22. package/apps/pwa/out/_next/static/chunks/{9834.63ab2bbc3153172e.js → 2312.0c8b61e22d3136d7.js} +1 -1
  23. package/apps/pwa/out/_next/static/chunks/{1800.f7ff51dc4978a958.js → 2573.387a707f6f9d720c.js} +2 -2
  24. package/apps/pwa/out/_next/static/chunks/273.f156eee28f11af77.js +1 -0
  25. package/apps/pwa/out/_next/static/chunks/2873.d2140e5a82546401.js +1 -0
  26. package/apps/pwa/out/_next/static/chunks/{9048.212083de42d25be3.js → 3072.01338851ca21a6f7.js} +2 -2
  27. package/apps/pwa/out/_next/static/chunks/3091.a5a4143dea33d197.js +90 -0
  28. package/apps/pwa/out/_next/static/chunks/3095.dc44586a5f83b86d.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/{2263.e09b6edea33bad2d.js → 3113.d7472bbc27d580c7.js} +1 -1
  30. package/apps/pwa/out/_next/static/chunks/3228-9650b6447b6fa288.js +1 -0
  31. package/apps/pwa/out/_next/static/chunks/3402.44b565b314f2263c.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/{addc2b32.8c4ff8f0aefe987d.js → 3557ac4a.c8931684475018ad.js} +1 -1
  33. package/apps/pwa/out/_next/static/chunks/360.ae9f9f4c33c395e8.js +79 -0
  34. package/apps/pwa/out/_next/static/chunks/3638.80412ea63aae8f3f.js +286 -0
  35. package/apps/pwa/out/_next/static/chunks/{a0a01613.6d1cca322238d7fe.js → 363bfab3.9623b9f89363541f.js} +1 -1
  36. package/apps/pwa/out/_next/static/chunks/3694.e0099884345f0198.js +64 -0
  37. package/apps/pwa/out/_next/static/chunks/3744.54f87db52acfd1ab.js +1 -0
  38. package/apps/pwa/out/_next/static/chunks/3903.3ef4656c67572764.js +1 -0
  39. package/apps/pwa/out/_next/static/chunks/3935.16701e33f8982dc9.js +1 -0
  40. package/apps/pwa/out/_next/static/chunks/3955.6922286237f2ac2b.js +24 -0
  41. package/apps/pwa/out/_next/static/chunks/3969.272ee7f9fd6c62e5.js +34 -0
  42. package/apps/pwa/out/_next/static/chunks/4199.ce86b281fd0a60b7.js +1 -0
  43. package/apps/pwa/out/_next/static/chunks/4311-6dd3144002cc0c2d.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/4426-2b230fad00c88930.js +1 -0
  45. package/apps/pwa/out/_next/static/chunks/{530.d26346e18089640a.js → 4445.7990c76b3751a918.js} +1 -1
  46. package/apps/pwa/out/_next/static/chunks/4447-14c5161fa59166c7.js +1 -0
  47. package/apps/pwa/out/_next/static/chunks/4856-b0b8075bba8c53f4.js +1 -0
  48. package/apps/pwa/out/_next/static/chunks/4870-de370d65e73c0bb5.js +1 -0
  49. package/apps/pwa/out/_next/static/chunks/4885-2c989165c4b29fad.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/4894.fec2ca9e99caefa2.js +1 -0
  51. package/apps/pwa/out/_next/static/chunks/5093.8f04b7f8cb899a95.js +94 -0
  52. package/apps/pwa/out/_next/static/chunks/5122-2bb177cebf89e19a.js +1 -0
  53. package/apps/pwa/out/_next/static/chunks/5154.5f56dc3902e3d275.js +36 -0
  54. package/apps/pwa/out/_next/static/chunks/529.64ff7257180b2ac9.js +1 -0
  55. package/apps/pwa/out/_next/static/chunks/5394.5dabeb0c62293a28.js +93 -0
  56. package/apps/pwa/out/_next/static/chunks/5461.08bf2404ff38ec85.js +1 -0
  57. package/apps/pwa/out/_next/static/chunks/5474-22e08a85583209b2.js +1 -0
  58. package/apps/pwa/out/_next/static/chunks/{1219.7e17964e0e7cd8e5.js → 5564.9e72532188f57586.js} +3 -3
  59. package/apps/pwa/out/_next/static/chunks/5567-265df39ed8fe9dcb.js +1 -0
  60. package/apps/pwa/out/_next/static/chunks/5570-a94ae19811cb2046.js +1 -0
  61. package/apps/pwa/out/_next/static/chunks/5574-b47c9ef2dceff1fd.js +1 -0
  62. package/apps/pwa/out/_next/static/chunks/5628.6ddb2d5ffba4ff4c.js +309 -0
  63. package/apps/pwa/out/_next/static/chunks/5730.43d7ccc09988e646.js +1 -0
  64. package/apps/pwa/out/_next/static/chunks/{1178.1aa9cad0e7695427.js → 5789.2da35c258292e1e5.js} +1 -1
  65. package/apps/pwa/out/_next/static/chunks/{4133.2056bb4d78f621af.js → 5824.da9527b15a3221e5.js} +1 -1
  66. package/apps/pwa/out/_next/static/chunks/5866.366966af47fdde5d.js +1 -0
  67. package/apps/pwa/out/_next/static/chunks/{4527.a69f2894d1bc9f4a.js → 5930.8b4df3b46c32b05a.js} +1 -1
  68. package/apps/pwa/out/_next/static/chunks/5963-c4d8469c52a0828b.js +1 -0
  69. package/apps/pwa/out/_next/static/chunks/6001.ec4049ceb3c43418.js +27 -0
  70. package/apps/pwa/out/_next/static/chunks/6120-471f03db1e299c0c.js +1 -0
  71. package/apps/pwa/out/_next/static/chunks/6175-41479e643aca1878.js +1 -0
  72. package/apps/pwa/out/_next/static/chunks/6291.a2de4f9787637075.js +104 -0
  73. package/apps/pwa/out/_next/static/chunks/6400-6699907c3537436a.js +1 -0
  74. package/apps/pwa/out/_next/static/chunks/6428.1f4327d6ae5fdf9a.js +287 -0
  75. package/apps/pwa/out/_next/static/chunks/644.02cb026fa44bcd0a.js +1 -0
  76. package/apps/pwa/out/_next/static/chunks/6466-6c87d60d3dd174fb.js +1 -0
  77. package/apps/pwa/out/_next/static/chunks/{9507.7026657a2f47eb64.js → 6540.c7bef57ad8f90a87.js} +16 -16
  78. package/apps/pwa/out/_next/static/chunks/6773.d4264665ab31bec0.js +1 -0
  79. package/apps/pwa/out/_next/static/chunks/6822.3c789f1a8443e774.js +1 -0
  80. package/apps/pwa/out/_next/static/chunks/{5574.f35accf4f132b29d.js → 6944.309f4e3ea54d1537.js} +1 -1
  81. package/apps/pwa/out/_next/static/chunks/7261-8ff69d3b2ee671fd.js +1 -0
  82. package/apps/pwa/out/_next/static/chunks/7266.084c6bbc70035140.js +1 -0
  83. package/apps/pwa/out/_next/static/chunks/7271.5c0100e7320aa856.js +1 -0
  84. package/apps/pwa/out/_next/static/chunks/7429-bf5731358c8f2f68.js +1 -0
  85. package/apps/pwa/out/_next/static/chunks/7464.01f7a0ae1215fbe7.js +1 -0
  86. package/apps/pwa/out/_next/static/chunks/7479.0fcad5f37eb64f0c.js +1 -0
  87. package/apps/pwa/out/_next/static/chunks/7483.427a2fe2049e388d.js +148 -0
  88. package/apps/pwa/out/_next/static/chunks/7502-be74718178e13e56.js +1 -0
  89. package/apps/pwa/out/_next/static/chunks/7530.0889722b19572065.js +24 -0
  90. package/apps/pwa/out/_next/static/chunks/7645.5b2ee3ad45c55ea3.js +1 -0
  91. package/apps/pwa/out/_next/static/chunks/7788.7d1c79f1a8e18946.js +1 -0
  92. package/apps/pwa/out/_next/static/chunks/7900.096f455c7c1efc53.js +77 -0
  93. package/apps/pwa/out/_next/static/chunks/7910-6f139bcafd238b9f.js +1 -0
  94. package/apps/pwa/out/_next/static/chunks/7966.f71c8958d1637548.js +1 -0
  95. package/apps/pwa/out/_next/static/chunks/{5947-135e26537d80a6b9.js → 8062-7cd0bcc7f01455ba.js} +3 -3
  96. package/apps/pwa/out/_next/static/chunks/8073.f6cf2f2c71aafbd0.js +1 -0
  97. package/apps/pwa/out/_next/static/chunks/8201.190b94088980f430.js +74 -0
  98. package/apps/pwa/out/_next/static/chunks/8205.282ca4bb62d54a0a.js +62 -0
  99. package/apps/pwa/out/_next/static/chunks/8313-dfa1021bb58a46d6.js +1 -0
  100. package/apps/pwa/out/_next/static/chunks/837-81336ebaca8198a8.js +1 -0
  101. package/apps/pwa/out/_next/static/chunks/8447.7963352ef5f71b76.js +1 -0
  102. package/apps/pwa/out/_next/static/chunks/8574-868e8129525ef6d9.js +1 -0
  103. package/apps/pwa/out/_next/static/chunks/8588.d1c6a2903727ce87.js +149 -0
  104. package/apps/pwa/out/_next/static/chunks/8637.90f92d5c8db4732c.js +1 -0
  105. package/apps/pwa/out/_next/static/chunks/{2743.5d3f76948be32b34.js → 868.6e953be05bcf6dca.js} +2 -2
  106. package/apps/pwa/out/_next/static/chunks/8706-e1fe0309343a2273.js +1 -0
  107. package/apps/pwa/out/_next/static/chunks/8749-06384cdfc4520add.js +1 -0
  108. package/apps/pwa/out/_next/static/chunks/{5964-7e21164a8f2cf0ce.js → 8861-cc1fa1c7b920fa8e.js} +1 -1
  109. package/apps/pwa/out/_next/static/chunks/8967-7dfebf710c9dec9d.js +1 -0
  110. package/apps/pwa/out/_next/static/chunks/{5159.115f1c2ca471386c.js → 8986.8e2e4efbfa655c59.js} +1 -1
  111. package/apps/pwa/out/_next/static/chunks/8997-3c564dbb3b58b055.js +1 -0
  112. package/apps/pwa/out/_next/static/chunks/923.2034d10ae91e9c20.js +1 -0
  113. package/apps/pwa/out/_next/static/chunks/9312.bc1b89c5d4368033.js +1 -0
  114. package/apps/pwa/out/_next/static/chunks/943.39ae5e82f4f1893f.js +1 -0
  115. package/apps/pwa/out/_next/static/chunks/9469.0500480de55fb205.js +32 -0
  116. package/apps/pwa/out/_next/static/chunks/9770.5f75bc6088b16109.js +1 -0
  117. package/apps/pwa/out/_next/static/chunks/9810-2543267050e58b82.js +1 -0
  118. package/apps/pwa/out/_next/static/chunks/9870.8419702475254745.js +8 -0
  119. package/apps/pwa/out/_next/static/chunks/9941-376d1b74d51b01a4.js +1 -0
  120. package/apps/pwa/out/_next/static/chunks/9958.385434ab04241758.js +1 -0
  121. package/apps/pwa/out/_next/static/chunks/{b2271e54.c1de3347976e90b0.js → aa57532f.510551854ff239b1.js} +1 -1
  122. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-deea1450b17cb8dc.js +1 -0
  123. package/apps/pwa/out/_next/static/chunks/app/approvals/page-70985b904f8650ef.js +1 -0
  124. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-a2e15e33e56a4353.js +1 -0
  125. package/apps/pwa/out/_next/static/chunks/app/board/page-fc72ce2db12d6ca9.js +1 -0
  126. package/apps/pwa/out/_next/static/chunks/app/botlab/page-be03e41f9436ebd9.js +1 -0
  127. package/apps/pwa/out/_next/static/chunks/app/bots/page-35a35999382da3f2.js +1 -0
  128. package/apps/pwa/out/_next/static/chunks/app/ceo/page-3ff5b1e6ca88b848.js +1 -0
  129. package/apps/pwa/out/_next/static/chunks/app/chat/page-a65101300a0ee5c1.js +1 -0
  130. package/apps/pwa/out/_next/static/chunks/app/consult/page-3aca5b6d67dba511.js +1 -0
  131. package/apps/pwa/out/_next/static/chunks/app/control/page-45b8ab0a71e61314.js +1 -0
  132. package/apps/pwa/out/_next/static/chunks/app/decisions/page-53046e282d4cda8e.js +1 -0
  133. package/apps/pwa/out/_next/static/chunks/app/design-check/page-17809e60e8354c17.js +1 -0
  134. package/apps/pwa/out/_next/static/chunks/app/editor/page-fd2442d347bf5898.js +1 -0
  135. package/apps/pwa/out/_next/static/chunks/app/exec/page-e9d720d6fc082e21.js +1 -0
  136. package/apps/pwa/out/_next/static/chunks/app/field/page-6b8b4267bab0a677.js +1 -0
  137. package/apps/pwa/out/_next/static/chunks/app/healthz/route-deea1450b17cb8dc.js +1 -0
  138. package/apps/pwa/out/_next/static/chunks/app/inside/page-8bcf6233ac7c17af.js +1 -0
  139. package/apps/pwa/out/_next/static/chunks/app/intake/page-7cc25591cfc2126f.js +1 -0
  140. package/apps/pwa/out/_next/static/chunks/app/layout-053e871f35c9beba.js +1 -0
  141. package/apps/pwa/out/_next/static/chunks/app/live/page-eff819f0c2d5e084.js +1 -0
  142. package/apps/pwa/out/_next/static/chunks/app/live-run/page-4d7e9d8db5274a87.js +1 -0
  143. package/apps/pwa/out/_next/static/chunks/app/loops/page-2b2d8f5aa5f69ad4.js +1 -0
  144. package/apps/pwa/out/_next/static/chunks/app/market/page-0b8f92c5062e68b1.js +1 -0
  145. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-1bd9d0fdac2ced3d.js +1 -0
  146. package/apps/pwa/out/_next/static/chunks/app/morning/page-9ee28f7df07428ea.js +1 -0
  147. package/apps/pwa/out/_next/static/chunks/app/not-found-44b7a1ff468c6648.js +1 -0
  148. package/apps/pwa/out/_next/static/chunks/app/observatory/page-cbc6523711dc1b65.js +1 -0
  149. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-fdad47899c2695be.js +1 -0
  150. package/apps/pwa/out/_next/static/chunks/app/ops/release/{page-40f9e19149024298.js → page-79a8b5fee4c20871.js} +1 -1
  151. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-03e8ac9606aeaffb.js +1 -0
  152. package/apps/pwa/out/_next/static/chunks/app/outputs/page-4cd55c55ec287e2f.js +1 -0
  153. package/apps/pwa/out/_next/static/chunks/app/page-db17b4305404161e.js +1 -0
  154. package/apps/pwa/out/_next/static/chunks/app/reflection/page-30041d848350fd02.js +1 -0
  155. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-c540da022dbf86b0.js +1 -0
  156. package/apps/pwa/out/_next/static/chunks/app/sessions/page-9399efc538da057f.js +1 -0
  157. package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-a142dfb0331a05f5.js → page-ca2d09128c585ca4.js} +1 -1
  158. package/apps/pwa/out/_next/static/chunks/app/settings/page-0e2635aecd343038.js +1 -0
  159. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-ef7f4399b6b7726e.js +1 -0
  160. package/apps/pwa/out/_next/static/chunks/app/setup/layout-deea1450b17cb8dc.js +1 -0
  161. package/apps/pwa/out/_next/static/chunks/app/setup/page-caf530535b875468.js +1 -0
  162. package/apps/pwa/out/_next/static/chunks/app/share/page-eaa20c1543a5fd8e.js +1 -0
  163. package/apps/pwa/out/_next/static/chunks/app/showroom/page-c541eb1b75120f61.js +1 -0
  164. package/apps/pwa/out/_next/static/chunks/app/tasks/page-ab5e213eda9479f4.js +1 -0
  165. package/apps/pwa/out/_next/static/chunks/app/term/page-0a815f34367b6db6.js +1 -0
  166. package/apps/pwa/out/_next/static/chunks/app/today/page-e858ba0ed9393b68.js +1 -0
  167. package/apps/pwa/out/_next/static/chunks/app/trace/page-1638ee9dd6e0582f.js +1 -0
  168. package/apps/pwa/out/_next/static/chunks/app/vault/page-4cf5a399109391f2.js +1 -0
  169. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-b4396321238798ec.js +1 -0
  170. package/apps/pwa/out/_next/static/chunks/app/workflows/page-cb72bfd21006781a.js +1 -0
  171. package/apps/pwa/out/_next/static/chunks/app/workspace/page-6135d0242afbc434.js +1 -0
  172. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-a1dccf907eddb856.js +1 -0
  173. package/apps/pwa/out/_next/static/chunks/{53aeb4ee-51c92f8d683f5220.js → c7945435-b44d4dbe90ba0dcb.js} +1 -1
  174. package/apps/pwa/out/_next/static/chunks/{2fdf2931.b431eee4fb468bc3.js → cb0f8df8.09a4a6be97e3fc5f.js} +1 -1
  175. package/apps/pwa/out/_next/static/chunks/{c0c1a699.0ba79f97005965d4.js → d96660fb.cbc21a2a88539dbb.js} +1 -1
  176. package/apps/pwa/out/_next/static/chunks/ea514caa.05a24f78d9dffe5f.js +122 -0
  177. package/apps/pwa/out/_next/static/chunks/{83184dcd-0ee6247ff41eb3f2.js → fb7d20d5-6892502b6ee39ca6.js} +1 -1
  178. package/apps/pwa/out/_next/static/chunks/framework-e8a911e19b1be41f.js +1 -0
  179. package/apps/pwa/out/_next/static/chunks/main-7022f418fabba021.js +1 -0
  180. package/apps/pwa/out/_next/static/chunks/main-app-e5f15df755756a1e.js +1 -0
  181. package/apps/pwa/out/_next/static/chunks/pages/_app-4525f91e36940c9b.js +1 -0
  182. package/apps/pwa/out/_next/static/chunks/pages/_error-140f0e519c085b0f.js +1 -0
  183. package/apps/pwa/out/_next/static/chunks/webpack-8f22ef63dcb865b1.js +1 -0
  184. package/apps/pwa/out/_next/static/css/{b9d8151e48998417.css → 62c858ad53854f47.css} +1 -1
  185. package/apps/pwa/out/_next/static/css/d1f8bb401740a549.css +1 -0
  186. package/apps/pwa/out/approvals/index.html +1 -1
  187. package/apps/pwa/out/approvals/index.txt +23 -23
  188. package/apps/pwa/out/autopilot/index.html +1 -1
  189. package/apps/pwa/out/autopilot/index.txt +23 -23
  190. package/apps/pwa/out/board/index.html +1 -1
  191. package/apps/pwa/out/board/index.txt +23 -23
  192. package/apps/pwa/out/botlab/index.html +1 -1
  193. package/apps/pwa/out/botlab/index.txt +22 -22
  194. package/apps/pwa/out/bots/index.html +1 -1
  195. package/apps/pwa/out/bots/index.txt +22 -22
  196. package/apps/pwa/out/ceo/index.html +1 -1
  197. package/apps/pwa/out/ceo/index.txt +23 -23
  198. package/apps/pwa/out/chat/index.html +1 -1
  199. package/apps/pwa/out/chat/index.txt +22 -22
  200. package/apps/pwa/out/consult/index.html +1 -1
  201. package/apps/pwa/out/consult/index.txt +23 -23
  202. package/apps/pwa/out/control/index.html +1 -1
  203. package/apps/pwa/out/control/index.txt +23 -23
  204. package/apps/pwa/out/decisions/index.html +1 -1
  205. package/apps/pwa/out/decisions/index.txt +22 -22
  206. package/apps/pwa/out/design-check/index.html +1 -1
  207. package/apps/pwa/out/design-check/index.txt +23 -23
  208. package/apps/pwa/out/editor/index.html +1 -1
  209. package/apps/pwa/out/editor/index.txt +24 -25
  210. package/apps/pwa/out/exec/index.html +1 -1
  211. package/apps/pwa/out/exec/index.txt +23 -23
  212. package/apps/pwa/out/field/index.html +1 -1
  213. package/apps/pwa/out/field/index.txt +23 -23
  214. package/apps/pwa/out/healthz +1 -1
  215. package/apps/pwa/out/index.html +1 -1
  216. package/apps/pwa/out/index.txt +22 -22
  217. package/apps/pwa/out/inside/index.html +1 -1
  218. package/apps/pwa/out/inside/index.txt +23 -23
  219. package/apps/pwa/out/intake/index.html +1 -1
  220. package/apps/pwa/out/intake/index.txt +23 -23
  221. package/apps/pwa/out/live/index.html +1 -1
  222. package/apps/pwa/out/live/index.txt +24 -25
  223. package/apps/pwa/out/live-run/index.html +1 -1
  224. package/apps/pwa/out/live-run/index.txt +24 -25
  225. package/apps/pwa/out/loops/index.html +1 -1
  226. package/apps/pwa/out/loops/index.txt +22 -22
  227. package/apps/pwa/out/market/index.html +1 -1
  228. package/apps/pwa/out/market/index.txt +22 -22
  229. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  230. package/apps/pwa/out/missions/placeholder/index.txt +22 -22
  231. package/apps/pwa/out/morning/index.html +1 -1
  232. package/apps/pwa/out/morning/index.txt +23 -23
  233. package/apps/pwa/out/observatory/index.html +1 -1
  234. package/apps/pwa/out/observatory/index.txt +23 -23
  235. package/apps/pwa/out/ops/checklist/index.html +1 -1
  236. package/apps/pwa/out/ops/checklist/index.txt +22 -22
  237. package/apps/pwa/out/ops/release/index.html +1 -1
  238. package/apps/pwa/out/ops/release/index.txt +22 -22
  239. package/apps/pwa/out/ops/seats/index.html +1 -1
  240. package/apps/pwa/out/ops/seats/index.txt +22 -22
  241. package/apps/pwa/out/outputs/index.html +1 -1
  242. package/apps/pwa/out/outputs/index.txt +22 -22
  243. package/apps/pwa/out/reflection/index.html +1 -1
  244. package/apps/pwa/out/reflection/index.txt +23 -23
  245. package/apps/pwa/out/scheduler/index.html +1 -1
  246. package/apps/pwa/out/scheduler/index.txt +23 -23
  247. package/apps/pwa/out/sessions/index.html +1 -1
  248. package/apps/pwa/out/sessions/index.txt +22 -22
  249. package/apps/pwa/out/settings/devices/index.html +1 -1
  250. package/apps/pwa/out/settings/devices/index.txt +23 -23
  251. package/apps/pwa/out/settings/index.html +1 -1
  252. package/apps/pwa/out/settings/index.txt +23 -23
  253. package/apps/pwa/out/setup/done/index.html +1 -1
  254. package/apps/pwa/out/setup/done/index.txt +23 -23
  255. package/apps/pwa/out/setup/index.html +1 -1
  256. package/apps/pwa/out/setup/index.txt +23 -23
  257. package/apps/pwa/out/share/index.html +1 -1
  258. package/apps/pwa/out/share/index.txt +23 -23
  259. package/apps/pwa/out/showroom/index.html +1 -1
  260. package/apps/pwa/out/showroom/index.txt +22 -22
  261. package/apps/pwa/out/tasks/index.html +1 -1
  262. package/apps/pwa/out/tasks/index.txt +23 -23
  263. package/apps/pwa/out/term/index.html +1 -1
  264. package/apps/pwa/out/term/index.txt +23 -23
  265. package/apps/pwa/out/today/index.html +1 -1
  266. package/apps/pwa/out/today/index.txt +23 -23
  267. package/apps/pwa/out/trace/index.html +1 -1
  268. package/apps/pwa/out/trace/index.txt +23 -23
  269. package/apps/pwa/out/vault/index.html +1 -1
  270. package/apps/pwa/out/vault/index.txt +23 -23
  271. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  272. package/apps/pwa/out/workflows/chat-ui/index.txt +23 -23
  273. package/apps/pwa/out/workflows/index.html +1 -1
  274. package/apps/pwa/out/workflows/index.txt +23 -23
  275. package/apps/pwa/out/workspace/index.html +1 -1
  276. package/apps/pwa/out/workspace/index.txt +23 -23
  277. package/apps/pwa/out/worktrees/index.html +1 -1
  278. package/apps/pwa/out/worktrees/index.txt +23 -23
  279. package/catalog/external-commands.yaml +5 -3
  280. package/catalog/meta/catalogVersion.yaml +2 -1
  281. package/catalog/signing/catalog-signing-public.pem +3 -0
  282. package/docs/goal-context/04-mock-scenarios.md +13 -0
  283. package/graphs/catalog/catalog.yaml +1 -1
  284. package/graphs/heal/heal-loop.yaml +1 -0
  285. package/graphs/intake/intake-daily.yaml +16 -0
  286. package/graphs/intake/news-sources.yaml +1 -0
  287. package/graphs/intake/recipes.yaml +7 -0
  288. package/graphs/overlays/examples/research-runtime-empty-terminal.yaml +28 -0
  289. package/graphs/overlays/stop-heal-ladder.yaml +113 -0
  290. package/graphs/profiles/local.yaml +5 -0
  291. package/graphs/profiles/pod.yaml +4 -0
  292. package/graphs/units/verbs/recipes.yaml +11 -11
  293. package/graphs/work/research-report.yaml +10 -2
  294. package/package.json +6 -2
  295. package/plugins/demo-absorption-briefing/plugin.json +18 -0
  296. package/plugins/demo-absorption-briefing/skills/demo-absorption-briefing/SKILL.md +8 -0
  297. package/plugins/demo-browser-hand/plugin.json +13 -0
  298. package/plugins/demo-browser-hand/skills/demo-browser-hand/SKILL.md +8 -0
  299. package/plugins/demo-linear-secretary/plugin.json +15 -0
  300. package/plugins/demo-linear-secretary/skills/demo-linear-secretary/SKILL.md +8 -0
  301. package/plugins/demo-plugin-wizard/plugin.json +11 -0
  302. package/plugins/demo-plugin-wizard/skills/demo-plugin-wizard/SKILL.md +8 -0
  303. package/plugins/demo-research/plugin.json +16 -0
  304. package/plugins/demo-research/skills/demo-research/SKILL.md +8 -0
  305. package/plugins/demo-video-briefing/plugin.json +15 -0
  306. package/plugins/demo-video-briefing/skills/demo-video-briefing/SKILL.md +8 -0
  307. package/scripts/build-signed-catalog.ts +48 -0
  308. package/scripts/ci-typecheck-main-delta.sh +88 -0
  309. package/scripts/codex-fallback-live-probe.ts +1 -1
  310. package/scripts/demo/demo1104-parallel-scenes.ts +301 -0
  311. package/scripts/demo/demo1104-stage.config.ts +21 -0
  312. package/scripts/demo/demo1104-stage.ts +316 -0
  313. package/scripts/goal-method-audit.ts +17 -1
  314. package/scripts/graph-units/land-enqueue.ts +90 -0
  315. package/scripts/heal/run-role.ts +99 -5
  316. package/scripts/hooks/codex-notify.sh +32 -0
  317. package/scripts/install.sh +10 -38
  318. package/scripts/land-global-checks.ts +6 -1
  319. package/scripts/landing-heal/land-plan.ts +57 -0
  320. package/scripts/lib/decompose-ab.ts +1 -0
  321. package/scripts/lib/fixtures/tui-regress/help-insufficient-commands.txt +15 -0
  322. package/scripts/lib/fixtures/tui-regress/help-live.txt +42 -0
  323. package/scripts/lib/fixtures/tui-regress/help-missing-keys.txt +21 -0
  324. package/scripts/lib/fixtures/tui-regress/help-original.txt +51 -0
  325. package/scripts/lib/fixtures/tui-regress/help-shifted-input.txt +26 -0
  326. package/scripts/lib/tui-regress-checks.ts +33 -13
  327. package/scripts/release-loop/auto-approve-node.ts +1 -1
  328. package/scripts/release-loop/gate-autoheal-sim-evidence.ts +60 -0
  329. package/scripts/release-loop/gate-baseline-receipt.ts +47 -9
  330. package/scripts/release-loop/gate-image-pin.ts +81 -0
  331. package/scripts/release-loop/gate-method.ts +139 -0
  332. package/scripts/release-loop/gate-node-sim.ts +207 -0
  333. package/scripts/release-loop/gate-node.ts +463 -64
  334. package/scripts/release-loop/gate-pod-image-sticky-plan.ts +99 -0
  335. package/scripts/release-loop/gate-radius-live.ts +97 -13
  336. package/scripts/release-loop/gate-shard-dispatch.ts +43 -0
  337. package/scripts/release-loop/gate-traffic-mode.ts +151 -3
  338. package/scripts/release-loop/gate-train-receipts.ts +122 -0
  339. package/scripts/release-loop/node-verdict.ts +38 -25
  340. package/scripts/release-loop/preflight.ts +362 -8
  341. package/scripts/release-loop/resume-release.ts +83 -8
  342. package/scripts/release-loop/unattended-release.ts +20 -9
  343. package/scripts/rhythm/daily.ts +36 -4
  344. package/scripts/rhythm/linear-progress-sync.ts +298 -0
  345. package/scripts/ux-sim.ts +18 -1
  346. package/scripts/video-pipeline.ts +9 -1
  347. package/scripts/warm-pool-provider-config.ts +286 -0
  348. package/skills/omni-crawl/SKILL.md +5 -2
  349. package/skills/omni-crawl/scripts/main.ts +84 -23
  350. package/src/acp/agent-manager.ts +1 -0
  351. package/src/acp/codex-app-server-agent.ts +33 -0
  352. package/src/acp/codex-app-server-client.ts +153 -16
  353. package/src/acp/core-turn-bridge.ts +27 -9
  354. package/src/acp/event-router.ts +20 -1
  355. package/src/acp/server.ts +6 -0
  356. package/src/agent/registry.ts +2 -0
  357. package/src/agent/task-notification.ts +25 -5
  358. package/src/agent/types.ts +6 -0
  359. package/src/agent-mission/browser-roundtrip-coordinator.ts +85 -0
  360. package/src/agent-mission/claude-headless.ts +4 -1
  361. package/src/agent-mission/codex-exec.ts +105 -0
  362. package/src/agent-mission/driver.ts +192 -11
  363. package/src/agent-mission/grok-auth-status.ts +47 -0
  364. package/src/agent-mission/hand-hooks.ts +101 -0
  365. package/src/agent-mission/mission-cli.ts +63 -4
  366. package/src/agent-mission/mission-hand-reply.ts +118 -0
  367. package/src/agent-mission/mission-line.ts +180 -0
  368. package/src/agent-mission/mission-out.ts +833 -0
  369. package/src/agent-mission/tool-install.ts +200 -3
  370. package/src/agent-substrate/context-strategy.ts +4 -4
  371. package/src/agent-substrate/self-review-cli.ts +178 -13
  372. package/src/agents/general-purpose.md +1 -1
  373. package/src/auto-research/research-turn.ts +211 -53
  374. package/src/autopilot/pr-manager.ts +69 -10
  375. package/src/autopilot/pty-control-loop.ts +2 -0
  376. package/src/boot/daemon-tools/self-implement.ts +3 -1
  377. package/src/boot/daemon-tools/web-search.ts +1 -2
  378. package/src/briefing/brief-items.ts +55 -11
  379. package/src/capture/tui-self-report.ts +22 -6
  380. package/src/chat/index.ts +1 -1
  381. package/src/cli/agent-cli.ts +2 -1
  382. package/src/cli/catalog-cli.ts +31 -0
  383. package/src/cli/connector-cli.ts +16 -0
  384. package/src/cli/doctor-fix.ts +2 -2
  385. package/src/cli/fixtures/review-loop-breaker/opwaiver-32646-1694ccc7a170.json +1 -0
  386. package/src/cli/fixtures/review-loop-breaker/opwaiver-32853-bd8f73595c83.json +1 -0
  387. package/src/cli/fixtures/review-loop-breaker/opwaiver-33097-1cc85900413c.json +1 -0
  388. package/src/cli/fixtures/review-loop-breaker/opwaiver-33106-972f31e4a3ca.json +1 -0
  389. package/src/cli/fixtures/review-loop-breaker/opwaiver-33113-3ab51f05968f.json +1 -0
  390. package/src/cli/fixtures/review-loop-breaker/pr-33097-1cc85900413c.json +1 -0
  391. package/src/cli/intake-source-cli.ts +44 -8
  392. package/src/cli/msg-cli.ts +32 -3
  393. package/src/cli/pr-cli.ts +21 -1
  394. package/src/cli/pr-land-heavy-remote.ts +75 -5
  395. package/src/cli/pr-land-overlap-evidence.ts +113 -2
  396. package/src/cli/pr-land-pod-shadow-job.ts +202 -4
  397. package/src/cli/pr-land-pod-shadow.ts +9 -2
  398. package/src/cli/release-checklist-read-cli.ts +57 -19
  399. package/src/cli/release-cli.ts +23 -3
  400. package/src/cli/research-cli.ts +13 -5
  401. package/src/cli/setup-cli.ts +166 -6
  402. package/src/cli/setup-keys.ts +187 -0
  403. package/src/cli/task-plugin-wizard-host.ts +114 -0
  404. package/src/cli/tasks-cli.ts +61 -9
  405. package/src/cli/tui-test-cli.ts +186 -0
  406. package/src/compact/auto.ts +8 -1
  407. package/src/config.ts +4 -2
  408. package/src/connectors/linear-daily-progress.ts +228 -0
  409. package/src/connectors/linear-projector.ts +34 -12
  410. package/src/connectors/linear.ts +151 -7
  411. package/src/control-plane/ledger.ts +52 -2
  412. package/src/control-plane/server.ts +16 -2
  413. package/src/core-turn/deep-turn-bloom.ts +67 -0
  414. package/src/core-turn/research-policy.ts +37 -0
  415. package/src/core-turn/run-core-turn.ts +606 -95
  416. package/src/core-turn/run-goal-loop.ts +18 -0
  417. package/src/core-turn/types.ts +4 -0
  418. package/src/dashboard/chat/acp-chat.ts +10 -0
  419. package/src/dashboard/classic-sidetab-mission-job.ts +161 -0
  420. package/src/dashboard/classic-sidetab-mission-pair.ts +81 -0
  421. package/src/dashboard/classic-sidetab-mission-trio.ts +219 -0
  422. package/src/dashboard/classic-sidetab-mission.ts +347 -0
  423. package/src/dashboard/index.ts +517 -18
  424. package/src/dashboard/input/agent-wake.ts +23 -1
  425. package/src/dashboard/input/chat-main-acp-dispatch.ts +23 -0
  426. package/src/dashboard/input/chat-main-plain-turn-runtime.ts +17 -0
  427. package/src/dashboard/mission-cli-consumer.ts +165 -0
  428. package/src/dashboard/plugin-wizard-runtime.ts +50 -0
  429. package/src/dashboard/plugin-wizard-tool.ts +55 -0
  430. package/src/dashboard/seat-boot-preamble.ts +171 -1
  431. package/src/dashboard/side-basic-runtime.ts +54 -1
  432. package/src/dashboard/sidetab-runtime.ts +90 -1
  433. package/src/dashboard/skill-catalog-preamble.ts +101 -0
  434. package/src/dashboard/skill-detection-runtime.ts +55 -15
  435. package/src/dashboard/slash-runtime/dashboard-handlers.ts +15 -0
  436. package/src/dashboard/terminal-popup-runtime.ts +6 -3
  437. package/src/dashboard/turn-preamble.ts +53 -15
  438. package/src/dashboard/turn-prompt-runtime.ts +4 -2
  439. package/src/decisions/linear-progress-summary.ts +78 -0
  440. package/src/decisions/linear-progress-sync.ts +152 -0
  441. package/src/domains/outbound-alert.ts +49 -11
  442. package/src/domains/telegram-kind-route.ts +16 -9
  443. package/src/graph-runner/arg-allowance.ts +13 -0
  444. package/src/graph-runner/graph-cli.ts +23 -7
  445. package/src/graph-runner/graph-run-api.ts +3 -1
  446. package/src/graph-runner/graph-tick.ts +10 -2
  447. package/src/graph-runner/runner.ts +86 -18
  448. package/src/graph-wizard/mission-compose.ts +407 -12
  449. package/src/grok/agent-search.ts +14 -4
  450. package/src/grok/credential.ts +28 -11
  451. package/src/harness/dispatch-backpressure.ts +54 -1
  452. package/src/harness/dispatch-shadow-compare.ts +74 -0
  453. package/src/harness/harness-cli-command.ts +11 -1
  454. package/src/harness/harness-pod-dispatch.ts +81 -15
  455. package/src/harness/harness-stop.ts +155 -11
  456. package/src/harness/skill-exec.ts +21 -9
  457. package/src/harness/skill-select.ts +1 -1
  458. package/src/hooks/error-report-cloud.ts +101 -0
  459. package/src/hooks/receiver.ts +29 -2
  460. package/src/index.ts +159 -13
  461. package/src/instance/spawn-elanous.ts +6 -4
  462. package/src/intake-plane/actions.ts +354 -0
  463. package/src/intake-plane/adapters/telegram-ambient.ts +34 -18
  464. package/src/intake-plane/intake-sources.ts +78 -8
  465. package/src/intake-plane/items.ts +14 -4
  466. package/src/intake-plane/linear-mission-sync.ts +80 -0
  467. package/src/intake-plane/linear-secretary-runtime.ts +178 -0
  468. package/src/intake-plane/linear-wish.ts +227 -1
  469. package/src/life/life-cli.ts +168 -0
  470. package/src/life/life-kinds.ts +160 -0
  471. package/src/live-supply/budget-consumer.ts +78 -0
  472. package/src/live-supply/read.ts +3 -0
  473. package/src/live-supply/snapshot.ts +45 -0
  474. package/src/llm/provider-credentials.ts +8 -2
  475. package/src/llm.ts +168 -9
  476. package/src/loops/orchestrator/coordinator-shadow.ts +226 -0
  477. package/src/loops/orchestrator/tick.ts +13 -0
  478. package/src/maturity/feature-maturity.ts +4 -4
  479. package/src/msg/msg-sse-server.ts +3 -3
  480. package/src/msg/msg-store.ts +177 -19
  481. package/src/native-tool-catalog.ts +38 -4
  482. package/src/nexus/api/harness-traversal.ts +11 -0
  483. package/src/nexus/api/http-server.ts +37 -1
  484. package/src/nexus/api/mcp-public-allowlist.ts +2 -0
  485. package/src/nexus/index.ts +7 -2
  486. package/src/nexus/outbound/format.ts +9 -0
  487. package/src/nexus/outbound/router.ts +98 -13
  488. package/src/nexus/setup-status.ts +9 -0
  489. package/src/nexus/state/state.ts +3 -0
  490. package/src/oauth/codex-account-store.ts +20 -7
  491. package/src/oauth/codex-owner-mirror.ts +44 -0
  492. package/src/oauth/codex.ts +17 -2
  493. package/src/oauth/fallback-chain.ts +47 -17
  494. package/src/onboarding/first-run-doctor.ts +8 -1
  495. package/src/onboarding/first-run.ts +85 -26
  496. package/src/onboarding/key-setup.ts +103 -0
  497. package/src/onboarding/menu-answer.ts +13 -0
  498. package/src/onboarding/secret-prompt.ts +37 -0
  499. package/src/plugins/install/plugin-install.ts +59 -0
  500. package/src/pr-tool/census.ts +133 -2
  501. package/src/pty-shell/pty-control-ipc.ts +4 -1
  502. package/src/pty-shell/tui-run-state.ts +188 -2
  503. package/src/registry/catalog-server.ts +232 -0
  504. package/src/registry/loader.ts +45 -1
  505. package/src/registry/types.ts +1 -2
  506. package/src/release-loop/checklist-target-path-hint.ts +13 -0
  507. package/src/release-loop/checklist.ts +9 -1
  508. package/src/repl/index.ts +43 -2
  509. package/src/research/axis-fanout.ts +136 -16
  510. package/src/research/bloom-exemplars.ts +3 -1
  511. package/src/research/gap-followup.ts +13 -1
  512. package/src/research/plugin-discovery.ts +36 -14
  513. package/src/research-bridge/invoke.ts +22 -5
  514. package/src/research-bridge/types.ts +2 -0
  515. package/src/self-dev/ask-launch-flow.ts +72 -1
  516. package/src/self-dev/decompose.ts +119 -5
  517. package/src/self-dev/dev-cli.ts +4 -1
  518. package/src/self-dev/dev-pipeline.ts +5 -0
  519. package/src/self-dev/draft-residue.ts +64 -5
  520. package/src/self-dev/graph-front-nodes.ts +2 -1
  521. package/src/self-dev/graph-journey-route.ts +76 -21
  522. package/src/self-dev/launch-preflight-text.ts +5 -6
  523. package/src/self-dev/launch-preflight.ts +58 -0
  524. package/src/self-dev/orchestrate.ts +13 -3
  525. package/src/self-dev/review-provider-fallback.ts +9 -2
  526. package/src/self-dev/run-supervisor.ts +42 -4
  527. package/src/self-dev/run-types.ts +4 -0
  528. package/src/self-dev/shard-aggregate.ts +4 -0
  529. package/src/self-dev/shard-run.ts +36 -15
  530. package/src/self-implement/abandoned-classification.ts +12 -3
  531. package/src/self-implement/budget-gate.ts +7 -0
  532. package/src/self-implement/child-llm-preference.ts +10 -1
  533. package/src/self-implement/doctor-visit.ts +118 -8
  534. package/src/self-implement/elanous-tui-spawn.ts +3 -0
  535. package/src/self-implement/fixtures/tdd-green-missing-passing-child-transcript.txt +2 -0
  536. package/src/self-implement/gate-artifacts.ts +2 -1
  537. package/src/self-implement/goal-author-cli.ts +4 -2
  538. package/src/self-implement/goal-author.ts +684 -7
  539. package/src/self-implement/graph-overlay-yaml.ts +37 -4
  540. package/src/self-implement/graph-overlay.ts +3 -1
  541. package/src/self-implement/graph-profile-yaml.ts +107 -0
  542. package/src/self-implement/graph-yaml.ts +26 -3
  543. package/src/self-implement/headless-elanous-driver.ts +31 -2
  544. package/src/self-implement/heal-intake.ts +112 -4
  545. package/src/self-implement/heal-triage.ts +20 -0
  546. package/src/self-implement/host-regate.ts +27 -4
  547. package/src/self-implement/merge-train.ts +5 -0
  548. package/src/self-implement/off-diff-evidence.ts +249 -5
  549. package/src/self-implement/orchestrator.ts +365 -25
  550. package/src/self-implement/regate-detail.ts +38 -1
  551. package/src/self-implement/rework-child-provider.ts +25 -0
  552. package/src/self-implement/run-chart.ts +122 -0
  553. package/src/self-implement/run-ledger.ts +162 -11
  554. package/src/self-implement/run-stop.ts +188 -4
  555. package/src/self-implement/runtime-overlay-run-steps.ts +95 -0
  556. package/src/self-implement/stop-autoheal.ts +13 -0
  557. package/src/self-implement/tdd-evidence-tags.ts +9 -0
  558. package/src/self-implement/train-gate-receipt.ts +166 -0
  559. package/src/self-implement/train-gate-remote.ts +62 -18
  560. package/src/session/chat.ts +40 -4
  561. package/src/session/session-fanout.ts +2 -0
  562. package/src/session/streaming/acp-stream-sink.ts +2 -1
  563. package/src/session/streaming/chunk-producer.ts +6 -0
  564. package/src/session/streaming/stream-compositor.ts +3 -2
  565. package/src/session-runtime/index.ts +135 -5
  566. package/src/session-runtime/retry-policy.ts +1 -1
  567. package/src/session-runtime/tier-flip.ts +3 -3
  568. package/src/setup/llm-env-detect.ts +3 -0
  569. package/src/setup/service-key-catalog.ts +46 -0
  570. package/src/setup/service-keys.ts +241 -0
  571. package/src/sidetab-core/model.ts +19 -5
  572. package/src/sidetab-core/visible-agent-mission.ts +262 -0
  573. package/src/skills/name-match.ts +20 -0
  574. package/src/skills/tools/agent.ts +53 -6
  575. package/src/skills/tools/omni-search.ts +173 -13
  576. package/src/task-agent/parent-host.ts +10 -0
  577. package/src/task-agent/pod-distributor-entry.ts +3 -0
  578. package/src/task-agent/repair-outcomes.ts +65 -0
  579. package/src/task-agent/task-hand.ts +47 -7
  580. package/src/task-orchestrator/dispatcher.ts +14 -1
  581. package/src/task-orchestrator/linear-capture-events.ts +55 -0
  582. package/src/task-orchestrator/store.ts +16 -5
  583. package/src/task-orchestrator/surfaces/pod-artifact-return.ts +4 -0
  584. package/src/task-orchestrator/surfaces/pod-distributor.ts +48 -0
  585. package/src/task-orchestrator/surfaces/pod-failure-reason.ts +92 -1
  586. package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +38 -1
  587. package/src/task-orchestrator/surfaces/pod-ledger-events.ts +113 -14
  588. package/src/task-orchestrator/surfaces/pod-si-warm-entry.ts +33 -6
  589. package/src/task-orchestrator/surfaces/pod-source-bundle.ts +45 -9
  590. package/src/task-orchestrator/surfaces/pod-source-receive.ts +178 -16
  591. package/src/task-orchestrator/surfaces/pod-source-spec.ts +3 -3
  592. package/src/task-orchestrator/surfaces/pod-warm-switch.ts +2 -0
  593. package/src/task-orchestrator/surfaces/pod-warm-worker-provider.ts +237 -41
  594. package/src/task-orchestrator/surfaces/pod-warm-worker-runtime.ts +80 -3
  595. package/src/task-orchestrator/surfaces/self-implement-pod.ts +337 -51
  596. package/src/task-orchestrator/surfaces/self-implement.ts +27 -3
  597. package/src/task-orchestrator/surfaces/warm-reuse-evidence.ts +169 -0
  598. package/src/task-orchestrator/types.ts +14 -0
  599. package/src/telegram.ts +14 -16
  600. package/src/terminal/coding-agent.ts +14 -5
  601. package/src/terminal/session-registry.ts +1 -1
  602. package/src/tokens.ts +35 -2
  603. package/src/tool-hints/gate.ts +1 -0
  604. package/src/tool-runtime/agent-runtime.ts +2 -0
  605. package/src/tool-runtime/browser-hand-runtime.ts +381 -0
  606. package/src/tool-runtime/browser-runtime.ts +2 -1
  607. package/src/tool-runtime/elanous-linear-issue-runtime.ts +76 -0
  608. package/src/tool-runtime/elanous-linear-secretary-runtime.ts +57 -0
  609. package/src/tool-runtime/elanous-skills-list-runtime.ts +5 -1
  610. package/src/tool-runtime/graph-mcp-runtimes.ts +240 -0
  611. package/src/tool-runtime/index.ts +6 -0
  612. package/src/tool-runtime/skill-exec-runtime.ts +33 -13
  613. package/src/tool-runtime/types.ts +4 -0
  614. package/src/tui/entry.ts +38 -5
  615. package/src/tui/sidetab/attach.ts +177 -1
  616. package/src/tui/sidetab/herdr-portal.ts +225 -0
  617. package/src/tui-client/local-chat-turn.ts +26 -13
  618. package/src/tui.ts +22 -0
  619. package/src/user-config.ts +34 -9
  620. package/src/user-intent/types.ts +4 -3
  621. package/src/ux-sim/browser-hand.ts +165 -0
  622. package/src/ux-sim/draft-inventory-builder.ts +198 -0
  623. package/src/ux-sim/fake-browser-page.ts +81 -0
  624. package/src/ux-sim/fake-kubectl.ts +37 -3
  625. package/src/ux-sim/fake-linear-device.ts +31 -0
  626. package/src/ux-sim/fault-pool-cap.ts +11 -6
  627. package/src/ux-sim/graph-engine-sim.ts +47 -0
  628. package/src/ux-sim/linear-secretary.ts +216 -0
  629. package/src/ux-sim/mock-scenario-catalog.json +93 -0
  630. package/src/ux-sim/mock-scenario-catalog.ts +274 -0
  631. package/src/ux-sim/onboarding-first-run-device.ts +31 -21
  632. package/src/ux-sim/scenario.ts +185 -48
  633. package/src/ux-sim/service-key-setup.ts +50 -0
  634. package/src/ux-sim/term-slash-device.ts +74 -0
  635. package/src/ux-sim/turn-throw-sim.ts +31 -0
  636. package/src/version/packed-revision.json +1 -1
  637. package/src/voice/voice-tts-singleton.ts +18 -3
  638. package/src/web-search/exa.ts +77 -0
  639. package/src/web-search/grok.ts +5 -9
  640. package/src/web-search/index.ts +30 -9
  641. package/src/web-search/provider.ts +4 -1
  642. package/src/web-search/x-intent.ts +5 -0
  643. package/apps/pwa/out/_next/static/chunks/1115.434ae2ecb0ad986d.js +0 -149
  644. package/apps/pwa/out/_next/static/chunks/1121.667cec0bcdbb3fdb.js +0 -36
  645. package/apps/pwa/out/_next/static/chunks/1197-54da59d250a6e476.js +0 -1
  646. package/apps/pwa/out/_next/static/chunks/1245.1cfd13b26dbd71d6.js +0 -287
  647. package/apps/pwa/out/_next/static/chunks/1494-f93046ff5c5a124d.js +0 -1
  648. package/apps/pwa/out/_next/static/chunks/1609.2b312797599b8e45.js +0 -1
  649. package/apps/pwa/out/_next/static/chunks/1661.0dd330f3d89f73f3.js +0 -1
  650. package/apps/pwa/out/_next/static/chunks/167-0c121e4b47e0f9da.js +0 -1
  651. package/apps/pwa/out/_next/static/chunks/1700.cdc2bf8cc685e221.js +0 -90
  652. package/apps/pwa/out/_next/static/chunks/1703-78e46acad29f3cfc.js +0 -1
  653. package/apps/pwa/out/_next/static/chunks/1765.679b29625c8e88d4.js +0 -77
  654. package/apps/pwa/out/_next/static/chunks/1971-7efb628c471dc5a0.js +0 -1
  655. package/apps/pwa/out/_next/static/chunks/1999-8de7b7bb36801913.js +0 -1
  656. package/apps/pwa/out/_next/static/chunks/2073.689dcb2680113d76.js +0 -1
  657. package/apps/pwa/out/_next/static/chunks/2198-557d49184104d165.js +0 -1
  658. package/apps/pwa/out/_next/static/chunks/221-440ce67fa9f5c839.js +0 -1
  659. package/apps/pwa/out/_next/static/chunks/2427-8ed8e98624cbd664.js +0 -1
  660. package/apps/pwa/out/_next/static/chunks/2457-725c5827bc003795.js +0 -1
  661. package/apps/pwa/out/_next/static/chunks/2491-2718b15a5ccb75a5.js +0 -1
  662. package/apps/pwa/out/_next/static/chunks/2527-5c636a43c7cc5dde.js +0 -1
  663. package/apps/pwa/out/_next/static/chunks/2692-1027cb16fe4ccf49.js +0 -1
  664. package/apps/pwa/out/_next/static/chunks/2773.0e80b9d325fb5635.js +0 -32
  665. package/apps/pwa/out/_next/static/chunks/2873-0f3cb56c8e3528ea.js +0 -1
  666. package/apps/pwa/out/_next/static/chunks/3097.864e92bdaa2f4d15.js +0 -79
  667. package/apps/pwa/out/_next/static/chunks/3136-d6870ba63300a93e.js +0 -1
  668. package/apps/pwa/out/_next/static/chunks/3173-0289113d434e83b7.js +0 -1
  669. package/apps/pwa/out/_next/static/chunks/325-dd9208a1a0ab7ce4.js +0 -1
  670. package/apps/pwa/out/_next/static/chunks/3283.528f430dd043fb4e.js +0 -74
  671. package/apps/pwa/out/_next/static/chunks/3295.cc8cb13b8f0dae1a.js +0 -1
  672. package/apps/pwa/out/_next/static/chunks/3688-86e3b88abf18cb3b.js +0 -1
  673. package/apps/pwa/out/_next/static/chunks/3804-7220467a5473a8df.js +0 -1
  674. package/apps/pwa/out/_next/static/chunks/3808.3e1e247ce7bdf889.js +0 -1
  675. package/apps/pwa/out/_next/static/chunks/3927.b5473ad109da44c5.js +0 -1
  676. package/apps/pwa/out/_next/static/chunks/3934-98ea94832e194796.js +0 -1
  677. package/apps/pwa/out/_next/static/chunks/4156.ba4de4a3664f04b4.js +0 -1
  678. package/apps/pwa/out/_next/static/chunks/4297-239ca82f293faab0.js +0 -1
  679. package/apps/pwa/out/_next/static/chunks/4320-dcc5a7734126f4c9.js +0 -1
  680. package/apps/pwa/out/_next/static/chunks/4434.94b4c552a2d91e69.js +0 -148
  681. package/apps/pwa/out/_next/static/chunks/4579.03e4092f73950965.js +0 -226
  682. package/apps/pwa/out/_next/static/chunks/4667.97e31aa38c95c475.js +0 -309
  683. package/apps/pwa/out/_next/static/chunks/4702.4a4adc1f16d7c93b.js +0 -64
  684. package/apps/pwa/out/_next/static/chunks/4815.481501c57b3568d9.js +0 -1
  685. package/apps/pwa/out/_next/static/chunks/487-d16e698ef14662d9.js +0 -1
  686. package/apps/pwa/out/_next/static/chunks/4888-7a38bf522c9cc6fe.js +0 -1
  687. package/apps/pwa/out/_next/static/chunks/5034-30e429d6bb086881.js +0 -1
  688. package/apps/pwa/out/_next/static/chunks/5040-35c494e60dadd97d.js +0 -1
  689. package/apps/pwa/out/_next/static/chunks/50c6ab1e.81a96e419350909d.js +0 -1
  690. package/apps/pwa/out/_next/static/chunks/5154.4b6a836179356f30.js +0 -94
  691. package/apps/pwa/out/_next/static/chunks/5228-a8200df486278f2d.js +0 -1
  692. package/apps/pwa/out/_next/static/chunks/5379.97af106d09566f69.js +0 -8
  693. package/apps/pwa/out/_next/static/chunks/5479-b6372e762719f92f.js +0 -1
  694. package/apps/pwa/out/_next/static/chunks/5534-699c5cbcbc96c5d7.js +0 -1
  695. package/apps/pwa/out/_next/static/chunks/5701.9c4e8f250312eeee.js +0 -24
  696. package/apps/pwa/out/_next/static/chunks/5892.2526d962339662d8.js +0 -1
  697. package/apps/pwa/out/_next/static/chunks/5937.c2df9e3447e1f667.js +0 -1
  698. package/apps/pwa/out/_next/static/chunks/5941.8210cc93c9202c0a.js +0 -286
  699. package/apps/pwa/out/_next/static/chunks/5975.4004f820ae3cecd5.js +0 -1
  700. package/apps/pwa/out/_next/static/chunks/6142-7ccd48795d7abd55.js +0 -1
  701. package/apps/pwa/out/_next/static/chunks/6160.bf9177b8c1bfb243.js +0 -1
  702. package/apps/pwa/out/_next/static/chunks/6398.71ba0690c8027555.js +0 -27
  703. package/apps/pwa/out/_next/static/chunks/6492.2bd5c10edc1a064e.js +0 -1
  704. package/apps/pwa/out/_next/static/chunks/6552.a3840aa7da35ad68.js +0 -62
  705. package/apps/pwa/out/_next/static/chunks/6736.1e36d3ac5fa5e41d.js +0 -24
  706. package/apps/pwa/out/_next/static/chunks/6900.3ccbe069bdd4556a.js +0 -1
  707. package/apps/pwa/out/_next/static/chunks/71327eb4.add50b365f94bf3a.js +0 -122
  708. package/apps/pwa/out/_next/static/chunks/7136.deca3e3a2ddaa374.js +0 -1
  709. package/apps/pwa/out/_next/static/chunks/7139-ee9f8ed2677a40b3.js +0 -1
  710. package/apps/pwa/out/_next/static/chunks/7191.fdc955f7d96064a4.js +0 -1
  711. package/apps/pwa/out/_next/static/chunks/7423-e0aa5d9e2962120e.js +0 -1
  712. package/apps/pwa/out/_next/static/chunks/7453.afc1ea7e05d56a8c.js +0 -1
  713. package/apps/pwa/out/_next/static/chunks/7503-5e6a3d7fe67e4a0c.js +0 -1
  714. package/apps/pwa/out/_next/static/chunks/7761-476fd7bf768abd34.js +0 -1
  715. package/apps/pwa/out/_next/static/chunks/7975.eafb12097a0a9e62.js +0 -1
  716. package/apps/pwa/out/_next/static/chunks/8050-9b364bdef8f71927.js +0 -1
  717. package/apps/pwa/out/_next/static/chunks/8152.64e253c35cccfaae.js +0 -1
  718. package/apps/pwa/out/_next/static/chunks/8190.620f0231b3924bcb.js +0 -1
  719. package/apps/pwa/out/_next/static/chunks/8448.45b6b1f1a99eeb35.js +0 -104
  720. package/apps/pwa/out/_next/static/chunks/8464-e24f10451b658aac.js +0 -1
  721. package/apps/pwa/out/_next/static/chunks/8477.e4c944bb3aa5d2f3.js +0 -1
  722. package/apps/pwa/out/_next/static/chunks/8710.ca385257d5ca422e.js +0 -1
  723. package/apps/pwa/out/_next/static/chunks/8830.627ccf294ec2465c.js +0 -1
  724. package/apps/pwa/out/_next/static/chunks/8880.64c445592f04acaf.js +0 -1
  725. package/apps/pwa/out/_next/static/chunks/8981.c706964c8ba27aff.js +0 -1
  726. package/apps/pwa/out/_next/static/chunks/9053.bd0fffaff8d3f836.js +0 -1
  727. package/apps/pwa/out/_next/static/chunks/9161-c66e723d7ec9d682.js +0 -1
  728. package/apps/pwa/out/_next/static/chunks/9185.1ecdf9b65c0bb684.js +0 -1
  729. package/apps/pwa/out/_next/static/chunks/9356.fccd5f58086b4ff5.js +0 -1
  730. package/apps/pwa/out/_next/static/chunks/9446.dc0406109371d329.js +0 -1
  731. package/apps/pwa/out/_next/static/chunks/9506.dcf25d467a42627e.js +0 -1
  732. package/apps/pwa/out/_next/static/chunks/9541.eb8e2f8480c7feca.js +0 -93
  733. package/apps/pwa/out/_next/static/chunks/9577.ed7582186efa5482.js +0 -1
  734. package/apps/pwa/out/_next/static/chunks/9579.4a8169600fff2058.js +0 -1
  735. package/apps/pwa/out/_next/static/chunks/9642-ff2e039eb62e3b92.js +0 -1
  736. package/apps/pwa/out/_next/static/chunks/9659-8f2323348113ad80.js +0 -1
  737. package/apps/pwa/out/_next/static/chunks/9724.4c5623af2db43153.js +0 -34
  738. package/apps/pwa/out/_next/static/chunks/9869.81a1e24ca682d5a0.js +0 -1
  739. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-8d9d76ea322d855c.js +0 -1
  740. package/apps/pwa/out/_next/static/chunks/app/approvals/page-ed2c4e59f9f1fe21.js +0 -1
  741. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-2dc99e03c5d76ca0.js +0 -1
  742. package/apps/pwa/out/_next/static/chunks/app/board/page-b5a3a788330e920f.js +0 -1
  743. package/apps/pwa/out/_next/static/chunks/app/botlab/page-14949db884bfa672.js +0 -1
  744. package/apps/pwa/out/_next/static/chunks/app/bots/page-ea76c85b36388a97.js +0 -1
  745. package/apps/pwa/out/_next/static/chunks/app/ceo/page-01716d20af62d325.js +0 -1
  746. package/apps/pwa/out/_next/static/chunks/app/chat/page-62baf6881a64b1e9.js +0 -1
  747. package/apps/pwa/out/_next/static/chunks/app/consult/page-f89ba590c2af8afa.js +0 -1
  748. package/apps/pwa/out/_next/static/chunks/app/control/page-22f8b0f78d9637e5.js +0 -1
  749. package/apps/pwa/out/_next/static/chunks/app/decisions/page-817f9d7e6820be80.js +0 -1
  750. package/apps/pwa/out/_next/static/chunks/app/design-check/page-ffb9bc0d2650249a.js +0 -1
  751. package/apps/pwa/out/_next/static/chunks/app/editor/page-f6d0e64873c0f629.js +0 -1
  752. package/apps/pwa/out/_next/static/chunks/app/exec/page-8bfa0d4ba23a209d.js +0 -1
  753. package/apps/pwa/out/_next/static/chunks/app/field/page-f5060462249e32c7.js +0 -1
  754. package/apps/pwa/out/_next/static/chunks/app/healthz/route-8d9d76ea322d855c.js +0 -1
  755. package/apps/pwa/out/_next/static/chunks/app/inside/page-b2049f8b1ef79117.js +0 -1
  756. package/apps/pwa/out/_next/static/chunks/app/intake/page-1446c5fa93907f11.js +0 -1
  757. package/apps/pwa/out/_next/static/chunks/app/layout-b48a7a07c5204156.js +0 -1
  758. package/apps/pwa/out/_next/static/chunks/app/live/page-e974b8c4ca47614c.js +0 -1
  759. package/apps/pwa/out/_next/static/chunks/app/live-run/page-8a919b869bd28594.js +0 -1
  760. package/apps/pwa/out/_next/static/chunks/app/loops/page-d31b8de6bfcd72ed.js +0 -1
  761. package/apps/pwa/out/_next/static/chunks/app/market/page-64765f817909db29.js +0 -1
  762. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-d128c8569a36ee9b.js +0 -1
  763. package/apps/pwa/out/_next/static/chunks/app/morning/page-b2679d357e9b9270.js +0 -1
  764. package/apps/pwa/out/_next/static/chunks/app/not-found-6fabec98a9e7b269.js +0 -1
  765. package/apps/pwa/out/_next/static/chunks/app/observatory/page-048f30149dcf1349.js +0 -1
  766. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-9113181d6b5a2725.js +0 -1
  767. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-1e7a755a13227611.js +0 -1
  768. package/apps/pwa/out/_next/static/chunks/app/outputs/page-3c8f8b67a203cf07.js +0 -1
  769. package/apps/pwa/out/_next/static/chunks/app/page-18032f5437215062.js +0 -1
  770. package/apps/pwa/out/_next/static/chunks/app/reflection/page-da3b739192fd0ee3.js +0 -1
  771. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-d332545571bd6fd1.js +0 -1
  772. package/apps/pwa/out/_next/static/chunks/app/sessions/page-5237aa5b3f3b8ec5.js +0 -1
  773. package/apps/pwa/out/_next/static/chunks/app/settings/page-d16e56c3989c147e.js +0 -1
  774. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-6b6593600869e2cd.js +0 -1
  775. package/apps/pwa/out/_next/static/chunks/app/setup/layout-8d9d76ea322d855c.js +0 -1
  776. package/apps/pwa/out/_next/static/chunks/app/setup/page-29e8b8a8396b55cb.js +0 -1
  777. package/apps/pwa/out/_next/static/chunks/app/share/page-b3414d856a1a7e2f.js +0 -1
  778. package/apps/pwa/out/_next/static/chunks/app/showroom/page-c3affa52b7ce961b.js +0 -1
  779. package/apps/pwa/out/_next/static/chunks/app/tasks/page-f21dddf9f95b6fab.js +0 -1
  780. package/apps/pwa/out/_next/static/chunks/app/term/page-f5a30a24745bf088.js +0 -1
  781. package/apps/pwa/out/_next/static/chunks/app/today/page-8c8569a09b4d4e34.js +0 -1
  782. package/apps/pwa/out/_next/static/chunks/app/trace/page-2ce32722c0243483.js +0 -1
  783. package/apps/pwa/out/_next/static/chunks/app/vault/page-4767b0d9c0a98c6c.js +0 -1
  784. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-116ee1339c387a75.js +0 -1
  785. package/apps/pwa/out/_next/static/chunks/app/workflows/page-35e8c50ca4ef7ffc.js +0 -1
  786. package/apps/pwa/out/_next/static/chunks/app/workspace/page-52e17113356bb891.js +0 -1
  787. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-7458ae05ed2ac1a0.js +0 -1
  788. package/apps/pwa/out/_next/static/chunks/framework-ad6c9dd524ad6ef8.js +0 -1
  789. package/apps/pwa/out/_next/static/chunks/main-app-2ddd1f2b44d6aa59.js +0 -1
  790. package/apps/pwa/out/_next/static/chunks/main-e3d82bdc358d20c5.js +0 -1
  791. package/apps/pwa/out/_next/static/chunks/pages/_app-71d5aa3896e1655f.js +0 -1
  792. package/apps/pwa/out/_next/static/chunks/pages/_error-03ffc54e00438546.js +0 -1
  793. package/apps/pwa/out/_next/static/chunks/webpack-56bb89af5a611d0b.js +0 -1
  794. package/apps/pwa/out/_next/static/css/be9019f696217521.css +0 -1
  795. package/apps/pwa/out/_next/static/css/d0a48dc099ed86cf.css +0 -1
  796. /package/apps/pwa/out/_next/static/{GiOLWmt4MqDZePubBjA4Q → Om5xS9jpjSXJCeTNGYEi4}/_ssgManifest.js +0 -0
@@ -17,9 +17,13 @@ import { gateHistory } from './gate-history.js';
17
17
  import { baselineFromCutLogs } from './baseline-from-logs';
18
18
  import { mergePartialFailures, readPartialPlanResult, testFileOfId, verifyDocsOnlyCarry } from './gate-partial';
19
19
  import { planGateRadiusShadow, shadowActual, writeGateRadiusShadow, type GateRadiusShadowPlan } from './gate-radius-shadow';
20
- import { planHardRehearsalRadius, partialGateImagesMatch, preflightGateImageDigest } from './gate-radius-live';
21
- import { actualGatePodImageDigest, gateBaselineReceipt, gateEnvFingerprint, gateGraphPins, gateSourcePins,
20
+ import { planHardRehearsalRadius, partialGateImagesMatch, preflightGateExecutionIdentity, preflightGateImageDigest } from './gate-radius-live';
21
+ import { actualGatePodImageDigest, gateBaselineReceipt, gateEnvFingerprint, gateGraphPins, gateSourcePins, parseGatePodRuntimeIdentity,
22
22
  POD_SWEEP_INTEGRATION_ONLY } from './gate-baseline-receipt';
23
+ import { gateImagePinPath, readGateImagePin, resolveGateImagePin, writeGateImagePin } from './gate-image-pin.js';
24
+ import { gateReceiptRegistryPath, planGateTrainReceiptCarry } from './gate-train-receipts.js';
25
+ import { gateEnvFingerprint as gateReceiptEnvFingerprint, gateLockfileHash } from '../../src/self-implement/gate-receipt-registry.js';
26
+ import { SPAWN_SYNC_MAX_BUFFER } from '../../src/util/spawn-sync-output.js';
23
27
  export { POD_SWEEP_INTEGRATION_ONLY } from './gate-baseline-receipt';
24
28
  import { logPlanWalltime, planDurations, readFileMemory } from './shard-plan';
25
29
  import { defaultGateHostMirrorReaders, resolveGateHostMirror, type GateHostMirror, type GateHostMirrorReaders } from './gate-host-mirror';
@@ -30,7 +34,7 @@ import { readJunitFileSeconds } from './gate-method';
30
34
  import { deriveCdpTestPatterns } from '../test-deterministic';
31
35
  import { emitNodeResult, readGraphContext } from './node-verdict.js';
32
36
  import { isGateExecutor, type GateExecutor } from './gate-executor.js';
33
- import { runPodCommand, parsePodCpu, podCpuMillis, resolvePodCpu, type RunPodCommandOptions, type PodCommandResult, type PodCpu } from '../../src/task-orchestrator/surfaces/pod-command-job.js';
37
+ import { runPodCommand, parsePodCpu, podCpuMillis, resolvePodCpu, podCommandImageFor, type RunPodCommandOptions, type PodCommandResult, type PodCpu } from '../../src/task-orchestrator/surfaces/pod-command-job.js';
34
38
  import { POD_BUN_CACHE_HOST_PATH, parseInstallSeconds, podBunCacheVolume } from '../../src/task-orchestrator/surfaces/pod-bun-cache.js';
35
39
  import { PodPoolScheduler, parsePodPool, checkPodPool, defaultRemoteRun, snapshotWarmPoolMembers, type PodPoolMember } from '../../src/task-orchestrator/surfaces/pod-pool.js';
36
40
  import { POD_INSTALL_SLOTS_DEFAULT, POD_INSTALL_SLOTS_HOST_PATH, installSlotScript } from '../../src/task-orchestrator/surfaces/pod-install-slots.js';
@@ -45,10 +49,10 @@ import { getUserConfig } from '../../src/user-config.js';
45
49
  import { beginGateShardAttempt, finalizeTerminalGateShards, gateShardsPath, recordGateShardsVerdict, writeGateShards, type GateJobTerminal, type GateShard } from '../../src/release-loop/gate-shards.js';
46
50
  import { appendGateShardLedger, gateShardLedgerPath, type GateShardLedgerRow } from '../../src/release-loop/gate-shard-ledger.js';
47
51
  import { markGateShardUnmeasured, nextGateShardAttempt, readGateShardManifest, readGateShardResult, saveGateShardResult } from './gate-shard-receipts.js';
48
- import { planGateShardDispatch, type GateShardReceipt } from './gate-shard-dispatch.js';
52
+ import { planGateShardDispatch, summarizeGateProgress, type GateProgressGroup, type GateShardReceipt } from './gate-shard-dispatch.js';
49
53
  import { defaultKubectl } from '../../src/task-orchestrator/surfaces/self-implement-pod.js';
50
54
  import { executeTuiNodeCheck, type ExecutedTuiCheck } from './tui-node-checks.js';
51
- import { runWithReleaseGateTraffic } from './gate-traffic-mode.js';
55
+ import { runWithReleaseGateTraffic, runWithReleasePwaTraffic } from './gate-traffic-mode.js';
52
56
  import { emitPodLifecycle, gatePodLifecycleEvent, gatePodOutcome } from '../../src/task-orchestrator/surfaces/pod-lifecycle-stages.js';
53
57
  import { gateHostReservationRoot, reserveReleaseGateHosts } from '../../src/release-loop/gate-host-reservation.js';
54
58
  import { normRoot } from '../../src/instance/resolve.js';
@@ -113,12 +117,28 @@ export interface GateTools {
113
117
  podCommand?: (options: RunPodCommandOptions) => Promise<PodCommandResult>;
114
118
  /** Kubernetes client (pool readiness · Pod lifecycle · Job log tail · failure evidence); absent = `defaultKubectl`. */
115
119
  kubectl?: typeof defaultKubectl;
120
+ /** Mock-only imageID observation seam; production reads the completed Job's child imageID from Kubernetes. */
121
+ podImageDigest?: (input: { job: PodCommandResult; host: string }) => string | null;
116
122
  /** SSH transport behind {@link GateRunner.command} on a remote host; absent = a real `ssh` child. */
117
123
  ssh?: (host: string, script: string, limitMs?: number) => { rc: number; output: string; timedOut?: boolean };
118
124
  /** SSH reachability probe of a multi-host roster; absent = `defaultRemoteRun`. */
119
125
  remoteRun?: typeof defaultRemoteRun;
120
126
  /** Pool admission a shard waits on (leases ⊕ a cluster reading); absent = the gate's own pool admission. */
121
127
  admission?: GateAdmission;
128
+ /** The live progress heartbeat scheduler; tests inject a deterministic tick, production uses an unref'd interval. */
129
+ progressScheduler?: GateProgressScheduler;
130
+ }
131
+
132
+ export type GateProgressScheduler = (tick: () => void, intervalMs: number) => () => void;
133
+ const scheduleGateProgress: GateProgressScheduler = (tick, intervalMs) => {
134
+ const timer = setInterval(tick, intervalMs);
135
+ timer.unref();
136
+ return () => clearInterval(timer);
137
+ };
138
+
139
+ export interface GateSweepProgress {
140
+ executor: 'host' | 'pod';
141
+ groups: readonly GateProgressGroup[];
122
142
  }
123
143
  export interface GateRunner {
124
144
  command(cmd: string, args: string[], cwd: string, limitMs?: number): Promise<CommandResult>;
@@ -128,7 +148,7 @@ export interface GateRunner {
128
148
  /** One checkout/install for a small independent file group; each file retains its own rc and log. */
129
149
  recheckPodBatch?(files: readonly string[], commit: string, pod: NonNullable<GateOptions['pod']>, timeoutMs: number): Promise<Map<string, CommandResult>>;
130
150
  /** `only` (GATE-PARTIAL) restricts the sweep to these test files; absent = every test file. */
131
- sweep(tree: string, logDir?: string, pod?: GateOptions['pod'], only?: readonly string[]): Promise<CommandResult>;
151
+ sweep(tree: string, logDir?: string, pod?: GateOptions['pod'], only?: readonly string[], onProgress?: (progress: GateSweepProgress) => void): Promise<CommandResult>;
132
152
  add(tree: string, commit: string): Promise<void>;
133
153
  remove(tree: string): Promise<void>;
134
154
  snapshot(tree: string, commit: string): Promise<void>;
@@ -137,6 +157,7 @@ export interface GateRunner {
137
157
  remoteMirror?: string;
138
158
  /** GATE-FLOW-SIM — the clock this runner measures with (the injected fake clock in a scenario); absent = `Date.now`. */
139
159
  now?(): number;
160
+ progressScheduler?: GateProgressScheduler;
140
161
  }
141
162
 
142
163
  /** One watchdog tick. Batch Kubernetes reads per host and trust only the current attempt's result marker. */
@@ -206,11 +227,17 @@ export function reconcileStaleGateShardBoard(input: {
206
227
  }
207
228
  export interface GateOptions {
208
229
  commit: string;
230
+ /** Explicit pre-cut warm↔fresh comparison, persisted before the ordinary gate reads warmHistory. */
231
+ warmPrecutCanary?: { file: string };
232
+ /** RELEASE-POLICY-PACK — the gate Pod image is tied to this resolved cut commit. */
233
+ imagePin?: 'cut-commit';
209
234
  /** The graph run that launched this gate; direct hard rehearsal instead pins source bytes explicitly. */
210
235
  graphRunId?: string;
211
236
  /** Only a direct hard rehearsal may pin graph bytes from its measured Git commit. */
212
237
  rehearsalBaseline?: boolean;
213
238
  forceFreeze?: boolean;
239
+ /** Force all release tests to run, bypassing every reusable partial plan. */
240
+ full?: boolean;
214
241
  version: string;
215
242
  baselineVersion?: string;
216
243
  baselineCommit?: string;
@@ -227,6 +254,10 @@ export interface GateOptions {
227
254
  recheckFileLimit?: number;
228
255
  /** freshShards — GATE-SHARD-RESUME off: every shard starts a Pod even when this logDir already holds its result. */
229
256
  pod?: { pool: string; shards?: number; shardTimeoutSeconds?: number; durationSource?: string; bunCache?: string; freshShards?: boolean;
257
+ imagePin?: 'cut-commit';
258
+ /** One immutable container image identity for this gate graph run and all its resumes. */
259
+ pinnedImageDigest?: string;
260
+ pinnedRunId?: string;
230
261
  /** Remeasure one named shard, or all incomplete shards with auto. Complete siblings keep their receipts. */
231
262
  rerouteShard?: string | 'auto';
232
263
  /** Pool context for the rerouted shard; the planner otherwise distributes missing shards over pool contexts. */
@@ -346,7 +377,7 @@ export interface GateResult {
346
377
  /** GATE-WARM-BOOT-SCAN — observation only: the cut sweep's test selection time (pod sweep · absent otherwise). */
347
378
  selectionMs?: number;
348
379
  error?: string;
349
- stalledShards?: Array<{ shard: number; files: string[]; reason: 'incomplete' | 'no-output' | 'job-failed' | 'exit-code' | 'unattributed' | 'timeout' | 'admission-timeout' | 'admission-error'; lastFile?: string; summaryFailures?: number; namedFailures?: number; detail?: string;
380
+ stalledShards?: Array<{ shard: number; files: string[]; reason: 'incomplete' | 'no-output' | 'job-failed' | 'exit-code' | 'unattributed' | 'timeout' | 'admission-timeout' | 'admission-error' | 'image-pin-mismatch'; lastFile?: string; summaryFailures?: number; namedFailures?: number; detail?: string;
350
381
  /** Pool member that ran the final unmeasured attempt; enables a bounded cross-host tail remeasure. */
351
382
  lastHost?: string;
352
383
  /** Failure of the optional cross-host tail attempt; original stall fields remain authoritative. */
@@ -360,6 +391,7 @@ export interface GateResult {
360
391
  partialFailures?: SweepFailures;
361
392
  /** GATE-PARTIAL — this verdict re-ran only these files and carried the prior gate's results for the rest. */
362
393
  partial?: { priorCommit: string; files: number };
394
+ trainReceiptReuse?: { carried: number; total: number; receipts: number; reasons: string[] };
363
395
  /** A failure that passed an evidence-checked isolated re-run: warning (flaky under load), not an introduced regression. */
364
396
  loadFlaky?: string[];
365
397
  /** File-level unhandled errors that reproduced or could not be measured in isolation. They keep the gate red. */
@@ -382,29 +414,151 @@ const WARM_REPLAY_BUDGET = 3;
382
414
  interface WarmVerdictSample { version: string; commit: string; baselineCommit: string; warmShadow: WarmShadowObservation[] }
383
415
  const recordValue = (value: unknown): Record<string, unknown> | null =>
384
416
  value !== null && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : null;
417
+ function validWarmShadowObservation(value: unknown): value is WarmShadowObservation {
418
+ const row = recordValue(value);
419
+ if (!row || !Array.isArray(row.files) || row.files.length !== 1 || typeof row.files[0] !== 'string'
420
+ || !/^(?:[\w.-]+\/)*[\w.-]+\.test\.[cm]?[jt]sx?$/.test(row.files[0])
421
+ || row.files[0].split('/').some((part) => part === '.' || part === '..')
422
+ || typeof row.attemptId !== 'string' || !row.attemptId || typeof row.reason !== 'string'
423
+ || !['observed', 'fallback', 'unmeasured'].includes(String(row.status))
424
+ || !['yes', 'no', 'failed', 'unavailable'].includes(String(row.quarantined))) return false;
425
+ if (row.finalized !== undefined && typeof row.finalized !== 'boolean') return false;
426
+ if (row.cutSha !== undefined && typeof row.cutSha !== 'string') return false;
427
+ if (row.baselineSha !== undefined && typeof row.baselineSha !== 'string') return false;
428
+ if (row.workerKey !== undefined) {
429
+ const key = recordValue(row.workerKey);
430
+ if (!key || typeof key.context !== 'string' || !key.context || typeof key.imageDigest !== 'string' || !key.imageDigest
431
+ || typeof key.lockHash !== 'string' || !key.lockHash || typeof key.architecture !== 'string' || !key.architecture) return false;
432
+ }
433
+ return true;
434
+ }
435
+ function validWarmVerdictSample(value: unknown): value is WarmVerdictSample {
436
+ const row = recordValue(value);
437
+ return !!row && typeof row.version === 'string' && typeof row.commit === 'string'
438
+ && typeof row.baselineCommit === 'string' && Array.isArray(row.warmShadow)
439
+ && row.warmShadow.every(validWarmShadowObservation);
440
+ }
385
441
  /** The verdict is atomically replaced; carry a bounded prior-verdict window in that same SSOT. */
386
442
  function readWarmVerdictHistory(path: string): WarmVerdictSample[] | undefined {
387
443
  if (!existsSync(path)) return [];
388
444
  try {
389
445
  const current = recordValue(JSON.parse(readFileSync(path, 'utf8')));
390
446
  if (!current || !Array.isArray(current.warmHistory)) return undefined;
391
- const history = [...current.warmHistory, { version: current.version, commit: current.commit,
392
- baselineCommit: current.baselineCommit, warmShadow: current.warmShadow }].slice(-WARM_VERDICT_HISTORY_LIMIT);
393
- if (history.some((entry) => { const row = recordValue(entry);
394
- return !row || typeof row.version !== 'string' || typeof row.commit !== 'string'
395
- || typeof row.baselineCommit !== 'string' || !Array.isArray(row.warmShadow); })) return undefined;
396
- return history as WarmVerdictSample[];
447
+ if (current.warmHistory.some((entry) => !validWarmVerdictSample(entry))) return undefined;
448
+ const hasCurrentSample = ['version', 'commit', 'baselineCommit', 'warmShadow'].some((key) => key in current);
449
+ const history = [...current.warmHistory];
450
+ if (current.warmHistoryOnly !== true || hasCurrentSample) {
451
+ const currentSample = { version: current.version, commit: current.commit,
452
+ baselineCommit: current.baselineCommit, warmShadow: current.warmShadow };
453
+ if (!validWarmVerdictSample(currentSample)) return undefined;
454
+ if (current.warmHistoryOnly !== true) history.push(currentSample);
455
+ }
456
+ return history.slice(-WARM_VERDICT_HISTORY_LIMIT) as WarmVerdictSample[];
397
457
  } catch { return undefined; }
398
458
  }
459
+
460
+ /** Append one actual pre-cut replay observation to the gate verdict's bounded history, preserving its gate result. */
461
+ function recordWarmPrecutCanarySample(path: string, sample: WarmVerdictSample): void {
462
+ if (!sample.version || !/^[0-9a-f]{7,40}$/i.test(sample.commit) || !/^[0-9a-f]{7,40}$/i.test(sample.baselineCommit)
463
+ || !Array.isArray(sample.warmShadow) || sample.warmShadow.length !== 1 || !sample.warmShadow.every(validWarmShadowObservation))
464
+ throw new Error('invalid pre-cut canary sample');
465
+ let current: Record<string, unknown> = {};
466
+ if (existsSync(path)) {
467
+ let decoded: unknown;
468
+ try { decoded = JSON.parse(readFileSync(path, 'utf8')); }
469
+ catch (error) { throw new Error(`gate verdict JSON is malformed: ${String(error)}`); }
470
+ const parsed = recordValue(decoded);
471
+ if (!parsed) throw new Error('gate verdict is not an object');
472
+ current = parsed;
473
+ if (current.warmHistory !== undefined && !Array.isArray(current.warmHistory)) throw new Error('gate verdict warmHistory is invalid');
474
+ }
475
+ const storedHistory = Array.isArray(current.warmHistory) ? [...current.warmHistory] : [];
476
+ if (storedHistory.some((entry) => !validWarmVerdictSample(entry))) throw new Error('gate verdict warmHistory contains an invalid row');
477
+ const history = storedHistory;
478
+ const hasCurrentSample = ['version', 'commit', 'baselineCommit', 'warmShadow'].some((key) => key in current);
479
+ if (current.warmHistoryOnly === true && hasCurrentSample) {
480
+ const previous = { version: current.version, commit: current.commit, baselineCommit: current.baselineCommit, warmShadow: current.warmShadow };
481
+ if (!validWarmVerdictSample(previous)) throw new Error('gate verdict current warm sample is invalid');
482
+ } else if (current.warmHistoryOnly !== true) {
483
+ const previous = { version: current.version, commit: current.commit, baselineCommit: current.baselineCommit, warmShadow: current.warmShadow };
484
+ if (!validWarmVerdictSample(previous)) throw new Error('gate verdict current warm sample is invalid');
485
+ history.push(previous);
486
+ }
487
+ history.push(sample);
488
+ const body = JSON.stringify({ ...current, warmHistory: history.slice(-WARM_VERDICT_HISTORY_LIMIT), warmHistoryOnly: true }, null, 2) + '\n';
489
+ mkdirSync(dirname(path), { recursive: true });
490
+ const temp = join(dirname(path), `.gate-verdict-${process.pid}-${randomUUID()}.tmp`);
491
+ try { writeFileSync(temp, body, { mode: 0o600, flag: 'wx' }); chmodSync(temp, 0o600); renameSync(temp, path); }
492
+ finally { if (existsSync(temp)) rmSync(temp); }
493
+ }
494
+
495
+ /** Run a bounded set of real fresh-Job ↔ warm-worker comparisons before the release gate. */
496
+ export async function runWarmPrecutCanary(input: { opts: GateOptions; runner: GateRunner; file: string;
497
+ config: unknown; historyPath: string; timeoutMs?: number }): Promise<{ samples: WarmShadowObservation[]; decision: ReturnType<typeof decideWarmReuse> }> {
498
+ const { opts, runner } = input;
499
+ if (!opts.pod || !runner.recheckPod || !opts.baselineCommit || !opts.pod.warmReplay || !opts.pod.warmFinalize || !opts.pod.warmQuarantine)
500
+ throw new Error('pre-cut canary requires a Pod pool, baseline commit, and warm worker hooks');
501
+ if (!/^(?:[\w.-]+\/)*[\w.-]+\.test\.[cm]?[jt]sx?$/.test(input.file)
502
+ || input.file.split('/').some((part) => part === '.' || part === '..')) throw new Error('pre-cut canary requires one repository test file');
503
+ if (readWarmVerdictHistory(input.historyPath) === undefined)
504
+ throw new Error('gate verdict contains malformed warm history or current sample; refusing canary without writing');
505
+ const samples = 2;
506
+ if (samples > WARM_REPLAY_BUDGET) throw new Error(`pre-cut canary sample count exceeds replay budget (${WARM_REPLAY_BUDGET})`);
507
+ const lock = await runner.localCommand('git', ['show', `${opts.commit}:bun.lock`], opts.repo ?? process.cwd());
508
+ if (lock.rc !== 0 || !lock.output) throw new Error('pre-cut canary cut lock is unmeasured');
509
+ if (!/^[0-9a-f]{40}$/i.test(opts.baselineCommit)) throw new Error('pre-cut canary baseline commit is invalid');
510
+ const baseline = await runner.localCommand('git', ['cat-file', '-e', `${opts.baselineCommit}^{commit}`], opts.repo ?? process.cwd());
511
+ if (baseline.rc !== 0) throw new Error(`pre-cut canary baseline commit is absent: ${opts.baselineCommit.slice(0, 12)}`);
512
+ const trackedFile = await runner.localCommand('git', ['cat-file', '-e', `${opts.commit}:${input.file}`], opts.repo ?? process.cwd());
513
+ if (trackedFile.rc !== 0) throw new Error(`pre-cut canary test file is absent from cut ${opts.commit.slice(0, 12)}`);
514
+ const lockHash = createHash('sha256').update(lock.output).digest('hex');
515
+ const pod = { ...opts.pod, warmProducerEnabled: false, warmCutSha: opts.commit, warmBaselineSha: opts.baselineCommit,
516
+ warmEligible: true, warmReplayOwner: true, warmShadowScope: { requested: false, attempts: 0 } };
517
+ const observations: WarmShadowObservation[] = [];
518
+ const attemptIds = new Set<string>();
519
+ for (let index = 0; index < samples; index++) {
520
+ let observation: WarmShadowObservation;
521
+ let failure: Error | undefined;
522
+ try {
523
+ const result = await runner.recheckPod(input.file, opts.commit, pod, input.timeoutMs ?? GATE_ISOLATED_TIMEOUT_MS);
524
+ observation = result.warmShadow ?? { files: [input.file], attemptId: `precut-unmeasured-${index + 1}-${randomUUID()}`,
525
+ status: 'unmeasured', reason: result.rc === 0 ? 'warm-shadow-missing' : `fresh-job-rc-${result.rc}`, quarantined: 'no' };
526
+ if (result.rc !== 0) { observation = { ...observation, status: 'unmeasured', reason: `fresh-job-rc-${result.rc}` };
527
+ failure = new Error(`pre-cut fresh Job failed on attempt ${index + 1}: rc=${result.rc}`); }
528
+ else if (observation.status !== 'observed' || !observation.workerKey
529
+ || observation.files.length !== 1 || observation.files[0] !== input.file
530
+ || observation.cutSha !== opts.commit || observation.baselineSha !== opts.baselineCommit
531
+ || observation.workerKey.lockHash !== lockHash || !['match', 'result'].includes(observation.reason)
532
+ || attemptIds.has(observation.attemptId)
533
+ || (observation.reason === 'match' && (observation.finalized !== true || observation.quarantined !== 'no'))) {
534
+ failure = new Error(`pre-cut warm comparison was not measured on attempt ${index + 1}: ${observation.reason}`);
535
+ }
536
+ } catch (error) {
537
+ observation = { files: [input.file], attemptId: `precut-error-${index + 1}-${randomUUID()}`,
538
+ status: 'unmeasured', reason: String(error), quarantined: 'unavailable' };
539
+ failure = error instanceof Error ? error : new Error(String(error));
540
+ }
541
+ attemptIds.add(observation.attemptId);
542
+ observations.push(observation);
543
+ recordWarmPrecutCanarySample(input.historyPath, { version: opts.version, commit: opts.commit,
544
+ baselineCommit: opts.baselineCommit, warmShadow: [observation] });
545
+ if (failure) throw failure;
546
+ }
547
+ const history = readWarmVerdictHistory(input.historyPath);
548
+ const evidence = warmEvidenceFromVerdicts(history, opts.version, opts.commit, opts.baselineCommit, lockHash);
549
+ const warmReuseConfig = (input.config && typeof input.config === 'object' && 'pod' in input.config
550
+ && recordValue((input.config as Record<string, unknown>).pod)?.warmReuse) as WarmReuseSwitchConfig | undefined;
551
+ return { samples: observations, decision: decideWarmReuse('gate-recheck', warmReuseConfig,
552
+ evidence ? { 'gate-recheck': evidence } : undefined) };
553
+ }
399
554
  /** Every counted match has a measured, finalized worker with this exact cut, baseline and lock. */
400
555
  export function warmEvidenceFromVerdicts(history: readonly WarmVerdictSample[] | undefined,
401
556
  version: string, head: string, baseline: string, lock: string): WarmReuseEvidence | undefined {
402
557
  if (!history?.length || !lock) return undefined;
403
- const rows = history.flatMap((verdict) => {
404
- if (verdict.version !== version || verdict.commit !== head || verdict.baselineCommit !== baseline
405
- || !Array.isArray(verdict.warmShadow)) return [null];
406
- return verdict.warmShadow.length ? verdict.warmShadow : [null];
407
- });
558
+ const matchingHistory = history.filter((verdict) => verdict.version === version && verdict.commit === head
559
+ && verdict.baselineCommit === baseline);
560
+ const rows = matchingHistory.flatMap((verdict) => verdict.warmShadow.length ? verdict.warmShadow : [null]);
561
+ if (!rows.length) return undefined;
408
562
  const attempts = new Set<string>();
409
563
  const key = rows.at(-1)?.workerKey;
410
564
  if (rows.some((entry) => {
@@ -807,6 +961,7 @@ export function gateShardShell(o: { parts: readonly (readonly string[])[]; cdpPa
807
961
  ...(o.beatSeconds ? gateBeatScript(o.beatSeconds, o.installSlots > 0) : []),
808
962
  ...(o.cachePrefix ? [o.cachePrefix] : []),
809
963
  gateEnvBaselineLine(),
964
+ `bun -e ${quote("const {createHash}=require('node:crypto');const keys=['ELANOUS_TEST_FAKE_LLM','LANG','LC_ALL','TZ'];const e=Object.fromEntries(keys.sort().map(k=>[k,process.env[k]??'']));const envFingerprint=createHash('sha256').update(JSON.stringify(e)).digest('hex');console.log(JSON.stringify({bunVersion:Bun.version,platform:process.platform,architecture:process.arch,envFingerprint}));")} > "$O/runtime-identity.json" 2> "$O/runtime-identity.err" || rm -f "$O/runtime-identity.json"`,
810
965
  ...(o.rulesBaseSha ? [`export ELANOUS_GATE_BASE_SHA=${quote(o.rulesBaseSha)}`] : []),
811
966
  // Seconds since this container's PID 1 started (the clone ran before this shell) — 0 when /proc cannot tell.
812
967
  `pod_age=$(awk -v t="$(cut -d' ' -f22 /proc/1/stat 2>/dev/null)" -v hz="$(getconf CLK_TCK 2>/dev/null)" '{ if (t > 0 && hz > 0) print int($1 - t / hz); else print 0 }' /proc/uptime 2>/dev/null)`,
@@ -1166,6 +1321,7 @@ export interface ShardArtifacts {
1166
1321
  finished: string[];
1167
1322
  /** Raw logs of the parts that did not finish (diagnostics · last started file). */
1168
1323
  unfinishedLog?: string;
1324
+ runtimeIdentity?: ReturnType<typeof parseGatePodRuntimeIdentity>;
1169
1325
  /** Original failure ids whose one same-Pod standalone retry completed cleanly. */
1170
1326
  loadFlaky?: string[];
1171
1327
  /** Files for which a same-Pod retry completed, even if it remained failing. */
@@ -1174,16 +1330,20 @@ export interface ShardArtifacts {
1174
1330
  /** Reads a shard Pod's outbox: the part layout ({@link gateShardShell}) or the older single-run one (shard.log · shard.rc · junit.xml). */
1175
1331
  export function readShardArtifacts(dir: string, parts: readonly (readonly string[])[]): ShardArtifacts {
1176
1332
  const read = (name: string) => { const path = join(dir, name); return existsSync(path) ? readFileSync(path, 'utf8') : undefined; };
1333
+ const identityText = read('runtime-identity.json');
1334
+ const runtimeIdentity = identityText === undefined ? undefined : parseGatePodRuntimeIdentity(identityText) ?? undefined;
1177
1335
  const rcOf = (text: string | undefined) => { const value = text?.trim() ?? ''; return /^\d+$/.test(value) && Number.isSafeInteger(Number(value)) ? Number(value) : undefined; };
1178
1336
  const installRc = rcOf(read('install.rc'));
1179
1337
  const install = read('install.log');
1180
1338
  if (installRc === undefined && install === undefined) {
1181
1339
  const junit = read('junit.xml');
1182
1340
  const rc = rcOf(read('shard.rc'));
1183
- return { output: read('shard.log'), ...(junit !== undefined ? { junit } : {}), ...(rc !== undefined ? { rc } : {}), timedOut: false, finished: [] };
1341
+ return { output: read('shard.log'), ...(junit !== undefined ? { junit } : {}), ...(rc !== undefined ? { rc } : {}),
1342
+ ...(runtimeIdentity ? { runtimeIdentity } : {}), timedOut: false, finished: [] };
1184
1343
  }
1185
1344
  const timedOut = read('shard.timeout') !== undefined;
1186
- if (installRc !== 0) return { output: install, ...(installRc !== undefined ? { rc: installRc } : {}), timedOut, finished: [] };
1345
+ if (installRc !== 0) return { output: install, ...(installRc !== undefined ? { rc: installRc } : {}),
1346
+ ...(runtimeIdentity ? { runtimeIdentity } : {}), timedOut, finished: [] };
1187
1347
  const good: Array<{ log: string; junit?: string; rc: 0 | 1; files: readonly string[]; loadFlaky?: string[]; loadFlakyAttemptedFiles?: string[] }> = [];
1188
1348
  const bad: string[] = [];
1189
1349
  const exitAnomaly: string[] = [];
@@ -1261,6 +1421,7 @@ export function readShardArtifacts(dir: string, parts: readonly (readonly string
1261
1421
  ...(attemptedFiles.size ? { loadFlakyAttemptedFiles: [...attemptedFiles].sort() } : {}),
1262
1422
  ...(bad.length ? { unfinishedLog: bad.join('\n') } : {}),
1263
1423
  ...(exitAnomaly.length && !otherBad ? { exitCodeOnly: exitAnomaly } : {}),
1424
+ ...(runtimeIdentity ? { runtimeIdentity } : {}),
1264
1425
  };
1265
1426
  }
1266
1427
 
@@ -1447,6 +1608,7 @@ class StalledPodShards extends Error {
1447
1608
  const exitCode = stalledShards.filter((item) => item.reason === 'exit-code');
1448
1609
  super(`pod sweep incomplete: stalled shards ${stalledShards.map(({ shard }) => shard).join(', ')}`
1449
1610
  + (unmeasured.length ? ` (unmeasured: no test output within ${unmeasured[0]!.stall!.seconds}s of checkout — shards ${[...new Set(unmeasured.map(({ shard }) => shard))].join(', ')})` : '')
1611
+ + (stalledShards.some((item) => item.reason === 'image-pin-mismatch') ? ` (image pin mismatch: ${stalledShards.filter((item) => item.reason === 'image-pin-mismatch').map((item) => item.detail ?? `shard ${item.shard}`).join('; ')})` : '')
1450
1612
  + (exitCode.length ? ` (exit-code anomaly: 0 fail but exit ≠ 0/1 — ${[...new Set(exitCode.flatMap(({ files }) => files))].join(', ')})` : '')
1451
1613
  + (abortedShards ? ` (중단 조각 ${abortedShards}: mid-run-stall)` : ''));
1452
1614
  }
@@ -1676,7 +1838,7 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
1676
1838
  if (!/^[0-9a-f]{40}$/.test(base)) throw new Error(`rules gate base invalid for ${commit.slice(0, 12)}: ${base}`);
1677
1839
  return base;
1678
1840
  };
1679
- const podSweep = async (tree: string, logDir: string | undefined, pod: NonNullable<GateOptions['pod']>, only?: readonly string[]): Promise<CommandResult> => {
1841
+ const podSweep = async (tree: string, logDir: string | undefined, pod: NonNullable<GateOptions['pod']>, only?: readonly string[], onProgress?: (progress: GateSweepProgress) => void): Promise<CommandResult> => {
1680
1842
  const sweepId = randomUUID();
1681
1843
  const shardCount = pod.shards ?? 24;
1682
1844
  const isolationConcurrency = pod.isolationConcurrency ?? 4;
@@ -1936,6 +2098,13 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
1936
2098
  // GATE-LIVE-OBS — root rows stay stable; each recursive split/retry and tail gets its own row.
1937
2099
  // Otherwise a finished root Job can hide a still-running child (or tail) behind 80/80 done.
1938
2100
  const board: GateShard[] = shards.map((item, index) => ({ id: `pod-${index}`, state: 'pending', plannedMin: Math.round(item.plannedSeconds / 6) / 10 }));
2101
+ const reportProgress = () => onProgress?.({ executor: 'pod', groups: board.map((row) => ({
2102
+ id: row.id, state: row.state === 'retry' ? 'running' : row.state,
2103
+ ...(row.host ? { host: row.host } : {}),
2104
+ ...(row.startedAt ? { startedAtMs: Date.parse(row.startedAt) } : {}),
2105
+ ...(row.endedAt ? { endedAtMs: Date.parse(row.endedAt) } : {}),
2106
+ })) });
2107
+ reportProgress();
1939
2108
  const filesById = new Map(shards.map((item, index) => [`pod-${index}`, [...item.files]]));
1940
2109
  const detailsById = new Map<string, Partial<GateShardLedgerRow>>();
1941
2110
  const observeShard = (row: GateShard) => {
@@ -1972,6 +2141,7 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
1972
2141
  for (const key of clear) delete next[key];
1973
2142
  if (JSON.stringify(next) === JSON.stringify(row)) return;
1974
2143
  board[index] = next;
2144
+ reportProgress();
1975
2145
  observeShard(next);
1976
2146
  flushBoard();
1977
2147
  };
@@ -2075,6 +2245,7 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
2075
2245
  let podStages: Record<string, number> | undefined;
2076
2246
  let podLifecycle: Record<string, string> | undefined;
2077
2247
  let imageDigest: string | null = null;
2248
+ let runtimeIdentity: ReturnType<typeof parseGatePodRuntimeIdentity> | undefined;
2078
2249
  let podJobName: string | undefined;
2079
2250
  let batchArtifactsComplete = true;
2080
2251
  let junit: string | undefined;
@@ -2086,6 +2257,7 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
2086
2257
  let lastFile: string | undefined;
2087
2258
  let jobFailed = false;
2088
2259
  let jobError = '';
2260
+ let imagePinMismatch = false;
2089
2261
  let failureCapture: ReturnType<NonNullable<NonNullable<GateOptions['pod']>['failureEvidence']>> | undefined;
2090
2262
  let jobNameForCapture: string | undefined;
2091
2263
  let jobContextForCapture: string | undefined;
@@ -2182,7 +2354,8 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
2182
2354
  pool: pod.pool, poolScheduler: trackedPool(shard, branch, jobName, resultFile, attemptId, (member) => {
2183
2355
  stamp('poolPlaced'); jobContext = member.context; jobContextForCapture = member.context; placedOn = member.context; placed();
2184
2356
  if (branch === '-tail') tailPlacedHost = member.context;
2185
- }, skipMembers, depth === 0 && branch === '' && hostRetries === 0 && !retriedNoOutput && !batchIsolation && skipMembers.size === 0), clone: true, source: { kind: 'commit', sha: commit }, deadlineSeconds: shardDeadline,
2357
+ }, skipMembers, depth === 0 && branch === '' && hostRetries === 0 && !retriedNoOutput && !batchIsolation && skipMembers.size === 0), clone: true, source: { kind: 'commit', sha: commit },
2358
+ ...(pod.imagePin === 'cut-commit' ? { imageCommit: commit } : {}), deadlineSeconds: shardDeadline,
2186
2359
  ...gateHostMirror(pod),
2187
2360
  ...(multiHost ? { statusUnreadableMs: memberUnreadableSeconds * 1000 } : {}),
2188
2361
  ...(bunCache ? { bunCache } : {}),
@@ -2198,7 +2371,9 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
2198
2371
  // poolOverride is the injected scheduler used by isolated tests; it has no authenticated kubectl
2199
2372
  // context. Those sweeps intentionally cannot mint a valid baseline receipt (missing observed digest).
2200
2373
  // Production pool sweeps always use this Kubernetes lookup; lookup failure is receipt-invalid only.
2201
- if (!poolOverride && placedOn) {
2374
+ if (tools.podImageDigest) {
2375
+ imageDigest = tools.podImageDigest({ job, host: placedOn ?? 'unobserved' });
2376
+ } else if (!poolOverride && placedOn) {
2202
2377
  try {
2203
2378
  const got = kubectl(['--context', placedOn, '-n', 'elanous-test', 'get', 'pods', '-l', `job-name=${job.job}`, '-o', 'json']);
2204
2379
  if (got.status === 0) {
@@ -2207,10 +2382,18 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
2207
2382
  }
2208
2383
  } catch { /* timings never change the verdict */ }
2209
2384
  }
2385
+ if (pod.pinnedImageDigest && imageDigest !== pod.pinnedImageDigest) {
2386
+ imagePinMismatch = true;
2387
+ debug.log('release-loop.gate', 'image-pin-mismatch', { runId: pod.pinnedRunId ?? null,
2388
+ shard, branch, host: placedOn ?? null, pinnedDigest: pod.pinnedImageDigest, actualDigest: imageDigest,
2389
+ decision: 'reject-job' }, { level: 'error' });
2390
+ throw new Error(`gate image pin mismatch: expected ${pod.pinnedImageDigest}, got ${imageDigest ?? 'unmeasured'}`);
2391
+ }
2210
2392
  jobExitCode = job.exitCode;
2211
2393
  if (job.exitCode !== 0) captureFailure();
2212
2394
  batchArtifactDir = job.artifactsDir;
2213
2395
  const read = readShardArtifacts(job.artifactsDir, parts);
2396
+ runtimeIdentity = read.runtimeIdentity;
2214
2397
  if (logDir) {
2215
2398
  mkdirSync(logDir, { recursive: true });
2216
2399
  let retryArtifactIndex = 0;
@@ -2319,7 +2502,7 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
2319
2502
  ...(failureCapture.logTail !== undefined ? { logTail: redactedGateLogTail(failureCapture.logTail) } : {}),
2320
2503
  ...(failureCapture.error ? { error: redactSecretText(failureCapture.error) } : {}),
2321
2504
  } } : {}),
2322
- ...(imageDigest ? { imageDigest } : {}),
2505
+ ...(imageDigest ? { imageDigest } : {}), ...(runtimeIdentity ? { runtimeIdentity } : {}),
2323
2506
  ...(fileSeconds !== undefined ? { fileSeconds, testSeconds } : {}), ...(testWallSeconds !== undefined ? { testWallSeconds } : {}),
2324
2507
  ...(podOverheadSeconds !== undefined ? { podOverheadSeconds } : {}),
2325
2508
  ...(unfinishedLogBytes !== undefined ? { unfinishedLogBytes } : {}),
@@ -2353,6 +2536,7 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
2353
2536
  let namedFailures: number | undefined;
2354
2537
  let unattributedDetail: string | undefined;
2355
2538
  if (descent === 'split') reason = 'incomplete';
2539
+ else if (imagePinMismatch) reason = 'image-pin-mismatch';
2356
2540
  // GATE-POD-STALL-FAILFAST: stopped with no test output — the existing «no output» reason (one fresh retry).
2357
2541
  else if (stall) reason = 'no-output';
2358
2542
  else if (timedOut) reason = 'timeout';
@@ -2471,6 +2655,8 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
2471
2655
  }
2472
2656
  }
2473
2657
  if (reason) {
2658
+ if (reason === 'image-pin-mismatch') return { runs: [], stalled: [{ shard, files: paths, reason,
2659
+ detail: jobError.slice(0, 300), ...(placedOn ? { lastHost: placedOn } : {}) }] };
2474
2660
  // GATE-SKIP-KNOWN-RETRY — a shard whose summary is complete but whose failures could not be read back (unattributed)
2475
2661
  // and whose every named failure is one the previous release already had is not re-run: re-running only re-measures
2476
2662
  // known failures (0.2.18 cut: 109 retry Pods · 1,840 shard-minutes, 4x the first pass). Those failures go to the
@@ -2623,7 +2809,10 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
2623
2809
  // has no base/environment/image binding and cannot authorize this cross-host measurement.
2624
2810
  // The sixth runShardImpl argument is `fresh`: a missing receipt must launch a new Job.
2625
2811
  const freshForReceipt = !!receiptRoot || pod.freshShards === true;
2626
- const done = await withRootSlot(() => runShard(item.files, shard, 0, '', false, freshForReceipt));
2812
+ const done = await withRootSlot(() => {
2813
+ markShard(shard, { state: 'running', startedAt: new Date().toISOString() }, ['waitReason']);
2814
+ return runShard(item.files, shard, 0, '', false, freshForReceipt);
2815
+ });
2627
2816
  shardDurations[shard] = clock() - shardStartedAt;
2628
2817
  const state = !done.stalled.length ? 'done' as const : done.stalled.some((stall) => stall.reason === 'timeout') ? 'timeout' as const : 'failed' as const;
2629
2818
  markShard(shard, { state, rc: done.stalled.length || done.runs.some((run) => run.fail || run.errors) ? 1 : 0, endedAt: new Date().toISOString() }, ['waitReason']);
@@ -2815,6 +3004,33 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
2815
3004
  await quarantine('gate-job-failed');
2816
3005
  throw error;
2817
3006
  }
3007
+ if (pod.pinnedImageDigest) {
3008
+ let actualDigest: string | null = null;
3009
+ if (tools.podImageDigest) {
3010
+ actualDigest = tools.podImageDigest({ job, host: producer?.context ?? 'unobserved' });
3011
+ } else if (!poolOverride) {
3012
+ // Rechecks launch through the pool scheduler too; find the Job in its configured
3013
+ // contexts so a recheck cannot silently escape the run-level image pin.
3014
+ const contexts = [...new Set(parsePodPool(pod.pool).map((member) => member.context))];
3015
+ const observed: string[] = [];
3016
+ for (const context of contexts) {
3017
+ try {
3018
+ const got = kubectl(['--context', context, '-n', 'elanous-test', 'get', 'pods', '-l', `job-name=${job.job}`, '-o', 'json']);
3019
+ if (got.status !== 0) continue;
3020
+ const digest = actualGatePodImageDigest(got.stdout);
3021
+ if (digest) observed.push(digest);
3022
+ } catch { /* inability to observe is rejected below */ }
3023
+ }
3024
+ if (new Set(observed).size === 1) actualDigest = observed[0]!;
3025
+ }
3026
+ if (actualDigest !== pod.pinnedImageDigest) {
3027
+ debug.log('release-loop.gate', 'image-pin-mismatch', { runId: pod.pinnedRunId ?? null,
3028
+ job: job.job, phase: 'recheck', host: producer?.context ?? null,
3029
+ pinnedDigest: pod.pinnedImageDigest, actualDigest, decision: 'reject-job' }, { level: 'error' });
3030
+ await quarantine('gate-image-pin-mismatch');
3031
+ throw new Error(`gate image pin mismatch: expected ${pod.pinnedImageDigest}, got ${actualDigest ?? 'unmeasured'} (recheck)`);
3032
+ }
3033
+ }
2818
3034
  try {
2819
3035
  const artifact = (name: string) => {
2820
3036
  const path = join(job.artifactsDir, name);
@@ -2959,8 +3175,9 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
2959
3175
  recheckPodBatch: (files, commit, pod, timeoutMs) => runPodRecheckBatch(files, commit, pod, timeoutMs),
2960
3176
  get remoteMirror() { return remoteMirror; },
2961
3177
  set remoteMirror(path) { remoteMirror = path; },
2962
- async sweep(tree, logDir, pod, only) {
2963
- if (pod) return podSweep(tree, logDir, pod, only);
3178
+ progressScheduler: tools.progressScheduler ?? scheduleGateProgress,
3179
+ async sweep(tree, logDir, pod, only, onProgress) {
3180
+ if (pod) return podSweep(tree, logDir, pod, only, onProgress);
2964
3181
  const listed = await command('git', ['ls-files', '*.test.*'], tree);
2965
3182
  check(listed, 'git ls-files tests');
2966
3183
  const allFiles = listed.output.split(/\r?\n/).filter((file) => /\.test\.(?:tsx?|jsx?|mts|cts)$/.test(file));
@@ -2982,13 +3199,26 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
2982
3199
  } else cdpPatterns = deriveCdpTestPatterns({ cwd: tree });
2983
3200
  const outputs: string[] = [];
2984
3201
  let total = { pass: 0, fail: 0, errors: 0, ran: 0, files: 0 };
2985
- for (const group of groups) {
2986
- if (!group.paths.length || !files.some((f) => group.paths.some((p) => f === p || f.startsWith(`${p}/`)))) continue;
3202
+ const selectedGroups = groups.filter((group) => group.paths.length && files.some((f) => group.paths.some((p) => f === p || f.startsWith(`${p}/`))));
3203
+ const progressGroups: GateProgressGroup[] = selectedGroups.map((group) => ({ id: group.name, state: 'pending', ...(remote ? { host: remote } : {}) }));
3204
+ onProgress?.({ executor: 'host', groups: progressGroups });
3205
+ for (const group of selectedGroups) {
3206
+ const progress = progressGroups.find((item) => item.id === group.name)!;
3207
+ progress.state = 'running'; progress.startedAtMs = clock();
3208
+ onProgress?.({ executor: 'host', groups: progressGroups });
2987
3209
  const start = Date.now();
2988
3210
  const groupIgnores = [...new Set(cdpPatterns)]
2989
3211
  .filter((pattern) => files.includes(pattern) && group.paths.some((p) => pattern === p || pattern.startsWith(`${p}/`)))
2990
3212
  .flatMap((pattern) => ['--path-ignore-patterns', pattern]);
2991
- const run = await command('bun', ['run', 'test:deterministic', ...groupIgnores, ...group.paths.map(asPath)], tree);
3213
+ let run: CommandResult;
3214
+ try { run = await command('bun', ['run', 'test:deterministic', ...groupIgnores, ...group.paths.map(asPath)], tree); }
3215
+ catch (error) {
3216
+ progress.state = 'failed'; progress.endedAtMs = clock();
3217
+ onProgress?.({ executor: 'host', groups: progressGroups });
3218
+ throw error;
3219
+ }
3220
+ progress.state = run.timedOut ? 'timeout' : run.rc === 0 ? 'done' : 'failed'; progress.endedAtMs = clock();
3221
+ onProgress?.({ executor: 'host', groups: progressGroups });
2992
3222
  if (logDir) {
2993
3223
  mkdirSync(logDir, { recursive: true });
2994
3224
  writeFileSync(join(logDir, `${group.name}.log`), run.output, { mode: 0o600 });
@@ -3120,7 +3350,8 @@ export function saveBaselineFileCache(ledger: string, commit: string, rows: Base
3120
3350
  }
3121
3351
 
3122
3352
  export async function judgeGate(opts: GateOptions, runner: GateRunner = createGateRunner(opts.repo ?? process.cwd(), opts.remote),
3123
- imageProbe: typeof preflightGateImageDigest = preflightGateImageDigest): Promise<GateResult> {
3353
+ imageProbe: typeof preflightGateImageDigest = preflightGateImageDigest,
3354
+ identityProbe: typeof preflightGateExecutionIdentity = preflightGateExecutionIdentity): Promise<GateResult> {
3124
3355
  if (opts.pod?.warmReplay) opts = { ...opts, pod: { ...opts.pod,
3125
3356
  warmShadowScope: { requested: false, attempts: 0 } } };
3126
3357
  const explicitConfig = getElanousConfigDirOverride();
@@ -3163,22 +3394,92 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
3163
3394
  let cutSweepRc: number | null = null;
3164
3395
  let radiusShadow: GateRadiusShadowPlan | undefined;
3165
3396
  const trees: string[] = [];
3397
+ const baselineVersion = opts.baselineVersion;
3166
3398
  try {
3167
3399
  if (!sha.test(opts.commit) || !versionPattern.test(opts.version)
3168
3400
  || (opts.baselineVersion && !versionPattern.test(opts.baselineVersion))
3169
3401
  || (opts.baselineCommit && !sha.test(opts.baselineCommit))) throw new Error('invalid commit or version');
3170
- if (!opts.baselineVersion) throw new Error('previous release version required (--baseline-version)');
3402
+ if (!baselineVersion) throw new Error('previous release version required (--baseline-version)');
3171
3403
  if (opts.rehearsalBaseline && (opts.graphRunId || !opts.pod || !/-rc\.\d+$/.test(opts.version)))
3172
3404
  throw new Error('rehearsal baseline requires a direct Pod gate with an isolated rc version');
3173
3405
  // Observation only. Failure to plan cannot change the gate selector or verdict.
3174
3406
  try { radiusShadow = planGateRadiusShadow({ repo, ledgerRoot: ledger, version: opts.version,
3175
- baselineVersion: opts.baselineVersion, forCommit: opts.commit, instanceRoot: root,
3407
+ baselineVersion, forCommit: opts.commit, instanceRoot: root,
3176
3408
  expectedBaselineCommit: opts.baselineCommit }); }
3177
3409
  catch (error) { debug.log('release-loop.gate', 'radius-shadow-unavailable', { reason: String(error) }); }
3178
3410
  if (!Number.isSafeInteger(opts.recheckFileLimit ?? GATE_HOST_RECHECK_FILE_LIMIT) || (opts.recheckFileLimit ?? GATE_HOST_RECHECK_FILE_LIMIT) < 1)
3179
3411
  throw new Error('invalid gate recheck file limit');
3180
3412
  if (opts.pod && (!opts.pod.pool || !Number.isSafeInteger(opts.pod.shards ?? 24) || (opts.pod.shards ?? 24) < 1
3181
3413
  || !Number.isSafeInteger(opts.pod.shardTimeoutSeconds ?? 1200) || (opts.pod.shardTimeoutSeconds ?? 1200) < 1)) throw new Error('invalid pod sweep options');
3414
+ if (opts.pod && opts.graphRunId) {
3415
+ const components = opts.pod.pool.split(',').map((part) => part.trim()).filter(Boolean);
3416
+ const members = parsePodPool(opts.pod.pool);
3417
+ const memberSpecs = members.map((member) => {
3418
+ const spec = components.find((part) => part.split(/[@:#]/, 1)[0] === member.context);
3419
+ if (!spec) throw new Error(`gate image pin cannot map pool member ${member.context}`);
3420
+ return { context: member.context, spec };
3421
+ });
3422
+ const poolImages = new Map<string, readonly string[]>();
3423
+ const currentImages: Record<string, string | null> = {};
3424
+ for (const member of memberSpecs) {
3425
+ const digest = imageProbe(member.spec, opts.commit);
3426
+ currentImages[member.context] = digest;
3427
+ poolImages.set(member.context, digest ? [digest] : []);
3428
+ }
3429
+ const pinRunId = opts.graphRunId;
3430
+ const pinPath = gateImagePinPath(root, opts.version, pinRunId);
3431
+ const gateImageTag = `${podCommandImageFor([], true).split(':')[0]}:${opts.commit.slice(0, 12)}`;
3432
+ let savedPin: ReturnType<typeof readGateImagePin>;
3433
+ try { savedPin = existsSync(pinPath) ? readGateImagePin(pinPath, pinRunId, gateImageTag, opts.commit) : null; }
3434
+ catch (error) {
3435
+ debug.log('release-loop.gate', 'image-pin', { runId: pinRunId, currentImages, decision: 'fail-closed',
3436
+ reason: 'saved-image-pin-invalid', error: String(error).slice(0, 300) }, { level: 'error' });
3437
+ throw new Error('gate resume refused: saved image digest pin is invalid');
3438
+ }
3439
+ const savedDigest = savedPin?.digest ?? null;
3440
+ let previouslyExecuted = false;
3441
+ if (opts.graphRunId) {
3442
+ // A gate's immutable pin is written before any Pod work. The graph state is only a legacy-run detector:
3443
+ // if it already records an executed gate but this pin does not exist, resume cannot safely invent one.
3444
+ const runPath = join(root, 'graph-runs', 'release-loop', `${opts.graphRunId}.json`);
3445
+ if (existsSync(runPath)) {
3446
+ try {
3447
+ const prior = JSON.parse(readFileSync(runPath, 'utf8')) as { nodes?: Array<{ nodeId?: unknown; executed?: unknown }> };
3448
+ previouslyExecuted = prior.nodes?.some((node) => node.nodeId === 'gate' && node.executed === true) ?? false;
3449
+ } catch { previouslyExecuted = true; }
3450
+ }
3451
+ }
3452
+ if (!savedDigest && previouslyExecuted) {
3453
+ debug.log('release-loop.gate', 'image-pin', { runId: opts.graphRunId, savedDigest: null, currentImages,
3454
+ decision: 'fail-closed', reason: 'resume-pin-missing' }, { level: 'error' });
3455
+ throw new Error('gate resume refused: saved image digest pin is missing');
3456
+ }
3457
+ const resolution = resolveGateImagePin({ ...(savedDigest ? { savedDigest } : {}),
3458
+ ...(!savedDigest ? { initialDigest: [...poolImages.values()].flat().find(Boolean) ?? null } : {}), poolImages });
3459
+ if (!resolution.ok) {
3460
+ debug.log('release-loop.gate', 'image-pin', { runId: opts.graphRunId ?? null, savedDigest, currentImages,
3461
+ decision: 'fail-closed', reason: resolution.reason }, { level: 'error' });
3462
+ throw new Error(`gate image pin refused: ${resolution.reason}`);
3463
+ }
3464
+ const selected = memberSpecs.filter((member) => resolution.eligiblePools.includes(member.context));
3465
+ if (!selected.length) throw new Error('gate image pin refused: no eligible pool');
3466
+ if (!savedDigest) {
3467
+ try { writeGateImagePin(pinPath, pinRunId, resolution.digest, gateImageTag, opts.commit); }
3468
+ catch (error) {
3469
+ debug.log('release-loop.gate', 'image-pin', { runId: pinRunId, pinnedDigest: resolution.digest,
3470
+ currentImages, decision: 'fail-closed', reason: 'pin-write-failed', error: String(error).slice(0, 300) }, { level: 'error' });
3471
+ throw error;
3472
+ }
3473
+ }
3474
+ opts = { ...opts, pod: { ...opts.pod, pool: selected.map((member) => member.spec).join(','),
3475
+ pinnedImageDigest: resolution.digest, pinnedRunId: pinRunId } };
3476
+ debug.log('release-loop.gate', 'image-pin', { runId: opts.graphRunId ?? null, pinnedDigest: resolution.digest,
3477
+ currentImages, eligiblePools: resolution.eligiblePools, excludedPools: members.map((member) => member.context)
3478
+ .filter((context) => !resolution.eligiblePools.includes(context)), decision: savedDigest ? 'resume-pinned' : 'pin-created' });
3479
+ } else if (opts.pod) {
3480
+ debug.log('release-loop.gate', 'image-pin-unscoped', { runId: null, decision: 'not-applicable',
3481
+ reason: 'direct-pod-gate-without-graph-run-id' });
3482
+ }
3182
3483
  if (opts.remote && !/^(?:[\w.-]+@)?[\w.-]+$/.test(opts.remote)) throw new Error('invalid ssh host');
3183
3484
  if (opts.remote) {
3184
3485
  runner.remoteMirror = undefined;
@@ -3189,8 +3490,8 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
3189
3490
  const homePath = home?.output.split(/\r?\n/).find((line) => line.startsWith('/'));
3190
3491
  if (home && (!homePath || !/^\/[^\r\n]*$/.test(homePath))) throw new Error('invalid remote home');
3191
3492
  const mirror = configured.startsWith('~/') ? join(homePath!, configured.slice(2)) : configured;
3192
- const baseSha = opts.baselineCommit ?? baselineReleaseCommit(opts.baselineVersion);
3193
- cachedBaseline(opts.baselineVersion, baseSha);
3493
+ const baseSha = opts.baselineCommit ?? baselineReleaseCommit(baselineVersion);
3494
+ cachedBaseline(baselineVersion, baseSha);
3194
3495
  const present = await runner.command('test', ['-d', mirror], '/tmp');
3195
3496
  if (present.rc === 1) {
3196
3497
  check(await runner.command('mkdir', ['-p', dirname(mirror)], '/tmp'), 'remote mirror parent');
@@ -3219,26 +3520,57 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
3219
3520
  // GATE-PARTIAL: a plan made by `release resume --from gate --partial` for exactly this commit re-runs only its files.
3220
3521
  // Its logs go to cut-partial/ — the full cut/ logs stay whole (the next release reads them as a baseline source).
3221
3522
  const partialRead = readPartialPlanResult(roots, opts.version, opts.commit);
3222
- const explicitPartial = partialRead.plan;
3523
+ const explicitPartial = opts.full ? null : partialRead.plan;
3223
3524
  const rehearsalStampPresent = ['hard-5h', 'hard-mid'].some((step) =>
3224
3525
  existsSync(join(root, 'release', opts.version, 'rehearsal', `${step}.json`)));
3225
- const currentImage = !explicitPartial && opts.graphRunId && opts.pod && rehearsalStampPresent
3226
- ? imageProbe(opts.pod.pool) : null;
3526
+ const currentIdentity = !opts.full && !explicitPartial && opts.graphRunId && opts.pod && rehearsalStampPresent
3527
+ ? identityProbe(opts.pod.pool, process.env, opts.commit) : null;
3528
+ const currentImage = currentIdentity?.imageDigest ?? (!opts.full && !explicitPartial
3529
+ && opts.graphRunId && opts.pod && rehearsalStampPresent ? imageProbe(opts.pod.pool, opts.commit) : null);
3227
3530
  const receiptImage = currentImage ?? (opts.graphRunId && opts.pod && process.env.ELANOUS_GRAPH_CONTEXT
3228
- ? imageProbe(opts.pod.pool) : null);
3229
- const receiptBase = opts.baselineCommit ?? (opts.baselineVersion && roots.some((location) =>
3230
- existsSync(join(location, 'release', opts.baselineVersion!, 'release.json')))
3231
- ? baselineReleaseCommit(opts.baselineVersion) : undefined);
3232
- const radius = !explicitPartial && opts.graphRunId && opts.pod && rehearsalStampPresent
3531
+ ? imageProbe(opts.pod.pool, opts.commit) : null);
3532
+ const receiptBase = opts.baselineCommit ?? (roots.some((location) =>
3533
+ existsSync(join(location, 'release', baselineVersion, 'release.json')))
3534
+ ? baselineReleaseCommit(baselineVersion) : undefined);
3535
+ const radius = !opts.full && !explicitPartial && opts.graphRunId && opts.pod && rehearsalStampPresent
3233
3536
  ? planHardRehearsalRadius({ root, repo, version: opts.version, forCommit: opts.commit,
3234
- graphPins: gateGraphPins(root, opts.graphRunId), envFingerprint: gateEnvFingerprint(process.env), imageDigest: currentImage })
3537
+ graphPins: gateGraphPins(root, opts.graphRunId), envFingerprint: gateEnvFingerprint(process.env), imageDigest: currentImage,
3538
+ executionIdentity: currentIdentity })
3235
3539
  : null;
3236
3540
  if (radius) debug.log('release-loop.gate', 'radius-live-plan', { version: opts.version, reason: radius.reason,
3237
3541
  files: radius.plan?.files.length ?? null, rehearsalRunId: radius.rehearsalRunId ?? null });
3238
- let partial = explicitPartial ?? radius?.plan ?? null;
3542
+ let partial = opts.full ? null : explicitPartial ?? radius?.plan ?? null;
3543
+ let trainReceiptPlan: ReturnType<typeof planGateTrainReceiptCarry> = null;
3544
+ if (!opts.full && !partial && opts.graphRunId && opts.pod) {
3545
+ const receiptPath = gateReceiptRegistryPath(repo);
3546
+ const bun = spawnSync('bun', ['--version'], { cwd: repo, encoding: 'utf8', maxBuffer: SPAWN_SYNC_MAX_BUFFER });
3547
+ if (receiptPath && bun.status === 0 && !bun.error) {
3548
+ try {
3549
+ trainReceiptPlan = planGateTrainReceiptCarry({ repo, commit: opts.commit, receiptPath,
3550
+ bunVersion: bun.stdout.trim(), lockfileHash: gateLockfileHash(repo), envFingerprint: gateReceiptEnvFingerprint(process.env) });
3551
+ if (trainReceiptPlan?.carriedFiles.length) partial = { version: opts.version, priorCommit: trainReceiptPlan.priorCommit,
3552
+ forCommit: opts.commit, files: trainReceiptPlan.files, priorFailures: [], priorErrors: [] };
3553
+ } catch (error) {
3554
+ debug.log('release-loop.gate', 'train-receipt-plan-failed', { version: opts.version, reason: String(error).slice(0, 240) });
3555
+ }
3556
+ }
3557
+ if (trainReceiptPlan) {
3558
+ result.trainReceiptReuse = { carried: trainReceiptPlan.carriedFiles.length, total: trainReceiptPlan.totalFiles,
3559
+ receipts: trainReceiptPlan.receipts.length, reasons: trainReceiptPlan.reasons };
3560
+ debug.log('release-loop.gate', 'train-receipt-plan', { version: opts.version, commit: opts.commit,
3561
+ carried: trainReceiptPlan.carriedFiles.length, total: trainReceiptPlan.totalFiles,
3562
+ ratio: trainReceiptPlan.carriedFiles.length / trainReceiptPlan.totalFiles,
3563
+ receipts: trainReceiptPlan.receipts, selectedFiles: trainReceiptPlan.files.length,
3564
+ reasons: trainReceiptPlan.reasons, action: trainReceiptPlan.carriedFiles.length ? 'run-remaining-tests' : 'full-sweep' });
3565
+ } else if (!opts.full && !explicitPartial && !radius?.plan) {
3566
+ debug.log('release-loop.gate', 'train-receipt-plan', { version: opts.version, commit: opts.commit,
3567
+ carried: 0, total: null, ratio: 0, receipts: [], selectedFiles: null,
3568
+ reasons: ['no safe receipt coverage'], action: 'full-sweep' });
3569
+ }
3570
+ }
3239
3571
  const explicitPodPlan = !!explicitPartial && explicitPartial.kind !== 'recheck' && !!opts.pod;
3240
3572
  const explicitPodDigest = explicitPodPlan ? explicitPartial!.shardImageDigests?.[0] : undefined;
3241
- const probedPodDigest = explicitPodDigest ? imageProbe(opts.pod!.pool) : null;
3573
+ const probedPodDigest = explicitPodDigest ? imageProbe(opts.pod!.pool, opts.commit) : null;
3242
3574
  if (explicitPodPlan && (!explicitPodDigest || probedPodDigest !== explicitPodDigest)) {
3243
3575
  debug.log('release-loop.gate', 'partial-image-preflight-fallback', { version: opts.version,
3244
3576
  reason: !explicitPodDigest || !probedPodDigest ? 'executed-pod-image-digest-missing' : 'executed-pod-image-digest-mismatch' }, { level: 'warn' });
@@ -3274,17 +3606,35 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
3274
3606
  let knownFailures: string[] = [];
3275
3607
  if (opts.pod) {
3276
3608
  try {
3277
- const knownSha = opts.baselineCommit ?? baselineReleaseCommit(opts.baselineVersion);
3278
- const saved = cachedBaseline(opts.baselineVersion, knownSha)?.saved;
3279
- const recorded = saved ? undefined : baselineFromCutLogs(join(ledger, 'release', opts.baselineVersion, 'gate-logs', 'cut'), knownSha);
3609
+ const knownSha = opts.baselineCommit ?? baselineReleaseCommit(baselineVersion);
3610
+ const saved = cachedBaseline(baselineVersion, knownSha)?.saved;
3611
+ const recorded = saved ? undefined : baselineFromCutLogs(join(ledger, 'release', baselineVersion, 'gate-logs', 'cut'), knownSha);
3280
3612
  const trustedKnown = saved ?? (recorded?.complete ? recorded : undefined);
3281
3613
  if (trustedKnown) knownFailures = [...trustedKnown.failures, ...(trustedKnown.errors ?? []), ...loadEnvKnownFailures(repo)];
3282
3614
  } catch (error) { debug.log('release-loop.gate', 'known-failures-unavailable', { error: String(error) }); }
3283
3615
  }
3284
- const durationSource = join(ledger, 'release', opts.baselineVersion, 'gate-logs', 'cut');
3285
- const runCut = (selected: typeof partial) => selected?.kind === 'carry' ? undefined : runner.sweep(cutTree,
3616
+ const durationSource = join(ledger, 'release', baselineVersion, 'gate-logs', 'cut');
3617
+ const runCut = async (selected: typeof partial) => {
3618
+ if (selected?.kind === 'carry') return undefined;
3619
+ const progressFile = opts.graphRunId && /^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(opts.graphRunId)
3620
+ ? join(ledger, 'graph-runs', 'release-loop', `${opts.graphRunId}.json.contexts`, 'gate-progress.jsonl') : undefined;
3621
+ let latest: GateSweepProgress | undefined;
3622
+ const emitProgress = () => {
3623
+ if (!latest) return;
3624
+ const nowMs = runner.now?.() ?? Date.now();
3625
+ const event = { version: opts.version, runId: opts.graphRunId ?? null, at: new Date(nowMs).toISOString(),
3626
+ ...summarizeGateProgress({ executor: latest.executor, groups: latest.groups, nowMs }) };
3627
+ debug.log('release-loop.gate', 'progress', event);
3628
+ if (progressFile) {
3629
+ try { mkdirSync(dirname(progressFile), { recursive: true }); appendFileSync(progressFile, `${JSON.stringify(event)}\n`, { mode: 0o600 }); }
3630
+ catch (error) { debug.log('release-loop.gate', 'progress-log-write-failed', { path: progressFile, error: String(error) }, { level: 'warn' }); }
3631
+ }
3632
+ };
3633
+ const cancel = (runner.progressScheduler ?? scheduleGateProgress)(emitProgress, 60_000);
3634
+ try {
3635
+ const sweepResult = await runner.sweep(cutTree,
3286
3636
  join(root, 'release', opts.version, 'gate-logs', selected ? 'cut-partial' : 'cut'),
3287
- opts.pod ? { ...opts.pod, durationSource, ...(knownFailures.length ? { knownFailures } : {}), onSelectionTimed: ({ ms }) => { result.selectionMs = ms; },
3637
+ opts.pod ? { ...opts.pod, ...(opts.imagePin ? { imagePin: opts.imagePin } : {}), durationSource, ...(knownFailures.length ? { knownFailures } : {}), onSelectionTimed: ({ ms }) => { result.selectionMs = ms; },
3288
3638
  // GATE-RISK-LANE — the watch list is read from this gate's own ledgers (the instance root ⊕ the machine ledger);
3289
3639
  // the cut sweep puts those files first, the baseline sweep keeps its plan.
3290
3640
  riskWatch: { instanceRoots: roots, ledgerRoots: roots },
@@ -3295,7 +3645,11 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
3295
3645
  receiptRoot: join(ledger, 'release', opts.version, 'gate-logs', 'cut', 'receipts'),
3296
3646
  base40: receiptBase,
3297
3647
  expectedImageDigest: receiptImage } } : undefined,
3298
- selected?.files);
3648
+ selected?.files, (progress) => { latest = progress; emitProgress(); });
3649
+ emitProgress();
3650
+ return sweepResult;
3651
+ } finally { cancel?.(); }
3652
+ };
3299
3653
  let cutRun = await runCut(partial);
3300
3654
  const radiusSelectionMatched = partial?.files && cutRun?.podSweep?.files
3301
3655
  && JSON.stringify([...partial.files].sort()) === JSON.stringify([...cutRun.podSweep.files].sort());
@@ -3376,13 +3730,13 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
3376
3730
  if (!work) throw new Error('gate workspace missing before baseline attribution recheck');
3377
3731
  const recheckTree = join(work, 'baseline-attribution-recheck');
3378
3732
  trees.push(recheckTree);
3379
- const baseShaForRecheck = opts.baselineCommit ?? baselineReleaseCommit(opts.baselineVersion);
3733
+ const baseShaForRecheck = opts.baselineCommit ?? baselineReleaseCommit(baselineVersion);
3380
3734
  await runner.add(recheckTree, baseShaForRecheck);
3381
3735
  for (const dir of [recheckTree, join(recheckTree, 'apps/pwa')])
3382
3736
  check(await runner.command('bun', ['install'], dir), `bun install ${dir}`);
3383
3737
  const rechecked = await runner.sweep(recheckTree,
3384
3738
  join(root, 'release', opts.version, 'gate-logs', 'baseline-attribution-recheck'),
3385
- opts.pod ? { ...opts.pod, durationSource } : undefined,
3739
+ opts.pod ? { ...opts.pod, ...(opts.imagePin ? { imagePin: opts.imagePin } : {}), durationSource } : undefined,
3386
3740
  targetFiles);
3387
3741
  if (rechecked.timedOut || rechecked.abortedShards || rechecked.stalledEnv?.length) {
3388
3742
  attribution.baselineResult = 'timeout';
@@ -3455,13 +3809,13 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
3455
3809
  }
3456
3810
  if (partial) result.partial = { priorCommit: partial.priorCommit, files: partial.files.length };
3457
3811
  cutFailures = cut;
3458
- const baseSha = opts.baselineCommit ?? baselineReleaseCommit(opts.baselineVersion);
3812
+ const baseSha = opts.baselineCommit ?? baselineReleaseCommit(baselineVersion);
3459
3813
  baselineCommitForVerdict = baseSha;
3460
- const cached = cachedBaseline(opts.baselineVersion, baseSha);
3461
- const recorded = cached ? undefined : baselineFromCutLogs(join(ledger, 'release', opts.baselineVersion, 'gate-logs', 'cut'), baseSha);
3814
+ const cached = cachedBaseline(baselineVersion, baseSha);
3815
+ const recorded = cached ? undefined : baselineFromCutLogs(join(ledger, 'release', baselineVersion, 'gate-logs', 'cut'), baseSha);
3462
3816
  const trusted = cached?.saved ?? (recorded?.complete ? recorded : undefined);
3463
3817
  result.baselineSource = cached ? cached.source : recorded?.complete ? 'cut-logs' : 'swept';
3464
- debug.log('release-loop.gate', 'baseline', { version: opts.baselineVersion, source: result.baselineSource, commit: baseSha });
3818
+ debug.log('release-loop.gate', 'baseline', { version: baselineVersion, source: result.baselineSource, commit: baseSha });
3465
3819
  let baseTree: string | undefined;
3466
3820
  const getBaseTree = async () => {
3467
3821
  if (!baseTree) {
@@ -3492,12 +3846,12 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
3492
3846
  present = cutFiles.filter((file) => known.has(file));
3493
3847
  }
3494
3848
  result.baselineDeferred = { cutFiles: cutFiles.length, swept: present.length };
3495
- debug.log('release-loop.gate', 'baseline-deferred', { version: opts.baselineVersion, commit: baseSha, cutFiles: cutFiles.length, swept: present.length });
3849
+ debug.log('release-loop.gate', 'baseline-deferred', { version: baselineVersion, commit: baseSha, cutFiles: cutFiles.length, swept: present.length });
3496
3850
  if (present.length) {
3497
3851
  // K9b — the baseline is swept on the same Pod pool as the cut, not on this host (0.2.6: an hour of local baseline
3498
3852
  // after a 23-minute Pod cut).
3499
3853
  const baseRun = await runner.sweep(baseTree!, join(root, 'release', opts.version, 'gate-logs', 'baseline'),
3500
- opts.pod ? { ...opts.pod, durationSource } : undefined,
3854
+ opts.pod ? { ...opts.pod, ...(opts.imagePin ? { imagePin: opts.imagePin } : {}), durationSource } : undefined,
3501
3855
  present);
3502
3856
  baseline = await localStalled(baseRun, baseTree!, baseSha, 'baseline');
3503
3857
  } else baseline = { failures: [], errors: [] };
@@ -3948,6 +4302,14 @@ export function parseOptions(args: string[], env: NodeJS.ProcessEnv): GateOption
3948
4302
  const fromGraph: Record<string, unknown> = context?.input ?? {};
3949
4303
  const graphCommit = context?.outputs['version-release']?.commit;
3950
4304
  if (typeof graphCommit === 'string') fromGraph.commit = graphCommit;
4305
+ if (fromGraph.gateImagePin !== undefined && fromGraph.gateImagePin !== 'cut-commit') {
4306
+ throw new Error(`unsupported gate image pin: ${String(fromGraph.gateImagePin)}`);
4307
+ }
4308
+ // Policy-selected Pod gates must pin their container to the resolved cut commit. Host sweeps do
4309
+ // not consume a container image pin, and the legacy no-executor path retains its old behavior.
4310
+ if (fromGraph.gateExecutor === 'pod' && fromGraph.gateImagePin !== 'cut-commit') {
4311
+ throw new Error('gateImagePin cut-commit is required for a policy-selected release gate');
4312
+ }
3951
4313
  const baselineCommit = context?.outputs.cutoff?.baseline as { sha?: unknown } | undefined;
3952
4314
  if (typeof baselineCommit?.sha === 'string') fromGraph.previousCommit = baselineCommit.sha;
3953
4315
  if (fromGraph.gateExecutor !== undefined && !isGateExecutor(fromGraph.gateExecutor)) throw new Error(`gateExecutor must be pod or host: ${String(fromGraph.gateExecutor)}`);
@@ -3960,6 +4322,7 @@ export function parseOptions(args: string[], env: NodeJS.ProcessEnv): GateOption
3960
4322
  || !['darwin', 'linux'].includes((value as { os?: string }).os ?? ''))))
3961
4323
  throw new Error('gateShardCapabilities must map pod-N to {os: darwin|linux}');
3962
4324
  const opts: GateOptions = {
4325
+ ...(fromGraph.gateImagePin === 'cut-commit' ? { imagePin: 'cut-commit' as const } : {}),
3963
4326
  rehearsalTui: fromGraph.rehearsalTui === true,
3964
4327
  recheckFileLimit: fromGraph.gateRecheckFileLimit !== undefined ? Number(fromGraph.gateRecheckFileLimit) : undefined,
3965
4328
  commit: typeof fromGraph.commit === 'string' ? fromGraph.commit : '',
@@ -3988,12 +4351,20 @@ export function parseOptions(args: string[], env: NodeJS.ProcessEnv): GateOption
3988
4351
  };
3989
4352
  if (isGateExecutor(fromGraph.gateExecutor)) opts.executor = fromGraph.gateExecutor;
3990
4353
  let freshShards = false;
4354
+ let warmPrecutCanaryFile: string | undefined;
3991
4355
  let rerouteShard: string | undefined;
3992
4356
  let shardHost: string | undefined;
3993
4357
  for (let i = 0; i < args.length; i++) {
3994
4358
  const arg = args[i];
3995
4359
  if (arg === '--help') return 'help';
3996
4360
  if (arg === '--json') continue;
4361
+ if (arg === '--precut-canary-file') {
4362
+ const value = args[++i];
4363
+ if (!value || value.startsWith('--')) throw new Error('--precut-canary-file requires one test file');
4364
+ warmPrecutCanaryFile = value;
4365
+ continue;
4366
+ }
4367
+ if (arg === '--full') { opts.full = true; continue; }
3997
4368
  if (arg === '--rehearsal-baseline') { opts.rehearsalBaseline = true; continue; }
3998
4369
  // Applied after the loop and only to a Pod gate — on its own it must not turn a local gate into a Pod one (review r5).
3999
4370
  if (arg === '--fresh-shards') { freshShards = true; continue; }
@@ -4037,6 +4408,7 @@ export function parseOptions(args: string[], env: NodeJS.ProcessEnv): GateOption
4037
4408
  else if (key === 'remoteMirror') opts.remoteMirror = value;
4038
4409
  }
4039
4410
  if (freshShards && opts.pod) opts.pod = { ...opts.pod, freshShards: true };
4411
+ if (warmPrecutCanaryFile) opts.warmPrecutCanary = { file: warmPrecutCanaryFile };
4040
4412
  if ((rerouteShard || shardHost) && !opts.pod?.pool) throw new Error('shard reroute requires a Pod pool');
4041
4413
  if (shardHost && !rerouteShard) throw new Error('--shard-host requires --reroute-shard');
4042
4414
  if (opts.pod && rerouteShard) opts.pod = { ...opts.pod, rerouteShard, ...(shardHost ? { shardHost } : {}) };
@@ -4072,12 +4444,26 @@ export async function runGateNode(argv: string[], env: NodeJS.ProcessEnv, runner
4072
4444
  catch (error) { debug.log('release-loop.gate', 'warm-profile-unavailable', { error: String(error) }); }
4073
4445
  }
4074
4446
  const opts = parsed === 'help' ? parsed : withWarmGateHooks(parsed, config, !!runner);
4447
+ if (opts !== 'help' && opts.warmPrecutCanary) {
4448
+ const canaryRunner = runner ?? createGateRunner(opts.repo ?? process.cwd(), opts.remote);
4449
+ const warmPrecutTestSeam = (runner as (GateRunner & { warmPrecutCanaryTestSeam?: {
4450
+ config: unknown; hooks: Pick<NonNullable<GateOptions['pod']>, 'warmReplay' | 'warmFinalize' | 'warmQuarantine' | 'warmReuseConfig'>;
4451
+ } }) | undefined)?.warmPrecutCanaryTestSeam;
4452
+ const canaryOpts = warmPrecutTestSeam && opts.pod ? { ...opts, pod: { ...opts.pod, ...warmPrecutTestSeam.hooks } } : opts;
4453
+ const canary = await runWarmPrecutCanary({ opts: canaryOpts, runner: canaryRunner, file: opts.warmPrecutCanary.file,
4454
+ config: warmPrecutTestSeam?.config ?? config,
4455
+ historyPath: verdictFile(opts.ledgerRoot ?? releaseLedgerRoot(), opts.version) });
4456
+ const output = { version: opts.version, commit: opts.commit, samples: canary.samples, decision: canary.decision };
4457
+ console.log(JSON.stringify(output));
4458
+ return 0;
4459
+ }
4075
4460
  if (opts !== 'help') {
4076
4461
  placement = gateExecutorOf(opts);
4077
- debug.log('release-loop.gate', 'executor', { version: opts.version, ...placement, requested: opts.executor ?? null });
4462
+ debug.log('release-loop.gate', 'executor', { version: opts.version, ...placement, requested: opts.executor ?? null,
4463
+ imagePin: opts.imagePin ?? null, imageCommit: opts.imagePin === 'cut-commit' ? opts.commit : null });
4078
4464
  }
4079
4465
  if (opts === 'help') {
4080
- console.log('Usage: bun scripts/release-loop/gate-node.ts --commit <sha> --version <v> [--baseline-version <prev>] [--baseline-commit <sha>] [--remote <ssh-host>] [--remote-mirror <path>] [--pod-pool <pool>] [--pod-shards <n>] [--pod-shard-timeout-seconds <n>] [--recheck-file-limit <n>] [--fresh-shards] [--reroute-shard <pod-N|auto>] [--shard-host <pool-context>] [--executor <pod|host>] [--rehearsal-baseline] [--json]\nWithout flags, input.commit, input.version and input.previousVersion come from the JSON file at ELANOUS_GRAPH_CONTEXT.\n--executor host (input.gateExecutor) ignores every pod option and sweeps on --remote (input.gateRemote, required).\n--rehearsal-baseline pins source graph bytes only for a direct Pod gate on an isolated rc version.');
4466
+ console.log('Usage: bun scripts/release-loop/gate-node.ts --commit <sha> --version <v> [--baseline-version <prev>] [--baseline-commit <sha>] [--remote <ssh-host>] [--remote-mirror <path>] [--pod-pool <pool>] [--precut-canary-file <test-file>] [--pod-shards <n>] [--pod-shard-timeout-seconds <n>] [--recheck-file-limit <n>] [--fresh-shards] [--reroute-shard <pod-N|auto>] [--shard-host <pool-context>] [--executor <pod|host>] [--rehearsal-baseline] [--full] [--json]\n--precut-canary-file runs exactly two one-file warm↔fresh comparisons and stores them in this version\'s gate-verdict warmHistory before the standard gate consumes it; decideWarmReuse applies minMatchSamples.\nWithout flags, input.commit, input.version and input.previousVersion come from the JSON file at ELANOUS_GRAPH_CONTEXT.\n--executor host (input.gateExecutor) ignores every pod option and sweeps on --remote (input.gateRemote, required).\n--rehearsal-baseline pins source graph bytes only for a direct Pod gate on an isolated rc version.\n--full disables receipt and partial reuse and runs the complete test inventory.');
4081
4467
  return 0;
4082
4468
  }
4083
4469
  version = opts.version;
@@ -4113,6 +4499,18 @@ export async function runGateNode(argv: string[], env: NodeJS.ProcessEnv, runner
4113
4499
  return decision;
4114
4500
  });
4115
4501
  result.autoheal = result.autoheals.find((decision) => decision.action === 'hitl') ?? result.autoheals[0];
4502
+ } else if (versionPattern.test(version)) {
4503
+ // Some failures (for example an unreachable gate host or a measured regression) have no
4504
+ // stalled shard. Keep them in the same durable policy ledger instead of silently skipping autoheal.
4505
+ const cause = gateAutohealCause(result);
4506
+ if (cause) {
4507
+ const signature = createHash('sha256').update(JSON.stringify({ cause,
4508
+ error: result.error ?? null, introduced: [...(result.introduced ?? [])].sort() })).digest('hex');
4509
+ result.autoheal = decideAndRecordGateAutoheal({ cause, signature, version, runId: graphRunId ?? 'direct',
4510
+ shardKey: 'gate', shardKeys: ['gate'], shardElapsedMs: result.durationMs, overallElapsedMs: result.durationMs,
4511
+ storeRoot: autohealStoreRoot || releaseLedgerRoot() });
4512
+ debug.log('release-loop.gate-autoheal', 'decision', { runId: graphRunId, version, ...result.autoheal });
4513
+ }
4116
4514
  }
4117
4515
  if (result.error) console.error(result.error);
4118
4516
  debug.log('release-loop.gate', 'result', { version, outcome: result.outcome, ...(placement ?? {}) });
@@ -4124,5 +4522,6 @@ if (import.meta.main) {
4124
4522
  // The graph spawns this file as its own process, which inherits no daemon sink — without this every
4125
4523
  // `release-loop.gate`/`release.gate`/`pod.pool` event stayed in the file trail and `elanous logs` read 0 rows.
4126
4524
  await registerStandaloneLogSink('release-loop');
4127
- process.exitCode = await runWithReleaseGateTraffic(process.env, () => runGateNode(process.argv.slice(2), process.env));
4525
+ process.exitCode = await runWithReleaseGateTraffic(process.env,
4526
+ () => runWithReleasePwaTraffic(process.env, () => runGateNode(process.argv.slice(2), process.env)));
4128
4527
  }